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)
pull/651/head
Brad House 12 months ago committed by GitHub
parent 873b09c7fa
commit d974c556bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      CMakeLists.txt
  2. 4
      appveyor.yml
  3. 7
      configure.ac
  4. 1
      docs/Makefile.inc
  5. 4
      docs/ares_get_servers_csv.3
  6. 5
      docs/ares_set_servers.3
  7. 47
      docs/ares_set_servers_csv.3
  8. 1
      include/ares.h
  9. 87
      m4/cares-functions.m4
  10. 2
      src/lib/Makefile.inc
  11. 594
      src/lib/ares__iface_ips.c
  12. 139
      src/lib/ares__iface_ips.h
  13. 32
      src/lib/ares__socket.c
  14. 15
      src/lib/ares_config.h.cmake
  15. 14
      src/lib/ares_init.c
  16. 11
      src/lib/ares_math.c
  17. 19
      src/lib/ares_private.h
  18. 16
      src/lib/ares_str.c
  19. 2
      src/lib/ares_str.h
  20. 88
      src/lib/ares_sysconfig.c
  21. 288
      src/lib/ares_update_servers.c
  22. 11
      src/lib/config-win32.h
  23. 20
      test/ares-test-init.cc
  24. 27
      test/ares-test-misc.cc
  25. 45
      test/ares-test.cc
  26. 2
      test/ares-test.h

