Coverage Report

Created: 2026-10-28 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/cf-ip-happy.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#ifdef HAVE_NETINET_IN_H
27
#include <netinet/in.h> /* <netinet/tcp.h> may need it */
28
#endif
29
#ifdef HAVE_SYS_IOCTL_H
30
#include <sys/ioctl.h>
31
#endif
32
#ifdef HAVE_NETDB_H
33
#include <netdb.h>
34
#endif
35
#ifdef HAVE_ARPA_INET_H
36
#include <arpa/inet.h>
37
#endif
38
39
#ifdef __VMS
40
#include <in.h>
41
#include <inet.h>
42
#endif
43
44
#include "urldata.h"
45
#include "connect.h"
46
#include "cfilters.h"
47
#include "cf-ip-happy.h"
48
#include "curl_addrinfo.h"
49
#include "curl_trc.h"
50
#include "multiif.h"
51
#include "progress.h"
52
#include "select.h"
53
#include "sockaddr.h"
54
#include "vdns/cf-dns.h"
55
#include "vquic/vquic.h" /* for quic cfilters */
56
57
58
struct transport_provider {
59
  cf_ip_connect_create *cf_create;
60
  uint8_t transport;
61
  bool tunnel;
62
};
63
64
static
65
#ifndef UNITTESTS
66
const
67
#endif
68
struct transport_provider transport_providers[] = {
69
  { Curl_cf_tcp_create, TRNSPRT_TCP, FALSE },
70
  { Curl_cf_tcp_create, TRNSPRT_TCP, TRUE },
71
#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3)
72
  { Curl_cf_quic_create, TRNSPRT_QUIC, FALSE },
73
#endif
74
#if !defined(CURL_DISABLE_HTTP) && defined(USE_PROXY_HTTP3)
75
  { Curl_cf_h3_proxy_create, TRNSPRT_QUIC, TRUE },
76
#endif
77
#ifndef CURL_DISABLE_TFTP
78
  { Curl_cf_udp_create, TRNSPRT_UDP, FALSE },
79
#endif
80
#ifdef USE_UNIX_SOCKETS
81
  { Curl_cf_unix_create, TRNSPRT_UNIX, FALSE },
82
  { Curl_cf_unix_create, TRNSPRT_UNIX, TRUE },
83
#endif
84
};
85
86
static cf_ip_connect_create *get_cf_create(uint8_t transport,
87
                                           bool tunnel)
88
407k
{
89
407k
  size_t i;
90
460k
  for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) {
91
460k
    if((transport == transport_providers[i].transport) &&
92
439k
       (tunnel == transport_providers[i].tunnel))
93
407k
      return transport_providers[i].cf_create;
94
460k
  }
95
0
  return NULL;
96
407k
}
97
98
#ifdef UNITTESTS
99
/* @unittest 2600 */
100
UNITTEST void debug_set_transport_provider(
101
  uint8_t transport, cf_ip_connect_create *cf_create);
102
UNITTEST void debug_set_transport_provider(
103
  uint8_t transport, cf_ip_connect_create *cf_create)
104
0
{
105
0
  size_t i;
106
0
  for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) {
107
0
    if(transport == transport_providers[i].transport) {
108
0
      transport_providers[i].cf_create = cf_create;
109
0
    }
110
0
  }
111
0
}
112
#endif /* UNITTESTS */
113
114
struct cf_ai_iter {
115
  struct Curl_cfilter *cf;
116
  struct Curl_peer *peer;
117
  int ai_family;
118
  unsigned int n;
119
};
120
121
static void cf_ai_iter_init(struct cf_ai_iter *iter,
122
                            struct Curl_cfilter *cf,
123
                            struct Curl_peer *peer,
124
                            int ai_family)
125
811k
{
126
811k
  iter->cf = cf;
127
811k
  iter->peer = peer; /* not linked, ctx->ballers owns and has same lifetime */
128
811k
  iter->ai_family = ai_family;
129
811k
  iter->n = 0;
130
811k
}
131
132
static const struct Curl_addrinfo *cf_ai_iter_next(struct cf_ai_iter *iter,
133
                                                   struct Curl_easy *data)
134
862k
{
135
862k
  const struct Curl_addrinfo *addr;
136
137
862k
  if(!iter->cf)
138
3.50k
    return NULL;
139
140
858k
  addr = Curl_conn_dns_get_ai(data, iter->peer, iter->cf->sockindex,
141
858k
                              iter->ai_family, iter->n);
142
858k
  if(addr)
143
407k
    iter->n++;
144
858k
  return addr;
145
862k
}
146
147
static bool cf_ai_iter_has_more(struct cf_ai_iter *iter,
148
                                struct Curl_easy *data)
149
60.8k
{
150
60.8k
  return (iter->cf &&
151
60.7k
          !!Curl_conn_dns_get_ai(data, iter->peer, iter->cf->sockindex,
152
60.7k
                                 iter->ai_family, iter->n));
153
60.8k
}
154
155
struct cf_ip_attempt {
156
  struct cf_ip_attempt *next;
157
  struct Curl_peer *origin;
158
  struct Curl_peer *peer;
159
  struct Curl_peer *tunnel_peer;
160
  struct Curl_sockaddr_ex addr;
161
  struct Curl_cfilter *cf;           /* current sub-cfilter connecting */
162
  cf_ip_connect_create *cf_create;
163
  CURLcode result;
164
  int ai_family;
165
  uint8_t transport_peer;
166
  uint8_t tunnel_transport;
167
  int error;
168
  BIT(connected);                    /* cf has connected */
169
  BIT(shutdown);                     /* cf has shutdown */
170
  BIT(inconclusive);                 /* connect was not a hard failure, we
171
                                      * might talk to a restarting server */
172
};
173
174
static void cf_ip_attempt_free(struct cf_ip_attempt *a,
175
                               struct Curl_easy *data)
176
1.22M
{
177
1.22M
  if(a) {
178
407k
    if(a->cf)
179
153
      Curl_conn_cf_discard_chain(&a->cf, data);
180
407k
    Curl_peer_unlink(&a->origin);
181
407k
    Curl_peer_unlink(&a->peer);
182
407k
    Curl_peer_unlink(&a->tunnel_peer);
183
407k
    curlx_free(a);
184
407k
  }
185
1.22M
}
186
187
static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa,
188
                                  struct Curl_easy *data,
189
                                  struct Curl_cfilter *cf,
190
                                  struct Curl_peer *origin,
191
                                  struct Curl_peer *peer,
192
                                  uint8_t transport_peer,
193
                                  struct Curl_sockaddr_ex *addr,
194
                                  int ai_family,
195
                                  struct Curl_peer *tunnel_peer,
196
                                  uint8_t tunnel_transport,
197
                                  cf_ip_connect_create *cf_create)
198
407k
{
199
407k
  struct Curl_cfilter *wcf;
200
407k
  struct cf_ip_attempt *a;
201
407k
  CURLcode result = CURLE_OK;
202
203
407k
  *pa = NULL;
204
407k
  a = curlx_calloc(1, sizeof(*a));
205
407k
  if(!a)
206
94
    return CURLE_OUT_OF_MEMORY;
207
208
407k
  Curl_peer_link(&a->origin, origin);
209
407k
  Curl_peer_link(&a->peer, peer);
210
407k
  a->transport_peer = transport_peer;
211
407k
  Curl_peer_link(&a->tunnel_peer, tunnel_peer);
212
407k
  a->tunnel_transport = tunnel_transport;
213
407k
  a->addr = *addr;
214
407k
  a->ai_family = ai_family;
215
407k
  a->result = CURLE_OK;
216
407k
  a->cf_create = cf_create;
217
407k
  *pa = a;
218
219
407k
  result = a->cf_create(&a->cf, data, a->origin, a->peer, a->transport_peer,
220
407k
                        cf->conn, &a->addr, a->tunnel_peer,
221
407k
                        a->tunnel_transport);
222
407k
  if(result)
223
203
    goto out;
224
225
  /* the new filter might have sub-filters */
226
816k
  for(wcf = a->cf; wcf; wcf = wcf->next) {
227
409k
    wcf->conn = cf->conn;
228
409k
    wcf->sockindex = cf->sockindex;
229
409k
  }
230
231
407k
out:
232
407k
  if(result) {
233
203
    cf_ip_attempt_free(a, data);
234
203
    *pa = NULL;
235
203
  }
236
407k
  return result;
237
406k
}
238
239
static CURLcode cf_ip_attempt_connect(struct cf_ip_attempt *a,
240
                                      struct Curl_easy *data,
241
                                      bool *connected)
242
417k
{
243
417k
  *connected = (bool)a->connected;
244
417k
  if(!a->result && !*connected) {
245
    /* evaluate again */
246
417k
    a->result = Curl_conn_cf_connect(a->cf, data, connected);
247
248
417k
    if(!a->result) {
249
396k
      if(*connected) {
250
385k
        a->connected = TRUE;
251
385k
      }
252
396k
    }
253
21.0k
    else {
254
21.0k
      if(a->result == CURLE_WEIRD_SERVER_REPLY)
255
0
        a->inconclusive = TRUE;
256
21.0k
      if(a->cf)
257
21.0k
        Curl_conn_cf_discard_chain(&a->cf, data);
258
21.0k
    }
259
417k
  }
260
417k
  return a->result;
261
417k
}
262
263
struct cf_ip_ballers {
264
  struct cf_ip_attempt *running;
265
  struct cf_ip_attempt **running_tail;  /* &running or &(last->next) */
266
  struct cf_ip_attempt *winner;
267
  struct cf_ai_iter addr_iter;
268
#ifdef USE_IPV6
269
  struct cf_ai_iter ipv6_iter;
270
#endif
271
  struct Curl_peer *origin;
272
  struct Curl_peer *peer;
273
  struct Curl_peer *tunnel_peer;
274
  cf_ip_connect_create *cf_create;   /* for creating cf */
275
  struct curltime last_attempt_started;
276
  timediff_t attempt_delay_ms;
277
  CURLcode last_dead_result;  /* result of last discarded hard failure */
278
  int last_attempt_ai_family;
279
  uint32_t max_concurrent;
280
  uint8_t transport_peer;
281
  uint8_t tunnel_transport;
282
  BIT(had_dead);              /* a hard failure was discarded */
283
};
284
285
static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a,
286
                                      struct Curl_cfilter *cf,
287
                                      struct Curl_easy *data)
288
0
{
289
0
  struct Curl_cfilter *wcf;
290
0
  CURLcode result;
291
292
0
  if(a->cf)
293
0
    Curl_conn_cf_discard_chain(&a->cf, data);
294
295
0
  a->result = CURLE_OK;
296
0
  a->connected = FALSE;
297
0
  a->inconclusive = FALSE;
298
0
  a->cf = NULL;
299
300
0
  result = a->cf_create(&a->cf, data, a->origin, a->peer, a->transport_peer,
301
0
                        cf->conn, &a->addr,
302
0
                        a->tunnel_peer, a->tunnel_transport);
303
0
  if(!result) {
304
0
    bool dummy;
305
    /* the new filter might have sub-filters */
306
0
    for(wcf = a->cf; wcf; wcf = wcf->next) {
307
0
      wcf->conn = cf->conn;
308
0
      wcf->sockindex = cf->sockindex;
309
0
    }
310
0
    a->result = cf_ip_attempt_connect(a, data, &dummy);
311
0
  }
312
0
  return result;
313
0
}
314
315
static void cf_ip_ballers_clear(struct Curl_easy *data,
316
                                struct cf_ip_ballers *bs)
317
1.20M
{
318
1.20M
  while(bs->running) {
319
99
    struct cf_ip_attempt *a = bs->running;
320
99
    bs->running = a->next;
321
99
    cf_ip_attempt_free(a, data);
322
99
  }
323
1.20M
  bs->running_tail = &bs->running;
324
1.20M
  cf_ip_attempt_free(bs->winner, data);
325
1.20M
  bs->winner = NULL;
326
1.20M
  Curl_peer_unlink(&bs->origin);
327
1.20M
  Curl_peer_unlink(&bs->peer);
328
1.20M
  Curl_peer_unlink(&bs->tunnel_peer);
329
1.20M
}
330
331
static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs,
332
                                   struct Curl_easy *data,
333
                                   struct Curl_peer *origin,
334
                                   struct Curl_peer *peer,
335
                                   uint8_t transport_peer,
336
                                   struct Curl_peer *tunnel_peer,
337
                                   uint8_t tunnel_transport,
338
                                   timediff_t attempt_delay_ms,
339
                                   uint32_t max_concurrent)
340
407k
{
341
407k
  memset(bs, 0, sizeof(*bs));
342
407k
  bs->running_tail = &bs->running;
343
407k
  bs->cf_create = get_cf_create(transport_peer, !!tunnel_peer);
344
407k
  if(!bs->cf_create) {
345
0
    failf(data, "unsupported transport type %u%s",
346
0
          transport_peer, tunnel_peer ? " to proxy" : "");
347
0
    return CURLE_UNSUPPORTED_PROTOCOL;
348
0
  }
349
407k
  Curl_peer_link(&bs->origin, origin);
350
407k
  Curl_peer_link(&bs->peer, peer);
351
407k
  bs->transport_peer = transport_peer;
352
407k
  Curl_peer_link(&bs->tunnel_peer, tunnel_peer);
353
407k
  bs->tunnel_transport = tunnel_transport;
354
407k
  bs->attempt_delay_ms = attempt_delay_ms;
355
407k
  bs->max_concurrent = max_concurrent;
356
407k
  bs->last_attempt_ai_family = AF_INET; /* so AF_INET6 is next */
357
407k
  return CURLE_OK;
358
407k
}
359
360
static void cf_ip_ballers_prune(struct cf_ip_ballers *bs,
361
                                struct Curl_cfilter *cf,
362
                                struct Curl_easy *data,
363
                                uint32_t max_concurrent)
364
407k
{
365
407k
  struct cf_ip_attempt *a = NULL, **panchor;
366
407k
  uint32_t ongoing = 0;
367
368
407k
  for(a = bs->running; a; a = a->next) {
369
60
    if(!a->result && !a->connected)
370
60
      ++ongoing;
371
60
  }
372
373
407k
  panchor = &bs->running;
374
407k
  while(*panchor && (ongoing > max_concurrent)) {
375
0
    a = *panchor;
376
0
    if(!a->result && !a->connected) {
377
0
      *panchor = a->next;
378
0
      if(!a->next)
379
0
        bs->running_tail = panchor;
380
0
      a->next = NULL;
381
0
      cf_ip_attempt_free(a, data);
382
0
      --ongoing;
383
0
      CURL_TRC_CF(data, cf, "discarding oldest attempt to keep limit");
384
0
    }
385
0
    else {
386
0
      panchor = &a->next;
387
0
    }
388
0
  }
389
407k
}
390
391
static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs,
392
                                  struct Curl_cfilter *cf,
393
                                  struct Curl_easy *data,
394
                                  bool dns_resolved,
395
                                  bool *connected)
396
425k
{
397
425k
  CURLcode result = CURLE_OK;
398
425k
  struct cf_ip_attempt *a = NULL, **panchor;
399
425k
  bool do_more;
400
425k
  timediff_t next_expire_ms;
401
425k
  uint32_t inconclusive, ongoing;
402
425k
  const struct curltime *pnow = NULL;
403
425k
  VERBOSE(int i);
404
405
425k
  if(bs->winner)
406
0
    return CURLE_OK;
407
408
831k
evaluate:
409
831k
  if(Curl_timeleft_now_ms(data, Curl_pgrs_now(data)) < 0) {
410
3
    failf(data, "Connection timeout after %" FMT_OFF_T " ms",
411
3
          Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE));
412
3
    return CURLE_OPERATION_TIMEDOUT;
413
3
  }
414
415
831k
  ongoing = inconclusive = 0;
416
417
  /* check if a running baller connects now */
418
831k
  VERBOSE(i = -1);
