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:
Ben Noordhuis 2023-08-03 22:18:50 +02:00 committed by GitHub
parent 124d55c970
commit e893cd6826
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 0 deletions

View File

@ -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);

View File

@ -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)

View File

@ -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;
}