Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hostcache: Cache negative name resolves #12406

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 16 additions & 22 deletions lib/hostasyn.c
Expand Up @@ -72,33 +72,27 @@ CURLcode Curl_addrinfo_callback(struct Curl_easy *data,

data->state.async.status = status;

if(CURL_ASYNC_SUCCESS == status) {
if(ai) {
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);

dns = Curl_cache_addr(data, ai,
data->state.async.hostname, 0,
data->state.async.port);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
dns = Curl_cache_addr(data, ai,
data->state.async.hostname, 0,
data->state.async.port);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);

if(!dns) {
/* failed to store, cleanup and return error */
Curl_freeaddrinfo(ai);
result = CURLE_OUT_OF_MEMORY;
}
}
else {
result = CURLE_OUT_OF_MEMORY;
}
if(!dns) {
/* failed to store, cleanup and return error */
Curl_freeaddrinfo(ai);
result = CURLE_OUT_OF_MEMORY;
}

data->state.async.dns = dns;
if(ai)
data->state.async.dns = dns;

/* Set async.done TRUE last in this function since it may be used multi-
threaded and once this is TRUE the other thread may read fields from the
async struct */
/* Set async.done TRUE last in this function since it may be used multi-
threaded and once this is TRUE the other thread may read fields from the
async struct */
data->state.async.done = TRUE;

/* IPv4: The input hostent struct will be freed by ares when we return from
Expand Down
155 changes: 94 additions & 61 deletions lib/hostip.c
Expand Up @@ -203,7 +203,10 @@ hostcache_timestamp_remove(void *datap, void *hc)
if(c->timestamp) {
/* age in seconds */
time_t age = prune->now - c->timestamp;
if(age >= prune->cache_timeout)
int lifetime = prune->cache_timeout;
if(!c->addr)
lifetime /= 2; /* negative entries get half lifetime */
if(age >= lifetime)
return TRUE;
if(age > prune->oldest)
prune->oldest = age;
Expand Down Expand Up @@ -279,7 +282,8 @@ static curl_simple_lock curl_jmpenv_lock;
/* lookup address, returns entry if found and not stale */
static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
const char *hostname,
int port)
int port,
bool *name_found)
{
struct Curl_dns_entry *dns = NULL;
char entry_id[MAX_HOSTCACHE_LEN];
Expand Down Expand Up @@ -315,7 +319,7 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
}

/* See if the returned entry matches the required resolve mode */
if(dns && data->conn->ip_version != CURL_IPRESOLVE_WHATEVER) {
if(dns && dns->addr && data->conn->ip_version != CURL_IPRESOLVE_WHATEVER) {
int pf = PF_INET;
bool found = false;
struct Curl_addrinfo *addr = dns->addr;
Expand All @@ -339,41 +343,12 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
}
}
return dns;
}

/*
* Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache.
*
* Curl_resolv() checks initially and multi_runsingle() checks each time
* it discovers the handle in the state WAITRESOLVE whether the hostname
* has already been resolved and the address has already been stored in
* the DNS cache. This short circuits waiting for a lot of pending
* lookups for the same hostname requested by different handles.
*
* Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
*
* The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after
* use, or we'll leak memory!
*/
struct Curl_dns_entry *
Curl_fetch_addr(struct Curl_easy *data,
const char *hostname,
int port)
{
struct Curl_dns_entry *dns = NULL;

if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);

dns = fetch_addr(data, hostname, port);

if(dns)
dns->inuse++; /* we use it! */

if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);

if(dns) {
*name_found = true;
if(!dns->addr)
dns = NULL;
}
return dns;
}

Expand Down Expand Up @@ -501,7 +476,8 @@ Curl_cache_addr(struct Curl_easy *data,
entry_len = create_hostcache_id(hostname, hostlen, port,
entry_id, sizeof(entry_id));

dns->inuse = 1; /* the cache has the first reference */
if(addr)
dns->inuse = 1; /* the cache has the first reference */
dns->addr = addr; /* this is the address(es) */
time(&dns->timestamp);
if(dns->timestamp == 0)
Expand All @@ -519,7 +495,8 @@ Curl_cache_addr(struct Curl_easy *data,
}

dns = dns2;
dns->inuse++; /* mark entry as in-use */
if(addr)
dns->inuse++; /* mark entry as in-use */
return dns;
}

Expand Down Expand Up @@ -691,6 +668,8 @@ enum resolve_t Curl_resolv(struct Curl_easy *data,
CURLcode result;
enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */
struct connectdata *conn = data->conn;
bool found = false;

/* We should intentionally error and not resolve .onion TLDs */
size_t hostname_len = strlen(hostname);
if(hostname_len >= 7 &&
Expand All @@ -709,18 +688,22 @@ enum resolve_t Curl_resolv(struct Curl_easy *data,
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);

dns = fetch_addr(data, hostname, port);
dns = fetch_addr(data, hostname, port, &found);

if(dns) {
infof(data, "Hostname %s was found in DNS cache", hostname);
dns->inuse++; /* we use it! */
rc = CURLRESOLV_RESOLVED;
}
else if(found) {
infof(data, "Hostname %s was found negative in DNS cache", hostname);
rc = CURLRESOLV_ERROR;
}

if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);

if(!dns) {
if(!found) {
/* The entry was not in the cache. Resolve it to IP address */

struct Curl_addrinfo *addr = NULL;
Expand Down Expand Up @@ -1065,13 +1048,18 @@ void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns)
static void freednsentry(void *freethis)
{
struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis;
DEBUGASSERT(dns && (dns->inuse>0));
DEBUGASSERT(dns);
if(dns->addr) {
DEBUGASSERT(dns->inuse>0);

dns->inuse--;
if(dns->inuse == 0) {
Curl_freeaddrinfo(dns->addr);
free(dns);
dns->inuse--;
if(dns->inuse == 0) {
Curl_freeaddrinfo(dns->addr);
free(dns);
}
}
else
free(dns);
}

/*
Expand Down Expand Up @@ -1372,23 +1360,66 @@ static void show_resolve_info(struct Curl_easy *data,
}
#endif

CURLcode Curl_resolv_check(struct Curl_easy *data,
struct Curl_dns_entry **dns)
static CURLcode check_cache(struct Curl_easy *data,
struct Curl_dns_entry **dns,
bool *found)
{
CURLcode result;
#if defined(CURL_DISABLE_DOH) && !defined(CURLRES_ASYNCH)
CURLcode result = CURLE_OK;

#ifndef CURLRES_ASYNCH
(void)data;
(void)dns;
(void)found;
#else
if(data->state.async.hostname) {
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);

*dns = fetch_addr(data, data->state.async.hostname,
data->conn->port, found);
if(*found) {
if(*dns)
(*dns)->inuse++;
else {
#ifndef CURL_DISABLE_PROXY
if(data->conn->bits.httpproxy)
result = CURLE_COULDNT_RESOLVE_PROXY;
else
#endif
#ifndef CURL_DISABLE_DOH
if(data->conn->bits.doh) {
result = Curl_doh_is_resolved(data, dns);
result = CURLE_COULDNT_RESOLVE_HOST;
}
}
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
else
#endif
result = Curl_resolver_is_resolved(data, dns);
if(*dns)
show_resolve_info(data, *dns);
return result;
}

CURLcode Curl_resolv_check(struct Curl_easy *data,
struct Curl_dns_entry **dns)
{
CURLcode result;
bool found = false;

result = check_cache(data, dns, &found);
if(!found) {
#ifndef CURL_DISABLE_DOH
if(data->conn->bits.doh) {
result = Curl_doh_is_resolved(data, dns);
}
else
#endif
result = Curl_resolver_is_resolved(data, dns);
if(*dns) {
show_resolve_info(data, *dns);
#ifdef CURLRES_ASYNCH
data->state.async.dns = *dns;
data->state.async.done = TRUE;
#endif
}
}

return result;
}

Expand All @@ -1415,16 +1446,18 @@ int Curl_resolv_getsock(struct Curl_easy *data,

Note: this function disconnects and frees the conn data in case of
resolve failure */
CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done)
CURLcode Curl_once_resolved(struct Curl_easy *data,
struct Curl_dns_entry *dns,
bool *protocol_done)
{
CURLcode result;
struct connectdata *conn = data->conn;

conn->dns_entry = dns;

#ifdef USE_CURL_ASYNC
if(data->state.async.dns) {
conn->dns_entry = data->state.async.dns;
if(data->state.async.dns)
data->state.async.dns = NULL;
}
#endif

result = Curl_setup_conn(data, protocol_done);
Expand Down
17 changes: 3 additions & 14 deletions lib/hostip.h
Expand Up @@ -137,7 +137,9 @@ void Curl_hostcache_prune(struct Curl_easy *data);
/* IPv4 threadsafe resolve function used for synch and asynch builds */
struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port);

CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_connect);
CURLcode Curl_once_resolved(struct Curl_easy *data,
struct Curl_dns_entry *dns,
bool *protocol_connect);

/*
* Curl_addrinfo_callback() is used when we build with any asynch specialty.
Expand All @@ -157,19 +159,6 @@ CURLcode Curl_addrinfo_callback(struct Curl_easy *data,
void Curl_printable_address(const struct Curl_addrinfo *ip,
char *buf, size_t bufsize);

/*
* Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache.
*
* Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
*
* The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after
* use, or we'll leak memory!
*/
struct Curl_dns_entry *
Curl_fetch_addr(struct Curl_easy *data,
const char *hostname,
int port);

/*
* Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
*
Expand Down
31 changes: 2 additions & 29 deletions lib/multi.c
Expand Up @@ -1965,34 +1965,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* awaiting an asynch name resolve to complete */
{
struct Curl_dns_entry *dns = NULL;
struct connectdata *conn = data->conn;
const char *hostname;

DEBUGASSERT(conn);
#ifndef CURL_DISABLE_PROXY
if(conn->bits.httpproxy)
hostname = conn->http_proxy.host.name;
else
#endif
if(conn->bits.conn_to_host)
hostname = conn->conn_to_host.name;
else
hostname = conn->host.name;

/* check if we have the name resolved by now */
dns = Curl_fetch_addr(data, hostname, (int)conn->port);

if(dns) {
#ifdef CURLRES_ASYNCH
data->state.async.dns = dns;
data->state.async.done = TRUE;
#endif
result = CURLE_OK;
infof(data, "Hostname '%s' was found in DNS cache", hostname);
}

if(!dns)
result = Curl_resolv_check(data, &dns);
result = Curl_resolv_check(data, &dns);

/* Update sockets here, because the socket(s) may have been
closed and the application thus needs to be told, even if it
Expand All @@ -2007,7 +1980,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
if(dns) {
/* Perform the next step in the connection phase, and then move on
to the WAITCONNECT state */
result = Curl_once_resolved(data, &connected);
result = Curl_once_resolved(data, dns, &connected);

if(result)
/* if Curl_once_resolved() returns failure, the connection struct
Expand Down