Skip to content

Commit

Permalink
Add support for wildcard name in DNS filter inline table
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Kaymakchi <tonysignal@gmail.com>
  • Loading branch information
StupidScience committed Jun 7, 2024
1 parent cd6f289 commit 5107d49
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 15 deletions.
4 changes: 4 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ new_features:
change: |
Added :ref:`bypass_overload_manager <envoy_v3_api_field_config.listener.v3.Listener.bypass_overload_manager>`
to bypass the overload manager for a listener. When set to true, the listener will not be subject to overload protection.
- area: dns_filter
change: |
Added support wildcard names in :ref:`inline_dns_table
<envoy_v3_api_field_extensions.filters.udp.dns_filter.v3.DnsFilterConfig.ServerContextConfig.inline_dns_table>`.
deprecated:
- area: tracing
Expand Down
40 changes: 25 additions & 15 deletions source/extensions/filters/udp/dns_filter/dns_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ DnsFilterEnvoyConfig::DnsFilterEnvoyConfig(
for (const auto& virtual_domain : dns_table.virtual_domains()) {
AddressConstPtrVec addrs{};

const absl::string_view domain_name = virtual_domain.name();
const absl::string_view suffix = Utils::getDomainSuffix(domain_name);
ENVOY_LOG(trace, "Loading configuration for domain: {}. Suffix: {}", domain_name, suffix);
const absl::string_view virtual_domain_name =
Utils::getVirtualDomainName(virtual_domain.name());
const absl::string_view suffix = Utils::getDomainSuffix(virtual_domain_name);
ENVOY_LOG(trace, "Loading configuration for domain: {}. Suffix: {}", virtual_domain_name,
suffix);

if (virtual_domain.endpoint().has_address_list()) {
const auto& address_list = virtual_domain.endpoint().address_list().address();
Expand All @@ -66,7 +68,7 @@ DnsFilterEnvoyConfig::DnsFilterEnvoyConfig(
if (virtual_domains != nullptr) {
// The suffix already has a node in the trie

auto existing_endpoint_config = virtual_domains->find(domain_name);
auto existing_endpoint_config = virtual_domains->find(virtual_domain_name);
if (existing_endpoint_config != virtual_domains->end()) {
// Update the existing endpoint config with the new addresses

Expand All @@ -76,11 +78,11 @@ DnsFilterEnvoyConfig::DnsFilterEnvoyConfig(
} else {
// Add a new endpoint config for the new domain
endpoint_config.address_list = absl::make_optional<AddressConstPtrVec>(std::move(addrs));
virtual_domains->emplace(std::string(domain_name), std::move(endpoint_config));
virtual_domains->emplace(std::string(virtual_domain_name), std::move(endpoint_config));
}
} else {
endpoint_config.address_list = absl::make_optional<AddressConstPtrVec>(std::move(addrs));
addEndpointToSuffix(suffix, domain_name, endpoint_config);
addEndpointToSuffix(suffix, virtual_domain_name, endpoint_config);
}
}

Expand Down Expand Up @@ -144,20 +146,20 @@ DnsFilterEnvoyConfig::DnsFilterEnvoyConfig(
// See if there's a suffix already configured
auto virtual_domains = dns_lookup_trie_.find(suffix);
if (virtual_domains == nullptr) {
addEndpointToSuffix(suffix, domain_name, endpoint_config);
addEndpointToSuffix(suffix, virtual_domain_name, endpoint_config);
} else {
// A domain can be redirected to one cluster. If it appears multiple times, the first
// entry is the only one used
if (virtual_domains->find(domain_name) == virtual_domains->end()) {
virtual_domains->emplace(domain_name, std::move(endpoint_config));
if (virtual_domains->find(virtual_domain_name) == virtual_domains->end()) {
virtual_domains->emplace(virtual_domain_name, std::move(endpoint_config));
}
}
}

std::chrono::seconds ttl = virtual_domain.has_answer_ttl()
? std::chrono::seconds(virtual_domain.answer_ttl().seconds())
: DEFAULT_RESOLVER_TTL;
domain_ttl_.emplace(virtual_domain.name(), ttl);
domain_ttl_.emplace(virtual_domain_name, ttl);
}

forward_queries_ = config.has_client_config();
Expand Down Expand Up @@ -390,12 +392,20 @@ const DnsEndpointConfig* DnsFilter::getEndpointConfigForDomain(const absl::strin
return nullptr;
}

const auto iter = virtual_domains->find(domain);
if (iter == virtual_domains->end()) {
ENVOY_LOG(debug, "No endpoint configuration exists for [{}]", domain);
return nullptr;
// Try to find exact match at first and then look for possible wildcard match
// moving to the next label on each iteration.
size_t pos = 0;
while (pos != domain.npos) {
const auto iter = virtual_domains->find(domain.substr(pos));
if (iter != virtual_domains->end()) {
return &(iter->second);
}

pos = domain.find('.', pos + 1);
}
return &(iter->second);

ENVOY_LOG(debug, "No endpoint configuration exists for [{}]", domain);
return nullptr;
}

const DnsSrvRecord* DnsFilter::getServiceConfigForDomain(const absl::string_view domain) {
Expand Down
13 changes: 13 additions & 0 deletions source/extensions/filters/udp/dns_filter/dns_filter_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ absl::string_view getDomainSuffix(const absl::string_view name) {
return name.substr(pos + 1);
}

absl::string_view getVirtualDomainName(const absl::string_view domain_name) {
// We can use names started with '.' as wildcard records in virtual domain name config
// since these are not valid domain names and in this way we optimize for future search against
// them. We expect only names like *.foo.com as a valid wildcard records because any other
// wildcard usages are not considered as valid ones, i.e. **.foo.com, *foo.bar.com, foo*.bar.com
// are all invalid.
if (domain_name.starts_with("*.")) {
return domain_name.substr(1);
}

return domain_name;
}

} // namespace Utils
} // namespace DnsFilter
} // namespace UdpFilters
Expand Down
7 changes: 7 additions & 0 deletions source/extensions/filters/udp/dns_filter/dns_filter_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ getAddressRecordType(const Network::Address::InstanceConstSharedPtr& ipaddr);
*/
absl::string_view getDomainSuffix(const absl::string_view name);

/**
* @brief For given domain name return virtual domain name that will be used for internal
* resolution. Valid wildcard names are sanitized and converted to format to store in
* virtual domain config.
*/
absl::string_view getVirtualDomainName(const absl::string_view domain_name);

} // namespace Utils
} // namespace DnsFilter
} // namespace UdpFilters
Expand Down
127 changes: 127 additions & 0 deletions test/extensions/filters/udp/dns_filter/dns_filter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2427,6 +2427,133 @@ stat_prefix: "my_prefix"
EXPECT_EQ(1, config_->stats().known_domain_queries_.value());
}

