From 2b3dfb4ad47ec05efad9af930c47968a49916999 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 31 Mar 2026 11:45:21 +0200 Subject: [PATCH] lib: make resolving HTTPS DNS records reliable: - allow to specify when they are wanted on starting a resolve - match dns cache entries accordingly. An entry which never tried to get HTTPS-RRs is no answer for a resolve that wants it. - fix late arrivals of resolve answers to match the "async" records that started them - if it still exists. - provide for multiple "async" resolves in a transfer at the same time. We may need to resolve an IP interface while the main connection resolve has not finished yet. - allow lookup of HTTPS-RR information as soon as it is available, even if A/AAAA queries are still ongoing. For this, the "async" infrastructure is changed: - Defined bits for DNS queries `CURL_DNSQ_A`, `CURL_DNSQ_AAAA` and `CURL_DNSQ_HTTPS`. These replace `ip_version` which says nothing about HTTPS. Use them in dns cache entries for matching. - enhance the `async->id` to be a unique `uint32_t` for resolves inside one multi. This is weak, as the id may wrap around. However it is combined with the `mid` of the easy handle, making collisions highly unlikely. `data->state.async` is only accessed in few places where the mid/async-id match is performed. - vtls: for ECH supporting TLS backends (openssl, rustls, wolfssl), retrieve the HTTPS-RR information from the dns connection filter. Delay the connect if the HTTPS-RR is needed, but has not been resolved yet. The implementation of all this is complete for the threaded resolver. c-ares resolver and DoH do not take advantage of all new async features yet. To be done in separate PRs. Details: c-ares: cleanup settings and initialisation. Any ares channel is only being created on starting a resolve and propagating operations in setopt.c to the channel are not helpful. Changed threaded+ares pollset handling so that they do not overwrite each others `ASYNC_NAME` timeouts. Add trace name 'threads' for tracing thread queue and pool used by threaded resolver. Closes #21175 --- docs/libcurl/curl_global_trace.md | 4 + lib/asyn-ares.c | 221 ++++++++-------- lib/asyn-base.c | 77 ++++-- lib/asyn-thrdd.c | 314 ++++++++++++---------- lib/asyn.h | 150 +++++------ lib/cf-dns.c | 78 ++++-- lib/cf-dns.h | 7 +- lib/cf-https-connect.c | 9 +- lib/cf-socket.c | 8 +- lib/connect.c | 8 +- lib/curl_trc.c | 9 + lib/curl_trc.h | 3 + lib/dnscache.c | 159 ++++++----- lib/dnscache.h | 21 +- lib/doh.c | 68 ++--- lib/doh.h | 6 +- lib/easy.c | 2 - lib/ftp.c | 20 +- lib/hostip.c | 424 ++++++++++++++++++++---------- lib/hostip.h | 93 +++++-- lib/hostip4.c | 4 +- lib/hostip6.c | 5 +- lib/httpsrr.c | 103 ++++++-- lib/httpsrr.h | 19 +- lib/multi.c | 2 +- lib/multihandle.h | 1 + lib/request.c | 8 +- lib/setopt.c | 20 +- lib/socks.c | 19 +- lib/thrdpool.c | 14 +- lib/thrdpool.h | 4 +- lib/thrdqueue.c | 12 +- lib/thrdqueue.h | 4 +- lib/url.c | 2 +- lib/urldata.h | 1 - lib/vtls/openssl.c | 73 ++--- lib/vtls/rustls.c | 46 ++-- lib/vtls/wolfssl.c | 84 +++--- tests/libtest/lib655.c | 11 + tests/unit/unit1658.c | 2 +- 40 files changed, 1242 insertions(+), 873 deletions(-) diff --git a/docs/libcurl/curl_global_trace.md b/docs/libcurl/curl_global_trace.md index 35bf6c6479..f78f2ca0b0 100644 --- a/docs/libcurl/curl_global_trace.md +++ b/docs/libcurl/curl_global_trace.md @@ -142,6 +142,10 @@ Tracing of SSL Session handling, e.g. caching/import/export. Tracing of SMTP operations when this protocol is enabled in your build. +## `threads` + +Tracing of thread queue and pools, used in threaded DNS resolving. + ## `timer` Tracing of timers set for transfers. diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c index 96a0a6df6c..11ca92b399 100644 --- a/lib/asyn-ares.c +++ b/lib/asyn-ares.c @@ -76,7 +76,13 @@ static int ares_ver = 0; static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, - bool reset_on_null); + struct Curl_resolv_async *async); +static CURLcode async_ares_set_dns_interface(struct Curl_easy *data, + struct Curl_resolv_async *async); +static CURLcode async_ares_set_dns_local_ip4(struct Curl_easy *data, + struct Curl_resolv_async *async); +static CURLcode async_ares_set_dns_local_ip6(struct Curl_easy *data, + struct Curl_resolv_async *async); /* * Curl_async_global_init() - the generic low-level asynchronous name @@ -152,19 +158,19 @@ static CURLcode async_ares_init(struct Curl_easy *data, goto out; } - rc = async_ares_set_dns_servers(data, FALSE); + rc = async_ares_set_dns_servers(data, async); if(rc && rc != CURLE_NOT_BUILT_IN) goto out; - rc = Curl_async_ares_set_dns_interface(data); + rc = async_ares_set_dns_interface(data, async); if(rc && rc != CURLE_NOT_BUILT_IN) goto out; - rc = Curl_async_ares_set_dns_local_ip4(data); + rc = async_ares_set_dns_local_ip4(data, async); if(rc && rc != CURLE_NOT_BUILT_IN) goto out; - rc = Curl_async_ares_set_dns_local_ip6(data); + rc = async_ares_set_dns_local_ip6(data, async); if(rc && rc != CURLE_NOT_BUILT_IN) goto out; @@ -178,28 +184,6 @@ out: return rc; } -static CURLcode async_ares_init_lazy(struct Curl_easy *data, - struct Curl_resolv_async *async) -{ - struct async_ares_ctx *ares = &async->ares; - if(!ares->channel) - return async_ares_init(data, async); - return CURLE_OK; -} - -CURLcode Curl_async_get_impl(struct Curl_easy *data, - struct Curl_resolv_async *async, - void **impl) -{ - struct async_ares_ctx *ares = &async->ares; - CURLcode result = CURLE_OK; - if(!ares->channel) { - result = async_ares_init(data, async); - } - *impl = ares->channel; - return result; -} - /* * async_ares_cleanup() cleans up async resolver data. */ @@ -238,18 +222,21 @@ void Curl_async_ares_destroy(struct Curl_easy *data, async_ares_cleanup(async); } -/* - * Curl_async_pollset() is called when someone from the outside world - * (using curl_multi_fdset()) wants to get our fd_set setup. - */ - -CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps) +CURLcode Curl_async_pollset(struct Curl_easy *data, + struct Curl_resolv_async *async, + struct easy_pollset *ps) { - struct Curl_resolv_async *async = data->state.async; - struct async_ares_ctx *ares = async ? &async->ares : NULL; - if(ares && ares->channel) - return Curl_ares_pollset(data, ares->channel, ps); - return CURLE_OK; + struct async_ares_ctx *ares = &async->ares; + CURLcode result = CURLE_OK; + + if(ares->channel) { + result = Curl_ares_pollset(data, ares->channel, ps); + if(!result) { + timediff_t ms = Curl_ares_timeout_ms(data, async, ares->channel); + Curl_expire(data, ms, EXPIRE_ASYNC_NAME); + } + } + return result; } /* @@ -276,25 +263,28 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, goto out; } - if(!ares->num_pending) { + if(!async->queries_ongoing) { /* 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_entry(data, &ares->temp_ai, - async->hostname, async->port, - async->ip_version); + Curl_dnscache_mk_entry(data, async->dns_queries, &ares->temp_ai, + async->hostname, async->port); if(!dns) { result = CURLE_OUT_OF_MEMORY; goto out; } #ifdef HTTPSRR_WORKS - { - struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); - if(!lhrr) - result = CURLE_OUT_OF_MEMORY; + 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); + } else - dns->hinfo = lhrr; + Curl_dns_entry_set_https_rr(dns, NULL); } #endif if(!result) { @@ -362,6 +352,27 @@ Curl_async_get_ai(struct Curl_easy *data, return NULL; } +#ifdef USE_HTTPSRR +const struct Curl_https_rrinfo * +Curl_async_get_https(struct Curl_easy *data, + struct Curl_resolv_async *async) +{ + if(Curl_async_knows_https(data, async)) + return &async->ares.hinfo; + return NULL; +} + +bool Curl_async_knows_https(struct Curl_easy *data, + struct Curl_resolv_async *async) +{ + (void)data; + if(async->dns_queries & CURL_DNSQ_HTTPS) + return !async->queries_ongoing; + return TRUE; /* we know it will never come */ +} + +#endif /* USE_HTTPSRR */ + /* * Curl_async_await() * @@ -373,17 +384,20 @@ Curl_async_get_ai(struct Curl_easy *data, * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. */ -CURLcode Curl_async_await(struct Curl_easy *data, - struct Curl_resolv_async *async, +CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id, struct Curl_dns_entry **pdns) { - struct async_ares_ctx *ares = &async->ares; + struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); + struct async_ares_ctx *ares = async ? &async->ares : NULL; struct curltime start = *Curl_pgrs_now(data); CURLcode result = CURLE_OK; DEBUGASSERT(pdns); *pdns = NULL; /* clear on entry */ + if(!ares) + return CURLE_FAILED_INIT; + /* Wait for the name resolve query to complete or time out. */ while(!result) { timediff_t timeout_ms; @@ -391,8 +405,8 @@ CURLcode Curl_async_await(struct Curl_easy *data, timeout_ms = Curl_timeleft_ms(data); if(!timeout_ms) { /* no applicable timeout from `data`*/ timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &start); - if(elapsed_ms < (CURL_TIMEOUT_RESOLVE * 1000)) - timeout_ms = (CURL_TIMEOUT_RESOLVE * 1000) - elapsed_ms; + if(elapsed_ms < CURL_TIMEOUT_RESOLVE_MS) + timeout_ms = CURL_TIMEOUT_RESOLVE_MS - elapsed_ms; else timeout_ms = -1; } @@ -503,8 +517,7 @@ async_ares_node2addr(struct ares_addrinfo_node *node) static void async_ares_addrinfo_cb(void *user_data, int status, int timeouts, struct ares_addrinfo *ares_ai) { - struct Curl_easy *data = (struct Curl_easy *)user_data; - struct Curl_resolv_async *async = data->state.async; + struct Curl_resolv_async *async = user_data; struct async_ares_ctx *ares = async ? &async->ares : NULL; (void)timeouts; @@ -516,11 +529,11 @@ static void async_ares_addrinfo_cb(void *user_data, int status, int timeouts, ares->temp_ai = async_ares_node2addr(ares_ai->nodes); ares_freeaddrinfo(ares_ai); } - ares->num_pending--; - CURL_TRC_DNS(data, "ares: addrinfo done, query status=%d, " - "overall status=%d, pending=%d, addr=%sfound", - status, ares->ares_status, ares->num_pending, - ares->temp_ai ? "" : "not "); + if(async->dns_queries & CURL_DNSQ_A) + async->dns_responses |= CURL_DNSQ_A; + if(async->dns_queries & CURL_DNSQ_AAAA) + async->dns_responses |= CURL_DNSQ_AAAA; + async->queries_ongoing--; } #ifdef USE_HTTPSRR @@ -528,23 +541,17 @@ static void async_ares_rr_done(void *user_data, ares_status_t status, size_t timeouts, const ares_dns_record_t *dnsrec) { - struct Curl_easy *data = user_data; - struct Curl_resolv_async *async = data->state.async; + struct Curl_resolv_async *async = user_data; struct async_ares_ctx *ares = async ? &async->ares : NULL; if(!ares) return; (void)timeouts; - --ares->num_pending; - CURL_TRC_DNS(data, "ares: httpsrr done, status=%d, pending=%d, " - "dnsres=%sfound", - status, ares->num_pending, - (dnsrec && - ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER)) ? - "" : "not "); + async->dns_responses |= CURL_DNSQ_HTTPS; + async->queries_ongoing--; if((ARES_SUCCESS != status) || !dnsrec) return; - ares->result = Curl_httpsrr_from_ares(data, dnsrec, &ares->hinfo); + ares->result = Curl_httpsrr_from_ares(dnsrec, &ares->hinfo); } #endif /* USE_HTTPSRR */ @@ -557,25 +564,24 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = &async->ares; -#ifdef USE_HTTPSRR - char *rrname = NULL; -#endif - if(async_ares_init_lazy(data, async)) + CURL_TRC_DNS(data, "ares getaddrinfo(%s:%u)", async->hostname, async->port); + if(ares->channel) { + DEBUGASSERT(0); return CURLE_FAILED_INIT; - -#ifdef USE_HTTPSRR - if(async->port != 443) { - rrname = curl_maprintf("_%d._https.%s", async->port, async->hostname); - if(!rrname) - return CURLE_OUT_OF_MEMORY; } -#endif + + if(async_ares_init(data, async)) + return CURLE_FAILED_INIT; /* initial status - failed */ ares->ares_status = ARES_ENOTFOUND; ares->result = CURLE_OK; + ares->result = Curl_resolv_announce_start(data, ares->channel); + if(ares->result) + return ares->result; + #if defined(CURLVERBOSE) && ARES_VERSION >= 0x011800 /* >= v1.24.0 */ if(CURL_TRC_DNS_is_verbose(data)) { char *csv = ares_get_servers_csv(ares->channel); @@ -590,12 +596,11 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, int pf = PF_INET; memset(&hints, 0, sizeof(hints)); #ifdef CURLRES_IPV6 - if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { - /* The stack seems to be IPv6-enabled */ - if(async->ip_version == CURL_IPRESOLVE_V6) - pf = PF_INET6; - else + if(async->dns_queries & CURL_DNSQ_AAAA) { + if(async->dns_queries & CURL_DNSQ_A) pf = PF_UNSPEC; + else + pf = PF_INET6; } #endif /* CURLRES_IPV6 */ CURL_TRC_DNS(data, "asyn-ares: fire off getaddrinfo for %s", @@ -610,23 +615,28 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, */ hints.ai_flags = ARES_AI_NUMERICSERV; curl_msnprintf(service, sizeof(service), "%d", async->port); - ares->num_pending = 1; + async->queries_ongoing = 1; ares_getaddrinfo(ares->channel, async->hostname, - service, &hints, async_ares_addrinfo_cb, data); + service, &hints, async_ares_addrinfo_cb, async); } #ifdef USE_HTTPSRR - { + memset(&ares->hinfo, 0, sizeof(ares->hinfo)); + if(async->dns_queries & CURL_DNSQ_HTTPS) { + char *rrname = NULL; + if(async->port != 443) { + rrname = curl_maprintf("_%d._https.%s", async->port, async->hostname); + if(!rrname) + return CURLE_OUT_OF_MEMORY; + } CURL_TRC_DNS(data, "asyn-ares: fire off query for HTTPSRR: %s", rrname ? rrname : async->hostname); - memset(&ares->hinfo, 0, sizeof(ares->hinfo)); - ares->hinfo.port = -1; ares->hinfo.rrname = rrname; - ares->num_pending++; /* one more */ + async->queries_ongoing++; ares_query_dnsrec(ares->channel, rrname ? rrname : async->hostname, ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, - async_ares_rr_done, data, NULL); + async_ares_rr_done, async, NULL); } #endif @@ -640,9 +650,8 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, * 2. When we lazy init the ares channel and NULL means that there * are no preferences and we do not reset any existing channel. */ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, - bool reset_on_null) + struct Curl_resolv_async *async) { - struct Curl_resolv_async *async = data->state.async; struct async_ares_ctx *ares = async ? &async->ares : NULL; CURLcode result = CURLE_NOT_BUILT_IN; const char *servers = data->set.str[STRING_DNS_SERVERS]; @@ -653,12 +662,8 @@ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, servers = getenv("CURL_DNS_SERVER"); #endif - if(!servers) { - if(reset_on_null) { - Curl_async_destroy(data); - } + if(!servers) return CURLE_OK; - } /* if channel is not there, this is a parameter check */ if(ares && ares->channel) @@ -681,14 +686,9 @@ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, return result; } -CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data) +static CURLcode async_ares_set_dns_interface(struct Curl_easy *data, + struct Curl_resolv_async *async) { - return async_ares_set_dns_servers(data, TRUE); -} - -CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data) -{ - struct Curl_resolv_async *async = data->state.async; struct async_ares_ctx *ares = async ? &async->ares : NULL; const char *interf = data->set.str[STRING_DNS_INTERFACE]; @@ -702,9 +702,9 @@ CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data) return CURLE_OK; } -CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data) +static CURLcode async_ares_set_dns_local_ip4(struct Curl_easy *data, + struct Curl_resolv_async *async) { - struct Curl_resolv_async *async = data->state.async; struct async_ares_ctx *ares = async ? &async->ares : NULL; struct in_addr a4; const char *local_ip4 = data->set.str[STRING_DNS_LOCAL_IP4]; @@ -726,10 +726,10 @@ CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data) return CURLE_OK; } -CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data) +static CURLcode async_ares_set_dns_local_ip6(struct Curl_easy *data, + struct Curl_resolv_async *async) { #ifdef USE_IPV6 - struct Curl_resolv_async *async = data->state.async; struct async_ares_ctx *ares = async ? &async->ares : NULL; unsigned char a6[INET6_ADDRSTRLEN]; const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6]; @@ -752,6 +752,7 @@ CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data) return CURLE_OK; #else /* no IPv6 support */ (void)data; + (void)async; return CURLE_NOT_BUILT_IN; #endif } diff --git a/lib/asyn-base.c b/lib/asyn-base.c index ab2d5fa239..027afa2abf 100644 --- a/lib/asyn-base.c +++ b/lib/asyn-base.c @@ -42,9 +42,11 @@ #endif #include "urldata.h" +#include "connect.h" #include "curl_trc.h" #include "hostip.h" #include "multiif.h" +#include "progress.h" #include "select.h" #include "url.h" @@ -53,6 +55,17 @@ **********************************************************************/ #ifdef CURLRES_ASYNCH +timediff_t Curl_async_timeleft_ms(struct Curl_easy *data, + struct Curl_resolv_async *async) +{ + if(async->timeout_ms) { + timediff_t elapsed_ms = + curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); + return async->timeout_ms - elapsed_ms; + } + return Curl_timeleft_ms(data); +} + #ifdef USE_ARES #if ARES_VERSION < 0x011000 @@ -71,12 +84,8 @@ CURLcode Curl_ares_pollset(struct Curl_easy *data, ares_channel channel, struct easy_pollset *ps) { - struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 }; - struct timeval timebuf; curl_socket_t sockets[16]; /* ARES documented limit */ unsigned int bitmap, i; - struct timeval *timeout; - timediff_t milli; CURLcode result = CURLE_OK; DEBUGASSERT(channel); @@ -97,15 +106,33 @@ CURLcode Curl_ares_pollset(struct Curl_easy *data, if(result) return result; } - - timeout = ares_timeout(channel, &maxtime, &timebuf); - if(!timeout) - timeout = &maxtime; - milli = curlx_tvtoms(timeout); - Curl_expire(data, milli, EXPIRE_ASYNC_NAME); return result; } +timediff_t Curl_ares_timeout_ms(struct Curl_easy *data, + struct Curl_resolv_async *async, + ares_channel channel) +{ + timediff_t async_timeout_ms; + + DEBUGASSERT(channel); + if(!channel) + return -1; + + async_timeout_ms = Curl_async_timeleft_ms(data, async); + if((async_timeout_ms > 0) && (async_timeout_ms < INT_MAX)) { + struct timeval timebuf; + struct timeval *timeout; + struct timeval end = { (int)async_timeout_ms / 1000, + ((int)async_timeout_ms % 1000) * 1000 }; + + timeout = ares_timeout(channel, &end, &timebuf); + if(timeout) + return curlx_tvtoms(timeout); + } + return async_timeout_ms; +} + /* * Curl_ares_perform() * @@ -178,36 +205,40 @@ int Curl_ares_perform(ares_channel channel, timediff_t timeout_ms) #include "doh.h" -void Curl_async_shutdown(struct Curl_easy *data) +void Curl_async_shutdown(struct Curl_easy *data, + struct Curl_resolv_async *async) { - if(data->state.async) { - CURL_TRC_DNS(data, "shutdown async"); + if(async) { + CURL_TRC_DNS(data, "[%u] shutdown async", async->id); + async->shutdown = TRUE; #ifdef USE_RESOLV_ARES - Curl_async_ares_shutdown(data, data->state.async); + Curl_async_ares_shutdown(data, async); #endif #ifdef USE_RESOLV_THREADED - Curl_async_thrdd_shutdown(data, data->state.async); + Curl_async_thrdd_shutdown(data, async); #endif #ifndef CURL_DISABLE_DOH - Curl_doh_cleanup(data, data->state.async); + Curl_doh_cleanup(data, async); #endif } } -void Curl_async_destroy(struct Curl_easy *data) +void Curl_async_destroy(struct Curl_easy *data, + struct Curl_resolv_async *async) { - if(data->state.async) { - CURL_TRC_DNS(data, "destroy async"); + if(async) { + CURL_TRC_DNS(data, "[%u] destroy async", async->id); + async->shutdown = TRUE; #ifdef USE_RESOLV_ARES - Curl_async_ares_destroy(data, data->state.async); + Curl_async_ares_destroy(data, async); #endif #ifdef USE_RESOLV_THREADED - Curl_async_thrdd_destroy(data, data->state.async); + Curl_async_thrdd_destroy(data, async); #endif #ifndef CURL_DISABLE_DOH - Curl_doh_cleanup(data, data->state.async); + Curl_doh_cleanup(data, async); #endif - Curl_safefree(data->state.async); + Curl_safefree(async); } } diff --git a/lib/asyn-thrdd.c b/lib/asyn-thrdd.c index a339ea3f7c..7611999895 100644 --- a/lib/asyn-thrdd.c +++ b/lib/asyn-thrdd.c @@ -99,16 +99,6 @@ void Curl_async_global_cleanup(void) #endif } -CURLcode Curl_async_get_impl(struct Curl_easy *data, - struct Curl_resolv_async *async, - void **impl) -{ - (void)data; - (void)async; - *impl = NULL; - return CURLE_OK; -} - #ifdef CURLVERBOSE #define CURL_ASYN_ITEM_DESC_LEN 64 #define async_item_description(x) (x)->description @@ -123,10 +113,10 @@ struct async_thrdd_item { #endif int sock_error; uint32_t mid; - uint32_t async_id; + uint32_t resolv_id; uint16_t port; - uint8_t ip_version; uint8_t transport; + uint8_t dns_queries; #ifdef DEBUGBUILD uint32_t delay_ms; uint32_t delay_fail_ms; @@ -147,13 +137,12 @@ static void async_thrdd_item_destroy(struct async_thrdd_item *item) /* Initialize context for threaded resolver */ static struct async_thrdd_item * async_thrdd_item_create(struct Curl_easy *data, + uint32_t resolv_id, uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, - uint32_t async_id) + uint8_t transport) { size_t hostlen = strlen(hostname); struct async_thrdd_item *item; - VERBOSE(const char *qtype); item = curlx_calloc(1, sizeof(*item) + hostlen); if(!item) @@ -161,18 +150,18 @@ async_thrdd_item_create(struct Curl_easy *data, if(hostlen) /* NUL byte of name already in struct size */ memcpy(item->hostname, hostname, hostlen); - item->port = port; - item->ip_version = ip_version; - item->transport = transport; item->mid = data->mid; - item->async_id = async_id; + item->resolv_id = resolv_id; + item->dns_queries = dns_queries; + item->port = port; + item->transport = transport; #ifdef CURLVERBOSE - qtype = (ip_version == CURL_IPRESOLVE_WHATEVER) ? "A+AAAA": - ((ip_version == CURL_IPRESOLVE_V6) ? "AAAA" : "A"); curl_msnprintf(item->description, sizeof(item->description), - "[%" FMT_OFF_T "/%d] %s %s:%u", - data->id, item->async_id, qtype, item->hostname, item->port); + "[%" FMT_OFF_T "/%u] %s %s:%u", + data->id, item->resolv_id, + Curl_resolv_query_str(dns_queries), + item->hostname, item->port); #endif #ifdef DEBUGBUILD @@ -205,14 +194,18 @@ static void async_thrdd_rr_done(void *user_data, ares_status_t status, size_t timeouts, const ares_dns_record_t *dnsrec) { - struct Curl_easy *data = user_data; - struct async_thrdd_ctx *thrdd = &data->state.async->thrdd; + struct Curl_resolv_async *async = user_data; + struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL; (void)timeouts; - thrdd->rr.done = TRUE; - if((ARES_SUCCESS != status) || !dnsrec) + if(!thrdd) return; - thrdd->rr.result = Curl_httpsrr_from_ares(data, dnsrec, &thrdd->rr.hinfo); + + async->dns_responses |= CURL_DNSQ_HTTPS; + async->queries_ongoing--; + async->done = !async->queries_ongoing; + if((ARES_SUCCESS == status) && dnsrec) + async->result = Curl_httpsrr_from_ares(dnsrec, &thrdd->rr.hinfo); } static CURLcode async_rr_start(struct Curl_easy *data, @@ -246,13 +239,13 @@ static CURLcode async_rr_start(struct Curl_easy *data, #endif memset(&thrdd->rr.hinfo, 0, sizeof(thrdd->rr.hinfo)); - thrdd->rr.hinfo.port = -1; thrdd->rr.hinfo.rrname = rrname; ares_query_dnsrec(thrdd->rr.channel, rrname ? rrname : async->hostname, ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, - async_thrdd_rr_done, data, NULL); - CURL_TRC_DNS(data, "Issued HTTPS-RR request for %s", + async_thrdd_rr_done, async, NULL); + async->queries_ongoing++; + CURL_TRC_DNS(data, "[HTTPS-RR] initiated request for %s", rrname ? rrname : async->hostname); return CURLE_OK; } @@ -275,7 +268,7 @@ void Curl_async_thrdd_destroy(struct Curl_easy *data, struct Curl_resolv_async *async) { (void)data; - if(async->thrdd.queued && !async->thrdd.done && + if(async->queries_ongoing && !async->done && data->multi && data->multi->resolv_thrdq) { /* Remove any resolve items still queued */ Curl_thrdq_clear(data->multi->resolv_thrdq, @@ -298,17 +291,19 @@ void Curl_async_thrdd_destroy(struct Curl_easy *data, * Waits for a resolve to finish. This function should be avoided since using * this risk getting the multi interface to "hang". */ -CURLcode Curl_async_await(struct Curl_easy *data, - struct Curl_resolv_async *async, +CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id, struct Curl_dns_entry **pdns) { - struct async_thrdd_ctx *thrdd = &async->thrdd; + struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); + struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL; timediff_t milli, ms; - CURL_TRC_DNS(data, "[async] await %s", async->hostname); - while(thrdd->queued && !thrdd->done) { + if(!thrdd) + return CURLE_FAILED_INIT; + + while(async->queries_ongoing && !async->done) { Curl_async_thrdd_multi_process(data->multi); - if(thrdd->done) + if(async->done) break; ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); @@ -320,8 +315,7 @@ CURLcode Curl_async_await(struct Curl_easy *data, milli = 50; else milli = 200; - CURL_TRC_DNS(data, "[async] await, waiting %" FMT_TIMEDIFF_T "ms", - milli); + CURL_TRC_DNS(data, "await, waiting %" FMT_TIMEDIFF_T "ms", milli); curlx_wait_ms(milli); } return Curl_async_take_result(data, async, pdns); @@ -350,19 +344,23 @@ static void async_thrdd_item_process(void *arg) memset(&hints, 0, sizeof(hints)); #ifdef CURLRES_IPV6 - if(item->ip_version != CURL_IPRESOLVE_V4) { - pf = (item->ip_version == CURL_IPRESOLVE_V6) ? PF_INET6 : PF_UNSPEC; + if(item->dns_queries & CURL_DNSQ_AAAA) { + pf = (item->dns_queries & CURL_DNSQ_A) ? PF_UNSPEC : PF_INET6; } #endif hints.ai_family = pf; hints.ai_socktype = Curl_socktype_for_transport(item->transport); hints.ai_protocol = Curl_protocol_for_transport(item->transport); +#ifdef __APPLE__ + /* If we leave `ai_flags == 0` then macOS is looking for IPV4MAPPED + * when doing AAAA queries. We do not want this "help". */ + hints.ai_flags = AI_ADDRCONFIG; +#endif + + curl_msnprintf(service, sizeof(service), "%u", item->port); #ifdef AI_NUMERICSERV - /* Without service and flags, resolvers might lookup up in more - * places than we want them to, causing a delay. */ hints.ai_flags |= AI_NUMERICSERV; #endif - curl_msnprintf(service, sizeof(service), "%u", item->port); rc = Curl_getaddrinfo_ex(item->hostname, service, &hints, &item->res); if(rc) { @@ -433,7 +431,7 @@ CURLcode Curl_async_thrdd_multi_init(struct Curl_multi *multi, { CURLcode result; DEBUGASSERT(!multi->resolv_thrdq); - result = Curl_thrdq_create(&multi->resolv_thrdq, "async", 0, + result = Curl_thrdq_create(&multi->resolv_thrdq, "DNS", 0, min_threads, max_threads, idle_time_ms, async_thrdd_item_free, async_thrdd_item_process, @@ -471,9 +469,12 @@ static void async_thrdd_report_item(struct Curl_easy *data, struct dynbuf tmp; const char *sep = ""; const struct Curl_addrinfo *ai = item->res; - int ai_family = (item->ip_version == CURL_IPRESOLVE_V6) ? AF_INET6 : AF_INET; + int ai_family = (item->dns_queries & CURL_DNSQ_AAAA) ? AF_INET6 : AF_INET; CURLcode result; + if(!Curl_trc_is_verbose(data)) + return; + curlx_dyn_init(&tmp, 1024); for(; ai; ai = ai->ai_next) { if(ai->ai_family == ai_family) { @@ -489,7 +490,7 @@ static void async_thrdd_report_item(struct Curl_easy *data, infof(data, "Host %s:%u resolved IPv%c: %s", item->hostname, item->port, - (item->ip_version == CURL_IPRESOLVE_V6) ? '6' : '4', + (item->dns_queries & CURL_DNSQ_AAAA) ? '6' : '4', (curlx_dyn_len(&tmp) ? curlx_dyn_ptr(&tmp) : "(none)")); out: curlx_dyn_free(&tmp); @@ -507,20 +508,20 @@ void Curl_async_thrdd_multi_process(struct Curl_multi *multi) while(!Curl_thrdq_recv(multi->resolv_thrdq, &qitem)) { /* dispatch resolve result */ struct async_thrdd_item *item = qitem; + struct Curl_resolv_async *async = NULL; data = Curl_multi_get_easy(multi, item->mid); - /* there is a chance that the original resolve was discarded and - * either no new, or a new resolve with a different id is ongoing. */ - if(data && data->conn && data->state.async && - (data->state.async->id == item->async_id)) { - struct Curl_resolv_async *async = data->state.async; + if(data) + async = Curl_async_get(data, item->resolv_id); + if(async) { struct async_thrdd_item **pdest = &async->thrdd.res_A; - --async->thrdd.queued; - async->thrdd.done = !async->thrdd.queued; + async->dns_responses |= item->dns_queries; + --async->queries_ongoing; + async->done = !async->queries_ongoing; #ifdef CURLRES_IPV6 - if(item->ip_version == CURL_IPRESOLVE_V6) + if(item->dns_queries & CURL_DNSQ_AAAA) pdest = &async->thrdd.res_AAAA; #endif if(!*pdest) { @@ -535,7 +536,7 @@ void Curl_async_thrdd_multi_process(struct Curl_multi *multi) async_thrdd_item_free(item); } #ifdef CURLVERBOSE - Curl_thrdq_trace(multi->resolv_thrdq, multi->admin, &Curl_trc_feat_dns); + Curl_thrdq_trace(multi->resolv_thrdq, multi->admin); #endif } @@ -550,25 +551,25 @@ CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, static CURLcode async_thrdd_query(struct Curl_easy *data, struct Curl_resolv_async *async, - uint8_t query_version) + uint8_t dns_queries) { struct async_thrdd_item *item; CURLcode result; - item = async_thrdd_item_create(data, async->hostname, async->port, - query_version, async->transport, - async->id); + item = async_thrdd_item_create(data, async->id, dns_queries, + async->hostname, async->port, + async->transport); if(!item) { result = CURLE_OUT_OF_MEMORY; goto out; } - CURL_TRC_DNS(data, "[async] queueing %s", item->description); + CURL_TRC_DNS(data, "queueing query %s", item->description); result = Curl_thrdq_send(data->multi->resolv_thrdq, item, async_item_description(item), async->timeout_ms); if(result) goto out; item = NULL; - async->thrdd.queued++; + async->queries_ongoing++; out: if(item) @@ -580,89 +581,87 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct Curl_resolv_async *async) { CURLcode result = CURLE_FAILED_INIT; + void *resolver = NULL; - if(async->thrdd.queued || async->thrdd.done) + if(async->queries_ongoing || async->done) return CURLE_FAILED_INIT; - switch(async->ip_version) { -#ifdef CURL_IPRESOLVE_V6 - case CURL_IPRESOLVE_V6: - result = async_thrdd_query(data, async, CURL_IPRESOLVE_V6); - break; +#ifdef USE_HTTPSRR_ARES + DEBUGASSERT(!async->thrdd.rr.channel); + if((async->dns_queries & CURL_DNSQ_HTTPS) && !async->is_ipaddr) { + result = async_rr_start(data, async); + if(result) + goto out; + resolver = async->thrdd.rr.channel; + } #endif - case CURL_IPRESOLVE_V4: - result = async_thrdd_query(data, async, CURL_IPRESOLVE_V4); - break; - default: + + result = Curl_resolv_announce_start(data, resolver); + if(result) + return result; + #ifdef CURL_IPRESOLVE_V6 - /* When resolving IP addresses, some resolvers (e.g. macOS) will - * happily "embed" an IPv4 address into the IPv6 equivalent. - * This will then confuse FTP that has been told an IPv4 for - * DATA, but suddenly sees IPv6. */ - if(Curl_ipv6works(data) && !Curl_is_ipv4addr(async->hostname)) { - result = async_thrdd_query(data, async, CURL_IPRESOLVE_V6); - if(result) - goto out; - } + /* Do not start an AAAA query for an ipv4 address when + * we will start an A query for it. */ + if((async->dns_queries & CURL_DNSQ_AAAA) && + !(async->is_ipv4addr && (async->dns_queries & CURL_DNSQ_A))) { + result = async_thrdd_query(data, async, CURL_DNSQ_AAAA); + if(result) + goto out; + } #endif - result = async_thrdd_query(data, async, CURL_IPRESOLVE_V4); - break; + if(async->dns_queries & CURL_DNSQ_A) { + result = async_thrdd_query(data, async, CURL_DNSQ_A); + if(result) + goto out; } if(result) goto out; #ifdef CURLVERBOSE - Curl_thrdq_trace(data->multi->resolv_thrdq, data, &Curl_trc_feat_dns); -#endif - -#ifdef USE_HTTPSRR_ARES - DEBUGASSERT(!async->thrdd.rr.channel); - if(async_rr_start(data, async)) - infof(data, "Failed HTTPS RR operation"); + Curl_thrdq_trace(data->multi->resolv_thrdq, data); #endif out: if(result) - CURL_TRC_DNS(data, "[async] error queueing %s:%d -> %d", + CURL_TRC_DNS(data, "error queueing query %s:%d -> %d", async->hostname, async->port, result); return result; } -CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps) +CURLcode Curl_async_pollset(struct Curl_easy *data, + struct Curl_resolv_async *async, + struct easy_pollset *ps) { - struct Curl_resolv_async *async = data->state.async; - struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL; + timediff_t timeout_ms; - if(!thrdd) - return CURLE_OK; + timeout_ms = Curl_async_timeleft_ms(data, async); #ifdef USE_HTTPSRR_ARES - if(thrdd->rr.channel) { - CURLcode result = Curl_ares_pollset(data, thrdd->rr.channel, ps); + if(async->thrdd.rr.channel) { + CURLcode result = Curl_ares_pollset(data, async->thrdd.rr.channel, ps); if(result) return result; + timeout_ms = Curl_ares_timeout_ms(data, async, async->thrdd.rr.channel); } #else (void)ps; #endif - if(!thrdd->done) { -#ifdef ENABLE_WAKEUP - /* The multi "wakeup" socket pair triggers result processing, - * no need for an extra timer. */ - (void)data; -#else - timediff_t milli; - timediff_t ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); - if(ms < 3) - milli = 1; - else if(ms <= 50) - milli = ms / 3; - else if(ms <= 250) - milli = 50; + if(!async->done) { +#ifndef ENABLE_WAKEUP + timediff_t stutter_ms, elapsed_ms; + elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); + if(elapsed_ms < 3) + stutter_ms = 1; + else if(elapsed_ms <= 50) + stutter_ms = elapsed_ms / 3; + else if(elapsed_ms <= 250) + stutter_ms = 50; else - milli = 200; - Curl_expire(data, milli, EXPIRE_ASYNC_NAME); + stutter_ms = 200; + timeout_ms = CURLMIN(stutter_ms, timeout_ms); #endif + Curl_expire(data, timeout_ms, EXPIRE_ASYNC_NAME); } return CURLE_OK; } @@ -677,11 +676,12 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, struct Curl_dns_entry **pdns) { struct async_thrdd_ctx *thrdd = &async->thrdd; + struct Curl_dns_entry *dns = NULL; CURLcode result = CURLE_OK; DEBUGASSERT(pdns); *pdns = NULL; - if(!thrdd->queued && !thrdd->done) { + if(!async->queries_ongoing && !async->done) { DEBUGASSERT(0); return CURLE_FAILED_INIT; } @@ -692,52 +692,60 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, (void)Curl_ares_perform(thrdd->rr.channel, 0); #endif - if(!thrdd->done) + if(!async->done) return CURLE_AGAIN; Curl_expire_done(data, EXPIRE_ASYNC_NAME); + if(async->result) + goto out; + if((thrdd->res_A && thrdd->res_A->res) || (thrdd->res_AAAA && thrdd->res_AAAA->res)) { - struct Curl_dns_entry *dns = - Curl_dnscache_mk_entry2(data, - thrdd->res_A ? &thrdd->res_A->res : NULL, - thrdd->res_AAAA ? &thrdd->res_AAAA->res : NULL, - async->hostname, async->port, async->ip_version); - if(!dns) + dns = Curl_dnscache_mk_entry2( + data, async->dns_queries, + thrdd->res_A ? &thrdd->res_A->res : NULL, + thrdd->res_AAAA ? &thrdd->res_AAAA->res : NULL, + async->hostname, async->port); + if(!dns) { result = CURLE_OUT_OF_MEMORY; + goto out; + } #ifdef USE_HTTPSRR_ARES - if(!result && thrdd->rr.channel) { - result = thrdd->rr.result; - if(!result) { - struct Curl_https_rrinfo *lhrr; + 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) + if(!lhrr) { result = CURLE_OUT_OF_MEMORY; - else - dns->hinfo = lhrr; + goto out; + } } + Curl_httpsrr_trace(data, lhrr); + Curl_dns_entry_set_https_rr(dns, lhrr); } #endif - if(!result && dns) { - CURL_TRC_DNS(data, "[async] resolving complete"); - *pdns = dns; - dns = NULL; - } - Curl_dns_entry_unlink(data, &dns); } + if(dns) { + CURL_TRC_DNS(data, "resolving complete"); + *pdns = dns; + dns = NULL; + } #ifdef CURLVERBOSE - Curl_thrdq_trace(data->multi->resolv_thrdq, data, &Curl_trc_feat_dns); + Curl_thrdq_trace(data->multi->resolv_thrdq, data); #endif + +out: + Curl_dns_entry_unlink(data, &dns); + Curl_async_thrdd_shutdown(data, async); if(!result && !*pdns) result = Curl_resolver_error(data, NULL); - Curl_async_thrdd_shutdown(data, async); if(result && (result != CURLE_COULDNT_RESOLVE_HOST) && (result != CURLE_COULDNT_RESOLVE_PROXY)) { - CURL_TRC_DNS(data, "[async] %s:%d: error %d", - async->hostname, async->port, result); + CURL_TRC_DNS(data, "Error %d resolving %s:%d", + result, async->hostname, async->port); } return result; } @@ -780,4 +788,30 @@ Curl_async_get_ai(struct Curl_easy *data, return NULL; } +#ifdef USE_HTTPSRR +const struct Curl_https_rrinfo * +Curl_async_get_https(struct Curl_easy *data, + struct Curl_resolv_async *async) +{ +#ifdef USE_HTTPSRR_ARES + if(Curl_async_knows_https(data, async)) + return &async->thrdd.rr.hinfo; +#else + (void)data; + (void)async; +#endif + return NULL; +} + +bool Curl_async_knows_https(struct Curl_easy *data, + struct Curl_resolv_async *async) +{ + (void)data; + if(async->dns_queries & CURL_DNSQ_HTTPS) + return ((async->dns_responses & CURL_DNSQ_HTTPS) || async->done); + return TRUE; /* we know it will never come */ +} + +#endif /* USE_HTTPSRR */ + #endif /* USE_RESOLV_THREADED */ diff --git a/lib/asyn.h b/lib/asyn.h index de9b5a5604..763a1a9e7a 100644 --- a/lib/asyn.h +++ b/lib/asyn.h @@ -33,6 +33,7 @@ struct Curl_easy; struct Curl_dns_entry; struct Curl_resolv_async; struct Curl_multi; +struct easy_pollset; #ifdef CURLRES_ASYNCH @@ -67,49 +68,6 @@ int Curl_async_global_init(void); */ void Curl_async_global_cleanup(void); -/* - * Curl_async_get_impl() - * Get the resolver implementation instance (c-ares channel) or NULL - * for passing to application callback. - */ -CURLcode Curl_async_get_impl(struct Curl_easy *easy, - struct Curl_resolv_async *async, - void **impl); - -/* Curl_async_pollset() - * - * This function is called from the Curl_multi_pollset() function. 'sock' is a - * pointer to an array to hold the file descriptors, with 'numsock' being the - * size of that array (in number of entries). This function is supposed to - * return bitmask indicating what file descriptors (referring to array indexes - * in the 'sock' array) to wait for, read/write. - */ -CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps); - -/* - * Take the result of an async resolve operation. - * Returns CURLE_OK with `*pdns` != NULL, CURLE_AGAIN while still - * ongoing or an error code for a failed resolve. - */ -CURLcode Curl_async_take_result(struct Curl_easy *data, - struct Curl_resolv_async *async, - struct Curl_dns_entry **pdns); - -/* - * Curl_async_await() - * - * Waits for a resolve to finish. This function should be avoided since using - * this risk getting the multi interface to "hang". - * - * On return 'entry' is assigned the resolved dns (CURLE_OK or NULL otherwise. - * - * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, - * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. - */ -CURLcode Curl_async_await(struct Curl_easy *data, - struct Curl_resolv_async *async, - struct Curl_dns_entry **pdns); - /* * Curl_async_getaddrinfo() - when using this resolver * @@ -129,6 +87,14 @@ Curl_async_get_ai(struct Curl_easy *data, struct Curl_resolv_async *async, int ai_family, unsigned int index); +#ifdef USE_HTTPSRR +const struct Curl_https_rrinfo * +Curl_async_get_https(struct Curl_easy *data, + struct Curl_resolv_async *async); +bool Curl_async_knows_https(struct Curl_easy *data, + struct Curl_resolv_async *async); +#endif /* USE_HTTPSRR */ + #ifdef USE_ARES /* common functions for c-ares and threaded resolver with HTTPSRR */ #include @@ -137,6 +103,10 @@ CURLcode Curl_ares_pollset(struct Curl_easy *data, ares_channel channel, struct easy_pollset *ps); +timediff_t Curl_ares_timeout_ms(struct Curl_easy *data, + struct Curl_resolv_async *async, + ares_channel channel); + int Curl_ares_perform(ares_channel channel, timediff_t timeout_ms); #endif @@ -144,7 +114,6 @@ int Curl_ares_perform(ares_channel channel, timediff_t timeout_ms); /* async resolving implementation using c-ares alone */ struct async_ares_ctx { ares_channel channel; - int num_pending; /* number of outstanding c-ares requests */ struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares parts */ int ares_status; /* ARES_SUCCESS, ARES_ENOTFOUND, etc. */ @@ -160,18 +129,6 @@ void Curl_async_ares_shutdown(struct Curl_easy *data, void Curl_async_ares_destroy(struct Curl_easy *data, struct Curl_resolv_async *async); -/* Set the DNS server to use by ares, from `data` settings. */ -CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data); - -/* Set the DNS interfacer to use by ares, from `data` settings. */ -CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data); - -/* Set the local ipv4 address to use by ares, from `data` settings. */ -CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data); - -/* Set the local ipv6 address to use by ares, from `data` settings. */ -CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data); - #endif /* USE_RESOLV_ARES */ #ifdef USE_RESOLV_THREADED @@ -186,12 +143,8 @@ struct async_thrdd_ctx { struct { ares_channel channel; struct Curl_https_rrinfo hinfo; - CURLcode result; - BIT(done); } rr; #endif - uint32_t queued; - BIT(done); }; void Curl_async_thrdd_shutdown(struct Curl_easy *data, @@ -217,15 +170,50 @@ CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, struct doh_probes; #endif +/* + * Curl_async_await() + * + * Waits for a resolve to finish. This function should be avoided since using + * this risk getting the multi interface to "hang". + * + * On return 'entry' is assigned the resolved dns (CURLE_OK or NULL otherwise. + * + * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, + * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. + */ +CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id, + struct Curl_dns_entry **pdns); + +/* + * Take the result of an async resolve operation. + * Returns CURLE_OK with `*pdns` != NULL, CURLE_AGAIN while still + * ongoing or an error code for a failed resolve. + */ +CURLcode Curl_async_take_result(struct Curl_easy *data, + struct Curl_resolv_async *async, + struct Curl_dns_entry **pdns); + +/* Curl_async_pollset() + * + * This function is called from the Curl_multi_pollset() function. 'sock' is a + * pointer to an array to hold the file descriptors, with 'numsock' being the + * size of that array (in number of entries). This function is supposed to + * return bitmask indicating what file descriptors (referring to array indexes + * in the 'sock' array) to wait for, read/write. + */ +CURLcode Curl_async_pollset(struct Curl_easy *data, + struct Curl_resolv_async *async, + struct easy_pollset *ps); + #else /* CURLRES_ASYNCH */ /* convert these functions if an asynch resolver is not used */ -#define Curl_async_get_impl(x, y, z) (*(z) = NULL, CURLE_OK) -#define Curl_async_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST -#define Curl_async_await(x, y, z) CURLE_COULDNT_RESOLVE_HOST #define Curl_async_global_init() CURLE_OK #define Curl_async_global_cleanup() Curl_nop_stmt #define Curl_async_get_ai(a,b,c,d) NULL +#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 #endif /* !CURLRES_ASYNCH */ #if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) @@ -233,7 +221,9 @@ struct doh_probes; #endif #ifdef USE_CURL_ASYNC + struct Curl_resolv_async { + struct Curl_resolv_async *next; #ifdef USE_RESOLV_ARES struct async_ares_ctx ares; #elif defined(USE_RESOLV_THREADED) @@ -245,31 +235,35 @@ struct Curl_resolv_async { struct curltime start; timediff_t interval_end; timediff_t timeout_ms; + CURLcode result; uint32_t poll_interval; uint32_t id; /* unique id per easy handle of the resolve operation */ /* what is being resolved */ uint16_t port; - uint8_t ip_version; + uint8_t dns_queries; /* what queries are being performed */ + uint8_t dns_responses; /* what queries had responses so far. */ uint8_t transport; + uint8_t queries_ongoing; + BIT(is_ipaddr); + BIT(is_ipv4addr); + BIT(done); + BIT(shutdown); char hostname[1]; }; -/* - * Curl_async_shutdown(). - * - * This shuts down all ongoing operations. - */ -void Curl_async_shutdown(struct Curl_easy *data); +timediff_t Curl_async_timeleft_ms(struct Curl_easy *data, + struct Curl_resolv_async *async); + +/* Shut down the given async resolve. */ +void Curl_async_shutdown(struct Curl_easy *data, + struct Curl_resolv_async *async); + +/* Frees the resources of the given async resolve and the struct itself. */ +void Curl_async_destroy(struct Curl_easy *data, + struct Curl_resolv_async *async); -/* - * Curl_async_destroy(). - * - * This frees the resources of any async resolve. - */ -void Curl_async_destroy(struct Curl_easy *data); #else /* !USE_CURL_ASYNC */ -#define Curl_async_shutdown(x) Curl_nop_stmt -#define Curl_async_destroy(x) Curl_nop_stmt +#define Curl_async_shutdown(x,y) Curl_nop_stmt #endif /* USE_CURL_ASYNC */ /********** end of generic resolver interface functions *****************/ diff --git a/lib/cf-dns.c b/lib/cf-dns.c index a3bf9a5620..30ba1cb94d 100644 --- a/lib/cf-dns.c +++ b/lib/cf-dns.c @@ -37,8 +37,9 @@ struct cf_dns_ctx { struct Curl_dns_entry *dns; CURLcode resolv_result; + uint32_t resolv_id; uint16_t port; - uint8_t ip_version; + uint8_t dns_queries; uint8_t transport; BIT(started); BIT(announced); @@ -47,9 +48,8 @@ struct cf_dns_ctx { }; static struct cf_dns_ctx * -cf_dns_ctx_create(struct Curl_easy *data, - const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, +cf_dns_ctx_create(struct Curl_easy *data, uint8_t dns_queries, + const char *hostname, uint16_t port, uint8_t transport, bool abstract_unix_socket, struct Curl_dns_entry *dns) { @@ -61,7 +61,7 @@ cf_dns_ctx_create(struct Curl_easy *data, return NULL; ctx->port = port; - ctx->ip_version = ip_version; + ctx->dns_queries = dns_queries; ctx->transport = transport; ctx->abstract_unix_socket = abstract_unix_socket; ctx->dns = Curl_dns_entry_link(data, dns); @@ -168,8 +168,9 @@ static CURLcode cf_dns_start(struct Curl_cfilter *cf, /* Resolve target host right on */ CURL_TRC_CF(data, cf, "resolve host %s:%u", ctx->hostname, ctx->port); - result = Curl_resolv(data, ctx->hostname, ctx->port, ctx->ip_version, - ctx->transport, timeout_ms, pdns); + result = Curl_resolv(data, ctx->dns_queries, + ctx->hostname, ctx->port, ctx->transport, + timeout_ms, &ctx->resolv_id, pdns); DEBUGASSERT(!result || !*pdns); if(!result) { /* resolved right away, either sync or from dnscache */ DEBUGASSERT(*pdns); @@ -210,9 +211,11 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, } if(!ctx->dns && !ctx->resolv_result) { - ctx->resolv_result = Curl_resolv_take_result(data, &ctx->dns); + 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 not complete yet"); + CURL_TRC_CF(data, cf, "DNS resolution ongoing for %s:%u", + ctx->hostname, ctx->port); } if(ctx->resolv_result) { @@ -242,7 +245,7 @@ static CURLcode cf_dns_connect(struct Curl_cfilter *cf, * that one's lock. */ DEBUGASSERT(*done); cf->connected = TRUE; - Curl_async_shutdown(data); + Curl_resolv_destroy(data, ctx->resolv_id); Curl_dns_entry_unlink(data, &ctx->dns); return CURLE_OK; } @@ -266,8 +269,14 @@ static CURLcode cf_dns_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, struct easy_pollset *ps) { +#ifdef USE_CURL_ASYNC if(!cf->connected) return Curl_resolv_pollset(data, ps); +#else + (void)cf; + (void)data; + (void)ps; +#endif return CURLE_OK; } @@ -315,9 +324,9 @@ struct Curl_cftype Curl_cft_dns = { static CURLcode cf_dns_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, bool abstract_unix_socket, struct Curl_dns_entry *dns) @@ -327,7 +336,7 @@ static CURLcode cf_dns_create(struct Curl_cfilter **pcf, CURLcode result = CURLE_OK; (void)data; - ctx = cf_dns_ctx_create(data, hostname, port, ip_version, transport, + ctx = cf_dns_ctx_create(data, dns_queries, hostname, port, transport, abstract_unix_socket, dns); if(!ctx) { result = CURLE_OUT_OF_MEMORY; @@ -347,13 +356,13 @@ out: * out the hostname/path and port where to connect to. */ static CURLcode cf_dns_conn_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + uint8_t dns_queries, uint8_t transport, struct Curl_dns_entry *dns) { struct connectdata *conn = data->conn; const char *hostname = NULL; uint16_t port = 0; - uint8_t ip_version = conn->ip_version; bool abstract_unix_socket = FALSE; #ifdef USE_UNIX_SOCKETS @@ -391,8 +400,9 @@ static CURLcode cf_dns_conn_create(struct Curl_cfilter **pcf, DEBUGASSERT(0); return CURLE_FAILED_INIT; } - return cf_dns_create(pcf, data, hostname, port, ip_version, - transport, abstract_unix_socket, dns); + return cf_dns_create(pcf, data, dns_queries, + hostname, port, transport, + abstract_unix_socket, dns); } /* Adds a "resolv" filter at the top of the connection's filter chain. @@ -404,6 +414,7 @@ static CURLcode cf_dns_conn_create(struct Curl_cfilter **pcf, CURLcode Curl_cf_dns_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, + uint8_t dns_queries, uint8_t transport, struct Curl_dns_entry *dns) { @@ -412,10 +423,11 @@ CURLcode Curl_cf_dns_add(struct Curl_easy *data, DEBUGASSERT(data); if(sockindex == FIRSTSOCKET) - result = cf_dns_conn_create(&cf, data, transport, dns); + result = cf_dns_conn_create(&cf, data, dns_queries, transport, dns); else if(dns) { - result = cf_dns_create(&cf, data, dns->hostname, dns->port, - dns->ip_version, transport, FALSE, dns); + result = cf_dns_create(&cf, data, dns_queries, + dns->hostname, dns->port, transport, + FALSE, dns); } else { DEBUGASSERT(0); @@ -436,16 +448,17 @@ out: */ CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport) { struct Curl_cfilter *cf; CURLcode result; - result = cf_dns_create(&cf, data, hostname, port, ip_version, - transport, FALSE, NULL); + result = cf_dns_create(&cf, data, dns_queries, + hostname, port, transport, + FALSE, NULL); if(result) return result; @@ -515,7 +528,7 @@ Curl_cf_dns_get_ai(struct Curl_cfilter *cf, else if(ctx->dns) return cf_dns_get_nth_ai(ctx->dns->addr, ai_family, index); else - return Curl_resolv_get_ai(data, ai_family, index); + return Curl_resolv_get_ai(data, ctx->resolv_id, ai_family, index); } } return NULL; @@ -548,9 +561,28 @@ Curl_conn_dns_get_https(struct Curl_easy *data, int sockindex) for(; cf; cf = cf->next) { if(cf->cft == &Curl_cft_dns) { struct cf_dns_ctx *ctx = cf->ctx; - return ctx->dns ? ctx->dns->hinfo : NULL; + if(ctx->dns) + return ctx->dns->hinfo; + else + return Curl_resolv_get_https(data, ctx->resolv_id); } } return NULL; } + +bool Curl_conn_dns_resolved_https(struct Curl_easy *data, int sockindex) +{ + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_dns) { + struct cf_dns_ctx *ctx = cf->ctx; + if(ctx->dns) + return TRUE; + else + return Curl_resolv_knows_https(data, ctx->resolv_id); + } + } + return FALSE; +} + #endif /* USE_HTTPSRR */ diff --git a/lib/cf-dns.h b/lib/cf-dns.h index 6ad8721283..909672e26a 100644 --- a/lib/cf-dns.h +++ b/lib/cf-dns.h @@ -33,14 +33,15 @@ struct Curl_addrinfo; CURLcode Curl_cf_dns_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, + uint8_t dns_queries, uint8_t transport, struct Curl_dns_entry *dns); CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport); CURLcode Curl_conn_dns_result(struct connectdata *conn, int sockindex); @@ -62,6 +63,10 @@ Curl_cf_dns_get_ai(struct Curl_cfilter *cf, #ifdef USE_HTTPSRR const struct Curl_https_rrinfo * Curl_conn_dns_get_https(struct Curl_easy *data, int sockindex); +bool Curl_conn_dns_resolved_https(struct Curl_easy *data, int sockindex); +#else +#define Curl_conn_dns_get_https(a,b) NULL +#define Curl_conn_dns_resolved_https(a,b) TRUE #endif diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index f324d5d2c3..197b1c73c0 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -330,13 +330,6 @@ static CURLcode cf_hc_resolv(struct Curl_cfilter *cf, * can no longer change that. Any HTTPSRR advice for other hosts and ports * we need to ignore. */ const struct Curl_https_rrinfo *rr; - bool need_https_rr = FALSE; - - if(need_https_rr) { - result = Curl_conn_dns_result(cf->conn, cf->sockindex); - if(result) - return result; - } /* Do we have HTTPS-RR information? */ rr = Curl_conn_dns_get_https(data, cf->sockindex); @@ -346,7 +339,7 @@ static CURLcode cf_hc_resolv(struct Curl_cfilter *cf, !rr->target[0] || (rr->target[0] == '.' && !rr->target[1])) && - (rr->port < 0 || /* for same port */ + (!rr->port_set || /* for same port */ rr->port == cf->conn->remote_port)) { for(i = 0; i < CURL_ARRAYSIZE(rr->alpns) && alpn_count < CURL_ARRAYSIZE(alpn_ids); ++i) { diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 4afdb4c54a..79a1a1e006 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -637,14 +637,14 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, * of the connection. The resolve functions should really be changed * to take a type parameter instead. */ - uint8_t ip_version = (af == AF_INET) ? - CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER; + uint8_t dns_queries = (af == AF_INET) ? + CURL_DNSQ_A : (CURL_DNSQ_A|CURL_DNSQ_AAAA); #ifdef USE_IPV6 if(af == AF_INET6) - ip_version = CURL_IPRESOLVE_V6; + dns_queries = CURL_DNSQ_AAAA; #endif - (void)Curl_resolv_blocking(data, host, 80, ip_version, transport, &h); + (void)Curl_resolv_blocking(data, dns_queries, host, 80, transport, &h); if(h) { int h_af = h->addr->ai_family; /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ diff --git a/lib/connect.c b/lib/connect.c index 6a0137c727..111d728252 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -557,6 +557,7 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, int ssl_mode) { CURLcode result = CURLE_OK; + uint8_t dns_queries; DEBUGASSERT(data); DEBUGASSERT(conn->scheme); @@ -580,7 +581,12 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, goto out; } - result = Curl_cf_dns_add(data, conn, sockindex, + 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, dns_queries, conn->transport_wanted, dns); DEBUGASSERT(conn->cfilter[sockindex]); out: diff --git a/lib/curl_trc.c b/lib/curl_trc.c index 0bfd3b834e..c6115cf7f6 100644 --- a/lib/curl_trc.c +++ b/lib/curl_trc.c @@ -223,6 +223,12 @@ struct curl_trc_feat Curl_trc_feat_timer = { "TIMER", CURL_LOG_LVL_NONE, }; +#ifdef USE_THREADS +struct curl_trc_feat Curl_trc_feat_threads = { + "THREADS", + CURL_LOG_LVL_NONE, +}; +#endif #endif #ifndef CURL_DISABLE_VERBOSE_STRINGS @@ -525,6 +531,9 @@ static struct trc_feat_def trc_feats[] = { { &Curl_trc_feat_write, TRC_CT_NONE }, { &Curl_trc_feat_dns, TRC_CT_NETWORK }, { &Curl_trc_feat_timer, TRC_CT_NETWORK }, +#ifdef USE_THREADS + { &Curl_trc_feat_threads, TRC_CT_NONE }, +#endif #ifndef CURL_DISABLE_FTP { &Curl_trc_feat_ftp, TRC_CT_PROTOCOL }, #endif diff --git a/lib/curl_trc.h b/lib/curl_trc.h index 362a20c800..b4ae8e5314 100644 --- a/lib/curl_trc.h +++ b/lib/curl_trc.h @@ -308,6 +308,9 @@ extern struct curl_trc_feat Curl_trc_feat_read; extern struct curl_trc_feat Curl_trc_feat_write; extern struct curl_trc_feat Curl_trc_feat_dns; extern struct curl_trc_feat Curl_trc_feat_timer; +#ifdef USE_THREADS +extern struct curl_trc_feat Curl_trc_feat_threads; +#endif #endif #ifndef CURL_DISABLE_VERBOSE_STRINGS diff --git a/lib/dnscache.c b/lib/dnscache.c index 8d7dcadb02..012da0f285 100644 --- a/lib/dnscache.c +++ b/lib/dnscache.c @@ -46,6 +46,7 @@ #include "curl_trc.h" #include "dnscache.h" #include "hash.h" +#include "hostip.h" #include "httpsrr.h" #include "progress.h" #include "rand.h" @@ -59,25 +60,6 @@ #define MAX_DNS_CACHE_SIZE 29999 -#ifdef CURLVERBOSE -static const char *dnscache_ipv_str(uint8_t ip_version) -{ - switch(ip_version) { - case CURL_IPRESOLVE_WHATEVER: - return "A+AAAA"; - case CURL_IPRESOLVE_V4: - return "A"; -#ifdef PF_INET6 - case CURL_IPRESOLVE_V6: - return "AAAA"; -#endif - default: - DEBUGASSERT(0); - return "???"; - } -} -#endif - static void dnscache_entry_free(struct Curl_dns_entry *dns) { Curl_freeaddrinfo(dns->addr); @@ -229,9 +211,9 @@ void Curl_dnscache_clear(struct Curl_easy *data) /* lookup address, returns entry if found and not stale */ static CURLcode fetch_addr(struct Curl_easy *data, struct Curl_dnscache *dnscache, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, struct Curl_dns_entry **pdns) { struct Curl_dns_entry *dns = NULL; @@ -273,43 +255,17 @@ static CURLcode fetch_addr(struct Curl_easy *data, } } - if(dns && dns->ip_version != ip_version) { - switch(dns->ip_version) { - case CURL_IPRESOLVE_WHATEVER: { - /* Do we have addresses that match the requested ip version? */ - int pf = PF_INET; - bool found = FALSE; - struct Curl_addrinfo *addr = dns->addr; - -#ifdef PF_INET6 - if(ip_version == CURL_IPRESOLVE_V6) - pf = PF_INET6; -#endif - - while(addr) { - if(addr->ai_family == pf) { - found = TRUE; - break; - } - addr = addr->ai_next; - } - - if(!found) { - /* We assume that CURL_IPRESOLVE_WHATEVER means we tried to - * get addresses for all supported types, but there are none - * for the ip version we need. This is a negative resolve. */ - CURL_TRC_DNS(data, "cache entry does not have type=%s addresses", - dnscache_ipv_str(ip_version)); - dns = NULL; - result = CURLE_COULDNT_RESOLVE_HOST; - } - break; - } - default: - /* different families, we return NULL + OK, so a new resolve - * attempt may get started. */ + if(dns) { + if((dns->dns_queries & dns_queries) != dns_queries) { + /* The entry does not cover all wanted DNS queries, a miss. */ dns = NULL; - break; + } + else if(!(dns->dns_responses & dns_queries)) { + /* The entry has no responses for the wanted DNS queries. */ + CURL_TRC_DNS(data, "cache entry does not have type=%s addresses", + Curl_resolv_query_str(dns_queries)); + dns = NULL; + result = CURLE_COULDNT_RESOLVE_HOST; } } @@ -336,9 +292,9 @@ static CURLcode fetch_addr(struct Curl_easy *data, * use, or we will leak memory! */ CURLcode Curl_dnscache_get(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, struct Curl_dns_entry **pentry) { struct Curl_dnscache *dnscache = dnscache_get(data); @@ -346,7 +302,7 @@ CURLcode Curl_dnscache_get(struct Curl_easy *data, CURLcode result = CURLE_OK; dnscache_lock(data, dnscache); - result = fetch_addr(data, dnscache, hostname, port, ip_version, &dns); + result = fetch_addr(data, dnscache, dns_queries, hostname, port, &dns); if(!result && dns) dns->refcount++; /* we pass out a reference */ else if(result) { @@ -441,14 +397,24 @@ UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, } #endif +static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, + int ai_family) +{ + for(; ai; ai = ai->ai_next) { + if(ai->ai_family == ai_family) + return TRUE; + } + return FALSE; +} + static struct Curl_dns_entry * dnscache_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, - uint8_t ip_version, bool permanent) { struct Curl_dns_entry *dns = NULL; @@ -459,6 +425,11 @@ dnscache_entry_create(struct Curl_easy *data, 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); + if(permanent) { dns->timestamp.tv_sec = 0; /* an entry that never goes stale */ dns->timestamp.tv_usec = 0; /* an entry that never goes stale */ @@ -466,10 +437,6 @@ dnscache_entry_create(struct Curl_easy *data, else { dns->timestamp = *Curl_pgrs_now(data); } - dns->port = port; - dns->ip_version = ip_version; - if(hostlen) - memcpy(dns->hostname, hostname, hostlen); /* Take the given address lists into the entry */ if(paddr1 && *paddr1) { @@ -484,6 +451,16 @@ dnscache_entry_create(struct Curl_easy *data, *paddr2 = NULL; } + if((dns_queries & CURL_DNSQ_A) && + dnscache_ai_has_family(dns->addr, PF_INET)) + dns->dns_responses |= CURL_DNSQ_A; + +#ifdef USE_IPV6 + if((dns_queries & CURL_DNSQ_AAAA) && + dnscache_ai_has_family(dns->addr, PF_INET6)) + dns->dns_responses |= CURL_DNSQ_AAAA; +#endif /* USE_IPV6 */ + #ifndef CURL_DISABLE_SHUFFLE_DNS /* shuffle addresses if requested */ if(data->set.dns_shuffle_addresses && dns->addr) { @@ -513,36 +490,54 @@ out: struct Curl_dns_entry * Curl_dnscache_mk_entry(struct Curl_easy *data, + uint8_t dns_queries, struct Curl_addrinfo **paddr, const char *hostname, - uint16_t port, - uint8_t ip_version) + uint16_t port) { - return dnscache_entry_create(data, paddr, NULL, hostname, + return dnscache_entry_create(data, dns_queries, paddr, NULL, hostname, hostname ? strlen(hostname) : 0, - port, ip_version, FALSE); + port, FALSE); } struct Curl_dns_entry * Curl_dnscache_mk_entry2(struct Curl_easy *data, + uint8_t dns_queries, struct Curl_addrinfo **paddr1, struct Curl_addrinfo **paddr2, const char *hostname, - uint16_t port, uint8_t ip_version) + uint16_t port) { - return dnscache_entry_create(data, paddr1, paddr2, hostname, + return dnscache_entry_create(data, dns_queries, paddr1, paddr2, hostname, hostname ? strlen(hostname) : 0, - port, ip_version, FALSE); + port, FALSE); } +#ifdef USE_HTTPSRR +void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns, + struct Curl_https_rrinfo *hinfo) +{ + /* 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); + if(dns->hinfo) { + Curl_httpsrr_cleanup(dns->hinfo); + curlx_free(dns->hinfo); + } + dns->hinfo = hinfo; + dns->dns_responses |= CURL_DNSQ_HTTPS; +} +#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, - uint8_t ip_version, bool permanent) { char entry_id[MAX_HOSTCACHE_LEN]; @@ -550,8 +545,8 @@ dnscache_add_addr(struct Curl_easy *data, struct Curl_dns_entry *dns; struct Curl_dns_entry *dns2; - dns = dnscache_entry_create(data, paddr, NULL, hostname, hlen, port, - ip_version, permanent); + dns = dnscache_entry_create(data, dns_queries, paddr, NULL, + hostname, hlen, port, permanent); if(!dns) return NULL; @@ -596,9 +591,9 @@ CURLcode Curl_dnscache_add(struct Curl_easy *data, } CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, + uint8_t dns_queries, const char *host, - uint16_t port, - uint8_t ip_version) + uint16_t port) { struct Curl_dnscache *dnscache = dnscache_get(data); struct Curl_dns_entry *dns; @@ -607,14 +602,14 @@ CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, return CURLE_FAILED_INIT; /* put this new host in the cache */ - dns = dnscache_add_addr(data, dnscache, NULL, host, strlen(host), - port, ip_version, FALSE); + dns = dnscache_add_addr(data, dnscache, dns_queries, NULL, + host, strlen(host), port, FALSE); if(dns) { /* release the returned reference; the cache itself will keep the * entry alive: */ dns->refcount--; CURL_TRC_DNS(data, "cache negative name resolve for %s:%d type=%s", - host, port, dnscache_ipv_str(ip_version)); + host, port, Curl_resolv_query_str(dns_queries)); return CURLE_OK; } return CURLE_OUT_OF_MEMORY; @@ -846,10 +841,10 @@ err: Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); } - /* put this new host in the cache */ - dns = dnscache_add_addr(data, dnscache, &head, curlx_str(&source), - curlx_strlen(&source), port, - CURL_IPRESOLVE_WHATEVER, permanent); + /* put this new host in the cache, an overridy for ALL dns queries */ + dns = dnscache_add_addr(data, dnscache, CURL_DNSQ_ALL, + &head, curlx_str(&source), + curlx_strlen(&source), port, permanent); if(dns) /* release the returned reference; the cache itself will keep the * entry alive: */ diff --git a/lib/dnscache.h b/lib/dnscache.h index b2b3e997fe..ba5eedb37d 100644 --- a/lib/dnscache.h +++ b/lib/dnscache.h @@ -47,7 +47,8 @@ struct Curl_dns_entry { uint32_t refcount; /* hostname port number that resolved to addr. */ uint16_t port; - uint8_t ip_version; + 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). */ char hostname[1]; }; @@ -63,17 +64,23 @@ struct Curl_dns_entry { */ struct Curl_dns_entry * Curl_dnscache_mk_entry(struct Curl_easy *data, + uint8_t dns_queries, struct Curl_addrinfo **paddr, const char *hostname, - uint16_t port, - uint8_t ip_version); + uint16_t port); struct Curl_dns_entry * Curl_dnscache_mk_entry2(struct Curl_easy *data, + uint8_t dns_queries, struct Curl_addrinfo **paddr1, struct Curl_addrinfo **paddr2, const char *hostname, - uint16_t port, uint8_t ip_version); + uint16_t port); + +#ifdef USE_HTTPSRR +void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns, + struct Curl_https_rrinfo *hinfo); +#endif /* USE_HTTPSRR */ /* Increase the ref counter and return it for storing in another place. * May be called with NULL, in which case it returns NULL. */ @@ -112,9 +119,9 @@ void Curl_dnscache_clear(struct Curl_easy *data); * entry was in the cache. */ CURLcode Curl_dnscache_get(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, struct Curl_dns_entry **pentry); /* @@ -127,9 +134,9 @@ CURLcode Curl_dnscache_add(struct Curl_easy *data, /* Store a "negative" entry for host:port, e.g. remember that * it could not be resolved. */ CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, + uint8_t dns_queries, const char *host, - uint16_t port, - uint8_t ip_version); + uint16_t port); /* * Populate the cache with specified entries from CURLOPT_RESOLVE. diff --git a/lib/doh.c b/lib/doh.c index fa4e14271d..53d080b3bb 100644 --- a/lib/doh.c +++ b/lib/doh.c @@ -40,6 +40,9 @@ #define DNS_CLASS_IN 0x01 +static void doh_close(struct Curl_easy *data, + struct Curl_resolv_async *async); + #ifdef CURLVERBOSE static const char * const errors[] = { "", @@ -216,22 +219,24 @@ static void doh_print_buf(struct Curl_easy *data, static void doh_probe_done(struct Curl_easy *data, struct Curl_easy *doh, CURLcode result) { - struct Curl_resolv_async *async = data->state.async; - struct doh_probes *dohp = async ? async->doh : NULL; + struct Curl_resolv_async *async = NULL; + struct doh_probes *dohp = NULL; struct doh_request *doh_req = NULL; int i; - if(!dohp) { + doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); + if(!doh_req) { DEBUGASSERT(0); return; } - doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); - /* A DoH response may arrive for a resolve operation already cancelled. */ - if(doh_req && (doh_req->async_id != async->id)) { - CURL_TRC_DNS(data, "ignoring DoH response from a previous resolve"); + async = Curl_async_get(data, doh_req->resolv_id); + if(!async) { + CURL_TRC_DNS(data, "[%u] ignoring outdated DoH response", + doh_req->resolv_id); return; } + dohp = async->doh; for(i = 0; i < DOH_SLOT_COUNT; ++i) { if(dohp->probe_resp[i].probe_mid == doh->mid) @@ -295,7 +300,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, DNStype dnstype, const char *host, const char *url, CURLM *multi, - uint32_t async_id, + uint32_t resolv_id, uint32_t *pmid) { struct Curl_easy *doh = NULL; @@ -309,7 +314,7 @@ static CURLcode doh_probe_run(struct Curl_easy *data, doh_req = curlx_calloc(1, sizeof(*doh_req)); if(!doh_req) return CURLE_OUT_OF_MEMORY; - doh_req->async_id = async_id; + doh_req->resolv_id = resolv_id; doh_req->dnstype = dnstype; curlx_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE); @@ -482,16 +487,18 @@ CURLcode Curl_doh(struct Curl_easy *data, data->sub_xfer_done = doh_probe_done; /* create IPv4 DoH request */ - result = doh_probe_run(data, CURL_DNS_TYPE_A, - async->hostname, data->set.str[STRING_DOH], - data->multi, async->id, - &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid); - if(result) - goto error; - dohp->pending++; + if(async->dns_queries & CURL_DNSQ_A) { + result = doh_probe_run(data, CURL_DNS_TYPE_A, + async->hostname, data->set.str[STRING_DOH], + data->multi, async->id, + &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid); + if(result) + goto error; + dohp->pending++; + } #ifdef USE_IPV6 - if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + if(async->dns_queries & CURL_DNSQ_AAAA) { /* create IPv6 DoH request */ result = doh_probe_run(data, CURL_DNS_TYPE_AAAA, async->hostname, data->set.str[STRING_DOH], @@ -504,8 +511,7 @@ CURLcode Curl_doh(struct Curl_easy *data, #endif #ifdef USE_HTTPSRR - if(conn->scheme->protocol & PROTO_FAMILY_HTTP) { - /* Only use HTTPS RR for HTTP(S) transfers */ + if(async->dns_queries & CURL_DNSQ_HTTPS) { char *qname = NULL; if(async->port != PORT_HTTPS) { qname = curl_maprintf("_%d._https.%s", async->port, async->hostname); @@ -1130,6 +1136,7 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, CURLcode result = CURLE_OUT_OF_MEMORY; size_t olen; + (void)data; *hrr = NULL; if(len <= 2) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -1147,7 +1154,6 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, result = CURLE_WEIRD_SERVER_REPLY; goto err; } - lhrr->port = -1; /* until set */ while(len >= 4) { pcode = doh_get16bit(cp, 0); plen = doh_get16bit(cp, 2); @@ -1157,9 +1163,10 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, result = CURLE_WEIRD_SERVER_REPLY; goto err; } - result = Curl_httpsrr_set(data, lhrr, pcode, cp, plen); + result = Curl_httpsrr_set(lhrr, pcode, cp, plen); if(result) goto err; + Curl_httpsrr_trace(data, lhrr); cp += plen; len -= plen; expected_min_pcode = pcode + 1; @@ -1214,10 +1221,10 @@ UNITTEST void doh_print_httpsrr(struct Curl_easy *data, #endif CURLcode Curl_doh_take_result(struct Curl_easy *data, + struct Curl_resolv_async *async, struct Curl_dns_entry **pdns) { - struct Curl_resolv_async *async = data->state.async; - struct doh_probes *dohp = async ? async->doh : NULL; + struct doh_probes *dohp = async->doh; CURLcode result = CURLE_OK; struct dohentry de; @@ -1237,7 +1244,7 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, memset(rc, 0, sizeof(rc)); /* remove DoH handles from multi handle and close them */ - Curl_doh_close(data); + doh_close(data, async); /* parse the responses, create the struct and return it! */ de_init(&de); for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { @@ -1268,8 +1275,8 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, goto error; /* we got a response, create a dns entry. */ - dns = Curl_dnscache_mk_entry(data, &ai, dohp->host, dohp->port, - async->ip_version); + dns = Curl_dnscache_mk_entry(data, async->dns_queries, + &ai, dohp->host, dohp->port); if(!dns) { result = CURLE_OUT_OF_MEMORY; goto error; @@ -1290,9 +1297,10 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data, #if defined(DEBUGBUILD) && defined(CURLVERBOSE) doh_print_httpsrr(data, hrr); #endif - dns->hinfo = hrr; + Curl_dns_entry_set_https_rr(dns, hrr); } #endif /* USE_HTTPSRR */ + /* and add the entry to the cache */ result = Curl_dnscache_add(data, dns); *pdns = dns; @@ -1313,9 +1321,9 @@ error: return result; } -void Curl_doh_close(struct Curl_easy *data) +static void doh_close(struct Curl_easy *data, + struct Curl_resolv_async *async) { - struct Curl_resolv_async *async = data->state.async; struct doh_probes *doh = async ? async->doh : NULL; if(doh && data->multi) { struct Curl_easy *probe_data; @@ -1348,7 +1356,7 @@ void Curl_doh_cleanup(struct Curl_easy *data, struct doh_probes *dohp = async->doh; if(dohp) { int i; - Curl_doh_close(data); + doh_close(data, async); for(i = 0; i < DOH_SLOT_COUNT; ++i) { curlx_dyn_free(&dohp->probe_resp[i].body); } diff --git a/lib/doh.h b/lib/doh.h index 1cf38aa060..00774465b7 100644 --- a/lib/doh.h +++ b/lib/doh.h @@ -93,7 +93,7 @@ struct doh_request { struct curl_slist *req_hds; struct dynbuf resp_body; size_t req_body_len; - uint32_t async_id; /* transfer specific id of the resolve operation */ + uint32_t resolv_id; /* id of the resolve operation */ DNStype dnstype; }; @@ -122,6 +122,7 @@ CURLcode Curl_doh(struct Curl_easy *data, struct Curl_resolv_async *async); CURLcode Curl_doh_take_result(struct Curl_easy *data, + struct Curl_resolv_async *async, struct Curl_dns_entry **dns); #define DOH_MAX_ADDR 24 @@ -163,7 +164,6 @@ struct dohentry { #endif }; -void Curl_doh_close(struct Curl_easy *data); void Curl_doh_cleanup(struct Curl_easy *data, struct Curl_resolv_async *async); #define Curl_doh_wanted(d) (!!(d)->set.doh) @@ -171,7 +171,7 @@ void Curl_doh_cleanup(struct Curl_easy *data, #else /* CURL_DISABLE_DOH */ #define Curl_doh(a, b) NULL -#define Curl_doh_take_result(x, y) CURLE_COULDNT_RESOLVE_HOST +#define Curl_doh_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST #define Curl_doh_wanted(d) FALSE #endif /* !CURL_DISABLE_DOH */ diff --git a/lib/easy.c b/lib/easy.c index f6d83ea05d..461b1dfc46 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1092,8 +1092,6 @@ void curl_easy_reset(CURL *d) /* clear all meta data */ Curl_meta_reset(data); - /* clear any resolve data */ - Curl_async_shutdown(data); /* zero out UserDefined data: */ Curl_freeset(data); memset(&data->set, 0, sizeof(struct UserDefined)); diff --git a/lib/ftp.c b/lib/ftp.c index 087c564b95..2f19d8af78 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -1062,9 +1062,9 @@ static CURLcode ftp_port_resolve_host(struct Curl_easy *data, CURLcode result; *resp = NULL; - result = Curl_resolv_blocking(data, host, 0, conn->ip_version, - Curl_conn_get_transport(data, conn), - dns_entryp); + result = Curl_resolv_blocking( + data, Curl_resolv_dns_queries(data, conn->ip_version), + host, 0, Curl_conn_get_transport(data, conn), dns_entryp); if(result) failf(data, "failed to resolve the address provided to PORT: %s", host); else { @@ -2162,10 +2162,10 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, if(result) goto error; - (void)Curl_resolv_blocking(data, host_name, ipquad.remote_port, - is_ipv6 ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_V4, - Curl_conn_get_transport(data, conn), - &dns); + (void)Curl_resolv_blocking( + data, is_ipv6 ? CURL_DNSQ_AAAA : CURL_DNSQ_A, + host_name, ipquad.remote_port, Curl_conn_get_transport(data, conn), + &dns); /* we connect to the proxy's port */ connectport = (unsigned short)ipquad.remote_port; @@ -2189,9 +2189,9 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, goto error; } - (void)Curl_resolv_blocking(data, newhost, newport, conn->ip_version, - Curl_conn_get_transport(data, conn), - &dns); + (void)Curl_resolv_blocking( + data, Curl_resolv_dns_queries(data, conn->ip_version), + newhost, newport, Curl_conn_get_transport(data, conn), &dns); connectport = newport; /* we connect to the remote port */ if(!dns) { diff --git a/lib/hostip.c b/lib/hostip.c index de40e9751a..4dd041954e 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -118,6 +118,48 @@ * * If the conversion fails, the target buffer is empty. */ + +uint8_t Curl_resolv_dns_queries(struct Curl_easy *data, uint8_t ip_version) +{ + (void)data; + switch(ip_version) { + case CURL_IPRESOLVE_V6: + return CURL_DNSQ_AAAA; + case CURL_IPRESOLVE_V4: + return CURL_DNSQ_A; + default: + if(Curl_ipv6works(data)) + return (CURL_DNSQ_A|CURL_DNSQ_AAAA); + else + return CURL_DNSQ_A; + } +} + +#ifdef CURLVERBOSE +const char *Curl_resolv_query_str(uint8_t dns_queries) +{ + switch(dns_queries) { + case (CURL_DNSQ_A|CURL_DNSQ_AAAA|CURL_DNSQ_HTTPS): + return "A+AAAA+HTTPS"; + case (CURL_DNSQ_A|CURL_DNSQ_AAAA): + return "A+AAAA"; + case (CURL_DNSQ_AAAA|CURL_DNSQ_HTTPS): + return "AAAA+HTTPS"; + case (CURL_DNSQ_AAAA): + return "AAAA"; + case (CURL_DNSQ_A|CURL_DNSQ_HTTPS): + return "A+HTTPS"; + case (CURL_DNSQ_A): + return "A"; + case (CURL_DNSQ_HTTPS): + return "HTTPS"; + default: + DEBUGASSERT(0); + return "???"; + } +} +#endif + void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf, size_t bufsize) { @@ -287,57 +329,85 @@ static bool tailmatch(const char *full, size_t flen, return curl_strnequal(part, &full[flen - plen], plen); } -static bool can_resolve_ip_version(struct Curl_easy *data, int ip_version) +static bool can_resolve_dns_queries(struct Curl_easy *data, + uint8_t dns_queries) { -#ifdef CURLRES_IPV6 - if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) - return FALSE; -#elif defined(CURLRES_IPV4) (void)data; - if(ip_version == CURL_IPRESOLVE_V6) + if((CURL_DNSQ_IP(dns_queries) == CURL_DNSQ_AAAA) && !Curl_ipv6works(data)) return FALSE; -#else -#error either CURLRES_IPV6 or CURLRES_IPV4 need to be defined -#endif return TRUE; } +CURLcode Curl_resolv_announce_start(struct Curl_easy *data, + void *resolver) +{ + if(data->set.resolver_start) { + int rc; + + CURL_TRC_DNS(data, "announcing resolve to application"); + Curl_set_in_callback(data, TRUE); + rc = data->set.resolver_start(resolver, NULL, + data->set.resolver_start_client); + Curl_set_in_callback(data, FALSE); + if(rc) { + CURL_TRC_DNS(data, "application aborted resolve"); + return CURLE_ABORTED_BY_CALLBACK; + } + } + return CURLE_OK; +} + #ifdef USE_CURL_ASYNC -static CURLcode hostip_async_new(struct Curl_easy *data, - const char *hostname, - uint16_t port, - uint8_t ip_version, - uint8_t transport, - timediff_t timeout_ms) +static struct Curl_resolv_async * +hostip_async_new(struct Curl_easy *data, + uint8_t dns_queries, + const char *hostname, + uint16_t port, + uint8_t transport, + timediff_t timeout_ms) { struct Curl_resolv_async *async; size_t hostlen = strlen(hostname); - DEBUGASSERT(!data->state.async); + if(!data->multi) { + DEBUGASSERT(0); + return NULL; + } + /* struct size already includes the NUL for hostname */ async = curlx_calloc(1, sizeof(*async) + hostlen); if(!async) - return CURLE_OUT_OF_MEMORY; + return NULL; - /* Even if this wraps (unlikely), it will be in time so far apart - * that it does not matter for all practical purposes. */ - async->id = data->state.next_async_id++; + /* Give every async resolve operation a "unique" id. This may + * wrap around after a long time, making collisions highly unlikely. + * As we keep the async structs at the easy handle, chances of + * easy `mid plus resolv->id` colliding should be astronomical. + * `resolv_id == 0` is never used. */ + if(data->multi->last_resolv_id == UINT32_MAX) + data->multi->last_resolv_id = 1; /* wrap around */ + else + data->multi->last_resolv_id++; + async->id = data->multi->last_resolv_id; + async->dns_queries = dns_queries; async->port = port; - async->ip_version = ip_version; async->transport = transport; async->start = *Curl_pgrs_now(data); async->timeout_ms = timeout_ms; - if(hostlen) + if(hostlen) { memcpy(async->hostname, hostname, hostlen); + async->is_ipaddr = Curl_is_ipaddr(async->hostname); + if(async->is_ipaddr) + async->is_ipv4addr = Curl_is_ipv4addr(async->hostname); + } - data->state.async = async; - return CURLE_OK; + return async; } static CURLcode hostip_resolv_take_result(struct Curl_easy *data, + struct Curl_resolv_async *async, struct Curl_dns_entry **pdns) { - struct Curl_resolv_async *async = data->state.async; CURLcode result; /* If async resolving is ongoing, this must be set */ @@ -346,7 +416,7 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, #ifndef CURL_DISABLE_DOH if(data->conn->bits.doh) - result = Curl_doh_take_result(data, pdns); + result = Curl_doh_take_result(data, async, pdns); else #endif result = Curl_async_take_result(data, async, pdns); @@ -362,87 +432,83 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, } const struct Curl_addrinfo * -Curl_resolv_get_ai(struct Curl_easy *data, int ai_family, - unsigned int index) +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 = data->state.async; + struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); if(async) return Curl_async_get_ai(data, async, ai_family, index); #else (void)data; + (void)resolv_id; (void)ai_family; (void)index; #endif return NULL; } -#endif /* USE_CURL_ASYNC */ - -static CURLcode hostip_resolv_announce(struct Curl_easy *data, - const char *hostname, - uint16_t port, - uint8_t ip_version, - uint8_t transport, - timediff_t timeout_ms) +#ifdef USE_HTTPSRR +const struct Curl_https_rrinfo * +Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id) { - if(data->set.resolver_start) { - void *resolver = NULL; - int st; #ifdef CURLRES_ASYNCH - CURLcode result; - if(!data->state.async) { - result = hostip_async_new(data, hostname, port, ip_version, - transport, timeout_ms); - if(result) - return result; - } - - result = Curl_async_get_impl(data, data->state.async, &resolver); - if(result) - return result; + struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); + if(async) + return Curl_async_get_https(data, async); #else - (void)hostname; - (void)port; - (void)ip_version; - (void)timeout_ms; - (void)transport; + (void)data; + (void)resolv_id; #endif - Curl_set_in_callback(data, TRUE); - st = data->set.resolver_start(resolver, NULL, - data->set.resolver_start_client); - Curl_set_in_callback(data, FALSE); - if(st) { - return CURLE_ABORTED_BY_CALLBACK; - } - } - return CURLE_OK; + return NULL; } +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; +} + +#endif /* USE_HTTPSRR */ + +#endif /* USE_CURL_ASYNC */ + static CURLcode hostip_resolv_start(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, timediff_t timeout_ms, bool allowDOH, + uint32_t *presolv_id, struct Curl_dns_entry **pdns) { +#ifdef USE_CURL_ASYNC + struct Curl_resolv_async *async = NULL; +#endif struct Curl_addrinfo *addr = NULL; size_t hostname_len; CURLcode result = CURLE_OK; + (void)timeout_ms; /* not in all ifdefs */ + *presolv_id = 0; *pdns = NULL; - /* really need to start a resolve operation */ - result = hostip_resolv_announce(data, hostname, port, ip_version, - transport, timeout_ms); - if(result) - goto out; - /* Check for "known" things to resolve ourselves. */ #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) + goto out; /* shortcut literal IP addresses, if we are not told to resolve them. */ result = Curl_str2addr(hostname, port, &addr); goto out; @@ -454,6 +520,9 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, 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; @@ -462,13 +531,18 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, #ifndef CURL_DISABLE_DOH if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) { - if(!data->state.async) { - result = hostip_async_new(data, hostname, port, ip_version, - transport, timeout_ms); - if(result) + result = Curl_resolv_announce_start(data, NULL); + if(result) + goto out; + if(!async) { + async = hostip_async_new(data, dns_queries, hostname, port, + transport, timeout_ms); + if(!async) { + result = CURLE_OUT_OF_MEMORY; goto out; + } } - result = Curl_doh(data, data->state.async); + result = Curl_doh(data, async); goto out; } #else @@ -476,30 +550,35 @@ static CURLcode hostip_resolv_start(struct Curl_easy *data, #endif /* Can we provide the requested IP specifics in resolving? */ - if(!can_resolve_ip_version(data, ip_version)) { + if(!can_resolve_dns_queries(data, dns_queries)) { result = CURLE_COULDNT_RESOLVE_HOST; goto out; } #ifdef CURLRES_ASYNCH (void)addr; - if(!data->state.async) { - result = hostip_async_new(data, hostname, port, ip_version, - transport, timeout_ms); - if(result) + if(!async) { + async = hostip_async_new(data, dns_queries, hostname, port, + transport, timeout_ms); + if(!async) { + result = CURLE_OUT_OF_MEMORY; goto out; + } } - result = Curl_async_getaddrinfo(data, data->state.async); + result = Curl_async_getaddrinfo(data, async); if(result == CURLE_AGAIN) { /* the answer might be there already. Check. */ - CURLcode r2 = hostip_resolv_take_result(data, pdns); + CURLcode r2 = hostip_resolv_take_result(data, async, pdns); if(r2) result = r2; else if(*pdns) result = CURLE_OK; } #else - addr = Curl_sync_getaddrinfo(data, hostname, port, ip_version, transport); + result = Curl_resolv_announce_start(data, NULL); + if(result) + goto out; + addr = Curl_sync_getaddrinfo(data, dns_queries, hostname, port, transport); if(!addr) result = CURLE_COULDNT_RESOLVE_HOST; #endif @@ -509,7 +588,7 @@ out: if(addr) { /* we got a response, create a dns entry, add to cache, return */ DEBUGASSERT(!*pdns); - *pdns = Curl_dnscache_mk_entry(data, &addr, hostname, port, ip_version); + *pdns = Curl_dnscache_mk_entry(data, dns_queries, &addr, hostname, port); if(!*pdns) result = CURLE_OUT_OF_MEMORY; } @@ -521,31 +600,39 @@ out: else if(addr) Curl_freeaddrinfo(addr); +#ifdef USE_CURL_ASYNC + if(async) { + if(result == CURLE_AGAIN) { /* still need it, link, return id. */ + *presolv_id = async->id; + async->next = data->state.async; + data->state.async = async; + } + else { + Curl_async_destroy(data, async); + } + } +#endif return result; } static CURLcode hostip_resolv(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, timediff_t timeout_ms, bool allowDOH, + uint32_t *presolv_id, struct Curl_dns_entry **pdns) { size_t hostname_len; CURLcode result = CURLE_COULDNT_RESOLVE_HOST; bool cache_dns = FALSE; + (void)timeout_ms; /* not used in all ifdefs */ + *presolv_id = 0; *pdns = NULL; -#ifdef USE_CURL_ASYNC - if(data->state.async) - Curl_async_destroy(data); -#else - (void)timeout_ms; -#endif - #ifndef CURL_DISABLE_DOH data->conn->bits.doh = FALSE; /* default is not */ #else @@ -563,9 +650,9 @@ static CURLcode hostip_resolv(struct Curl_easy *data, } #ifdef DEBUGBUILD - CURL_TRC_DNS(data, "hostip_resolv(%s:%u, ip=%x)", - hostname, port, ip_version); - if((ip_version == CURL_IPRESOLVE_V6) && + 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) && getenv("CURL_DBG_RESOLV_FAIL_IPV6")) { infof(data, "DEBUG fail ipv6 resolve"); result = Curl_resolver_error(data, NULL); @@ -573,7 +660,7 @@ static CURLcode hostip_resolv(struct Curl_easy *data, } #endif /* Let's check our DNS cache first */ - result = Curl_dnscache_get(data, hostname, port, ip_version, pdns); + result = Curl_dnscache_get(data, dns_queries, hostname, port, pdns); if(*pdns) { infof(data, "Hostname %s was found in DNS cache", hostname); result = CURLE_OK; @@ -585,18 +672,18 @@ static CURLcode hostip_resolv(struct Curl_easy *data, else { /* No luck, we need to start resolving. */ cache_dns = TRUE; - result = hostip_resolv_start(data, hostname, port, ip_version, - transport, timeout_ms, allowDOH, pdns); + result = hostip_resolv_start(data, dns_queries, hostname, port, + transport, timeout_ms, allowDOH, + presolv_id, pdns); } out: if(result && (result != CURLE_AGAIN)) { Curl_dns_entry_unlink(data, pdns); - Curl_async_shutdown(data); if((result == CURLE_COULDNT_RESOLVE_HOST) || (result == CURLE_COULDNT_RESOLVE_PROXY)) { if(cache_dns) - Curl_dnscache_add_negative(data, hostname, port, ip_version); + Curl_dnscache_add_negative(data, dns_queries, hostname, port); failf(data, "Could not resolve: %s:%u", hostname, port); } else { @@ -609,40 +696,39 @@ out: Curl_dns_entry_unlink(data, pdns); } - CURL_TRC_DNS(data, "hostip_resolv(%s:%u, ip=%x, timeout_ms=%" FMT_TIMEDIFF_T - ") -> %d, dns %sfound", - hostname, port, ip_version, timeout_ms, result, - *pdns ? "" : "not "); return result; } CURLcode Curl_resolv_blocking(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, struct Curl_dns_entry **pdns) { CURLcode result; + uint32_t resolv_id; DEBUGASSERT(hostname && *hostname); *pdns = NULL; /* We cannot do a blocking resolve using DoH currently */ - result = hostip_resolv(data, hostname, port, ip_version, - transport, 0, FALSE, pdns); + result = hostip_resolv(data, dns_queries, + hostname, port, transport, 0, FALSE, + &resolv_id, pdns); switch(result) { case CURLE_OK: DEBUGASSERT(*pdns); - return CURLE_OK; + break; #ifdef USE_CURL_ASYNC case CURLE_AGAIN: DEBUGASSERT(!*pdns); - if(!data->state.async) - return CURLE_FAILED_INIT; - return Curl_async_await(data, data->state.async, pdns); + result = Curl_async_await(data, resolv_id, pdns); + Curl_resolv_destroy(data, resolv_id); + break; #endif default: - return result; + break; } + return result; } #ifdef USE_ALARM_TIMEOUT @@ -661,11 +747,12 @@ CURL_NORETURN static void alarmfunc(int sig) #ifdef USE_ALARM_TIMEOUT static CURLcode resolv_alarm_timeout(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, timediff_t timeout_ms, + uint32_t *presolv_id, struct Curl_dns_entry **entry) { #ifdef HAVE_SIGACTION @@ -742,8 +829,8 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, /* Perform the actual name resolution. This might be interrupted by an * alarm if it takes too long. */ - result = hostip_resolv(data, hostname, port, ip_version, transport, - timeout_ms, TRUE, entry); + result = hostip_resolv(data, dns_queries, hostname, port, transport, + timeout_ms, FALSE, presolv_id, entry); clean_up: if(!prev_alarm) @@ -816,19 +903,23 @@ clean_up: * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL */ CURLcode Curl_resolv(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, timediff_t timeout_ms, + uint32_t *presolv_id, struct Curl_dns_entry **entry) { DEBUGASSERT(hostname && *hostname); + *presolv_id = 0; *entry = NULL; if(timeout_ms < 0) /* got an already expired timeout */ return CURLE_OPERATION_TIMEDOUT; + else if(!timeout_ms) + timeout_ms = CURL_TIMEOUT_RESOLVE_MS; #ifdef USE_ALARM_TIMEOUT if(timeout_ms && data->set.no_signal) { @@ -836,8 +927,8 @@ CURLcode Curl_resolv(struct Curl_easy *data, timeout_ms = 0; } if(timeout_ms && !Curl_doh_wanted(data)) { - return resolv_alarm_timeout(data, hostname, port, ip_version, - transport, timeout_ms, entry); + return resolv_alarm_timeout(data, dns_queries, hostname, port, transport, + timeout_ms, presolv_id, entry); } #endif /* !USE_ALARM_TIMEOUT */ @@ -846,16 +937,28 @@ CURLcode Curl_resolv(struct Curl_easy *data, infof(data, "timeout on name lookup is not supported"); #endif - return hostip_resolv(data, hostname, port, ip_version, transport, - timeout_ms, TRUE, entry); + return hostip_resolv(data, dns_queries, hostname, port, transport, + timeout_ms, TRUE, presolv_id, entry); } #ifdef USE_CURL_ASYNC -CURLcode Curl_resolv_take_result(struct Curl_easy *data, - struct Curl_dns_entry **pdns) + +struct Curl_resolv_async *Curl_async_get(struct Curl_easy *data, + uint32_t resolv_id) { struct Curl_resolv_async *async = data->state.async; + for(; async; async = async->next) { + if(async->id == resolv_id) + return async; + } + return NULL; +} + +CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id, + struct Curl_dns_entry **pdns) +{ + struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); CURLcode result; /* If async resolving is ongoing, this must be set */ @@ -863,20 +966,20 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, return CURLE_FAILED_INIT; /* check if we have the name resolved by now (from someone else) */ - result = Curl_dnscache_get(data, async->hostname, async->port, - async->ip_version, pdns); + result = Curl_dnscache_get(data, async->dns_queries, + async->hostname, async->port, pdns); if(*pdns) { /* Tell a possibly async resolver we no longer need the results. */ infof(data, "Hostname '%s' was found in DNS cache", async->hostname); - Curl_async_shutdown(data); + Curl_async_shutdown(data, async); return CURLE_OK; } else if(result) { - Curl_async_shutdown(data); + Curl_async_shutdown(data, async); return Curl_resolver_error(data, NULL); } - result = hostip_resolv_take_result(data, pdns); + result = hostip_resolv_take_result(data, async, pdns); if(*pdns) { /* Add to cache */ @@ -886,8 +989,8 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, } else if((result == CURLE_COULDNT_RESOLVE_HOST) || (result == CURLE_COULDNT_RESOLVE_PROXY)) { - Curl_dnscache_add_negative(data, async->hostname, - async->port, async->ip_version); + Curl_dnscache_add_negative(data, async->dns_queries, + async->hostname, async->port); failf(data, "Could not resolve: %s:%u", async->hostname, async->port); } else if(result) { @@ -897,26 +1000,57 @@ CURLcode Curl_resolv_take_result(struct Curl_easy *data, return result; } -#endif /* USE_CURL_ASYNC */ - CURLcode Curl_resolv_pollset(struct Curl_easy *data, struct easy_pollset *ps) { -#ifdef CURLRES_ASYNCH -#ifndef CURL_DISABLE_DOH - if(data->conn->bits.doh) - /* nothing to wait for during DoH resolve, those handles have their own - sockets */ - return CURLE_OK; -#endif - return Curl_async_pollset(data, ps); -#else - (void)data; + struct Curl_resolv_async *async = data->state.async; + CURLcode result = CURLE_OK; + (void)ps; - return CURLE_OK; + for(; async && !result; async = async->next) { +#ifndef CURL_DISABLE_DOH + if(async->doh) /* DoH has nothing for the pollset */ + continue; #endif + result = Curl_async_pollset(data, async, ps); + } + return result; } +void Curl_resolv_destroy(struct Curl_easy *data, uint32_t resolv_id) +{ + struct Curl_resolv_async **panchor = &data->state.async; + + for(; *panchor; panchor = &(*panchor)->next) { + struct Curl_resolv_async *async = *panchor; + if(async->id == resolv_id) { + *panchor = async->next; + Curl_async_destroy(data, async); + break; + } + } +} + +void Curl_resolv_shutdown_all(struct Curl_easy *data) +{ + struct Curl_resolv_async *async = data->state.async; + for(; async; async = async->next) { + Curl_async_shutdown(data, async); + } +} + +void Curl_resolv_destroy_all(struct Curl_easy *data) +{ + while(data->state.async) { + struct Curl_resolv_async *async = data->state.async; + data->state.async = async->next; + Curl_async_destroy(data, async); + } +} + +#endif /* USE_CURL_ASYNC */ + + /* * Curl_resolver_error() calls failf() with the appropriate message after a * resolve error @@ -964,7 +1098,7 @@ CURLcode Curl_resolv_unix(struct Curl_easy *data, return result; } - *pdns = Curl_dnscache_mk_entry(data, &addr, NULL, 0, 0); + *pdns = Curl_dnscache_mk_entry(data, 0, &addr, NULL, 0); return *pdns ? CURLE_OK : CURLE_OUT_OF_MEMORY; } #endif /* USE_UNIX_SOCKETS */ diff --git a/lib/hostip.h b/lib/hostip.h index d69abebe94..2c72db6de4 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -35,8 +35,7 @@ */ #define CURL_HOSTENT_SIZE 9000 -#define CURL_TIMEOUT_RESOLVE 300 /* when using asynch methods, we allow this - many seconds for a name resolve */ +#define CURL_TIMEOUT_RESOLVE_MS (300 * 1000) struct addrinfo; struct hostent; @@ -47,6 +46,21 @@ struct Curl_https_rrinfo; struct Curl_multi; struct Curl_dns_entry; +/* DNS query types */ +#define CURL_DNSQ_A (1U << 0) +#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)) + +#ifdef CURLVERBOSE +const char *Curl_resolv_query_str(uint8_t dns_queries); +#endif + +/* Return CURL_DNSQ_* bits for the transfer and ip_version. */ +uint8_t Curl_resolv_dns_queries(struct Curl_easy *data, uint8_t ip_version); + enum alpnid { ALPN_none = 0, ALPN_h1 = CURLALTSVC_H1, @@ -80,50 +94,79 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, uint16_t port); void Curl_printable_address(const struct Curl_addrinfo *ip, char *buf, size_t bufsize); -/* - * Curl_resolv() returns an entry with the info for the specified host - * and port. - * - * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after - * use, or we will leak memory! +/* Start DNS resolving for the given parameters. 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(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, timediff_t timeout_ms, + uint32_t *presolv_id, struct Curl_dns_entry **pdns); CURLcode Curl_resolv_blocking(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport, struct Curl_dns_entry **pdns); -CURLcode Curl_resolv_timeout(struct Curl_easy *data, - const char *hostname, int port, - int ip_version, - struct Curl_dns_entry **entry, - timediff_t timeoutms); +/* Announce start of a resolve operation to application callback, + * passing the resolver implementation (maybe NULL). */ +CURLcode Curl_resolv_announce_start(struct Curl_easy *data, + void *resolver); #ifdef USE_CURL_ASYNC -CURLcode Curl_resolv_take_result(struct Curl_easy *data, - struct Curl_dns_entry **pdns); -const struct Curl_addrinfo * -Curl_resolv_get_ai(struct Curl_easy *data, int ai_family, - unsigned int index); -#else -#define Curl_resolv_take_result(x, y) CURLE_NOT_BUILT_IN -#define Curl_resolv_get_ai(x,y,z) NULL -#endif CURLcode Curl_resolv_pollset(struct Curl_easy *data, struct easy_pollset *ps); +/* Get the `async` struct for the given `resolv_id`, if it exists. */ +struct Curl_resolv_async *Curl_async_get(struct Curl_easy *data, + uint32_t resolv_id); + +/* Shut down all resolves of the given easy handle. */ +void Curl_resolv_shutdown_all(struct Curl_easy *data); + +/* Destroy all resolve resources of the given easy handle. */ +void Curl_resolv_destroy_all(struct Curl_easy *data); + +CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id, + struct Curl_dns_entry **pdns); + +void Curl_resolv_destroy(struct Curl_easy *data, uint32_t resolv_id); + +const struct Curl_addrinfo * +Curl_resolv_get_ai(struct Curl_easy *data, uint32_t resolv_id, + int ai_family, unsigned int index); +#ifdef USE_HTTPSRR +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); +#endif /* USE_HTTPSRR */ + +#else /* USE_CURL_ASYNC */ +#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_get_ai(x,y,z, a) NULL +#define Curl_resolv_get_https(x,y) NULL +#define Curl_resolv_knows_https(x,y) TRUE +#define Curl_resolv_pollset(x,y) CURLE_OK +#define Curl_resolv_destroy(x,y) Curl_nop_stmt +#endif /* USE_CURL_ASYNC, else */ + + CURLcode Curl_resolver_error(struct Curl_easy *data, const char *detail); + + #ifdef CURLRES_SYNCH /* * Curl_sync_getaddrinfo() is the non-async low-level name resolve API. @@ -131,9 +174,9 @@ CURLcode Curl_resolver_error(struct Curl_easy *data, const char *detail); * support and platform. */ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport); #endif diff --git a/lib/hostip4.c b/lib/hostip4.c index bff42f23a2..2eb66ca42a 100644 --- a/lib/hostip4.c +++ b/lib/hostip4.c @@ -68,14 +68,14 @@ * */ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport) { struct Curl_addrinfo *ai = NULL; - (void)ip_version; + (void)dns_queries; (void)transport; ai = Curl_ipv4_resolve_r(hostname, port); diff --git a/lib/hostip6.c b/lib/hostip6.c index 146086645a..7412f428a4 100644 --- a/lib/hostip6.c +++ b/lib/hostip6.c @@ -63,9 +63,9 @@ * Curl_freeaddrinfo(), nothing else. */ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + uint8_t dns_queries, const char *hostname, uint16_t port, - uint8_t ip_version, uint8_t transport) { struct addrinfo hints; @@ -78,8 +78,7 @@ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, #endif int pf = PF_INET; - if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) - /* The stack seems to be IPv6-enabled */ + if(dns_queries & CURL_DNSQ_AAAA) pf = PF_UNSPEC; memset(&hints, 0, sizeof(hints)); diff --git a/lib/httpsrr.c b/lib/httpsrr.c index 169a0b8d4a..b8132ff2c9 100644 --- a/lib/httpsrr.c +++ b/lib/httpsrr.c @@ -30,6 +30,7 @@ #include "connect.h" #include "curl_trc.h" #include "curlx/strdup.h" +#include "curlx/inet_ntop.h" static CURLcode httpsrr_decode_alpn(const uint8_t *cp, size_t len, unsigned char *alpns) @@ -68,25 +69,90 @@ static CURLcode httpsrr_decode_alpn(const uint8_t *cp, size_t len, return CURLE_OK; } -CURLcode Curl_httpsrr_set(struct Curl_easy *data, - struct Curl_https_rrinfo *hi, +#ifdef CURLVERBOSE +static void httpsrr_report_addr(struct Curl_easy *data, 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; + + 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 = ", "; + } + + 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); +} + +void Curl_httpsrr_trace(struct Curl_easy *data, + struct Curl_https_rrinfo *hi) +{ + if(!hi || !hi->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"); +} + +#else +#define httpsrr_report_addr(a,b,c,d) Curl_nop_stmt +#endif /* CURLVERBOSE */ + +CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *hi, uint16_t rrkey, const uint8_t *val, size_t vlen) { CURLcode result = CURLE_OK; switch(rrkey) { case HTTPS_RR_CODE_MANDATORY: - CURL_TRC_DNS(data, "HTTPS RR MANDATORY left to implement"); + hi->mandatory = TRUE; break; case HTTPS_RR_CODE_ALPN: /* str_list */ result = httpsrr_decode_alpn(val, vlen, hi->alpns); - CURL_TRC_DNS(data, "HTTPS RR ALPN: %u %u %u %u", - hi->alpns[0], hi->alpns[1], hi->alpns[2], hi->alpns[3]); break; case HTTPS_RR_CODE_NO_DEF_ALPN: if(vlen) /* no data */ return CURLE_BAD_FUNCTION_ARGUMENT; hi->no_def_alpn = TRUE; - CURL_TRC_DNS(data, "HTTPS RR no-def-alpn"); break; case HTTPS_RR_CODE_IPV4: /* addr4 list */ if(!vlen || (vlen & 3)) /* the size must be 4-byte aligned */ @@ -96,7 +162,6 @@ CURLcode Curl_httpsrr_set(struct Curl_easy *data, if(!hi->ipv4hints) return CURLE_OUT_OF_MEMORY; hi->ipv4hints_len = vlen; - CURL_TRC_DNS(data, "HTTPS RR IPv4"); break; case HTTPS_RR_CODE_ECH: if(!vlen) @@ -106,7 +171,6 @@ CURLcode Curl_httpsrr_set(struct Curl_easy *data, if(!hi->echconfiglist) return CURLE_OUT_OF_MEMORY; hi->echconfiglist_len = vlen; - CURL_TRC_DNS(data, "HTTPS RR ECH"); break; case HTTPS_RR_CODE_IPV6: /* addr6 list */ if(!vlen || (vlen & 15)) /* the size must be 16-byte aligned */ @@ -116,16 +180,15 @@ CURLcode Curl_httpsrr_set(struct Curl_easy *data, if(!hi->ipv6hints) return CURLE_OUT_OF_MEMORY; hi->ipv6hints_len = vlen; - CURL_TRC_DNS(data, "HTTPS RR IPv6"); break; case HTTPS_RR_CODE_PORT: if(vlen != 2) return CURLE_BAD_FUNCTION_ARGUMENT; - hi->port = (unsigned short)((val[0] << 8) | val[1]); - CURL_TRC_DNS(data, "HTTPS RR port %u", hi->port); + hi->port = (uint16_t)((val[0] << 8) | val[1]); + hi->port_set = TRUE; break; default: - CURL_TRC_DNS(data, "HTTPS RR unknown code"); + /* unknown code */ break; } return result; @@ -147,12 +210,12 @@ void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo) Curl_safefree(rrinfo->ipv4hints); Curl_safefree(rrinfo->ipv6hints); Curl_safefree(rrinfo->rrname); + rrinfo->complete = FALSE; } #ifdef USE_ARES -static CURLcode httpsrr_opt(struct Curl_easy *data, - const ares_dns_rr_t *rr, +static CURLcode httpsrr_opt(const ares_dns_rr_t *rr, ares_dns_rr_key_t key, size_t idx, struct Curl_https_rrinfo *hinfo) { @@ -161,11 +224,10 @@ static CURLcode httpsrr_opt(struct Curl_easy *data, size_t len = 0; code = ares_dns_rr_get_opt(rr, key, idx, &val, &len); - return Curl_httpsrr_set(data, hinfo, code, val, len); + return Curl_httpsrr_set(hinfo, code, val, len); } -CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data, - const ares_dns_record_t *dnsrec, +CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, struct Curl_https_rrinfo *hinfo) { CURLcode result = CURLE_OK; @@ -188,18 +250,17 @@ CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data, result = CURLE_OUT_OF_MEMORY; goto out; } - CURL_TRC_DNS(data, "HTTPS RR target: %s", hinfo->target); } - CURL_TRC_DNS(data, "HTTPS RR priority: %u", - ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY)); + hinfo->priority = ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY); for(opt = 0; opt < ares_dns_rr_get_opt_cnt(rr, ARES_RR_HTTPS_PARAMS); opt++) { - result = httpsrr_opt(data, rr, ARES_RR_HTTPS_PARAMS, opt, hinfo); + result = httpsrr_opt(rr, ARES_RR_HTTPS_PARAMS, opt, hinfo); if(result) break; } } out: + hinfo->complete = !result; Curl_safefree(hinfo->rrname); return result; } diff --git a/lib/httpsrr.h b/lib/httpsrr.h index 8fe7c0c85c..eba69489c5 100644 --- a/lib/httpsrr.h +++ b/lib/httpsrr.h @@ -51,13 +51,15 @@ struct Curl_https_rrinfo { size_t ipv6hints_len; unsigned char alpns[MAX_HTTPSRR_ALPNS]; /* keytag = 1 */ /* store parsed alpnid entries in the array, end with ALPN_none */ - int port; /* -1 means not set */ + uint16_t port; /* -1 means not set */ uint16_t priority; 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_easy *data, - struct Curl_https_rrinfo *hi, +CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *hi, uint16_t rrkey, const uint8_t *val, size_t vlen); struct Curl_https_rrinfo * @@ -77,10 +79,17 @@ void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); #define HTTPS_RR_CODE_IPV6 0x06 #ifdef USE_ARES -CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data, - const ares_dns_record_t *dnsrec, +CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec, struct Curl_https_rrinfo *hinfo); #endif /* USE_ARES */ + +#ifdef CURLVERBOSE +void Curl_httpsrr_trace(struct Curl_easy *data, + struct Curl_https_rrinfo *hi); +#else +#define Curl_httpsrr_trace(a,b) Curl_nop_stmt +#endif + #endif /* USE_HTTPSRR */ #endif /* HEADER_CURL_HTTPSRR_H */ diff --git a/lib/multi.c b/lib/multi.c index d491586721..d1d1ad4abe 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -702,7 +702,7 @@ static CURLcode multi_done(struct Curl_easy *data, return CURLE_OK; /* Shut down any ongoing async resolver operation. */ - Curl_async_shutdown(data); + Curl_resolv_shutdown_all(data); /* Cleanup possible redirect junk */ Curl_safefree(data->req.newurl); diff --git a/lib/multihandle.h b/lib/multihandle.h index 9631c00131..a0519889ab 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -175,6 +175,7 @@ struct Curl_multi { #ifdef DEBUGBUILD unsigned int now_access_count; #endif + uint32_t last_resolv_id; /* id of the last DNS resolve operation */ BIT(ipv6_works); BIT(multiplexing); /* multiplexing wanted */ BIT(recheckstate); /* see Curl_multi_connchanged */ diff --git a/lib/request.c b/lib/request.c index f1aacb9a5a..96aa9bf327 100644 --- a/lib/request.c +++ b/lib/request.c @@ -102,9 +102,6 @@ CURLcode Curl_req_done(struct SingleRequest *req, if(!aborted) (void)req_flush(data); Curl_client_reset(data); -#ifndef CURL_DISABLE_DOH - Curl_doh_close(data); -#endif return CURLE_OK; } @@ -117,9 +114,8 @@ void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data) if(req->sendbuf_init) Curl_bufq_reset(&req->sendbuf); -#ifndef CURL_DISABLE_DOH - Curl_doh_close(data); -#endif + /* clear any resolve data */ + Curl_resolv_destroy_all(data); /* Can no longer memset() this struct as we need to keep some state */ req->size = -1; req->maxdownload = -1; diff --git a/lib/setopt.c b/lib/setopt.c index a179a48a9a..bbc5ba457e 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -2386,28 +2386,16 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, #endif #ifdef USE_RESOLV_ARES case CURLOPT_DNS_SERVERS: - result = Curl_setstropt(&s->str[STRING_DNS_SERVERS], ptr); - if(result) - break; - return Curl_async_ares_set_dns_servers(data); + return Curl_setstropt(&s->str[STRING_DNS_SERVERS], ptr); case CURLOPT_DNS_INTERFACE: - result = Curl_setstropt(&s->str[STRING_DNS_INTERFACE], ptr); - if(result) - break; - return Curl_async_ares_set_dns_interface(data); + return Curl_setstropt(&s->str[STRING_DNS_INTERFACE], ptr); case CURLOPT_DNS_LOCAL_IP4: - result = Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP4], ptr); - if(result) - break; - return Curl_async_ares_set_dns_local_ip4(data); + return Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP4], ptr); case CURLOPT_DNS_LOCAL_IP6: - result = Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP6], ptr); - if(result) - break; - return Curl_async_ares_set_dns_local_ip6(data); + return Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP6], ptr); #endif #ifdef USE_UNIX_SOCKETS diff --git a/lib/socks.c b/lib/socks.c index 25232c7504..2d43b12220 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -102,6 +102,7 @@ struct socks_state { const char *proxy_user; const char *proxy_password; CURLproxycode presult; + uint32_t resolv_id; unsigned char version; BIT(resolve_local); BIT(start_resolving); @@ -323,10 +324,11 @@ static CURLproxycode socks4_resolving(struct socks_state *sx, sx->start_resolving = FALSE; DEBUGASSERT(sx->hostname && *sx->hostname); - result = Curl_resolv(data, sx->hostname, sx->remote_port, - cf->conn->ip_version, + result = Curl_resolv(data, + Curl_resolv_dns_queries(data, cf->conn->ip_version), + sx->hostname, sx->remote_port, Curl_conn_cf_get_transport(cf, data), - 0, &dns); + 0, &sx->resolv_id, &dns); if(result == CURLE_AGAIN) { CURL_TRC_CF(data, cf, "SOCKS4 non-blocking resolve of %s", sx->hostname); return CURLPX_OK; @@ -336,7 +338,7 @@ static CURLproxycode socks4_resolving(struct socks_state *sx, } else { /* check if we have the name resolved by now */ - result = Curl_resolv_take_result(data, &dns); + result = Curl_resolv_take_result(data, sx->resolv_id, &dns); if(!result && !dns) return CURLPX_OK; } @@ -858,10 +860,11 @@ static CURLproxycode socks5_resolving(struct socks_state *sx, sx->start_resolving = FALSE; DEBUGASSERT(sx->hostname && *sx->hostname); - result = Curl_resolv(data, sx->hostname, sx->remote_port, - cf->conn->ip_version, + result = Curl_resolv(data, + Curl_resolv_dns_queries(data, cf->conn->ip_version), + sx->hostname, sx->remote_port, Curl_conn_cf_get_transport(cf, data), - 0, &dns); + 0, &sx->resolv_id, &dns); if(result == CURLE_AGAIN) { CURL_TRC_CF(data, cf, "SOCKS5 non-blocking resolve of %s", sx->hostname); return CURLPX_OK; @@ -871,7 +874,7 @@ static CURLproxycode socks5_resolving(struct socks_state *sx, } else { /* check if we have the name resolved by now */ - result = Curl_resolv_take_result(data, &dns); + result = Curl_resolv_take_result(data, sx->resolv_id, &dns); if(!result && !dns) return CURLPX_OK; } diff --git a/lib/thrdpool.c b/lib/thrdpool.c index 5e83d0a6c8..22faa396ed 100644 --- a/lib/thrdpool.c +++ b/lib/thrdpool.c @@ -435,38 +435,38 @@ out: #ifdef CURLVERBOSE void Curl_thrdpool_trace(struct curl_thrdpool *tpool, - struct Curl_easy *data, - struct curl_trc_feat *feat) + struct Curl_easy *data) { + struct curl_trc_feat *feat = &Curl_trc_feat_threads; if(Curl_trc_ft_is_verbose(data, feat)) { struct Curl_llist_node *e; struct curltime now = curlx_now(); Curl_mutex_acquire(&tpool->lock); if(!Curl_llist_count(&tpool->slots)) { - Curl_trc_feat_infof(data, feat, "[%s] [TPOOL] no threads running", + Curl_trc_feat_infof(data, feat, "[TPOOL-%s] no threads running", tpool->name); } for(e = Curl_llist_head(&tpool->slots); e; e = Curl_node_next(e)) { struct thrdslot *tslot = Curl_node_elem(e); timediff_t elapsed_ms = curlx_ptimediff_ms(&now, &tslot->starttime); if(!tslot->running) { - Curl_trc_feat_infof(data, feat, "[%s] [TPOOL] [%u]: not running", + Curl_trc_feat_infof(data, feat, "[TPOOL-%s] [%u]: not running", tpool->name, tslot->id); } else if(!tslot->starttime.tv_sec && !tslot->starttime.tv_usec) { - Curl_trc_feat_infof(data, feat, "[%s] [TPOOL] [%u]: starting...", + Curl_trc_feat_infof(data, feat, "[TPOOL-%s] [%u]: starting...", tpool->name, tslot->id); } else if(tslot->idle) { - Curl_trc_feat_infof(data, feat, "[%s] [TPOOL] [%u]: idle for %" + Curl_trc_feat_infof(data, feat, "[TPOOL-%s] [%u]: idle for %" FMT_TIMEDIFF_T "ms", tpool->name, tslot->id, elapsed_ms); } else { timediff_t remain_ms = tslot->work_timeout_ms ? (tslot->work_timeout_ms - elapsed_ms) : 0; - Curl_trc_feat_infof(data, feat, "[%s] [TPOOL] [%u]: busy %" + Curl_trc_feat_infof(data, feat, "[TPOOL-%s] [%u]: busy %" FMT_TIMEDIFF_T "ms, timeout in %" FMT_TIMEDIFF_T "ms: %s", tpool->name, tslot->id, elapsed_ms, remain_ms, diff --git a/lib/thrdpool.h b/lib/thrdpool.h index 1aba5c2605..cc81fb6fcb 100644 --- a/lib/thrdpool.h +++ b/lib/thrdpool.h @@ -30,7 +30,6 @@ struct curl_thrdpool; struct Curl_easy; -struct curl_trc_feat; /* Invoked under thread pool lock to get an "item" to work on. Must * return NULL if there is nothing to do. @@ -99,8 +98,7 @@ CURLcode Curl_thrdpool_set_props(struct curl_thrdpool *tpool, #ifdef CURLVERBOSE void Curl_thrdpool_trace(struct curl_thrdpool *tpool, - struct Curl_easy *data, - struct curl_trc_feat *feat); + struct Curl_easy *data); #endif #endif /* USE_THREADS */ diff --git a/lib/thrdqueue.c b/lib/thrdqueue.c index c9079f011e..1521ccfbee 100644 --- a/lib/thrdqueue.c +++ b/lib/thrdqueue.c @@ -380,27 +380,27 @@ CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, #ifdef CURLVERBOSE void Curl_thrdq_trace(struct curl_thrdq *tqueue, - struct Curl_easy *data, - struct curl_trc_feat *feat) + struct Curl_easy *data) { + struct curl_trc_feat *feat = &Curl_trc_feat_threads; if(Curl_trc_ft_is_verbose(data, feat)) { struct Curl_llist_node *e; struct thrdq_item *qitem; - Curl_thrdpool_trace(tqueue->tpool, data, feat); + Curl_thrdpool_trace(tqueue->tpool, data); Curl_mutex_acquire(&tqueue->lock); if(!Curl_llist_count(&tqueue->sendq) && !Curl_llist_count(&tqueue->recvq)) { - Curl_trc_feat_infof(data, feat, "[%s] [QUEUE] empty", tqueue->name); + Curl_trc_feat_infof(data, feat, "[TQUEUE-%s] empty", tqueue->name); } for(e = Curl_llist_head(&tqueue->sendq); e; e = Curl_node_next(e)) { qitem = Curl_node_elem(e); - Curl_trc_feat_infof(data, feat, "[%s] [QUEUE] in: %s", + Curl_trc_feat_infof(data, feat, "[TQUEUE-%s] in: %s", tqueue->name, qitem->description); } for(e = Curl_llist_head(&tqueue->recvq); e; e = Curl_node_next(e)) { qitem = Curl_node_elem(e); - Curl_trc_feat_infof(data, feat, "[%s] [QUEUE] out: %s", + Curl_trc_feat_infof(data, feat, "[TQUEUE-%s] out: %s", tqueue->name, qitem->description); } Curl_mutex_release(&tqueue->lock); diff --git a/lib/thrdqueue.h b/lib/thrdqueue.h index 8f5a69dd6c..d267f5d091 100644 --- a/lib/thrdqueue.h +++ b/lib/thrdqueue.h @@ -29,7 +29,6 @@ #ifdef USE_THREADS struct Curl_easy; -struct curl_trc_feat; struct curl_thrdq; typedef enum { @@ -111,8 +110,7 @@ CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, #ifdef CURLVERBOSE void Curl_thrdq_trace(struct curl_thrdq *tqueue, - struct Curl_easy *data, - struct curl_trc_feat *feat); + struct Curl_easy *data); #endif #endif /* USE_THREADS */ diff --git a/lib/url.c b/lib/url.c index 2b1c8ace57..037d41390b 100644 --- a/lib/url.c +++ b/lib/url.c @@ -239,7 +239,7 @@ CURLcode Curl_close(struct Curl_easy **datap) curlx_free(data->state.range); /* release any resolve information this transfer kept */ - Curl_async_destroy(data); + Curl_resolv_destroy_all(data); data->set.verbose = FALSE; /* no more calls to DEBUGFUNCTION */ data->magic = 0; /* force a clear AFTER the possibly enforced removal from diff --git a/lib/urldata.h b/lib/urldata.h index 4b7f99769b..dfc3436b08 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -723,7 +723,6 @@ struct UrlState { #ifdef USE_CURL_ASYNC struct Curl_resolv_async *async; /* asynchronous name resolver data */ - uint32_t next_async_id; /* id of the next async resolve operation */ #endif #ifdef USE_OPENSSL diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index bb66edf013..2bf870139b 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -37,6 +37,7 @@ #include "curlx/inet_pton.h" #include "vtls/openssl.h" #include "connect.h" +#include "cf-dns.h" #include "progress.h" #include "vtls/vtls.h" #include "vtls/vtls_int.h" @@ -3423,6 +3424,16 @@ ossl_init_session_and_alpns(struct ossl_ctx *octx, } #ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST +static bool ossl_ech_need_httpsrr(struct Curl_easy *data) +{ + if(!CURLECH_ENABLED(data)) + return FALSE; + if((data->set.tls_ech & CURLECH_GREASE) || + (data->set.tls_ech & CURLECH_CLA_CFG)) + return FALSE; + return TRUE; +} + static CURLcode ossl_init_ech(struct ossl_ctx *octx, struct Curl_cfilter *cf, struct Curl_easy *data, @@ -3488,49 +3499,32 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, infof(data, "ECH: ECHConfig from command line"); } else { - struct Curl_dns_entry *dns = NULL; + const struct Curl_https_rrinfo *rinfo = + Curl_conn_dns_get_https(data, cf->sockindex); - if(peer->hostname) - result = Curl_dnscache_get(data, peer->hostname, peer->port, - cf->conn->ip_version, &dns); - if(!dns) { - if(result) { - failf(data, "ECH: could not resolve %s:%d", - peer->hostname, peer->port); - return result; - } - infof(data, "ECH: requested but no DNS info available"); - if(data->set.tls_ech & CURLECH_HARD) - return CURLE_SSL_CONNECT_ERROR; - } - else { - struct Curl_https_rrinfo *rinfo = NULL; + if(rinfo && rinfo->echconfiglist) { + const unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; - rinfo = dns->hinfo; - if(rinfo && rinfo->echconfiglist) { - unsigned char *ecl = rinfo->echconfiglist; - size_t elen = rinfo->echconfiglist_len; - - infof(data, "ECH: ECHConfig from DoH HTTPS RR"); - if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { - infof(data, "ECH: SSL_set1_ech_config_list failed"); - if(data->set.tls_ech & CURLECH_HARD) - return CURLE_SSL_CONNECT_ERROR; - } - else { - trying_ech_now = 1; - infof(data, "ECH: imported ECHConfigList of length %zu", elen); - } - } - else { - infof(data, "ECH: requested but no ECHConfig available"); + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); + if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { + infof(data, "ECH: SSL_set1_ech_config_list failed"); if(data->set.tls_ech & CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } - Curl_dns_entry_unlink(data, &dns); + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %zu", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; } } #ifdef HAVE_BORINGSSL_LIKE + (void)peer; if(trying_ech_now && outername) { infof(data, "ECH: setting public_name not supported with BoringSSL"); return CURLE_SSL_CONNECT_ERROR; @@ -4949,6 +4943,15 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_NONE; if(ssl_connect_1 == connssl->connecting_state) { +#ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST + /* if we do ECH and need the HTTPS-RR information for it, + * we delay the connect until it arrives or DNS resolve fails. */ + if(ossl_ech_need_httpsrr(data) && + !Curl_conn_dns_resolved_https(data, cf->sockindex)) { + CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); + return CURLE_OK; + } +#endif CURL_TRC_CF(data, cf, "ossl_connect, step1"); result = ossl_connect_step1(cf, data); if(result) diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index b171583155..ec3bb015ec 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -32,6 +32,7 @@ #include "curlx/fopen.h" #include "curlx/strerr.h" #include "urldata.h" +#include "cf-dns.h" #include "curl_trc.h" #include "httpsrr.h" #include "vtls/vtls.h" @@ -909,16 +910,26 @@ cleanup: } #ifdef USE_ECH + +static bool cr_ech_need_httpsrr(struct Curl_easy *data) +{ + if(!CURLECH_ENABLED(data)) + return FALSE; + if((data->set.tls_ech & CURLECH_GREASE) || + (data->set.tls_ech & CURLECH_CLA_CFG)) + return FALSE; + return TRUE; +} + static CURLcode init_config_builder_ech(struct Curl_easy *data, - const struct ssl_connect_data *connssl, + struct Curl_cfilter *cf, struct rustls_client_config_builder *builder) { const rustls_hpke *hpke = rustls_supported_hpke(); unsigned char *ech_config = NULL; size_t ech_config_len = 0; struct Curl_dns_entry *dns = NULL; - struct Curl_https_rrinfo *rinfo = NULL; CURLcode result = CURLE_OK; rustls_result rr; @@ -963,22 +974,9 @@ init_config_builder_ech(struct Curl_easy *data, } } else { - if(connssl->peer.hostname) { - result = Curl_dnscache_get(data, connssl->peer.hostname, - connssl->peer.port, data->conn->ip_version, - &dns); - } - if(!dns) { - if(result) { - failf(data, "ECH: could not resolve %s:%d", - connssl->peer.hostname, connssl->peer.port); - return result; - } - failf(data, "rustls: ECH requested but no DNS info available"); - result = CURLE_SSL_CONNECT_ERROR; - goto cleanup; - } - rinfo = dns->hinfo; + const struct Curl_https_rrinfo *rinfo = + Curl_conn_dns_get_https(data, cf->sockindex); + if(!rinfo || !rinfo->echconfiglist) { failf(data, "rustls: ECH requested but no ECHConfig available"); result = CURLE_SSL_CONNECT_ERROR; @@ -1075,7 +1073,7 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, #ifdef USE_ECH if(CURLECH_ENABLED(data)) { - result = init_config_builder_ech(data, connssl, config_builder); + result = init_config_builder_ech(data, cf, config_builder); if(result != CURLE_OK && data->set.tls_ech & CURLECH_HARD) { rustls_client_config_builder_free(config_builder); return result; @@ -1146,6 +1144,16 @@ static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, CURL_TRC_CF(data, cf, "cr_connect, state=%d", connssl->state); *done = FALSE; +#ifdef USE_ECH + /* if we do ECH and need the HTTPS-RR information for it, + * we delay the connect until it arrives or DNS resolve fails. */ + if(cr_ech_need_httpsrr(data) && + !Curl_conn_dns_resolved_https(data, cf->sockindex)) { + CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); + return CURLE_OK; + } +#endif /* USE_ECH */ + if(!backend->conn) { result = cr_init_backend(cf, data, (struct rustls_ssl_backend_data *)connssl->backend); diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 328a149f63..97f5f5549d 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -56,6 +56,7 @@ #include "urldata.h" #include "curl_trc.h" #include "httpsrr.h" +#include "cf-dns.h" #include "vtls/vtls.h" #include "vtls/vtls_int.h" #include "vtls/vtls_scache.h" @@ -1289,55 +1290,31 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, } } else { - struct ssl_connect_data *connssl = cf->ctx; - struct Curl_dns_entry *dns = NULL; - CURLcode result; + const struct Curl_https_rrinfo *rinfo = + Curl_conn_dns_get_https(data, cf->sockindex); - result = Curl_dnscache_get(data, connssl->peer.hostname, - connssl->peer.port, cf->conn->ip_version, - &dns); - if(!dns) { - if(result) { - failf(data, "ECH: could not resolve %s:%d", - connssl->peer.hostname, - connssl->peer.port); - return result; - } - infof(data, "ECH: requested but no DNS info" - " available"); - if(data->set.tls_ech & CURLECH_HARD) - return CURLE_SSL_CONNECT_ERROR; - } - else { - struct Curl_https_rrinfo *rinfo = NULL; + if(rinfo && rinfo->echconfiglist) { + const unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; - rinfo = dns->hinfo; - if(rinfo && rinfo->echconfiglist) { - unsigned char *ecl = rinfo->echconfiglist; - size_t elen = rinfo->echconfiglist_len; - - infof(data, "ECH: ECHConfig from DoH HTTPS RR"); - if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32)elen) != - WOLFSSL_SUCCESS) { - infof(data, "ECH: wolfSSL_SetEchConfigs failed"); - if(data->set.tls_ech & CURLECH_HARD) { - Curl_dns_entry_unlink(data, &dns); - return CURLE_SSL_CONNECT_ERROR; - } - } - else { - trying_ech_now = 1; - infof(data, "ECH: imported ECHConfigList of length %zu", elen); - } - } - else { - infof(data, "ECH: requested but no ECHConfig available"); + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); + if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32)elen) != + WOLFSSL_SUCCESS) { + infof(data, "ECH: wolfSSL_SetEchConfigs failed"); if(data->set.tls_ech & CURLECH_HARD) { - Curl_dns_entry_unlink(data, &dns); return CURLE_SSL_CONNECT_ERROR; } } - Curl_dns_entry_unlink(data, &dns); + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %zu", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) { + return CURLE_SSL_CONNECT_ERROR; + } } } @@ -1510,6 +1487,18 @@ out: return result; } +#ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG +static bool wssl_ech_need_httpsrr(struct Curl_easy *data) +{ + if(!CURLECH_ENABLED(data)) + return FALSE; + if((data->set.tls_ech & CURLECH_GREASE) || + (data->set.tls_ech & CURLECH_CLA_CFG)) + return FALSE; + return TRUE; +} +#endif + /* * This function loads all the client/CA certificates and CRLs. Setup the TLS * layer and do all necessary magic. @@ -2179,6 +2168,15 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_NONE; if(ssl_connect_1 == connssl->connecting_state) { +#ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG + /* if we do ECH and need the HTTPS-RR information for it, + * we delay the connect until it arrives or DNS resolve fails. */ + if(wssl_ech_need_httpsrr(data) && + !Curl_conn_dns_resolved_https(data, cf->sockindex)) { + CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); + return CURLE_OK; + } +#endif /* HAVE_WOLFSSL_CTX_GENERATEECHCONFIG */ result = wssl_connect_step1(cf, data); if(result) return result; diff --git a/tests/libtest/lib655.c b/tests/libtest/lib655.c index 3fc58fe7b1..e74d2de17d 100644 --- a/tests/libtest/lib655.c +++ b/tests/libtest/lib655.c @@ -23,6 +23,8 @@ ***************************************************************************/ #include "first.h" +#include "testtrace.h" + static const char *TEST_DATA_STRING = "Test data"; static int cb_count = 0; @@ -61,6 +63,9 @@ static CURLcode test_lib655(const char *URL) CURL *curl; CURLcode result = CURLE_OK; + debug_config.nohex = TRUE; + debug_config.tracetime = TRUE; + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { curl_mfprintf(stderr, "curl_global_init() failed\n"); return TEST_ERR_MAJOR_BAD; @@ -74,6 +79,9 @@ static CURLcode test_lib655(const char *URL) /* Set the URL that is about to receive our first request. */ test_setopt(curl, CURLOPT_URL, URL); + test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + test_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + test_setopt(curl, CURLOPT_VERBOSE, 1L); test_setopt(curl, CURLOPT_RESOLVER_START_DATA, TEST_DATA_STRING); test_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_alloc_cb_fail); @@ -91,6 +99,9 @@ static CURLcode test_lib655(const char *URL) /* Set the URL that receives our second request. */ test_setopt(curl, CURLOPT_URL, libtest_arg2); + test_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + test_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + test_setopt(curl, CURLOPT_VERBOSE, 1L); test_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_alloc_cb_pass); diff --git a/tests/unit/unit1658.c b/tests/unit/unit1658.c index efff31f2bf..f2536e6e62 100644 --- a/tests/unit/unit1658.c +++ b/tests/unit/unit1658.c @@ -66,7 +66,7 @@ static void rrresults(struct Curl_https_rrinfo *rr, CURLcode res) curl_msnprintf(p, pend - p, "no-def-alpn|"); p += strlen(p); } - if(rr->port >= 0) { + if(rr->port_set) { curl_msnprintf(p, pend - p, "port:%d|", rr->port); p += strlen(p); }