blob: 45af232c8f477f16261291e16535e0a4c2c1e2f8 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/dns/dns_config_service_win.h"
#include <string>
#include <vector>
#include "base/check.h"
#include "base/memory/free_deleter.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/dns/public/dns_protocol.h"
#include "net/dns/public/win_dns_system_settings.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace net {
namespace {
TEST(DnsConfigServiceWinTest, ParseSearchList) {
const struct TestCase {
const wchar_t* input;
std::vector<std::string> expected;
} cases[] = {
{L"chromium.org", {"chromium.org"}},
{L"chromium.org,org", {"chromium.org", "org"}},
// Empty suffixes terminate the list
{L"crbug.com,com,,org", {"crbug.com", "com"}},
// IDN are converted to punycode
{L"\u017c\xf3\u0142ta.pi\u0119\u015b\u0107.pl,pl",
{"xn--ta-4ja03asj.xn--pi-wla5e0q.pl", "pl"}},
// Empty search list is invalid
{L"", {}},
{L",,", {}},
};
for (const auto& t : cases) {
EXPECT_EQ(internal::ParseSearchList(t.input), t.expected);
}
}
struct AdapterInfo {
IFTYPE if_type;
IF_OPER_STATUS oper_status;
const WCHAR* dns_suffix;
std::string dns_server_addresses[4]; // Empty string indicates end.
uint16_t ports[4];
};
std::unique_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> CreateAdapterAddresses(
const AdapterInfo* infos) {
size_t num_adapters = 0;
size_t num_addresses = 0;
for (size_t i = 0; infos[i].if_type; ++i) {
++num_adapters;
for (size_t j = 0; !infos[i].dns_server_addresses[j].empty(); ++j) {
++num_addresses;
}
}
size_t heap_size = num_adapters * sizeof(IP_ADAPTER_ADDRESSES) +
num_addresses * (sizeof(IP_ADAPTER_DNS_SERVER_ADDRESS) +
sizeof(struct sockaddr_storage));
std::unique_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> heap(
static_cast<IP_ADAPTER_ADDRESSES*>(malloc(heap_size)));
CHECK(heap.get());
memset(heap.get(), 0, heap_size);
IP_ADAPTER_ADDRESSES* adapters = heap.get();
IP_ADAPTER_DNS_SERVER_ADDRESS* addresses =
reinterpret_cast<IP_ADAPTER_DNS_SERVER_ADDRESS*>(adapters + num_adapters);
struct sockaddr_storage* storage =
reinterpret_cast<struct sockaddr_storage*>(addresses + num_addresses);
for (size_t i = 0; i < num_adapters; ++i) {
const AdapterInfo& info = infos[i];
IP_ADAPTER_ADDRESSES* adapter = adapters + i;
if (i + 1 < num_adapters)
adapter->Next = adapter + 1;
adapter->IfType = info.if_type;
adapter->OperStatus = info.oper_status;
adapter->DnsSuffix = const_cast<PWCHAR>(info.dns_suffix);
IP_ADAPTER_DNS_SERVER_ADDRESS* address = nullptr;
for (size_t j = 0; !info.dns_server_addresses[j].empty(); ++j) {
--num_addresses;
if (j == 0) {
address = adapter->FirstDnsServerAddress = addresses + num_addresses;
} else {
// Note that |address| is moving backwards.
address = address->Next = address - 1;
}
IPAddress ip;
CHECK(ip.AssignFromIPLiteral(info.dns_server_addresses[j]));
IPEndPoint ipe = IPEndPoint(ip, info.ports[j]);
address->Address.lpSockaddr =
reinterpret_cast<LPSOCKADDR>(storage + num_addresses);
socklen_t length = sizeof(struct sockaddr_storage);
CHECK(ipe.ToSockAddr(address->Address.lpSockaddr, &length));
address->Address.iSockaddrLength = static_cast<int>(length);
}
}
return heap;
}
TEST(DnsConfigServiceWinTest, ConvertAdapterAddresses) {
// Check nameservers and connection-specific suffix.
const struct TestCase {
AdapterInfo input_adapters[4]; // |if_type| == 0 indicates end.
std::string expected_nameservers[4]; // Empty string indicates end.
std::string expected_suffix;
uint16_t expected_ports[4];
} cases[] = {
{ // Ignore loopback and inactive adapters.
{
{ IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"funnyloop",
{ "2.0.0.2" } },
{ IF_TYPE_FASTETHER, IfOperStatusDormant, L"example.com",
{ "1.0.0.1" } },
{ IF_TYPE_USB, IfOperStatusUp, L"chromium.org",
{ "10.0.0.10", "2001:FFFF::1111" } },
{ 0 },
},
{ "10.0.0.10", "2001:FFFF::1111" },
"chromium.org",
},
{ // Respect configured ports.
{
{ IF_TYPE_USB, IfOperStatusUp, L"chromium.org",
{ "10.0.0.10", "2001:FFFF::1111" }, { 1024, 24 } },
{ 0 },
},
{ "10.0.0.10", "2001:FFFF::1111" },
"chromium.org",
{ 1024, 24 },
},
{ // Use the preferred adapter (first in binding order) and filter
// stateless DNS discovery addresses.
{
{ IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"funnyloop",
{ "2.0.0.2" } },
{ IF_TYPE_FASTETHER, IfOperStatusUp, L"example.com",
{ "1.0.0.1", "fec0:0:0:ffff::2", "8.8.8.8" } },
{ IF_TYPE_USB, IfOperStatusUp, L"chromium.org",
{ "10.0.0.10", "2001:FFFF::1111" } },
{ 0 },
},
{ "1.0.0.1", "8.8.8.8" },
"example.com",
},
{ // No usable adapters.
{
{ IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"localhost",
{ "2.0.0.2" } },
{ IF_TYPE_FASTETHER, IfOperStatusDormant, L"example.com",
{ "1.0.0.1" } },
{ IF_TYPE_USB, IfOperStatusUp, L"chromium.org" },
{ 0 },
},
},
};
for (const auto& t : cases) {
WinDnsSystemSettings settings;
settings.addresses = CreateAdapterAddresses(t.input_adapters);
// Default settings for the rest.
std::vector<IPEndPoint> expected_nameservers;
for (size_t j = 0; !t.expected_nameservers[j].empty(); ++j) {
IPAddress ip;
ASSERT_TRUE(ip.AssignFromIPLiteral(t.expected_nameservers[j]));
uint16_t port = t.expected_ports[j];
if (!port)
port = dns_protocol::kDefaultPort;
expected_nameservers.push_back(IPEndPoint(ip, port));
}
absl::optional<DnsConfig> config =
internal::ConvertSettingsToDnsConfig(settings);
bool expected_success = !expected_nameservers.empty();
EXPECT_EQ(expected_success, config.has_value());
if (config.has_value()) {
EXPECT_EQ(expected_nameservers, config->nameservers);
EXPECT_THAT(config->search, testing::ElementsAre(t.expected_suffix));
}
}
}
TEST(DnsConfigServiceWinTest, ConvertSuffixSearch) {
AdapterInfo infos[2] = {
{ IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } },
{ 0 },
};
const struct TestCase {
struct {
absl::optional<std::wstring> policy_search_list;
absl::optional<std::wstring> tcpip_search_list;
absl::optional<std::wstring> tcpip_domain;
absl::optional<std::wstring> primary_dns_suffix;
WinDnsSystemSettings::DevolutionSetting policy_devolution;
WinDnsSystemSettings::DevolutionSetting dnscache_devolution;
WinDnsSystemSettings::DevolutionSetting tcpip_devolution;
} input_settings;
std::vector<std::string> expected_search;
} cases[] = {
{
// Policy SearchList override.
{
L"policy.searchlist.a,policy.searchlist.b",
L"tcpip.searchlist.a,tcpip.searchlist.b",
L"tcpip.domain",
L"primary.dns.suffix",
},
{"policy.searchlist.a", "policy.searchlist.b"},
},
{
// User-specified SearchList override.
{
absl::nullopt,
L"tcpip.searchlist.a,tcpip.searchlist.b",
L"tcpip.domain",
L"primary.dns.suffix",
},
{"tcpip.searchlist.a", "tcpip.searchlist.b"},
},
{
// Void SearchList. Using tcpip.domain
{
L",bad.searchlist,parsed.as.empty",
L"tcpip.searchlist,good.but.overridden",
L"tcpip.domain",
absl::nullopt,
},
{"tcpip.domain", "connection.suffix"},
},
{
// Void SearchList. Using primary.dns.suffix
{
L",bad.searchlist,parsed.as.empty",
L"tcpip.searchlist,good.but.overridden",
L"tcpip.domain",
L"primary.dns.suffix",
},
{"primary.dns.suffix", "connection.suffix"},
},
{
// Void SearchList. Using tcpip.domain when primary.dns.suffix is
// empty
{
L",bad.searchlist,parsed.as.empty",
L"tcpip.searchlist,good.but.overridden",
L"tcpip.domain",
L"",
},
{"tcpip.domain", "connection.suffix"},
},
{
// Void SearchList. Using tcpip.domain when primary.dns.suffix is NULL
{
L",bad.searchlist,parsed.as.empty",
L"tcpip.searchlist,good.but.overridden",
L"tcpip.domain",
L"",
},
{"tcpip.domain", "connection.suffix"},
},
{
// No primary suffix. Devolution does not matter.
{
absl::nullopt,
absl::nullopt,
L"",
L"",
{1, 2},
},
{"connection.suffix"},
},
{
// Devolution enabled by policy, level by dnscache.
{
absl::nullopt,
absl::nullopt,
L"a.b.c.d.e",
absl::nullopt,
{1, absl::nullopt}, // policy_devolution: enabled, level
{0, 3}, // dnscache_devolution
{0, 1}, // tcpip_devolution
},
{"a.b.c.d.e", "connection.suffix", "b.c.d.e", "c.d.e"},
},
{
// Devolution enabled by dnscache, level by policy.
{
absl::nullopt,
absl::nullopt,
L"a.b.c.d.e",
L"f.g.i.l.j",
{absl::nullopt, 4},
{1, absl::nullopt},
{0, 3},
},
{"f.g.i.l.j", "connection.suffix", "g.i.l.j"},
},
{
// Devolution enabled by default.
{
absl::nullopt,
absl::nullopt,
L"a.b.c.d.e",
absl::nullopt,
{absl::nullopt, absl::nullopt},
{absl::nullopt, 3},
{absl::nullopt, 1},
},
{"a.b.c.d.e", "connection.suffix", "b.c.d.e", "c.d.e"},
},
{
// Devolution enabled at level = 2, but nothing to devolve.
{
absl::nullopt,
absl::nullopt,
L"a.b",
absl::nullopt,
{absl::nullopt, absl::nullopt},
{absl::nullopt, 2},
{absl::nullopt, 2},
},
{"a.b", "connection.suffix"},
},
{
// Devolution disabled when no explicit level.
{
absl::nullopt,
absl::nullopt,
L"a.b.c.d.e",
absl::nullopt,
{1, absl::nullopt},
{1, absl::nullopt},
{1, absl::nullopt},
},
{"a.b.c.d.e", "connection.suffix"},
},
{
// Devolution disabled by policy level.
{
absl::nullopt,
absl::nullopt,
L"a.b.c.d.e",
absl::nullopt,
{absl::nullopt, 1},
{1, 3},
{1, 4},
},
{"a.b.c.d.e", "connection.suffix"},
},
{
// Devolution disabled by user setting.
{
absl::nullopt,
absl::nullopt,
L"a.b.c.d.e",
absl::nullopt,
{absl::nullopt, 3},
{absl::nullopt, 3},
{0, 3},
},
{"a.b.c.d.e", "connection.suffix"},
},
};
for (auto& t : cases) {
WinDnsSystemSettings settings;
settings.addresses = CreateAdapterAddresses(infos);
settings.policy_search_list = t.input_settings.policy_search_list;
settings.tcpip_search_list = t.input_settings.tcpip_search_list;
settings.tcpip_domain = t.input_settings.tcpip_domain;
settings.primary_dns_suffix = t.input_settings.primary_dns_suffix;
settings.policy_devolution = t.input_settings.policy_devolution;
settings.dnscache_devolution = t.input_settings.dnscache_devolution;
settings.tcpip_devolution = t.input_settings.tcpip_devolution;
EXPECT_THAT(
internal::ConvertSettingsToDnsConfig(settings),
testing::Optional(testing::Field(
&DnsConfig::search, testing::ElementsAreArray(t.expected_search))));
}
}
TEST(DnsConfigServiceWinTest, AppendToMultiLabelName) {
AdapterInfo infos[2] = {
{ IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } },
{ 0 },
};
const struct TestCase {
absl::optional<DWORD> input;
bool expected_output;
} cases[] = {
{0, false},
{1, true},
{absl::nullopt, false},
};
for (const auto& t : cases) {
WinDnsSystemSettings settings;
settings.addresses = CreateAdapterAddresses(infos);
settings.append_to_multi_label_name = t.input;
EXPECT_THAT(
internal::ConvertSettingsToDnsConfig(settings),
testing::Optional(testing::Field(&DnsConfig::append_to_multi_label_name,
testing::Eq(t.expected_output))));
}
}
// Setting have_name_resolution_policy_table should set `unhandled_options`.
TEST(DnsConfigServiceWinTest, HaveNRPT) {
AdapterInfo infos[2] = {
{ IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } },
{ 0 },
};
const struct TestCase {
bool have_nrpt;
bool unhandled_options;
} cases[] = {
{false, false},
{true, true},
};
for (const auto& t : cases) {
WinDnsSystemSettings settings;
settings.addresses = CreateAdapterAddresses(infos);
settings.have_name_resolution_policy = t.have_nrpt;
absl::optional<DnsConfig> config =
internal::ConvertSettingsToDnsConfig(settings);
ASSERT_TRUE(config.has_value());
EXPECT_EQ(t.unhandled_options, config->unhandled_options);
EXPECT_EQ(t.have_nrpt, config->use_local_ipv6);
}
}
// Setting have_proxy should set `unhandled_options`.
TEST(DnsConfigServiceWinTest, HaveProxy) {
AdapterInfo infos[2] = {
{IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", {"1.0.0.1"}},
{0},
};
const struct TestCase {
bool have_proxy;
bool unhandled_options;
} cases[] = {
{false, false},
{true, true},
};
for (const auto& t : cases) {
WinDnsSystemSettings settings;
settings.addresses = CreateAdapterAddresses(infos);
settings.have_proxy = t.have_proxy;
EXPECT_THAT(
internal::ConvertSettingsToDnsConfig(settings),
testing::Optional(testing::Field(&DnsConfig::unhandled_options,
testing::Eq(t.unhandled_options))));
}
}
// Setting uses_vpn should set `unhandled_options`.
TEST(DnsConfigServiceWinTest, UsesVpn) {
AdapterInfo infos[3] = {
{IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", {"1.0.0.1"}},
{IF_TYPE_PPP, IfOperStatusUp, L"connection.suffix", {"1.0.0.1"}},
{0},
};
WinDnsSystemSettings settings;
settings.addresses = CreateAdapterAddresses(infos);
EXPECT_THAT(internal::ConvertSettingsToDnsConfig(settings),
testing::Optional(testing::Field(&DnsConfig::unhandled_options,
testing::IsTrue())));
}
// Setting adapter specific nameservers should set `unhandled_options`.
TEST(DnsConfigServiceWinTest, AdapterSpecificNameservers) {
AdapterInfo infos[3] = {
{IF_TYPE_FASTETHER,
IfOperStatusUp,
L"example.com",
{"1.0.0.1", "fec0:0:0:ffff::2", "8.8.8.8"}},
{IF_TYPE_USB,
IfOperStatusUp,
L"chromium.org",
{"10.0.0.10", "2001:FFFF::1111"}},
{0},
};
WinDnsSystemSettings settings;
settings.addresses = CreateAdapterAddresses(infos);
EXPECT_THAT(internal::ConvertSettingsToDnsConfig(settings),
testing::Optional(testing::Field(&DnsConfig::unhandled_options,
testing::IsTrue())));
}
// Setting adapter specific nameservers for non operational adapter should not
// set `unhandled_options`.
TEST(DnsConfigServiceWinTest, AdapterSpecificNameserversForNo) {
AdapterInfo infos[3] = {
{IF_TYPE_FASTETHER,
IfOperStatusUp,
L"example.com",
{"1.0.0.1", "fec0:0:0:ffff::2", "8.8.8.8"}},
{IF_TYPE_USB,
IfOperStatusDown,
L"chromium.org",
{"10.0.0.10", "2001:FFFF::1111"}},
{0},
};
WinDnsSystemSettings settings;
settings.addresses = CreateAdapterAddresses(infos);
EXPECT_THAT(internal::ConvertSettingsToDnsConfig(settings),
testing::Optional(testing::Field(&DnsConfig::unhandled_options,
testing::IsFalse())));
}
} // namespace
} // namespace net