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 d8f9b717fb00..eae3b8f74261 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 @@ -67,9 +67,9 @@ message DnsCacheConfig { // The minimum rate that DNS resolution will occur. Per ``dns_refresh_rate``, once a host is // resolved, the DNS TTL will be used, with a minimum set by ``dns_min_refresh_rate``. - // ``dns_min_refresh_rate`` defaults to 5s and must also be >= 5s. + // ``dns_min_refresh_rate`` defaults to 5s and must also be >= 1s. google.protobuf.Duration dns_min_refresh_rate = 14 - [(validate.rules).duration = {gte {seconds: 5}}]; + [(validate.rules).duration = {gte {seconds: 1}}]; // The TTL for hosts that are unused. Hosts that have not been used in the configured time // interval will be purged. If not specified defaults to 5m. diff --git a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc index df443ae619cc..806135fae878 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -50,9 +50,11 @@ name: dynamic_forward_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig dns_cache_config: + dns_min_refresh_rate: 1s name: foo dns_lookup_family: {} max_hosts: {} + host_ttl: 1s dns_cache_circuit_breaker: max_pending_requests: {}{}{} )EOF", @@ -127,9 +129,11 @@ name: envoy.clusters.dynamic_forward_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig dns_cache_config: + dns_min_refresh_rate: 1s name: foo dns_lookup_family: {} max_hosts: {} + host_ttl: 1s dns_cache_circuit_breaker: max_pending_requests: {}{}{} )EOF", @@ -162,9 +166,8 @@ name: envoy.clusters.dynamic_forward_proxy if (use_cache_file_) { cache_file_value_contents_ += absl::StrCat(Network::Test::getLoopbackAddressUrlString(version_), ":", - fake_upstreams_[0]->localAddress()->ip()->port(), "|1000000|0"); - std::string host = - fmt::format("localhost:{}", fake_upstreams_[0]->localAddress()->ip()->port()); + fake_upstreams_[0]->localAddress()->ip()->port(), "|", dns_cache_ttl_ , "|0"); + std::string host = fmt::format("{}:{}", dns_hostname_, fake_upstreams_[0]->localAddress()->ip()->port()); TestEnvironment::writeStringToFileForTest("dns_cache.txt", absl::StrCat(host.length(), "\n", host, cache_file_value_contents_.length(), @@ -254,8 +257,10 @@ name: envoy.clusters.dynamic_forward_proxy envoy::config::cluster::v3::Cluster cluster_; std::string cache_file_value_contents_; bool use_cache_file_{}; + uint32_t dns_cache_ttl_{1000000}; std::string filename_; std::string key_value_config_; + std::string dns_hostname_{"localhost"}; }; int64_t getHeaderValue(const Http::ResponseHeaderMap& headers, absl::string_view name) { @@ -646,6 +651,59 @@ TEST_P(ProxyFilterIntegrationTest, UseCacheFile) { EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_cx_http1_total")->value()); } +TEST_P(ProxyFilterIntegrationTest, UseCacheFileShortTtl) { + upstream_tls_ = false; // avoid cert errors for unknown hostname + use_cache_file_ = true; + dns_cache_ttl_ = 2; + + dns_hostname_ = "not_actually_localhost"; // Set to a name that won't resolve. + initializeWithArgs(); + std::string host = fmt::format("{}:{}", dns_hostname_, fake_upstreams_[0]->localAddress()->ip()->port()); + codec_client_ = makeHttpConnection(lookupPort("http")); + const Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", host}}; + + auto response = + sendRequestAndWaitForResponse(request_headers, 1024, default_response_headers_, 1024); + checkSimpleRequestSuccess(1024, 1024, response.get()); + EXPECT_EQ(1, test_server_->counter("dns_cache.foo.cache_load")->value()); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_cx_http1_total")->value()); + + // Wait for the host to be removed due to short TTL + test_server_->waitForCounterGe("dns_cache.foo.host_removed", 1); + + // Send a request and expect an error due to 1) removed host and 2) DNS resolution fail. + response = codec_client_->makeHeaderOnlyRequest(request_headers); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ("503", response->headers().getStatusValue()); +} + +TEST_P(ProxyFilterIntegrationTest, UseCacheFileShortTtlHostActive) { + upstream_tls_ = false; // avoid cert errors for unknown hostname + use_cache_file_ = true; + dns_cache_ttl_ = 2; + + dns_hostname_ = "not_actually_localhost"; // Set to a name that won't resolve. + initializeWithArgs(); + std::string host = fmt::format("{}:{}", dns_hostname_, fake_upstreams_[0]->localAddress()->ip()->port()); + codec_client_ = makeHttpConnection(lookupPort("http")); + const Http::TestRequestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", host}}; + + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + waitForNextUpstreamRequest(); + + // Wait for the host to be removed due to short TTL + test_server_->waitForCounterGe("dns_cache.foo.host_removed", 1); + + // Finish the response. + upstream_request_->encodeHeaders(default_response_headers_, true); + + // The disconnect will trigger failure. + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ("200", response->headers().getStatusValue()); +} + TEST_P(ProxyFilterIntegrationTest, UseCacheFileAndTestHappyEyeballs) { upstream_tls_ = false; // upstream creation doesn't handle autonomous_upstream_ autonomous_upstream_ = true;