From 7ad33fe56f25424fb51a10405b849de81ff74b98 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 15 Jul 2026 17:21:04 +0200 Subject: [PATCH] cf-dns: resolve on filter demand Use separate dns cache entries for addresses (A+AAAA) and HTTPS-RR results. That makes also "negative" results independent of each other. Dns cache entries, once in use, can no longer be modified safely, as concurrent use would require each access then to be done under lock. By using separate entries, we can update a HTTPS entry without needing to duplicate an existing address entry for the same host+port. Connection filters can now ask for DNS resolves. This works at any time during connection setup and while connect is going on. `Curl_conn_dns_add_addr_resolve()` and `Curl_conn_dns_add_https_resolve()` are used for that. They check if the resolve is already ongoing, can be added to a resolve not started yet or, as last resort, create a new filter instance and add it to the connection (it's easier to add more filters than making the same filter handle multiple resolves. Since DNS filters are removed once the connection is established, there is no later penalty). HTTPS-RR queries are added by the `HTTPS-CONNECT`, `SSL` and `QUIC` filters. The latter will only do that when ECH is configured and supported. That means we trigger HTTPS-RR queries only when the results matter. Add test_22_06 for ALPN influenced via HTTPS-RR when tunneling through a proxy. This did not work before. Adjust test2100 to use https: as the previous http: URL no longer triggers HTTPS-RR resolves. Closes #22216 --- lib/asyn-ares.c | 108 ++++++------- lib/asyn-base.c | 7 +- lib/asyn-thrdd.c | 34 ++-- lib/asyn.h | 6 +- lib/cf-dns.c | 285 +++++++++++++++++++++------------ lib/cf-dns.h | 36 ++--- lib/cf-https-connect.c | 38 +++-- lib/cf-https-connect.h | 2 + lib/cf-ip-happy.c | 16 +- lib/cf-setup.c | 2 +- lib/cfilters.c | 33 ++-- lib/cfilters.h | 8 +- lib/connect.c | 22 +-- lib/dnscache.c | 246 ++++++++++++++++++++-------- lib/dnscache.h | 15 +- lib/doh.c | 129 ++++++--------- lib/hostip.c | 105 +++++++----- lib/hostip.h | 20 ++- lib/httpsrr.c | 49 +++--- lib/httpsrr.h | 9 +- lib/multi.c | 5 +- lib/socks.c | 86 ++++------ lib/vquic/vquic.c | 24 ++- lib/vquic/vquic.h | 1 + lib/vtls/vtls.c | 15 +- tests/data/test2100 | 49 +++--- tests/data/test716 | 2 +- tests/http/test_06_eyeballs.py | 2 +- tests/http/test_22_httpsrr.py | 19 +++ tests/unit/unit1607.c | 6 +- tests/unit/unit1658.c | 3 +- 31 files changed, 809 insertions(+), 573 deletions(-) diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c index ef4bbcbcc6..106c7333f5 100644 --- a/lib/asyn-ares.c +++ b/lib/asyn-ares.c @@ -188,25 +188,6 @@ out: return result; } -/* - * async_ares_cleanup() cleans up async resolver data. - */ -static void async_ares_cleanup(struct Curl_resolv_async *async) -{ - struct async_ares_ctx *ares = &async->ares; - if(ares->res_A) { - Curl_freeaddrinfo(ares->res_A); - ares->res_A = NULL; - } - if(ares->res_AAAA) { - Curl_freeaddrinfo(ares->res_AAAA); - ares->res_AAAA = NULL; - } -#ifdef USE_HTTPSRR - Curl_httpsrr_cleanup(&ares->hinfo); -#endif -} - void Curl_async_ares_shutdown(struct Curl_easy *data, struct Curl_resolv_async *async) { @@ -227,7 +208,19 @@ void Curl_async_ares_destroy(struct Curl_easy *data, ares_destroy(ares->channel); ares->channel = NULL; } - async_ares_cleanup(async); + if(ares->res_A) { + Curl_freeaddrinfo(ares->res_A); + ares->res_A = NULL; + } + if(ares->res_AAAA) { + Curl_freeaddrinfo(ares->res_AAAA); + ares->res_AAAA = NULL; + } +#ifdef USE_HTTPSRR + curlx_safefree(ares->https_name); + Curl_httpsrr_destroy(ares->hinfo); + ares->hinfo = NULL; +#endif } CURLcode Curl_async_pollset(struct Curl_easy *data, @@ -259,6 +252,7 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, struct Curl_dns_entry **pdns) { struct async_ares_ctx *ares = &async->ares; + struct Curl_dns_entry *dns = NULL; CURLcode result = CURLE_OK; DEBUGASSERT(pdns); @@ -279,34 +273,31 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, /* all c-ares operations done, what is the result to report? */ result = ares->result; if(ares->ares_status == ARES_SUCCESS && !result) { - struct Curl_dns_entry *dns = - Curl_dnscache_mk_entry2(data, async->dns_queries, - &ares->res_AAAA, &ares->res_A, - async->hostname, async->port); - if(!dns) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } -#ifdef HTTPSRR_WORKS - if(async->dns_queries & CURL_DNSQ_HTTPS) { - if(ares->hinfo.complete) { - struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); - if(!lhrr) - result = CURLE_OUT_OF_MEMORY; - else - Curl_dns_entry_set_https_rr(dns, lhrr); + + if(CURL_DNSQ_IS_ADDR(async->dns_queries)) { + dns = Curl_dnsc_mk_addr2(data, async->dns_queries, + &ares->res_AAAA, &ares->res_A, + async->hostname, async->port); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + +#ifdef HTTPSRR_WORKS + if(!dns && (async->dns_queries & CURL_DNSQ_HTTPS)) { + dns = Curl_dnsc_mk_https(data, &ares->hinfo, + async->hostname, async->port); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - else - Curl_dns_entry_set_https_rr(dns, NULL); } #endif - if(!result) { - *pdns = dns; - } } /* if we have not found anything, report the proper * CURLE_COULDNT_RESOLVE_* code */ - if(!result && !*pdns) { + if(!result && !dns) { const char *msg = NULL; /* only an authoritative "does not exist" answer from every query may be cached as a negative entry, not transient failures like @@ -319,13 +310,16 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, result = Curl_async_failed(data, async, msg); } - CURL_TRC_DNS(data, "ares: is_resolved() result=%d, dns=%sfound", - (int)result, *pdns ? "" : "not "); - async_ares_cleanup(async); + CURL_TRC_DNS(data, "[%s] ares_take_result, result=%d, ares_result=%d, " + "ares_status=%d, dns=%sfound", + Curl_resolv_query_str(async->dns_queries), + (int)result, (int)ares->result, ares->ares_status, + dns ? "" : "not "); out: if(result != CURLE_AGAIN) ares->result = result; + *pdns = result ? NULL : dns; return result; } @@ -400,7 +394,7 @@ const struct Curl_https_rrinfo *Curl_async_get_https( struct Curl_resolv_async *async) { if(Curl_async_knows_https(data, async)) - return &async->ares.hinfo; + return async->ares.hinfo; return NULL; } @@ -626,6 +620,9 @@ static void async_ares_rr_done(void *user_data, ares_status_t status, async->dns_responses |= CURL_DNSQ_HTTPS; async->queries_ongoing--; async->done = !async->queries_ongoing; + ares->ares_status = status; + if((status != ARES_ENOTFOUND) && (status != ARES_ENODATA)) + ares->transient_err = TRUE; if((ARES_SUCCESS != status) || !dnsrec) return; ares->result = Curl_httpsrr_from_ares(dnsrec, &ares->hinfo); @@ -677,7 +674,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct ares_addrinfo_hints hints; memset(&hints, 0, sizeof(hints)); - CURL_TRC_DNS(data, "ares: query AAAA records for %s", async->hostname); + CURL_TRC_DNS(data, "[AAAA] ares: query records for %s", async->hostname); hints.ai_family = PF_INET6; hints.ai_socktype = socktype; hints.ai_flags = ARES_AI_NUMERICSERV; @@ -691,7 +688,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct ares_addrinfo_hints hints; memset(&hints, 0, sizeof(hints)); - CURL_TRC_DNS(data, "ares: query A records for %s", async->hostname); + CURL_TRC_DNS(data, "[A] ares: query records for %s", async->hostname); hints.ai_family = PF_INET; hints.ai_socktype = socktype; hints.ai_flags = ARES_AI_NUMERICSERV; @@ -701,20 +698,19 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, } #ifdef USE_HTTPSRR - memset(&ares->hinfo, 0, sizeof(ares->hinfo)); if(async->dns_queries & CURL_DNSQ_HTTPS) { - char *rrname = NULL; + ares->https_name = NULL; if(async->port != 443) { - rrname = curl_maprintf("_%d._https.%s", async->port, async->hostname); - if(!rrname) + ares->https_name = curl_maprintf("_%d._https.%s", + async->port, async->hostname); + if(!ares->https_name) return CURLE_OUT_OF_MEMORY; } - CURL_TRC_DNS(data, "ares: query HTTPS records for %s", - rrname ? rrname : async->hostname); - ares->hinfo.rrname = rrname; + CURL_TRC_DNS(data, "[HTTPS] ares: query records for %s", + ares->https_name ? ares->https_name : async->hostname); async->queries_ongoing++; ares_query_dnsrec(ares->channel, - rrname ? rrname : async->hostname, + ares->https_name ? ares->https_name : async->hostname, ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, async_ares_rr_done, async, NULL); } diff --git a/lib/asyn-base.c b/lib/asyn-base.c index 62cb0effe4..b140b0f5b9 100644 --- a/lib/asyn-base.c +++ b/lib/asyn-base.c @@ -256,9 +256,10 @@ CURLcode Curl_async_failed(struct Curl_easy *data, } #endif - failf(data, "Could not resolve %s: %s%s%s%s", - host_or_proxy, async->hostname, - detail ? " (" : "", detail ? detail : "", detail ? ")" : ""); + if(async->dns_queries & (CURL_DNSQ_A|CURL_DNSQ_AAAA)) + failf(data, "Could not resolve %s: %s%s%s%s", + host_or_proxy, async->hostname, + detail ? " (" : "", detail ? detail : "", detail ? ")" : ""); return result; } diff --git a/lib/asyn-thrdd.c b/lib/asyn-thrdd.c index 29e9b138d3..a3166f52f8 100644 --- a/lib/asyn-thrdd.c +++ b/lib/asyn-thrdd.c @@ -242,8 +242,7 @@ static CURLcode async_rr_start(struct Curl_easy *data, } #endif - memset(&thrdd->rr.hinfo, 0, sizeof(thrdd->rr.hinfo)); - thrdd->rr.hinfo.rrname = rrname; + thrdd->rr.https_name = rrname; async->queries_ongoing++; ares_query_dnsrec(thrdd->rr.channel, rrname ? rrname : async->hostname, ARES_CLASS_IN, @@ -291,7 +290,8 @@ void Curl_async_thrdd_destroy(struct Curl_easy *data, ares_destroy(async->thrdd.rr.channel); async->thrdd.rr.channel = NULL; } - Curl_httpsrr_cleanup(&async->thrdd.rr.hinfo); + curlx_safefree(async->thrdd.rr.https_name); + Curl_httpsrr_destroy(async->thrdd.rr.hinfo); #endif async_thrdd_item_destroy(async->thrdd.res_A); async->thrdd.res_A = NULL; @@ -668,6 +668,9 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, #endif out: + if(!async->queries_ongoing) + async->done = TRUE; + if(result) CURL_TRC_DNS(data, "error queueing query %s:%d -> %d", async->hostname, async->port, (int)result); @@ -769,7 +772,7 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, if((thrdd->res_A && thrdd->res_A->res) || (thrdd->res_AAAA && thrdd->res_AAAA->res)) { - dns = Curl_dnscache_mk_entry2( + dns = Curl_dnsc_mk_addr2( data, async->dns_queries, thrdd->res_A ? &thrdd->res_A->res : NULL, thrdd->res_AAAA ? &thrdd->res_AAAA->res : NULL, @@ -778,22 +781,19 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, result = CURLE_OUT_OF_MEMORY; goto out; } + } #ifdef USE_HTTPSRR_ARES - if(thrdd->rr.channel) { - struct Curl_https_rrinfo *lhrr = NULL; - if(thrdd->rr.hinfo.complete) { - lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo); - if(!lhrr) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - Curl_httpsrr_trace(data, lhrr); - Curl_dns_entry_set_https_rr(dns, lhrr); + if(!dns && thrdd->rr.channel) { + Curl_httpsrr_trace(data, thrdd->rr.hinfo); + dns = Curl_dnsc_mk_https(data, &thrdd->rr.hinfo, + async->hostname, async->port); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; } -#endif } +#endif if(dns) { *pdns = dns; @@ -864,7 +864,7 @@ const struct Curl_https_rrinfo *Curl_async_get_https( { #ifdef USE_HTTPSRR_ARES if(Curl_async_knows_https(data, async)) - return &async->thrdd.rr.hinfo; + return async->thrdd.rr.hinfo; #else (void)data; (void)async; diff --git a/lib/asyn.h b/lib/asyn.h index e4bc05e0dd..76996f888a 100644 --- a/lib/asyn.h +++ b/lib/asyn.h @@ -120,7 +120,8 @@ struct async_ares_ctx { CURLcode result; /* CURLE_OK or error handling response */ struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ #ifdef USE_HTTPSRR - struct Curl_https_rrinfo hinfo; + char *https_name; + struct Curl_https_rrinfo *hinfo; #endif BIT(transient_err); /* an A/AAAA query failed without the resolver answering that the name does not exist */ @@ -144,7 +145,8 @@ struct async_thrdd_ctx { #if defined(USE_HTTPSRR) && defined(USE_ARES) struct { ares_channel channel; - struct Curl_https_rrinfo hinfo; + char *https_name; + struct Curl_https_rrinfo *hinfo; } rr; #endif }; diff --git a/lib/cf-dns.c b/lib/cf-dns.c index 6388d65a9e..4152a00c17 100644 --- a/lib/cf-dns.c +++ b/lib/cf-dns.c @@ -30,6 +30,7 @@ #include "dnscache.h" #include "httpsrr.h" #include "curl_trc.h" +#include "multiif.h" #include "progress.h" #include "url.h" #include "cf-dns.h" @@ -44,7 +45,6 @@ struct cf_dns_ctx { uint8_t transport; BIT(started); BIT(announced); - BIT(complete_resolve); BIT(for_proxy); }; @@ -52,8 +52,7 @@ static struct cf_dns_ctx *cf_dns_ctx_create(struct Curl_easy *data, struct Curl_peer *peer, uint8_t dns_queries, uint8_t transport, - bool for_proxy, - bool complete_resolve) + bool for_proxy) { struct cf_dns_ctx *ctx; @@ -65,10 +64,10 @@ static struct cf_dns_ctx *cf_dns_ctx_create(struct Curl_easy *data, ctx->dns_queries = dns_queries; ctx->transport = transport; ctx->for_proxy = for_proxy; - ctx->complete_resolve = complete_resolve; - CURL_TRC_DNS(data, "created DNS filter for %s:%u, transport=%x, queries=%x", - peer->hostname, peer->port, ctx->transport, ctx->dns_queries); + CURL_TRC_DNS(data, "[%s] created DNS filter for %s:%u, transport=%x", + Curl_resolv_query_str(ctx->dns_queries), + peer->hostname, peer->port, ctx->transport); return ctx; } @@ -131,22 +130,28 @@ static void cf_dns_report(struct Curl_cfilter *cf, } else { curlx_dyn_init(&tmp, 1024); - infof(data, "Host %s:%u was resolved.", dns->hostname, dns->port); + if(CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + 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); + cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr); #endif - cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr); + 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"); + else if(ctx->dns_queries & CURL_DNSQ_HTTPS) { + if(!dns->hinfo) + infof(data, "HTTPS-RR %s:%u: -", dns->hostname, dns->port); + else if(!Curl_httpsrr_applicable(data, dns->hinfo)) + infof(data, "HTTPS-RR %s:%u: not applicable", + dns->hostname, dns->port); + else { + CURLcode result = Curl_httpsrr_print(&tmp, dns->hinfo); + if(!result) + infof(data, "HTTPS-RR %s:%u: %s", + dns->hostname, dns->port, curlx_dyn_ptr(&tmp)); + else + infof(data, "Error printing HTTPS-RR information"); + } } #endif curlx_dyn_free(&tmp); @@ -165,25 +170,40 @@ static CURLcode cf_dns_start(struct Curl_cfilter *cf, { struct cf_dns_ctx *ctx = cf->ctx; timediff_t timeout_ms = Curl_timeleft_ms(data); - CURLcode result; + CURLcode result = CURLE_OK; *pdns = NULL; - CURL_TRC_CF(data, cf, "cf_dns_start %s %s:%u", + CURL_TRC_CF(data, cf, "[%s] cf_dns_start %s %s:%u", + Curl_resolv_query_str(ctx->dns_queries), ctx->peer->unix_socket ? "unix-domain-socket" : "host", ctx->peer->hostname, ctx->peer->port); if(ctx->peer->unix_socket) - ctx->dns_queries = 0; - else if(Curl_is_ipv4addr(ctx->peer->hostname)) - ctx->dns_queries |= CURL_DNSQ_A; + ctx->dns_queries = CURL_DNSQ_A; /* treat it like an A resolve */ + + if(CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + if(Curl_is_ipv4addr(ctx->peer->hostname)) + ctx->dns_queries |= CURL_DNSQ_A; #ifdef USE_IPV6 - else if(ctx->peer->ipv6) - ctx->dns_queries |= CURL_DNSQ_AAAA; + else if(ctx->peer->ipv6) + ctx->dns_queries |= CURL_DNSQ_AAAA; #endif - result = Curl_resolv(data, ctx->peer, ctx->dns_queries, ctx->transport, - (bool)ctx->for_proxy, timeout_ms, - &ctx->resolv_id, pdns); + result = Curl_resolv(data, ctx->peer, ctx->dns_queries, ctx->transport, + (bool)ctx->for_proxy, timeout_ms, + &ctx->resolv_id, pdns); + } +#ifdef USE_HTTPSRR + else if(ctx->dns_queries == CURL_DNSQ_HTTPS) { + result = Curl_resolv_https(data, ctx->peer, (bool)ctx->for_proxy, + timeout_ms, &ctx->resolv_id, pdns); + } +#endif + else { + failf(data, "unsupported DNS queries %x", ctx->dns_queries); + return CURLE_FAILED_INIT; + } + DEBUGASSERT(!result || !*pdns); if(!result) { /* resolved right away, either sync or from dnscache */ DEBUGASSERT(*pdns); @@ -201,7 +221,8 @@ static CURLcode cf_dns_start(struct Curl_cfilter *cf, } else { DEBUGASSERT(result); - failf(data, "Could not resolve: %s", ctx->peer->hostname); + if(ctx->dns_queries & (CURL_DNSQ_A|CURL_DNSQ_AAAA)) + failf(data, "Could not resolve: %s", ctx->peer->hostname); return result; } } @@ -222,8 +243,6 @@ static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf, /* 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). */ @@ -246,6 +265,7 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, bool *done) { struct cf_dns_ctx *ctx = cf->ctx; + bool ip_query = (ctx->dns_queries & (CURL_DNSQ_A|CURL_DNSQ_AAAA)); if(cf->connected) { *done = TRUE; @@ -263,7 +283,8 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, Curl_resolv_take_result(data, ctx->resolv_id, &ctx->dns); } - if(ctx->resolv_result) { + if(ctx->resolv_result && ip_query) { + /* failing A|AAAA resolves is a hard failure. */ CURL_TRC_CF(data, cf, "error resolving: %d", (int)ctx->resolv_result); return ctx->resolv_result; } @@ -289,13 +310,9 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, DEBUGASSERT(sub_done); } - /* sub filter chain is connected */ + /* sub filter chain is connected, this means the filter has done + * its work and is connected as well, if it has results or not. */ 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; cf->connected = TRUE; Curl_resolv_destroy(data, ctx->resolv_id); @@ -315,8 +332,24 @@ static CURLcode cf_dns_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { #ifdef USE_CURL_ASYNC - if(!cf->connected) + if(!cf->connected) { + /* The pollset only works when we have started the resolving. + * Otherwise we might wait on the WAKEUP socketpair forever. + * Since DNS filters can be added in the middle of a connect + * attempt, they are not always started that way. */ + struct cf_dns_ctx *ctx = cf->ctx; + if(!ctx->started) { + CURL_TRC_CF(data, cf, "adjust_pollset, starting %s:%u queries=%s", + ctx->peer->hostname, ctx->peer->port, + Curl_resolv_query_str(ctx->dns_queries)); + ctx->started = TRUE; + ctx->resolv_result = cf_dns_start(cf, data, &ctx->dns); + if(ctx->resolv_result || ctx->dns) { + Curl_multi_mark_dirty(data); + } + } return Curl_resolv_pollset(data, ps); + } #else (void)cf; (void)data; @@ -351,7 +384,7 @@ static CURLcode cf_dns_cntrl(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_dns = { "DNS", - CF_TYPE_SETUP, + CF_TYPE_SETUP | CF_TYPE_DNS, CURL_LOG_LVL_NONE, cf_dns_destroy, cf_dns_connect, @@ -371,8 +404,7 @@ static CURLcode cf_dns_create(struct Curl_cfilter **pcf, struct Curl_peer *peer, uint8_t dns_queries, uint8_t transport, - bool for_proxy, - bool complete_resolve) + bool for_proxy) { struct Curl_cfilter *cf = NULL; struct cf_dns_ctx *ctx; @@ -380,7 +412,7 @@ static CURLcode cf_dns_create(struct Curl_cfilter **pcf, (void)data; ctx = cf_dns_ctx_create(data, peer, dns_queries, transport, - for_proxy, complete_resolve); + for_proxy); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -397,12 +429,12 @@ out: /* Adds a "resolv" filter at the top of the connection's filter chain. * The filter will resolve the peer on the first connect attempt. */ -CURLcode Curl_cf_dns_add(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - struct Curl_peer *peer, - uint8_t dns_queries, - uint8_t transport) +static CURLcode cf_dns_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) { struct Curl_cfilter *cf = NULL; bool for_proxy = FALSE; @@ -415,8 +447,7 @@ CURLcode Curl_cf_dns_add(struct Curl_easy *data, (peer == conn->http_proxy.peer); #endif - result = cf_dns_create(&cf, data, peer, dns_queries, transport, - for_proxy, FALSE); + result = cf_dns_create(&cf, data, peer, dns_queries, transport, for_proxy); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); @@ -430,18 +461,16 @@ out: * See socks.c on how this is used to make a non-blocking DNS * resolve during connect. */ -CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_peer *peer, - uint8_t transport, - bool complete_resolve) +static CURLcode cf_dns_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) { struct Curl_cfilter *cf; CURLcode result; - result = cf_dns_create(&cf, data, peer, dns_queries, transport, - FALSE, complete_resolve); + result = cf_dns_create(&cf, data, peer, dns_queries, transport, FALSE); if(result) return result; @@ -449,38 +478,93 @@ CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, return CURLE_OK; } -/* Return the resolv result from the first "resolv" filter, starting - * the given filter `cf` downwards. - */ -static CURLcode cf_dns_result(struct Curl_cfilter *cf, - struct Curl_peer *peer) +static CURLcode cf_dns_add_resolve(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) { - for(; cf; cf = cf->next) { + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + struct Curl_cfilter *cf_dns = NULL; + bool is_addr = CURL_DNSQ_IS_ADDR(dns_queries); + + if((dns_queries & CURL_DNSQ_HTTPS) && + Curl_is_ipaddr(peer->hostname)) { + dns_queries = (uint8_t)(dns_queries & ~CURL_DNSQ_HTTPS); + } + + for(; cf && dns_queries; cf = cf->next) { if(cf->cft == &Curl_cft_dns) { struct cf_dns_ctx *ctx = cf->ctx; + cf_dns = cf; if(Curl_peer_same_destination(ctx->peer, peer)) { - if(ctx->dns || ctx->resolv_result) - return ctx->resolv_result; - return CURLE_AGAIN; + /* subtract queries already being scheduled/ongoing */ + dns_queries = (uint8_t)(~ctx->dns_queries & dns_queries); + if(!dns_queries) { /* already there */ + return CURLE_OK; + } + else if(is_addr && !ctx->started && + CURL_DNSQ_IS_ADDR(ctx->dns_queries)) { + ctx->dns_queries |= dns_queries; + CURL_TRC_DNS(data, "[%s] added queries=%s for %s:%u", + Curl_resolv_query_str(ctx->dns_queries), + Curl_resolv_query_str(dns_queries), + peer->hostname, peer->port); + return CURLE_OK; + } } - return CURLE_OK; /* ok, but no results */ } } - return CURLE_FAILED_INIT; + + if(dns_queries) { + /* No existing filter is handling these, add a new DNS filter + * (after the last one if there was one already. FCFS. */ + if(cf_dns) + return cf_dns_insert_after(cf_dns, data, peer, dns_queries, transport); + else + return cf_dns_add(data, conn, sockindex, peer, dns_queries, transport); + } + return CURLE_OK; } -/* Return the result of the DNS resolution for peer. Searches for a "resolv" - * filter from the top of the filter chain down. Returns +CURLcode Curl_conn_dns_add_addr_resolve(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport) +{ + /* should only have address query bits */ + DEBUGASSERT(!(dns_queries & ~CURL_DNSQ_ADDR)); + return cf_dns_add_resolve(data, conn, sockindex, peer, + (dns_queries & CURL_DNSQ_ADDR), transport); +} + +/* Return the result of the DNS address resolution for peer. + * Searches for a DNS filter from the top of the filter chain down. Returns * - CURLE_AGAIN when not done yet * - CURLE_OK when DNS was successfully resolved * - CURLR_FAILED_INIT when no resolv filter was found * - error returned by the DNS resolv */ -CURLcode Curl_conn_dns_result(struct connectdata *conn, - int sockindex, - struct Curl_peer *peer) +CURLcode Curl_conn_dns_addr_result(struct connectdata *conn, + int sockindex, + struct Curl_peer *peer) { - return cf_dns_result(conn->cfilter[sockindex], peer); + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_dns) { + struct cf_dns_ctx *ctx = cf->ctx; + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & (CURL_DNSQ_A|CURL_DNSQ_AAAA))) { + if(ctx->dns || ctx->resolv_result) + return ctx->resolv_result; + return CURLE_AGAIN; + } + } + } + return CURLE_FAILED_INIT; /* no one is resolving */ } static const struct Curl_addrinfo *cf_dns_get_nth_ai( @@ -508,20 +592,25 @@ static const struct Curl_addrinfo *cf_dns_get_nth_ai( } /* Return the addrinfo at `index` for the given `family` from the - * first "resolve" filter underneath `cf`. If the DNS resolving is + * first "resolve" filter at the connection. If the DNS resolving is * not done yet or if no address for the family exists, returns NULL. */ -const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct Curl_peer *peer, - int ai_family, - unsigned int index) +const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data, + struct Curl_peer *peer, + int sockindex, + int ai_family, + unsigned int index) { - (void)data; + struct connectdata *conn = data->conn; + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + for(; cf; cf = cf->next) { if(cf->cft == &Curl_cft_dns) { struct cf_dns_ctx *ctx = cf->ctx; - if(Curl_peer_same_destination(ctx->peer, peer)) { + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & (CURL_DNSQ_A|CURL_DNSQ_AAAA))) { + CURL_TRC_CF(data, cf, "get %uth result for %s:%u, family=%d, dns=%d", + index, peer->hostname, peer->port, ai_family, !!ctx->dns); if(ctx->resolv_result) return NULL; else if(ctx->dns) @@ -534,22 +623,16 @@ const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf, return NULL; } -/* Return the addrinfo at `index` for the given `family` from the - * first "resolve" filter at the connection. If the DNS resolving is - * not done yet or if no address for the family exists, returns NULL. - */ -const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data, - struct Curl_peer *peer, - int sockindex, - int ai_family, - unsigned int index) +#ifdef USE_HTTPSRR +CURLcode Curl_conn_dns_add_https_resolve(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + struct Curl_peer *peer) { - struct connectdata *conn = data->conn; - return Curl_cf_dns_get_ai(conn->cfilter[sockindex], data, peer, - ai_family, index); + return cf_dns_add_resolve(data, conn, sockindex, peer, + CURL_DNSQ_HTTPS, conn->transport_wanted); } -#ifdef USE_HTTPSRR /* Return the HTTPS-RR info from the first "resolve" filter at the * connection. If the DNS resolving is not done yet or if there * is no HTTPS-RR info, returns NULL. @@ -563,7 +646,8 @@ Curl_conn_dns_get_https(struct Curl_easy *data, for(; cf; cf = cf->next) { if(cf->cft == &Curl_cft_dns) { struct cf_dns_ctx *ctx = cf->ctx; - if(Curl_peer_same_destination(ctx->peer, peer)) { + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & CURL_DNSQ_HTTPS)) { if(ctx->dns) return ctx->dns->hinfo; else @@ -582,7 +666,8 @@ bool Curl_conn_dns_resolved_https(struct Curl_easy *data, for(; cf; cf = cf->next) { if(cf->cft == &Curl_cft_dns) { struct cf_dns_ctx *ctx = cf->ctx; - if(Curl_peer_same_destination(ctx->peer, peer)) { + if(Curl_peer_same_destination(ctx->peer, peer) && + (ctx->dns_queries & CURL_DNSQ_HTTPS)) { if(ctx->dns) return TRUE; else diff --git a/lib/cf-dns.h b/lib/cf-dns.h index 891b1efea6..ef90d711e2 100644 --- a/lib/cf-dns.h +++ b/lib/cf-dns.h @@ -31,23 +31,16 @@ struct Curl_dns_entry; struct Curl_addrinfo; struct Curl_peer; -CURLcode Curl_cf_dns_add(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - struct Curl_peer *peer, - uint8_t dns_queries, - uint8_t transport); +CURLcode Curl_conn_dns_add_addr_resolve(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + struct Curl_peer *peer, + uint8_t dns_queries, + uint8_t transport); -CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_peer *peer, - uint8_t transport, - bool complete_resolve); - -CURLcode Curl_conn_dns_result(struct connectdata *conn, - int sockindex, - struct Curl_peer *peer); +CURLcode Curl_conn_dns_addr_result(struct connectdata *conn, + int sockindex, + struct Curl_peer *peer); const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data, struct Curl_peer *peer, @@ -55,13 +48,12 @@ const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data, int ai_family, unsigned int index); -const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct Curl_peer *peer, - int ai_family, - unsigned int index); - #ifdef USE_HTTPSRR +CURLcode Curl_conn_dns_add_https_resolve(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + struct Curl_peer *peer); + const struct Curl_https_rrinfo * Curl_conn_dns_get_https(struct Curl_easy *data, int sockindex, diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index 1a2e966ef6..c39f59f2ee 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -110,6 +110,7 @@ static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b, struct cf_hc_ctx { cf_hc_state state; + struct Curl_peer *destination; /* who we ultimately want to talk to */ struct curltime started; /* when connect started */ CURLcode result; /* overall result */ CURLcode check_h3_result; @@ -123,21 +124,14 @@ struct cf_hc_ctx { BIT(ballers_complete); }; -static void cf_hc_ctx_close(struct Curl_easy *data, - struct cf_hc_ctx *ctx) +static void cf_hc_ctx_destroy(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_discard(&ctx->ballers[i], data); - } -} - -static void cf_hc_ctx_destroy(struct Curl_easy *data, - struct cf_hc_ctx *ctx) -{ - if(ctx) { - cf_hc_ctx_close(data, ctx); + Curl_peer_unlink(&ctx->destination); curlx_free(ctx); } } @@ -222,7 +216,6 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, 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); @@ -296,6 +289,7 @@ static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf, enum alpnid not_this_one) { #ifdef USE_HTTPSRR + struct cf_hc_ctx *ctx = cf->ctx; /* 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 @@ -304,9 +298,9 @@ static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf, size_t i; /* Do we have HTTPS-RR information? */ - rr = Curl_conn_dns_get_https( - data, cf->sockindex, Curl_conn_get_destination(cf->conn, cf->sockindex)); + rr = Curl_conn_dns_get_https(data, cf->sockindex, ctx->destination); + CURL_TRC_CF(data, cf, "HTTPS-RR %savailable", rr ? "" : "not "); /* 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) { @@ -495,7 +489,7 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, if(!ctx->httpsrr_resolved) { ctx->httpsrr_resolved = Curl_conn_dns_resolved_https( - data, cf->sockindex, Curl_conn_get_destination(cf->conn, cf->sockindex)); + data, cf->sockindex, ctx->destination); #ifdef DEBUGBUILD if(!ctx->httpsrr_resolved && getenv("CURL_DBG_AWAIT_HTTPSRR")) { CURL_TRC_CF(data, cf, "awaiting HTTPS-RR"); @@ -744,7 +738,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_HTTPSRR, + CF_TYPE_SETUP, CURL_LOG_LVL_NONE, cf_hc_destroy, cf_hc_connect, @@ -761,6 +755,7 @@ struct Curl_cftype Curl_cft_http_connect = { static CURLcode cf_hc_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *destination, uint8_t def_transport) { struct Curl_cfilter *cf = NULL; @@ -772,6 +767,7 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } + Curl_peer_link(&ctx->destination, destination); ctx->def_transport = def_transport; ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout; ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2; @@ -788,6 +784,7 @@ out: } static CURLcode cf_hc_add(struct Curl_easy *data, + struct Curl_peer *destination, struct connectdata *conn, int sockindex, uint8_t def_transport) @@ -796,15 +793,21 @@ static CURLcode cf_hc_add(struct Curl_easy *data, CURLcode result = CURLE_OK; DEBUGASSERT(data); - result = cf_hc_create(&cf, data, def_transport); + result = cf_hc_create(&cf, data, destination, def_transport); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); + +#ifdef USE_HTTPSRR + result = Curl_conn_dns_add_https_resolve(data, cf->conn, cf->sockindex, + destination); +#endif out: return result; } CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct Curl_peer *destination, struct connectdata *conn, int sockindex) { @@ -821,7 +824,8 @@ CURLcode Curl_cf_https_setup(struct Curl_easy *data, !conn->bits.tls_enable_alpn) goto out; - result = cf_hc_add(data, conn, sockindex, conn->transport_wanted); + result = cf_hc_add(data, destination, conn, sockindex, + conn->transport_wanted); out: return result; diff --git a/lib/cf-https-connect.h b/lib/cf-https-connect.h index 3160c0382a..caa2b17e7f 100644 --- a/lib/cf-https-connect.h +++ b/lib/cf-https-connect.h @@ -31,10 +31,12 @@ struct Curl_cfilter; struct Curl_easy; struct connectdata; struct Curl_cftype; +struct Curl_peer; extern struct Curl_cftype Curl_cft_http_connect; CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct Curl_peer *destination, struct connectdata *conn, int sockindex); diff --git a/lib/cf-ip-happy.c b/lib/cf-ip-happy.c index 91ff029f6f..794d5e4cb9 100644 --- a/lib/cf-ip-happy.c +++ b/lib/cf-ip-happy.c @@ -857,7 +857,8 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, *done = FALSE; if(!ctx->dns_resolved) { - result = Curl_conn_dns_result(cf->conn, cf->sockindex, ctx->ballers.peer); + result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex, + ctx->ballers.peer); if(!result) ctx->dns_resolved = TRUE; else if(result == CURLE_AGAIN) { @@ -888,10 +889,6 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, 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 */ @@ -900,13 +897,16 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, 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); + ctx->ballers.peer->hostname, + ipquad.remote_ip, ipquad.remote_port); } } #endif + 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); data->info.numconnects++; /* to track the # of connections made */ } break; diff --git a/lib/cf-setup.c b/lib/cf-setup.c index b073989a4e..7699ef5dfe 100644 --- a/lib/cf-setup.c +++ b/lib/cf-setup.c @@ -251,7 +251,7 @@ static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf, (int)result); return result; } - result = Curl_cf_quic_insert_after(cf, origin, peer); + result = Curl_cf_quic_insert_after(cf, data, origin, peer); if(result) { CURL_TRC_CF(data, cf, "adding QUIC filter failed -> %d", (int)result); return result; diff --git a/lib/cfilters.c b/lib/cfilters.c index c5ee51a1b4..238698d332 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -644,17 +644,6 @@ 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) { @@ -728,17 +717,17 @@ CURLcode Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { CURLcode result = CURLE_OK; - /* Get the lowest not-connected filter, if there are any */ - while(cf && !cf->connected && cf->next && !cf->next->connected) - cf = cf->next; - /* Skip all filters that have already shut down */ - while(cf && cf->shutdown) - cf = cf->next; - /* From there on, give all filters a chance to adjust the pollset. - * Lower filters are called later, so they may override */ - while(cf && !result) { - result = cf->cft->adjust_pollset(cf, data, ps); - cf = cf->next; + /* Go through all filters, top to bottom, and let them manage the pollset + * - connected filters can do so + * - CF_TYPE_DNS filters can + * - unconnected filters without next or connect next can + */ + for(; cf && !result; cf = cf->next) { + if(cf->shutdown) + continue; + if(cf->connected || (cf->cft->flags & CF_TYPE_DNS) || + !cf->next || cf->next->connected) + result = cf->cft->adjust_pollset(cf, data, ps); } return result; } diff --git a/lib/cfilters.h b/lib/cfilters.h index b1aad029e0..f55a572e2e 100644 --- a/lib/cfilters.h +++ b/lib/cfilters.h @@ -197,7 +197,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 + * CF_TYPE_DNS filter managing DNS resolve operations */ #define CF_TYPE_IP_CONNECT (1 << 0) #define CF_TYPE_SSL (1 << 1) @@ -205,7 +205,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) +#define CF_TYPE_DNS (1 << 6) /* A connection filter type, e.g. specific implementation. */ struct Curl_cftype { @@ -352,10 +352,6 @@ 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/connect.c b/lib/connect.c index 86e97b955a..ca81849ce7 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -237,22 +237,23 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, int sockindex, int ssl_mode) { + struct Curl_peer *first_peer = Curl_conn_get_first_peer(conn, sockindex); CURLcode result = CURLE_OK; - struct Curl_peer *peer = Curl_conn_get_first_peer(conn, sockindex); uint8_t dns_queries; DEBUGASSERT(data); DEBUGASSERT(conn->scheme); DEBUGASSERT(!conn->cfilter[sockindex]); - if(!peer) + if(!first_peer) return CURLE_FAILED_INIT; #ifndef CURL_DISABLE_HTTP if(!conn->cfilter[sockindex] && conn->scheme->protocol == CURLPROTO_HTTPS) { DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE); - result = Curl_cf_https_setup(data, conn, sockindex); + result = Curl_cf_https_setup( + data, Curl_conn_get_destination(conn, sockindex), conn, sockindex); if(result) goto out; } @@ -266,13 +267,16 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, goto out; } + /* Whatever the filter chain will be in the end, it will need the + * resolving of `first_peer`. Add that now so the resolve is started + * right away. */ dns_queries = Curl_resolv_dns_queries(data, conn->ip_version); -#ifdef USE_HTTPSRR - if(sockindex == FIRSTSOCKET) - dns_queries |= CURL_DNSQ_HTTPS; -#endif - result = Curl_cf_dns_add(data, conn, sockindex, peer, dns_queries, - conn->transport_wanted); + result = Curl_conn_dns_add_addr_resolve(data, conn, sockindex, + first_peer, dns_queries, + conn->transport_wanted); + if(result) + goto out; + DEBUGASSERT(conn->cfilter[sockindex]); out: return result; diff --git a/lib/dnscache.c b/lib/dnscache.c index 9c8341e56f..9f435cc0cd 100644 --- a/lib/dnscache.c +++ b/lib/dnscache.c @@ -64,10 +64,7 @@ static void dnscache_entry_free(struct Curl_dns_entry *dns) { Curl_freeaddrinfo(dns->addr); #ifdef USE_HTTPSRR - if(dns->hinfo) { - Curl_httpsrr_cleanup(dns->hinfo); - curlx_free(dns->hinfo); - } + Curl_httpsrr_destroy(dns->hinfo); #endif curlx_free(dns); } @@ -76,17 +73,19 @@ static void dnscache_entry_free(struct Curl_dns_entry *dns) * Create a hostcache id string for the provided host + port, to be used by * the DNS caching. Without alloc. Return length of the id string. */ -static size_t create_dnscache_id(const char *name, +static size_t create_dnscache_id(char type, const char *name, size_t nlen, /* 0 or actual name length */ - uint16_t port, char *ptr, size_t buflen) + uint16_t port, + char *buf, size_t buflen) { size_t len = nlen ? nlen : strlen(name); DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); - if(len > (buflen - 7)) - len = buflen - 7; + if(len > (buflen - 8)) + len = buflen - 8; /* store and lower case the name */ - Curl_strntolower(ptr, name, len); - return curl_msnprintf(&ptr[len], 7, ":%u", port) + len; + buf[0] = type; + Curl_strntolower(buf + 1, name, len); + return curl_msnprintf(&buf[len + 1], 7, ":%u", port) + len + 1; } struct dnscache_prune_data { @@ -219,6 +218,8 @@ static CURLcode fetch_addr(struct Curl_easy *data, struct Curl_dns_entry *dns = NULL; char entry_id[MAX_HOSTCACHE_LEN]; size_t entry_len; + char entry_type = CURL_DNSQ_IS_ADDR(dns_queries) ? + CURL_DNST_ADDR : CURL_DNST_HTTPS; CURLcode result = CURLE_OK; *pdns = NULL; @@ -226,15 +227,16 @@ static CURLcode fetch_addr(struct Curl_easy *data, return CURLE_OK; /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(hostname, 0, port, + entry_len = create_dnscache_id(entry_type, hostname, 0, port, entry_id, sizeof(entry_id)); /* See if it is already in our dns cache */ dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); /* No entry found in cache, check if we might have a wildcard entry */ - if(!dns && data->state.wildcard_resolve) { - entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id)); + if(!dns && data->state.wildcard_resolve && CURL_DNSQ_IS_ADDR(dns_queries)) { + entry_len = create_dnscache_id(CURL_DNST_ADDR, "*", 1, port, + entry_id, sizeof(entry_id)); /* See if it is already in our dns cache */ dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); @@ -255,9 +257,11 @@ static CURLcode fetch_addr(struct Curl_easy *data, } } - if(dns) { - if((dns->dns_queries & dns_queries) != dns_queries) { - /* The entry does not cover all wanted DNS queries, a miss. */ + /* We need to cache address information and HTTPS-RR separately. */ + if(dns && CURL_DNSQ_IS_ADDR(dns_queries)) { + if((uint8_t)(dns->dns_queries & dns_queries) != + (uint8_t)(dns_queries & CURL_DNSQ_ADDR)) { + /* The entry does not cover all wanted address queries, a miss. */ dns = NULL; } else if(!(dns->dns_responses & dns_queries)) { @@ -267,12 +271,12 @@ static CURLcode fetch_addr(struct Curl_easy *data, dns = NULL; result = CURLE_COULDNT_RESOLVE_HOST; } + else if(dns && !dns->addr) { /* negative entry */ + dns = NULL; + result = CURLE_COULDNT_RESOLVE_HOST; + } } - if(dns && !dns->addr) { /* negative entry */ - dns = NULL; - result = CURLE_COULDNT_RESOLVE_HOST; - } *pdns = dns; return result; } @@ -311,6 +315,9 @@ CURLcode Curl_dnscache_get(struct Curl_easy *data, } dnscache_unlock(data, dnscache); + CURL_TRC_DNS(data, "cache lookup %s:%u queries=%s -> %d %sfound", + hostname, port, Curl_resolv_query_str(dns_queries), + (int)result, dns ? "" : "not "); *pentry = dns; return result; } @@ -407,11 +414,8 @@ static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, return FALSE; } -static struct Curl_dns_entry *dnscache_entry_create( +static struct Curl_dns_entry *dnsc_entry_create( struct Curl_easy *data, - uint8_t dns_queries, - struct Curl_addrinfo **paddr1, - struct Curl_addrinfo **paddr2, const char *hostname, size_t hostlen, uint16_t port, @@ -425,7 +429,6 @@ static struct Curl_dns_entry *dnscache_entry_create( goto out; dns->refcount = 1; /* the cache has the first reference */ - dns->dns_queries = dns_queries; dns->port = port; if(hostlen) memcpy(dns->hostname, hostname, hostlen); @@ -438,6 +441,29 @@ static struct Curl_dns_entry *dnscache_entry_create( dns->timestamp = *Curl_pgrs_now(data); } +out: + return dns; +} + +static struct Curl_dns_entry * +dnsc_entry_assign_addr( + struct Curl_easy *data, + struct Curl_dns_entry *dns, + uint8_t dns_queries, + struct Curl_addrinfo **paddr1, + struct Curl_addrinfo **paddr2) +{ + if(!dns) + goto out; + /* only do this when this is the only reference */ + DEBUGASSERT(dns->refcount == 1); + DEBUGASSERT(dns->type == CURL_DNST_INIT); + + dns->type = CURL_DNST_ADDR; + /* queries should only be about addresses */ + DEBUGASSERT(!(dns_queries & ~CURL_DNSQ_ADDR)); + dns->dns_queries = (dns_queries & CURL_DNSQ_ADDR); + /* Take the given address lists into the entry */ if(paddr1 && *paddr1) { dns->addr = *paddr1; @@ -488,67 +514,129 @@ out: return dns; } -struct Curl_dns_entry *Curl_dnscache_mk_entry(struct Curl_easy *data, +struct Curl_dns_entry *Curl_dnsc_mk_addr(struct Curl_easy *data, uint8_t dns_queries, struct Curl_addrinfo **paddr, const char *hostname, uint16_t port) { - return dnscache_entry_create(data, dns_queries, paddr, NULL, hostname, - hostname ? strlen(hostname) : 0, - port, FALSE); + struct Curl_dns_entry *dns = dnsc_entry_create( + data, hostname, hostname ? strlen(hostname) : 0, port, FALSE); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); + return dns; } -struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data, +struct Curl_dns_entry *Curl_dnsc_mk_addr2(struct Curl_easy *data, uint8_t dns_queries, struct Curl_addrinfo **paddr1, struct Curl_addrinfo **paddr2, const char *hostname, uint16_t port) { - return dnscache_entry_create(data, dns_queries, paddr1, paddr2, hostname, - hostname ? strlen(hostname) : 0, - port, FALSE); + struct Curl_dns_entry *dns = dnsc_entry_create( + data, hostname, hostname ? strlen(hostname) : 0, port, FALSE); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr1, paddr2); + return dns; } #ifdef USE_HTTPSRR -void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns, - struct Curl_https_rrinfo *hinfo) +static struct Curl_dns_entry * +dnsc_entry_assign_https(struct Curl_dns_entry *dns, + struct Curl_https_rrinfo **phinfo) { + if(!dns) + goto out; /* only do this when this is the only reference */ DEBUGASSERT(dns->refcount == 1); - /* it should have been in the queries */ - DEBUGASSERT(dns->dns_queries & CURL_DNSQ_HTTPS); + DEBUGASSERT(dns->type == CURL_DNST_INIT); + if(dns->hinfo) { - Curl_httpsrr_cleanup(dns->hinfo); - curlx_free(dns->hinfo); + Curl_httpsrr_destroy(dns->hinfo); + dns->hinfo = NULL; } - dns->hinfo = hinfo; - dns->dns_responses |= CURL_DNSQ_HTTPS; + dns->type = CURL_DNST_HTTPS; + dns->dns_responses = dns->dns_queries = CURL_DNSQ_HTTPS; + if(phinfo) { + dns->hinfo = *phinfo; + *phinfo = NULL; + } +out: + if(phinfo && *phinfo) { + Curl_httpsrr_destroy(*phinfo); + *phinfo = NULL; + } + return dns; } + +struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, + struct Curl_https_rrinfo **phinfo, + const char *hostname, + uint16_t port) +{ + struct Curl_dns_entry *dns = dnsc_entry_create( + data, hostname, hostname ? strlen(hostname) : 0, port, FALSE); + dns = dnsc_entry_assign_https(dns, phinfo); + return dns; +} + +static struct Curl_dns_entry *dnsc_add_https(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + struct Curl_https_rrinfo **phinfo, + const char *hostname, + size_t hlen, + uint16_t port, + bool permanent) +{ + char entry_id[MAX_HOSTCACHE_LEN]; + size_t entry_len; + struct Curl_dns_entry *dns, *dns2; + + dns = dnsc_entry_create(data, hostname, hostname ? strlen(hostname) : 0, + port, permanent); + dns = dnsc_entry_assign_https(dns, phinfo); + if(!dns) + return NULL; + + /* Create an entry id, based upon the hostname and port */ + entry_len = create_dnscache_id(CURL_DNST_HTTPS, hostname, hlen, port, + entry_id, sizeof(entry_id)); + + /* Store the resolved data in our DNS cache. */ + dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1, + (void *)dns); + if(!dns2) { + dnscache_entry_free(dns); + return NULL; + } + + dns = dns2; + dns->refcount++; /* mark entry as in-use */ + return dns; +} + #endif /* USE_HTTPSRR */ -static struct Curl_dns_entry *dnscache_add_addr(struct Curl_easy *data, - struct Curl_dnscache *dnscache, - uint8_t dns_queries, - struct Curl_addrinfo **paddr, - const char *hostname, - size_t hlen, - uint16_t port, - bool permanent) +static struct Curl_dns_entry *dnsc_add_addr(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + uint8_t dns_queries, + struct Curl_addrinfo **paddr, + const char *hostname, + size_t hlen, + uint16_t port, + bool permanent) { char entry_id[MAX_HOSTCACHE_LEN]; size_t entry_len; struct Curl_dns_entry *dns; struct Curl_dns_entry *dns2; - dns = dnscache_entry_create(data, dns_queries, paddr, NULL, - hostname, hlen, port, permanent); + dns = dnsc_entry_create(data, hostname, hlen, port, permanent); + dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); if(!dns) return NULL; /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(hostname, hlen, port, + entry_len = create_dnscache_id(CURL_DNST_ADDR, hostname, hlen, port, entry_id, sizeof(entry_id)); /* Store the resolved data in our DNS cache. */ @@ -573,8 +661,12 @@ CURLcode Curl_dnscache_add(struct Curl_easy *data, if(!dnscache) return CURLE_FAILED_INIT; + if(!entry || (entry->type == CURL_DNST_INIT)) + return CURLE_BAD_FUNCTION_ARGUMENT; + /* Create an entry id, based upon the hostname and port */ - idlen = create_dnscache_id(entry->hostname, 0, entry->port, id, sizeof(id)); + idlen = create_dnscache_id(entry->type, entry->hostname, 0, entry->port, + id, sizeof(id)); /* Store the resolved data in our DNS cache and up ref count */ dnscache_lock(data, dnscache); @@ -584,6 +676,9 @@ CURLcode Curl_dnscache_add(struct Curl_easy *data, } entry->refcount++; dnscache_unlock(data, dnscache); + CURL_TRC_DNS(data, "cached entry for %s:%u queries=%s", + entry->hostname, entry->port, + Curl_resolv_query_str(entry->dns_queries)); return CURLE_OK; } @@ -593,16 +688,35 @@ CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, uint16_t port) { struct Curl_dnscache *dnscache = dnscache_get(data); - struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns = NULL; + CURLcode result = CURLE_OK; + DEBUGASSERT(dnscache); if(!dnscache) return CURLE_FAILED_INIT; dnscache_lock(data, dnscache); - /* put this new host in the cache */ - dns = dnscache_add_addr(data, dnscache, dns_queries, NULL, - host, strlen(host), port, FALSE); + if(dns_queries & CURL_DNSQ_ADDR) { + /* put this new host in the cache */ + dns = dnsc_add_addr(data, dnscache, dns_queries, NULL, + host, strlen(host), port, FALSE); + if(!dns) + result = CURLE_OUT_OF_MEMORY; + } +#ifdef USE_HTTPSRR + else if(dns_queries == CURL_DNSQ_HTTPS) { + dns = dnsc_add_https(data, dnscache, NULL, + host, strlen(host), port, FALSE); + if(!dns) + result = CURLE_OUT_OF_MEMORY; + } +#endif + else { + /* a query we do not know, just cache nothing */ + DEBUGASSERT(0); + } + if(dns) { /* release the returned reference; the cache itself will keep the * entry alive: */ @@ -610,10 +724,9 @@ CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, dnscache_unlock(data, dnscache); CURL_TRC_DNS(data, "cache negative name resolve for %s:%d type=%s", host, port, Curl_resolv_query_str(dns_queries)); - return CURLE_OK; } dnscache_unlock(data, dnscache); - return CURLE_OUT_OF_MEMORY; + return result; } /* @@ -697,7 +810,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) if(!curlx_str_number(&host, &num, 0xffff)) { /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(curlx_str(&source), + entry_len = create_dnscache_id(CURL_DNST_ADDR, curlx_str(&source), curlx_strlen(&source), (uint16_t)num, entry_id, sizeof(entry_id)); dnscache_lock(data, dnscache); @@ -802,7 +915,8 @@ err: } /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source), + entry_len = create_dnscache_id(CURL_DNST_ADDR, + curlx_str(&source), curlx_strlen(&source), port, entry_id, sizeof(entry_id)); dnscache_lock(data, dnscache); @@ -828,10 +942,10 @@ err: Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); } - /* put this new host in the cache, an override for ALL dns queries */ - dns = dnscache_add_addr(data, dnscache, CURL_DNSQ_ALL, - &head, curlx_str(&source), - curlx_strlen(&source), port, permanent); + /* put this new host in the cache, override all address queries */ + dns = dnsc_add_addr(data, dnscache, CURL_DNSQ_ADDR, + &head, curlx_str(&source), + curlx_strlen(&source), port, permanent); if(dns) /* release the returned reference; the cache itself will keep the * entry alive: */ @@ -842,7 +956,7 @@ err: if(!dns) return CURLE_OUT_OF_MEMORY; - infof(data, "Added %.*s:%u:%s to DNS cache%s", + infof(data, "[DNS] added %.*s:%u:%s to cache%s", (int)curlx_strlen(&source), curlx_str(&source), port, addresses, permanent ? "" : " (non-permanent)"); diff --git a/lib/dnscache.h b/lib/dnscache.h index 9239977cf8..c029eb9d30 100644 --- a/lib/dnscache.h +++ b/lib/dnscache.h @@ -36,6 +36,10 @@ struct easy_pollset; struct Curl_https_rrinfo; struct Curl_multi; +#define CURL_DNST_INIT '\0' +#define CURL_DNST_ADDR 'A' +#define CURL_DNST_HTTPS 'H' + struct Curl_dns_entry { struct Curl_addrinfo *addr; #ifdef USE_HTTPSRR @@ -47,6 +51,7 @@ struct Curl_dns_entry { uint32_t refcount; /* hostname port number that resolved to addr. */ uint16_t port; + char type; /* CURL_DNST_ADDR or CURL_DNST_HTTPS */ uint8_t dns_queries; /* CURL_DNSQ_* type of queries performed for this */ uint8_t dns_responses; /* CURL_DNSQ_* type this entry has responses for */ /* hostname that resolved to addr. may be NULL (Unix domain sockets). */ @@ -62,13 +67,13 @@ struct Curl_dns_entry { * * Returns entry or NULL on OOM. */ -struct Curl_dns_entry *Curl_dnscache_mk_entry(struct Curl_easy *data, +struct Curl_dns_entry *Curl_dnsc_mk_addr(struct Curl_easy *data, uint8_t dns_queries, struct Curl_addrinfo **paddr, const char *hostname, uint16_t port); -struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data, +struct Curl_dns_entry *Curl_dnsc_mk_addr2(struct Curl_easy *data, uint8_t dns_queries, struct Curl_addrinfo **paddr1, struct Curl_addrinfo **paddr2, @@ -76,8 +81,10 @@ struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data, uint16_t port); #ifdef USE_HTTPSRR -void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns, - struct Curl_https_rrinfo *hinfo); +struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, + struct Curl_https_rrinfo **phinfo, + const char *hostname, + uint16_t port); #endif /* USE_HTTPSRR */ /* unlink a dns entry, frees all resources if it was the last reference. diff --git a/lib/doh.c b/lib/doh.c index b1f138f32c..f336eb5003 100644 --- a/lib/doh.c +++ b/lib/doh.c @@ -1189,47 +1189,11 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, *hrr = lhrr; return CURLE_OK; err: - Curl_httpsrr_cleanup(lhrr); - curlx_safefree(lhrr); + Curl_httpsrr_destroy(lhrr); return result; } -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) -static void doh_print_httpsrr(struct Curl_easy *data, - struct Curl_https_rrinfo *hrr) -{ - DEBUGASSERT(hrr); - infof(data, "HTTPS RR: priority %d, target: %s", hrr->priority, hrr->target); - if(hrr->alpns[0] != ALPN_none) - infof(data, "HTTPS RR: alpns %u %u %u %u", - hrr->alpns[0], hrr->alpns[1], hrr->alpns[2], hrr->alpns[3]); - else - infof(data, "HTTPS RR: no alpns"); - if(hrr->no_def_alpn) - infof(data, "HTTPS RR: no_def_alpn set"); - else - infof(data, "HTTPS RR: no_def_alpn not set"); - if(hrr->ipv4hints) { - doh_print_buf(data, "HTTPS RR: ipv4hints", - hrr->ipv4hints, hrr->ipv4hints_len); - } - else - infof(data, "HTTPS RR: no ipv4hints"); - if(hrr->echconfiglist) { - doh_print_buf(data, "HTTPS RR: ECHConfigList", - hrr->echconfiglist, hrr->echconfiglist_len); - } - else - infof(data, "HTTPS RR: no ECHConfigList"); - if(hrr->ipv6hints) { - doh_print_buf(data, "HTTPS RR: ipv6hint", - hrr->ipv6hints, hrr->ipv6hints_len); - } - else - infof(data, "HTTPS RR: no ipv6hints"); -} -# endif -#endif +#endif /* USE_HTTPSRR */ CURLcode Curl_doh_take_result(struct Curl_easy *data, struct Curl_resolv_async *async, @@ -1250,6 +1214,7 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } else if(!dohp->pending) { + struct Curl_dns_entry *dns = NULL; DOHcode rc[DOH_SLOT_COUNT]; bool negative = TRUE; int slot; @@ -1277,37 +1242,48 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, } } /* next slot */ - if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) { - /* we have an address, of one kind or other */ - struct Curl_dns_entry *dns; - struct Curl_addrinfo *ai; + if(CURL_DNSQ_IS_ADDR(async->dns_queries)) { + if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) { + /* we have an address, of one kind or other */ + struct Curl_addrinfo *ai; - if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) { - CURL_TRC_DNS(data, "hostname: %s", dohp->host); - doh_show(data, &de); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) { + CURL_TRC_DNS(data, "hostname: %s", dohp->host); + doh_show(data, &de); + } + + result = doh2ai(&de, dohp->host, dohp->port, &ai); + if(result) { + /* a decoded response without any usable address, e.g. only + CNAME records, is an authoritative "no data" answer */ + if((result == CURLE_COULDNT_RESOLVE_HOST) && negative) + async->negative_answer = TRUE; + goto error; + } + + /* we got a response, create a dns entry. */ + dns = Curl_dnsc_mk_addr(data, async->dns_queries, + &ai, dohp->host, dohp->port); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + } /* address processing done */ + else { + /* every query failed. Only NXDOMAIN answers for all of them + make this a negative answer, eligible for caching. */ + async->negative_answer = negative; + result = async->for_proxy ? + CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } + } - result = doh2ai(&de, dohp->host, dohp->port, &ai); - if(result) { - /* a decoded response without any usable address, e.g. only - CNAME records, is an authoritative "no data" answer */ - if((result == CURLE_COULDNT_RESOLVE_HOST) && negative) - async->negative_answer = TRUE; - goto error; - } - - /* we got a response, create a dns entry. */ - dns = Curl_dnscache_mk_entry(data, async->dns_queries, - &ai, dohp->host, dohp->port); - if(!dns) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - - /* Now add and HTTPSRR information if we have */ #ifdef USE_HTTPSRR + if(!dns && (async->dns_queries & CURL_DNSQ_HTTPS)) { + /* Now add and HTTPSRR information if we have */ + struct Curl_https_rrinfo *hrr = NULL; + if(de.numhttps_rrs > 0 && result == CURLE_OK) { - struct Curl_https_rrinfo *hrr = NULL; result = doh_resp_decode_httpsrr(data, de.https_rrs->val, de.https_rrs->len, &hrr); if(result) { @@ -1316,25 +1292,20 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, goto error; } infof(data, "Some HTTPS RR to process"); -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - doh_print_httpsrr(data, hrr); -#endif - Curl_dns_entry_set_https_rr(dns, hrr); } + Curl_httpsrr_trace(data, hrr); + dns = Curl_dnsc_mk_https(data, &hrr, async->hostname, async->port); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + } #endif /* USE_HTTPSRR */ - /* and add the entry to the cache */ + /* and add the entry to the cache */ + if(dns) result = Curl_dnscache_add(data, dns); - *pdns = dns; - } /* address processing done */ - else { - /* every query failed. Only NXDOMAIN answers for all of them - make this a negative answer, eligible for caching. */ - async->negative_answer = negative; - result = async->for_proxy ? - CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; - } - + *pdns = dns; } /* !dohp->pending */ else /* wait for pending DoH transactions to complete */ diff --git a/lib/hostip.c b/lib/hostip.c index 6afe63ff45..dc39f581b2 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -354,7 +354,7 @@ static bool can_resolve_dns_queries(struct Curl_easy *data, uint8_t dns_queries) { (void)data; - if((CURL_DNSQ_IP(dns_queries) == CURL_DNSQ_AAAA) && !ipv6works(data)) + if((CURL_DNSQ_IS_ADDR(dns_queries) == CURL_DNSQ_AAAA) && !ipv6works(data)) return FALSE; return TRUE; } @@ -445,7 +445,7 @@ 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, "resolve incomplete, queries=%s, responses=%s, " + CURL_TRC_DNS(data, "[%s] resolve incomplete, responses=%s, " "ongoing=%d for %s:%d", Curl_resolv_query_str(async->dns_queries), Curl_resolv_query_str(async->dns_responses), @@ -458,11 +458,13 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, else if(result) { /* a local failure, not a resolve answer. Keep the error as it is so it does not get treated as one. */ - CURL_TRC_DNS(data, "resolve error %d for %s:%u", + CURL_TRC_DNS(data, "[%s] resolve error %d for %s:%u", + Curl_resolv_query_str(async->dns_queries), (int)result, async->hostname, async->port); } else { - CURL_TRC_DNS(data, "resolve complete for %s:%u", + CURL_TRC_DNS(data, "[%s] resolve complete for %s:%u", + Curl_resolv_query_str(async->dns_queries), async->hostname, async->port); DEBUGASSERT(*pdns); } @@ -514,6 +516,18 @@ const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data, } #ifdef USE_HTTPSRR + +CURLcode Curl_resolv_https(struct Curl_easy *data, + struct Curl_peer *peer, + bool for_proxy, + timediff_t timeout_ms, + uint32_t *presolv_id, + struct Curl_dns_entry **pdns) +{ + return Curl_resolv(data, peer, CURL_DNSQ_HTTPS, TRNSPRT_TCP, + for_proxy, timeout_ms, presolv_id, pdns); +} + const struct Curl_https_rrinfo * Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id) { @@ -555,6 +569,7 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, #endif struct Curl_addrinfo *addr = NULL; size_t hostname_len; + bool addr_queries = (dns_queries & (CURL_DNSQ_A|CURL_DNSQ_AAAA)); CURLcode result = CURLE_OK; *pnegative = FALSE; @@ -564,33 +579,34 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, *pdns = NULL; /* Check for "known" things to resolve ourselves. */ + if(addr_queries) { #ifndef USE_RESOLVE_ON_IPS - if(Curl_is_ipaddr(hostname)) { - /* test655 verifies that the announce is done, even though there - * is no real resolving. So, keep doing this. */ - result = Curl_resolv_announce_start(data, NULL); - if(result) + if(Curl_is_ipaddr(hostname)) { + /* test655 verifies that the announce is done, even though there + * is no real resolving. So, keep doing this. */ + result = Curl_resolv_announce_start(data, NULL); + if(result) + goto out; + /* shortcut literal IP addresses, if we are not told to resolve them. */ + result = Curl_str2addr(hostname, port, &addr); goto out; - /* shortcut literal IP addresses, if we are not told to resolve them. */ - result = Curl_str2addr(hostname, port, &addr); - goto out; - } + } #endif - hostname_len = strlen(hostname); - if(curl_strequal(hostname, "localhost") || - curl_strequal(hostname, "localhost.") || - tailmatch(hostname, hostname_len, STRCONST(".localhost")) || - tailmatch(hostname, hostname_len, STRCONST(".localhost."))) { - result = Curl_resolv_announce_start(data, NULL); - if(result) + hostname_len = strlen(hostname); + if(curl_strequal(hostname, "localhost") || + curl_strequal(hostname, "localhost.") || + tailmatch(hostname, hostname_len, STRCONST(".localhost")) || + tailmatch(hostname, hostname_len, STRCONST(".localhost."))) { + result = Curl_resolv_announce_start(data, NULL); + if(result) + goto out; + addr = get_localhost(port, hostname); + if(!addr) + result = CURLE_OUT_OF_MEMORY; goto out; - addr = get_localhost(port, hostname); - if(!addr) - result = CURLE_OUT_OF_MEMORY; - goto out; + } } - #ifndef CURL_DISABLE_DOH if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) { result = Curl_resolv_announce_start(data, NULL); @@ -654,7 +670,7 @@ out: if(addr) { /* we got a response, create a dns entry, add to cache, return */ DEBUGASSERT(!*pdns); - *pdns = Curl_dnscache_mk_entry(data, dns_queries, &addr, hostname, port); + *pdns = Curl_dnsc_mk_addr(data, dns_queries, &addr, hostname, port); if(!*pdns) result = CURLE_OUT_OF_MEMORY; } @@ -717,9 +733,9 @@ static CURLcode hostip_resolv(struct Curl_easy *data, } #ifdef DEBUGBUILD - CURL_TRC_DNS(data, "hostip_resolv(%s:%u, queries=%s)", - hostname, port, Curl_resolv_query_str(dns_queries)); - if((CURL_DNSQ_IP(dns_queries) == CURL_DNSQ_AAAA) && + CURL_TRC_DNS(data, "[%s] hostip_resolv(%s:%u)", + Curl_resolv_query_str(dns_queries), hostname, port); + if((CURL_DNSQ_IS_ADDR(dns_queries) == CURL_DNSQ_AAAA) && getenv("CURL_DBG_RESOLV_FAIL_IPV6")) { infof(data, "DEBUG fail ipv6 resolve"); result = hostip_resolv_failed(data, hostname, for_proxy); @@ -742,6 +758,8 @@ static CURLcode hostip_resolv(struct Curl_easy *data, result = hostip_resolv_start(data, dns_queries, hostname, port, transport, for_proxy, timeout_ms, allowDOH, presolv_id, pdns, &negative); + CURL_TRC_DNS(data, "[%s] hostip_resolv started -> %d", + Curl_resolv_query_str(dns_queries), (int)result); } out: @@ -750,7 +768,8 @@ out: if(IS_RESOLV_FAIL(result)) { if(cache_dns && negative) Curl_dnscache_add_negative(data, dns_queries, hostname, port); - failf(data, "Could not resolve: %s:%u", hostname, port); + if(dns_queries & (CURL_DNSQ_A|CURL_DNSQ_AAAA)) + failf(data, "Could not resolve: %s:%u", hostname, port); } else { failf(data, "Error %d resolving %s:%u", (int)result, hostname, port); @@ -967,7 +986,7 @@ static CURLcode resolv_unix(struct Curl_easy *data, return result; } - *pdns = Curl_dnscache_mk_entry(data, 0, &addr, NULL, 0); + *pdns = Curl_dnsc_mk_addr(data, 0, &addr, NULL, 0); return *pdns ? CURLE_OK : CURLE_OUT_OF_MEMORY; } #endif /* USE_UNIX_SOCKETS */ @@ -1011,7 +1030,7 @@ CURLcode Curl_resolv(struct Curl_easy *data, timeout_ms = CURL_TIMEOUT_RESOLVE_MS; #ifdef USE_UNIX_SOCKETS - if(peer->unix_socket) + if((dns_queries & CURL_DNSQ_ADDR) && peer->unix_socket) return resolv_unix(data, peer->hostname, (bool)peer->abstract_uds, pdns); #else if(peer->unix_socket) @@ -1019,14 +1038,17 @@ CURLcode Curl_resolv(struct Curl_easy *data, #endif #ifdef USE_ALARM_TIMEOUT - if(timeout_ms && data->set.no_signal) { - /* Cannot use ALARM when signals are disabled */ - timeout_ms = 0; - } - if(timeout_ms && !Curl_doh_wanted(data)) { - return resolv_alarm_timeout(data, dns_queries, peer->hostname, peer->port, - transport, for_proxy, timeout_ms, presolv_id, - pdns); + if(dns_queries & CURL_DNSQ_ADDR) { + if(timeout_ms && data->set.no_signal) { + /* Cannot use ALARM when signals are disabled */ + timeout_ms = 0; + } + if(timeout_ms && !Curl_doh_wanted(data)) { + return resolv_alarm_timeout(data, dns_queries, + peer->hostname, peer->port, + transport, for_proxy, timeout_ms, + presolv_id, pdns); + } } #endif /* !USE_ALARM_TIMEOUT */ @@ -1094,7 +1116,8 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id, if(async->negative_answer) Curl_dnscache_add_negative(data, async->dns_queries, async->hostname, async->port); - failf(data, "Could not resolve: %s:%u", async->hostname, async->port); + if(async->dns_queries & (CURL_DNSQ_A|CURL_DNSQ_AAAA)) + failf(data, "Could not resolve: %s:%u", async->hostname, async->port); } else if(result) { failf(data, "Error %d resolving %s:%u", diff --git a/lib/hostip.h b/lib/hostip.h index 45370ec48e..eef7991847 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -52,8 +52,8 @@ struct Curl_peer; #define CURL_DNSQ_AAAA (1U << 1) #define CURL_DNSQ_HTTPS (1U << 2) -#define CURL_DNSQ_ALL (CURL_DNSQ_A | CURL_DNSQ_AAAA | CURL_DNSQ_HTTPS) -#define CURL_DNSQ_IP(x) (uint8_t)((x)&(CURL_DNSQ_A | CURL_DNSQ_AAAA)) +#define CURL_DNSQ_ADDR (uint8_t)(CURL_DNSQ_A | CURL_DNSQ_AAAA) +#define CURL_DNSQ_IS_ADDR(x) (uint8_t)((x)&(CURL_DNSQ_ADDR)) #ifdef CURLVERBOSE const char *Curl_resolv_query_str(uint8_t dns_queries); @@ -153,6 +153,22 @@ const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data, int ai_family, unsigned int index); #ifdef USE_HTTPSRR + +/* Start DNS resolving for HTTPS records. Returns + * - CURLE_OK: `*pdns` is the resolved DNS entry (needs to be unlinked). + * `*presolv_id` is 0. + * - CURLE_AGAIN: resolve is asynchronous and not finished yet. + * `presolv_id` is the identifier for querying results later. + * - other: the operation failed, `*pdns` is NULL, `*presolv_id` is 0. + */ +CURLcode Curl_resolv_https(struct Curl_easy *data, + struct Curl_peer *peer, + bool for_proxy, + timediff_t timeout_ms, + uint32_t *presolv_id, + struct Curl_dns_entry **pdns); + + const struct Curl_https_rrinfo *Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id); bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id); diff --git a/lib/httpsrr.c b/lib/httpsrr.c index 53647b81ed..a05a5cb5c1 100644 --- a/lib/httpsrr.c +++ b/lib/httpsrr.c @@ -97,7 +97,7 @@ void Curl_httpsrr_trace(struct Curl_easy *data, struct dynbuf tmp; CURLcode result; - if(!rr || !rr->complete) { + if(!rr) { CURL_TRC_DNS(data, "[HTTPS-RR] not available"); return; } @@ -225,23 +225,15 @@ CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr, return result; } -struct Curl_https_rrinfo *Curl_httpsrr_dup_move( - struct Curl_https_rrinfo *rrinfo) +void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo) { - struct Curl_https_rrinfo *dup = curlx_memdup(rrinfo, sizeof(*rrinfo)); - if(dup) - memset(rrinfo, 0, sizeof(*rrinfo)); - return dup; -} - -void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo) -{ - curlx_safefree(rrinfo->target); - curlx_safefree(rrinfo->echconfiglist); - curlx_safefree(rrinfo->ipv4hints); - curlx_safefree(rrinfo->ipv6hints); - curlx_safefree(rrinfo->rrname); - rrinfo->complete = FALSE; + if(rrinfo) { + curlx_free(rrinfo->target); + curlx_free(rrinfo->echconfiglist); + curlx_free(rrinfo->ipv4hints); + curlx_free(rrinfo->ipv6hints); + curlx_free(rrinfo); + } } bool Curl_httpsrr_applicable(struct Curl_easy *data, @@ -269,11 +261,16 @@ static CURLcode httpsrr_opt(const ares_dns_rr_t *rr, } CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, - struct Curl_https_rrinfo *hinfo) + struct Curl_https_rrinfo **phinfo) { - CURLcode result = CURLE_OK; + struct Curl_https_rrinfo *hinfo = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; size_t i; + hinfo = curlx_calloc(1, sizeof(*hinfo)); + if(!hinfo) + goto out; + for(i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) { const char *target; size_t opt; @@ -297,12 +294,20 @@ CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, opt++) { result = httpsrr_opt(rr, ARES_RR_HTTPS_PARAMS, opt, hinfo); if(result) - break; + goto out; } } + result = CURLE_OK; + out: - hinfo->complete = !result; - curlx_safefree(hinfo->rrname); + if(result) { + *phinfo = NULL; + Curl_httpsrr_destroy(hinfo); + } + else { + DEBUGASSERT(hinfo); + *phinfo = hinfo; + } return result; } diff --git a/lib/httpsrr.h b/lib/httpsrr.h index 2ee1beab3e..8d6eed1c93 100644 --- a/lib/httpsrr.h +++ b/lib/httpsrr.h @@ -38,7 +38,6 @@ struct Curl_easy; struct dynbuf; struct Curl_https_rrinfo { - char *rrname; /* if NULL, the same as the URL hostname */ /* * Fields from HTTPS RR. The only mandatory fields are priority and target. * See https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2 @@ -57,16 +56,12 @@ struct Curl_https_rrinfo { BIT(no_def_alpn); /* keytag = 2 */ BIT(mandatory); /* keytag = 0 */ BIT(port_set); /* port value has been assigned */ - BIT(complete); /* values have been successfully assigned */ }; 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( - struct Curl_https_rrinfo *rrinfo); - -void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); +void Curl_httpsrr_destroy(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, @@ -85,7 +80,7 @@ bool Curl_httpsrr_applicable(struct Curl_easy *data, #ifdef USE_ARES CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, - struct Curl_https_rrinfo *hinfo); + struct Curl_https_rrinfo **phinfo); #endif /* USE_ARES */ #ifdef CURLVERBOSE diff --git a/lib/multi.c b/lib/multi.c index 265a247227..9e3b092e48 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -1182,10 +1182,7 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, break; case MSTATE_CONNECTING: - if(data->conn && !data->conn->bits.dns_resolved) - result = Curl_resolv_pollset(data, ps); - if(!result) - result = mstate_connecting_pollset(data, ps); + result = mstate_connecting_pollset(data, ps); break; case MSTATE_PROTOCONNECT: diff --git a/lib/socks.c b/lib/socks.c index ab3663e886..71c7d1bb56 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -105,7 +105,6 @@ struct socks_ctx { uint8_t proxy_type; unsigned char version; BIT(resolve_local); - BIT(start_resolving); BIT(socks4a); }; @@ -318,32 +317,19 @@ static CURLproxycode socks4_resolving(struct socks_ctx *sx, const struct Curl_addrinfo *ai = NULL; CURLcode result; size_t nwritten; - bool dns_done; *done = FALSE; - if(sx->start_resolving) { - /* need to resolve hostname to add destination address */ - sx->start_resolving = FALSE; - result = Curl_cf_dns_insert_after( - cf, data, Curl_resolv_dns_queries(data, sx->ip_version), - sx->dest, TRNSPRT_TCP, TRUE); - if(result) { - failf(data, "unable to create DNS filter for socks"); - return CURLPX_UNKNOWN_FAIL; - } - } - - /* resolve the hostname by connecting the DNS filter */ - result = Curl_conn_cf_connect(cf->next, data, &dns_done); + result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex, sx->dest); if(result) { - failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", - sx->dest->hostname); - return CURLPX_RESOLVE_HOST; - } - else if(!dns_done) + if(result != CURLE_AGAIN) { + failf(data, "error %d resolving SOCKS destination %s:%u", + (int)result, sx->dest->hostname, sx->dest->port); + return CURLPX_RESOLVE_HOST; + } return CURLPX_OK; + } - ai = Curl_cf_dns_get_ai(cf->next, data, sx->dest, AF_INET, 0); + ai = Curl_conn_dns_get_ai(data, sx->dest, cf->sockindex, AF_INET, 0); if(ai) { struct sockaddr_in *saddr_in; char ipbuf[64]; @@ -476,9 +462,7 @@ process_state: case SOCKS4_ST_START: Curl_bufq_reset(&sx->iobuf); - sx->start_resolving = FALSE; sx->socks4a = (sx->proxy_type == CURLPROXY_SOCKS4A); - sx->resolve_local = !sx->socks4a; sx->presult = CURLPX_OK; /* SOCKS4 can only do IPv4, insist! */ @@ -526,7 +510,6 @@ process_state: sxstate(sx, cf, data, SOCKS4_ST_SEND); goto process_state; } - sx->start_resolving = TRUE; sxstate(sx, cf, data, SOCKS4_ST_RESOLVING); FALLTHROUGH(); @@ -835,37 +818,24 @@ static CURLproxycode socks5_resolving(struct socks_ctx *sx, CURLcode result; CURLproxycode presult = CURLPX_OK; size_t nwritten; - bool dns_done; *done = FALSE; - if(sx->start_resolving) { - /* need to resolve hostname to add destination address */ - sx->start_resolving = FALSE; - result = Curl_cf_dns_insert_after( - cf, data, Curl_resolv_dns_queries(data, sx->ip_version), - sx->dest, TRNSPRT_TCP, TRUE); - if(result) { - failf(data, "unable to create DNS filter for socks"); - return CURLPX_UNKNOWN_FAIL; - } - } - - /* resolve the hostname by connecting the DNS filter */ - result = Curl_conn_cf_connect(cf->next, data, &dns_done); + result = Curl_conn_dns_addr_result(cf->conn, cf->sockindex, sx->dest); if(result) { - failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", - sx->dest->hostname); - return CURLPX_RESOLVE_HOST; - } - else if(!dns_done) + if(result != CURLE_AGAIN) { + failf(data, "error %d resolving SOCKS destination %s:%u", + (int)result, sx->dest->hostname, sx->dest->port); + return CURLPX_RESOLVE_HOST; + } return CURLPX_OK; + } #ifdef USE_IPV6 if(data->set.ipver != CURL_IPRESOLVE_V4) - ai = Curl_cf_dns_get_ai(cf->next, data, sx->dest, AF_INET6, 0); + ai = Curl_conn_dns_get_ai(data, sx->dest, cf->sockindex, AF_INET6, 0); #endif if(!ai) - ai = Curl_cf_dns_get_ai(cf->next, data, sx->dest, AF_INET, 0); + ai = Curl_conn_dns_get_ai(data, sx->dest, cf->sockindex, AF_INET, 0); if(!ai) { failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", @@ -1037,7 +1007,6 @@ process_state: switch(sx->state) { case SOCKS_ST_INIT: sx->version = 5; - sx->resolve_local = (sx->proxy_type == CURLPROXY_SOCKS5); sxstate(sx, cf, data, SOCKS5_ST_START); FALLTHROUGH(); @@ -1126,7 +1095,6 @@ process_state: sxstate(sx, cf, data, SOCKS5_ST_REQ1_SEND); goto process_state; } - sx->start_resolving = TRUE; sxstate(sx, cf, data, SOCKS5_ST_RESOLVING); FALLTHROUGH(); @@ -1351,6 +1319,8 @@ CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, { struct Curl_cfilter *cf; struct socks_ctx *ctx; + bool resolve_local = FALSE; + uint8_t dns_queries = Curl_resolv_dns_queries(data, ip_version); CURLcode result; if(!dest) @@ -1358,10 +1328,16 @@ CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, switch(proxy_type) { case CURLPROXY_SOCKS5: + resolve_local = TRUE; + break; case CURLPROXY_SOCKS5_HOSTNAME: + break; case CURLPROXY_SOCKS4: + resolve_local = TRUE; + dns_queries = (uint8_t)(dns_queries & ~CURL_DNSQ_AAAA); + break; case CURLPROXY_SOCKS4A: - break; /* all supported */ + break; default: failf(data, "unknown proxytype %d option given", proxy_type); return CURLE_COULDNT_CONNECT; @@ -1376,13 +1352,21 @@ CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, Curl_peer_link(&ctx->dest, dest); ctx->ip_version = ip_version; ctx->proxy_type = proxy_type; + ctx->resolve_local = resolve_local; Curl_creds_link(&ctx->creds, creds); Curl_bufq_init2(&ctx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS, BUFQ_OPT_SOFT_LIMIT); result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, ctx); - if(!result) + if(!result) { Curl_conn_cf_insert_after(cf_at, cf); + if(ctx->resolve_local) { + result = Curl_conn_dns_add_addr_resolve(data, cf_at->conn, + cf_at->sockindex, + ctx->dest, dns_queries, + TRNSPRT_TCP); + } + } else socks_proxy_ctx_free(ctx); return result; diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index 4cfd3fbac4..802ffb7d30 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -41,6 +41,7 @@ #include "curlx/dynbuf.h" #include "curlx/fopen.h" #include "cfilters.h" +#include "cf-dns.h" #include "vquic/cf-ngtcp2.h" #include "vquic/cf-ngtcp2-cmn.h" #include "vquic/cf-ngtcp2-proxy.h" @@ -763,19 +764,36 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, } CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, struct Curl_peer *origin, struct Curl_peer *peer) { + CURLcode result; + + (void)data; /* not used in all cases and compilers are stupid */ #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - return Curl_cf_ngtcp2_insert_after(cf_at, origin, peer); + result = Curl_cf_ngtcp2_insert_after(cf_at, origin, peer); #elif defined(USE_QUICHE) - return Curl_cf_quiche_insert_after(cf_at, origin, peer); + result = Curl_cf_quiche_insert_after(cf_at, origin, peer); #else (void)cf_at; (void)origin; (void)peer; - return CURLE_NOT_BUILT_IN; + result = CURLE_NOT_BUILT_IN; #endif + +#if defined(USE_HTTPSRR) && defined(USE_ECH) + /* When using ECH, kick off the HTTPS-RR resolve */ + if(!result && (origin->scheme->family == CURLPROTO_HTTP) && + CURLECH_ENABLED(data) && + Curl_ssl_supports(data, SSLSUPP_ECH) && + (data->set.tls_ech != CURLECH_GREASE) && + !data->set.str[STRING_ECH_CONFIG]) { + result = Curl_conn_dns_add_https_resolve(data, cf_at->conn, + cf_at->sockindex, origin); + } +#endif /* USE_HTTPSRR && USE_ECH */ + return result; } CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, diff --git a/lib/vquic/vquic.h b/lib/vquic/vquic.h index 5211a9b33a..8674a3001d 100644 --- a/lib/vquic/vquic.h +++ b/lib/vquic/vquic.h @@ -40,6 +40,7 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, int *qlogfdp); CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, struct Curl_peer *origin, struct Curl_peer *peer); diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 02dbb9f7c7..816912a135 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -46,6 +46,7 @@ #include "urldata.h" #include "cfilters.h" +#include "cf-dns.h" #include "vtls/vtls.h" /* generic SSL protos etc */ #include "vtls/vtls_int.h" @@ -1421,8 +1422,20 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, result = cf_ssl_create(&cf, data, cf_at->conn); if(!result) result = cf_ssl_peer_init(cf, origin, peer, &cf_at->conn->ssl_config); - if(!result) + if(!result) { Curl_conn_cf_insert_after(cf_at, cf); +#if defined(USE_HTTPSRR) && defined(USE_ECH) + /* When using ECH, kick off the HTTPS-RR resolve */ + if((origin->scheme->family == CURLPROTO_HTTP) && + CURLECH_ENABLED(data) && + Curl_ssl_supports(data, SSLSUPP_ECH) && + (data->set.tls_ech != CURLECH_GREASE) && + !data->set.str[STRING_ECH_CONFIG]) { + result = Curl_conn_dns_add_https_resolve(data, cf->conn, cf->sockindex, + origin); + } +#endif /* USE_HTTPSRR && USE_ECH */ + } else if(cf) Curl_conn_cf_discard_chain(&cf, data); return result; diff --git a/tests/data/test2100 b/tests/data/test2100 index 8793c57865..1bb5361e15 100644 --- a/tests/data/test2100 +++ b/tests/data/test2100 @@ -41,6 +41,7 @@ Funny-head: yesyes http +https # requires Debug so that it can use the DoH server without https @@ -55,7 +56,7 @@ IPv6 HTTP GET using DoH (with HTTPS RR) -http://foo.example.com:%HTTPPORT/%TESTNUMBER --doh-url http://%HOSTIP:%HTTPPORT/%TESTNUMBER0001 +https://foo.example.com:%HTTPSPORT/%TESTNUMBER --insecure --doh-insecure --doh-url https://%HOSTIP:%HTTPSPORT/%TESTNUMBER0001 @@ -71,43 +72,43 @@ s/com\x00\x00(\x1c|\x01)/com-00-00!/g; %if HTTPSRR POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT -Accept: */* -Content-Type: application/dns-message -Content-Length: 33 - -%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT -Accept: */* -Content-Type: application/dns-message -Content-Length: 33 - -%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT +Host: %HOSTIP:%HTTPSPORT Accept: */* Content-Type: application/dns-message Content-Length: 47 -%hex[%00%00%01%00%00%01%00%00%00%00%00%00%06_%HTTPPORT%06_https%03foo%07example%03com%00%00A%00%01]hex%GET /%TESTNUMBER HTTP/1.1 -Host: foo.example.com:%HTTPPORT -User-Agent: curl/%VERSION -Accept: */* - -%else -POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%06_%HTTPSPORT%06_https%03foo%07example%03com%00%00A%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPSPORT Accept: */* Content-Type: application/dns-message Content-Length: 33 %hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 -Host: %HOSTIP:%HTTPPORT +Host: %HOSTIP:%HTTPSPORT Accept: */* Content-Type: application/dns-message Content-Length: 33 %hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%GET /%TESTNUMBER HTTP/1.1 -Host: foo.example.com:%HTTPPORT +Host: foo.example.com:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + +%else +POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPSPORT +Accept: */* +Content-Type: application/dns-message +Content-Length: 33 + +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%POST /%TESTNUMBER0001 HTTP/1.1 +Host: %HOSTIP:%HTTPSPORT +Accept: */* +Content-Type: application/dns-message +Content-Length: 33 + +%hex[%00%00%01%00%00%01%00%00%00%00%00%00%03foo%07example%03com-00-00!%00%01]hex%GET /%TESTNUMBER HTTP/1.1 +Host: foo.example.com:%HTTPSPORT User-Agent: curl/%VERSION Accept: */* diff --git a/tests/data/test716 b/tests/data/test716 index 6f692fbd09..266826c8d5 100644 --- a/tests/data/test716 +++ b/tests/data/test716 @@ -30,7 +30,7 @@ SOCKS5 proxy with too long username # it should never connect to the target server -http://hohoho.example.com:99/%TESTNUMBER -x socks5://%repeat[256 x A]%:b@%HOSTIP:%SOCKSPORT +http://localhost:99/%TESTNUMBER -x socks5://%repeat[256 x A]%:b@%HOSTIP:%SOCKSPORT diff --git a/tests/http/test_06_eyeballs.py b/tests/http/test_06_eyeballs.py index d883822d54..8e2104df19 100644 --- a/tests/http/test_06_eyeballs.py +++ b/tests/http/test_06_eyeballs.py @@ -133,7 +133,7 @@ class TestEyeballs: # no immediately failed attempts, as should be he_timers_set = [line for line in r.trace_lines if re.match(r'.*\[TIMER] \[HAPPY_EYEBALLS] set for', line)] - assert len(he_timers_set) == 2, f'found: {"".join(he_timers_set)}\n{r.dump_logs()}' + assert len(he_timers_set) >= 2, f'found: {"".join(he_timers_set)}\n{r.dump_logs()}' # download using HTTP/3 on missing server with alt-svc pointing there @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") diff --git a/tests/http/test_22_httpsrr.py b/tests/http/test_22_httpsrr.py index 51ed3bbd9d..0b95472aa7 100644 --- a/tests/http/test_22_httpsrr.py +++ b/tests/http/test_22_httpsrr.py @@ -129,3 +129,22 @@ class TestHTTPSRR: r.check_exit_code(0) r.check_stats(count=1, http_status=200, exitcode=0) assert r.stats[0]['http_version'] == '2', f'{r}' + + # download https: via https: proxytunnel + @pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'), + reason='curl lacks HTTPS-proxy support') + @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available") + def test_22_06_httpsrr_proxy(self, env: Env, httpd, nghttpx_fwd, dnsd): + dnsd.set_answers(addr_a=['127.0.0.1'], + https=['10 . alpn=http/1.1']) + run_env = os.environ.copy() + run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}' + run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1' + run_env['CURL_QUICK_EXIT'] = '1' + curl = CurlClient(env=env, run_env=run_env) + url = f'https://localhost:{env.https_port}/data.json' + xargs = curl.get_proxy_args(tunnel=True) + r = curl.http_download(urls=[url], alpn_proto='h2', with_stats=True, + extra_args=xargs) + r.check_response(count=1, http_status=200) + assert r.stats[0]['http_version'] == '1.1', f'{r}' diff --git a/tests/unit/unit1607.c b/tests/unit/unit1607.c index d4052b762f..2b55621340 100644 --- a/tests/unit/unit1607.c +++ b/tests/unit/unit1607.c @@ -51,7 +51,7 @@ static CURLcode test_unit1607(const char *arg) /* lowercase host and port to retrieve the addresses from hostcache */ const char *host; - int port; + uint16_t port; /* whether we expect a permanent or non-permanent cache entry */ bool permanent; @@ -113,6 +113,7 @@ static CURLcode test_unit1607(const char *arg) easy = curl_easy_init(); if(!easy) goto error; + curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); /* create a multi handle and add the easy handle to it so that the hostcache is setup */ @@ -126,7 +127,8 @@ static CURLcode test_unit1607(const char *arg) Curl_loadhostpairs(easy); - entry_id = (void *)curl_maprintf("%s:%d", tests[i].host, tests[i].port); + entry_id = (void *)curl_maprintf("%c%s:%u", CURL_DNST_ADDR, + tests[i].host, tests[i].port); if(!entry_id) goto error; dns = Curl_hash_pick(&multi->dnscache.entries, diff --git a/tests/unit/unit1658.c b/tests/unit/unit1658.c index 49859c69c8..041ced7f29 100644 --- a/tests/unit/unit1658.c +++ b/tests/unit/unit1658.c @@ -544,8 +544,7 @@ static CURLcode test_unit1658(const char *arg) /* free the generated struct */ if(hrr) { - Curl_httpsrr_cleanup(hrr); - curl_free(hrr); + Curl_httpsrr_destroy(hrr); } } curl_easy_cleanup(easy);