Commit 7b7db236 authored by Daniel Stenberg's avatar Daniel Stenberg
Browse files

- Senthil Raja Velu reported a problem when CURLOPT_INTERFACE and

  CURLOPT_LOCALPORT were used together (the local port bind failed), and
  Markus Koetter provided the fix!
parent 794b4da8
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -6,6 +6,11 @@

                                  Changelog

Daniel Stenberg (27 Feb 2009)
- Senthil Raja Velu reported a problem when CURLOPT_INTERFACE and
  CURLOPT_LOCALPORT were used together (the local port bind failed), and
  Markus Koetter provided the fix!

Daniel Stenberg (25 Feb 2009)
- As Daniel Fandrich figured out, we must do the GnuTLS initing in the
  curl_global_init() function to properly maintain the performing functions
+3 −1
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ This release includes the following bugfixes:
 o CURLINFO_CONTENT_LENGTH_DOWNLOAD and CURLINFO_CONTENT_LENGTH_UPLOAD are -1
   if unknown
 o Negotiate proxy authentication
 o CURLOPT_INTERFACE and CURLOPT_LOCALPORT used together

This release includes the following known bugs:

@@ -57,6 +58,7 @@ advice from friends like these:
 Peter Sylvester, Chad Monroe, Markus Moeller, Yang Tse, Scott Cantor,
 Patrick Scott, Hidemoto Nakada, Jocelyn Jaubert, Andre Guibert de Bruet,
 Kamil Dudka, Patrik Thunstrom, Linus Nielsen Feltzing, Mark Incley,
 Daniel Johnson, James Cheng, Brian J. Murrell
 Daniel Johnson, James Cheng, Brian J. Murrell, Senthil Raja Velu,
 Markus Koetter

        Thanks! (and sorry if I forgot to mention someone)
+101 −107
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
 * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@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
@@ -54,8 +54,8 @@
#include <arpa/inet.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h> /* required for free() prototype, without it, this crashes */
#endif              /* on macos 68K */
#include <stdlib.h>
#endif

#if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE))
#include <sys/filio.h>
@@ -277,12 +277,15 @@ static CURLcode bindlocal(struct connectdata *conn,
                          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 */

  struct Curl_dns_entry *h=NULL;
  unsigned short port = data->set.localport; /* use this port number, 0 for
                                                "random" */
@@ -290,40 +293,64 @@ static CURLcode bindlocal(struct connectdata *conn,
  int portnum = data->set.localportrange;
  const char *dev = data->set.str[STRING_DEVICE];
  int error;
  char myhost[256] = "";
  int done = 0; /* -1 for error, 1 for address found */

  /*************************************************************
   * Select device to bind socket to
   *************************************************************/
  if ( !dev && !port )
    /* 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 rc;
    bool was_iface = FALSE;

    /* 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);
      infof(data, "Local Interface %s is ip %s using address family %i\n",
            dev, myhost, af);
      done = 1;

      if(h) {
        was_iface = TRUE;
#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, 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
    }

    if(!was_iface) {
    else {
      /*
       * 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
       * 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
@@ -338,92 +365,64 @@ static CURLcode bindlocal(struct connectdata *conn,

      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)
        Curl_printable_address(h->addr, myhost, sizeof(myhost));
        infof(data, "Name '%s' family %i resolved to '%s' family %i\n",
              dev, af, myhost, h->addr->ai_family);
        Curl_resolv_unlock(data, h);
      return CURLE_INTERFACE_FAILED;
        done = 1;
      }

    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.
      else {
        /*
         * provided dev was no interface (or interfaces are not supported
         * e.g. solaris) no ip address and no domain we fail here
         */
      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 */
        done = -1;
      }
    }
#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);

    }
    if(done > 0) {
#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);
      /* ipv6 address */
      if ( (af == AF_INET6) &&
           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);
      }
      else
#endif
      /* ipv4 address */
      if ( af == AF_INET && 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);
      }
    }
  else
    /* no local kind of binding was requested */
    return CURLE_OK;

  do {

    /* Set port number to bind to, 0 makes the system pick one */
    if(sock->sa_family == AF_INET)
      me.sin_port = htons(port);
    if(done < 1) {
      failf(data, "Couldn't bind to '%s'", dev);
      return CURLE_INTERFACE_FAILED;
    }
  }
  else {
    /* no device was given, prepare sa to match af's needs */
#ifdef ENABLE_IPV6
    if ( af == AF_INET6 ) {
      si6->sin6_family = AF_INET6;
      si6->sin6_port = htons(port);
      sizeof_sa = sizeof(struct sockaddr_in6);
    }
    else
      me6.sin6_port = htons(port);
#endif
    if ( af == AF_INET ) {
      si4->sin_family = AF_INET;
      si4->sin_port = htons(port);
      sizeof_sa = sizeof(struct sockaddr_in);
    }
  }

    if( bind(sockfd, sock, socksize) >= 0) {
  do {
    if( bind(sockfd, sock, sizeof_sa) >= 0) {
    /* we succeeded to bind */
      struct Curl_sockaddr_storage add;
      socklen_t size = sizeof(add);
@@ -432,26 +431,23 @@ static CURLcode bindlocal(struct connectdata *conn,
        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 +456,6 @@ 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));
  if(h)
    Curl_resolv_unlock(data, h);

  return CURLE_INTERFACE_FAILED;
}