cURL / Mailing Lists / curl-library / Single Mail

curl-library

Re: CURLOPT_LOCALPORT option broken ?!

From: <koettermarkus_at_gmx.de>
Date: Fri, 20 Feb 2009 00:20:39 +0100

Daniel Stenberg wrote:
> Any chance you can look into this issue?

host and port options are exclusive.

static CURLcode bindlocal(struct connectdata *conn,
                           curl_socket_t sockfd, int af)
{
   if(dev && (strlen(dev)<255) ) {
     ...
   }
   else if(port) {
    ...
   }
}

Sorry for not giving line numbers, viewcvs does not provide them.
I fixed this.
My diff is quite large, as I decided to use a Curl_sockaddr_storage for
ipv4 and ipv6 depending on the address type, saving 30 lines and making
the code easier to read, keeping all required comments.
I hope the indenting style applies to the rules.

Now it works this way:

1) try to get dev ip address, if dev is a nic alias (eth0), we get the
ip in myhost, if no port is given and SO_BINDTODEVICE is availible, use it.

2) if dev was no interface, it is either an ip address or a domain name,
use Curl_resolv to get it, store the result-ip-string to myhost.

3) try to parse myhost for ipv6 and set the options in the
Curl_sockaddr_storage

4) no success parsing ipv6, try ipv4, set options in the
Curl_sockaddr_storage

5) no success for both, we fail.

6) bind() for the given port-range until we succeed binding.

7) fail if we run out of ports

I did not run the test suite, but from testing multiple cases using the
curl cli I'm confident this does not introduce any regression.

Patch is attached, used the cvs dump from 20090219, diff is in -Naur format.

MfG
Markus

--- lib/connect.c.orig 2009-02-19 23:50:15.000000000 +0100
+++ lib/connect.c 2009-02-20 00:08:31.000000000 +0100
@@ -18,7 +18,7 @@
  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  * KIND, either express or implied.
  *
- * $Id: connect.c,v 1.211 2008/12/30 08:05:38 gknauf Exp $
+ * $Id: connect.c,v 1.211 2008-12-30 08:05:38 gknauf Exp $
  ***************************************************************************/
 
 #include "setup.h"
@@ -277,12 +277,16 @@
                           curl_socket_t sockfd, int af)
 {
   struct SessionHandle *data = conn->data;
- struct sockaddr_in me;
+
+ struct Curl_sockaddr_storage sa;
+ struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */
+ socklen_t sizeof_sa = 0; /* size of the data sock points to */
+ struct sockaddr_in *si4 = (struct sockaddr_in *)&sa;
 #ifdef ENABLE_IPV6
- struct sockaddr_in6 me6;
+ struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa;
 #endif
- struct sockaddr *sock = NULL; /* bind to this address */
- socklen_t socksize = 0; /* size of the data sock points to */
+ int socket_domain = 0;
+
   struct Curl_dns_entry *h=NULL;
   unsigned short port = data->set.localport; /* use this port number, 0 for
                                                 "random" */
@@ -290,29 +294,49 @@
   int portnum = data->set.localportrange;
   const char *dev = data->set.str[STRING_DEVICE];
   int error;
+ char myhost[256] = "";
 
   /*************************************************************
    * Select device to bind socket to
    *************************************************************/
+ if ( !dev && !port )
+ /* no local kind of binding was requested */
+ return CURLE_OK;
+
+
   if(dev && (strlen(dev)<255) ) {
- char myhost[256] = "";
- int rc;
- bool was_iface = FALSE;
 
- if(Curl_if2ip(af, dev, myhost, sizeof(myhost))) {
+ /* interface */
+ if(Curl_if2ip(af, dev, myhost, sizeof(myhost)))
+ {
       /*
        * We now have the numerical IP address in the 'myhost' buffer
        */
- rc = Curl_resolv(conn, myhost, 0, &h);
- if(rc == CURLRESOLV_PENDING)
- (void)Curl_wait_for_resolv(conn, &h);
-
- if(h) {
- was_iface = TRUE;
+ infof(data, "Interface %s is ip %s \n", dev, myhost);
+#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.
+ *
+ */
+ if(!port) {
+ /* Only bind to the interface when no port was given
+ */
+ if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
+ dev, 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 */
+ }
       }
- }
-
- if(!was_iface) {
+#endif
+ }else
+ {
       /*
        * This was not an interface, resolve the name as a host name
        * or IP number
@@ -324,6 +348,8 @@
        * to take a type parameter instead.
        */
       long ipver = data->set.ip_version;
+ int rc;
+
       if (af == AF_INET)
         data->set.ip_version = CURL_IPRESOLVE_V4;
 #ifdef ENABLE_IPV6
@@ -339,92 +365,39 @@
       if(h) {
         /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */
         Curl_printable_address(h->addr, myhost, sizeof myhost);
- }
- }
-
- if(!*myhost || !h) {
- /* need to fix this
- h=Curl_gethost(data,
- getmyhost(*myhost,sizeof(myhost)),
- hostent_buf,
- sizeof(hostent_buf));
- */
- failf(data, "Couldn't bind to '%s'", dev);
- if(h)
+ infof(data, "Domain %s is ip %s \n", dev, myhost);
         Curl_resolv_unlock(data, h);
- return CURLE_INTERFACE_FAILED;
+ }
     }
 
- infof(data, "Bind local address to %s\n", myhost);
-
- sock = h->addr->ai_addr;
- socksize = h->addr->ai_addrlen;
-
-#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.
- *
- */
- if(was_iface) {
- /* 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, 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 */
- }
+#ifdef ENABLE_IPV6
+ /* ipv6 address */
+ if ( inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0 ) {
+ si6->sin6_family = AF_INET6;
+ si6->sin6_port = htons(port);
+ sizeof_sa = sizeof(struct sockaddr_in6);
+ socket_domain = PF_INET6;
+ goto bind_now;
     }
 #endif
- }
- else if(port) {
- /* if a local port number is requested but no local IP, extract the
- address from the socket */
- if(af == AF_INET) {
- memset(&me, 0, sizeof(me));
- me.sin_family = AF_INET;
- me.sin_addr.s_addr = INADDR_ANY;
-
- sock = (struct sockaddr *)&me;
- socksize = sizeof(me);
 
+ /* ipv4 address */
+ if ( inet_pton(AF_INET, myhost, &si4->sin_addr) > 0 ) {
+ si4->sin_family = AF_INET;
+ si4->sin_port = htons(port);
+ sizeof_sa = sizeof(struct sockaddr_in);
+ socket_domain = PF_INET;
+ goto bind_now;
     }
-#ifdef ENABLE_IPV6
- else { /* AF_INET6 */
- memset(&me6, 0, sizeof(me6));
- me6.sin6_family = AF_INET6;
- /* in6addr_any isn't always available and since me6 has just been
- cleared, it's not strictly necessary to use it here */
- /*me6.sin6_addr = in6addr_any;*/
 
- sock = (struct sockaddr *)&me6;
- socksize = sizeof(me6);
- }
-#endif
+ failf(data, "Couldn't bind to '%s'", myhost);
+ return CURLE_INTERFACE_FAILED;
   }
- else
- /* no local kind of binding was requested */
- return CURLE_OK;
 
+bind_now:
   do {
-
- /* Set port number to bind to, 0 makes the system pick one */
- if(sock->sa_family == AF_INET)
- me.sin_port = htons(port);
-#ifdef ENABLE_IPV6
- else
- me6.sin6_port = htons(port);
-#endif
-
- if( bind(sockfd, sock, socksize) >= 0) {
- /* we succeeded to bind */
+ if( bind(sockfd, sock, sizeof_sa) >= 0) {
+ /* we succeeded to bind */
       struct Curl_sockaddr_storage add;
       socklen_t size = sizeof(add);
       memset(&add, 0, sizeof(struct Curl_sockaddr_storage));
@@ -432,26 +405,23 @@
         data->state.os_errno = error = SOCKERRNO;
         failf(data, "getsockname() failed with errno %d: %s",
               error, Curl_strerror(conn, error));
- if(h)
- Curl_resolv_unlock(data, h);
         return CURLE_INTERFACE_FAILED;
       }
- /* We re-use/clobber the port variable here below */
- if(((struct sockaddr *)&add)->sa_family == AF_INET)
- port = ntohs(((struct sockaddr_in *)&add)->sin_port);
-#ifdef ENABLE_IPV6
- else
- port = ntohs(((struct sockaddr_in6 *)&add)->sin6_port);
-#endif
       infof(data, "Local port: %d\n", port);
       conn->bits.bound = TRUE;
- if(h)
- Curl_resolv_unlock(data, h);
       return CURLE_OK;
     }
+
     if(--portnum > 0) {
       infof(data, "Bind to local port %d failed, trying next\n", port);
       port++; /* try next port */
+ /* We re-use/clobber the port variable here below */
+ if(sock->sa_family == AF_INET)
+ si4->sin_port = ntohs(port);
+#ifdef ENABLE_IPV6
+ else
+ si6->sin6_port = ntohs(port);
+#endif
     }
     else
       break;
@@ -460,8 +430,6 @@
   data->state.os_errno = error = SOCKERRNO;
   failf(data, "bind failed with errno %d: %s",
         error, Curl_strerror(conn, error));
- if(h)
- Curl_resolv_unlock(data, h);
 
   return CURLE_INTERFACE_FAILED;
 }
Received on 2009-02-20