c-ares: really lazy init channel

Only initialize the c-ares channel when we start resolving and not
alreads when the application sets `CURLOPT_DNS_SERVERS` and friends.

Creating an ares channel takes considerable time and when we have the
DNS information for a transfer already cached, we do not need it.

Closes #17167
This commit is contained in:
Stefan Eissing 2025-04-24 12:18:33 +02:00 committed by Daniel Stenberg
parent 437c72fbba
commit 7bf576064c
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
4 changed files with 86 additions and 115 deletions

View file

@ -113,6 +113,9 @@
static int ares_ver = 0; static int ares_ver = 0;
static CURLcode async_ares_set_dns_servers(struct Curl_easy *data,
bool reset_on_null);
/* /*
* Curl_async_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 * resolve API. Called from curl_global_init() to initialize global resolver
@ -159,6 +162,8 @@ static CURLcode async_ares_init(struct Curl_easy *data)
int status; int status;
struct ares_options options; struct ares_options options;
int optmask = ARES_OPT_SOCK_STATE_CB; int optmask = ARES_OPT_SOCK_STATE_CB;
CURLcode rc = CURLE_OK;
options.sock_state_cb = sock_state_cb; options.sock_state_cb = sock_state_cb;
options.sock_state_cb_data = data; options.sock_state_cb_data = data;
@ -181,25 +186,35 @@ static CURLcode async_ares_init(struct Curl_easy *data)
status = ares_init_options(&ares->channel, &options, optmask); status = ares_init_options(&ares->channel, &options, optmask);
if(status != ARES_SUCCESS) { if(status != ARES_SUCCESS) {
ares->channel = NULL; ares->channel = NULL;
if(status == ARES_ENOMEM) rc = (status == ARES_ENOMEM) ?
return CURLE_OUT_OF_MEMORY; CURLE_OUT_OF_MEMORY : CURLE_FAILED_INIT;
else goto out;
return CURLE_FAILED_INIT;
} }
#if defined(CURLDEBUG) && defined(HAVE_CARES_PORTS_CSV)
else {
const char *env = getenv("CURL_DNS_SERVER");
if(env) {
int rc = ares_set_servers_ports_csv(ares->channel, env);
if(rc)
infof(data, "ares_set_servers_ports_csv failed: %d", rc);
}
}
#endif
return CURLE_OK; rc = async_ares_set_dns_servers(data, FALSE);
/* make sure that all other returns from this function should destroy the if(rc && rc != CURLE_NOT_BUILT_IN)
ares channel before returning error! */ goto out;
rc = Curl_async_ares_set_dns_interface(data);
if(rc && rc != CURLE_NOT_BUILT_IN)
goto out;
rc = Curl_async_ares_set_dns_local_ip4(data);
if(rc && rc != CURLE_NOT_BUILT_IN)
goto out;
rc = Curl_async_ares_set_dns_local_ip6(data);
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;
} }
static CURLcode async_ares_init_lazy(struct Curl_easy *data) static CURLcode async_ares_init_lazy(struct Curl_easy *data)
@ -794,43 +809,39 @@ struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data,
return NULL; /* no struct yet */ return NULL; /* no struct yet */
} }
CURLcode Curl_set_dns_servers(struct Curl_easy *data, /* Set what DNS server are is to use. This is called in 2 situations:
char *servers) * 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,
bool reset_on_null)
{ {
struct async_ares_ctx *ares = &data->state.async.ares; struct async_ares_ctx *ares = &data->state.async.ares;
CURLcode result = CURLE_NOT_BUILT_IN; CURLcode result = CURLE_NOT_BUILT_IN;
int ares_result; const char *servers = data->set.str[STRING_DNS_SERVERS];
int ares_result = ARES_SUCCESS;
#if defined(CURLDEBUG) && defined(HAVE_CARES_SERVERS_CSV)
if(getenv("CURL_DNS_SERVER"))
servers = getenv("CURL_DNS_SERVER");
#endif
/* If server is NULL, this purges all DNS servers from c-ares. Reset it to
* default.
*/
if(!servers) { if(!servers) {
Curl_async_destroy(data); if(reset_on_null) {
result = async_ares_init_lazy(data); Curl_async_destroy(data);
if(!result) {
/* this now needs to restore the other options set to c-ares */
if(data->set.str[STRING_DNS_INTERFACE])
(void)Curl_set_dns_interface(data,
data->set.str[STRING_DNS_INTERFACE]);
if(data->set.str[STRING_DNS_LOCAL_IP4])
(void)Curl_set_dns_local_ip4(data,
data->set.str[STRING_DNS_LOCAL_IP4]);
if(data->set.str[STRING_DNS_LOCAL_IP6])
(void)Curl_set_dns_local_ip6(data,
data->set.str[STRING_DNS_LOCAL_IP6]);
} }
return result; return CURLE_OK;
} }
#ifdef HAVE_CARES_SERVERS_CSV #ifdef HAVE_CARES_SERVERS_CSV
result = async_ares_init_lazy(data); /* if channel is not there, this is just a parameter check */
if(result) if(ares->channel)
return result;
#ifdef HAVE_CARES_PORTS_CSV #ifdef HAVE_CARES_PORTS_CSV
ares_result = ares_set_servers_ports_csv(ares->channel, servers); ares_result = ares_set_servers_ports_csv(ares->channel, servers);
#else #else
ares_result = ares_set_servers_csv(ares->channel, servers); ares_result = ares_set_servers_csv(ares->channel, servers);
#endif #endif
switch(ares_result) { switch(ares_result) {
case ARES_SUCCESS: case ARES_SUCCESS:
@ -854,21 +865,23 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data,
return result; return result;
} }
CURLcode Curl_set_dns_interface(struct Curl_easy *data, CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data)
const char *interf) {
return async_ares_set_dns_servers(data, TRUE);
}
CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data)
{ {
#ifdef HAVE_CARES_LOCAL_DEV #ifdef HAVE_CARES_LOCAL_DEV
struct async_ares_ctx *ares = &data->state.async.ares; struct async_ares_ctx *ares = &data->state.async.ares;
CURLcode result; const char *interf = data->set.str[STRING_DNS_INTERFACE];
if(!interf) if(!interf)
interf = ""; interf = "";
result = async_ares_init_lazy(data); /* if channel is not there, this is just a parameter check */
if(result) if(ares->channel)
return result; ares_set_local_dev(ares->channel, interf);
ares_set_local_dev(ares->channel, interf);
return CURLE_OK; return CURLE_OK;
#else /* c-ares version too old! */ #else /* c-ares version too old! */
@ -878,13 +891,12 @@ CURLcode Curl_set_dns_interface(struct Curl_easy *data,
#endif #endif
} }
CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data)
const char *local_ip4)
{ {
#ifdef HAVE_CARES_SET_LOCAL #ifdef HAVE_CARES_SET_LOCAL
struct async_ares_ctx *ares = &data->state.async.ares; struct async_ares_ctx *ares = &data->state.async.ares;
struct in_addr a4; struct in_addr a4;
CURLcode result; const char *local_ip4 = data->set.str[STRING_DNS_LOCAL_IP4];
if((!local_ip4) || (local_ip4[0] == 0)) { if((!local_ip4) || (local_ip4[0] == 0)) {
a4.s_addr = 0; /* disabled: do not bind to a specific address */ a4.s_addr = 0; /* disabled: do not bind to a specific address */
@ -896,11 +908,9 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data,
} }
} }
result = async_ares_init_lazy(data); /* if channel is not there yet, this is just a parameter check */
if(result) if(ares->channel)
return result; ares_set_local_ip4(ares->channel, ntohl(a4.s_addr));
ares_set_local_ip4(ares->channel, ntohl(a4.s_addr));
return CURLE_OK; return CURLE_OK;
#else /* c-ares version too old! */ #else /* c-ares version too old! */
@ -910,13 +920,12 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data,
#endif #endif
} }
CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data)
const char *local_ip6)
{ {
#if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6) #if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6)
struct async_ares_ctx *ares = &data->state.async.ares; struct async_ares_ctx *ares = &data->state.async.ares;
unsigned char a6[INET6_ADDRSTRLEN]; unsigned char a6[INET6_ADDRSTRLEN];
CURLcode result; const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6];
if((!local_ip6) || (local_ip6[0] == 0)) { if((!local_ip6) || (local_ip6[0] == 0)) {
/* disabled: do not bind to a specific address */ /* disabled: do not bind to a specific address */
@ -929,11 +938,9 @@ CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data,
} }
} }
result = async_ares_init_lazy(data); /* if channel is not there, this is just a parameter check */
if(result) if(ares->channel)
return result; ares_set_local_ip6(ares->channel, a6);
ares_set_local_ip6(ares->channel, a6);
return CURLE_OK; return CURLE_OK;
#else /* c-ares version too old! */ #else /* c-ares version too old! */

