#include "curl_setup.h"
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETINET_IN6_H
#include <netinet/in6.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 "curl_addrinfo.h"
#include "curl_share.h"
#include "curl_trc.h"
#include "dnscache.h"
#include "hash.h"
#include "hostip.h"
#include "httpsrr.h"
#include "progress.h"
#include "rand.h"
#include "strcase.h"
#include "curlx/inet_ntop.h"
#include "curlx/inet_pton.h"
#include "curlx/strcopy.h"
#include "curlx/strparse.h"
#define MAX_HOSTCACHE_LEN (255 + 7)
#define MAX_DNS_CACHE_SIZE 29999
static void dnscache_entry_free(struct Curl_dns_entry *dns)
{
Curl_freeaddrinfo(dns->addr);
#ifdef USE_HTTPSRR
if(dns->hinfo) {
Curl_httpsrr_cleanup(dns->hinfo);
curlx_free(dns->hinfo);
}
#endif
curlx_free(dns);
}
static size_t create_dnscache_id(const char *name,
size_t nlen,
uint16_t port, char *ptr, size_t buflen)
{
size_t len = nlen ? nlen : strlen(name);
DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN);
if(len > (buflen - 7))
len = buflen - 7;
Curl_strntolower(ptr, name, len);
return curl_msnprintf(&ptr[len], 7, ":%u", port) + len;
}
struct dnscache_prune_data {
struct curltime now;
timediff_t oldest_ms;
timediff_t max_age_ms;
};
static int dnscache_entry_is_stale(void *datap, void *hc)
{
struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap;
struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc;
if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) {
timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp);
if(!dns->addr)
age *= 2;
if(age >= prune->max_age_ms)
return TRUE;
if(age > prune->oldest_ms)
prune->oldest_ms = age;
}
return FALSE;
}
static timediff_t dnscache_prune(struct Curl_hash *hostcache,
timediff_t cache_timeout_ms,
struct curltime now)
{
struct dnscache_prune_data user;
user.max_age_ms = cache_timeout_ms;
user.now = now;
user.oldest_ms = 0;
Curl_hash_clean_with_criterium(hostcache,
(void *)&user,
dnscache_entry_is_stale);
return user.oldest_ms;
}
static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
{
if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS))
return &data->share->dnscache;
if(data->multi)
return &data->multi->dnscache;
return NULL;
}
static void dnscache_lock(struct Curl_easy *data,
struct Curl_dnscache *dnscache)
{
if(data->share && dnscache == &data->share->dnscache)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
}
static void dnscache_unlock(struct Curl_easy *data,
struct Curl_dnscache *dnscache)
{
if(data->share && dnscache == &data->share->dnscache)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
void Curl_dnscache_prune(struct Curl_easy *data)
{
struct Curl_dnscache *dnscache = dnscache_get(data);
timediff_t timeout_ms = data->set.dns_cache_timeout_ms;
if(!dnscache || (timeout_ms == -1))
return;
dnscache_lock(data, dnscache);
do {
timediff_t oldest_ms =
dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data));
if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)
timeout_ms = oldest_ms / 2;
else
break;
} while(timeout_ms);
dnscache_unlock(data, dnscache);
}
void Curl_dnscache_clear(struct Curl_easy *data)
{
struct Curl_dnscache *dnscache = dnscache_get(data);
if(dnscache) {
dnscache_lock(data, dnscache);
Curl_hash_clean(&dnscache->entries);
dnscache_unlock(data, dnscache);
}
}
static CURLcode fetch_addr(struct Curl_easy *data,
struct Curl_dnscache *dnscache,
uint8_t dns_queries,
const char *hostname,
uint16_t port,
struct Curl_dns_entry **pdns)
{
struct Curl_dns_entry *dns = NULL;
char entry_id[MAX_HOSTCACHE_LEN];
size_t entry_len;
CURLcode result = CURLE_OK;
*pdns = NULL;
if(!dnscache)
return CURLE_OK;
entry_len = create_dnscache_id(hostname, 0, port,
entry_id, sizeof(entry_id));
dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
if(!dns && data->state.wildcard_resolve) {
entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id));
dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
}
if(dns && (data->set.dns_cache_timeout_ms != -1)) {
struct dnscache_prune_data user;
user.now = *Curl_pgrs_now(data);
user.max_age_ms = data->set.dns_cache_timeout_ms;
user.oldest_ms = 0;
if(dnscache_entry_is_stale(&user, dns)) {
infof(data, "Hostname in DNS cache was stale, zapped");
dns = NULL;
Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
}
}
if(dns) {
if((dns->dns_queries & dns_queries) != dns_queries) {
dns = NULL;
}
else if(!(dns->dns_responses & dns_queries)) {
CURL_TRC_DNS(data, "cache entry does not have type=%s addresses",
Curl_resolv_query_str(dns_queries));
dns = NULL;
result = CURLE_COULDNT_RESOLVE_HOST;
}
}
if(dns && !dns->addr) {
dns = NULL;
result = CURLE_COULDNT_RESOLVE_HOST;
}
*pdns = dns;
return result;
}
CURLcode Curl_dnscache_get(struct Curl_easy *data,
uint8_t dns_queries,
const char *hostname,
uint16_t port,
struct Curl_dns_entry **pentry)
{
struct Curl_dnscache *dnscache = dnscache_get(data);
struct Curl_dns_entry *dns = NULL;
CURLcode result = CURLE_OK;
dnscache_lock(data, dnscache);
result = fetch_addr(data, dnscache, dns_queries, hostname, port, &dns);
if(!result && dns)
dns->refcount++;
else if(result) {
DEBUGASSERT(!dns);
dns = NULL;
}
dnscache_unlock(data, dnscache);
*pentry = dns;
return result;
}
#ifndef CURL_DISABLE_SHUFFLE_DNS
static int num_addresses(const struct Curl_addrinfo *addr)
{
int i = 0;
while(addr) {
addr = addr->ai_next;
i++;
}
return i;
}
UNITTEST CURLcode dns_shuffle_addr(struct Curl_easy *data,
struct Curl_addrinfo **addr);
UNITTEST CURLcode dns_shuffle_addr(struct Curl_easy *data,
struct Curl_addrinfo **addr)
{
CURLcode result = CURLE_OK;
const int num_addrs = num_addresses(*addr);
if(num_addrs > 1) {
struct Curl_addrinfo **nodes;
CURL_TRC_DNS(data, "Shuffling %d addresses", num_addrs);
nodes = curlx_malloc(num_addrs * sizeof(*nodes));
if(nodes) {
int i;
unsigned int *rnd;
const size_t rnd_size = num_addrs * sizeof(*rnd);
nodes[0] = *addr;
for(i = 1; i < num_addrs; i++) {
nodes[i] = nodes[i - 1]->ai_next;
}
rnd = curlx_malloc(rnd_size);
if(rnd) {
if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
struct Curl_addrinfo *swap_tmp;
for(i = num_addrs - 1; i > 0; i--) {
swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)];
nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i];
nodes[i] = swap_tmp;
}
for(i = 1; i < num_addrs; i++) {
nodes[i - 1]->ai_next = nodes[i];
}
nodes[num_addrs - 1]->ai_next = NULL;
*addr = nodes[0];
}
curlx_free(rnd);
}
else
result = CURLE_OUT_OF_MEMORY;
curlx_free(nodes);
}
else
result = CURLE_OUT_OF_MEMORY;
}
return result;
}
#endif
static bool dnscache_ai_has_family(struct Curl_addrinfo *ai,
int ai_family)
{
for(; ai; ai = ai->ai_next) {
if(ai->ai_family == ai_family)
return TRUE;
}
return FALSE;
}
static struct Curl_dns_entry *dnscache_entry_create(
struct Curl_easy *data,
uint8_t dns_queries,
struct Curl_addrinfo **paddr1,
struct Curl_addrinfo **paddr2,
const char *hostname,
size_t hostlen,
uint16_t port,
bool permanent)
{
struct Curl_dns_entry *dns = NULL;
dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen);
if(!dns)
goto out;
dns->refcount = 1;
dns->dns_queries = dns_queries;
dns->port = port;
if(hostlen)
memcpy(dns->hostname, hostname, hostlen);
if(permanent) {
dns->timestamp.tv_sec = 0;
dns->timestamp.tv_usec = 0;
}
else {
dns->timestamp = *Curl_pgrs_now(data);
}
if(paddr1 && *paddr1) {
dns->addr = *paddr1;
*paddr1 = NULL;
}
if(paddr2 && *paddr2) {
struct Curl_addrinfo **phead = &dns->addr;
while(*phead)
phead = &(*phead)->ai_next;
*phead = *paddr2;
*paddr2 = NULL;
}
if((dns_queries & CURL_DNSQ_A) &&
dnscache_ai_has_family(dns->addr, PF_INET))
dns->dns_responses |= CURL_DNSQ_A;
#ifdef USE_IPV6
if((dns_queries & CURL_DNSQ_AAAA) &&
dnscache_ai_has_family(dns->addr, PF_INET6))
dns->dns_responses |= CURL_DNSQ_AAAA;
#endif
#ifndef CURL_DISABLE_SHUFFLE_DNS
if(data->set.dns_shuffle_addresses && dns->addr) {
CURLcode result = dns_shuffle_addr(data, &dns->addr);
if(result) {
dnscache_entry_free(dns);
dns = NULL;
goto out;
}
}
#else
(void)data;
#endif
out:
if(paddr1 && *paddr1) {
Curl_freeaddrinfo(*paddr1);
*paddr1 = NULL;
}
if(paddr2 && *paddr2) {
Curl_freeaddrinfo(*paddr2);
*paddr2 = NULL;
}
return dns;
}
struct Curl_dns_entry *Curl_dnscache_mk_entry(struct Curl_easy *data,
uint8_t dns_queries,
struct Curl_addrinfo **paddr,
const char *hostname,
uint16_t port)
{
return dnscache_entry_create(data, dns_queries, paddr, NULL, hostname,
hostname ? strlen(hostname) : 0,
port, FALSE);
}
struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data,
uint8_t dns_queries,
struct Curl_addrinfo **paddr1,
struct Curl_addrinfo **paddr2,
const char *hostname,
uint16_t port)
{
return dnscache_entry_create(data, dns_queries, paddr1, paddr2, hostname,
hostname ? strlen(hostname) : 0,
port, FALSE);
}
#ifdef USE_HTTPSRR
void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns,
struct Curl_https_rrinfo *hinfo)
{
DEBUGASSERT(dns->refcount == 1);
DEBUGASSERT(dns->dns_queries & CURL_DNSQ_HTTPS);
if(dns->hinfo) {
Curl_httpsrr_cleanup(dns->hinfo);
curlx_free(dns->hinfo);
}
dns->hinfo = hinfo;
dns->dns_responses |= CURL_DNSQ_HTTPS;
}
#endif
static struct Curl_dns_entry *dnscache_add_addr(struct Curl_easy *data,
struct Curl_dnscache *dnscache,
uint8_t dns_queries,
struct Curl_addrinfo **paddr,
const char *hostname,
size_t hlen,
uint16_t port,
bool permanent)
{
char entry_id[MAX_HOSTCACHE_LEN];
size_t entry_len;
struct Curl_dns_entry *dns;
struct Curl_dns_entry *dns2;
dns = dnscache_entry_create(data, dns_queries, paddr, NULL,
hostname, hlen, port, permanent);
if(!dns)
return NULL;
entry_len = create_dnscache_id(hostname, hlen, port,
entry_id, sizeof(entry_id));
dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1,
(void *)dns);
if(!dns2) {
dnscache_entry_free(dns);
return NULL;
}
dns = dns2;
dns->refcount++;
return dns;
}
CURLcode Curl_dnscache_add(struct Curl_easy *data,
struct Curl_dns_entry *entry)
{
struct Curl_dnscache *dnscache = dnscache_get(data);
char id[MAX_HOSTCACHE_LEN];
size_t idlen;
if(!dnscache)
return CURLE_FAILED_INIT;
idlen = create_dnscache_id(entry->hostname, 0, entry->port, id, sizeof(id));
dnscache_lock(data, dnscache);
if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) {
dnscache_unlock(data, dnscache);
return CURLE_OUT_OF_MEMORY;
}
entry->refcount++;
dnscache_unlock(data, dnscache);
return CURLE_OK;
}
CURLcode Curl_dnscache_add_negative(struct Curl_easy *data,
uint8_t dns_queries,
const char *host,
uint16_t port)
{
struct Curl_dnscache *dnscache = dnscache_get(data);
struct Curl_dns_entry *dns;
DEBUGASSERT(dnscache);
if(!dnscache)
return CURLE_FAILED_INIT;
dnscache_lock(data, dnscache);
dns = dnscache_add_addr(data, dnscache, dns_queries, NULL,
host, strlen(host), port, FALSE);
if(dns) {
dns->refcount--;
dnscache_unlock(data, dnscache);
CURL_TRC_DNS(data, "cache negative name resolve for %s:%d type=%s",
host, port, Curl_resolv_query_str(dns_queries));
return CURLE_OK;
}
dnscache_unlock(data, dnscache);
return CURLE_OUT_OF_MEMORY;
}
void Curl_dns_entry_unlink(struct Curl_easy *data,
struct Curl_dns_entry **pdns)
{
if(*pdns) {
struct Curl_dnscache *dnscache = dnscache_get(data);
struct Curl_dns_entry *dns = *pdns;
*pdns = NULL;
dnscache_lock(data, dnscache);
dns->refcount--;
if(dns->refcount == 0)
dnscache_entry_free(dns);
dnscache_unlock(data, dnscache);
}
}
static void dnscache_entry_dtor(void *entry)
{
struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry;
DEBUGASSERT(dns && (dns->refcount > 0));
dns->refcount--;
if(dns->refcount == 0)
dnscache_entry_free(dns);
}
void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size)
{
Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare,
dnscache_entry_dtor);
}
void Curl_dnscache_destroy(struct Curl_dnscache *dns)
{
Curl_hash_destroy(&dns->entries);
}
CURLcode Curl_loadhostpairs(struct Curl_easy *data)
{
struct Curl_dnscache *dnscache = dnscache_get(data);
struct curl_slist *hostp;
if(!dnscache)
return CURLE_FAILED_INIT;
data->state.wildcard_resolve = FALSE;
for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
char entry_id[MAX_HOSTCACHE_LEN];
const char *host = hostp->data;
struct Curl_str source;
if(!host)
continue;
if(*host == '-') {
curl_off_t num = 0;
size_t entry_len;
host++;
if(!curlx_str_single(&host, '[')) {
if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
curlx_str_single(&host, ']') ||
curlx_str_single(&host, ':'))
continue;
}
else {
if(curlx_str_until(&host, &source, 4096, ':') ||
curlx_str_single(&host, ':')) {
continue;
}
}
if(!curlx_str_number(&host, &num, 0xffff)) {
entry_len = create_dnscache_id(curlx_str(&source),
curlx_strlen(&source), (uint16_t)num,
entry_id, sizeof(entry_id));
dnscache_lock(data, dnscache);
Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
dnscache_unlock(data, dnscache);
}
}
else {
struct Curl_dns_entry *dns;
struct Curl_addrinfo *head = NULL, *tail = NULL;
size_t entry_len;
char address[64];
curl_off_t tmpofft = 0;
uint16_t port = 0;
bool permanent = TRUE;
bool error = TRUE;
VERBOSE(const char *addresses = NULL);
if(*host == '+') {
host++;
permanent = FALSE;
}
if(!curlx_str_single(&host, '[')) {
if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
curlx_str_single(&host, ']'))
continue;
}
else {
if(curlx_str_until(&host, &source, 4096, ':'))
continue;
}
if(curlx_str_single(&host, ':') ||
curlx_str_number(&host, &tmpofft, 0xffff) ||
curlx_str_single(&host, ':'))
goto err;
port = (uint16_t)tmpofft;
VERBOSE(addresses = host);
while(*host) {
struct Curl_str target;
struct Curl_addrinfo *ai;
CURLcode result;
if(!curlx_str_single(&host, '[')) {
if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') ||
curlx_str_single(&host, ']'))
goto err;
}
else {
if(curlx_str_until(&host, &target, 4096, ',')) {
if(curlx_str_single(&host, ','))
goto err;
continue;
}
}
#ifndef USE_IPV6
if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) {
infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.",
(int)curlx_strlen(&target), curlx_str(&target));
if(curlx_str_single(&host, ','))
goto err;
continue;
}
#endif
if(curlx_strlen(&target) >= sizeof(address))
goto err;
memcpy(address, curlx_str(&target), curlx_strlen(&target));
address[curlx_strlen(&target)] = '\0';
result = Curl_str2addr(address, port, &ai);
if(result) {
infof(data, "Resolve IP address '%s' found is illegal", address);
goto err;
}
if(tail) {
tail->ai_next = ai;
tail = tail->ai_next;
}
else {
head = tail = ai;
}
if(curlx_str_single(&host, ','))
break;
}
if(!head)
goto err;
error = FALSE;
err:
if(error) {
failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data);
Curl_freeaddrinfo(head);
return CURLE_SETOPT_OPTION_SYNTAX;
}
entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source),
port, entry_id, sizeof(entry_id));
dnscache_lock(data, dnscache);
dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
if(dns) {
infof(data, "RESOLVE %.*s:%u - old addresses discarded",
(int)curlx_strlen(&source),
curlx_str(&source), port);
Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
}
dns = dnscache_add_addr(data, dnscache, CURL_DNSQ_ALL,
&head, curlx_str(&source),
curlx_strlen(&source), port, permanent);
if(dns)
dns->refcount--;
dnscache_unlock(data, dnscache);
if(!dns)
return CURLE_OUT_OF_MEMORY;
infof(data, "Added %.*s:%u:%s to DNS cache%s",
(int)curlx_strlen(&source), curlx_str(&source), port, addresses,
permanent ? "" : " (non-permanent)");
if(curlx_str_casecompare(&source, "*")) {
infof(data, "RESOLVE *:%u using wildcard", port);
data->state.wildcard_resolve = TRUE;
}
}
}
data->state.resolve = NULL;
return CURLE_OK;
}