mirror of
https://github.com/curl/curl.git
synced 2026-08-25 06:03:32 +03:00
socket: support binding to interface *AND* IP
Introduce new notation for CURLOPT_INTERFACE / --interface: ifhost!<interface>!<host> Binding to an interface doesn't set the address, and an interface can have multiple addresses. When binding to an address (without interface), the kernel is free to choose the route, and it can route through any device that can access the target address, not necessarily the one with the chosen address. Moreover, it is possible for different interfaces to have the same IP address, on which case we need to provide a way to be more specific. Factor out the parsing part of interface option, and add unit tests: 1663. Closes #13719
This commit is contained in:
parent
23fe1a52dc
commit
3060557af7
10 changed files with 322 additions and 79 deletions
202
lib/cf-socket.c
202
lib/cf-socket.c
|
|
@ -78,6 +78,7 @@
|
|||
#include "multihandle.h"
|
||||
#include "rand.h"
|
||||
#include "share.h"
|
||||
#include "strdup.h"
|
||||
#include "version_win32.h"
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
|
|
@ -435,6 +436,82 @@ void Curl_sndbuf_init(curl_socket_t sockfd)
|
|||
}
|
||||
#endif /* USE_WINSOCK */
|
||||
|
||||
/*
|
||||
* Curl_parse_interface()
|
||||
*
|
||||
* This is used to parse interface argument in the following formats.
|
||||
* In all the examples, `host` can be an IP address or a hostname.
|
||||
*
|
||||
* <iface_or_host> - can be either an interface name or a host.
|
||||
* if!<iface> - interface name.
|
||||
* host!<host> - host name.
|
||||
* ifhost!<iface>!<host> - interface name and host name.
|
||||
*
|
||||
* Parameters:
|
||||
*
|
||||
* input [in] - input string.
|
||||
* len [in] - length of the input string.
|
||||
* dev [in/out] - address where a pointer to newly allocated memory
|
||||
* holding the interface-or-host will be stored upon
|
||||
* completion.
|
||||
* iface [in/out] - address where a pointer to newly allocated memory
|
||||
* holding the interface will be stored upon completion.
|
||||
* host [in/out] - address where a pointer to newly allocated memory
|
||||
* holding the host will be stored upon completion.
|
||||
*
|
||||
* Returns CURLE_OK on success.
|
||||
*/
|
||||
CURLcode Curl_parse_interface(const char *input, size_t len,
|
||||
char **dev, char **iface, char **host)
|
||||
{
|
||||
static const char if_prefix[] = "if!";
|
||||
static const char host_prefix[] = "host!";
|
||||
static const char if_host_prefix[] = "ifhost!";
|
||||
|
||||
DEBUGASSERT(dev);
|
||||
DEBUGASSERT(iface);
|
||||
DEBUGASSERT(host);
|
||||
|
||||
if(strncmp(if_prefix, input, strlen(if_prefix)) == 0) {
|
||||
input += strlen(if_prefix);
|
||||
if(!*input)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
*iface = Curl_memdup0(input, len - strlen(if_prefix));
|
||||
return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
if(strncmp(host_prefix, input, strlen(host_prefix)) == 0) {
|
||||
input += strlen(host_prefix);
|
||||
if(!*input)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
*host = Curl_memdup0(input, len - strlen(host_prefix));
|
||||
return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
if(strncmp(if_host_prefix, input, strlen(if_host_prefix)) == 0) {
|
||||
const char *host_part;
|
||||
input += strlen(if_host_prefix);
|
||||
len -= strlen(if_host_prefix);
|
||||
host_part = memchr(input, '!', len);
|
||||
if(!host_part || !*(host_part + 1))
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
*iface = Curl_memdup0(input, host_part - input);
|
||||
if(!*iface)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
++host_part;
|
||||
*host = Curl_memdup0(host_part, len - (host_part - input));
|
||||
if(!*host) {
|
||||
free(*iface);
|
||||
*iface = NULL;
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
if(!*input)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
*dev = Curl_memdup0(input, len);
|
||||
return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_BINDLOCAL
|
||||
static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
||||
curl_socket_t sockfd, int af, unsigned int scope)
|
||||
|
|
@ -453,6 +530,10 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
/* how many port numbers to try to bind to, increasing one at a time */
|
||||
int portnum = data->set.localportrange;
|
||||
const char *dev = data->set.str[STRING_DEVICE];
|
||||
const char *iface_input = data->set.str[STRING_INTERFACE];
|
||||
const char *host_input = data->set.str[STRING_BINDHOST];
|
||||
const char *iface = iface_input ? iface_input : dev;
|
||||
const char *host = host_input ? host_input : dev;
|
||||
int error;
|
||||
#ifdef IP_BIND_ADDRESS_NO_PORT
|
||||
int on = 1;
|
||||
|
|
@ -464,81 +545,72 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
/*************************************************************
|
||||
* Select device to bind socket to
|
||||
*************************************************************/
|
||||
if(!dev && !port)
|
||||
if(!iface && !host && !port)
|
||||
/* no local kind of binding was requested */
|
||||
return CURLE_OK;
|
||||
|
||||
memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
|
||||
|
||||
if(dev && (strlen(dev)<255) ) {
|
||||
if(iface && (strlen(iface)<255) ) {
|
||||
char myhost[256] = "";
|
||||
int done = 0; /* -1 for error, 1 for address found */
|
||||
bool is_interface = FALSE;
|
||||
bool is_host = FALSE;
|
||||
static const char *if_prefix = "if!";
|
||||
static const char *host_prefix = "host!";
|
||||
|
||||
if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) {
|
||||
dev += strlen(if_prefix);
|
||||
is_interface = TRUE;
|
||||
}
|
||||
else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) {
|
||||
dev += strlen(host_prefix);
|
||||
is_host = TRUE;
|
||||
}
|
||||
if2ip_result_t if2ip_result = IF2IP_NOT_FOUND;
|
||||
|
||||
/* interface */
|
||||
if(!is_host) {
|
||||
#ifdef SO_BINDTODEVICE
|
||||
/*
|
||||
* This binds the local socket to a particular interface. This will
|
||||
* force even requests to other local interfaces to go out the external
|
||||
* interface. Only bind to the interface when specified as interface,
|
||||
* not just as a hostname or ip address.
|
||||
*
|
||||
* The interface might be a VRF, eg: vrf-blue, which means it cannot be
|
||||
* converted to an IP address and would fail Curl_if2ip. Simply try to
|
||||
* use it straight away.
|
||||
*/
|
||||
if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
|
||||
dev, (curl_socklen_t)strlen(dev) + 1) == 0) {
|
||||
/* This is often "errno 1, error: Operation not permitted" if you're
|
||||
* not running as root or another suitable privileged user. If it
|
||||
* succeeds it means the parameter was a valid interface and not an IP
|
||||
* address. Return immediately.
|
||||
*/
|
||||
infof(data, "socket successfully bound to interface '%s'", dev);
|
||||
/*
|
||||
* This binds the local socket to a particular interface. This will
|
||||
* force even requests to other local interfaces to go out the external
|
||||
* interface. Only bind to the interface when specified as interface,
|
||||
* not just as a hostname or ip address.
|
||||
*
|
||||
* The interface might be a VRF, eg: vrf-blue, which means it cannot be
|
||||
* converted to an IP address and would fail Curl_if2ip. Simply try to
|
||||
* use it straight away.
|
||||
*/
|
||||
if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
|
||||
iface, (curl_socklen_t)strlen(iface) + 1) == 0) {
|
||||
/* This is often "errno 1, error: Operation not permitted" if you're
|
||||
* not running as root or another suitable privileged user. If it
|
||||
* succeeds it means the parameter was a valid interface and not an IP
|
||||
* address. Return immediately.
|
||||
*/
|
||||
if(!host_input) {
|
||||
infof(data, "socket successfully bound to interface '%s'", iface);
|
||||
return CURLE_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
switch(Curl_if2ip(af,
|
||||
#ifdef USE_IPV6
|
||||
scope, conn->scope_id,
|
||||
#endif
|
||||
dev, myhost, sizeof(myhost))) {
|
||||
case IF2IP_NOT_FOUND:
|
||||
if(is_interface) {
|
||||
/* Do not fall back to treating it as a host name */
|
||||
failf(data, "Couldn't bind to interface '%s'", dev);
|
||||
return CURLE_INTERFACE_FAILED;
|
||||
}
|
||||
break;
|
||||
case IF2IP_AF_NOT_SUPPORTED:
|
||||
/* Signal the caller to try another address family if available */
|
||||
return CURLE_UNSUPPORTED_PROTOCOL;
|
||||
case IF2IP_FOUND:
|
||||
is_interface = TRUE;
|
||||
/*
|
||||
* We now have the numerical IP address in the 'myhost' buffer
|
||||
*/
|
||||
infof(data, "Local Interface %s is ip %s using address family %i",
|
||||
dev, myhost, af);
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!is_interface) {
|
||||
#endif
|
||||
if(!host_input) {
|
||||
/* Discover IP from input device, then bind to it */
|
||||
if2ip_result = Curl_if2ip(af,
|
||||
#ifdef USE_IPV6
|
||||
scope, conn->scope_id,
|
||||
#endif
|
||||
iface, myhost, sizeof(myhost));
|
||||
}
|
||||
switch(if2ip_result) {
|
||||
case IF2IP_NOT_FOUND:
|
||||
if(iface_input && !host_input) {
|
||||
/* Do not fall back to treating it as a host name */
|
||||
failf(data, "Couldn't bind to interface '%s'", iface);
|
||||
return CURLE_INTERFACE_FAILED;
|
||||
}
|
||||
break;
|
||||
case IF2IP_AF_NOT_SUPPORTED:
|
||||
/* Signal the caller to try another address family if available */
|
||||
return CURLE_UNSUPPORTED_PROTOCOL;
|
||||
case IF2IP_FOUND:
|
||||
/*
|
||||
* We now have the numerical IP address in the 'myhost' buffer
|
||||
*/
|
||||
host = myhost;
|
||||
infof(data, "Local Interface %s is ip %s using address family %i",
|
||||
iface, host, af);
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
if(!iface_input || host_input) {
|
||||
/*
|
||||
* This was not an interface, resolve the name as a host name
|
||||
* or IP number
|
||||
|
|
@ -557,7 +629,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
conn->ip_version = CURL_IPRESOLVE_V6;
|
||||
#endif
|
||||
|
||||
rc = Curl_resolv(data, dev, 80, FALSE, &h);
|
||||
rc = Curl_resolv(data, host, 80, FALSE, &h);
|
||||
if(rc == CURLRESOLV_PENDING)
|
||||
(void)Curl_resolver_wait_resolv(data, &h);
|
||||
conn->ip_version = ipver;
|
||||
|
|
@ -566,7 +638,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
/* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */
|
||||
Curl_printable_address(h->addr, myhost, sizeof(myhost));
|
||||
infof(data, "Name '%s' family %i resolved to '%s' family %i",
|
||||
dev, af, myhost, h->addr->ai_family);
|
||||
host, af, myhost, h->addr->ai_family);
|
||||
Curl_resolv_unlock(data, h);
|
||||
if(af != h->addr->ai_family) {
|
||||
/* bad IP version combo, signal the caller to try another address
|
||||
|
|
@ -628,7 +700,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
the error buffer, so the user receives this error message instead of a
|
||||
generic resolve error. */
|
||||
data->state.errorbuf = FALSE;
|
||||
failf(data, "Couldn't bind to '%s'", dev);
|
||||
failf(data, "Couldn't bind to '%s'", host);
|
||||
return CURLE_INTERFACE_FAILED;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@ struct Curl_sockaddr_ex {
|
|||
};
|
||||
#define sa_addr _sa_ex_u.addr
|
||||
|
||||
/*
|
||||
* Parse interface option, and return the interface name and the host part.
|
||||
*/
|
||||
CURLcode Curl_parse_interface(const char *input, size_t len,
|
||||
char **dev, char **iface, char **host);
|
||||
|
||||
/*
|
||||
* Create a socket based on info from 'conn' and 'ai'.
|
||||
|
|
|
|||
42
lib/setopt.c
42
lib/setopt.c
|
|
@ -139,6 +139,42 @@ static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp)
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static CURLcode setstropt_interface(
|
||||
char *option, char **devp, char **ifacep, char **hostp)
|
||||
{
|
||||
char *dev = NULL;
|
||||
char *iface = NULL;
|
||||
char *host = NULL;
|
||||
size_t len;
|
||||
CURLcode result;
|
||||
|
||||
DEBUGASSERT(devp);
|
||||
DEBUGASSERT(ifacep);
|
||||
DEBUGASSERT(hostp);
|
||||
|
||||
/* Parse the interface details */
|
||||
if(!option || !*option)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
len = strlen(option);
|
||||
if(len > 255)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
|
||||
result = Curl_parse_interface(option, len, &dev, &iface, &host);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
free(*devp);
|
||||
*devp = dev;
|
||||
|
||||
free(*ifacep);
|
||||
*ifacep = iface;
|
||||
|
||||
free(*hostp);
|
||||
*hostp = host;
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
#define C_SSLVERSION_VALUE(x) (x & 0xffff)
|
||||
#define C_SSLVERSION_MAX_VALUE(x) (x & 0xffff0000)
|
||||
|
||||
|
|
@ -1881,8 +1917,10 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
|
|||
* Set what interface or address/hostname to bind the socket to when
|
||||
* performing an operation and thus what from-IP your connection will use.
|
||||
*/
|
||||
result = Curl_setstropt(&data->set.str[STRING_DEVICE],
|
||||
va_arg(param, char *));
|
||||
result = setstropt_interface(va_arg(param, char *),
|
||||
&data->set.str[STRING_DEVICE],
|
||||
&data->set.str[STRING_INTERFACE],
|
||||
&data->set.str[STRING_BINDHOST]);
|
||||
break;
|
||||
#ifndef CURL_DISABLE_BINDLOCAL
|
||||
case CURLOPT_LOCALPORT:
|
||||
|
|
|
|||
|
|
@ -1458,6 +1458,8 @@ enum dupstring {
|
|||
STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */
|
||||
STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL doesn't specify */
|
||||
STRING_DEVICE, /* local network interface/address to use */
|
||||
STRING_INTERFACE, /* local network interface to use */
|
||||
STRING_BINDHOST, /* local address to use */
|
||||
STRING_ENCODING, /* Accept-Encoding string */
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
STRING_FTP_ACCOUNT, /* ftp account data */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue