Coverage Report

Created: 2026-01-10 06:51

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