Coverage Report

Created: 2026-03-07 07:03

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