mirror of
https://github.com/curl/curl.git
synced 2026-06-22 17:15:38 +03:00
Before this patch curl used the C preprocessor to override standard memory allocation symbols: malloc, calloc, strdup, realloc, free. The goal of these is to replace them with curl's debug wrappers in `CURLDEBUG` builds, another was to replace them with the wrappers calling user-defined allocators in libcurl. This solution needed a bunch of workarounds to avoid breaking external headers: it relied on include order to do the overriding last. For "unity" builds it needed to reset overrides before external includes. Also in test apps, which are always built as single source files. It also needed the `(symbol)` trick to avoid overrides in some places. This would still not fix cases where the standard symbols were macros. It was also fragile and difficult to figure out which was the actual function behind an alloc or free call in a specific piece of code. This in turn caused bugs where the wrong allocator was accidentally called. To avoid these problems, this patch replaces this solution with `curlx_`-prefixed allocator macros, and mapping them _once_ to either the libcurl wrappers, the debug wrappers or the standard ones, matching the rest of the code in libtests. This concludes the long journey to avoid redefining standard functions in the curl codebase. Note: I did not update `packages/OS400/*.c` sources. They did not `#include` `curl_setup.h`, `curl_memory.h` or `memdebug.h`, meaning the overrides were never applied to them. This may or may not have been correct. For now I suppressed the direct use of standard allocators via a local `.checksrc`. Probably they (except for `curlcl.c`) should be updated to include `curl_setup.h` and use the `curlx_` macros. This patch changes mappings in two places: - `lib/curl_threads.c` in libtests: Before this patch it mapped to libcurl allocators. After, it maps to standard allocators, like the rest of libtests code. - `units`: before this patch it mapped to standard allocators. After, it maps to libcurl allocators. Also: - drop all position-dependent `curl_memory.h` and `memdebug.h` includes, and delete the now unnecessary headers. - rename `Curl_tcsdup` macro to `curlx_tcsdup` and define like the other allocators. - map `curlx_strdup()` to `_strdup()` on Windows (was: `strdup()`). To fix warnings silenced via `_CRT_NONSTDC_NO_DEPRECATE`. - multibyte: map `curlx_convert_*()` to `_strdup()` on Windows (was: `strdup()`). - src: do not reuse the `strdup` name for the local replacement. - lib509: call `_strdup()` on Windows (was: `strdup()`). - test1132: delete test obsoleted by this patch. - CHECKSRC.md: update text for `SNPRINTF`. - checksrc: ban standard allocator symbols. Follow-up tob12da22db1#18866 Follow-up todb98daab05#18844 Follow-up to4deea9396b#18814 Follow-up to9678ff5b1b#18776 Follow-up to10bac43b87#18774 Follow-up to20142f5d06#18634 Follow-up tobf7375ecc5#18503 Follow-up to9863599d69#18502 Follow-up to3bb5e58c10#17827 Closes #19626
979 lines
28 KiB
C
979 lines
28 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at https://curl.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
* SPDX-License-Identifier: curl
|
|
*
|
|
***************************************************************************/
|
|
|
|
#include "curl_setup.h"
|
|
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h> /* <netinet/tcp.h> may need it */
|
|
#endif
|
|
#ifdef HAVE_SYS_UN_H
|
|
#include <sys/un.h> /* for sockaddr_un */
|
|
#endif
|
|
#ifdef HAVE_LINUX_TCP_H
|
|
#include <linux/tcp.h>
|
|
#elif defined(HAVE_NETINET_TCP_H)
|
|
#include <netinet/tcp.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_IOCTL_H
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
#ifdef HAVE_NETDB_H
|
|
#include <netdb.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#ifdef __VMS
|
|
#include <in.h>
|
|
#include <inet.h>
|
|
#endif
|
|
|
|
#include "urldata.h"
|
|
#include "connect.h"
|
|
#include "cfilters.h"
|
|
#include "cf-ip-happy.h"
|
|
#include "curl_trc.h"
|
|
#include "multiif.h"
|
|
#include "progress.h"
|
|
#include "select.h"
|
|
#include "vquic/vquic.h" /* for quic cfilters */
|
|
|
|
|
|
struct transport_provider {
|
|
uint8_t transport;
|
|
cf_ip_connect_create *cf_create;
|
|
};
|
|
|
|
static
|
|
#ifndef UNITTESTS
|
|
const
|
|
#endif
|
|
struct transport_provider transport_providers[] = {
|
|
{ TRNSPRT_TCP, Curl_cf_tcp_create },
|
|
#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3)
|
|
{ TRNSPRT_QUIC, Curl_cf_quic_create },
|
|
#endif
|
|
#ifndef CURL_DISABLE_TFTP
|
|
{ TRNSPRT_UDP, Curl_cf_udp_create },
|
|
#endif
|
|
#ifdef USE_UNIX_SOCKETS
|
|
{ TRNSPRT_UNIX, Curl_cf_unix_create },
|
|
#endif
|
|
};
|
|
|
|
static cf_ip_connect_create *get_cf_create(uint8_t transport)
|
|
{
|
|
size_t i;
|
|
for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) {
|
|
if(transport == transport_providers[i].transport)
|
|
return transport_providers[i].cf_create;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef UNITTESTS
|
|
/* used by unit2600.c */
|
|
void Curl_debug_set_transport_provider(uint8_t transport,
|
|
cf_ip_connect_create *cf_create)
|
|
{
|
|
size_t i;
|
|
for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) {
|
|
if(transport == transport_providers[i].transport) {
|
|
transport_providers[i].cf_create = cf_create;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif /* UNITTESTS */
|
|
|
|
|
|
struct cf_ai_iter {
|
|
const struct Curl_addrinfo *head;
|
|
const struct Curl_addrinfo *last;
|
|
int ai_family;
|
|
int n;
|
|
};
|
|
|
|
static void cf_ai_iter_init(struct cf_ai_iter *iter,
|
|
const struct Curl_addrinfo *list,
|
|
int ai_family)
|
|
{
|
|
iter->head = list;
|
|
iter->ai_family = ai_family;
|
|
iter->last = NULL;
|
|
iter->n = -1;
|
|
}
|
|
|
|
static const struct Curl_addrinfo *cf_ai_iter_next(struct cf_ai_iter *iter)
|
|
{
|
|
const struct Curl_addrinfo *addr;
|
|
if(iter->n < 0) {
|
|
iter->n++;
|
|
for(addr = iter->head; addr; addr = addr->ai_next) {
|
|
if(addr->ai_family == iter->ai_family)
|
|
break;
|
|
}
|
|
iter->last = addr;
|
|
}
|
|
else if(iter->last) {
|
|
iter->n++;
|
|
for(addr = iter->last->ai_next; addr; addr = addr->ai_next) {
|
|
if(addr->ai_family == iter->ai_family)
|
|
break;
|
|
}
|
|
iter->last = addr;
|
|
}
|
|
return iter->last;
|
|
}
|
|
|
|
static bool cf_ai_iter_has_more(struct cf_ai_iter *iter)
|
|
{
|
|
const struct Curl_addrinfo *addr = iter->last ? iter->last->ai_next :
|
|
((iter->n < 0) ? iter->head : NULL);
|
|
while(addr) {
|
|
if(addr->ai_family == iter->ai_family)
|
|
return TRUE;
|
|
addr = addr->ai_next;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
struct cf_ip_attempt {
|
|
struct cf_ip_attempt *next;
|
|
const struct Curl_addrinfo *addr; /* List of addresses to try, not owned */
|
|
struct Curl_cfilter *cf; /* current sub-cfilter connecting */
|
|
cf_ip_connect_create *cf_create;
|
|
struct curltime started; /* start of current attempt */
|
|
CURLcode result;
|
|
int ai_family;
|
|
uint8_t transport;
|
|
int error;
|
|
BIT(connected); /* cf has connected */
|
|
BIT(shutdown); /* cf has shutdown */
|
|
BIT(inconclusive); /* connect was not a hard failure, we
|
|
* might talk to a restarting server */
|
|
};
|
|
|
|
static void cf_ip_attempt_free(struct cf_ip_attempt *a,
|
|
struct Curl_easy *data)
|
|
{
|
|
if(a) {
|
|
if(a->cf)
|
|
Curl_conn_cf_discard_chain(&a->cf, data);
|
|
curlx_free(a);
|
|
}
|
|
}
|
|
|
|
static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa,
|
|
struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
const struct Curl_addrinfo *addr,
|
|
int ai_family,
|
|
uint8_t transport,
|
|
cf_ip_connect_create *cf_create)
|
|
{
|
|
struct Curl_cfilter *wcf;
|
|
struct cf_ip_attempt *a;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
*pa = NULL;
|
|
a = curlx_calloc(1, sizeof(*a));
|
|
if(!a)
|
|
return CURLE_OUT_OF_MEMORY;
|
|
|
|
a->addr = addr;
|
|
a->ai_family = ai_family;
|
|
a->transport = transport;
|
|
a->result = CURLE_OK;
|
|
a->cf_create = cf_create;
|
|
*pa = a;
|
|
|
|
result = a->cf_create(&a->cf, data, cf->conn, a->addr, transport);
|
|
if(result)
|
|
goto out;
|
|
|
|
/* the new filter might have sub-filters */
|
|
for(wcf = a->cf; wcf; wcf = wcf->next) {
|
|
wcf->conn = cf->conn;
|
|
wcf->sockindex = cf->sockindex;
|
|
}
|
|
|
|
out:
|
|
if(result) {
|
|
cf_ip_attempt_free(a, data);
|
|
*pa = NULL;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_ip_attempt_connect(struct cf_ip_attempt *a,
|
|
struct Curl_easy *data,
|
|
bool *connected)
|
|
{
|
|
*connected = a->connected;
|
|
if(!a->result && !*connected) {
|
|
/* evaluate again */
|
|
a->result = Curl_conn_cf_connect(a->cf, data, connected);
|
|
|
|
if(!a->result) {
|
|
if(*connected) {
|
|
a->connected = TRUE;
|
|
}
|
|
}
|
|
else if(a->result == CURLE_WEIRD_SERVER_REPLY)
|
|
a->inconclusive = TRUE;
|
|
}
|
|
return a->result;
|
|
}
|
|
|
|
struct cf_ip_ballers {
|
|
struct cf_ip_attempt *running;
|
|
struct cf_ip_attempt *winner;
|
|
struct cf_ai_iter addr_iter;
|
|
#ifdef USE_IPV6
|
|
struct cf_ai_iter ipv6_iter;
|
|
#endif
|
|
cf_ip_connect_create *cf_create; /* for creating cf */
|
|
struct curltime started;
|
|
struct curltime last_attempt_started;
|
|
timediff_t attempt_delay_ms;
|
|
int last_attempt_ai_family;
|
|
uint8_t transport;
|
|
};
|
|
|
|
static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a,
|
|
struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct Curl_cfilter *cf_prev = a->cf;
|
|
struct Curl_cfilter *wcf;
|
|
CURLcode result;
|
|
|
|
/* When restarting, we tear down and existing filter *after* we
|
|
* started up the new one. This gives us a new socket number and
|
|
* probably a new local port. Which may prevent confusion. */
|
|
a->result = CURLE_OK;
|
|
a->connected = FALSE;
|
|
a->inconclusive = FALSE;
|
|
a->cf = NULL;
|
|
|
|
result = a->cf_create(&a->cf, data, cf->conn, a->addr, a->transport);
|
|
if(!result) {
|
|
bool dummy;
|
|
/* the new filter might have sub-filters */
|
|
for(wcf = a->cf; wcf; wcf = wcf->next) {
|
|
wcf->conn = cf->conn;
|
|
wcf->sockindex = cf->sockindex;
|
|
}
|
|
a->result = cf_ip_attempt_connect(a, data, &dummy);
|
|
}
|
|
if(cf_prev)
|
|
Curl_conn_cf_discard_chain(&cf_prev, data);
|
|
return result;
|
|
}
|
|
|
|
static void cf_ip_ballers_clear(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct cf_ip_ballers *bs)
|
|
{
|
|
(void)cf;
|
|
while(bs->running) {
|
|
struct cf_ip_attempt *a = bs->running;
|
|
bs->running = a->next;
|
|
cf_ip_attempt_free(a, data);
|
|
}
|
|
cf_ip_attempt_free(bs->winner, data);
|
|
bs->winner = NULL;
|
|
}
|
|
|
|
static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs, int ip_version,
|
|
const struct Curl_addrinfo *addr_list,
|
|
cf_ip_connect_create *cf_create,
|
|
uint8_t transport,
|
|
timediff_t attempt_delay_ms)
|
|
{
|
|
memset(bs, 0, sizeof(*bs));
|
|
bs->cf_create = cf_create;
|
|
bs->transport = transport;
|
|
bs->attempt_delay_ms = attempt_delay_ms;
|
|
bs->last_attempt_ai_family = AF_INET; /* so AF_INET6 is next */
|
|
|
|
if(transport == TRNSPRT_UNIX) {
|
|
#ifdef USE_UNIX_SOCKETS
|
|
cf_ai_iter_init(&bs->addr_iter, addr_list, AF_UNIX);
|
|
#else
|
|
return CURLE_UNSUPPORTED_PROTOCOL;
|
|
#endif
|
|
}
|
|
else { /* TCP/UDP/QUIC */
|
|
#ifdef USE_IPV6
|
|
if(ip_version == CURL_IPRESOLVE_V6)
|
|
cf_ai_iter_init(&bs->addr_iter, NULL, AF_INET);
|
|
else
|
|
cf_ai_iter_init(&bs->addr_iter, addr_list, AF_INET);
|
|
|
|
if(ip_version == CURL_IPRESOLVE_V4)
|
|
cf_ai_iter_init(&bs->ipv6_iter, NULL, AF_INET6);
|
|
else
|
|
cf_ai_iter_init(&bs->ipv6_iter, addr_list, AF_INET6);
|
|
#else
|
|
(void)ip_version;
|
|
cf_ai_iter_init(&bs->addr_iter, addr_list, AF_INET);
|
|
#endif
|
|
}
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs,
|
|
struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *connected)
|
|
{
|
|
CURLcode result = CURLE_OK;
|
|
struct cf_ip_attempt *a = NULL, **panchor;
|
|
bool do_more;
|
|
struct curltime now;
|
|
timediff_t next_expire_ms;
|
|
int i, inconclusive, ongoing;
|
|
|
|
if(bs->winner)
|
|
return CURLE_OK;
|
|
|
|
evaluate:
|
|
now = curlx_now();
|
|
ongoing = inconclusive = 0;
|
|
|
|
/* check if a running baller connects now */
|
|
i = -1;
|
|
for(panchor = &bs->running; *panchor; panchor = &((*panchor)->next)) {
|
|
++i;
|
|
a = *panchor;
|
|
a->result = cf_ip_attempt_connect(a, data, connected);
|
|
if(!a->result) {
|
|
if(*connected) {
|
|
/* connected, declare the winner, remove from running,
|
|
* clear remaining running list. */
|
|
CURL_TRC_CF(data, cf, "connect attempt #%d successful", i);
|
|
bs->winner = a;
|
|
*panchor = a->next;
|
|
a->next = NULL;
|
|
while(bs->running) {
|
|
a = bs->running;
|
|
bs->running = a->next;
|
|
cf_ip_attempt_free(a, data);
|
|
}
|
|
return CURLE_OK;
|
|
}
|
|
/* still running */
|
|
++ongoing;
|
|
}
|
|
else if(a->inconclusive) /* failed, but inconclusive */
|
|
++inconclusive;
|
|
}
|
|
if(bs->running)
|
|
CURL_TRC_CF(data, cf, "checked connect attempts: "
|
|
"%d ongoing, %d inconclusive", ongoing, inconclusive);
|
|
|
|
/* no attempt connected yet, start another one? */
|
|
if(!ongoing) {
|
|
if(!bs->started.tv_sec && !bs->started.tv_usec)
|
|
bs->started = now;
|
|
do_more = TRUE;
|
|
}
|
|
else {
|
|
bool more_possible = cf_ai_iter_has_more(&bs->addr_iter);
|
|
#ifdef USE_IPV6
|
|
if(!more_possible)
|
|
more_possible = cf_ai_iter_has_more(&bs->ipv6_iter);
|
|
#endif
|
|
do_more = more_possible &&
|
|
(curlx_timediff_ms(now, bs->last_attempt_started) >=
|
|
bs->attempt_delay_ms);
|
|
if(do_more)
|
|
CURL_TRC_CF(data, cf, "happy eyeballs timeout expired, "
|
|
"start next attempt");
|
|
}
|
|
|
|
if(do_more) {
|
|
/* start the next attempt if there is another ip address to try.
|
|
* Alternate between address families when possible. */
|
|
const struct Curl_addrinfo *addr = NULL;
|
|
int ai_family = 0;
|
|
#ifdef USE_IPV6
|
|
if((bs->last_attempt_ai_family == AF_INET) ||
|
|
!cf_ai_iter_has_more(&bs->addr_iter)) {
|
|
addr = cf_ai_iter_next(&bs->ipv6_iter);
|
|
ai_family = bs->ipv6_iter.ai_family;
|
|
}
|
|
#endif
|
|
if(!addr) {
|
|
addr = cf_ai_iter_next(&bs->addr_iter);
|
|
ai_family = bs->addr_iter.ai_family;
|
|
}
|
|
|
|
if(addr) { /* try another address */
|
|
result = cf_ip_attempt_new(&a, cf, data, addr, ai_family,
|
|
bs->transport, bs->cf_create);
|
|
CURL_TRC_CF(data, cf, "starting %s attempt for ipv%s -> %d",
|
|
bs->running ? "next" : "first",
|
|
(ai_family == AF_INET) ? "4" : "6", result);
|
|
if(result)
|
|
goto out;
|
|
DEBUGASSERT(a);
|
|
|
|
/* append to running list */
|
|
panchor = &bs->running;
|
|
while(*panchor)
|
|
panchor = &((*panchor)->next);
|
|
*panchor = a;
|
|
bs->last_attempt_started = now;
|
|
bs->last_attempt_ai_family = ai_family;
|
|
/* and run everything again */
|
|
goto evaluate;
|
|
}
|
|
else if(inconclusive) {
|
|
/* tried all addresses, no success but some where inconclusive.
|
|
* Let's restart the inconclusive ones. */
|
|
timediff_t since_ms = curlx_timediff_ms(now, bs->last_attempt_started);
|
|
timediff_t delay_ms = bs->attempt_delay_ms - since_ms;
|
|
if(delay_ms <= 0) {
|
|
CURL_TRC_CF(data, cf, "all attempts inconclusive, restarting one");
|
|
i = -1;
|
|
for(a = bs->running; a; a = a->next) {
|
|
++i;
|
|
if(!a->inconclusive)
|
|
continue;
|
|
result = cf_ip_attempt_restart(a, cf, data);
|
|
CURL_TRC_CF(data, cf, "restarted baller %d -> %d", i, result);
|
|
if(result) /* serious failure */
|
|
goto out;
|
|
bs->last_attempt_started = now;
|
|
goto evaluate;
|
|
}
|
|
DEBUGASSERT(0); /* should not come here */
|
|
}
|
|
else {
|
|
/* let's wait some more before restarting */
|
|
infof(data, "connect attempts inconclusive, retrying "
|
|
"in %" FMT_TIMEDIFF_T "ms", delay_ms);
|
|
Curl_expire(data, delay_ms, EXPIRE_HAPPY_EYEBALLS);
|
|
}
|
|
/* attempt timeout for restart has not expired yet */
|
|
goto out;
|
|
}
|
|
else if(!ongoing) {
|
|
/* no more addresses, no inconclusive attempts */
|
|
CURL_TRC_CF(data, cf, "no more attempts to try");
|
|
result = CURLE_COULDNT_CONNECT;
|
|
i = 0;
|
|
for(a = bs->running; a; a = a->next) {
|
|
CURL_TRC_CF(data, cf, "baller %d: result=%d", i, a->result);
|
|
if(a->result)
|
|
result = a->result;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
if(!result) {
|
|
bool more_possible;
|
|
|
|
/* when do we need to be called again? */
|
|
next_expire_ms = Curl_timeleft_ms(data, &now, TRUE);
|
|
if(next_expire_ms <= 0) {
|
|
failf(data, "Connection timeout after %" FMT_OFF_T " ms",
|
|
curlx_timediff_ms(now, data->progress.t_startsingle));
|
|
return CURLE_OPERATION_TIMEDOUT;
|
|
}
|
|
|
|
more_possible = cf_ai_iter_has_more(&bs->addr_iter);
|
|
#ifdef USE_IPV6
|
|
if(!more_possible)
|
|
more_possible = cf_ai_iter_has_more(&bs->ipv6_iter);
|
|
#endif
|
|
if(more_possible) {
|
|
timediff_t expire_ms, elapsed_ms;
|
|
elapsed_ms = curlx_timediff_ms(now, bs->last_attempt_started);
|
|
expire_ms = CURLMAX(bs->attempt_delay_ms - elapsed_ms, 0);
|
|
next_expire_ms = CURLMIN(next_expire_ms, expire_ms);
|
|
if(next_expire_ms <= 0) {
|
|
CURL_TRC_CF(data, cf, "HAPPY_EYBALLS timeout due, re-evaluate");
|
|
goto evaluate;
|
|
}
|
|
CURL_TRC_CF(data, cf, "next HAPPY_EYBALLS timeout in %" FMT_TIMEDIFF_T
|
|
"ms", next_expire_ms);
|
|
Curl_expire(data, next_expire_ms, EXPIRE_HAPPY_EYEBALLS);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_ip_ballers_shutdown(struct cf_ip_ballers *bs,
|
|
struct Curl_easy *data,
|
|
bool *done)
|
|
{
|
|
struct cf_ip_attempt *a;
|
|
|
|
/* shutdown all ballers that have not done so already. If one fails,
|
|
* continue shutting down others until all are shutdown. */
|
|
*done = TRUE;
|
|
for(a = bs->running; a; a = a->next) {
|
|
bool bdone = FALSE;
|
|
if(a->shutdown)
|
|
continue;
|
|
a->result = a->cf->cft->do_shutdown(a->cf, data, &bdone);
|
|
if(a->result || bdone)
|
|
a->shutdown = TRUE; /* treat a failed shutdown as done */
|
|
else
|
|
*done = FALSE;
|
|
}
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode cf_ip_ballers_pollset(struct cf_ip_ballers *bs,
|
|
struct Curl_easy *data,
|
|
struct easy_pollset *ps)
|
|
{
|
|
struct cf_ip_attempt *a;
|
|
CURLcode result = CURLE_OK;
|
|
for(a = bs->running; a && !result; a = a->next) {
|
|
if(a->result)
|
|
continue;
|
|
result = Curl_conn_cf_adjust_pollset(a->cf, data, ps);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static bool cf_ip_ballers_pending(struct cf_ip_ballers *bs,
|
|
const struct Curl_easy *data)
|
|
{
|
|
struct cf_ip_attempt *a;
|
|
|
|
for(a = bs->running; a; a = a->next) {
|
|
if(a->result)
|
|
continue;
|
|
if(a->cf->cft->has_data_pending(a->cf, data))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static struct curltime cf_ip_ballers_max_time(struct cf_ip_ballers *bs,
|
|
struct Curl_easy *data,
|
|
int query)
|
|
{
|
|
struct curltime t, tmax;
|
|
struct cf_ip_attempt *a;
|
|
|
|
memset(&tmax, 0, sizeof(tmax));
|
|
for(a = bs->running; a; a = a->next) {
|
|
memset(&t, 0, sizeof(t));
|
|
if(!a->cf->cft->query(a->cf, data, query, NULL, &t)) {
|
|
if((t.tv_sec || t.tv_usec) && curlx_timediff_us(t, tmax) > 0)
|
|
tmax = t;
|
|
}
|
|
}
|
|
return tmax;
|
|
}
|
|
|
|
static int cf_ip_ballers_min_reply_ms(struct cf_ip_ballers *bs,
|
|
struct Curl_easy *data)
|
|
{
|
|
int reply_ms = -1, breply_ms;
|
|
struct cf_ip_attempt *a;
|
|
|
|
for(a = bs->running; a; a = a->next) {
|
|
if(!a->cf->cft->query(a->cf, data, CF_QUERY_CONNECT_REPLY_MS,
|
|
&breply_ms, NULL)) {
|
|
if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms))
|
|
reply_ms = breply_ms;
|
|
}
|
|
}
|
|
return reply_ms;
|
|
}
|
|
|
|
|
|
typedef enum {
|
|
SCFST_INIT,
|
|
SCFST_WAITING,
|
|
SCFST_DONE
|
|
} cf_connect_state;
|
|
|
|
struct cf_ip_happy_ctx {
|
|
uint8_t transport;
|
|
cf_ip_connect_create *cf_create;
|
|
cf_connect_state state;
|
|
struct cf_ip_ballers ballers;
|
|
struct curltime started;
|
|
};
|
|
|
|
|
|
static CURLcode is_connected(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *connected)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
struct connectdata *conn = cf->conn;
|
|
CURLcode result;
|
|
|
|
result = cf_ip_ballers_run(&ctx->ballers, cf, data, connected);
|
|
|
|
if(!result)
|
|
return CURLE_OK;
|
|
|
|
{
|
|
const char *hostname, *proxy_name = NULL;
|
|
char viamsg[160];
|
|
#ifndef CURL_DISABLE_PROXY
|
|
if(conn->bits.socksproxy)
|
|
proxy_name = conn->socks_proxy.host.name;
|
|
else if(conn->bits.httpproxy)
|
|
proxy_name = conn->http_proxy.host.name;
|
|
#endif
|
|
hostname = conn->bits.conn_to_host ? conn->conn_to_host.name :
|
|
conn->host.name;
|
|
|
|
#ifdef USE_UNIX_SOCKETS
|
|
if(conn->unix_domain_socket)
|
|
curl_msnprintf(viamsg, sizeof(viamsg), "over %s",
|
|
conn->unix_domain_socket);
|
|
else
|
|
#endif
|
|
{
|
|
int port;
|
|
if(cf->sockindex == SECONDARYSOCKET)
|
|
port = conn->secondary_port;
|
|
else if(cf->conn->bits.conn_to_port)
|
|
port = conn->conn_to_port;
|
|
else
|
|
port = conn->remote_port;
|
|
curl_msnprintf(viamsg, sizeof(viamsg), "port %u", port);
|
|
}
|
|
|
|
failf(data, "Failed to connect to %s %s %s%s%safter "
|
|
"%" FMT_TIMEDIFF_T " ms: %s",
|
|
hostname, viamsg,
|
|
proxy_name ? "via " : "",
|
|
proxy_name ? proxy_name : "",
|
|
proxy_name ? " " : "",
|
|
curlx_timediff_ms(curlx_now(), data->progress.t_startsingle),
|
|
curl_easy_strerror(result));
|
|
}
|
|
|
|
#ifdef SOCKETIMEDOUT
|
|
if(SOCKETIMEDOUT == data->state.os_errno)
|
|
result = CURLE_OPERATION_TIMEDOUT;
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Connect to the given host with timeout, proxy or remote does not matter.
|
|
* There might be more than one IP address to try out.
|
|
*/
|
|
static CURLcode start_connect(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
struct Curl_dns_entry *dns = data->state.dns[cf->sockindex];
|
|
|
|
if(!dns)
|
|
return CURLE_FAILED_INIT;
|
|
|
|
if(Curl_timeleft_ms(data, NULL, TRUE) < 0) {
|
|
/* a precaution, no need to continue if time already is up */
|
|
failf(data, "Connection time-out");
|
|
return CURLE_OPERATION_TIMEDOUT;
|
|
}
|
|
|
|
CURL_TRC_CF(data, cf, "init ip ballers for transport %u", ctx->transport);
|
|
ctx->started = curlx_now();
|
|
return cf_ip_ballers_init(&ctx->ballers, cf->conn->ip_version,
|
|
dns->addr, ctx->cf_create, ctx->transport,
|
|
data->set.happy_eyeballs_timeout);
|
|
}
|
|
|
|
static void cf_ip_happy_ctx_clear(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
|
|
DEBUGASSERT(ctx);
|
|
DEBUGASSERT(data);
|
|
cf_ip_ballers_clear(cf, data, &ctx->ballers);
|
|
}
|
|
|
|
static CURLcode cf_ip_happy_shutdown(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *done)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
DEBUGASSERT(data);
|
|
if(cf->connected) {
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
result = cf_ip_ballers_shutdown(&ctx->ballers, data, done);
|
|
CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done);
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_ip_happy_adjust_pollset(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct easy_pollset *ps)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
if(!cf->connected) {
|
|
result = cf_ip_ballers_pollset(&ctx->ballers, data, ps);
|
|
CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %d socks", result, ps->n);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *done)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
if(cf->connected) {
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
DEBUGASSERT(ctx);
|
|
*done = FALSE;
|
|
|
|
switch(ctx->state) {
|
|
case SCFST_INIT:
|
|
DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data));
|
|
DEBUGASSERT(!cf->connected);
|
|
result = start_connect(cf, data);
|
|
if(result)
|
|
return result;
|
|
ctx->state = SCFST_WAITING;
|
|
FALLTHROUGH();
|
|
case SCFST_WAITING:
|
|
result = is_connected(cf, data, done);
|
|
if(!result && *done) {
|
|
DEBUGASSERT(ctx->ballers.winner);
|
|
DEBUGASSERT(ctx->ballers.winner->cf);
|
|
DEBUGASSERT(ctx->ballers.winner->cf->connected);
|
|
/* we have a winner. Install and activate it.
|
|
* close/free all others. */
|
|
ctx->state = SCFST_DONE;
|
|
cf->connected = TRUE;
|
|
cf->next = ctx->ballers.winner->cf;
|
|
ctx->ballers.winner->cf = NULL;
|
|
cf_ip_happy_ctx_clear(cf, data);
|
|
Curl_expire_done(data, EXPIRE_HAPPY_EYEBALLS);
|
|
|
|
if(cf->conn->handler->protocol & PROTO_FAMILY_SSH)
|
|
Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */
|
|
#ifndef CURL_DISABLE_VERBOSE_STRINGS
|
|
if(Curl_trc_cf_is_verbose(cf, data)) {
|
|
struct ip_quadruple ipquad;
|
|
bool is_ipv6;
|
|
if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad)) {
|
|
const char *host;
|
|
int port;
|
|
Curl_conn_get_current_host(data, cf->sockindex, &host, &port);
|
|
CURL_TRC_CF(data, cf, "Connected to %s (%s) port %u",
|
|
host, ipquad.remote_ip, ipquad.remote_port);
|
|
}
|
|
}
|
|
#endif
|
|
data->info.numconnects++; /* to track the # of connections made */
|
|
}
|
|
break;
|
|
case SCFST_DONE:
|
|
*done = TRUE;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void cf_ip_happy_close(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
|
|
CURL_TRC_CF(data, cf, "close");
|
|
cf_ip_happy_ctx_clear(cf, data);
|
|
cf->connected = FALSE;
|
|
ctx->state = SCFST_INIT;
|
|
|
|
if(cf->next) {
|
|
cf->next->cft->do_close(cf->next, data);
|
|
Curl_conn_cf_discard_chain(&cf->next, data);
|
|
}
|
|
}
|
|
|
|
static bool cf_ip_happy_data_pending(struct Curl_cfilter *cf,
|
|
const struct Curl_easy *data)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
|
|
if(!cf->connected) {
|
|
return cf_ip_ballers_pending(&ctx->ballers, data);
|
|
}
|
|
return cf->next->cft->has_data_pending(cf->next, data);
|
|
}
|
|
|
|
static CURLcode cf_ip_happy_query(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
int query, int *pres1, void *pres2)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
|
|
if(!cf->connected) {
|
|
switch(query) {
|
|
case CF_QUERY_CONNECT_REPLY_MS: {
|
|
*pres1 = cf_ip_ballers_min_reply_ms(&ctx->ballers, data);
|
|
CURL_TRC_CF(data, cf, "query connect reply: %dms", *pres1);
|
|
return CURLE_OK;
|
|
}
|
|
case CF_QUERY_TIMER_CONNECT: {
|
|
struct curltime *when = pres2;
|
|
*when = cf_ip_ballers_max_time(&ctx->ballers, data,
|
|
CF_QUERY_TIMER_CONNECT);
|
|
return CURLE_OK;
|
|
}
|
|
case CF_QUERY_TIMER_APPCONNECT: {
|
|
struct curltime *when = pres2;
|
|
*when = cf_ip_ballers_max_time(&ctx->ballers, data,
|
|
CF_QUERY_TIMER_APPCONNECT);
|
|
return CURLE_OK;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return cf->next ?
|
|
cf->next->cft->query(cf->next, data, query, pres1, pres2) :
|
|
CURLE_UNKNOWN_OPTION;
|
|
}
|
|
|
|
static void cf_ip_happy_destroy(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = cf->ctx;
|
|
|
|
CURL_TRC_CF(data, cf, "destroy");
|
|
if(ctx) {
|
|
cf_ip_happy_ctx_clear(cf, data);
|
|
}
|
|
/* release any resources held in state */
|
|
Curl_safefree(ctx);
|
|
}
|
|
|
|
struct Curl_cftype Curl_cft_ip_happy = {
|
|
"HAPPY-EYEBALLS",
|
|
0,
|
|
CURL_LOG_LVL_NONE,
|
|
cf_ip_happy_destroy,
|
|
cf_ip_happy_connect,
|
|
cf_ip_happy_close,
|
|
cf_ip_happy_shutdown,
|
|
cf_ip_happy_adjust_pollset,
|
|
cf_ip_happy_data_pending,
|
|
Curl_cf_def_send,
|
|
Curl_cf_def_recv,
|
|
Curl_cf_def_cntrl,
|
|
Curl_cf_def_conn_is_alive,
|
|
Curl_cf_def_conn_keep_alive,
|
|
cf_ip_happy_query,
|
|
};
|
|
|
|
/**
|
|
* Create an IP happy eyeball connection filter that uses the, once resolved,
|
|
* address information to connect on ip families based on connection
|
|
* configuration.
|
|
* @param pcf output, the created cfilter
|
|
* @param data easy handle used in creation
|
|
* @param conn connection the filter is created for
|
|
* @param cf_create method to create the sub-filters performing the
|
|
* actual connects.
|
|
*/
|
|
static CURLcode cf_ip_happy_create(struct Curl_cfilter **pcf,
|
|
struct Curl_easy *data,
|
|
struct connectdata *conn,
|
|
cf_ip_connect_create *cf_create,
|
|
uint8_t transport)
|
|
{
|
|
struct cf_ip_happy_ctx *ctx = NULL;
|
|
CURLcode result;
|
|
|
|
(void)data;
|
|
(void)conn;
|
|
*pcf = NULL;
|
|
ctx = curlx_calloc(1, sizeof(*ctx));
|
|
if(!ctx) {
|
|
result = CURLE_OUT_OF_MEMORY;
|
|
goto out;
|
|
}
|
|
ctx->transport = transport;
|
|
ctx->cf_create = cf_create;
|
|
|
|
result = Curl_cf_create(pcf, &Curl_cft_ip_happy, ctx);
|
|
|
|
out:
|
|
if(result) {
|
|
Curl_safefree(*pcf);
|
|
curlx_free(ctx);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at,
|
|
struct Curl_easy *data,
|
|
uint8_t transport)
|
|
{
|
|
cf_ip_connect_create *cf_create;
|
|
struct Curl_cfilter *cf;
|
|
CURLcode result;
|
|
|
|
/* Need to be first */
|
|
DEBUGASSERT(cf_at);
|
|
cf_create = get_cf_create(transport);
|
|
if(!cf_create) {
|
|
CURL_TRC_CF(data, cf_at, "unsupported transport type %u", transport);
|
|
return CURLE_UNSUPPORTED_PROTOCOL;
|
|
}
|
|
result = cf_ip_happy_create(&cf, data, cf_at->conn, cf_create, transport);
|
|
if(result)
|
|
return result;
|
|
|
|
Curl_conn_cf_insert_after(cf_at, cf);
|
|
return CURLE_OK;
|
|
}
|