summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2023-05-08 11:11:36 +0200
committerDaniel Stenberg <daniel@haxx.se>2023-05-08 14:55:26 +0200
commit9ed7d56e044f5aa1b2928ccde6245d0162cf3d0a (patch)
tree4b0e9a4b4fc9e1bd309a2a79132690fc8316c6d5
parentf62557276a9e2a5fb1876d87f5eee614ad7ef109 (diff)
downloadcurl-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.312
-rw-r--r--lib/hostip.c43
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");