You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1228 lines
34 KiB
1228 lines
34 KiB
/* MIT License |
|
* |
|
* Copyright (c) 1998 Massachusetts Institute of Technology |
|
* Copyright (c) 2008 Daniel Stenberg |
|
* 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_private.h" |
|
|
|
#ifdef HAVE_ARPA_INET_H |
|
# include <arpa/inet.h> |
|
#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_data.h" |
|
#include "ares_inet_net_pton.h" |
|
|
|
typedef struct { |
|
struct ares_addr addr; |
|
unsigned short tcp_port; |
|
unsigned short udp_port; |
|
|
|
char ll_iface[IF_NAMESIZE]; |
|
unsigned int ll_scope; |
|
} ares_sconfig_t; |
|
|
|
static ares_bool_t ares__addr_match(const struct ares_addr *addr1, |
|
const struct ares_addr *addr2) |
|
{ |
|
if (addr1 == NULL && addr2 == NULL) { |
|
return ARES_TRUE; /* LCOV_EXCL_LINE: DefensiveCoding */ |
|
} |
|
|
|
if (addr1 == NULL || addr2 == NULL) { |
|
return ARES_FALSE; /* LCOV_EXCL_LINE: DefensiveCoding */ |
|
} |
|
|
|
if (addr1->family != addr2->family) { |
|
return ARES_FALSE; |
|
} |
|
|
|
if (addr1->family == AF_INET && memcmp(&addr1->addr.addr4, &addr2->addr.addr4, |
|
sizeof(addr1->addr.addr4)) == 0) { |
|
return ARES_TRUE; |
|
} |
|
|
|
if (addr1->family == AF_INET6 && |
|
memcmp(&addr1->addr.addr6._S6_un._S6_u8, &addr2->addr.addr6._S6_un._S6_u8, |
|
sizeof(addr1->addr.addr6._S6_un._S6_u8)) == 0) { |
|
return ARES_TRUE; |
|
} |
|
|
|
return ARES_FALSE; |
|
} |
|
|
|
ares_bool_t ares__subnet_match(const struct ares_addr *addr, |
|
const struct ares_addr *subnet, |
|
unsigned char netmask) |
|
{ |
|
const unsigned char *addr_ptr; |
|
const unsigned char *subnet_ptr; |
|
size_t len; |
|
size_t i; |
|
|
|
if (addr == NULL || subnet == NULL) { |
|
return ARES_FALSE; /* LCOV_EXCL_LINE: DefensiveCoding */ |
|
} |
|
|
|
if (addr->family != subnet->family) { |
|
return ARES_FALSE; |
|
} |
|
|
|
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; /* LCOV_EXCL_LINE: DefensiveCoding */ |
|
} |
|
} else if (addr->family == AF_INET6) { |
|
addr_ptr = (const unsigned char *)&addr->addr.addr6; |
|
subnet_ptr = (const unsigned char *)&subnet->addr.addr6; |
|
len = 16; |
|
|
|
if (netmask > 128) { |
|
return ARES_FALSE; /* LCOV_EXCL_LINE: DefensiveCoding */ |
|
} |
|
} else { |
|
return ARES_FALSE; /* LCOV_EXCL_LINE: DefensiveCoding */ |
|
} |
|
|
|
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_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. */ |
|
const struct { |
|
const unsigned char netbase[16]; |
|
unsigned char netmask; |
|
} blacklist_v6[] = { |
|
/* fec0::/10 was deprecated by [RFC3879] in September 2004. Formerly a |
|
* Site-Local scoped address prefix. These are never valid DNS servers, |
|
* but are known to be returned at least sometimes on Windows and Android. |
|
*/ |
|
{ { 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|
0x00, 0x00, 0x00, 0x00 }, |
|
10 } |
|
}; |
|
|
|
size_t i; |
|
|
|
if (addr->family != AF_INET6) { |
|
return ARES_FALSE; |
|
} |
|
|
|
/* See if ipaddr matches any of the entries in the blacklist. */ |
|
for (i = 0; i < sizeof(blacklist_v6) / sizeof(*blacklist_v6); i++) { |
|
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; |
|
} |
|
} |
|
return ARES_FALSE; |
|
} |
|
|
|
/* Parse address and port in these formats, either ipv4 or ipv6 addresses |
|
* are allowed: |
|
* ipaddr |
|
* ipv4addr:port |
|
* [ipaddr] |
|
* [ipaddr]:port |
|
* |
|
* Modifiers: %iface |
|
* |
|
* TODO: #domain modifier |
|
* |
|
* If a port is not specified, will set port to 0. |
|
* |
|
* Will fail if an IPv6 nameserver as detected by |
|
* ares_ipv6_server_blacklisted() |
|
* |
|
* Returns an error code on failure, else ARES_SUCCESS |
|
*/ |
|
|
|
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; |
|
|
|
memset(sconfig, 0, sizeof(*sconfig)); |
|
|
|
/* Consume any leading whitespace */ |
|
ares__buf_consume_whitespace(buf, ARES_TRUE); |
|
|
|
/* pop off IP address. If it is in [ ] then it can be ipv4 or ipv6. If |
|
* not, ipv4 only */ |
|
if (ares__buf_begins_with(buf, (const unsigned char *)"[", 1)) { |
|
/* Consume [ */ |
|
ares__buf_consume(buf, 1); |
|
|
|
ares__buf_tag(buf); |
|
|
|
/* Consume until ] */ |
|
if (ares__buf_consume_until_charset(buf, (const unsigned char *)"]", 1, |
|
ARES_TRUE) == 0) { |
|
return ARES_EBADSTR; |
|
} |
|
|
|
status = ares__buf_tag_fetch_string(buf, ipaddr, sizeof(ipaddr)); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
|
|
/* Skip over ] */ |
|
ares__buf_consume(buf, 1); |
|
} else { |
|
size_t offset; |
|
|
|
/* Not in [ ], see if '.' is in first 4 characters, if it is, then its ipv4, |
|
* otherwise treat as ipv6 */ |
|
ares__buf_tag(buf); |
|
|
|
offset = ares__buf_consume_until_charset(buf, (const unsigned char *)".", 1, |
|
ARES_TRUE); |
|
ares__buf_tag_rollback(buf); |
|
ares__buf_tag(buf); |
|
|
|
if (offset > 0 && offset < 4) { |
|
/* IPv4 */ |
|
if (ares__buf_consume_charset(buf, (const unsigned char *)"0123456789.", |
|
11) == 0) { |
|
return ARES_EBADSTR; |
|
} |
|
} else { |
|
/* IPv6 */ |
|
const unsigned char ipv6_charset[] = "ABCDEFabcdef0123456789.:"; |
|
if (ares__buf_consume_charset(buf, ipv6_charset, |
|
sizeof(ipv6_charset) - 1) == 0) { |
|
return ARES_EBADSTR; |
|
} |
|
} |
|
|
|
status = ares__buf_tag_fetch_string(buf, ipaddr, sizeof(ipaddr)); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
} |
|
|
|
/* Convert ip address from string to network byte order */ |
|
sconfig->addr.family = AF_UNSPEC; |
|
if (ares_dns_pton(ipaddr, &sconfig->addr, &addrlen) == NULL) { |
|
return ARES_EBADSTR; |
|
} |
|
|
|
/* Pull off port */ |
|
if (ares__buf_begins_with(buf, (const unsigned char *)":", 1)) { |
|
char portstr[6]; |
|
|
|
/* Consume : */ |
|
ares__buf_consume(buf, 1); |
|
|
|
ares__buf_tag(buf); |
|
|
|
/* Read numbers */ |
|
if (ares__buf_consume_charset(buf, (const unsigned char *)"0123456789", |
|
10) == 0) { |
|
return ARES_EBADSTR; |
|
} |
|
|
|
status = ares__buf_tag_fetch_string(buf, portstr, sizeof(portstr)); |
|
if (status != ARES_SUCCESS) { |
|
return status; |
|
} |
|
|
|
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) - 1) == 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 |
|
* after we didn't read */ |
|
ares__buf_consume_whitespace(buf, ARES_TRUE); |
|
|
|
if (ares__buf_len(buf) != 0) { |
|
return ARES_EBADSTR; |
|
} |
|
|
|
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[IF_NAMESIZE] = ""; |
|
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, |
|
const char *ll_iface) |
|
{ |
|
ares_sconfig_t *s; |
|
ares_status_t status; |
|
|
|
if (sconfig == NULL || addr == NULL) { |
|
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */ |
|
} |
|
|
|
/* Silently skip blacklisted IPv6 servers. */ |
|
if (ares_server_blacklisted(addr)) { |
|
return ARES_SUCCESS; |
|
} |
|
|
|
s = ares_malloc_zero(sizeof(*s)); |
|
if (s == NULL) { |
|
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
if (*sconfig == NULL) { |
|
*sconfig = ares__llist_create(ares_free); |
|
if (*sconfig == NULL) { |
|
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
memcpy(&s->addr, addr, sizeof(s->addr)); |
|
s->udp_port = udp_port; |
|
s->tcp_port = tcp_port; |
|
|
|
/* Handle link-local enumeration. If an interface is specified on a |
|
* non-link-local address, we'll simply end up ignoring that */ |
|
if (ares__addr_is_linklocal(&s->addr)) { |
|
if (ares_strlen(ll_iface) == 0) { |
|
/* Silently ignore this entry, we require an interface */ |
|
status = ARES_SUCCESS; |
|
goto fail; |
|
} |
|
status = ares__sconfig_linklocal(s, ll_iface); |
|
/* Silently ignore this entry, we can't validate the interface */ |
|
if (status != ARES_SUCCESS) { |
|
status = ARES_SUCCESS; |
|
goto fail; |
|
} |
|
} |
|
|
|
if (ares__llist_insert_last(*sconfig, s) == NULL) { |
|
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
return ARES_SUCCESS; |
|
|
|
fail: |
|
ares_free(s); |
|
|
|
return status; |
|
} |
|
|
|
/* Add the IPv4 or IPv6 nameservers in str (separated by commas or spaces) to |
|
* the servers list, updating servers and nservers as required. |
|
* |
|
* If a nameserver is encapsulated in [ ] it may optionally include a port |
|
* suffix, e.g.: |
|
* [127.0.0.1]:59591 |
|
* |
|
* The extended format is required to support OpenBSD's resolv.conf format: |
|
* https://man.openbsd.org/OpenBSD-5.1/resolv.conf.5 |
|
* As well as MacOS libresolv that may include a non-default port number. |
|
* |
|
* This will silently ignore blacklisted IPv6 nameservers as detected by |
|
* ares_ipv6_server_blacklisted(). |
|
* |
|
* Returns an error code on failure, else ARES_SUCCESS. |
|
*/ |
|
ares_status_t ares__sconfig_append_fromstr(ares__llist_t **sconfig, |
|
const char *str, |
|
ares_bool_t ignore_invalid) |
|
{ |
|
ares_status_t status = ARES_SUCCESS; |
|
ares__buf_t *buf = NULL; |
|
ares__llist_t *list = NULL; |
|
ares__llist_node_t *node; |
|
|
|
/* On Windows, there may be more than one nameserver specified in the same |
|
* registry key, so we parse input as a space or comma separated list. |
|
*/ |
|
buf = ares__buf_create_const((const unsigned char *)str, ares_strlen(str)); |
|
if (buf == NULL) { |
|
status = ARES_ENOMEM; |
|
goto done; |
|
} |
|
|
|
status = ares__buf_split(buf, (const unsigned char *)" ,", 2, |
|
ARES_BUF_SPLIT_NONE, 0, &list); |
|
if (status != ARES_SUCCESS) { |
|
goto done; |
|
} |
|
|
|
for (node = ares__llist_node_first(list); node != NULL; |
|
node = ares__llist_node_next(node)) { |
|
ares__buf_t *entry = ares__llist_node_val(node); |
|
ares_sconfig_t s; |
|
|
|
status = parse_nameserver(entry, &s); |
|
if (status != ARES_SUCCESS) { |
|
if (ignore_invalid) { |
|
continue; |
|
} else { |
|
goto done; |
|
} |
|
} |
|
|
|
status = ares__sconfig_append(sconfig, &s.addr, s.udp_port, s.tcp_port, |
|
s.ll_iface); |
|
if (status != ARES_SUCCESS) { |
|
goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
status = ARES_SUCCESS; |
|
|
|
done: |
|
ares__llist_destroy(list); |
|
ares__buf_destroy(buf); |
|
return status; |
|
} |
|
|
|
static unsigned short ares__sconfig_get_port(const ares_channel_t *channel, |
|
const ares_sconfig_t *s, |
|
ares_bool_t is_tcp) |
|
{ |
|
unsigned short port = is_tcp ? s->tcp_port : s->udp_port; |
|
|
|
if (port == 0) { |
|
port = is_tcp ? channel->tcp_port : channel->udp_port; |
|
} |
|
|
|
if (port == 0) { |
|
port = 53; |
|
} |
|
|
|
return port; |
|
} |
|
|
|
static ares__slist_node_t *ares__server_find(ares_channel_t *channel, |
|
const ares_sconfig_t *s) |
|
{ |
|
ares__slist_node_t *node; |
|
|
|
for (node = ares__slist_node_first(channel->servers); node != NULL; |
|
node = ares__slist_node_next(node)) { |
|
const ares_server_t *server = ares__slist_node_val(node); |
|
|
|
if (!ares__addr_match(&server->addr, &s->addr)) { |
|
continue; |
|
} |
|
|
|
if (server->tcp_port != ares__sconfig_get_port(channel, s, ARES_TRUE)) { |
|
continue; |
|
} |
|
|
|
if (server->udp_port != ares__sconfig_get_port(channel, s, ARES_FALSE)) { |
|
continue; |
|
} |
|
|
|
return node; |
|
} |
|
return NULL; |
|
} |
|
|
|
static ares_bool_t ares__server_isdup(const ares_channel_t *channel, |
|
ares__llist_node_t *s) |
|
{ |
|
/* Scan backwards to see if this is a duplicate */ |
|
ares__llist_node_t *prev; |
|
const ares_sconfig_t *server = ares__llist_node_val(s); |
|
|
|
for (prev = ares__llist_node_prev(s); prev != NULL; |
|
prev = ares__llist_node_prev(prev)) { |
|
const ares_sconfig_t *p = ares__llist_node_val(prev); |
|
|
|
if (!ares__addr_match(&server->addr, &p->addr)) { |
|
continue; |
|
} |
|
|
|
if (ares__sconfig_get_port(channel, server, ARES_TRUE) != |
|
ares__sconfig_get_port(channel, p, ARES_TRUE)) { |
|
continue; |
|
} |
|
|
|
if (ares__sconfig_get_port(channel, server, ARES_FALSE) != |
|
ares__sconfig_get_port(channel, p, ARES_FALSE)) { |
|
continue; |
|
} |
|
|
|
return ARES_TRUE; |
|
} |
|
|
|
return ARES_FALSE; |
|
} |
|
|
|
static ares_status_t ares__server_create(ares_channel_t *channel, |
|
const ares_sconfig_t *sconfig, |
|
size_t idx) |
|
{ |
|
ares_status_t status; |
|
ares_server_t *server = ares_malloc_zero(sizeof(*server)); |
|
|
|
if (server == NULL) { |
|
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
server->idx = idx; |
|
server->channel = channel; |
|
server->udp_port = ares__sconfig_get_port(channel, sconfig, ARES_FALSE); |
|
server->tcp_port = ares__sconfig_get_port(channel, sconfig, ARES_TRUE); |
|
server->addr.family = sconfig->addr.family; |
|
server->next_retry_time.sec = 0; |
|
server->next_retry_time.usec = 0; |
|
|
|
if (sconfig->addr.family == AF_INET) { |
|
memcpy(&server->addr.addr.addr4, &sconfig->addr.addr.addr4, |
|
sizeof(server->addr.addr.addr4)); |
|
} else if (sconfig->addr.family == AF_INET6) { |
|
memcpy(&server->addr.addr.addr6, &sconfig->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->connections = ares__llist_create(NULL); |
|
if (server->connections == NULL) { |
|
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
if (ares__slist_insert(channel->servers, server) == NULL) { |
|
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
status = ARES_SUCCESS; |
|
|
|
done: |
|
if (status != ARES_SUCCESS) { |
|
ares__destroy_server(server); /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
return status; |
|
} |
|
|
|
static ares_bool_t ares__server_in_newconfig(const ares_server_t *server, |
|
ares__llist_t *srvlist) |
|
{ |
|
ares__llist_node_t *node; |
|
const ares_channel_t *channel = server->channel; |
|
|
|
for (node = ares__llist_node_first(srvlist); node != NULL; |
|
node = ares__llist_node_next(node)) { |
|
const ares_sconfig_t *s = ares__llist_node_val(node); |
|
|
|
if (!ares__addr_match(&server->addr, &s->addr)) { |
|
continue; |
|
} |
|
|
|
if (server->tcp_port != ares__sconfig_get_port(channel, s, ARES_TRUE)) { |
|
continue; |
|
} |
|
|
|
if (server->udp_port != ares__sconfig_get_port(channel, s, ARES_FALSE)) { |
|
continue; |
|
} |
|
|
|
return ARES_TRUE; |
|
} |
|
|
|
return ARES_FALSE; |
|
} |
|
|
|
static ares_bool_t ares__servers_remove_stale(ares_channel_t *channel, |
|
ares__llist_t *srvlist) |
|
{ |
|
ares_bool_t stale_removed = ARES_FALSE; |
|
ares__slist_node_t *snode = ares__slist_node_first(channel->servers); |
|
|
|
while (snode != NULL) { |
|
ares__slist_node_t *snext = ares__slist_node_next(snode); |
|
const ares_server_t *server = ares__slist_node_val(snode); |
|
if (!ares__server_in_newconfig(server, srvlist)) { |
|
/* This will clean up all server state via the destruction callback and |
|
* move any queries to new servers */ |
|
ares__slist_node_destroy(snode); |
|
stale_removed = ARES_TRUE; |
|
} |
|
snode = snext; |
|
} |
|
return stale_removed; |
|
} |
|
|
|
static void ares__servers_trim_single(ares_channel_t *channel) |
|
{ |
|
while (ares__slist_len(channel->servers) > 1) { |
|
ares__slist_node_destroy(ares__slist_node_last(channel->servers)); |
|
} |
|
} |
|
|
|
ares_status_t ares__servers_update(ares_channel_t *channel, |
|
ares__llist_t *server_list, |
|
ares_bool_t user_specified) |
|
{ |
|
ares__llist_node_t *node; |
|
size_t idx = 0; |
|
ares_status_t status; |
|
ares_bool_t list_changed = ARES_FALSE; |
|
|
|
if (channel == NULL) { |
|
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */ |
|
} |
|
|
|
/* NOTE: a NULL or zero entry server list is considered valid due to |
|
* real-world people needing support for this for their test harnesses |
|
*/ |
|
|
|
/* Add new entries */ |
|
for (node = ares__llist_node_first(server_list); node != NULL; |
|
node = ares__llist_node_next(node)) { |
|
const ares_sconfig_t *sconfig = ares__llist_node_val(node); |
|
ares__slist_node_t *snode; |
|
|
|
/* If a server has already appeared in the list of new servers, skip it. */ |
|
if (ares__server_isdup(channel, node)) { |
|
continue; |
|
} |
|
|
|
snode = ares__server_find(channel, sconfig); |
|
if (snode != NULL) { |
|
ares_server_t *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 |
|
* allocations so can't fail. */ |
|
ares__slist_node_reinsert(snode); |
|
} |
|
} else { |
|
status = ares__server_create(channel, sconfig, idx); |
|
if (status != ARES_SUCCESS) { |
|
goto done; |
|
} |
|
|
|
list_changed = ARES_TRUE; |
|
} |
|
|
|
idx++; |
|
} |
|
|
|
/* Remove any servers that don't exist in the current configuration */ |
|
if (ares__servers_remove_stale(channel, server_list)) { |
|
list_changed = ARES_TRUE; |
|
} |
|
|
|
/* Trim to one server if ARES_FLAG_PRIMARY is set. */ |
|
if (channel->flags & ARES_FLAG_PRIMARY) { |
|
ares__servers_trim_single(channel); |
|
} |
|
|
|
if (user_specified) { |
|
/* Save servers as if they were passed in as an option */ |
|
channel->optmask |= ARES_OPT_SERVERS; |
|
} |
|
|
|
/* Clear any cached query results only if the server list changed */ |
|
if (list_changed) { |
|
ares__qcache_flush(channel->qcache); |
|
} |
|
|
|
status = ARES_SUCCESS; |
|
|
|
done: |
|
return status; |
|
} |
|
|
|
static ares_status_t |
|
ares_addr_node_to_sconfig_llist(const struct ares_addr_node *servers, |
|
ares__llist_t **llist) |
|
{ |
|
const struct ares_addr_node *node; |
|
ares__llist_t *s; |
|
|
|
*llist = NULL; |
|
|
|
s = ares__llist_create(ares_free); |
|
if (s == NULL) { |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
for (node = servers; node != NULL; node = node->next) { |
|
ares_sconfig_t *sconfig; |
|
|
|
/* Invalid entry */ |
|
if (node->family != AF_INET && node->family != AF_INET6) { |
|
continue; |
|
} |
|
|
|
sconfig = ares_malloc_zero(sizeof(*sconfig)); |
|
if (sconfig == NULL) { |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
sconfig->addr.family = node->family; |
|
if (node->family == AF_INET) { |
|
memcpy(&sconfig->addr.addr.addr4, &node->addr.addr4, |
|
sizeof(sconfig->addr.addr.addr4)); |
|
} else if (sconfig->addr.family == AF_INET6) { |
|
memcpy(&sconfig->addr.addr.addr6, &node->addr.addr6, |
|
sizeof(sconfig->addr.addr.addr6)); |
|
} |
|
|
|
if (ares__llist_insert_last(s, sconfig) == NULL) { |
|
ares_free(sconfig); /* LCOV_EXCL_LINE: OutOfMemory */ |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
*llist = s; |
|
return ARES_SUCCESS; |
|
|
|
/* LCOV_EXCL_START: OutOfMemory */ |
|
fail: |
|
ares__llist_destroy(s); |
|
return ARES_ENOMEM; |
|
/* LCOV_EXCL_STOP */ |
|
} |
|
|
|
static ares_status_t |
|
ares_addrpnode_to_sconfig_llist(const struct ares_addr_port_node *servers, |
|
ares__llist_t **llist) |
|
{ |
|
const struct ares_addr_port_node *node; |
|
ares__llist_t *s; |
|
|
|
*llist = NULL; |
|
|
|
s = ares__llist_create(ares_free); |
|
if (s == NULL) { |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
for (node = servers; node != NULL; node = node->next) { |
|
ares_sconfig_t *sconfig; |
|
|
|
/* Invalid entry */ |
|
if (node->family != AF_INET && node->family != AF_INET6) { |
|
continue; |
|
} |
|
|
|
sconfig = ares_malloc_zero(sizeof(*sconfig)); |
|
if (sconfig == NULL) { |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
sconfig->addr.family = node->family; |
|
if (node->family == AF_INET) { |
|
memcpy(&sconfig->addr.addr.addr4, &node->addr.addr4, |
|
sizeof(sconfig->addr.addr.addr4)); |
|
} else if (sconfig->addr.family == AF_INET6) { |
|
memcpy(&sconfig->addr.addr.addr6, &node->addr.addr6, |
|
sizeof(sconfig->addr.addr.addr6)); |
|
} |
|
|
|
sconfig->tcp_port = (unsigned short)node->tcp_port; |
|
sconfig->udp_port = (unsigned short)node->udp_port; |
|
|
|
if (ares__llist_insert_last(s, sconfig) == NULL) { |
|
ares_free(sconfig); /* LCOV_EXCL_LINE: OutOfMemory */ |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
*llist = s; |
|
return ARES_SUCCESS; |
|
|
|
/* LCOV_EXCL_START: OutOfMemory */ |
|
fail: |
|
ares__llist_destroy(s); |
|
return ARES_ENOMEM; |
|
/* LCOV_EXCL_STOP */ |
|
} |
|
|
|
ares_status_t ares_in_addr_to_sconfig_llist(const struct in_addr *servers, |
|
size_t nservers, |
|
ares__llist_t **llist) |
|
{ |
|
size_t i; |
|
ares__llist_t *s; |
|
|
|
*llist = NULL; |
|
|
|
s = ares__llist_create(ares_free); |
|
if (s == NULL) { |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
for (i = 0; servers != NULL && i < nservers; i++) { |
|
ares_sconfig_t *sconfig; |
|
|
|
sconfig = ares_malloc_zero(sizeof(*sconfig)); |
|
if (sconfig == NULL) { |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
sconfig->addr.family = AF_INET; |
|
memcpy(&sconfig->addr.addr.addr4, &servers[i], |
|
sizeof(sconfig->addr.addr.addr4)); |
|
|
|
if (ares__llist_insert_last(s, sconfig) == NULL) { |
|
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
*llist = s; |
|
return ARES_SUCCESS; |
|
|
|
/* LCOV_EXCL_START: OutOfMemory */ |
|
fail: |
|
ares__llist_destroy(s); |
|
return ARES_ENOMEM; |
|
/* LCOV_EXCL_STOP */ |
|
} |
|
|
|
/* Write out the details of a server to a buffer */ |
|
ares_status_t ares_get_server_addr(const ares_server_t *server, |
|
ares__buf_t *buf) |
|
{ |
|
ares_status_t status; |
|
char addr[INET6_ADDRSTRLEN]; |
|
|
|
/* ipv4addr or [ipv6addr] */ |
|
if (server->addr.family == AF_INET6) { |
|
status = ares__buf_append_byte(buf, '['); |
|
if (status != ARES_SUCCESS) { |
|
return status; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
ares_inet_ntop(server->addr.family, &server->addr.addr, addr, sizeof(addr)); |
|
|
|
status = ares__buf_append_str(buf, addr); |
|
if (status != ARES_SUCCESS) { |
|
return status; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
if (server->addr.family == AF_INET6) { |
|
status = ares__buf_append_byte(buf, ']'); |
|
if (status != ARES_SUCCESS) { |
|
return status; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
/* :port */ |
|
status = ares__buf_append_byte(buf, ':'); |
|
if (status != ARES_SUCCESS) { |
|
return status; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
status = ares__buf_append_num_dec(buf, server->udp_port, 0); |
|
if (status != ARES_SUCCESS) { |
|
return status; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
/* %iface */ |
|
if (ares_strlen(server->ll_iface)) { |
|
status = ares__buf_append_byte(buf, '%'); |
|
if (status != ARES_SUCCESS) { |
|
return status; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
status = ares__buf_append_str(buf, server->ll_iface); |
|
if (status != ARES_SUCCESS) { |
|
return status; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
return ARES_SUCCESS; |
|
} |
|
|
|
int ares_get_servers(const ares_channel_t *channel, |
|
struct ares_addr_node **servers) |
|
{ |
|
struct ares_addr_node *srvr_head = NULL; |
|
struct ares_addr_node *srvr_last = NULL; |
|
struct ares_addr_node *srvr_curr; |
|
ares_status_t status = ARES_SUCCESS; |
|
ares__slist_node_t *node; |
|
|
|
if (channel == NULL) { |
|
return ARES_ENODATA; |
|
} |
|
|
|
ares__channel_lock(channel); |
|
|
|
for (node = ares__slist_node_first(channel->servers); node != NULL; |
|
node = ares__slist_node_next(node)) { |
|
const ares_server_t *server = ares__slist_node_val(node); |
|
|
|
/* Allocate storage for this server node appending it to the list */ |
|
srvr_curr = ares_malloc_data(ARES_DATATYPE_ADDR_NODE); |
|
if (!srvr_curr) { |
|
status = ARES_ENOMEM; |
|
break; |
|
} |
|
if (srvr_last) { |
|
srvr_last->next = srvr_curr; |
|
} else { |
|
srvr_head = srvr_curr; |
|
} |
|
srvr_last = srvr_curr; |
|
|
|
/* Fill this server node data */ |
|
srvr_curr->family = server->addr.family; |
|
if (srvr_curr->family == AF_INET) { |
|
memcpy(&srvr_curr->addr.addr4, &server->addr.addr.addr4, |
|
sizeof(srvr_curr->addr.addr4)); |
|
} else { |
|
memcpy(&srvr_curr->addr.addr6, &server->addr.addr.addr6, |
|
sizeof(srvr_curr->addr.addr6)); |
|
} |
|
} |
|
|
|
if (status != ARES_SUCCESS) { |
|
ares_free_data(srvr_head); |
|
srvr_head = NULL; |
|
} |
|
|
|
*servers = srvr_head; |
|
|
|
ares__channel_unlock(channel); |
|
|
|
return (int)status; |
|
} |
|
|
|
int ares_get_servers_ports(const ares_channel_t *channel, |
|
struct ares_addr_port_node **servers) |
|
{ |
|
struct ares_addr_port_node *srvr_head = NULL; |
|
struct ares_addr_port_node *srvr_last = NULL; |
|
struct ares_addr_port_node *srvr_curr; |
|
ares_status_t status = ARES_SUCCESS; |
|
ares__slist_node_t *node; |
|
|
|
if (channel == NULL) { |
|
return ARES_ENODATA; |
|
} |
|
|
|
ares__channel_lock(channel); |
|
|
|
for (node = ares__slist_node_first(channel->servers); node != NULL; |
|
node = ares__slist_node_next(node)) { |
|
const ares_server_t *server = ares__slist_node_val(node); |
|
|
|
/* Allocate storage for this server node appending it to the list */ |
|
srvr_curr = ares_malloc_data(ARES_DATATYPE_ADDR_PORT_NODE); |
|
if (!srvr_curr) { |
|
status = ARES_ENOMEM; |
|
break; |
|
} |
|
if (srvr_last) { |
|
srvr_last->next = srvr_curr; |
|
} else { |
|
srvr_head = srvr_curr; |
|
} |
|
srvr_last = srvr_curr; |
|
|
|
/* Fill this server node data */ |
|
srvr_curr->family = server->addr.family; |
|
srvr_curr->udp_port = server->udp_port; |
|
srvr_curr->tcp_port = server->tcp_port; |
|
|
|
if (srvr_curr->family == AF_INET) { |
|
memcpy(&srvr_curr->addr.addr4, &server->addr.addr.addr4, |
|
sizeof(srvr_curr->addr.addr4)); |
|
} else { |
|
memcpy(&srvr_curr->addr.addr6, &server->addr.addr.addr6, |
|
sizeof(srvr_curr->addr.addr6)); |
|
} |
|
} |
|
|
|
if (status != ARES_SUCCESS) { |
|
ares_free_data(srvr_head); |
|
srvr_head = NULL; |
|
} |
|
|
|
*servers = srvr_head; |
|
|
|
ares__channel_unlock(channel); |
|
return (int)status; |
|
} |
|
|
|
int ares_set_servers(ares_channel_t *channel, |
|
const struct ares_addr_node *servers) |
|
{ |
|
ares__llist_t *slist; |
|
ares_status_t status; |
|
|
|
if (channel == NULL) { |
|
return ARES_ENODATA; |
|
} |
|
|
|
status = ares_addr_node_to_sconfig_llist(servers, &slist); |
|
if (status != ARES_SUCCESS) { |
|
return (int)status; |
|
} |
|
|
|
ares__channel_lock(channel); |
|
status = ares__servers_update(channel, slist, ARES_TRUE); |
|
ares__channel_unlock(channel); |
|
|
|
ares__llist_destroy(slist); |
|
|
|
return (int)status; |
|
} |
|
|
|
int ares_set_servers_ports(ares_channel_t *channel, |
|
const struct ares_addr_port_node *servers) |
|
{ |
|
ares__llist_t *slist; |
|
ares_status_t status; |
|
|
|
if (channel == NULL) { |
|
return ARES_ENODATA; |
|
} |
|
|
|
status = ares_addrpnode_to_sconfig_llist(servers, &slist); |
|
if (status != ARES_SUCCESS) { |
|
return (int)status; |
|
} |
|
|
|
ares__channel_lock(channel); |
|
status = ares__servers_update(channel, slist, ARES_TRUE); |
|
ares__channel_unlock(channel); |
|
|
|
ares__llist_destroy(slist); |
|
|
|
return (int)status; |
|
} |
|
|
|
/* Incoming string format: host[:port][,host[:port]]... */ |
|
/* IPv6 addresses with ports require square brackets [fe80::1]:53 */ |
|
static ares_status_t set_servers_csv(ares_channel_t *channel, const char *_csv) |
|
{ |
|
ares_status_t status; |
|
ares__llist_t *slist = NULL; |
|
|
|
if (channel == NULL) { |
|
return ARES_ENODATA; |
|
} |
|
|
|
if (ares_strlen(_csv) == 0) { |
|
/* blank all servers */ |
|
ares__channel_lock(channel); |
|
status = ares__servers_update(channel, NULL, ARES_TRUE); |
|
ares__channel_unlock(channel); |
|
return status; |
|
} |
|
|
|
status = ares__sconfig_append_fromstr(&slist, _csv, ARES_FALSE); |
|
if (status != ARES_SUCCESS) { |
|
ares__llist_destroy(slist); |
|
return status; |
|
} |
|
|
|
ares__channel_lock(channel); |
|
status = ares__servers_update(channel, slist, ARES_TRUE); |
|
ares__channel_unlock(channel); |
|
|
|
ares__llist_destroy(slist); |
|
|
|
return status; |
|
} |
|
|
|
/* We'll go ahead and honor ports anyhow */ |
|
int ares_set_servers_csv(ares_channel_t *channel, const char *_csv) |
|
{ |
|
return (int)set_servers_csv(channel, _csv); |
|
} |
|
|
|
int ares_set_servers_ports_csv(ares_channel_t *channel, const char *_csv) |
|
{ |
|
return (int)set_servers_csv(channel, _csv); |
|
} |
|
|
|
char *ares_get_servers_csv(const 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; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
|
|
for (node = ares__slist_node_first(channel->servers); node != NULL; |
|
node = ares__slist_node_next(node)) { |
|
ares_status_t status; |
|
const ares_server_t *server = ares__slist_node_val(node); |
|
|
|
if (ares__buf_len(buf)) { |
|
status = ares__buf_append_byte(buf, ','); |
|
if (status != ARES_SUCCESS) { |
|
goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
status = ares_get_server_addr(server, buf); |
|
if (status != ARES_SUCCESS) { |
|
goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
|
} |
|
} |
|
|
|
out = ares__buf_finish_str(buf, NULL); |
|
buf = NULL; |
|
|
|
done: |
|
ares__channel_unlock(channel); |
|
ares__buf_destroy(buf); |
|
return out; |
|
} |
|
|
|
void ares_set_server_state_callback(ares_channel_t *channel, |
|
ares_server_state_callback cb, void *data) |
|
{ |
|
if (channel == NULL) { |
|
return; /* LCOV_EXCL_LINE: DefensiveCoding */ |
|
} |
|
channel->server_state_cb = cb; |
|
channel->server_state_cb_data = data; |
|
}
|
|
|