TEST_F(DnsFilterTest, WildcardName) {
InSequence s;

const std::string wildcard_virtual_domain = R"EOF(
stat_prefix: "my_prefix"
server_config:
inline_dns_table:
external_retry_count: 0
virtual_domains:
- name: "*.foobaz.com"
endpoint:
address_list:
address:
- "10.0.0.1"
)EOF";
setup(wildcard_virtual_domain);

const std::list<std::string> expected_address{"10.0.0.1"};
const std::string domain("www.foobaz.com");

const std::string query =
Utils::buildQueryForDomain(domain, DNS_RECORD_TYPE_A, DNS_RECORD_CLASS_IN);
ASSERT_FALSE(query.empty());
sendQueryFromClient("10.0.0.1:1000", query);

response_ctx_ = ResponseValidator::createResponseContext(udp_response_, counters_);
EXPECT_TRUE(response_ctx_->parse_status_);
EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_ctx_->getQueryResponseCode());

for (const auto& answer : response_ctx_->answers_) {
EXPECT_EQ(answer.first, domain);
Utils::verifyAddress(expected_address, answer.second);
}

// Validate stats
EXPECT_EQ(1, config_->stats().downstream_rx_queries_.value());
EXPECT_EQ(1, config_->stats().known_domain_queries_.value());
}

TEST_F(DnsFilterTest, WildcardSubdomainPrevails) {
InSequence s;

const std::string wildcard_with_subdomain_virtual_domain = R"EOF(
stat_prefix: "my_prefix"
server_config:
inline_dns_table:
external_retry_count: 0
virtual_domains:
- name: "*.foo1.com"
endpoint:
address_list:
address:
- "10.0.0.1"
- name: "*.foo2.foo1.com"
endpoint:
address_list:
address:
- "10.0.0.2"
)EOF";
setup(wildcard_with_subdomain_virtual_domain);

