]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
dns: extend CURLOPT_RESOLVE syntax for adding non-permanent entries
authorPaul Groke <paul.groke@dynatrace.com>
Thu, 10 Dec 2020 16:38:14 +0000 (17:38 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 29 Dec 2020 16:49:36 +0000 (17:49 +0100)
Extend the syntax of CURLOPT_RESOLVE strings: allow using a '+' prefix
(similar to the existing '-' prefix for removing entries) to add
DNS cache entries that will time out just like entries that are added
by libcurl itself.

Append " (non-permanent)" to info log message in case a non-permanent
entry is added.

Adjust relevant comments to reflect the new behavior.

Adjust documentation.

Extend unit1607 to test the new functionality.

Closes #6294

docs/cmdline-opts/resolve.d
docs/libcurl/opts/CURLOPT_RESOLVE.3
lib/hostip.c
lib/hostip.h
lib/setopt.c
tests/unit/unit1607.c

index 41f6a1bd57496c248fdc9c5d4d485ba807f996af..ff10f28d969b6c85ea9297fdd041c83ed9287439 100644 (file)
@@ -1,5 +1,5 @@
 Long: resolve
-Arg: <host:port:addr[,addr]...>
+Arg: <[+]host:port:addr[,addr]...>
 Help: Resolve the host+port to this address
 Added: 7.21.3
 Category: connection
@@ -19,10 +19,18 @@ with a specific host and port will be used first.
 The provided address set by this option will be used even if --ipv4 or --ipv6
 is set to make curl use another IP version.
 
+By prefixing the host with a '+' you can make the entry time out after curl's
+default timeout (1 minute). Note that this will only make sense for long
+running parallel transfers with a lot of files. In such cases, if this option
+is used curl will try to resolve the host as it normally would once the
+timeout has expired.
+
 Support for providing the IP address within [brackets] was added in 7.57.0.
 
 Support for providing multiple IP addresses per entry was added in 7.59.0.
 
 Support for resolving with wildcard was added in 7.64.0.
 
+Support for the '+' prefix was was added in 7.75.0.
+
 This option can be used many times to add many host names to resolve.
index 64440e3b68d192d5555145fc935ac7dc8c5a8213..fe47328189a914339bc4856982b4d1c5e6faaf9b 100644 (file)
@@ -37,7 +37,7 @@ list of \fBstruct curl_slist\fP structs properly filled in. Use
 to clean up an entire list.
 
 Each single name resolve string should be written using the format
-HOST:PORT:ADDRESS[,ADDRESS]... where HOST is the name libcurl will try
+[+]HOST:PORT:ADDRESS[,ADDRESS]... where HOST is the name libcurl will try
 to resolve, PORT is the port number of the service where libcurl wants
 to connect to the HOST and ADDRESS is one or more numerical IP
 addresses. If you specify multiple ip addresses they need to be
@@ -46,14 +46,16 @@ ADDRESS entries can of course be either IPv4 or IPv6 style addressing.
 
 This option effectively pre-populates the DNS cache with entries for the
 host+port pair so redirects and everything that operations against the
-HOST+PORT will instead use your provided ADDRESS. Addresses set with
-\fICURLOPT_RESOLVE(3)\fP will not time-out from the DNS cache like ordinary
-entries.
+HOST+PORT will instead use your provided ADDRESS.
 
-If the DNS cache already have an entry for the given host+port pair, then
+The optional leading "+" signifies whether the new entry should time-out or
+not. Entires added with "HOST:..." will never time-out whereas entries added
+with "+HOST:..." will time-out just like ordinary DNS cache entries.
+
+If the DNS cache already has an entry for the given host+port pair, then
 this entry will be removed and a new entry will be created. This is because
-old entry may have have different addresses or be ordinary entries with
-time-outs.
+the old entry may have have different addresses or a different time-out
+setting.
 
 The provided ADDRESS set by this option will be used even if
 \fICURLOPT_IPRESOLVE(3)\fP is set to make libcurl use another IP version.
@@ -66,6 +68,9 @@ and port number must exactly match what was already added previously.
 Support for providing the ADDRESS within [brackets] was added in 7.57.0.
 
 Support for providing multiple IP addresses per entry was added in 7.59.0.
+
+Support for adding non-permanent entries by using the "+" prefix was added in
+7.75.0.
 .SH DEFAULT
 NULL
 .SH PROTOCOLS
index ab1f6df051d694fcada3046488a903ab9969772f..50fb42f212e0e8dcc1b1d86d47669a31a051c2ee 100644 (file)
@@ -444,7 +444,7 @@ Curl_cache_addr(struct Curl_easy *data,
   dns->addr = addr; /* this is the address(es) */
   time(&dns->timestamp);
   if(dns->timestamp == 0)
-    dns->timestamp = 1;   /* zero indicates CURLOPT_RESOLVE entry */
+    dns->timestamp = 1;   /* zero indicates permanent CURLOPT_RESOLVE entry */
 
   /* Store the resolved data in our DNS cache. */
   dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len + 1,
@@ -916,17 +916,24 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
       char *addr_end;
       char *port_ptr;
       char *end_ptr;
+      bool permanent = TRUE;
+      char *host_begin;
       char *host_end;
       unsigned long tmp_port;
       bool error = true;
 
-      host_end = strchr(hostp->data, ':');
+      host_begin = hostp->data;
+      if(host_begin[0] == '+') {
+        host_begin++;
+        permanent = FALSE;
+      }
+      host_end = strchr(host_begin, ':');
       if(!host_end ||
-         ((host_end - hostp->data) >= (ptrdiff_t)sizeof(hostname)))
+         ((host_end - host_begin) >= (ptrdiff_t)sizeof(hostname)))
         goto err;
 
-      memcpy(hostname, hostp->data, host_end - hostp->data);
-      hostname[host_end - hostp->data] = '\0';
+      memcpy(hostname, host_begin, host_end - host_begin);
+      hostname[host_end - host_begin] = '\0';
 
       port_ptr = host_end + 1;
       tmp_port = strtoul(port_ptr, &end_ptr, 10);
@@ -1008,18 +1015,22 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
       if(data->share)
         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
 
-      /* See if its already in our dns cache */
+      /* See if it's already in our dns cache */
       dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
 
       if(dns) {
         infof(data, "RESOLVE %s:%d is - old addresses discarded!\n",
                 hostname, port);
-        /* delete old entry entry, there are two reasons for this
+        /* delete old entry, there are two reasons for this
          1. old entry may have different addresses.
          2. even if entry with correct addresses is already in the cache,
             but if it is close to expire, then by the time next http
             request is made, it can get expired and pruned because old
-            entry is not necessarily marked as added by CURLOPT_RESOLVE. */
+            entry is not necessarily marked as permanent.
+         3. when adding a non-permanent entry, we want it to remove and
+            replace an existing permanent entry.
+         4. when adding a non-permanent entry, we want it to get a "fresh"
+            timeout that starts _now_. */
 
         Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
       }
@@ -1027,10 +1038,11 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
       /* put this new host in the cache */
       dns = Curl_cache_addr(data, head, hostname, port);
       if(dns) {
-        dns->timestamp = 0; /* mark as added by CURLOPT_RESOLVE */
+        if(permanent)
+          dns->timestamp = 0; /* mark as permanent */
         /* release the returned reference; the cache itself will keep the
          * entry alive: */
-            dns->inuse--;
+        dns->inuse--;
       }
 
       if(data->share)
@@ -1040,8 +1052,8 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
         Curl_freeaddrinfo(head);
         return CURLE_OUT_OF_MEMORY;
       }
