Coverage Report

Created: 2025-10-10 06:31

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