diff --git a/src/unix/tcp.c b/src/unix/tcp.c index d6c848f4..c225a57c 100644 --- a/src/unix/tcp.c +++ b/src/unix/tcp.c @@ -27,6 +27,9 @@ #include #include +#include +#include +#include static int maybe_bind_socket(int fd) { union uv__sockaddr s; @@ -198,11 +201,50 @@ int uv__tcp_bind(uv_tcp_t* tcp, } +static int uv__is_ipv6_link_local(const struct sockaddr* addr) { + const struct sockaddr_in6* a6; + uint8_t b[2]; + + if (addr->sa_family != AF_INET6) + return 0; + + a6 = (const struct sockaddr_in6*) addr; + memcpy(b, &a6->sin6_addr, sizeof(b)); + + return b[0] == 0xFE && b[1] == 0x80; +} + + +static int uv__ipv6_link_local_scope_id(void) { + struct sockaddr_in6* a6; + struct ifaddrs* ifa; + struct ifaddrs* p; + int rv; + + if (getifaddrs(&ifa)) + return 0; + + for (p = ifa; p != NULL; p = p->ifa_next) + if (uv__is_ipv6_link_local(p->ifa_addr)) + break; + + rv = 0; + if (p != NULL) { + a6 = (struct sockaddr_in6*) p->ifa_addr; + rv = a6->sin6_scope_id; + } + + freeifaddrs(ifa); + return rv; +} + + int uv__tcp_connect(uv_connect_t* req, uv_tcp_t* handle, const struct sockaddr* addr, unsigned int addrlen, uv_connect_cb cb) { + struct sockaddr_in6 tmp6; int err; int r; @@ -220,6 +262,14 @@ int uv__tcp_connect(uv_connect_t* req, if (err) return err; + if (uv__is_ipv6_link_local(addr)) { + memcpy(&tmp6, addr, sizeof(tmp6)); + if (tmp6.sin6_scope_id == 0) { + tmp6.sin6_scope_id = uv__ipv6_link_local_scope_id(); + addr = (void*) &tmp6; + } + } + do { errno = 0; r = connect(uv__stream_fd(handle), addr, addrlen); diff --git a/test/test-list.h b/test/test-list.h index ca59890b..6e6e0824 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -128,6 +128,7 @@ TEST_DECLARE (tcp_bind_or_listen_error_after_close) TEST_DECLARE (tcp_listen_without_bind) TEST_DECLARE (tcp_connect_error_fault) TEST_DECLARE (tcp_connect6_error_fault) +TEST_DECLARE (tcp_connect6_link_local) TEST_DECLARE (tcp_connect_timeout) TEST_DECLARE (tcp_local_connect_timeout) TEST_DECLARE (tcp6_local_connect_timeout) @@ -727,6 +728,7 @@ TASK_LIST_START TEST_ENTRY (tcp_listen_without_bind) TEST_ENTRY (tcp_connect_error_fault) TEST_ENTRY (tcp_connect6_error_fault) + TEST_ENTRY (tcp_connect6_link_local) TEST_ENTRY (tcp_connect_timeout) TEST_ENTRY (tcp_local_connect_timeout) TEST_ENTRY (tcp6_local_connect_timeout) diff --git a/test/test-tcp-connect6-error.c b/test/test-tcp-connect6-error.c index 8646dd56..075670ce 100644 --- a/test/test-tcp-connect6-error.c +++ b/test/test-tcp-connect6-error.c @@ -69,3 +69,38 @@ TEST_IMPL(tcp_connect6_error_fault) { MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } + + +TEST_IMPL(tcp_connect6_link_local) { + struct sockaddr_in6 addr; + uv_connect_t req; + uv_tcp_t server; + +#if defined(__QEMU__) + /* qemu's sockaddr_in6 translation is broken pre-qemu 8.0.0 + * when host endianness != guest endiannes. + * Fixed in https://github.com/qemu/qemu/commit/44cf6731d6b. + */ + RETURN_SKIP("Test does not currently work in QEMU"); +#endif /* defined(__QEMU__) */ + + ASSERT_OK(uv_ip6_addr("fe80::0bad:babe", 1337, &addr)); + ASSERT_OK(uv_tcp_init(uv_default_loop(), &server)); + + /* We're making two shaky assumptions here: + * 1. There is a network interface that routes IPv6 link-local traffic, and + * 2. There is no firewall rule that blackholes or otherwise hard-kills the + * connection attempt to the address above, i.e., we don't expect the + * connect() system call to fail synchronously. + */ + ASSERT_OK(uv_tcp_connect(&req, + &server, + (struct sockaddr*) &addr, + connect_cb)); + + uv_close((uv_handle_t*) &server, NULL); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +}