Coverage Report

Created: 2026-09-01 06:59

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