diff --git a/Makefile.am b/Makefile.am index 97552e3c4..1355ece49 100644 --- a/Makefile.am +++ b/Makefile.am @@ -85,6 +85,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-getsockname.c \ test/test-hrtime.c \ test/test-idle.c \ + test/test-ip6-addr.c \ test/test-ipc-send-recv.c \ test/test-ipc.c \ test/test-list.h \ diff --git a/checksparse.sh b/checksparse.sh index 0da9a15a1..765bc6ad2 100755 --- a/checksparse.sh +++ b/checksparse.sh @@ -106,6 +106,7 @@ test/test-getaddrinfo.c test/test-getsockname.c test/test-hrtime.c test/test-idle.c +test/test-ip6-addr.c test/test-ipc-send-recv.c test/test-ipc.c test/test-loop-handles.c diff --git a/include/uv-darwin.h b/include/uv-darwin.h index 110115cd0..43b261f5f 100644 --- a/include/uv-darwin.h +++ b/include/uv-darwin.h @@ -58,4 +58,6 @@ #define UV_HAVE_KQUEUE 1 +#define UV_PLATFORM_HAS_IP6_LINK_LOCAL_ADDRESS + #endif /* UV_DARWIN_H */ diff --git a/include/uv-linux.h b/include/uv-linux.h index 9b38405a1..62ebfe2a0 100644 --- a/include/uv-linux.h +++ b/include/uv-linux.h @@ -31,4 +31,6 @@ void* watchers[2]; \ int wd; \ +#define UV_PLATFORM_HAS_IP6_LINK_LOCAL_ADDRESS + #endif /* UV_LINUX_H */ diff --git a/include/uv-win.h b/include/uv-win.h index 1c27452e1..f36e89351 100644 --- a/include/uv-win.h +++ b/include/uv-win.h @@ -582,3 +582,5 @@ int uv_utf16_to_utf8(const WCHAR* utf16Buffer, size_t utf16Size, char* utf8Buffer, size_t utf8Size); int uv_utf8_to_utf16(const char* utf8Buffer, WCHAR* utf16Buffer, size_t utf16Size); + +#define UV_PLATFORM_HAS_IP6_LINK_LOCAL_ADDRESS diff --git a/src/uv-common.c b/src/uv-common.c index 64a25991a..9b1cefbfd 100644 --- a/src/uv-common.c +++ b/src/uv-common.c @@ -28,6 +28,9 @@ #include /* malloc */ #include /* memset */ +#if defined(UV_PLATFORM_HAS_IP6_LINK_LOCAL_ADDRESS) && !defined(_WIN32) +# include /* if_nametoindex */ +#endif #define XX(uc, lc) case UV_##uc: return sizeof(uv_##lc##_t); @@ -173,11 +176,36 @@ struct sockaddr_in uv_ip4_addr(const char* ip, int port) { struct sockaddr_in6 uv_ip6_addr(const char* ip, int port) { struct sockaddr_in6 addr; + char address_part[40]; + size_t address_part_size; + const char* zone_index; memset(&addr, 0, sizeof(struct sockaddr_in6)); addr.sin6_family = AF_INET6; addr.sin6_port = htons(port); + +#if defined(UV_PLATFORM_HAS_IP6_LINK_LOCAL_ADDRESS) + zone_index = strchr(ip, '%'); + if (zone_index != NULL) { + address_part_size = sizeof(address_part); + assert((unsigned)(zone_index - ip) < address_part_size); + strncpy(address_part, ip, zone_index - ip); + address_part[address_part_size - 1] = '\0'; + + ip = address_part; + + zone_index++; /* skip '%' */ + /* NOTE: unknown interface (id=0) is silently ignored */ +#ifdef _WIN32 + addr.sin6_scope_id = atoi(zone_index); +#else + addr.sin6_scope_id = if_nametoindex(zone_index); +#endif + } +#endif + + /* result code is ignored - we assume ip is a valid IPv6 address */ uv_inet_pton(AF_INET6, ip, &addr.sin6_addr); return addr; diff --git a/test/test-ip6-addr.c b/test/test-ip6-addr.c new file mode 100644 index 000000000..24dd6355d --- /dev/null +++ b/test/test-ip6-addr.c @@ -0,0 +1,108 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + +#include +#include + +#ifdef __linux__ +# include +# include +#endif + +typedef void (*iface_info_cb)(const char* ip6_addr, const char* device_name, + unsigned iface_index); + +void call_iface_info_cb(iface_info_cb iface_cb, + char const* iface_name, + struct sockaddr_in6 const* address) { + char string_address[INET6_ADDRSTRLEN]; + uv_err_t result; + + result = uv_inet_ntop(AF_INET6, + &address->sin6_addr, + string_address, + INET6_ADDRSTRLEN); + ASSERT(result.code == UV_OK); + + iface_cb(string_address, iface_name, address->sin6_scope_id); +} + + +void foreach_ip6_interface(iface_info_cb iface_cb) { + uv_err_t result; + int count, ix; + uv_interface_address_t* addresses; + + result = uv_interface_addresses(&addresses, &count); + ASSERT(result.code == UV_OK); + + for (ix = 0; ix < count; ix++) { + if (addresses[ix].address.address4.sin_family != AF_INET6) + continue; + + call_iface_info_cb(iface_cb, + addresses[ix].name, + &addresses[ix].address.address6); + } + + uv_free_interface_addresses(addresses, count); +} + + +void test_ip6_addr_scope(const char* ip6_addr, + const char* device_name, + unsigned iface_index) { + /* 40 bytes address, 16 bytes device name, plus reserve */ + char scoped_addr[128]; + struct sockaddr_in6 addr; + + /* skip addresses that are not link-local */ + if (strncmp(ip6_addr, "fe80::", 6) != 0) return; + +#ifdef _WIN32 + sprintf(scoped_addr, "%s%%%d", ip6_addr, iface_index); +#else + sprintf(scoped_addr, "%s%%%s", ip6_addr, device_name); +#endif + + LOGF("Testing link-local address %s (iface_index: 0x%02x, device_name: %s)\n", + scoped_addr, + iface_index, + device_name); + + addr = uv_ip6_addr(scoped_addr, TEST_PORT); + + LOGF("Got scope_id 0x%02x\n", addr.sin6_scope_id); + ASSERT(iface_index == addr.sin6_scope_id); +} + + +TEST_IMPL(ip6_addr_link_local) { +#ifdef UV_PLATFORM_HAS_IP6_LINK_LOCAL_ADDRESS + foreach_ip6_interface(&test_ip6_addr_scope); + return 0; +#else + RETURN_SKIP("Qualified link-local addresses are not supported."); +#endif +} diff --git a/test/test-list.h b/test/test-list.h index 514ab676e..d3371a809 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -211,6 +211,7 @@ TEST_DECLARE (dlerror) TEST_DECLARE (poll_duplex) TEST_DECLARE (poll_unidirectional) TEST_DECLARE (poll_close) +TEST_DECLARE (ip6_addr_link_local) #ifdef _WIN32 TEST_DECLARE (spawn_detect_pipe_name_collisions_on_windows) TEST_DECLARE (argument_escaping) @@ -499,6 +500,7 @@ TASK_LIST_START TEST_ENTRY (strlcpy) TEST_ENTRY (strlcat) TEST_ENTRY (dlerror) + TEST_ENTRY (ip6_addr_link_local) #if 0 /* These are for testing the test runner. */ TEST_ENTRY (fail_always) diff --git a/uv.gyp b/uv.gyp index 12b5c3537..63a00661d 100644 --- a/uv.gyp +++ b/uv.gyp @@ -378,6 +378,7 @@ 'test/test-udp-multicast-join.c', 'test/test-dlerror.c', 'test/test-udp-multicast-ttl.c', + 'test/test-ip6-addr.c', ], 'conditions': [ [ 'OS=="win"', {