On 12/13/2017 12:19 AM, Daniel Stenberg wrote:
On Thu, 9 Nov 2017, [email protected] wrote:

Hi Ben!

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.

This patch has clearly been neglected by me, and while I'm not quite ready to 
take it on just yet[*] I wanted to ask if you could consider pushing this to
github as a PR?

That way we'd get it tested and verified much better before merge, and it also 
helps out to tell us when it starts to get merge confliects (and it makes it
harder for us/me to forget it). Like it does now: the changes in lib/connect.c 
needs a rebase/edit to apply on current git master.

Further: I would like to get a clearer view on what exactly CURLOPT_LOCALADDR provides 
and offers that CURLOPT_INTERFACE prefixed with "host!" doesn't do? And
if so, can't we instead make CURLOPT_INTERFACE use that? Or perhaps even add 
another prefix for CURLOPT_INTERFACE if it truly has to be different. To me, 
this
feels like adding an entirely new easy option for a very minor variation of an 
already existing option.

[*] = I'll take off on an extended vacation now for a few weeks ahead so I 
personally won't work on anything larger until I get back.


Hello,

I just rebased, and here is the (untested) patch again.  I'll be testing this, 
but since my normal
use case is pretty specific, and there were some tricky conflicts in the 
connect.c code,
it could use some other testing as well....

Thanks,
Ben

--
Ben Greear <[email protected]>
Candela Technologies Inc  http://www.candelatech.com

>From 2ef4dcbfe71c6acb8e5a2dc11ed30b0cbb645eba Mon Sep 17 00:00:00 2001
From: Ben Greear <[email protected]>
Date: Mon, 22 Aug 2016 16:40:32 -0700
Subject: [PATCH] curl: Support CURLOPT_LOCALADDR to bind to local address.

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 <[email protected]>
---
 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, <[email protected]>, 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

-------------------------------------------------------------------
Unsubscribe: https://cool.haxx.se/list/listinfo/curl-library
Etiquette:   https://curl.haxx.se/mail/etiquette.html

Reply via email to