From 809dda3a37363160d4bf5ea2dafa0bcb8188a3f0 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 17 Apr 2026 10:26:25 +0200 Subject: [PATCH] Happy Eyeballs: add resolution time delay HEv3 describes conditions on when first connect attempts shall be started. https://www.ietf.org/archive/id/draft-ietf-happy-happyeyeballs-v3-01.html Chapter 4.2 libcurl now waits 50ms for AAAA and HTTPS results (when requested) to return before continuing with the connect. Added HTTPS-RR to the "was resolved" information info message. Changed logging of HTTPS-RR to a one-liner with RFC 9460 like formatting. This way the user can see if/what was resolved and used in connecting. Closes #21354 --- lib/asyn-ares.c | 3 + lib/asyn-thrdd.c | 14 ++-- lib/asyn.h | 2 + lib/cf-dns.c | 86 +++++++++++++------- lib/cf-dns.h | 4 - lib/cf-https-connect.c | 14 +--- lib/cf-ip-happy.c | 3 +- lib/cfilters.c | 11 +++ lib/cfilters.h | 6 ++ lib/hostip.c | 81 +++++++++++-------- lib/hostip.h | 13 +++ lib/httpsrr.c | 180 +++++++++++++++++++++++++---------------- lib/httpsrr.h | 13 ++- 13 files changed, 272 insertions(+), 158 deletions(-) diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c index 4d14a632b4..187ffbbe54 100644 --- a/lib/asyn-ares.c +++ b/lib/asyn-ares.c @@ -563,6 +563,7 @@ static void async_ares_A_cb(void *user_data, int status, int timeouts, async->dns_responses |= CURL_DNSQ_A; async->queries_ongoing--; + async->done = !async->queries_ongoing; if(status == ARES_SUCCESS) { ares->ares_status = ARES_SUCCESS; ares->res_A = async_ares_node2addr(ares_ai->nodes); @@ -585,6 +586,7 @@ static void async_ares_AAAA_cb(void *user_data, int status, int timeouts, async->dns_responses |= CURL_DNSQ_AAAA; async->queries_ongoing--; + async->done = !async->queries_ongoing; if(status == ARES_SUCCESS) { ares->ares_status = ARES_SUCCESS; ares->res_AAAA = async_ares_node2addr(ares_ai->nodes); @@ -609,6 +611,7 @@ static void async_ares_rr_done(void *user_data, ares_status_t status, (void)timeouts; async->dns_responses |= CURL_DNSQ_HTTPS; async->queries_ongoing--; + async->done = !async->queries_ongoing; if((ARES_SUCCESS != status) || !dnsrec) return; ares->result = Curl_httpsrr_from_ares(dnsrec, &ares->hinfo); diff --git a/lib/asyn-thrdd.c b/lib/asyn-thrdd.c index 79e1fd2449..f19aab0c2d 100644 --- a/lib/asyn-thrdd.c +++ b/lib/asyn-thrdd.c @@ -484,7 +484,7 @@ static void async_thrdd_report_item(struct Curl_easy *data, int ai_family = (item->dns_queries & CURL_DNSQ_AAAA) ? AF_INET6 : AF_INET; CURLcode result; - if(!Curl_trc_is_verbose(data)) + if(!CURL_TRC_DNS_is_verbose(data)) return; curlx_dyn_init(&tmp, 1024); @@ -500,9 +500,10 @@ static void async_thrdd_report_item(struct Curl_easy *data, } } - infof(data, "Host %s:%u resolved IPv%c: %s", item->hostname, item->port, - (item->dns_queries & CURL_DNSQ_AAAA) ? '6' : '4', - (curlx_dyn_len(&tmp) ? curlx_dyn_ptr(&tmp) : "(none)")); + CURL_TRC_DNS(data, "Host %s:%u resolved IPv%c: %s", + item->hostname, item->port, + (item->dns_queries & CURL_DNSQ_AAAA) ? '6' : '4', + (curlx_dyn_len(&tmp) ? curlx_dyn_ptr(&tmp) : "(none)")); out: curlx_dyn_free(&tmp); } @@ -707,8 +708,10 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, return CURLE_AGAIN; Curl_expire_done(data, EXPIRE_ASYNC_NAME); - if(async->result) + if(async->result) { + result = async->result; goto out; + } if((thrdd->res_A && thrdd->res_A->res) || (thrdd->res_AAAA && thrdd->res_AAAA->res)) { @@ -739,7 +742,6 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, } if(dns) { - CURL_TRC_DNS(data, "resolving complete"); *pdns = dns; dns = NULL; } diff --git a/lib/asyn.h b/lib/asyn.h index 06387a85ae..8c1bd8fd6b 100644 --- a/lib/asyn.h +++ b/lib/asyn.h @@ -214,6 +214,8 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, #define Curl_async_await(a, b, c) CURLE_COULDNT_RESOLVE_HOST #define Curl_async_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST #define Curl_async_pollset(x, y, z) CURLE_OK +#define Curl_async_get_https(x, y) NULL +#define Curl_async_knows_https(x, y) TRUE #endif /* !CURLRES_ASYNCH */ #if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) diff --git a/lib/cf-dns.c b/lib/cf-dns.c index 98784b0cf6..54ac0eebe8 100644 --- a/lib/cf-dns.c +++ b/lib/cf-dns.c @@ -28,6 +28,7 @@ #include "cfilters.h" #include "connect.h" #include "dnscache.h" +#include "httpsrr.h" #include "curl_trc.h" #include "progress.h" #include "url.h" @@ -138,11 +139,24 @@ static void cf_dns_report(struct Curl_cfilter *cf, break; default: curlx_dyn_init(&tmp, 1024); - infof(data, "Host %s:%d was resolved.", dns->hostname, dns->port); + infof(data, "Host %s:%u was resolved.", dns->hostname, dns->port); #ifdef CURLRES_IPV6 cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr); #endif cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr); +#ifdef USE_HTTPSRR + if(!dns->hinfo) + infof(data, "HTTPS-RR: -"); + else if(!Curl_httpsrr_applicable(data, dns->hinfo)) + infof(data, "HTTPS-RR: not applicable"); + else { + CURLcode result = Curl_httpsrr_print(&tmp, dns->hinfo); + if(!result) + infof(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp)); + else + infof(data, "Error printing HTTPS-RR information"); + } +#endif curlx_dyn_free(&tmp); break; } @@ -205,6 +219,41 @@ static CURLcode cf_dns_start(struct Curl_cfilter *cf, } } +#define CURL_HEV3_RESOLVE_DELAY_MS 50 + +static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_dns_ctx *ctx = cf->ctx; + + if(ctx->resolv_result) + return TRUE; + else if(ctx->dns) + return TRUE; +#ifdef USE_CURL_ASYNC + else { + /* We want AAAA answer as we prefer ipv6. If a sub-filter desires + * HTTPS-RR, we check for that query as well. */ + uint8_t wanted_answers = CURL_DNSQ_AAAA; + if(Curl_conn_cf_wants_httpsrr(cf, data)) + wanted_answers |= CURL_DNSQ_HTTPS; + + /* Note: if a query was never started, it is considered to have + * an answer (e.g. a negative one). */ + if(Curl_resolv_has_answers(data, ctx->resolv_id, wanted_answers)) + return TRUE; + /* If the wanted answers are not available after a delay, + * we let the connect attempts start anyway. */ + return Curl_resolv_elapsed_ms(data, ctx->resolv_id) >= + CURL_HEV3_RESOLVE_DELAY_MS; + } +#else + (void)data; + DEBUGASSERT(0); /* We should not come here */ + return FALSE; +#endif /* USE_CURL_ASYNC */ +} + static CURLcode cf_dns_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) @@ -225,9 +274,6 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, if(!ctx->dns && !ctx->resolv_result) { ctx->resolv_result = Curl_resolv_take_result(data, ctx->resolv_id, &ctx->dns); - if(!ctx->dns && !ctx->resolv_result) - CURL_TRC_CF(data, cf, "DNS resolution ongoing for %s:%u", - ctx->hostname, ctx->port); } if(ctx->resolv_result) { @@ -244,19 +290,23 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, cf_dns_report(cf, data, ctx->dns); } + if(!cf_dns_ready_to_connect(cf, data)) { + return CURLE_OK; + } + if(cf->next && !cf->next->connected) { bool sub_done; CURLcode result = Curl_conn_cf_connect(cf->next, data, &sub_done); - CURL_TRC_CF(data, cf, "connect subfilters -> %d, done=%d", - result, sub_done); if(result || !sub_done) return result; DEBUGASSERT(sub_done); } /* sub filter chain is connected */ + CURL_TRC_CF(data, cf, "connected filter chain below"); if(ctx->complete_resolve && !ctx->dns && !ctx->resolv_result) { /* This filter only connects when it has resolved everything. */ + CURL_TRC_CF(data, cf, "delay connect until resolve complete"); return CURLE_OK; } *done = TRUE; @@ -536,30 +586,6 @@ static const struct Curl_addrinfo *cf_dns_get_nth_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. diff --git a/lib/cf-dns.h b/lib/cf-dns.h index 8e796d349b..3c46b1bf3d 100644 --- a/lib/cf-dns.h +++ b/lib/cf-dns.h @@ -47,10 +47,6 @@ CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, CURLcode Curl_conn_dns_result(struct connectdata *conn, int sockindex); -/* 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, int sockindex, int ai_family, diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index 5407dc73a6..d1d8f51076 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -306,13 +306,8 @@ static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf, /* 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)) { + /* We do not support `rr->no_def_alpn`. */ + if(Curl_httpsrr_applicable(data, rr) && !rr->no_def_alpn) { 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 */ @@ -509,9 +504,6 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, switch(ctx->state) { case CF_HC_RESOLV: - /* 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(); @@ -761,7 +753,7 @@ static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_cftype Curl_cft_http_connect = { "HTTPS-CONNECT", - CF_TYPE_SETUP, + CF_TYPE_SETUP | CF_TYPE_HTTPSRR, CURL_LOG_LVL_NONE, cf_hc_destroy, cf_hc_connect, diff --git a/lib/cf-ip-happy.c b/lib/cf-ip-happy.c index f246fb8e16..3064612d52 100644 --- a/lib/cf-ip-happy.c +++ b/lib/cf-ip-happy.c @@ -814,8 +814,9 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, result = Curl_conn_dns_result(cf->conn, cf->sockindex); if(!result) ctx->dns_resolved = TRUE; - else if(result == CURLE_AGAIN) /* not complete yet */ + else if(result == CURLE_AGAIN) { result = CURLE_OK; + } else /* real error */ goto out; } diff --git a/lib/cfilters.c b/lib/cfilters.c index e3668af473..4655414131 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -748,6 +748,17 @@ int Curl_protocol_for_transport(uint8_t transport) } } +bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)data; + for(; cf; cf = cf->next) { + if(cf->cft->flags & CF_TYPE_HTTPSRR) + return TRUE; + } + return FALSE; +} + const char *Curl_conn_get_alpn_negotiated(struct Curl_easy *data, struct connectdata *conn) { diff --git a/lib/cfilters.h b/lib/cfilters.h index fc77e61dfe..ac56737ccb 100644 --- a/lib/cfilters.h +++ b/lib/cfilters.h @@ -201,6 +201,7 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, * CF_TYPE_HTTP implement a version of the HTTP protocol * CF_TYPE_SETUP filter is only needed for connection setup and * can be removed once connected + * CF_TYPE_HTTPSRR filter that wants HTTPS-RR information */ #define CF_TYPE_IP_CONNECT (1 << 0) #define CF_TYPE_SSL (1 << 1) @@ -208,6 +209,7 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, #define CF_TYPE_PROXY (1 << 3) #define CF_TYPE_HTTP (1 << 4) #define CF_TYPE_SETUP (1 << 5) +#define CF_TYPE_HTTPSRR (1 << 6) /* A connection filter type, e.g. specific implementation. */ struct Curl_cftype { @@ -354,6 +356,10 @@ int Curl_protocol_for_transport(uint8_t transport); const char *Curl_conn_cf_get_alpn_negotiated(struct Curl_cfilter *cf, struct Curl_easy *data); +/* The filter (or one of its sub-filters) wants HTTPS-RR information. */ +bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf, + struct Curl_easy *data); + #define CURL_CF_SSL_DEFAULT (-1) #define CURL_CF_SSL_DISABLE 0 #define CURL_CF_SSL_ENABLE 1 diff --git a/lib/hostip.c b/lib/hostip.c index f1a12bb21f..0d1095e33b 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -431,10 +431,11 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, result = Curl_async_take_result(data, async, pdns); if(result == CURLE_AGAIN) { - CURL_TRC_DNS(data, "result incomplete, queries=%s, responses=%s, " - "ongoing=%d", Curl_resolv_query_str(async->dns_queries), + CURL_TRC_DNS(data, "resolve incomplete, queries=%s, responses=%s, " + "ongoing=%d for %s:%d", + Curl_resolv_query_str(async->dns_queries), Curl_resolv_query_str(async->dns_responses), - async->queries_ongoing); + async->queries_ongoing, async->hostname, async->port); result = CURLE_OK; } else if(result) { @@ -442,64 +443,74 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, Curl_resolver_error(data, NULL); } else { - CURL_TRC_DNS(data, "result complete"); + CURL_TRC_DNS(data, "resolve complete for %s:%u", + async->hostname, async->port); DEBUGASSERT(*pdns); } return result; } +timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data, + uint32_t resolv_id) +{ + struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); + if(!async) + return CURL_TIMEOUT_RESOLVE_MS; + return curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); +} + +bool Curl_resolv_has_answers(struct Curl_easy *data, + uint32_t resolv_id, uint8_t dns_queries) +{ + struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); + uint8_t check_queries; + /* a no longer existing/running resolve has all answers. */ + if(!async || async->done) + return TRUE; + /* Relevant are only queries undertaken. Others are considered answered. */ + check_queries = (dns_queries & async->dns_queries); + if((check_queries & async->dns_responses) != check_queries) { + return FALSE; + } + return TRUE; +} + const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data, uint32_t resolv_id, int ai_family, unsigned int index) { -#ifdef CURLRES_ASYNCH struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); - if(async) { - if((ai_family == AF_INET) && !(async->dns_queries & CURL_DNSQ_A)) - return NULL; -#ifdef USE_IPV6 - if((ai_family == AF_INET6) && !(async->dns_queries & CURL_DNSQ_AAAA)) - return NULL; -#endif - return Curl_async_get_ai(data, async, ai_family, index); - } -#else - (void)data; - (void)resolv_id; - (void)ai_family; (void)index; + if(!async) + return NULL; + if((ai_family == AF_INET) && !(async->dns_queries & CURL_DNSQ_A)) + return NULL; +#ifdef USE_IPV6 + if((ai_family == AF_INET6) && !(async->dns_queries & CURL_DNSQ_AAAA)) + return NULL; #endif - return NULL; + return Curl_async_get_ai(data, async, ai_family, index); } + #ifdef USE_HTTPSRR const struct Curl_https_rrinfo * Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id) { -#ifdef CURLRES_ASYNCH struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); - if(async) - return Curl_async_get_https(data, async); -#else - (void)data; - (void)resolv_id; -#endif - return NULL; + if(!async) + return NULL; + return Curl_async_get_https(data, async); } bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id) { -#ifdef CURLRES_ASYNCH struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); - if(async) - return Curl_async_knows_https(data, async); -#else - (void)data; - (void)resolv_id; -#endif - return TRUE; + if(!async) + return TRUE; + return Curl_async_knows_https(data, async); } #endif /* USE_HTTPSRR */ diff --git a/lib/hostip.h b/lib/hostip.h index 96547e33b7..368883b8e7 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -136,6 +136,17 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id, void Curl_resolv_destroy(struct Curl_easy *data, uint32_t resolv_id); +/* How much time has gone by since start of resolve. + * Returns CURL_TIMEOUT_RESOLVE_MS if `resolv_id` is no longer valid. */ +timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data, + uint32_t resolv_id); + +/* Return TRUE if `resolv_id` has answers (positive or negative) to + * all queries in `dns_queries`. + * Queries not requested are considered answered. */ +bool Curl_resolv_has_answers(struct Curl_easy *data, + uint32_t resolv_id, uint8_t dns_queries); + const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data, uint32_t resolv_id, int ai_family, @@ -150,6 +161,8 @@ bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id); #define Curl_resolv_shutdown_all(x) Curl_nop_stmt #define Curl_resolv_destroy_all(x) Curl_nop_stmt #define Curl_resolv_take_result(x, y, z) CURLE_NOT_BUILT_IN +#define Curl_resolv_elapsed_ms(x, y) CURL_TIMEOUT_RESOLVE_MS +#define Curl_resolv_has_answers(x, y, z) TRUE #define Curl_resolv_get_ai(x, y, z, a) NULL #define Curl_resolv_get_https(x, y) NULL #define Curl_resolv_knows_https(x, y) TRUE diff --git a/lib/httpsrr.c b/lib/httpsrr.c index 8d02d24542..bebd7e33b1 100644 --- a/lib/httpsrr.c +++ b/lib/httpsrr.c @@ -70,122 +70,153 @@ static CURLcode httpsrr_decode_alpn(const uint8_t *cp, size_t len, } #ifdef CURLVERBOSE -static void httpsrr_report_addr(struct Curl_easy *data, int ai_family, - const uint8_t *addr, size_t total_len) + +static CURLcode httpsrr_print_addr(struct dynbuf *dyn, + int ai_family, + const uint8_t *addr, + size_t total_len) { char buf[MAX_IPADR_LEN]; - struct dynbuf tmp; size_t i, alen = (ai_family == AF_INET6) ? 16 : 4; const char *sep = ""; - bool incomplete = FALSE; - CURLcode result; + CURLcode result = CURLE_OK; - if(!CURL_TRC_DNS_is_verbose(data)) - return; - - curlx_dyn_init(&tmp, 1024); - for(i = 0; i < (total_len / alen); ++i) { - if(!curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf))) { - CURL_TRC_DNS(data, "[HTTPS-RR] error parsing address #%zu", i); - goto out; - } - result = curlx_dyn_addf(&tmp, "%s%s", sep, buf); - if(result) { - incomplete = TRUE; - break; - } - sep = ", "; + for(i = 0; (i < (total_len / alen)) && !result; ++i) { + if(!curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf))) + result = curlx_dyn_add(dyn, ""); + else + result = curlx_dyn_addf(dyn, "%s%s", sep, buf); + sep = ","; } - - CURL_TRC_DNS(data, "[HTTPS-RR] IPv%d: %s%s", - (ai_family == AF_INET6) ? 6 : 4, - curlx_dyn_len(&tmp) ? curlx_dyn_ptr(&tmp) : "(none)", - incomplete ? " ..." : ""); -out: - curlx_dyn_free(&tmp); + return result; } void Curl_httpsrr_trace(struct Curl_easy *data, - struct Curl_https_rrinfo *hi) + struct Curl_https_rrinfo *rr) { - if(!hi || !hi->complete) { + struct dynbuf tmp; + CURLcode result; + + if(!rr || !rr->complete) { CURL_TRC_DNS(data, "[HTTPS-RR] not available"); return; } - - if(hi->target) - CURL_TRC_DNS(data, "[HTTPS-RR] target: %s", hi->target); - if(hi->priority) - CURL_TRC_DNS(data, "[HTTPS-RR] priority: %u", hi->priority); - if(hi->mandatory) - CURL_TRC_DNS(data, "[HTTPS-RR] MANDATORY present, but not supported"); - if(hi->alpns[0]) - CURL_TRC_DNS(data, "[HTTPS-RR] ALPN: %u %u %u %u", - hi->alpns[0], hi->alpns[1], hi->alpns[2], hi->alpns[3]); - if(hi->port_set) - CURL_TRC_DNS(data, "[HTTPS-RR] port %u", hi->port); - if(hi->no_def_alpn) - CURL_TRC_DNS(data, "[HTTPS-RR] no-def-alpn"); - if(hi->ipv6hints_len) - httpsrr_report_addr(data, AF_INET6, hi->ipv6hints, hi->ipv6hints_len); - if(hi->ipv4hints_len) - httpsrr_report_addr(data, AF_INET, hi->ipv4hints, hi->ipv4hints_len); - if(hi->echconfiglist_len) - CURL_TRC_DNS(data, "[HTTPS-RR] ECH"); + curlx_dyn_init(&tmp, 1024); + result = Curl_httpsrr_print(&tmp, rr); + if(!result) + CURL_TRC_DNS(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp)); + else + CURL_TRC_DNS(data, "Error printing HTTPS-RR information"); + curlx_dyn_free(&tmp); +} + +CURLcode Curl_httpsrr_print(struct dynbuf *tmp, + struct Curl_https_rrinfo *rr) +{ + CURLcode result; + int i; + + curlx_dyn_reset(tmp); + result = curlx_dyn_addf(tmp, "%u %s", rr->priority, + rr->target ? rr->target : "."); + if(!result && rr->mandatory) + result = curlx_dyn_add(tmp, " mandatory-keys(ignored)"); + if(!result && rr->alpns[0]) { + const char *sep = "", *name; + result = curlx_dyn_add(tmp, " alpn="); + for(i = 0; !result && (i < 4); ++i) { + switch(rr->alpns[i]) { + case ALPN_h1: + name = "http/1.1"; + break; + case ALPN_h2: + name = "h2"; + break; + case ALPN_h3: + name = "h3"; + break; + default: + name = NULL; + } + if(name) { + result = curlx_dyn_addf(tmp, "%s%s", sep, name); + sep = ","; + } + } + } + if(!result && rr->port_set) { + result = curlx_dyn_addf(tmp, " port=%u", rr->port); + } + if(!result && rr->no_def_alpn) + result = curlx_dyn_add(tmp, " no-default-alpn"); + if(!result && rr->ipv6hints_len) { + result = curlx_dyn_add(tmp, " ipv6hint="); + if(!result) + result = httpsrr_print_addr( + tmp, AF_INET6, rr->ipv6hints, rr->ipv6hints_len); + } + if(!result && rr->ipv4hints_len) { + result = curlx_dyn_add(tmp, " ipv4hint="); + if(!result) + result = httpsrr_print_addr( + tmp, AF_INET, rr->ipv4hints, rr->ipv4hints_len); + } + if(!result && rr->echconfiglist_len) + result = curlx_dyn_addf(tmp, " ech=<%zu bytes>", rr->echconfiglist_len); + + return result; } -#else -#define httpsrr_report_addr(a, b, c, d) Curl_nop_stmt #endif /* CURLVERBOSE */ -CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *hi, +CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr, uint16_t rrkey, const uint8_t *val, size_t vlen) { CURLcode result = CURLE_OK; switch(rrkey) { case HTTPS_RR_CODE_MANDATORY: - hi->mandatory = TRUE; + rr->mandatory = TRUE; break; case HTTPS_RR_CODE_ALPN: /* str_list */ - result = httpsrr_decode_alpn(val, vlen, hi->alpns); + result = httpsrr_decode_alpn(val, vlen, rr->alpns); break; case HTTPS_RR_CODE_NO_DEF_ALPN: if(vlen) /* no data */ return CURLE_BAD_FUNCTION_ARGUMENT; - hi->no_def_alpn = TRUE; + rr->no_def_alpn = TRUE; break; case HTTPS_RR_CODE_IPV4: /* addr4 list */ if(!vlen || (vlen & 3)) /* the size must be 4-byte aligned */ return CURLE_BAD_FUNCTION_ARGUMENT; - curlx_free(hi->ipv4hints); - hi->ipv4hints = curlx_memdup(val, vlen); - if(!hi->ipv4hints) + curlx_free(rr->ipv4hints); + rr->ipv4hints = curlx_memdup(val, vlen); + if(!rr->ipv4hints) return CURLE_OUT_OF_MEMORY; - hi->ipv4hints_len = vlen; + rr->ipv4hints_len = vlen; break; case HTTPS_RR_CODE_ECH: if(!vlen) return CURLE_BAD_FUNCTION_ARGUMENT; - curlx_free(hi->echconfiglist); - hi->echconfiglist = curlx_memdup(val, vlen); - if(!hi->echconfiglist) + curlx_free(rr->echconfiglist); + rr->echconfiglist = curlx_memdup(val, vlen); + if(!rr->echconfiglist) return CURLE_OUT_OF_MEMORY; - hi->echconfiglist_len = vlen; + rr->echconfiglist_len = vlen; break; case HTTPS_RR_CODE_IPV6: /* addr6 list */ if(!vlen || (vlen & 15)) /* the size must be 16-byte aligned */ return CURLE_BAD_FUNCTION_ARGUMENT; - curlx_free(hi->ipv6hints); - hi->ipv6hints = curlx_memdup(val, vlen); - if(!hi->ipv6hints) + curlx_free(rr->ipv6hints); + rr->ipv6hints = curlx_memdup(val, vlen); + if(!rr->ipv6hints) return CURLE_OUT_OF_MEMORY; - hi->ipv6hints_len = vlen; + rr->ipv6hints_len = vlen; break; case HTTPS_RR_CODE_PORT: if(vlen != 2) return CURLE_BAD_FUNCTION_ARGUMENT; - hi->port = (uint16_t)((val[0] << 8) | val[1]); - hi->port_set = TRUE; + rr->port = (uint16_t)((val[0] << 8) | val[1]); + rr->port_set = TRUE; break; default: /* unknown code */ @@ -213,6 +244,17 @@ void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo) rrinfo->complete = FALSE; } +bool Curl_httpsrr_applicable(struct Curl_easy *data, + const struct Curl_https_rrinfo *rr) +{ + if(!data->conn || !rr) + return FALSE; + return (data->conn && rr && + (!rr->target || !rr->target[0] || + (rr->target[0] == '.' && !rr->target[1])) && + (!rr->port_set || rr->port == data->conn->remote_port)); +} + #ifdef USE_ARES static CURLcode httpsrr_opt(const ares_dns_rr_t *rr, diff --git a/lib/httpsrr.h b/lib/httpsrr.h index 68f5dad875..b28e3271be 100644 --- a/lib/httpsrr.h +++ b/lib/httpsrr.h @@ -35,6 +35,7 @@ #define MAX_HTTPSRR_ALPNS 4 struct Curl_easy; +struct dynbuf; struct Curl_https_rrinfo { char *rrname; /* if NULL, the same as the URL hostname */ @@ -59,7 +60,7 @@ struct Curl_https_rrinfo { BIT(complete); /* values have been successfully assigned */ }; -CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *hi, +CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr, uint16_t rrkey, const uint8_t *val, size_t vlen); struct Curl_https_rrinfo *Curl_httpsrr_dup_move( @@ -67,6 +68,11 @@ struct Curl_https_rrinfo *Curl_httpsrr_dup_move( void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); +/* TRUE if the record is applicable to the transfer and its connection. */ +bool Curl_httpsrr_applicable(struct Curl_easy *data, + const struct Curl_https_rrinfo *rr); + + /* * Code points for DNS wire format SvcParams as per RFC 9460 */ @@ -84,8 +90,11 @@ CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, #endif /* USE_ARES */ #ifdef CURLVERBOSE + +CURLcode Curl_httpsrr_print(struct dynbuf *tmp, + struct Curl_https_rrinfo *rr); void Curl_httpsrr_trace(struct Curl_easy *data, - struct Curl_https_rrinfo *hi); + struct Curl_https_rrinfo *rr); #else #define Curl_httpsrr_trace(a, b) Curl_nop_stmt #endif