diff --git a/docs/src/misc.rst b/docs/src/misc.rst index 299cf034370..2968d1cea1c 100644 --- a/docs/src/misc.rst +++ b/docs/src/misc.rst @@ -283,6 +283,60 @@ API and :man:`inet_pton(3)`. On success they return 0. In case of error the target `dst` pointer is unmodified. +.. c:macro:: UV_IF_NAMESIZE + + Maximum IPv6 interface identifier name length. Defined as + `IFNAMSIZ` on Unix and `IF_NAMESIZE` on Linux and Windows. + + .. versionadded:: 1.16.0 + +.. c:function:: int uv_if_indextoname(unsigned int ifindex, char* buffer, size_t* size) + + IPv6-capable implementation of :man:`if_indextoname(3)`. When called, + `*size` indicates the length of the `buffer`, which is used to store the + result. + On success, zero is returned, `buffer` contains the interface name, and + `*size` represents the string length of the `buffer`, excluding the NUL + terminator byte from `*size`. On error, a negative result is + returned. If `buffer` is not large enough to hold the result, + `UV_ENOBUFS` is returned, and `*size` represents the necessary size in + bytes, including the NUL terminator byte into the `*size`. + + On Unix, the returned interface name can be used directly as an + interface identifier in scoped IPv6 addresses, e.g. + `fe80::abc:def1:2345%en0`. + + On Windows, the returned interface cannot be used as an interface + identifier, as Windows uses numerical interface identifiers, e.g. + `fe80::abc:def1:2345%5`. + + To get an interface identifier in a cross-platform compatible way, + use `uv_if_indextoiid()`. + + Example: + + :: + + char ifname[UV_IF_NAMESIZE]; + size_t size = sizeof(ifname); + uv_if_indextoname(sin6->sin6_scope_id, ifname, &size); + + .. versionadded:: 1.16.0 + +.. c:function:: int uv_if_indextoiid(unsigned int ifindex, char* buffer, size_t* size) + + Retrieves a network interface identifier suitable for use in an IPv6 scoped + address. On Windows, returns the numeric `ifindex` as a string. On all other + platforms, `uv_if_indextoname()` is called. The result is written to + `buffer`, with `*size` indicating the length of `buffer`. If `buffer` is not + large enough to hold the result, then `UV_ENOBUFS` is returned, and `*size` + represents the size, including the NUL byte, required to hold the + result. + + See `uv_if_indextoname` for further details. + + .. versionadded:: 1.16.0 + .. c:function:: int uv_exepath(char* buffer, size_t* size) Gets the executable path. diff --git a/include/uv-unix.h b/include/uv-unix.h index 6565ff441ef..99aab2b8a01 100644 --- a/include/uv-unix.h +++ b/include/uv-unix.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include diff --git a/include/uv.h b/include/uv.h index b8adbc9a256..3f61812081d 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1408,6 +1408,21 @@ UV_EXTERN int uv_ip6_name(const struct sockaddr_in6* src, char* dst, size_t size UV_EXTERN int uv_inet_ntop(int af, const void* src, char* dst, size_t size); UV_EXTERN int uv_inet_pton(int af, const char* src, void* dst); +#if defined(IF_NAMESIZE) +# define UV_IF_NAMESIZE (IF_NAMESIZE + 1) +#elif defined(IFNAMSIZ) +# define UV_IF_NAMESIZE (IFNAMSIZ + 1) +#else +# define UV_IF_NAMESIZE (16 + 1) +#endif + +UV_EXTERN int uv_if_indextoname(unsigned int ifindex, + char* buffer, + size_t* size); +UV_EXTERN int uv_if_indextoiid(unsigned int ifindex, + char* buffer, + size_t* size); + UV_EXTERN int uv_exepath(char* buffer, size_t* size); UV_EXTERN int uv_cwd(char* buffer, size_t* size); diff --git a/src/unix/getaddrinfo.c b/src/unix/getaddrinfo.c index 2049aea2f38..99a6ef33be3 100644 --- a/src/unix/getaddrinfo.c +++ b/src/unix/getaddrinfo.c @@ -200,3 +200,32 @@ void uv_freeaddrinfo(struct addrinfo* ai) { if (ai) freeaddrinfo(ai); } + + +int uv_if_indextoname(unsigned int ifindex, char* buffer, size_t* size) { + char ifname_buf[UV_IF_NAMESIZE]; + size_t len; + + if (buffer == NULL || size == NULL || *size == 0) + return UV_EINVAL; + + if (if_indextoname(ifindex, ifname_buf) == NULL) + return -errno; + + len = strnlen(ifname_buf, sizeof(ifname_buf)); + + if (*size <= len) { + *size = len + 1; + return UV_ENOBUFS; + } + + memcpy(buffer, ifname_buf, len); + buffer[len] = '\0'; + *size = len; + + return 0; +} + +int uv_if_indextoiid(unsigned int ifindex, char* buffer, size_t* size) { + return uv_if_indextoname(ifindex, buffer, size); +} diff --git a/src/win/getaddrinfo.c b/src/win/getaddrinfo.c index baab838898a..ab1599d46db 100644 --- a/src/win/getaddrinfo.c +++ b/src/win/getaddrinfo.c @@ -28,6 +28,8 @@ /* EAI_* constants. */ #include +/* Needed for ConvertInterfaceIndexToLuid and ConvertInterfaceLuidToNameA */ +#include int uv__getaddrinfo_translate_error(int sys_err) { switch (sys_err) { @@ -380,3 +382,69 @@ int uv_getaddrinfo(uv_loop_t* loop, } return uv_translate_sys_error(err); } + +int uv_if_indextoname(unsigned int ifindex, char* buffer, size_t* size) { + NET_LUID luid; + wchar_t wname[NDIS_IF_MAX_STRING_SIZE + 1]; /* Add one for the NUL. */ + DWORD bufsize; + int r; + + if (buffer == NULL || size == NULL || *size == 0) + return UV_EINVAL; + + r = ConvertInterfaceIndexToLuid(ifindex, &luid); + + if (r != 0) + return uv_translate_sys_error(r); + + r = ConvertInterfaceLuidToNameW(&luid, wname, ARRAY_SIZE(wname)); + + if (r != 0) + return uv_translate_sys_error(r); + + /* Check how much space we need */ + bufsize = WideCharToMultiByte(CP_UTF8, 0, wname, -1, NULL, 0, NULL, NULL); + + if (bufsize == 0) { + return uv_translate_sys_error(GetLastError()); + } else if (bufsize > *size) { + *size = bufsize; + return UV_ENOBUFS; + } + + /* Convert to UTF-8 */ + bufsize = WideCharToMultiByte(CP_UTF8, + 0, + wname, + -1, + buffer, + *size, + NULL, + NULL); + + if (bufsize == 0) + return uv_translate_sys_error(GetLastError()); + + *size = bufsize - 1; + return 0; +} + +int uv_if_indextoiid(unsigned int ifindex, char* buffer, size_t* size) { + int r; + + if (buffer == NULL || size == NULL || *size == 0) + return UV_EINVAL; + + r = snprintf(buffer, *size, "%d", ifindex); + + if (r < 0) + return uv_translate_sys_error(r); + + if (r >= (int) *size) { + *size = r + 1; + return UV_ENOBUFS; + } + + *size = r; + return 0; +} diff --git a/test/test-ip6-addr.c b/test/test-ip6-addr.c index 156fccde39d..25570dcacde 100644 --- a/test/test-ip6-addr.c +++ b/test/test-ip6-addr.c @@ -44,8 +44,12 @@ TEST_IMPL(ip6_addr_link_local) { const char* device_name; /* 40 bytes address, 16 bytes device name, plus reserve. */ char scoped_addr[128]; + size_t scoped_addr_len; + char interface_id[UV_IF_NAMESIZE]; + size_t interface_id_len; int count; int ix; + int r; ASSERT(0 == uv_interface_addresses(&addresses, &count)); @@ -67,19 +71,29 @@ TEST_IMPL(ip6_addr_link_local) { iface_index = address->address.address6.sin6_scope_id; device_name = address->name; + scoped_addr_len = sizeof(scoped_addr); + ASSERT(0 == uv_if_indextoname(iface_index, scoped_addr, &scoped_addr_len)); +#ifndef _WIN32 + /* This assert fails on Windows, as Windows semantics are different. */ + ASSERT(0 == strcmp(device_name, scoped_addr)); +#endif + + interface_id_len = sizeof(interface_id); + r = uv_if_indextoiid(iface_index, interface_id, &interface_id_len); + ASSERT(0 == r); #ifdef _WIN32 - snprintf(scoped_addr, - sizeof(scoped_addr), - "%s%%%d", - string_address, - iface_index); + /* On Windows, the interface identifier is the numeric string of the index. */ + ASSERT(strtol(interface_id, NULL, 10) == iface_index); #else + /* On Unix/Linux, the interface identifier is the interface device name. */ + ASSERT(0 == strcmp(device_name, interface_id)); +#endif + snprintf(scoped_addr, sizeof(scoped_addr), "%s%%%s", string_address, - device_name); -#endif + interface_id); fprintf(stderr, "Testing link-local address %s " "(iface_index: 0x%02x, device_name: %s)\n", @@ -96,6 +110,9 @@ TEST_IMPL(ip6_addr_link_local) { uv_free_interface_addresses(addresses, count); + scoped_addr_len = sizeof(scoped_addr); + ASSERT(0 != uv_if_indextoname((unsigned int)-1, scoped_addr, &scoped_addr_len)); + MAKE_VALGRIND_HAPPY(); return 0; }