From 2f4dc6525c130a8817e8a525159ec8bd78e9c8ae Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 17 Feb 2025 08:33:52 +0100 Subject: [PATCH] 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 --- docs/cmdline-opts/resolve.md | 9 +- docs/libcurl/opts/CURLOPT_RESOLVE.md | 5 + lib/hostip.c | 144 +++++++++++++-------------- 3 files changed, 84 insertions(+), 74 deletions(-) diff --git a/docs/cmdline-opts/resolve.md b/docs/cmdline-opts/resolve.md index 2b71d9a590..18733df0c2 100644 --- a/docs/cmdline-opts/resolve.md +++ b/docs/cmdline-opts/resolve.md @@ -12,6 +12,7 @@ See-also: - alt-svc Example: - --resolve example.com:443:127.0.0.1 $URL + - --resolve example.com:443:[2001:db8::252f:efd6] $URL --- # `--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 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 -the number used for the specific protocol the host is used for. It means -you need several entries if you want to provide address for the same host but +the number used for the specific protocol the host is used for. It means you +need several entries if you want to provide addresses for the same host but different ports. 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 expired. +Provide IPv6 addresses within [brackets]. + To redirect connects from a specific hostname or any hostname, independently of port number, consider the --connect-to option. Support for resolving with wildcard was added in 7.64.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. diff --git a/docs/libcurl/opts/CURLOPT_RESOLVE.md b/docs/libcurl/opts/CURLOPT_RESOLVE.md index 4dc404bbf2..952ff824e7 100644 --- a/docs/libcurl/opts/CURLOPT_RESOLVE.md +++ b/docs/libcurl/opts/CURLOPT_RESOLVE.md @@ -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 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 ones. Set it to NULL to disable its use again. @@ -90,6 +92,7 @@ int main(void) CURL *curl; struct curl_slist *host = NULL; 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(); 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 7.75.0. +Support for specifying the host component as an IPv6 address was added in 8.13.0. + # %AVAILABILITY% # RETURN VALUE diff --git a/lib/hostip.c b/lib/hostip.c index 4dbedfb012..17d335564f 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -1124,43 +1124,46 @@ void Curl_hostcache_clean(struct Curl_easy *data, CURLcode Curl_loadhostpairs(struct Curl_easy *data) { struct curl_slist *hostp; - const char *host_end; /* Default is no wildcard found */ data->state.wildcard_resolve = FALSE; for(hostp = data->state.resolve; hostp; hostp = hostp->next) { char entry_id[MAX_HOSTCACHE_LEN]; - if(!hostp->data) + const char *host = hostp->data; + struct Curl_str source; + if(!host) continue; - if(hostp->data[0] == '-') { + if(*host == '-') { curl_off_t num = 0; size_t entry_len; - size_t hlen = 0; - host_end = strchr(&hostp->data[1], ':'); - - if(host_end) { - hlen = host_end - &hostp->data[1]; - host_end++; - if(Curl_str_number(&host_end, &num, 0xffff)) - host_end = NULL; + host++; + if(!Curl_str_single(&host, '[')) { + if(Curl_str_until(&host, &source, MAX_IPADR_LEN, ']') || + Curl_str_single(&host, ']') || + Curl_str_single(&host, ':')) + continue; } - if(!host_end) { - infof(data, "Bad syntax CURLOPT_RESOLVE removal entry '%s'", - hostp->data); - continue; + else { + if(Curl_str_until(&host, &source, 4096, ':') || + Curl_str_single(&host, ':')) { + 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 */ - Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); + if(!Curl_str_number(&host, &num, 0xffff)) { + /* 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) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + /* delete entry, ignore if it did not exist */ + Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + } } else { struct Curl_dns_entry *dns; @@ -1170,71 +1173,66 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) #if !defined(CURL_DISABLE_VERBOSE_STRINGS) const char *addresses = NULL; #endif - const char *addr_begin; - const char *addr_end; - const char *port_ptr; curl_off_t port = 0; - const char *end_ptr; bool permanent = TRUE; bool error = TRUE; - char *host_begin = hostp->data; - size_t hlen = 0; - if(host_begin[0] == '+') { - host_begin++; + if(*host == '+') { + host++; permanent = FALSE; } - host_end = strchr(host_begin, ':'); - if(!host_end) - goto err; - hlen = host_end - host_begin; - - port_ptr = host_end + 1; - if(Curl_str_number(&port_ptr, &port, 0xffff) || - (*port_ptr != ':')) + if(!Curl_str_single(&host, '[')) { + 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; - end_ptr = port_ptr; #if !defined(CURL_DISABLE_VERBOSE_STRINGS) - addresses = end_ptr + 1; + addresses = host; #endif - while(*end_ptr) { - size_t alen; + /* start the address section */ + while(*host) { + struct Curl_str target; struct Curl_addrinfo *ai; - addr_begin = end_ptr + 1; - addr_end = strchr(addr_begin, ','); - if(!addr_end) - 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) != ']') + if(!Curl_str_single(&host, '[')) { + if(Curl_str_until(&host, &target, MAX_IPADR_LEN, ']') || + Curl_str_single(&host, ']')) goto err; - ++addr_begin; - --addr_end; } - - alen = addr_end - addr_begin; - if(!alen) - continue; - - if(alen >= sizeof(address)) - goto err; - - memcpy(address, addr_begin, alen); - address[alen] = '\0'; - + else { + if(Curl_str_until(&host, &target, 4096, ',')) { + if(Curl_str_single(&host, ',')) + goto err; + /* survive nothing but just a comma */ + continue; + } + } #ifndef USE_IPV6 - if(strchr(address, ':')) { + if(memchr(target.str, ':', target.len)) { infof(data, "Ignoring resolve address '%s', missing IPv6 support.", address); + if(Curl_str_single(&host, ',')) + goto err; continue; } #endif + if(target.len >= sizeof(address)) + goto err; + + memcpy(address, target.str, target.len); + address[target.len] = '\0'; + ai = Curl_str2addr(address, (int)port); if(!ai) { infof(data, "Resolve address '%s' found illegal", address); @@ -1248,6 +1246,8 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) else { head = tail = ai; } + if(Curl_str_single(&host, ',')) + break; } if(!head) @@ -1263,7 +1263,7 @@ err: } /* 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)); if(data->share) @@ -1274,7 +1274,7 @@ err: if(dns) { 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 1. old entry may have different addresses. 2. even if entry with correct addresses is already in the cache, @@ -1290,7 +1290,7 @@ err: } /* 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); if(dns) { /* release the returned reference; the cache itself will keep the @@ -1307,12 +1307,12 @@ err: } #ifndef CURL_DISABLE_VERBOSE_STRINGS 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)"); #endif /* 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", port); data->state.wildcard_resolve = TRUE; -- 2.47.2