unix: set ipv6 scope id for link-local addresses (#4107)
Link-local addresses (prefix fe80::/64) don't route unless you specify the network interface to use so make libuv do that. Fixes: https://github.com/nodejs/node/issues/48846
This commit is contained in:
parent
124d55c970
commit
e893cd6826
@ -27,6 +27,9 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <ifaddrs.h>
|
||||
|
||||
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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user