Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/external/curl/lib/cf-ip-happy.c
2652 views
1
/***************************************************************************
2
* _ _ ____ _
3
* Project ___| | | | _ \| |
4
* / __| | | | |_) | |
5
* | (__| |_| | _ <| |___
6
* \___|\___/|_| \_\_____|
7
*
8
* Copyright (C) Daniel Stenberg, <[email protected]>, 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
{
92
size_t i;
93
for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) {
94
if(transport == transport_providers[i].transport)
95
return transport_providers[i].cf_create;
96
}
97
return NULL;
98
}
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
{
127
iter->head = list;
128
iter->ai_family = ai_family;
129
iter->last = NULL;
130
iter->n = -1;
131
}
132
133
static const struct Curl_addrinfo *cf_ai_iter_next(struct cf_ai_iter *iter)
134
{
135
const struct Curl_addrinfo *addr;
136
if(iter->n < 0) {
137
iter->n++;
138
for(addr = iter->head; addr; addr = addr->ai_next) {
139
if(addr->ai_family == iter->ai_family)
140
break;
141
}
142
iter->last = addr;
143
}
144
else if(iter->last) {
145
iter->n++;
146
for(addr = iter->last->ai_next; addr; addr = addr->ai_next) {
147
if(addr->ai_family == iter->ai_family)
148
break;
149
}
150
iter->last = addr;
151
}
152
return iter->last;
153
}
154
155
static bool cf_ai_iter_has_more(struct cf_ai_iter *iter)
156
{
157
const struct Curl_addrinfo *addr = iter->last ? iter->last->ai_next :
158
((iter->n < 0) ? iter->head : NULL);
159
while(addr) {
160
if(addr->ai_family == iter->ai_family)
161
return TRUE;
162
addr = addr->ai_next;
163
}
164
return FALSE;
165
}
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
{
186
if(a) {
187
if(a->cf)
188
Curl_conn_cf_discard_chain(&a->cf, data);
189
free(a);
190
}
191
}
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
{
201
struct Curl_cfilter *wcf;
202
struct cf_ip_attempt *a;
203
CURLcode result = CURLE_OK;
204
205
*pa = NULL;
206
a = calloc(1, sizeof(*a));
207
if(!a)
208
return CURLE_OUT_OF_MEMORY;
209
210
a->addr = addr;
211
a->ai_family = ai_family;
212
a->transport = transport;
213
a->result = CURLE_OK;
214
a->cf_create = cf_create;
215
*pa = a;
216
217
result = a->cf_create(&a->cf, data, cf->conn, a->addr, transport);
218
if(result)
219
goto out;
220
221
/* the new filter might have sub-filters */
222
for(wcf = a->cf; wcf; wcf = wcf->next) {
223
wcf->conn = cf->conn;
224
wcf->sockindex = cf->sockindex;
225
}
226
227
out:
228
if(result) {
229
cf_ip_attempt_free(a, data);
230
*pa = NULL;
231
}
232
return result;
233
}
234
235
static CURLcode cf_ip_attempt_connect(struct cf_ip_attempt *a,
236
struct Curl_easy *data,
237
bool *connected)
238
{
239
*connected = a->connected;
240
if(!a->result && !*connected) {
241
/* evaluate again */
242
a->result = Curl_conn_cf_connect(a->cf, data, connected);
243
244
if(!a->result) {
245
if(*connected) {
246
a->connected = TRUE;
247
}
248
}
249
else if(a->result == CURLE_WEIRD_SERVER_REPLY)
250
a->inconclusive = TRUE;
251
}
252
return a->result;
253
}
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
{
274
struct Curl_cfilter *cf_prev = a->cf;
275
struct Curl_cfilter *wcf;
276
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
a->result = CURLE_OK;
282
a->connected = FALSE;
283
a->inconclusive = FALSE;
284
a->cf = NULL;
285
286
result = a->cf_create(&a->cf, data, cf->conn, a->addr, a->transport);
287
if(!result) {
288
bool dummy;
289
/* the new filter might have sub-filters */
290
for(wcf = a->cf; wcf; wcf = wcf->next) {
291
wcf->conn = cf->conn;
292
wcf->sockindex = cf->sockindex;
293
}
294
a->result = cf_ip_attempt_connect(a, data, &dummy);
295
}
296
if(cf_prev)
297
Curl_conn_cf_discard_chain(&cf_prev, data);
298
return result;
299
}
300
301
static void cf_ip_ballers_clear(struct Curl_cfilter *cf,
302
struct Curl_easy *data,
303
struct cf_ip_ballers *bs)
304
{
305
(void)cf;
306
while(bs->running) {
307
struct cf_ip_attempt *a = bs->running;
308
bs->running = a->next;
309
cf_ip_attempt_free(a, data);
310
}
311
cf_ip_attempt_free(bs->winner, data);
312
bs->winner = NULL;
313
}
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
{
321
memset(bs, 0, sizeof(*bs));
322
bs->cf_create = cf_create;
323
bs->transport = transport;
324
bs->attempt_delay_ms = attempt_delay_ms;
325
bs->last_attempt_ai_family = AF_INET; /* so AF_INET6 is next */
326
327
if(transport == TRNSPRT_UNIX) {
328
#ifdef USE_UNIX_SOCKETS
329
cf_ai_iter_init(&bs->addr_iter, addr_list, AF_UNIX);
330
#else
331
return CURLE_UNSUPPORTED_PROTOCOL;
332
#endif
333
}
334
else { /* TCP/UDP/QUIC */
335
#ifdef USE_IPV6
336
if(ip_version == CURL_IPRESOLVE_V6)
337
cf_ai_iter_init(&bs->addr_iter, NULL, AF_INET);
338
else
339
cf_ai_iter_init(&bs->addr_iter, addr_list, AF_INET);
340
341
if(ip_version == CURL_IPRESOLVE_V4)
342
cf_ai_iter_init(&bs->ipv6_iter, NULL, AF_INET6);
343
else
344
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
}
350
return CURLE_OK;
351
}
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
{
358
CURLcode result = CURLE_OK;
359
struct cf_ip_attempt *a = NULL, **panchor;
360
bool do_more;
361
struct curltime now;
362
timediff_t next_expire_ms;
363
int i, inconclusive, ongoing;
364
365
if(bs->winner)
366
return CURLE_OK;
367
368
evaluate:
369
now = curlx_now();
370
ongoing = inconclusive = 0;
371
372
/* check if a running baller connects now */
373
i = -1;
374
for(panchor = &bs->running; *panchor; panchor = &((*panchor)->next)) {
375
++i;
376
a = *panchor;
377
a->result = cf_ip_attempt_connect(a, data, connected);
378
if(!a->result) {
379
if(*connected) {
380
/* connected, declare the winner, remove from running,
381
* clear remaining running list. */
382
CURL_TRC_CF(data, cf, "connect attempt #%d successful", i);
383
bs->winner = a;
384
*panchor = a->next;
385
a->next = NULL;
386
while(bs->running) {
387
a = bs->running;
388
bs->running = a->next;
389
cf_ip_attempt_free(a, data);
390
}
391
return CURLE_OK;
392
}
393
/* still running */
394
++ongoing;
395
}
396
else if(a->inconclusive) /* failed, but inconclusive */
397
++inconclusive;
398
}
399
if(bs->running)
400
CURL_TRC_CF(data, cf, "checked connect attempts: "
401
"%d ongoing, %d inconclusive", ongoing, inconclusive);
402
403
/* no attempt connected yet, start another one? */
404
if(!ongoing) {
405
if(!bs->started.tv_sec && !bs->started.tv_usec)
406
bs->started = now;
407
do_more = TRUE;
408
}
409
else {
410
bool more_possible = cf_ai_iter_has_more(&bs->addr_iter);
411
#ifdef USE_IPV6
412
if(!more_possible)
413
more_possible = cf_ai_iter_has_more(&bs->ipv6_iter);
414
#endif
415
do_more = more_possible &&
416
(curlx_timediff(now, bs->last_attempt_started) >=
417
bs->attempt_delay_ms);
418
if(do_more)
419
CURL_TRC_CF(data, cf, "happy eyeballs timeout expired, "
420
"start next attempt");
421
}
422
423
if(do_more) {
424
/* start the next attempt if there is another ip address to try.
425
* Alternate between address families when possible. */
426
const struct Curl_addrinfo *addr = NULL;
427
int ai_family = 0;
428
#ifdef USE_IPV6
429
if((bs->last_attempt_ai_family == AF_INET) ||
430
!cf_ai_iter_has_more(&bs->addr_iter)) {
431
addr = cf_ai_iter_next(&bs->ipv6_iter);
432
ai_family = bs->ipv6_iter.ai_family;
433
}
434
#endif
435
if(!addr) {
436
addr = cf_ai_iter_next(&bs->addr_iter);
437
ai_family = bs->addr_iter.ai_family;
438
}
439
440
if(addr) { /* try another address */
441
result = cf_ip_attempt_new(&a, cf, data, addr, ai_family,
442
bs->transport, bs->cf_create);
443
CURL_TRC_CF(data, cf, "starting %s attempt for ipv%s -> %d",
444
bs->running ? "next" : "first",
445
(ai_family == AF_INET) ? "4" : "6", result);
446
if(result)
447
goto out;
448
DEBUGASSERT(a);
449
450
/* append to running list */
451
panchor = &bs->running;
452
while(*panchor)
453
panchor = &((*panchor)->next);
454
*panchor = a;
455
bs->last_attempt_started = now;
456
bs->last_attempt_ai_family = ai_family;
457
/* and run everything again */
458
goto evaluate;
459
}
460
else if(inconclusive) {
461
/* tried all addresses, no success but some where inconclusive.
462
* Let's restart the inconclusive ones. */
463
timediff_t since_ms = curlx_timediff(now, bs->last_attempt_started);
464
timediff_t delay_ms = bs->attempt_delay_ms - since_ms;
465
if(delay_ms <= 0) {
466
CURL_TRC_CF(data, cf, "all attempts inconclusive, restarting one");
467
i = -1;
468
for(a = bs->running; a; a = a->next) {
469
++i;
470
if(!a->inconclusive)
471
continue;
472
result = cf_ip_attempt_restart(a, cf, data);
473
CURL_TRC_CF(data, cf, "restarted baller %d -> %d", i, result);
474
if(result) /* serious failure */
475
goto out;
476
bs->last_attempt_started = now;
477
goto evaluate;
478
}
479
DEBUGASSERT(0); /* should not come here */
480
}
481
else {
482
/* let's wait some more before restarting */
483
infof(data, "connect attempts inconclusive, retrying "
484
"in %" FMT_TIMEDIFF_T "ms", delay_ms);
485
Curl_expire(data, delay_ms, EXPIRE_HAPPY_EYEBALLS);
486
}
487
/* attempt timeout for restart has not expired yet */
488
goto out;
489
}
490
else if(!ongoing) {
491
/* no more addresses, no inconclusive attempts */
492
CURL_TRC_CF(data, cf, "no more attempts to try");
493
result = CURLE_COULDNT_CONNECT;
494
i = 0;
495
for(a = bs->running; a; a = a->next) {
496
CURL_TRC_CF(data, cf, "baller %d: result=%d", i, a->result);
497
if(a->result)
498
result = a->result;
499
}
500
}
501
}
502
503
out:
504
if(!result) {
505
bool more_possible;
506
507
/* when do we need to be called again? */
508
next_expire_ms = Curl_timeleft(data, &now, TRUE);
509
if(next_expire_ms <= 0) {
510
failf(data, "Connection timeout after %" FMT_OFF_T " ms",
511
curlx_timediff(now, data->progress.t_startsingle));
512
return CURLE_OPERATION_TIMEDOUT;
513
}
514
515
more_possible = cf_ai_iter_has_more(&bs->addr_iter);
516
#ifdef USE_IPV6
517
if(!more_possible)
518
more_possible = cf_ai_iter_has_more(&bs->ipv6_iter);
519
#endif
520
if(more_possible) {
521
timediff_t expire_ms, elapsed_ms;
522
elapsed_ms = curlx_timediff(now, bs->last_attempt_started);
523
expire_ms = CURLMAX(bs->attempt_delay_ms - elapsed_ms, 0);
524
next_expire_ms = CURLMIN(next_expire_ms, expire_ms);
525
if(next_expire_ms <= 0) {
526
CURL_TRC_CF(data, cf, "HAPPY_EYBALLS timeout due, re-evaluate");
527
goto evaluate;
528
}
529
CURL_TRC_CF(data, cf, "next HAPPY_EYBALLS timeout in %" FMT_TIMEDIFF_T
530
"ms", next_expire_ms);
531
Curl_expire(data, next_expire_ms, EXPIRE_HAPPY_EYEBALLS);
532
}
533
}
534
return result;
535
}
536
537
static CURLcode cf_ip_ballers_shutdown(struct cf_ip_ballers *bs,
538
struct Curl_easy *data,
539
bool *done)
540
{
541
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
*done = TRUE;
546
for(a = bs->running; a; a = a->next) {
547
bool bdone = FALSE;
548
if(a->shutdown)
549
continue;
550
a->result = a->cf->cft->do_shutdown(a->cf, data, &bdone);
551
if(a->result || bdone)
552
a->shutdown = TRUE; /* treat a failed shutdown as done */
553
else
554
*done = FALSE;
555
}
556
return CURLE_OK;
557
}
558
559
static CURLcode cf_ip_ballers_pollset(struct cf_ip_ballers *bs,
560
struct Curl_easy *data,
561
struct easy_pollset *ps)
562
{
563
struct cf_ip_attempt *a;
564
CURLcode result = CURLE_OK;
565
for(a = bs->running; a && !result; a = a->next) {
566
if(a->result)
567
continue;
568
result = Curl_conn_cf_adjust_pollset(a->cf, data, ps);
569
}
570
return result;
571
}
572
573
static bool cf_ip_ballers_pending(struct cf_ip_ballers *bs,
574
const struct Curl_easy *data)
575
{
576
struct cf_ip_attempt *a;
577
578
for(a = bs->running; a; a = a->next) {
579
if(a->result)
580
continue;
581
if(a->cf->cft->has_data_pending(a->cf, data))
582
return TRUE;
583
}
584
return FALSE;
585
}
586
587
static struct curltime cf_ip_ballers_max_time(struct cf_ip_ballers *bs,
588
struct Curl_easy *data,
589
int query)
590
{
591
struct curltime t, tmax;
592
struct cf_ip_attempt *a;
593
594
memset(&tmax, 0, sizeof(tmax));
595
for(a = bs->running; a; a = a->next) {
596
memset(&t, 0, sizeof(t));
597
if(!a->cf->cft->query(a->cf, data, query, NULL, &t)) {
598
if((t.tv_sec || t.tv_usec) && curlx_timediff_us(t, tmax) > 0)
599
tmax = t;
600
}
601
}
602
return tmax;
603
}
604
605
static int cf_ip_ballers_min_reply_ms(struct cf_ip_ballers *bs,
606
struct Curl_easy *data)
607
{
608
int reply_ms = -1, breply_ms;
609
struct cf_ip_attempt *a;
610
611
for(a = bs->running; a; a = a->next) {
612
if(!a->cf->cft->query(a->cf, data, CF_QUERY_CONNECT_REPLY_MS,
613
&breply_ms, NULL)) {
614
if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms))
615
reply_ms = breply_ms;
616
}
617
}
618
return reply_ms;
619
}
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
{
641
struct cf_ip_happy_ctx *ctx = cf->ctx;
642
struct connectdata *conn = cf->conn;
643
CURLcode result;
644
645
result = cf_ip_ballers_run(&ctx->ballers, cf, data, connected);
646
647
if(!result)
648
return CURLE_OK;
649
650
{
651
const char *hostname, *proxy_name = NULL;
652
char viamsg[160];
653
#ifndef CURL_DISABLE_PROXY
654
if(conn->bits.socksproxy)
655
proxy_name = conn->socks_proxy.host.name;
656
else if(conn->bits.httpproxy)
657
proxy_name = conn->http_proxy.host.name;
658
#endif
659
hostname = conn->bits.conn_to_host ? conn->conn_to_host.name :
660
conn->host.name;
661
662
#ifdef USE_UNIX_SOCKETS
663
if(conn->unix_domain_socket)
664
curl_msnprintf(viamsg, sizeof(viamsg), "over %s",
665
conn->unix_domain_socket);
666
else
667
#endif
668
{
669
int port;
670
if(cf->sockindex == SECONDARYSOCKET)
671
port = conn->secondary_port;
672
else if(cf->conn->bits.conn_to_port)
673
port = conn->conn_to_port;
674
else
675
port = conn->remote_port;
676
curl_msnprintf(viamsg, sizeof(viamsg), "port %u", port);
677
}
678
679
failf(data, "Failed to connect to %s %s %s%s%safter "
680
"%" FMT_TIMEDIFF_T " ms: %s",
681
hostname, viamsg,
682
proxy_name ? "via " : "",
683
proxy_name ? proxy_name : "",
684
proxy_name ? " " : "",
685
curlx_timediff(curlx_now(), data->progress.t_startsingle),
686
curl_easy_strerror(result));
687
}
688
689
#ifdef SOCKETIMEDOUT
690
if(SOCKETIMEDOUT == data->state.os_errno)
691
result = CURLE_OPERATION_TIMEDOUT;
692
#endif
693
694
return result;
695
}
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
{
704
struct cf_ip_happy_ctx *ctx = cf->ctx;
705
struct Curl_dns_entry *dns = data->state.dns[cf->sockindex];
706
707
if(!dns)
708
return CURLE_FAILED_INIT;
709
710
if(Curl_timeleft(data, NULL, TRUE) < 0) {
711
/* a precaution, no need to continue if time already is up */
712
failf(data, "Connection time-out");
713
return CURLE_OPERATION_TIMEDOUT;
714
}
715
716
CURL_TRC_CF(data, cf, "init ip ballers for transport %d", ctx->transport);
717
ctx->started = curlx_now();
718
return cf_ip_ballers_init(&ctx->ballers, cf->conn->ip_version,
719
dns->addr, ctx->cf_create, ctx->transport,
720
data->set.happy_eyeballs_timeout);
721
}
722
723
static void cf_ip_happy_ctx_clear(struct Curl_cfilter *cf,
724
struct Curl_easy *data)
725
{
726
struct cf_ip_happy_ctx *ctx = cf->ctx;
727
728
DEBUGASSERT(ctx);
729
DEBUGASSERT(data);
730
cf_ip_ballers_clear(cf, data, &ctx->ballers);
731
}
732
733
static CURLcode cf_ip_happy_shutdown(struct Curl_cfilter *cf,
734
struct Curl_easy *data,
735
bool *done)
736
{
737
struct cf_ip_happy_ctx *ctx = cf->ctx;
738
CURLcode result = CURLE_OK;
739
740
DEBUGASSERT(data);
741
if(cf->connected) {
742
*done = TRUE;
743
return CURLE_OK;
744
}
745
746
result = cf_ip_ballers_shutdown(&ctx->ballers, data, done);
747
CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done);
748
return result;
749
}
750
751
static CURLcode cf_ip_happy_adjust_pollset(struct Curl_cfilter *cf,
752
struct Curl_easy *data,
753
struct easy_pollset *ps)
754
{
755
struct cf_ip_happy_ctx *ctx = cf->ctx;
756
CURLcode result = CURLE_OK;
757
758
if(!cf->connected) {
759
result = cf_ip_ballers_pollset(&ctx->ballers, data, ps);
760
CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %d socks", result, ps->n);
761
}
762
return result;
763
}
764
765
static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf,
766
struct Curl_easy *data,
767
bool *done)
768
{
769
struct cf_ip_happy_ctx *ctx = cf->ctx;
770
CURLcode result = CURLE_OK;
771
772
if(cf->connected) {
773
*done = TRUE;
774
return CURLE_OK;
775
}
776
777
DEBUGASSERT(ctx);
778
*done = FALSE;
779
780
switch(ctx->state) {
781
case SCFST_INIT:
782
DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data));
783
DEBUGASSERT(!cf->connected);
784
result = start_connect(cf, data);
785
if(result)
786
return result;
787
ctx->state = SCFST_WAITING;
788
FALLTHROUGH();
789
case SCFST_WAITING:
790
result = is_connected(cf, data, done);
791
if(!result && *done) {
792
DEBUGASSERT(ctx->ballers.winner);
793
DEBUGASSERT(ctx->ballers.winner->cf);
794
DEBUGASSERT(ctx->ballers.winner->cf->connected);
795
/* we have a winner. Install and activate it.
796
* close/free all others. */
797
ctx->state = SCFST_DONE;
798
cf->connected = TRUE;
799
cf->next = ctx->ballers.winner->cf;
800
ctx->ballers.winner->cf = NULL;
801
cf_ip_happy_ctx_clear(cf, data);
802
Curl_expire_done(data, EXPIRE_HAPPY_EYEBALLS);
803
804
if(cf->conn->handler->protocol & PROTO_FAMILY_SSH)
805
Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */
806
#ifndef CURL_DISABLE_VERBOSE_STRINGS
807
if(Curl_trc_cf_is_verbose(cf, data)) {
808
struct ip_quadruple ipquad;
809
bool is_ipv6;
810
if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad)) {
811
const char *host;
812
int port;
813
Curl_conn_get_current_host(data, cf->sockindex, &host, &port);
814
CURL_TRC_CF(data, cf, "Connected to %s (%s) port %u",
815
host, ipquad.remote_ip, ipquad.remote_port);
816
}
817
}
818
#endif
819
data->info.numconnects++; /* to track the # of connections made */
820
}
821
break;
822
case SCFST_DONE:
823
*done = TRUE;
824
break;
825
}
826
return result;
827
}
828
829
static void cf_ip_happy_close(struct Curl_cfilter *cf,
830
struct Curl_easy *data)
831
{
832
struct cf_ip_happy_ctx *ctx = cf->ctx;
833
834
CURL_TRC_CF(data, cf, "close");
835
cf_ip_happy_ctx_clear(cf, data);
836
cf->connected = FALSE;
837
ctx->state = SCFST_INIT;
838
839
if(cf->next) {
840
cf->next->cft->do_close(cf->next, data);
841
Curl_conn_cf_discard_chain(&cf->next, data);
842
}
843
}
844
845
static bool cf_ip_happy_data_pending(struct Curl_cfilter *cf,
846
const struct Curl_easy *data)
847
{
848
struct cf_ip_happy_ctx *ctx = cf->ctx;
849
850
if(!cf->connected) {
851
return cf_ip_ballers_pending(&ctx->ballers, data);
852
}
853
return cf->next->cft->has_data_pending(cf->next, data);
854
}
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
{
860
struct cf_ip_happy_ctx *ctx = cf->ctx;
861
862
if(!cf->connected) {
863
switch(query) {
864
case CF_QUERY_CONNECT_REPLY_MS: {
865
*pres1 = cf_ip_ballers_min_reply_ms(&ctx->ballers, data);
866
CURL_TRC_CF(data, cf, "query connect reply: %dms", *pres1);
867
return CURLE_OK;
868
}
869
case CF_QUERY_TIMER_CONNECT: {
870
struct curltime *when = pres2;
871
*when = cf_ip_ballers_max_time(&ctx->ballers, data,
872
CF_QUERY_TIMER_CONNECT);
873
return CURLE_OK;
874
}
875
case CF_QUERY_TIMER_APPCONNECT: {
876
struct curltime *when = pres2;
877
*when = cf_ip_ballers_max_time(&ctx->ballers, data,
878
CF_QUERY_TIMER_APPCONNECT);
879
return CURLE_OK;
880
}
881
default:
882
break;
883
}
884
}
885
886
return cf->next ?
887
cf->next->cft->query(cf->next, data, query, pres1, pres2) :
888
CURLE_UNKNOWN_OPTION;
889
}
890
891
static void cf_ip_happy_destroy(struct Curl_cfilter *cf,
892
struct Curl_easy *data)
893
{
894
struct cf_ip_happy_ctx *ctx = cf->ctx;
895
896
CURL_TRC_CF(data, cf, "destroy");
897
if(ctx) {
898
cf_ip_happy_ctx_clear(cf, data);
899
}
900
/* release any resources held in state */
901
Curl_safefree(ctx);
902
}
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
{
938
struct cf_ip_happy_ctx *ctx = NULL;
939
CURLcode result;
940
941
(void)data;
942
(void)conn;
943
*pcf = NULL;
944
ctx = calloc(1, sizeof(*ctx));
945
if(!ctx) {
946
result = CURLE_OUT_OF_MEMORY;
947
goto out;
948
}
949
ctx->transport = transport;
950
ctx->cf_create = cf_create;
951
952
result = Curl_cf_create(pcf, &Curl_cft_ip_happy, ctx);
953
954
out:
955
if(result) {
956
Curl_safefree(*pcf);
957
free(ctx);
958
}
959
return result;
960
}
961
962
CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at,
963
struct Curl_easy *data,
964
int transport)
965
{
966
cf_ip_connect_create *cf_create;
967
struct Curl_cfilter *cf;
968
CURLcode result;
969
970
/* Need to be first */
971
DEBUGASSERT(cf_at);
972
cf_create = get_cf_create(transport);
973
if(!cf_create) {
974
CURL_TRC_CF(data, cf_at, "unsupported transport type %d", transport);
975
return CURLE_UNSUPPORTED_PROTOCOL;
976
}
977
result = cf_ip_happy_create(&cf, data, cf_at->conn, cf_create, transport);
978
if(result)
979
return result;
980
981
Curl_conn_cf_insert_after(cf_at, cf);
982
return CURLE_OK;
983
}
984
985