diff --git a/CHANGES b/CHANGES index 635740f3f..4074f10e7 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/RELEASE-NOTES b/RELEASE-NOTES index a65fb2bee..744569586 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -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) diff --git a/lib/connect.c b/lib/connect.c index 47e8681c5..3b2b65c71 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2008, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2009, Daniel Stenberg, , 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 #endif #ifdef HAVE_STDLIB_H -#include /* required for free() prototype, without it, this crashes */ -#endif /* on macos 68K */ +#include +#endif #if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE)) #include @@ -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 && (strlen(dev)<255) ) { - char myhost[256] = ""; - int rc; - bool was_iface = FALSE; + 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) ) { + + /* 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,93 +365,65 @@ static CURLcode bindlocal(struct connectdata *conn, if(h) { /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ - Curl_printable_address(h->addr, myhost, sizeof myhost); + 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); + done = 1; + } + else { + /* + * provided dev was no interface (or interfaces are not supported + * e.g. solaris) no ip address and no domain we fail here + */ + done = -1; } } - if(!*myhost || !h) { - /* need to fix this - h=Curl_gethost(data, - getmyhost(*myhost,sizeof(myhost)), - hostent_buf, - sizeof(hostent_buf)); - */ + if(done > 0) { +#ifdef ENABLE_IPV6 + /* 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); + } + } + + if(done < 1) { failf(data, "Couldn't bind to '%s'", dev); - if(h) - 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 */ - } - } -#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); - - } + else { + /* no device was given, prepare sa to match af's needs */ #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); + if ( af == AF_INET6 ) { + si6->sin6_family = AF_INET6; + si6->sin6_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in6); } + else #endif + if ( af == AF_INET ) { + 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); -#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 +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; }