Coverage Report

Created: 2026-09-14 07:05

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