diff options
author | Daniel Stenberg <daniel@haxx.se> | 2023-05-08 11:11:36 +0200 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2023-05-08 14:55:26 +0200 |
commit | 9ed7d56e044f5aa1b2928ccde6245d0162cf3d0a (patch) | |
tree | 4b0e9a4b4fc9e1bd309a2a79132690fc8316c6d5 | |
parent | f62557276a9e2a5fb1876d87f5eee614ad7ef109 (diff) | |
download | curl-9ed7d56e044f5aa1b2928ccde6245d0162cf3d0a.tar.gz |
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
-rw-r--r-- | docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.3 | 12 | ||||
-rw-r--r-- | lib/hostip.c | 43 |
2 files changed, 42 insertions, 13 deletions
diff --git a/docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.3 b/docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.3 index bd9207831..e841f74f6 100644 --- a/docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.3 +++ b/docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.3 @@ -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 diff --git a/lib/hostip.c b/lib/hostip.c index a1982aa0a..50e483013 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -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"); |