]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
hostip: enforce a maximum DNS cache size independent of timeout value
authorDaniel Stenberg <daniel@haxx.se>
Mon, 8 May 2023 09:11:36 +0000 (11:11 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 8 May 2023 12:55:26 +0000 (14:55 +0200)
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

docs/libcurl/opts/CURLOPT_DNS_CACHE_TIMEOUT.3
lib/hostip.c

index bd9207831167852a4f73ee4e455212fb9f143b7e..e841f74f66a12067460f472aaa1b91cd102fc161 100644 (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
index a1982aa0a2c683b0493f2e634d1cfcdc9ab5afa4..50e483013faea749db2ba88e809516a31ef52003 100644 (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");