From 8324dc8b1a730445e7aa91f62d7f88b8376e4aee Mon Sep 17 00:00:00 2001 From: Paul Groke Date: Thu, 10 Dec 2020 17:38:14 +0100 Subject: [PATCH] dns: extend CURLOPT_RESOLVE syntax for adding non-permanent entries 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 | 10 ++++++- docs/libcurl/opts/CURLOPT_RESOLVE.3 | 19 ++++++++----- lib/hostip.c | 36 ++++++++++++++++--------- lib/hostip.h | 2 +- lib/setopt.c | 13 +++++---- tests/unit/unit1607.c | 42 +++++++++++++++++++---------- 6 files changed, 82 insertions(+), 40 deletions(-) diff --git a/docs/cmdline-opts/resolve.d b/docs/cmdline-opts/resolve.d index 41f6a1bd5..ff10f28d9 100644 --- a/docs/cmdline-opts/resolve.d +++ b/docs/cmdline-opts/resolve.d @@ -1,5 +1,5 @@ Long: resolve -Arg: +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. diff --git a/docs/libcurl/opts/CURLOPT_RESOLVE.3 b/docs/libcurl/opts/CURLOPT_RESOLVE.3 index 64440e3b6..fe4732818 100644 --- a/docs/libcurl/opts/CURLOPT_RESOLVE.3 +++ b/docs/libcurl/opts/CURLOPT_RESOLVE.3 @@ -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 diff --git a/lib/hostip.c b/lib/hostip.c index ab1f6df05..50fb42f21 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -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') { diff --git a/lib/hostip.h b/lib/hostip.h index 724a03d7f..18485ec61 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -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; diff --git a/lib/setopt.c b/lib/setopt.c index a6cc562eb..cd7ec3fa8 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -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 *); diff --git a/tests/unit/unit1607.c b/tests/unit/unit1607.c index f1016837b..5be42dbeb 100644 --- a/tests/unit/unit1607.c +++ b/tests/unit/unit1607.c @@ -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; }