dnscache: own source file, improvements
No binary (build failed)
—
(log yok)
| @@ -235,7 +235,7 @@ function in a new field in the `dohentry` structure. | |
| The qname for the DoH query is modified if the port number is not 443, as | |
| defined in the SVCB specification. | |
| - | When the DoH process has worked, `Curl_doh_is_resolved()` now also returns |
| + | When the DoH process has worked, `Curl_doh_take_result()` now also returns |
| the relevant HTTPS RR value data in the `Curl_dns_entry` structure. | |
| That is later accessed when the TLS session is being established, if ECH is | |
| enabled (from `lib/vtls/openssl.c` as described above). | |
| @@ -181,6 +181,7 @@ LIB_CFILES = \ | |
| cw-out.c \ | |
| cw-pause.c \ | |
| dict.c \ | |
| + | dnscache.c \ |
| doh.c \ | |
| dynhds.c \ | |
| easy.c \ | |
| @@ -316,6 +317,7 @@ LIB_HFILES = \ | |
| cw-out.h \ | |
| cw-pause.h \ | |
| dict.h \ | |
| + | dnscache.h \ |
| doh.h \ | |
| dynhds.h \ | |
| easy_lock.h \ | |
| @@ -118,7 +118,7 @@ void Curl_amiga_cleanup(void) | |
| * allocates memory also. | |
| */ | |
| - | struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port) |
| + | struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, uint16_t port) |
| { | |
| struct Curl_addrinfo *ai = NULL; | |
| struct hostent *h; | |
| @@ -142,9 +142,10 @@ static void sock_state_cb(void *data, ares_socket_t socket_fd, | |
| } | |
| } | |
| - | static CURLcode async_ares_init(struct Curl_easy *data) |
| + | static CURLcode async_ares_init(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct async_ares_ctx *ares = &async->ares; |
| int status; | |
| struct ares_options options; | |
| int optmask = ARES_OPT_SOCK_STATE_CB; | |
| @@ -202,20 +203,23 @@ out: | |
| return rc; | |
| } | |
| - | static CURLcode async_ares_init_lazy(struct Curl_easy *data) |
| + | static CURLcode async_ares_init_lazy(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct async_ares_ctx *ares = &async->ares; |
| if(!ares->channel) | |
| - | return async_ares_init(data); |
| + | return async_ares_init(data, async); |
| return CURLE_OK; | |
| } | |
| - | CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) |
| + | CURLcode Curl_async_get_impl(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async, |
| + | void **impl) |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct async_ares_ctx *ares = &async->ares; |
| CURLcode result = CURLE_OK; | |
| if(!ares->channel) { | |
| - | result = async_ares_init(data); |
| + | result = async_ares_init(data, async); |
| } | |
| *impl = ares->channel; | |
| return result; | |
| @@ -224,9 +228,9 @@ CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) | |
| /* | |
| * async_ares_cleanup() cleans up async resolver data. | |
| */ | |
| - | static void async_ares_cleanup(struct Curl_easy *data) |
| + | static void async_ares_cleanup(struct Curl_resolv_async *async) |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct async_ares_ctx *ares = &async->ares; |
| if(ares->temp_ai) { | |
| Curl_freeaddrinfo(ares->temp_ai); | |
| ares->temp_ai = NULL; | |
| @@ -236,24 +240,27 @@ static void async_ares_cleanup(struct Curl_easy *data) | |
| #endif | |
| } | |
| - | void Curl_async_ares_shutdown(struct Curl_easy *data) |
| + | void Curl_async_ares_shutdown(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| /* c-ares has a method to "cancel" operations on a channel, but | |
| * as reported in #18216, this does not totally reset the channel | |
| * and ares may get stuck. | |
| * We need to destroy the channel and on demand create a new | |
| * one to avoid that. */ | |
| - | Curl_async_ares_destroy(data); |
| + | Curl_async_ares_destroy(data, async); |
| } | |
| - | void Curl_async_ares_destroy(struct Curl_easy *data) |
| + | void Curl_async_ares_destroy(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct async_ares_ctx *ares = &async->ares; |
| + | (void)data; |
| if(ares->channel) { | |
| ares_destroy(ares->channel); | |
| ares->channel = NULL; | |
| } | |
| - | async_ares_cleanup(data); |
| + | async_ares_cleanup(async); |
| } | |
| /* | |
| @@ -263,32 +270,31 @@ void Curl_async_ares_destroy(struct Curl_easy *data) | |
| CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps) | |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| - | if(ares->channel) |
| + | 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; | |
| } | |
| /* | |
| - | * Curl_async_is_resolved() is called repeatedly to check if a previous |
| + | * Curl_async_take_result() is called repeatedly to check if a previous |
| * name resolve request has completed. It should also make sure to time-out if | |
| * the operation seems to take too long. | |
| * | |
| * Returns normal CURLcode errors. | |
| */ | |
| - | CURLcode Curl_async_is_resolved(struct Curl_easy *data, |
| - | struct Curl_dns_entry **dns) |
| + | CURLcode Curl_async_take_result(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async, |
| + | struct Curl_dns_entry **pdns) |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct async_ares_ctx *ares = &async->ares; |
| CURLcode result = CURLE_OK; | |
| - | DEBUGASSERT(dns); |
| - | *dns = NULL; |
| - | |
| - | if(data->state.async.done) { |
| - | *dns = data->state.async.dns; |
| - | return ares->result; |
| - | } |
| + | DEBUGASSERT(pdns); |
| + | *pdns = NULL; |
| + | if(!ares) |
| + | return CURLE_FAILED_INIT; |
| if(Curl_ares_perform(ares->channel, 0) < 0) { | |
| result = CURLE_UNRECOVERABLE_POLL; | |
| @@ -322,15 +328,13 @@ CURLcode Curl_async_is_resolved(struct Curl_easy *data, | |
| if(!ares->num_pending) { | |
| /* all c-ares operations done, what is the result to report? */ | |
| - | Curl_resolv_unlink(data, &data->state.async.dns); |
| - | data->state.async.done = TRUE; |
| result = ares->result; | |
| if(ares->ares_status == ARES_SUCCESS && !result) { | |
| - | data->state.async.dns = |
| + | struct Curl_dns_entry *dns = |
| Curl_dnscache_mk_entry(data, &ares->temp_ai, | |
| - | data->state.async.hostname, 0, |
| - | data->state.async.port, FALSE); |
| - | if(!data->state.async.dns) { |
| + | async->hostname, async->port, |
| + | async->ip_version); |
| + | if(!dns) { |
| result = CURLE_OUT_OF_MEMORY; | |
| goto out; | |
| } | |
| @@ -340,28 +344,29 @@ CURLcode Curl_async_is_resolved(struct Curl_easy *data, | |
| if(!lhrr) | |
| result = CURLE_OUT_OF_MEMORY; | |
| else | |
| - | data->state.async.dns->hinfo = lhrr; |
| + | dns->hinfo = lhrr; |
| } | |
| #endif | |
| - | if(!result) |
| - | result = Curl_dnscache_add(data, data->state.async.dns); |
| + | 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 && !data->state.async.dns) { |
| + | 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); | |
| } | |
| - | if(result) |
| - | Curl_resolv_unlink(data, &data->state.async.dns); |
| - | *dns = data->state.async.dns; |
| CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound", | |
| - | result, *dns ? "" : "not "); |
| - | async_ares_cleanup(data); |
| + | result, *pdns ? "" : "not "); |
| + | async_ares_cleanup(async); |
| } | |
| + | else |
| + | return CURLE_AGAIN; |
| out: | |
| ares->result = result; | |
| @@ -406,14 +411,15 @@ static timediff_t async_ares_poll_timeout(struct async_ares_ctx *ares, | |
| * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. | |
| */ | |
| CURLcode Curl_async_await(struct Curl_easy *data, | |
| - | struct Curl_dns_entry **dns) |
| + | struct Curl_resolv_async *async, |
| + | struct Curl_dns_entry **pdns) |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct async_ares_ctx *ares = &async->ares; |
| struct curltime start = *Curl_pgrs_now(data); | |
| CURLcode result = CURLE_OK; | |
| - | DEBUGASSERT(dns); |
| - | *dns = NULL; /* clear on entry */ |
| + | DEBUGASSERT(pdns); |
| + | *pdns = NULL; /* clear on entry */ |
| /* Wait for the name resolve query to complete or time out. */ | |
| while(!result) { | |
| @@ -439,8 +445,10 @@ CURLcode Curl_async_await(struct Curl_easy *data, | |
| break; | |
| } | |
| - | result = Curl_async_is_resolved(data, dns); |
| - | if(result || data->state.async.done) |
| + | result = Curl_async_take_result(data, async, pdns); |
| + | if(result == CURLE_AGAIN) |
| + | result = CURLE_OK; |
| + | else if(result || *pdns) |
| break; | |
| if(Curl_pgrsUpdate(data)) { | |
| @@ -449,11 +457,6 @@ CURLcode Curl_async_await(struct Curl_easy *data, | |
| } | |
| } | |
| - | /* Operation complete, if the lookup was successful we now have the entry |
| - | in the cache. */ |
| - | data->state.async.done = TRUE; |
| - | *dns = data->state.async.dns; |
| - | |
| if(result) | |
| ares_cancel(ares->channel); | |
| return result; | |
| @@ -502,17 +505,19 @@ static void async_ares_hostbyname_cb(void *user_data, | |
| struct hostent *hostent) | |
| { | |
| struct Curl_easy *data = (struct Curl_easy *)user_data; | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct Curl_resolv_async *async = data->state.async; |
| + | struct async_ares_ctx *ares = async ? &async->ares : NULL; |
| (void)timeouts; /* ignored */ | |
| + | if(!ares) |
| + | return; |
| if(ARES_EDESTRUCTION == status) | |
| return; | |
| if(ARES_SUCCESS == status) { | |
| ares->ares_status = status; /* one success overrules any error */ | |
| - | async_addr_concat(&ares->temp_ai, |
| - | Curl_he2ai(hostent, data->state.async.port)); |
| + | async_addr_concat(&ares->temp_ai, Curl_he2ai(hostent, async->port)); |
| } | |
| else if(ares->ares_status != ARES_SUCCESS) { | |
| /* no success so far, remember last error */ | |
| @@ -664,8 +669,12 @@ 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 async_ares_ctx *ares = &data->state.async.ares; |
| + | struct Curl_resolv_async *async = data->state.async; |
| + | struct async_ares_ctx *ares = async ? &async->ares : NULL; |
| + | |
| (void)timeouts; | |
| + | if(!ares) |
| + | return; |
| if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ | |
| ares->ares_status = status; | |
| if(status == ARES_SUCCESS) { | |
| @@ -687,8 +696,11 @@ static void async_ares_rr_done(void *user_data, ares_status_t status, | |
| const ares_dns_record_t *dnsrec) | |
| { | |
| struct Curl_easy *data = user_data; | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct Curl_resolv_async *async = data->state.async; |
| + | 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, " | |
| @@ -708,27 +720,20 @@ static void async_ares_rr_done(void *user_data, ares_status_t status, | |
| * | |
| * Starts a name resolve for the given hostname and port number. | |
| */ | |
| - | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, |
| - | int port, int ip_version) |
| + | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | struct async_ares_ctx *ares = &async->ares; |
| #ifdef USE_HTTPSRR | |
| char *rrname = NULL; | |
| #endif | |
| - | if(async_ares_init_lazy(data)) |
| + | if(async_ares_init_lazy(data, async)) |
| return CURLE_FAILED_INIT; | |
| - | data->state.async.done = FALSE; /* not done */ |
| - | data->state.async.dns = NULL; /* clear */ |
| - | data->state.async.port = port; |
| - | data->state.async.ip_version = ip_version; |
| - | data->state.async.hostname = curlx_strdup(hostname); |
| - | if(!data->state.async.hostname) |
| - | return CURLE_OUT_OF_MEMORY; |
| #ifdef USE_HTTPSRR | |
| - | if(port != 443) { |
| - | rrname = curl_maprintf("_%d._https.%s", port, hostname); |
| + | if(async->port != 443) { |
| + | rrname = curl_maprintf("_%d._https.%s", async->port, async->hostname); |
| if(!rrname) | |
| return CURLE_OUT_OF_MEMORY; | |
| } | |
| @@ -753,9 +758,9 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, | |
| int pf = PF_INET; | |
| memset(&hints, 0, sizeof(hints)); | |
| #ifdef CURLRES_IPV6 | |
| - | if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| + | if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| /* The stack seems to be IPv6-enabled */ | |
| - | if(ip_version == CURL_IPRESOLVE_V6) |
| + | if(async->ip_version == CURL_IPRESOLVE_V6) |
| pf = PF_INET6; | |
| else | |
| pf = PF_UNSPEC; | |
| @@ -772,23 +777,23 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, | |
| * accordingly to save a call to getservbyname in inside C-Ares | |
| */ | |
| hints.ai_flags = ARES_AI_NUMERICSERV; | |
| - | curl_msnprintf(service, sizeof(service), "%d", port); |
| + | curl_msnprintf(service, sizeof(service), "%d", async->port); |
| ares->num_pending = 1; | |
| - | ares_getaddrinfo(ares->channel, data->state.async.hostname, |
| + | ares_getaddrinfo(ares->channel, async->hostname, |
| service, &hints, async_ares_addrinfo_cb, data); | |
| } | |
| #else | |
| #if ARES_VERSION >= 0x010601 /* IPv6 supported since 1.6.1 */ | |
| - | if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| + | if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| /* The stack seems to be IPv6-enabled */ | |
| /* areschannel is already setup in the Curl_open() function */ | |
| CURL_TRC_DNS(data, "asyn-ares: fire off query for A"); | |
| - | ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET, |
| + | ares_gethostbyname(ares->channel, async->hostname, PF_INET, |
| async_ares_hostbyname_cb, data); | |
| CURL_TRC_DNS(data, "asyn-ares: fire off query for AAAA"); | |
| ares->num_pending = 2; | |
| - | ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET6, |
| + | ares_gethostbyname(ares->channel, async->hostname, PF_INET6, |
| async_ares_hostbyname_cb, data); | |
| } | |
| else | |
| @@ -797,20 +802,20 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, | |
| /* areschannel is already setup in the Curl_open() function */ | |
| CURL_TRC_DNS(data, "asyn-ares: fire off query for A"); | |
| ares->num_pending = 1; | |
| - | ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET, |
| + | ares_gethostbyname(ares->channel, async->hostname, PF_INET, |
| async_ares_hostbyname_cb, data); | |
| } | |
| #endif | |
| #ifdef USE_HTTPSRR | |
| { | |
| CURL_TRC_DNS(data, "asyn-ares: fire off query for HTTPSRR: %s", | |
| - | rrname ? rrname : data->state.async.hostname); |
| + | rrname ? rrname : async->hostname); |
| memset(&ares->hinfo, 0, sizeof(ares->hinfo)); | |
| ares->hinfo.port = -1; | |
| ares->hinfo.rrname = rrname; | |
| ares->num_pending++; /* one more */ | |
| ares_query_dnsrec(ares->channel, | |
| - | rrname ? rrname : data->state.async.hostname, |
| + | rrname ? rrname : async->hostname, |
| ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, | |
| async_ares_rr_done, data, NULL); | |
| } | |
| @@ -828,7 +833,8 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, | |
| static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, | |
| bool reset_on_null) | |
| { | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | 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]; | |
| int ares_result = ARES_SUCCESS; | |
| @@ -847,7 +853,7 @@ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, | |
| #ifdef HAVE_CARES_SERVERS_CSV | |
| /* if channel is not there, this is a parameter check */ | |
| - | if(ares->channel) |
| + | if(ares && ares->channel) |
| #ifdef HAVE_CARES_PORTS_CSV | |
| ares_result = ares_set_servers_ports_csv(ares->channel, servers); | |
| #else | |
| @@ -883,14 +889,15 @@ CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data) | |
| CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data) | |
| { | |
| #ifdef HAVE_CARES_LOCAL_DEV | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | 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]; | |
| if(!interf) | |
| interf = ""; | |
| /* if channel is not there, this is a parameter check */ | |
| - | if(ares->channel) |
| + | if(ares && ares->channel) |
| ares_set_local_dev(ares->channel, interf); | |
| return CURLE_OK; | |
| @@ -903,7 +910,8 @@ CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data) | |
| CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data) | |
| { | |
| #ifdef HAVE_CARES_SET_LOCAL | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | 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]; | |
| @@ -918,7 +926,7 @@ CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data) | |
| } | |
| /* if channel is not there yet, this is a parameter check */ | |
| - | if(ares->channel) |
| + | if(ares && ares->channel) |
| ares_set_local_ip4(ares->channel, ntohl(a4.s_addr)); | |
| return CURLE_OK; | |
| @@ -931,7 +939,8 @@ CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data) | |
| CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data) | |
| { | |
| #if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6) | |
| - | struct async_ares_ctx *ares = &data->state.async.ares; |
| + | 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]; | |
| @@ -947,7 +956,7 @@ CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data) | |
| } | |
| /* if channel is not there, this is a parameter check */ | |
| - | if(ares->channel) |
| + | if(ares && ares->channel) |
| ares_set_local_ip6(ares->channel, a6); | |
| return CURLE_OK; | |
| @@ -43,6 +43,7 @@ | |
| #endif | |
| #include "urldata.h" | |
| + | #include "curl_trc.h" |
| #include "hostip.h" | |
| #include "multiif.h" | |
| #include "select.h" | |
| @@ -180,30 +181,35 @@ int Curl_ares_perform(ares_channel channel, timediff_t timeout_ms) | |
| void Curl_async_shutdown(struct Curl_easy *data) | |
| { | |
| + | if(data->state.async) { |
| + | CURL_TRC_DNS(data, "shutdown async"); |
| #ifdef CURLRES_ARES | |
| - | Curl_async_ares_shutdown(data); |
| + | Curl_async_ares_shutdown(data, data->state.async); |
| #endif | |
| #ifdef CURLRES_THREADED | |
| - | Curl_async_thrdd_shutdown(data); |
| + | Curl_async_thrdd_shutdown(data, data->state.async); |
| #endif | |
| #ifndef CURL_DISABLE_DOH | |
| - | Curl_doh_cleanup(data); |
| + | Curl_doh_cleanup(data, data->state.async); |
| #endif | |
| - | Curl_safefree(data->state.async.hostname); |
| + | } |
| } | |
| void Curl_async_destroy(struct Curl_easy *data) | |
| { | |
| + | if(data->state.async) { |
| + | CURL_TRC_DNS(data, "destroy async"); |
| #ifdef CURLRES_ARES | |
| - | Curl_async_ares_destroy(data); |
| + | Curl_async_ares_destroy(data, data->state.async); |
| #endif | |
| #ifdef CURLRES_THREADED | |
| - | Curl_async_thrdd_destroy(data); |
| + | Curl_async_thrdd_destroy(data, data->state.async); |
| #endif | |
| #ifndef CURL_DISABLE_DOH | |
| - | Curl_doh_cleanup(data); |
| + | Curl_doh_cleanup(data, data->state.async); |
| #endif | |
| - | Curl_safefree(data->state.async.hostname); |
| + | Curl_safefree(data->state.async); |
| + | } |
| } | |
| #endif /* USE_CURL_ASYNC */ | |
| @@ -101,12 +101,12 @@ void Curl_async_global_cleanup(void) | |
| #endif | |
| } | |
| - | static void async_thrdd_destroy(struct Curl_easy *data); |
| - | static void async_thrdd_shutdown(struct Curl_easy *data); |
| - | |
| - | CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) |
| + | 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; | |
| } | |
| @@ -144,7 +144,7 @@ static void addr_ctx_unlink(struct async_thrdd_addr_ctx **paddr_ctx, | |
| /* Initialize context for threaded resolver */ | |
| static struct async_thrdd_addr_ctx * | |
| addr_ctx_create(struct Curl_easy *data, | |
| - | const char *hostname, int port, |
| + | struct Curl_resolv_async *async, |
| const struct addrinfo *hints) | |
| { | |
| struct async_thrdd_addr_ctx *addr_ctx = curlx_calloc(1, sizeof(*addr_ctx)); | |
| @@ -152,7 +152,7 @@ addr_ctx_create(struct Curl_easy *data, | |
| return NULL; | |
| addr_ctx->thread_hnd = curl_thread_t_null; | |
| - | addr_ctx->port = port; |
| + | addr_ctx->port = async->port; |
| addr_ctx->ref_count = 1; | |
| #ifdef HAVE_GETADDRINFO | |
| @@ -177,7 +177,7 @@ addr_ctx_create(struct Curl_easy *data, | |
| /* Copying hostname string because original can be destroyed by parent | |
| * thread during gethostbyname execution. | |
| */ | |
| - | addr_ctx->hostname = curlx_strdup(hostname); |
| + | addr_ctx->hostname = curlx_strdup(async->hostname); |
| if(!addr_ctx->hostname) | |
| goto err_exit; | |
| @@ -287,9 +287,10 @@ static CURL_THREAD_RETURN_T CURL_STDCALL gethostbyname_thread(void *arg) | |
| /* | |
| * async_thrdd_destroy() cleans up async resolver data and thread handle. | |
| */ | |
| - | static void async_thrdd_destroy(struct Curl_easy *data) |
| + | static void async_thrdd_destroy(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; |
| + | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| struct async_thrdd_addr_ctx *addr = thrdd->addr; | |
| #ifdef USE_HTTPSRR_ARES | |
| @@ -333,7 +334,7 @@ static void async_thrdd_rr_done(void *user_data, ares_status_t status, | |
| const ares_dns_record_t *dnsrec) | |
| { | |
| struct Curl_easy *data = user_data; | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; |
| + | struct async_thrdd_ctx *thrdd = &data->state.async->thrdd; |
| (void)timeouts; | |
| thrdd->rr.done = TRUE; | |
| @@ -344,7 +345,7 @@ static void async_thrdd_rr_done(void *user_data, ares_status_t status, | |
| static CURLcode async_rr_start(struct Curl_easy *data, int port) | |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; |
| + | struct async_thrdd_ctx *thrdd = &data->state.async->thrdd; |
| int status; | |
| char *rrname = NULL; | |
| @@ -390,38 +391,21 @@ static CURLcode async_rr_start(struct Curl_easy *data, int port) | |
| * Returns FALSE in case of failure, otherwise TRUE. | |
| */ | |
| static bool async_thrdd_init(struct Curl_easy *data, | |
| - | const char *hostname, int port, int ip_version, |
| + | struct Curl_resolv_async *async, |
| const struct addrinfo *hints) | |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; |
| + | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| struct async_thrdd_addr_ctx *addr_ctx; | |
| /* !checksrc! disable ERRNOVAR 1 */ | |
| int err = ENOMEM; | |
| - | if(thrdd->addr |
| - | #ifdef USE_HTTPSRR_ARES |
| - | || thrdd->rr.channel |
| - | #endif |
| - | ) { |
| - | CURL_TRC_DNS(data, "starting new resolve, with previous not cleaned up"); |
| - | async_thrdd_destroy(data); |
| - | DEBUGASSERT(!thrdd->addr); |
| + | DEBUGASSERT(!thrdd->addr); |
| #ifdef USE_HTTPSRR_ARES | |
| - | DEBUGASSERT(!thrdd->rr.channel); |
| + | DEBUGASSERT(!thrdd->rr.channel); |
| #endif | |
| - | } |
| - | data->state.async.dns = NULL; |
| - | data->state.async.done = FALSE; |
| - | data->state.async.port = port; |
| - | data->state.async.ip_version = ip_version; |
| - | curlx_free(data->state.async.hostname); |
| - | data->state.async.hostname = curlx_strdup(hostname); |
| - | if(!data->state.async.hostname) |
| - | goto err_exit; |
| - | |
| - | addr_ctx = addr_ctx_create(data, hostname, port, hints); |
| + | addr_ctx = addr_ctx_create(data, async, hints); |
| if(!addr_ctx) | |
| goto err_exit; | |
| thrdd->addr = addr_ctx; | |
| @@ -445,22 +429,24 @@ static bool async_thrdd_init(struct Curl_easy *data, | |
| } | |
| #ifdef USE_HTTPSRR_ARES | |
| - | if(async_rr_start(data, port)) |
| + | if(async_rr_start(data, async->port)) |
| infof(data, "Failed HTTPS RR operation"); | |
| #endif | |
| - | CURL_TRC_DNS(data, "resolve thread started for of %s:%d", hostname, port); |
| + | CURL_TRC_DNS(data, "resolve thread started for of %s:%d", |
| + | async->hostname, async->port); |
| return TRUE; | |
| err_exit: | |
| CURL_TRC_DNS(data, "resolve thread failed init: %d", err); | |
| - | async_thrdd_destroy(data); |
| + | async_thrdd_destroy(data, async); |
| errno = err; | |
| return FALSE; | |
| } | |
| - | static void async_thrdd_shutdown(struct Curl_easy *data) |
| + | static void async_thrdd_shutdown(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; |
| + | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| struct async_thrdd_addr_ctx *addr_ctx = thrdd->addr; | |
| curl_bit done; | |
| @@ -491,15 +477,16 @@ static void async_thrdd_shutdown(struct Curl_easy *data) | |
| * 'entry' may be NULL and then no data is returned | |
| */ | |
| static CURLcode asyn_thrdd_await(struct Curl_easy *data, | |
| - | struct async_thrdd_addr_ctx *addr_ctx, |
| + | struct Curl_resolv_async *async, |
| struct Curl_dns_entry **entry) | |
| { | |
| + | struct async_thrdd_addr_ctx *addr_ctx = async->thrdd.addr; |
| CURLcode result = CURLE_OK; | |
| - | if(addr_ctx->thread_hnd != curl_thread_t_null) { |
| + | if(addr_ctx && (addr_ctx->thread_hnd != curl_thread_t_null)) { |
| /* not interested in result? cancel, if still running... */ | |
| if(!entry) | |
| - | async_thrdd_shutdown(data); |
| + | async_thrdd_shutdown(data, async); |
| if(addr_ctx->thread_hnd != curl_thread_t_null) { | |
| CURL_TRC_DNS(data, "resolve, wait for thread to finish"); | |
| @@ -508,14 +495,13 @@ static CURLcode asyn_thrdd_await(struct Curl_easy *data, | |
| } | |
| } | |
| - | if(entry) |
| - | result = Curl_async_is_resolved(data, entry); |
| + | if(entry) { |
| + | result = Curl_async_take_result(data, async, entry); |
| + | if(result == CURLE_AGAIN) |
| + | result = CURLE_OK; |
| + | } |
| } | |
| - | data->state.async.done = TRUE; |
| - | if(entry) |
| - | *entry = data->state.async.dns; |
| - | |
| return result; | |
| } | |
| @@ -523,19 +509,19 @@ static CURLcode asyn_thrdd_await(struct Curl_easy *data, | |
| * Until we gain a way to signal the resolver threads to stop early, we must | |
| * wait for them and ignore their results. | |
| */ | |
| - | void Curl_async_thrdd_shutdown(struct Curl_easy *data) |
| + | void Curl_async_thrdd_shutdown(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | async_thrdd_shutdown(data); |
| + | async_thrdd_shutdown(data, async); |
| } | |
| - | void Curl_async_thrdd_destroy(struct Curl_easy *data) |
| + | void Curl_async_thrdd_destroy(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; |
| - | |
| - | if(thrdd->addr && !data->set.quick_exit) { |
| - | (void)asyn_thrdd_await(data, thrdd->addr, NULL); |
| + | if(!data->set.quick_exit) { |
| + | (void)asyn_thrdd_await(data, async, NULL); |
| } | |
| - | async_thrdd_destroy(data); |
| + | async_thrdd_destroy(data, async); |
| } | |
| /* | |
| @@ -552,34 +538,26 @@ void Curl_async_thrdd_destroy(struct Curl_easy *data) | |
| * This is the version for resolves-in-a-thread. | |
| */ | |
| CURLcode Curl_async_await(struct Curl_easy *data, | |
| - | struct Curl_dns_entry **dns) |
| + | struct Curl_resolv_async *async, |
| + | struct Curl_dns_entry **entry) |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; |
| - | if(thrdd->addr) |
| - | return asyn_thrdd_await(data, thrdd->addr, dns); |
| - | return CURLE_FAILED_INIT; |
| + | return asyn_thrdd_await(data, async, entry); |
| } | |
| /* | |
| - | * Curl_async_is_resolved() is called repeatedly to check if a previous |
| + | * Curl_async_take_result() is called repeatedly to check if a previous |
| * name resolve request has completed. It should also make sure to time-out if | |
| * the operation seems to take too long. | |
| */ | |
| - | CURLcode Curl_async_is_resolved(struct Curl_easy *data, |
| - | struct Curl_dns_entry **dns) |
| + | CURLcode Curl_async_take_result(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async, |
| + | struct Curl_dns_entry **pdns) |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; |
| + | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| curl_bit done = FALSE; | |
| - | DEBUGASSERT(dns); |
| - | *dns = NULL; |
| - | |
| - | if(data->state.async.done) { |
| - | *dns = data->state.async.dns; |
| - | CURL_TRC_DNS(data, "threaded: is_resolved(), already done, dns=%sfound", |
| - | *dns ? "" : "not "); |
| - | return CURLE_OK; |
| - | } |
| + | DEBUGASSERT(pdns); |
| + | *pdns = NULL; |
| #ifdef USE_HTTPSRR_ARES | |
| /* best effort, ignore errors */ | |
| @@ -598,16 +576,14 @@ CURLcode Curl_async_is_resolved(struct Curl_easy *data, | |
| if(done) { | |
| CURLcode result = CURLE_OK; | |
| - | data->state.async.done = TRUE; |
| - | Curl_resolv_unlink(data, &data->state.async.dns); |
| Curl_expire_done(data, EXPIRE_ASYNC_NAME); | |
| if(thrdd->addr->res) { | |
| - | data->state.async.dns = |
| + | struct Curl_dns_entry *dns = |
| Curl_dnscache_mk_entry(data, &thrdd->addr->res, | |
| - | data->state.async.hostname, 0, |
| - | data->state.async.port, FALSE); |
| - | if(!data->state.async.dns) |
| + | async->hostname, async->port, |
| + | async->ip_version); |
| + | if(!dns) |
| result = CURLE_OUT_OF_MEMORY; | |
| #ifdef USE_HTTPSRR_ARES | |
| @@ -619,22 +595,21 @@ CURLcode Curl_async_is_resolved(struct Curl_easy *data, | |
| if(!lhrr) | |
| result = CURLE_OUT_OF_MEMORY; | |
| else | |
| - | data->state.async.dns->hinfo = lhrr; |
| + | dns->hinfo = lhrr; |
| } | |
| } | |
| #endif | |
| - | if(!result && data->state.async.dns) |
| - | result = Curl_dnscache_add(data, data->state.async.dns); |
| + | if(!result && dns) { |
| + | result = Curl_dnscache_add(data, dns); |
| + | *pdns = dns; |
| + | } |
| } | |
| - | if(!result && !data->state.async.dns) |
| + | if(!result && !*pdns) |
| result = Curl_resolver_error(data, NULL); | |
| - | if(result) |
| - | Curl_resolv_unlink(data, &data->state.async.dns); |
| - | *dns = data->state.async.dns; |
| CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound", | |
| - | result, *dns ? "" : "not "); |
| - | async_thrdd_shutdown(data); |
| + | result, *pdns ? "" : "not "); |
| + | async_thrdd_shutdown(data, async); |
| return result; | |
| } | |
| else { | |
| @@ -657,13 +632,13 @@ CURLcode Curl_async_is_resolved(struct Curl_easy *data, | |
| thrdd->addr->interval_end = elapsed + thrdd->addr->poll_interval; | |
| Curl_expire(data, thrdd->addr->poll_interval, EXPIRE_ASYNC_NAME); | |
| - | return CURLE_OK; |
| + | return CURLE_AGAIN; |
| } | |
| } | |
| CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps) | |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; |
| + | struct async_thrdd_ctx *thrdd = &data->state.async->thrdd; |
| CURLcode result = CURLE_OK; | |
| curl_bit thrd_done; | |
| @@ -711,13 +686,13 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps) | |
| /* | |
| * Curl_async_getaddrinfo() - for platforms without getaddrinfo | |
| */ | |
| - | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, |
| - | int port, int ip_version) |
| + | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| (void)ip_version; | |
| /* fire up a new resolver thread! */ | |
| - | if(async_thrdd_init(data, hostname, port, ip_version, NULL)) { |
| + | if(async_thrdd_init(data, async, NULL)) { |
| return CURLE_OK; | |
| } | |
| @@ -730,23 +705,22 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, | |
| /* | |
| * Curl_async_getaddrinfo() - for getaddrinfo | |
| */ | |
| - | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, |
| - | int port, int ip_version) |
| + | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| struct addrinfo hints; | |
| int pf = PF_INET; | |
| - | CURL_TRC_DNS(data, "init threaded resolve of %s:%d", hostname, port); |
| + | CURL_TRC_DNS(data, "init threaded resolve of %s:%d", |
| + | async->hostname, async->port); |
| #ifdef CURLRES_IPV6 | |
| - | if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| + | if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| /* The stack seems to be IPv6-enabled */ | |
| - | if(ip_version == CURL_IPRESOLVE_V6) |
| + | if(async->ip_version == CURL_IPRESOLVE_V6) |
| pf = PF_INET6; | |
| else | |
| pf = PF_UNSPEC; | |
| } | |
| - | #else |
| - | (void)ip_version; |
| #endif /* CURLRES_IPV6 */ | |
| memset(&hints, 0, sizeof(hints)); | |
| @@ -756,7 +730,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, | |
| SOCK_STREAM : SOCK_DGRAM; | |
| /* fire up a new resolver thread! */ | |
| - | if(async_thrdd_init(data, hostname, port, ip_version, &hints)) |
| + | if(async_thrdd_init(data, async, &hints)) |
| return CURLE_OK; | |
| failf(data, "getaddrinfo() thread failed to start"); | |
| @@ -31,6 +31,7 @@ | |
| struct Curl_easy; | |
| struct Curl_dns_entry; | |
| + | struct Curl_resolv_async; |
| #ifdef CURLRES_ASYNCH | |
| @@ -70,7 +71,9 @@ void Curl_async_global_cleanup(void); | |
| * Get the resolver implementation instance (c-ares channel) or NULL | |
| * for passing to application callback. | |
| */ | |
| - | CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl); |
| + | CURLcode Curl_async_get_impl(struct Curl_easy *easy, |
| + | struct Curl_resolv_async *async, |
| + | void **impl); |
| /* Curl_async_pollset() | |
| * | |
| @@ -83,16 +86,13 @@ CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl); | |
| CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps); | |
| /* | |
| - | * Curl_async_is_resolved() |
| - | * |
| - | * Called repeatedly to check if a previous name resolve request has |
| - | * completed. It should also make sure to time-out if the operation seems to |
| - | * take too long. |
| - | * |
| - | * Returns normal CURLcode errors. |
| + | * 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_is_resolved(struct Curl_easy *data, |
| - | struct Curl_dns_entry **dns); |
| + | CURLcode Curl_async_take_result(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async, |
| + | struct Curl_dns_entry **pdns); |
| /* | |
| * Curl_async_await() | |
| @@ -106,7 +106,8 @@ CURLcode Curl_async_is_resolved(struct Curl_easy *data, | |
| * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. | |
| */ | |
| CURLcode Curl_async_await(struct Curl_easy *data, | |
| - | struct Curl_dns_entry **dns); |
| + | struct Curl_resolv_async *async, |
| + | struct Curl_dns_entry **pdns); |
| /* | |
| * Curl_async_getaddrinfo() - when using this resolver | |
| @@ -119,8 +120,8 @@ CURLcode Curl_async_await(struct Curl_easy *data, | |
| * Each resolver backend must of course make sure to return data in the | |
| * correct format to comply with this. | |
| */ | |
| - | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname, |
| - | int port, int ip_version); |
| + | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async); |
| #ifdef USE_ARES | |
| /* common functions for c-ares and threaded resolver with HTTPSRR */ | |
| @@ -150,8 +151,10 @@ struct async_ares_ctx { | |
| #endif | |
| }; | |
| - | void Curl_async_ares_shutdown(struct Curl_easy *data); |
| - | void Curl_async_ares_destroy(struct Curl_easy *data); |
| + | void Curl_async_ares_shutdown(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async); |
| + | 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); | |
| @@ -211,8 +214,10 @@ struct async_thrdd_ctx { | |
| #endif | |
| }; | |
| - | void Curl_async_thrdd_shutdown(struct Curl_easy *data); |
| - | void Curl_async_thrdd_destroy(struct Curl_easy *data); |
| + | void Curl_async_thrdd_shutdown(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async); |
| + | void Curl_async_thrdd_destroy(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async); |
| #endif /* CURLRES_THREADED */ | |
| @@ -223,11 +228,11 @@ struct doh_probes; | |
| #else /* CURLRES_ASYNCH */ | |
| /* convert these functions if an asynch resolver is not used */ | |
| - | #define Curl_async_get_impl(x, y) (*(y) = NULL, CURLE_OK) |
| - | #define Curl_async_is_resolved(x, y) CURLE_COULDNT_RESOLVE_HOST |
| - | #define Curl_async_await(x, y) CURLE_COULDNT_RESOLVE_HOST |
| - | #define Curl_async_global_init() CURLE_OK |
| - | #define Curl_async_global_cleanup() Curl_nop_stmt |
| + | #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 |
| #endif /* !CURLRES_ASYNCH */ | |
| @@ -236,7 +241,7 @@ struct doh_probes; | |
| #endif | |
| #ifdef USE_CURL_ASYNC | |
| - | struct Curl_async { |
| + | struct Curl_resolv_async { |
| #ifdef CURLRES_ARES | |
| struct async_ares_ctx ares; | |
| #elif defined(CURLRES_THREADED) | |
| @@ -245,11 +250,11 @@ struct Curl_async { | |
| #ifndef CURL_DISABLE_DOH | |
| struct doh_probes *doh; /* DoH specific data for this request */ | |
| #endif | |
| - | struct Curl_dns_entry *dns; /* result of resolving on success */ |
| - | char *hostname; /* copy of the params resolv started with */ |
| - | int port; |
| - | int ip_version; |
| - | BIT(done); |
| + | uint32_t id; /* unique id per easy handle of the resolve operation */ |
| + | /* what is being resolved */ |
| + | uint16_t port; |
| + | uint8_t ip_version; |
| + | char hostname[1]; |
| }; | |
| /* | |
| @@ -641,8 +641,8 @@ 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. | |
| */ | |
| - | int ip_version = (af == AF_INET) ? |
| - | CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER; |
| + | uint8_t ip_version = (af == AF_INET) ? |
| + | CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER; |
| #ifdef USE_IPV6 | |
| if(af == AF_INET6) | |
| ip_version = CURL_IPRESOLVE_V6; | |
| @@ -655,7 +655,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, | |
| Curl_printable_address(h->addr, myhost, sizeof(myhost)); | |
| infof(data, "Name '%s' family %i resolved to '%s' family %i", | |
| host, af, myhost, h_af); | |
| - | Curl_resolv_unlink(data, &h); /* this will NULL, potential free h */ |
| + | Curl_dns_entry_unlink(data, &h); /* this will NULL, potential free h */ |
| if(af != h_af) { | |
| /* bad IP version combo, signal the caller to try another address | |
| family if available */ | |
| @@ -564,7 +564,7 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, | |
| DEBUGASSERT(conn->scheme); | |
| DEBUGASSERT(dns); | |
| - | Curl_resolv_unlink(data, &data->state.dns[sockindex]); |
| + | Curl_dns_entry_unlink(data, &data->state.dns[sockindex]); |
| data->state.dns[sockindex] = dns; | |
| #ifndef CURL_DISABLE_HTTP | |
| @@ -588,7 +588,7 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, | |
| DEBUGASSERT(conn->cfilter[sockindex]); | |
| out: | |
| if(result) | |
| - | Curl_resolv_unlink(data, &data->state.dns[sockindex]); |
| + | Curl_dns_entry_unlink(data, &data->state.dns[sockindex]); |
| return result; | |
| } | |
| @@ -404,7 +404,7 @@ static CURLcode ip2addr(struct Curl_addrinfo **addrp, int af, | |
| * Given an IPv4 or IPv6 dotted string address, this converts it to a proper | |
| * allocated Curl_addrinfo struct and returns it. | |
| */ | |
| - | CURLcode Curl_str2addr(const char *dotted, int port, |
| + | CURLcode Curl_str2addr(const char *dotted, uint16_t port, |
| struct Curl_addrinfo **addrp) | |
| { | |
| struct in_addr in; | |
| @@ -72,7 +72,7 @@ struct Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port); | |
| #endif | |
| bool Curl_is_ipaddr(const char *address); | |
| - | CURLcode Curl_str2addr(const char *dotted, int port, |
| + | CURLcode Curl_str2addr(const char *dotted, uint16_t port, |
| struct Curl_addrinfo **addrp); | |
| #ifdef USE_UNIX_SOCKETS | |
| @@ -0,0 +1,832 @@ | |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, et al. |
| + | * |
| + | * This software is licensed as described in the file COPYING, which |
| + | * you should have received as part of this distribution. The terms |
| + | * are also available at https://curl.se/docs/copyright.html. |
| + | * |
| + | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| + | * copies of the Software, and permit persons to whom the Software is |
| + | * furnished to do so, under the terms of the COPYING file. |
| + | * |
| + | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| + | * KIND, either express or implied. |
| + | * |
| + | * SPDX-License-Identifier: curl |
| + | * |
| + | ***************************************************************************/ |
| + | #include "curl_setup.h" |
| + | |
| + | #ifdef HAVE_NETINET_IN_H |
| + | #include <netinet/in.h> |
| + | #endif |
| + | #ifdef HAVE_NETINET_IN6_H |
| + | #include <netinet/in6.h> |
| + | #endif |
| + | #ifdef HAVE_NETDB_H |
| + | #include <netdb.h> |
| + | #endif |
| + | #ifdef HAVE_ARPA_INET_H |
| + | #include <arpa/inet.h> |
| + | #endif |
| + | #ifdef __VMS |
| + | #include <in.h> |
| + | #include <inet.h> |
| + | #endif |
| + | |
| + | #include "urldata.h" |
| + | #include "curl_addrinfo.h" |
| + | #include "curl_share.h" |
| + | #include "curl_trc.h" |
| + | #include "dnscache.h" |
| + | #include "hash.h" |
| + | #include "httpsrr.h" |
| + | #include "progress.h" |
| + | #include "rand.h" |
| + | #include "strcase.h" |
| + | #include "curlx/inet_ntop.h" |
| + | #include "curlx/inet_pton.h" |
| + | #include "curlx/strcopy.h" |
| + | #include "curlx/strparse.h" |
| + | |
| + | #define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */ |
| + | |
| + | #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); |
| + | #ifdef USE_HTTPSRR |
| + | if(dns->hinfo) { |
| + | Curl_httpsrr_cleanup(dns->hinfo); |
| + | curlx_free(dns->hinfo); |
| + | } |
| + | #endif |
| + | curlx_free(dns); |
| + | } |
| + | |
| + | /* |
| + | * Create a hostcache id string for the provided host + port, to be used by |
| + | * the DNS caching. Without alloc. Return length of the id string. |
| + | */ |
| + | static size_t create_dnscache_id(const char *name, |
| + | size_t nlen, /* 0 or actual name length */ |
| + | uint16_t port, char *ptr, size_t buflen) |
| + | { |
| + | size_t len = nlen ? nlen : strlen(name); |
| + | DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); |
| + | if(len > (buflen - 7)) |
| + | len = buflen - 7; |
| + | /* store and lower case the name */ |
| + | Curl_strntolower(ptr, name, len); |
| + | return curl_msnprintf(&ptr[len], 7, ":%u", port) + len; |
| + | } |
| + | |
| + | struct dnscache_prune_data { |
| + | struct curltime now; |
| + | timediff_t oldest_ms; /* oldest time in cache not pruned. */ |
| + | timediff_t max_age_ms; |
| + | }; |
| + | |
| + | /* |
| + | * This function is set as a callback to be called for every entry in the DNS |
| + | * cache when we want to prune old unused entries. |
| + | * |
| + | * Returning non-zero means remove the entry, return 0 to keep it in the |
| + | * cache. |
| + | */ |
| + | static int dnscache_entry_is_stale(void *datap, void *hc) |
| + | { |
| + | struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap; |
| + | struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc; |
| + | |
| + | if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) { |
| + | /* get age in milliseconds */ |
| + | timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp); |
| + | if(!dns->addr) |
| + | age *= 2; /* negative entries age twice as fast */ |
| + | if(age >= prune->max_age_ms) |
| + | return TRUE; |
| + | if(age > prune->oldest_ms) |
| + | prune->oldest_ms = age; |
| + | } |
| + | return FALSE; |
| + | } |
| + | |
| + | /* |
| + | * Prune the DNS cache. This assumes that a lock has already been taken. |
| + | * Returns the 'age' of the oldest still kept entry - in milliseconds. |
| + | */ |
| + | static timediff_t dnscache_prune(struct Curl_hash *hostcache, |
| + | timediff_t cache_timeout_ms, |
| + | struct curltime now) |
| + | { |
| + | struct dnscache_prune_data user; |
| + | |
| + | user.max_age_ms = cache_timeout_ms; |
| + | user.now = now; |
| + | user.oldest_ms = 0; |
| + | |
| + | Curl_hash_clean_with_criterium(hostcache, |
| + | (void *)&user, |
| + | dnscache_entry_is_stale); |
| + | |
| + | return user.oldest_ms; |
| + | } |
| + | |
| + | static struct Curl_dnscache *dnscache_get(struct Curl_easy *data) |
| + | { |
| + | if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) |
| + | return &data->share->dnscache; |
| + | if(data->multi) |
| + | return &data->multi->dnscache; |
| + | return NULL; |
| + | } |
| + | |
| + | static void dnscache_lock(struct Curl_easy *data, |
| + | struct Curl_dnscache *dnscache) |
| + | { |
| + | if(data->share && dnscache == &data->share->dnscache) |
| + | Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); |
| + | } |
| + | |
| + | static void dnscache_unlock(struct Curl_easy *data, |
| + | struct Curl_dnscache *dnscache) |
| + | { |
| + | if(data->share && dnscache == &data->share->dnscache) |
| + | Curl_share_unlock(data, CURL_LOCK_DATA_DNS); |
| + | } |
| + | |
| + | /* |
| + | * Library-wide function for pruning the DNS cache. This function takes and |
| + | * returns the appropriate locks. |
| + | */ |
| + | void Curl_dnscache_prune(struct Curl_easy *data) |
| + | { |
| + | struct Curl_dnscache *dnscache = dnscache_get(data); |
| + | /* the timeout may be set -1 (forever) */ |
| + | timediff_t timeout_ms = data->set.dns_cache_timeout_ms; |
| + | |
| + | if(!dnscache || (timeout_ms == -1)) |
| + | /* NULL hostcache means we cannot do it */ |
| + | return; |
| + | |
| + | dnscache_lock(data, dnscache); |
| + | |
| + | do { |
| + | /* Remove outdated and unused entries from the hostcache */ |
| + | timediff_t oldest_ms = |
| + | dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data)); |
| + | |
| + | if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE) |
| + | /* prune the ones over half this age */ |
| + | timeout_ms = oldest_ms / 2; |
| + | else |
| + | break; |
| + | |
| + | /* if the cache size is still too big, use the oldest age as new prune |
| + | limit */ |
| + | } while(timeout_ms); |
| + | |
| + | dnscache_unlock(data, dnscache); |
| + | } |
| + | |
| + | void Curl_dnscache_clear(struct Curl_easy *data) |
| + | { |
| + | struct Curl_dnscache *dnscache = dnscache_get(data); |
| + | if(dnscache) { |
| + | dnscache_lock(data, dnscache); |
| + | Curl_hash_clean(&dnscache->entries); |
| + | dnscache_unlock(data, dnscache); |
| + | } |
| + | } |
| + | |
| + | /* lookup address, returns entry if found and not stale */ |
| + | static CURLcode fetch_addr(struct Curl_easy *data, |
| + | struct Curl_dnscache *dnscache, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | struct Curl_dns_entry **pdns) |
| + | { |
| + | struct Curl_dns_entry *dns = NULL; |
| + | char entry_id[MAX_HOSTCACHE_LEN]; |
| + | size_t entry_len; |
| + | CURLcode result = CURLE_OK; |
| + | |
| + | *pdns = NULL; |
| + | if(!dnscache) |
| + | return CURLE_OK; |
| + | |
| + | /* Create an entry id, based upon the hostname and port */ |
| + | entry_len = create_dnscache_id(hostname, 0, port, |
| + | entry_id, sizeof(entry_id)); |
| + | |
| + | /* See if it is already in our dns cache */ |
| + | dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); |
| + | |
| + | /* No entry found in cache, check if we might have a wildcard entry */ |
| + | if(!dns && data->state.wildcard_resolve) { |
| + | entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id)); |
| + | |
| + | /* See if it is already in our dns cache */ |
| + | dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); |
| + | } |
| + | |
| + | if(dns && (data->set.dns_cache_timeout_ms != -1)) { |
| + | /* See whether the returned entry is stale. Done before we release lock */ |
| + | struct dnscache_prune_data user; |
| + | |
| + | user.now = *Curl_pgrs_now(data); |
| + | user.max_age_ms = data->set.dns_cache_timeout_ms; |
| + | user.oldest_ms = 0; |
| + | |
| + | if(dnscache_entry_is_stale(&user, dns)) { |
| + | infof(data, "Hostname in DNS cache was stale, zapped"); |
| + | dns = NULL; /* the memory deallocation is being handled by the hash */ |
| + | Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); |
| + | } |
| + | } |
| + | |
| + | 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. */ |
| + | dns = NULL; |
| + | break; |
| + | } |
| + | } |
| + | |
| + | if(dns && !dns->addr) { /* negative entry */ |
| + | dns = NULL; |
| + | result = CURLE_COULDNT_RESOLVE_HOST; |
| + | } |
| + | *pdns = dns; |
| + | return result; |
| + | } |
| + | |
| + | /* |
| + | * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache. |
| + | * |
| + | * Curl_resolv() checks initially and multi_runsingle() checks each time |
| + | * it discovers the handle in the state WAITRESOLVE whether the hostname |
| + | * has already been resolved and the address has already been stored in |
| + | * the DNS cache. This short circuits waiting for a lot of pending |
| + | * lookups for the same hostname requested by different handles. |
| + | * |
| + | * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. |
| + | * |
| + | * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after |
| + | * use, or we will leak memory! |
| + | */ |
| + | CURLcode Curl_dnscache_get(struct Curl_easy *data, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | struct Curl_dns_entry **pentry) |
| + | { |
| + | struct Curl_dnscache *dnscache = dnscache_get(data); |
| + | struct Curl_dns_entry *dns = NULL; |
| + | CURLcode result = CURLE_OK; |
| + | |
| + | dnscache_lock(data, dnscache); |
| + | result = fetch_addr(data, dnscache, hostname, port, ip_version, &dns); |
| + | if(!result && dns) |
| + | dns->refcount++; /* we pass out a reference */ |
| + | else if(result) { |
| + | DEBUGASSERT(!dns); |
| + | dns = NULL; |
| + | } |
| + | dnscache_unlock(data, dnscache); |
| + | |
| + | *pentry = dns; |
| + | return result; |
| + | } |
| + | |
| + | #ifndef CURL_DISABLE_SHUFFLE_DNS |
| + | /* |
| + | * Return # of addresses in a Curl_addrinfo struct |
| + | */ |
| + | static int num_addresses(const struct Curl_addrinfo *addr) |
| + | { |
| + | int i = 0; |
| + | while(addr) { |
| + | addr = addr->ai_next; |
| + | i++; |
| + | } |
| + | return i; |
| + | } |
| + | |
| + | UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, |
| + | struct Curl_addrinfo **addr); |
| + | /* |
| + | * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo' |
| + | * struct by re-linking its linked list. |
| + | * |
| + | * The addr argument should be the address of a pointer to the head node of a |
| + | * `Curl_addrinfo` list and it will be modified to point to the new head after |
| + | * shuffling. |
| + | * |
| + | * Not declared static only to make it easy to use in a unit test! |
| + | * |
| + | * @unittest: 1608 |
| + | */ |
| + | UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, |
| + | struct Curl_addrinfo **addr) |
| + | { |
| + | CURLcode result = CURLE_OK; |
| + | const int num_addrs = num_addresses(*addr); |
| + | |
| + | if(num_addrs > 1) { |
| + | struct Curl_addrinfo **nodes; |
| + | CURL_TRC_DNS(data, "Shuffling %i addresses", num_addrs); |
| + | |
| + | nodes = curlx_malloc(num_addrs * sizeof(*nodes)); |
| + | if(nodes) { |
| + | int i; |
| + | unsigned int *rnd; |
| + | const size_t rnd_size = num_addrs * sizeof(*rnd); |
| + | |
| + | /* build a plain array of Curl_addrinfo pointers */ |
| + | nodes[0] = *addr; |
| + | for(i = 1; i < num_addrs; i++) { |
| + | nodes[i] = nodes[i - 1]->ai_next; |
| + | } |
| + | |
| + | rnd = curlx_malloc(rnd_size); |
| + | if(rnd) { |
| + | /* Fisher-Yates shuffle */ |
| + | if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) { |
| + | struct Curl_addrinfo *swap_tmp; |
| + | for(i = num_addrs - 1; i > 0; i--) { |
| + | swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)]; |
| + | nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i]; |
| + | nodes[i] = swap_tmp; |
| + | } |
| + | |
| + | /* relink list in the new order */ |
| + | for(i = 1; i < num_addrs; i++) { |
| + | nodes[i - 1]->ai_next = nodes[i]; |
| + | } |
| + | |
| + | nodes[num_addrs - 1]->ai_next = NULL; |
| + | *addr = nodes[0]; |
| + | } |
| + | curlx_free(rnd); |
| + | } |
| + | else |
| + | result = CURLE_OUT_OF_MEMORY; |
| + | curlx_free(nodes); |
| + | } |
| + | else |
| + | result = CURLE_OUT_OF_MEMORY; |
| + | } |
| + | return result; |
| + | } |
| + | #endif |
| + | |
| + | static struct Curl_dns_entry * |
| + | dnscache_entry_create(struct Curl_easy *data, |
| + | struct Curl_addrinfo **paddr, |
| + | const char *hostname, |
| + | size_t hostlen, /* length or zero */ |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | bool permanent) |
| + | { |
| + | struct Curl_dns_entry *dns = NULL; |
| + | |
| + | #ifndef CURL_DISABLE_SHUFFLE_DNS |
| + | /* shuffle addresses if requested */ |
| + | if(data->set.dns_shuffle_addresses && paddr) { |
| + | CURLcode result = Curl_shuffle_addr(data, paddr); |
| + | if(result) |
| + | goto out; |
| + | } |
| + | #else |
| + | (void)data; |
| + | #endif |
| + | if(!hostlen) |
| + | hostlen = strlen(hostname); |
| + | |
| + | /* Create a new cache entry */ |
| + | dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen); |
| + | if(!dns) |
| + | goto out; |
| + | |
| + | dns->refcount = 1; /* the cache has the first reference */ |
| + | dns->addr = paddr ? *paddr : NULL; /* this is the address(es) */ |
| + | if(permanent) { |
| + | dns->timestamp.tv_sec = 0; /* an entry that never goes stale */ |
| + | dns->timestamp.tv_usec = 0; /* an entry that never goes stale */ |
| + | } |
| + | else { |
| + | dns->timestamp = *Curl_pgrs_now(data); |
| + | } |
| + | dns->port = port; |
| + | dns->ip_version = ip_version; |
| + | if(hostlen) |
| + | memcpy(dns->hostname, hostname, hostlen); |
| + | |
| + | out: |
| + | if(paddr) { |
| + | if(!dns) |
| + | Curl_freeaddrinfo(*paddr); |
| + | *paddr = NULL; |
| + | } |
| + | return dns; |
| + | } |
| + | |
| + | struct Curl_dns_entry * |
| + | Curl_dnscache_mk_entry(struct Curl_easy *data, |
| + | struct Curl_addrinfo **paddr, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version) |
| + | { |
| + | return dnscache_entry_create(data, paddr, hostname, 0, |
| + | port, ip_version, FALSE); |
| + | } |
| + | |
| + | static struct Curl_dns_entry * |
| + | dnscache_add_addr(struct Curl_easy *data, |
| + | struct Curl_dnscache *dnscache, |
| + | struct Curl_addrinfo **paddr, |
| + | const char *hostname, |
| + | size_t hlen, /* length or zero */ |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | bool permanent) |
| + | { |
| + | char entry_id[MAX_HOSTCACHE_LEN]; |
| + | size_t entry_len; |
| + | struct Curl_dns_entry *dns; |
| + | struct Curl_dns_entry *dns2; |
| + | |
| + | dns = dnscache_entry_create(data, paddr, hostname, hlen, port, |
| + | ip_version, permanent); |
| + | if(!dns) |
| + | return NULL; |
| + | |
| + | /* Create an entry id, based upon the hostname and port */ |
| + | entry_len = create_dnscache_id(hostname, hlen, port, |
| + | entry_id, sizeof(entry_id)); |
| + | |
| + | /* Store the resolved data in our DNS cache. */ |
| + | dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1, |
| + | (void *)dns); |
| + | if(!dns2) { |
| + | dnscache_entry_free(dns); |
| + | return NULL; |
| + | } |
| + | |
| + | dns = dns2; |
| + | dns->refcount++; /* mark entry as in-use */ |
| + | return dns; |
| + | } |
| + | |
| + | CURLcode Curl_dnscache_add(struct Curl_easy *data, |
| + | struct Curl_dns_entry *entry) |
| + | { |
| + | struct Curl_dnscache *dnscache = dnscache_get(data); |
| + | char id[MAX_HOSTCACHE_LEN]; |
| + | size_t idlen; |
| + | |
| + | if(!dnscache) |
| + | return CURLE_FAILED_INIT; |
| + | /* Create an entry id, based upon the hostname and port */ |
| + | idlen = create_dnscache_id(entry->hostname, 0, entry->port, id, sizeof(id)); |
| + | |
| + | /* Store the resolved data in our DNS cache and up ref count */ |
| + | dnscache_lock(data, dnscache); |
| + | if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) { |
| + | dnscache_unlock(data, dnscache); |
| + | return CURLE_OUT_OF_MEMORY; |
| + | } |
| + | entry->refcount++; |
| + | dnscache_unlock(data, dnscache); |
| + | return CURLE_OK; |
| + | } |
| + | |
| + | CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, |
| + | const char *host, |
| + | uint16_t port, |
| + | uint8_t ip_version) |
| + | { |
| + | struct Curl_dnscache *dnscache = dnscache_get(data); |
| + | struct Curl_dns_entry *dns; |
| + | DEBUGASSERT(dnscache); |
| + | if(!dnscache) |
| + | return CURLE_FAILED_INIT; |
| + | |
| + | /* put this new host in the cache */ |
| + | dns = dnscache_add_addr(data, dnscache, NULL, host, 0, |
| + | port, ip_version, 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)); |
| + | return CURLE_OK; |
| + | } |
| + | return CURLE_OUT_OF_MEMORY; |
| + | } |
| + | |
| + | /* |
| + | * Curl_dns_entry_unlink() releases a reference to the given cached DNS entry. |
| + | * When the reference count reaches 0, the entry is destroyed. It is important |
| + | * that only one unlink is made for each Curl_resolv() call. |
| + | * |
| + | * May be called with 'data' == NULL for global cache. |
| + | */ |
| + | void Curl_dns_entry_unlink(struct Curl_easy *data, |
| + | struct Curl_dns_entry **pdns) |
| + | { |
| + | if(*pdns) { |
| + | struct Curl_dnscache *dnscache = dnscache_get(data); |
| + | struct Curl_dns_entry *dns = *pdns; |
| + | *pdns = NULL; |
| + | dnscache_lock(data, dnscache); |
| + | dns->refcount--; |
| + | if(dns->refcount == 0) |
| + | dnscache_entry_free(dns); |
| + | dnscache_unlock(data, dnscache); |
| + | } |
| + | } |
| + | |
| + | static void dnscache_entry_dtor(void *entry) |
| + | { |
| + | struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry; |
| + | DEBUGASSERT(dns && (dns->refcount > 0)); |
| + | dns->refcount--; |
| + | if(dns->refcount == 0) |
| + | dnscache_entry_free(dns); |
| + | } |
| + | |
| + | /* |
| + | * Curl_dnscache_init() inits a new DNS cache. |
| + | */ |
| + | void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size) |
| + | { |
| + | Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare, |
| + | dnscache_entry_dtor); |
| + | } |
| + | |
| + | void Curl_dnscache_destroy(struct Curl_dnscache *dns) |
| + | { |
| + | Curl_hash_destroy(&dns->entries); |
| + | } |
| + | |
| + | CURLcode Curl_loadhostpairs(struct Curl_easy *data) |
| + | { |
| + | struct Curl_dnscache *dnscache = dnscache_get(data); |
| + | struct curl_slist *hostp; |
| + | |
| + | if(!dnscache) |
| + | return CURLE_FAILED_INIT; |
| + | |
| + | /* Default is no wildcard found */ |
| + | data->state.wildcard_resolve = FALSE; |
| + | |
| + | for(hostp = data->state.resolve; hostp; hostp = hostp->next) { |
| + | char entry_id[MAX_HOSTCACHE_LEN]; |
| + | const char *host = hostp->data; |
| + | struct Curl_str source; |
| + | if(!host) |
| + | continue; |
| + | if(*host == '-') { |
| + | curl_off_t num = 0; |
| + | size_t entry_len; |
| + | host++; |
| + | if(!curlx_str_single(&host, '[')) { |
| + | if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || |
| + | curlx_str_single(&host, ']') || |
| + | curlx_str_single(&host, ':')) |
| + | continue; |
| + | } |
| + | else { |
| + | if(curlx_str_until(&host, &source, 4096, ':') || |
| + | curlx_str_single(&host, ':')) { |
| + | continue; |
| + | } |
| + | } |
| + | |
| + | if(!curlx_str_number(&host, &num, 0xffff)) { |
| + | /* Create an entry id, based upon the hostname and port */ |
| + | entry_len = create_dnscache_id(curlx_str(&source), |
| + | curlx_strlen(&source), (uint16_t)num, |
| + | entry_id, sizeof(entry_id)); |
| + | dnscache_lock(data, dnscache); |
| + | /* delete entry, ignore if it did not exist */ |
| + | Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); |
| + | dnscache_unlock(data, dnscache); |
| + | } |
| + | } |
| + | else { |
| + | struct Curl_dns_entry *dns; |
| + | struct Curl_addrinfo *head = NULL, *tail = NULL; |
| + | size_t entry_len; |
| + | char address[64]; |
| + | curl_off_t tmpofft = 0; |
| + | uint16_t port = 0; |
| + | bool permanent = TRUE; |
| + | bool error = TRUE; |
| + | VERBOSE(const char *addresses = NULL); |
| + | |
| + | if(*host == '+') { |
| + | host++; |
| + | permanent = FALSE; |
| + | } |
| + | if(!curlx_str_single(&host, '[')) { |
| + | if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || |
| + | curlx_str_single(&host, ']')) |
| + | continue; |
| + | } |
| + | else { |
| + | if(curlx_str_until(&host, &source, 4096, ':')) |
| + | continue; |
| + | } |
| + | if(curlx_str_single(&host, ':') || |
| + | curlx_str_number(&host, &tmpofft, 0xffff) || |
| + | curlx_str_single(&host, ':')) |
| + | goto err; |
| + | port = (uint16_t)tmpofft; |
| + | |
| + | VERBOSE(addresses = host); |
| + | |
| + | /* start the address section */ |
| + | while(*host) { |
| + | struct Curl_str target; |
| + | struct Curl_addrinfo *ai; |
| + | CURLcode result; |
| + | |
| + | if(!curlx_str_single(&host, '[')) { |
| + | if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') || |
| + | curlx_str_single(&host, ']')) |
| + | goto err; |
| + | } |
| + | else { |
| + | if(curlx_str_until(&host, &target, 4096, ',')) { |
| + | if(curlx_str_single(&host, ',')) |
| + | goto err; |
| + | /* survive nothing but a comma */ |
| + | continue; |
| + | } |
| + | } |
| + | #ifndef USE_IPV6 |
| + | if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) { |
| + | infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.", |
| + | (int)curlx_strlen(&target), curlx_str(&target)); |
| + | if(curlx_str_single(&host, ',')) |
| + | goto err; |
| + | continue; |
| + | } |
| + | #endif |
| + | |
| + | if(curlx_strlen(&target) >= sizeof(address)) |
| + | goto err; |
| + | |
| + | memcpy(address, curlx_str(&target), curlx_strlen(&target)); |
| + | address[curlx_strlen(&target)] = '\0'; |
| + | |
| + | result = Curl_str2addr(address, port, &ai); |
| + | if(result) { |
| + | infof(data, "Resolve address '%s' found illegal", address); |
| + | goto err; |
| + | } |
| + | |
| + | if(tail) { |
| + | tail->ai_next = ai; |
| + | tail = tail->ai_next; |
| + | } |
| + | else { |
| + | head = tail = ai; |
| + | } |
| + | if(curlx_str_single(&host, ',')) |
| + | break; |
| + | } |
| + | |
| + | if(!head) |
| + | goto err; |
| + | |
| + | error = FALSE; |
| + | err: |
| + | if(error) { |
| + | failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data); |
| + | Curl_freeaddrinfo(head); |
| + | return CURLE_SETOPT_OPTION_SYNTAX; |
| + | } |
| + | |
| + | /* Create an entry id, based upon the hostname and port */ |
| + | entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source), |
| + | port, entry_id, sizeof(entry_id)); |
| + | |
| + | dnscache_lock(data, dnscache); |
| + | |
| + | /* See if it is already in our dns cache */ |
| + | dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); |
| + | |
| + | if(dns) { |
| + | infof(data, "RESOLVE %.*s:%u - old addresses discarded", |
| + | (int)curlx_strlen(&source), |
| + | curlx_str(&source), port); |
| + | /* delete old entry, there are two reasons for this |
| + | 1. old entry may have different addresses. |
| + | 2. even if entry with correct addresses is already in the cache, |
| + | but if it is close to expire, then by the time next http |
| + | request is made, it can get expired and pruned because old |
| + | entry is not necessarily marked as permanent. |
| + | 3. when adding a non-permanent entry, we want it to remove and |
| + | replace an existing permanent entry. |
| + | 4. when adding a non-permanent entry, we want it to get a "fresh" |
| + | timeout that starts _now_. */ |
| + | |
| + | 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); |
| + | if(dns) |
| + | /* release the returned reference; the cache itself will keep the |
| + | * entry alive: */ |
| + | dns->refcount--; |
| + | |
| + | dnscache_unlock(data, dnscache); |
| + | |
| + | if(!dns) |
| + | return CURLE_OUT_OF_MEMORY; |
| + | |
| + | infof(data, "Added %.*s:%u:%s to DNS cache%s", |
| + | (int)curlx_strlen(&source), curlx_str(&source), port, addresses, |
| + | permanent ? "" : " (non-permanent)"); |
| + | |
| + | /* Wildcard hostname */ |
| + | if(curlx_str_casecompare(&source, "*")) { |
| + | infof(data, "RESOLVE *:%u using wildcard", port); |
| + | data->state.wildcard_resolve = TRUE; |
| + | } |
| + | } |
| + | } |
| + | data->state.resolve = NULL; /* dealt with now */ |
| + | |
| + | return CURLE_OK; |
| + | } |
| @@ -0,0 +1,126 @@ | |
| + | #ifndef HEADER_CURL_DNSCACHE_H |
| + | #define HEADER_CURL_DNSCACHE_H |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, et al. |
| + | * |
| + | * This software is licensed as described in the file COPYING, which |
| + | * you should have received as part of this distribution. The terms |
| + | * are also available at https://curl.se/docs/copyright.html. |
| + | * |
| + | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| + | * copies of the Software, and permit persons to whom the Software is |
| + | * furnished to do so, under the terms of the COPYING file. |
| + | * |
| + | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| + | * KIND, either express or implied. |
| + | * |
| + | * SPDX-License-Identifier: curl |
| + | * |
| + | ***************************************************************************/ |
| + | #include "curl_setup.h" |
| + | |
| + | #include "hash.h" |
| + | |
| + | struct addrinfo; |
| + | struct hostent; |
| + | struct Curl_easy; |
| + | struct connectdata; |
| + | struct easy_pollset; |
| + | struct Curl_https_rrinfo; |
| + | struct Curl_multi; |
| + | |
| + | struct Curl_dns_entry { |
| + | struct Curl_addrinfo *addr; |
| + | #ifdef USE_HTTPSRR |
| + | struct Curl_https_rrinfo *hinfo; |
| + | #endif |
| + | /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */ |
| + | struct curltime timestamp; |
| + | /* reference counter, entry is freed on reaching 0 */ |
| + | uint32_t refcount; |
| + | /* hostname port number that resolved to addr. */ |
| + | uint16_t port; |
| + | uint8_t ip_version; |
| + | /* hostname that resolved to addr. may be NULL (Unix domain sockets). */ |
| + | char hostname[1]; |
| + | }; |
| + | |
| + | /* |
| + | * Create a `Curl_dns_entry` with a reference count of 1. |
| + | * Use `Curl_dns_entry_unlink()` to release your hold on it. |
| + | * |
| + | * The call takes ownership of `addr`, even in case of failure, and always |
| + | * clears `*paddr`. It makes a copy of `hostname`. |
| + | * |
| + | * Returns entry or NULL on OOM. |
| + | */ |
| + | struct Curl_dns_entry * |
| + | Curl_dnscache_mk_entry(struct Curl_easy *data, |
| + | struct Curl_addrinfo **paddr, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version); |
| + | |
| + | /* unlink a dns entry, frees all resources if it was the last reference. |
| + | * Always clears `*pdns`` */ |
| + | void Curl_dns_entry_unlink(struct Curl_easy *data, |
| + | struct Curl_dns_entry **pdns); |
| + | |
| + | |
| + | struct Curl_dnscache { |
| + | struct Curl_hash entries; |
| + | }; |
| + | |
| + | /* init a new dns cache */ |
| + | void Curl_dnscache_init(struct Curl_dnscache *dns, size_t hashsize); |
| + | |
| + | void Curl_dnscache_destroy(struct Curl_dnscache *dns); |
| + | |
| + | /* prune old entries from the DNS cache */ |
| + | void Curl_dnscache_prune(struct Curl_easy *data); |
| + | |
| + | /* clear the DNS cache */ |
| + | void Curl_dnscache_clear(struct Curl_easy *data); |
| + | |
| + | /* |
| + | * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache. |
| + | * |
| + | * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. |
| + | * |
| + | * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after |
| + | * use, or we will leak memory! |
| + | * Returns CURLE_OK or CURLE_COULDNT_RESOLVE_HOST when a negative |
| + | * entry was in the cache. |
| + | */ |
| + | CURLcode Curl_dnscache_get(struct Curl_easy *data, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | struct Curl_dns_entry **pentry); |
| + | |
| + | /* |
| + | * Curl_dnscache_addr() adds `entry` to the cache, increasing its |
| + | * reference count on success. |
| + | */ |
| + | CURLcode Curl_dnscache_add(struct Curl_easy *data, |
| + | struct Curl_dns_entry *entry); |
| + | |
| + | /* 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, |
| + | const char *host, |
| + | uint16_t port, |
| + | uint8_t ip_version); |
| + | |
| + | /* |
| + | * Populate the cache with specified entries from CURLOPT_RESOLVE. |
| + | */ |
| + | CURLcode Curl_loadhostpairs(struct Curl_easy *data); |
| + | |
| + | #endif /* HEADER_CURL_DNSCACHE_H */ |
| @@ -216,45 +216,57 @@ 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 doh_probes *dohp = data->state.async.doh; |
| - | DEBUGASSERT(dohp); |
| - | if(dohp) { |
| - | struct doh_request *doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); |
| - | int i; |
| + | struct Curl_resolv_async *async = data->state.async; |
| + | struct doh_probes *dohp = async ? async->doh : NULL; |
| + | struct doh_request *doh_req = NULL; |
| + | int i; |
| - | for(i = 0; i < DOH_SLOT_COUNT; ++i) { |
| - | if(dohp->probe_resp[i].probe_mid == doh->mid) |
| - | break; |
| - | } |
| - | if(i >= DOH_SLOT_COUNT) { |
| - | failf(data, "unknown sub request done"); |
| - | return; |
| - | } |
| + | if(!dohp) { |
| + | DEBUGASSERT(0); |
| + | return; |
| + | } |
| - | dohp->pending--; |
| - | infof(doh, "a DoH request is completed, %u to go", dohp->pending); |
| - | dohp->probe_resp[i].result = result; |
| - | /* We expect either the meta data still to exist or the sub request |
| - | * to have already failed. */ |
| - | DEBUGASSERT(doh_req || result); |
| - | if(doh_req) { |
| - | if(!result) { |
| - | dohp->probe_resp[i].dnstype = doh_req->dnstype; |
| - | result = curlx_dyn_addn(&dohp->probe_resp[i].body, |
| - | curlx_dyn_ptr(&doh_req->resp_body), |
| - | curlx_dyn_len(&doh_req->resp_body)); |
| - | curlx_dyn_free(&doh_req->resp_body); |
| - | } |
| - | Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); |
| - | } |
| + | 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"); |
| + | return; |
| + | } |
| - | if(result) |
| - | infof(doh, "DoH request %s", curl_easy_strerror(result)); |
| + | for(i = 0; i < DOH_SLOT_COUNT; ++i) { |
| + | if(dohp->probe_resp[i].probe_mid == doh->mid) |
| + | break; |
| + | } |
| + | /* We really should have found the slot where to store the response */ |
| + | if(i >= DOH_SLOT_COUNT) { |
| + | DEBUGASSERT(0); |
| + | failf(data, "DoH: unknown sub request done"); |
| + | return; |
| + | } |
| - | if(!dohp->pending) { |
| - | /* DoH completed, run the transfer picking up the results */ |
| - | Curl_multi_mark_dirty(data); |
| + | dohp->pending--; |
| + | infof(doh, "a DoH request is completed, %u to go", dohp->pending); |
| + | dohp->probe_resp[i].result = result; |
| + | /* We expect either the meta data still to exist or the sub request |
| + | * to have already failed. */ |
| + | DEBUGASSERT(doh_req || result); |
| + | if(doh_req) { |
| + | if(!result) { |
| + | dohp->probe_resp[i].dnstype = doh_req->dnstype; |
| + | result = curlx_dyn_addn(&dohp->probe_resp[i].body, |
| + | curlx_dyn_ptr(&doh_req->resp_body), |
| + | curlx_dyn_len(&doh_req->resp_body)); |
| + | curlx_dyn_free(&doh_req->resp_body); |
| } | |
| + | Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); |
| + | } |
| + | |
| + | if(result) |
| + | infof(doh, "DoH request %s", curl_easy_strerror(result)); |
| + | |
| + | if(!dohp->pending) { |
| + | /* DoH completed, run the transfer picking up the results */ |
| + | Curl_multi_mark_dirty(data); |
| } | |
| } | |
| @@ -283,6 +295,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 *pmid) | |
| { | |
| struct Curl_easy *doh = NULL; | |
| @@ -296,6 +309,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->dnstype = dnstype; | |
| curlx_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE); | |
| @@ -436,8 +450,8 @@ error: | |
| * a 'Curl_addrinfo *' with the address information. | |
| */ | |
| - | CURLcode Curl_doh(struct Curl_easy *data, const char *hostname, |
| - | int port, int ip_version) |
| + | CURLcode Curl_doh(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| CURLcode result = CURLE_OK; | |
| struct doh_probes *dohp = NULL; | |
| @@ -445,21 +459,13 @@ CURLcode Curl_doh(struct Curl_easy *data, const char *hostname, | |
| size_t i; | |
| DEBUGASSERT(conn); | |
| - | DEBUGASSERT(!data->state.async.doh); |
| - | DEBUGASSERT(hostname && hostname[0]); |
| - | if(data->state.async.doh) |
| - | Curl_doh_cleanup(data); |
| - | |
| - | data->state.async.done = FALSE; |
| - | data->state.async.port = port; |
| - | data->state.async.ip_version = ip_version; |
| - | curlx_free(data->state.async.hostname); |
| - | data->state.async.hostname = curlx_strdup(hostname); |
| - | if(!data->state.async.hostname) |
| - | return CURLE_OUT_OF_MEMORY; |
| + | DEBUGASSERT(!async->doh); |
| + | DEBUGASSERT(async->hostname[0]); |
| + | if(async->doh) |
| + | Curl_doh_cleanup(data, async); |
| /* start clean, consider allocating this struct on demand */ | |
| - | data->state.async.doh = dohp = curlx_calloc(1, sizeof(struct doh_probes)); |
| + | async->doh = dohp = curlx_calloc(1, sizeof(struct doh_probes)); |
| if(!dohp) | |
| return CURLE_OUT_OF_MEMORY; | |
| @@ -469,27 +475,27 @@ CURLcode Curl_doh(struct Curl_easy *data, const char *hostname, | |
| } | |
| conn->bits.doh = TRUE; | |
| - | dohp->host = data->state.async.hostname; |
| - | dohp->port = data->state.async.port; |
| + | dohp->host = async->hostname; |
| + | dohp->port = async->port; |
| /* We are making sub easy handles and want to be called back when | |
| * one is done. */ | |
| data->sub_xfer_done = doh_probe_done; | |
| /* create IPv4 DoH request */ | |
| result = doh_probe_run(data, CURL_DNS_TYPE_A, | |
| - | hostname, data->set.str[STRING_DOH], |
| - | data->multi, |
| + | 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((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| + | if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| /* create IPv6 DoH request */ | |
| result = doh_probe_run(data, CURL_DNS_TYPE_AAAA, | |
| - | hostname, data->set.str[STRING_DOH], |
| - | data->multi, |
| + | async->hostname, data->set.str[STRING_DOH], |
| + | data->multi, async->id, |
| &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid); | |
| if(result) | |
| goto error; | |
| @@ -501,14 +507,15 @@ CURLcode Curl_doh(struct Curl_easy *data, const char *hostname, | |
| if(conn->scheme->protocol & PROTO_FAMILY_HTTP) { | |
| /* Only use HTTPS RR for HTTP(S) transfers */ | |
| char *qname = NULL; | |
| - | if(port != PORT_HTTPS) { |
| - | qname = curl_maprintf("_%d._https.%s", port, hostname); |
| + | if(async->port != PORT_HTTPS) { |
| + | qname = curl_maprintf("_%d._https.%s", async->port, async->hostname); |
| if(!qname) | |
| goto error; | |
| } | |
| result = doh_probe_run(data, CURL_DNS_TYPE_HTTPS, | |
| - | qname ? qname : hostname, data->set.str[STRING_DOH], |
| - | data->multi, |
| + | qname ? qname : async->hostname, |
| + | data->set.str[STRING_DOH], data->multi, |
| + | async->id, |
| &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid); | |
| curlx_free(qname); | |
| if(result) | |
| @@ -519,7 +526,7 @@ CURLcode Curl_doh(struct Curl_easy *data, const char *hostname, | |
| return CURLE_OK; | |
| error: | |
| - | Curl_doh_cleanup(data); |
| + | Curl_doh_cleanup(data, async); |
| return result; | |
| } | |
| @@ -1206,13 +1213,15 @@ UNITTEST void doh_print_httpsrr(struct Curl_easy *data, | |
| # endif | |
| #endif | |
| - | CURLcode Curl_doh_is_resolved(struct Curl_easy *data, |
| - | struct Curl_dns_entry **dnsp) |
| + | CURLcode Curl_doh_take_result(struct Curl_easy *data, |
| + | struct Curl_dns_entry **pdns) |
| { | |
| + | struct Curl_resolv_async *async = data->state.async; |
| + | struct doh_probes *dohp = async ? async->doh : NULL; |
| CURLcode result = CURLE_OK; | |
| - | struct doh_probes *dohp = data->state.async.doh; |
| struct dohentry de; | |
| - | *dnsp = NULL; /* defaults to no response */ |
| + | |
| + | *pdns = NULL; /* defaults to no response */ |
| if(!dohp) | |
| return CURLE_OUT_OF_MEMORY; | |
| @@ -1226,9 +1235,6 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, | |
| DOHcode rc[DOH_SLOT_COUNT]; | |
| int slot; | |
| - | /* Clear any result the might still be there */ |
| - | Curl_resolv_unlink(data, &data->state.async.dns); |
| - | |
| memset(rc, 0, sizeof(rc)); | |
| /* remove DoH handles from multi handle and close them */ | |
| Curl_doh_close(data); | |
| @@ -1247,7 +1253,6 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, | |
| } | |
| } /* next slot */ | |
| - | result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */ |
| if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) { | |
| /* we have an address, of one kind or other */ | |
| struct Curl_dns_entry *dns; | |
| @@ -1263,50 +1268,55 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, | |
| goto error; | |
| /* we got a response, create a dns entry. */ | |
| - | dns = Curl_dnscache_mk_entry(data, &ai, dohp->host, 0, |
| - | dohp->port, FALSE); |
| - | if(dns) { |
| - | /* Now add and HTTPSRR information if we have */ |
| + | dns = Curl_dnscache_mk_entry(data, &ai, dohp->host, dohp->port, |
| + | async->ip_version); |
| + | if(!dns) { |
| + | result = CURLE_OUT_OF_MEMORY; |
| + | goto error; |
| + | } |
| + | |
| + | /* Now add and HTTPSRR information if we have */ |
| #ifdef USE_HTTPSRR | |
| - | if(de.numhttps_rrs > 0 && result == CURLE_OK) { |
| - | struct Curl_https_rrinfo *hrr = NULL; |
| - | result = doh_resp_decode_httpsrr(data, de.https_rrs->val, |
| - | de.https_rrs->len, &hrr); |
| - | if(result) { |
| - | infof(data, "Failed to decode HTTPS RR"); |
| - | Curl_resolv_unlink(data, &dns); |
| - | goto error; |
| - | } |
| - | infof(data, "Some HTTPS RR to process"); |
| + | if(de.numhttps_rrs > 0 && result == CURLE_OK) { |
| + | struct Curl_https_rrinfo *hrr = NULL; |
| + | result = doh_resp_decode_httpsrr(data, de.https_rrs->val, |
| + | de.https_rrs->len, &hrr); |
| + | if(result) { |
| + | infof(data, "Failed to decode HTTPS RR"); |
| + | Curl_dns_entry_unlink(data, &dns); |
| + | goto error; |
| + | } |
| + | infof(data, "Some HTTPS RR to process"); |
| #if defined(DEBUGBUILD) && defined(CURLVERBOSE) | |
| - | doh_print_httpsrr(data, hrr); |
| + | doh_print_httpsrr(data, hrr); |
| #endif | |
| - | dns->hinfo = hrr; |
| - | } |
| - | #endif /* USE_HTTPSRR */ |
| - | /* and add the entry to the cache */ |
| - | data->state.async.dns = dns; |
| - | result = Curl_dnscache_add(data, dns); |
| - | *dnsp = data->state.async.dns; |
| + | dns->hinfo = hrr; |
| } | |
| + | #endif /* USE_HTTPSRR */ |
| + | /* and add the entry to the cache */ |
| + | result = Curl_dnscache_add(data, dns); |
| + | *pdns = dns; |
| } /* address processing done */ | |
| + | else { |
| + | result = CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY : |
| + | CURLE_COULDNT_RESOLVE_HOST; |
| + | } |
| - | /* All done */ |
| - | data->state.async.done = TRUE; |
| } /* !dohp->pending */ | |
| else | |
| /* wait for pending DoH transactions to complete */ | |
| - | return CURLE_OK; |
| + | return CURLE_AGAIN; |
| error: | |
| de_cleanup(&de); | |
| - | Curl_doh_cleanup(data); |
| + | Curl_doh_cleanup(data, async); |
| return result; | |
| } | |
| void Curl_doh_close(struct Curl_easy *data) | |
| { | |
| - | struct doh_probes *doh = data->state.async.doh; |
| + | 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; | |
| uint32_t mid; | |
| @@ -1332,16 +1342,17 @@ void Curl_doh_close(struct Curl_easy *data) | |
| } | |
| } | |
| - | void Curl_doh_cleanup(struct Curl_easy *data) |
| + | void Curl_doh_cleanup(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct doh_probes *dohp = data->state.async.doh; |
| + | struct doh_probes *dohp = async->doh; |
| if(dohp) { | |
| int i; | |
| Curl_doh_close(data); | |
| for(i = 0; i < DOH_SLOT_COUNT; ++i) { | |
| curlx_dyn_free(&dohp->probe_resp[i].body); | |
| } | |
| - | Curl_safefree(data->state.async.doh); |
| + | Curl_safefree(async->doh); |
| } | |
| } | |
| @@ -28,6 +28,8 @@ | |
| /* enums outside of the #ifdef to make the types work in unitprotos.h even on | |
| builds without DoH support */ | |
| + | struct Curl_resolv_async; |
| + | |
| typedef enum { | |
| DOH_OK, | |
| DOH_DNS_BAD_LABEL, /* 1 */ | |
| @@ -91,6 +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 */ |
| DNStype dnstype; | |
| }; | |
| @@ -106,7 +109,7 @@ struct doh_response { | |
| struct doh_probes { | |
| struct doh_response probe_resp[DOH_SLOT_COUNT]; | |
| unsigned int pending; /* still outstanding probes */ | |
| - | int port; |
| + | uint16_t port; |
| const char *host; | |
| }; | |
| @@ -115,11 +118,11 @@ struct doh_probes { | |
| * name and returns a 'Curl_addrinfo *' with the address information. | |
| */ | |
| - | CURLcode Curl_doh(struct Curl_easy *data, const char *hostname, |
| - | int port, int ip_version); |
| + | CURLcode Curl_doh(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async); |
| - | CURLcode Curl_doh_is_resolved(struct Curl_easy *data, |
| - | struct Curl_dns_entry **dnsp); |
| + | CURLcode Curl_doh_take_result(struct Curl_easy *data, |
| + | struct Curl_dns_entry **dns); |
| #define DOH_MAX_ADDR 24 | |
| #define DOH_MAX_CNAME 4 | |
| @@ -161,11 +164,15 @@ struct dohentry { | |
| }; | |
| void Curl_doh_close(struct Curl_easy *data); | |
| - | void Curl_doh_cleanup(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) |
| + | |
| #else /* CURL_DISABLE_DOH */ | |
| - | #define Curl_doh(a, b, c, d, e) NULL |
| - | #define Curl_doh_is_resolved(x, y) CURLE_COULDNT_RESOLVE_HOST |
| + | #define Curl_doh(a, b) NULL |
| + | #define Curl_doh_take_result(x, y) CURLE_COULDNT_RESOLVE_HOST |
| + | #define Curl_doh_wanted(d) FALSE |
| #endif /* !CURL_DISABLE_DOH */ | |
| #endif /* HEADER_CURL_DOH_H */ | |
| @@ -1093,8 +1093,8 @@ void curl_easy_reset(CURL *d) | |
| Curl_meta_reset(data); | |
| /* clear any resolve data */ | |
| Curl_async_shutdown(data); | |
| - | Curl_resolv_unlink(data, &data->state.dns[0]); |
| - | Curl_resolv_unlink(data, &data->state.dns[1]); |
| + | Curl_dns_entry_unlink(data, &data->state.dns[0]); |
| + | Curl_dns_entry_unlink(data, &data->state.dns[1]); |
| /* zero out UserDefined data: */ | |
| Curl_freeset(data); | |
| memset(&data->set, 0, sizeof(struct UserDefined)); | |
| @@ -1387,7 +1387,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, | |
| /* cleanup */ | |
| if(dns_entry) | |
| - | Curl_resolv_unlink(data, &dns_entry); |
| + | Curl_dns_entry_unlink(data, &dns_entry); |
| if(result) { | |
| ftp_state(data, ftpc, FTP_STOP); | |
| } | |
| @@ -46,21 +46,18 @@ | |
| #include "urldata.h" | |
| #include "curl_addrinfo.h" | |
| #include "curl_trc.h" | |
| - | #include "connect.h" |
| + | #include "dnscache.h" |
| #include "hostip.h" | |
| - | #include "hash.h" |
| #include "httpsrr.h" | |
| - | #include "rand.h" |
| - | #include "curl_share.h" |
| #include "url.h" | |
| - | #include "curlx/inet_ntop.h" |
| - | #include "curlx/inet_pton.h" |
| #include "multiif.h" | |
| - | #include "doh.h" |
| #include "progress.h" | |
| + | #include "doh.h" |
| #include "select.h" | |
| #include "strcase.h" | |
| #include "easy_lock.h" | |
| + | #include "curlx/inet_ntop.h" |
| + | #include "curlx/inet_pton.h" |
| #include "curlx/strcopy.h" | |
| #include "curlx/strparse.h" | |
| @@ -136,7 +133,7 @@ static void show_resolve_info(struct Curl_easy *data, | |
| a = dns->addr; | |
| infof(data, "Host %s:%d was resolved.", | |
| - | (dns->hostname[0] ? dns->hostname : "(none)"), dns->hostport); |
| + | (dns->hostname[0] ? dns->hostname : "(none)"), dns->port); |
| curlx_dyn_init(&out[0], 1024); | |
| #ifdef CURLRES_IPV6 | |
| @@ -181,18 +178,6 @@ fail: | |
| #define show_resolve_info(x, y) Curl_nop_stmt | |
| #endif | |
| - | static void dnscache_entry_free(struct Curl_dns_entry *dns) |
| - | { |
| - | Curl_freeaddrinfo(dns->addr); |
| - | #ifdef USE_HTTPSRR |
| - | if(dns->hinfo) { |
| - | Curl_httpsrr_cleanup(dns->hinfo); |
| - | curlx_free(dns->hinfo); |
| - | } |
| - | #endif |
| - | curlx_free(dns); |
| - | } |
| - | |
| /* | |
| * Curl_printable_address() stores a printable version of the 1st address | |
| * given in the 'ai' argument. The result will be stored in the buf that is | |
| @@ -226,142 +211,6 @@ void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf, | |
| } | |
| } | |
| - | /* |
| - | * Create a hostcache id string for the provided host + port, to be used by |
| - | * the DNS caching. Without alloc. Return length of the id string. |
| - | */ |
| - | static size_t create_dnscache_id(const char *name, |
| - | size_t nlen, /* 0 or actual name length */ |
| - | int port, char *ptr, size_t buflen) |
| - | { |
| - | size_t len = nlen ? nlen : strlen(name); |
| - | DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); |
| - | if(len > (buflen - 7)) |
| - | len = buflen - 7; |
| - | /* store and lower case the name */ |
| - | Curl_strntolower(ptr, name, len); |
| - | return curl_msnprintf(&ptr[len], 7, ":%u", port) + len; |
| - | } |
| - | |
| - | struct dnscache_prune_data { |
| - | struct curltime now; |
| - | timediff_t oldest_ms; /* oldest time in cache not pruned. */ |
| - | timediff_t max_age_ms; |
| - | }; |
| - | |
| - | /* |
| - | * This function is set as a callback to be called for every entry in the DNS |
| - | * cache when we want to prune old unused entries. |
| - | * |
| - | * Returning non-zero means remove the entry, return 0 to keep it in the |
| - | * cache. |
| - | */ |
| - | static int dnscache_entry_is_stale(void *datap, void *hc) |
| - | { |
| - | struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap; |
| - | struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc; |
| - | |
| - | if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) { |
| - | /* get age in milliseconds */ |
| - | timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp); |
| - | if(!dns->addr) |
| - | age *= 2; /* negative entries age twice as fast */ |
| - | if(age >= prune->max_age_ms) |
| - | return TRUE; |
| - | if(age > prune->oldest_ms) |
| - | prune->oldest_ms = age; |
| - | } |
| - | return FALSE; |
| - | } |
| - | |
| - | /* |
| - | * Prune the DNS cache. This assumes that a lock has already been taken. |
| - | * Returns the 'age' of the oldest still kept entry - in milliseconds. |
| - | */ |
| - | static timediff_t dnscache_prune(struct Curl_hash *hostcache, |
| - | timediff_t cache_timeout_ms, |
| - | struct curltime now) |
| - | { |
| - | struct dnscache_prune_data user; |
| - | |
| - | user.max_age_ms = cache_timeout_ms; |
| - | user.now = now; |
| - | user.oldest_ms = 0; |
| - | |
| - | Curl_hash_clean_with_criterium(hostcache, |
| - | (void *)&user, |
| - | dnscache_entry_is_stale); |
| - | |
| - | return user.oldest_ms; |
| - | } |
| - | |
| - | static struct Curl_dnscache *dnscache_get(struct Curl_easy *data) |
| - | { |
| - | if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) |
| - | return &data->share->dnscache; |
| - | if(data->multi) |
| - | return &data->multi->dnscache; |
| - | return NULL; |
| - | } |
| - | |
| - | static void dnscache_lock(struct Curl_easy *data, |
| - | struct Curl_dnscache *dnscache) |
| - | { |
| - | if(data->share && dnscache == &data->share->dnscache) |
| - | Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); |
| - | } |
| - | |
| - | static void dnscache_unlock(struct Curl_easy *data, |
| - | struct Curl_dnscache *dnscache) |
| - | { |
| - | if(data->share && dnscache == &data->share->dnscache) |
| - | Curl_share_unlock(data, CURL_LOCK_DATA_DNS); |
| - | } |
| - | |
| - | /* |
| - | * Library-wide function for pruning the DNS cache. This function takes and |
| - | * returns the appropriate locks. |
| - | */ |
| - | void Curl_dnscache_prune(struct Curl_easy *data) |
| - | { |
| - | struct Curl_dnscache *dnscache = dnscache_get(data); |
| - | /* the timeout may be set -1 (forever) */ |
| - | timediff_t timeout_ms = data->set.dns_cache_timeout_ms; |
| - | |
| - | if(!dnscache || (timeout_ms == -1)) |
| - | /* NULL hostcache means we cannot do it */ |
| - | return; |
| - | |
| - | dnscache_lock(data, dnscache); |
| - | |
| - | do { |
| - | /* Remove outdated and unused entries from the hostcache */ |
| - | timediff_t oldest_ms = |
| - | dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data)); |
| - | |
| - | if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE) |
| - | /* prune the ones over half this age */ |
| - | timeout_ms = oldest_ms / 2; |
| - | else |
| - | break; |
| - | |
| - | /* if the cache size is still too big, use the oldest age as new prune |
| - | limit */ |
| - | } while(timeout_ms); |
| - | |
| - | dnscache_unlock(data, dnscache); |
| - | } |
| - | |
| - | void Curl_dnscache_clear(struct Curl_easy *data) |
| - | { |
| - | struct Curl_dnscache *dnscache = dnscache_get(data); |
| - | if(dnscache) { |
| - | dnscache_lock(data, dnscache); |
| - | Curl_hash_clean(&dnscache->entries); |
| - | dnscache_unlock(data, dnscache); |
| - | } |
| - | } |
| - | |
| #ifdef USE_ALARM_TIMEOUT | |
| /* Beware this is a global and unique instance. This is used to store the | |
| return address that we can jump back to from inside a signal handler. This | |
| @@ -370,305 +219,9 @@ static sigjmp_buf curl_jmpenv; | |
| static curl_simple_lock curl_jmpenv_lock; | |
| #endif | |
| - | /* lookup address, returns entry if found and not stale */ |
| - | static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, |
| - | struct Curl_dnscache *dnscache, |
| - | const char *hostname, |
| - | int port, |
| - | int ip_version) |
| - | { |
| - | struct Curl_dns_entry *dns = NULL; |
| - | char entry_id[MAX_HOSTCACHE_LEN]; |
| - | size_t entry_len; |
| - | |
| - | if(!dnscache) |
| - | return NULL; |
| - | |
| - | /* Create an entry id, based upon the hostname and port */ |
| - | entry_len = create_dnscache_id(hostname, 0, port, |
| - | entry_id, sizeof(entry_id)); |
| - | |
| - | /* See if it is already in our dns cache */ |
| - | dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); |
| - | |
| - | /* No entry found in cache, check if we might have a wildcard entry */ |
| - | if(!dns && data->state.wildcard_resolve) { |
| - | entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id)); |
| - | |
| - | /* See if it is already in our dns cache */ |
| - | dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); |
| - | } |
| - | |
| - | if(dns && (data->set.dns_cache_timeout_ms != -1)) { |
| - | /* See whether the returned entry is stale. Done before we release lock */ |
| - | struct dnscache_prune_data user; |
| - | |
| - | user.now = *Curl_pgrs_now(data); |
| - | user.max_age_ms = data->set.dns_cache_timeout_ms; |
| - | user.oldest_ms = 0; |
| - | |
| - | if(dnscache_entry_is_stale(&user, dns)) { |
| - | infof(data, "Hostname in DNS cache was stale, zapped"); |
| - | dns = NULL; /* the memory deallocation is being handled by the hash */ |
| - | Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); |
| - | } |
| - | } |
| - | |
| - | /* See if the returned entry matches the required resolve mode */ |
| - | if(dns && ip_version != CURL_IPRESOLVE_WHATEVER) { |
| - | 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) { |
| - | infof(data, "Hostname in DNS cache does not have needed family, zapped"); |
| - | dns = NULL; /* the memory deallocation is being handled by the hash */ |
| - | Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); |
| - | } |
| - | } |
| - | return dns; |
| - | } |
| - | |
| - | /* |
| - | * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache. |
| - | * |
| - | * Curl_resolv() checks initially and multi_runsingle() checks each time |
| - | * it discovers the handle in the state WAITRESOLVE whether the hostname |
| - | * has already been resolved and the address has already been stored in |
| - | * the DNS cache. This short circuits waiting for a lot of pending |
| - | * lookups for the same hostname requested by different handles. |
| - | * |
| - | * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. |
| - | * |
| - | * The returned data *MUST* be "released" with Curl_resolv_unlink() after |
| - | * use, or we will leak memory! |
| - | */ |
| - | struct Curl_dns_entry *Curl_dnscache_get(struct Curl_easy *data, |
| - | const char *hostname, |
| - | int port, |
| - | int ip_version) |
| - | { |
| - | struct Curl_dnscache *dnscache = dnscache_get(data); |
| - | struct Curl_dns_entry *dns = NULL; |
| - | |
| - | dnscache_lock(data, dnscache); |
| - | |
| - | dns = fetch_addr(data, dnscache, hostname, port, ip_version); |
| - | if(dns) |
| - | dns->refcount++; /* we use it! */ |
| - | |
| - | dnscache_unlock(data, dnscache); |
| - | |
| - | return dns; |
| - | } |
| - | |
| - | #ifndef CURL_DISABLE_SHUFFLE_DNS |
| - | /* |
| - | * Return # of addresses in a Curl_addrinfo struct |
| - | */ |
| - | static int num_addresses(const struct Curl_addrinfo *addr) |
| - | { |
| - | int i = 0; |
| - | while(addr) { |
| - | addr = addr->ai_next; |
| - | i++; |
| - | } |
| - | return i; |
| - | } |
| - | |
| - | UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, |
| - | struct Curl_addrinfo **addr); |
| - | /* |
| - | * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo' |
| - | * struct by re-linking its linked list. |
| - | * |
| - | * The addr argument should be the address of a pointer to the head node of a |
| - | * `Curl_addrinfo` list and it will be modified to point to the new head after |
| - | * shuffling. |
| - | * |
| - | * Not declared static only to make it easy to use in a unit test! |
| - | * |
| - | * @unittest: 1608 |
| - | */ |
| - | UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, |
| - | struct Curl_addrinfo **addr) |
| - | { |
| - | CURLcode result = CURLE_OK; |
| - | const int num_addrs = num_addresses(*addr); |
| - | |
| - | if(num_addrs > 1) { |
| - | struct Curl_addrinfo **nodes; |
| - | infof(data, "Shuffling %i addresses", num_addrs); |
| - | |
| - | nodes = curlx_malloc(num_addrs * sizeof(*nodes)); |
| - | if(nodes) { |
| - | int i; |
| - | unsigned int *rnd; |
| - | const size_t rnd_size = num_addrs * sizeof(*rnd); |
| - | |
| - | /* build a plain array of Curl_addrinfo pointers */ |
| - | nodes[0] = *addr; |
| - | for(i = 1; i < num_addrs; i++) { |
| - | nodes[i] = nodes[i - 1]->ai_next; |
| - | } |
| - | |
| - | rnd = curlx_malloc(rnd_size); |
| - | if(rnd) { |
| - | /* Fisher-Yates shuffle */ |
| - | if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) { |
| - | struct Curl_addrinfo *swap_tmp; |
| - | for(i = num_addrs - 1; i > 0; i--) { |
| - | swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)]; |
| - | nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i]; |
| - | nodes[i] = swap_tmp; |
| - | } |
| - | |
| - | /* relink list in the new order */ |
| - | for(i = 1; i < num_addrs; i++) { |
| - | nodes[i - 1]->ai_next = nodes[i]; |
| - | } |
| - | |
| - | nodes[num_addrs - 1]->ai_next = NULL; |
| - | *addr = nodes[0]; |
| - | } |
| - | curlx_free(rnd); |
| - | } |
| - | else |
| - | result = CURLE_OUT_OF_MEMORY; |
| - | curlx_free(nodes); |
| - | } |
| - | else |
| - | result = CURLE_OUT_OF_MEMORY; |
| - | } |
| - | return result; |
| - | } |
| - | #endif |
| - | |
| - | struct Curl_dns_entry * |
| - | Curl_dnscache_mk_entry(struct Curl_easy *data, |
| - | struct Curl_addrinfo **paddr, |
| - | const char *hostname, |
| - | size_t hostlen, /* length or zero */ |
| - | int port, |
| - | bool permanent) |
| - | { |
| - | struct Curl_dns_entry *dns = NULL; |
| - | |
| - | #ifndef CURL_DISABLE_SHUFFLE_DNS |
| - | /* shuffle addresses if requested */ |
| - | if(data->set.dns_shuffle_addresses && paddr) { |
| - | CURLcode result = Curl_shuffle_addr(data, paddr); |
| - | if(result) |
| - | goto out; |
| - | } |
| - | #else |
| - | (void)data; |
| - | #endif |
| - | if(!hostlen) |
| - | hostlen = strlen(hostname); |
| - | |
| - | /* Create a new cache entry */ |
| - | dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen); |
| - | if(!dns) |
| - | goto out; |
| - | |
| - | dns->refcount = 1; /* the cache has the first reference */ |
| - | dns->addr = paddr ? *paddr : NULL; /* this is the address(es) */ |
| - | if(permanent) { |
| - | dns->timestamp.tv_sec = 0; /* an entry that never goes stale */ |
| - | dns->timestamp.tv_usec = 0; /* an entry that never goes stale */ |
| - | } |
| - | else { |
| - | dns->timestamp = *Curl_pgrs_now(data); |
| - | } |
| - | dns->hostport = port; |
| - | if(hostlen) |
| - | memcpy(dns->hostname, hostname, hostlen); |
| - | |
| - | out: |
| - | if(paddr) { |
| - | if(!dns) |
| - | Curl_freeaddrinfo(*paddr); |
| - | *paddr = NULL; |
| - | } |
| - | return dns; |
| - | } |
| - | |
| - | static struct Curl_dns_entry * |
| - | dnscache_add_addr(struct Curl_easy *data, |
| - | struct Curl_dnscache *dnscache, |
| - | struct Curl_addrinfo **paddr, |
| - | const char *hostname, |
| - | size_t hlen, /* length or zero */ |
| - | int port, |
| - | bool permanent) |
| - | { |
| - | char entry_id[MAX_HOSTCACHE_LEN]; |
| - | size_t entry_len; |
| - | struct Curl_dns_entry *dns; |
| - | struct Curl_dns_entry *dns2; |
| - | |
| - | dns = Curl_dnscache_mk_entry(data, paddr, hostname, hlen, port, permanent); |
| - | if(!dns) |
| - | return NULL; |
| - | |
| - | /* Create an entry id, based upon the hostname and port */ |
| - | entry_len = create_dnscache_id(hostname, hlen, port, |
| - | entry_id, sizeof(entry_id)); |
| - | |
| - | /* Store the resolved data in our DNS cache. */ |
| - | dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1, |
| - | (void *)dns); |
| - | if(!dns2) { |
| - | dnscache_entry_free(dns); |
| - | return NULL; |
| - | } |
| - | |
| - | dns = dns2; |
| - | dns->refcount++; /* mark entry as in-use */ |
| - | return dns; |
| - | } |
| - | |
| - | CURLcode Curl_dnscache_add(struct Curl_easy *data, |
| - | struct Curl_dns_entry *entry) |
| - | { |
| - | struct Curl_dnscache *dnscache = dnscache_get(data); |
| - | char id[MAX_HOSTCACHE_LEN]; |
| - | size_t idlen; |
| - | |
| - | if(!dnscache) |
| - | return CURLE_FAILED_INIT; |
| - | /* Create an entry id, based upon the hostname and port */ |
| - | idlen = create_dnscache_id(entry->hostname, 0, entry->hostport, |
| - | id, sizeof(id)); |
| - | |
| - | /* Store the resolved data in our DNS cache and up ref count */ |
| - | dnscache_lock(data, dnscache); |
| - | if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) { |
| - | dnscache_unlock(data, dnscache); |
| - | return CURLE_OUT_OF_MEMORY; |
| - | } |
| - | entry->refcount++; |
| - | dnscache_unlock(data, dnscache); |
| - | return CURLE_OK; |
| - | } |
| - | |
| #ifdef USE_IPV6 | |
| /* return a static IPv6 ::1 for the name */ | |
| - | static struct Curl_addrinfo *get_localhost6(int port, const char *name) |
| + | static struct Curl_addrinfo *get_localhost6(uint16_t port, const char *name) |
| { | |
| struct Curl_addrinfo *ca; | |
| const size_t ss_size = sizeof(struct sockaddr_in6); | |
| @@ -704,7 +257,7 @@ static struct Curl_addrinfo *get_localhost6(int port, const char *name) | |
| #endif | |
| /* return a static IPv4 127.0.0.1 for the given name */ | |
| - | static struct Curl_addrinfo *get_localhost(int port, const char *name) |
| + | static struct Curl_addrinfo *get_localhost(uint16_t port, const char *name) |
| { | |
| struct Curl_addrinfo *ca; | |
| struct Curl_addrinfo *ca6; | |
| @@ -816,70 +369,59 @@ static bool can_resolve_ip_version(struct Curl_easy *data, int ip_version) | |
| return TRUE; | |
| } | |
| - | static CURLcode store_negative_resolve(struct Curl_easy *data, |
| - | const char *host, |
| - | int port) |
| - | { |
| - | struct Curl_dnscache *dnscache = dnscache_get(data); |
| - | struct Curl_dns_entry *dns; |
| - | DEBUGASSERT(dnscache); |
| - | if(!dnscache) |
| - | return CURLE_FAILED_INIT; |
| + | #ifdef USE_CURL_ASYNC |
| + | static CURLcode hostip_async_new(struct Curl_easy *data, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version) |
| + | { |
| + | struct Curl_resolv_async *async; |
| + | size_t hostlen = strlen(hostname); |
| + | |
| + | DEBUGASSERT(!data->state.async); |
| + | /* struct size already includes the NUL for hostname */ |
| + | async = curlx_calloc(1, sizeof(*async) + hostlen); |
| + | if(!async) |
| + | return CURLE_OUT_OF_MEMORY; |
| - | /* put this new host in the cache */ |
| - | dns = dnscache_add_addr(data, dnscache, NULL, host, 0, port, FALSE); |
| - | if(dns) { |
| - | /* release the returned reference; the cache itself will keep the |
| - | * entry alive: */ |
| - | dns->refcount--; |
| - | infof(data, "Store negative name resolve for %s:%d", host, port); |
| - | return CURLE_OK; |
| - | } |
| - | return CURLE_OUT_OF_MEMORY; |
| + | /* 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++; |
| + | async->port = port; |
| + | async->ip_version = ip_version; |
| + | if(hostlen) |
| + | memcpy(async->hostname, hostname, hostlen); |
| + | |
| + | data->state.async = async; |
| + | return CURLE_OK; |
| } | |
| + | #endif |
| - | /* |
| - | * Curl_resolv() is the main name resolve function within libcurl. It resolves |
| - | * a name and returns a pointer to the entry in the 'entry' argument. This |
| - | * function might return immediately if we are using asynch resolves. See the |
| - | * return codes. |
| - | * |
| - | * The cache entry we return will get its 'inuse' counter increased when this |
| - | * function is used. You MUST call Curl_resolv_unlink() later (when you are |
| - | * done using this struct) to decrease the reference counter again. |
| - | * |
| - | * Return codes: |
| - | * CURLE_OK = success, *entry set to non-NULL |
| - | * CURLE_AGAIN = resolving in progress, *entry == NULL |
| - | * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL |
| - | * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL |
| - | */ |
| - | CURLcode Curl_resolv(struct Curl_easy *data, |
| - | const char *hostname, |
| - | int port, |
| - | int ip_version, |
| - | bool allowDOH, |
| - | struct Curl_dns_entry **entry) |
| + | static CURLcode hostip_resolv(struct Curl_easy *data, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | bool allowDOH, |
| + | struct Curl_dns_entry **entry) |
| { | |
| - | struct Curl_dnscache *dnscache = dnscache_get(data); |
| struct Curl_dns_entry *dns = NULL; | |
| struct Curl_addrinfo *addr = NULL; | |
| bool respwait = FALSE; | |
| size_t hostname_len; | |
| - | CURLcode result = CURLE_COULDNT_RESOLVE_HOST; |
| + | CURLcode r2, result = CURLE_COULDNT_RESOLVE_HOST; |
| *entry = NULL; | |
| + | #ifdef USE_CURL_ASYNC |
| + | if(data->state.async) |
| + | Curl_async_destroy(data); |
| + | #endif |
| + | |
| #ifndef CURL_DISABLE_DOH | |
| data->conn->bits.doh = FALSE; /* default is not */ | |
| #else | |
| (void)allowDOH; | |
| #endif | |
| - | DEBUGASSERT(dnscache); |
| - | if(!dnscache) { |
| - | result = CURLE_BAD_FUNCTION_ARGUMENT; |
| - | goto error; |
| - | } |
| /* We should intentionally error and not resolve .onion TLDs */ | |
| hostname_len = strlen(hostname); | |
| @@ -891,24 +433,41 @@ CURLcode Curl_resolv(struct Curl_easy *data, | |
| goto error; | |
| } | |
| + | #ifdef DEBUGBUILD |
| + | CURL_TRC_DNS(data, "hostip_resolv(%s:%u, ip=%x)", |
| + | hostname, port, ip_version); |
| + | if((ip_version == CURL_IPRESOLVE_V6) && |
| + | getenv("CURL_DBG_RESOLV_FAIL_IPV6")) { |
| + | infof(data, "DEBUG fail ipv6 resolve"); |
| + | result = Curl_resolver_error(data, NULL); |
| + | goto error; |
| + | } |
| + | #endif |
| /* Let's check our DNS cache first */ | |
| - | dnscache_lock(data, dnscache); |
| - | dns = fetch_addr(data, dnscache, hostname, port, ip_version); |
| - | if(dns) |
| - | dns->refcount++; /* we pass out the reference. */ |
| - | dnscache_unlock(data, dnscache); |
| + | r2 = Curl_dnscache_get(data, hostname, port, ip_version, &dns); |
| if(dns) { | |
| infof(data, "Hostname %s was found in DNS cache", hostname); | |
| result = CURLE_OK; | |
| goto out; | |
| } | |
| + | else if(r2) { |
| + | DEBUGASSERT(!dns); |
| + | infof(data, "Negative DNS entry"); |
| + | result = Curl_resolver_error(data, NULL); |
| + | goto error; |
| + | } |
| /* No luck, we need to resolve hostname. Notify user callback. */ | |
| if(data->set.resolver_start) { | |
| void *resolver = NULL; | |
| int st; | |
| #ifdef CURLRES_ASYNCH | |
| - | result = Curl_async_get_impl(data, &resolver); |
| + | if(!data->state.async) { |
| + | result = hostip_async_new(data, hostname, port, ip_version); |
| + | if(result) |
| + | goto error; |
| + | } |
| + | result = Curl_async_get_impl(data, data->state.async, &resolver); |
| if(result) | |
| goto error; | |
| #endif | |
| @@ -941,7 +500,12 @@ CURLcode Curl_resolv(struct Curl_easy *data, | |
| } | |
| #ifndef CURL_DISABLE_DOH | |
| else if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) { | |
| - | result = Curl_doh(data, hostname, port, ip_version); |
| + | if(!data->state.async) { |
| + | result = hostip_async_new(data, hostname, port, ip_version); |
| + | if(result) |
| + | goto error; |
| + | } |
| + | result = Curl_doh(data, data->state.async); |
| respwait = TRUE; | |
| } | |
| #endif | |
| @@ -953,7 +517,12 @@ CURLcode Curl_resolv(struct Curl_easy *data, | |
| } | |
| #ifdef CURLRES_ASYNCH | |
| - | result = Curl_async_getaddrinfo(data, hostname, port, ip_version); |
| + | if(!data->state.async) { |
| + | result = hostip_async_new(data, hostname, port, ip_version); |
| + | if(result) |
| + | goto error; |
| + | } |
| + | result = Curl_async_getaddrinfo(data, data->state.async); |
| respwait = TRUE; | |
| #else | |
| respwait = FALSE; /* no async waiting here */ | |
| @@ -969,17 +538,12 @@ out: | |
| if(result) | |
| ; | |
| else if(dns) { | |
| - | if(!dns->addr) { |
| - | infof(data, "Negative DNS entry"); |
| - | dns->refcount--; |
| - | return CURLE_COULDNT_RESOLVE_HOST; |
| - | } |
| *entry = dns; | |
| return CURLE_OK; | |
| } | |
| else if(addr) { | |
| /* we got a response, create a dns entry, add to cache, return */ | |
| - | dns = Curl_dnscache_mk_entry(data, &addr, hostname, 0, port, FALSE); |
| + | dns = Curl_dnscache_mk_entry(data, &addr, hostname, port, ip_version); |
| if(!dns || Curl_dnscache_add(data, dns)) { | |
| /* this is OOM or similar, do not store such negative resolves */ | |
| result = CURLE_OUT_OF_MEMORY; | |
| @@ -991,7 +555,8 @@ out: | |
| } | |
| else if(respwait) { | |
| #ifdef USE_CURL_ASYNC | |
| - | if(!Curl_resolv_check(data, &dns)) { |
| + | result = Curl_resolv_take_result(data, &dns); |
| + | if(!result) { |
| *entry = dns; | |
| return dns ? CURLE_OK : CURLE_AGAIN; | |
| } | |
| @@ -1000,37 +565,36 @@ out: | |
| } | |
| error: | |
| if(dns) | |
| - | Curl_resolv_unlink(data, &dns); |
| + | Curl_dns_entry_unlink(data, &dns); |
| Curl_async_shutdown(data); | |
| if(result == CURLE_COULDNT_RESOLVE_HOST) | |
| - | store_negative_resolve(data, hostname, port); |
| + | Curl_dnscache_add_negative(data, hostname, port, ip_version); |
| DEBUGASSERT(result); | |
| return result; | |
| } | |
| CURLcode Curl_resolv_blocking(struct Curl_easy *data, | |
| const char *hostname, | |
| - | int port, |
| - | int ip_version, |
| - | struct Curl_dns_entry **entry) |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | struct Curl_dns_entry **pdns) |
| { | |
| CURLcode result; | |
| DEBUGASSERT(hostname && *hostname); | |
| - | *entry = NULL; |
| - | result = Curl_resolv(data, hostname, port, ip_version, FALSE, entry); |
| + | *pdns = NULL; |
| + | /* We cannot do a blocking resolve using DoH currently */ |
| + | result = hostip_resolv(data, hostname, port, ip_version, FALSE, pdns); |
| switch(result) { | |
| case CURLE_OK: | |
| - | DEBUGASSERT(*entry); |
| + | DEBUGASSERT(*pdns); |
| return CURLE_OK; | |
| + | #ifdef USE_CURL_ASYNC |
| case CURLE_AGAIN: | |
| - | DEBUGASSERT(!*entry); |
| - | result = Curl_async_await(data, entry); |
| - | if(result || !*entry) { |
| - | /* close the connection, since we cannot return failure here without |
| - | cleaning up this connection properly. */ |
| - | connclose(data->conn, "async resolve failed"); |
| - | } |
| - | return result; |
| + | DEBUGASSERT(!*pdns); |
| + | if(!data->state.async) |
| + | return CURLE_FAILED_INIT; |
| + | return Curl_async_await(data, data->state.async, pdns); |
| + | #endif |
| default: | |
| return result; | |
| } | |
| @@ -1049,35 +613,15 @@ CURL_NORETURN static void alarmfunc(int sig) | |
| } | |
| #endif /* USE_ALARM_TIMEOUT */ | |
| - | /* |
| - | * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a |
| - | * timeout. This function might return immediately if we are using asynch |
| - | * resolves. See the return codes. |
| - | * |
| - | * The cache entry we return will get its 'inuse' counter increased when this |
| - | * function is used. You MUST call Curl_resolv_unlink() later (when you are |
| - | * done using this struct) to decrease the reference counter again. |
| - | * |
| - | * If built with a synchronous resolver and use of signals is not |
| - | * disabled by the application, then a nonzero timeout will cause a |
| - | * timeout after the specified number of milliseconds. Otherwise, timeout |
| - | * is ignored. |
| - | * |
| - | * Return codes: |
| - | * CURLE_OK = success, *entry set to non-NULL |
| - | * CURLE_AGAIN = resolving in progress, *entry == NULL |
| - | * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL |
| - | * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL |
| - | */ |
| + | #ifdef USE_ALARM_TIMEOUT |
| - | CURLcode Curl_resolv_timeout(struct Curl_easy *data, |
| - | const char *hostname, |
| - | int port, |
| - | int ip_version, |
| - | struct Curl_dns_entry **entry, |
| - | timediff_t timeoutms) |
| + | static CURLcode resolv_alarm_timeout(struct Curl_easy *data, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | timediff_t timeoutms, |
| + | struct Curl_dns_entry **entry) |
| { | |
| - | #ifdef USE_ALARM_TIMEOUT |
| #ifdef HAVE_SIGACTION | |
| struct sigaction keep_sigact; /* store the old struct here */ | |
| volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */ | |
| @@ -1089,32 +633,17 @@ CURLcode Curl_resolv_timeout(struct Curl_easy *data, | |
| #endif /* HAVE_SIGACTION */ | |
| volatile long timeout; | |
| volatile unsigned int prev_alarm = 0; | |
| - | #endif /* USE_ALARM_TIMEOUT */ |
| CURLcode result; | |
| DEBUGASSERT(hostname && *hostname); | |
| - | *entry = NULL; |
| - | |
| - | if(timeoutms < 0) |
| - | /* got an already expired timeout */ |
| - | return CURLE_OPERATION_TIMEDOUT; |
| - | |
| - | #ifdef USE_ALARM_TIMEOUT |
| - | if(data->set.no_signal) |
| - | /* Ignore the timeout when signals are disabled */ |
| - | timeout = 0; |
| - | else |
| - | timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms; |
| - | |
| - | if(!timeout |
| + | DEBUGASSERT(timeoutms > 0); |
| + | DEBUGASSERT(data->set.no_signal); |
| #ifndef CURL_DISABLE_DOH | |
| - | || data->set.doh |
| + | DEBUGASSERT(!data->set.doh); |
| #endif | |
| - | ) |
| - | /* USE_ALARM_TIMEOUT defined, but no timeout actually requested or resolve |
| - | done using DoH */ |
| - | return Curl_resolv(data, hostname, port, ip_version, TRUE, entry); |
| + | *entry = NULL; |
| + | timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms; |
| if(timeout < 1000) { | |
| /* The alarm() function only provides integer second resolution, so if | |
| we want to wait less than one second we must bail out already now. */ | |
| @@ -1165,23 +694,11 @@ CURLcode Curl_resolv_timeout(struct Curl_easy *data, | |
| prev_alarm = alarm(curlx_sltoui(timeout / 1000L)); | |
| } | |
| - | #else /* !USE_ALARM_TIMEOUT */ |
| - | #ifndef CURLRES_ASYNCH |
| - | if(timeoutms) |
| - | infof(data, "timeout on name lookup is not supported"); |
| - | #else |
| - | (void)timeoutms; |
| - | #endif |
| - | #endif /* USE_ALARM_TIMEOUT */ |
| - | |
| /* Perform the actual name resolution. This might be interrupted by an | |
| - | * alarm if it takes too long. |
| - | */ |
| - | result = Curl_resolv(data, hostname, port, ip_version, TRUE, entry); |
| + | * alarm if it takes too long. */ |
| + | result = hostip_resolv(data, hostname, port, ip_version, TRUE, entry); |
| - | #ifdef USE_ALARM_TIMEOUT |
| clean_up: | |
| - | |
| if(!prev_alarm) | |
| /* deactivate a possibly active alarm before uninstalling the handler */ | |
| alarm(0); | |
| @@ -1224,290 +741,112 @@ clean_up: | |
| else | |
| alarm((unsigned int)alarm_set); | |
| } | |
| - | #endif /* USE_ALARM_TIMEOUT */ |
| return result; | |
| } | |
| - | /* |
| - | * Curl_resolv_unlink() releases a reference to the given cached DNS entry. |
| - | * When the reference count reaches 0, the entry is destroyed. It is important |
| - | * that only one unlink is made for each Curl_resolv() call. |
| - | * |
| - | * May be called with 'data' == NULL for global cache. |
| - | */ |
| - | void Curl_resolv_unlink(struct Curl_easy *data, struct Curl_dns_entry **pdns) |
| - | { |
| - | if(*pdns) { |
| - | struct Curl_dnscache *dnscache = dnscache_get(data); |
| - | struct Curl_dns_entry *dns = *pdns; |
| - | *pdns = NULL; |
| - | dnscache_lock(data, dnscache); |
| - | dns->refcount--; |
| - | if(dns->refcount == 0) |
| - | dnscache_entry_free(dns); |
| - | dnscache_unlock(data, dnscache); |
| - | } |
| - | } |
| - | |
| - | static void dnscache_entry_dtor(void *entry) |
| - | { |
| - | struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry; |
| - | DEBUGASSERT(dns && (dns->refcount > 0)); |
| - | dns->refcount--; |
| - | if(dns->refcount == 0) |
| - | dnscache_entry_free(dns); |
| - | } |
| + | #endif /* USE_ALARM_TIMEOUT */ |
| /* | |
| - | * Curl_dnscache_init() inits a new DNS cache. |
| + | * Curl_resolv() is the main name resolve function within libcurl. It resolves |
| + | * a name and returns a pointer to the entry in the 'entry' argument. This |
| + | * function might return immediately if we are using asynch resolves. See the |
| + | * return codes. |
| + | * |
| + | * The cache entry we return will get its 'inuse' counter increased when this |
| + | * function is used. You MUST call Curl_dns_entry_unlink() later (when you are |
| + | * done using this struct) to decrease the reference counter again. |
| + | * |
| + | * If built with a synchronous resolver and use of signals is not |
| + | * disabled by the application, then a nonzero timeout will cause a |
| + | * timeout after the specified number of milliseconds. Otherwise, timeout |
| + | * is ignored. |
| + | * |
| + | * Return codes: |
| + | * CURLE_OK = success, *entry set to non-NULL |
| + | * CURLE_AGAIN = resolving in progress, *entry == NULL |
| + | * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL |
| + | * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL |
| */ | |
| - | void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size) |
| - | { |
| - | Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare, |
| - | dnscache_entry_dtor); |
| - | } |
| - | |
| - | void Curl_dnscache_destroy(struct Curl_dnscache *dns) |
| - | { |
| - | Curl_hash_destroy(&dns->entries); |
| - | } |
| - | |
| - | CURLcode Curl_loadhostpairs(struct Curl_easy *data) |
| + | CURLcode Curl_resolv(struct Curl_easy *data, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | timediff_t timeoutms, |
| + | struct Curl_dns_entry **entry) |
| { | |
| - | struct Curl_dnscache *dnscache = dnscache_get(data); |
| - | struct curl_slist *hostp; |
| + | DEBUGASSERT(hostname && *hostname); |
| + | *entry = NULL; |
| - | if(!dnscache) |
| - | return CURLE_FAILED_INIT; |
| + | if(timeoutms < 0) |
| + | /* got an already expired timeout */ |
| + | return CURLE_OPERATION_TIMEDOUT; |
| - | /* Default is no wildcard found */ |
| - | data->state.wildcard_resolve = FALSE; |
| - | |
| - | for(hostp = data->state.resolve; hostp; hostp = hostp->next) { |
| - | char entry_id[MAX_HOSTCACHE_LEN]; |
| - | const char *host = hostp->data; |
| - | struct Curl_str source; |
| - | if(!host) |
| - | continue; |
| - | if(*host == '-') { |
| - | curl_off_t num = 0; |
| - | size_t entry_len; |
| - | host++; |
| - | if(!curlx_str_single(&host, '[')) { |
| - | if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || |
| - | curlx_str_single(&host, ']') || |
| - | curlx_str_single(&host, ':')) |
| - | continue; |
| - | } |
| - | else { |
| - | if(curlx_str_until(&host, &source, 4096, ':') || |
| - | curlx_str_single(&host, ':')) { |
| - | continue; |
| - | } |
| - | } |
| + | #ifdef USE_ALARM_TIMEOUT |
| + | if(timeoutms && !data->set.no_signal) { |
| + | /* Cannot use ALARM when signals are disabled */ |
| + | timeoutms = 0; |
| + | } |
| + | if(timeoutms && !Curl_doh_wanted(data)) { |
| + | return resolv_alarm_timeout(data, hostname, port, ip_version, |
| + | timeoutms, entry); |
| + | } |
| + | #endif /* !USE_ALARM_TIMEOUT */ |
| - | if(!curlx_str_number(&host, &num, 0xffff)) { |
| - | /* Create an entry id, based upon the hostname and port */ |
| - | entry_len = create_dnscache_id(curlx_str(&source), |
| - | curlx_strlen(&source), (int)num, |
| - | entry_id, sizeof(entry_id)); |
| - | dnscache_lock(data, dnscache); |
| - | /* delete entry, ignore if it did not exist */ |
| - | Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); |
| - | dnscache_unlock(data, dnscache); |
| - | } |
| - | } |
| - | else { |
| - | struct Curl_dns_entry *dns; |
| - | struct Curl_addrinfo *head = NULL, *tail = NULL; |
| - | size_t entry_len; |
| - | char address[64]; |
| - | curl_off_t port = 0; |
| - | bool permanent = TRUE; |
| - | bool error = TRUE; |
| - | VERBOSE(const char *addresses = NULL); |
| - | |
| - | if(*host == '+') { |
| - | host++; |
| - | permanent = FALSE; |
| - | } |
| - | if(!curlx_str_single(&host, '[')) { |
| - | if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || |
| - | curlx_str_single(&host, ']')) |
| - | continue; |
| - | } |
| - | else { |
| - | if(curlx_str_until(&host, &source, 4096, ':')) |
| - | continue; |
| - | } |
| - | if(curlx_str_single(&host, ':') || |
| - | curlx_str_number(&host, &port, 0xffff) || |
| - | curlx_str_single(&host, ':')) |
| - | goto err; |
| - | |
| - | VERBOSE(addresses = host); |
| - | |
| - | /* start the address section */ |
| - | while(*host) { |
| - | struct Curl_str target; |
| - | struct Curl_addrinfo *ai; |
| - | CURLcode result; |
| - | |
| - | if(!curlx_str_single(&host, '[')) { |
| - | if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') || |
| - | curlx_str_single(&host, ']')) |
| - | goto err; |
| - | } |
| - | else { |
| - | if(curlx_str_until(&host, &target, 4096, ',')) { |
| - | if(curlx_str_single(&host, ',')) |
| - | goto err; |
| - | /* survive nothing but a comma */ |
| - | continue; |
| - | } |
| - | } |
| - | #ifndef USE_IPV6 |
| - | if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) { |
| - | infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.", |
| - | (int)curlx_strlen(&target), curlx_str(&target)); |
| - | if(curlx_str_single(&host, ',')) |
| - | goto err; |
| - | continue; |
| - | } |
| + | #ifndef CURLRES_ASYNCH |
| + | if(timeoutms) |
| + | infof(data, "timeout on name lookup is not supported"); |
| #endif | |
| - | if(curlx_strlen(&target) >= sizeof(address)) |
| - | goto err; |
| - | |
| - | memcpy(address, curlx_str(&target), curlx_strlen(&target)); |
| - | address[curlx_strlen(&target)] = '\0'; |
| - | |
| - | result = Curl_str2addr(address, (int)port, &ai); |
| - | if(result) { |
| - | infof(data, "Resolve address '%s' found illegal", address); |
| - | goto err; |
| - | } |
| - | |
| - | if(tail) { |
| - | tail->ai_next = ai; |
| - | tail = tail->ai_next; |
| - | } |
| - | else { |
| - | head = tail = ai; |
| - | } |
| - | if(curlx_str_single(&host, ',')) |
| - | break; |
| - | } |
| - | |
| - | if(!head) |
| - | goto err; |
| - | |
| - | error = FALSE; |
| - | err: |
| - | if(error) { |
| - | failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data); |
| - | Curl_freeaddrinfo(head); |
| - | return CURLE_SETOPT_OPTION_SYNTAX; |
| - | } |
| - | |
| - | /* Create an entry id, based upon the hostname and port */ |
| - | entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source), |
| - | (int)port, |
| - | entry_id, sizeof(entry_id)); |
| - | |
| - | dnscache_lock(data, dnscache); |
| - | |
| - | /* See if it is already in our dns cache */ |
| - | dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); |
| - | |
| - | if(dns) { |
| - | infof(data, "RESOLVE %.*s:%" CURL_FORMAT_CURL_OFF_T |
| - | " - old addresses discarded", (int)curlx_strlen(&source), |
| - | curlx_str(&source), port); |
| - | /* delete old entry, there are two reasons for this |
| - | 1. old entry may have different addresses. |
| - | 2. even if entry with correct addresses is already in the cache, |
| - | but if it is close to expire, then by the time next http |
| - | request is made, it can get expired and pruned because old |
| - | entry is not necessarily marked as permanent. |
| - | 3. when adding a non-permanent entry, we want it to remove and |
| - | replace an existing permanent entry. |
| - | 4. when adding a non-permanent entry, we want it to get a "fresh" |
| - | timeout that starts _now_. */ |
| - | |
| - | 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), (int)port, permanent); |
| - | if(dns) |
| - | /* release the returned reference; the cache itself will keep the |
| - | * entry alive: */ |
| - | dns->refcount--; |
| - | |
| - | dnscache_unlock(data, dnscache); |
| - | |
| - | if(!dns) |
| - | return CURLE_OUT_OF_MEMORY; |
| - | |
| - | infof(data, "Added %.*s:%" CURL_FORMAT_CURL_OFF_T ":%s to DNS cache%s", |
| - | (int)curlx_strlen(&source), curlx_str(&source), port, addresses, |
| - | permanent ? "" : " (non-permanent)"); |
| - | |
| - | /* Wildcard hostname */ |
| - | if(curlx_str_casecompare(&source, "*")) { |
| - | infof(data, "RESOLVE *:%" CURL_FORMAT_CURL_OFF_T " using wildcard", |
| - | port); |
| - | data->state.wildcard_resolve = TRUE; |
| - | } |
| - | } |
| - | } |
| - | data->state.resolve = NULL; /* dealt with now */ |
| - | |
| - | return CURLE_OK; |
| + | return hostip_resolv(data, hostname, port, ip_version, TRUE, entry); |
| } | |
| + | |
| #ifdef USE_CURL_ASYNC | |
| - | CURLcode Curl_resolv_check(struct Curl_easy *data, |
| - | struct Curl_dns_entry **dns) |
| + | CURLcode Curl_resolv_take_result(struct Curl_easy *data, |
| + | struct Curl_dns_entry **pdns) |
| { | |
| + | struct Curl_resolv_async *async = data->state.async; |
| CURLcode result; | |
| /* If async resolving is ongoing, this must be set */ | |
| - | if(!data->state.async.hostname) |
| + | if(!async) |
| return CURLE_FAILED_INIT; | |
| /* check if we have the name resolved by now (from someone else) */ | |
| - | *dns = Curl_dnscache_get(data, data->state.async.hostname, |
| - | data->state.async.port, |
| - | data->state.async.ip_version); |
| - | if(*dns) { |
| + | result = Curl_dnscache_get(data, async->hostname, async->port, |
| + | async->ip_version, pdns); |
| + | if(*pdns) { |
| /* Tell a possibly async resolver we no longer need the results. */ | |
| - | infof(data, "Hostname '%s' was found in DNS cache", |
| - | data->state.async.hostname); |
| + | infof(data, "Hostname '%s' was found in DNS cache", async->hostname); |
| Curl_async_shutdown(data); | |
| - | data->state.async.dns = *dns; |
| - | data->state.async.done = TRUE; |
| return CURLE_OK; | |
| } | |
| + | else if(result) { |
| + | result = Curl_resolver_error(data, NULL); |
| + | return result; |
| + | } |
| #ifndef CURL_DISABLE_DOH | |
| - | if(data->conn->bits.doh) { |
| - | result = Curl_doh_is_resolved(data, dns); |
| - | if(result) |
| - | Curl_resolver_error(data, NULL); |
| - | } |
| + | if(data->conn->bits.doh) |
| + | result = Curl_doh_take_result(data, pdns); |
| else | |
| #endif | |
| - | result = Curl_async_is_resolved(data, dns); |
| - | if(*dns) |
| - | show_resolve_info(data, *dns); |
| - | if((result == CURLE_COULDNT_RESOLVE_HOST) || |
| - | (result == CURLE_COULDNT_RESOLVE_PROXY)) |
| - | store_negative_resolve(data, data->state.async.hostname, |
| - | data->state.async.port); |
| + | result = Curl_async_take_result(data, async, pdns); |
| + | |
| + | if(!result) { |
| + | DEBUGASSERT(*pdns); |
| + | show_resolve_info(data, *pdns); |
| + | } |
| + | else if(result == CURLE_AGAIN) |
| + | result = CURLE_OK; |
| + | else { |
| + | Curl_dnscache_add_negative(data, async->hostname, async->port, |
| + | async->ip_version); |
| + | Curl_async_shutdown(data); |
| + | Curl_resolver_error(data, NULL); |
| + | } |
| + | |
| return result; | |
| } | |
| #endif | |
| @@ -1530,34 +869,6 @@ CURLcode Curl_resolv_pollset(struct Curl_easy *data, | |
| #endif | |
| } | |
| - | /* Call this function after Curl_connect() has returned async=TRUE and |
| - | then a successful name resolve has been received. |
| - | |
| - | Note: this function disconnects and frees the conn data in case of |
| - | resolve failure */ |
| - | CURLcode Curl_once_resolved(struct Curl_easy *data, |
| - | struct Curl_dns_entry *dns, |
| - | bool *protocol_done) |
| - | { |
| - | CURLcode result; |
| - | struct connectdata *conn = data->conn; |
| - | |
| - | #ifdef USE_CURL_ASYNC |
| - | if(data->state.async.dns) { |
| - | DEBUGASSERT(data->state.async.dns == dns); |
| - | data->state.async.dns = NULL; |
| - | } |
| - | #endif |
| - | |
| - | result = Curl_setup_conn(data, dns, protocol_done); |
| - | |
| - | if(result) { |
| - | Curl_detach_connection(data); |
| - | Curl_conn_terminate(data, conn, TRUE); |
| - | } |
| - | return result; |
| - | } |
| - | |
| /* | |
| * Curl_resolver_error() calls failf() with the appropriate message after a | |
| * resolve error | |
| @@ -45,6 +45,7 @@ struct connectdata; | |
| struct easy_pollset; | |
| struct Curl_https_rrinfo; | |
| struct Curl_multi; | |
| + | struct Curl_dns_entry; |
| enum alpnid { | |
| ALPN_none = 0, | |
| @@ -53,53 +54,8 @@ enum alpnid { | |
| ALPN_h3 = CURLALTSVC_H3 | |
| }; | |
| - | struct Curl_dns_entry { |
| - | struct Curl_addrinfo *addr; |
| - | #ifdef USE_HTTPSRR |
| - | struct Curl_https_rrinfo *hinfo; |
| - | #endif |
| - | /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */ |
| - | struct curltime timestamp; |
| - | /* reference counter, entry is freed on reaching 0 */ |
| - | size_t refcount; |
| - | /* hostname port number that resolved to addr. */ |
| - | int hostport; |
| - | /* hostname that resolved to addr. may be NULL (Unix domain sockets). */ |
| - | char hostname[1]; |
| - | }; |
| - | |
| - | struct Curl_dnscache { |
| - | struct Curl_hash entries; |
| - | }; |
| - | |
| bool Curl_host_is_ipnum(const char *hostname); | |
| - | /* |
| - | * Curl_resolv() returns an entry with the info for the specified host |
| - | * and port. |
| - | * |
| - | * The returned data *MUST* be "released" with Curl_resolv_unlink() after |
| - | * use, or we will leak memory! |
| - | */ |
| - | CURLcode Curl_resolv(struct Curl_easy *data, |
| - | const char *hostname, |
| - | int port, |
| - | int ip_version, |
| - | bool allowDOH, |
| - | struct Curl_dns_entry **entry); |
| - | |
| - | CURLcode Curl_resolv_blocking(struct Curl_easy *data, |
| - | const char *hostname, |
| - | int port, |
| - | int ip_version, |
| - | struct Curl_dns_entry **entry); |
| - | |
| - | CURLcode Curl_resolv_timeout(struct Curl_easy *data, |
| - | const char *hostname, int port, |
| - | int ip_version, |
| - | struct Curl_dns_entry **entry, |
| - | timediff_t timeoutms); |
| - | |
| #ifdef USE_IPV6 | |
| /* probe if it seems to work */ | |
| @@ -113,87 +69,50 @@ bool Curl_ipv6works(struct Curl_easy *data); | |
| #define Curl_ipv6works(x) FALSE | |
| #endif | |
| - | /* unlink a dns entry, potentially shared with a cache */ |
| - | void Curl_resolv_unlink(struct Curl_easy *data, |
| - | struct Curl_dns_entry **pdns); |
| - | |
| - | /* init a new dns cache */ |
| - | void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size); |
| - | |
| - | void Curl_dnscache_destroy(struct Curl_dnscache *dns); |
| - | |
| - | /* prune old entries from the DNS cache */ |
| - | void Curl_dnscache_prune(struct Curl_easy *data); |
| - | |
| - | /* clear the DNS cache */ |
| - | void Curl_dnscache_clear(struct Curl_easy *data); |
| - | |
| /* IPv4 thread-safe resolve function used for synch and asynch builds */ | |
| - | struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port); |
| - | |
| - | CURLcode Curl_once_resolved(struct Curl_easy *data, |
| - | struct Curl_dns_entry *dns, |
| - | bool *protocol_done); |
| + | struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, uint16_t port); |
| /* | |
| * Curl_printable_address() returns a printable version of the 1st address | |
| * given in the 'ip' argument. The result will be stored in the buf that is | |
| * bufsize bytes big. | |
| */ | |
| - | void Curl_printable_address(const struct Curl_addrinfo *ai, |
| + | void Curl_printable_address(const struct Curl_addrinfo *ip, |
| char *buf, size_t bufsize); | |
| /* | |
| - | * Make a `Curl_dns_entry`. |
| - | * Creates a dnscache entry *without* adding it to a dnscache. This allows |
| - | * further modifications of the entry *before* then adding it to a cache. |
| - | * |
| - | * The entry is created with a reference count of 1. |
| - | * Use `Curl_resolv_unlink()` to release your hold on it. |
| - | * |
| - | * The call takes ownership of `addr`, even in case of failure, and always |
| - | * clears `*paddr`. It makes a copy of `hostname`. |
| - | * |
| - | * Returns entry or NULL on OOM. |
| - | */ |
| - | struct Curl_dns_entry * |
| - | Curl_dnscache_mk_entry(struct Curl_easy *data, |
| - | struct Curl_addrinfo **paddr, |
| - | const char *hostname, |
| - | size_t hostlen, /* length or zero */ |
| - | int port, |
| - | bool permanent); |
| - | |
| - | /* |
| - | * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache. |
| - | * |
| - | * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. |
| + | * Curl_resolv() returns an entry with the info for the specified host |
| + | * and port. |
| * | |
| - | * The returned data *MUST* be "released" with Curl_resolv_unlink() after |
| + | * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after |
| * use, or we will leak memory! | |
| */ | |
| - | struct Curl_dns_entry *Curl_dnscache_get(struct Curl_easy *data, |
| - | const char *hostname, int port, |
| - | int ip_version); |
| + | CURLcode Curl_resolv(struct Curl_easy *data, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | timediff_t timeoutms, |
| + | struct Curl_dns_entry **pdns); |
| - | /* |
| - | * Curl_dnscache_addr() adds `entry` to the cache, increasing its |
| - | * reference count on success. |
| - | */ |
| - | CURLcode Curl_dnscache_add(struct Curl_easy *data, |
| - | struct Curl_dns_entry *entry); |
| + | CURLcode Curl_resolv_blocking(struct Curl_easy *data, |
| + | const char *hostname, |
| + | uint16_t port, |
| + | uint8_t ip_version, |
| + | struct Curl_dns_entry **pdns); |
| - | /* |
| - | * Populate the cache with specified entries from CURLOPT_RESOLVE. |
| - | */ |
| - | CURLcode Curl_loadhostpairs(struct Curl_easy *data); |
| + | CURLcode Curl_resolv_timeout(struct Curl_easy *data, |
| + | const char *hostname, int port, |
| + | int ip_version, |
| + | struct Curl_dns_entry **entry, |
| + | timediff_t timeoutms); |
| #ifdef USE_CURL_ASYNC | |
| - | CURLcode Curl_resolv_check(struct Curl_easy *data, |
| - | struct Curl_dns_entry **dns); |
| + | CURLcode Curl_resolv_take_result(struct Curl_easy *data, |
| + | struct Curl_dns_entry **pdns); |
| #else | |
| - | #define Curl_resolv_check(x, y) CURLE_NOT_BUILT_IN |
| + | #define Curl_resolv_take_result(x, y) CURLE_NOT_BUILT_IN |
| #endif | |
| + | |
| CURLcode Curl_resolv_pollset(struct Curl_easy *data, | |
| struct easy_pollset *ps); | |
| @@ -207,8 +126,8 @@ CURLcode Curl_resolver_error(struct Curl_easy *data, const char *detail); | |
| */ | |
| struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, | |
| const char *hostname, | |
| - | int port, |
| - | int ip_version); |
| + | uint16_t port, |
| + | uint8_t ip_version); |
| #endif | |
| @@ -69,8 +69,8 @@ | |
| */ | |
| struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, | |
| const char *hostname, | |
| - | int port, |
| - | int ip_version) |
| + | uint16_t port, |
| + | uint8_t ip_version) |
| { | |
| struct Curl_addrinfo *ai = NULL; | |
| @@ -95,7 +95,7 @@ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, | |
| * | |
| */ | |
| struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, | |
| - | int port) |
| + | uint16_t port) |
| { | |
| #if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) && \ | |
| defined(HAVE_GETHOSTBYNAME_R_3) | |
| @@ -64,8 +64,8 @@ | |
| */ | |
| struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, | |
| const char *hostname, | |
| - | int port, |
| - | int ip_version) |
| + | uint16_t port, |
| + | uint8_t ip_version) |
| { | |
| struct addrinfo hints; | |
| struct Curl_addrinfo *res; | |
| @@ -633,8 +633,8 @@ static void multi_done_locked(struct connectdata *conn, | |
| data->state.done = TRUE; /* called now! */ | |
| data->state.recent_conn_id = conn->connection_id; | |
| - | Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */ |
| - | Curl_resolv_unlink(data, &data->state.dns[1]); |
| + | Curl_dns_entry_unlink(data, &data->state.dns[0]); /* done with this */ |
| + | Curl_dns_entry_unlink(data, &data->state.dns[1]); |
| Curl_dnscache_prune(data); | |
| if(multi_conn_should_close(conn, data, (bool)mdctx->premature)) { | |
| @@ -2295,12 +2295,13 @@ static CURLMcode state_resolving(struct Curl_multi *multi, | |
| CURLcode *resultp) | |
| { | |
| struct Curl_dns_entry *dns = NULL; | |
| - | CURLcode result; |
| CURLMcode mresult = CURLM_OK; | |
| + | CURLcode result; |
| - | result = Curl_resolv_check(data, &dns); |
| - | CURL_TRC_DNS(data, "Curl_resolv_check() -> %d, %s", |
| + | result = Curl_resolv_take_result(data, &dns); |
| + | CURL_TRC_DNS(data, "Curl_resolv_take_result() -> %d, %s", |
| result, dns ? "found" : "missing"); | |
| + | |
| /* Update sockets here, because the socket(s) may have been closed and the | |
| application thus needs to be told, even if it is likely that the same | |
| socket(s) will again be used further down. If the name has not yet been | |
| @@ -2314,23 +2315,21 @@ static CURLMcode state_resolving(struct Curl_multi *multi, | |
| bool connected; | |
| /* Perform the next step in the connection phase, and then move on to the | |
| WAITCONNECT state */ | |
| - | result = Curl_once_resolved(data, dns, &connected); |
| - | |
| - | if(result) |
| - | /* if Curl_once_resolved() returns failure, the connection struct is |
| - | already freed and gone */ |
| - | data->conn = NULL; /* no more connection */ |
| - | else { |
| - | /* call again please so that we get the next socket setup */ |
| - | mresult = CURLM_CALL_MULTI_PERFORM; |
| - | if(connected) |
| - | multistate(data, MSTATE_PROTOCONNECT); |
| - | else { |
| - | multistate(data, MSTATE_CONNECTING); |
| - | } |
| + | result = Curl_setup_conn(data, dns, &connected); |
| + | if(result) { |
| + | /* setup failed, terminate connection */ |
| + | struct connectdata *conn = data->conn; |
| + | Curl_detach_connection(data); |
| + | Curl_conn_terminate(data, conn, TRUE); |
| + | goto out; |
| } | |
| + | |
| + | /* call again please so that we get the next socket setup */ |
| + | mresult = CURLM_CALL_MULTI_PERFORM; |
| + | multistate(data, connected ? MSTATE_PROTOCONNECT : MSTATE_CONNECTING); |
| } | |
| + | out: |
| if(result) | |
| /* failure detected */ | |
| *stream_errorp = TRUE; | |
| @@ -27,7 +27,7 @@ | |
| #include "hash.h" | |
| #include "conncache.h" | |
| #include "cshutdn.h" | |
| - | #include "hostip.h" |
| + | #include "dnscache.h" |
| #include "multi_ev.h" | |
| #include "multi_ntfy.h" | |
| #include "psl.h" | |
| @@ -1508,8 +1508,8 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, | |
| data->psl = data->multi ? &data->multi->psl : NULL; | |
| #endif | |
| if(data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) { | |
| - | Curl_resolv_unlink(data, &data->state.dns[0]); |
| - | Curl_resolv_unlink(data, &data->state.dns[1]); |
| + | Curl_dns_entry_unlink(data, &data->state.dns[0]); |
| + | Curl_dns_entry_unlink(data, &data->state.dns[1]); |
| } | |
| data->share->dirty--; | |
| @@ -98,7 +98,7 @@ struct socks_state { | |
| enum socks_state_t state; | |
| struct bufq iobuf; | |
| const char *hostname; | |
| - | int remote_port; |
| + | uint16_t remote_port; |
| const char *proxy_user; | |
| const char *proxy_password; | |
| CURLproxycode presult; | |
| @@ -324,7 +324,7 @@ static CURLproxycode socks4_resolving(struct socks_state *sx, | |
| DEBUGASSERT(sx->hostname && *sx->hostname); | |
| result = Curl_resolv(data, sx->hostname, sx->remote_port, | |
| - | cf->conn->ip_version, TRUE, &dns); |
| + | cf->conn->ip_version, 0, &dns); |
| if(result == CURLE_AGAIN) { | |
| CURL_TRC_CF(data, cf, "SOCKS4 non-blocking resolve of %s", sx->hostname); | |
| return CURLPX_OK; | |
| @@ -334,7 +334,7 @@ static CURLproxycode socks4_resolving(struct socks_state *sx, | |
| } | |
| else { | |
| /* check if we have the name resolved by now */ | |
| - | result = Curl_resolv_check(data, &dns); |
| + | result = Curl_resolv_take_result(data, &dns); |
| if(!result && !dns) | |
| return CURLPX_OK; | |
| } | |
| @@ -342,7 +342,7 @@ static CURLproxycode socks4_resolving(struct socks_state *sx, | |
| if(result || !dns) { | |
| failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", sx->hostname); | |
| if(dns) | |
| - | Curl_resolv_unlink(data, &dns); |
| + | Curl_dns_entry_unlink(data, &dns); |
| return CURLPX_RESOLVE_HOST; | |
| } | |
| @@ -368,12 +368,12 @@ static CURLproxycode socks4_resolving(struct socks_state *sx, | |
| (unsigned char *)&saddr_in->sin_addr.s_addr, 4, | |
| &nwritten); | |
| - | Curl_resolv_unlink(data, &dns); /* not used anymore from now on */ |
| + | Curl_dns_entry_unlink(data, &dns); /* not used anymore from now on */ |
| if(result || (nwritten != 4)) | |
| return CURLPX_SEND_REQUEST; | |
| } | |
| else { | |
| - | Curl_resolv_unlink(data, &dns); |
| + | Curl_dns_entry_unlink(data, &dns); |
| failf(data, "SOCKS4 connection to %s not supported", sx->hostname); | |
| return CURLPX_RESOLVE_HOST; | |
| } | |
| @@ -495,7 +495,7 @@ process_state: | |
| /* SOCKS4 can only do IPv4, insist! */ | |
| cf->conn->ip_version = CURL_IPRESOLVE_V4; | |
| - | CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%d", |
| + | CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%u", |
| sx->socks4a ? "a" : "", | |
| cf->conn->bits.httpproxy ? " HTTP proxy" : "", | |
| sx->hostname, sx->remote_port); | |
| @@ -826,7 +826,7 @@ static CURLproxycode socks5_req1_init(struct socks_state *sx, | |
| result = Curl_bufq_write(&sx->iobuf, req, 2, &nwritten); | |
| if(result || (nwritten != 2)) | |
| return CURLPX_SEND_REQUEST; | |
| - | CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (remotely resolved)", |
| + | CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%u (remotely resolved)", |
| sx->hostname, sx->remote_port); | |
| return CURLPX_OK; | |
| } | |
| @@ -853,7 +853,7 @@ static CURLproxycode socks5_resolving(struct socks_state *sx, | |
| DEBUGASSERT(sx->hostname && *sx->hostname); | |
| result = Curl_resolv(data, sx->hostname, sx->remote_port, | |
| - | cf->conn->ip_version, TRUE, &dns); |
| + | cf->conn->ip_version, 0, &dns); |
| if(result == CURLE_AGAIN) { | |
| CURL_TRC_CF(data, cf, "SOCKS5 non-blocking resolve of %s", sx->hostname); | |
| return CURLPX_OK; | |
| @@ -863,7 +863,7 @@ static CURLproxycode socks5_resolving(struct socks_state *sx, | |
| } | |
| else { | |
| /* check if we have the name resolved by now */ | |
| - | result = Curl_resolv_check(data, &dns); |
| + | result = Curl_resolv_take_result(data, &dns); |
| if(!result && !dns) | |
| return CURLPX_OK; | |
| } | |
| @@ -899,7 +899,7 @@ static CURLproxycode socks5_resolving(struct socks_state *sx, | |
| destlen = 4; | |
| saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr; | |
| destination = (const unsigned char *)&saddr_in->sin_addr.s_addr; | |
| - | CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (locally resolved)", |
| + | CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%u (locally resolved)", |
| dest, sx->remote_port); | |
| } | |
| #ifdef USE_IPV6 | |
| @@ -909,7 +909,7 @@ static CURLproxycode socks5_resolving(struct socks_state *sx, | |
| destlen = 16; | |
| saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr; | |
| destination = (const unsigned char *)&saddr_in6->sin6_addr.s6_addr; | |
| - | CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%d (locally resolved)", |
| + | CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%u (locally resolved)", |
| dest, sx->remote_port); | |
| } | |
| #endif | |
| @@ -942,7 +942,7 @@ static CURLproxycode socks5_resolving(struct socks_state *sx, | |
| out: | |
| if(dns) | |
| - | Curl_resolv_unlink(data, &dns); |
| + | Curl_dns_entry_unlink(data, &dns); |
| *done = (presult == CURLPX_OK); | |
| return presult; | |
| } | |
| @@ -1062,7 +1062,7 @@ process_state: | |
| case SOCKS5_ST_START: | |
| if(cf->conn->bits.httpproxy) | |
| - | CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %d", |
| + | CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %u", |
| sx->hostname, sx->remote_port); | |
| presult = socks5_req0_init(cf, sx, data); | |
| if(presult) | |
| @@ -1252,10 +1252,10 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, | |
| sockindex == SECONDARYSOCKET ? | |
| conn->secondaryhostname : conn->host.name; | |
| sx->remote_port = | |
| - | conn->bits.httpproxy ? (int)conn->http_proxy.port : |
| + | conn->bits.httpproxy ? conn->http_proxy.port : |
| sockindex == SECONDARYSOCKET ? conn->secondary_port : | |
| conn->bits.conn_to_port ? conn->conn_to_port : | |
| - | conn->remote_port; |
| + | (uint16_t)conn->remote_port; |
| sx->proxy_user = conn->socks_proxy.user; | |
| sx->proxy_password = conn->socks_proxy.passwd; | |
| Curl_bufq_init2(&sx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS, | |
| @@ -59,6 +59,7 @@ | |
| #include "hostip.h" | |
| #include "cfilters.h" | |
| #include "cw-out.h" | |
| + | #include "dnscache.h" |
| #include "transfer.h" | |
| #include "sendf.h" | |
| #include "curl_trc.h" | |
| @@ -245,8 +245,8 @@ CURLcode Curl_close(struct Curl_easy **datap) | |
| /* release any resolve information this transfer kept */ | |
| Curl_async_destroy(data); | |
| - | Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */ |
| - | Curl_resolv_unlink(data, &data->state.dns[1]); |
| + | Curl_dns_entry_unlink(data, &data->state.dns[0]); /* done with this */ |
| + | Curl_dns_entry_unlink(data, &data->state.dns[1]); |
| data->set.verbose = FALSE; /* no more calls to DEBUGFUNCTION */ | |
| data->magic = 0; /* force a clear AFTER the possibly enforced removal from | |
| @@ -2821,7 +2821,7 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, | |
| if(port >= 0) { | |
| conn->conn_to_port = (uint16_t)port; | |
| conn->bits.conn_to_port = TRUE; | |
| - | infof(data, "Connecting to port: %d", port); |
| + | infof(data, "Connecting to port: %u", conn->conn_to_port); |
| } | |
| else { | |
| /* no "connect to port" */ | |
| @@ -2991,7 +2991,6 @@ static CURLcode resolve_unix(struct Curl_easy *data, | |
| *************************************************************/ | |
| static CURLcode resolve_server(struct Curl_easy *data, | |
| struct connectdata *conn, | |
| - | bool *async, |
| struct Curl_dns_entry **pdns) | |
| { | |
| struct hostname *ehost; | |
| @@ -3035,30 +3034,32 @@ static CURLcode resolve_server(struct Curl_easy *data, | |
| ehost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host; | |
| /* If not connecting via a proxy, extract the port from the URL, if it is | |
| * there, thus overriding any defaults that might have been set above. */ | |
| - | eport = conn->bits.conn_to_port ? conn->conn_to_port : conn->remote_port; |
| + | eport = conn->bits.conn_to_port ? |
| + | conn->conn_to_port : (uint16_t)conn->remote_port; |
| } | |
| - | result = Curl_resolv_timeout(data, ehost->name, |
| - | eport, conn->ip_version, |
| - | pdns, timeout_ms); |
| + | result = Curl_resolv(data, ehost->name, eport, |
| + | conn->ip_version, timeout_ms, pdns); |
| DEBUGASSERT(!result || !*pdns); | |
| - | if(result == CURLE_AGAIN) { |
| - | *async = TRUE; |
| + | if(!result) { /* resolved right away, either sync or from dnscache */ |
| + | DEBUGASSERT(*pdns); |
| return CURLE_OK; | |
| } | |
| - | else if(result == CURLE_OPERATION_TIMEDOUT) { |
| + | else if(result == CURLE_AGAIN) { /* async resolv in progress */ |
| + | return CURLE_AGAIN; |
| + | } |
| + | else if(result == CURLE_OPERATION_TIMEDOUT) { /* took too long */ |
| failf(data, "Failed to resolve %s '%s' with timeout after %" | |
| FMT_TIMEDIFF_T " ms", peertype, ehost->dispname, | |
| curlx_ptimediff_ms(Curl_pgrs_now(data), | |
| &data->progress.t_startsingle)); | |
| return CURLE_OPERATION_TIMEDOUT; | |
| } | |
| - | else if(result) { |
| + | else { |
| + | DEBUGASSERT(result); |
| failf(data, "Could not resolve %s: %s", peertype, ehost->dispname); | |
| return result; | |
| } | |
| - | DEBUGASSERT(*pdns); |
| - | return CURLE_OK; |
| } | |
| static void url_move_hostname(struct hostname *dest, struct hostname *src) | |
| @@ -3594,14 +3595,17 @@ CURLcode Curl_connect(struct Curl_easy *data, | |
| * Resolve the address of the server or proxy | |
| *************************************************************/ | |
| struct Curl_dns_entry *dns; | |
| - | result = resolve_server(data, conn, asyncp, &dns); |
| + | result = resolve_server(data, conn, &dns); |
| if(!result) { | |
| - | *asyncp = !dns; |
| - | if(dns) |
| - | /* DNS resolution is done: that is either because this is a reused |
| - | connection, in which case DNS was unnecessary, or because DNS |
| - | really did finish already (synch resolver/fast async resolve) */ |
| - | result = Curl_setup_conn(data, dns, protocol_done); |
| + | DEBUGASSERT(dns); |
| + | /* DNS resolution is done: that is either because this is a reused |
| + | connection, in which case DNS was unnecessary, or because DNS |
| + | really did finish already (synch resolver/fast async resolve) */ |
| + | result = Curl_setup_conn(data, dns, protocol_done); |
| + | } |
| + | else if(result == CURLE_AGAIN) { |
| + | *asyncp = TRUE; |
| + | result = CURLE_OK; |
| } | |
| } | |
| } | |
| @@ -719,7 +719,8 @@ struct UrlState { | |
| struct Curl_dns_entry *dns[2]; /* DNS to connect FIRST/SECONDARY */ | |
| #ifdef USE_CURL_ASYNC | |
| - | struct Curl_async async; /* asynchronous name resolver data */ |
| + | 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 | |
| @@ -3469,7 +3469,7 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, | |
| size_t ech_config_len = 0; | |
| char *outername = data->set.str[STRING_ECH_PUBLIC]; | |
| int trying_ech_now = 0; | |
| - | CURLcode result; |
| + | CURLcode result = CURLE_OK; |
| if(!CURLECH_ENABLED(data)) | |
| return CURLE_OK; | |
| @@ -3528,9 +3528,14 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, | |
| struct Curl_dns_entry *dns = NULL; | |
| if(peer->hostname) | |
| - | dns = Curl_dnscache_get(data, peer->hostname, peer->port, |
| - | cf->conn->ip_version); |
| + | 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; | |
| @@ -3559,7 +3564,7 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, | |
| if(data->set.tls_ech & CURLECH_HARD) | |
| return CURLE_SSL_CONNECT_ERROR; | |
| } | |
| - | Curl_resolv_unlink(data, &dns); |
| + | Curl_dns_entry_unlink(data, &dns); |
| } | |
| } | |
| #ifdef HAVE_BORINGSSL_LIKE | |
| @@ -964,10 +964,16 @@ init_config_builder_ech(struct Curl_easy *data, | |
| } | |
| else { | |
| if(connssl->peer.hostname) { | |
| - | dns = Curl_dnscache_get(data, connssl->peer.hostname, |
| - | connssl->peer.port, data->conn->ip_version); |
| + | 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; | |
| @@ -997,7 +1003,7 @@ cleanup: | |
| curlx_free(ech_config); | |
| } | |
| if(dns) { | |
| - | Curl_resolv_unlink(data, &dns); |
| + | Curl_dns_entry_unlink(data, &dns); |
| } | |
| return result; | |
| } | |
| @@ -1248,7 +1248,7 @@ CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, | |
| { | |
| ehostname = cf->conn->host.name; | |
| edispname = cf->conn->host.dispname; | |
| - | peer->port = cf->conn->remote_port; |
| + | peer->port = (uint16_t)cf->conn->remote_port; |
| } | |
| /* hostname MUST exist and not be empty */ | |
| @@ -1382,9 +1382,15 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, | |
| struct ssl_connect_data *connssl = cf->ctx; | |
| struct Curl_dns_entry *dns = NULL; | |
| - | dns = Curl_dnscache_get(data, connssl->peer.hostname, connssl->peer.port, |
| - | cf->conn->ip_version); |
| + | 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); |
| + | goto out; |
| + | } |
| infof(data, "ECH: requested but no DNS info available"); | |
| if(data->set.tls_ech & CURLECH_HARD) { | |
| result = CURLE_SSL_CONNECT_ERROR; | |
| @@ -1420,7 +1426,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, | |
| goto out; | |
| } | |
| } | |
| - | Curl_resolv_unlink(data, &dns); |
| + | Curl_dns_entry_unlink(data, &dns); |
| } | |
| } | |
| @@ -288,6 +288,7 @@ static void usage_hx_download(const char *msg) | |
| " -r <host>:<port>:<addr> resolve information\n" | |
| " -T number max concurrent connections total\n" | |
| " -V http_version (http/1.1, h2, h3) http version to use\n" | |
| + | " -6 use ipv6 for resolving the FIRST url\n" |
| ); | |
| } | |
| @@ -315,11 +316,12 @@ static CURLcode test_cli_hx_download(const char *URL) | |
| size_t max_total_conns = 0; | |
| int fresh_connect = 0; | |
| char *cafile = NULL; | |
| + | bool first_ipv6 = FALSE; |
| CURLcode result = CURLE_OK; | |
| (void)URL; | |
| - | while((ch = cgetopt(test_argc, test_argv, "aefhm:n:xA:C:F:M:P:r:T:V:")) |
| + | while((ch = cgetopt(test_argc, test_argv, "aefhm:n:xA:C:F:M:P:r:T:V:6")) |
| != -1) { | |
| const char *opt = coptarg; | |
| curl_off_t num; | |
| @@ -390,6 +392,9 @@ static CURLcode test_cli_hx_download(const char *URL) | |
| } | |
| break; | |
| } | |
| + | case '6': |
| + | first_ipv6 = TRUE; |
| + | break; |
| default: | |
| usage_hx_download("invalid option"); | |
| result = (CURLcode)1; | |
| @@ -466,6 +471,8 @@ static CURLcode test_cli_hx_download(const char *URL) | |
| result = (CURLcode)1; | |
| goto cleanup; | |
| } | |
| + | if(!i && first_ipv6) |
| + | curl_easy_setopt(t->curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); |
| curl_multi_add_handle(multi, t->curl); | |
| t->started = 1; | |
| ++active_transfers; | |
| @@ -43,6 +43,10 @@ extern const struct entry_s s_entries[]; | |
| extern int unitfail; /* for unittests */ | |
| + | #ifdef UNITTESTS |
| + | #include "unitprotos.h" |
| + | #endif |
| + | |
| #include "curlx/base64.h" /* for curlx_base64* */ | |
| #include "curlx/dynbuf.h" /* for curlx_dyn_*() */ | |
| #include "curlx/fopen.h" /* for curlx_f*() */ | |
| @@ -35,7 +35,7 @@ | |
| #include "curl_addrinfo.h" | |
| #include "hash.h" | |
| - | #include "hostip.h" |
| + | #include "dnscache.h" |
| static struct Curl_dnscache hp; | |
| static char *data_key; | |