419
863k
  for(panchor = &bs->running; *panchor;) {
420
417k
    VERBOSE(++i);
421
417k
    a = *panchor;
422
417k
    a->result = cf_ip_attempt_connect(a, data, connected);
423
417k
    if(!a->result) {
424
396k
      if(*connected) {
425
        /* connected, declare the winner, remove from running,
426
         * clear remaining running list. */
427
385k
        CURL_TRC_CF(data, cf, "connect attempt #%d successful", i);
428
385k
        bs->winner = a;
429
385k
        *panchor = a->next;
430
385k
        a->next = NULL;
431
385k
        while(bs->running) {
432
54
          a = bs->running;
433
54
          bs->running = a->next;
434
54
          cf_ip_attempt_free(a, data);
435
54
        }
436
385k
        bs->running_tail = &bs->running;
437
385k
        return CURLE_OK;
438
385k
      }
439
      /* still running */
440
10.3k
      ++ongoing;
441
10.3k
      panchor = &(*panchor)->next;
442
10.3k
    }
443
21.0k
    else if(a->inconclusive) { /* failed, but inconclusive */
444
0
      ++inconclusive;
445
0
      panchor = &(*panchor)->next;
446
0
    }
447
21.0k
    else {
448
      /* hard failure, discard right away */
449
21.0k
      *panchor = a->next;
450
21.0k
      if(!a->next)
451
21.0k
        bs->running_tail = panchor;
452
21.0k
      a->next = NULL;
453
21.0k
      bs->last_dead_result = a->result;
454
21.0k
      bs->had_dead = TRUE;
455
21.0k
      cf_ip_attempt_free(a, data);
456
21.0k
    }
457
417k
  }
458
446k
  if(bs->running)
459
10.2k
    CURL_TRC_CF(data, cf, "checked connect attempts: "
460
446k
                "%u ongoing, %u inconclusive", ongoing, inconclusive);
461
462
  /* no attempt connected yet, start another one? */
463
446k
  pnow = Curl_pgrs_now(data);
464
446k
  if(!ongoing) {
465
435k
    do_more = TRUE;
466
435k
  }
467
10.2k
  else {
468
10.2k
    bool more_possible = cf_ai_iter_has_more(&bs->addr_iter, data);
469
10.2k
#ifdef USE_IPV6
470
10.2k
    if(!more_possible)
471
10.2k
      more_possible = cf_ai_iter_has_more(&bs->ipv6_iter, data);
472
10.2k
#endif
473
10.2k
    do_more = more_possible &&
474
60
      (curlx_ptimediff_ms(pnow, &bs->last_attempt_started) >=
475
60
       bs->attempt_delay_ms);
476
10.2k
    if(do_more)
477
60
      CURL_TRC_CF(data, cf, "happy eyeballs timeout expired, "
478
10.2k
                  "start next attempt");
479
10.2k
  }
480
481
446k
  if(do_more) {
482
    /* start the next attempt if there is another ip address to try.
483
     * Alternate between address families when possible. */
484
436k
    const struct Curl_addrinfo *ai = NULL;
485
436k
    int ai_family = 0;
486
436k
    CURL_TRC_CF(data, cf, "want to do more");
487
436k
#ifdef USE_IPV6
488
436k
    if((bs->last_attempt_ai_family == AF_INET) ||
489
435k
       !cf_ai_iter_has_more(&bs->addr_iter, data)) {
490
435k
      ai = cf_ai_iter_next(&bs->ipv6_iter, data);
491
435k
      ai_family = bs->ipv6_iter.ai_family;
492
435k
      CURL_TRC_CF(data, cf, "check for next AAAA address: %s",
493
435k
                  ai ? "found" : "none");
494
435k
    }
495
436k
#endif
496
436k
    if(!ai) {
497
426k
      ai = cf_ai_iter_next(&bs->addr_iter, data);
498
426k
      ai_family = bs->addr_iter.ai_family;
499
426k
      CURL_TRC_CF(data, cf, "check for next A address: %s",
500
426k
                  ai ? "found" : "none");
501
426k
    }
502
    /* We are (re-)starting attempts. We are not interested in
503
     * keeping old failure information. The new attempt will either
504
     * succeed or persist new failure. */
505
436k
    Curl_reset_fail(data);
506
507
436k
    if(ai) {  /* try another address */
508
407k
      struct Curl_sockaddr_ex addr;
509
510
      /* Discard oldest to make room for new attempt */
511
407k
      if(bs->max_concurrent)
512
407k
        cf_ip_ballers_prune(bs, cf, data, bs->max_concurrent - 1);
513
514
407k
      result = Curl_socket_addr_from_ai(&addr, ai, bs->transport_peer);
515
407k
      if(result)
516
0
        goto out;
517
518
407k
      result = cf_ip_attempt_new(&a, data, cf, bs->origin, bs->peer,
519
407k
                                 bs->transport_peer, &addr, ai_family,
520
407k
                                 bs->tunnel_peer, bs->tunnel_transport,
521
407k
                                 bs->cf_create);
522
407k
      CURL_TRC_CF(data, cf, "starting %s attempt for ipv%s -> %d",
523
407k
                  bs->running ? "next" : "first",
524
407k
                  (ai_family == AF_INET) ? "4" : "6", (int)result);
525
407k
      if(result)
526
297
        goto out;
527
406k
      DEBUGASSERT(a);
528
529
      /* append to running list */
530
406k
      *bs->running_tail = a;
531
406k
      a->next = NULL;
532
406k
      bs->running_tail = &a->next;
533
406k
      bs->last_attempt_started = *pnow;
534
406k
      bs->last_attempt_ai_family = ai_family;
535
      /* and run everything again */
536
406k
      goto evaluate;
537
406k
    }
538
28.8k
    else if(inconclusive) {
539
      /* tried all addresses, no success but some where inconclusive.
540
       * Let's restart the inconclusive ones. */
541
0
      timediff_t since_ms =
542
0
        curlx_ptimediff_ms(pnow, &bs->last_attempt_started);
543
0
      timediff_t delay_ms = bs->attempt_delay_ms - since_ms;
544
0
      if(delay_ms <= 0) {
545
0
        CURL_TRC_CF(data, cf, "all attempts inconclusive, restarting one");
546
0
        VERBOSE(i = -1);
547
0
        for(a = bs->running; a; a = a->next) {
548
0
          VERBOSE(++i);
549
0
          if(!a->inconclusive)
550
0
            continue;
551
0
          result = cf_ip_attempt_restart(a, cf, data);
552
0
          CURL_TRC_CF(data, cf, "restarted baller %d -> %d", i, (int)result);
553
0
          if(result) /* serious failure */
554
0
            goto out;
555
0
          bs->last_attempt_started = *pnow;
556
0
          goto evaluate;
557
0
        }
558
0
        DEBUGASSERT(0); /* should not come here */
559
0
      }
560
0
      else {
561
        /* let's wait some more before restarting */
562
0
        infof(data, "connect attempts inconclusive, retrying "
563
0
                    "in %" FMT_TIMEDIFF_T "ms", delay_ms);
564
0
        Curl_expire_set(data, EXPIRE_HAPPY_EYEBALLS, delay_ms, pnow);
565
0
      }
566
      /* attempt timeout for restart has not expired yet */
567
0
      goto out;
568
0
    }
569
28.8k
    else if(!ongoing && dns_resolved) {
570
      /* no more addresses, no inconclusive attempts */
571
20.6k
      CURL_TRC_CF(data, cf, "no more attempts to try");
572
20.6k
      DEBUGASSERT(!bs->running);
573
20.6k
      result = bs->had_dead ? bs->last_dead_result : CURLE_COULDNT_CONNECT;
574
20.6k
    }
575
436k
  }
