curl/lib/cf-ip-happy.c
Stefan Eissing 92db819714
cf-dns: pass peer for result lookups
The DNS filter knows the peer it resolves and the code parts that want
the results know the peer as well. Pass it to lookup methods to make
sure results match.

Background: when tunneling, the resolved peer is not always the one that
other filters are looking for. Especially when HTTPS-RR results are
accessed in TLS filters, those will differ.

This prevents a HTTPS-RR for a proxy to be used for the origin when ECH
is activated. To make ECH work through a tunnel, we need to start an
additional resolve. Something to be fixed after 8.21.

Closes #22042
2026-06-16 23:15:43 +02:00

1054 lines
31 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_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-dns.h"
#include "cf-ip-happy.h"
#include "curl_addrinfo.h"
#include "curl_trc.h"
#include "multiif.h"
#include "progress.h"
#include "select.h"
#include "sockaddr.h"
#include "vquic/vquic.h" /* for quic cfilters */
struct transport_provider {
cf_ip_connect_create *cf_create;
uint8_t transport;
bool tunnel;
};
static
#ifndef UNITTESTS
const
#endif
struct transport_provider transport_providers[] = {
{ Curl_cf_tcp_create, TRNSPRT_TCP, FALSE },
{ Curl_cf_tcp_create, TRNSPRT_TCP, TRUE },
#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3)
{ Curl_cf_quic_create, TRNSPRT_QUIC, FALSE },
#endif
#if !defined(CURL_DISABLE_HTTP) && defined(USE_PROXY_HTTP3)
{ Curl_cf_h3_proxy_create, TRNSPRT_QUIC, TRUE },
#endif
#ifndef CURL_DISABLE_TFTP
{ Curl_cf_udp_create, TRNSPRT_UDP, FALSE },
#endif
#ifdef USE_UNIX_SOCKETS
{ Curl_cf_unix_create, TRNSPRT_UNIX, FALSE },
{ Curl_cf_unix_create, TRNSPRT_UNIX, TRUE },
#endif
};
static cf_ip_connect_create *get_cf_create(uint8_t transport,
bool tunnel)
{
size_t i;
for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) {
if((transport == transport_providers[i].transport) &&
(tunnel == transport_providers[i].tunnel))
return transport_providers[i].cf_create;
}
return NULL;
}
#ifdef UNITTESTS
/* @unittest 2600 */
UNITTEST void debug_set_transport_provider(
uint8_t transport, cf_ip_connect_create *cf_create);
UNITTEST void 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;
}
}
}
#endif /* UNITTESTS */
struct cf_ai_iter {
struct Curl_cfilter *cf;
struct Curl_peer *peer;
int ai_family;
unsigned int n;
};
static void cf_ai_iter_init(struct cf_ai_iter *iter,
struct Curl_cfilter *cf,
struct Curl_peer *peer,
int ai_family)
{
iter->cf = cf;
iter->peer = peer; /* not linked, ctx->ballers owns and has same lifetime */
iter->ai_family = ai_family;
iter->n = 0;
}
static const struct Curl_addrinfo *cf_ai_iter_next(struct cf_ai_iter *iter,
struct Curl_easy *data)
{
const struct Curl_addrinfo *addr;
if(!iter->cf)
return NULL;
addr = Curl_conn_dns_get_ai(data, iter->peer, iter->cf->sockindex,
iter->ai_family, iter->n);
if(addr)
iter->n++;
return addr;
}
static bool cf_ai_iter_has_more(struct cf_ai_iter *iter,
struct Curl_easy *data)
{
return (iter->cf &&
!!Curl_conn_dns_get_ai(data, iter->peer, iter->cf->sockindex,
iter->ai_family, iter->n));
}
struct cf_ip_attempt {
struct cf_ip_attempt *next;
struct Curl_peer *origin;
struct Curl_peer *peer;
struct Curl_peer *tunnel_peer;
struct Curl_sockaddr_ex addr;
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_peer;
uint8_t tunnel_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);
Curl_peer_unlink(&a->origin);
Curl_peer_unlink(&a->peer);
Curl_peer_unlink(&a->tunnel_peer);
curlx_free(a);
}
}
static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa,
struct Curl_easy *data,
struct Curl_cfilter *cf,
struct Curl_peer *origin,
struct Curl_peer *peer,
uint8_t transport_peer,
struct Curl_sockaddr_ex *addr,
int ai_family,
struct Curl_peer *tunnel_peer,
uint8_t tunnel_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;
Curl_peer_link(&a->origin, origin);
Curl_peer_link(&a->peer, peer);
a->transport_peer = transport_peer;
Curl_peer_link(&a->tunnel_peer, tunnel_peer);
a->tunnel_transport = tunnel_transport;
a->addr = *addr;
a->ai_family = ai_family;
a->result = CURLE_OK;
a->cf_create = cf_create;
*pa = a;
result = a->cf_create(&a->cf, data, a->origin, a->peer, a->transport_peer,
cf->conn, &a->addr, a->tunnel_peer,
a->tunnel_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 = (bool)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;
if(a->cf)
Curl_conn_cf_discard_chain(&a->cf, data);
}
}
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
struct Curl_peer *origin;
struct Curl_peer *peer;
struct Curl_peer *tunnel_peer;
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;
uint32_t max_concurrent;
uint8_t transport_peer;
uint8_t tunnel_transport;
};
static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a,
struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct Curl_cfilter *wcf;
CURLcode result;
if(a->cf)
Curl_conn_cf_discard_chain(&a->cf, data);
a->result = CURLE_OK;
a->connected = FALSE;
a->inconclusive = FALSE;
a->cf = NULL;
result = a->cf_create(&a->cf, data, a->origin, a->peer, a->transport_peer,
cf->conn, &a->addr,
a->tunnel_peer, a->tunnel_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);
}
return result;
}
static void cf_ip_ballers_clear(struct Curl_easy *data,
struct cf_ip_ballers *bs)
{
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;
Curl_peer_unlink(&bs->origin);
Curl_peer_unlink(&bs->peer);
Curl_peer_unlink(&bs->tunnel_peer);
}
static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs,
struct Curl_easy *data,
struct Curl_peer *origin,
struct Curl_peer *peer,
uint8_t transport_peer,
struct Curl_peer *tunnel_peer,
uint8_t tunnel_transport,
timediff_t attempt_delay_ms,
uint32_t max_concurrent)
{
memset(bs, 0, sizeof(*bs));
bs->cf_create = get_cf_create(transport_peer, !!tunnel_peer);
if(!bs->cf_create) {
failf(data, "unsupported transport type %u%s",
transport_peer, tunnel_peer ? " to proxy" : "");
return CURLE_UNSUPPORTED_PROTOCOL;
}
Curl_peer_link(&bs->origin, origin);
Curl_peer_link(&bs->peer, peer);
bs->transport_peer = transport_peer;
Curl_peer_link(&bs->tunnel_peer, tunnel_peer);
bs->tunnel_transport = tunnel_transport;
bs->attempt_delay_ms = attempt_delay_ms;
bs->max_concurrent = max_concurrent;
bs->last_attempt_ai_family = AF_INET; /* so AF_INET6 is next */
return CURLE_OK;
}
static void cf_ip_ballers_prune(struct cf_ip_ballers *bs,
struct Curl_cfilter *cf,
struct Curl_easy *data,
uint32_t max_concurrent)
{
struct cf_ip_attempt *a = NULL, **panchor;
uint32_t ongoing = 0;
for(a = bs->running; a; a = a->next) {
if(!a->result && !a->connected)
++ongoing;
}
panchor = &bs->running;
while(*panchor && (ongoing > max_concurrent)) {
a = *panchor;
if(!a->result && !a->connected) {
*panchor = a->next;
a->next = NULL;
cf_ip_attempt_free(a, data);
--ongoing;
CURL_TRC_CF(data, cf, "discarding oldest attempt to keep limit");
}
else {
panchor = &a->next;
}
}
}
static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs,
struct Curl_cfilter *cf,
struct Curl_easy *data,
bool dns_resolved,
bool *connected)
{
CURLcode result = CURLE_OK;
struct cf_ip_attempt *a = NULL, **panchor;
bool do_more;
timediff_t next_expire_ms;
uint32_t inconclusive, ongoing;
VERBOSE(int i);
if(bs->winner)
return CURLE_OK;
evaluate:
ongoing = inconclusive = 0;
/* check if a running baller connects now */
VERBOSE(i = -1);
for(panchor = &bs->running; *panchor; panchor = &((*panchor)->next)) {
VERBOSE(++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: "
"%u ongoing, %u inconclusive", ongoing, inconclusive);
/* no attempt connected yet, start another one? */
if(!ongoing) {
if(!bs->started.tv_sec && !bs->started.tv_usec)
bs->started = *Curl_pgrs_now(data);
do_more = TRUE;
}
else {
bool more_possible = cf_ai_iter_has_more(&bs->addr_iter, data);
#ifdef USE_IPV6
if(!more_possible)
more_possible = cf_ai_iter_has_more(&bs->ipv6_iter, data);
#endif
do_more = more_possible &&
(curlx_ptimediff_ms(Curl_pgrs_now(data), &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 *ai = NULL;
int ai_family = 0;
CURL_TRC_CF(data, cf, "want to do more");
#ifdef USE_IPV6
if((bs->last_attempt_ai_family == AF_INET) ||
!cf_ai_iter_has_more(&bs->addr_iter, data)) {
ai = cf_ai_iter_next(&bs->ipv6_iter, data);
ai_family = bs->ipv6_iter.ai_family;
CURL_TRC_CF(data, cf, "check for next AAAA address: %s",
ai ? "found" : "none");
}
#endif
if(!ai) {
ai = cf_ai_iter_next(&bs->addr_iter, data);
ai_family = bs->addr_iter.ai_family;
CURL_TRC_CF(data, cf, "check for next A address: %s",
ai ? "found" : "none");
}
/* We are (re-)starting attempts. We are not interested in
* keeping old failure information. The new attempt will either
* succeed or persist new failure. */
Curl_reset_fail(data);
if(ai) { /* try another address */
struct Curl_sockaddr_ex addr;
/* Discard oldest to make room for new attempt */
if(bs->max_concurrent)
cf_ip_ballers_prune(bs, cf, data, bs->max_concurrent - 1);
result = Curl_socket_addr_from_ai(&addr, ai, bs->transport_peer);
if(result)
goto out;
result = cf_ip_attempt_new(&a, data, cf, bs->origin, bs->peer,
bs->transport_peer, &addr, ai_family,
bs->tunnel_peer, bs->tunnel_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", (int)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 = *Curl_pgrs_now(data);
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_ptimediff_ms(Curl_pgrs_now(data), &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");
VERBOSE(i = -1);
for(a = bs->running; a; a = a->next) {
VERBOSE(++i);
if(!a->inconclusive)
continue;
result = cf_ip_attempt_restart(a, cf, data);
CURL_TRC_CF(data, cf, "restarted baller %d -> %d", i, (int)result);
if(result) /* serious failure */
goto out;
bs->last_attempt_started = *Curl_pgrs_now(data);
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 && dns_resolved) {
/* no more addresses, no inconclusive attempts */
CURL_TRC_CF(data, cf, "no more attempts to try");
result = CURLE_COULDNT_CONNECT;
VERBOSE(i = 0);
for(a = bs->running; a; a = a->next) {
CURL_TRC_CF(data, cf, "baller %d: result=%d", i, (int)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);
if(next_expire_ms < 0) {
failf(data, "Connection timeout after %" FMT_OFF_T " ms",
curlx_ptimediff_ms(Curl_pgrs_now(data),
&data->progress.t_startsingle));
return CURLE_OPERATION_TIMEDOUT;
}
more_possible = cf_ai_iter_has_more(&bs->addr_iter, data);
#ifdef USE_IPV6
if(!more_possible)
more_possible = cf_ai_iter_has_more(&bs->ipv6_iter, data);
#endif
if(more_possible) {
timediff_t expire_ms, elapsed_ms;
elapsed_ms =
curlx_ptimediff_ms(Curl_pgrs_now(data), &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_EYEBALLS timeout due, re-evaluate");
goto evaluate;
}
CURL_TRC_CF(data, cf, "next HAPPY_EYEBALLS 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 || !a->cf)
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 && 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 && !a->cf->cft->query(a->cf, data, query, NULL, &t)) {
if((t.tv_sec || t.tv_usec) && curlx_ptimediff_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 && !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 {
cf_ip_connect_create *cf_create;
cf_connect_state state;
struct cf_ip_ballers ballers;
struct curltime started;
BIT(dns_resolved);
};
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,
(bool)ctx->dns_resolved, connected);
if(!result)
return CURLE_OK;
else {
struct Curl_peer *peer = NULL, *proxy_peer = NULL;
char viamsg[160];
peer = Curl_conn_get_first_peer(conn, cf->sockindex);
if(!conn->origin || !peer)
return CURLE_FAILED_INIT;
#ifndef CURL_DISABLE_PROXY
if(conn->socks_proxy.peer)
proxy_peer = conn->socks_proxy.peer;
else if(conn->http_proxy.peer)
proxy_peer = conn->http_proxy.peer;
#endif
viamsg[0] = 0;
if(!Curl_peer_equal(peer, conn->origin) &&
!Curl_peer_equal(peer, proxy_peer)) {
#ifdef USE_UNIX_SOCKETS
if(peer->unix_socket)
curl_msnprintf(viamsg, sizeof(viamsg), " over unix://%s",
peer->hostname);
else
#endif
curl_msnprintf(viamsg, sizeof(viamsg), " via %s:%u",
peer->hostname, peer->port);
}
failf(data, "Failed to connect to %s:%u%s %s%s%safter "
"%" FMT_TIMEDIFF_T " ms: %s",
conn->origin->hostname, conn->origin->port, viamsg,
proxy_peer ? "over proxy " : "",
proxy_peer ? proxy_peer->hostname : "",
proxy_peer ? " " : "",
curlx_ptimediff_ms(Curl_pgrs_now(data),
&data->progress.t_startsingle),
curl_easy_strerror(result));
#ifdef SOCKETIMEDOUT
if(SOCKETIMEDOUT == data->state.os_errno)
result = CURLE_OPERATION_TIMEDOUT;
#endif
return result;
}
}
#define IP_HE_MAX_CONCURRENT_ATTEMPTS 6
static CURLcode cf_ip_happy_init(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_ip_happy_ctx *ctx = cf->ctx;
if(Curl_timeleft_ms(data) < 0) {
/* a precaution, no need to continue if time already is up */
failf(data, "Connection time-out");
return CURLE_OPERATION_TIMEDOUT;
}
if(ctx->ballers.transport_peer == TRNSPRT_UNIX) {
#ifdef USE_UNIX_SOCKETS
cf_ai_iter_init(&ctx->ballers.addr_iter, cf, ctx->ballers.peer, AF_UNIX);
#else
return CURLE_UNSUPPORTED_PROTOCOL;
#endif
}
else { /* TCP/UDP/QUIC */
#ifdef USE_IPV6
cf_ai_iter_init(&ctx->ballers.ipv6_iter, cf, ctx->ballers.peer, AF_INET6);
#endif
cf_ai_iter_init(&ctx->ballers.addr_iter, cf, ctx->ballers.peer, AF_INET);
}
CURL_TRC_CF(data, cf, "init ip ballers for transport %u",
ctx->ballers.transport_peer);
ctx->started = *Curl_pgrs_now(data);
return CURLE_OK;
}
static void cf_ip_happy_ctx_clear(struct cf_ip_happy_ctx *ctx,
struct Curl_easy *data)
{
DEBUGASSERT(ctx);
if(ctx)
cf_ip_ballers_clear(data, &ctx->ballers);
}
static void cf_ip_happy_ctx_destroy(struct cf_ip_happy_ctx *ctx,
struct Curl_easy *data)
{
if(ctx) {
cf_ip_happy_ctx_clear(ctx, data);
curlx_free(ctx);
}
}
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", (int)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, %u socks", (int)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;
/* -Werror=null-dereference finds false positives suddenly. */
if(!data)
return CURLE_FAILED_INIT;
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
DEBUGASSERT(ctx);
*done = FALSE;
if(!ctx->dns_resolved) {
result = Curl_conn_dns_result(cf->conn, cf->sockindex, ctx->ballers.peer);
if(!result)
ctx->dns_resolved = TRUE;
else if(result == CURLE_AGAIN) {
result = CURLE_OK;
}
else /* real error */
goto out;
}
switch(ctx->state) {
case SCFST_INIT:
DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data));
DEBUGASSERT(!cf->connected);
result = cf_ip_happy_init(cf, data);
if(result)
goto out;
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(ctx, data);
Curl_expire_done(data, EXPIRE_HAPPY_EYEBALLS);
/* whatever errors were reported by ballers, clear our errorbuf */
Curl_reset_fail(data);
if(cf->conn->scheme->protocol & PROTO_FAMILY_SSH)
Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */
#ifdef CURLVERBOSE
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;
Curl_conn_get_current_host(data, cf->sockindex, &host, NULL);
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;
}
out:
return result;
}
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(ctx, data);
cf_ip_happy_ctx_destroy(ctx, data);
}
}
struct Curl_cftype Curl_cft_ip_happy = {
"HAPPY-EYEBALLS",
CF_TYPE_SETUP,
CURL_LOG_LVL_NONE,
cf_ip_happy_destroy,
cf_ip_happy_connect,
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,
};
static CURLcode cf_ip_happy_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct Curl_peer *origin,
struct Curl_peer *peer,
uint8_t transport_peer,
struct connectdata *conn,
struct Curl_peer *tunnel_peer,
uint8_t tunnel_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;
}
result = cf_ip_ballers_init(&ctx->ballers, data,
origin, peer, transport_peer,
tunnel_peer, tunnel_transport,
data->set.happy_eyeballs_timeout,
IP_HE_MAX_CONCURRENT_ATTEMPTS);
if(result)
goto out;
result = Curl_cf_create(pcf, &Curl_cft_ip_happy, ctx);
out:
if(result) {
curlx_safefree(*pcf);
cf_ip_happy_ctx_destroy(ctx, data);
}
return result;
}
CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at,
struct Curl_easy *data,
struct Curl_peer *origin,
struct Curl_peer *peer,
uint8_t transport_peer,
struct Curl_peer *tunnel_peer,
uint8_t tunnel_transport)
{
struct Curl_cfilter *cf;
CURLcode result;
/* Need to be first */
DEBUGASSERT(cf_at);
result = cf_ip_happy_create(&cf, data, origin, peer, transport_peer,
cf_at->conn, tunnel_peer, tunnel_transport);
if(result)
return result;
Curl_conn_cf_insert_after(cf_at, cf);
return CURLE_OK;
}