diff --git a/api/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto b/api/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto index 79d6752502094..5c35e80d591fd 100644 --- a/api/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto +++ b/api/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.common.dynamic_forward_proxy.v3; import "envoy/config/cluster/v3/cluster.proto"; +import "envoy/config/core/v3/address.proto"; import "envoy/config/core/v3/resolver.proto"; import "google/protobuf/duration.proto"; @@ -29,7 +30,7 @@ message DnsCacheCircuitBreakers { // Configuration for the dynamic forward proxy DNS cache. See the :ref:`architecture overview // ` for more information. -// [#next-free-field: 10] +// [#next-free-field: 11] message DnsCacheConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.config.common.dynamic_forward_proxy.v2alpha.DnsCacheConfig"; @@ -108,4 +109,9 @@ message DnsCacheConfig { // DNS resolution configuration which includes the underlying dns resolver addresses and options. config.core.v3.DnsResolutionConfig dns_resolution_config = 9; + + // Hostnames that should be preresolved into the cache upon creation. This might provide a + // performance improvement, in the form of cache hits, for hostnames that are going to be + // resolved during steady state and are known at config load time. + repeated config.core.v3.SocketAddress preresolve_hostnames = 10; } diff --git a/api/envoy/extensions/common/dynamic_forward_proxy/v4alpha/dns_cache.proto b/api/envoy/extensions/common/dynamic_forward_proxy/v4alpha/dns_cache.proto index 89b2fb12a0923..08b78d3fa45ca 100644 --- a/api/envoy/extensions/common/dynamic_forward_proxy/v4alpha/dns_cache.proto +++ b/api/envoy/extensions/common/dynamic_forward_proxy/v4alpha/dns_cache.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.common.dynamic_forward_proxy.v4alpha; import "envoy/config/cluster/v4alpha/cluster.proto"; +import "envoy/config/core/v4alpha/address.proto"; import "envoy/config/core/v4alpha/resolver.proto"; import "google/protobuf/duration.proto"; @@ -31,7 +32,7 @@ message DnsCacheCircuitBreakers { // Configuration for the dynamic forward proxy DNS cache. See the :ref:`architecture overview // ` for more information. -// [#next-free-field: 10] +// [#next-free-field: 11] message DnsCacheConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig"; @@ -105,4 +106,9 @@ message DnsCacheConfig { // DNS resolution configuration which includes the underlying dns resolver addresses and options. config.core.v4alpha.DnsResolutionConfig dns_resolution_config = 9; + + // Hostnames that should be preresolved into the cache upon creation. This might provide a + // performance improvement, in the form of cache hits, for hostnames that are going to be + // resolved during steady state and are known at config load time. + repeated config.core.v4alpha.SocketAddress preresolve_hostnames = 10; } diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 2d880ad41d483..6637b5bda9554 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -71,6 +71,7 @@ New Features * bootstrap: added :ref:`dns_resolution_config ` to aggregate all of the DNS resolver configuration in a single message. By setting one such configuration option *no_default_search_domain* as true the DNS resolver will not use the default search domains. And by setting the configuration *resolvers* we can specify the external DNS servers to be used for external DNS query. * cluster: added :ref:`dns_resolution_config ` to aggregate all of the DNS resolver configuration in a single message. By setting one such configuration option *no_default_search_domain* as true the DNS resolver will not use the default search domains. * crash support: restore crash context when continuing to processing requests or responses as a result of an asynchronous callback that invokes a filter directly. This is unlike the call stacks that go through the various network layers, to eventually reach the filter. For a concrete example see: ``Envoy::Extensions::HttpFilters::Cache::CacheFilter::getHeaders`` which posts a callback on the dispatcher that will invoke the filter directly. +* dns cache: added :ref:`preresolve_hostnames ` option to the DNS cache config. This option allows hostnames to be preresolved into the cache upon cache creation. This might provide performance improvement, in the form of cache hits, for hostnames that are going to be resolved during steady state and are known at config load time. * dns resolver: added *DnsResolverOptions* protobuf message to reconcile all of the DNS lookup option flags. By setting the configuration option :ref:`use_tcp_for_dns_lookups ` as true we can make the underlying dns resolver library to make only TCP queries to the DNS servers and by setting the configuration option :ref:`no_default_search_domain ` as true the DNS resolver library will not use the default search domains. * dns resolver: added *DnsResolutionConfig* to combine :ref:`dns_resolver_options ` and :ref:`resolvers ` in a single protobuf message. The field *resolvers* can be specified with a list of DNS resolver addresses. If specified, DNS client library will perform resolution via the underlying DNS resolvers. Otherwise, the default system resolvers (e.g., /etc/resolv.conf) will be used. * dns_filter: added :ref:`dns_resolution_config ` to aggregate all of the DNS resolver configuration in a single message. By setting the configuration option *use_tcp_for_dns_lookups* to true we can make dns filter's external resolvers to answer queries using TCP only, by setting the configuration option *no_default_search_domain* as true the DNS resolver will not use the default search domains. And by setting the configuration *resolvers* we can specify the external DNS servers to be used for external DNS query which replaces the pre-existing alpha api field *upstream_resolvers*. diff --git a/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto b/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto index 79d6752502094..5c35e80d591fd 100644 --- a/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto +++ b/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.common.dynamic_forward_proxy.v3; import "envoy/config/cluster/v3/cluster.proto"; +import "envoy/config/core/v3/address.proto"; import "envoy/config/core/v3/resolver.proto"; import "google/protobuf/duration.proto"; @@ -29,7 +30,7 @@ message DnsCacheCircuitBreakers { // Configuration for the dynamic forward proxy DNS cache. See the :ref:`architecture overview // ` for more information. -// [#next-free-field: 10] +// [#next-free-field: 11] message DnsCacheConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.config.common.dynamic_forward_proxy.v2alpha.DnsCacheConfig"; @@ -108,4 +109,9 @@ message DnsCacheConfig { // DNS resolution configuration which includes the underlying dns resolver addresses and options. config.core.v3.DnsResolutionConfig dns_resolution_config = 9; + + // Hostnames that should be preresolved into the cache upon creation. This might provide a + // performance improvement, in the form of cache hits, for hostnames that are going to be + // resolved during steady state and are known at config load time. + repeated config.core.v3.SocketAddress preresolve_hostnames = 10; } diff --git a/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v4alpha/dns_cache.proto b/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v4alpha/dns_cache.proto index a9040a90dfc20..dde756a1608a2 100644 --- a/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v4alpha/dns_cache.proto +++ b/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v4alpha/dns_cache.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.common.dynamic_forward_proxy.v4alpha; import "envoy/config/cluster/v4alpha/cluster.proto"; +import "envoy/config/core/v4alpha/address.proto"; import "envoy/config/core/v4alpha/resolver.proto"; import "google/protobuf/duration.proto"; @@ -32,7 +33,7 @@ message DnsCacheCircuitBreakers { // Configuration for the dynamic forward proxy DNS cache. See the :ref:`architecture overview // ` for more information. -// [#next-free-field: 10] +// [#next-free-field: 11] message DnsCacheConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig"; @@ -111,4 +112,9 @@ message DnsCacheConfig { // DNS resolution configuration which includes the underlying dns resolver addresses and options. config.core.v4alpha.DnsResolutionConfig dns_resolution_config = 9; + + // Hostnames that should be preresolved into the cache upon creation. This might provide a + // performance improvement, in the form of cache hits, for hostnames that are going to be + // resolved during steady state and are known at config load time. + repeated config.core.v4alpha.SocketAddress preresolve_hostnames = 10; } diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc index 15e3adb4d715b..312c920c27bb6 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc @@ -33,6 +33,25 @@ DnsCacheImpl::DnsCacheImpl( host_ttl_(PROTOBUF_GET_MS_OR_DEFAULT(config, host_ttl, 300000)), max_hosts_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_hosts, 1024)) { tls_slot_.set([&](Event::Dispatcher&) { return std::make_shared(*this); }); + + if (static_cast(config.preresolve_hostnames().size()) > max_hosts_) { + throw EnvoyException(fmt::format( + "DNS Cache [{}] configured with preresolve_hostnames={} larger than max_hosts={}", + config.name(), config.preresolve_hostnames().size(), max_hosts_)); + } + + // Preresolved hostnames are resolved without a read lock on primary hosts because it is done + // during object construction. + for (const auto& hostname : config.preresolve_hostnames()) { + // No need to get a resolution handle on this resolution as the only outcome needed is for the + // cache to load an entry. Further if this particular resolution fails all the is lost is the + // potential optimization of having the entry be preresolved the first time a true consumer of + // this DNS cache asks for it. + main_thread_dispatcher_.post( + [this, host = hostname.address(), default_port = hostname.port_value()]() { + startCacheLoad(host, default_port); + }); + } } DnsCacheImpl::~DnsCacheImpl() { diff --git a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc index 25f4b42fc4c6d..215e391d5fb63 100644 --- a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc +++ b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc @@ -28,9 +28,17 @@ namespace { class DnsCacheImplTest : public testing::Test, public Event::TestUsingSimulatedTime { public: - void initialize() { + void initialize(std::vector preresolve_hostnames = {}, uint32_t max_hosts = 1024) { config_.set_name("foo"); config_.set_dns_lookup_family(envoy::config::cluster::v3::Cluster::V4_ONLY); + config_.mutable_max_hosts()->set_value(max_hosts); + if (!preresolve_hostnames.empty()) { + for (const auto& hostname : preresolve_hostnames) { + envoy::config::core::v3::SocketAddress* address = config_.add_preresolve_hostnames(); + address->set_address(hostname); + address->set_port_value(443); + } + } EXPECT_CALL(dispatcher_, isThreadSafe).WillRepeatedly(Return(true)); @@ -98,6 +106,34 @@ MATCHER_P(CustomDnsResolversSizeEquals, expected_resolvers, "") { return expected_resolvers.size() == arg.size(); } +TEST_F(DnsCacheImplTest, PreresolveSuccess) { + Network::DnsResolver::ResolveCb resolve_cb; + EXPECT_CALL(*resolver_, resolve("bar.baz.com", _, _)) + .WillOnce(DoAll(SaveArg<2>(&resolve_cb), Return(&resolver_->active_query_))); + EXPECT_CALL( + update_callbacks_, + onDnsHostAddOrUpdate("bar.baz.com", DnsHostInfoEquals("10.0.0.1:443", "bar.baz.com", false))); + + initialize({"bar.baz.com"} /* preresolve_hostnames */); + + resolve_cb(Network::DnsResolver::ResolutionStatus::Success, + TestUtility::makeDnsResponse({"10.0.0.1"})); + checkStats(1 /* attempt */, 1 /* success */, 0 /* failure */, 1 /* address changed */, + 1 /* added */, 0 /* removed */, 1 /* num hosts */); + + MockLoadDnsCacheEntryCallbacks callbacks; + auto result = dns_cache_->loadDnsCacheEntry("bar.baz.com", 80, callbacks); + EXPECT_EQ(DnsCache::LoadDnsCacheEntryStatus::InCache, result.status_); + EXPECT_EQ(result.handle_, nullptr); + EXPECT_NE(absl::nullopt, result.host_info_); +} + +TEST_F(DnsCacheImplTest, PreresolveFailure) { + EXPECT_THROW_WITH_MESSAGE( + initialize({"bar.baz.com"} /* preresolve_hostnames */, 0 /* max_hosts */), EnvoyException, + "DNS Cache [foo] configured with preresolve_hostnames=1 larger than max_hosts=0"); +} + // Basic successful resolution and then re-resolution. TEST_F(DnsCacheImplTest, ResolveSuccess) { initialize(); @@ -698,8 +734,7 @@ TEST_F(DnsCacheImplTest, InvalidPort) { // Max host overflow. TEST_F(DnsCacheImplTest, MaxHostOverflow) { - config_.mutable_max_hosts()->set_value(0); - initialize(); + initialize({} /* preresolve_hostnames */, 0 /* max_hosts */); InSequence s; MockLoadDnsCacheEntryCallbacks callbacks; diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 6291ed0b42e91..f5dad4b68e77f 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -937,6 +937,8 @@ preorder prepend prepended prepends +preresolve +preresolved prev probabilistically proc