View file

@ -154,31 +154,17 @@ struct async_ares_ctx {
void Curl_async_ares_shutdown(struct Curl_easy *data); void Curl_async_ares_shutdown(struct Curl_easy *data);
void Curl_async_ares_destroy(struct Curl_easy *data); void Curl_async_ares_destroy(struct Curl_easy *data);
/* /* Set the DNS server to use by ares, from `data` settings. */
* Function provided by the resolver backend to set DNS servers to use. CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data);
*/
CURLcode Curl_set_dns_servers(struct Curl_easy *data, char *servers);
/* /* Set the DNS interfacer to use by ares, from `data` settings. */
* Function provided by the resolver backend to set CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data);
* outgoing interface to use for DNS requests
*/
CURLcode Curl_set_dns_interface(struct Curl_easy *data,
const char *interf);
/* /* Set the local ipv4 address to use by ares, from `data` settings. */
* Function provided by the resolver backend to set CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data);
* 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);
/* /* Set the local ipv6 address to use by ares, from `data` settings. */
* Function provided by the resolver backend to set CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data);
* 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 */ #endif /* CURLRES_ARES */

View file

@ -1050,28 +1050,6 @@ CURL *curl_easy_duphandle(CURL *d)
} }
#endif #endif
#ifdef CURLRES_ARES
{
CURLcode rc;
rc = Curl_set_dns_servers(outcurl, data->set.str[STRING_DNS_SERVERS]);
if(rc && rc != CURLE_NOT_BUILT_IN)
goto fail;
rc = Curl_set_dns_interface(outcurl, data->set.str[STRING_DNS_INTERFACE]);
if(rc && rc != CURLE_NOT_BUILT_IN)
goto fail;
rc = Curl_set_dns_local_ip4(outcurl, data->set.str[STRING_DNS_LOCAL_IP4]);
if(rc && rc != CURLE_NOT_BUILT_IN)
goto fail;
rc = Curl_set_dns_local_ip6(outcurl, data->set.str[STRING_DNS_LOCAL_IP6]);
if(rc && rc != CURLE_NOT_BUILT_IN)
goto fail;
}
#endif /* USE_ARES */
outcurl->magic = CURLEASY_MAGIC_NUMBER; outcurl->magic = CURLEASY_MAGIC_NUMBER;
/* we reach this point and thus we are OK */ /* we reach this point and thus we are OK */

