mirror of
https://github.com/curl/curl.git
synced 2026-04-14 22:41:40 +03:00
parent
21fc17b265
commit
789282cb8d
6 changed files with 30 additions and 270 deletions
233
lib/asyn-ares.c
233
lib/asyn-ares.c
|
|
@ -58,35 +58,10 @@
|
|||
#include "progress.h"
|
||||
#include "curlx/timediff.h"
|
||||
#include "httpsrr.h"
|
||||
|
||||
#include <ares.h>
|
||||
#include <ares_version.h> /* really old c-ares did not include this by
|
||||
itself */
|
||||
|
||||
#if ARES_VERSION >= 0x010704
|
||||
#define HAVE_CARES_SERVERS_CSV 1
|
||||
#define HAVE_CARES_LOCAL_DEV 1
|
||||
#define HAVE_CARES_SET_LOCAL 1
|
||||
#endif
|
||||
|
||||
#if ARES_VERSION >= 0x010b00
|
||||
#define HAVE_CARES_PORTS_CSV 1
|
||||
#endif
|
||||
|
||||
#if ARES_VERSION >= 0x011000
|
||||
/* 1.16.0 or later has ares_getaddrinfo */
|
||||
#define HAVE_CARES_GETADDRINFO 1
|
||||
#else
|
||||
/* How long we are willing to wait for additional parallel responses after
|
||||
obtaining a "definitive" one. For old c-ares without getaddrinfo.
|
||||
|
||||
This is intended to equal the c-ares default timeout. curl always uses that
|
||||
default value. Unfortunately, c-ares does not expose its default timeout in
|
||||
its API, but it is officially documented as 5 seconds.
|
||||
|
||||
See query_completed_cb() for an explanation of how this is used.
|
||||
*/
|
||||
#define HAPPY_EYEBALLS_DNS_TIMEOUT 5000
|
||||
#if ARES_VERSION < 0x011000
|
||||
#error "requires c-ares 1.16.0 or newer"
|
||||
#endif
|
||||
|
||||
#ifdef USE_HTTPSRR
|
||||
|
|
@ -301,31 +276,6 @@ CURLcode Curl_async_take_result(struct Curl_easy *data,
|
|||
goto out;
|
||||
}
|
||||
|
||||
#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(ares->num_pending &&
|
||||
/* This is only set to non-zero if the timer was started. */
|
||||
(ares->happy_eyeballs_dns_time.tv_sec ||
|
||||
ares->happy_eyeballs_dns_time.tv_usec) &&
|
||||
(curlx_ptimediff_ms(Curl_pgrs_now(data),
|
||||
&ares->happy_eyeballs_dns_time) >=
|
||||
HAPPY_EYEBALLS_DNS_TIMEOUT)) {
|
||||
/* Remember that the EXPIRE_HAPPY_EYEBALLS_DNS timer is no longer
|
||||
running. */
|
||||
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);
|
||||
DEBUGASSERT(ares->num_pending == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!ares->num_pending) {
|
||||
/* all c-ares operations done, what is the result to report? */
|
||||
result = ares->result;
|
||||
|
|
@ -462,134 +412,6 @@ CURLcode Curl_async_await(struct Curl_easy *data,
|
|||
return result;
|
||||
}
|
||||
|
||||
#ifndef HAVE_CARES_GETADDRINFO
|
||||
|
||||
/* Connects results to the list */
|
||||
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(*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 */
|
||||
{
|
||||
/* 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_async_getaddrinfo(), when using ares, is completed either
|
||||
* successfully or with failure.
|
||||
*/
|
||||
static void async_ares_hostbyname_cb(void *user_data,
|
||||
int status,
|
||||
int timeouts,
|
||||
struct hostent *hostent)
|
||||
{
|
||||
struct Curl_easy *data = (struct Curl_easy *)user_data;
|
||||
struct Curl_resolv_async *async = data->state.async;
|
||||
struct async_ares_ctx *ares = async ? &async->ares : NULL;
|
||||
|
||||
(void)timeouts; /* ignored */
|
||||
if(!ares)
|
||||
return;
|
||||
|
||||
if(ARES_EDESTRUCTION == status)
|
||||
return;
|
||||
|
||||
if(ARES_SUCCESS == status) {
|
||||
ares->ares_status = status; /* one success overrules any error */
|
||||
async_addr_concat(&ares->temp_ai, Curl_he2ai(hostent, async->port));
|
||||
}
|
||||
else if(ares->ares_status != ARES_SUCCESS) {
|
||||
/* no success so far, remember last error */
|
||||
ares->ares_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_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(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
|
||||
aggressive in our timeouts for the other response. Other errors are
|
||||
either a result of bad input (which should affect all parallel
|
||||
requests), local or network conditions, non-definitive server
|
||||
responses, or us cancelling the request. */
|
||||
&& (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(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
|
||||
talking to a pool of DNS servers that can only successfully resolve
|
||||
IPv4 address, for example).
|
||||
|
||||
it is also possible that the other request could always take longer
|
||||
because it needs more time or only the second DNS server can fulfill it
|
||||
successfully. Yet, to align with the philosophy of Happy Eyeballs, we
|
||||
do not want to wait _too_ long or users will think requests are slow
|
||||
when IPv6 lookups do not actually work (but IPv4 ones do).
|
||||
|
||||
Now that we have a usable answer (some IPv4 addresses, some IPv6
|
||||
addresses, or "no such domain"), we start a timeout for the remaining
|
||||
pending responses. Even though it is typical that this resolved
|
||||
request came back quickly, that need not be the case. It might be that
|
||||
this completing request did not get a result from the first DNS
|
||||
server or even the first round of the whole DNS server pool. This
|
||||
could already be a long time after we issued the DNS queries in
|
||||
the first place. Without modifying c-ares, we cannot know exactly
|
||||
where in its retry cycle we are. We could guess based on how much
|
||||
time has gone by, but it does not really matter. Happy Eyeballs tells
|
||||
us that, given usable information in hand, we do not want to
|
||||
wait "too much longer" after we get a result.
|
||||
|
||||
We wait an additional amount of time equal to the default c-ares
|
||||
query timeout. That is enough time for a typical parallel response to
|
||||
arrive without being "too long". Even on a network where one of the two
|
||||
types of queries is failing or timing out constantly, this will usually
|
||||
mean we wait a total of the default c-ares timeout (5 seconds) plus the
|
||||
round trip time for the successful request, which seems bearable. The
|
||||
downside is that c-ares might race with us to issue one more retry
|
||||
before we give up, but it seems better to "waste" that request instead
|
||||
of trying to guess the perfect timeout to prevent it. After all, we do
|
||||
not even know where in the c-ares retry cycle each request is.
|
||||
*/
|
||||
ares->happy_eyeballs_dns_time = *Curl_pgrs_now(data);
|
||||
Curl_expire(data, HAPPY_EYEBALLS_DNS_TIMEOUT, EXPIRE_HAPPY_EYEBALLS_DNS);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
/* c-ares 1.16.0 or later */
|
||||
|
||||
/*
|
||||
* async_ares_node2addr() converts an address list provided by c-ares
|
||||
* to an internal libcurl compatible list.
|
||||
|
|
@ -688,8 +510,6 @@ static void async_ares_addrinfo_cb(void *user_data, int status, int timeouts,
|
|||
ares->temp_ai ? "" : "not ");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_HTTPSRR
|
||||
static void async_ares_rr_done(void *user_data, ares_status_t status,
|
||||
size_t timeouts,
|
||||
|
|
@ -751,7 +571,6 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CARES_GETADDRINFO
|
||||
{
|
||||
struct ares_addrinfo_hints hints;
|
||||
char service[12];
|
||||
|
|
@ -782,30 +601,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
|
|||
ares_getaddrinfo(ares->channel, async->hostname,
|
||||
service, &hints, async_ares_addrinfo_cb, data);
|
||||
}
|
||||
#else
|
||||
|
||||
#if ARES_VERSION >= 0x010601 /* IPv6 supported since 1.6.1 */
|
||||
if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
|
||||
/* The stack seems to be IPv6-enabled */
|
||||
/* areschannel is already setup in the Curl_open() function */
|
||||
CURL_TRC_DNS(data, "asyn-ares: fire off query for A");
|
||||
ares_gethostbyname(ares->channel, async->hostname, PF_INET,
|
||||
async_ares_hostbyname_cb, data);
|
||||
CURL_TRC_DNS(data, "asyn-ares: fire off query for AAAA");
|
||||
ares->num_pending = 2;
|
||||
ares_gethostbyname(ares->channel, async->hostname, PF_INET6,
|
||||
async_ares_hostbyname_cb, data);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* areschannel is already setup in the Curl_open() function */
|
||||
CURL_TRC_DNS(data, "asyn-ares: fire off query for A");
|
||||
ares->num_pending = 1;
|
||||
ares_gethostbyname(ares->channel, async->hostname, PF_INET,
|
||||
async_ares_hostbyname_cb, data);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_HTTPSRR
|
||||
{
|
||||
CURL_TRC_DNS(data, "asyn-ares: fire off query for HTTPSRR: %s",
|
||||
|
|
@ -839,7 +635,7 @@ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data,
|
|||
const char *servers = data->set.str[STRING_DNS_SERVERS];
|
||||
int ares_result = ARES_SUCCESS;
|
||||
|
||||
#if defined(DEBUGBUILD) && defined(HAVE_CARES_SERVERS_CSV)
|
||||
#ifdef DEBUGBUILD
|
||||
if(getenv("CURL_DNS_SERVER"))
|
||||
servers = getenv("CURL_DNS_SERVER");
|
||||
#endif
|
||||
|
|
@ -851,14 +647,9 @@ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data,
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
#ifdef HAVE_CARES_SERVERS_CSV
|
||||
/* if channel is not there, this is a parameter check */
|
||||
if(ares && ares->channel)
|
||||
#ifdef HAVE_CARES_PORTS_CSV
|
||||
ares_result = ares_set_servers_ports_csv(ares->channel, servers);
|
||||
#else
|
||||
ares_result = ares_set_servers_csv(ares->channel, servers);
|
||||
#endif
|
||||
switch(ares_result) {
|
||||
case ARES_SUCCESS:
|
||||
result = CURLE_OK;
|
||||
|
|
@ -874,10 +665,6 @@ static CURLcode async_ares_set_dns_servers(struct Curl_easy *data,
|
|||
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
break;
|
||||
}
|
||||
#else /* c-ares version too old! */
|
||||
(void)data;
|
||||
(void)(ares_result);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -888,7 +675,6 @@ CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data)
|
|||
|
||||
CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data)
|
||||
{
|
||||
#ifdef HAVE_CARES_LOCAL_DEV
|
||||
struct Curl_resolv_async *async = data->state.async;
|
||||
struct async_ares_ctx *ares = async ? &async->ares : NULL;
|
||||
const char *interf = data->set.str[STRING_DNS_INTERFACE];
|
||||
|
|
@ -901,15 +687,10 @@ CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data)
|
|||
ares_set_local_dev(ares->channel, interf);
|
||||
|
||||
return CURLE_OK;
|
||||
#else /* c-ares version too old! */
|
||||
(void)data;
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
#endif
|
||||
}
|
||||
|
||||
CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data)
|
||||
{
|
||||
#ifdef HAVE_CARES_SET_LOCAL
|
||||
struct Curl_resolv_async *async = data->state.async;
|
||||
struct async_ares_ctx *ares = async ? &async->ares : NULL;
|
||||
struct in_addr a4;
|
||||
|
|
@ -930,15 +711,11 @@ CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data)
|
|||
ares_set_local_ip4(ares->channel, ntohl(a4.s_addr));
|
||||
|
||||
return CURLE_OK;
|
||||
#else /* c-ares version too old! */
|
||||
(void)data;
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
#endif
|
||||
}
|
||||
|
||||
CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data)
|
||||
{
|
||||
#if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6)
|
||||
#ifdef USE_IPV6
|
||||
struct Curl_resolv_async *async = data->state.async;
|
||||
struct async_ares_ctx *ares = async ? &async->ares : NULL;
|
||||
unsigned char a6[INET6_ADDRSTRLEN];
|
||||
|
|
@ -960,7 +737,7 @@ CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data)
|
|||
ares_set_local_ip6(ares->channel, a6);
|
||||
|
||||
return CURLE_OK;
|
||||
#else /* c-ares version too old! */
|
||||
#else /* no IPv6 support */
|
||||
(void)data;
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue