curl / Mailing Lists / curl-library / Single Mail

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

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