View file

@ -2540,25 +2540,25 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option,
result = Curl_setstropt(&data->set.str[STRING_DNS_SERVERS], ptr); result = Curl_setstropt(&data->set.str[STRING_DNS_SERVERS], ptr);
if(result) if(result)
return result; return result;
return Curl_set_dns_servers(data, data->set.str[STRING_DNS_SERVERS]); return Curl_async_ares_set_dns_servers(data);
case CURLOPT_DNS_INTERFACE: case CURLOPT_DNS_INTERFACE:
result = Curl_setstropt(&data->set.str[STRING_DNS_INTERFACE], ptr); result = Curl_setstropt(&data->set.str[STRING_DNS_INTERFACE], ptr);
if(result) if(result)
return result; return result;
return Curl_set_dns_interface(data, data->set.str[STRING_DNS_INTERFACE]); return Curl_async_ares_set_dns_interface(data);
case CURLOPT_DNS_LOCAL_IP4: case CURLOPT_DNS_LOCAL_IP4:
result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP4], ptr); result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP4], ptr);
if(result) if(result)
return result; return result;
return Curl_set_dns_local_ip4(data, data->set.str[STRING_DNS_LOCAL_IP4]); return Curl_async_ares_set_dns_local_ip4(data);
case CURLOPT_DNS_LOCAL_IP6: case CURLOPT_DNS_LOCAL_IP6:
result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP6], ptr); result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP6], ptr);
if(result) if(result)
return result; return result;
return Curl_set_dns_local_ip6(data, data->set.str[STRING_DNS_LOCAL_IP6]); return Curl_async_ares_set_dns_local_ip6(data);
#endif #endif
#ifdef USE_UNIX_SOCKETS #ifdef USE_UNIX_SOCKETS