From: Stefan Eissing Date: Wed, 15 Jan 2025 15:45:25 +0000 (+0100) Subject: cf-https-connect: look into httpsrr alpns when available X-Git-Tag: curl-8_12_0~111 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a6eac834811a77ad283c498c7f61424ccef2912b;p=thirdparty%2Fcurl.git cf-https-connect: look into httpsrr alpns when available Improved the filter implementation to be flexible in which order h3 and h2/h1 are attempted. When HTTPSRR is enabled, look at the ALPNs it found and use the order given for connecting in default setups. Closes #16012 --- diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index 9eb38f415c..cb06d2d070 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -31,6 +31,7 @@ #include "curl_trc.h" #include "cfilters.h" #include "connect.h" +#include "hostip.h" #include "multiif.h" #include "cf-https-connect.h" #include "http2.h" @@ -42,6 +43,10 @@ #include "memdebug.h" +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + typedef enum { CF_HC_INIT, CF_HC_CONNECT, @@ -55,7 +60,7 @@ struct cf_hc_baller { CURLcode result; struct curltime started; int reply_ms; - BIT(enabled); + enum alpnid alpn_id; BIT(shutdown); }; @@ -73,7 +78,7 @@ static void cf_hc_baller_reset(struct cf_hc_baller *b, static bool cf_hc_baller_is_active(struct cf_hc_baller *b) { - return b->enabled && b->cf && !b->result; + return b->cf && !b->result; } static bool cf_hc_baller_has_started(struct cf_hc_baller *b) @@ -84,7 +89,7 @@ static bool cf_hc_baller_has_started(struct cf_hc_baller *b) static int cf_hc_baller_reply_ms(struct cf_hc_baller *b, struct Curl_easy *data) { - if(b->reply_ms < 0) + if(b->cf && (b->reply_ms < 0)) b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS, &b->reply_ms, NULL); return b->reply_ms; @@ -116,8 +121,8 @@ struct cf_hc_ctx { const struct Curl_dns_entry *remotehost; struct curltime started; /* when connect started */ CURLcode result; /* overall result */ - struct cf_hc_baller h3_baller; - struct cf_hc_baller h21_baller; + struct cf_hc_baller ballers[2]; + size_t baller_count; unsigned int soft_eyeballs_timeout_ms; unsigned int hard_eyeballs_timeout_ms; }; @@ -125,17 +130,32 @@ struct cf_hc_ctx { static void cf_hc_baller_init(struct cf_hc_baller *b, struct Curl_cfilter *cf, struct Curl_easy *data, - const char *name, int transport) { struct cf_hc_ctx *ctx = cf->ctx; struct Curl_cfilter *save = cf->next; - b->name = name; cf->next = NULL; b->started = Curl_now(); - b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost, - transport, CURL_CF_SSL_ENABLE); + switch(b->alpn_id) { + case ALPN_h3: + b->name = "h3"; + transport = TRNSPRT_QUIC; + break; + case ALPN_h2: + b->name = "h2"; + break; + case ALPN_h1: + b->name = "h1"; + break; + default: + b->result = CURLE_FAILED_INIT; + break; + } + + if(!b->result) + b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost, + transport, CURL_CF_SSL_ENABLE); b->cf = cf->next; cf->next = save; } @@ -157,10 +177,11 @@ static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b, static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_hc_ctx *ctx = cf->ctx; + size_t i; if(ctx) { - cf_hc_baller_reset(&ctx->h3_baller, data); - cf_hc_baller_reset(&ctx->h21_baller, data); + for(i = 0; i < ctx->baller_count; ++i) + cf_hc_baller_reset(&ctx->ballers[i], data); ctx->state = CF_HC_INIT; ctx->result = CURLE_OK; ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout; @@ -175,12 +196,12 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, struct cf_hc_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; int reply_ms; + size_t i; DEBUGASSERT(winner->cf); - if(winner != &ctx->h3_baller) - cf_hc_baller_reset(&ctx->h3_baller, data); - if(winner != &ctx->h21_baller) - cf_hc_baller_reset(&ctx->h21_baller, data); + 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) @@ -218,31 +239,31 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, } -static bool time_to_start_h21(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct curltime now) +static bool time_to_start_next(struct Curl_cfilter *cf, + struct Curl_easy *data, + size_t idx, struct curltime now) { struct cf_hc_ctx *ctx = cf->ctx; timediff_t elapsed_ms; - if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller)) + if(idx >= ctx->baller_count) + return FALSE; + if(cf_hc_baller_has_started(&ctx->ballers[idx])) return FALSE; - - if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller)) - return TRUE; elapsed_ms = Curl_timediff(now, ctx->started); if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) { - CURL_TRC_CF(data, cf, "hard timeout of %dms reached, starting h21", - ctx->hard_eyeballs_timeout_ms); + CURL_TRC_CF(data, cf, "hard timeout of %dms reached, starting %s", + ctx->hard_eyeballs_timeout_ms, ctx->ballers[idx].name); return TRUE; } - if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) { - if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) { - CURL_TRC_CF(data, cf, "soft timeout of %dms reached, h3 has not " - "seen any data, starting h21", - ctx->soft_eyeballs_timeout_ms); + 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 %dms reached, %s has not " + "seen any data, starting %s", + ctx->soft_eyeballs_timeout_ms, + ctx->ballers[idx - 1].name, ctx->ballers[idx].name); return TRUE; } /* set the effective hard timeout again */ @@ -259,6 +280,7 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, struct cf_hc_ctx *ctx = cf->ctx; struct curltime now; CURLcode result = CURLE_OK; + size_t i, failed_ballers; (void)blocking; if(cf->connected) { @@ -270,51 +292,54 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, now = Curl_now(); switch(ctx->state) { case CF_HC_INIT: - DEBUGASSERT(!ctx->h3_baller.cf); - DEBUGASSERT(!ctx->h21_baller.cf); DEBUGASSERT(!cf->next); + for(i = 0; i < ctx->baller_count; i++) + DEBUGASSERT(!ctx->ballers[i].cf); CURL_TRC_CF(data, cf, "connect, init"); ctx->started = now; - if(ctx->h3_baller.enabled) { - cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC); - if(ctx->h21_baller.enabled) - Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); - } - else if(ctx->h21_baller.enabled) - cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", - cf->conn->transport); + cf_hc_baller_init(&ctx->ballers[0], cf, data, cf->conn->transport); + if(ctx->baller_count > 1) + Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); ctx->state = CF_HC_CONNECT; FALLTHROUGH(); case CF_HC_CONNECT: - if(cf_hc_baller_is_active(&ctx->h3_baller)) { - result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done); + if(cf_hc_baller_is_active(&ctx->ballers[0])) { + result = cf_hc_baller_connect(&ctx->ballers[0], cf, data, done); if(!result && *done) { - result = baller_connected(cf, data, &ctx->h3_baller); + result = baller_connected(cf, data, &ctx->ballers[0]); goto out; } } - if(time_to_start_h21(cf, data, now)) { - cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", - cf->conn->transport); + if(time_to_start_next(cf, data, 1, now)) { + cf_hc_baller_init(&ctx->ballers[1], cf, data, cf->conn->transport); } - if(cf_hc_baller_is_active(&ctx->h21_baller)) { - CURL_TRC_CF(data, cf, "connect, check h21"); - result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done); + 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); + result = cf_hc_baller_connect(&ctx->ballers[1], cf, data, done); if(!result && *done) { - result = baller_connected(cf, data, &ctx->h21_baller); + result = baller_connected(cf, data, &ctx->ballers[1]); goto out; } } - if((!ctx->h3_baller.enabled || ctx->h3_baller.result) && - (!ctx->h21_baller.enabled || ctx->h21_baller.result)) { - /* both failed or disabled. we give up */ + failed_ballers = 0; + for(i = 0; i < ctx->baller_count; i++) { + if(ctx->ballers[i].result) + ++failed_ballers; + } + + if(failed_ballers == ctx->baller_count) { + /* all have failed. we give up */ CURL_TRC_CF(data, cf, "connect, all failed"); - result = ctx->result = ctx->h3_baller.enabled ? - ctx->h3_baller.result : ctx->h21_baller.result; + for(i = 0; i < ctx->baller_count; i++) { + if(ctx->ballers[i].result) { + result = ctx->ballers[i].result; + break; + } + } ctx->state = CF_HC_FAILURE; goto out; } @@ -344,7 +369,6 @@ static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { struct cf_hc_ctx *ctx = cf->ctx; - struct cf_hc_baller *ballers[2]; size_t i; CURLcode result = CURLE_OK; @@ -356,10 +380,8 @@ static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf, /* shutdown all ballers that have not done so already. If one fails, * continue shutting down others until all are shutdown. */ - ballers[0] = &ctx->h3_baller; - ballers[1] = &ctx->h21_baller; - for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { - struct cf_hc_baller *b = ballers[i]; + 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) continue; @@ -369,14 +391,14 @@ static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf, } *done = TRUE; - for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { - if(ballers[i] && !ballers[i]->shutdown) + for(i = 0; i < ctx->baller_count; i++) { + if(!ctx->ballers[i].shutdown) *done = FALSE; } if(*done) { - for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { - if(ballers[i] && ballers[i]->result) - result = ballers[i]->result; + for(i = 0; i < ctx->baller_count; i++) { + if(ctx->ballers[i].result) + result = ctx->ballers[i].result; } } CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done); @@ -389,13 +411,10 @@ static void cf_hc_adjust_pollset(struct Curl_cfilter *cf, { if(!cf->connected) { struct cf_hc_ctx *ctx = cf->ctx; - struct cf_hc_baller *ballers[2]; size_t i; - ballers[0] = &ctx->h3_baller; - ballers[1] = &ctx->h21_baller; - for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { - struct cf_hc_baller *b = ballers[i]; + for(i = 0; i < ctx->baller_count; i++) { + struct cf_hc_baller *b = &ctx->ballers[i]; if(!cf_hc_baller_is_active(b)) continue; Curl_conn_cf_adjust_pollset(b->cf, data, ps); @@ -408,13 +427,16 @@ static bool cf_hc_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_hc_ctx *ctx = cf->ctx; + size_t i; if(cf->connected) return cf->next->cft->has_data_pending(cf->next, data); CURL_TRC_CF((struct Curl_easy *)data, cf, "data_pending"); - return cf_hc_baller_data_pending(&ctx->h3_baller, data) - || cf_hc_baller_data_pending(&ctx->h21_baller, data); + for(i = 0; i < ctx->baller_count; i++) + if(cf_hc_baller_data_pending(&ctx->ballers[i], data)) + return TRUE; + return FALSE; } static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf, @@ -422,21 +444,17 @@ static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf, int query) { struct cf_hc_ctx *ctx = cf->ctx; - struct Curl_cfilter *cfb; struct curltime t, tmax; + size_t i; memset(&tmax, 0, sizeof(tmax)); - memset(&t, 0, sizeof(t)); - cfb = ctx->h21_baller.enabled ? ctx->h21_baller.cf : NULL; - if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) - tmax = t; - } - memset(&t, 0, sizeof(t)); - cfb = ctx->h3_baller.enabled ? ctx->h3_baller.cf : NULL; - if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) - tmax = t; + for(i = 0; i < ctx->baller_count; i++) { + struct Curl_cfilter *cfb = ctx->ballers[i].cf; + memset(&t, 0, sizeof(t)); + if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { + if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) + tmax = t; + } } return tmax; } @@ -446,6 +464,7 @@ static CURLcode cf_hc_query(struct Curl_cfilter *cf, int query, int *pres1, void *pres2) { struct cf_hc_ctx *ctx = cf->ctx; + size_t i; if(!cf->connected) { switch(query) { @@ -460,11 +479,11 @@ static CURLcode cf_hc_query(struct Curl_cfilter *cf, return CURLE_OK; } case CF_QUERY_NEED_FLUSH: { - if(cf_hc_baller_needs_flush(&ctx->h3_baller, data) - || cf_hc_baller_needs_flush(&ctx->h21_baller, data)) { - *pres1 = TRUE; - return CURLE_OK; - } + for(i = 0; i < ctx->baller_count; i++) + if(cf_hc_baller_needs_flush(&ctx->ballers[i], data)) { + *pres1 = TRUE; + return CURLE_OK; + } break; } default: @@ -482,14 +501,17 @@ static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf, { struct cf_hc_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; + size_t i; if(!cf->connected) { - result = cf_hc_baller_cntrl(&ctx->h3_baller, data, event, arg1, arg2); - if(!result || (result == CURLE_AGAIN)) - result = cf_hc_baller_cntrl(&ctx->h21_baller, data, event, arg1, arg2); - if(result == CURLE_AGAIN) - result = CURLE_OK; + for(i = 0; i < ctx->baller_count; i++) { + result = cf_hc_baller_cntrl(&ctx->ballers[i], data, event, arg1, arg2); + if(result && (result != CURLE_AGAIN)) + goto out; + } + result = CURLE_OK; } +out: return result; } @@ -537,23 +559,37 @@ struct Curl_cftype Curl_cft_http_connect = { static CURLcode cf_hc_create(struct Curl_cfilter **pcf, struct Curl_easy *data, const struct Curl_dns_entry *remotehost, - bool try_h3, bool try_h21) + enum alpnid *alpnids, size_t alpn_count) { struct Curl_cfilter *cf = NULL; struct cf_hc_ctx *ctx; CURLcode result = CURLE_OK; + size_t i; + + DEBUGASSERT(alpnids); + DEBUGASSERT(alpn_count); + DEBUGASSERT(alpn_count <= ARRAYSIZE(ctx->ballers)); + if(!alpn_count || (alpn_count > ARRAYSIZE(ctx->ballers))) { + failf(data, "https-connect filter create with unsupported %zu ALPN ids", + alpn_count); + return CURLE_FAILED_INIT; + } - (void)data; ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } ctx->remotehost = remotehost; - ctx->h3_baller.enabled = try_h3; - ctx->h21_baller.enabled = try_h21; + for(i = 0; i < alpn_count; ++i) + ctx->ballers[i].alpn_id = alpnids[i]; + for(; i < ARRAYSIZE(ctx->ballers); ++i) + ctx->ballers[i].alpn_id = ALPN_none; + ctx->baller_count = alpn_count; result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx); + CURL_TRC_CF(data, cf, "created with %zu ALPNs -> %d", + ctx->baller_count, result); if(result) goto out; ctx = NULL; @@ -569,13 +605,13 @@ static CURLcode cf_http_connect_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, const struct Curl_dns_entry *remotehost, - bool try_h3, bool try_h21) + enum alpnid *alpn_ids, size_t alpn_count) { struct Curl_cfilter *cf; CURLcode result = CURLE_OK; DEBUGASSERT(data); - result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21); + result = cf_hc_create(&cf, data, remotehost, alpn_ids, alpn_count); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); @@ -588,31 +624,86 @@ CURLcode Curl_cf_https_setup(struct Curl_easy *data, int sockindex, const struct Curl_dns_entry *remotehost) { - bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */ + enum alpnid alpn_ids[2]; + size_t alpn_count = 0; CURLcode result = CURLE_OK; (void)sockindex; (void)remotehost; - if(!conn->bits.tls_enable_alpn) - goto out; - - if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { - result = Curl_conn_may_http3(data, conn); - if(result) /* cannot do it */ - goto out; - try_h3 = TRUE; - try_h21 = FALSE; + if(conn->bits.tls_enable_alpn) { + switch(data->state.httpwant) { + case CURL_HTTP_VERSION_NONE: + /* No preferences by transfer setup. Choose best defaults */ +#ifdef USE_HTTPSRR + if(conn->dns_entry && conn->dns_entry->hinfo && + !conn->dns_entry->hinfo->no_def_alpn) { + size_t i, j; + for(i = 0; i < ARRAYSIZE(conn->dns_entry->hinfo->alpns) && + alpn_count < ARRAYSIZE(alpn_ids); ++i) { + bool present = FALSE; + enum alpnid alpn = conn->dns_entry->hinfo->alpns[i]; + for(j = 0; j < alpn_count; ++j) { + if(alpn == alpn_ids[j]) { + present = TRUE; + break; + } + } + if(!present) { + switch(alpn) { + case ALPN_h3: + if(Curl_conn_may_http3(data, conn)) + break; /* not possible */ + FALLTHROUGH(); + case ALPN_h2: + case ALPN_h1: + alpn_ids[alpn_count++] = alpn; + break; + default: /* ignore */ + break; + } + } + } + } +#endif + if(!alpn_count) + alpn_ids[alpn_count++] = ALPN_h2; + break; + case CURL_HTTP_VERSION_3ONLY: + result = Curl_conn_may_http3(data, conn); + if(result) /* cannot do it */ + goto out; + alpn_ids[alpn_count++] = ALPN_h3; + break; + case CURL_HTTP_VERSION_3: + /* We assume that silently not even trying H3 is ok here */ + /* TODO: should we fail instead? */ + if(Curl_conn_may_http3(data, conn) == CURLE_OK) + alpn_ids[alpn_count++] = ALPN_h3; + alpn_ids[alpn_count++] = ALPN_h2; + break; + case CURL_HTTP_VERSION_2_0: + case CURL_HTTP_VERSION_2TLS: + case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: + alpn_ids[alpn_count++] = ALPN_h2; + break; + case CURL_HTTP_VERSION_1_0: + case CURL_HTTP_VERSION_1_1: + alpn_ids[alpn_count++] = ALPN_h1; + break; + default: + alpn_ids[alpn_count++] = ALPN_h2; + break; + } } - else if(data->state.httpwant >= CURL_HTTP_VERSION_3) { - /* We assume that silently not even trying H3 is ok here */ - /* TODO: should we fail instead? */ - try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK); - try_h21 = TRUE; + + /* If we identified ALPNs to use, install our filter. Otherwise, + * install nothing, so our call will use a default connect setup. */ + if(alpn_count) { + result = cf_http_connect_add(data, conn, sockindex, remotehost, + alpn_ids, alpn_count); } - result = cf_http_connect_add(data, conn, sockindex, remotehost, - try_h3, try_h21); out: return result; } diff --git a/lib/doh.c b/lib/doh.c index 834c00346b..d4500b3bd5 100644 --- a/lib/doh.c +++ b/lib/doh.c @@ -1105,7 +1105,7 @@ static CURLcode doh_decode_rdata_alpn(unsigned char *cp, size_t len, if(id != ALPN_none) { if(idnum == MAX_HTTPSRR_ALPNS) break; - alpns[idnum++] = id; + alpns[idnum++] = (unsigned char)id; } Curl_dyn_reset(&dval); }