1
0
mirror of https://github.com/moparisthebest/curl synced 2024-11-14 21:45:13 -05:00

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
This commit is contained in:
Paul Groke 2020-12-10 17:38:14 +01:00 committed by Daniel Stenberg
parent 68dde8e330
commit 8324dc8b1a
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
6 changed files with 82 additions and 40 deletions

View File

@ -1,5 +1,5 @@
Long: resolve Long: resolve
Arg: <host:port:addr[,addr]...> Arg: <[+]host:port:addr[,addr]...>
Help: Resolve the host+port to this address Help: Resolve the host+port to this address
Added: 7.21.3 Added: 7.21.3
Category: connection 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 The provided address set by this option will be used even if --ipv4 or --ipv6
is set to make curl use another IP version. 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 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 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 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. This option can be used many times to add many host names to resolve.

View File

@ -37,7 +37,7 @@ list of \fBstruct curl_slist\fP structs properly filled in. Use
to clean up an entire list. to clean up an entire list.
Each single name resolve string should be written using the format 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 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 to connect to the HOST and ADDRESS is one or more numerical IP
addresses. If you specify multiple ip addresses they need to be 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 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 pair so redirects and everything that operations against the
HOST+PORT will instead use your provided ADDRESS. Addresses set with HOST+PORT will instead use your provided ADDRESS.
\fICURLOPT_RESOLVE(3)\fP will not time-out from the DNS cache like ordinary
entries.
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 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 the old entry may have have different addresses or a different time-out
time-outs. setting.
The provided ADDRESS set by this option will be used even if 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. \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 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 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 .SH DEFAULT
NULL NULL
.SH PROTOCOLS .SH PROTOCOLS

View File

@ -444,7 +444,7 @@ Curl_cache_addr(struct Curl_easy *data,
dns->addr = addr; /* this is the address(es) */ dns->addr = addr; /* this is the address(es) */
time(&dns->timestamp); time(&dns->timestamp);
if(dns->timestamp == 0) 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. */ /* Store the resolved data in our DNS cache. */
dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len + 1, 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 *addr_end;
char *port_ptr; char *port_ptr;
char *end_ptr; char *end_ptr;
bool permanent = TRUE;
char *host_begin;
char *host_end; char *host_end;
unsigned long tmp_port; unsigned long tmp_port;
bool error = true; 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 || if(!host_end ||
((host_end - hostp->data) >= (ptrdiff_t)sizeof(hostname))) ((host_end - host_begin) >= (ptrdiff_t)sizeof(hostname)))
goto err; goto err;
memcpy(hostname, hostp->data, host_end - hostp->data); memcpy(hostname, host_begin, host_end - host_begin);
hostname[host_end - hostp->data] = '\0'; hostname[host_end - host_begin] = '\0';
port_ptr = host_end + 1; port_ptr = host_end + 1;
tmp_port = strtoul(port_ptr, &end_ptr, 10); tmp_port = strtoul(port_ptr, &end_ptr, 10);
@ -1008,18 +1015,22 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
if(data->share) if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); 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); dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
if(dns) { if(dns) {
infof(data, "RESOLVE %s:%d is - old addresses discarded!\n", infof(data, "RESOLVE %s:%d is - old addresses discarded!\n",
hostname, port); 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. 1. old entry may have different addresses.
2. even if entry with correct addresses is already in the cache, 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 but if it is close to expire, then by the time next http
request is made, it can get expired and pruned because old 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); Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
} }
@ -1027,7 +1038,8 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
/* put this new host in the cache */ /* put this new host in the cache */
dns = Curl_cache_addr(data, head, hostname, port); dns = Curl_cache_addr(data, head, hostname, port);
if(dns) { 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 /* release the returned reference; the cache itself will keep the
* entry alive: */ * entry alive: */
dns->inuse--; dns->inuse--;
@ -1040,8 +1052,8 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
Curl_freeaddrinfo(head); Curl_freeaddrinfo(head);
return CURLE_OUT_OF_MEMORY; return CURLE_OUT_OF_MEMORY;
} }
infof(data, "Added %s:%d:%s to DNS cache\n", infof(data, "Added %s:%d:%s to DNS cache%s\n",
hostname, port, addresses); hostname, port, addresses, permanent ? "" : " (non-permanent)");
/* Wildcard hostname */ /* Wildcard hostname */
if(hostname[0] == '*' && hostname[1] == '\0') { if(hostname[0] == '*' && hostname[1] == '\0') {

View File

@ -65,7 +65,7 @@ struct Curl_hash *Curl_global_host_cache_init(void);
struct Curl_dns_entry { struct Curl_dns_entry {
struct Curl_addrinfo *addr; 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; time_t timestamp;
/* use-counter, use Curl_resolv_unlock to release reference */ /* use-counter, use Curl_resolv_unlock to release reference */
long inuse; long inuse;

View File

@ -1460,13 +1460,16 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
break; break;
case CURLOPT_RESOLVE: case CURLOPT_RESOLVE:
/* /*
* List of NAME:[address] names to populate the DNS cache with * List of HOST:PORT:[addresses] strings to populate the DNS cache with
* Prefix the NAME with dash (-) to _remove_ the name from the cache. * Entries added this way will remain in the cache until explicitly
*
* Names added with this API will remain in the cache until explicitly
* removed or the handle is cleaned up. * 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. * that aren't actually in use right now will be pruned immediately.
*/ */
data->set.resolve = va_arg(param, struct curl_slist *); data->set.resolve = va_arg(param, struct curl_slist *);

View File

@ -49,6 +49,9 @@ struct testcase {
const char *host; const char *host;
int port; int port;
/* whether we expect a permanent or non-permanent cache entry */
bool permanent;
/* 0 to 9 addresses expected from hostcache */ /* 0 to 9 addresses expected from hostcache */
const char *address[10]; const char *address[10];
}; };
@ -67,34 +70,37 @@ static const char skip = 0;
static const struct testcase tests[] = { static const struct testcase tests[] = {
/* spaces aren't allowed, for now */ /* spaces aren't allowed, for now */
{ "test.com:80:127.0.0.1, 127.0.0.2", { "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,,,,::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:::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:[::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:::1",
"test.com", 80, { IPV6ONLY("::1"), } "test.com", 80, TRUE, { IPV6ONLY("::1"), }
}, },
{ "test.com:80:[::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, { "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, { "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, { "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, { "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; break;
} }
if(dns->timestamp != 0) { if(dns->timestamp != 0 && tests[i].permanent) {
fprintf(stderr, "%s:%d tests[%d] failed. the timestamp is not zero. " fprintf(stderr, "%s:%d tests[%d] failed. the timestamp is not zero "
"for tests[%d].address[%d\n", "but tests[%d].permanent is TRUE\n",
__FILE__, __LINE__, i, i, j); __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; problem = true;
break; break;
} }