dns: https-eyeballing async

Make cf-https-connect work async correctly:
- only start first baller when at least one A/AAAA address
  is available
- select first connect attempt after that with HTTPS-RR info
  there or not.
- select second connect attempt only when HTTPS-RR is resolved
  (may have resolved to "not known") and select possible ALPN
  from things known by then. May not select any second attempt
  when first already covers everything.

This means when the HTTPS-RR is known at/before the first address
is resolved, everything behaves as before. When the HTTPS-RR is
late, a first connection attempt will have been started. Any
ALPN preference from the HTTPS-RR that is not already ongoing will
then start the second attempt.

For HTTPS-RRs that recommend 2 or more ALPNs, the first will always
be attempted: either it is already ongong or it will be the ALPN
for the second attempt. The 2nd ALPN recommendation from HTTPS-RR
*may* be honored or not, depending on what is already selected.

The difference in behaviour between early/late HTTPS-RR resolve
cannot be helped - unless we do not perform any attempts before
it arrives. Trade offs.

Closes #21267
This commit is contained in:
Stefan Eissing 2026-04-08 11:45:30 +02:00 committed by Daniel Stenberg
parent 17e8200733
commit 567803db79
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
4 changed files with 284 additions and 238 deletions

View File

@ -509,6 +509,30 @@ cf_dns_get_nth_ai(const struct Curl_addrinfo *ai,
return NULL;
}
bool Curl_conn_dns_has_any_ai(struct Curl_easy *data, int sockindex)
{
struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
(void)data;
for(; cf; cf = cf->next) {
if(cf->cft == &Curl_cft_dns) {
struct cf_dns_ctx *ctx = cf->ctx;
if(ctx->resolv_result)
return FALSE;
else if(ctx->dns)
return !!ctx->dns->addr;
else
#ifdef USE_IPV6
return Curl_resolv_get_ai(data, ctx->resolv_id, AF_INET, 0) ||
Curl_resolv_get_ai(data, ctx->resolv_id, AF_INET6, 0);
#else
return !!Curl_resolv_get_ai(data, ctx->resolv_id, AF_INET, 0);
#endif
}
}
return FALSE;
}
/* Return the addrinfo at `index` for the given `family` from the
* first "resolve" filter underneath `cf`. If the DNS resolving is
* not done yet or if no address for the family exists, returns NULL.

View File

@ -47,6 +47,9 @@ CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at,
CURLcode Curl_conn_dns_result(struct connectdata *conn, int sockindex);
CURLcode Curl_cf_dns_result(struct Curl_cfilter *cf);
/* Returns TRUE if any addressinfo is available via
* `Curl_conn_dns_get_ai()`. */
bool Curl_conn_dns_has_any_ai(struct Curl_easy *data, int sockindex);
const struct Curl_addrinfo *
Curl_conn_dns_get_ai(struct Curl_easy *data,

View File

@ -58,19 +58,17 @@ struct cf_hc_baller {
BIT(shutdown);
};
static void cf_hc_baller_reset(struct cf_hc_baller *b,
struct Curl_easy *data)
static void cf_hc_baller_discard(struct cf_hc_baller *b,
struct Curl_easy *data)
{
if(b->cf) {
Curl_conn_cf_close(b->cf, data);
Curl_conn_cf_discard_chain(&b->cf, data);
b->cf = NULL;
}
b->result = CURLE_OK;
b->reply_ms = -1;
}
static bool cf_hc_baller_is_active(struct cf_hc_baller *b)
static bool cf_hc_baller_is_connecting(struct cf_hc_baller *b)
{
return b->cf && !b->result;
}
@ -114,24 +112,24 @@ struct cf_hc_ctx {
cf_hc_state state;
struct curltime started; /* when connect started */
CURLcode result; /* overall result */
CURLcode check_h3_result;
struct cf_hc_baller ballers[2];
size_t baller_count;
timediff_t soft_eyeballs_timeout_ms;
timediff_t hard_eyeballs_timeout_ms;
uint8_t def_transport;
BIT(httpsrr_resolved);
BIT(checked_h3);
BIT(ballers_complete);
};
static void cf_hc_ctx_reset(struct Curl_easy *data,
static void cf_hc_ctx_close(struct Curl_easy *data,
struct cf_hc_ctx *ctx)
{
if(ctx) {
size_t i;
for(i = 0; i < ctx->baller_count; ++i)
cf_hc_baller_reset(&ctx->ballers[i], data);
ctx->state = CF_HC_RESOLV;
ctx->result = CURLE_OK;
ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout;
ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 4;
cf_hc_baller_discard(&ctx->ballers[i], data);
}
}
@ -139,7 +137,7 @@ static void cf_hc_ctx_destroy(struct Curl_easy *data,
struct cf_hc_ctx *ctx)
{
if(ctx) {
cf_hc_ctx_reset(data, ctx);
cf_hc_ctx_close(data, ctx);
curlx_free(ctx);
}
}
@ -150,6 +148,10 @@ static void cf_hc_baller_assign(struct cf_hc_baller *b,
{
b->alpn_id = alpn_id;
b->transport = def_transport;
b->cf = NULL;
b->result = CURLE_OK;
b->reply_ms = -1;
b->shutdown = FALSE;
switch(b->alpn_id) {
case ALPN_h3:
b->name = "h3";
@ -172,24 +174,14 @@ static void cf_hc_baller_assign(struct cf_hc_baller *b,
static void cf_hc_baller_init(struct cf_hc_baller *b,
struct Curl_cfilter *cf,
struct Curl_easy *data,
uint8_t transport)
struct Curl_easy *data)
{
struct Curl_cfilter *save = cf->next;
cf->next = NULL;
b->started = *Curl_pgrs_now(data);
switch(b->alpn_id) {
case ALPN_h3:
transport = TRNSPRT_QUIC;
break;
default:
break;
}
if(!b->result)
b->result = Curl_cf_setup_insert_after(cf, data, transport,
CURL_CF_SSL_ENABLE);
b->result = Curl_cf_setup_insert_after(cf, data, b->transport,
CURL_CF_SSL_ENABLE);
b->cf = cf->next;
cf->next = save;
}
@ -213,39 +205,34 @@ static CURLcode baller_connected(struct Curl_cfilter *cf,
struct cf_hc_baller *winner)
{
struct cf_hc_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
int reply_ms;
size_t i;
DEBUGASSERT(winner->cf);
for(i = 0; i < ctx->baller_count; ++i)
if(winner != &ctx->ballers[i])
cf_hc_baller_reset(&ctx->ballers[i], data);
/* Make the winner's connection filter out own sub-filter, check, move,
* close all remaining. */
if(cf->next) {
DEBUGASSERT(0);
return CURLE_FAILED_INIT;
}
if(!winner->cf) {
DEBUGASSERT(0);
return CURLE_FAILED_INIT;
}
reply_ms = cf_hc_baller_reply_ms(winner, data);
if(reply_ms >= 0)
CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms",
winner->name,
(int)curlx_ptimediff_ms(Curl_pgrs_now(data),
&winner->started), reply_ms);
else
CURL_TRC_CF(data, cf, "deferred handshake %s: %dms",
winner->name, (int)curlx_ptimediff_ms(Curl_pgrs_now(data),
&winner->started));
/* install the winning filter below this one. */
cf->next = winner->cf;
winner->cf = NULL;
/* whatever errors where reported by ballers, clear our errorbuf */
ctx->state = CF_HC_SUCCESS;
cf->connected = TRUE;
cf_hc_ctx_close(data, ctx);
/* ballers may have failf()'d, the winner resets it, so our
* errorbuf is clean again. */
Curl_reset_fail(data);
#ifdef USE_NGHTTP2
{
/* Using nghttp2, we add the filter "below" us, so when the conn
* closes, we tear it down for a fresh reconnect */
/* For a negotiated HTTP/2 connection insert the h2 filter. */
const char *alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data);
if(alpn && !strcmp("h2", alpn)) {
result = Curl_http2_switch_at(cf, data);
CURLcode result = Curl_http2_switch_at(cf, data);
if(result) {
ctx->state = CF_HC_FAILURE;
ctx->result = result;
@ -254,198 +241,235 @@ static CURLcode baller_connected(struct Curl_cfilter *cf,
}
}
#endif
ctx->state = CF_HC_SUCCESS;
cf->connected = TRUE;
return result;
return CURLE_OK;
}
static bool time_to_start_next(struct Curl_cfilter *cf,
struct Curl_easy *data,
size_t idx, struct curltime now)
static bool time_to_start_baller2(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_hc_ctx *ctx = cf->ctx;
timediff_t elapsed_ms;
size_t i;
if(idx >= ctx->baller_count)
if(ctx->baller_count < 2)
return FALSE;
if(cf_hc_baller_has_started(&ctx->ballers[idx]))
else if(cf_hc_baller_has_started(&ctx->ballers[1]))
return FALSE;
for(i = 0; i < idx; i++) {
if(!ctx->ballers[i].result)
break;
}
if(i == idx) {
CURL_TRC_CF(data, cf, "all previous attempts failed, starting %s",
ctx->ballers[idx].name);
else if(ctx->ballers[0].result) {
CURL_TRC_CF(data, cf, "%s baller failed, starting %s",
ctx->ballers[0].name, ctx->ballers[1].name);
return TRUE;
}
elapsed_ms = curlx_ptimediff_ms(&now, &ctx->started);
elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->started);
if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) {
CURL_TRC_CF(data, cf, "hard timeout of %" FMT_TIMEDIFF_T "ms reached, "
"starting %s",
ctx->hard_eyeballs_timeout_ms, ctx->ballers[idx].name);
CURL_TRC_CF(data, cf, "%s inconclusive after %" FMT_TIMEDIFF_T ", "
"starting %s", ctx->ballers[0].name,
ctx->hard_eyeballs_timeout_ms, ctx->ballers[1].name);
return TRUE;
}
if((idx > 0) && (elapsed_ms >= ctx->soft_eyeballs_timeout_ms)) {
if(cf_hc_baller_reply_ms(&ctx->ballers[idx - 1], data) < 0) {
CURL_TRC_CF(data, cf, "soft timeout of %" FMT_TIMEDIFF_T "ms reached, "
"%s has not seen any data, starting %s",
ctx->soft_eyeballs_timeout_ms,
ctx->ballers[idx - 1].name, ctx->ballers[idx].name);
else if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) {
if(cf_hc_baller_reply_ms(&ctx->ballers[0], data) < 0) {
CURL_TRC_CF(data, cf, "%s has not seen any data after %"
FMT_TIMEDIFF_T "ms, starting %s",
ctx->ballers[0].name, ctx->soft_eyeballs_timeout_ms,
ctx->ballers[1].name);
return TRUE;
}
/* set the effective hard timeout again */
Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms,
EXPIRE_ALPN_EYEBALLS);
}
return FALSE;
}
static bool cf_https_alpns_contain(enum alpnid id,
enum alpnid *list, size_t len)
{
size_t i;
for(i = 0; i < len; ++i) {
if(id == list[i])
return TRUE;
}
return FALSE;
}
static CURLcode cf_hc_resolv(struct Curl_cfilter *cf,
struct Curl_easy *data)
static bool cf_hc_may_h3(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_hc_ctx *ctx = cf->ctx;
enum alpnid alpn_ids[2];
size_t alpn_count = 0, i;
CURLcode result = CURLE_OK;
if(!ctx->checked_h3) {
ctx->check_h3_result =
Curl_conn_may_http3(data, cf->conn, ctx->def_transport);
ctx->checked_h3 = TRUE;
}
return !ctx->check_h3_result;
}
if(cf->conn->bits.tls_enable_alpn) {
static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf,
struct Curl_easy *data,
enum alpnid not_this_one)
{
#ifdef USE_HTTPSRR
/* Is there an HTTPSRR use its ALPNs here.
* We are here after having selected a connection to a host+port and
* can no longer change that. Any HTTPSRR advice for other hosts and ports
* we need to ignore. */
const struct Curl_https_rrinfo *rr;
/* Is there an HTTPSRR use its ALPNs here.
* We are here after having selected a connection to a host+port and
* can no longer change that. Any HTTPSRR advice for other hosts and ports
* we need to ignore. */
const struct Curl_https_rrinfo *rr;
size_t i;
/* Do we have HTTPS-RR information? */
rr = Curl_conn_dns_get_https(data, cf->sockindex);
/* Do we have HTTPS-RR information? */
rr = Curl_conn_dns_get_https(data, cf->sockindex);
if(rr && !rr->no_def_alpn && /* ALPNs are defaults */
(!rr->target || /* for same host */
!rr->target[0] ||
(rr->target[0] == '.' &&
!rr->target[1])) &&
(!rr->port_set || /* for same port */
rr->port == cf->conn->remote_port)) {
for(i = 0; i < CURL_ARRAYSIZE(rr->alpns) &&
alpn_count < CURL_ARRAYSIZE(alpn_ids); ++i) {
enum alpnid alpn = (enum alpnid)rr->alpns[i];
if(cf_https_alpns_contain(alpn, alpn_ids, alpn_count))
continue;
switch(alpn) {
case ALPN_h3:
if(Curl_conn_may_http3(data, cf->conn, ctx->def_transport))
break; /* not possible */
if(data->state.http_neg.allowed & CURL_HTTP_V3x) {
CURL_TRC_CF(data, cf, "adding h3 via HTTPS-RR");
alpn_ids[alpn_count++] = alpn;
}
break;
case ALPN_h2:
if(data->state.http_neg.allowed & CURL_HTTP_V2x) {
CURL_TRC_CF(data, cf, "adding h2 via HTTPS-RR");
alpn_ids[alpn_count++] = alpn;
}
break;
case ALPN_h1:
if(data->state.http_neg.allowed & CURL_HTTP_V1x) {
CURL_TRC_CF(data, cf, "adding h1 via HTTPS-RR");
alpn_ids[alpn_count++] = alpn;
}
break;
default: /* ignore */
break;
if(rr && !rr->no_def_alpn && /* ALPNs are defaults */
(!rr->target || /* for same host */
!rr->target[0] ||
(rr->target[0] == '.' &&
!rr->target[1])) &&
(!rr->port_set || /* for same port */
rr->port == cf->conn->remote_port)) {
for(i = 0; i < CURL_ARRAYSIZE(rr->alpns); ++i) {
enum alpnid alpn_rr = (enum alpnid)rr->alpns[i];
if(alpn_rr == not_this_one) /* don't want this one */
continue;
switch(alpn_rr) {
case ALPN_h3:
if((data->state.http_neg.allowed & CURL_HTTP_V3x) &&
cf_hc_may_h3(cf, data)) {
return alpn_rr;
}
break;
case ALPN_h2:
if(data->state.http_neg.allowed & CURL_HTTP_V2x) {
return alpn_rr;
}
break;
case ALPN_h1:
if(data->state.http_neg.allowed & CURL_HTTP_V1x) {
return alpn_rr;
}
break;
default: /* ignore */
break;
}
}
}
#else
(void)cf;
(void)data;
(void)not_this_one;
#endif
return ALPN_none;
}
/* Add preferred HTTP version ALPN first */
if(data->state.http_neg.preferred &&
(alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&
(data->state.http_neg.preferred & data->state.http_neg.allowed)) {
enum alpnid alpn_pref = ALPN_none;
switch(data->state.http_neg.preferred) {
case CURL_HTTP_V3x:
if(!Curl_conn_may_http3(data, cf->conn, ctx->def_transport))
alpn_pref = ALPN_h3;
break;
case CURL_HTTP_V2x:
alpn_pref = ALPN_h2;
break;
case CURL_HTTP_V1x:
alpn_pref = ALPN_h1;
break;
default:
break;
}
if(alpn_pref &&
!cf_https_alpns_contain(alpn_pref, alpn_ids, alpn_count)) {
alpn_ids[alpn_count++] = alpn_pref;
}
}
if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&
(data->state.http_neg.wanted & CURL_HTTP_V3x) &&
!cf_https_alpns_contain(ALPN_h3, alpn_ids, alpn_count)) {
result = Curl_conn_may_http3(data, cf->conn, ctx->def_transport);
if(!result) {
CURL_TRC_CF(data, cf, "adding wanted h3");
alpn_ids[alpn_count++] = ALPN_h3;
}
else if(data->state.http_neg.wanted == CURL_HTTP_V3x)
goto out; /* only h3 allowed, not possible, error out */
}
if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&
(data->state.http_neg.wanted & CURL_HTTP_V2x) &&
!cf_https_alpns_contain(ALPN_h2, alpn_ids, alpn_count)) {
CURL_TRC_CF(data, cf, "adding wanted h2");
alpn_ids[alpn_count++] = ALPN_h2;
}
else if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&
(data->state.http_neg.wanted & CURL_HTTP_V1x) &&
!cf_https_alpns_contain(ALPN_h1, alpn_ids, alpn_count)) {
CURL_TRC_CF(data, cf, "adding wanted h1");
alpn_ids[alpn_count++] = ALPN_h1;
static enum alpnid cf_hc_get_pref_alpn(struct Curl_cfilter *cf,
struct Curl_easy *data,
enum alpnid not_this_one)
{
if((data->state.http_neg.preferred & data->state.http_neg.allowed)) {
switch(data->state.http_neg.preferred) {
case CURL_HTTP_V3x:
if(cf_hc_may_h3(cf, data) && (ALPN_h3 != not_this_one))
return ALPN_h3;
break;
case CURL_HTTP_V2x:
if(ALPN_h2 != not_this_one)
return ALPN_h2;
break;
case CURL_HTTP_V1x:
/* If we are trying h2 already, h1 is already used as fallback */
if((ALPN_h1 != not_this_one) && (ALPN_h2 != not_this_one))
return ALPN_h1;
break;
default:
break;
}
}
return ALPN_none;
}
if(!alpn_count) {
alpn_ids[0] = ALPN_none;
alpn_count = 1;
static enum alpnid cf_hc_get_first_alpn(struct Curl_cfilter *cf,
struct Curl_easy *data,
http_majors choices,
enum alpnid not_this_one)
{
if((ALPN_h3 != not_this_one) && (choices & CURL_HTTP_V3x) &&
cf_hc_may_h3(cf, data)) {
return ALPN_h3;
}
if((ALPN_h2 != not_this_one) && (choices & CURL_HTTP_V2x)) {
return ALPN_h2;
}
/* If we are trying h2 already, h1 is already used as fallback */
if((ALPN_h1 != not_this_one) && (ALPN_h2 != not_this_one) &&
(choices & CURL_HTTP_V1x)) {
return ALPN_h1;
}
return ALPN_none;
}
static CURLcode cf_hc_set_baller1(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_hc_ctx *ctx = cf->ctx;
enum alpnid alpn1 = ALPN_none;
VERBOSE(const char *source = "HTTPS-RR");
DEBUGASSERT(cf->conn->bits.tls_enable_alpn);
alpn1 = cf_hc_get_httpsrr_alpn(cf, data, ALPN_none);
if(alpn1 == ALPN_none) {
/* preference is configured and allowed, can we use it? */
VERBOSE(source = "preferred version");
alpn1 = cf_hc_get_pref_alpn(cf, data, ALPN_none);
}
if(alpn1 == ALPN_none) {
VERBOSE(source = "wanted versions");
alpn1 = cf_hc_get_first_alpn(cf, data,
data->state.http_neg.wanted,
ALPN_none);
}
if(alpn1 == ALPN_none) {
VERBOSE(source = "allowed versions");
alpn1 = cf_hc_get_first_alpn(cf, data,
data->state.http_neg.allowed,
ALPN_none);
}
/* Install ballers for the ALPNs we selected. */
DEBUGASSERT(alpn_count <= CURL_ARRAYSIZE(ctx->ballers));
if(!alpn_count || (alpn_count > CURL_ARRAYSIZE(ctx->ballers))) {
failf(data, "https-connect filter create with unsupported %zu ALPN ids",
alpn_count);
result = CURLE_FAILED_INIT;
goto out;
if(alpn1 == ALPN_none) {
/* None of the wanted/allowed HTTP versions could be chosen */
if(ctx->check_h3_result) {
CURL_TRC_CF(data, cf, "unable to use HTTP/3");
return ctx->check_h3_result;
}
CURL_TRC_CF(data, cf, "unable to select HTTP version");
return CURLE_FAILED_INIT;
}
for(i = 0; i < alpn_count; ++i)
cf_hc_baller_assign(&ctx->ballers[i], alpn_ids[i], ctx->def_transport);
for(; i < CURL_ARRAYSIZE(ctx->ballers); ++i)
ctx->ballers[i].alpn_id = ALPN_none;
ctx->baller_count = alpn_count;
cf_hc_baller_assign(&ctx->ballers[0], alpn1, ctx->def_transport);
ctx->baller_count = 1;
CURL_TRC_CF(data, cf, "1st attempt uses %s from %s",
ctx->ballers[0].name, source);
return CURLE_OK;
}
out:
return result;
static void cf_hc_set_baller2(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_hc_ctx *ctx = cf->ctx;
enum alpnid alpn2 = ALPN_none, alpn1 = ctx->ballers[0].alpn_id;
VERBOSE(const char *source = "HTTPS-RR");
if(ctx->ballers_complete)
return; /* already done */
if(!ctx->httpsrr_resolved)
return; /* HTTPS-RR pending */
alpn2 = cf_hc_get_httpsrr_alpn(cf, data, alpn1);
if(alpn2 == ALPN_none) {
/* preference is configured and allowed, can we use it? */
VERBOSE(source = "preferred version");
alpn2 = cf_hc_get_pref_alpn(cf, data, alpn1);
}
if(alpn2 == ALPN_none) {
VERBOSE(source = "wanted versions");
alpn2 = cf_hc_get_first_alpn(cf, data,
data->state.http_neg.wanted,
alpn1);
}
if(alpn2 != ALPN_none) {
cf_hc_baller_assign(&ctx->ballers[1], alpn2, ctx->def_transport);
ctx->baller_count = 2;
CURL_TRC_CF(data, cf, "2nd attempt uses %s from %s",
ctx->ballers[1].name, source);
}
ctx->ballers_complete = TRUE;
}
static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
@ -454,7 +478,6 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
{
struct cf_hc_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
size_t i, failed_ballers;
if(cf->connected) {
*done = TRUE;
@ -462,35 +485,41 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
}
*done = FALSE;
if(!ctx->httpsrr_resolved)
ctx->httpsrr_resolved = Curl_conn_dns_resolved_https(data, cf->sockindex);
switch(ctx->state) {
case CF_HC_RESOLV:
result = cf_hc_resolv(cf, data);
if(result) {
if(result == CURLE_AGAIN)
result = CURLE_OK;
goto out;
}
DEBUGASSERT(ctx->baller_count);
/* Without any addressinfo, delay the start of balling. */
if(!Curl_conn_dns_has_any_ai(data, cf->sockindex))
return CURLE_OK;
ctx->state = CF_HC_INIT;
FALLTHROUGH();
case CF_HC_INIT:
DEBUGASSERT(!cf->next);
for(i = 0; i < ctx->baller_count; i++)
DEBUGASSERT(!ctx->ballers[i].cf);
CURL_TRC_CF(data, cf, "connect, init");
result = cf_hc_set_baller1(cf, data);
if(result) {
ctx->result = result;
ctx->state = CF_HC_FAILURE;
goto out;
}
cf_hc_set_baller2(cf, data);
ctx->started = *Curl_pgrs_now(data);
cf_hc_baller_init(&ctx->ballers[0], cf, data, ctx->ballers[0].transport);
if(ctx->baller_count > 1) {
cf_hc_baller_init(&ctx->ballers[0], cf, data);
if((ctx->baller_count > 1) || !ctx->ballers_complete) {
Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS);
CURL_TRC_CF(data, cf, "set next attempt to start in %" FMT_TIMEDIFF_T
"ms", ctx->soft_eyeballs_timeout_ms);
}
ctx->state = CF_HC_CONNECT;
FALLTHROUGH();
case CF_HC_CONNECT:
if(cf_hc_baller_is_active(&ctx->ballers[0])) {
if(!ctx->ballers_complete)
cf_hc_set_baller2(cf, data);
if(cf_hc_baller_is_connecting(&ctx->ballers[0])) {
result = cf_hc_baller_connect(&ctx->ballers[0], cf, data, done);
if(!result && *done) {
result = baller_connected(cf, data, &ctx->ballers[0]);
@ -498,12 +527,11 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
}
}
if(time_to_start_next(cf, data, 1, *Curl_pgrs_now(data))) {
cf_hc_baller_init(&ctx->ballers[1], cf, data, ctx->ballers[1].transport);
if(time_to_start_baller2(cf, data)) {
cf_hc_baller_init(&ctx->ballers[1], cf, data);
}
if((ctx->baller_count > 1) && cf_hc_baller_is_active(&ctx->ballers[1])) {
CURL_TRC_CF(data, cf, "connect, check %s", ctx->ballers[1].name);
if(cf_hc_baller_is_connecting(&ctx->ballers[1])) {
result = cf_hc_baller_connect(&ctx->ballers[1], cf, data, done);
if(!result && *done) {
result = baller_connected(cf, data, &ctx->ballers[1]);
@ -511,21 +539,12 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
}
}
failed_ballers = 0;
for(i = 0; i < ctx->baller_count; i++) {
if(ctx->ballers[i].result)
++failed_ballers;
}
if(failed_ballers == ctx->baller_count) {
if(ctx->ballers[0].result &&
(ctx->ballers[1].result ||
(ctx->ballers_complete && (ctx->baller_count < 2)))) {
/* all have failed. we give up */
CURL_TRC_CF(data, cf, "connect, all attempts failed");
for(i = 0; i < ctx->baller_count; i++) {
if(ctx->ballers[i].result) {
result = ctx->ballers[i].result;
break;
}
}
ctx->result = result = ctx->ballers[0].result;
ctx->state = CF_HC_FAILURE;
goto out;
}
@ -569,7 +588,7 @@ static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf,
for(i = 0; i < ctx->baller_count; i++) {
struct cf_hc_baller *b = &ctx->ballers[i];
bool bdone = FALSE;
if(!cf_hc_baller_is_active(b) || b->shutdown)
if(!cf_hc_baller_is_connecting(b) || b->shutdown)
continue;
b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone);
if(b->result || bdone)
@ -602,7 +621,7 @@ static CURLcode cf_hc_adjust_pollset(struct Curl_cfilter *cf,
for(i = 0; (i < ctx->baller_count) && !result; i++) {
struct cf_hc_baller *b = &ctx->ballers[i];
if(!cf_hc_baller_is_active(b))
if(!cf_hc_baller_is_connecting(b))
continue;
result = Curl_conn_cf_adjust_pollset(b->cf, data, ps);
}
@ -705,7 +724,7 @@ out:
static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
CURL_TRC_CF(data, cf, "close");
cf_hc_ctx_reset(data, cf->ctx);
cf_hc_ctx_close(data, cf->ctx);
cf->connected = FALSE;
if(cf->next) {

View File

@ -725,7 +725,7 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data,
unsigned char transport)
{
if(transport == TRNSPRT_UNIX) {
/* cannot do QUIC over a Unix domain socket */
failf(data, "HTTP/3 cannot be used over UNIX domain sockets");
return CURLE_QUIC_CONNECT_ERROR;
}
if(!(conn->scheme->flags & PROTOPT_SSL)) {