Skip to content

Commit

Permalink
dns cache: add ability to prefetch hostnames to resolve during cache …
Browse files Browse the repository at this point in the history
…construction (#16891)

Commit Message: this PR adds the ability to prefetch hostnames to resolve during cache construction.
Additional Description: This feature can be used as a performance improvement for setups that use dns cache, but know a priori some of the hostnames that will be used.
Risk Level: low -- adds a feature that is off by default.
Testing: new tests
Docs Changes: added documentation in the API
Release Notes: inline

Signed-off-by: Jose Nino <jnino@lyft.com>
  • Loading branch information
junr03 committed Jun 21, 2021
1 parent b99ff7d commit 7b492bd
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 7 deletions.
Expand Up @@ -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";
Expand All @@ -29,7 +30,7 @@ message DnsCacheCircuitBreakers {

// Configuration for the dynamic forward proxy DNS cache. See the :ref:`architecture overview
// <arch_overview_http_dynamic_forward_proxy>` 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";
Expand Down Expand Up @@ -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;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Expand Up @@ -85,6 +85,7 @@ New Features
* composite filter: can now be used with filters that also add an access logger, such as the WASM filter.
* connection_limit: added new :ref:`Network connection limit filter <config_network_filters_connection_limit>`.
* 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 <envoy_v3_api_field_extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.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 <envoy_v3_api_field_config.core.v3.DnsResolverOptions.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 <envoy_v3_api_field_config.core.v3.DnsResolverOptions.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 <envoy_v3_api_field_config.core.v3.DnsResolutionConfig.dns_resolver_options>` and :ref:`resolvers <envoy_v3_api_field_config.core.v3.DnsResolutionConfig.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 <envoy_v3_api_field_extensions.filters.udp.dns_filter.v3alpha.DnsFilterConfig.ClientContextConfig.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*.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc
Expand Up @@ -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<ThreadLocalHostInfo>(*this); });

if (static_cast<size_t>(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() {
Expand Down
Expand Up @@ -28,9 +28,17 @@ namespace {

class DnsCacheImplTest : public testing::Test, public Event::TestUsingSimulatedTime {
public:
void initialize() {
void initialize(std::vector<std::string> 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));

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions tools/spelling/spelling_dictionary.txt
Expand Up @@ -937,6 +937,8 @@ preorder
prepend
prepended
prepends
preresolve
preresolved
prev
probabilistically
proc
Expand Down

0 comments on commit 7b492bd

Please sign in to comment.