Support configuration of DNS server ports (#534)

As per https://man.openbsd.org/OpenBSD-5.1/resolv.conf.5 we should
support bracketed syntax for resolv.conf entries to contain an optional
port number.

We also need to utilize this format for configuration of MacOS
DNS servers as seen when using the Viscosity OpenVPN client, where
it starts a private DNS server listening on localhost on a non-standard
port.

Fix By: Brad House (@bradh352)
pull/535/head
Brad House 2 years ago committed by GitHub
parent c1b00c41a7
commit a21d67e679
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 156
      src/lib/ares_init.c

@ -73,7 +73,7 @@ static int init_by_defaults(ares_channel channel);
#ifndef WATT32
static int config_nameserver(struct server_state **servers, int *nservers,
char *str);
const char *str);
#endif
static int set_search(ares_channel channel, const char *str);
static int set_options(ares_channel channel, const char *str);
@ -1313,18 +1313,28 @@ static int init_by_resolv_conf(ares_channel channel)
int nscount = res_getservers(&res, addr, MAXNS);
int i;
for (i = 0; i < nscount; ++i) {
char str[INET6_ADDRSTRLEN];
char ipaddr[INET6_ADDRSTRLEN] = "";
char ipaddr_port[INET6_ADDRSTRLEN + 8]; /* [%s]:NNNNN */
unsigned short port = 0;
int config_status;
sa_family_t family = addr[i].sin.sin_family;
if (family == AF_INET) {
ares_inet_ntop(family, &addr[i].sin.sin_addr, str, sizeof(str));
ares_inet_ntop(family, &addr[i].sin.sin_addr, ipaddr, sizeof(ipaddr));
port = ntohs(addr[i].sin.sin_port);
} else if (family == AF_INET6) {
ares_inet_ntop(family, &addr[i].sin6.sin6_addr, str, sizeof(str));
ares_inet_ntop(family, &addr[i].sin6.sin6_addr, ipaddr, sizeof(ipaddr));
port = ntohs(addr[i].sin6.sin6_port);
} else {
continue;
}
config_status = config_nameserver(&servers, &nservers, str);
if (port) {
snprintf(ipaddr_port, sizeof(ipaddr_port), "[%s]:%u", ipaddr, port);
} else {
snprintf(ipaddr_port, sizeof(ipaddr_port), "%s", ipaddr);
}
config_status = config_nameserver(&servers, &nservers, ipaddr_port);
if (config_status != ARES_SUCCESS) {
status = config_status;
break;
@ -1831,8 +1841,112 @@ static int ares_ipv6_server_blacklisted(const unsigned char ipaddr[16])
return 0;
}
/* Add the IPv4 or IPv6 nameservers in str (separated by commas) to the
* servers list, updating servers and nservers as required.
/* Parse address and port in these formats, either ipv4 or ipv6 addresses
* are allowed:
* ipaddr
* [ipaddr]
* [ipaddr]:port
*
* 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 int parse_dnsaddrport(const char *str, size_t len,
struct ares_addr *host, unsigned short *port)
{
char ipaddr[INET6_ADDRSTRLEN] = "";
char ipport[6] = "";
size_t mylen;
const char *addr_start = NULL;
const char *addr_end = NULL;
const char *port_start = NULL;
const char *port_end = NULL;
/* Must start with [, hex digit or : */
if (len == 0 || (*str != '[' && !isxdigit(*str) && *str != ':')) {
return ARES_EBADSTR;
}
/* If it starts with a bracket, must end with a bracket */
if (*str == '[') {
const char *ptr;
addr_start = str+1;
ptr = memchr(addr_start, ']', len-1);
if (ptr == NULL) {
return ARES_EBADSTR;
}
addr_end = ptr-1;
/* Try to pull off port */
if ((size_t)(ptr - str) < len) {
ptr++;
if (*ptr != ':') {
return ARES_EBADSTR;
}
/* Missing port number */
if ((size_t)(ptr - str) == len) {
return ARES_EBADSTR;
}
port_start = ptr+1;
port_end = str+(len-1);
}
} else {
addr_start = str;
addr_end = str+(len-1);
}
mylen = (addr_end-addr_start)+1;
/* Larger than buffer with null term */
if (mylen+1 > sizeof(ipaddr)) {
return ARES_EBADSTR;
}
memset(ipaddr, 0, sizeof(ipaddr));
memcpy(ipaddr, addr_start, mylen);
if (port_start) {
mylen = (port_end-port_start)+1;
/* Larger than buffer with null term */
if (mylen+1 > sizeof(ipport)) {
return ARES_EBADSTR;
}
memset(ipport, 0, sizeof(ipport));
memcpy(ipport, port_start, mylen);
} else {
snprintf(ipport, sizeof(ipport), "0");
}
/* Convert textual address to binary format. */
if (ares_inet_pton(AF_INET, ipaddr, &host->addrV4) == 1) {
host->family = AF_INET;
} else if (ares_inet_pton(AF_INET6, ipaddr, &host->addrV6) == 1
/* Silently skip blacklisted IPv6 servers. */
&& !ares_ipv6_server_blacklisted(
(const unsigned char *)&host->addrV6)) {
host->family = AF_INET6;
} else {
return ARES_EBADSTR;
}
*port = (unsigned short)atoi(ipport);
return ARES_SUCCESS;
}
/* 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().
@ -1840,16 +1954,18 @@ static int ares_ipv6_server_blacklisted(const unsigned char ipaddr[16])
* Returns an error code on failure, else ARES_SUCCESS.
*/
static int config_nameserver(struct server_state **servers, int *nservers,
char *str)
const char *str)
{
struct ares_addr host;
struct server_state *newserv;
char *p, *txtaddr;
const char *p, *txtaddr;
/* On Windows, there may be more than one nameserver specified in the same
* registry key, so we parse input as a space or comma seperated list.
*/
for (p = str; p;)
{
unsigned short port;
/* Skip whitespace and commas. */
while (*p && (ISSPACE(*p) || (*p == ',')))
p++;
@ -1863,23 +1979,11 @@ static int config_nameserver(struct server_state **servers, int *nservers,
/* Advance past this address. */
while (*p && !ISSPACE(*p) && (*p != ','))
p++;
if (*p)
/* Null terminate this address. */
*p++ = '\0';
else
/* Reached end of input, done when this address is processed. */
p = NULL;
/* Convert textual address to binary format. */
if (ares_inet_pton(AF_INET, txtaddr, &host.addrV4) == 1)
host.family = AF_INET;
else if (ares_inet_pton(AF_INET6, txtaddr, &host.addrV6) == 1
/* Silently skip blacklisted IPv6 servers. */
&& !ares_ipv6_server_blacklisted(
(const unsigned char *)&host.addrV6))
host.family = AF_INET6;
else
if (parse_dnsaddrport(txtaddr, p-txtaddr, &host, &port) !=
ARES_SUCCESS) {
continue;
}
/* Resize servers state array. */
newserv = ares_realloc(*servers, (*nservers + 1) *
@ -1889,8 +1993,8 @@ static int config_nameserver(struct server_state **servers, int *nservers,
/* Store address data. */
newserv[*nservers].addr.family = host.family;
newserv[*nservers].addr.udp_port = 0;
newserv[*nservers].addr.tcp_port = 0;
newserv[*nservers].addr.udp_port = htons(port);
newserv[*nservers].addr.tcp_port = htons(port);
if (host.family == AF_INET)
memcpy(&newserv[*nservers].addr.addrV4, &host.addrV4,
sizeof(host.addrV4));

Loading…
Cancel
Save