hostip: make CURLOPT_RESOLVE support replacing IPv6 addresses

This also applies to --resolve of course.

Applied strparse functions on the function.

Fixes #16357
Reported-by: rmg-x on github
Closes #16358
Assisted-by: Jay Satiro
This commit is contained in:
Daniel Stenberg 2025-02-17 08:33:52 +01:00
parent 61f85bf967
commit 2f4dc6525c
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
3 changed files with 84 additions and 74 deletions

View File

@ -12,6 +12,7 @@ See-also:
- alt-svc - alt-svc
Example: Example:
- --resolve example.com:443:127.0.0.1 $URL - --resolve example.com:443:127.0.0.1 $URL
- --resolve example.com:443:[2001:db8::252f:efd6] $URL
--- ---
# `--resolve` # `--resolve`
@ -20,8 +21,8 @@ Provide a custom address for a specific host and port pair. Using this, you
can make the curl requests(s) use a specified address and prevent the can make the curl requests(s) use a specified address and prevent the
otherwise normally resolved address to be used. Consider it a sort of otherwise normally resolved address to be used. Consider it a sort of
/etc/hosts alternative provided on the command line. The port number should be /etc/hosts alternative provided on the command line. The port number should be
the number used for the specific protocol the host is used for. It means the number used for the specific protocol the host is used for. It means you
you need several entries if you want to provide address for the same host but need several entries if you want to provide addresses for the same host but
different ports. different ports.
By specifying `*` as host you can tell curl to resolve any host and specific By specifying `*` as host you can tell curl to resolve any host and specific
@ -37,9 +38,13 @@ parallel transfers with a lot of files. In such cases, if this option is used
curl tries to resolve the host as it normally would once the timeout has curl tries to resolve the host as it normally would once the timeout has
expired. expired.
Provide IPv6 addresses within [brackets].
To redirect connects from a specific hostname or any hostname, independently To redirect connects from a specific hostname or any hostname, independently
of port number, consider the --connect-to option. of port number, consider the --connect-to option.
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 added in 7.75.0. Support for the '+' prefix was added in 7.75.0.
Support for specifying the host component as an IPv6 address was added in 8.13.0.

View File

@ -73,6 +73,8 @@ resolves, include a string in the linked list that uses the format
The entry to remove must be prefixed with a dash, and the hostname and port The entry to remove must be prefixed with a dash, and the hostname and port
number must exactly match what was added previously. number must exactly match what was added previously.
Provide IPv6 addresses within [brackets].
Using this option multiple times makes the last set list override the previous Using this option multiple times makes the last set list override the previous
ones. Set it to NULL to disable its use again. ones. Set it to NULL to disable its use again.
@ -90,6 +92,7 @@ int main(void)
CURL *curl; CURL *curl;
struct curl_slist *host = NULL; struct curl_slist *host = NULL;
host = curl_slist_append(NULL, "example.com:443:127.0.0.1"); host = curl_slist_append(NULL, "example.com:443:127.0.0.1");
host = curl_slist_append(host, "example.com:443:[2001:db8::252f:efd6]");
curl = curl_easy_init(); curl = curl_easy_init();
if(curl) { if(curl) {
@ -117,6 +120,8 @@ 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 Support for adding non-permanent entries by using the "+" prefix was added in
7.75.0. 7.75.0.
Support for specifying the host component as an IPv6 address was added in 8.13.0.
# %AVAILABILITY% # %AVAILABILITY%
# RETURN VALUE # RETURN VALUE

View File

@ -1124,43 +1124,46 @@ void Curl_hostcache_clean(struct Curl_easy *data,
CURLcode Curl_loadhostpairs(struct Curl_easy *data) CURLcode Curl_loadhostpairs(struct Curl_easy *data)
{ {
struct curl_slist *hostp; struct curl_slist *hostp;
const char *host_end;
/* Default is no wildcard found */ /* Default is no wildcard found */
data->state.wildcard_resolve = FALSE; data->state.wildcard_resolve = FALSE;
for(hostp = data->state.resolve; hostp; hostp = hostp->next) { for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
char entry_id[MAX_HOSTCACHE_LEN]; char entry_id[MAX_HOSTCACHE_LEN];
if(!hostp->data) const char *host = hostp->data;
struct Curl_str source;
if(!host)
continue; continue;
if(hostp->data[0] == '-') { if(*host == '-') {
curl_off_t num = 0; curl_off_t num = 0;
size_t entry_len; size_t entry_len;
size_t hlen = 0; host++;
host_end = strchr(&hostp->data[1], ':'); if(!Curl_str_single(&host, '[')) {
if(Curl_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
if(host_end) { Curl_str_single(&host, ']') ||
hlen = host_end - &hostp->data[1]; Curl_str_single(&host, ':'))
host_end++; continue;
if(Curl_str_number(&host_end, &num, 0xffff))
host_end = NULL;
} }
if(!host_end) { else {
infof(data, "Bad syntax CURLOPT_RESOLVE removal entry '%s'", if(Curl_str_until(&host, &source, 4096, ':') ||
hostp->data); Curl_str_single(&host, ':')) {
continue; continue;
}
} }
/* Create an entry id, based upon the hostname and port */
entry_len = create_hostcache_id(&hostp->data[1], hlen, (int)num,
entry_id, sizeof(entry_id));
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
/* delete entry, ignore if it did not exist */ if(!Curl_str_number(&host, &num, 0xffff)) {
Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); /* Create an entry id, based upon the hostname and port */
entry_len = create_hostcache_id(source.str, source.len, (int)num,
entry_id, sizeof(entry_id));
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
if(data->share) /* delete entry, ignore if it did not exist */
Curl_share_unlock(data, CURL_LOCK_DATA_DNS); Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
} }
else { else {
struct Curl_dns_entry *dns; struct Curl_dns_entry *dns;
@ -1170,71 +1173,66 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
#if !defined(CURL_DISABLE_VERBOSE_STRINGS) #if !defined(CURL_DISABLE_VERBOSE_STRINGS)
const char *addresses = NULL; const char *addresses = NULL;
#endif #endif
const char *addr_begin;
const char *addr_end;
const char *port_ptr;
curl_off_t port = 0; curl_off_t port = 0;
const char *end_ptr;
bool permanent = TRUE; bool permanent = TRUE;
bool error = TRUE; bool error = TRUE;
char *host_begin = hostp->data;
size_t hlen = 0;
if(host_begin[0] == '+') { if(*host == '+') {
host_begin++; host++;
permanent = FALSE; permanent = FALSE;
} }
host_end = strchr(host_begin, ':'); if(!Curl_str_single(&host, '[')) {
if(!host_end) if(Curl_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
Curl_str_single(&host, ']'))
continue;
}
else {
if(Curl_str_until(&host, &source, 4096, ':'))
continue;
}
if(Curl_str_single(&host, ':') ||
Curl_str_number(&host, &port, 0xffff) ||
Curl_str_single(&host, ':'))
goto err; goto err;
hlen = host_end - host_begin;
port_ptr = host_end + 1;
if(Curl_str_number(&port_ptr, &port, 0xffff) ||
(*port_ptr != ':'))
goto err;
end_ptr = port_ptr;
#if !defined(CURL_DISABLE_VERBOSE_STRINGS) #if !defined(CURL_DISABLE_VERBOSE_STRINGS)
addresses = end_ptr + 1; addresses = host;
#endif #endif
while(*end_ptr) { /* start the address section */
size_t alen; while(*host) {
struct Curl_str target;
struct Curl_addrinfo *ai; struct Curl_addrinfo *ai;
addr_begin = end_ptr + 1; if(!Curl_str_single(&host, '[')) {
addr_end = strchr(addr_begin, ','); if(Curl_str_until(&host, &target, MAX_IPADR_LEN, ']') ||
if(!addr_end) Curl_str_single(&host, ']'))
addr_end = addr_begin + strlen(addr_begin);
end_ptr = addr_end;
/* allow IP(v6) address within [brackets] */
if(*addr_begin == '[') {
if(addr_end == addr_begin || *(addr_end - 1) != ']')
goto err; goto err;
++addr_begin;
--addr_end;
} }
else {
alen = addr_end - addr_begin; if(Curl_str_until(&host, &target, 4096, ',')) {
if(!alen) if(Curl_str_single(&host, ','))
continue; goto err;
/* survive nothing but just a comma */
if(alen >= sizeof(address)) continue;
goto err; }
}
memcpy(address, addr_begin, alen);
address[alen] = '\0';
#ifndef USE_IPV6 #ifndef USE_IPV6
if(strchr(address, ':')) { if(memchr(target.str, ':', target.len)) {
infof(data, "Ignoring resolve address '%s', missing IPv6 support.", infof(data, "Ignoring resolve address '%s', missing IPv6 support.",
address); address);
if(Curl_str_single(&host, ','))
goto err;
continue; continue;
} }
#endif #endif
if(target.len >= sizeof(address))
goto err;
memcpy(address, target.str, target.len);
address[target.len] = '\0';
ai = Curl_str2addr(address, (int)port); ai = Curl_str2addr(address, (int)port);
if(!ai) { if(!ai) {
infof(data, "Resolve address '%s' found illegal", address); infof(data, "Resolve address '%s' found illegal", address);
@ -1248,6 +1246,8 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
else { else {
head = tail = ai; head = tail = ai;
} }
if(Curl_str_single(&host, ','))
break;
} }
if(!head) if(!head)
@ -1263,7 +1263,7 @@ err:
} }
/* Create an entry id, based upon the hostname and port */ /* Create an entry id, based upon the hostname and port */
entry_len = create_hostcache_id(host_begin, hlen, (int)port, entry_len = create_hostcache_id(source.str, source.len, (int)port,
entry_id, sizeof(entry_id)); entry_id, sizeof(entry_id));
if(data->share) if(data->share)
@ -1274,7 +1274,7 @@ err:
if(dns) { if(dns) {
infof(data, "RESOLVE %.*s:%" CURL_FORMAT_CURL_OFF_T infof(data, "RESOLVE %.*s:%" CURL_FORMAT_CURL_OFF_T
" - old addresses discarded", (int)hlen, host_begin, port); " - old addresses discarded", (int)source.len, source.str, port);
/* delete old 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,
@ -1290,7 +1290,7 @@ err:
} }
/* put this new host in the cache */ /* put this new host in the cache */
dns = Curl_cache_addr(data, head, host_begin, hlen, (int)port, dns = Curl_cache_addr(data, head, source.str, source.len, (int)port,
permanent); permanent);
if(dns) { if(dns) {
/* release the returned reference; the cache itself will keep the /* release the returned reference; the cache itself will keep the
@ -1307,12 +1307,12 @@ err:
} }
#ifndef CURL_DISABLE_VERBOSE_STRINGS #ifndef CURL_DISABLE_VERBOSE_STRINGS
infof(data, "Added %.*s:%" CURL_FORMAT_CURL_OFF_T ":%s to DNS cache%s", infof(data, "Added %.*s:%" CURL_FORMAT_CURL_OFF_T ":%s to DNS cache%s",
(int)hlen, host_begin, port, addresses, (int)source.len, source.str, port, addresses,
permanent ? "" : " (non-permanent)"); permanent ? "" : " (non-permanent)");
#endif #endif
/* Wildcard hostname */ /* Wildcard hostname */
if((hlen == 1) && (host_begin[0] == '*')) { if((source.len == 1) && (source.str[0] == '*')) {
infof(data, "RESOLVE *:%" CURL_FORMAT_CURL_OFF_T " using wildcard", infof(data, "RESOLVE *:%" CURL_FORMAT_CURL_OFF_T " using wildcard",
port); port);
data->state.wildcard_resolve = TRUE; data->state.wildcard_resolve = TRUE;