Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dfp: Insert correct preresolved hostname key in DNS cache #30784

Merged
merged 5 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ bug_fixes:
in BUFFERED BodySendMode + SEND HeaderSendMode. It is now external processor's responsibility to set the content length
correctly matched to the mutated body. if those two doesn't match, the mutation will be rejected and local reply with error
status will be returned.
- area: dynamic_forward_proxy
change: |
Fixed a bug where the preresolved hostnames specified in the Dynamic Forward Proxy cluster
config would not use the normalized hostname as the DNS cache key, which is the same key
used for retrieval. This caused cache misses on initial use, even though the host DNS entry
was pre-resolved. The fix is guarded by runtime guard ``envoy.reloadable_features.normalize_host_for_preresolve_dfp_dns``,
which defaults to true.

removed_config_or_runtime:
# *Normally occurs at the end of the* :ref:`deprecation period <deprecated>`
Expand Down
1 change: 1 addition & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ RUNTIME_GUARD(envoy_reloadable_features_lowercase_scheme);
RUNTIME_GUARD(envoy_reloadable_features_no_downgrade_to_canonical_name);
RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name);
RUNTIME_GUARD(envoy_reloadable_features_no_full_scan_certs_on_sni_mismatch);
RUNTIME_GUARD(envoy_reloadable_features_normalize_host_for_preresolve_dfp_dns);
RUNTIME_GUARD(envoy_reloadable_features_oauth_make_token_cookie_httponly);
RUNTIME_GUARD(envoy_reloadable_features_oauth_use_standard_max_age_value);
RUNTIME_GUARD(envoy_reloadable_features_oauth_use_url_encoding);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "source/common/network/dns_resolver/dns_factory_util.h"
#include "source/common/network/resolver_impl.h"
#include "source/common/network/utility.h"
#include "source/common/runtime/runtime_features.h"

