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
This commit is contained in:
Lucas Clemente Vella 2021-04-24 20:04:53 +01:00 committed by Daniel Stenberg
parent ac54b10933
commit 84d2839740
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
11 changed files with 69 additions and 100 deletions

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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));

View File

@ -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",

View File

@ -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],

View File

@ -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;

View File

@ -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);

View File

@ -32,7 +32,7 @@ http
HTTP GET with localhost --interface
</name>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER -4 --interface localhost
http://%HOSTIP:%HTTPPORT/%TESTNUMBER -4 --interface 127.0.0.1
</command>
<precheck>
perl -e "print 'Test requires default test client host address' if ( '%CLIENTIP' ne '127.0.0.1' );"