Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dns: extend CURLOPT_RESOLVE syntax for adding non-permanent entries #6294

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/cmdline-opts/resolve.d
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
19 changes: 12 additions & 7 deletions docs/libcurl/opts/CURLOPT_RESOLVE.3
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down
36 changes: 24 additions & 12 deletions lib/hostip.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1008,29 +1015,34 @@ 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);
}

/* 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)
Expand All @@ -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') {
Expand Down
2 changes: 1 addition & 1 deletion lib/hostip.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 8 additions & 5 deletions lib/setopt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1444,13 +1444,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 *);
Expand Down
42 changes: 28 additions & 14 deletions tests/unit/unit1607.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};
Expand All @@ -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", }
},
};

Expand Down Expand Up @@ -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;
}
Expand Down