From 9081014c2c467077723d5ae1d0081003b3eb3504 Mon Sep 17 00:00:00 2001 From: Patrick Monnerat Date: Tue, 16 Dec 2014 13:29:01 +0100 Subject: IPV6: address scope != scope id There was a confusion between these: this commit tries to disambiguate them. - Scope can be computed from the address itself. - Scope id is scope dependent: it is currently defined as 1-based local interface index for link-local scoped addresses, and as a site index(?) for (obsolete) site-local addresses. Linux only supports it for link-local addresses. The URL parser properly parses a scope id as an interface index, but stores it in a field named "scope": confusion. The field has been renamed into "scope_id". Curl_if2ip() used the scope id as it was a scope. This caused failures to bind to an interface. Scope is now computed from the addresses and Curl_if2ip() matches them. If redundantly specified in the URL, scope id is check for mismatch with the interface index. This commit should fix SF bug #1451. --- lib/connect.c | 12 ++++++----- lib/ftp.c | 5 +++-- lib/if2ip.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- lib/if2ip.h | 13 ++++++++++-- lib/imap.c | 1 - lib/pop3.c | 1 - lib/smtp.c | 1 - lib/url.c | 10 ++++----- lib/urldata.h | 4 ++-- 9 files changed, 84 insertions(+), 29 deletions(-) diff --git a/lib/connect.c b/lib/connect.c index 203345b92..5d48bbf71 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -238,7 +238,7 @@ long Curl_timeleft(struct SessionHandle *data, } static CURLcode bindlocal(struct connectdata *conn, - curl_socket_t sockfd, int af) + curl_socket_t sockfd, int af, unsigned int scope) { struct SessionHandle *data = conn->data; @@ -286,7 +286,8 @@ static CURLcode bindlocal(struct connectdata *conn, /* interface */ if(!is_host) { - switch(Curl_if2ip(af, conn->scope, dev, myhost, sizeof(myhost))) { + 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 */ @@ -1043,7 +1044,8 @@ static CURLcode singleipconnect(struct connectdata *conn, /* possibly bind the local end to an IP, interface or port */ if(addr.family == AF_INET || addr.family == AF_INET6) { - result = bindlocal(conn, sockfd, addr.family); + result = bindlocal(conn, sockfd, addr.family, + Curl_ipv6_scope((struct sockaddr*)&addr.sa_addr)); if(result) { Curl_closesocket(conn, sockfd); /* close socket and bail out */ if(result == CURLE_UNSUPPORTED_PROTOCOL) { @@ -1319,9 +1321,9 @@ CURLcode Curl_socket(struct connectdata *conn, return CURLE_COULDNT_CONNECT; #if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) - if(conn->scope && (addr->family == AF_INET6)) { + if(conn->scope_id && (addr->family == AF_INET6)) { struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; - sa6->sin6_scope_id = conn->scope; + sa6->sin6_scope_id = conn->scope_id; } #endif diff --git a/lib/ftp.c b/lib/ftp.c index 34964d6b0..3eabec08c 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -1079,8 +1079,9 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, if(*addr != '\0') { /* attempt to get the address of the given interface name */ - switch(Curl_if2ip(conn->ip_addr->ai_family, conn->scope, addr, - hbuf, sizeof(hbuf))) { + switch(Curl_if2ip(conn->ip_addr->ai_family, + Curl_ipv6_scope(conn->ip_addr->ai_addr), + conn->scope_id, addr, hbuf, sizeof(hbuf))) { case IF2IP_NOT_FOUND: /* not an interface, use the given string as host name instead */ host = addr; diff --git a/lib/if2ip.c b/lib/if2ip.c index 05ae7d6f8..6fc09ec63 100644 --- a/lib/if2ip.c +++ b/lib/if2ip.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2014, 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 @@ -63,6 +63,38 @@ /* ------------------------------------------------------------------ */ +/* Return the scope of the given address. */ +unsigned int Curl_ipv6_scope(const struct sockaddr *sa) +{ +#ifndef ENABLE_IPV6 + (void) sa; +#else + if(sa->sa_family == AF_INET6) { + const struct sockaddr_in6 * sa6 = (const struct sockaddr_in6 *) sa; + const unsigned char * b = sa6->sin6_addr.s6_addr; + unsigned short w = (unsigned short) ((b[0] << 8) | b[1]); + + switch(w & 0xFFC0) { + case 0xFE80: + return IPV6_SCOPE_LINKLOCAL; + case 0xFEC0: + return IPV6_SCOPE_SITELOCAL; + case 0x0000: + w = b[1] | b[2] | b[3] | b[4] | b[5] | b[6] | b[7] | b[8] | b[9] | + b[10] | b[11] | b[12] | b[13] | b[14]; + if(w || b[15] != 0x01) + break; + return IPV6_SCOPE_NODELOCAL; + default: + break; + } + } +#endif + + return IPV6_SCOPE_GLOBAL; +} + + #if defined(HAVE_GETIFADDRS) bool Curl_if_is_interface_name(const char *interf) @@ -84,7 +116,8 @@ bool Curl_if_is_interface_name(const char *interf) } if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, - const char *interf, char *buf, int buf_size) + unsigned int remote_scope_id, const char *interf, + char *buf, int buf_size) { struct ifaddrs *iface, *head; if2ip_result_t res = IF2IP_NOT_FOUND; @@ -105,20 +138,29 @@ if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, #ifdef ENABLE_IPV6 if(af == AF_INET6) { unsigned int scopeid = 0; + unsigned int ifscope = Curl_ipv6_scope(iface->ifa_addr); + + if(ifscope != remote_scope) { + /* We are interested only in interface addresses whose + scope matches the remote address we want to + connect to: global for global, link-local for + link-local, etc... */ + if(res == IF2IP_NOT_FOUND) res = IF2IP_AF_NOT_SUPPORTED; + continue; + } + addr = &((struct sockaddr_in6 *)iface->ifa_addr)->sin6_addr; #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID /* Include the scope of this interface as part of the address */ scopeid = ((struct sockaddr_in6 *)iface->ifa_addr)->sin6_scope_id; -#endif - if(scopeid != remote_scope) { - /* We are interested only in interface addresses whose - scope ID matches the remote address we want to - connect to: global (0) for global, link-local for - link-local, etc... */ + + /* If given, scope id should match. */ + if(remote_scope_id && scopeid != remote_scope_id) { if(res == IF2IP_NOT_FOUND) res = IF2IP_AF_NOT_SUPPORTED; continue; } +#endif if(scopeid) snprintf(scope, sizeof(scope), "%%%u", scopeid); } @@ -154,7 +196,8 @@ bool Curl_if_is_interface_name(const char *interf) } if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, - const char *interf, char *buf, int buf_size) + unsigned int remote_scope_id, const char *interf, + char *buf, int buf_size) { struct ifreq req; struct in_addr in; @@ -163,6 +206,7 @@ if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, size_t len; (void)remote_scope; + (void)remote_scope_id; if(!interf || (af != AF_INET)) return IF2IP_NOT_FOUND; @@ -205,10 +249,12 @@ bool Curl_if_is_interface_name(const char *interf) } if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, - const char *interf, char *buf, int buf_size) + unsigned int remote_scope_id, const char *interf, + char *buf, int buf_size) { (void) af; (void) remote_scope; + (void) remote_scope_id; (void) interf; (void) buf; (void) buf_size; diff --git a/lib/if2ip.h b/lib/if2ip.h index ac5875237..78bb0bd59 100644 --- a/lib/if2ip.h +++ b/lib/if2ip.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2014, 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 @@ -23,6 +23,14 @@ ***************************************************************************/ #include "curl_setup.h" +/* IPv6 address scopes. */ +#define IPV6_SCOPE_GLOBAL 0 /* Global scope. */ +#define IPV6_SCOPE_LINKLOCAL 1 /* Link-local scope. */ +#define IPV6_SCOPE_SITELOCAL 2 /* Site-local scope (deprecated). */ +#define IPV6_SCOPE_NODELOCAL 3 /* Loopback. */ + +unsigned int Curl_ipv6_scope(const struct sockaddr *sa); + bool Curl_if_is_interface_name(const char *interf); typedef enum { @@ -32,7 +40,8 @@ typedef enum { } if2ip_result_t; if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, - const char *interf, char *buf, int buf_size); + unsigned int remote_scope_id, const char *interf, + char *buf, int buf_size); #ifdef __INTERIX diff --git a/lib/imap.c b/lib/imap.c index cd96cd804..86cfcf51e 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -61,7 +61,6 @@ #include #include "urldata.h" #include "sendf.h" -#include "if2ip.h" #include "hostip.h" #include "progress.h" #include "transfer.h" diff --git a/lib/pop3.c b/lib/pop3.c index 9c6cc8b36..66004599a 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -63,7 +63,6 @@ #include #include "urldata.h" #include "sendf.h" -#include "if2ip.h" #include "hostip.h" #include "progress.h" #include "transfer.h" diff --git a/lib/smtp.c b/lib/smtp.c index 4016741dc..7bd5158bf 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -62,7 +62,6 @@ #include #include "urldata.h" #include "sendf.h" -#include "if2ip.h" #include "hostip.h" #include "progress.h" #include "transfer.h" diff --git a/lib/url.c b/lib/url.c index 5b7870f32..cbc750904 100644 --- a/lib/url.c +++ b/lib/url.c @@ -2369,7 +2369,7 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, * know that an unsigned int will always hold the value so we blindly * typecast to this type */ - data->set.scope = curlx_sltoui(va_arg(param, long)); + data->set.scope_id = curlx_sltoui(va_arg(param, long)); break; case CURLOPT_PROTOCOLS: @@ -4094,7 +4094,7 @@ static CURLcode parseurlandfillconn(struct SessionHandle *data, /* The address scope was well formed. Knock it out of the hostname. */ memmove(percent, endp, strlen(endp)+1); - conn->scope = (unsigned int)scope; + conn->scope_id = (unsigned int)scope; } else { /* Zone identifier is not numeric */ @@ -4120,7 +4120,7 @@ static CURLcode parseurlandfillconn(struct SessionHandle *data, memmove(percent, percent + identifier_offset + strlen(ifname), identifier_offset + strlen(ifname)); - conn->scope = scopeidx; + conn->scope_id = scopeidx; } else #endif /* HAVE_NET_IF_H && IFNAMSIZ */ @@ -4129,9 +4129,9 @@ static CURLcode parseurlandfillconn(struct SessionHandle *data, } } - if(data->set.scope) + if(data->set.scope_id) /* Override any scope that was set above. */ - conn->scope = data->set.scope; + conn->scope_id = data->set.scope_id; /* Remove the fragment part of the path. Per RFC 2396, this is always the last part of the URI. We are looking for the first '#' so that we deal diff --git a/lib/urldata.h b/lib/urldata.h index a2dc1c362..79fcd9a4d 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -894,7 +894,7 @@ struct connectdata { the ip_addr itself. */ char ip_addr_str[MAX_IPADR_LEN]; - unsigned int scope; /* address scope for IPv6 */ + unsigned int scope_id; /* Scope id for IPv6 */ int socktype; /* SOCK_STREAM or SOCK_DGRAM */ @@ -1618,7 +1618,7 @@ struct UserDefined { bool proxy_transfer_mode; /* set transfer mode (;type=) when doing FTP via an HTTP proxy */ char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ - unsigned int scope; /* address scope for IPv6 */ + unsigned int scope_id; /* Scope id for IPv6 */ long allowed_protocols; long redir_protocols; #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) -- cgit v1.2.3