Coverage Report

Created: 2026-06-15 07:03

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