From 56e40ae6a532850b5037ccfb528ed44f09f3d484 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 11 Apr 2025 14:43:45 +0200 Subject: [PATCH] asyn resolver code improvements "asyn" is the internal name under which both c-ares and threaded resolver operate. Make the naming more consistent. Implement the c-ares resolver in `asyn-ares.*` and the threaded resolver in `asyn-thrdd.*`. The common functions are in `asyn-base.c`. When `CURLRES_ASYNCH` is defined, either of the two is used and `data->state.async` exists. Members of that struct vary for the selected implementation, but have the fields `hostname`, `port` and `ip_version` always present. This are populated when the async resolving starts and eliminate the need to pass them again when checking on the status and processing the results of the resolving. Add a `Curl_resolv_blocking()` to `hostip.h` that relieves FTP and SOCKS from having to repeat the same code. `Curl_resolv_check()` remains the function to check for status of ongoing resolving. Now it also performs internally the check if the needed DNS entry exists in the dnscache and if so, aborts the asnyc operation. (libcurl right now does not check for duplicate resolve attempts. an area for future improvements). The number of functions in `asyn.h` has been reduced. There were subtle difference in "cancel()" and "kill()" calls, both replaced by `Curl_async_shutdown()` now. This changes behaviour for threaded resolver insofar as the resolving thread is now always joined unless `data->set.quick_exit` is set. Before this was only done on some code paths. A future improvement would be a thread pool that keeps a limit and also could handle joins more gracefully. DoH, not previously tagged under "asny", has its struct `doh_probes` now also in `data->state.async`, moved there from `data->req` because it makes more sense. Further integration of DoH underneath the "asyn" umbrella seems like a good idea. Closes #16963 --- lib/Makefile.inc | 5 +- lib/asyn-ares.c | 567 +++++++++++++--------------- lib/asyn-base.c | 171 +++++++++ lib/asyn-thrdd.c | 754 +++++++++++++++++++++++++++++++++++++ lib/asyn-thread.c | 797 ---------------------------------------- lib/asyn.h | 309 ++++++++-------- lib/cf-socket.c | 17 +- lib/cshutdn.c | 3 - lib/doh.c | 81 ++-- lib/doh.h | 3 +- lib/easy.c | 14 +- lib/ftp.c | 41 +-- lib/hostasyn.c | 114 ------ lib/hostip.c | 525 ++++++++++++++------------ lib/hostip.h | 140 +++---- lib/hostip4.c | 26 +- lib/hostip6.c | 26 +- lib/hostsyn.c | 104 ------ lib/httpsrr.c | 32 +- lib/httpsrr.h | 7 +- lib/multi.c | 35 +- lib/request.h | 6 - lib/setopt.c | 2 +- lib/socks.c | 59 +-- lib/url.c | 32 +- lib/urldata.h | 15 - lib/vtls/openssl.c | 3 +- lib/vtls/rustls.c | 6 +- lib/vtls/wolfssl.c | 3 +- tests/http/scorecard.py | 11 +- 30 files changed, 1838 insertions(+), 2070 deletions(-) create mode 100644 lib/asyn-base.c create mode 100644 lib/asyn-thrdd.c delete mode 100644 lib/asyn-thread.c delete mode 100644 lib/hostasyn.c delete mode 100644 lib/hostsyn.c diff --git a/lib/Makefile.inc b/lib/Makefile.inc index a8e4da1cf9..c8689a578c 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -111,7 +111,8 @@ LIB_CFILES = \ altsvc.c \ amigaos.c \ asyn-ares.c \ - asyn-thread.c \ + asyn-base.c \ + asyn-thrdd.c \ base64.c \ bufq.c \ bufref.c \ @@ -166,11 +167,9 @@ LIB_CFILES = \ hash_offt.c \ headers.c \ hmac.c \ - hostasyn.c \ hostip.c \ hostip4.c \ hostip6.c \ - hostsyn.c \ hsts.c \ http.c \ http1.c \ diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c index a8bcaddab8..6c7922914e 100644 --- a/lib/asyn-ares.c +++ b/lib/asyn-ares.c @@ -24,7 +24,7 @@ #include "curl_setup.h" -#ifdef USE_ARES +#ifdef CURLRES_ARES /*********************************************************************** * Only for ares-enabled builds @@ -66,109 +66,6 @@ #include /* really old c-ares did not include this by itself */ -#ifdef USE_HTTPSRR -/* 1.28.0 and later have ares_query_dnsrec */ -#if ARES_VERSION < 0x011c00 -#error "requires c-ares 1.28.0 or newer for HTTPSRR" -#endif -#define HTTPSRR_WORKS -#else -#if ARES_VERSION < 0x010600 -#error "requires c-ares 1.6.0 or newer" -#endif -#endif - -/* - * Curl_ares_getsock() is called when the outside world (using - * curl_multi_fdset()) wants to get our fd_set setup and we are talking with - * ares. The caller must make sure that this function is only called when we - * have a working ares channel. - * - * Returns: sockets-in-use-bitmap - */ - -int Curl_ares_getsock(struct Curl_easy *data, - ares_channel channel, - curl_socket_t *socks) -{ - struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 }; - struct timeval timebuf; - int max = ares_getsock(channel, - (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); - struct timeval *timeout = ares_timeout(channel, &maxtime, &timebuf); - timediff_t milli = curlx_tvtoms(timeout); - Curl_expire(data, milli, EXPIRE_ASYNC_NAME); - return max; -} - -/* - * Curl_ares_perform() - * - * 1) Ask ares what sockets it currently plays with, then - * 2) wait for the timeout period to check for action on ares' sockets. - * 3) tell ares to act on all the sockets marked as "with action" - * - * return number of sockets it worked on, or -1 on error - */ - -int Curl_ares_perform(ares_channel channel, - timediff_t timeout_ms) -{ - int nfds; - int bitmask; - ares_socket_t socks[ARES_GETSOCK_MAXNUM]; - struct pollfd pfd[ARES_GETSOCK_MAXNUM]; - int i; - int num = 0; - - if(!channel) - return 0; - - bitmask = ares_getsock(channel, socks, ARES_GETSOCK_MAXNUM); - - for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) { - pfd[i].events = 0; - pfd[i].revents = 0; - if(ARES_GETSOCK_READABLE(bitmask, i)) { - pfd[i].fd = socks[i]; - pfd[i].events |= POLLRDNORM|POLLIN; - } - if(ARES_GETSOCK_WRITABLE(bitmask, i)) { - pfd[i].fd = socks[i]; - pfd[i].events |= POLLWRNORM|POLLOUT; - } - if(pfd[i].events) - num++; - else - break; - } - - if(num) { - nfds = Curl_poll(pfd, (unsigned int)num, timeout_ms); - if(nfds < 0) - return -1; - } - else - nfds = 0; - - if(!nfds) - /* Call ares_process() unconditionally here, even if we simply timed out - above, as otherwise the ares name resolve will not timeout! */ - ares_process_fd(channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD); - else { - /* move through the descriptors and ask for processing on them */ - for(i = 0; i < num; i++) - ares_process_fd(channel, - (pfd[i].revents & (POLLRDNORM|POLLIN)) ? - pfd[i].fd : ARES_SOCKET_BAD, - (pfd[i].revents & (POLLWRNORM|POLLOUT)) ? - pfd[i].fd : ARES_SOCKET_BAD); - } - return nfds; -} - -#ifdef CURLRES_ARES - #if ARES_VERSION >= 0x010601 /* IPv6 supported since 1.6.1 */ #define HAVE_CARES_IPV6 1 @@ -210,11 +107,11 @@ int Curl_ares_perform(ares_channel channel, static int ares_ver = 0; /* - * Curl_resolver_global_init() - the generic low-level asynchronous name + * Curl_async_global_init() - the generic low-level asynchronous name * resolve API. Called from curl_global_init() to initialize global resolver * environment. Initializes ares library. */ -int Curl_resolver_global_init(void) +int Curl_async_global_init(void) { #ifdef CARES_HAVE_ARES_LIBRARY_INIT if(ares_library_init(ARES_LIB_INIT_ALL)) { @@ -226,12 +123,12 @@ int Curl_resolver_global_init(void) } /* - * Curl_resolver_global_cleanup() + * Curl_async_global_cleanup() * * Called from curl_global_cleanup() to destroy global resolver environment. * Deinitializes ares library. */ -void Curl_resolver_global_cleanup(void) +void Curl_async_global_cleanup(void) { #ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP ares_library_cleanup(); @@ -249,21 +146,16 @@ static void sock_state_cb(void *data, ares_socket_t socket_fd, } } -/* - * Curl_resolver_init() - * - * Called from curl_easy_init() -> Curl_open() to initialize resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Fills the passed pointer by the initialized ares_channel. - */ -CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) +static CURLcode async_ares_init(struct Curl_easy *data) { + struct async_ares_ctx *ares = &data->state.async.ares; int status; struct ares_options options; int optmask = ARES_OPT_SOCK_STATE_CB; options.sock_state_cb = sock_state_cb; - options.sock_state_cb_data = easy; + options.sock_state_cb_data = data; + DEBUGASSERT(!ares->channel); /* if c ares < 1.20.0: curl set timeout to CARES_TIMEOUT_PER_ATTEMPT (2s) @@ -279,8 +171,9 @@ CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) optmask |= ARES_OPT_TIMEOUTMS; } - status = ares_init_options((ares_channel*)resolver, &options, optmask); + status = ares_init_options(&ares->channel, &options, optmask); if(status != ARES_SUCCESS) { + ares->channel = NULL; if(status == ARES_ENOMEM) return CURLE_OUT_OF_MEMORY; else @@ -291,157 +184,152 @@ CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) ares channel before returning error! */ } -/* - * Curl_resolver_cleanup() - * - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Destroys the ares channel. - */ -void Curl_resolver_cleanup(void *resolver) +static CURLcode async_ares_init_lazy(struct Curl_easy *data) { - ares_destroy((ares_channel)resolver); + struct async_ares_ctx *ares = &data->state.async.ares; + if(!ares->channel) + return async_ares_init(data); + return CURLE_OK; } -/* - * Curl_resolver_duphandle() - * - * Called from curl_easy_duphandle() to duplicate resolver URL-state specific - * environment ('resolver' member of the UrlState structure). Duplicates the - * 'from' ares channel and passes the resulting channel to the 'to' pointer. - */ -CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) +CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) { - (void)from; - /* - * it would be better to call ares_dup instead, but right now - * it is not possible to set 'sock_state_cb_data' outside of - * ares_init_options - */ - return Curl_resolver_init(easy, to); + struct async_ares_ctx *ares = &data->state.async.ares; + CURLcode result = CURLE_OK; + if(!ares->channel) { + result = async_ares_init(data); + } + *impl = ares->channel; + return result; } -static void destroy_async_data(struct Curl_async *async); - -/* - * Cancel all possibly still on-going resolves for this connection. - */ -void Curl_resolver_cancel(struct Curl_easy *data) -{ - DEBUGASSERT(data); - if(data->state.async.resolver) - ares_cancel((ares_channel)data->state.async.resolver); - destroy_async_data(&data->state.async); -} +static void async_ares_destroy(struct Curl_easy *data); /* - * We are equivalent to Curl_resolver_cancel() for the c-ares resolver. We - * never block. + * For asyn-ares, this is the same as abort. */ -void Curl_resolver_kill(struct Curl_easy *data) +void Curl_async_shutdown(struct Curl_easy *data) { - /* We do not need to check the resolver state because we can be called safely - at any time and we always do the same thing. */ - Curl_resolver_cancel(data); + struct async_ares_ctx *ares = &data->state.async.ares; + if(ares->channel) { + ares_cancel(ares->channel); + ares->channel = NULL; + } + async_ares_destroy(data); } /* - * destroy_async_data() cleans up async resolver data. + * async_ares_destroy() cleans up async resolver data. */ -static void destroy_async_data(struct Curl_async *async) +static void async_ares_destroy(struct Curl_easy *data) { - struct thread_data *res = &async->thdata; - if(res->temp_ai) { - Curl_freeaddrinfo(res->temp_ai); - res->temp_ai = NULL; + struct async_ares_ctx *ares = &data->state.async.ares; + if(ares->temp_ai) { + Curl_freeaddrinfo(ares->temp_ai); + ares->temp_ai = NULL; } - Curl_safefree(res->hostname); + Curl_safefree(data->state.async.hostname); } /* - * Curl_resolver_getsock() is called when someone from the outside world + * Curl_async_getsock() is called when someone from the outside world * (using curl_multi_fdset()) wants to get our fd_set setup. */ -int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks) +int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *socks) { - return Curl_ares_getsock(data, (ares_channel)data->state.async.resolver, - socks); + struct async_ares_ctx *ares = &data->state.async.ares; + DEBUGASSERT(ares->channel); + return Curl_ares_getsock(data, ares->channel, socks); } /* - * Curl_resolver_is_resolved() is called repeatedly to check if a previous + * Curl_async_is_resolved() 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_resolver_is_resolved(struct Curl_easy *data, - struct Curl_dns_entry **dns) +CURLcode Curl_async_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns) { - struct thread_data *res = &data->state.async.thdata; + struct async_ares_ctx *ares = &data->state.async.ares; CURLcode result = CURLE_OK; DEBUGASSERT(dns); *dns = NULL; - if(Curl_ares_perform((ares_channel)data->state.async.resolver, 0) < 0) + if(data->state.async.done) { + *dns = data->state.async.dns; + return CURLE_OK; + } + + if(Curl_ares_perform(ares->channel, 0) < 0) return CURLE_UNRECOVERABLE_POLL; #ifndef HAVE_CARES_GETADDRINFO /* Now that we have checked for any last minute results above, see if there are any responses still pending when the EXPIRE_HAPPY_EYEBALLS_DNS timer expires. */ - if(res->num_pending + if(ares->num_pending /* This is only set to non-zero if the timer was started. */ - && (res->happy_eyeballs_dns_time.tv_sec - || res->happy_eyeballs_dns_time.tv_usec) - && (Curl_timediff(Curl_now(), res->happy_eyeballs_dns_time) + && (ares->happy_eyeballs_dns_time.tv_sec + || ares->happy_eyeballs_dns_time.tv_usec) + && (Curl_timediff(Curl_now(), ares->happy_eyeballs_dns_time) >= HAPPY_EYEBALLS_DNS_TIMEOUT)) { /* Remember that the EXPIRE_HAPPY_EYEBALLS_DNS timer is no longer running. */ - memset( - &res->happy_eyeballs_dns_time, 0, sizeof(res->happy_eyeballs_dns_time)); + memset(&ares->happy_eyeballs_dns_time, 0, + sizeof(ares->happy_eyeballs_dns_time)); /* Cancel the raw c-ares request, which will fire query_completed_cb() with ARES_ECANCELLED synchronously for all pending responses. This will leave us with res->num_pending == 0, which is perfect for the next block. */ - ares_cancel((ares_channel)data->state.async.resolver); + ares_cancel(ares->channel); DEBUGASSERT(res->num_pending == 0); } #endif - if(!res->num_pending) { - (void)Curl_addrinfo_callback(data, res->last_status, res->temp_ai); - /* temp_ai ownership is moved to the connection, so we need not free-up - them */ - res->temp_ai = NULL; - - result = res->result; - if(!data->state.async.dns) - result = Curl_resolver_error(data); - if(!result) { - *dns = data->state.async.dns; + 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->last_status == CURL_ASYNC_SUCCESS && !result) { + data->state.async.dns = + Curl_dnscache_mk_entry(data, ares->temp_ai, + data->state.async.hostname, 0, + data->state.async.port, FALSE); + ares->temp_ai = NULL; /* temp_ai now owned by entry */ #ifdef HTTPSRR_WORKS - { - struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&res->hinfo); - if(!lhrr) - result = CURLE_OUT_OF_MEMORY; - else - (*dns)->hinfo = lhrr; - } + if(data->state.async.dns) { + struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); + if(!lhrr) + result = CURLE_OUT_OF_MEMORY; + else + data->state.async.dns->hinfo = lhrr; + } #endif + if(!result && data->state.async.dns) + result = Curl_dnscache_add(data, data->state.async.dns); } - - destroy_async_data(&data->state.async); + /* if we have not found anything, report the proper + * CURLE_COULDNT_RESOLVE_* code */ + if(!result && !data->state.async.dns) + result = Curl_resolver_error(data); + 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_destroy(data); } - return result; } /* - * Curl_resolver_wait_resolv() + * Curl_async_await() * * Waits for a resolve to finish. This function should be avoided since using * this risk getting the multi interface to "hang". @@ -451,9 +339,10 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. */ -CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **entry) +CURLcode Curl_async_await(struct Curl_easy *data, + struct Curl_dns_entry **entry) { + struct async_ares_ctx *ares = &data->state.async.ares; CURLcode result = CURLE_OK; timediff_t timeout; struct curltime now = Curl_now(); @@ -485,7 +374,7 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, store.tv_sec = itimeout/1000; store.tv_usec = (itimeout%1000)*1000; - tvp = ares_timeout((ares_channel)data->state.async.resolver, &store, &tv); + tvp = ares_timeout(ares->channel, &store, &tv); /* use the timeout period ares returned to us above if less than one second is left, otherwise just use 1000ms to make sure the progress @@ -495,11 +384,10 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, else timeout_ms = 1000; - if(Curl_ares_perform((ares_channel)data->state.async.resolver, - timeout_ms) < 0) + if(Curl_ares_perform(ares->channel, timeout_ms) < 0) return CURLE_UNRECOVERABLE_POLL; - result = Curl_resolver_is_resolved(data, entry); + result = Curl_async_is_resolved(data, entry); if(result || data->state.async.done) break; @@ -519,68 +407,62 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, if(timeout < 0) result = CURLE_OPERATION_TIMEDOUT; } - if(result) - /* failure, so we cancel the ares operation */ - ares_cancel((ares_channel)data->state.async.resolver); /* Operation complete, if the lookup was successful we now have the entry in the cache. */ + data->state.async.done = TRUE; if(entry) *entry = data->state.async.dns; if(result) - /* close the connection, since we cannot return failure here without - cleaning up this connection properly. */ - connclose(data->conn, "c-ares resolve failed"); - + ares_cancel(ares->channel); return result; } #ifndef HAVE_CARES_GETADDRINFO /* Connects results to the list */ -static void compound_results(struct thread_data *res, - struct Curl_addrinfo *ai) +static void async_addr_concat(struct Curl_addrinfo **pbase, + struct Curl_addrinfo *ai) { if(!ai) return; + /* When adding `ai` to an existing address list, we prefer ipv6 + * to be in front. */ #ifdef USE_IPV6 /* CURLRES_IPV6 */ - if(res->temp_ai && res->temp_ai->ai_family == PF_INET6) { - /* We have results already, put the new IPv6 entries at the head of the - list. */ - struct Curl_addrinfo *temp_ai_tail = res->temp_ai; - - while(temp_ai_tail->ai_next) - temp_ai_tail = temp_ai_tail->ai_next; - - temp_ai_tail->ai_next = ai; + if(*pbase && (*pbase)->ai_family == PF_INET6) { + /* ipv6 already in front, append `ai` */ + struct Curl_addrinfo *tail = *pbase; + while(tail->ai_next) + tail = tail->ai_next; + tail->ai_next = ai; } else #endif /* CURLRES_IPV6 */ { - /* Add the new results to the list of old results. */ - struct Curl_addrinfo *ai_tail = ai; - while(ai_tail->ai_next) - ai_tail = ai_tail->ai_next; - - ai_tail->ai_next = res->temp_ai; - res->temp_ai = ai; + /* prepend to the (possibly) existing list. */ + struct Curl_addrinfo *tail = ai; + while(tail->ai_next) + tail = tail->ai_next; + tail->ai_next = *pbase; + *pbase = ai; } } /* * ares_query_completed_cb() is the callback that ares will call when - * the host query initiated by ares_gethostbyname() from Curl_getaddrinfo(), - * when using ares, is completed either successfully or with failure. + * the host query initiated by ares_gethostbyname() from + * Curl_async_getaddrinfo(), when using ares, is completed either + * successfully or with failure. */ -static void query_completed_cb(void *arg, /* (struct connectdata *) */ - int status, - int timeouts, - struct hostent *hostent) +static void async_ares_hostbyname_cb(void *user_data, + int status, + int timeouts, + struct hostent *hostent) { - struct Curl_easy *data = (struct Curl_easy *)arg; - struct thread_data *res = &data->state.async.thdata; + struct Curl_easy *data = (struct Curl_easy *)user_data; + struct async_ares_ctx *ares = &data->state.async.ares; (void)timeouts; /* ignored */ @@ -589,24 +471,27 @@ static void query_completed_cb(void *arg, /* (struct connectdata *) */ be valid so only defer it when we know the 'status' says its fine! */ return; - res->num_pending--; - if(CURL_ASYNC_SUCCESS == status) { - struct Curl_addrinfo *ai = Curl_he2ai(hostent, data->state.async.port); - if(ai) { - compound_results(res, ai); - } + ares->last_status = status; /* one success overrules any error */ + async_addr_concat(&ares->temp_ai, + Curl_he2ai(hostent, data->state.async.port)); + } + else if(ares->last_status != ARES_SUCCESS) { + /* no success so far, remember error */ + ares->last_status = status; } - /* A successful result overwrites any previous error */ - if(res->last_status != ARES_SUCCESS) - res->last_status = status; + ares->num_pending--; + + CURL_TRC_DNS(data, "ares: hostbyname done, status=%d, pending=%d, " + "addr=%sfound", + status, ares->num_pending, ares->temp_ai ? "" : "not "); /* If there are responses still pending, we presume they must be the complementary IPv4 or IPv6 lookups that we started in parallel in - Curl_resolver_getaddrinfo() (for Happy Eyeballs). If we have got a + Curl_async_getaddrinfo() (for Happy Eyeballs). If we have got a "definitive" response from one of a set of parallel queries, we need to think about how long we are willing to wait for more responses. */ - if(res->num_pending + if(ares->num_pending /* Only these c-ares status values count as "definitive" for these purposes. For example, ARES_ENODATA is what we expect when there is no IPv6 entry for a domain name, and that is not a reason to get more @@ -617,7 +502,7 @@ static void query_completed_cb(void *arg, /* (struct connectdata *) */ && (status == ARES_SUCCESS || status == ARES_ENOTFOUND)) { /* Right now, there can only be up to two parallel queries, so do not bother handling any other cases. */ - DEBUGASSERT(res->num_pending == 1); + DEBUGASSERT(ares->num_pending == 1); /* it is possible that one of these parallel queries could succeed quickly, but the other could always fail or timeout (when we are @@ -656,19 +541,21 @@ static void query_completed_cb(void *arg, /* (struct connectdata *) */ timeout to prevent it. After all, we do not even know where in the c-ares retry cycle each request is. */ - res->happy_eyeballs_dns_time = Curl_now(); + ares->happy_eyeballs_dns_time = Curl_now(); Curl_expire(data, HAPPY_EYEBALLS_DNS_TIMEOUT, EXPIRE_HAPPY_EYEBALLS_DNS); } } + #else /* c-ares 1.16.0 or later */ /* - * ares2addr() converts an address list provided by c-ares to an internal - * libcurl compatible list + * async_ares_node2addr() converts an address list provided by c-ares + * to an internal libcurl compatible list. */ -static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) +static struct Curl_addrinfo * +async_ares_node2addr(struct ares_addrinfo_node *node) { /* traverse the ares_addrinfo_node list */ struct ares_addrinfo_node *ai; @@ -738,48 +625,78 @@ static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) return cafirst; } -static void addrinfo_cb(void *arg, int status, int timeouts, - struct ares_addrinfo *result) +static void async_ares_addrinfo_cb(void *user_data, int status, int timeouts, + struct ares_addrinfo *result) { - struct Curl_easy *data = (struct Curl_easy *)arg; - struct thread_data *res = &data->state.async.thdata; + struct Curl_easy *data = (struct Curl_easy *)user_data; + struct async_ares_ctx *ares = &data->state.async.ares; (void)timeouts; + CURL_TRC_DNS(data, "asyn-ares: addrinfo callback, status=%d", status); if(ARES_SUCCESS == status) { - res->temp_ai = ares2addr(result->nodes); - res->last_status = CURL_ASYNC_SUCCESS; + ares->temp_ai = async_ares_node2addr(result->nodes); + ares->last_status = CURL_ASYNC_SUCCESS; ares_freeaddrinfo(result); } - res->num_pending--; + ares->num_pending--; + CURL_TRC_DNS(data, "ares: addrinfo done, status=%d, pending=%d, " + "addr=%sfound", + status, ares->num_pending, ares->temp_ai ? "" : "not "); } #endif +#ifdef USE_HTTPSRR +static void async_ares_rr_done(void *user_data, ares_status_t status, + size_t timeouts, + const ares_dns_record_t *dnsrec) +{ + struct Curl_easy *data = user_data; + struct async_ares_ctx *ares = &data->state.async.ares; + + (void)timeouts; + --ares->num_pending; + CURL_TRC_DNS(data, "ares: httpsrr done, status=%d, pending=%d, " + "dnsres=%sfound", + status, ares->num_pending, + (dnsrec && + ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER)) ? + "" : "not "); + if((ARES_SUCCESS != status) || !dnsrec) + return; + ares->result = Curl_httpsrr_from_ares(data, dnsrec, &ares->hinfo); +} +#endif /* USE_HTTPSRR */ + /* - * Curl_resolver_getaddrinfo() - when using ares + * Curl_async_getaddrinfo() - when using ares * * Returns name information about the given hostname and port number. If * successful, the 'hostent' is returned and the fourth argument will point to * memory we need to free after use. That memory *MUST* be freed with * Curl_freeaddrinfo(), nothing else. */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp) { - struct thread_data *res = &data->state.async.thdata; + struct async_ares_ctx *ares = &data->state.async.ares; *waitp = 0; /* default to synchronous response */ - res->hostname = strdup(hostname); - if(!res->hostname) + if(async_ares_init_lazy(data)) return NULL; - data->state.async.port = port; 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 = strdup(hostname); + if(!data->state.async.hostname) + return NULL; /* initial status - failed */ - res->last_status = ARES_ENOTFOUND; + ares->last_status = ARES_ENOTFOUND; #ifdef HAVE_CARES_GETADDRINFO { @@ -788,15 +705,18 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, int pf = PF_INET; memset(&hints, 0, sizeof(hints)); #ifdef CURLRES_IPV6 - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { /* The stack seems to be IPv6-enabled */ - if(data->conn->ip_version == CURL_IPRESOLVE_V6) + if(ip_version == CURL_IPRESOLVE_V6) pf = PF_INET6; else pf = PF_UNSPEC; } #endif /* CURLRES_IPV6 */ + CURL_TRC_DNS(data, "asyn-ares: fire off getaddrinfo for %s", + (pf == PF_UNSPEC) ? "A+AAAA" : + ((pf == PF_INET) ? "A" : "AAAA")); hints.ai_family = pf; hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? SOCK_STREAM : SOCK_DGRAM; @@ -805,44 +725,43 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, */ hints.ai_flags = ARES_AI_NUMERICSERV; msnprintf(service, sizeof(service), "%d", port); - res->num_pending = 1; - ares_getaddrinfo((ares_channel)data->state.async.resolver, hostname, - service, &hints, addrinfo_cb, data); + ares->num_pending = 1; + ares_getaddrinfo(ares->channel, data->state.async.hostname, + service, &hints, async_ares_addrinfo_cb, data); } #else #ifdef HAVE_CARES_IPV6 - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { /* The stack seems to be IPv6-enabled */ - res->num_pending = 2; - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, - PF_INET, query_completed_cb, data); - ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, - PF_INET6, query_completed_cb, data); + CURL_TRC_DNS(data, "asyn-ares: fire off query for A"); + ares_gethostbyname(ares->channel, 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, + async_ares_hostbyname_cb, data); } else #endif { - res->num_pending = 1; - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.async.resolver, - hostname, PF_INET, - query_completed_cb, data); + 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, + async_ares_hostbyname_cb, data); } #endif #ifdef USE_HTTPSRR { CURL_TRC_DNS(data, "asyn-ares: fire off query for HTTPSRR"); - res->num_pending++; /* one more */ - memset(&res->hinfo, 0, sizeof(struct Curl_https_rrinfo)); - res->hinfo.port = -1; - ares_query_dnsrec((ares_channel)data->state.async.resolver, - hostname, ARES_CLASS_IN, - ARES_REC_TYPE_HTTPS, - Curl_dnsrec_done_cb, data, NULL); + memset(&ares->hinfo, 0, sizeof(ares->hinfo)); + ares->hinfo.port = -1; + ares->num_pending++; /* one more */ + ares_query_dnsrec(ares->channel, data->state.async.hostname, + ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, + async_ares_rr_done, data, NULL); } #endif *waitp = 1; /* expect asynchronous response */ @@ -853,6 +772,7 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, CURLcode Curl_set_dns_servers(struct Curl_easy *data, char *servers) { + struct async_ares_ctx *ares = &data->state.async.ares; CURLcode result = CURLE_NOT_BUILT_IN; int ares_result; @@ -860,8 +780,8 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, * default. */ if(!servers) { - Curl_resolver_cleanup(data->state.async.resolver); - result = Curl_resolver_init(data, &data->state.async.resolver); + Curl_async_shutdown(data); + result = async_ares_init_lazy(data); if(!result) { /* this now needs to restore the other options set to c-ares */ if(data->set.str[STRING_DNS_INTERFACE]) @@ -879,10 +799,9 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, #ifdef HAVE_CARES_SERVERS_CSV #ifdef HAVE_CARES_PORTS_CSV - ares_result = ares_set_servers_ports_csv(data->state.async.resolver, - servers); + ares_result = ares_set_servers_ports_csv(ares->channel, servers); #else - ares_result = ares_set_servers_csv(data->state.async.resolver, servers); + ares_result = ares_set_servers_csv(ares->channel, servers); #endif switch(ares_result) { case ARES_SUCCESS: @@ -910,10 +829,17 @@ CURLcode Curl_set_dns_interface(struct Curl_easy *data, const char *interf) { #ifdef HAVE_CARES_LOCAL_DEV + struct async_ares_ctx *ares = &data->state.async.ares; + CURLcode result; + if(!interf) interf = ""; - ares_set_local_dev((ares_channel)data->state.async.resolver, interf); + result = async_ares_init_lazy(data); + if(result) + return result; + + ares_set_local_dev(ares->channel, interf); return CURLE_OK; #else /* c-ares version too old! */ @@ -927,7 +853,9 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, const char *local_ip4) { #ifdef HAVE_CARES_SET_LOCAL + struct async_ares_ctx *ares = &data->state.async.ares; struct in_addr a4; + CURLcode result; if((!local_ip4) || (local_ip4[0] == 0)) { a4.s_addr = 0; /* disabled: do not bind to a specific address */ @@ -939,8 +867,11 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, } } - ares_set_local_ip4((ares_channel)data->state.async.resolver, - ntohl(a4.s_addr)); + result = async_ares_init_lazy(data); + if(result) + return result; + + ares_set_local_ip4(ares->channel, ntohl(a4.s_addr)); return CURLE_OK; #else /* c-ares version too old! */ @@ -954,7 +885,9 @@ CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, const char *local_ip6) { #if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6) + struct async_ares_ctx *ares = &data->state.async.ares; unsigned char a6[INET6_ADDRSTRLEN]; + CURLcode result; if((!local_ip6) || (local_ip6[0] == 0)) { /* disabled: do not bind to a specific address */ @@ -967,7 +900,11 @@ CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, } } - ares_set_local_ip6((ares_channel)data->state.async.resolver, a6); + result = async_ares_init_lazy(data); + if(result) + return result; + + ares_set_local_ip6(ares->channel, a6); return CURLE_OK; #else /* c-ares version too old! */ @@ -977,14 +914,4 @@ CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, #endif } -void Curl_resolver_set_result(struct Curl_easy *data, - struct Curl_dns_entry *dnsentry) -{ - Curl_resolver_cancel(data); - data->state.async.dns = dnsentry; - data->state.async.done = TRUE; -} - #endif /* CURLRES_ARES */ - -#endif /* USE_ARES */ diff --git a/lib/asyn-base.c b/lib/asyn-base.c new file mode 100644 index 0000000000..4b1063d45a --- /dev/null +++ b/lib/asyn-base.c @@ -0,0 +1,171 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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" + +/*********************************************************************** + * Only for builds using asynchronous name resolves + **********************************************************************/ +#ifdef CURLRES_ASYNCH + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#ifdef USE_ARES +#include +#include /* really old c-ares did not include this by + itself */ +#endif + +#include "urldata.h" +#include "asyn.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "multiif.h" +#include "select.h" +#include "share.h" +#include "url.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + + +#ifdef USE_ARES +/* common functions for c-ares and threaded resolver with HTTPSRR */ + +#ifdef USE_HTTPSRR +/* 1.28.0 and later have ares_query_dnsrec */ +#if ARES_VERSION < 0x011c00 +#error "requires c-ares 1.28.0 or newer for HTTPSRR" +#endif +#define HTTPSRR_WORKS +#else +#if ARES_VERSION < 0x010600 +#error "requires c-ares 1.6.0 or newer" +#endif +#endif + +/* + * Curl_ares_getsock() is called when the outside world (using + * curl_multi_fdset()) wants to get our fd_set setup and we are talking with + * ares. The caller must make sure that this function is only called when we + * have a working ares channel. + * + * Returns: sockets-in-use-bitmap + */ + +int Curl_ares_getsock(struct Curl_easy *data, + ares_channel channel, + curl_socket_t *socks) +{ + struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 }; + struct timeval timebuf; + int max = ares_getsock(channel, + (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); + struct timeval *timeout = ares_timeout(channel, &maxtime, &timebuf); + timediff_t milli = curlx_tvtoms(timeout); + Curl_expire(data, milli, EXPIRE_ASYNC_NAME); + return max; +} + +/* + * Curl_ares_perform() + * + * 1) Ask ares what sockets it currently plays with, then + * 2) wait for the timeout period to check for action on ares' sockets. + * 3) tell ares to act on all the sockets marked as "with action" + * + * return number of sockets it worked on, or -1 on error + */ +int Curl_ares_perform(ares_channel channel, + timediff_t timeout_ms) +{ + int nfds; + int bitmask; + ares_socket_t socks[ARES_GETSOCK_MAXNUM]; + struct pollfd pfd[ARES_GETSOCK_MAXNUM]; + int i; + int num = 0; + + if(!channel) + return 0; + + bitmask = ares_getsock(channel, socks, ARES_GETSOCK_MAXNUM); + + for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) { + pfd[i].events = 0; + pfd[i].revents = 0; + if(ARES_GETSOCK_READABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLRDNORM|POLLIN; + } + if(ARES_GETSOCK_WRITABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLWRNORM|POLLOUT; + } + if(pfd[i].events) + num++; + else + break; + } + + if(num) { + nfds = Curl_poll(pfd, (unsigned int)num, timeout_ms); + if(nfds < 0) + return -1; + } + else + nfds = 0; + + if(!nfds) + /* Call ares_process() unconditionally here, even if we simply timed out + above, as otherwise the ares name resolve will not timeout! */ + ares_process_fd(channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD); + else { + /* move through the descriptors and ask for processing on them */ + for(i = 0; i < num; i++) + ares_process_fd(channel, + (pfd[i].revents & (POLLRDNORM|POLLIN)) ? + pfd[i].fd : ARES_SOCKET_BAD, + (pfd[i].revents & (POLLWRNORM|POLLOUT)) ? + pfd[i].fd : ARES_SOCKET_BAD); + } + return nfds; +} + +#endif + +#endif /* CURLRES_ASYNCH */ diff --git a/lib/asyn-thrdd.c b/lib/asyn-thrdd.c new file mode 100644 index 0000000000..a6c4df2b82 --- /dev/null +++ b/lib/asyn-thrdd.c @@ -0,0 +1,754 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "socketpair.h" + +/*********************************************************************** + * Only for threaded name resolves builds + **********************************************************************/ +#ifdef CURLRES_THREADED + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include +#endif + +#ifdef HAVE_GETADDRINFO +# define RESOLVER_ENOMEM EAI_MEMORY /* = WSA_NOT_ENOUGH_MEMORY on Windows */ +#else +# define RESOLVER_ENOMEM SOCKENOMEM +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "url.h" +#include "multiif.h" +#include "inet_ntop.h" +#include "curl_threads.h" +#include "strdup.h" + +#ifdef USE_ARES +#include +#ifdef USE_HTTPSRR +#define USE_HTTPSRR_ARES /* the combo */ +#endif +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +/* + * Curl_async_global_init() + * Called from curl_global_init() to initialize global resolver environment. + * Does nothing here. + */ +int Curl_async_global_init(void) +{ +#if defined(USE_ARES) && defined(CARES_HAVE_ARES_LIBRARY_INIT) + if(ares_library_init(ARES_LIB_INIT_ALL)) { + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + +/* + * Curl_async_global_cleanup() + * Called from curl_global_cleanup() to destroy global resolver environment. + * Does nothing here. + */ +void Curl_async_global_cleanup(void) +{ +#if defined(USE_ARES) && defined(CARES_HAVE_ARES_LIBRARY_INIT) + ares_library_cleanup(); +#endif +} + +static void async_thrdd_destroy(struct Curl_easy *); + +CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) +{ + (void)data; + *impl = NULL; + return CURLE_OK; +} + +/* Destroy context of threaded resolver */ +static void addr_ctx_destroy(struct async_thrdd_addr_ctx *addr_ctx) +{ + if(addr_ctx) { + DEBUGASSERT(!addr_ctx->ref_count); + Curl_mutex_destroy(&addr_ctx->mutx); + free(addr_ctx->hostname); + if(addr_ctx->res) + Curl_freeaddrinfo(addr_ctx->res); +#ifndef CURL_DISABLE_SOCKETPAIR + /* + * close one end of the socket pair (may be done in resolver thread); + * the other end (for reading) is always closed in the parent thread. + */ +#ifndef USE_EVENTFD + if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) { + wakeup_close(addr_ctx->sock_pair[1]); + } +#endif +#endif + free(addr_ctx); + } +} + +/* Initialize context for threaded resolver */ +static struct async_thrdd_addr_ctx * +addr_ctx_create(const char *hostname, int port, + const struct addrinfo *hints) +{ + struct async_thrdd_addr_ctx *addr_ctx = calloc(1, sizeof(*addr_ctx)); + if(!addr_ctx) + return NULL; + + addr_ctx->thread_hnd = curl_thread_t_null; + addr_ctx->port = port; +#ifndef CURL_DISABLE_SOCKETPAIR + addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; + addr_ctx->sock_pair[1] = CURL_SOCKET_BAD; +#endif + addr_ctx->ref_count = 0; + +#ifdef HAVE_GETADDRINFO + DEBUGASSERT(hints); + addr_ctx->hints = *hints; +#else + (void) hints; +#endif + + Curl_mutex_init(&addr_ctx->mutx); + +#ifndef CURL_DISABLE_SOCKETPAIR + /* create socket pair or pipe */ + if(wakeup_create(addr_ctx->sock_pair, FALSE) < 0) { + addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; + addr_ctx->sock_pair[1] = CURL_SOCKET_BAD; + goto err_exit; + } +#endif + addr_ctx->sock_error = CURL_ASYNC_SUCCESS; + + /* Copying hostname string because original can be destroyed by parent + * thread during gethostbyname execution. + */ + addr_ctx->hostname = strdup(hostname); + if(!addr_ctx->hostname) + goto err_exit; + + addr_ctx->ref_count = 1; + return addr_ctx; + +err_exit: +#ifndef CURL_DISABLE_SOCKETPAIR + if(addr_ctx->sock_pair[0] != CURL_SOCKET_BAD) { + wakeup_close(addr_ctx->sock_pair[0]); + addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; + } +#endif + addr_ctx_destroy(addr_ctx); + return NULL; +} + +#ifdef HAVE_GETADDRINFO + +/* + * getaddrinfo_thread() resolves a name and then exits. + * + * For builds without ARES, but with USE_IPV6, create a resolver thread + * and wait on it. + */ +static +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) +DWORD +#else +unsigned int +#endif +CURL_STDCALL getaddrinfo_thread(void *arg) +{ + struct async_thrdd_addr_ctx *addr_ctx = arg; + char service[12]; + int rc; + bool all_gone; + + msnprintf(service, sizeof(service), "%d", addr_ctx->port); + + rc = Curl_getaddrinfo_ex(addr_ctx->hostname, service, + &addr_ctx->hints, &addr_ctx->res); + + if(rc) { + addr_ctx->sock_error = SOCKERRNO ? SOCKERRNO : rc; + if(addr_ctx->sock_error == 0) + addr_ctx->sock_error = RESOLVER_ENOMEM; + } + else { + Curl_addrinfo_set_port(addr_ctx->res, addr_ctx->port); + } + + Curl_mutex_acquire(&addr_ctx->mutx); + if(addr_ctx->ref_count > 1) { + /* Someone still waiting on our results. */ +#ifndef CURL_DISABLE_SOCKETPAIR + if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) { +#ifdef USE_EVENTFD + const uint64_t buf[1] = { 1 }; +#else + const char buf[1] = { 1 }; +#endif + /* DNS has been resolved, signal client task */ + if(wakeup_write(addr_ctx->sock_pair[1], buf, sizeof(buf)) < 0) { + /* update sock_erro to errno */ + addr_ctx->sock_error = SOCKERRNO; + } + } +#endif + } + /* thread gives up its reference to the shared data now. */ + --addr_ctx->ref_count; + all_gone = !addr_ctx->ref_count; + Curl_mutex_release(&addr_ctx->mutx); + if(all_gone) + addr_ctx_destroy(addr_ctx); + + return 0; +} + +#else /* HAVE_GETADDRINFO */ + +/* + * gethostbyname_thread() resolves a name and then exits. + */ +static +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) +DWORD +#else +unsigned int +#endif +CURL_STDCALL gethostbyname_thread(void *arg) +{ + struct async_thrdd_addr_ctx *addr_ctx = arg; + bool all_gone; + + addr_ctx->res = Curl_ipv4_resolve_r(addr_ctx->hostname, addr_ctx->port); + + if(!addr_ctx->res) { + addr_ctx->sock_error = SOCKERRNO; + if(addr_ctx->sock_error == 0) + addr_ctx->sock_error = RESOLVER_ENOMEM; + } + + Curl_mutex_acquire(&addr_ctx->mutx); + /* thread gives up its reference to the shared data now. */ + --addr_ctx->ref_count; + all_gone = !addr_ctx->ref_count;; + Curl_mutex_release(&addr_ctx->mutx); + if(all_gone) + addr_ctx_destroy(addr_ctx); + + return 0; +} + +#endif /* HAVE_GETADDRINFO */ + +/* + * async_thrdd_destroy() cleans up async resolver data and thread handle. + */ +static void async_thrdd_destroy(struct Curl_easy *data) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + struct async_thrdd_addr_ctx *addr = thrdd->addr; +#ifdef USE_HTTPSRR_ARES + if(thrdd->rr.channel) { + ares_destroy(thrdd->rr.channel); + thrdd->rr.channel = NULL; + } + Curl_httpsrr_cleanup(&thrdd->rr.hinfo); +#endif + + if(addr) { +#ifndef CURL_DISABLE_SOCKETPAIR + curl_socket_t sock_rd = addr->sock_pair[0]; +#endif + bool done; + + /* Release our reference to the data shared with the thread. */ + Curl_mutex_acquire(&addr->mutx); + thrdd->addr = NULL; + --addr->ref_count; + CURL_TRC_DNS(data, "resolve, destroy async data, shared ref=%d", + addr->ref_count); + done = !addr->ref_count; + Curl_mutex_release(&addr->mutx); + + if(!done) { + /* thread is still running. Detach the thread, it will + * trigger the cleanup when it releases its reference. */ + Curl_thread_destroy(&addr->thread_hnd); + } + else { + /* thread has released its reference, join it and + * release the memory we shared with it. */ + if(addr->thread_hnd != curl_thread_t_null) + Curl_thread_join(&addr->thread_hnd); + addr_ctx_destroy(addr); + } +#ifndef CURL_DISABLE_SOCKETPAIR + /* + * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE + * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL + */ + Curl_multi_will_close(data, sock_rd); + wakeup_close(sock_rd); +#endif + } + + Curl_safefree(data->state.async.hostname); +} + +#ifdef USE_HTTPSRR_ARES + +static void async_thrdd_rr_done(void *user_data, ares_status_t status, + size_t timeouts, + const ares_dns_record_t *dnsrec) +{ + struct Curl_easy *data = user_data; + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + + (void)timeouts; + thrdd->rr.done = TRUE; + if((ARES_SUCCESS != status) || !dnsrec) + return; + thrdd->rr.result = Curl_httpsrr_from_ares(data, dnsrec, &thrdd->rr.hinfo); +} + +static CURLcode async_rr_start(struct Curl_easy *data) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + int status; + + DEBUGASSERT(!thrdd->rr.channel); + status = ares_init_options(&thrdd->rr.channel, NULL, 0); + if(status != ARES_SUCCESS) { + thrdd->rr.channel = NULL; + return CURLE_FAILED_INIT; + } + + memset(&thrdd->rr.hinfo, 0, sizeof(thrdd->rr.hinfo)); + thrdd->rr.hinfo.port = -1; + ares_query_dnsrec(thrdd->rr.channel, + data->conn->host.name, ARES_CLASS_IN, + ARES_REC_TYPE_HTTPS, + async_thrdd_rr_done, data, NULL); + return CURLE_OK; +} +#endif + +/* + * async_thrdd_init() starts a new thread that performs the actual + * resolve. This function returns before the resolve is done. + * + * 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, + const struct addrinfo *hints) +{ + struct async_thrdd_ctx *thrdd = &data->state.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); +#ifdef USE_HTTPSRR_ARES + 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; + data->state.async.hostname = strdup(hostname); + if(!data->state.async.hostname) + goto err_exit; + + addr_ctx = addr_ctx_create(hostname, port, hints); + if(!addr_ctx) + goto err_exit; + thrdd->addr = addr_ctx; + + Curl_mutex_acquire(&addr_ctx->mutx); + DEBUGASSERT(addr_ctx->ref_count == 1); + /* passing addr_ctx to the thread adds a reference */ + addr_ctx->start = Curl_now(); + ++addr_ctx->ref_count; +#ifdef HAVE_GETADDRINFO + addr_ctx->thread_hnd = Curl_thread_create(getaddrinfo_thread, addr_ctx); +#else + addr_ctx->thread_hnd = Curl_thread_create(gethostbyname_thread, addr_ctx); +#endif + if(addr_ctx->thread_hnd == curl_thread_t_null) { + /* The thread never started, remove its reference that never happened. */ + --addr_ctx->ref_count; + err = errno; + Curl_mutex_release(&addr_ctx->mutx); + goto err_exit; + } + Curl_mutex_release(&addr_ctx->mutx); + +#ifdef USE_HTTPSRR_ARES + if(async_rr_start(data)) + infof(data, "Failed HTTPS RR operation"); +#endif + CURL_TRC_DNS(data, "resolve thread started for of %s:%d", hostname, port); + return TRUE; + +err_exit: + CURL_TRC_DNS(data, "resolve thread failed init: %d", err); + async_thrdd_destroy(data); + CURL_SETERRNO(err); + return FALSE; +} + +/* + * '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_dns_entry **entry) +{ + CURLcode result = CURLE_OK; + + DEBUGASSERT(addr_ctx->thread_hnd != curl_thread_t_null); + + CURL_TRC_DNS(data, "resolve, wait for thread to finish"); + /* wait for the thread to resolve the name */ + if(Curl_thread_join(&addr_ctx->thread_hnd)) { + if(entry) + result = Curl_async_is_resolved(data, entry); + } + else + DEBUGASSERT(0); + + data->state.async.done = TRUE; + if(entry) + *entry = data->state.async.dns; + + async_thrdd_destroy(data); + return result; +} + + +/* + * Until we gain a way to signal the resolver threads to stop early, we must + * simply wait for them and ignore their results. + */ +void Curl_async_shutdown(struct Curl_easy *data) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + + /* If we are still resolving, we must wait for the threads to fully clean up, + unfortunately. Otherwise, we can simply cancel to clean up any resolver + data. */ + if(thrdd->addr && (thrdd->addr->thread_hnd != curl_thread_t_null) && + !data->set.quick_exit) + (void)asyn_thrdd_await(data, thrdd->addr, NULL); + else + async_thrdd_destroy(data); +} + +/* + * Curl_async_await() + * + * Waits for a resolve to finish. This function should be avoided since using + * this risk getting the multi interface to "hang". + * + * If 'entry' is non-NULL, make it point to the resolved dns entry + * + * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, + * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. + * + * This is the version for resolves-in-a-thread. + */ +CURLcode Curl_async_await(struct Curl_easy *data, + struct Curl_dns_entry **entry) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + if(thrdd->addr) + return asyn_thrdd_await(data, thrdd->addr, entry); + return CURLE_FAILED_INIT; +} + +/* + * Curl_async_is_resolved() 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) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + bool 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; + } + +#ifdef USE_HTTPSRR_ARES + /* best effort, ignore errors */ + if(thrdd->rr.channel) + (void)Curl_ares_perform(thrdd->rr.channel, 0); +#endif + + DEBUGASSERT(thrdd->addr); + if(!thrdd->addr) + return CURLE_FAILED_INIT; + + Curl_mutex_acquire(&thrdd->addr->mutx); + done = (thrdd->addr->ref_count == 1); + Curl_mutex_release(&thrdd->addr->mutx); + + if(done) { + CURLcode result = CURLE_OK; + + data->state.async.done = TRUE; + Curl_resolv_unlink(data, &data->state.async.dns); + + if(thrdd->addr->res) { + data->state.async.dns = + Curl_dnscache_mk_entry(data, thrdd->addr->res, + data->state.async.hostname, 0, + data->state.async.port, FALSE); + thrdd->addr->res = NULL; + if(!data->state.async.dns) + result = CURLE_OUT_OF_MEMORY; + +#ifdef USE_HTTPSRR_ARES + if(thrdd->rr.channel) { + result = thrdd->rr.result; + if(!result) { + struct Curl_https_rrinfo *lhrr; + lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo); + if(!lhrr) { + async_thrdd_destroy(data); + return CURLE_OUT_OF_MEMORY; + } + data->state.async.dns->hinfo = lhrr; + } + } +#endif + if(!result && data->state.async.dns) + result = Curl_dnscache_add(data, data->state.async.dns); + } + + if(!result && !data->state.async.dns) + result = Curl_resolver_error(data); + 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_destroy(data); + return result; + } + else { + /* poll for name lookup done with exponential backoff up to 250ms */ + /* should be fine even if this converts to 32-bit */ + timediff_t elapsed = Curl_timediff(Curl_now(), + data->progress.t_startsingle); + if(elapsed < 0) + elapsed = 0; + + if(thrdd->addr->poll_interval == 0) + /* Start at 1ms poll interval */ + thrdd->addr->poll_interval = 1; + else if(elapsed >= thrdd->addr->interval_end) + /* Back-off exponentially if last interval expired */ + thrdd->addr->poll_interval *= 2; + + if(thrdd->addr->poll_interval > 250) + thrdd->addr->poll_interval = 250; + + thrdd->addr->interval_end = elapsed + thrdd->addr->poll_interval; + Curl_expire(data, thrdd->addr->poll_interval, EXPIRE_ASYNC_NAME); + return CURLE_OK; + } +} + +int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + int ret_val = 0; +#if !defined(CURL_DISABLE_SOCKETPAIR) || defined(USE_HTTPSRR_ARES) + int socketi = 0; +#else + (void)socks; +#endif + +#ifdef USE_HTTPSRR_ARES + if(thrdd->rr.channel) { + ret_val = Curl_ares_getsock(data, thrdd->rr.channel, socks); + for(socketi = 0; socketi < (MAX_SOCKSPEREASYHANDLE - 1); socketi++) + if(!ARES_GETSOCK_READABLE(ret_val, socketi) && + !ARES_GETSOCK_WRITABLE(ret_val, socketi)) + break; + } +#endif + if(!thrdd->addr) + return ret_val; + +#ifndef CURL_DISABLE_SOCKETPAIR + if(thrdd->addr) { + /* return read fd to client for polling the DNS resolution status */ + socks[socketi] = thrdd->addr->sock_pair[0]; + ret_val |= GETSOCK_READSOCK(socketi); + } + else +#endif + { + timediff_t milli; + timediff_t ms = Curl_timediff(Curl_now(), thrdd->addr->start); + if(ms < 3) + milli = 0; + else if(ms <= 50) + milli = ms/3; + else if(ms <= 250) + milli = 50; + else + milli = 200; + Curl_expire(data, milli, EXPIRE_ASYNC_NAME); + } + + return ret_val; +} + +#ifndef HAVE_GETADDRINFO +/* + * Curl_async_getaddrinfo() - for platforms without getaddrinfo + */ +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp) +{ + (void)ip_version; + *waitp = 0; /* default to synchronous response */ + + /* fire up a new resolver thread! */ + if(async_thrdd_init(data, hostname, port, ip_version, NULL)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + failf(data, "getaddrinfo() thread failed"); + + return NULL; +} + +#else /* !HAVE_GETADDRINFO */ + +/* + * Curl_async_getaddrinfo() - for getaddrinfo + */ +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp) +{ + struct addrinfo hints; + int pf = PF_INET; + *waitp = 0; /* default to synchronous response */ + + CURL_TRC_DNS(data, "init threaded resolve of %s:%d", hostname, port); +#ifdef CURLRES_IPV6 + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + if(ip_version == CURL_IPRESOLVE_V6) + pf = PF_INET6; + else + pf = PF_UNSPEC; + } +#else + (void)ip_version; +#endif /* CURLRES_IPV6 */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? + SOCK_STREAM : SOCK_DGRAM; + + /* fire up a new resolver thread! */ + if(async_thrdd_init(data, hostname, port, ip_version, &hints)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + failf(data, "getaddrinfo() thread failed to start"); + return NULL; + +} + +#endif /* !HAVE_GETADDRINFO */ + +#endif /* CURLRES_THREADED */ diff --git a/lib/asyn-thread.c b/lib/asyn-thread.c deleted file mode 100644 index a10f64ee2c..0000000000 --- a/lib/asyn-thread.c +++ /dev/null @@ -1,797 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , 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 "socketpair.h" - -/*********************************************************************** - * Only for threaded name resolves builds - **********************************************************************/ -#ifdef CURLRES_THREADED - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) -# include -#endif - -#ifdef HAVE_GETADDRINFO -# define RESOLVER_ENOMEM EAI_MEMORY /* = WSA_NOT_ENOUGH_MEMORY on Windows */ -#else -# define RESOLVER_ENOMEM SOCKENOMEM -#endif - -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "hash.h" -#include "share.h" -#include "url.h" -#include "multiif.h" -#include "inet_ntop.h" -#include "curl_threads.h" -#include "connect.h" -#include "strdup.h" - -#ifdef USE_ARES -#include -#ifdef USE_HTTPSRR -#define USE_HTTPSRR_ARES /* the combo */ -#endif -#endif - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - - -/* - * Curl_resolver_global_init() - * Called from curl_global_init() to initialize global resolver environment. - * Does nothing here. - */ -int Curl_resolver_global_init(void) -{ - return CURLE_OK; -} - -/* - * Curl_resolver_global_cleanup() - * Called from curl_global_cleanup() to destroy global resolver environment. - * Does nothing here. - */ -void Curl_resolver_global_cleanup(void) -{ -} - -/* - * Curl_resolver_init() - * Called from curl_easy_init() -> Curl_open() to initialize resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). - */ -CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) -{ - (void)easy; - (void)resolver; - return CURLE_OK; -} - -/* - * Curl_resolver_cleanup() - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). - */ -void Curl_resolver_cleanup(void *resolver) -{ - (void)resolver; -} - -/* - * Curl_resolver_duphandle() - * Called from curl_easy_duphandle() to duplicate resolver URL state-specific - * environment ('resolver' member of the UrlState structure). - */ -CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) -{ - (void)from; - return Curl_resolver_init(easy, to); -} - -static void destroy_async_data(struct Curl_easy *); - -/* - * Cancel all possibly still on-going resolves for this connection. - */ -void Curl_resolver_cancel(struct Curl_easy *data) -{ - destroy_async_data(data); -} - -static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data) -{ - return data->state.async.thdata.tsd; -} - -/* Destroy resolver thread synchronization data */ -static -void destroy_thread_sync_data(struct thread_sync_data *tsd) -{ - if(tsd) { - DEBUGASSERT(!tsd->ref_count); - Curl_mutex_destroy(&tsd->mutx); - free(tsd->hostname); - if(tsd->res) - Curl_freeaddrinfo(tsd->res); -#ifndef CURL_DISABLE_SOCKETPAIR - /* - * close one end of the socket pair (may be done in resolver thread); - * the other end (for reading) is always closed in the parent thread. - */ -#ifndef USE_EVENTFD - if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { - wakeup_close(tsd->sock_pair[1]); - } -#endif -#endif - free(tsd); - } -} - -/* Initialize resolver thread synchronization data */ -static -int init_thread_sync_data(struct thread_data *td, - const char *hostname, - int port, - const struct addrinfo *hints) -{ - struct thread_sync_data *tsd; - - DEBUGASSERT(!td->tsd); - tsd = calloc(1, sizeof(*tsd)); - if(!tsd) - return 0; - - tsd->port = port; -#ifndef CURL_DISABLE_SOCKETPAIR - tsd->sock_pair[0] = CURL_SOCKET_BAD; - tsd->sock_pair[1] = CURL_SOCKET_BAD; -#endif - tsd->ref_count = 0; - -#ifdef HAVE_GETADDRINFO - DEBUGASSERT(hints); - tsd->hints = *hints; -#else - (void) hints; -#endif - - Curl_mutex_init(&tsd->mutx); - -#ifndef CURL_DISABLE_SOCKETPAIR - /* create socket pair or pipe */ - if(wakeup_create(tsd->sock_pair, FALSE) < 0) { - tsd->sock_pair[0] = CURL_SOCKET_BAD; - tsd->sock_pair[1] = CURL_SOCKET_BAD; - goto err_exit; - } -#endif - tsd->sock_error = CURL_ASYNC_SUCCESS; - - /* Copying hostname string because original can be destroyed by parent - * thread during gethostbyname execution. - */ - tsd->hostname = strdup(hostname); - if(!tsd->hostname) - goto err_exit; - - td->init = TRUE; - td->tsd = tsd; - tsd->ref_count = 1; - return 1; - -err_exit: -#ifndef CURL_DISABLE_SOCKETPAIR - if(tsd->sock_pair[0] != CURL_SOCKET_BAD) { - wakeup_close(tsd->sock_pair[0]); - tsd->sock_pair[0] = CURL_SOCKET_BAD; - } -#endif - destroy_thread_sync_data(tsd); - return 0; -} - -static CURLcode getaddrinfo_complete(struct Curl_easy *data) -{ - struct thread_sync_data *tsd = conn_thread_sync_data(data); - CURLcode result; - - result = Curl_addrinfo_callback(data, tsd->sock_error, tsd->res); - /* The tsd->res structure has been copied to async.dns and perhaps the DNS - cache. Set our copy to NULL so destroy_thread_sync_data does not free it. - */ - tsd->res = NULL; - - return result; -} - - -#ifdef HAVE_GETADDRINFO - -/* - * getaddrinfo_thread() resolves a name and then exits. - * - * For builds without ARES, but with USE_IPV6, create a resolver thread - * and wait on it. - */ -static -#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) -DWORD -#else -unsigned int -#endif -CURL_STDCALL getaddrinfo_thread(void *arg) -{ - struct thread_sync_data *tsd = arg; - char service[12]; - int rc; - bool all_gone; - - msnprintf(service, sizeof(service), "%d", tsd->port); - - rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res); - - if(rc) { - tsd->sock_error = SOCKERRNO ? SOCKERRNO : rc; - if(tsd->sock_error == 0) - tsd->sock_error = RESOLVER_ENOMEM; - } - else { - Curl_addrinfo_set_port(tsd->res, tsd->port); - } - - Curl_mutex_acquire(&tsd->mutx); - if(tsd->ref_count > 1) { - /* Someone still waiting on our results. */ -#ifndef CURL_DISABLE_SOCKETPAIR - if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { -#ifdef USE_EVENTFD - const uint64_t buf[1] = { 1 }; -#else - const char buf[1] = { 1 }; -#endif - /* DNS has been resolved, signal client task */ - if(wakeup_write(tsd->sock_pair[1], buf, sizeof(buf)) < 0) { - /* update sock_erro to errno */ - tsd->sock_error = SOCKERRNO; - } - } -#endif - } - /* thread gives up its reference to the shared data now. */ - --tsd->ref_count; - all_gone = !tsd->ref_count; - Curl_mutex_release(&tsd->mutx); - if(all_gone) - destroy_thread_sync_data(tsd); - - return 0; -} - -#else /* HAVE_GETADDRINFO */ - -/* - * gethostbyname_thread() resolves a name and then exits. - */ -static -#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) -DWORD -#else -unsigned int -#endif -CURL_STDCALL gethostbyname_thread(void *arg) -{ - struct thread_sync_data *tsd = arg; - bool all_gone; - - tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port); - - if(!tsd->res) { - tsd->sock_error = SOCKERRNO; - if(tsd->sock_error == 0) - tsd->sock_error = RESOLVER_ENOMEM; - } - - Curl_mutex_acquire(&tsd->mutx); - /* thread gives up its reference to the shared data now. */ - --tsd->ref_count; - all_gone = !tsd->ref_count;; - Curl_mutex_release(&tsd->mutx); - if(all_gone) - destroy_thread_sync_data(tsd); - - return 0; -} - -#endif /* HAVE_GETADDRINFO */ - -/* - * destroy_async_data() cleans up async resolver data and thread handle. - */ -static void destroy_async_data(struct Curl_easy *data) -{ - struct Curl_async *async = &data->state.async; - struct thread_data *td = &async->thdata; - - if(td->init) { - bool done; - -#ifdef USE_HTTPSRR_ARES - if(td->channel) { - ares_destroy(td->channel); - td->channel = NULL; - } -#endif - - if(td->tsd) { -#ifndef CURL_DISABLE_SOCKETPAIR - curl_socket_t sock_rd = td->tsd->sock_pair[0]; -#endif - /* Release our reference to the data shared with the thread. */ - Curl_mutex_acquire(&td->tsd->mutx); - --td->tsd->ref_count; - CURL_TRC_DNS(data, "resolve, destroy async data, shared ref=%d", - td->tsd->ref_count); - done = !td->tsd->ref_count; - Curl_mutex_release(&td->tsd->mutx); - - if(!done) { - /* thread is still running. Detach the thread, it will - * trigger the cleanup when it releases its reference. */ - Curl_thread_destroy(&td->thread_hnd); - } - else { - /* thread has released its reference, join it and - * release the memory we shared with it. */ - if(td->thread_hnd != curl_thread_t_null) - Curl_thread_join(&td->thread_hnd); - destroy_thread_sync_data(td->tsd); - } - td->tsd = NULL; -#ifndef CURL_DISABLE_SOCKETPAIR - /* - * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE - * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL - */ - Curl_multi_will_close(data, sock_rd); - wakeup_close(sock_rd); -#endif - } - - td->init = FALSE; - } -} - -#ifdef USE_HTTPSRR_ARES -static CURLcode resolve_httpsrr(struct Curl_easy *data, - struct Curl_async *async) -{ - int status = ares_init_options(&async->thdata.channel, NULL, 0); - if(status != ARES_SUCCESS) - return CURLE_FAILED_INIT; - - memset(&async->thdata.hinfo, 0, sizeof(struct Curl_https_rrinfo)); - async->thdata.hinfo.port = -1; - ares_query_dnsrec(async->thdata.channel, - data->conn->host.name, ARES_CLASS_IN, - ARES_REC_TYPE_HTTPS, - Curl_dnsrec_done_cb, data, NULL); - - return CURLE_OK; -} -#endif - -/* - * init_resolve_thread() starts a new thread that performs the actual - * resolve. This function returns before the resolve is done. - * - * Returns FALSE in case of failure, otherwise TRUE. - */ -static bool init_resolve_thread(struct Curl_easy *data, - const char *hostname, int port, - const struct addrinfo *hints) -{ - struct thread_data *td = &data->state.async.thdata; - /* !checksrc! disable ERRNOVAR 1 */ - int err = ENOMEM; - struct Curl_async *async = &data->state.async; - - if(async->done && td->tsd) { - CURL_TRC_DNS(data, "starting new resolve, with previous not cleaned up" - " for '%s:%d'", td->tsd->hostname, td->tsd->port); - destroy_async_data(data); - DEBUGASSERT(!td->tsd); - } - - async->port = port; - async->done = FALSE; - async->dns = NULL; - td->thread_hnd = curl_thread_t_null; - td->start = Curl_now(); - - if(!init_thread_sync_data(td, hostname, port, hints)) - goto err_exit; - DEBUGASSERT(td->tsd); - - Curl_mutex_acquire(&td->tsd->mutx); - DEBUGASSERT(td->tsd->ref_count == 1); - /* passing td->tsd to the thread adds a reference */ - ++td->tsd->ref_count; -#ifdef HAVE_GETADDRINFO - td->thread_hnd = Curl_thread_create(getaddrinfo_thread, td->tsd); -#else - td->thread_hnd = Curl_thread_create(gethostbyname_thread, td->tsd); -#endif - if(td->thread_hnd == curl_thread_t_null) { - /* The thread never started, remove its reference that never happened. */ - --td->tsd->ref_count; - err = errno; - Curl_mutex_release(&td->tsd->mutx); - goto err_exit; - } - -#ifdef USE_HTTPSRR_ARES - if(resolve_httpsrr(data, async)) - infof(data, "Failed HTTPS RR operation"); -#endif - Curl_mutex_release(&td->tsd->mutx); - CURL_TRC_DNS(data, "resolve thread started for of %s:%d", hostname, port); - return TRUE; - -err_exit: - CURL_TRC_DNS(data, "resolve thread failed init: %d", err); - destroy_async_data(data); - CURL_SETERRNO(err); - return FALSE; -} - -/* - * 'entry' may be NULL and then no data is returned - */ -static CURLcode thread_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **entry, - bool report) -{ - struct thread_data *td; - CURLcode result = CURLE_OK; - - DEBUGASSERT(data); - td = &data->state.async.thdata; - DEBUGASSERT(td); - DEBUGASSERT(td->thread_hnd != curl_thread_t_null); - - CURL_TRC_DNS(data, "resolve, wait for thread to finish"); - /* wait for the thread to resolve the name */ - if(Curl_thread_join(&td->thread_hnd)) { - if(entry) - result = getaddrinfo_complete(data); - } - else - DEBUGASSERT(0); - - data->state.async.done = TRUE; - - if(entry) - *entry = data->state.async.dns; - - if(!data->state.async.dns && report) - /* a name was not resolved, report error */ - result = Curl_resolver_error(data); - - destroy_async_data(data); - - if(!data->state.async.dns && report) - connclose(data->conn, "asynch resolve failed"); - - return result; -} - - -/* - * Until we gain a way to signal the resolver threads to stop early, we must - * simply wait for them and ignore their results. - */ -void Curl_resolver_kill(struct Curl_easy *data) -{ - struct thread_data *td = &data->state.async.thdata; - - /* If we are still resolving, we must wait for the threads to fully clean up, - unfortunately. Otherwise, we can simply cancel to clean up any resolver - data. */ - if((td->thread_hnd != curl_thread_t_null) && !data->set.quick_exit) - (void)thread_wait_resolv(data, NULL, FALSE); - else - Curl_resolver_cancel(data); -} - -/* - * Curl_resolver_wait_resolv() - * - * Waits for a resolve to finish. This function should be avoided since using - * this risk getting the multi interface to "hang". - * - * If 'entry' is non-NULL, make it point to the resolved dns entry - * - * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, - * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. - * - * This is the version for resolves-in-a-thread. - */ -CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **entry) -{ - return thread_wait_resolv(data, entry, TRUE); -} - -/* - * Curl_resolver_is_resolved() 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_resolver_is_resolved(struct Curl_easy *data, - struct Curl_dns_entry **entry) -{ - struct thread_data *td = &data->state.async.thdata; - bool done = FALSE; - - DEBUGASSERT(entry); - *entry = NULL; - -#ifdef USE_HTTPSRR_ARES - (void)Curl_ares_perform(td->channel, 0); /* ignore errors */ -#endif - - DEBUGASSERT(td->tsd); - if(!td->tsd) - return CURLE_FAILED_INIT; - - Curl_mutex_acquire(&td->tsd->mutx); - done = (td->tsd->ref_count == 1); - Curl_mutex_release(&td->tsd->mutx); - - CURL_TRC_DNS(data, "resolve, thread %sfinished", done ? "" : "not "); - if(done) { - CURLcode result = td->result; - getaddrinfo_complete(data); - - if(!result && !data->state.async.dns) - result = Curl_resolver_error(data); - - if(result) { - destroy_async_data(data); - return result; - } -#ifdef USE_HTTPSRR_ARES - { - struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&td->hinfo); - if(!lhrr) { - destroy_async_data(data); - return CURLE_OUT_OF_MEMORY; - } - data->state.async.dns->hinfo = lhrr; - } -#endif - destroy_async_data(data); - *entry = data->state.async.dns; - } - else { - /* poll for name lookup done with exponential backoff up to 250ms */ - /* should be fine even if this converts to 32-bit */ - timediff_t elapsed = Curl_timediff(Curl_now(), - data->progress.t_startsingle); - if(elapsed < 0) - elapsed = 0; - - if(td->poll_interval == 0) - /* Start at 1ms poll interval */ - td->poll_interval = 1; - else if(elapsed >= td->interval_end) - /* Back-off exponentially if last interval expired */ - td->poll_interval *= 2; - - if(td->poll_interval > 250) - td->poll_interval = 250; - - td->interval_end = elapsed + td->poll_interval; - Curl_expire(data, td->poll_interval, EXPIRE_ASYNC_NAME); - } - - return CURLE_OK; -} - -int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks) -{ - int ret_val = 0; - struct thread_data *td = &data->state.async.thdata; -#if !defined(CURL_DISABLE_SOCKETPAIR) || defined(USE_HTTPSRR_ARES) - int socketi = 0; -#else - (void)socks; -#endif - -#ifdef USE_HTTPSRR_ARES - if(td->init && td->channel) { - ret_val = Curl_ares_getsock(data, td->channel, socks); - for(socketi = 0; socketi < (MAX_SOCKSPEREASYHANDLE - 1); socketi++) - if(!ARES_GETSOCK_READABLE(ret_val, socketi) && - !ARES_GETSOCK_WRITABLE(ret_val, socketi)) - break; - } -#endif -#ifndef CURL_DISABLE_SOCKETPAIR - if(td->init && td->tsd) { - /* return read fd to client for polling the DNS resolution status */ - socks[socketi] = td->tsd->sock_pair[0]; - ret_val |= GETSOCK_READSOCK(socketi); - } - else -#endif - { - timediff_t milli; - timediff_t ms = Curl_timediff(Curl_now(), td->start); - if(ms < 3) - milli = 0; - else if(ms <= 50) - milli = ms/3; - else if(ms <= 250) - milli = 50; - else - milli = 200; - Curl_expire(data, milli, EXPIRE_ASYNC_NAME); - } - - return ret_val; -} - -#ifndef HAVE_GETADDRINFO -/* - * Curl_getaddrinfo() - for platforms without getaddrinfo - */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) -{ - *waitp = 0; /* default to synchronous response */ - - /* fire up a new resolver thread! */ - if(init_resolve_thread(data, hostname, port, NULL)) { - *waitp = 1; /* expect asynchronous response */ - return NULL; - } - - failf(data, "getaddrinfo() thread failed"); - - return NULL; -} - -#else /* !HAVE_GETADDRINFO */ - -/* - * Curl_resolver_getaddrinfo() - for getaddrinfo - */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) -{ - struct addrinfo hints; - int pf = PF_INET; - *waitp = 0; /* default to synchronous response */ - - CURL_TRC_DNS(data, "init threaded resolve of %s:%d", hostname, port); -#ifdef CURLRES_IPV6 - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { - /* The stack seems to be IPv6-enabled */ - if(data->conn->ip_version == CURL_IPRESOLVE_V6) - pf = PF_INET6; - else - pf = PF_UNSPEC; - } -#endif /* CURLRES_IPV6 */ - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = pf; - hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? - SOCK_STREAM : SOCK_DGRAM; - - /* fire up a new resolver thread! */ - if(init_resolve_thread(data, hostname, port, &hints)) { - *waitp = 1; /* expect asynchronous response */ - return NULL; - } - - failf(data, "getaddrinfo() thread failed to start"); - return NULL; - -} - -#endif /* !HAVE_GETADDRINFO */ - -void Curl_resolver_set_result(struct Curl_easy *data, - struct Curl_dns_entry *dnsentry) -{ - destroy_async_data(data); - data->state.async.dns = dnsentry; - data->state.async.done = TRUE; -} - -CURLcode Curl_set_dns_servers(struct Curl_easy *data, - char *servers) -{ - (void)data; - (void)servers; - return CURLE_NOT_BUILT_IN; - -} - -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf) -{ - (void)data; - (void)interf; - return CURLE_NOT_BUILT_IN; -} - -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4) -{ - (void)data; - (void)local_ip4; - return CURLE_NOT_BUILT_IN; -} - -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6) -{ - (void)data; - (void)local_ip6; - return CURLE_NOT_BUILT_IN; -} - -#endif /* CURLRES_THREADED */ diff --git a/lib/asyn.h b/lib/asyn.h index ab7a973d15..7bc791f3c4 100644 --- a/lib/asyn.h +++ b/lib/asyn.h @@ -25,6 +25,9 @@ ***************************************************************************/ #include "curl_setup.h" + +#ifdef CURLRES_ASYNCH + #include "curl_addrinfo.h" #include "httpsrr.h" @@ -34,72 +37,10 @@ struct Curl_easy; struct connectdata; struct Curl_dns_entry; -#ifdef CURLRES_THREADED -#include "curl_threads.h" - -/* Data for synchronization between resolver thread and its parent */ -struct thread_sync_data { - char *hostname; /* hostname to resolve, Curl_async.hostname - duplicate */ - curl_mutex_t mutx; -#ifndef CURL_DISABLE_SOCKETPAIR - curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */ -#endif - struct Curl_addrinfo *res; -#ifdef HAVE_GETADDRINFO - struct addrinfo hints; -#endif - int port; - int sock_error; - int ref_count; -}; - -struct thread_data { - curl_thread_t thread_hnd; - unsigned int poll_interval; - timediff_t interval_end; - struct curltime start; - struct thread_sync_data *tsd; - CURLcode result; /* CURLE_OK or error handling response */ -#if defined(USE_HTTPSRR) && defined(USE_ARES) - struct Curl_https_rrinfo hinfo; - ares_channel channel; - int num_pending; /* number of outstanding c-ares requests */ -#endif - bool init; -}; - -#elif defined(CURLRES_ARES) /* CURLRES_THREADED */ - -struct thread_data { - int num_pending; /* number of outstanding c-ares requests */ - struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares - parts */ - int last_status; - CURLcode result; /* CURLE_OK or error handling response */ -#ifndef HAVE_CARES_GETADDRINFO - struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ -#endif -#ifdef USE_HTTPSRR - struct Curl_https_rrinfo hinfo; -#endif - char *hostname; -}; - -#endif /* CURLRES_ARES */ - -#ifdef USE_ARES -#include - -/* for HTTPS RR purposes as well */ -int Curl_ares_getsock(struct Curl_easy *data, - ares_channel channel, - curl_socket_t *socks); -int Curl_ares_perform(ares_channel channel, - timediff_t timeout_ms); +#if defined(CURLRES_ARES) && defined(CURLRES_THREADED) +#error cannot have both CURLRES_ARES and CURLRES_THREADED defined #endif - /* * This header defines all functions in the internal asynch resolver interface. * All asynch resolvers need to provide these functions. @@ -108,74 +49,34 @@ int Curl_ares_perform(ares_channel channel, */ /* - * Curl_resolver_global_init() + * Curl_async_global_init() * * Called from curl_global_init() to initialize global resolver environment. * Returning anything else than CURLE_OK fails curl_global_init(). */ -int Curl_resolver_global_init(void); +int Curl_async_global_init(void); /* - * Curl_resolver_global_cleanup() + * Curl_async_global_cleanup() * Called from curl_global_cleanup() to destroy global resolver environment. */ -void Curl_resolver_global_cleanup(void); - -/* - * Curl_resolver_init() - * Called from curl_easy_init() -> Curl_open() to initialize resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Should fill the passed pointer by the initialized handler. - * Returning anything else than CURLE_OK fails curl_easy_init() with the - * correspondent code. - */ -CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver); - -/* - * Curl_resolver_cleanup() - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Should destroy the handler and free all resources connected to - * it. - */ -void Curl_resolver_cleanup(void *resolver); +void Curl_async_global_cleanup(void); /* - * Curl_resolver_duphandle() - * Called from curl_easy_duphandle() to duplicate resolver URL-state specific - * environment ('resolver' member of the UrlState structure). Should - * duplicate the 'from' handle and pass the resulting handle to the 'to' - * pointer. Returning anything else than CURLE_OK causes failed - * curl_easy_duphandle() call. + * Curl_async_get_impl() + * Get the resolver implementation instance (c-ares channel) or NULL + * for passing to application callback. */ -CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, - void *from); +CURLcode Curl_async_get_impl(struct Curl_easy *easy, void **impl); /* - * Curl_resolver_cancel(). - * - * It is called from inside other functions to cancel currently performing - * resolver request. Should also free any temporary resources allocated to - * perform a request. This never waits for resolver threads to complete. + * Curl_async_shutdown(). * - * It is safe to call this when conn is in any state. + * This frees the resources of any async resolve operation. */ -void Curl_resolver_cancel(struct Curl_easy *data); +void Curl_async_shutdown(struct Curl_easy *data); -/* - * Curl_resolver_kill(). - * - * This acts like Curl_resolver_cancel() except it will block until any threads - * associated with the resolver are complete. This never blocks for resolvers - * that do not use threads. This is intended to be the "last chance" function - * that cleans up an in-progress resolver completely (before its owner is about - * to die). - * - * It is safe to call this when conn is in any state. - */ -void Curl_resolver_kill(struct Curl_easy *data); - -/* Curl_resolver_getsock() +/* Curl_async_getsock() * * This function is called from the Curl_multi_getsock() function. 'sock' is a * pointer to an array to hold the file descriptors, with 'numsock' being the @@ -183,10 +84,10 @@ void Curl_resolver_kill(struct Curl_easy *data); * return bitmask indicating what file descriptors (referring to array indexes * in the 'sock' array) to wait for, read/write. */ -int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *sock); +int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *sock); /* - * Curl_resolver_is_resolved() + * 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 @@ -194,25 +95,25 @@ int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *sock); * * Returns normal CURLcode errors. */ -CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, - struct Curl_dns_entry **dns); +CURLcode Curl_async_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns); /* - * Curl_resolver_wait_resolv() + * Curl_async_await() * * Waits for a resolve to finish. This function should be avoided since using * this risk getting the multi interface to "hang". * - * If 'entry' is non-NULL, make it point to the resolved dns entry + * On return 'entry' is assigned the resolved dns (CURLE_OK or NULL otherwise. * * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. */ -CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **dnsentry); +CURLcode Curl_async_await(struct Curl_easy *data, + struct Curl_dns_entry **dnsentry); /* - * Curl_resolver_getaddrinfo() - when using this resolver + * Curl_async_getaddrinfo() - when using this resolver * * Returns name information about the given hostname and port number. If * successful, the 'hostent' is returned and the fourth argument will point to @@ -222,38 +123,148 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, * Each resolver backend must of course make sure to return data in the * correct format to comply with this. */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp); +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp); + +#ifdef USE_ARES +/* common functions for c-ares and threaded resolver with HTTPSRR */ +#include + +int Curl_ares_getsock(struct Curl_easy *data, + ares_channel channel, + curl_socket_t *socks); +int Curl_ares_perform(ares_channel channel, + timediff_t timeout_ms); +#endif + +#ifdef CURLRES_ARES +/* async resolving implementation using c-ares alone */ +struct async_ares_ctx { + ares_channel channel; + int num_pending; /* number of outstanding c-ares requests */ + struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares + parts */ + int last_status; + CURLcode result; /* CURLE_OK or error handling response */ +#ifndef HAVE_CARES_GETADDRINFO + struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ +#endif +#ifdef USE_HTTPSRR + struct Curl_https_rrinfo hinfo; +#endif +}; /* - * Set `dnsentry` as result of resolve operation, replacing any - * ongoing resolve attempts. + * Function provided by the resolver backend to set DNS servers to use. */ -void Curl_resolver_set_result(struct Curl_easy *data, - struct Curl_dns_entry *dnsentry); +CURLcode Curl_set_dns_servers(struct Curl_easy *data, char *servers); -#ifndef CURLRES_ASYNCH -/* convert these functions if an asynch resolver is not used */ -#define Curl_resolver_cancel(x) Curl_nop_stmt -#define Curl_resolver_kill(x) Curl_nop_stmt -#define Curl_resolver_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST -#define Curl_resolver_wait_resolv(x,y) CURLE_COULDNT_RESOLVE_HOST -#define Curl_resolver_duphandle(x,y,z) CURLE_OK -#define Curl_resolver_init(x,y) CURLE_OK -#define Curl_resolver_global_init() CURLE_OK -#define Curl_resolver_global_cleanup() Curl_nop_stmt -#define Curl_resolver_set_result(x,y) Curl_nop_stmt -#define Curl_resolver_cleanup(x) Curl_nop_stmt +/* + * Function provided by the resolver backend to set + * outgoing interface to use for DNS requests + */ +CURLcode Curl_set_dns_interface(struct Curl_easy *data, + const char *interf); + +/* + * Function provided by the resolver backend to set + * local IPv4 address to use as source address for DNS requests + */ +CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, + const char *local_ip4); + +/* + * Function provided by the resolver backend to set + * local IPv6 address to use as source address for DNS requests + */ +CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, + const char *local_ip6); + +#endif /* CURLRES_ARES */ + +#ifdef CURLRES_THREADED +/* async resolving implementation using POSIX threads */ +#include "curl_threads.h" + +/* Context for threaded address resolver */ +struct async_thrdd_addr_ctx { + curl_thread_t thread_hnd; + char *hostname; /* hostname to resolve, Curl_async.hostname + duplicate */ + curl_mutex_t mutx; +#ifndef CURL_DISABLE_SOCKETPAIR + curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */ +#endif + struct Curl_addrinfo *res; +#ifdef HAVE_GETADDRINFO + struct addrinfo hints; #endif + struct curltime start; + timediff_t interval_end; + unsigned int poll_interval; + int port; + int sock_error; + int ref_count; +}; -#ifdef CURLRES_ASYNCH -#define Curl_resolver_asynch() 1 -#else -#define Curl_resolver_asynch() 0 +/* Context for threaded resolver */ +struct async_thrdd_ctx { + /* `addr` is a pointer since this memory is shared with a started + * thread. Since threads cannot be killed, we use reference counting + * so that we can "release" our pointer to this memory while the + * thread is still running. */ + struct async_thrdd_addr_ctx *addr; +#if defined(USE_HTTPSRR) && defined(USE_ARES) + struct { + ares_channel channel; + struct Curl_https_rrinfo hinfo; + CURLcode result; + BIT(done); + } rr; #endif +}; + +#endif /* CURLRES_THREADED */ + +#ifndef CURL_DISABLE_DOH +struct doh_probes; +#endif + +#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_shutdown(x) Curl_nop_stmt +#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 +#endif /* !CURLRES_ASYNCH */ + +#if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) +#define USE_CURL_ASYNC + +struct Curl_async { +#ifdef CURLRES_ARES /* */ + struct async_ares_ctx ares; +#elif defined(CURLRES_THREADED) + struct async_thrdd_ctx thrdd; +#endif +#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); +}; + +#endif /********** end of generic resolver interface functions *****************/ #endif /* HEADER_CURL_ASYN_H */ diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 4bc57b8572..4c71296290 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -674,21 +674,14 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, * of the connection. The resolve functions should really be changed * to take a type parameter instead. */ - unsigned char ipver = conn->ip_version; - int rc; - - if(af == AF_INET) - conn->ip_version = CURL_IPRESOLVE_V4; + int ip_version = (af == AF_INET) ? + CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER; #ifdef USE_IPV6 - else if(af == AF_INET6) - conn->ip_version = CURL_IPRESOLVE_V6; + if(af == AF_INET6) + ip_version = CURL_IPRESOLVE_V6; #endif - rc = Curl_resolv(data, host, 80, FALSE, &h); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(data, &h); - conn->ip_version = ipver; - + (void)Curl_resolv_blocking(data, host, 80, ip_version, &h); if(h) { int h_af = h->addr->ai_family; /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ diff --git a/lib/cshutdn.c b/lib/cshutdn.c index 83972e3756..f013405b25 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -81,9 +81,6 @@ static void cshutdn_run_conn_handler(struct Curl_easy *data, conn->handler->disconnect(data, conn, conn->bits.aborted); } - /* possible left-overs from the async name resolvers */ - Curl_resolver_cancel(data); - conn->bits.shutdown_handler = TRUE; } } diff --git a/lib/doh.c b/lib/doh.c index fe5faccbef..5b70fffc24 100644 --- a/lib/doh.c +++ b/lib/doh.c @@ -222,7 +222,7 @@ static int doh_done(struct Curl_easy *doh, CURLcode result) DEBUGASSERT(0); } else { - struct doh_probes *dohp = data->req.doh; + struct doh_probes *dohp = data->state.async.doh; /* one of the DoH request done for the 'data' transfer is now complete! */ dohp->pending--; infof(doh, "a DoH request is completed, %u to go", dohp->pending); @@ -403,21 +403,28 @@ error: struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, const char *hostname, int port, + int ip_version, int *waitp) { CURLcode result = CURLE_OK; struct doh_probes *dohp; struct connectdata *conn = data->conn; size_t i; + *waitp = FALSE; - (void)hostname; - (void)port; - DEBUGASSERT(!data->req.doh); + DEBUGASSERT(!data->state.async.doh); DEBUGASSERT(conn); + data->state.async.done = FALSE; + data->state.async.port = port; + data->state.async.ip_version = ip_version; + data->state.async.hostname = strdup(hostname); + if(!data->state.async.hostname) + return NULL; + /* start clean, consider allocating this struct on demand */ - dohp = data->req.doh = calloc(1, sizeof(struct doh_probes)); + dohp = data->state.async.doh = calloc(1, sizeof(struct doh_probes)); if(!dohp) return NULL; @@ -426,8 +433,8 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, } conn->bits.doh = TRUE; - dohp->host = hostname; - dohp->port = port; + dohp->host = data->state.async.hostname; + dohp->port = data->state.async.port; dohp->req_hds = curl_slist_append(NULL, "Content-Type: application/dns-message"); @@ -435,6 +442,7 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, goto error; /* create IPv4 DoH request */ + (void)ip_version; /* WHY not select on this for ipv4? */ result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV4], DNS_TYPE_A, hostname, data->set.str[STRING_DOH], data->multi, dohp->req_hds); @@ -443,7 +451,7 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, dohp->pending++; #ifdef USE_IPV6 - if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { /* create IPv6 DoH request */ result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV6], DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH], @@ -1164,7 +1172,7 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dnsp) { CURLcode result; - struct doh_probes *dohp = data->req.doh; + struct doh_probes *dohp = data->state.async.doh; *dnsp = NULL; /* defaults to no response */ if(!dohp) return CURLE_OUT_OF_MEMORY; @@ -1180,6 +1188,9 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct dohentry de; 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); @@ -1219,34 +1230,35 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, return result; } - /* we got a response, store it in the cache */ - dns = Curl_cache_addr(data, ai, dohp->host, 0, dohp->port, FALSE); + /* we got a response, create a dns entry. */ + dns = Curl_dnscache_mk_entry(data, ai, dohp->host, 0, dohp->port, FALSE); if(dns) { - data->state.async.dns = dns; - *dnsp = dns; - result = CURLE_OK; /* address resolution OK */ - } - } /* address processing done */ - - /* Now process any build-specific attributes retrieved from DNS */ + /* Now add and HTTPSRR information if we have */ #ifdef USE_HTTPSRR - if(de.numhttps_rrs > 0 && result == CURLE_OK && *dnsp) { - 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"); - return result; - } - infof(data, "Some HTTPS RR to process"); + if(de.numhttps_rrs > 0 && result == CURLE_OK && *dnsp) { + 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"); + return result; + } + infof(data, "Some HTTPS RR to process"); # ifdef DEBUGBUILD - doh_print_httpsrr(data, hrr); + doh_print_httpsrr(data, hrr); # endif - (*dnsp)->hinfo = hrr; - } + dns->hinfo = hrr; + } #endif + /* and add the entry to the cache */ + data->state.async.dns = dns; + result = Curl_dnscache_add(data, dns); + *dnsp = data->state.async.dns; + } + } /* address processing done */ /* All done */ + data->state.async.done = TRUE; de_cleanup(&de); Curl_doh_cleanup(data); return result; @@ -1259,7 +1271,7 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, void Curl_doh_close(struct Curl_easy *data) { - struct doh_probes *doh = data->req.doh; + struct doh_probes *doh = data->state.async.doh; if(doh && data->multi) { struct Curl_easy *probe_data; curl_off_t mid; @@ -1288,13 +1300,14 @@ void Curl_doh_close(struct Curl_easy *data) void Curl_doh_cleanup(struct Curl_easy *data) { - struct doh_probes *doh = data->req.doh; + struct doh_probes *doh = data->state.async.doh; if(doh) { Curl_doh_close(data); curl_slist_free_all(doh->req_hds); - data->req.doh->req_hds = NULL; - Curl_safefree(data->req.doh); + data->state.async.doh->req_hds = NULL; + Curl_safefree(data->state.async.doh); } + Curl_safefree(data->state.async.hostname); } #endif /* CURL_DISABLE_DOH */ diff --git a/lib/doh.h b/lib/doh.h index 9c41ed818e..de709cf06d 100644 --- a/lib/doh.h +++ b/lib/doh.h @@ -105,6 +105,7 @@ struct doh_probes { struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, const char *hostname, int port, + int ip_version, int *waitp); CURLcode Curl_doh_is_resolved(struct Curl_easy *data, @@ -168,7 +169,7 @@ UNITTEST void de_cleanup(struct dohentry *d); #endif #else /* if DoH is disabled */ -#define Curl_doh(a,b,c,d) NULL +#define Curl_doh(a,b,c,d,e) NULL #define Curl_doh_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST #endif diff --git a/lib/easy.c b/lib/easy.c index aa1bedec38..5376492578 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -185,7 +185,7 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } - if(Curl_resolver_global_init()) { + if(Curl_async_global_init()) { DEBUGF(fprintf(stderr, "Error: resolver_global_init failed\n")); goto fail; } @@ -288,7 +288,7 @@ void curl_global_cleanup(void) } Curl_ssl_cleanup(); - Curl_resolver_global_cleanup(); + Curl_async_global_cleanup(); #ifdef _WIN32 Curl_win32_cleanup(easy_init_flags); @@ -1038,15 +1038,7 @@ CURL *curl_easy_duphandle(CURL *d) } #endif -#ifdef CURLRES_ASYNCH - /* Clone the resolver handle, if present, for the new handle */ - if(Curl_resolver_duphandle(outcurl, - &outcurl->state.async.resolver, - data->state.async.resolver)) - goto fail; -#endif - -#ifdef USE_ARES +#ifdef CURLRES_ARES { CURLcode rc; diff --git a/lib/ftp.c b/lib/ftp.c index a60af878ed..393db0b4c8 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -867,7 +867,6 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, struct sockaddr_in6 * const sa6 = (void *)sa; #endif static const char mode[][5] = { "EPRT", "PORT" }; - enum resolve_t rc; int error; char *host = NULL; char *string_ftpport = data->set.str[STRING_FTPPORT]; @@ -1014,14 +1013,12 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, } /* resolv ip/host to ip */ - rc = Curl_resolv(data, host, 0, FALSE, &dns_entry); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(data, &dns_entry); - if(dns_entry) { + res = NULL; + result = Curl_resolv_blocking(data, host, 0, conn->ip_version, &dns_entry); + if(!result) { + DEBUGASSERT(dns_entry); res = dns_entry->addr; } - else - res = NULL; /* failure! */ if(!res) { failf(data, "failed to resolve the address provided to PORT: %s", host); @@ -1786,8 +1783,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result; - struct Curl_dns_entry *addr = NULL; - enum resolve_t rc; + struct Curl_dns_entry *dns = NULL; unsigned short connectport; /* the local port connect() should use! */ struct pingpong *pp = &ftpc->pp; char *str = @@ -1885,16 +1881,12 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, */ const char * const host_name = conn->bits.socksproxy ? conn->socks_proxy.host.name : conn->http_proxy.host.name; - rc = Curl_resolv(data, host_name, conn->primary.remote_port, FALSE, &addr); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING, ignores the return code but 'addr' will be NULL in - case of failure */ - (void)Curl_resolver_wait_resolv(data, &addr); - + (void)Curl_resolv_blocking(data, host_name, conn->primary.remote_port, + conn->ip_version, &dns); /* we connect to the proxy's port */ connectport = (unsigned short)conn->primary.remote_port; - if(!addr) { + if(!dns) { failf(data, "cannot resolve proxy host %s:%hu", host_name, connectport); return CURLE_COULDNT_RESOLVE_PROXY; } @@ -1913,26 +1905,23 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } - rc = Curl_resolv(data, ftpc->newhost, ftpc->newport, FALSE, &addr); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING */ - (void)Curl_resolver_wait_resolv(data, &addr); - + (void)Curl_resolv_blocking(data, ftpc->newhost, ftpc->newport, + conn->ip_version, &dns); connectport = ftpc->newport; /* we connect to the remote port */ - if(!addr) { + if(!dns) { failf(data, "cannot resolve new host %s:%hu", ftpc->newhost, connectport); return CURLE_FTP_CANT_GET_HOST; } } - result = Curl_conn_setup(data, conn, SECONDARYSOCKET, addr, + result = Curl_conn_setup(data, conn, SECONDARYSOCKET, dns, conn->bits.ftp_use_data_ssl ? CURL_CF_SSL_ENABLE : CURL_CF_SSL_DISABLE); if(result) { - Curl_resolv_unlink(data, &addr); /* we are done using this address */ + Curl_resolv_unlink(data, &dns); /* we are done using this dns entry */ if(ftpc->count1 == 0 && ftpcode == 229) return ftp_epsv_disable(data, conn); @@ -1948,9 +1937,9 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, if(data->set.verbose) /* this just dumps information about this second connection */ - ftp_pasv_verbose(data, addr->addr, ftpc->newhost, connectport); + ftp_pasv_verbose(data, dns->addr, ftpc->newhost, connectport); - Curl_resolv_unlink(data, &addr); /* we are done using this address */ + Curl_resolv_unlink(data, &dns); /* we are done using this address */ free(conn->secondaryhostname); conn->secondary_port = ftpc->newport; diff --git a/lib/hostasyn.c b/lib/hostasyn.c deleted file mode 100644 index 6314f38351..0000000000 --- a/lib/hostasyn.c +++ /dev/null @@ -1,114 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , 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" - -/*********************************************************************** - * Only for builds using asynchronous name resolves - **********************************************************************/ -#ifdef CURLRES_ASYNCH - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "hash.h" -#include "share.h" -#include "url.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* - * Curl_addrinfo_callback() gets called by ares, gethostbyname_thread() - * or getaddrinfo_thread() when we got the name resolved (or not!). - * - * If the status argument is CURL_ASYNC_SUCCESS, this function takes - * ownership of the Curl_addrinfo passed, storing the resolved data - * in the DNS cache. - * - * The storage operation locks and unlocks the DNS cache. - */ -CURLcode Curl_addrinfo_callback(struct Curl_easy *data, - int status, - struct Curl_addrinfo *ai) -{ - struct Curl_dns_entry *dns = NULL; - CURLcode result = CURLE_OK; - - if(CURL_ASYNC_SUCCESS == status) { - if(ai) { - dns = Curl_cache_addr(data, ai, - data->conn->host.dispname, 0, - data->state.async.port, FALSE); - if(!dns) { - /* failed to store, return error */ - result = CURLE_OUT_OF_MEMORY; - } - } - else { - result = CURLE_OUT_OF_MEMORY; - } - } - - data->state.async.dns = dns; - - /* Set async.done TRUE last in this function since it may be used multi- - threaded and once this is TRUE the other thread may read fields from the - async struct */ - data->state.async.done = TRUE; - - /* IPv4: The input hostent struct will be freed by ares when we return from - this function */ - return result; -} - -/* - * Curl_getaddrinfo() is the generic low-level name resolve API within this - * source file. There are several versions of this function - for different - * name resolve layers (selected at build-time). They all take this same set - * of arguments - */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) -{ - return Curl_resolver_getaddrinfo(data, hostname, port, waitp); -} - -#endif /* CURLRES_ASYNCH */ diff --git a/lib/hostip.c b/lib/hostip.c index c82ffb6a6a..eb552af875 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -48,6 +48,7 @@ #include "urldata.h" #include "sendf.h" +#include "connect.h" #include "hostip.h" #include "hash.h" #include "rand.h" @@ -58,6 +59,7 @@ #include "multiif.h" #include "doh.h" #include "warnless.h" +#include "select.h" #include "strcase.h" #include "easy_lock.h" #include "strparse.h" @@ -106,15 +108,13 @@ * The host*.c sources files are split up like this: * * hostip.c - method-independent resolver functions and utility functions - * hostasyn.c - functions for asynchronous name resolves - * hostsyn.c - functions for synchronous name resolves * hostip4.c - IPv4 specific functions * hostip6.c - IPv6 specific functions - * + * asyn.h - common functions for all async resolvers * The two asynchronous name resolver backends are implemented in: - * asyn-ares.c - functions for ares-using name resolves - * asyn-thread.c - functions for threaded name resolves - + * asyn-ares.c - async resolver using c-ares + * asyn-thread.c - async resolver using POSIX threads + * * The hostip.h is the united header file for all this. It defines the * CURLRES_* defines based on the config*.h and curl_setup.h defines. */ @@ -302,7 +302,8 @@ static curl_simple_lock curl_jmpenv_lock; static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, struct Curl_dnscache *dnscache, const char *hostname, - int port) + int port, + int ip_version) { struct Curl_dns_entry *dns = NULL; char entry_id[MAX_HOSTCACHE_LEN]; @@ -342,13 +343,13 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, } /* See if the returned entry matches the required resolve mode */ - if(dns && data->conn->ip_version != CURL_IPRESOLVE_WHATEVER) { + if(dns && ip_version != CURL_IPRESOLVE_WHATEVER) { int pf = PF_INET; bool found = FALSE; struct Curl_addrinfo *addr = dns->addr; #ifdef PF_INET6 - if(data->conn->ip_version == CURL_IPRESOLVE_V6) + if(ip_version == CURL_IPRESOLVE_V6) pf = PF_INET6; #endif @@ -370,7 +371,7 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, } /* - * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache. + * 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 @@ -384,16 +385,17 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, * use, or we will leak memory! */ struct Curl_dns_entry * -Curl_fetch_addr(struct Curl_easy *data, - const char *hostname, - int port) +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); + dns = fetch_addr(data, dnscache, hostname, port, ip_version); if(dns) dns->refcount++; /* we use it! */ @@ -484,19 +486,15 @@ UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, } #endif -static struct Curl_dns_entry * -dnscache_add_addr(struct Curl_easy *data, - struct Curl_dnscache *dnscache, - struct Curl_addrinfo *addr, - const char *hostname, - size_t hostlen, /* length or zero */ - int port, - bool permanent) +struct Curl_dns_entry * +Curl_dnscache_mk_entry(struct Curl_easy *data, + struct Curl_addrinfo *addr, + const char *hostname, + size_t hostlen, /* 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; #ifndef CURL_DISABLE_SHUFFLE_DNS /* shuffle addresses if requested */ @@ -518,10 +516,6 @@ dnscache_add_addr(struct Curl_easy *data, return NULL; } - /* Create an entry id, based upon the hostname and port */ - entry_len = create_dnscache_id(hostname, hostlen, port, - entry_id, sizeof(entry_id)); - dns->refcount = 1; /* the cache has the first reference */ dns->addr = addr; /* this is the address(es) */ if(permanent) @@ -535,6 +529,31 @@ dnscache_add_addr(struct Curl_easy *data, if(hostlen) memcpy(dns->hostname, hostname, hostlen); + return dns; +} + +static struct Curl_dns_entry * +dnscache_add_addr(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + struct Curl_addrinfo *addr, + 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, addr, 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); @@ -548,36 +567,28 @@ dnscache_add_addr(struct Curl_easy *data, return dns; } -/* - * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. - * - * When calling Curl_resolv() has resulted in a response with a returned - * address, we call this function to store the information in the dns - * cache etc - * - * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. - */ -struct Curl_dns_entry * -Curl_cache_addr(struct Curl_easy *data, - struct Curl_addrinfo *addr, - const char *hostname, - size_t hostlen, /* length or zero */ - int port, - bool permanent) +CURLcode Curl_dnscache_add(struct Curl_easy *data, + struct Curl_dns_entry *entry) { struct Curl_dnscache *dnscache = dnscache_get(data); - struct Curl_dns_entry *dns; + char id[MAX_HOSTCACHE_LEN]; + size_t idlen; - if(!dnscache) { - Curl_freeaddrinfo(addr); - return NULL; - } + 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); - dns = dnscache_add_addr(data, dnscache, addr, - hostname, hostlen, port, permanent); + 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 dns; + return CURLE_OK; } #ifdef USE_IPV6 @@ -714,15 +725,64 @@ bool Curl_host_is_ipnum(const char *hostname) /* return TRUE if 'part' is a case insensitive tail of 'full' */ -static bool tailmatch(const char *full, const char *part) +static bool tailmatch(const char *full, size_t flen, + const char *part, size_t plen) { - size_t plen = strlen(part); - size_t flen = strlen(full); if(plen > flen) return FALSE; return strncasecompare(part, &full[flen - plen], plen); } +static struct Curl_addrinfo * +convert_ipaddr_direct(const char *hostname, int port, bool *is_ipaddr) +{ + struct in_addr in; + *is_ipaddr = FALSE; + /* First check if this is an IPv4 address string */ + if(curlx_inet_pton(AF_INET, hostname, &in) > 0) { + /* This is a dotted IP address 123.123.123.123-style */ + *is_ipaddr = TRUE; +#ifdef USE_RESOLVE_ON_IPS + (void)port; + return NULL; +#else + return Curl_ip2addr(AF_INET, &in, hostname, port); +#endif + } +#ifdef USE_IPV6 + else { + struct in6_addr in6; + /* check if this is an IPv6 address string */ + if(curlx_inet_pton(AF_INET6, hostname, &in6) > 0) { + /* This is an IPv6 address literal */ + *is_ipaddr = TRUE; +#ifdef USE_RESOLVE_ON_IPS + return NULL; +#else + return Curl_ip2addr(AF_INET6, &in6, hostname, port); +#endif + } + } +#endif /* USE_IPV6 */ + *is_ipaddr = FALSE; + return NULL; +} + +static bool can_resolve_ip_version(struct Curl_easy *data, int ip_version) +{ +#ifdef CURLRES_IPV6 + if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) + return FALSE; +#elif defined(CURLRES_IPV4) + (void)data; + if(ip_version == CURL_IPRESOLVE_V6) + return FALSE; +#else +#error either CURLRES_IPV6 or CURLRES_IPV4 need to be defined +#endif + return TRUE; +} + /* * 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 (if one @@ -734,176 +794,165 @@ static bool tailmatch(const char *full, const char *part) * done using this struct) to decrease the reference counter again. * * Return codes: - * - * CURLRESOLV_ERROR (-1) = error, no pointer - * CURLRESOLV_RESOLVED (0) = OK, pointer provided - * CURLRESOLV_PENDING (1) = waiting for response, no pointer + * 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 */ - -enum resolve_t Curl_resolv(struct Curl_easy *data, - const char *hostname, - int port, - bool allowDOH, - struct Curl_dns_entry **entry) +CURLcode Curl_resolv(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + bool allowDOH, + struct Curl_dns_entry **entry) { struct Curl_dnscache *dnscache = dnscache_get(data); struct Curl_dns_entry *dns = NULL; - CURLcode result; - enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */ - struct connectdata *conn = data->conn; + struct Curl_addrinfo *addr = NULL; + int respwait = 0; + bool is_ipaddr; + size_t hostname_len; + +#ifndef CURL_DISABLE_DOH + data->conn->bits.doh = FALSE; /* default is not */ +#else + (void)allowDOH; +#endif + if(!dnscache) + goto error; + /* We should intentionally error and not resolve .onion TLDs */ - size_t hostname_len = strlen(hostname); + hostname_len = strlen(hostname); if(hostname_len >= 7 && (curl_strequal(&hostname[hostname_len - 6], ".onion") || curl_strequal(&hostname[hostname_len - 7], ".onion."))) { failf(data, "Not resolving .onion address (RFC 7686)"); - return CURLRESOLV_ERROR; + goto error; } - *entry = NULL; -#ifndef CURL_DISABLE_DOH - conn->bits.doh = FALSE; /* default is not */ -#else - (void)allowDOH; -#endif - - if(!dnscache) - return CURLRESOLV_ERROR; + /* Let's check our DNS cache first */ dnscache_lock(data, dnscache); - - dns = fetch_addr(data, dnscache, hostname, port); - + dns = fetch_addr(data, dnscache, hostname, port, ip_version); + if(dns) + dns->refcount++; /* we pass out the reference. */ + dnscache_unlock(data, dnscache); if(dns) { infof(data, "Hostname %s was found in DNS cache", hostname); - dns->refcount++; /* we use it! */ - rc = CURLRESOLV_RESOLVED; + goto out; } - dnscache_unlock(data, dnscache); + /* No luck, we need to resolve hostname. Notify user callback. */ + if(data->set.resolver_start) { + void *resolver; + int st; + if(Curl_async_get_impl(data, &resolver)) + goto error; + Curl_set_in_callback(data, TRUE); + st = data->set.resolver_start(resolver, NULL, + data->set.resolver_start_client); + Curl_set_in_callback(data, FALSE); + if(st) + goto error; + } - if(!dns) { - /* The entry was not in the cache. Resolve it to IP address */ + /* shortcut literal IP addresses, if we are not told to resolve them. */ + addr = convert_ipaddr_direct(hostname, port, &is_ipaddr); + if(addr) + goto out; - struct Curl_addrinfo *addr = NULL; - int respwait = 0; -#if !defined(CURL_DISABLE_DOH) || !defined(USE_RESOLVE_ON_IPS) - struct in_addr in; -#endif -#ifndef CURL_DISABLE_DOH #ifndef USE_RESOLVE_ON_IPS - const + /* allowed to convert, hostname is IP address, then NULL means error */ + if(is_ipaddr) + goto error; #endif - bool ipnum = FALSE; + + /* Really need a resolver for hostname. */ + if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) + goto error; + + if(!is_ipaddr && + (strcasecompare(hostname, "localhost") || + strcasecompare(hostname, "localhost.") || + tailmatch(hostname, hostname_len, STRCONST(".localhost")) || + tailmatch(hostname, hostname_len, STRCONST(".localhost.")))) { + addr = get_localhost(port, hostname); + } +#ifndef CURL_DISABLE_DOH + else if(!is_ipaddr && allowDOH && data->set.doh) { + addr = Curl_doh(data, hostname, port, ip_version, &respwait); + } #endif + else { + /* Can we provide the requested IP specifics in resolving? */ + if(!can_resolve_ip_version(data, ip_version)) + goto error; - /* notify the resolver start callback */ - if(data->set.resolver_start) { - int st; - Curl_set_in_callback(data, TRUE); - st = data->set.resolver_start( #ifdef CURLRES_ASYNCH - data->state.async.resolver, + addr = Curl_async_getaddrinfo(data, hostname, port, ip_version, &respwait); #else - NULL, + respwait = 0; /* no async waiting here */ + addr = Curl_sync_getaddrinfo(data, hostname, port, ip_version); #endif - NULL, - data->set.resolver_start_client); - Curl_set_in_callback(data, FALSE); - if(st) - return CURLRESOLV_ERROR; - } - -#ifndef USE_RESOLVE_ON_IPS - /* First check if this is an IPv4 address string */ - if(curlx_inet_pton(AF_INET, hostname, &in) > 0) { - /* This is a dotted IP address 123.123.123.123-style */ - addr = Curl_ip2addr(AF_INET, &in, hostname, port); - if(!addr) - return CURLRESOLV_ERROR; - } -#ifdef USE_IPV6 - else { - struct in6_addr in6; - /* check if this is an IPv6 address string */ - if(curlx_inet_pton(AF_INET6, hostname, &in6) > 0) { - /* This is an IPv6 address literal */ - addr = Curl_ip2addr(AF_INET6, &in6, hostname, port); - if(!addr) - return CURLRESOLV_ERROR; - } - } -#endif /* USE_IPV6 */ + } -#else /* if USE_RESOLVE_ON_IPS */ -#ifndef CURL_DISABLE_DOH - /* First check if this is an IPv4 address string */ - if(curlx_inet_pton(AF_INET, hostname, &in) > 0) - /* This is a dotted IP address 123.123.123.123-style */ - ipnum = TRUE; -#ifdef USE_IPV6 - else { - struct in6_addr in6; - /* check if this is an IPv6 address string */ - if(curlx_inet_pton(AF_INET6, hostname, &in6) > 0) - /* This is an IPv6 address literal */ - ipnum = TRUE; +out: + /* We either have found a `dns` or looked up the `addr` + * or `respwait` is set for an async operation. + * Everything else is a failure to resolve. */ + if(dns) { + *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); + if(!dns) + goto error; + if(Curl_dnscache_add(data, dns)) + goto error; + show_resolve_info(data, dns); + *entry = dns; + return CURLE_OK; + } + else if(respwait) { + if(!Curl_resolv_check(data, &dns)) { + *entry = dns; + return dns ? CURLE_OK : CURLE_AGAIN; } -#endif /* USE_IPV6 */ -#endif /* CURL_DISABLE_DOH */ - -#endif /* !USE_RESOLVE_ON_IPS */ + } +error: + if(dns) + Curl_resolv_unlink(data, &dns); + *entry = NULL; + return CURLE_COULDNT_RESOLVE_HOST; +} - if(!addr) { - if(conn->ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) - return CURLRESOLV_ERROR; +CURLcode Curl_resolv_blocking(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + struct Curl_dns_entry **dnsentry) +{ + CURLcode result; - if(strcasecompare(hostname, "localhost") || - strcasecompare(hostname, "localhost.") || - tailmatch(hostname, ".localhost") || - tailmatch(hostname, ".localhost.")) - addr = get_localhost(port, hostname); -#ifndef CURL_DISABLE_DOH - else if(allowDOH && data->set.doh && !ipnum) - addr = Curl_doh(data, hostname, port, &respwait); -#endif - else { - /* Check what IP specifics the app has requested and if we can provide - * it. If not, bail out. */ - if(!Curl_ipvalid(data, conn)) - return CURLRESOLV_ERROR; - /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a - non-zero value indicating that we need to wait for the response to - the resolve call */ - addr = Curl_getaddrinfo(data, hostname, port, &respwait); - } - } - if(!addr) { - if(respwait) { - /* the response to our resolve call will come asynchronously at - a later time, good or bad */ - /* First, check that we have not received the info by now */ - result = Curl_resolv_check(data, &dns); - if(result) /* error detected */ - return CURLRESOLV_ERROR; - if(dns) - rc = CURLRESOLV_RESOLVED; /* pointer provided */ - else - rc = CURLRESOLV_PENDING; /* no info yet */ - } - } - else { - /* we got a response, store it in the cache */ - dns = Curl_cache_addr(data, addr, hostname, 0, port, FALSE); - if(dns) { - rc = CURLRESOLV_RESOLVED; - show_resolve_info(data, dns); - } + *dnsentry = NULL; + result = Curl_resolv(data, hostname, port, ip_version, FALSE, dnsentry); + switch(result) { + case CURLE_OK: + DEBUGASSERT(*dnsentry); + return CURLE_OK; + case CURLE_AGAIN: + DEBUGASSERT(!*dnsentry); + result = Curl_async_await(data, dnsentry); + if(result || !*dnsentry) { + /* close the connection, since we cannot return failure here without + cleaning up this connection properly. */ + connclose(data->conn, "async resolve failed"); } + return result; + default: + return result; } - - *entry = dns; - - return rc; } #ifdef USE_ALARM_TIMEOUT @@ -935,18 +984,18 @@ void alarmfunc(int sig) * is ignored. * * Return codes: - * - * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired - * CURLRESOLV_ERROR (-1) = error, no pointer - * CURLRESOLV_RESOLVED (0) = OK, pointer provided - * CURLRESOLV_PENDING (1) = waiting for response, no pointer + * 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 */ -enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, - const char *hostname, - int port, - struct Curl_dns_entry **entry, - timediff_t timeoutms) +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_ALARM_TIMEOUT #ifdef HAVE_SIGACTION @@ -961,13 +1010,13 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, volatile long timeout; volatile unsigned int prev_alarm = 0; #endif /* USE_ALARM_TIMEOUT */ - enum resolve_t rc; + CURLcode result; *entry = NULL; if(timeoutms < 0) /* got an already expired timeout */ - return CURLRESOLV_TIMEDOUT; + return CURLE_OPERATION_TIMEDOUT; #ifdef USE_ALARM_TIMEOUT if(data->set.no_signal) @@ -983,7 +1032,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, ) /* USE_ALARM_TIMEOUT defined, but no timeout actually requested or resolve done using DoH */ - return Curl_resolv(data, hostname, port, TRUE, entry); + return Curl_resolv(data, hostname, port, ip_version, TRUE, entry); if(timeout < 1000) { /* The alarm() function only provides integer second resolution, so if @@ -991,7 +1040,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, failf(data, "remaining timeout of %ld too small to resolve via SIGALRM method", timeout); - return CURLRESOLV_TIMEDOUT; + return CURLE_OPERATION_TIMEDOUT; } /* This allows us to time-out from the name resolver, as the timeout will generate a signal and we will siglongjmp() from that here. @@ -1004,7 +1053,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, if(sigsetjmp(curl_jmpenv, 1)) { /* this is coming from a siglongjmp() after an alarm signal */ failf(data, "name lookup timed out"); - rc = CURLRESOLV_ERROR; + result = CURLE_OPERATION_TIMEDOUT; goto clean_up; } else { @@ -1035,19 +1084,19 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, prev_alarm = alarm(curlx_sltoui(timeout/1000L)); } -#else +#else /* USE_ALARM_TIMEOUT */ #ifndef CURLRES_ASYNCH if(timeoutms) infof(data, "timeout on name lookup is not supported"); #else (void)timeoutms; /* timeoutms not used with an async resolver */ #endif -#endif /* USE_ALARM_TIMEOUT */ +#endif /* else USE_ALARM_TIMEOUT */ /* Perform the actual name resolution. This might be interrupted by an * alarm if it takes too long. */ - rc = Curl_resolv(data, hostname, port, TRUE, entry); + result = Curl_resolv(data, hostname, port, ip_version, TRUE, entry); #ifdef USE_ALARM_TIMEOUT clean_up: @@ -1088,7 +1137,7 @@ clean_up: will not, and zero would be to switch it off so we never set it to less than 1! */ alarm(1); - rc = CURLRESOLV_TIMEDOUT; + result = CURLE_OPERATION_TIMEDOUT; failf(data, "Previous alarm fired off"); } else @@ -1096,7 +1145,7 @@ clean_up: } #endif /* USE_ALARM_TIMEOUT */ - return rc; + return result; } static void dnscache_entry_free(struct Curl_dns_entry *dns) @@ -1120,15 +1169,16 @@ static void dnscache_entry_free(struct Curl_dns_entry *dns) */ void Curl_resolv_unlink(struct Curl_easy *data, struct Curl_dns_entry **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); + 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) @@ -1426,17 +1476,32 @@ CURLcode Curl_resolv_check(struct Curl_easy *data, struct Curl_dns_entry **dns) { CURLcode result; -#if defined(CURL_DISABLE_DOH) && !defined(CURLRES_ASYNCH) - (void)data; - (void)dns; -#endif + + /* If async resolving is ongoing, this must be set */ + if(!data->state.async.hostname) + 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) { + /* Tell a possibly async resolver we no longer need the results. */ + Curl_async_shutdown(data); + data->state.async.dns = *dns; + data->state.async.done = TRUE; + infof(data, "Hostname '%s' was found in DNS cache", + data->state.async.hostname); + return CURLE_OK; + } + #ifndef CURL_DISABLE_DOH if(data->conn->bits.doh) { result = Curl_doh_is_resolved(data, dns); } else #endif - result = Curl_resolver_is_resolved(data, dns); + result = Curl_async_is_resolved(data, dns); if(*dns) show_resolve_info(data, *dns); return result; @@ -1452,7 +1517,7 @@ int Curl_resolv_getsock(struct Curl_easy *data, sockets */ return GETSOCK_BLANK; #endif - return Curl_resolver_getsock(data, socks); + return Curl_async_getsock(data, socks); #else (void)data; (void)socks; diff --git a/lib/hostip.h b/lib/hostip.h index f92d64a9cf..99635bf6bc 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -89,22 +89,24 @@ bool Curl_host_is_ipnum(const char *hostname); * The returned data *MUST* be "released" with Curl_resolv_unlink() after * use, or we will leak memory! */ -/* return codes */ -enum resolve_t { - CURLRESOLV_TIMEDOUT = -2, - CURLRESOLV_ERROR = -1, - CURLRESOLV_RESOLVED = 0, - CURLRESOLV_PENDING = 1 -}; -enum resolve_t Curl_resolv(struct Curl_easy *data, - const char *hostname, - int port, - bool allowDOH, - struct Curl_dns_entry **dnsentry); -enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, - const char *hostname, int port, - struct Curl_dns_entry **dnsentry, - timediff_t timeoutms); +CURLcode Curl_resolv(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + bool allowDOH, + struct Curl_dns_entry **dnsentry); + +CURLcode Curl_resolv_blocking(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + struct Curl_dns_entry **dnsentry); + +CURLcode Curl_resolv_timeout(struct Curl_easy *data, + const char *hostname, int port, + int ip_version, + struct Curl_dns_entry **dnsentry, + timediff_t timeoutms); #ifdef USE_IPV6 /* @@ -115,24 +117,6 @@ bool Curl_ipv6works(struct Curl_easy *data); #define Curl_ipv6works(x) FALSE #endif -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn); - - -/* - * Curl_getaddrinfo() is the generic low-level name resolve API within this - * source file. There are several versions of this function - for different - * name resolve layers (selected at build-time). They all take this same set - * of arguments - */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp); - /* unlink a dns entry, potentially shared with a cache */ void Curl_resolv_unlink(struct Curl_easy *data, @@ -151,16 +135,6 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port); CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_connect); -/* - * Curl_addrinfo_callback() is used when we build with any asynch specialty. - * Handles end of async request processing. Inserts ai into dnscache when - * status is CURL_ASYNC_SUCCESS. Twiddles fields in conn to indicate async - * request completed whether successful or failed. - */ -CURLcode Curl_addrinfo_callback(struct Curl_easy *data, - int status, - struct Curl_addrinfo *ai); - /* * 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 @@ -170,54 +144,44 @@ void Curl_printable_address(const struct Curl_addrinfo *ip, char *buf, size_t bufsize); /* - * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache. + * 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. * - * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. + * The entry is created with a reference count of 1. + * Use `Curl_resolv_unlink()` to release your hold on it. * - * The returned data *MUST* be "released" with Curl_resolv_unlink() after - * use, or we will leak memory! + * The call takes ownership of `addr`and makes a copy of `hostname`. + * + * Returns entry or NULL on OOM. */ struct Curl_dns_entry * -Curl_fetch_addr(struct Curl_easy *data, - const char *hostname, - int port); +Curl_dnscache_mk_entry(struct Curl_easy *data, + struct Curl_addrinfo *addr, + const char *hostname, + size_t hostlen, /* length or zero */ + int port, + bool permanent); /* - * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. - * Call takes ownership of `addr` and free's it on failure. - * @param permanent iff TRUE, entry will never become stale - * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. + * 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_resolv_unlink() after + * use, or we will leak memory! */ struct Curl_dns_entry * -Curl_cache_addr(struct Curl_easy *data, struct Curl_addrinfo *addr, - const char *hostname, size_t hostlen, int port, - bool permanent); - -/* - * Function provided by the resolver backend to set DNS servers to use. - */ -CURLcode Curl_set_dns_servers(struct Curl_easy *data, char *servers); +Curl_dnscache_get(struct Curl_easy *data, + const char *hostname, + int port, int ip_version); /* - * Function provided by the resolver backend to set - * outgoing interface to use for DNS requests + * Curl_dnscache_addr() adds `entry` to the cache, increasing its + * reference count on success. */ -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf); - -/* - * Function provided by the resolver backend to set - * local IPv4 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4); - -/* - * Function provided by the resolver backend to set - * local IPv6 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6); +CURLcode Curl_dnscache_add(struct Curl_easy *data, + struct Curl_dns_entry *entry); /* * Populate the cache with specified entries from CURLOPT_RESOLVE. @@ -229,4 +193,18 @@ int Curl_resolv_getsock(struct Curl_easy *data, curl_socket_t *socks); CURLcode Curl_resolver_error(struct Curl_easy *data); + +#ifdef CURLRES_SYNCH +/* + * Curl_sync_getaddrinfo() is the non-async low-level name resolve API. + * There are several versions of this function - depending on IPV6 + * support and platform. + */ +struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version); + +#endif + #endif /* HEADER_CURL_HOSTIP_H */ diff --git a/lib/hostip4.c b/lib/hostip4.c index 41ccbf8191..14e4d98a86 100644 --- a/lib/hostip4.c +++ b/lib/hostip4.c @@ -54,24 +54,11 @@ #include "curl_memory.h" #include "memdebug.h" -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) -{ - (void)data; - if(conn->ip_version == CURL_IPRESOLVE_V6) - /* An IPv6 address was requested and we cannot get/use one */ - return FALSE; - - return TRUE; /* OK, proceed */ -} #ifdef CURLRES_SYNCH /* - * Curl_getaddrinfo() - the IPv4 synchronous version. + * Curl_sync_getaddrinfo() - the IPv4 synchronous version. * * The original code to this function was from the Dancer source code, written * by Bjorn Reese, it has since been patched and modified considerably. @@ -86,19 +73,18 @@ bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) * flavours have thread-safe versions of the plain gethostbyname() etc. * */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version) { struct Curl_addrinfo *ai = NULL; + (void)ip_version; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif - *waitp = 0; /* synchronous response only */ - ai = Curl_ipv4_resolve_r(hostname, port); if(!ai) infof(data, "Curl_ipv4_resolve_r failed for %s", hostname); diff --git a/lib/hostip6.c b/lib/hostip6.c index 7c68c40c9c..0d5770d3cf 100644 --- a/lib/hostip6.c +++ b/lib/hostip6.c @@ -56,18 +56,6 @@ #include "curl_memory.h" #include "memdebug.h" -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) -{ - if(conn->ip_version == CURL_IPRESOLVE_V6) - return Curl_ipv6works(data); - - return TRUE; -} - #if defined(CURLRES_SYNCH) #ifdef DEBUG_ADDRINFO @@ -87,7 +75,7 @@ static void dump_addrinfo(const struct Curl_addrinfo *ai) #endif /* - * Curl_getaddrinfo() when built IPv6-enabled (non-threading and + * Curl_sync_getaddrinfo() when built IPv6-enabled (non-threading and * non-ares version). * * Returns name information about the given hostname and port number. If @@ -95,10 +83,10 @@ static void dump_addrinfo(const struct Curl_addrinfo *ai) * to memory we need to free after use. That memory *MUST* be freed with * Curl_freeaddrinfo(), nothing else. */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version) { struct addrinfo hints; struct Curl_addrinfo *res; @@ -110,9 +98,7 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, #endif int pf = PF_INET; - *waitp = 0; /* synchronous response only */ - - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) /* The stack seems to be IPv6-enabled */ pf = PF_UNSPEC; diff --git a/lib/hostsyn.c b/lib/hostsyn.c deleted file mode 100644 index ca8b0758c4..0000000000 --- a/lib/hostsyn.c +++ /dev/null @@ -1,104 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , 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" - -/*********************************************************************** - * Only for builds using synchronous name resolves - **********************************************************************/ -#ifdef CURLRES_SYNCH - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "hash.h" -#include "share.h" -#include "url.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* - * Function provided by the resolver backend to set DNS servers to use. - */ -CURLcode Curl_set_dns_servers(struct Curl_easy *data, - char *servers) -{ - (void)data; - (void)servers; - return CURLE_NOT_BUILT_IN; - -} - -/* - * Function provided by the resolver backend to set - * outgoing interface to use for DNS requests - */ -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf) -{ - (void)data; - (void)interf; - return CURLE_NOT_BUILT_IN; -} - -/* - * Function provided by the resolver backend to set - * local IPv4 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4) -{ - (void)data; - (void)local_ip4; - return CURLE_NOT_BUILT_IN; -} - -/* - * Function provided by the resolver backend to set - * local IPv6 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6) -{ - (void)data; - (void)local_ip6; - return CURLE_NOT_BUILT_IN; -} - -#endif /* truly sync */ diff --git a/lib/httpsrr.c b/lib/httpsrr.c index 003790bcbc..df93ac34ac 100644 --- a/lib/httpsrr.c +++ b/lib/httpsrr.c @@ -159,31 +159,23 @@ void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo) static CURLcode httpsrr_opt(struct Curl_easy *data, const ares_dns_rr_t *rr, - ares_dns_rr_key_t key, size_t idx) + ares_dns_rr_key_t key, size_t idx, + struct Curl_https_rrinfo *hinfo) { - size_t len = 0; const unsigned char *val = NULL; unsigned short code; - struct thread_data *res = &data->state.async.thdata; - struct Curl_https_rrinfo *hi = &res->hinfo; + size_t len = 0; code = ares_dns_rr_get_opt(rr, key, idx, &val, &len); - return Curl_httpsrr_set(data, hi, code, val, len); + return Curl_httpsrr_set(data, hinfo, code, val, len); } -void Curl_dnsrec_done_cb(void *arg, ares_status_t status, - size_t timeouts, - const ares_dns_record_t *dnsrec) +CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data, + const ares_dns_record_t *dnsrec, + struct Curl_https_rrinfo *hinfo) { - struct Curl_easy *data = arg; CURLcode result = CURLE_OK; size_t i; - struct thread_data *res = &data->state.async.thdata; - - res->num_pending--; - (void)timeouts; - if((ARES_SUCCESS != status) || !dnsrec) - return; for(i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) { const char *target; @@ -196,24 +188,24 @@ void Curl_dnsrec_done_cb(void *arg, ares_status_t status, is in ServiceMode */ target = ares_dns_rr_get_str(rr, ARES_RR_HTTPS_TARGET); if(target && target[0]) { - res->hinfo.target = strdup(target); - if(!res->hinfo.target) { + hinfo->target = strdup(target); + if(!hinfo->target) { result = CURLE_OUT_OF_MEMORY; goto out; } - CURL_TRC_DNS(data, "HTTPS RR target: %s", res->hinfo.target); + CURL_TRC_DNS(data, "HTTPS RR target: %s", hinfo->target); } CURL_TRC_DNS(data, "HTTPS RR priority: %u", ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY)); for(opt = 0; opt < ares_dns_rr_get_opt_cnt(rr, ARES_RR_HTTPS_PARAMS); opt++) { - result = httpsrr_opt(data, rr, ARES_RR_HTTPS_PARAMS, opt); + result = httpsrr_opt(data, rr, ARES_RR_HTTPS_PARAMS, opt, hinfo); if(result) break; } } out: - res->result = result; + return result; } #endif /* USE_ARES */ diff --git a/lib/httpsrr.h b/lib/httpsrr.h index 1d71a57433..f946282cb9 100644 --- a/lib/httpsrr.h +++ b/lib/httpsrr.h @@ -77,10 +77,9 @@ void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); #define HTTPS_RR_CODE_IPV6 0x06 #if defined(USE_ARES) -void Curl_dnsrec_done_cb(void *arg, ares_status_t status, - size_t timeouts, - const ares_dns_record_t *dnsrec); - +CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data, + const ares_dns_record_t *dnsrec, + struct Curl_https_rrinfo *hinfo); #endif /* USE_ARES */ #endif /* USE_HTTPSRR */ diff --git a/lib/multi.c b/lib/multi.c index db2bfdcfaa..c3dc3fb5e2 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -538,8 +538,8 @@ static CURLcode multi_done(struct Curl_easy *data, /* Stop if multi_done() has already been called */ return CURLE_OK; - /* Stop the resolver and free its own resources (but not dns_entry yet). */ - Curl_resolver_kill(data); + /* Shut down any ongoing async resolver operation. */ + Curl_async_shutdown(data); /* Cleanup possible redirect junk */ Curl_safefree(data->req.newurl); @@ -2050,35 +2050,12 @@ static CURLMcode state_resolving(struct Curl_multi *multi, CURLcode *resultp) { struct Curl_dns_entry *dns = NULL; - struct connectdata *conn = data->conn; - const char *hostname; - CURLcode result = CURLE_OK; + CURLcode result; CURLMcode rc = CURLM_OK; - DEBUGASSERT(conn); -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy) - hostname = conn->http_proxy.host.name; - else -#endif - if(conn->bits.conn_to_host) - hostname = conn->conn_to_host.name; - else - hostname = conn->host.name; - - /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, hostname, conn->primary.remote_port); - - if(dns) { - /* Tell a possibly async resolver we no longer need the results. */ - Curl_resolver_set_result(data, dns); - result = CURLE_OK; - infof(data, "Hostname '%s' was found in DNS cache", hostname); - } - - if(!dns) - result = Curl_resolv_check(data, &dns); - + result = Curl_resolv_check(data, &dns); + CURL_TRC_DNS(data, "Curl_resolv_check() -> %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 diff --git a/lib/request.h b/lib/request.h index 4c77be962f..0020fa9b56 100644 --- a/lib/request.h +++ b/lib/request.h @@ -32,9 +32,6 @@ /* forward declarations */ struct UserDefined; -#ifndef CURL_DISABLE_DOH -struct doh_probes; -#endif enum expect100 { EXP100_SEND_DATA, /* enough waiting, just send the body now */ @@ -117,9 +114,6 @@ struct SingleRequest { struct SSHPROTO *ssh; struct TELNET *telnet; } p; -#ifndef CURL_DISABLE_DOH - struct doh_probes *doh; /* DoH specific data for this request */ -#endif #ifndef CURL_DISABLE_COOKIES unsigned char setcookies; #endif diff --git a/lib/setopt.c b/lib/setopt.c index f982366209..07ccb933f8 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -2534,7 +2534,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, break; #endif #endif -#ifdef USE_ARES +#ifdef CURLRES_ARES case CURLOPT_DNS_SERVERS: result = Curl_setstropt(&data->set.str[STRING_DNS_SERVERS], ptr); if(result) diff --git a/lib/socks.c b/lib/socks.c index 431f9d57bd..b6bbfacc8f 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -321,16 +321,16 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, /* DNS resolve only for SOCKS4, not SOCKS4a */ if(!protocol4a) { - enum resolve_t rc = - Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns); + result = Curl_resolv(data, sx->hostname, sx->remote_port, + cf->conn->ip_version, TRUE, &dns); - if(rc == CURLRESOLV_ERROR) - return CURLPX_RESOLVE_HOST; - else if(rc == CURLRESOLV_PENDING) { + if(result == CURLE_AGAIN) { sxstate(sx, data, CONNECT_RESOLVING); infof(data, "SOCKS4 non-blocking resolve of %s", sx->hostname); return CURLPX_OK; } + else if(result) + return CURLPX_RESOLVE_HOST; sxstate(sx, data, CONNECT_RESOLVED); goto CONNECT_RESOLVED; } @@ -341,21 +341,11 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, case CONNECT_RESOLVING: /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, sx->hostname, conn->primary.remote_port); - - if(dns) { - /* Tell a possibly async resolver we no longer need the results. */ - Curl_resolver_set_result(data, dns); - infof(data, "Hostname '%s' was found", sx->hostname); - sxstate(sx, data, CONNECT_RESOLVED); - } - else { - result = Curl_resolv_check(data, &dns); - if(!dns) { - if(result) - return CURLPX_RESOLVE_HOST; - return CURLPX_OK; - } + result = Curl_resolv_check(data, &dns); + if(!dns) { + if(result) + return CURLPX_RESOLVE_HOST; + return CURLPX_OK; } FALLTHROUGH(); case CONNECT_RESOLVED: @@ -792,16 +782,15 @@ CONNECT_AUTH_INIT: case CONNECT_REQ_INIT: CONNECT_REQ_INIT: if(socks5_resolve_local) { - enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port, - TRUE, &dns); + result = Curl_resolv(data, sx->hostname, sx->remote_port, + cf->conn->ip_version, TRUE, &dns); - if(rc == CURLRESOLV_ERROR) - return CURLPX_RESOLVE_HOST; - - if(rc == CURLRESOLV_PENDING) { + if(result == CURLE_AGAIN) { sxstate(sx, data, CONNECT_RESOLVING); return CURLPX_OK; } + else if(result) + return CURLPX_RESOLVE_HOST; sxstate(sx, data, CONNECT_RESOLVED); goto CONNECT_RESOLVED; } @@ -809,21 +798,11 @@ CONNECT_REQ_INIT: case CONNECT_RESOLVING: /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, sx->hostname, sx->remote_port); - - if(dns) { - /* Tell a possibly async resolver we no longer need the results. */ - Curl_resolver_set_result(data, dns); - infof(data, "SOCKS5: hostname '%s' found", sx->hostname); - } - + result = Curl_resolv_check(data, &dns); if(!dns) { - result = Curl_resolv_check(data, &dns); - if(!dns) { - if(result) - return CURLPX_RESOLVE_HOST; - return CURLPX_OK; - } + if(result) + return CURLPX_RESOLVE_HOST; + return CURLPX_OK; } FALLTHROUGH(); case CONNECT_RESOLVED: diff --git a/lib/url.c b/lib/url.c index 4a9e6b5fea..856d71366e 100644 --- a/lib/url.c +++ b/lib/url.c @@ -290,9 +290,7 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_safefree(data->info.contenttype); Curl_safefree(data->info.wouldredirect); - /* this destroys the channel and we cannot use it anymore after this */ - Curl_resolver_cancel(data); - Curl_resolver_cleanup(data->state.async.resolver); + Curl_async_shutdown(data); data_priority_cleanup(data); @@ -519,12 +517,6 @@ CURLcode Curl_open(struct Curl_easy **curl) #endif Curl_netrc_init(&data->state.netrc); - result = Curl_resolver_init(data, &data->state.async.resolver); - if(result) { - DEBUGF(fprintf(stderr, "Error: resolver_init failed\n")); - goto out; - } - result = Curl_init_userdefined(data); if(result) goto out; @@ -544,7 +536,6 @@ CURLcode Curl_open(struct Curl_easy **curl) out: if(result) { - Curl_resolver_cleanup(data->state.async.resolver); Curl_dyn_free(&data->state.headerb); Curl_freeset(data); Curl_req_free(&data->req, data); @@ -3173,7 +3164,7 @@ static CURLcode resolve_server(struct Curl_easy *data, struct hostname *ehost; timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); const char *peertype = "host"; - int rc; + CURLcode result; #ifdef USE_UNIX_SOCKETS char *unix_path = conn->unix_domain_socket; @@ -3214,22 +3205,25 @@ static CURLcode resolve_server(struct Curl_easy *data, if(!conn->hostname_resolve) return CURLE_OUT_OF_MEMORY; - rc = Curl_resolv_timeout(data, conn->hostname_resolve, - conn->primary.remote_port, - &conn->dns_entry, timeout_ms); - if(rc == CURLRESOLV_PENDING) + result = Curl_resolv_timeout(data, conn->hostname_resolve, + conn->primary.remote_port, conn->ip_version, + &conn->dns_entry, timeout_ms); + if(result == CURLE_AGAIN) { + DEBUGASSERT(!conn->dns_entry); *async = TRUE; - else if(rc == CURLRESOLV_TIMEDOUT) { + return CURLE_OK; + } + else if(result == CURLE_OPERATION_TIMEDOUT) { failf(data, "Failed to resolve %s '%s' with timeout after %" FMT_TIMEDIFF_T " ms", peertype, ehost->dispname, Curl_timediff(Curl_now(), data->progress.t_startsingle)); return CURLE_OPERATION_TIMEDOUT; } - else if(!conn->dns_entry) { + else if(result) { failf(data, "Could not resolve %s: %s", peertype, ehost->dispname); - return CURLE_COULDNT_RESOLVE_HOST; + return result; } - + DEBUGASSERT(conn->dns_entry); return CURLE_OK; } diff --git a/lib/urldata.h b/lib/urldata.h index b662073f71..ebcfeb2a7f 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -556,21 +556,6 @@ struct hostname { #define CURL_WANT_RECV(data) \ (((data)->req.keepon & KEEP_RECVBITS) == KEEP_RECV) -#if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) -#define USE_CURL_ASYNC -struct Curl_async { - struct Curl_dns_entry *dns; -#ifdef CURLRES_ASYNCH - struct thread_data thdata; - void *resolver; /* resolver state, if it is used in the URL state - - ares_channel e.g. */ -#endif - int port; - BIT(done); /* set TRUE when the lookup is complete */ -}; - -#endif - #define FIRSTSOCKET 0 #define SECONDARYSOCKET 1 diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 4c068adc6b..4a59dd70ee 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3975,7 +3975,8 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, struct Curl_dns_entry *dns = NULL; if(peer->hostname) - dns = Curl_fetch_addr(data, peer->hostname, peer->port); + dns = Curl_dnscache_get(data, peer->hostname, peer->port, + cf->conn->ip_version); if(!dns) { infof(data, "ECH: requested but no DNS info available"); if(data->set.tls_ech & CURLECH_HARD) diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 695988ea0f..24ab09caa9 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -980,10 +980,8 @@ init_config_builder_ech(struct Curl_easy *data, } else { if(connssl->peer.hostname) { - dns = Curl_fetch_addr( - data, - connssl->peer.hostname, - connssl->peer.port); + dns = Curl_dnscache_get(data, connssl->peer.hostname, + connssl->peer.port, data->conn->ip_version); } if(!dns) { failf(data, "rustls: ECH requested but no DNS info available"); diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index a29267054f..d12eec6257 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -1379,7 +1379,8 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, struct ssl_connect_data *connssl = cf->ctx; struct Curl_dns_entry *dns = NULL; - dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port); + dns = Curl_dnscache_get(data, connssl->peer.hostname, connssl->peer.port, + cf->conn->ip_version); if(!dns) { infof(data, "ECH: requested but no DNS info available"); if(data->set.tls_ech & CURLECH_HARD) { diff --git a/tests/http/scorecard.py b/tests/http/scorecard.py index f19db34cad..7f8cd2a766 100644 --- a/tests/http/scorecard.py +++ b/tests/http/scorecard.py @@ -113,11 +113,12 @@ class ScoreCard: def setup_resources(self, server_docs: str, downloads: Optional[List[int]] = None): - for fsize in downloads: - label = self.fmt_size(fsize) - fname = f'score{label}.data' - self._make_docs_file(docs_dir=server_docs, - fname=fname, fsize=fsize) + if downloads is not None: + for fsize in downloads: + label = self.fmt_size(fsize) + fname = f'score{label}.data' + self._make_docs_file(docs_dir=server_docs, + fname=fname, fsize=fsize) self._make_docs_file(docs_dir=server_docs, fname='reqs10.data', fsize=10*1024) -- 2.47.2