const std::list<std::string> expected_address{"10.0.0.2"};
const std::string domain("www.foo2.foo1.com");

const std::string query =
Utils::buildQueryForDomain(domain, DNS_RECORD_TYPE_A, DNS_RECORD_CLASS_IN);
ASSERT_FALSE(query.empty());
sendQueryFromClient("10.0.0.1:1000", query);

response_ctx_ = ResponseValidator::createResponseContext(udp_response_, counters_);
EXPECT_TRUE(response_ctx_->parse_status_);
EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_ctx_->getQueryResponseCode());

for (const auto& answer : response_ctx_->answers_) {
EXPECT_EQ(answer.first, domain);
Utils::verifyAddress(expected_address, answer.second);
}

// Validate stats
EXPECT_EQ(1, config_->stats().downstream_rx_queries_.value());
EXPECT_EQ(1, config_->stats().known_domain_queries_.value());
}

TEST_F(DnsFilterTest, WildcardExactNamePrevails) {
InSequence s;

const std::string wildcard_and_exact_name_virtual_domain = R"EOF(
stat_prefix: "my_prefix"
server_config:
inline_dns_table:
external_retry_count: 0
virtual_domains:
- name: "*.foo1.com"
endpoint:
address_list:
address:
- "10.0.0.1"
- name: "bar.foo1.com"
endpoint:
address_list:
address:
- "10.0.0.2"
)EOF";
setup(wildcard_and_exact_name_virtual_domain);

const std::list<std::string> expected_address{"10.0.0.2"};
const std::string domain("bar.foo1.com");

const std::string query =
Utils::buildQueryForDomain(domain, DNS_RECORD_TYPE_A, DNS_RECORD_CLASS_IN);
ASSERT_FALSE(query.empty());
sendQueryFromClient("10.0.0.1:1000", query);

response_ctx_ = ResponseValidator::createResponseContext(udp_response_, counters_);
EXPECT_TRUE(response_ctx_->parse_status_);
EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_ctx_->getQueryResponseCode());

for (const auto& answer : response_ctx_->answers_) {
EXPECT_EQ(answer.first, domain);
Utils::verifyAddress(expected_address, answer.second);
}

// Validate stats
EXPECT_EQ(1, config_->stats().downstream_rx_queries_.value());
EXPECT_EQ(1, config_->stats().known_domain_queries_.value());
}

} // namespace
} // namespace DnsFilter
} // namespace UdpFilters
Expand Down
26 changes: 26 additions & 0 deletions test/extensions/filters/udp/dns_filter/dns_filter_utils_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ TEST_F(DnsFilterUtilsTest, GetDomainSuffixTest) {
{"_ldap._tcp.Default-First-Site-Name._sites.dc._msdcs.utelsystems.local",
"utelsystems.local"},
{"primary.voip.subzero.com", "subzero.com"},
{"*.voip.subzero.com", "subzero.com"},
{"*.subzero.com", "subzero.com"},
{".subzero.com", "subzero.com"},
{"subzero.com", "subzero.com"},
{"subzero", "subzero"},
{".com", "com"},
Expand All @@ -119,6 +122,29 @@ TEST_F(DnsFilterUtilsTest, GetDomainSuffixTest) {
}
}

TEST_F(DnsFilterUtilsTest, GetVirtualDomainName) {
struct DomainSuffixTestData {
const std::string domain;
const std::string expected_name;
} suffix_data[] = {
{"_ldap._tcp.Default-First-Site-Name._sites.dc._msdcs.utelsystems.local",
"_ldap._tcp.Default-First-Site-Name._sites.dc._msdcs.utelsystems.local"},
{"primary.voip.subzero.com", "primary.voip.subzero.com"},
{"*.subzero.com", ".subzero.com"},
{"*www.subzero.com", "*www.subzero.com"},
{"subzero", "subzero"},
{".com", ".com"},
{"*.", "."},
{".", "."},
{"", ""},
};

for (auto& ptr : suffix_data) {
const absl::string_view result = Utils::getVirtualDomainName(ptr.domain);
EXPECT_EQ(ptr.expected_name, result);
}
}

} // namespace
} // namespace Utils
} // namespace DnsFilter
Expand Down

0 comments on commit 5107d49

Please sign in to comment.