mirror of
https://github.com/curl/curl.git
synced 2026-04-11 12:01:42 +08:00
Make separate queries for A and AAAA records. Hand out A/AAAA/HTTPS information before all queries are complete. Closes #21205
825 lines
23 KiB
C
825 lines
23 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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 <netinet/in.h>
|
|
#endif
|
|
#ifdef HAVE_NETDB_H
|
|
#include <netdb.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
#ifdef __VMS
|
|
#include <in.h>
|
|
#include <inet.h>
|
|
#endif
|
|
|
|
#include "urldata.h"
|
|
#include "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 <ares.h>
|
|
|
|
#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".
|
|
*
|
|
* 'entry' 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 */
|