576
577
39.3k
out:
578
39.3k
  if(!result) {
579
18.3k
    bool more_possible;
580
581
    /* when do we need to be called again? */
582
18.3k
    pnow = Curl_pgrs_now(data);
583
18.3k
    next_expire_ms = Curl_timeleft_now_ms(data, pnow);
584
18.3k
    if(next_expire_ms < 0) {
585
0
      failf(data, "Connection timeout after %" FMT_OFF_T " ms",
586
0
            Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE));
587
0
      return CURLE_OPERATION_TIMEDOUT;
588
0
    }
589
590
18.3k
    more_possible = cf_ai_iter_has_more(&bs->addr_iter, data);
591
18.3k
#ifdef USE_IPV6
592
18.3k
    if(!more_possible)
593
18.3k
      more_possible = cf_ai_iter_has_more(&bs->ipv6_iter, data);
594
18.3k
#endif
595
18.3k
    if(more_possible) {
596
0
      timediff_t expire_ms, elapsed_ms;
597
598
0
      elapsed_ms = curlx_ptimediff_ms(pnow, &bs->last_attempt_started);
599
0
      expire_ms = CURLMAX(bs->attempt_delay_ms - elapsed_ms, 0);
600
0
      next_expire_ms = CURLMIN(next_expire_ms, expire_ms);
601
0
      if(next_expire_ms <= 0) {
602
0
        CURL_TRC_CF(data, cf, "HAPPY_EYEBALLS timeout due, re-evaluate");
603
0
        goto evaluate;
604
0
      }
605
0
      CURL_TRC_CF(data, cf, "next HAPPY_EYEBALLS timeout in %" FMT_TIMEDIFF_T
606
0
                  "ms", next_expire_ms);
607
0
      Curl_expire_set(data, EXPIRE_HAPPY_EYEBALLS, next_expire_ms, pnow);
608
0
    }
609
18.3k
  }
610
39.3k
  return result;
611
39.3k
}
612
613
static CURLcode cf_ip_ballers_shutdown(struct cf_ip_ballers *bs,
614
                                       struct Curl_easy *data,
615
                                       bool *done)
616
0
{
617
0
  struct cf_ip_attempt *a;
618
619
  /* shutdown all ballers that have not done so already. If one fails,
620
   * continue shutting down others until all are shutdown. */
621
0
  *done = TRUE;
622
0
  for(a = bs->running; a; a = a->next) {
623
0
    bool bdone = FALSE;
624
0
    if(a->shutdown || !a->cf)
625
0
      continue;
626
0
    a->result = a->cf->cft->do_shutdown(a->cf, data, &bdone);
627
0
    if(a->result || bdone)
628
0
      a->shutdown = TRUE; /* treat a failed shutdown as done */
629
0
    else
630
0
      *done = FALSE;
631
0
  }
632
0
  return CURLE_OK;
633
0
}
634
635
static CURLcode cf_ip_ballers_pollset(struct cf_ip_ballers *bs,
636
                                      struct Curl_easy *data,
637
                                      struct easy_pollset *ps)
638
13.4k
{
639
13.4k
  struct cf_ip_attempt *a;
640
13.4k
  CURLcode result = CURLE_OK;
641
21.5k
  for(a = bs->running; a && !result; a = a->next) {
642
8.11k
    if(a->result)
643
0
      continue;
644
8.11k
    result = Curl_conn_cf_adjust_pollset(a->cf, data, ps);
645
8.11k
  }
646
13.4k
  return result;
647
13.4k
}
648
649
static bool cf_ip_ballers_pending(struct cf_ip_ballers *bs,
650
                                  const struct Curl_easy *data)
651
0
{
652
0
  struct cf_ip_attempt *a;
653
654
0
  for(a = bs->running; a; a = a->next) {
655
0
    if(a->result)
656
0
      continue;
657
0
    if(a->cf && a->cf->cft->has_data_pending(a->cf, data))
658
0
      return TRUE;
659
0
  }
660
0
  return FALSE;
661
0
}
662
663
static int cf_ip_ballers_min_reply_ms(struct cf_ip_ballers *bs,
664
                                      struct Curl_easy *data)
665
0
{
666
0
  int reply_ms = -1, breply_ms;
667
0
  struct cf_ip_attempt *a;
668
669
0
  for(a = bs->running; a; a = a->next) {
670
0
    if(a->cf && !a->cf->cft->query(a->cf, data, CF_QUERY_CONNECT_REPLY_MS,
671
0
                                   &breply_ms, NULL) &&
672
0
       breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms))
673
0
      reply_ms = breply_ms;
674
0
  }
675
0
  return reply_ms;
676
0
}
677
678
typedef enum {
679
  SCFST_INIT,
680
  SCFST_WAITING,
681
  SCFST_DONE
682
} cf_connect_state;
683
684
struct cf_ip_happy_ctx {
685
  cf_ip_connect_create *cf_create;
686
  cf_connect_state state;
687
  struct cf_ip_ballers ballers;
688
  BIT(dns_resolved);
689
};
690
691
static CURLcode is_connected(struct Curl_cfilter *cf,
692
                             struct Curl_easy *data,
693
                             bool *connected)
694
425k
{
695
425k
  struct cf_ip_happy_ctx *ctx = cf->ctx;
696
425k
  struct connectdata *conn = cf->conn;
697
425k
  CURLcode result;
698
699
425k
  result = cf_ip_ballers_run(&ctx->ballers, cf, data,
700
425k
                             (bool)ctx->dns_resolved, connected);
701
702
425k
  if(!result)
703
404k
    return CURLE_OK;
704
20.9k
  else {
705
20.9k
    struct Curl_peer *peer = NULL, *proxy_peer = NULL;
706
20.9k
    char viamsg[160];
707
708
20.9k
    peer = Curl_conn_get_first_peer(conn, cf->sockindex);
709
20.9k
    if(!conn->origin || !peer)
710
0
      return CURLE_FAILED_INIT;
711
712
20.9k
#ifndef CURL_DISABLE_PROXY
713
20.9k
    if(conn->socks_proxy.peer)
714
211
      proxy_peer = conn->socks_proxy.peer;
715
20.7k
    else if(conn->http_proxy.peer)
716
1.26k
      proxy_peer = conn->http_proxy.peer;
717
20.9k
#endif
718
719
20.9k
    viamsg[0] = 0;
720
20.9k
    if(!Curl_peer_equal(peer, conn->origin) &&
721
19.2k
       !Curl_peer_equal(peer, proxy_peer)) {
722
18.7k
#ifdef USE_UNIX_SOCKETS
723
18.7k
      if(peer->unix_socket)
724
136
        curl_msnprintf(viamsg, sizeof(viamsg), " over unix://%s",
725
136
                       peer->hostname);
726
18.5k
      else
727
18.5k
#endif
728
18.5k
      curl_msnprintf(viamsg, sizeof(viamsg), " via %s:%u",
729
18.5k
                     peer->hostname, peer->port);
730
18.7k
    }
731
732
20.9k
    failf(data, "Failed to connect to %s:%u%s %s%s%safter "
733
20.9k
          "%" FMT_TIMEDIFF_T " ms: %s",
734
20.9k
          conn->origin->hostname, conn->origin->port, viamsg,
735
20.9k
          proxy_peer ? "over proxy " : "",
736
20.9k
          proxy_peer ? proxy_peer->hostname : "",
737
20.9k
          proxy_peer ? " " : "",
738
20.9k
          Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE),
739
20.9k
          curl_easy_strerror(result));
740
741
20.9k
#ifdef SOCKETIMEDOUT
742
20.9k
    if(SOCKETIMEDOUT == data->state.os_errno)
743
0
      result = CURLE_OPERATION_TIMEDOUT;
744
20.9k
#endif
745
746
20.9k
    return result;
747
20.9k
  }
748
425k
}
749
750
407k
#define IP_HE_MAX_CONCURRENT_ATTEMPTS     6
751
752
static CURLcode cf_ip_happy_init(struct Curl_cfilter *cf,
753
                                 struct Curl_easy *data)
754
407k
{
755
407k
  struct cf_ip_happy_ctx *ctx = cf->ctx;
756
757
407k
  if(Curl_timeleft_ms(data) < 0) {
758
    /* a precaution, no need to continue if time already is up */
759
0
    failf(data, "Connection time-out");
760
0
    return CURLE_OPERATION_TIMEDOUT;
761
0
  }
762
763
407k
  if(ctx->ballers.transport_peer == TRNSPRT_UNIX) {
764
3.33k
#ifdef USE_UNIX_SOCKETS
765
3.33k
    cf_ai_iter_init(&ctx->ballers.addr_iter, cf, ctx->ballers.peer, AF_UNIX);
766
#else
767
    return CURLE_UNSUPPORTED_PROTOCOL;
768
#endif
769
3.33k
  }
770
403k
  else { /* TCP/UDP/QUIC */
771
403k
#ifdef USE_IPV6
772
403k
    cf_ai_iter_init(&ctx->ballers.ipv6_iter, cf, ctx->ballers.peer, AF_INET6);
773
403k
#endif
774
403k
    cf_ai_iter_init(&ctx->ballers.addr_iter, cf, ctx->ballers.peer, AF_INET);
775
403k
  }
776
777
407k
  CURL_TRC_CF(data, cf, "init ip ballers for transport %u",
778
407k
              ctx->ballers.transport_peer);
779
407k
  return CURLE_OK;
780
407k
}
781
782
static void cf_ip_happy_ctx_clear(struct cf_ip_happy_ctx *ctx,
783
                                  struct Curl_easy *data)
