-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
Description
On an interface configured with both an IPv6 global unicast address (GUA - 2000::/3) and a unique local address (ULA - fc00::/7), curl will bind to the ULA when trying to reach a global address if the --interface option is specified.
This occurs because the interface option bypasses the normal source address selection rules, and the Curl_ipv6_scope function (lib/if2ip.c) does not recognise ULAs, which are similar in function to the now-deprecated site-local addresses.
I did this
$ curl -v -6 --interface eno1 -o - -D - http://www.archlinux.org/check_network_status.txt
* Trying 2a01:4f8:172:1d86::1...
* TCP_NODELAY set
* Local Interface eno1 is ip fdxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx using address family 10
* SO_BINDTODEVICE eno1 failed with errno 1: Operation not permitted; will do regular bind
* Local port: 0
* connect to 2a01:4f8:172:1d86::1 port 80 failed: Connection timed out
* Failed to connect to www.archlinux.org port 80: Connection timed out
* Closing connection 0
curl: (7) Failed to connect to www.archlinux.org port 80: Connection timed out
I expected the following
* Trying 2a01:4f8:172:1d86::1...
* TCP_NODELAY set
* Local Interface eno1 is ip 2a00:23c4:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx using address family 10
* SO_BINDTODEVICE eno1 failed with errno 1: Operation not permitted; will do regular bind
* Local port: 0
* Connected to www.archlinux.org (2a01:4f8:172:1d86::1) port 80 (#0)
> GET /check_network_status.txt HTTP/1.1
...
curl/libcurl version
This was git master (7e949de):
curl 7.55.0-DEV (x86_64-pc-linux-gnu) libcurl/7.55.0-DEV OpenSSL/1.1.0f zlib/1.2.11 libpsl/0.17.0 (+libicu/59.1) libssh2/1.8.0 nghttp2/1.23.1 librtmp/2.3
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS Debug TrackMemory IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL
operating system
$ uname -a
Linux bodger 4.12.4-1-ARCH #1 SMP PREEMPT Fri Jul 28 18:54:18 UTC 2017 x86_64 GNU/Linux
Possible fixes
I patched Curl_ipv6_scope locally to treat the ULA prefix in the same way as site-local addresses.
diff --git a/lib/if2ip.c b/lib/if2ip.c
index 3e74cc687..5bd81aca8 100644
--- a/lib/if2ip.c
+++ b/lib/if2ip.c
@@ -85,6 +85,9 @@ unsigned int Curl_ipv6_scope(const struct sockaddr *sa)
default:
break;
}
+
+ if((b[0] & 0xFE) == 0xFC) /* Handle ULAs */
+ return IPV6_SCOPE_SITELOCAL;
}
#endifThis resolves the issue, but perhaps I should have defined another IPV6_SCOPE_ constant instead?
Another solution in my case would be to skip the bind call when setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, ...) succeeds (see 'Context' below). Perhaps both changes would be a good idea?
Naturally, this approach will never be perfect without emulating the logic in RFC6724, and it has potential implications when temporary/privacy addresses are in use, or there are multiple global unicast prefixes, and so forth. For the most part this isn't a problem because the --interface option shouldn't be used (or could be used with an address), but...
Context
On my network, I have a 'smart' thermostat from a subsidiary of a certain internet giant, which insists on handing out a ULA prefix even while it accepts prefixes advertised by my router. This confuses the NetworkManager connectivity check, which uses libcurl to perform its connectivity test, with curl_easy_setopt(ehandle, CURLOPT_INTERFACE, "if!eno1"); to control the outgoing interface (see https://github.com/NetworkManager/NetworkManager/blob/master/src/nm-connectivity.c). As the ULA address is enumerated before the GUA address, the connectivity test always fails due to this issue.
Interestingly, although NetworkManager has CAP_NET_RAW allowing it to use SO_BINDTODEVICE in connect.c's bindlocal function, the latter goes on to perform a bind against the incorrect address anyway. If it did not do this, the IPv6 stack's own source address selection mechanism would work.
It's possible to debate whether the problem is in libcurl's behaviour or NetworkManager's use of it. I'm raising the issue here initially because the "interface" option as implemented doesn't seem to match expectations: doing just SO_BINDTODEVICE would be better, when possible. The alternative would require NetworkManager to supply a preferred address without the benefit of the resolved target address. Having said that, it seems to already have an algorithm for choosing an address to display in the little control panel thing - maybe that's a better guess than curl makes :)