Coverage Report

Created: 2025-08-03 06:36

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