From d974c556bbd078e425419096056b9870068b43b3 Mon Sep 17 00:00:00 2001 From: Brad House Date: Thu, 7 Dec 2023 07:39:42 -0500 Subject: [PATCH] Support ipv6 link-local servers and %iface syntax (#646) Some environments may send router advertisements on a link setting their link-local (fe80::/10) address as a valid DNS server to the remote system. This will cause a DNS entry to be created like `fe80::1%iface`, since all link-local network interfaces are technically part of the same /10 subnet, it must be told what interface to send packets through explicitly if there are multiple physical interfaces. This PR adds support for the %iface modifier when setting DNS servers via `/etc/resolv.conf` as well as via `ares_set_servers_csv()`. For MacOS and iOS it is assumed that libresolve will set the `sin6_scope_id` and should be supported, but my test systems don't seem to read the Router Advertisement for RDNSS link-local. Specifying the link-local dns server on MacOS via adig has been tested and confirmed working. For Windows, this is similar to MacOS in that the system doesn't seem to honor the RDNSS RA, but specifying manually has been tested to work. At this point, Android support does not exist. Fixes Bug #462 Supersedes PR #463 Fix By: Brad House (@bradh352) and Serhii Purik (@sergvpurik) --- CMakeLists.txt | 6 + appveyor.yml | 4 +- configure.ac | 7 +- docs/Makefile.inc | 1 + docs/ares_get_servers_csv.3 | 4 + docs/ares_set_servers.3 | 5 + docs/ares_set_servers_csv.3 | 49 ++- include/ares.h | 1 + m4/cares-functions.m4 | 87 +++++ src/lib/Makefile.inc | 2 + src/lib/ares__iface_ips.c | 594 ++++++++++++++++++++++++++++++++++ src/lib/ares__iface_ips.h | 139 ++++++++ src/lib/ares__socket.c | 52 +-- src/lib/ares_config.h.cmake | 15 + src/lib/ares_init.c | 14 +- src/lib/ares_math.c | 11 + src/lib/ares_private.h | 19 +- src/lib/ares_str.c | 16 + src/lib/ares_str.h | 8 +- src/lib/ares_sysconfig.c | 98 +++++- src/lib/ares_update_servers.c | 290 +++++++++++++++-- src/lib/config-win32.h | 11 + test/ares-test-init.cc | 20 +- test/ares-test-misc.cc | 27 +- test/ares-test.cc | 45 +-- test/ares-test.h | 2 +- 26 files changed, 1365 insertions(+), 162 deletions(-) create mode 100644 docs/ares_get_servers_csv.3 create mode 100644 src/lib/ares__iface_ips.c create mode 100644 src/lib/ares__iface_ips.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f8f419f2..97d6dd0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,6 +225,7 @@ CHECK_INCLUDE_FILES (sys/select.h HAVE_SYS_SELECT_H) CHECK_INCLUDE_FILES (sys/stat.h HAVE_SYS_STAT_H) CHECK_INCLUDE_FILES (sys/time.h HAVE_SYS_TIME_H) CHECK_INCLUDE_FILES (sys/uio.h HAVE_SYS_UIO_H) +CHECK_INCLUDE_FILES (ifaddrs.h HAVE_IFADDRS_H) CHECK_INCLUDE_FILES (time.h HAVE_TIME_H) CHECK_INCLUDE_FILES (dlfcn.h HAVE_DLFCN_H) CHECK_INCLUDE_FILES (unistd.h HAVE_UNISTD_H) @@ -307,6 +308,7 @@ CARES_EXTRAINCLUDE_IFSET (HAVE_ARPA_INET_H arpa/inet.h) CARES_EXTRAINCLUDE_IFSET (HAVE_ARPA_NAMESER_H arpa/nameser.h) CARES_EXTRAINCLUDE_IFSET (HAVE_NETDB_H netdb.h) CARES_EXTRAINCLUDE_IFSET (HAVE_NET_IF_H net/if.h) +CARES_EXTRAINCLUDE_IFSET (HAVE_IFADDRS_H ifaddrs.h) CARES_EXTRAINCLUDE_IFSET (HAVE_NETINET_IN_H netinet/in.h) CARES_EXTRAINCLUDE_IFSET (HAVE_NETINET_TCP_H netinet/tcp.h) CARES_EXTRAINCLUDE_IFSET (HAVE_SIGNAL_H signal.h) @@ -406,6 +408,9 @@ CHECK_SYMBOL_EXISTS (getservbyport_r "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETSERV CHECK_SYMBOL_EXISTS (getservbyname_r "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETSERVBYNAME_R) CHECK_SYMBOL_EXISTS (gettimeofday "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETTIMEOFDAY) CHECK_SYMBOL_EXISTS (if_indextoname "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_IF_INDEXTONAME) +CHECK_SYMBOL_EXISTS (if_nametoindex "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_IF_NAMETOINDEX) +CHECK_SYMBOL_EXISTS (ConvertInterfaceIndexToLuid "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_CONVERTINTERFACEINDEXTOLUID) +CHECK_SYMBOL_EXISTS (ConvertInterfaceLuidToNameA "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_CONVERTINTERFACELUIDTONAMEA) CHECK_SYMBOL_EXISTS (inet_net_pton "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_INET_NET_PTON) IF (NOT WIN32) # Disabled on Windows, because these functions are only really supported on Windows @@ -432,6 +437,7 @@ CHECK_SYMBOL_EXISTS (strnicmp "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_STRNICM CHECK_SYMBOL_EXISTS (writev "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_WRITEV) CHECK_SYMBOL_EXISTS (arc4random_buf "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_ARC4RANDOM_BUF) CHECK_SYMBOL_EXISTS (stat "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_STAT) +CHECK_SYMBOL_EXISTS (getifaddrs "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETIFADDRS) # On Android, the system headers may define __system_property_get(), but excluded # from libc. We need to perform a link test instead of a header/symbol test. diff --git a/appveyor.yml b/appveyor.yml index 45b4444b..de06b10e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ # Copyright (C) The c-ares project and its contributors # SPDX-License-Identifier: MIT -image: Visual Studio 2022 +image: Previous Visual Studio 2022 # Github/Bitbucket only: get source code for one particular commit as zip archive, instead of git clone'ing. shallow_clone: true @@ -27,7 +27,7 @@ environment: - COMPILER: MSVC BUILDTOOL: CMAKE TESTTYPE: NONE - MSVC_SETUP_ARG: x64 store 10.0.22621.0 + MSVC_SETUP_ARG: x64 store MSVC_SETUP_PATH: C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat CMAKE_EXTRA_OPTIONS: -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -A x64 TESTDIRSUFFIX: Release\ diff --git a/configure.ac b/configure.ac index 0a1a5f26..b9e41558 100644 --- a/configure.ac +++ b/configure.ac @@ -581,6 +581,7 @@ AC_CHECK_HEADERS( netinet/in.h \ netinet/tcp.h \ net/if.h \ + ifaddrs.h \ errno.h \ socket.h \ strings.h \ @@ -752,6 +753,7 @@ CARES_CHECK_FUNC_STRNICMP CARES_CHECK_FUNC_WRITEV CARES_CHECK_FUNC_ARC4RANDOM_BUF CARES_CHECK_FUNC_STAT +CARES_CHECK_FUNC_GETIFADDRS dnl check for AF_INET6 CARES_CHECK_CONSTANT( @@ -921,7 +923,10 @@ AC_CHECK_MEMBER(struct addrinfo.ai_flags, AC_CHECK_FUNCS([bitncmp \ gettimeofday \ - if_indextoname + if_indextoname \ + if_nametoindex \ + ConvertInterfaceIndexToLuid \ + ConvertInterfaceLuidToNameA ],[ ],[ func="$ac_func" diff --git a/docs/Makefile.inc b/docs/Makefile.inc index e81e0ca1..e3800159 100644 --- a/docs/Makefile.inc +++ b/docs/Makefile.inc @@ -74,6 +74,7 @@ MANPAGES = ares_cancel.3 \ ares_free_string.3 \ ares_freeaddrinfo.3 \ ares_get_servers.3 \ + ares_get_servers_csv.3 \ ares_get_servers_ports.3 \ ares_getaddrinfo.3 \ ares_gethostbyaddr.3 \ diff --git a/docs/ares_get_servers_csv.3 b/docs/ares_get_servers_csv.3 new file mode 100644 index 00000000..77fd3bba --- /dev/null +++ b/docs/ares_get_servers_csv.3 @@ -0,0 +1,4 @@ +.\" +.\" Copyright (C) Daniel Stenberg +.\" SPDX-License-Identifier: MIT +.so man3/ares_set_servers_csv.3 diff --git a/docs/ares_set_servers.3 b/docs/ares_set_servers.3 index e2817e72..5e629d67 100644 --- a/docs/ares_set_servers.3 +++ b/docs/ares_set_servers.3 @@ -89,6 +89,11 @@ c-ares library initialization not yet performed. .BR ares_get_servers (3), .BR ares_init_options (3), .BR ares_dup (3) + +.SH NOTE +Deprecated functions as of c-ares 1.24.0 due to inability to set all available +server options. Use \fBares_set_servers_csv(3)\fP. + .SH AVAILABILITY \fBares_set_servers(3)\fP was added in c-ares 1.7.1; \fBares_set_servers_ports(3)\fP was added in c-ares 1.11.0. diff --git a/docs/ares_set_servers_csv.3 b/docs/ares_set_servers_csv.3 index 44ea72b7..7d86c74b 100644 --- a/docs/ares_set_servers_csv.3 +++ b/docs/ares_set_servers_csv.3 @@ -15,9 +15,10 @@ .\" .\" SPDX-License-Identifier: MIT .\" -.TH ARES_SET_SERVERS_CSV 3 "30 June 2010" +.TH ARES_SET_SERVERS_CSV 3 "5 Dec 2023" .SH NAME -ares_set_servers_csv, ares_set_servers_ports_csv \- Set list of DNS servers to be used. +ares_set_servers_csv, ares_set_servers_ports_csv, ares_get_servers_csv \- Set +or Get a list of DNS servers used for queries. .SH SYNOPSIS .nf #include @@ -25,10 +26,12 @@ ares_set_servers_csv, ares_set_servers_ports_csv \- Set list of DNS servers to b int ares_set_servers_csv(ares_channel_t *\fIchannel\fP, const char* \fIservers\fP) int ares_set_servers_ports_csv(ares_channel_t *\fIchannel\fP, const char* \fIservers\fP) + +char *ares_get_servers_csv(ares_channel_t *\fIchannel\fP) .fi .SH DESCRIPTION -The \fBares_set_servers_csv\fP and \fBares_set_servers_ports_csv\fPfunctions set -the list of DNS servers that ARES will query. As of v1.22.0 this function can +The \fBares_set_servers_csv\fP and \fBares_set_servers_ports_csv\fP functions set +the list of DNS servers that c-ares will query. As of v1.22.0 this function can be called on an active channel with running queries, previously it would return ARES_ENOTIMP. @@ -36,22 +39,37 @@ Though not recommended, passing NULL for servers will clear all configured servers and make an inoperable channel, this may be advantageous for test simulation but unlikely to be useful in production. -The format of the servers option is: +The \fBares_get_servers_csv\fP retrieves the list of servers in comma delimited +format. + +The input and output format is a comma separated list of servers. Each server +entry may contain these forms: + +ip[:port][%iface] -host[:port][,host[:port]]... +The \fBhost\fP may be encapsulated in square brackets ([ ]), and must be if +using ipv6 and also specifying a port. + +The \fBport\fP is optional, and will default to 53 or the value specified in +\fBares_init_options(3)\fP. + +The \fBiface\fP is specific to IPv6 link-local servers (fe80::/10) and should +not otherwise be used. For example: -192.168.1.100,192.168.1.101,3.4.5.6 +192.168.1.100,192.168.1.101:53,[1:2:3::4]:53,[fe80::1]:53%eth0 .PP -The \fBares_set_servers_csv\fP function will ignore any port values specified in -the input string, whereare the \fBares_set_servers_ports_csv\fP function will -apply any specified port values as the UDP and TCP port to be used for that -particular nameserver. +As of c-ares 1.24.0, \fBares_set_servers_csv\fP and \fBares_set_servers_ports_csv\fP +are identical. Prior versions would simply omit ports in \fBares_set_servers_csv\fP +but due to the addition of link local interface support, this difference was +removed. .SH RETURN VALUES .B ares_set_servers_csv(3) -This function may return any of the following values: +and +.B ares_set_servers_ports_csv(3) +may return any of the following values: .TP 15 .B ARES_SUCCESS The name servers configuration was successfully initialized. @@ -66,10 +84,15 @@ was invalid. .TP 15 .B ARES_ENOTINITIALIZED c-ares library initialization not yet performed. +.PP +.B ares_get_servers_csv(3) +returns a string representing the servers configured which must be freed with +\fBares_free_string(3)\fP. If it returns NULL, this is an out of memory condition. .SH SEE ALSO .BR ares_set_servers (3) .SH AVAILABILITY -\fBares_set_servers_csv\fP was added in c-ares 1.7.2; +\fBares_set_servers_csv\fP was added in c-ares 1.7.2 \fBares_set_servers_ports_csv\fP was added in c-ares 1.11.0. +\fBares_get_servers_csv\fP was added in c-ares 1.24.0. .SH AUTHOR Ben Greear diff --git a/include/ares.h b/include/ares.h index 6564a69d..e3c7a933 100644 --- a/include/ares.h +++ b/include/ares.h @@ -730,6 +730,7 @@ CARES_EXTERN int ares_set_servers_csv(ares_channel_t *channel, const char *servers); CARES_EXTERN int ares_set_servers_ports_csv(ares_channel_t *channel, const char *servers); +CARES_EXTERN char *ares_get_servers_csv(ares_channel_t *channel); CARES_EXTERN int ares_get_servers(ares_channel_t *channel, struct ares_addr_node **servers); diff --git a/m4/cares-functions.m4 b/m4/cares-functions.m4 index 84bca23f..be2031e9 100644 --- a/m4/cares-functions.m4 +++ b/m4/cares-functions.m4 @@ -3877,6 +3877,93 @@ AC_DEFUN([CARES_CHECK_FUNC_WRITEV], [ fi ]) +dnl CARES_CHECK_FUNC_GETIFADDRS +dnl ------------------------------------------------- +dnl Verify if getifaddrs is available, prototyped, and +dnl can be compiled. If all of these are true, and +dnl usage has not been previously disallowed with +dnl shell variable cares_disallow_getifaddrs, then +dnl HAVE_GETIFADDRS will be defined. + +AC_DEFUN([CARES_CHECK_FUNC_GETIFADDRS], [ + # + tst_links_getifaddrs="unknown" + tst_proto_getifaddrs="unknown" + tst_compi_getifaddrs="unknown" + tst_allow_getifaddrs="unknown" + # + AC_MSG_CHECKING([if getifaddrs can be linked]) + AC_LINK_IFELSE([ + AC_LANG_FUNC_LINK_TRY([getifaddrs]) + ],[ + AC_MSG_RESULT([yes]) + tst_links_getifaddrs="yes" + ],[ + AC_MSG_RESULT([no]) + tst_links_getifaddrs="no" + ]) + # + if test "$tst_links_getifaddrs" = "yes"; then + AC_MSG_CHECKING([if getifaddrs is prototyped]) + AC_EGREP_CPP([getifaddrs],[ + #include + ],[ + AC_MSG_RESULT([yes]) + tst_proto_getifaddrs="yes" + ],[ + AC_MSG_RESULT([no]) + tst_proto_getifaddrs="no" + ]) + fi + # + if test "$tst_proto_getifaddrs" = "yes"; then + AC_MSG_CHECKING([if getifaddrs is compilable]) + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM([[ + #include + #include + #include + #include + ]],[[ + if(0 != getifaddrs(NULL)) + return 1; + ]]) + ],[ + AC_MSG_RESULT([yes]) + tst_compi_getifaddrs="yes" + ],[ + AC_MSG_RESULT([no]) + tst_compi_getifaddrs="no" + ]) + fi + # + if test "$tst_compi_getifaddrs" = "yes"; then + AC_MSG_CHECKING([if getifaddrs usage allowed]) + if test "x$cares_disallow_getifaddrs" != "xyes"; then + AC_MSG_RESULT([yes]) + tst_allow_getifaddrs="yes" + else + AC_MSG_RESULT([no]) + tst_allow_getifaddrs="no" + fi + fi + # + AC_MSG_CHECKING([if getifaddrs might be used]) + if test "$tst_links_getifaddrs" = "yes" && + test "$tst_proto_getifaddrs" = "yes" && + test "$tst_compi_getifaddrs" = "yes" && + test "$tst_allow_getifaddrs" = "yes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE_UNQUOTED(HAVE_GETIFADDRS, 1, + [Define to 1 if you have the getifaddrs function.]) + ac_cv_func_getifaddrs="yes" + else + AC_MSG_RESULT([no]) + ac_cv_func_getifaddrs="no" + fi +]) + + dnl CARES_CHECK_FUNC_STAT dnl ------------------------------------------------- dnl Verify if stat is available, prototyped, and diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index 8324a355..0c6473be 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -10,6 +10,7 @@ CSOURCES = ares__addrinfo2hostent.c \ ares__htable_asvp.c \ ares__htable_strvp.c \ ares__htable_szvp.c \ + ares__iface_ips.c \ ares__llist.c \ ares__parse_into_addrinfo.c \ ares__read_line.c \ @@ -82,6 +83,7 @@ HHEADERS = ares__buf.h \ ares__htable_asvp.h \ ares__htable_strvp.h \ ares__htable_szvp.h \ + ares__iface_ips.h \ ares__llist.h \ ares__slist.h \ ares_android.h \ diff --git a/src/lib/ares__iface_ips.c b/src/lib/ares__iface_ips.c new file mode 100644 index 00000000..ca17da7f --- /dev/null +++ b/src/lib/ares__iface_ips.c @@ -0,0 +1,594 @@ +/* MIT License + * + * Copyright (c) 2023 Brad House + * + * 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 (including the next + * paragraph) 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. + * + * SPDX-License-Identifier: MIT + */ +#include "ares_setup.h" +#include "ares.h" +#include "ares_private.h" + +#ifdef USE_WINSOCK +# include +# include +# if defined(HAVE_IPHLPAPI_H) +# include +# endif +# if defined(HAVE_NETIOAPI_H) +# include +# endif +#endif + +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#ifdef HAVE_NET_IF_H +# include +#endif +#ifdef HAVE_IFADDRS_H +# include +#endif +#ifdef HAVE_SYS_IOCTL_H +# include +#endif +#ifdef HAVE_NETINET_IN_H +# include +#endif + +#ifndef IFNAMSIZ +# define IFNAMSIZ 64 +#endif + +static ares_status_t ares__iface_ips_enumerate(ares__iface_ips_t *ips, + const char *name); + +typedef struct { + char *name; + struct ares_addr addr; + unsigned char netmask; + unsigned int ll_scope; + ares__iface_ip_flags_t flags; +} ares__iface_ip_t; + +struct ares__iface_ips { + ares__iface_ip_t *ips; + size_t cnt; + size_t alloc_size; + ares__iface_ip_flags_t enum_flags; +}; + +static ares__iface_ips_t *ares__iface_ips_alloc(ares__iface_ip_flags_t flags) +{ + ares__iface_ips_t *ips = ares_malloc_zero(sizeof(*ips)); + if (ips == NULL) { + return NULL; + } + + /* Prealloc 4 entries */ + ips->alloc_size = 4; + ips->ips = ares_malloc_zero(ips->alloc_size * sizeof(*ips->ips)); + if (ips->ips == NULL) { + ares_free(ips); + return NULL; + } + ips->enum_flags = flags; + return ips; +} + +static void ares__iface_ip_destroy(ares__iface_ip_t *ip) +{ + if (ip == NULL) { + return; + } + ares_free(ip->name); + memset(ip, 0, sizeof(*ip)); +} + +void ares__iface_ips_destroy(ares__iface_ips_t *ips) +{ + size_t i; + + if (ips == NULL) { + return; + } + + for (i = 0; i < ips->cnt; i++) { + ares__iface_ip_destroy(&ips->ips[i]); + } + ares_free(ips->ips); + ares_free(ips); +} + +ares_status_t ares__iface_ips(ares__iface_ips_t **ips, + ares__iface_ip_flags_t flags, const char *name) +{ + ares_status_t status; + + if (ips == NULL) { + return ARES_EFORMERR; + } + + *ips = ares__iface_ips_alloc(flags); + if (*ips == NULL) { + return ARES_ENOMEM; + } + + status = ares__iface_ips_enumerate(*ips, name); + if (status != ARES_SUCCESS) { + ares__iface_ips_destroy(*ips); + *ips = NULL; + return status; + } + + return ARES_SUCCESS; +} + +static ares_status_t + ares__iface_ips_add(ares__iface_ips_t *ips, ares__iface_ip_flags_t flags, + const char *name, const struct ares_addr *addr, + unsigned char netmask, unsigned int ll_scope) +{ + size_t idx; + + if (ips == NULL || name == NULL || addr == NULL) { + return ARES_EFORMERR; + } + + /* Don't want loopback */ + if (flags & ARES_IFACE_IP_LOOPBACK && + !(ips->enum_flags & ARES_IFACE_IP_LOOPBACK)) { + return ARES_SUCCESS; + } + + /* Don't want offline */ + if (flags & ARES_IFACE_IP_OFFLINE && + !(ips->enum_flags & ARES_IFACE_IP_OFFLINE)) { + return ARES_SUCCESS; + } + + /* Check for link-local */ + if (ares__addr_is_linklocal(addr)) { + flags |= ARES_IFACE_IP_LINKLOCAL; + } + if (flags & ARES_IFACE_IP_LINKLOCAL && + !(ips->enum_flags & ARES_IFACE_IP_LINKLOCAL)) { + return ARES_SUCCESS; + } + + /* Set address flag based on address provided */ + if (addr->family == AF_INET) { + flags |= ARES_IFACE_IP_V4; + } + + if (addr->family == AF_INET6) { + flags |= ARES_IFACE_IP_V6; + } + + /* If they specified either v4 or v6 validate flags otherwise assume they + * want to enumerate both */ + if (ips->enum_flags & (ARES_IFACE_IP_V4 | ARES_IFACE_IP_V6)) { + if (flags & ARES_IFACE_IP_V4 && !(ips->enum_flags & ARES_IFACE_IP_V4)) { + return ARES_SUCCESS; + } + if (flags & ARES_IFACE_IP_V6 && !(ips->enum_flags & ARES_IFACE_IP_V6)) { + return ARES_SUCCESS; + } + } + + /* Allocate more ips */ + if (ips->cnt + 1 > ips->alloc_size) { + void *temp; + size_t alloc_size; + + alloc_size = ares__round_up_pow2(ips->alloc_size + 1); + temp = ares_realloc_zero(ips->ips, ips->alloc_size * sizeof(ips->ips), + alloc_size * sizeof(ips->ips)); + if (temp == NULL) { + return ARES_ENOMEM; + } + ips->ips = temp; + ips->alloc_size = alloc_size; + } + + /* Add */ + idx = ips->cnt++; + + ips->ips[idx].flags = flags; + ips->ips[idx].netmask = netmask; + ips->ips[idx].ll_scope = ll_scope; + memcpy(&ips->ips[idx].addr, addr, sizeof(*addr)); + ips->ips[idx].name = ares_strdup(name); + if (ips->ips[idx].name == NULL) { + return ARES_ENOMEM; + } + + return ARES_SUCCESS; +} + +size_t ares__iface_ips_cnt(const ares__iface_ips_t *ips) +{ + if (ips == NULL) { + return 0; + } + return ips->cnt; +} + +const char *ares__iface_ips_get_name(const ares__iface_ips_t *ips, size_t idx) +{ + if (ips == NULL || idx >= ips->cnt) { + return NULL; + } + return ips->ips[idx].name; +} + +const struct ares_addr *ares__iface_ips_get_addr(const ares__iface_ips_t *ips, + size_t idx) +{ + if (ips == NULL || idx >= ips->cnt) { + return NULL; + } + return &ips->ips[idx].addr; +} + +ares__iface_ip_flags_t ares__iface_ips_get_flags(const ares__iface_ips_t *ips, + size_t idx) +{ + if (ips == NULL || idx >= ips->cnt) { + return 0; + } + return ips->ips[idx].flags; +} + +unsigned char ares__iface_ips_get_netmask(const ares__iface_ips_t *ips, + size_t idx) +{ + if (ips == NULL || idx >= ips->cnt) { + return 0; + } + return ips->ips[idx].netmask; +} + +unsigned int ares__iface_ips_get_ll_scope(const ares__iface_ips_t *ips, + size_t idx) +{ + if (ips == NULL || idx >= ips->cnt) { + return 0; + } + return ips->ips[idx].ll_scope; +} + + +#ifdef USE_WINSOCK + +#if 0 +static char *wcharp_to_charp(const wchar_t *in) +{ + char *out; + int len; + + len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL); + if (len == -1) { + return NULL; + } + + out = ares_malloc_zero((size_t)len + 1); + + if (WideCharToMultiByte(CP_UTF8, 0, in, -1, out, len, NULL, NULL) == -1) { + ares_free(out); + return NULL; + } + + return out; +} +#endif + +static ares_bool_t name_match(const char *name, const char *adapter_name, + unsigned int ll_scope) +{ + if (name == NULL || *name == 0) { + return ARES_TRUE; + } + + if (strcasecmp(name, adapter_name) == 0) { + return ARES_TRUE; + } + + if (ares_str_isnum(name) && (unsigned int)atoi(name) == ll_scope) { + return ARES_TRUE; + } + + return ARES_FALSE; +} + +static ares_status_t ares__iface_ips_enumerate(ares__iface_ips_t *ips, + const char *name) +{ + ULONG myflags = GAA_FLAG_INCLUDE_PREFIX /*|GAA_FLAG_INCLUDE_ALL_INTERFACES */; + ULONG outBufLen = 0; + DWORD retval; + IP_ADAPTER_ADDRESSES *addresses = NULL; + IP_ADAPTER_ADDRESSES *address = NULL; + ares_status_t status = ARES_SUCCESS; + + /* Get necessary buffer size */ + GetAdaptersAddresses(AF_UNSPEC, myflags, NULL, NULL, &outBufLen); + if (outBufLen == 0) { + status = ARES_EFILE; + goto done; + } + + addresses = ares_malloc_zero(outBufLen); + if (addresses == NULL) { + status = ARES_ENOMEM; + goto done; + } + + retval = + GetAdaptersAddresses(AF_UNSPEC, myflags, NULL, addresses, &outBufLen); + if (retval != ERROR_SUCCESS) { + status = ARES_EFILE; + goto done; + } + + for (address = addresses; address != NULL; address = address->Next) { + IP_ADAPTER_UNICAST_ADDRESS *ipaddr = NULL; + ares__iface_ip_flags_t addrflag = 0; + char ifname[64] = ""; + +#if defined(HAVE_CONVERTINTERFACEINDEXTOLUID) && defined(HAVE_CONVERTINTERFACELUIDTONAMEA) + /* Retrieve name from interface index. + * address->AdapterName appears to be a GUID/UUID of some sort, not a name. + * address->FriendlyName is user-changeable. + * That said, this doesn't appear to help us out on systems that don't + * have if_nametoindex() or if_indextoname() as they don't have these + * functions either! */ + NET_LUID luid; + ConvertInterfaceIndexToLuid(address->IfIndex, &luid); + ConvertInterfaceLuidToNameA(&luid, ifname, sizeof(ifname)); +#else + ares_strcpy(ifname, address->AdapterName, sizeof(ifname)); +#endif + + if (address->OperStatus != IfOperStatusUp) { + addrflag |= ARES_IFACE_IP_OFFLINE; + } + + if (address->IfType == IF_TYPE_SOFTWARE_LOOPBACK) { + addrflag |= ARES_IFACE_IP_LOOPBACK; + } + + for (ipaddr = address->FirstUnicastAddress; ipaddr != NULL; + ipaddr = ipaddr->Next) { + struct ares_addr addr; + + if (ipaddr->Address.lpSockaddr->sa_family == AF_INET) { + struct sockaddr_in *sockaddr_in = + (struct sockaddr_in *)((void *)ipaddr->Address.lpSockaddr); + addr.family = AF_INET; + memcpy(&addr.addr.addr4, &sockaddr_in->sin_addr, + sizeof(addr.addr.addr4)); + } else if (ipaddr->Address.lpSockaddr->sa_family == AF_INET6) { + struct sockaddr_in6 *sockaddr_in6 = + (struct sockaddr_in6 *)((void *)ipaddr->Address.lpSockaddr); + addr.family = AF_INET6; + memcpy(&addr.addr.addr6, &sockaddr_in6->sin6_addr, + sizeof(addr.addr.addr6)); + } else { + /* Unknown */ + continue; + } + + /* Sometimes windows may use numerics to indicate a DNS server's adapter, + * which corresponds to the index rather than the name. Check and + * validate both. */ + if (!name_match(name, ifname, address->Ipv6IfIndex)) { + continue; + } + + status = ares__iface_ips_add(ips, addrflag, ifname, &addr, + ipaddr->OnLinkPrefixLength /* netmask */, + address->Ipv6IfIndex /* ll_scope */); + + if (status != ARES_SUCCESS) { + goto done; + } + } + } + +done: + ares_free(addresses); + return status; +} + +#elif defined(HAVE_GETIFADDRS) + +static unsigned char count_addr_bits(const unsigned char *addr, size_t addr_len) +{ + size_t i; + unsigned char count = 0; + + for (i = 0; i < addr_len; i++) { + count += ares__count_bits_u8(addr[i]); + } + return count; +} + +static ares_status_t ares__iface_ips_enumerate(ares__iface_ips_t *ips, + const char *name) +{ + struct ifaddrs *ifap = NULL; + struct ifaddrs *ifa = NULL; + ares_status_t status = ARES_SUCCESS; + + if (getifaddrs(&ifap) != 0) { + status = ARES_EFILE; + goto done; + } + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + ares__iface_ip_flags_t addrflag = 0; + struct ares_addr addr; + unsigned char netmask = 0; + unsigned int ll_scope = 0; + + if (ifa->ifa_addr == NULL) { + continue; + } + + if (!(ifa->ifa_flags & IFF_UP)) { + addrflag |= ARES_IFACE_IP_OFFLINE; + } + + if (ifa->ifa_flags & IFF_LOOPBACK) { + addrflag |= ARES_IFACE_IP_LOOPBACK; + } + + if (ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *sockaddr_in = + (struct sockaddr_in *)((void *)ifa->ifa_addr); + addr.family = AF_INET; + memcpy(&addr.addr.addr4, &sockaddr_in->sin_addr, sizeof(addr.addr.addr4)); + /* netmask */ + sockaddr_in = (struct sockaddr_in *)((void *)ifa->ifa_netmask); + netmask = count_addr_bits((const void *)&sockaddr_in->sin_addr, 4); + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *sockaddr_in6 = + (struct sockaddr_in6 *)((void *)ifa->ifa_addr); + addr.family = AF_INET6; + memcpy(&addr.addr.addr6, &sockaddr_in6->sin6_addr, + sizeof(addr.addr.addr6)); + /* netmask */ + sockaddr_in6 = (struct sockaddr_in6 *)((void *)ifa->ifa_netmask); + netmask = count_addr_bits((const void *)&sockaddr_in6->sin6_addr, 16); +# ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + ll_scope = sockaddr_in6->sin6_scope_id; +# endif + } else { + /* unknown */ + continue; + } + + /* Name mismatch */ + if (strcasecmp(ifa->ifa_name, name) != 0) { + continue; + } + + status = ares__iface_ips_add(ips, addrflag, ifa->ifa_name, &addr, netmask, + ll_scope); + if (status != ARES_SUCCESS) { + goto done; + } + } + +done: + freeifaddrs(ifap); + return status; +} + +#else + +static ares_status_t ares__iface_ips_enumerate(ares__iface_ips_t *ips, + ares__iface_ip_flags_t flags, + const char *name) +{ + (void)ips; + (void)flags; + return ARES_ENOTIMP; +} + +#endif + + +unsigned int ares__if_nametoindex(const char *name) +{ +#ifdef HAVE_IF_NAMETOINDEX + return if_nametoindex(name); +#else + ares_status_t status; + ares__iface_ips_t *ips = NULL; + size_t i; + unsigned int index = 0; + + status = + ares__iface_ips(&ips, ARES_IFACE_IP_V6 | ARES_IFACE_IP_LINKLOCAL, name); + if (status != ARES_SUCCESS) { + goto done; + } + + for (i = 0; i < ares__iface_ips_cnt(ips); i++) { + if (ares__iface_ips_get_flags(ips, i) & ARES_IFACE_IP_LINKLOCAL) { + index = ares__iface_ips_get_ll_scope(ips, i); + goto done; + } + } + +done: + ares__iface_ips_destroy(ips); + return index; +#endif +} + +const char *ares__if_indextoname(unsigned int index, char *name, + size_t name_len) +{ +#ifdef HAVE_IF_INDEXTONAME + if (name_len < IFNAMSIZ) { + return NULL; + } + return if_indextoname(index, name); +#else + ares_status_t status; + ares__iface_ips_t *ips = NULL; + size_t i; + const char *ptr = NULL; + + if (name_len < IFNAMSIZ) { + goto done; + } + + if (index == 0) { + goto done; + } + + status = + ares__iface_ips(&ips, ARES_IFACE_IP_V6 | ARES_IFACE_IP_LINKLOCAL, NULL); + if (status != ARES_SUCCESS) { + goto done; + } + + for (i = 0; i < ares__iface_ips_cnt(ips); i++) { + if (ares__iface_ips_get_flags(ips, i) & ARES_IFACE_IP_LINKLOCAL && + ares__iface_ips_get_ll_scope(ips, i) == index) { + ares_strcpy(name, ares__iface_ips_get_name(ips, i), name_len); + ptr = name; + goto done; + } + } + +done: + ares__iface_ips_destroy(ips); + return ptr; +#endif +} diff --git a/src/lib/ares__iface_ips.h b/src/lib/ares__iface_ips.h new file mode 100644 index 00000000..3edc3bc6 --- /dev/null +++ b/src/lib/ares__iface_ips.h @@ -0,0 +1,139 @@ +/* MIT License + * + * Copyright (c) 2023 Brad House + * + * 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 (including the next + * paragraph) 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. + * + * SPDX-License-Identifier: MIT + */ +#ifndef __ARES__IFACE_IPS_H +#define __ARES__IFACE_IPS_H + +/*! Flags for interface ip addresses. */ +typedef enum { + ARES_IFACE_IP_V4 = 1 << 0, /*!< IPv4 address. During enumeration if + * this flag is set ARES_IFACE_IP_V6 + * is not, will only enumerate v4 + * addresses. */ + ARES_IFACE_IP_V6 = 1 << 1, /*!< IPv6 address. During enumeration if + * this flag is set ARES_IFACE_IP_V4 + * is not, will only enumerate v6 + * addresses. */ + ARES_IFACE_IP_LOOPBACK = 1 << 2, /*!< Loopback adapter */ + ARES_IFACE_IP_OFFLINE = 1 << 3, /*!< Adapter offline */ + ARES_IFACE_IP_LINKLOCAL = 1 << 4, /*!< Link-local ip address */ + /*! Default, enumerate all ips for online interfaces, including loopback */ + ARES_IFACE_IP_DEFAULT = (ARES_IFACE_IP_V4 | ARES_IFACE_IP_V6 | + ARES_IFACE_IP_LOOPBACK | ARES_IFACE_IP_LINKLOCAL) +} ares__iface_ip_flags_t; + +struct ares__iface_ips; + +/*! Opaque pointer for holding enumerated interface ip addresses */ +typedef struct ares__iface_ips ares__iface_ips_t; + +/*! Destroy ip address enumeration created by ares__iface_ips(). + * + * \param[in] ips Initialized IP address enumeration structure + */ +void ares__iface_ips_destroy(ares__iface_ips_t *ips); + +/*! Enumerate ip addresses on interfaces + * + * \param[out] ips Returns initialized ip address structure + * \param[in] flags Flags for enumeration + * \param[in] name Interface name to enumerate, or NULL to enumerate all + * \return ARES_ENOMEM on out of memory, ARES_ENOTIMP if not supported on + * the system, ARES_SUCCESS on success + */ +ares_status_t ares__iface_ips(ares__iface_ips_t **ips, + ares__iface_ip_flags_t flags, const char *name); + +/*! Count of ips enumerated + * + * \param[in] ips Initialized IP address enumeration structure + * \return count + */ +size_t ares__iface_ips_cnt(const ares__iface_ips_t *ips); + +/*! Retrieve interface name + * + * \param[in] ips Initialized IP address enumeration structure + * \param[in] idx Index of entry to pull + * \return interface name + */ +const char *ares__iface_ips_get_name(const ares__iface_ips_t *ips, size_t idx); + +/*! Retrieve interface address + * + * \param[in] ips Initialized IP address enumeration structure + * \param[in] idx Index of entry to pull + * \return interface address + */ +const struct ares_addr *ares__iface_ips_get_addr(const ares__iface_ips_t *ips, + size_t idx); + +/*! Retrieve interface address flags + * + * \param[in] ips Initialized IP address enumeration structure + * \param[in] idx Index of entry to pull + * \return interface address flags + */ +ares__iface_ip_flags_t ares__iface_ips_get_flags(const ares__iface_ips_t *ips, + size_t idx); + +/*! Retrieve interface address netmask + * + * \param[in] ips Initialized IP address enumeration structure + * \param[in] idx Index of entry to pull + * \return interface address netmask + */ +unsigned char ares__iface_ips_get_netmask(const ares__iface_ips_t *ips, + size_t idx); + +/*! Retrieve interface ipv6 link local scope + * + * \param[in] ips Initialized IP address enumeration structure + * \param[in] idx Index of entry to pull + * \return interface ipv6 link local scope + */ +unsigned int ares__iface_ips_get_ll_scope(const ares__iface_ips_t *ips, + size_t idx); + + +/*! Retrieve the interface index (aka link local scope) from the interface + * name. + * + * \param[in] name Interface name + * \return 0 on failure, index otherwise + */ +unsigned int ares__if_nametoindex(const char *name); + +/*! Retrieves the interface name from the index (aka link local scope) + * + * \param[in] index Interface index (> 0) + * \param[in] name Buffer to hold name + * \param[in] name_len Length of provided buffer, must be at least IFNAMSIZ + * \return NULL on failure, or pointer to name on success + */ +const char *ares__if_indextoname(unsigned int index, char *name, + size_t name_len); + +#endif diff --git a/src/lib/ares__socket.c b/src/lib/ares__socket.c index 67cd9376..587340ed 100644 --- a/src/lib/ares__socket.c +++ b/src/lib/ares__socket.c @@ -159,8 +159,7 @@ static void set_ipv6_v6only(ares_socket_t sockfd, int on) # define set_ipv6_v6only(s, v) #endif -static int configure_socket(ares_socket_t s, int family, - ares_channel_t *channel) +static int configure_socket(ares_socket_t s, struct server_state *server) { union { struct sockaddr sa; @@ -168,6 +167,9 @@ static int configure_socket(ares_socket_t s, int family, struct sockaddr_in6 sa6; } local; + ares_socklen_t bindlen = 0; + ares_channel_t *channel = server->channel; + /* do not set options for user-managed sockets */ if (channel->sock_funcs && channel->sock_funcs->asocket) { return 0; @@ -206,26 +208,27 @@ static int configure_socket(ares_socket_t s, int family, } #endif - if (family == AF_INET) { - if (channel->local_ip4) { - memset(&local.sa4, 0, sizeof(local.sa4)); - local.sa4.sin_family = AF_INET; - local.sa4.sin_addr.s_addr = htonl(channel->local_ip4); - if (bind(s, &local.sa, sizeof(local.sa4)) < 0) { - return -1; - } - } - } else if (family == AF_INET6) { - if (memcmp(channel->local_ip6, ares_in6addr_any._S6_un._S6_u8, - sizeof(channel->local_ip6)) != 0) { - memset(&local.sa6, 0, sizeof(local.sa6)); - local.sa6.sin6_family = AF_INET6; - memcpy(&local.sa6.sin6_addr, channel->local_ip6, - sizeof(channel->local_ip6)); - if (bind(s, &local.sa, sizeof(local.sa6)) < 0) { - return -1; - } - } + if (server->addr.family == AF_INET && channel->local_ip4) { + memset(&local.sa4, 0, sizeof(local.sa4)); + local.sa4.sin_family = AF_INET; + local.sa4.sin_addr.s_addr = htonl(channel->local_ip4); + bindlen = sizeof(local.sa4); + } else if (server->addr.family == AF_INET6 && server->ll_scope == 0 && + memcmp(channel->local_ip6, ares_in6addr_any._S6_un._S6_u8, + sizeof(channel->local_ip6)) != 0) { + /* Only if not link-local and an ip other than "::" is specified */ + memset(&local.sa6, 0, sizeof(local.sa6)); + local.sa6.sin6_family = AF_INET6; + memcpy(&local.sa6.sin6_addr, channel->local_ip6, + sizeof(channel->local_ip6)); + bindlen = sizeof(local.sa6); + } + + if (bindlen && bind(s, &local.sa, bindlen) < 0) { + return -1; + } + + if (server->addr.family == AF_INET6) { set_ipv6_v6only(s, 0); } @@ -267,6 +270,9 @@ ares_status_t ares__open_connection(ares_channel_t *channel, saddr.sa6.sin6_port = htons(is_tcp ? server->tcp_port : server->udp_port); memcpy(&saddr.sa6.sin6_addr, &server->addr.addr.addr6, sizeof(saddr.sa6.sin6_addr)); +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + saddr.sa6.sin6_scope_id = server->ll_scope; +#endif break; default: return ARES_EBADFAMILY; /* LCOV_EXCL_LINE */ @@ -279,7 +285,7 @@ ares_status_t ares__open_connection(ares_channel_t *channel, } /* Configure it. */ - if (configure_socket(s, server->addr.family, channel) < 0) { + if (configure_socket(s, server) < 0) { ares__close_socket(channel, s); return ARES_ECONNREFUSED; } diff --git a/src/lib/ares_config.h.cmake b/src/lib/ares_config.h.cmake index 84880695..c2974dce 100644 --- a/src/lib/ares_config.h.cmake +++ b/src/lib/ares_config.h.cmake @@ -142,6 +142,15 @@ /* Define to 1 if you have the `if_indextoname' function. */ #cmakedefine HAVE_IF_INDEXTONAME +/* Define to 1 if you have the `if_nametoindex' function. */ +#cmakedefine HAVE_IF_NAMETOINDEX + +/* Define to 1 if you have the `ConvertInterfaceIndexToLuid' function. */ +#cmakedefine HAVE_CONVERTINTERFACEINDEXTOLUID + +/* Define to 1 if you have the `ConvertInterfaceLuidToNameA' function. */ +#cmakedefine HAVE_CONVERTINTERFACELUIDTONAMEA + /* Define to 1 if you have a IPv6 capable working inet_net_pton function. */ #cmakedefine HAVE_INET_NET_PTON @@ -332,6 +341,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_TIME_H +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IFADDRS_H + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UNISTD_H @@ -362,6 +374,9 @@ /* Define if have arc4random_buf() */ #cmakedefine HAVE_ARC4RANDOM_BUF +/* Define if have getifaddrs() */ +#cmakedefine HAVE_GETIFADDRS + /* Define if have stat() */ #cmakedefine HAVE_STAT diff --git a/src/lib/ares_init.c b/src/lib/ares_init.c index d737b585..463027e0 100644 --- a/src/lib/ares_init.c +++ b/src/lib/ares_init.c @@ -161,7 +161,7 @@ static ares_status_t init_by_defaults(ares_channel_t *channel) addr.family = AF_INET; addr.addr.addr4.s_addr = htonl(INADDR_LOOPBACK); - rc = ares__sconfig_append(&sconfig, &addr, 0, 0); + rc = ares__sconfig_append(&sconfig, &addr, 0, 0, NULL); if (rc != ARES_SUCCESS) { return rc; } @@ -430,7 +430,6 @@ ares_status_t ares_reinit(ares_channel_t *channel) int ares_dup(ares_channel_t **dest, ares_channel_t *src) { struct ares_options opts; - struct ares_addr_port_node *servers; ares_status_t rc; int optmask; @@ -480,18 +479,21 @@ int ares_dup(ares_channel_t **dest, ares_channel_t *src) * the case, pull them in. * * We don't want to clone system-configuration servers though. + * + * We must use the "csv" format to get things like link-local address support */ if (optmask & ARES_OPT_SERVERS) { - rc = (ares_status_t)ares_get_servers_ports(src, &servers); - if (rc != ARES_SUCCESS) { + char *csv = ares_get_servers_csv(src); + if (csv == NULL) { ares_destroy(*dest); *dest = NULL; + rc = ARES_ENOMEM; goto done; } - rc = (ares_status_t)ares_set_servers_ports(*dest, servers); - ares_free_data(servers); + rc = (ares_status_t)ares_set_servers_ports_csv(*dest, csv); + ares_free_string(csv); if (rc != ARES_SUCCESS) { ares_destroy(*dest); *dest = NULL; diff --git a/src/lib/ares_math.c b/src/lib/ares_math.c index ea29bd10..eaefd6c5 100644 --- a/src/lib/ares_math.c +++ b/src/lib/ares_math.c @@ -132,3 +132,14 @@ size_t ares__count_hexdigits(size_t n) return digits; } + +unsigned char ares__count_bits_u8(unsigned char x) +{ + /* Implementation obtained from: + * http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable */ +#define B2(n) n, n + 1, n + 1, n + 2 +#define B4(n) B2(n), B2(n + 1), B2(n + 1), B2(n + 2) +#define B6(n) B4(n), B4(n + 1), B4(n + 1), B4(n + 2) + static const unsigned char lookup[256] = { B6(0), B6(1), B6(1), B6(2) }; + return lookup[x]; +} diff --git a/src/lib/ares_private.h b/src/lib/ares_private.h index cf02571a..05ef45cb 100644 --- a/src/lib/ares_private.h +++ b/src/lib/ares_private.h @@ -123,6 +123,7 @@ typedef struct ares_rand_state ares_rand_state; #include "ares__htable_asvp.h" #include "ares__buf.h" #include "ares_dns_private.h" +#include "ares__iface_ips.h" #ifndef HAVE_GETENV # include "ares_getenv.h" @@ -167,14 +168,17 @@ struct server_connection { }; struct server_state { + /* Configuration */ size_t idx; /* index for server in system configuration */ - size_t consec_failures; /* Consecutive query failure count - * can be hard errors or timeouts - */ struct ares_addr addr; unsigned short udp_port; /* host byte order */ unsigned short tcp_port; /* host byte order */ + char ll_iface[64]; /* IPv6 Link Local Interface */ + unsigned int ll_scope; /* IPv6 Link Local Scope */ + size_t consec_failures; /* Consecutive query failure count + * can be hard errors or timeouts + */ ares__llist_t *connections; struct server_connection *tcp_conn; @@ -480,7 +484,8 @@ ares_status_t ares__servers_update(ares_channel_t *channel, ares_status_t ares__sconfig_append(ares__llist_t **sconfig, const struct ares_addr *addr, unsigned short udp_port, - unsigned short tcp_port); + unsigned short tcp_port, + const char *ll_iface); ares_status_t ares__sconfig_append_fromstr(ares__llist_t **sconfig, const char *str, ares_bool_t ignore_invalid); @@ -565,11 +570,17 @@ ares_status_t ares__dns_name_write(ares__buf_t *buf, ares__llist_t **list, (x && x->lookups && ares__slist_len(x->servers) > 0 && x->ndots > 0 && \ x->timeout > 0 && x->tries > 0) +ares_bool_t ares__subnet_match(const struct ares_addr *addr, + const struct ares_addr *subnet, + unsigned char netmask); +ares_bool_t ares__addr_is_linklocal(const struct ares_addr *addr); + size_t ares__round_up_pow2(size_t n); size_t ares__log2(size_t n); size_t ares__pow(size_t x, size_t y); size_t ares__count_digits(size_t n); size_t ares__count_hexdigits(size_t n); +unsigned char ares__count_bits_u8(unsigned char x); void ares__qcache_destroy(ares__qcache_t *cache); ares_status_t ares__qcache_create(ares_rand_state *rand_state, unsigned int max_ttl, diff --git a/src/lib/ares_str.c b/src/lib/ares_str.c index 11794774..b44f4819 100644 --- a/src/lib/ares_str.c +++ b/src/lib/ares_str.c @@ -93,3 +93,19 @@ size_t ares_strcpy(char *dest, const char *src, size_t dest_size) dest[len] = 0; return len; } + +ares_bool_t ares_str_isnum(const char *str) +{ + size_t i; + + if (str == NULL || *str == 0) { + return ARES_FALSE; + } + + for (i = 0; str[i] != 0; i++) { + if (str[i] < '0' || str[i] > '9') { + return ARES_FALSE; + } + } + return ARES_TRUE; +} diff --git a/src/lib/ares_str.h b/src/lib/ares_str.h index fa5d5509..c1cfa9f7 100644 --- a/src/lib/ares_str.h +++ b/src/lib/ares_str.h @@ -28,10 +28,11 @@ #define HEADER_CARES_STRDUP_H #include "ares_setup.h" +#include "ares.h" -char *ares_strdup(const char *s1); +char *ares_strdup(const char *s1); -size_t ares_strlen(const char *str); +size_t ares_strlen(const char *str); /*! Copy string from source to destination with destination buffer size * provided. The destination is guaranteed to be null terminated, if the @@ -43,7 +44,8 @@ size_t ares_strlen(const char *str); * \param[in] dest_size Size of destination buffer * \return String length. Will be at most dest_size-1 */ -size_t ares_strcpy(char *dest, const char *src, size_t dest_size); +size_t ares_strcpy(char *dest, const char *src, size_t dest_size); +ares_bool_t ares_str_isnum(const char *str); #endif /* HEADER_CARES_STRDUP_H */ diff --git a/src/lib/ares_sysconfig.c b/src/lib/ares_sysconfig.c index da329da2..815f487b 100644 --- a/src/lib/ares_sysconfig.c +++ b/src/lib/ares_sysconfig.c @@ -171,7 +171,7 @@ typedef struct { /* Room enough for the string form of any IPv4 or IPv6 address that * ares_inet_ntop() will create. Based on the existing c-ares practice. */ - char text[INET6_ADDRSTRLEN + 8]; /* [%s]:NNNNN */ + char text[INET6_ADDRSTRLEN + 8 + 64]; /* [%s]:NNNNN%iface */ } Address; /* Sort Address values \a left and \a right by metric, returning the usual @@ -416,6 +416,9 @@ static ares_bool_t get_DNS_Windows(char **outptr) ntohs(namesrvr.sa4->sin_port)); ++addressesIndex; } else if (namesrvr.sa->sa_family == AF_INET6) { + unsigned int ll_scope = 0; + struct ares_addr addr; + if (memcmp(&namesrvr.sa6->sin6_addr, &ares_in6addr_any, sizeof(namesrvr.sa6->sin6_addr)) == 0) { continue; @@ -433,6 +436,14 @@ static ares_bool_t get_DNS_Windows(char **outptr) addressesSize = newSize; } + /* See if its link-local */ + memset(&addr, 0, sizeof(addr)); + addr.family = AF_INET6; + memcpy(&addr.addr.addr6, &namesrvr.sa6->sin6_addr, 16); + if (ares__addr_is_linklocal(&addr)) { + ll_scope = ipaaEntry->Ipv6IfIndex; + } + addresses[addressesIndex].metric = getBestRouteMetric(&ipaaEntry->Luid, (SOCKADDR_INET *)(namesrvr.sa), ipaaEntry->Ipv6Metric); @@ -444,9 +455,16 @@ static ares_bool_t get_DNS_Windows(char **outptr) sizeof(ipaddr))) { continue; } - snprintf(addresses[addressesIndex].text, - sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr, - ntohs(namesrvr.sa6->sin6_port)); + + if (ll_scope) { + snprintf(addresses[addressesIndex].text, + sizeof(addresses[addressesIndex].text), "[%s]:%u%%%u", ipaddr, + ntohs(namesrvr.sa6->sin6_port), ll_scope); + } else { + snprintf(addresses[addressesIndex].text, + sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr, + ntohs(namesrvr.sa6->sin6_port)); + } ++addressesIndex; } else { /* Skip non-IPv4/IPv6 addresses completely. */ @@ -661,7 +679,7 @@ static ares_status_t ares__init_sysconfig_mvs(ares_sysconfig_t *sysconfig) status = ares__sconfig_append(&sysconfig->sconfig, &addr, htons(addr_in->sin_port), - htons(addr_in->sin_port)); + htons(addr_in->sin_port), NULL); if (status != ARES_SUCCESS) { return status; @@ -678,7 +696,7 @@ static ares_status_t ares__init_sysconfig_mvs(ares_sysconfig_t *sysconfig) status = ares__sconfig_append(&sysconfig->sconfig, &addr, htons(addr_in->sin_port), - htons(addr_in->sin_port)); + htons(addr_in->sin_port), NULL); if (status != ARES_SUCCESS) { return status; @@ -742,7 +760,7 @@ static ares_status_t ares__init_sysconfig_watt32(ares_sysconfig_t *sysconfig) addr.family = AF_INET; addr.addr.addr4.s_addr = htonl(def_nameservers[i]); - status = ares__sconfig_append(&sysconfig->sconfig, &addr, 0, 0); + status = ares__sconfig_append(&sysconfig->sconfig, &addr, 0, 0, NULL); if (status != ARES_SUCCESS) { return status; @@ -825,6 +843,7 @@ static ares_status_t ares__init_sysconfig_libresolv(ares_sysconfig_t *sysconfig) int nscount; size_t i; size_t entries = 0; + ares__buf_t *ipbuf = NULL; memset(&res, 0, sizeof(res)); @@ -836,8 +855,9 @@ static ares_status_t ares__init_sysconfig_libresolv(ares_sysconfig_t *sysconfig) for (i = 0; i < (size_t)nscount; ++i) { char ipaddr[INET6_ADDRSTRLEN] = ""; - char ipaddr_port[INET6_ADDRSTRLEN + 8]; /* [%s]:NNNNN */ - unsigned short port = 0; + char *ipstr = NULL; + unsigned short port = 0; + unsigned int ll_scope = 0; sa_family_t family = addr[i].sin.sin_family; if (family == AF_INET) { @@ -845,19 +865,68 @@ static ares_status_t ares__init_sysconfig_libresolv(ares_sysconfig_t *sysconfig) port = ntohs(addr[i].sin.sin_port); } else if (family == AF_INET6) { ares_inet_ntop(family, &addr[i].sin6.sin6_addr, ipaddr, sizeof(ipaddr)); - port = ntohs(addr[i].sin6.sin6_port); + port = ntohs(addr[i].sin6.sin6_port); + ll_scope = addr[i].sin6.sin6_scope_id; } else { continue; } + + /* [ip]:port%iface */ + ipbuf = ares__buf_create(); + if (ipbuf == NULL) { + status = ARES_ENOMEM; + goto done; + } + + status = ares__buf_append_str(ipbuf, "["); + if (status != ARES_SUCCESS) { + goto done; + } + + status = ares__buf_append_str(ipbuf, ipaddr); + if (status != ARES_SUCCESS) { + goto done; + } + + status = ares__buf_append_str(ipbuf, "]"); + if (status != ARES_SUCCESS) { + goto done; + } + if (port) { - snprintf(ipaddr_port, sizeof(ipaddr_port), "[%s]:%u", ipaddr, port); - } else { - snprintf(ipaddr_port, sizeof(ipaddr_port), "%s", ipaddr); + status = ares__buf_append_str(ipbuf, ":"); + if (status != ARES_SUCCESS) { + goto done; + } + status = ares__buf_append_num_dec(ipbuf, port, 0); + if (status != ARES_SUCCESS) { + goto done; + } + } + + if (ll_scope) { + status = ares__buf_append_str(ipbuf, "%"); + if (status != ARES_SUCCESS) { + goto done; + } + status = ares__buf_append_num_dec(ipbuf, ll_scope, 0); + if (status != ARES_SUCCESS) { + goto done; + } + } + + ipstr = ares__buf_finish_str(ipbuf, NULL); + ipbuf = NULL; + if (ipstr == NULL) { + status = ARES_ENOMEM; + goto done; } status = - ares__sconfig_append_fromstr(&sysconfig->sconfig, ipaddr_port, ARES_TRUE); + ares__sconfig_append_fromstr(&sysconfig->sconfig, ipstr, ARES_TRUE); + + ares_free(ipstr); if (status != ARES_SUCCESS) { goto done; } @@ -908,6 +977,7 @@ static ares_status_t ares__init_sysconfig_libresolv(ares_sysconfig_t *sysconfig) } done: + ares__buf_destroy(ipbuf); res_ndestroy(&res); return status; } diff --git a/src/lib/ares_update_servers.c b/src/lib/ares_update_servers.c index 550dc70e..5f27e57d 100644 --- a/src/lib/ares_update_servers.c +++ b/src/lib/ares_update_servers.c @@ -30,16 +30,41 @@ #ifdef HAVE_ARPA_INET_H # include #endif +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#ifdef HAVE_NET_IF_H +# include +#endif + +#if defined(USE_WINSOCK) +# if defined(HAVE_IPHLPAPI_H) +# include +# endif +# if defined(HAVE_NETIOAPI_H) +# include +# endif +#endif #include "ares.h" #include "ares_data.h" #include "ares_inet_net_pton.h" #include "ares_private.h" +#ifndef IFNAMSIZ +# define IFNAMSIZ 64 +#endif + typedef struct { struct ares_addr addr; unsigned short tcp_port; unsigned short udp_port; + + char ll_iface[IFNAMSIZ]; + unsigned int ll_scope; } ares_sconfig_t; static ares_bool_t ares__addr_match(const struct ares_addr *addr1, @@ -71,32 +96,53 @@ static ares_bool_t ares__addr_match(const struct ares_addr *addr1, return ARES_FALSE; } -/* Validate that the ip address matches the subnet (network base and network - * mask) specified. Addresses are specified in standard Network Byte Order as - * 16 bytes, and the netmask is 0 to 128 (bits). - */ -static ares_bool_t ares_ipv6_subnet_matches(const unsigned char netbase[16], - unsigned char netmask, - const unsigned char *ipaddr) +ares_bool_t ares__subnet_match(const struct ares_addr *addr, + const struct ares_addr *subnet, + unsigned char netmask) { - unsigned char mask[16] = { 0 }; - unsigned char i; + const unsigned char *addr_ptr; + const unsigned char *subnet_ptr; + size_t len; + size_t i; + + if (addr == NULL || subnet == NULL) { + return ARES_FALSE; + } - /* Misuse */ - if (netmask > 128) { + if (addr->family != subnet->family) { return ARES_FALSE; } - /* Quickly set whole bytes */ - memset(mask, 0xFF, netmask / 8); + if (addr->family == AF_INET) { + addr_ptr = (const unsigned char *)&addr->addr.addr4; + subnet_ptr = (const unsigned char *)&subnet->addr.addr4; + len = 4; + + if (netmask > 32) { + return ARES_FALSE; + } + } else if (addr->family == AF_INET6) { + addr_ptr = (const unsigned char *)&addr->addr.addr6; + subnet_ptr = (const unsigned char *)&subnet->addr.addr6; + len = 16; - /* Set remaining bits */ - if (netmask % 8 && netmask < 128 /* Silence coverity */) { - mask[netmask / 8] = (unsigned char)(0xff << (8 - (netmask % 8))); + if (netmask > 128) { + return ARES_FALSE; + } + } else { + return ARES_FALSE; } - for (i = 0; i < 16; i++) { - if ((netbase[i] & mask[i]) != (ipaddr[i] & mask[i])) { + for (i = 0; i < len && netmask > 0; i++) { + unsigned char mask = 0xff; + if (netmask < 8) { + mask <<= (8 - netmask); + netmask = 0; + } else { + netmask -= 8; + } + + if ((addr_ptr[i] & mask) != (subnet_ptr[i] & mask)) { return ARES_FALSE; } } @@ -104,6 +150,20 @@ static ares_bool_t ares_ipv6_subnet_matches(const unsigned char netbase[16], return ARES_TRUE; } +ares_bool_t ares__addr_is_linklocal(const struct ares_addr *addr) +{ + struct ares_addr subnet; + const unsigned char subnetaddr[16] = { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + + /* fe80::/10 */ + subnet.family = AF_INET6; + memcpy(&subnet.addr.addr6, subnetaddr, 16); + + return ares__subnet_match(addr, &subnet, 10); +} + static ares_bool_t ares_server_blacklisted(const struct ares_addr *addr) { /* A list of blacklisted IPv6 subnets. */ @@ -128,9 +188,10 @@ static ares_bool_t ares_server_blacklisted(const struct ares_addr *addr) /* See if ipaddr matches any of the entries in the blacklist. */ for (i = 0; i < sizeof(blacklist_v6) / sizeof(*blacklist_v6); i++) { - if (ares_ipv6_subnet_matches(blacklist_v6[i].netbase, - blacklist_v6[i].netmask, - (const unsigned char *)&addr->addr.addr6)) { + struct ares_addr subnet; + subnet.family = AF_INET6; + memcpy(&subnet.addr.addr6, blacklist_v6[i].netbase, 16); + if (ares__subnet_match(addr, &subnet, blacklist_v6[i].netmask)) { return ARES_TRUE; } } @@ -144,8 +205,9 @@ static ares_bool_t ares_server_blacklisted(const struct ares_addr *addr) * [ipaddr] * [ipaddr]:port * - * TODO: in the future we need to add % and # syntax modifications to support - * the link-local interface name and domain, respectively. + * Modifiers: %iface + * + * TODO: #domain modifier * * If a port is not specified, will set port to 0. * @@ -155,14 +217,13 @@ static ares_bool_t ares_server_blacklisted(const struct ares_addr *addr) * Returns an error code on failure, else ARES_SUCCESS */ -static ares_status_t parse_dnsaddrport(ares__buf_t *buf, struct ares_addr *host, - unsigned short *port) +static ares_status_t parse_nameserver(ares__buf_t *buf, ares_sconfig_t *sconfig) { ares_status_t status; char ipaddr[INET6_ADDRSTRLEN] = ""; size_t addrlen; - *port = 0; + memset(sconfig, 0, sizeof(*sconfig)); /* Consume any leading whitespace */ ares__buf_consume_whitespace(buf, ARES_TRUE); @@ -222,8 +283,8 @@ static ares_status_t parse_dnsaddrport(ares__buf_t *buf, struct ares_addr *host, } /* Convert ip address from string to network byte order */ - host->family = AF_UNSPEC; - if (ares_dns_pton(ipaddr, host, &addrlen) == NULL) { + sconfig->addr.family = AF_UNSPEC; + if (ares_dns_pton(ipaddr, &sconfig->addr, &addrlen) == NULL) { return ARES_EBADSTR; } @@ -247,7 +308,30 @@ static ares_status_t parse_dnsaddrport(ares__buf_t *buf, struct ares_addr *host, return status; } - *port = (unsigned short)atoi(portstr); + sconfig->udp_port = (unsigned short)atoi(portstr); + sconfig->tcp_port = sconfig->udp_port; + } + + /* Pull off interface modifier */ + if (ares__buf_begins_with(buf, (const unsigned char *)"%", 1)) { + const unsigned char iface_charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789.-_\\:{}"; + /* Consume % */ + ares__buf_consume(buf, 1); + + ares__buf_tag(buf); + + if (ares__buf_consume_charset(buf, iface_charset, sizeof(iface_charset)) == + 0) { + return ARES_EBADSTR; + } + + status = ares__buf_tag_fetch_string(buf, sconfig->ll_iface, + sizeof(sconfig->ll_iface)); + if (status != ARES_SUCCESS) { + return status; + } } /* Consume any trailing whitespace so we can bail out if there is something @@ -261,10 +345,40 @@ static ares_status_t parse_dnsaddrport(ares__buf_t *buf, struct ares_addr *host, return ARES_SUCCESS; } +static ares_status_t ares__sconfig_linklocal(ares_sconfig_t *s, + const char *ll_iface) +{ + unsigned int ll_scope = 0; + + if (ares_str_isnum(ll_iface)) { + char ifname[IFNAMSIZ] = ""; + ll_scope = (unsigned int)atoi(ll_iface); + if (ares__if_indextoname(ll_scope, ifname, sizeof(ifname)) == NULL) { + DEBUGF(fprintf(stderr, "Interface %s for ipv6 Link Local not found\n", + ll_iface)); + return ARES_ENOTFOUND; + } + ares_strcpy(s->ll_iface, ifname, sizeof(s->ll_iface)); + s->ll_scope = ll_scope; + return ARES_SUCCESS; + } + + ll_scope = ares__if_nametoindex(ll_iface); + if (ll_scope == 0) { + DEBUGF(fprintf(stderr, "Interface %s for ipv6 Link Local not found\n", + ll_iface)); + return ARES_ENOTFOUND; + } + ares_strcpy(s->ll_iface, ll_iface, sizeof(s->ll_iface)); + s->ll_scope = ll_scope; + return ARES_SUCCESS; +} + ares_status_t ares__sconfig_append(ares__llist_t **sconfig, const struct ares_addr *addr, unsigned short udp_port, - unsigned short tcp_port) + unsigned short tcp_port, + const char *ll_iface) { ares_sconfig_t *s; ares_status_t status; @@ -295,6 +409,16 @@ ares_status_t ares__sconfig_append(ares__llist_t **sconfig, s->udp_port = udp_port; s->tcp_port = tcp_port; + /* Handle link-local enumeration */ + if (ares_strlen(ll_iface) && ares__addr_is_linklocal(&s->addr)) { + status = ares__sconfig_linklocal(s, ll_iface); + /* Silently ignore this entry */ + if (status != ARES_SUCCESS) { + status = ARES_SUCCESS; + goto fail; + } + } + if (ares__llist_insert_last(*sconfig, s) == NULL) { status = ARES_ENOMEM; goto fail; @@ -350,11 +474,10 @@ ares_status_t ares__sconfig_append_fromstr(ares__llist_t **sconfig, for (node = ares__llist_node_first(list); node != NULL; node = ares__llist_node_next(node)) { - ares__buf_t *entry = ares__llist_node_val(node); - struct ares_addr host; - unsigned short port; + ares__buf_t *entry = ares__llist_node_val(node); + ares_sconfig_t s; - status = parse_dnsaddrport(entry, &host, &port); + status = parse_nameserver(entry, &s); if (status != ARES_SUCCESS) { if (ignore_invalid) { continue; @@ -363,7 +486,8 @@ ares_status_t ares__sconfig_append_fromstr(ares__llist_t **sconfig, } } - status = ares__sconfig_append(sconfig, &host, port, port); + status = ares__sconfig_append(sconfig, &s.addr, s.udp_port, s.tcp_port, + s.ll_iface); if (status != ARES_SUCCESS) { goto done; } @@ -476,6 +600,12 @@ static ares_status_t ares__server_create(ares_channel_t *channel, sizeof(server->addr.addr.addr6)); } + /* Copy over link-local settings */ + if (ares_strlen(sconfig->ll_iface)) { + ares_strcpy(server->ll_iface, sconfig->ll_iface, sizeof(server->ll_iface)); + server->ll_scope = sconfig->ll_scope; + } + server->tcp_parser = ares__buf_create(); if (server->tcp_parser == NULL) { status = ARES_ENOMEM; @@ -594,6 +724,14 @@ ares_status_t ares__servers_update(ares_channel_t *channel, if (snode != NULL) { struct server_state *server = ares__slist_node_val(snode); + /* Copy over link-local settings. Its possible some of this data has + * changed, maybe ... */ + if (ares_strlen(sconfig->ll_iface)) { + ares_strcpy(server->ll_iface, sconfig->ll_iface, + sizeof(server->ll_iface)); + server->ll_scope = sconfig->ll_scope; + } + if (server->idx != idx) { server->idx = idx; /* Index changed, reinsert node, doesn't require any memory @@ -975,3 +1113,85 @@ int ares_set_servers_ports_csv(ares_channel_t *channel, const char *_csv) /* NOTE: lock is in ares__servers_update() */ return (int)set_servers_csv(channel, _csv); } + +char *ares_get_servers_csv(ares_channel_t *channel) +{ + ares__buf_t *buf = NULL; + char *out = NULL; + ares__slist_node_t *node; + + ares__channel_lock(channel); + + buf = ares__buf_create(); + if (buf == NULL) { + goto done; + } + + for (node = ares__slist_node_first(channel->servers); node != NULL; + node = ares__slist_node_next(node)) { + ares_status_t status; + const struct server_state *server = ares__slist_node_val(node); + char addr[64]; + + if (ares__buf_len(buf)) { + status = ares__buf_append_byte(buf, ','); + if (status != ARES_SUCCESS) { + goto done; + } + } + + /* ipv4addr or [ipv6addr] */ + if (server->addr.family == AF_INET6) { + status = ares__buf_append_byte(buf, '['); + if (status != ARES_SUCCESS) { + goto done; + } + } + + ares_inet_ntop(server->addr.family, &server->addr.addr, addr, sizeof(addr)); + + status = ares__buf_append_str(buf, addr); + if (status != ARES_SUCCESS) { + goto done; + } + + if (server->addr.family == AF_INET6) { + status = ares__buf_append_byte(buf, ']'); + if (status != ARES_SUCCESS) { + goto done; + } + } + + /* :port */ + status = ares__buf_append_byte(buf, ':'); + if (status != ARES_SUCCESS) { + goto done; + } + + status = ares__buf_append_num_dec(buf, server->udp_port, 0); + if (status != ARES_SUCCESS) { + goto done; + } + + /* %iface */ + if (ares_strlen(server->ll_iface)) { + status = ares__buf_append_byte(buf, '%'); + if (status != ARES_SUCCESS) { + goto done; + } + + status = ares__buf_append_str(buf, server->ll_iface); + if (status != ARES_SUCCESS) { + goto done; + } + } + } + + out = ares__buf_finish_str(buf, NULL); + buf = NULL; + +done: + ares__channel_unlock(channel); + ares__buf_destroy(buf); + return out; +} diff --git a/src/lib/config-win32.h b/src/lib/config-win32.h index 2701f173..2a16d426 100644 --- a/src/lib/config-win32.h +++ b/src/lib/config-win32.h @@ -363,6 +363,17 @@ # define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 #endif +#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600) && !defined(__WATCOMC__) +/* Define if you have if_nametoindex() */ +# define HAVE_IF_NAMETOINDEX 1 +/* Define if you have if_indextoname() */ +# define HAVE_IF_INDEXTONAME 1 +/* Define to 1 if you have the `ConvertInterfaceIndexToLuid' function. */ +# define HAVE_CONVERTINTERFACEINDEXTOLUID 1 +/* Define to 1 if you have the `ConvertInterfaceLuidToNameA' function. */ +# define HAVE_CONVERTINTERFACELUIDTONAMEA 1 +#endif + /* ---------------------------------------------------------------- */ /* Win CE */ /* ---------------------------------------------------------------- */ diff --git a/test/ares-test-init.cc b/test/ares-test-init.cc index 46baff95..cbddfac0 100644 --- a/test/ares-test-init.cc +++ b/test/ares-test-init.cc @@ -391,8 +391,8 @@ CONTAINED_TEST_F(LibraryTest, ContainerChannelInit, "myhostname", "mydomainname.org", filelist) { ares_channel_t *channel = nullptr; EXPECT_EQ(ARES_SUCCESS, ares_init(&channel)); - std::vector actual = GetNameServers(channel); - std::vector expected = {"1.2.3.4:53"}; + std::string actual = GetNameServers(channel); + std::string expected = "1.2.3.4:53"; EXPECT_EQ(expected, actual); EXPECT_EQ(2, channel->ndomains); EXPECT_EQ(std::string("first.com"), std::string(channel->domains[0])); @@ -640,11 +640,9 @@ CONTAINED_TEST_F(LibraryTest, ContainerBlacklistedIpv6, "myhostname", "mydomainname.org", blacklistedIpv6) { ares_channel_t *channel = nullptr; EXPECT_EQ(ARES_SUCCESS, ares_init(&channel)); - std::vector actual = GetNameServers(channel); - std::vector expected = { - "254.192.1.1:53", - "[ffc0:0000:0000:0000:0000:0000:0000:c001]:53" - }; + std::string actual = GetNameServers(channel); + std::string expected = "254.192.1.1:53," + "[ffc0::c001]:53"; EXPECT_EQ(expected, actual); EXPECT_EQ(1, channel->ndomains); @@ -662,8 +660,8 @@ CONTAINED_TEST_F(LibraryTest, ContainerMultiResolvInit, "myhostname", "mydomainname.org", multiresolv) { ares_channel_t *channel = nullptr; EXPECT_EQ(ARES_SUCCESS, ares_init(&channel)); - std::vector actual = GetNameServers(channel); - std::vector expected = {"[0001:0000:0000:0000:0000:0000:0000:0002]:53"}; + std::string actual = GetNameServers(channel); + std::string expected = "[1::2]:53"; EXPECT_EQ(expected, actual); EXPECT_EQ(1, channel->ndomains); @@ -693,8 +691,8 @@ CONTAINED_TEST_F(LibraryTest, ContainerEmptyInit, "host.domain.org", "domain.org", empty) { ares_channel_t *channel = nullptr; EXPECT_EQ(ARES_SUCCESS, ares_init(&channel)); - std::vector actual = GetNameServers(channel); - std::vector expected = {"127.0.0.1:53"}; + std::string actual = GetNameServers(channel); + std::string expected = "127.0.0.1:53"; EXPECT_EQ(expected, actual); EXPECT_EQ(1, channel->ndomains); diff --git a/test/ares-test-misc.cc b/test/ares-test-misc.cc index 7bdade67..0dbccd0d 100644 --- a/test/ares-test-misc.cc +++ b/test/ares-test-misc.cc @@ -25,11 +25,9 @@ namespace ares { namespace test { TEST_F(DefaultChannelTest, GetServers) { - std::vector servers = GetNameServers(channel_); + std::string servers = GetNameServers(channel_); if (verbose) { - for (const std::string& server : servers) { - std::cerr << "Nameserver: " << server << std::endl; - } + std::cerr << "Nameserver: " << servers << std::endl; } } @@ -52,7 +50,7 @@ TEST_F(DefaultChannelTest, SetServers) { * See: https://github.com/nodejs/node/pull/50800 */ EXPECT_EQ(ARES_SUCCESS, ares_set_servers(channel_, nullptr)); - std::vector expected_empty = { }; + std::string expected_empty = ""; EXPECT_EQ(expected_empty, GetNameServers(channel_)); HostResult result; ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result); @@ -72,7 +70,7 @@ TEST_F(DefaultChannelTest, SetServers) { EXPECT_EQ(ARES_ENODATA, ares_set_servers(nullptr, &server1)); EXPECT_EQ(ARES_SUCCESS, ares_set_servers(channel_, &server1)); - std::vector expected = {"1.2.3.4:53", "2.3.4.5:53"}; + std::string expected = "1.2.3.4:53,2.3.4.5:53"; EXPECT_EQ(expected, GetNameServers(channel_)); } @@ -84,7 +82,7 @@ TEST_F(DefaultChannelTest, SetServersPorts) { * See: https://github.com/nodejs/node/pull/50800 */ EXPECT_EQ(ARES_SUCCESS, ares_set_servers_ports(channel_, nullptr)); - std::vector expected_empty = { }; + std::string expected_empty = ""; EXPECT_EQ(expected_empty, GetNameServers(channel_)); struct ares_addr_port_node server1; @@ -102,7 +100,7 @@ TEST_F(DefaultChannelTest, SetServersPorts) { EXPECT_EQ(ARES_ENODATA, ares_set_servers_ports(nullptr, &server1)); EXPECT_EQ(ARES_SUCCESS, ares_set_servers_ports(channel_, &server1)); - std::vector expected = {"1.2.3.4:111", "2.3.4.5:53"}; + std::string expected = "1.2.3.4:111,2.3.4.5:53"; EXPECT_EQ(expected, GetNameServers(channel_)); } @@ -120,17 +118,15 @@ TEST_F(DefaultChannelTest, SetServersCSV) { * See: https://github.com/nodejs/node/pull/50800 */ EXPECT_EQ(ARES_SUCCESS, ares_set_servers_csv(channel_, NULL)); - std::vector expected_empty = { }; + std::string expected_empty = ""; EXPECT_EQ(expected_empty, GetNameServers(channel_)); EXPECT_EQ(ARES_SUCCESS, ares_set_servers_csv(channel_, "")); EXPECT_EQ(expected_empty, GetNameServers(channel_)); - - EXPECT_EQ(ARES_SUCCESS, ares_set_servers_csv(channel_, "1.2.3.4,0102:0304:0506:0708:0910:1112:1314:1516,2.3.4.5")); - std::vector expected = {"1.2.3.4:53", "[0102:0304:0506:0708:0910:1112:1314:1516]:53", "2.3.4.5:53"}; + std::string expected = "1.2.3.4:53,[102:304:506:708:910:1112:1314:1516]:53,2.3.4.5:53"; EXPECT_EQ(expected, GetNameServers(channel_)); // Same, with spaces @@ -138,10 +134,15 @@ TEST_F(DefaultChannelTest, SetServersCSV) { ares_set_servers_csv(channel_, "1.2.3.4 , [0102:0304:0506:0708:0910:1112:1314:1516]:53, 2.3.4.5")); EXPECT_EQ(expected, GetNameServers(channel_)); + // Ignore invalid link-local interface, keep rest. + EXPECT_EQ(ARES_SUCCESS, + ares_set_servers_csv(channel_, "1.2.3.4 , [0102:0304:0506:0708:0910:1112:1314:1516]:53, [fe80::1]:53%iface0, 2.3.4.5")); + EXPECT_EQ(expected, GetNameServers(channel_)); + // Same, with ports EXPECT_EQ(ARES_SUCCESS, ares_set_servers_ports_csv(channel_, "1.2.3.4:54,[0102:0304:0506:0708:0910:1112:1314:1516]:80,2.3.4.5:55")); - std::vector expected2 = {"1.2.3.4:54", "[0102:0304:0506:0708:0910:1112:1314:1516]:80", "2.3.4.5:55"}; + std::string expected2 = {"1.2.3.4:54,[102:304:506:708:910:1112:1314:1516]:80,2.3.4.5:55"}; EXPECT_EQ(expected2, GetNameServers(channel_)); // Should survive duplication diff --git a/test/ares-test.cc b/test/ares-test.cc index 5e383961..108891ab 100644 --- a/test/ares-test.cc +++ b/test/ares-test.cc @@ -544,10 +544,7 @@ MockChannelOptsTest::MockChannelOptsTest(int count, } if (verbose) { std::cerr << "Configured library with servers:"; - std::vector servers = GetNameServers(channel_); - for (const auto& server : servers) { - std::cerr << " " << server; - } + std::cerr << GetNameServers(channel_); std::cerr << std::endl; } } @@ -779,38 +776,14 @@ void NameInfoCallback(void *data, int status, int timeouts, if (verbose) std::cerr << "NameInfoCallback(" << *result << ")" << std::endl; } -std::vector GetNameServers(ares_channel_t *channel) { - struct ares_addr_port_node* servers = nullptr; - EXPECT_EQ(ARES_SUCCESS, ares_get_servers_ports(channel, &servers)); - struct ares_addr_port_node* server = servers; - std::vector results; - while (server) { - std::stringstream ss; - switch (server->family) { - case AF_INET: - ss << AddressToString((char*)&server->addr.addr4, 4); - break; - case AF_INET6: - if (server->udp_port != 0) { - ss << '['; - } - ss << AddressToString((char*)&server->addr.addr6, 16); - if (server->udp_port != 0) { - ss << ']'; - } - break; - default: - results.push_back(""); - break; - } - if (server->udp_port != 0) { - ss << ":" << server->udp_port; - } - results.push_back(ss.str()); - server = server->next; - } - if (servers) ares_free_data(servers); - return results; +std::string GetNameServers(ares_channel_t *channel) { + char *csv = ares_get_servers_csv(channel); + EXPECT_NE((char *)NULL, csv); + + std::string servers(csv); + + ares_free_string(csv); + return servers; } TransientDir::TransientDir(const std::string& dirname) : dirname_(dirname) { diff --git a/test/ares-test.h b/test/ares-test.h index 9bd26202..a04e8db6 100644 --- a/test/ares-test.h +++ b/test/ares-test.h @@ -444,7 +444,7 @@ void AddrInfoCallback(void *data, int status, int timeouts, struct ares_addrinfo *res); // Retrieve the name servers used by a channel. -std::vector GetNameServers(ares_channel_t *channel); +std::string GetNameServers(ares_channel_t *channel); // RAII class to temporarily create a directory of a given name. class TransientDir {