namespace Envoy {
namespace Extensions {
Expand Down Expand Up @@ -58,7 +59,13 @@ DnsCacheImpl::DnsCacheImpl(
// cache to load an entry. Further if this particular resolution fails all the is lost is the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I'm trying to comment on line 56)

Wow, is this the correct type for hostname?

repeated config.core.v3.SocketAddress preresolve_hostnames = 10;

Hostname is a bizarre term for this as it seems to be a protocol (tcp/udp), an IP address, a port, and a resolver name. Am I reading that correctly? bizarre

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also surprised SocketAddress was used for preresolved hostnames config, and doesn't seem to be the right structure to represent a DNS cache entry. Changing it is a good idea but probably outside the scope of the PR. I do share your concern though..

// potential optimization of having the entry be preresolved the first time a true consumer of
// this DNS cache asks for it.
startCacheLoad(hostname.address(), hostname.port_value(), false);
const std::string host =
(Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.normalize_host_for_preresolve_dfp_dns"))
? DnsHostInfo::normalizeHostForDfp(hostname.address(), hostname.port_value())
: hostname.address();
ENVOY_LOG(debug, "DNS pre-resolve starting for host {}", host);
startCacheLoad(host, hostname.port_value(), false);
}
}

Expand Down
1 change: 1 addition & 0 deletions test/extensions/common/dynamic_forward_proxy/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ envoy_cc_test(
"//test/mocks/thread_local:thread_local_mocks",
"//test/test_common:registry_lib",
"//test/test_common:simulated_time_system_lib",
"//test/test_common:test_runtime_lib",
"@envoy_api//envoy/config/cluster/v3:pkg_cc_proto",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
"@envoy_api//envoy/extensions/common/dynamic_forward_proxy/v3:pkg_cc_proto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
#include "test/mocks/thread_local/mocks.h"
#include "test/test_common/registry.h"
#include "test/test_common/simulated_time_system.h"
#include "test/test_common/test_runtime.h"
#include "test/test_common/utility.h"

#include "absl/strings/str_cat.h"

using testing::AtLeast;
using testing::DoAll;
using testing::InSequence;
Expand All @@ -37,15 +40,17 @@ static const absl::optional<std::chrono::seconds> kNoTtl = absl::nullopt;
class DnsCacheImplTest : public testing::Test, public Event::TestUsingSimulatedTime {
public:
DnsCacheImplTest() : registered_dns_factory_(dns_resolver_factory_) {}
void initialize(std::vector<std::string> preresolve_hostnames = {}, uint32_t max_hosts = 1024) {
void initialize(
std::vector<std::pair<std::string /*host*/, uint32_t /*port*/>> 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) {
for (const auto& [host, port] : preresolve_hostnames) {
envoy::config::core::v3::SocketAddress* address = config_.add_preresolve_hostnames();
address->set_address(hostname);
address->set_port_value(443);
address->set_address(host);
address->set_port_value(port);
}
}

Expand Down Expand Up @@ -127,36 +132,60 @@ void verifyCaresDnsConfigAndUnpack(
typed_dns_resolver_config.typed_config().UnpackTo(&cares);
}

TEST_F(DnsCacheImplTest, PreresolveSuccess) {
class DnsCacheImplPreresolveTest : public DnsCacheImplTest,
public testing::WithParamInterface<bool> {
public:
bool normalizeDfpHost() { return GetParam(); }
};

INSTANTIATE_TEST_SUITE_P(DnsCachePreresolveNormalizedDfpHost, DnsCacheImplPreresolveTest,
testing::Bool());

TEST_P(DnsCacheImplPreresolveTest, PreresolveSuccess) {
TestScopedRuntime scoped_runtime;
scoped_runtime.mergeValues({{"envoy.reloadable_features.normalize_host_for_preresolve_dfp_dns",
absl::StrCat(normalizeDfpHost())}});

Network::DnsResolver::ResolveCb resolve_cb;
std::string hostname = "bar.baz.com:443";
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:443",
DnsHostInfoEquals("10.0.0.1:443", "bar.baz.com", false)));
std::string host = "bar.baz.com";
uint32_t port = 443;
std::string authority = absl::StrCat(host, ":", port);
EXPECT_CALL(*resolver_, resolve(host, _, _))
.WillRepeatedly(DoAll(SaveArg<2>(&resolve_cb), Return(&resolver_->active_query_)));
EXPECT_CALL(
update_callbacks_,
onDnsHostAddOrUpdate(authority, DnsHostInfoEquals("10.0.0.1:443", "bar.baz.com", false)));
EXPECT_CALL(update_callbacks_,
onDnsResolutionComplete("bar.baz.com:443",
onDnsResolutionComplete(authority,
DnsHostInfoEquals("10.0.0.1:443", "bar.baz.com", false),
Network::DnsResolver::ResolutionStatus::Success));

initialize({"bar.baz.com:443"} /* preresolve_hostnames */);
initialize({{normalizeDfpHost() ? host : authority, port}} /* 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", 443, false, callbacks);
if (normalizeDfpHost()) {
// Retrieve with the hostname and port in the "host".
auto result = dns_cache_->loadDnsCacheEntry(authority, port, false, callbacks);
EXPECT_EQ(DnsCache::LoadDnsCacheEntryStatus::InCache, result.status_);
EXPECT_EQ(result.handle_, nullptr);
EXPECT_NE(absl::nullopt, result.host_info_);
}
// Retrieve with the hostname only in the "host".
auto result = dns_cache_->loadDnsCacheEntry(host, port, false, callbacks);
EXPECT_EQ(DnsCache::LoadDnsCacheEntryStatus::InCache, result.status_);
EXPECT_EQ(result.handle_, nullptr);
EXPECT_NE(absl::nullopt, result.host_info_);
}

TEST_F(DnsCacheImplTest, PreresolveFailure) {
TEST_P(DnsCacheImplPreresolveTest, PreresolveFailure) {
EXPECT_THROW_WITH_MESSAGE(
initialize({"bar.baz.com"} /* preresolve_hostnames */, 0 /* max_hosts */), EnvoyException,
initialize({{"bar.baz.com", 443}} /* preresolve_hostnames */, 0 /* max_hosts */),
EnvoyException,
"DNS Cache [foo] configured with preresolve_hostnames=1 larger than max_hosts=0");
}

Expand Down
Loading