| @@ -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); |
| - | |
| - | 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. */ |
| + | /* 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; |
| + | } |
| + | |
| 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) |
| + | static bool cf_hc_may_h3(struct Curl_cfilter *cf, |
| + | struct Curl_easy *data) |
| { |
| - | size_t i; |
| - | for(i = 0; i < len; ++i) { |
| - | if(id == list[i]) |
| - | return TRUE; |
| + | struct cf_hc_ctx *ctx = cf->ctx; |
| + | if(!ctx->checked_h3) { |
| + | ctx->check_h3_result = |
| + | Curl_conn_may_http3(data, cf->conn, ctx->def_transport); |
| + | ctx->checked_h3 = TRUE; |
| } |
| - | return FALSE; |
| + | return !ctx->check_h3_result; |
| } |
| |
| - | static CURLcode cf_hc_resolv(struct Curl_cfilter *cf, |
| - | struct Curl_easy *data) |
| + | static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf, |
| + | struct Curl_easy *data, |
| + | enum alpnid not_this_one) |
| { |
| - | struct cf_hc_ctx *ctx = cf->ctx; |
| - | enum alpnid alpn_ids[2]; |
| - | size_t alpn_count = 0, i; |
| - | CURLcode result = CURLE_OK; |
| - | |
| - | if(cf->conn->bits.tls_enable_alpn) { |
| #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; |
| - | |
| - | /* 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; |
| - | } |
| - | } |
| - | } |
| - | #endif |
| + | /* 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; |
| |
| - | /* 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; |
| + | /* 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); ++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 CURL_HTTP_V2x: |
| - | alpn_pref = ALPN_h2; |
| + | case ALPN_h2: |
| + | if(data->state.http_neg.allowed & CURL_HTTP_V2x) { |
| + | return alpn_rr; |
| + | } |
| break; |
| - | case CURL_HTTP_V1x: |
| - | alpn_pref = ALPN_h1; |
| + | case ALPN_h1: |
| + | if(data->state.http_neg.allowed & CURL_HTTP_V1x) { |
| + | return alpn_rr; |
| + | } |
| break; |
| - | default: |
| + | default: /* ignore */ |
| break; |
| } |
| - | if(alpn_pref && |
| - | !cf_https_alpns_contain(alpn_pref, alpn_ids, alpn_count)) { |
| - | alpn_ids[alpn_count++] = alpn_pref; |
| - | } |
| } |
| + | } |
| + | #else |
| + | (void)cf; |
| + | (void)data; |
| + | (void)not_this_one; |
| + | #endif |
| + | return ALPN_none; |
| + | } |
| |
| - | 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; |
| + | } |
| |
| - | /* 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; |
| + | 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); |
| } |
| |
| - | 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; |
| + | 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; |
| + | } |
| |
| - | out: |
| - | return result; |
| + | 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; |
| + | } |
| + | |
| + | 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) { |