From 84d2839740ca78041ac7419d9aaeac55c1e1c729 Mon Sep 17 00:00:00 2001 From: Lucas Clemente Vella Date: Sat, 24 Apr 2021 20:04:53 +0100 Subject: [PATCH] CURLOPT_IPRESOLVE: preventing wrong IP version from being used In some situations, it was possible that a transfer was setup to use an specific IP version, but due do DNS caching or connection reuse, it ended up using a different IP version from requested. This commit changes the effect of CURLOPT_IPRESOLVE from simply restricting address resolution to preventing the wrong connection type being used, when choosing a connection from the pool, and to restricting what addresses could be used when establishing a new connection. It is important that all addresses versions are resolved, even if not used in that transfer in particular, because the result is cached, and could be useful for a different transfer with a different CURLOPT_IPRESOLVE setting. Closes #6853 --- docs/libcurl/curl_easy_setopt.3 | 2 +- docs/libcurl/opts/CURLOPT_IPRESOLVE.3 | 13 +++---- docs/libcurl/opts/CURLOPT_RESOLVE.3 | 4 +-- include/curl/curl.h | 10 +++--- lib/asyn-ares.c | 49 +++++++-------------------- lib/asyn-thread.c | 19 ++--------- lib/connect.c | 27 ++++++++++++--- lib/doh.c | 18 +++++----- lib/hostip6.c | 19 ++--------- lib/url.c | 6 ++++ tests/data/test1082 | 2 +- 11 files changed, 69 insertions(+), 100 deletions(-) diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index 0c4f9b7ca..ecba0d5d4 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -494,7 +494,7 @@ Timeout for the connection phase. See \fICURLOPT_CONNECTTIMEOUT(3)\fP .IP CURLOPT_CONNECTTIMEOUT_MS Millisecond timeout for the connection phase. See \fICURLOPT_CONNECTTIMEOUT_MS(3)\fP .IP CURLOPT_IPRESOLVE -IP version to resolve to. See \fICURLOPT_IPRESOLVE(3)\fP +IP version to use. See \fICURLOPT_IPRESOLVE(3)\fP .IP CURLOPT_CONNECT_ONLY Only connect, nothing else. See \fICURLOPT_CONNECT_ONLY(3)\fP .IP CURLOPT_USE_SSL diff --git a/docs/libcurl/opts/CURLOPT_IPRESOLVE.3 b/docs/libcurl/opts/CURLOPT_IPRESOLVE.3 index 4bdd8b2fe..6d534da9e 100644 --- a/docs/libcurl/opts/CURLOPT_IPRESOLVE.3 +++ b/docs/libcurl/opts/CURLOPT_IPRESOLVE.3 @@ -29,14 +29,15 @@ CURLOPT_IPRESOLVE \- specify which IP protocol version to use CURLcode curl_easy_setopt(CURL *handle, CURLOPT_IPRESOLVE, long resolve); .SH DESCRIPTION Allows an application to select what kind of IP addresses to use when -resolving host names. This is only interesting when using host names that -resolve addresses using more than one version of IP. The allowed values are: +establishing a connection or choosing one from the connection pool. This is +interesting when using host names that resolve addresses using more than +one version of IP. The allowed values are: .IP CURL_IPRESOLVE_WHATEVER -Default, resolves addresses to all IP versions that your system allows. +Default, can use addresses of all IP versions that your system allows. .IP CURL_IPRESOLVE_V4 -Resolve to IPv4 addresses. +Uses only IPv4 addresses. .IP CURL_IPRESOLVE_V6 -Resolve to IPv6 addresses. +Uses only IPv6 addresses. .SH DEFAULT CURL_IPRESOLVE_WHATEVER .SH PROTOCOLS @@ -47,7 +48,7 @@ CURL *curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/foo.bin"); - /* resolve host name using IPv6-names only */ + /* of all addresses example.com resolves to, only IPv6 ones are used */ curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); ret = curl_easy_perform(curl); diff --git a/docs/libcurl/opts/CURLOPT_RESOLVE.3 b/docs/libcurl/opts/CURLOPT_RESOLVE.3 index 20ee69b0c..57a5401e4 100644 --- a/docs/libcurl/opts/CURLOPT_RESOLVE.3 +++ b/docs/libcurl/opts/CURLOPT_RESOLVE.3 @@ -57,8 +57,8 @@ this entry will be removed and a new entry will be created. This is because 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. +An ADDRESS provided by this option will only be use if not restricted by +the setting of \fICURLOPT_IPRESOLVE(3)\fP to a different IP version. Remove names from the DNS cache again, to stop providing these fake resolves, by including a string in the linked list that uses the format diff --git a/include/curl/curl.h b/include/curl/curl.h index e8f9db52f..97de8c88a 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -1466,8 +1466,8 @@ typedef enum { #define CURLOPT_SERVER_RESPONSE_TIMEOUT CURLOPT_FTP_RESPONSE_TIMEOUT /* Set this option to one of the CURL_IPRESOLVE_* defines (see below) to - tell libcurl to resolve names to those IP versions only. This only has - affect on systems with support for more than one, i.e IPv4 _and_ IPv6. */ + tell libcurl to use those IP versions only. This only has effect on + systems with support for more than one, i.e IPv4 _and_ IPv6. */ CURLOPT(CURLOPT_IPRESOLVE, CURLOPTTYPE_VALUES, 113), /* Set this option to limit the size of a file that will be downloaded from @@ -2135,10 +2135,10 @@ typedef enum { /* Below here follows defines for the CURLOPT_IPRESOLVE option. If a host name resolves addresses using more than one IP protocol version, this option might be handy to force libcurl to use a specific IP version. */ -#define CURL_IPRESOLVE_WHATEVER 0 /* default, resolves addresses to all IP +#define CURL_IPRESOLVE_WHATEVER 0 /* default, uses addresses to all IP versions that your system allows */ -#define CURL_IPRESOLVE_V4 1 /* resolve to IPv4 addresses */ -#define CURL_IPRESOLVE_V6 2 /* resolve to IPv6 addresses */ +#define CURL_IPRESOLVE_V4 1 /* uses only IPv4 addresses/connections */ +#define CURL_IPRESOLVE_V6 2 /* uses only IPv6 addresses/connections */ /* three convenient "aliases" that follow the name scheme better */ #define CURLOPT_RTSPHEADER CURLOPT_HTTPHEADER diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c index b5ec12a71..782784735 100644 --- a/lib/asyn-ares.c +++ b/lib/asyn-ares.c @@ -620,28 +620,9 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, int *waitp) { char *bufp; - int family = PF_INET; *waitp = 0; /* default to synchronous response */ -#ifdef ENABLE_IPV6 - switch(data->set.ipver) { - default: -#if ARES_VERSION >= 0x010601 - family = PF_UNSPEC; /* supported by c-ares since 1.6.1, so for older - c-ares versions this just falls through and defaults - to PF_INET */ - break; -#endif - case CURL_IPRESOLVE_V4: - family = PF_INET; - break; - case CURL_IPRESOLVE_V6: - family = PF_INET6; - break; - } -#endif /* ENABLE_IPV6 */ - bufp = strdup(hostname); if(bufp) { struct thread_data *res = NULL; @@ -661,33 +642,27 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, /* initial status - failed */ res->last_status = ARES_ENOTFOUND; -#ifdef ENABLE_IPV6 - if(family == PF_UNSPEC) { - if(Curl_ipv6works(data)) { - res->num_pending = 2; - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, - PF_INET, query_completed_cb, data); - ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, - PF_INET6, query_completed_cb, data); - } - else { - res->num_pending = 1; +#if ARES_VERSION >= 0x010601 + /* IPv6 supported by c-ares since 1.6.1 */ + if(Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + res->num_pending = 2; - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, - PF_INET, query_completed_cb, data); - } + /* areschannel is already setup in the Curl_open() function */ + ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, + PF_INET, query_completed_cb, data); + ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, + PF_INET6, query_completed_cb, data); } else -#endif /* ENABLE_IPV6 */ +#endif /* ARES_VERSION >= 0x010601 */ { res->num_pending = 1; /* areschannel is already setup in the Curl_open() function */ ares_gethostbyname((ares_channel)data->state.async.resolver, - hostname, family, + hostname, PF_INET, query_completed_cb, data); } diff --git a/lib/asyn-thread.c b/lib/asyn-thread.c index cd93492f2..36f68cb49 100644 --- a/lib/asyn-thread.c +++ b/lib/asyn-thread.c @@ -701,24 +701,9 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, *waitp = 0; /* default to synchronous response */ #ifdef CURLRES_IPV6 - /* - * Check if a limited name resolve has been requested. - */ - switch(data->set.ipver) { - case CURL_IPRESOLVE_V4: - pf = PF_INET; - break; - case CURL_IPRESOLVE_V6: - pf = PF_INET6; - break; - default: + if(Curl_ipv6works(data)) + /* The stack seems to be IPv6-enabled */ pf = PF_UNSPEC; - break; - } - - if((pf != PF_INET) && !Curl_ipv6works(data)) - /* The stack seems to be a non-IPv6 one */ - pf = PF_INET; #endif /* CURLRES_IPV6 */ memset(&hints, 0, sizeof(hints)); diff --git a/lib/connect.c b/lib/connect.c index 824ced31e..d9317f378 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -1367,14 +1367,31 @@ CURLcode Curl_connecthost(struct Curl_easy *data, conn->timeoutms_per_addr[1] = conn->tempaddr[1]->ai_next == NULL ? timeout_ms : timeout_ms / 2; - conn->tempfamily[0] = conn->tempaddr[0]? - conn->tempaddr[0]->ai_family:0; + if(conn->ip_version == CURL_IPRESOLVE_WHATEVER) { + /* any IP version is allowed */ + conn->tempfamily[0] = conn->tempaddr[0]? + conn->tempaddr[0]->ai_family:0; #ifdef ENABLE_IPV6 - conn->tempfamily[1] = conn->tempfamily[0] == AF_INET6 ? - AF_INET : AF_INET6; + conn->tempfamily[1] = conn->tempfamily[0] == AF_INET6 ? + AF_INET : AF_INET6; #else - conn->tempfamily[1] = AF_UNSPEC; + conn->tempfamily[1] = AF_UNSPEC; #endif + } + else { + /* only one IP version is allowed */ + conn->tempfamily[0] = (conn->ip_version == CURL_IPRESOLVE_V4) ? + AF_INET : +#ifdef ENABLE_IPV6 + AF_INET6; +#else + AF_UNSPEC; +#endif + conn->tempfamily[1] = AF_UNSPEC; + + ainext(conn, 0, FALSE); /* find first address of the right type */ + } + ainext(conn, 1, FALSE); /* assigns conn->tempaddr[1] accordingly */ DEBUGF(infof(data, "family0 == %s, family1 == %s\n", diff --git a/lib/doh.c b/lib/doh.c index 18d7221c7..36f8cd58d 100644 --- a/lib/doh.c +++ b/lib/doh.c @@ -420,17 +420,15 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, if(!dohp->headers) goto error; - if(conn->ip_version != CURL_IPRESOLVE_V6) { - /* create IPv4 DOH request */ - result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V4], - DNS_TYPE_A, hostname, data->set.str[STRING_DOH], - data->multi, dohp->headers); - if(result) - goto error; - dohp->pending++; - } + /* create IPv4 DOH request */ + result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V4], + DNS_TYPE_A, hostname, data->set.str[STRING_DOH], + data->multi, dohp->headers); + if(result) + goto error; + dohp->pending++; - if(conn->ip_version != CURL_IPRESOLVE_V4) { + if(Curl_ipv6works(data)) { /* create IPv6 DOH request */ result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6], DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH], diff --git a/lib/hostip6.c b/lib/hostip6.c index 53b3c6722..9791d8646 100644 --- a/lib/hostip6.c +++ b/lib/hostip6.c @@ -140,26 +140,13 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, #ifndef USE_RESOLVE_ON_IPS char addrbuf[128]; #endif - int pf; + int pf = PF_INET; *waitp = 0; /* synchronous response only */ - /* Check if a limited name resolve has been requested */ - switch(data->set.ipver) { - case CURL_IPRESOLVE_V4: - pf = PF_INET; - break; - case CURL_IPRESOLVE_V6: - pf = PF_INET6; - break; - default: + if(Curl_ipv6works(data)) + /* The stack seems to be IPv6-enabled */ pf = PF_UNSPEC; - break; - } - - if((pf != PF_INET) && !Curl_ipv6works(data)) - /* The stack seems to be a non-IPv6 one */ - pf = PF_INET; memset(&hints, 0, sizeof(hints)); hints.ai_family = pf; diff --git a/lib/url.c b/lib/url.c index 5f6462617..1ee38af0d 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1172,6 +1172,12 @@ ConnectionExists(struct Curl_easy *data, continue; } + if(data->set.ipver != CURL_IPRESOLVE_WHATEVER + && data->set.ipver != check->ip_version) { + /* skip because the connection is not via the requested IP version */ + continue; + } + if(bundle->multiuse == BUNDLE_MULTIPLEX) multiplexed = CONN_INUSE(check); diff --git a/tests/data/test1082 b/tests/data/test1082 index 3ee436bf0..d4dd0e9b5 100644 --- a/tests/data/test1082 +++ b/tests/data/test1082 @@ -32,7 +32,7 @@ http HTTP GET with localhost --interface -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -4 --interface localhost +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -4 --interface 127.0.0.1 perl -e "print 'Test requires default test client host address' if ( '%CLIENTIP' ne '127.0.0.1' );"