-      infof(data, "Added %s:%d:%s to DNS cache\n",
-            hostname, port, addresses);
+      infof(data, "Added %s:%d:%s to DNS cache%s\n",
+            hostname, port, addresses, permanent ? "" : " (non-permanent)");
 
       /* Wildcard hostname */
       if(hostname[0] == '*' && hostname[1] == '\0') {
index 724a03d7fb91259e770497d5f793c0d6edc0ebee..18485ec6107b82d878ad4f635cdd4fbceef5567f 100644 (file)
@@ -65,7 +65,7 @@ struct Curl_hash *Curl_global_host_cache_init(void);
 
 struct Curl_dns_entry {
   struct Curl_addrinfo *addr;
-  /* timestamp == 0 -- CURLOPT_RESOLVE entry, doesn't timeout */
+  /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (doesn't time out) */
   time_t timestamp;
   /* use-counter, use Curl_resolv_unlock to release reference */
   long inuse;
index a6cc562eba9f17a80673dc4ec02ddeca589af5d7..cd7ec3fa80ca4d5269992a6d875fb0683cc377fa 100644 (file)
@@ -1460,13 +1460,16 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     break;
   case CURLOPT_RESOLVE:
     /*
-     * List of NAME:[address] names to populate the DNS cache with
-     * Prefix the NAME with dash (-) to _remove_ the name from the cache.
-     *
-     * Names added with this API will remain in the cache until explicitly
+     * List of HOST:PORT:[addresses] strings to populate the DNS cache with
+     * Entries added this way will remain in the cache until explicitly
      * removed or the handle is cleaned up.
      *
-     * This API can remove any name from the DNS cache, but only entries
+     * Prefix the HOST with plus sign (+) to have the entry expire just like
+     * automatically added entries.
+     *
+     * Prefix the HOST with dash (-) to _remove_ the entry from the cache.
+     *
+     * This API can remove any entry from the DNS cache, but only entries
      * that aren't actually in use right now will be pruned immediately.
      */
     data->set.resolve = va_arg(param, struct curl_slist *);
index f1016837b6119bea6d3dd80311c764d45b9203e3..5be42dbeb7e4e2ede54005edd751b0d750837c6b 100644 (file)
@@ -49,6 +49,9 @@ struct testcase {
   const char *host;
   int port;
 
+  /* whether we expect a permanent or non-permanent cache entry */
+  bool permanent;
+
   /* 0 to 9 addresses expected from hostcache */
   const char *address[10];
 };
@@ -67,34 +70,37 @@ static const char skip = 0;
 static const struct testcase tests[] = {
   /* spaces aren't allowed, for now */
   { "test.com:80:127.0.0.1, 127.0.0.2",
-    "test.com", 80, { NULL, }
+    "test.com", 80, TRUE, { NULL, }
   },
   { "TEST.com:80:,,127.0.0.1,,,127.0.0.2,,,,::1,,,",
-    "test.com", 80, { "127.0.0.1", "127.0.0.2", IPV6ONLY("::1"), }
+    "test.com", 80, TRUE, { "127.0.0.1", "127.0.0.2", IPV6ONLY("::1"), }
   },
   { "test.com:80:::1,127.0.0.1",
-    "test.com", 80, { IPV6ONLY("::1"), "127.0.0.1", }
+    "test.com", 80, TRUE, { IPV6ONLY("::1"), "127.0.0.1", }
   },
   { "test.com:80:[::1],127.0.0.1",
-    "test.com", 80, { IPV6ONLY("::1"), "127.0.0.1", }
+    "test.com", 80, TRUE, { IPV6ONLY("::1"), "127.0.0.1", }
   },
   { "test.com:80:::1",
-    "test.com", 80, { IPV6ONLY("::1"), }
+    "test.com", 80, TRUE, { IPV6ONLY("::1"), }
   },
   { "test.com:80:[::1]",
-    "test.com", 80, { IPV6ONLY("::1"), }
+    "test.com", 80, TRUE, { IPV6ONLY("::1"), }
   },
   { "test.com:80:127.0.0.1",
-    "test.com", 80, { "127.0.0.1", }
+    "test.com", 80, TRUE, { "127.0.0.1", }
   },
   { "test.com:80:,127.0.0.1",
-    "test.com", 80, { "127.0.0.1", }
+    "test.com", 80, TRUE, { "127.0.0.1", }
   },
   { "test.com:80:127.0.0.1,",
-    "test.com", 80, { "127.0.0.1", }
+    "test.com", 80, TRUE, { "127.0.0.1", }
   },
   { "test.com:0:127.0.0.1",
-    "test.com", 0, { "127.0.0.1", }
+    "test.com", 0, TRUE, { "127.0.0.1", }
+  },
+  { "+test.com:80:127.0.0.1,",
+    "test.com", 80, FALSE, { "127.0.0.1", }
   },
 };
 
@@ -188,10 +194,18 @@ UNITTEST_START
         break;
       }
 
-      if(dns->timestamp != 0) {
-        fprintf(stderr, "%s:%d tests[%d] failed. the timestamp is not zero. "
-                "for tests[%d].address[%d\n",
-                __FILE__, __LINE__, i, i, j);
+      if(dns->timestamp != 0 && tests[i].permanent) {
+        fprintf(stderr, "%s:%d tests[%d] failed. the timestamp is not zero "
+                "but tests[%d].permanent is TRUE\n",
+                __FILE__, __LINE__, i, i);
+        problem = true;
+        break;
+      }
+
+      if(dns->timestamp == 0 && !tests[i].permanent) {
+        fprintf(stderr, "%s:%d tests[%d] failed. the timestamp is zero "
+                "but tests[%d].permanent is FALSE\n",
+                __FILE__, __LINE__, i, i);
         problem = true;
         break;
       }