Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply the IPv6 server blacklist to all nameserver sources, not just Windows #193

Merged
merged 1 commit into from May 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
137 changes: 74 additions & 63 deletions ares_init.c
Expand Up @@ -997,63 +997,6 @@ static int compareAddresses(const void *arg1,
return 0;
}

/* Validate that the ip address matches the subnet (network base and network
* mask) specified. Addresses are specified in standard Network Byte Order as
* 16 bytes, and the netmask is 0 to 128 (bits).
*/
static int ares_ipv6_subnet_matches(const unsigned char netbase[16],
unsigned char netmask,
const unsigned char ipaddr[16])
{
unsigned char mask[16] = { 0 };
unsigned char i;

/* Misuse */
if (netmask > 128)
return 0;

/* Quickly set whole bytes */
memset(mask, 0xFF, netmask / 8);

/* Set remaining bits */
if(netmask % 8) {
mask[netmask / 8] = (unsigned char)(0xff << (8 - (netmask % 8)));
}

for (i=0; i<16; i++) {
if ((netbase[i] & mask[i]) != (ipaddr[i] & mask[i]))
return 0;
}

return 1;
}

static int ares_ipv6_server_blacklisted(const unsigned char ipaddr[16])
{
const struct {
const char *netbase;
unsigned char netmask;
} blacklist[] = {
/* Deprecated by [RFC3879] in September 2004. Formerly a Site-Local scoped
* address prefix. Causes known issues on Windows as these are not valid DNS
* servers. */
{ "fec0::", 10 },
{ NULL, 0 }
};
size_t i;

for (i=0; blacklist[i].netbase != NULL; i++) {
unsigned char netbase[16];

if (ares_inet_pton(AF_INET6, blacklist[i].netbase, netbase) != 1)
continue;

if (ares_ipv6_subnet_matches(netbase, blacklist[i].netmask, ipaddr))
return 1;
}
return 0;
}

/* There can be multiple routes to "the Internet". And there can be different
* DNS servers associated with each of the interfaces that offer those routes.
* We have to assume that any DNS server can serve any request. But, some DNS
Expand Down Expand Up @@ -1276,11 +1219,6 @@ static int get_DNS_AdaptersAddresses(char **outptr)
sizeof(namesrvr.sa6->sin6_addr)) == 0)
continue;

if (ares_ipv6_server_blacklisted(
(const unsigned char *)&namesrvr.sa6->sin6_addr)
)
continue;

/* Allocate room for another address, if necessary, else skip. */
if(addressesIndex == addressesSize) {
const size_t newSize = addressesSize + 4;
Expand Down Expand Up @@ -2109,6 +2047,76 @@ static int config_lookup(ares_channel channel, const char *str,
#endif /* !WIN32 & !WATT32 & !ANDROID & !__ANDROID__ & !CARES_USE_LIBRESOLV */

#ifndef WATT32
/* Validate that the ip address matches the subnet (network base and network
* mask) specified. Addresses are specified in standard Network Byte Order as
* 16 bytes, and the netmask is 0 to 128 (bits).
*/
static int ares_ipv6_subnet_matches(const unsigned char netbase[16],
unsigned char netmask,
const unsigned char ipaddr[16])
{
unsigned char mask[16] = { 0 };
unsigned char i;

/* Misuse */
if (netmask > 128)
return 0;

/* Quickly set whole bytes */
memset(mask, 0xFF, netmask / 8);

/* Set remaining bits */
if(netmask % 8) {
mask[netmask / 8] = (unsigned char)(0xff << (8 - (netmask % 8)));
}

for (i=0; i<16; i++) {
if ((netbase[i] & mask[i]) != (ipaddr[i] & mask[i]))
return 0;
}

return 1;
}

/* Return true iff the IPv6 ipaddr is blacklisted. */
static int ares_ipv6_server_blacklisted(const unsigned char ipaddr[16])
{
/* A list of blacklisted IPv6 subnets. */
const struct {
const unsigned char netbase[16];
unsigned char netmask;
} blacklist[] = {
/* 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;

/* See if ipaddr matches any of the entries in the blacklist. */
for (i = 0; i < sizeof(blacklist) / sizeof(blacklist[0]); ++i) {
if (ares_ipv6_subnet_matches(
blacklist[i].netbase, blacklist[i].netmask, ipaddr))
return 1;
}
return 0;
}

/* Add the IPv4 or IPv6 nameservers in str (separated by commas) to the
* servers list, updating servers and nservers as required.
*
* This will silently ignore blacklisted IPv6 nameservers as detected by
* ares_ipv6_server_blacklisted().
*
* Returns an error code on failure, else ARES_SUCCESS.
*/
static int config_nameserver(struct server_state **servers, int *nservers,
char *str)
{
Expand Down Expand Up @@ -2143,7 +2151,10 @@ static int config_nameserver(struct server_state **servers, int *nservers,
/* 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)
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
continue;
Expand Down
30 changes: 30 additions & 0 deletions test/ares-test-init.cc
Expand Up @@ -525,6 +525,36 @@ CONTAINED_TEST_F(LibraryTest, ContainerRotateOverride,
return HasFailure();
}

// Test that blacklisted IPv6 resolves are ignored. They're filtered from any
// source, so resolv.conf is as good as any.
NameContentList blacklistedIpv6 = {
{"/etc/resolv.conf", " nameserver 254.192.1.1\n" // 0xfe.0xc0.0x01.0x01
" nameserver fec0::dead\n" // Blacklisted
" nameserver ffc0::c001\n" // Not blacklisted
" domain first.com\n"},
{"/etc/nsswitch.conf", "hosts: files\n"}};
CONTAINED_TEST_F(LibraryTest, ContainerBlacklistedIpv6,
"myhostname", "mydomainname.org", blacklistedIpv6) {
ares_channel channel = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_init(&channel));
std::vector<std::string> actual = GetNameServers(channel);
std::vector<std::string> expected = {
"254.192.1.1",
"ffc0:0000:0000:0000:0000:0000:0000:c001"
};
EXPECT_EQ(expected, actual);

struct ares_options opts;
int optmask = 0;
ares_save_options(channel, &opts, &optmask);
EXPECT_EQ(1, opts.ndomains);
EXPECT_EQ(std::string("first.com"), std::string(opts.domains[0]));
ares_destroy_options(&opts);

ares_destroy(channel);
return HasFailure();
}

NameContentList multiresolv = {
{"/etc/resolv.conf", " nameserver 1::2 ; ;;\n"
" domain first.com\n"},
Expand Down