diff --git a/CHANGES b/CHANGES index 9c338d6e3..fa03ea073 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,12 @@ Changelog +Daniel Fandrich (9 Oct 2008) +- Fixed the --interface option to work with IPv6 connections on glibc + systems supporting getifaddrs(). Also fixed a problem where an IPv6 + address could be chosen instead of an IPv4 one for --interface when it + involved a name lookup. + Daniel Fandrich (8 Oct 2008) - Added tests 1082 through 1085 to test symbolic --interface parameters diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 4d8e21ec7..918be61b6 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -15,6 +15,7 @@ This release includes the following changes: o Better detect HTTP 1.0 servers and don't do HTTP 1.1 requests on them o configure --disable-proxy disables proxy o Added CURLOPT_USERNAME and CURLOPT_PASSWORD + o --interface now works with IPv6 connections on glibc systems This release includes the following bugfixes: diff --git a/configure.ac b/configure.ac index bf86bf038..25572cd7a 100644 --- a/configure.ac +++ b/configure.ac @@ -2061,6 +2061,7 @@ AC_CHECK_FUNCS([basename \ fork \ geteuid \ gethostbyaddr \ + getifaddrs \ getpass_r \ getppid \ getprotobyname \ diff --git a/lib/connect.c b/lib/connect.c index 3773b8280..07d5f3887 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -284,15 +284,16 @@ int waitconnect(curl_socket_t sockfd, /* socket */ } static CURLcode bindlocal(struct connectdata *conn, - curl_socket_t sockfd) + curl_socket_t sockfd, int af) { -#ifdef ENABLE_IPV6 - char ipv6_addr[16]; -#endif struct SessionHandle *data = conn->data; struct sockaddr_in me; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 me6; +#endif struct sockaddr *sock = NULL; /* bind to this address */ - socklen_t socksize; /* size of the data sock points to */ + 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" */ /* how many port numbers to try to bind to, increasing one at a time */ @@ -303,20 +304,13 @@ static CURLcode bindlocal(struct connectdata *conn, * Select device to bind socket to *************************************************************/ if(dev && (strlen(dev)<255) ) { - struct Curl_dns_entry *h=NULL; char myhost[256] = ""; - in_addr_t in; int rc; bool was_iface = FALSE; - int in6 = -1; - /* First check if the given name is an IP address */ - in=inet_addr((char *) dev); - - if((in == CURL_INADDR_NONE) && - Curl_if2ip(dev, myhost, sizeof(myhost))) { + if(Curl_if2ip(af, dev, myhost, sizeof(myhost))) { /* - * We now have the numerical IPv4-style x.y.z.w in the 'myhost' buffer + * We now have the numerical IP address in the 'myhost' buffer */ rc = Curl_resolv(conn, myhost, 0, &h); if(rc == CURLRESOLV_PENDING) @@ -324,7 +318,6 @@ static CURLcode bindlocal(struct connectdata *conn, if(h) { was_iface = TRUE; - Curl_resolv_unlock(data, h); } } @@ -333,22 +326,30 @@ static CURLcode bindlocal(struct connectdata *conn, * 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; + if (af == AF_INET) + data->set.ip_version = CURL_IPRESOLVE_V4; + else if (af == AF_INET6) + data->set.ip_version = CURL_IPRESOLVE_V6; + rc = Curl_resolv(conn, dev, 0, &h); if(rc == CURLRESOLV_PENDING) (void)Curl_wait_for_resolv(conn, &h); + data->set.ip_version = ipver; if(h) { - if(in == CURL_INADDR_NONE) - /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ - Curl_printable_address(h->addr, myhost, sizeof myhost); - else - /* we know data->set.device is shorter than the myhost array */ - strcpy(myhost, dev); - Curl_resolv_unlock(data, h); + /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ + Curl_printable_address(h->addr, myhost, sizeof myhost); } } - if(! *myhost) { + if(!*myhost || !h) { /* need to fix this h=Curl_gethost(data, getmyhost(*myhost,sizeof(myhost)), @@ -356,11 +357,16 @@ static CURLcode bindlocal(struct connectdata *conn, sizeof(hostent_buf)); */ 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 @@ -378,42 +384,37 @@ static CURLcode bindlocal(struct connectdata *conn, dev, strlen(dev)+1) != 0) { /* printf("Failed to BINDTODEVICE, socket: %d device: %s error: %s\n", sockfd, dev, Curl_strerror(SOCKERRNO)); */ - infof(data, "SO_BINDTODEVICE %s failed\n", dev); + int error = ERRNO; + 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 - - in=inet_addr(myhost); - -#ifdef ENABLE_IPV6 - in6 = Curl_inet_pton (AF_INET6, myhost, (void *)&ipv6_addr); -#endif - if(CURL_INADDR_NONE == in && -1 == in6) { - failf(data,"couldn't find my own IP address (%s)", myhost); - return CURLE_INTERFACE_FAILED; - } /* end of inet_addr */ - - if( h ) { - Curl_addrinfo *addr = h->addr; - sock = addr->ai_addr; - socksize = addr->ai_addrlen; - } - else - return CURLE_INTERFACE_FAILED; - } else if(port) { /* if a local port number is requested but no local IP, extract the address from the socket */ - memset(&me, 0, sizeof(struct sockaddr)); - me.sin_family = AF_INET; - me.sin_addr.s_addr = INADDR_ANY; + if(af == AF_INET) { + memset(&me, 0, sizeof(struct sockaddr)); + me.sin_family = AF_INET; + me.sin_addr.s_addr = INADDR_ANY; - sock = (struct sockaddr *)&me; - socksize = sizeof(struct sockaddr); + sock = (struct sockaddr *)&me; + socksize = sizeof(struct sockaddr); + } +#ifdef ENABLE_IPV6 + else { /* AF_INET6 */ + memset(&me6, 0, sizeof(struct sockaddr)); + me6.sin6_family = AF_INET6; + me6.sin6_addr = in6addr_any; + + sock = (struct sockaddr *)&me6; + socksize = sizeof(struct sockaddr); + } +#endif } else /* no local kind of binding was requested */ @@ -432,11 +433,11 @@ static CURLcode bindlocal(struct connectdata *conn, if( bind(sockfd, sock, socksize) >= 0) { /* we succeeded to bind */ struct Curl_sockaddr_storage add; - socklen_t size; - - size = sizeof(add); + socklen_t size = sizeof(add); if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) { failf(data, "getsockname() failed"); + if(h) + Curl_resolv_unlock(data, h); return CURLE_INTERFACE_FAILED; } /* We re-use/clobber the port variable here below */ @@ -448,6 +449,8 @@ static CURLcode bindlocal(struct connectdata *conn, #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) { @@ -461,8 +464,10 @@ static CURLcode bindlocal(struct connectdata *conn, data->state.os_errno = SOCKERRNO; failf(data, "bind failure: %s", Curl_strerror(conn, data->state.os_errno)); - return CURLE_INTERFACE_FAILED; + if(h) + Curl_resolv_unlock(data, h); + return CURLE_INTERFACE_FAILED; } /* @@ -822,7 +827,7 @@ singleipconnect(struct connectdata *conn, } /* possibly bind the local end to an IP, interface or port */ - res = bindlocal(conn, sockfd); + res = bindlocal(conn, sockfd, addr->family); if(res) { sclose(sockfd); /* close socket and bail out */ return CURL_SOCKET_BAD; diff --git a/lib/ftp.c b/lib/ftp.c index 8409fdc15..3d116b543 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -892,7 +892,8 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, if(data->set.str[STRING_FTPPORT] && (strlen(data->set.str[STRING_FTPPORT]) > 1)) { /* attempt to get the address of the given interface name */ - if(!Curl_if2ip(data->set.str[STRING_FTPPORT], hbuf, sizeof(hbuf))) + if(!Curl_if2ip(conn->ip_addr->ai_family, data->set.str[STRING_FTPPORT], + hbuf, sizeof(hbuf))) /* not an interface, use the given string as host name instead */ host = data->set.str[STRING_FTPPORT]; else @@ -964,8 +965,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, /* It failed. Bind the address used for the control connection instead */ sslen = sizeof(ss); - if(getsockname(conn->sock[FIRSTSOCKET], - (struct sockaddr *)sa, &sslen)) { + if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { failf(data, "getsockname() failed: %s", Curl_strerror(conn, SOCKERRNO) ); sclose(portsock); @@ -973,7 +973,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, } /* set port number to zero to make bind() pick "any" */ - if(((struct sockaddr *)sa)->sa_family == AF_INET) + if(sa->sa_family == AF_INET) ((struct sockaddr_in *)sa)->sin_port=0; else ((struct sockaddr_in6 *)sa)->sin6_port =0; @@ -981,7 +981,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, if(sslen > (socklen_t)sizeof(ss)) sslen = sizeof(ss); - if(bind(portsock, (struct sockaddr *)sa, sslen)) { + if(bind(portsock, sa, sslen)) { failf(data, "bind failed: %s", Curl_strerror(conn, SOCKERRNO)); sclose(portsock); return CURLE_FTP_PORT_FAILED; @@ -1112,7 +1112,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, /* this is an IPv4 address */ addr = Curl_ip2addr(in, ftpportstr, 0); else { - if(Curl_if2ip(ftpportstr, myhost, sizeof(myhost))) { + if(Curl_if2ip(AF_INET, ftpportstr, myhost, sizeof(myhost))) { /* The interface to IP conversion provided a dotted address */ in=inet_addr(myhost); addr = Curl_ip2addr(in, myhost, 0); diff --git a/lib/if2ip.c b/lib/if2ip.c index 94a09c11e..a54050040 100644 --- a/lib/if2ip.c +++ b/lib/if2ip.c @@ -42,6 +42,60 @@ !defined(__AMIGA__) && !defined(__minix) && !defined(__SYMBIAN32__) && \ !defined(__WATCOMC__) +#if defined(HAVE_GETIFADDRS) + +/* + * glibc provides getifaddrs() to provide a list of all interfaces and their + * addresses. + */ + +#include + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#include "inet_ntop.h" +#include "strequal.h" + +char *Curl_if2ip(int af, const char *interface, char *buf, int buf_size) +{ + struct ifaddrs *iface, *head; + char *ip=NULL; + + if (getifaddrs(&head) >= 0) { + for (iface=head; iface != NULL; iface=iface->ifa_next) { + if ((iface->ifa_addr->sa_family == af) && + curl_strequal(iface->ifa_name, interface)) { + void *addr; + char scope[12]=""; + if (af == AF_INET6) { + unsigned int scopeid; + addr = &((struct sockaddr_in6 *)iface->ifa_addr)->sin6_addr; + /* Include the scope of this interface as part of the address */ + scopeid = ((struct sockaddr_in6 *)iface->ifa_addr)->sin6_scope_id; + if (scopeid) + snprintf(scope, sizeof(scope), "%%%u", scopeid); + } else + addr = &((struct sockaddr_in *)iface->ifa_addr)->sin_addr; + ip = (char *) Curl_inet_ntop(af, addr, buf, buf_size); + Curl_strlcat(buf, scope, buf_size); + break; + } + } + freeifaddrs(head); + } + return ip; +} + +#else + #ifdef HAVE_SYS_SOCKET_H #include #endif @@ -83,12 +137,12 @@ #define SYS_ERROR -1 -char *Curl_if2ip(const char *interface, char *buf, int buf_size) +char *Curl_if2ip(int af, const char *interface, char *buf, int buf_size) { int dummy; char *ip=NULL; - if(!interface) + if(!interface || (af != AF_INET)) return NULL; dummy = socket(AF_INET, SOCK_STREAM, 0); @@ -124,11 +178,13 @@ char *Curl_if2ip(const char *interface, char *buf, int buf_size) } return ip; } +#endif /* -- end of if2ip() -- */ #else -char *Curl_if2ip(const char *interf, char *buf, int buf_size) +char *Curl_if2ip(int af, const char *interf, char *buf, int buf_size) { + (void) af; (void) interf; (void) buf; (void) buf_size; diff --git a/lib/if2ip.h b/lib/if2ip.h index 4e86e2b27..6888a4cb8 100644 --- a/lib/if2ip.h +++ b/lib/if2ip.h @@ -24,7 +24,7 @@ ***************************************************************************/ #include "setup.h" -extern char *Curl_if2ip(const char *interf, char *buf, int buf_size); +extern char *Curl_if2ip(int af, const char *interf, char *buf, int buf_size); #ifdef __INTERIX #include