diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c index 11ca92b399..50a67051c0 100644 --- a/lib/asyn-ares.c +++ b/lib/asyn-ares.c @@ -132,6 +132,10 @@ static CURLcode async_ares_init(struct Curl_easy *data, int optmask = ARES_OPT_SOCK_STATE_CB; CURLcode rc = CURLE_OK; + /* initial status - failed */ + ares->ares_status = ARES_ENOTFOUND; + async->queries_ongoing = 0; + options.sock_state_cb = sock_state_cb; options.sock_state_cb_data = data; @@ -190,9 +194,13 @@ out: static void async_ares_cleanup(struct Curl_resolv_async *async) { struct async_ares_ctx *ares = &async->ares; - if(ares->temp_ai) { - Curl_freeaddrinfo(ares->temp_ai); - ares->temp_ai = NULL; + if(ares->res_A) { + Curl_freeaddrinfo(ares->res_A); + ares->res_A = NULL; + } + if(ares->res_AAAA) { + Curl_freeaddrinfo(ares->res_AAAA); + ares->res_AAAA = NULL; } #ifdef USE_HTTPSRR Curl_httpsrr_cleanup(&ares->hinfo); @@ -263,53 +271,55 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, goto out; } - 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, async->dns_queries, &ares->temp_ai, - async->hostname, async->port); - if(!dns) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } -#ifdef HTTPSRR_WORKS - if(async->dns_queries & CURL_DNSQ_HTTPS) { - if(ares->hinfo.complete) { - struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); - if(!lhrr) - result = CURLE_OUT_OF_MEMORY; - else - Curl_dns_entry_set_https_rr(dns, lhrr); - } - else - Curl_dns_entry_set_https_rr(dns, NULL); - } -#endif - if(!result) { - result = Curl_dnscache_add(data, dns); - *pdns = dns; - } - } - /* if we have not found anything, report the proper - * CURLE_COULDNT_RESOLVE_* code */ - if(!result && !*pdns) { - const char *msg = NULL; - if(ares->ares_status != ARES_SUCCESS) - msg = ares_strerror(ares->ares_status); - result = Curl_resolver_error(data, msg); - } - - CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound", - result, *pdns ? "" : "not "); - async_ares_cleanup(async); + if(async->queries_ongoing) { + result = CURLE_AGAIN; + goto out; } - else - return CURLE_AGAIN; + + /* all c-ares operations done, what is the result to report? */ + result = ares->result; + if(ares->ares_status == ARES_SUCCESS && !result) { + struct Curl_dns_entry *dns = + Curl_dnscache_mk_entry2(data, async->dns_queries, + &ares->res_AAAA, &ares->res_A, + async->hostname, async->port); + if(!dns) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } +#ifdef HTTPSRR_WORKS + if(async->dns_queries & CURL_DNSQ_HTTPS) { + if(ares->hinfo.complete) { + struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); + if(!lhrr) + result = CURLE_OUT_OF_MEMORY; + else + Curl_dns_entry_set_https_rr(dns, lhrr); + } + else + Curl_dns_entry_set_https_rr(dns, NULL); + } +#endif + if(!result) { + *pdns = dns; + } + } + /* if we have not found anything, report the proper + * CURLE_COULDNT_RESOLVE_* code */ + if(!result && !*pdns) { + const char *msg = NULL; + if(ares->ares_status != ARES_SUCCESS) + msg = ares_strerror(ares->ares_status); + result = Curl_resolver_error(data, msg); + } + + CURL_TRC_DNS(data, "ares: is_resolved() result=%d, dns=%sfound", + result, *pdns ? "" : "not "); + async_ares_cleanup(async); out: - ares->result = result; + if(result != CURLE_AGAIN) + ares->result = result; return result; } @@ -339,16 +349,41 @@ static timediff_t async_ares_poll_timeout(struct async_ares_ctx *ares, return 1000; } +static const struct Curl_addrinfo * +async_ares_get_ai(const struct Curl_addrinfo *ai, + int ai_family, unsigned int index) +{ + unsigned int i = 0; + for(i = 0; ai; ai = ai->ai_next) { + if(ai->ai_family == ai_family) { + if(i == index) + return ai; + ++i; + } + } + return NULL; +} + const struct Curl_addrinfo * Curl_async_get_ai(struct Curl_easy *data, struct Curl_resolv_async *async, int ai_family, unsigned int index) { - /* Not supported by our implementation yet. */ + struct async_ares_ctx *ares = &async->ares; + (void)data; - (void)async; - (void)ai_family; - (void)index; + switch(ai_family) { + case AF_INET: + if(ares->res_A) + return async_ares_get_ai(ares->res_A, ai_family, index); + break; + case AF_INET6: + if(ares->res_AAAA) + return async_ares_get_ai(ares->res_AAAA, ai_family, index); + break; + default: + break; + } return NULL; } @@ -367,7 +402,8 @@ bool Curl_async_knows_https(struct Curl_easy *data, { (void)data; if(async->dns_queries & CURL_DNSQ_HTTPS) - return !async->queries_ongoing; + return ((async->dns_responses & CURL_DNSQ_HTTPS) || + !async->queries_ongoing); return TRUE; /* we know it will never come */ } @@ -514,28 +550,50 @@ async_ares_node2addr(struct ares_addrinfo_node *node) return cafirst; } -static void async_ares_addrinfo_cb(void *user_data, int status, int timeouts, - struct ares_addrinfo *ares_ai) +static void async_ares_A_cb(void *user_data, int status, int timeouts, + struct ares_addrinfo *ares_ai) { struct Curl_resolv_async *async = user_data; struct async_ares_ctx *ares = async ? &async->ares : NULL; (void)timeouts; - if(!ares) + if(!async) return; - if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ - ares->ares_status = status; + + async->dns_responses |= CURL_DNSQ_A; + async->queries_ongoing--; if(status == ARES_SUCCESS) { - ares->temp_ai = async_ares_node2addr(ares_ai->nodes); + ares->ares_status = ARES_SUCCESS; + ares->res_A = async_ares_node2addr(ares_ai->nodes); ares_freeaddrinfo(ares_ai); } - 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--; + else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ + ares->ares_status = status; } +#ifdef CURLRES_IPV6 +static void async_ares_AAAA_cb(void *user_data, int status, int timeouts, + struct ares_addrinfo *ares_ai) +{ + struct Curl_resolv_async *async = user_data; + struct async_ares_ctx *ares = async ? &async->ares : NULL; + + (void)timeouts; + if(!async) + return; + + async->dns_responses |= CURL_DNSQ_AAAA; + async->queries_ongoing--; + if(status == ARES_SUCCESS) { + ares->ares_status = ARES_SUCCESS; + ares->res_AAAA = async_ares_node2addr(ares_ai->nodes); + ares_freeaddrinfo(ares_ai); + } + else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ + ares->ares_status = status; +} +#endif /* CURLRES_IPV6 */ + #ifdef USE_HTTPSRR static void async_ares_rr_done(void *user_data, ares_status_t status, size_t timeouts, @@ -544,8 +602,9 @@ static void async_ares_rr_done(void *user_data, ares_status_t status, struct Curl_resolv_async *async = user_data; struct async_ares_ctx *ares = async ? &async->ares : NULL; - if(!ares) + if(!async) return; + (void)timeouts; async->dns_responses |= CURL_DNSQ_HTTPS; async->queries_ongoing--; @@ -564,60 +623,63 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = &async->ares; + char service[12]; + int socktype; + CURLcode result = CURLE_OK; - CURL_TRC_DNS(data, "ares getaddrinfo(%s:%u)", async->hostname, async->port); if(ares->channel) { DEBUGASSERT(0); - return CURLE_FAILED_INIT; + result = CURLE_FAILED_INIT; + goto out; } - if(async_ares_init(data, async)) - return CURLE_FAILED_INIT; + result = async_ares_init(data, async); + if(result) + goto out; - /* 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; + result = Curl_resolv_announce_start(data, ares->channel); + if(result) + goto out; #if defined(CURLVERBOSE) && ARES_VERSION >= 0x011800 /* >= v1.24.0 */ if(CURL_TRC_DNS_is_verbose(data)) { char *csv = ares_get_servers_csv(ares->channel); - CURL_TRC_DNS(data, "asyn-ares: servers=%s", csv); + CURL_TRC_DNS(data, "ares: servers=%s", csv); ares_free_string(csv); } #endif - { - struct ares_addrinfo_hints hints; - char service[12]; - int pf = PF_INET; - memset(&hints, 0, sizeof(hints)); + curl_msnprintf(service, sizeof(service), "%d", async->port); + socktype = + (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ? + SOCK_STREAM : SOCK_DGRAM; + #ifdef CURLRES_IPV6 - 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", - (pf == PF_UNSPEC) ? "A+AAAA" : - ((pf == PF_INET) ? "A" : "AAAA")); - hints.ai_family = pf; - hints.ai_socktype = - (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ? - SOCK_STREAM : SOCK_DGRAM; - /* Since the service is a numerical one, set the hint flags - * accordingly to save a call to getservbyname in inside C-Ares - */ + if(async->dns_queries & CURL_DNSQ_AAAA) { + struct ares_addrinfo_hints hints; + + memset(&hints, 0, sizeof(hints)); + CURL_TRC_DNS(data, "ares: query AAAA records for %s", async->hostname); + hints.ai_family = PF_INET6; + hints.ai_socktype = socktype; hints.ai_flags = ARES_AI_NUMERICSERV; - curl_msnprintf(service, sizeof(service), "%d", async->port); - async->queries_ongoing = 1; ares_getaddrinfo(ares->channel, async->hostname, - service, &hints, async_ares_addrinfo_cb, async); + service, &hints, async_ares_AAAA_cb, async); + async->queries_ongoing++; + } +#endif /* CURLRES_IPV6 */ + + if(async->dns_queries & CURL_DNSQ_A) { + struct ares_addrinfo_hints hints; + + memset(&hints, 0, sizeof(hints)); + CURL_TRC_DNS(data, "ares: query A records for %s", async->hostname); + hints.ai_family = PF_INET; + hints.ai_socktype = socktype; + hints.ai_flags = ARES_AI_NUMERICSERV; + ares_getaddrinfo(ares->channel, async->hostname, + service, &hints, async_ares_A_cb, async); + async->queries_ongoing++; } #ifdef USE_HTTPSRR @@ -629,7 +691,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, if(!rrname) return CURLE_OUT_OF_MEMORY; } - CURL_TRC_DNS(data, "asyn-ares: fire off query for HTTPSRR: %s", + CURL_TRC_DNS(data, "ares: query HTTPS records for %s", rrname ? rrname : async->hostname); ares->hinfo.rrname = rrname; async->queries_ongoing++; @@ -638,9 +700,11 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, async_ares_rr_done, async, NULL); } -#endif +#endif /* USE_HTTPSRR */ - return CURLE_OK; +out: + ares->result = result; + return result ? result : (async->queries_ongoing ? CURLE_AGAIN : CURLE_OK); } /* Set what DNS server are is to use. This is called in 2 situations: diff --git a/lib/asyn.h b/lib/asyn.h index 763a1a9e7a..41f588b51f 100644 --- a/lib/asyn.h +++ b/lib/asyn.h @@ -114,8 +114,8 @@ 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; - struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares - parts */ + struct Curl_addrinfo *res_A; + struct Curl_addrinfo *res_AAAA; int ares_status; /* ARES_SUCCESS, ARES_ENOTFOUND, etc. */ CURLcode result; /* CURLE_OK or error handling response */ struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ diff --git a/lib/hostip.c b/lib/hostip.c index 4dd041954e..106763798f 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -153,6 +153,8 @@ const char *Curl_resolv_query_str(uint8_t dns_queries) return "A"; case (CURL_DNSQ_HTTPS): return "HTTPS"; + case 0: + return "-"; default: DEBUGASSERT(0); return "???"; @@ -421,12 +423,21 @@ static CURLcode hostip_resolv_take_result(struct Curl_easy *data, #endif result = Curl_async_take_result(data, async, pdns); - if(result == CURLE_AGAIN) + if(result == CURLE_AGAIN) { + CURL_TRC_DNS(data, "result incomplete, queries=%s, responses=%s, " + "ongoing=%d", Curl_resolv_query_str(async->dns_queries), + Curl_resolv_query_str(async->dns_responses), + async->queries_ongoing); result = CURLE_OK; - else if(result) + } + else if(result) { + CURL_TRC_DNS(data, "result error %d", result); Curl_resolver_error(data, NULL); - else + } + else { + CURL_TRC_DNS(data, "result complete"); DEBUGASSERT(*pdns); + } return result; } diff --git a/tests/data/test2104 b/tests/data/test2104 index e16e1e14c6..bd468efeeb 100644 --- a/tests/data/test2104 +++ b/tests/data/test2104 @@ -27,21 +27,21 @@ Get three URLs with bad hostname - cache CURL_DNS_SERVER=127.0.0.1:%DNSPORT -http://examplehost.example/%TESTNUMBER http://examplehost.example/%TESTNUMBER http://examplehost.example/%TESTNUMBER +http://examplehost.invalid/%TESTNUMBER http://examplehost.invalid/%TESTNUMBER http://examplehost.invalid/%TESTNUMBER # Verify data after the test has been "shot" -# curl: (6) Could not resolve host: examplehost.example +# curl: (6) Could not resolve host: examplehost.invalid 6 # Ignore HTTPS requests here - -QNAME examplehost.example QTYPE A -QNAME examplehost.example QTYPE AAAA + +QNAME examplehost.invalid QTYPE A +QNAME examplehost.invalid QTYPE AAAA diff --git a/tests/http/test_21_resolve.py b/tests/http/test_21_resolve.py index 35997f2aba..ac7fc3b553 100644 --- a/tests/http/test_21_resolve.py +++ b/tests/http/test_21_resolve.py @@ -35,7 +35,6 @@ log = logging.getLogger(__name__) @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") -@pytest.mark.skipif(condition=Env.curl_uses_lib('c-ares'), reason="c-ares resolver skipped") @pytest.mark.skipif(condition=not Env.curl_has_feature('AsynchDNS'), reason="needs AsynchDNS") class TestResolve: @@ -102,6 +101,7 @@ class TestResolve: assert os.path.exists(dfiles[1]) # use .invalid host name, parallel, single resolve thread + @pytest.mark.skipif(condition=Env.curl_uses_lib('c-ares'), reason="c-ares resolver skipped") def test_21_05_resolv_single_thread(self, env: Env, httpd, nghttpx): count = 10 delay_ms = 50