#include "curl_setup.h"
#ifdef CURLRES_ARES
#include <limits.h>
#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 "sendf.h"
#include "hostip.h"
#include "hash.h"
#include "share.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 "strdup.h"
#include <ares.h>
#include <ares_version.h>
#if ARES_VERSION >= 0x010601
#define HAVE_CARES_IPV6 1
#endif
#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
#define HAVE_CARES_GETADDRINFO 1
#endif
#ifdef USE_HTTPSRR
#if ARES_VERSION < 0x011c00
#error "requires c-ares 1.28.0 or newer for HTTPSRR"
#endif
#define HTTPSRR_WORKS
#endif
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
#define HAPPY_EYEBALLS_DNS_TIMEOUT 5000
#define CARES_TIMEOUT_PER_ATTEMPT 2000
static int ares_ver = 0;
static CURLcode async_ares_set_dns_servers(struct Curl_easy *data,
bool reset_on_null);
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;
}
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 async_ares_ctx *ares = &data->state.async.ares;
int status;
struct ares_options options;
int optmask = ARES_OPT_SOCK_STATE_CB;
CURLcode rc = CURLE_OK;
options.sock_state_cb = sock_state_cb;
options.sock_state_cb_data = data;
DEBUGASSERT(!ares->channel);
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, FALSE);
if(rc && rc != CURLE_NOT_BUILT_IN)
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)
{
struct async_ares_ctx *ares = &data->state.async.ares;
if(!ares->channel)
return async_ares_init(data);
return CURLE_OK;
}
CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl)
{
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 async_ares_cleanup(struct Curl_easy *data);
void Curl_async_ares_shutdown(struct Curl_easy *data)
{
struct async_ares_ctx *ares = &data->state.async.ares;
if(ares->channel)
ares_cancel(ares->channel);
async_ares_cleanup(data);
}
void Curl_async_ares_destroy(struct Curl_easy *data)
{
struct async_ares_ctx *ares = &data->state.async.ares;
Curl_async_ares_shutdown(data);
if(ares->channel) {
ares_destroy(ares->channel);
ares->channel = NULL;
}
}
static void async_ares_cleanup(struct Curl_easy *data)
{
struct async_ares_ctx *ares = &data->state.async.ares;
if(ares->temp_ai) {
Curl_freeaddrinfo(ares->temp_ai);
ares->temp_ai = NULL;
}
#ifdef USE_HTTPSRR
Curl_httpsrr_cleanup(&ares->hinfo);
#endif
}
int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *socks)
{
struct async_ares_ctx *ares = &data->state.async.ares;
DEBUGASSERT(ares->channel);
return Curl_ares_getsock(data, ares->channel, socks);
}
CURLcode Curl_async_is_resolved(struct Curl_easy *data,
struct Curl_dns_entry **dns)
{
struct async_ares_ctx *ares = &data->state.async.ares;
CURLcode result = CURLE_OK;
DEBUGASSERT(dns);
*dns = NULL;
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
if(ares->num_pending
&& (ares->happy_eyeballs_dns_time.tv_sec
|| ares->happy_eyeballs_dns_time.tv_usec)
&& (curlx_timediff(curlx_now(), ares->happy_eyeballs_dns_time)
>= HAPPY_EYEBALLS_DNS_TIMEOUT)) {
memset(&ares->happy_eyeballs_dns_time, 0,
sizeof(ares->happy_eyeballs_dns_time));
ares_cancel(ares->channel);
DEBUGASSERT(ares->num_pending == 0);
}
#endif
if(!ares->num_pending) {
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;
#ifdef HTTPSRR_WORKS
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);
}
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_cleanup(data);
}
return result;
}
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 = curlx_now();
DEBUGASSERT(entry);
*entry = NULL;
timeout = Curl_timeleft(data, &now, TRUE);
if(timeout < 0) {
connclose(data->conn, "Timed out before name resolve started");
return CURLE_OPERATION_TIMEDOUT;
}
if(!timeout)
timeout = CURL_TIMEOUT_RESOLVE * 1000;
while(!result) {
struct timeval *tvp, tv, store;
int itimeout;
timediff_t timeout_ms;
#if TIMEDIFF_T_MAX > INT_MAX
itimeout = (timeout > INT_MAX) ? INT_MAX : (int)timeout;
#else
itimeout = (int)timeout;
#endif
store.tv_sec = itimeout/1000;
store.tv_usec = (itimeout%1000)*1000;
tvp = ares_timeout(ares->channel, &store, &tv);
if(!tvp->tv_sec)
timeout_ms = (timediff_t)(tvp->tv_usec/1000);
else
timeout_ms = 1000;
if(Curl_ares_perform(ares->channel, timeout_ms) < 0)
return CURLE_UNRECOVERABLE_POLL;
result = Curl_async_is_resolved(data, entry);
if(result || data->state.async.done)
break;
if(Curl_pgrsUpdate(data))
result = CURLE_ABORTED_BY_CALLBACK;
else {
struct curltime now2 = curlx_now();
timediff_t timediff = curlx_timediff(now2, now);
if(timediff <= 0)
timeout -= 1;
else if(timediff > timeout)
timeout = -1;
else
timeout -= timediff;
now = now2;
}
if(timeout < 0)
result = CURLE_OPERATION_TIMEDOUT;
}
data->state.async.done = TRUE;
if(entry)
*entry = data->state.async.dns;
if(result)
ares_cancel(ares->channel);
return result;
}
#ifndef HAVE_CARES_GETADDRINFO
static void async_addr_concat(struct Curl_addrinfo **pbase,
struct Curl_addrinfo *ai)
{
if(!ai)
return;
#ifdef USE_IPV6
if(*pbase && (*pbase)->ai_family == PF_INET6) {
struct Curl_addrinfo *tail = *pbase;
while(tail->ai_next)
tail = tail->ai_next;
tail->ai_next = ai;
}
else
#endif
{
struct Curl_addrinfo *tail = ai;
while(tail->ai_next)
tail = tail->ai_next;
tail->ai_next = *pbase;
*pbase = ai;
}
}
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 async_ares_ctx *ares = &data->state.async.ares;
(void)timeouts;
if(ARES_EDESTRUCTION == status)
return;
if(CURL_ASYNC_SUCCESS == status) {
ares->last_status = status;
async_addr_concat(&ares->temp_ai,
Curl_he2ai(hostent, data->state.async.port));
}
else if(ares->last_status != ARES_SUCCESS) {
ares->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(ares->num_pending
&& (status == ARES_SUCCESS || status == ARES_ENOTFOUND)) {
DEBUGASSERT(ares->num_pending == 1);
ares->happy_eyeballs_dns_time = curlx_now();
Curl_expire(data, HAPPY_EYEBALLS_DNS_TIMEOUT,
EXPIRE_HAPPY_EYEBALLS_DNS);
}
}
#else
static struct Curl_addrinfo *
async_ares_node2addr(struct ares_addrinfo_node *node)
{
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;
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;
if(!ai->ai_addr || !(ai->ai_addrlen > 0))
continue;
if((size_t)ai->ai_addrlen < ss_size)
continue;
ca = malloc(sizeof(struct Curl_addrinfo) + ss_size);
if(!ca) {
error = EAI_MEMORY;
break;
}
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(!cafirst)
cafirst = ca;
if(calast)
calast->ai_next = ca;
calast = ca;
}
if(error) {
Curl_freeaddrinfo(cafirst);
cafirst = NULL;
}
return cafirst;
}
static void async_ares_addrinfo_cb(void *user_data, int status, int timeouts,
struct ares_addrinfo *result)
{
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) {
ares->temp_ai = async_ares_node2addr(result->nodes);
ares->last_status = CURL_ASYNC_SUCCESS;
ares_freeaddrinfo(result);
}
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
struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data,
const char *hostname,
int port,
int ip_version,
int *waitp)
{
struct async_ares_ctx *ares = &data->state.async.ares;
*waitp = 0;
if(async_ares_init_lazy(data))
return NULL;
data->state.async.done = FALSE;
data->state.async.dns = NULL;
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;
ares->last_status = ARES_ENOTFOUND;
#ifdef HAVE_CARES_GETADDRINFO
{
struct ares_addrinfo_hints hints;
char service[12];
int pf = PF_INET;
memset(&hints, 0, sizeof(hints));
#ifdef CURLRES_IPV6
if((ip_version != CURL_IPRESOLVE_V4) &&
Curl_ipv6works(data)) {
if(ip_version == CURL_IPRESOLVE_V6)
pf = PF_INET6;
else
pf = PF_UNSPEC;
}
#endif
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;
hints.ai_flags = ARES_AI_NUMERICSERV;
msnprintf(service, sizeof(service), "%d", port);
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((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(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
{
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");
memset(&ares->hinfo, 0, sizeof(ares->hinfo));
ares->hinfo.port = -1;
ares->num_pending++;
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;
return NULL;
}
static CURLcode async_ares_set_dns_servers(struct Curl_easy *data,
bool reset_on_null)
{
struct async_ares_ctx *ares = &data->state.async.ares;
CURLcode result = CURLE_NOT_BUILT_IN;
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(!servers) {
if(reset_on_null) {
Curl_async_destroy(data);
}
return CURLE_OK;
}
#ifdef HAVE_CARES_SERVERS_CSV
if(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;
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;
}
#else
(void)data;
(void)(ares_result);
#endif
return result;
}
CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data)
{
return async_ares_set_dns_servers(data, TRUE);
}
CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data)
{
#ifdef HAVE_CARES_LOCAL_DEV
struct async_ares_ctx *ares = &data->state.async.ares;
const char *interf = data->set.str[STRING_DNS_INTERFACE];
if(!interf)
interf = "";
if(ares->channel)
ares_set_local_dev(ares->channel, interf);
return CURLE_OK;
#else
(void)data;
(void)interf;
return CURLE_NOT_BUILT_IN;
#endif
}
CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data)
{
#ifdef HAVE_CARES_SET_LOCAL
struct async_ares_ctx *ares = &data->state.async.ares;
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;
}
else {
if(curlx_inet_pton(AF_INET, local_ip4, &a4) != 1) {
DEBUGF(infof(data, "bad DNS IPv4 address"));
return CURLE_BAD_FUNCTION_ARGUMENT;
}
}
if(ares->channel)
ares_set_local_ip4(ares->channel, ntohl(a4.s_addr));
return CURLE_OK;
#else
(void)data;
(void)local_ip4;
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)
struct async_ares_ctx *ares = &data->state.async.ares;
unsigned char a6[INET6_ADDRSTRLEN];
const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6];
if((!local_ip6) || (local_ip6[0] == 0)) {
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(ares->channel)
ares_set_local_ip6(ares->channel, a6);
return CURLE_OK;
#else
(void)data;
return CURLE_NOT_BUILT_IN;
#endif
}
#endif