curl / Mailing Lists / curl-library / Single Mail

curl-library

[PATCH] curl: Support CURLOPT_LOCALADDR to bind to local address.

From: <greearb_at_candelatech.com>
Date: Thu, 9 Nov 2017 07:17:20 -0800

From: Ben Greear <greearb_at_candelatech.com>

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>

---
We have been using this for some time in one form or another.  It
is messing with some tricky code though, so it could use a good
review.
 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                         | 158 +++++++++++++++++++++-------------
 lib/url.c                             |  23 ++++-
 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 +
 13 files changed, 185 insertions(+), 62 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 2982056..ea79af8 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 0000000..fc17ec5
--- /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 6803a2e..b9e155c 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -1815,6 +1815,9 @@ typedef enum {
   /* Post MIME data. */
   CINIT(MIMEPOST, OBJECTPOINT, 269),
 
+  /* Set the IP-Address string to use as outgoing IP Addr */
+  CINIT(LOCALADDR, OBJECTPOINT, 270),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h
index 10c74c7..b825892 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 927ff7c..54c5141 100755
--- a/lib/connect.c
+++ b/lib/connect.c
@@ -255,7 +255,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
@@ -264,16 +287,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;
@@ -283,59 +297,78 @@ static CURLcode bindlocal(struct connectdata *conn,
       is_host = TRUE;
     }
 
+  decided_is_host:
     /* interface */
-    if(!is_host) {
-      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;
+
+    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:
+            if(is_interface) {
+              /* 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;
+            }
+            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;
+        }/* 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
-           *
-           * This feature allows one to tightly bind 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.
-           */
-          if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
-                        dev, (curl_socklen_t)strlen(dev)+1) != 0) {
-            error = SOCKERRNO;
-            infof(data, "SO_BINDTODEVICE %s failed with errno %d: %s;"
-                  " will do regular bind\n",
-                  dev, error, Curl_strerror(conn, error));
-            /* This is typically "errno 1, error: Operation not permitted" if
-               you're not running as root or another suitable privileged
-               user */
-          }
-#endif
-          break;
+      /* I am not sure any other OSs than Linux that provide this feature,
+       * and at the least I cannot test. --Ben
+       *
+       * This feature allows one to tightly bind 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.
+       */
+      if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
+                    dev, (curl_socklen_t)strlen(dev)+1) != 0) {
+        error = SOCKERRNO;
+        infof(data, "SO_BINDTODEVICE %s failed with errno %d: %s;"
+              " will do regular bind\n",
+              dev, error, Curl_strerror(conn, error));
+        /* This is typically "errno 1, error: Operation not permitted" if
+           you're not running as root or another suitable privileged
+           user */
       }
+#endif
+    }
+    else {
+      strncpy(myhost, dev, sizeof(myhost));
+      myhost[sizeof(myhost)-1] = 0;
+      resolv_myhost = TRUE;
     }
-    if(!is_interface) {
+
+    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
@@ -351,7 +384,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;
@@ -408,7 +441,9 @@ static CURLcode bindlocal(struct connectdata *conn,
     }
 
     if(done < 1) {
-      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;
     }
   }
@@ -462,8 +497,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/url.c b/lib/url.c
index 9799096..ef2e6f2 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -2102,6 +2102,14 @@ CURLcode Curl_setopt(struct Curl_easy *data, CURLoption option,
     result = 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 = 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.
@@ -3072,6 +3080,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);
 
@@ -3701,7 +3710,7 @@ ConnectionExists(struct Curl_easy *data,
            already in use so we skip it */
         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.
@@ -3716,7 +3725,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;
       }
 
@@ -4329,6 +4341,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;
 
@@ -4346,6 +4363,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
   free(conn->master_buffer);
   free(conn->localdev);
   free(conn);
+  free(conn->localaddr);
   return NULL;
 }
 
@@ -6353,6 +6371,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 09ac9d1..d8c3d2c 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1020,6 +1020,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 */
@@ -1478,6 +1479,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 de2c9cc..3aa33a6 100644
--- a/packages/OS400/ccsidcurl.c
+++ b/packages/OS400/ccsidcurl.c
@@ -1152,6 +1152,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 755195c..11a6fda 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 23943fe..05a63f0 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 31fc7a1..2f7671b 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 */
@@ -745,6 +746,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 486f65d..9af98db 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 25ea9c4..6325492 100644
--- a/src/tool_operate.c
+++ b/src/tool_operate.c
@@ -1237,6 +1237,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.4.11
-------------------------------------------------------------------
Unsubscribe: https://cool.haxx.se/list/listinfo/curl-library
Etiquette:   https://curl.haxx.se/mail/etiquette.html
Received on 2017-11-09