From 5107d494cc634527249b94e0dcf0ca2f32fc27a7 Mon Sep 17 00:00:00 2001 From: Anton Kaymakchi Date: Thu, 6 Jun 2024 14:40:59 +0000 Subject: [PATCH] Add support for wildcard name in DNS filter inline table Signed-off-by: Anton Kaymakchi --- changelogs/current.yaml | 4 + .../filters/udp/dns_filter/dns_filter.cc | 40 +++--- .../udp/dns_filter/dns_filter_utils.cc | 13 ++ .../filters/udp/dns_filter/dns_filter_utils.h | 7 + .../filters/udp/dns_filter/dns_filter_test.cc | 127 ++++++++++++++++++ .../udp/dns_filter/dns_filter_utils_test.cc | 26 ++++ 6 files changed, 202 insertions(+), 15 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8056cacd31e74..cc94b482436d5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -226,6 +226,10 @@ new_features: change: | Added :ref:`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 + `. deprecated: - area: tracing diff --git a/source/extensions/filters/udp/dns_filter/dns_filter.cc b/source/extensions/filters/udp/dns_filter/dns_filter.cc index 90ada509f5d0b..2f9afae0c87e1 100644 --- a/source/extensions/filters/udp/dns_filter/dns_filter.cc +++ b/source/extensions/filters/udp/dns_filter/dns_filter.cc @@ -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(); @@ -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 @@ -76,11 +78,11 @@ DnsFilterEnvoyConfig::DnsFilterEnvoyConfig( } else { // Add a new endpoint config for the new domain endpoint_config.address_list = absl::make_optional(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(std::move(addrs)); - addEndpointToSuffix(suffix, domain_name, endpoint_config); + addEndpointToSuffix(suffix, virtual_domain_name, endpoint_config); } } @@ -144,12 +146,12 @@ 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)); } } } @@ -157,7 +159,7 @@ DnsFilterEnvoyConfig::DnsFilterEnvoyConfig( 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(); @@ -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) { diff --git a/source/extensions/filters/udp/dns_filter/dns_filter_utils.cc b/source/extensions/filters/udp/dns_filter/dns_filter_utils.cc index c37f6d77563dd..ea013f0eaa38a 100644 --- a/source/extensions/filters/udp/dns_filter/dns_filter_utils.cc +++ b/source/extensions/filters/udp/dns_filter/dns_filter_utils.cc @@ -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 diff --git a/source/extensions/filters/udp/dns_filter/dns_filter_utils.h b/source/extensions/filters/udp/dns_filter/dns_filter_utils.h index 34b2091f21054..cc3f6447ba536 100644 --- a/source/extensions/filters/udp/dns_filter/dns_filter_utils.h +++ b/source/extensions/filters/udp/dns_filter/dns_filter_utils.h @@ -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 diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc index 5ba68539b9025..348b37ce5f60c 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc @@ -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 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 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 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 diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_utils_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_utils_test.cc index e07d67113db8c..08d1b4f777b0c 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_utils_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_utils_test.cc @@ -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"}, @@ -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