784
1.20M
{
785
1.20M
  DEBUGASSERT(ctx);
786
1.20M
  if(ctx)
787
1.20M
    cf_ip_ballers_clear(data, &ctx->ballers);
788
1.20M
}
789
790
static void cf_ip_happy_ctx_destroy(struct cf_ip_happy_ctx *ctx,
791
                                    struct Curl_easy *data)
792
407k
{
793
407k
  if(ctx) {
794
407k
    cf_ip_happy_ctx_clear(ctx, data);
795
407k
    curlx_free(ctx);
796
407k
  }
797
407k
}
798
799
static CURLcode cf_ip_happy_shutdown(struct Curl_cfilter *cf,
800
                                     struct Curl_easy *data,
801
                                     bool *done)
802
0
{
803
0
  struct cf_ip_happy_ctx *ctx = cf->ctx;
804
0
  CURLcode result = CURLE_OK;
805
806
0
  DEBUGASSERT(data);
807
0
  if(cf->connected) {
808
0
    *done = TRUE;
809
0
    return CURLE_OK;
810
0
  }
811
812
0
  result = cf_ip_ballers_shutdown(&ctx->ballers, data, done);
813
0
  CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", (int)result, *done);
814
0
  return result;
815
0
}
816
817
static CURLcode cf_ip_happy_adjust_pollset(struct Curl_cfilter *cf,
818
                                           struct Curl_easy *data,
819
                                           struct easy_pollset *ps)
820
119k
{
821
119k
  struct cf_ip_happy_ctx *ctx = cf->ctx;
822
119k
  CURLcode result = CURLE_OK;
823
824
119k
  if(!cf->connected) {
825
13.4k
    result = cf_ip_ballers_pollset(&ctx->ballers, data, ps);
826
13.4k
    CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %u socks", (int)result,
827
13.4k
                ps->n);
828
13.4k
  }
829
119k
  return result;
830
119k
}
831
832
static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf,
833
                                    struct Curl_easy *data,
834
                                    bool *done)
835
506k
{
836
506k
  struct cf_ip_happy_ctx *ctx = cf->ctx;
837
506k
  CURLcode result = CURLE_OK;
838
839
  /* -Werror=null-dereference finds false positives suddenly. */
840
506k
  if(!data)
841
0
    return CURLE_FAILED_INIT;
842
843
506k
  if(cf->connected) {
844
81.1k
    *done = TRUE;
845
81.1k
    return CURLE_OK;
846
81.1k
  }
847
848
425k
  DEBUGASSERT(ctx);
849
425k
  *done = FALSE;
850
851
425k
  if(!ctx->dns_resolved) {
852
414k
    result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex,
853
414k
                                       ctx->ballers.peer);
854
414k
    if(!result)
855
406k
      ctx->dns_resolved = TRUE;
856
8.49k
    else if(result == CURLE_AGAIN) {
857
8.49k
      result = CURLE_OK;
858
8.49k
    }
859
0
    else /* real error */
860
0
      goto out;
861
414k
  }
862
863
425k
  switch(ctx->state) {
864
407k
  case SCFST_INIT:
865
407k
    DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data));
866
407k
    DEBUGASSERT(!cf->connected);
867
407k
    result = cf_ip_happy_init(cf, data);
868
407k
    if(result)
869
0
      goto out;
870
407k
    ctx->state = SCFST_WAITING;
871
407k
    FALLTHROUGH();
872
425k
  case SCFST_WAITING:
873
425k
    result = is_connected(cf, data, done);
874
425k
    if(!result && *done) {
875
385k
      DEBUGASSERT(ctx->ballers.winner);
876
385k
      DEBUGASSERT(ctx->ballers.winner->cf);
877
385k
      DEBUGASSERT(ctx->ballers.winner->cf->connected);
878
      /* we have a winner. Install and activate it.
879
       * close/free all others. */
880
385k
      ctx->state = SCFST_DONE;
881
385k
      cf->connected = TRUE;
882
385k
      cf->next = ctx->ballers.winner->cf;
883
385k
      ctx->ballers.winner->cf = NULL;
884
885
385k
      if(cf->conn->scheme->protocol & PROTO_FAMILY_SSH)
886
0
        Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */
887
385k
#ifdef CURLVERBOSE
888
385k
      if(Curl_trc_cf_is_verbose(cf, data)) {
889
12.6k
        struct ip_quadruple ipquad;
890
12.6k
        bool is_ipv6;
891
12.6k
        if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad)) {
892
12.6k
          CURL_TRC_CF(data, cf, "Connected to %s (%s) port %u",
893
12.6k
                      ctx->ballers.peer->hostname,
894
12.6k
                      ipquad.remote_ip, ipquad.remote_port);
895
12.6k
        }
896
12.6k
      }
897
385k
#endif
898
385k
      cf_ip_happy_ctx_clear(ctx, data);
899
385k
      Curl_expire_clear(data, EXPIRE_HAPPY_EYEBALLS);
900
      /* whatever errors were reported by ballers, clear our errorbuf */
901
385k
      Curl_reset_fail(data);
902
385k
      data->info.numconnects++; /* to track the # of connections made */