@ -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/stat.h HAVE_SYS_STAT_H)
CHECK_INCLUDE_FILES (sys/time.h HAVE_SYS_TIME_H) CHECK_INCLUDE_FILES (sys/time.h HAVE_SYS_TIME_H)
CHECK_INCLUDE_FILES (sys/uio.h HAVE_SYS_UIO_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 (time.h HAVE_TIME_H)
CHECK_INCLUDE_FILES (dlfcn.h HAVE_DLFCN_H) CHECK_INCLUDE_FILES (dlfcn.h HAVE_DLFCN_H)
CHECK_INCLUDE_FILES (unistd.h HAVE_UNISTD_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_ARPA_NAMESER_H arpa/nameser.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_NETDB_H netdb.h) CARES_EXTRAINCLUDE_IFSET (HAVE_NETDB_H netdb.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_NET_IF_H net/if.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_IN_H netinet/in.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_NETINET_TCP_H netinet/tcp.h) CARES_EXTRAINCLUDE_IFSET (HAVE_NETINET_TCP_H netinet/tcp.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SIGNAL_H signal.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 (getservbyname_r "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETSERVBYNAME_R)
CHECK_SYMBOL_EXISTS (gettimeofday "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETTIMEOFDAY) 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_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) CHECK_SYMBOL_EXISTS (inet_net_pton "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_INET_NET_PTON)
IF (NOT WIN32) IF (NOT WIN32)
# Disabled on Windows, because these functions are only really supported on Windows # 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 (writev "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_WRITEV)
CHECK_SYMBOL_EXISTS (arc4random_buf "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_ARC4RANDOM_BUF) 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 (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 # 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. # from libc. We need to perform a link test instead of a header/symbol test.

@ -1,7 +1,7 @@
# Copyright (C) The c-ares project and its contributors # Copyright (C) The c-ares project and its contributors
# SPDX-License-Identifier: MIT # 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. # Github/Bitbucket only: get source code for one particular commit as zip archive, instead of git clone'ing.
shallow_clone: true shallow_clone: true
@ -27,7 +27,7 @@ environment:
- COMPILER: MSVC - COMPILER: MSVC
BUILDTOOL: CMAKE BUILDTOOL: CMAKE
TESTTYPE: NONE 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 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 CMAKE_EXTRA_OPTIONS: -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -A x64
TESTDIRSUFFIX: Release\ TESTDIRSUFFIX: Release\

@ -581,6 +581,7 @@ AC_CHECK_HEADERS(
netinet/in.h \ netinet/in.h \
netinet/tcp.h \ netinet/tcp.h \
net/if.h \ net/if.h \
ifaddrs.h \
errno.h \ errno.h \
socket.h \ socket.h \
strings.h \ strings.h \
@ -752,6 +753,7 @@ CARES_CHECK_FUNC_STRNICMP
CARES_CHECK_FUNC_WRITEV CARES_CHECK_FUNC_WRITEV
CARES_CHECK_FUNC_ARC4RANDOM_BUF CARES_CHECK_FUNC_ARC4RANDOM_BUF
CARES_CHECK_FUNC_STAT CARES_CHECK_FUNC_STAT
CARES_CHECK_FUNC_GETIFADDRS
dnl check for AF_INET6 dnl check for AF_INET6
CARES_CHECK_CONSTANT( CARES_CHECK_CONSTANT(
@ -921,7 +923,10 @@ AC_CHECK_MEMBER(struct addrinfo.ai_flags,
AC_CHECK_FUNCS([bitncmp \ AC_CHECK_FUNCS([bitncmp \
gettimeofday \ gettimeofday \
if_indextoname if_indextoname \
if_nametoindex \
ConvertInterfaceIndexToLuid \
ConvertInterfaceLuidToNameA
],[ ],[
],[ ],[
func="$ac_func" func="$ac_func"

@ -74,6 +74,7 @@ MANPAGES = ares_cancel.3 \
ares_free_string.3 \ ares_free_string.3 \
ares_freeaddrinfo.3 \ ares_freeaddrinfo.3 \
ares_get_servers.3 \ ares_get_servers.3 \
ares_get_servers_csv.3 \
ares_get_servers_ports.3 \ ares_get_servers_ports.3 \
ares_getaddrinfo.3 \ ares_getaddrinfo.3 \
ares_gethostbyaddr.3 \ ares_gethostbyaddr.3 \

@ -0,0 +1,4 @@
.\"
.\" Copyright (C) Daniel Stenberg
.\" SPDX-License-Identifier: MIT
.so man3/ares_set_servers_csv.3

@ -89,6 +89,11 @@ c-ares library initialization not yet performed.
.BR ares_get_servers (3), .BR ares_get_servers (3),
.BR ares_init_options (3), .BR ares_init_options (3),
.BR ares_dup (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 .SH AVAILABILITY
\fBares_set_servers(3)\fP was added in c-ares 1.7.1; \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. \fBares_set_servers_ports(3)\fP was added in c-ares 1.11.0.

@ -15,9 +15,10 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.TH ARES_SET_SERVERS_CSV 3 "30 June 2010" .TH ARES_SET_SERVERS_CSV 3 "5 Dec 2023"
.SH NAME .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 .SH SYNOPSIS
.nf .nf
#include <ares.h> #include <ares.h>
@ -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_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) 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 .fi
.SH DESCRIPTION .SH DESCRIPTION
The \fBares_set_servers_csv\fP and \fBares_set_servers_ports_csv\fP functions set The \fBares_set_servers_csv\fP and \fBares_set_servers_ports_csv\fP functions set
the list of DNS servers that ARES will query. As of v1.22.0 this function can 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 be called on an active channel with running queries, previously it would return
ARES_ENOTIMP. 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 servers and make an inoperable channel, this may be advantageous for test
simulation but unlikely to be useful in production. 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: 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 .PP
The \fBares_set_servers_csv\fP function will ignore any port values specified in As of c-ares 1.24.0, \fBares_set_servers_csv\fP and \fBares_set_servers_ports_csv\fP
the input string, whereare the \fBares_set_servers_ports_csv\fP function will are identical. Prior versions would simply omit ports in \fBares_set_servers_csv\fP
apply any specified port values as the UDP and TCP port to be used for that but due to the addition of link local interface support, this difference was
particular nameserver. removed.
.SH RETURN VALUES .SH RETURN VALUES
.B ares_set_servers_csv(3) .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 .TP 15
.B ARES_SUCCESS .B ARES_SUCCESS
The name servers configuration was successfully initialized. The name servers configuration was successfully initialized.
@ -66,10 +84,15 @@ was invalid.
.TP 15 .TP 15
.B ARES_ENOTINITIALIZED .B ARES_ENOTINITIALIZED
c-ares library initialization not yet performed. 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 .SH SEE ALSO
.BR ares_set_servers (3) .BR ares_set_servers (3)
.SH AVAILABILITY .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_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 .SH AUTHOR
Ben Greear Ben Greear

@ -730,6 +730,7 @@ CARES_EXTERN int ares_set_servers_csv(ares_channel_t *channel,
const char *servers); const char *servers);
CARES_EXTERN int ares_set_servers_ports_csv(ares_channel_t *channel, CARES_EXTERN int ares_set_servers_ports_csv(ares_channel_t *channel,
const char *servers); const char *servers);
CARES_EXTERN char *ares_get_servers_csv(ares_channel_t *channel);
CARES_EXTERN int ares_get_servers(ares_channel_t *channel, CARES_EXTERN int ares_get_servers(ares_channel_t *channel,
struct ares_addr_node **servers); struct ares_addr_node **servers);

@ -3877,6 +3877,93 @@ AC_DEFUN([CARES_CHECK_FUNC_WRITEV], [
fi 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 <ifaddrs.h>
],[
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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ifaddrs.h>
]],[[
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 CARES_CHECK_FUNC_STAT
dnl ------------------------------------------------- dnl -------------------------------------------------
dnl Verify if stat is available, prototyped, and dnl Verify if stat is available, prototyped, and

@ -10,6 +10,7 @@ CSOURCES = ares__addrinfo2hostent.c \
ares__htable_asvp.c \ ares__htable_asvp.c \
ares__htable_strvp.c \ ares__htable_strvp.c \
ares__htable_szvp.c \ ares__htable_szvp.c \
ares__iface_ips.c \
ares__llist.c \ ares__llist.c \
ares__parse_into_addrinfo.c \ ares__parse_into_addrinfo.c \
ares__read_line.c \ ares__read_line.c \
@ -82,6 +83,7 @@ HHEADERS = ares__buf.h \
ares__htable_asvp.h \ ares__htable_asvp.h \
ares__htable_strvp.h \ ares__htable_strvp.h \
ares__htable_szvp.h \ ares__htable_szvp.h \
ares__iface_ips.h \
ares__llist.h \ ares__llist.h \
ares__slist.h \ ares__slist.h \
ares_android.h \ ares_android.h \

@ -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 <winsock2.h>
# include <ws2tcpip.h>
# if defined(HAVE_IPHLPAPI_H)
# include <iphlpapi.h>
# endif
# if defined(HAVE_NETIOAPI_H)
# include <netioapi.h>
# endif
#endif
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#ifdef HAVE_NET_IF_H
# include <net/if.h>
#endif
#ifdef HAVE_IFADDRS_H
# include <ifaddrs.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#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
}

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

@ -159,8 +159,7 @@ static void set_ipv6_v6only(ares_socket_t sockfd, int on)
# define set_ipv6_v6only(s, v) # define set_ipv6_v6only(s, v)
#endif #endif
static int configure_socket(ares_socket_t s, int family, static int configure_socket(ares_socket_t s, struct server_state *server)
ares_channel_t *channel)
{ {
union { union {
struct sockaddr sa; struct sockaddr sa;
@ -168,6 +167,9 @@ static int configure_socket(ares_socket_t s, int family,
struct sockaddr_in6 sa6; struct sockaddr_in6 sa6;
} local; } local;
ares_socklen_t bindlen = 0;
ares_channel_t *channel = server->channel;
/* do not set options for user-managed sockets */ /* do not set options for user-managed sockets */
if (channel->sock_funcs && channel->sock_funcs->asocket) { if (channel->sock_funcs && channel->sock_funcs->asocket) {
return 0; return 0;
@ -206,26 +208,27 @@ static int configure_socket(ares_socket_t s, int family,
} }
#endif #endif
if (family == AF_INET) { if (server->addr.family == AF_INET && channel->local_ip4) {
if (channel->local_ip4) {
memset(&local.sa4, 0, sizeof(local.sa4)); memset(&local.sa4, 0, sizeof(local.sa4));
local.sa4.sin_family = AF_INET; local.sa4.sin_family = AF_INET;
local.sa4.sin_addr.s_addr = htonl(channel->local_ip4); local.sa4.sin_addr.s_addr = htonl(channel->local_ip4);
if (bind(s, &local.sa, sizeof(local.sa4)) < 0) { bindlen = sizeof(local.sa4);
return -1; } else if (server->addr.family == AF_INET6 && server->ll_scope == 0 &&
} memcmp(channel->local_ip6, ares_in6addr_any._S6_un._S6_u8,
}
} else if (family == AF_INET6) {
if (memcmp(channel->local_ip6, ares_in6addr_any._S6_un._S6_u8,
sizeof(channel->local_ip6)) != 0) { sizeof(channel->local_ip6)) != 0) {
/* Only if not link-local and an ip other than "::" is specified */
memset(&local.sa6, 0, sizeof(local.sa6)); memset(&local.sa6, 0, sizeof(local.sa6));
local.sa6.sin6_family = AF_INET6; local.sa6.sin6_family = AF_INET6;
memcpy(&local.sa6.sin6_addr, channel->local_ip6, memcpy(&local.sa6.sin6_addr, channel->local_ip6,
sizeof(channel->local_ip6)); sizeof(channel->local_ip6));
if (bind(s, &local.sa, sizeof(local.sa6)) < 0) { bindlen = sizeof(local.sa6);
return -1;
} }
if (bindlen && bind(s, &local.sa, bindlen) < 0) {
return -1;
} }
if (server->addr.family == AF_INET6) {
set_ipv6_v6only(s, 0); 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); saddr.sa6.sin6_port = htons(is_tcp ? server->tcp_port : server->udp_port);
memcpy(&saddr.sa6.sin6_addr, &server->addr.addr.addr6, memcpy(&saddr.sa6.sin6_addr, &server->addr.addr.addr6,
sizeof(saddr.sa6.sin6_addr)); sizeof(saddr.sa6.sin6_addr));
#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
saddr.sa6.sin6_scope_id = server->ll_scope;
#endif
break; break;
default: default:
return ARES_EBADFAMILY; /* LCOV_EXCL_LINE */ return ARES_EBADFAMILY; /* LCOV_EXCL_LINE */
@ -279,7 +285,7 @@ ares_status_t ares__open_connection(ares_channel_t *channel,
} }
/* Configure it. */ /* Configure it. */
if (configure_socket(s, server->addr.family, channel) < 0) { if (configure_socket(s, server) < 0) {
ares__close_socket(channel, s); ares__close_socket(channel, s);
return ARES_ECONNREFUSED; return ARES_ECONNREFUSED;
} }

@ -142,6 +142,15 @@
/* Define to 1 if you have the `if_indextoname' function. */ /* Define to 1 if you have the `if_indextoname' function. */
#cmakedefine HAVE_IF_INDEXTONAME #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. */ /* Define to 1 if you have a IPv6 capable working inet_net_pton function. */
#cmakedefine HAVE_INET_NET_PTON #cmakedefine HAVE_INET_NET_PTON
@ -332,6 +341,9 @@
/* Define to 1 if you have the <time.h> header file. */ /* Define to 1 if you have the <time.h> header file. */
#cmakedefine HAVE_TIME_H #cmakedefine HAVE_TIME_H
/* Define to 1 if you have the <ifaddrs.h> header file. */
#cmakedefine HAVE_IFADDRS_H
/* Define to 1 if you have the <unistd.h> header file. */ /* Define to 1 if you have the <unistd.h> header file. */
#cmakedefine HAVE_UNISTD_H #cmakedefine HAVE_UNISTD_H
@ -362,6 +374,9 @@
/* Define if have arc4random_buf() */ /* Define if have arc4random_buf() */
#cmakedefine HAVE_ARC4RANDOM_BUF #cmakedefine HAVE_ARC4RANDOM_BUF
/* Define if have getifaddrs() */
#cmakedefine HAVE_GETIFADDRS
/* Define if have stat() */ /* Define if have stat() */
#cmakedefine HAVE_STAT #cmakedefine HAVE_STAT

@ -161,7 +161,7 @@ static ares_status_t init_by_defaults(ares_channel_t *channel)
addr.family = AF_INET; addr.family = AF_INET;
addr.addr.addr4.s_addr = htonl(INADDR_LOOPBACK); 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) { if (rc != ARES_SUCCESS) {
return rc; 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) int ares_dup(ares_channel_t **dest, ares_channel_t *src)
{ {
struct ares_options opts; struct ares_options opts;
struct ares_addr_port_node *servers;
ares_status_t rc; ares_status_t rc;
int optmask; int optmask;
@ -480,18 +479,21 @@ int ares_dup(ares_channel_t **dest, ares_channel_t *src)
* the case, pull them in. * the case, pull them in.
* *
* We don't want to clone system-configuration servers though. * 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) { if (optmask & ARES_OPT_SERVERS) {
rc = (ares_status_t)ares_get_servers_ports(src, &servers); char *csv = ares_get_servers_csv(src);
if (rc != ARES_SUCCESS) { if (csv == NULL) {
ares_destroy(*dest); ares_destroy(*dest);
*dest = NULL; *dest = NULL;
rc = ARES_ENOMEM;
goto done; goto done;
} }
rc = (ares_status_t)ares_set_servers_ports(*dest, servers); rc = (ares_status_t)ares_set_servers_ports_csv(*dest, csv);
ares_free_data(servers); ares_free_string(csv);
if (rc != ARES_SUCCESS) { if (rc != ARES_SUCCESS) {
ares_destroy(*dest); ares_destroy(*dest);
*dest = NULL; *dest = NULL;

@ -132,3 +132,14 @@ size_t ares__count_hexdigits(size_t n)
return digits; 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];
}

@ -123,6 +123,7 @@ typedef struct ares_rand_state ares_rand_state;
#include "ares__htable_asvp.h" #include "ares__htable_asvp.h"
#include "ares__buf.h" #include "ares__buf.h"
#include "ares_dns_private.h" #include "ares_dns_private.h"
#include "ares__iface_ips.h"
#ifndef HAVE_GETENV #ifndef HAVE_GETENV
# include "ares_getenv.h" # include "ares_getenv.h"
@ -167,14 +168,17 @@ struct server_connection {
}; };
struct server_state { struct server_state {
/* Configuration */
size_t idx; /* index for server in system 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; struct ares_addr addr;
unsigned short udp_port; /* host byte order */ unsigned short udp_port; /* host byte order */
unsigned short tcp_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; ares__llist_t *connections;
struct server_connection *tcp_conn; 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, ares_status_t ares__sconfig_append(ares__llist_t **sconfig,
const struct ares_addr *addr, const struct ares_addr *addr,
unsigned short udp_port, 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, ares_status_t ares__sconfig_append_fromstr(ares__llist_t **sconfig,
const char *str, const char *str,
ares_bool_t ignore_invalid); 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 && x->lookups && ares__slist_len(x->servers) > 0 && x->ndots > 0 && \
x->timeout > 0 && x->tries > 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__round_up_pow2(size_t n);
size_t ares__log2(size_t n); size_t ares__log2(size_t n);
size_t ares__pow(size_t x, size_t y); size_t ares__pow(size_t x, size_t y);
size_t ares__count_digits(size_t n); size_t ares__count_digits(size_t n);
size_t ares__count_hexdigits(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); void ares__qcache_destroy(ares__qcache_t *cache);
ares_status_t ares__qcache_create(ares_rand_state *rand_state, ares_status_t ares__qcache_create(ares_rand_state *rand_state,
unsigned int max_ttl, unsigned int max_ttl,

@ -93,3 +93,19 @@ size_t ares_strcpy(char *dest, const char *src, size_t dest_size)
dest[len] = 0; dest[len] = 0;
return len; 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;
}

@ -28,6 +28,7 @@
#define HEADER_CARES_STRDUP_H #define HEADER_CARES_STRDUP_H
#include "ares_setup.h" #include "ares_setup.h"
#include "ares.h"
char *ares_strdup(const char *s1); char *ares_strdup(const char *s1);
@ -45,5 +46,6 @@ size_t ares_strlen(const char *str);
*/ */
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 */ #endif /* HEADER_CARES_STRDUP_H */

@ -171,7 +171,7 @@ typedef struct {
/* Room enough for the string form of any IPv4 or IPv6 address that /* 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. * 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; } Address;
/* Sort Address values \a left and \a right by metric, returning the usual /* 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)); ntohs(namesrvr.sa4->sin_port));
++addressesIndex; ++addressesIndex;
} else if (namesrvr.sa->sa_family == AF_INET6) { } 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, if (memcmp(&namesrvr.sa6->sin6_addr, &ares_in6addr_any,
sizeof(namesrvr.sa6->sin6_addr)) == 0) { sizeof(namesrvr.sa6->sin6_addr)) == 0) {
continue; continue;
@ -433,6 +436,14 @@ static ares_bool_t get_DNS_Windows(char **outptr)
addressesSize = newSize; 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 = addresses[addressesIndex].metric =
getBestRouteMetric(&ipaaEntry->Luid, (SOCKADDR_INET *)(namesrvr.sa), getBestRouteMetric(&ipaaEntry->Luid, (SOCKADDR_INET *)(namesrvr.sa),
ipaaEntry->Ipv6Metric); ipaaEntry->Ipv6Metric);
@ -444,9 +455,16 @@ static ares_bool_t get_DNS_Windows(char **outptr)
sizeof(ipaddr))) { sizeof(ipaddr))) {
continue; continue;
} }
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, snprintf(addresses[addressesIndex].text,
sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr, sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr,
ntohs(namesrvr.sa6->sin6_port)); ntohs(namesrvr.sa6->sin6_port));
}
++addressesIndex; ++addressesIndex;
} else { } else {
/* Skip non-IPv4/IPv6 addresses completely. */ /* Skip non-IPv4/IPv6 addresses completely. */
@ -661,7 +679,7 @@ static ares_status_t ares__init_sysconfig_mvs(ares_sysconfig_t *sysconfig)
status = status =
ares__sconfig_append(&sysconfig->sconfig, &addr, htons(addr_in->sin_port), 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) { if (status != ARES_SUCCESS) {
return status; return status;
@ -678,7 +696,7 @@ static ares_status_t ares__init_sysconfig_mvs(ares_sysconfig_t *sysconfig)
status = status =
ares__sconfig_append(&sysconfig->sconfig, &addr, htons(addr_in->sin_port), 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) { if (status != ARES_SUCCESS) {
return status; return status;
@ -742,7 +760,7 @@ static ares_status_t ares__init_sysconfig_watt32(ares_sysconfig_t *sysconfig)
addr.family = AF_INET; addr.family = AF_INET;
addr.addr.addr4.s_addr = htonl(def_nameservers[i]); 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) { if (status != ARES_SUCCESS) {
return status; return status;
@ -825,6 +843,7 @@ static ares_status_t ares__init_sysconfig_libresolv(ares_sysconfig_t *sysconfig)
int nscount; int nscount;
size_t i; size_t i;
size_t entries = 0; size_t entries = 0;
ares__buf_t *ipbuf = NULL;
memset(&res, 0, sizeof(res)); 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) { for (i = 0; i < (size_t)nscount; ++i) {
char ipaddr[INET6_ADDRSTRLEN] = ""; char ipaddr[INET6_ADDRSTRLEN] = "";
char ipaddr_port[INET6_ADDRSTRLEN + 8]; /* [%s]:NNNNN */ char *ipstr = NULL;
unsigned short port = 0; unsigned short port = 0;
unsigned int ll_scope = 0;
sa_family_t family = addr[i].sin.sin_family; sa_family_t family = addr[i].sin.sin_family;
if (family == AF_INET) { if (family == AF_INET) {
@ -846,18 +866,67 @@ static ares_status_t ares__init_sysconfig_libresolv(ares_sysconfig_t *sysconfig)
} else if (family == AF_INET6) { } else if (family == AF_INET6) {
ares_inet_ntop(family, &addr[i].sin6.sin6_addr, ipaddr, sizeof(ipaddr)); 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 { } else {
continue; 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) { if (port) {
snprintf(ipaddr_port, sizeof(ipaddr_port), "[%s]:%u", ipaddr, port); status = ares__buf_append_str(ipbuf, ":");
} else { if (status != ARES_SUCCESS) {
snprintf(ipaddr_port, sizeof(ipaddr_port), "%s", ipaddr); 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 = 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) { if (status != ARES_SUCCESS) {
goto done; goto done;
} }
@ -908,6 +977,7 @@ static ares_status_t ares__init_sysconfig_libresolv(ares_sysconfig_t *sysconfig)
} }
done: done:
ares__buf_destroy(ipbuf);
res_ndestroy(&res); res_ndestroy(&res);
return status; return status;
} }

@ -30,16 +30,41 @@
#ifdef HAVE_ARPA_INET_H #ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h> # include <arpa/inet.h>
#endif #endif
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#ifdef HAVE_NET_IF_H
# include <net/if.h>
#endif
#if defined(USE_WINSOCK)
# if defined(HAVE_IPHLPAPI_H)
# include <iphlpapi.h>
# endif
# if defined(HAVE_NETIOAPI_H)
# include <netioapi.h>
# endif
#endif
#include "ares.h" #include "ares.h"
#include "ares_data.h" #include "ares_data.h"
#include "ares_inet_net_pton.h" #include "ares_inet_net_pton.h"
#include "ares_private.h" #include "ares_private.h"
#ifndef IFNAMSIZ
# define IFNAMSIZ 64
#endif
typedef struct { typedef struct {
struct ares_addr addr; struct ares_addr addr;
unsigned short tcp_port; unsigned short tcp_port;
unsigned short udp_port; unsigned short udp_port;
char ll_iface[IFNAMSIZ];
unsigned int ll_scope;
} ares_sconfig_t; } ares_sconfig_t;
static ares_bool_t ares__addr_match(const struct ares_addr *addr1, 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; return ARES_FALSE;
} }
/* Validate that the ip address matches the subnet (network base and network ares_bool_t ares__subnet_match(const struct ares_addr *addr,
* mask) specified. Addresses are specified in standard Network Byte Order as const struct ares_addr *subnet,
* 16 bytes, and the netmask is 0 to 128 (bits). unsigned char netmask)
*/
static ares_bool_t ares_ipv6_subnet_matches(const unsigned char netbase[16],
unsigned char netmask,
const unsigned char *ipaddr)
{ {
unsigned char mask[16] = { 0 }; const unsigned char *addr_ptr;
unsigned char i; const unsigned char *subnet_ptr;
size_t len;
size_t i;
/* Misuse */ if (addr == NULL || subnet == NULL) {
if (netmask > 128) { return ARES_FALSE;
}
if (addr->family != subnet->family) {
return ARES_FALSE; return ARES_FALSE;
} }
/* Quickly set whole bytes */ if (addr->family == AF_INET) {
memset(mask, 0xFF, netmask / 8); addr_ptr = (const unsigned char *)&addr->addr.addr4;
subnet_ptr = (const unsigned char *)&subnet->addr.addr4;
len = 4;
/* Set remaining bits */ if (netmask > 32) {
if (netmask % 8 && netmask < 128 /* Silence coverity */) { return ARES_FALSE;
mask[netmask / 8] = (unsigned char)(0xff << (8 - (netmask % 8)));
} }
} else if (addr->family == AF_INET6) {
addr_ptr = (const unsigned char *)&addr->addr.addr6;
subnet_ptr = (const unsigned char *)&subnet->addr.addr6;
len = 16;
for (i = 0; i < 16; i++) { if (netmask > 128) {
if ((netbase[i] & mask[i]) != (ipaddr[i] & mask[i])) { return ARES_FALSE;
}
} else {
return ARES_FALSE;
}
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; return ARES_FALSE;
} }
} }
@ -104,6 +150,20 @@ static ares_bool_t ares_ipv6_subnet_matches(const unsigned char netbase[16],
return ARES_TRUE; 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) static ares_bool_t ares_server_blacklisted(const struct ares_addr *addr)
{ {
/* A list of blacklisted IPv6 subnets. */ /* 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. */ /* See if ipaddr matches any of the entries in the blacklist. */
for (i = 0; i < sizeof(blacklist_v6) / sizeof(*blacklist_v6); i++) { for (i = 0; i < sizeof(blacklist_v6) / sizeof(*blacklist_v6); i++) {
if (ares_ipv6_subnet_matches(blacklist_v6[i].netbase, struct ares_addr subnet;
blacklist_v6[i].netmask, subnet.family = AF_INET6;
(const unsigned char *)&addr->addr.addr6)) { memcpy(&subnet.addr.addr6, blacklist_v6[i].netbase, 16);
if (ares__subnet_match(addr, &subnet, blacklist_v6[i].netmask)) {
return ARES_TRUE; return ARES_TRUE;
} }
} }
@ -144,8 +205,9 @@ static ares_bool_t ares_server_blacklisted(const struct ares_addr *addr)
* [ipaddr] * [ipaddr]
* [ipaddr]:port * [ipaddr]:port
* *
* TODO: in the future we need to add % and # syntax modifications to support * Modifiers: %iface
* the link-local interface name and domain, respectively. *
* TODO: #domain modifier
* *
* If a port is not specified, will set port to 0. * 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 * Returns an error code on failure, else ARES_SUCCESS
*/ */
static ares_status_t parse_dnsaddrport(ares__buf_t *buf, struct ares_addr *host, static ares_status_t parse_nameserver(ares__buf_t *buf, ares_sconfig_t *sconfig)
unsigned short *port)
{ {
ares_status_t status; ares_status_t status;
char ipaddr[INET6_ADDRSTRLEN] = ""; char ipaddr[INET6_ADDRSTRLEN] = "";
size_t addrlen; size_t addrlen;
*port = 0; memset(sconfig, 0, sizeof(*sconfig));
/* Consume any leading whitespace */ /* Consume any leading whitespace */
ares__buf_consume_whitespace(buf, ARES_TRUE); 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 */ /* Convert ip address from string to network byte order */
host->family = AF_UNSPEC; sconfig->addr.family = AF_UNSPEC;
if (ares_dns_pton(ipaddr, host, &addrlen) == NULL) { if (ares_dns_pton(ipaddr, &sconfig->addr, &addrlen) == NULL) {
return ARES_EBADSTR; return ARES_EBADSTR;
} }
@ -247,7 +308,30 @@ static ares_status_t parse_dnsaddrport(ares__buf_t *buf, struct ares_addr *host,
return status; 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 /* 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; 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, ares_status_t ares__sconfig_append(ares__llist_t **sconfig,
const struct ares_addr *addr, const struct ares_addr *addr,
unsigned short udp_port, unsigned short udp_port,
unsigned short tcp_port) unsigned short tcp_port,
const char *ll_iface)
{ {
ares_sconfig_t *s; ares_sconfig_t *s;
ares_status_t status; ares_status_t status;
@ -295,6 +409,16 @@ ares_status_t ares__sconfig_append(ares__llist_t **sconfig,
s->udp_port = udp_port; s->udp_port = udp_port;
s->tcp_port = tcp_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) { if (ares__llist_insert_last(*sconfig, s) == NULL) {
status = ARES_ENOMEM; status = ARES_ENOMEM;
goto fail; goto fail;
@ -351,10 +475,9 @@ ares_status_t ares__sconfig_append_fromstr(ares__llist_t **sconfig,
for (node = ares__llist_node_first(list); node != NULL; for (node = ares__llist_node_first(list); node != NULL;
node = ares__llist_node_next(node)) { node = ares__llist_node_next(node)) {
ares__buf_t *entry = ares__llist_node_val(node); ares__buf_t *entry = ares__llist_node_val(node);
struct ares_addr host; ares_sconfig_t s;
unsigned short port;
status = parse_dnsaddrport(entry, &host, &port); status = parse_nameserver(entry, &s);
if (status != ARES_SUCCESS) { if (status != ARES_SUCCESS) {
if (ignore_invalid) { if (ignore_invalid) {
continue; 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) { if (status != ARES_SUCCESS) {
goto done; goto done;
} }
@ -476,6 +600,12 @@ static ares_status_t ares__server_create(ares_channel_t *channel,
sizeof(server->addr.addr.addr6)); 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(); server->tcp_parser = ares__buf_create();
if (server->tcp_parser == NULL) { if (server->tcp_parser == NULL) {
status = ARES_ENOMEM; status = ARES_ENOMEM;
@ -594,6 +724,14 @@ ares_status_t ares__servers_update(ares_channel_t *channel,
if (snode != NULL) { if (snode != NULL) {
struct server_state *server = ares__slist_node_val(snode); 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) { if (server->idx != idx) {
server->idx = idx; server->idx = idx;
/* Index changed, reinsert node, doesn't require any memory /* 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() */ /* NOTE: lock is in ares__servers_update() */
return (int)set_servers_csv(channel, _csv); 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;
}

@ -363,6 +363,17 @@
# define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 # define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1
#endif #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 */ /* Win CE */
/* ---------------------------------------------------------------- */ /* ---------------------------------------------------------------- */

@ -391,8 +391,8 @@ CONTAINED_TEST_F(LibraryTest, ContainerChannelInit,
"myhostname", "mydomainname.org", filelist) { "myhostname", "mydomainname.org", filelist) {
ares_channel_t *channel = nullptr; ares_channel_t *channel = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_init(&channel)); EXPECT_EQ(ARES_SUCCESS, ares_init(&channel));
std::vector<std::string> actual = GetNameServers(channel); std::string actual = GetNameServers(channel);
std::vector<std::string> expected = {"1.2.3.4:53"}; std::string expected = "1.2.3.4:53";
EXPECT_EQ(expected, actual); EXPECT_EQ(expected, actual);
EXPECT_EQ(2, channel->ndomains); EXPECT_EQ(2, channel->ndomains);
EXPECT_EQ(std::string("first.com"), std::string(channel->domains[0])); EXPECT_EQ(std::string("first.com"), std::string(channel->domains[0]));
@ -640,11 +640,9 @@ CONTAINED_TEST_F(LibraryTest, ContainerBlacklistedIpv6,
"myhostname", "mydomainname.org", blacklistedIpv6) { "myhostname", "mydomainname.org", blacklistedIpv6) {
ares_channel_t *channel = nullptr; ares_channel_t *channel = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_init(&channel)); EXPECT_EQ(ARES_SUCCESS, ares_init(&channel));
std::vector<std::string> actual = GetNameServers(channel); std::string actual = GetNameServers(channel);
std::vector<std::string> expected = { std::string expected = "254.192.1.1:53,"
"254.192.1.1:53", "[ffc0::c001]:53";
"[ffc0:0000:0000:0000:0000:0000:0000:c001]:53"
};
EXPECT_EQ(expected, actual); EXPECT_EQ(expected, actual);
EXPECT_EQ(1, channel->ndomains); EXPECT_EQ(1, channel->ndomains);
@ -662,8 +660,8 @@ CONTAINED_TEST_F(LibraryTest, ContainerMultiResolvInit,
"myhostname", "mydomainname.org", multiresolv) { "myhostname", "mydomainname.org", multiresolv) {
ares_channel_t *channel = nullptr; ares_channel_t *channel = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_init(&channel)); EXPECT_EQ(ARES_SUCCESS, ares_init(&channel));
std::vector<std::string> actual = GetNameServers(channel); std::string actual = GetNameServers(channel);
std::vector<std::string> expected = {"[0001:0000:0000:0000:0000:0000:0000:0002]:53"}; std::string expected = "[1::2]:53";
EXPECT_EQ(expected, actual); EXPECT_EQ(expected, actual);
EXPECT_EQ(1, channel->ndomains); EXPECT_EQ(1, channel->ndomains);
@ -693,8 +691,8 @@ CONTAINED_TEST_F(LibraryTest, ContainerEmptyInit,
"host.domain.org", "domain.org", empty) { "host.domain.org", "domain.org", empty) {
ares_channel_t *channel = nullptr; ares_channel_t *channel = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_init(&channel)); EXPECT_EQ(ARES_SUCCESS, ares_init(&channel));
std::vector<std::string> actual = GetNameServers(channel); std::string actual = GetNameServers(channel);
std::vector<std::string> expected = {"127.0.0.1:53"}; std::string expected = "127.0.0.1:53";
EXPECT_EQ(expected, actual); EXPECT_EQ(expected, actual);
EXPECT_EQ(1, channel->ndomains); EXPECT_EQ(1, channel->ndomains);

@ -25,11 +25,9 @@ namespace ares {
namespace test { namespace test {
TEST_F(DefaultChannelTest, GetServers) { TEST_F(DefaultChannelTest, GetServers) {
std::vector<std::string> servers = GetNameServers(channel_); std::string servers = GetNameServers(channel_);
if (verbose) { if (verbose) {
for (const std::string& server : servers) { std::cerr << "Nameserver: " << servers << std::endl;
std::cerr << "Nameserver: " << server << std::endl;
}
} }
} }
@ -52,7 +50,7 @@ TEST_F(DefaultChannelTest, SetServers) {
* See: https://github.com/nodejs/node/pull/50800 * See: https://github.com/nodejs/node/pull/50800
*/ */
EXPECT_EQ(ARES_SUCCESS, ares_set_servers(channel_, nullptr)); EXPECT_EQ(ARES_SUCCESS, ares_set_servers(channel_, nullptr));
std::vector<std::string> expected_empty = { }; std::string expected_empty = "";
EXPECT_EQ(expected_empty, GetNameServers(channel_)); EXPECT_EQ(expected_empty, GetNameServers(channel_));
HostResult result; HostResult result;
ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &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_ENODATA, ares_set_servers(nullptr, &server1));
EXPECT_EQ(ARES_SUCCESS, ares_set_servers(channel_, &server1)); EXPECT_EQ(ARES_SUCCESS, ares_set_servers(channel_, &server1));
std::vector<std::string> 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_)); EXPECT_EQ(expected, GetNameServers(channel_));
} }
@ -84,7 +82,7 @@ TEST_F(DefaultChannelTest, SetServersPorts) {
* See: https://github.com/nodejs/node/pull/50800 * See: https://github.com/nodejs/node/pull/50800
*/ */
EXPECT_EQ(ARES_SUCCESS, ares_set_servers_ports(channel_, nullptr)); EXPECT_EQ(ARES_SUCCESS, ares_set_servers_ports(channel_, nullptr));
std::vector<std::string> expected_empty = { }; std::string expected_empty = "";
EXPECT_EQ(expected_empty, GetNameServers(channel_)); EXPECT_EQ(expected_empty, GetNameServers(channel_));
struct ares_addr_port_node server1; 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_ENODATA, ares_set_servers_ports(nullptr, &server1));
EXPECT_EQ(ARES_SUCCESS, ares_set_servers_ports(channel_, &server1)); EXPECT_EQ(ARES_SUCCESS, ares_set_servers_ports(channel_, &server1));
std::vector<std::string> 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_)); EXPECT_EQ(expected, GetNameServers(channel_));
} }
@ -120,17 +118,15 @@ TEST_F(DefaultChannelTest, SetServersCSV) {
* See: https://github.com/nodejs/node/pull/50800 * See: https://github.com/nodejs/node/pull/50800
*/ */
EXPECT_EQ(ARES_SUCCESS, ares_set_servers_csv(channel_, NULL)); EXPECT_EQ(ARES_SUCCESS, ares_set_servers_csv(channel_, NULL));
std::vector<std::string> expected_empty = { }; std::string expected_empty = "";
EXPECT_EQ(expected_empty, GetNameServers(channel_)); EXPECT_EQ(expected_empty, GetNameServers(channel_));
EXPECT_EQ(ARES_SUCCESS, ares_set_servers_csv(channel_, "")); EXPECT_EQ(ARES_SUCCESS, ares_set_servers_csv(channel_, ""));
EXPECT_EQ(expected_empty, GetNameServers(channel_)); EXPECT_EQ(expected_empty, GetNameServers(channel_));
EXPECT_EQ(ARES_SUCCESS, 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")); ares_set_servers_csv(channel_, "1.2.3.4,0102:0304:0506:0708:0910:1112:1314:1516,2.3.4.5"));
std::vector<std::string> 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_)); EXPECT_EQ(expected, GetNameServers(channel_));
// Same, with spaces // 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")); 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_)); 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 // Same, with ports
EXPECT_EQ(ARES_SUCCESS, 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")); 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<std::string> 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_)); EXPECT_EQ(expected2, GetNameServers(channel_));
// Should survive duplication // Should survive duplication

45
test/ares-test.cc vendored

@ -544,10 +544,7 @@ MockChannelOptsTest::MockChannelOptsTest(int count,
} }
if (verbose) { if (verbose) {
std::cerr << "Configured library with servers:"; std::cerr << "Configured library with servers:";
std::vector<std::string> servers = GetNameServers(channel_); std::cerr << GetNameServers(channel_);
for (const auto& server : servers) {
std::cerr << " " << server;
}
std::cerr << std::endl; std::cerr << std::endl;
} }
} }
@ -779,38 +776,14 @@ void NameInfoCallback(void *data, int status, int timeouts,
if (verbose) std::cerr << "NameInfoCallback(" << *result << ")" << std::endl; if (verbose) std::cerr << "NameInfoCallback(" << *result << ")" << std::endl;
} }
std::vector<std::string> GetNameServers(ares_channel_t *channel) { std::string GetNameServers(ares_channel_t *channel) {
struct ares_addr_port_node* servers = nullptr; char *csv = ares_get_servers_csv(channel);
EXPECT_EQ(ARES_SUCCESS, ares_get_servers_ports(channel, &servers)); EXPECT_NE((char *)NULL, csv);
struct ares_addr_port_node* server = servers;
std::vector<std::string> results; std::string servers(csv);
while (server) {
std::stringstream ss; ares_free_string(csv);
switch (server->family) { return servers;
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("<unknown family>");
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;
} }
TransientDir::TransientDir(const std::string& dirname) : dirname_(dirname) { TransientDir::TransientDir(const std::string& dirname) : dirname_(dirname) {

2
test/ares-test.h vendored

@ -444,7 +444,7 @@ void AddrInfoCallback(void *data, int status, int timeouts,
struct ares_addrinfo *res); struct ares_addrinfo *res);
// Retrieve the name servers used by a channel. // Retrieve the name servers used by a channel.
std::vector<std::string> GetNameServers(ares_channel_t *channel); std::string GetNameServers(ares_channel_t *channel);
// RAII class to temporarily create a directory of a given name. // RAII class to temporarily create a directory of a given name.
class TransientDir { class TransientDir {

Loading…
Cancel
Save