curl-library
[PATCH] curl: Support CURLOPT_LOCALADDR to bind to local address.
From: Ben Greear <greearb_at_candelatech.com>
Date: Mon, 22 Aug 2016 16:40:32 -0700
Date: Mon, 22 Aug 2016 16:40:32 -0700
This allows a user to bind to both an interface (with CURLOPT_INTERFACE)
and a local IP address. In doing so, it allows the user to work-around
some serious performance issues on machines with lots of virtual interfaces
because curl no longer has to scan all interfaces each time it makes
a connection.
Signed-off-by: Ben Greear <greearb_at_candelatech.com>
--- docs/libcurl/curl_easy_setopt.3 | 2 + docs/libcurl/opts/CURLOPT_LOCALADDR.3 | 46 ++++++++++++++ include/curl/curl.h | 3 + include/curl/typecheck-gcc.h | 1 + lib/connect.c | 113 ++++++++++++++++++++++------------ lib/setopt.c | 8 +++ lib/url.c | 15 ++++- lib/urldata.h | 2 + packages/OS400/ccsidcurl.c | 1 + src/tool_cfgable.c | 1 + src/tool_cfgable.h | 1 + src/tool_getparam.c | 5 ++ src/tool_help.c | 2 + src/tool_operate.c | 2 + 14 files changed, 160 insertions(+), 42 deletions(-) create mode 100644 docs/libcurl/opts/CURLOPT_LOCALADDR.3 diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index 0249e6bbc..52a047b35 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -185,6 +185,8 @@ Proxy authentication service name. \fICURLOPT_PROXY_SERVICE_NAME(3)\fP Authentication service name. \fICURLOPT_SERVICE_NAME(3)\fP .IP CURLOPT_INTERFACE Bind connection locally to this. See \fICURLOPT_INTERFACE(3)\fP +.IP CURLOPT_LOCALADDR +Set local IP address to use. See \fICURLOPT_LOCALADDR(3)\fP .IP CURLOPT_LOCALPORT Bind connection locally to this port. See \fICURLOPT_LOCALPORT(3)\fP .IP CURLOPT_LOCALPORTRANGE diff --git a/docs/libcurl/opts/CURLOPT_LOCALADDR.3 b/docs/libcurl/opts/CURLOPT_LOCALADDR.3 new file mode 100644 index 000000000..fc17ec55d --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_LOCALADDR.3 @@ -0,0 +1,46 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel_at_haxx.se>, et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at http://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_LOCALADDR 3 "24 Feb 2015" "libcurl 7.37.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_LOCALADDR \- source IP address for outgoing traffic +.SH SYNOPSIS +#include <curl/curl.h> + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_LOCALADDR, char *ip_addr); +.SH DESCRIPTION +Pass a char * as parameter. This sets the \fIP Address\fP to use as +for outgoing connections. + +.SH DEFAULT +NULL, use whatever the TCP stack finds suitable +.SH PROTOCOLS +All +.SH EXAMPLE +TODO +.SH AVAILABILITY +Not upstream yet. +.SH RETURN VALUE +Returns CURLE_OK on success or +CURLE_OUT_OF_MEMORY if there was insufficient heap space. +.SH "SEE ALSO" +.BR CURLOPT_SOCKOPTFUNCTION "(3), " CURLOPT_INTERFACE "(3), " diff --git a/include/curl/curl.h b/include/curl/curl.h index 69283eb08..39994c053 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -1823,6 +1823,9 @@ typedef enum { seconds since 1 Jan 1970. */ CINIT(TIMEVALUE_LARGE, OFF_T, 270), + /* Set the IP-Address string to use as outgoing IP Addr */ + CINIT(LOCALADDR, OBJECTPOINT, 271), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h index 10c74c764..b82589266 100644 --- a/include/curl/typecheck-gcc.h +++ b/include/curl/typecheck-gcc.h @@ -270,6 +270,7 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_off_t, (option) == CURLOPT_ISSUERCERT || \ (option) == CURLOPT_KEYPASSWD || \ (option) == CURLOPT_KRBLEVEL || \ + (option) == CURLOPT_LOCALADDR || \ (option) == CURLOPT_LOGIN_OPTIONS || \ (option) == CURLOPT_MAIL_AUTH || \ (option) == CURLOPT_MAIL_FROM || \ diff --git a/lib/connect.c b/lib/connect.c index 3edb71eb7..9b239d9b7 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -257,7 +257,30 @@ static CURLcode bindlocal(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 *addr = data->set.str[STRING_LOCALADDR]; int error; + bool is_interface = FALSE; + bool is_host = FALSE; + static const char *if_prefix = "if!"; + static const char *host_prefix = "host!"; + bool resolv_myhost = false; + char myhost[256] = ""; + int done = 0; + + memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); + + infof(data, "bind-local, addr: %s dev: %s\n", + addr, dev); + + /* The original code only took 'dev', which could be device name or addr. + * If only addr is set, then just pretend it was 'dev' to re-use as much + * of the old logic as possible. */ + if(addr && !dev) { + is_host = TRUE; + dev = addr; + addr = NULL; + goto decided_is_host; + } /************************************************************* * Select device to bind socket to @@ -266,16 +289,7 @@ static CURLcode bindlocal(struct connectdata *conn, /* no local kind of binding was requested */ return CURLE_OK; - memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); - if(dev && (strlen(dev)<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; @@ -285,8 +299,39 @@ static CURLcode bindlocal(struct connectdata *conn, is_host = TRUE; } + decided_is_host: /* interface */ - if(!is_host) { + if((!is_host) && (is_interface || Curl_if_is_interface_name(dev))) { + is_interface = TRUE; /* in case we had to call Curl_if_is_interface...*/ + if(addr && dev) { + strncpy(myhost, addr, sizeof(myhost)); + myhost[sizeof(myhost)-1] = 0; + resolv_myhost = TRUE; + } + else { + /* Discover IP addr for interface */ + switch(Curl_if2ip(af, scope, conn->scope_id, dev, + myhost, sizeof(myhost))) { + case IF2IP_NOT_FOUND: + /* Do not fall back to treating it as a host name */ + failf(data, "Couldn't bind to interface '%s', addr: '%s'", + dev, addr); + return CURLE_INTERFACE_FAILED; + 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 + */ + infof(data, + "Local Interface %s is ip %s using address family %i\n", + dev, myhost, af); + done = 1; + break; + }/* switch */ + }/* else */ + #ifdef SO_BINDTODEVICE /* I am not sure any other OSs than Linux that provide this feature, * and at the least I cannot test. --Ben @@ -314,34 +359,17 @@ static CURLcode bindlocal(struct connectdata *conn, return CURLE_OK; } #endif - - switch(Curl_if2ip(af, scope, conn->scope_id, 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\n", - dev, myhost, af); - done = 1; - break; - } } - if(!is_interface) { + else { + strncpy(myhost, dev, sizeof(myhost)); + myhost[sizeof(myhost)-1] = 0; + resolv_myhost = TRUE; + } + + if(resolv_myhost || !is_interface) { /* - * This was not an interface, resolve the name as a host name - * or IP number + * addr was specified, or this was not an interface. + * Resolve the name as a host name or IP number * * Temporarily force name resolution to use only the address type * of the connection. The resolve functions should really be changed @@ -357,7 +385,7 @@ static CURLcode bindlocal(struct connectdata *conn, conn->ip_version = CURL_IPRESOLVE_V6; #endif - rc = Curl_resolv(conn, dev, 0, &h); + rc = Curl_resolv(conn, myhost, 0, &h); if(rc == CURLRESOLV_PENDING) (void)Curl_resolver_wait_resolv(conn, &h); conn->ip_version = ipver; @@ -418,7 +446,9 @@ static CURLcode bindlocal(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', addr '%s', done: %d" + " is-host: %d is-interface: %d", + dev, addr, done, is_host, is_interface); return CURLE_INTERFACE_FAILED; } } @@ -472,8 +502,11 @@ static CURLcode bindlocal(struct connectdata *conn, } data->state.os_errno = error = SOCKERRNO; - failf(data, "bind failed with errno %d: %s", - error, Curl_strerror(conn, error)); + failf(data, "bindlocal: bind failed with errno %d: %s, dev: %s " + "is_host: %i is_interface: %i port: %hu addr: %s " + "sock->sa_family: %hu myhost: %s resolv-myhost: %i af: %i", + error, Curl_strerror(conn, error), dev, is_host, is_interface, + port, addr, sock->sa_family, myhost, resolv_myhost, af); return CURLE_INTERFACE_FAILED; } diff --git a/lib/setopt.c b/lib/setopt.c index 686e9dbce..c6d11ce24 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -1610,6 +1610,14 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, result = Curl_setstropt(&data->set.str[STRING_DEVICE], va_arg(param, char *)); break; + case CURLOPT_LOCALADDR: + /* + * Set what local address 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_LOCALADDR], + va_arg(param, char *)); + break; case CURLOPT_LOCALPORT: /* * Set what local port to bind the socket to when performing an operation. diff --git a/lib/url.c b/lib/url.c index 74813e874..df2c192bf 100644 --- a/lib/url.c +++ b/lib/url.c @@ -707,6 +707,7 @@ static void conn_free(struct connectdata *conn) Curl_llist_destroy(&conn->recv_pipe, NULL); Curl_safefree(conn->localdev); + Curl_safefree(conn->localaddr); Curl_free_primary_ssl_config(&conn->ssl_config); Curl_free_primary_ssl_config(&conn->proxy_ssl_config); @@ -1268,7 +1269,7 @@ ConnectionExists(struct Curl_easy *data, if they belong to the same multi handle */ continue; - if(needle->localdev || needle->localport) { + if(needle->localdev || needle->localport || needle->localaddr) { /* If we are bound to a specific local end (IP+port), we must not re-use a random other one, although if we didn't ask for a particular one we can reuse one that was bound. @@ -1283,7 +1284,10 @@ ConnectionExists(struct Curl_easy *data, if((check->localport != needle->localport) || (check->localportrange != needle->localportrange) || (needle->localdev && - (!check->localdev || strcmp(check->localdev, needle->localdev)))) + (!check->localdev || strcmp(check->localdev, needle->localdev))) || + (needle->localaddr && + (!check->localaddr || + strcmp(check->localaddr, needle->localaddr)))) continue; } @@ -1912,6 +1916,11 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) if(!conn->localdev) goto error; } + if(data->set.str[STRING_LOCALADDR]) { + conn->localaddr = strdup(data->set.str[STRING_LOCALADDR]); + if(!conn->localaddr) + goto error; + } conn->localportrange = data->set.localportrange; conn->localport = data->set.localport; @@ -1932,6 +1941,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) free(conn->ssl_extra); #endif free(conn); + free(conn->localaddr); return NULL; } @@ -3985,6 +3995,7 @@ static void reuse_conn(struct connectdata *old_conn, Curl_safefree(old_conn->http_proxy.passwd); Curl_safefree(old_conn->socks_proxy.passwd); Curl_safefree(old_conn->localdev); + Curl_safefree(old_conn->localaddr); Curl_llist_destroy(&old_conn->send_pipe, NULL); Curl_llist_destroy(&old_conn->recv_pipe, NULL); diff --git a/lib/urldata.h b/lib/urldata.h index 6c594fe8d..a8ec20dad 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1002,6 +1002,7 @@ struct connectdata { that subsequent bound-requested connections aren't accidentally re-using wrong connections. */ char *localdev; + char *localaddr; /* Local addr in dot notation */ unsigned short localport; int localportrange; struct http_connect_state *connect_state; /* for HTTP CONNECT */ @@ -1451,6 +1452,7 @@ enum dupstring { Each such pointer must be added manually to Curl_dupset() --- */ STRING_COPYPOSTFIELDS, /* if POST, set the fields' values here */ + STRING_LOCALADDR, /* Local IP Addr to use. */ STRING_LAST /* not used, just an end-of-list marker */ }; diff --git a/packages/OS400/ccsidcurl.c b/packages/OS400/ccsidcurl.c index 0ca6d6866..74e140f7e 100644 --- a/packages/OS400/ccsidcurl.c +++ b/packages/OS400/ccsidcurl.c @@ -1153,6 +1153,7 @@ curl_easy_setopt_ccsid(CURL * curl, CURLoption tag, ...) case CURLOPT_FTP_ACCOUNT: case CURLOPT_FTP_ALTERNATIVE_TO_USER: case CURLOPT_INTERFACE: + case CURLOPT_LOCALADDR: case CURLOPT_ISSUERCERT: case CURLOPT_KEYPASSWD: case CURLOPT_KRBLEVEL: diff --git a/src/tool_cfgable.c b/src/tool_cfgable.c index d77488166..5960713aa 100644 --- a/src/tool_cfgable.c +++ b/src/tool_cfgable.c @@ -61,6 +61,7 @@ static void free_config_fields(struct OperationConfig *config) Curl_safefree(config->headerfile); Curl_safefree(config->ftpport); Curl_safefree(config->iface); + Curl_safefree(config->localaddr); Curl_safefree(config->range); diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index 713739e7a..1188f28e9 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -69,6 +69,7 @@ struct OperationConfig { char *headerfile; char *ftpport; char *iface; + char *localaddr; /* local IP address */ int localport; int localportrange; unsigned short porttouse; diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 015d63551..6dd87ee22 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -108,6 +108,7 @@ static const struct LongShort aliases[]= { {"*u", "crlf", ARG_BOOL}, {"*v", "stderr", ARG_STRING}, {"*w", "interface", ARG_STRING}, + {"*W", "localaddr", ARG_STRING}, {"*x", "krb", ARG_STRING}, {"*x", "krb4", ARG_STRING}, /* 'krb4' is the previous name */ @@ -769,6 +770,10 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ /* interface */ GetStr(&config->iface, nextarg); break; + case 'W': /* --localaddr */ + /* addr in dot notation */ + GetStr(&config->localaddr, nextarg); + break; case 'x': /* --krb */ /* kerberos level string */ if(curlinfo->features & CURL_VERSION_KERBEROS4) diff --git a/src/tool_help.c b/src/tool_help.c index 70b2e8a1b..364e749d5 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -184,6 +184,8 @@ static const struct helptxt helptext[] = { "Allow insecure server connections when using SSL"}, {" --interface <name>", "Use network INTERFACE (or address)"}, + {" --localaddr <IP-ADDR>", + "Specify local IP-Address to use"}, {"-4, --ipv4", "Resolve names to IPv4 addresses"}, {"-6, --ipv6", diff --git a/src/tool_operate.c b/src/tool_operate.c index 5401955af..2ed13cffb 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1241,6 +1241,8 @@ static CURLcode operate_do(struct GlobalConfig *global, my_setopt_str(curl, CURLOPT_INTERFACE, config->iface); my_setopt_str(curl, CURLOPT_KRBLEVEL, config->krblevel); + my_setopt_str(curl, CURLOPT_LOCALADDR, config->localaddr); + progressbarinit(&progressbar, config); if((global->progressmode == CURL_PROGRESS_BAR) && !global->noprogress && !global->mute) { -- 2.14.3 --------------C546A2C0F71BCBB3F87876BE Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: base64 Content-Disposition: inline LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t LS0tLS0tLS0tLQpVbnN1YnNjcmliZTogaHR0cHM6Ly9jb29sLmhheHguc2UvbGlzdC9saXN0aW5m by9jdXJsLWxpYnJhcnkKRXRpcXVldHRlOiAgIGh0dHBzOi8vY3VybC5oYXh4LnNlL21haWwvZXRp cXVldHRlLmh0bWw= --------------C546A2C0F71BCBB3F87876BE--Received on 2001-09-17