hostip: enforce a maximum DNS cache size independent of timeout value

To reduce the damage an application can cause if using -1 or other
ridiculous timeout values and letting the cache live long times.

The maximum number of entries in the DNS cache is now totally
arbitrarily and hard-coded set to 29999.

Closes #11084
This commit is contained in:
Daniel Stenberg 2023-05-08 11:11:36 +02:00
parent f62557276a
commit 9ed7d56e04
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
2 changed files with 42 additions and 13 deletions

View File

@ -37,15 +37,23 @@ memory and used for this number of seconds. Set to zero to completely disable
caching, or set to -1 to make the cached entries remain forever. By default,
libcurl caches this info for 60 seconds.
We recommend users not to tamper with this option unless strictly necessary.
If you do, be careful of using large values that can make the cache size grow
significantly if many different host names are used within that timeout
period.
The name resolve functions of various libc implementations do not re-read name
server information unless explicitly told so (for example, by calling
\fIres_init(3)\fP). This may cause libcurl to keep using the older server even
if DHCP has updated the server info, and this may look like a DNS cache issue
to the casual libcurl-app user.
Note that DNS entries have a "TTL" property but libcurl does not use that. This
DNS cache timeout is entirely speculative that a name will resolve to the same
DNS entries have a "TTL" property but libcurl does not use that. This DNS
cache timeout is entirely speculative that a name will resolve to the same
address for a certain small amount of time into the future.
Since version 8.1.0, libcurl prunes entries from the DNS cache if it excceeds
30,000 entries no matter which timeout value is used.
.SH DEFAULT
60
.SH PROTOCOLS

View File

@ -85,6 +85,8 @@
#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
#define MAX_DNS_CACHE_SIZE 29999
/*
* hostip.c explained
* ==================
@ -198,6 +200,7 @@ create_hostcache_id(const char *name,
struct hostcache_prune_data {
time_t now;
int cache_timeout;
int oldest; /* oldest time in cache not pruned */
};
/*
@ -210,28 +213,39 @@ struct hostcache_prune_data {
static int
hostcache_timestamp_remove(void *datap, void *hc)
{
struct hostcache_prune_data *data =
struct hostcache_prune_data *prune =
(struct hostcache_prune_data *) datap;
struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
return (0 != c->timestamp)
&& (data->now - c->timestamp >= data->cache_timeout);
if(c->timestamp) {
/* age in seconds */
time_t age = prune->now - c->timestamp;
if(age >= prune->cache_timeout)
return TRUE;
if(age > prune->oldest)
prune->oldest = (int)age;
}
return FALSE;
}
/*
* Prune the DNS cache. This assumes that a lock has already been taken.
* Returns the 'age' of the oldest still kept entry.
*/
static void
static int
hostcache_prune(struct Curl_hash *hostcache, int cache_timeout, time_t now)
{
struct hostcache_prune_data user;
user.cache_timeout = cache_timeout;
user.now = now;
user.oldest = 0;
Curl_hash_clean_with_criterium(hostcache,
(void *) &user,
hostcache_timestamp_remove);
return user.oldest;
}
/*
@ -241,10 +255,11 @@ hostcache_prune(struct Curl_hash *hostcache, int cache_timeout, time_t now)
void Curl_hostcache_prune(struct Curl_easy *data)
{
time_t now;
/* the timeout may be set -1 (forever) */
int timeout = data->set.dns_cache_timeout;
if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
/* cache forever means never prune, and NULL hostcache means
we can't do it */
if(!data->dns.hostcache)
/* NULL hostcache means we can't do it */
return;
if(data->share)
@ -252,10 +267,15 @@ void Curl_hostcache_prune(struct Curl_easy *data)
time(&now);
/* Remove outdated and unused entries from the hostcache */
hostcache_prune(data->dns.hostcache,
data->set.dns_cache_timeout,
now);
do {
/* Remove outdated and unused entries from the hostcache */
int oldest = hostcache_prune(data->dns.hostcache, timeout, now);
timeout = oldest;
/* if the cache size is still too big, use the oldest age as new
prune limit */
} while(timeout && (data->dns.hostcache->size > MAX_DNS_CACHE_SIZE));
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
@ -298,6 +318,7 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
time(&user.now);
user.cache_timeout = data->set.dns_cache_timeout;
user.oldest = 0;
if(hostcache_timestamp_remove(&user, dns)) {
infof(data, "Hostname in DNS cache was stale, zapped");