/*************************************************************************** * _ _ ____ _ * 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" #ifdef USE_RESOLV_ARES /*********************************************************************** * Only for ares-enabled builds and only for functions that fulfill * the asynch resolver backend API as defined in asyn.h, * nothing else belongs in this file! **********************************************************************/ #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 "cfilters.h" #include "curl_addrinfo.h" #include "curl_trc.h" #include "hostip.h" #include "url.h" #include "multiif.h" #include "curlx/inet_pton.h" #include "connect.h" #include "select.h" #include "progress.h" #include "curlx/timediff.h" #include "httpsrr.h" #include #if ARES_VERSION < 0x011000 #error "requires c-ares 1.16.0 or newer" #endif #ifdef USE_HTTPSRR #if ARES_VERSION < 0x011c00 #error "requires c-ares 1.28.0 or newer for HTTPSRR" #endif #define HTTPSRR_WORKS #endif #define CARES_TIMEOUT_PER_ATTEMPT 2000 static int ares_ver = 0; static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, struct Curl_resolv_async *async); static CURLcode async_ares_set_dns_interface(struct Curl_easy *data, struct Curl_resolv_async *async); static CURLcode async_ares_set_dns_local_ip4(struct Curl_easy *data, struct Curl_resolv_async *async); static CURLcode async_ares_set_dns_local_ip6(struct Curl_easy *data, struct Curl_resolv_async *async); /* * Curl_async_global_init() - the generic low-level asynchronous name * resolve API. Called from curl_global_init() to initialize global resolver * environment. Initializes ares library. */ int Curl_async_global_init(void) { #ifdef CARES_HAVE_ARES_LIBRARY_INIT if(ares_library_init(ARES_LIB_INIT_ALL)) { return CURLE_FAILED_INIT; } #endif ares_version(&ares_ver); return CURLE_OK; } /* * Curl_async_global_cleanup() * * Called from curl_global_cleanup() to destroy global resolver environment. * Deinitializes ares library. */ void Curl_async_global_cleanup(void) { #ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP ares_library_cleanup(); #endif } static void sock_state_cb(void *data, ares_socket_t socket_fd, int readable, int writable) { struct Curl_easy *easy = data; if(!readable && !writable) { DEBUGASSERT(easy); Curl_multi_will_close(easy, socket_fd); } } static CURLcode async_ares_init(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = &async->ares; int status; struct ares_options options; int optmask = ARES_OPT_SOCK_STATE_CB; CURLcode rc = CURLE_OK; /* initial status - failed */ ares->ares_status = ARES_ENOTFOUND; async->queries_ongoing = 0; options.sock_state_cb = sock_state_cb; options.sock_state_cb_data = data; DEBUGASSERT(!ares->channel); /* if c ares < 1.20.0: curl set timeout to CARES_TIMEOUT_PER_ATTEMPT (2s) if c-ares >= 1.20.0 it already has the timeout to 2s, curl does not need to set the timeout value; if c-ares >= 1.24.0, user can set the timeout via /etc/resolv.conf to overwrite c-ares' timeout. */ DEBUGASSERT(ares_ver); if(ares_ver < 0x011400) { options.timeout = CARES_TIMEOUT_PER_ATTEMPT; optmask |= ARES_OPT_TIMEOUTMS; } status = ares_init_options(&ares->channel, &options, optmask); if(status != ARES_SUCCESS) { ares->channel = NULL; rc = (status == ARES_ENOMEM) ? CURLE_OUT_OF_MEMORY : CURLE_FAILED_INIT; goto out; } rc = async_ares_set_dns_servers(data, async); if(rc && rc != CURLE_NOT_BUILT_IN) goto out; rc = async_ares_set_dns_interface(data, async); if(rc && rc != CURLE_NOT_BUILT_IN) goto out; rc = async_ares_set_dns_local_ip4(data, async); if(rc && rc != CURLE_NOT_BUILT_IN) goto out; rc = async_ares_set_dns_local_ip6(data, async); if(rc && rc != CURLE_NOT_BUILT_IN) goto out; rc = CURLE_OK; out: if(rc && ares->channel) { ares_destroy(ares->channel); ares->channel = NULL; } return rc; } /* * async_ares_cleanup() cleans up async resolver data. */ static void async_ares_cleanup(struct Curl_resolv_async *async) { struct async_ares_ctx *ares = &async->ares; if(ares->res_A) { Curl_freeaddrinfo(ares->res_A); ares->res_A = NULL; } if(ares->res_AAAA) { Curl_freeaddrinfo(ares->res_AAAA); ares->res_AAAA = NULL; } #ifdef USE_HTTPSRR Curl_httpsrr_cleanup(&ares->hinfo); #endif } void Curl_async_ares_shutdown(struct Curl_easy *data, struct Curl_resolv_async *async) { /* c-ares has a method to "cancel" operations on a channel, but * as reported in #18216, this does not totally reset the channel * and ares may get stuck. * We need to destroy the channel and on demand create a new * one to avoid that. */ Curl_async_ares_destroy(data, async); } void Curl_async_ares_destroy(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = &async->ares; (void)data; if(ares->channel) { ares_destroy(ares->channel); ares->channel = NULL; } async_ares_cleanup(async); } CURLcode Curl_async_pollset(struct Curl_easy *data, struct Curl_resolv_async *async, struct easy_pollset *ps) { struct async_ares_ctx *ares = &async->ares; CURLcode result = CURLE_OK; if(ares->channel) { result = Curl_ares_pollset(data, ares->channel, ps); if(!result) { timediff_t ms = Curl_ares_timeout_ms(data, async, ares->channel); Curl_expire(data, ms, EXPIRE_ASYNC_NAME); } } return result; } /* * Curl_async_take_result() is called repeatedly to check if a previous * name resolve request has completed. It should also make sure to time-out if * the operation seems to take too long. * * Returns normal CURLcode errors. */ CURLcode Curl_async_take_result(struct Curl_easy *data, struct Curl_resolv_async *async, struct Curl_dns_entry **pdns) { struct async_ares_ctx *ares = &async->ares; CURLcode result = CURLE_OK; DEBUGASSERT(pdns); *pdns = NULL; if(!ares) return CURLE_FAILED_INIT; if(Curl_ares_perform(ares->channel, 0) < 0) { result = CURLE_UNRECOVERABLE_POLL; goto out; } if(async->queries_ongoing) { result = CURLE_AGAIN; goto out; } /* all c-ares operations done, what is the result to report? */ result = ares->result; if(ares->ares_status == ARES_SUCCESS && !result) { struct Curl_dns_entry *dns = Curl_dnscache_mk_entry2(data, async->dns_queries, &ares->res_AAAA, &ares->res_A, async->hostname, async->port); if(!dns) { result = CURLE_OUT_OF_MEMORY; goto out; } #ifdef HTTPSRR_WORKS if(async->dns_queries & CURL_DNSQ_HTTPS) { if(ares->hinfo.complete) { struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); if(!lhrr) result = CURLE_OUT_OF_MEMORY; else Curl_dns_entry_set_https_rr(dns, lhrr); } else Curl_dns_entry_set_https_rr(dns, NULL); } #endif if(!result) { *pdns = dns; } } /* if we have not found anything, report the proper * CURLE_COULDNT_RESOLVE_* code */ if(!result && !*pdns) { const char *msg = NULL; if(ares->ares_status != ARES_SUCCESS) msg = ares_strerror(ares->ares_status); result = Curl_resolver_error(data, msg); } CURL_TRC_DNS(data, "ares: is_resolved() result=%d, dns=%sfound", result, *pdns ? "" : "not "); async_ares_cleanup(async); out: if(result != CURLE_AGAIN) ares->result = result; return result; } static timediff_t async_ares_poll_timeout(struct async_ares_ctx *ares, timediff_t timeout_ms) { struct timeval *ares_calced, time_buf, max_timeout; int itimeout_ms; #if TIMEDIFF_T_MAX > INT_MAX itimeout_ms = (timeout_ms > INT_MAX) ? INT_MAX : ((timeout_ms < 0) ? -1 : (int)timeout_ms); #else itimeout_ms = (int)timeout_ms; #endif max_timeout.tv_sec = itimeout_ms / 1000; max_timeout.tv_usec = (itimeout_ms % 1000) * 1000; /* c-ares tells us the shortest timeout of any operation on channel */ ares_calced = ares_timeout(ares->channel, &max_timeout, &time_buf); /* use the timeout period ares returned to us above if less than one second is left, otherwise use 1000ms to make sure the progress callback gets called frequent enough */ if(!ares_calced->tv_sec) return (timediff_t)(ares_calced->tv_usec / 1000); else return 1000; } static const struct Curl_addrinfo *async_ares_get_ai( const struct Curl_addrinfo *ai, int ai_family, unsigned int index) { unsigned int i = 0; for(i = 0; ai; ai = ai->ai_next) { if(ai->ai_family == ai_family) { if(i == index) return ai; ++i; } } return NULL; } const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data, struct Curl_resolv_async *async, int ai_family, unsigned int index) { struct async_ares_ctx *ares = &async->ares; (void)data; switch(ai_family) { case AF_INET: if(ares->res_A) return async_ares_get_ai(ares->res_A, ai_family, index); break; case AF_INET6: if(ares->res_AAAA) return async_ares_get_ai(ares->res_AAAA, ai_family, index); break; default: break; } return NULL; } #ifdef USE_HTTPSRR const struct Curl_https_rrinfo *Curl_async_get_https( struct Curl_easy *data, struct Curl_resolv_async *async) { if(Curl_async_knows_https(data, async)) return &async->ares.hinfo; return NULL; } bool Curl_async_knows_https(struct Curl_easy *data, struct Curl_resolv_async *async) { (void)data; if(async->dns_queries & CURL_DNSQ_HTTPS) return ((async->dns_responses & CURL_DNSQ_HTTPS) || !async->queries_ongoing); return TRUE; /* we know it will never come */ } #endif /* USE_HTTPSRR */ /* * Curl_async_await() * * Waits for a resolve to finish. This function should be avoided since using * this risk getting the multi interface to "hang". * * 'pdns' MUST be non-NULL. * * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. */ CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id, struct Curl_dns_entry **pdns) { struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); struct async_ares_ctx *ares = async ? &async->ares : NULL; struct curltime start = *Curl_pgrs_now(data); CURLcode result = CURLE_OK; DEBUGASSERT(pdns); *pdns = NULL; /* clear on entry */ if(!ares) return CURLE_FAILED_INIT; /* Wait for the name resolve query to complete or time out. */ while(!result) { timediff_t timeout_ms; timeout_ms = Curl_timeleft_ms(data); if(!timeout_ms) { /* no applicable timeout from `data`*/ timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &start); if(elapsed_ms < CURL_TIMEOUT_RESOLVE_MS) timeout_ms = CURL_TIMEOUT_RESOLVE_MS - elapsed_ms; else timeout_ms = -1; } if(timeout_ms < 0) { result = CURLE_OPERATION_TIMEDOUT; break; } if(Curl_ares_perform(ares->channel, async_ares_poll_timeout(ares, timeout_ms)) < 0) { result = CURLE_UNRECOVERABLE_POLL; break; } result = Curl_async_take_result(data, async, pdns); if(result == CURLE_AGAIN) result = CURLE_OK; else if(result || *pdns) break; if(Curl_pgrsUpdate(data)) { result = CURLE_ABORTED_BY_CALLBACK; break; } } if(result) ares_cancel(ares->channel); return result; } /* * async_ares_node2addr() converts an address list provided by c-ares * to an internal libcurl compatible list. */ static struct Curl_addrinfo *async_ares_node2addr( struct ares_addrinfo_node *node) { /* traverse the ares_addrinfo_node list */ struct ares_addrinfo_node *ai; struct Curl_addrinfo *cafirst = NULL; struct Curl_addrinfo *calast = NULL; int error = 0; for(ai = node; ai != NULL; ai = ai->ai_next) { size_t ss_size; struct Curl_addrinfo *ca; /* ignore elements with unsupported address family, settle family-specific sockaddr structure size. */ if(ai->ai_family == AF_INET) ss_size = sizeof(struct sockaddr_in); #ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) ss_size = sizeof(struct sockaddr_in6); #endif else continue; /* ignore elements without required address info */ if(!ai->ai_addr || !(ai->ai_addrlen > 0)) continue; /* ignore elements with bogus address size */ if((size_t)ai->ai_addrlen < ss_size) continue; ca = curlx_malloc(sizeof(struct Curl_addrinfo) + ss_size); if(!ca) { error = EAI_MEMORY; break; } /* copy each structure member individually, member ordering, size, or padding might be different for each platform. */ ca->ai_flags = ai->ai_flags; ca->ai_family = ai->ai_family; ca->ai_socktype = ai->ai_socktype; ca->ai_protocol = ai->ai_protocol; ca->ai_addrlen = (curl_socklen_t)ss_size; ca->ai_addr = NULL; ca->ai_canonname = NULL; ca->ai_next = NULL; ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo)); memcpy(ca->ai_addr, ai->ai_addr, ss_size); /* if the return list is empty, this becomes the first element */ if(!cafirst) cafirst = ca; /* add this element last in the return list */ if(calast) calast->ai_next = ca; calast = ca; } /* if we failed, destroy the Curl_addrinfo list */ if(error) { Curl_freeaddrinfo(cafirst); cafirst = NULL; } return cafirst; } static void async_ares_A_cb(void *user_data, int status, int timeouts, struct ares_addrinfo *ares_ai) { struct Curl_resolv_async *async = user_data; struct async_ares_ctx *ares = async ? &async->ares : NULL; (void)timeouts; if(!async) return; async->dns_responses |= CURL_DNSQ_A; async->queries_ongoing--; if(status == ARES_SUCCESS) { ares->ares_status = ARES_SUCCESS; ares->res_A = async_ares_node2addr(ares_ai->nodes); ares_freeaddrinfo(ares_ai); } else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ ares->ares_status = status; } #ifdef CURLRES_IPV6 static void async_ares_AAAA_cb(void *user_data, int status, int timeouts, struct ares_addrinfo *ares_ai) { struct Curl_resolv_async *async = user_data; struct async_ares_ctx *ares = async ? &async->ares : NULL; (void)timeouts; if(!async) return; async->dns_responses |= CURL_DNSQ_AAAA; async->queries_ongoing--; if(status == ARES_SUCCESS) { ares->ares_status = ARES_SUCCESS; ares->res_AAAA = async_ares_node2addr(ares_ai->nodes); ares_freeaddrinfo(ares_ai); } else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */ ares->ares_status = status; } #endif /* CURLRES_IPV6 */ #ifdef USE_HTTPSRR static void async_ares_rr_done(void *user_data, ares_status_t status, size_t timeouts, const ares_dns_record_t *dnsrec) { struct Curl_resolv_async *async = user_data; struct async_ares_ctx *ares = async ? &async->ares : NULL; if(!async) return; (void)timeouts; async->dns_responses |= CURL_DNSQ_HTTPS; async->queries_ongoing--; if((ARES_SUCCESS != status) || !dnsrec) return; ares->result = Curl_httpsrr_from_ares(dnsrec, &ares->hinfo); } #endif /* USE_HTTPSRR */ /* * Curl_async_getaddrinfo() - when using ares * * Starts a name resolve for the given hostname and port number. */ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = &async->ares; char service[12]; int socktype; CURLcode result = CURLE_OK; if(ares->channel) { DEBUGASSERT(0); result = CURLE_FAILED_INIT; goto out; } result = async_ares_init(data, async); if(result) goto out; result = Curl_resolv_announce_start(data, ares->channel); if(result) goto out; #if defined(CURLVERBOSE) && ARES_VERSION >= 0x011800 /* >= v1.24.0 */ if(CURL_TRC_DNS_is_verbose(data)) { char *csv = ares_get_servers_csv(ares->channel); CURL_TRC_DNS(data, "ares: servers=%s", csv); ares_free_string(csv); } #endif curl_msnprintf(service, sizeof(service), "%d", async->port); socktype = (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ? SOCK_STREAM : SOCK_DGRAM; #ifdef CURLRES_IPV6 if(async->dns_queries & CURL_DNSQ_AAAA) { struct ares_addrinfo_hints hints; memset(&hints, 0, sizeof(hints)); CURL_TRC_DNS(data, "ares: query AAAA records for %s", async->hostname); hints.ai_family = PF_INET6; hints.ai_socktype = socktype; hints.ai_flags = ARES_AI_NUMERICSERV; ares_getaddrinfo(ares->channel, async->hostname, service, &hints, async_ares_AAAA_cb, async); async->queries_ongoing++; } #endif /* CURLRES_IPV6 */ if(async->dns_queries & CURL_DNSQ_A) { struct ares_addrinfo_hints hints; memset(&hints, 0, sizeof(hints)); CURL_TRC_DNS(data, "ares: query A records for %s", async->hostname); hints.ai_family = PF_INET; hints.ai_socktype = socktype; hints.ai_flags = ARES_AI_NUMERICSERV; ares_getaddrinfo(ares->channel, async->hostname, service, &hints, async_ares_A_cb, async); async->queries_ongoing++; } #ifdef USE_HTTPSRR memset(&ares->hinfo, 0, sizeof(ares->hinfo)); if(async->dns_queries & CURL_DNSQ_HTTPS) { char *rrname = NULL; if(async->port != 443) { rrname = curl_maprintf("_%d._https.%s", async->port, async->hostname); if(!rrname) return CURLE_OUT_OF_MEMORY; } CURL_TRC_DNS(data, "ares: query HTTPS records for %s", rrname ? rrname : async->hostname); ares->hinfo.rrname = rrname; async->queries_ongoing++; ares_query_dnsrec(ares->channel, rrname ? rrname : async->hostname, ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, async_ares_rr_done, async, NULL); } #endif /* USE_HTTPSRR */ out: ares->result = result; return result ? result : (async->queries_ongoing ? CURLE_AGAIN : CURLE_OK); } /* Set what DNS server are is to use. This is called in 2 situations: * 1. when the application does 'CURLOPT_DNS_SERVERS' and passing NULL * means any previous set value should be unset. Which means * we need to destroy and create the are channel anew, if there is one. * 2. When we lazy init the ares channel and NULL means that there * are no preferences and we do not reset any existing channel. */ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = async ? &async->ares : NULL; CURLcode result = CURLE_NOT_BUILT_IN; const char *servers = data->set.str[STRING_DNS_SERVERS]; int ares_result = ARES_SUCCESS; #ifdef DEBUGBUILD if(getenv("CURL_DNS_SERVER")) servers = getenv("CURL_DNS_SERVER"); #endif if(!servers) return CURLE_OK; /* if channel is not there, this is a parameter check */ if(ares && ares->channel) ares_result = ares_set_servers_ports_csv(ares->channel, servers); switch(ares_result) { case ARES_SUCCESS: result = CURLE_OK; break; case ARES_ENOMEM: result = CURLE_OUT_OF_MEMORY; break; case ARES_ENOTINITIALIZED: case ARES_ENODATA: case ARES_EBADSTR: default: DEBUGF(infof(data, "bad servers set")); result = CURLE_BAD_FUNCTION_ARGUMENT; break; } return result; } static CURLcode async_ares_set_dns_interface(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = async ? &async->ares : NULL; const char *interf = data->set.str[STRING_DNS_INTERFACE]; if(!interf) interf = ""; /* if channel is not there, this is a parameter check */ if(ares && ares->channel) ares_set_local_dev(ares->channel, interf); return CURLE_OK; } static CURLcode async_ares_set_dns_local_ip4(struct Curl_easy *data, struct Curl_resolv_async *async) { struct async_ares_ctx *ares = async ? &async->ares : NULL; struct in_addr a4; const char *local_ip4 = data->set.str[STRING_DNS_LOCAL_IP4]; if(!local_ip4 || (local_ip4[0] == 0)) { a4.s_addr = 0; /* disabled: do not bind to a specific address */ } else { if(curlx_inet_pton(AF_INET, local_ip4, &a4) != 1) { DEBUGF(infof(data, "bad DNS IPv4 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } /* if channel is not there yet, this is a parameter check */ if(ares && ares->channel) ares_set_local_ip4(ares->channel, ntohl(a4.s_addr)); return CURLE_OK; } static CURLcode async_ares_set_dns_local_ip6(struct Curl_easy *data, struct Curl_resolv_async *async) { #ifdef USE_IPV6 struct async_ares_ctx *ares = async ? &async->ares : NULL; unsigned char a6[INET6_ADDRSTRLEN]; const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6]; if(!local_ip6 || (local_ip6[0] == 0)) { /* disabled: do not bind to a specific address */ memset(a6, 0, sizeof(a6)); } else { if(curlx_inet_pton(AF_INET6, local_ip6, a6) != 1) { DEBUGF(infof(data, "bad DNS IPv6 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } /* if channel is not there, this is a parameter check */ if(ares && ares->channel) ares_set_local_ip6(ares->channel, a6); return CURLE_OK; #else /* no IPv6 support */ (void)data; (void)async; return CURLE_NOT_BUILT_IN; #endif } #endif /* USE_RESOLV_ARES */