903
385k
    }
904
425k
    break;
905
425k
  case SCFST_DONE:
906
0
    *done = TRUE;
907
0
    break;
908
425k
  }
909
425k
out:
910
425k
  return result;
911
425k
}
912
913
static bool cf_ip_happy_data_pending(struct Curl_cfilter *cf,
914
                                     const struct Curl_easy *data)
915
0
{
916
0
  struct cf_ip_happy_ctx *ctx = cf->ctx;
917
918
0
  if(!cf->connected) {
919
0
    return cf_ip_ballers_pending(&ctx->ballers, data);
920
0
  }
921
0
  return cf->next->cft->has_data_pending(cf->next, data);
922
0
}
923
924
static CURLcode cf_ip_happy_query(struct Curl_cfilter *cf,
925
                                  struct Curl_easy *data,
926
                                  int query, int *pres1, void *pres2)
927
1.08M
{
928
1.08M
  struct cf_ip_happy_ctx *ctx = cf->ctx;
929
930
1.08M
  if(!cf->connected) {
931
435k
    switch(query) {
932
0
    case CF_QUERY_CONNECT_REPLY_MS: {
933
0
      *pres1 = cf_ip_ballers_min_reply_ms(&ctx->ballers, data);
934
0
      CURL_TRC_CF(data, cf, "query connect reply: %dms", *pres1);
935
0
      return CURLE_OK;
936
0
    }
937
435k
    default:
938
435k
      break;
939
435k
    }
940
435k
  }
941
942
1.08M
  return cf->next ?
943
646k
    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
944
1.08M
    CURLE_UNKNOWN_OPTION;
945
1.08M
}
946
947
static void cf_ip_happy_destroy(struct Curl_cfilter *cf,
948
                                struct Curl_easy *data)
949
407k
{
950
407k
  struct cf_ip_happy_ctx *ctx = cf->ctx;
951
952
407k
  CURL_TRC_CF(data, cf, "destroy");
953
407k
  if(ctx) {
954
407k
    cf_ip_happy_ctx_clear(ctx, data);
955
407k
    cf_ip_happy_ctx_destroy(ctx, data);
956
407k
  }
957
407k
}
958
959
struct Curl_cftype Curl_cft_ip_happy = {
960
  "HAPPY-EYEBALLS",
961
  CF_TYPE_SETUP,
962
  CURL_LOG_LVL_NONE,
963
  cf_ip_happy_destroy,
964
  cf_ip_happy_connect,
965
  cf_ip_happy_shutdown,
966
  cf_ip_happy_adjust_pollset,
967
  cf_ip_happy_data_pending,
968
  Curl_cf_def_send,
969
  Curl_cf_def_recv,
970
  Curl_cf_def_cntrl,
971
  Curl_cf_def_conn_is_alive,
972
  Curl_cf_def_conn_keep_alive,
973
  cf_ip_happy_query,
974
};
975
976
static CURLcode cf_ip_happy_create(struct Curl_cfilter **pcf,
977
                                   struct Curl_easy *data,
978
                                   struct Curl_peer *origin,
979
                                   struct Curl_peer *peer,
980
                                   uint8_t transport_peer,
981
                                   struct connectdata *conn,
982
                                   struct Curl_peer *tunnel_peer,
983
                                   uint8_t tunnel_transport)
984
407k
{
985
407k
  struct cf_ip_happy_ctx *ctx = NULL;
986
407k
  CURLcode result;
987
988
407k
  (void)data;
989
407k
  (void)conn;
990
407k
  *pcf = NULL;
991
407k
  ctx = curlx_calloc(1, sizeof(*ctx));
992
407k
  if(!ctx) {
993
102
    result = CURLE_OUT_OF_MEMORY;
994
102
    goto out;
995
102
  }
996
407k
  result = cf_ip_ballers_init(&ctx->ballers, data,
997
407k
                              origin, peer, transport_peer,
998
407k
                              tunnel_peer, tunnel_transport,
999
407k
                              data->set.happy_eyeballs_timeout,
1000
407k
                              IP_HE_MAX_CONCURRENT_ATTEMPTS);
1001
407k
  if(result)
1002
0
    goto out;
1003
1004
407k
  result = Curl_cf_create(pcf, &Curl_cft_ip_happy, ctx);
1005
1006
407k
out:
1007
407k
  if(result) {
1008
237
    curlx_safefree(*pcf);
1009
237
    cf_ip_happy_ctx_destroy(ctx, data);
1010
237
  }
1011
407k
  return result;
1012
407k
}
1013
1014
CURLcode Curl_cf_ip_happy_insert_after(struct Curl_cfilter *cf_at,
1015
                                       struct Curl_easy *data,
1016
                                       struct Curl_peer *origin,
1017
                                       struct Curl_peer *peer,
1018
                                       uint8_t transport_peer,
1019
                                       struct Curl_peer *tunnel_peer,
1020
                                       uint8_t tunnel_transport)
1021
407k
{
1022
407k
  struct Curl_cfilter *cf;
1023
407k
  CURLcode result;
1024
1025
  /* Need to be first */
1026
407k
  DEBUGASSERT(cf_at);
1027
407k
  result = cf_ip_happy_create(&cf, data, origin, peer, transport_peer,
1028
407k
                              cf_at->conn, tunnel_peer, tunnel_transport);
1029
407k
  if(result)
1030
237
    return result;
1031
1032
407k
  Curl_conn_cf_insert_after(cf_at, cf);
1033
407k
  return CURLE_OK;
1034
407k
}