// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/dns/resolve_context.h" #include #include #include #include #include "base/bind.h" #include "base/memory/ref_counted.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "base/time/time.h" #include "net/base/address_list.h" #include "net/base/features.h" #include "net/base/ip_address.h" #include "net/base/ip_endpoint.h" #include "net/base/mock_network_change_notifier.h" #include "net/base/network_change_notifier.h" #include "net/base/network_isolation_key.h" #include "net/dns/dns_config.h" #include "net/dns/dns_server_iterator.h" #include "net/dns/dns_session.h" #include "net/dns/host_cache.h" #include "net/dns/public/dns_over_https_config.h" #include "net/dns/public/dns_over_https_server_config.h" #include "net/dns/public/dns_protocol.h" #include "net/dns/public/dns_query_type.h" #include "net/dns/public/host_resolver_source.h" #include "net/socket/socket_test_util.h" #include "net/test/test_with_task_environment.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_builder.h" #include "net/url_request/url_request_test_util.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { class ResolveContextTest : public TestWithTaskEnvironment { public: ResolveContextTest() = default; scoped_refptr CreateDnsSession(const DnsConfig& config) { auto null_random_callback = base::BindRepeating([](int, int) -> int { IMMEDIATE_CRASH(); }); return base::MakeRefCounted(config, null_random_callback, nullptr /* netlog */); } protected: test::ScopedMockNetworkChangeNotifier mock_notifier_; private: std::unique_ptr socket_factory_ = std::make_unique(); }; DnsConfig CreateDnsConfig(int num_servers, int num_doh_servers) { DnsConfig config; for (int i = 0; i < num_servers; ++i) { IPEndPoint dns_endpoint(IPAddress(192, 168, 1, static_cast(i)), dns_protocol::kDefaultPort); config.nameservers.push_back(dns_endpoint); } std::vector templates; templates.reserve(num_doh_servers); for (int i = 0; i < num_doh_servers; ++i) { templates.push_back( base::StringPrintf("https://mock.http/doh_test_%d{?dns}", i)); } config.doh_config = *DnsOverHttpsConfig::FromTemplatesForTesting(std::move(templates)); return config; } // Simulate a new session with the same pointer as an old deleted session by // invalidating WeakPtrs. TEST_F(ResolveContextTest, ReusedSessionPointer) { DnsConfig config = CreateDnsConfig(1 /* num_servers */, 3 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); auto request_context = CreateTestURLRequestContextBuilder()->Build(); ResolveContext context(request_context.get(), true /* enable_caching */); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); // Mark probe success for the "original" (pre-invalidation) session. context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); ASSERT_TRUE(context.GetDohServerAvailability(1u, session.get())); // Simulate session destruction and recreation on the same pointer. session->InvalidateWeakPtrsForTesting(); // Expect |session| should now be treated as a new session, not matching // |context|'s "current" session. Expect availability from the "old" session // should not be read and RecordServerSuccess() should have no effect because // the "new" session has not yet been marked as "current" through // InvalidateCaches(). EXPECT_FALSE(context.GetDohServerAvailability(1u, session.get())); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); EXPECT_FALSE(context.GetDohServerAvailability(1u, session.get())); } TEST_F(ResolveContextTest, DohServerAvailability_InitialAvailability) { DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); auto request_context = CreateTestURLRequestContextBuilder()->Build(); ResolveContext context(request_context.get(), true /* enable_caching */); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); EXPECT_EQ(context.NumAvailableDohServers(session.get()), 0u); std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); EXPECT_FALSE(doh_itr->AttemptAvailable()); } TEST_F(ResolveContextTest, DohServerAvailability_RecordedSuccess) { DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); auto request_context = CreateTestURLRequestContextBuilder()->Build(); ResolveContext context(request_context.get(), true /* enable_caching */); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); ASSERT_EQ(context.NumAvailableDohServers(session.get()), 0u); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); EXPECT_EQ(context.NumAvailableDohServers(session.get()), 1u); std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u); } TEST_F(ResolveContextTest, DohServerAvailability_NoCurrentSession) { DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); auto request_context = CreateTestURLRequestContextBuilder()->Build(); ResolveContext context(request_context.get(), true /* enable_caching */); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); EXPECT_FALSE(doh_itr->AttemptAvailable()); EXPECT_EQ(0u, context.NumAvailableDohServers(session.get())); EXPECT_FALSE(context.GetDohServerAvailability(1, session.get())); } TEST_F(ResolveContextTest, DohServerAvailability_DifferentSession) { DnsConfig config1 = CreateDnsConfig(1 /* num_servers */, 3 /* num_doh_servers */); scoped_refptr session1 = CreateDnsSession(config1); DnsConfig config2 = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session2 = CreateDnsSession(config2); auto request_context = CreateTestURLRequestContextBuilder()->Build(); ResolveContext context(request_context.get(), true /* enable_caching */); context.InvalidateCachesAndPerSessionData(session2.get(), true /* network_change */); // Use current session to set a probe result. context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session2.get()); std::unique_ptr doh_itr = context.GetDohIterator( session1->config(), SecureDnsMode::kAutomatic, session1.get()); EXPECT_FALSE(doh_itr->AttemptAvailable()); EXPECT_EQ(0u, context.NumAvailableDohServers(session1.get())); EXPECT_FALSE(context.GetDohServerAvailability(1u, session1.get())); // Different session for RecordServerFailure() should have no effect. ASSERT_TRUE(context.GetDohServerAvailability(1u, session2.get())); for (int i = 0; i < ResolveContext::kAutomaticModeFailureLimit; ++i) { context.RecordServerFailure(1u /* server_index */, true /* is_doh_server */, ERR_FAILED, session1.get()); } EXPECT_TRUE(context.GetDohServerAvailability(1u, session2.get())); } TEST_F(ResolveContextTest, DohServerIndexToUse) { DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); auto request_context = CreateTestURLRequestContextBuilder()->Build(); ResolveContext context(request_context.get(), true /* enable_caching */); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); context.RecordServerSuccess(0u /* server_index */, true /* is_doh_server */, session.get()); EXPECT_EQ(context.NumAvailableDohServers(session.get()), 1u); std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u); EXPECT_FALSE(doh_itr->AttemptAvailable()); } TEST_F(ResolveContextTest, DohServerIndexToUse_NoneEligible) { DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); auto request_context = CreateTestURLRequestContextBuilder()->Build(); ResolveContext context(request_context.get(), true /* enable_caching */); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); EXPECT_FALSE(doh_itr->AttemptAvailable()); } TEST_F(ResolveContextTest, DohServerIndexToUse_SecureMode) { DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); auto request_context = CreateTestURLRequestContextBuilder()->Build(); ResolveContext context(request_context.get(), true /* enable_caching */); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kSecure, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u); } class TestDnsObserver : public NetworkChangeNotifier::DNSObserver { public: void OnDNSChanged() override { ++dns_changed_calls_; } int dns_changed_calls() const { return dns_changed_calls_; } private: int dns_changed_calls_ = 0; }; TEST_F(ResolveContextTest, DohServerAvailabilityNotification) { TestDnsObserver config_observer; NetworkChangeNotifier::AddDNSObserver(&config_observer); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); auto request_context = CreateTestURLRequestContextBuilder()->Build(); ResolveContext context(request_context.get(), true /* enable_caching */); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); base::RunLoop().RunUntilIdle(); // Notifications are async. EXPECT_EQ(0, config_observer.dns_changed_calls()); // Expect notification on first available DoH server. ASSERT_EQ(0u, context.NumAvailableDohServers(session.get())); context.RecordServerSuccess(0u /* server_index */, true /* is_doh_server */, session.get()); ASSERT_EQ(1u, context.NumAvailableDohServers(session.get())); base::RunLoop().RunUntilIdle(); // Notifications are async. EXPECT_EQ(1, config_observer.dns_changed_calls()); // No notifications as additional servers are available or unavailable. context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); base::RunLoop().RunUntilIdle(); // Notifications are async. EXPECT_EQ(1, config_observer.dns_changed_calls()); for (int i = 0; i < ResolveContext::kAutomaticModeFailureLimit; ++i) { ASSERT_EQ(2u, context.NumAvailableDohServers(session.get())); context.RecordServerFailure(0u /* server_index */, true /* is_doh_server */, ERR_FAILED, session.get()); base::RunLoop().RunUntilIdle(); // Notifications are async. EXPECT_EQ(1, config_observer.dns_changed_calls()); } ASSERT_EQ(1u, context.NumAvailableDohServers(session.get())); // Expect notification on last server unavailable. for (int i = 0; i < ResolveContext::kAutomaticModeFailureLimit; ++i) { ASSERT_EQ(1u, context.NumAvailableDohServers(session.get())); base::RunLoop().RunUntilIdle(); // Notifications are async. EXPECT_EQ(1, config_observer.dns_changed_calls()); context.RecordServerFailure(1u /* server_index */, true /* is_doh_server */, ERR_FAILED, session.get()); } ASSERT_EQ(0u, context.NumAvailableDohServers(session.get())); base::RunLoop().RunUntilIdle(); // Notifications are async. EXPECT_EQ(2, config_observer.dns_changed_calls()); NetworkChangeNotifier::RemoveDNSObserver(&config_observer); } TEST_F(ResolveContextTest, HostCacheInvalidation) { ResolveContext context(nullptr /* url_request_context */, true /* enable_caching */); base::TimeTicks now; HostCache::Key key("example.com", DnsQueryType::UNSPECIFIED, 0, HostResolverSource::ANY, NetworkIsolationKey()); context.host_cache()->Set( key, HostCache::Entry(OK, /*ip_endpoints=*/{}, /*aliases=*/{}, HostCache::Entry::SOURCE_UNKNOWN), now, base::Seconds(10)); ASSERT_TRUE(context.host_cache()->Lookup(key, now)); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); EXPECT_FALSE(context.host_cache()->Lookup(key, now)); // Re-add to the host cache and now add some DoH server status. context.host_cache()->Set( key, HostCache::Entry(OK, /*ip_endpoints=*/{}, /*aliases=*/{}, HostCache::Entry::SOURCE_UNKNOWN), now, base::Seconds(10)); context.RecordServerSuccess(0u /* server_index */, true /* is_doh_server */, session.get()); ASSERT_TRUE(context.host_cache()->Lookup(key, now)); ASSERT_TRUE(context.GetDohServerAvailability(0u, session.get())); // Invalidate again. DnsConfig config2 = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session2 = CreateDnsSession(config2); context.InvalidateCachesAndPerSessionData(session2.get(), true /* network_change */); EXPECT_FALSE(context.host_cache()->Lookup(key, now)); EXPECT_FALSE(context.GetDohServerAvailability(0u, session.get())); EXPECT_FALSE(context.GetDohServerAvailability(0u, session2.get())); } TEST_F(ResolveContextTest, HostCacheInvalidation_SameSession) { ResolveContext context(nullptr /* url_request_context */, true /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); // Initial invalidation just to set the session. context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); // Add to the host cache and add some DoH server status. base::TimeTicks now; HostCache::Key key("example.com", DnsQueryType::UNSPECIFIED, 0, HostResolverSource::ANY, NetworkIsolationKey()); context.host_cache()->Set( key, HostCache::Entry(OK, /*ip_endpoints=*/{}, /*aliases=*/{"example.com"}, HostCache::Entry::SOURCE_UNKNOWN), now, base::Seconds(10)); context.RecordServerSuccess(0u /* server_index */, true /* is_doh_server */, session.get()); ASSERT_TRUE(context.host_cache()->Lookup(key, now)); ASSERT_TRUE(context.GetDohServerAvailability(0u, session.get())); // Invalidate again with the same session. context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); // Expect host cache to be invalidated but not the per-session data. EXPECT_FALSE(context.host_cache()->Lookup(key, now)); EXPECT_TRUE(context.GetDohServerAvailability(0u, session.get())); } TEST_F(ResolveContextTest, Failures_Consecutive) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); // Expect server preference to change after |config.attempts| failures. for (int i = 0; i < config.attempts; i++) { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); context.RecordServerFailure(1u /* server_index */, false /* is_doh_server */, ERR_FAILED, session.get()); } { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); } // Expect failures to be reset on successful request. context.RecordServerSuccess(1u /* server_index */, false /* is_doh_server */, session.get()); { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); } } TEST_F(ResolveContextTest, Failures_NonConsecutive) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); for (int i = 0; i < config.attempts - 1; i++) { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); context.RecordServerFailure(1u /* server_index */, false /* is_doh_server */, ERR_FAILED, session.get()); } { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); } context.RecordServerSuccess(1u /* server_index */, false /* is_doh_server */, session.get()); { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); } // Expect server stay preferred through non-consecutive failures. context.RecordServerFailure(1u /* server_index */, false /* is_doh_server */, ERR_FAILED, session.get()); { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); } } TEST_F(ResolveContextTest, Failures_NoSession) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); // No expected change from recording failures. for (int i = 0; i < config.attempts; i++) { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); EXPECT_FALSE(classic_itr->AttemptAvailable()); context.RecordServerFailure(1u /* server_index */, false /* is_doh_server */, ERR_FAILED, session.get()); } std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); EXPECT_FALSE(classic_itr->AttemptAvailable()); } TEST_F(ResolveContextTest, Failures_DifferentSession) { DnsConfig config1 = CreateDnsConfig(1 /* num_servers */, 3 /* num_doh_servers */); scoped_refptr session1 = CreateDnsSession(config1); DnsConfig config2 = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session2 = CreateDnsSession(config2); ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); context.InvalidateCachesAndPerSessionData(session2.get(), true /* network_change */); // No change from recording failures to wrong session. for (int i = 0; i < config1.attempts; i++) { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session2->config(), session2.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); context.RecordServerFailure(1u /* server_index */, false /* is_doh_server */, ERR_FAILED, session1.get()); } std::unique_ptr classic_itr = context.GetClassicDnsIterator(session2->config(), session2.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); } // Test 2 of 3 servers failing. TEST_F(ResolveContextTest, TwoFailures) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(3 /* num_servers */, 2 /* num_doh_servers */); config.attempts = 1; scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); // Expect server preference to change after |config.attempts| failures. for (int i = 0; i < config.attempts; i++) { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 2u); context.RecordServerFailure(0u /* server_index */, false /* is_doh_server */, ERR_FAILED, session.get()); context.RecordServerFailure(1u /* server_index */, false /* is_doh_server */, ERR_FAILED, session.get()); } { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 2u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); } // Expect failures to be reset on successful request. context.RecordServerSuccess(0u /* server_index */, false /* is_doh_server */, session.get()); context.RecordServerSuccess(1u /* server_index */, false /* is_doh_server */, session.get()); { std::unique_ptr classic_itr = context.GetClassicDnsIterator(session->config(), session.get()); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 1u); ASSERT_TRUE(classic_itr->AttemptAvailable()); EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 2u); } } class TestDohStatusObserver : public ResolveContext::DohStatusObserver { public: void OnSessionChanged() override { ++session_changes_; } void OnDohServerUnavailable(bool network_change) override { ++server_unavailable_notifications_; } int session_changes() const { return session_changes_; } int server_unavailable_notifications() const { return server_unavailable_notifications_; } private: int session_changes_ = 0; int server_unavailable_notifications_ = 0; }; TEST_F(ResolveContextTest, DohFailures_Consecutive) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); TestDohStatusObserver observer; context.RegisterDohStatusObserver(&observer); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); for (size_t i = 0; i < ResolveContext::kAutomaticModeFailureLimit; i++) { std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u); EXPECT_EQ(1u, context.NumAvailableDohServers(session.get())); EXPECT_EQ(0, observer.server_unavailable_notifications()); context.RecordServerFailure(1u /* server_index */, true /* is_doh_server */, ERR_FAILED, session.get()); } std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); EXPECT_FALSE(doh_itr->AttemptAvailable()); EXPECT_EQ(0u, context.NumAvailableDohServers(session.get())); EXPECT_EQ(1, observer.server_unavailable_notifications()); context.UnregisterDohStatusObserver(&observer); } TEST_F(ResolveContextTest, DohFailures_NonConsecutive) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); TestDohStatusObserver observer; context.RegisterDohStatusObserver(&observer); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); for (size_t i = 0; i < ResolveContext::kAutomaticModeFailureLimit - 1; i++) { std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u); EXPECT_EQ(1u, context.NumAvailableDohServers(session.get())); context.RecordServerFailure(1u /* server_index */, true /* is_doh_server */, ERR_FAILED, session.get()); } { std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u); } EXPECT_EQ(1u, context.NumAvailableDohServers(session.get())); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); { std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u); } EXPECT_EQ(1u, context.NumAvailableDohServers(session.get())); // Expect a single additional failure should not make a DoH server unavailable // because the success resets failure tracking. context.RecordServerFailure(1u /* server_index */, true /* is_doh_server */, ERR_FAILED, session.get()); { std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u); } EXPECT_EQ(1u, context.NumAvailableDohServers(session.get())); EXPECT_EQ(0, observer.server_unavailable_notifications()); context.UnregisterDohStatusObserver(&observer); } TEST_F(ResolveContextTest, DohFailures_SuccessAfterFailures) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); TestDohStatusObserver observer; context.RegisterDohStatusObserver(&observer); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); for (size_t i = 0; i < ResolveContext::kAutomaticModeFailureLimit; i++) { context.RecordServerFailure(1u /* server_index */, true /* is_doh_server */, ERR_FAILED, session.get()); } ASSERT_EQ(0u, context.NumAvailableDohServers(session.get())); EXPECT_EQ(1, observer.server_unavailable_notifications()); // Expect a single success to make an unavailable DoH server available again. context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); { std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u); } EXPECT_EQ(1u, context.NumAvailableDohServers(session.get())); EXPECT_EQ(1, observer.server_unavailable_notifications()); context.UnregisterDohStatusObserver(&observer); } TEST_F(ResolveContextTest, DohFailures_NoSession) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); // No expected change from recording failures. for (size_t i = 0; i < ResolveContext::kAutomaticModeFailureLimit; i++) { EXPECT_EQ(0u, context.NumAvailableDohServers(session.get())); context.RecordServerFailure(1u /* server_index */, true /* is_doh_server */, ERR_FAILED, session.get()); } EXPECT_EQ(0u, context.NumAvailableDohServers(session.get())); } TEST_F(ResolveContextTest, DohFailures_DifferentSession) { DnsConfig config1 = CreateDnsConfig(1 /* num_servers */, 3 /* num_doh_servers */); scoped_refptr session1 = CreateDnsSession(config1); DnsConfig config2 = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session2 = CreateDnsSession(config2); ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); context.InvalidateCachesAndPerSessionData(session2.get(), true /* network_change */); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session2.get()); ASSERT_EQ(1u, context.NumAvailableDohServers(session2.get())); // No change from recording failures to wrong session. for (size_t i = 0; i < ResolveContext::kAutomaticModeFailureLimit; i++) { EXPECT_EQ(1u, context.NumAvailableDohServers(session2.get())); context.RecordServerFailure(1u /* server_index */, true /* is_doh_server */, ERR_FAILED, session1.get()); } EXPECT_EQ(1u, context.NumAvailableDohServers(session2.get())); } // Test 2 of 3 DoH servers failing. TEST_F(ResolveContextTest, TwoDohFailures) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 3 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); context.RecordServerSuccess(0u /* server_index */, true /* is_doh_server */, session.get()); context.RecordServerSuccess(1u /* server_index */, true /* is_doh_server */, session.get()); context.RecordServerSuccess(2u /* server_index */, true /* is_doh_server */, session.get()); // Expect server preference to change after |config.attempts| failures. for (int i = 0; i < config.attempts; i++) { std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 2u); context.RecordServerFailure(0u /* server_index */, true /* is_doh_server */, ERR_FAILED, session.get()); context.RecordServerFailure(1u /* server_index */, true /* is_doh_server */, ERR_FAILED, session.get()); } std::unique_ptr doh_itr = context.GetDohIterator( session->config(), SecureDnsMode::kAutomatic, session.get()); ASSERT_TRUE(doh_itr->AttemptAvailable()); EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 2u); } // Expect default calculated fallback period to be within 10ms of // |DnsConfig::fallback_period|. TEST_F(ResolveContextTest, FallbackPeriod_Default) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); base::TimeDelta delta = context.NextClassicFallbackPeriod(0 /* server_index */, 0 /* attempt */, session.get()) - config.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); delta = context.NextDohFallbackPeriod(0 /* doh_server_index */, session.get()) - config.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); } // Expect short calculated fallback period to be within 10ms of // |DnsConfig::fallback_period|. TEST_F(ResolveContextTest, FallbackPeriod_ShortConfigured) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); config.fallback_period = base::Milliseconds(15); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); base::TimeDelta delta = context.NextClassicFallbackPeriod(0 /* server_index */, 0 /* attempt */, session.get()) - config.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); delta = context.NextDohFallbackPeriod(0 /* doh_server_index */, session.get()) - config.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); } // Expect long calculated fallback period to be equal to // |DnsConfig::fallback_period|. (Default max fallback period is 5 seconds, so // NextClassicFallbackPeriod() should return exactly the config fallback // period.) TEST_F(ResolveContextTest, FallbackPeriod_LongConfigured) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); config.fallback_period = base::Seconds(15); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); EXPECT_EQ(context.NextClassicFallbackPeriod(0 /* server_index */, 0 /* attempt */, session.get()), config.fallback_period); EXPECT_EQ( context.NextDohFallbackPeriod(0 /* doh_server_index */, session.get()), config.fallback_period); } // Expect fallback periods to increase on recording long round-trip times. TEST_F(ResolveContextTest, FallbackPeriod_LongRtt) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); for (int i = 0; i < 50; ++i) { context.RecordRtt(0u /* server_index */, false /* is_doh_server */, base::Minutes(10), OK, session.get()); context.RecordRtt(1u /* server_index */, true /* is_doh_server */, base::Minutes(10), OK, session.get()); } // Expect servers with high recorded RTT to have increased fallback periods // (>10ms). base::TimeDelta delta = context.NextClassicFallbackPeriod(0u /* server_index */, 0 /* attempt */, session.get()) - config.fallback_period; EXPECT_GT(delta, base::Milliseconds(10)); delta = context.NextDohFallbackPeriod(1u, session.get()) - config.fallback_period; EXPECT_GT(delta, base::Milliseconds(10)); // Servers without recorded RTT expected to remain the same (<=10ms). delta = context.NextClassicFallbackPeriod(1u /* server_index */, 0 /* attempt */, session.get()) - config.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); delta = context.NextDohFallbackPeriod(0u /* doh_server_index */, session.get()) - config.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); } // Expect recording round-trip times to have no affect on fallback period // without a current session. TEST_F(ResolveContextTest, FallbackPeriod_NoSession) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); for (int i = 0; i < 50; ++i) { context.RecordRtt(0u /* server_index */, false /* is_doh_server */, base::Minutes(10), OK, session.get()); context.RecordRtt(1u /* server_index */, true /* is_doh_server */, base::Minutes(10), OK, session.get()); } base::TimeDelta delta = context.NextClassicFallbackPeriod(0u /* server_index */, 0 /* attempt */, session.get()) - config.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); delta = context.NextDohFallbackPeriod(1u /* doh_server_index */, session.get()) - config.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); } // Expect recording round-trip times to have no affect on fallback periods // without a current session. TEST_F(ResolveContextTest, FallbackPeriod_DifferentSession) { DnsConfig config1 = CreateDnsConfig(1 /* num_servers */, 3 /* num_doh_servers */); scoped_refptr session1 = CreateDnsSession(config1); DnsConfig config2 = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session2 = CreateDnsSession(config2); ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); context.InvalidateCachesAndPerSessionData(session2.get(), true /* network_change */); // Record RTT's to increase fallback periods for current session. for (int i = 0; i < 50; ++i) { context.RecordRtt(0u /* server_index */, false /* is_doh_server */, base::Minutes(10), OK, session2.get()); context.RecordRtt(1u /* server_index */, true /* is_doh_server */, base::Minutes(10), OK, session2.get()); } // Expect normal short fallback periods for other session. base::TimeDelta delta = context.NextClassicFallbackPeriod(0u /* server_index */, 0 /* attempt */, session1.get()) - config1.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); delta = context.NextDohFallbackPeriod(0u /* doh_server_index */, session1.get()) - config1.fallback_period; EXPECT_LE(delta, base::Milliseconds(10)); // Recording RTT's for other session should have no effect on current session // fallback periods. base::TimeDelta fallback_period = context.NextClassicFallbackPeriod( 0u /* server_index */, 0 /* attempt */, session2.get()); for (int i = 0; i < 50; ++i) { context.RecordRtt(0u /* server_index */, false /* is_doh_server */, base::Milliseconds(1), OK, session1.get()); } EXPECT_EQ(fallback_period, context.NextClassicFallbackPeriod(0u /* server_index */, 0 /* attempt */, session2.get())); } // Expect minimum timeout will be used when fallback period is small. TEST_F(ResolveContextTest, SecureTransactionTimeout_SmallFallbackPeriod) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(0 /* num_servers */, 1 /* num_doh_servers */); config.fallback_period = base::TimeDelta(); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); EXPECT_EQ( context.SecureTransactionTimeout(SecureDnsMode::kSecure, session.get()), features::kDnsMinTransactionTimeout.Get()); } // Expect multiplier on fallback period to be used when larger than minimum // timeout. TEST_F(ResolveContextTest, SecureTransactionTimeout_LongFallbackPeriod) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); const base::TimeDelta kFallbackPeriod = base::Minutes(5); DnsConfig config = CreateDnsConfig(0 /* num_servers */, 1 /* num_doh_servers */); config.fallback_period = kFallbackPeriod; scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); base::TimeDelta expected = kFallbackPeriod * features::kDnsTransactionTimeoutMultiplier.Get(); ASSERT_GT(expected, features::kDnsMinTransactionTimeout.Get()); EXPECT_EQ( context.SecureTransactionTimeout(SecureDnsMode::kSecure, session.get()), expected); } TEST_F(ResolveContextTest, SecureTransactionTimeout_LongRtt) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(0 /* num_servers */, 2 /* num_doh_servers */); config.fallback_period = base::TimeDelta(); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); // Record long RTTs for only 1 server. for (int i = 0; i < 50; ++i) { context.RecordRtt(1u /* server_index */, true /* is_doh_server */, base::Minutes(10), OK, session.get()); } // No expected change from recording RTT to single server because lowest // fallback period is used. EXPECT_EQ( context.SecureTransactionTimeout(SecureDnsMode::kSecure, session.get()), features::kDnsMinTransactionTimeout.Get()); // Record long RTTs for remaining server. for (int i = 0; i < 50; ++i) { context.RecordRtt(0u /* server_index */, true /* is_doh_server */, base::Minutes(10), OK, session.get()); } // Expect longer timeouts. EXPECT_GT( context.SecureTransactionTimeout(SecureDnsMode::kSecure, session.get()), features::kDnsMinTransactionTimeout.Get()); } TEST_F(ResolveContextTest, SecureTransactionTimeout_DifferentSession) { const base::TimeDelta kFallbackPeriod = base::Minutes(5); DnsConfig config1 = CreateDnsConfig(0 /* num_servers */, 1 /* num_doh_servers */); config1.fallback_period = kFallbackPeriod; scoped_refptr session1 = CreateDnsSession(config1); DnsConfig config2 = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session2 = CreateDnsSession(config2); ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); context.InvalidateCachesAndPerSessionData(session1.get(), true /* network_change */); // Confirm that if session data were used, the timeout would be higher than // the min. base::TimeDelta multiplier_expected = kFallbackPeriod * features::kDnsTransactionTimeoutMultiplier.Get(); ASSERT_GT(multiplier_expected, features::kDnsMinTransactionTimeout.Get()); // Expect timeout always minimum with wrong session. EXPECT_EQ( context.SecureTransactionTimeout(SecureDnsMode::kSecure, session2.get()), features::kDnsMinTransactionTimeout.Get()); } // Expect minimum timeout will be used when fallback period is small. TEST_F(ResolveContextTest, ClassicTransactionTimeout_SmallFallbackPeriod) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(1 /* num_servers */, 0 /* num_doh_servers */); config.fallback_period = base::TimeDelta(); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); EXPECT_EQ(context.ClassicTransactionTimeout(session.get()), features::kDnsMinTransactionTimeout.Get()); } // Expect multiplier on fallback period to be used when larger than minimum // timeout. TEST_F(ResolveContextTest, ClassicTransactionTimeout_LongFallbackPeriod) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); const base::TimeDelta kFallbackPeriod = base::Minutes(5); DnsConfig config = CreateDnsConfig(1 /* num_servers */, 0 /* num_doh_servers */); config.fallback_period = kFallbackPeriod; scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); base::TimeDelta expected = kFallbackPeriod * features::kDnsTransactionTimeoutMultiplier.Get(); ASSERT_GT(expected, features::kDnsMinTransactionTimeout.Get()); EXPECT_EQ(context.ClassicTransactionTimeout(session.get()), expected); } TEST_F(ResolveContextTest, ClassicTransactionTimeout_LongRtt) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 0 /* num_doh_servers */); config.fallback_period = base::TimeDelta(); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); // Record long RTTs for only 1 server. for (int i = 0; i < 50; ++i) { context.RecordRtt(1u /* server_index */, false /* is_doh_server */, base::Minutes(10), OK, session.get()); } // No expected change from recording RTT to single server because lowest // fallback period is used. EXPECT_EQ(context.ClassicTransactionTimeout(session.get()), features::kDnsMinTransactionTimeout.Get()); // Record long RTTs for remaining server. for (int i = 0; i < 50; ++i) { context.RecordRtt(0u /* server_index */, false /* is_doh_server */, base::Minutes(10), OK, session.get()); } // Expect longer timeouts. EXPECT_GT(context.ClassicTransactionTimeout(session.get()), features::kDnsMinTransactionTimeout.Get()); } TEST_F(ResolveContextTest, ClassicTransactionTimeout_DifferentSession) { const base::TimeDelta kFallbackPeriod = base::Minutes(5); DnsConfig config1 = CreateDnsConfig(1 /* num_servers */, 0 /* num_doh_servers */); config1.fallback_period = kFallbackPeriod; scoped_refptr session1 = CreateDnsSession(config1); DnsConfig config2 = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session2 = CreateDnsSession(config2); ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); context.InvalidateCachesAndPerSessionData(session1.get(), true /* network_change */); // Confirm that if session data were used, the timeout would be higher than // the min. If timeout defaults are ever changed to break this assertion, then // the expected wrong-session timeout could be the same as an actual // from-session timeout, making this test seem to pass even if the behavior // under test were broken. base::TimeDelta multiplier_expected = kFallbackPeriod * features::kDnsTransactionTimeoutMultiplier.Get(); ASSERT_GT(multiplier_expected, features::kDnsMinTransactionTimeout.Get()); // Expect timeout always minimum with wrong session. EXPECT_EQ(context.ClassicTransactionTimeout(session2.get()), features::kDnsMinTransactionTimeout.Get()); } // Ensures that reported negative RTT values don't cause a crash. Regression // test for https://crbug.com/753568. TEST_F(ResolveContextTest, NegativeRtt) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 2 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); context.RecordRtt(0 /* server_index */, false /* is_doh_server */, base::Milliseconds(-1), OK /* rv */, session.get()); context.RecordRtt(0 /* server_index */, true /* is_doh_server */, base::Milliseconds(-1), OK /* rv */, session.get()); } TEST_F(ResolveContextTest, SessionChange) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); TestDohStatusObserver observer; context.RegisterDohStatusObserver(&observer); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 3 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); EXPECT_EQ(observer.session_changes(), 1); // Should get a server unavailable notification because there is >0 DoH // servers that are reset on cache invalidation. EXPECT_EQ(observer.server_unavailable_notifications(), 1); context.UnregisterDohStatusObserver(&observer); } TEST_F(ResolveContextTest, SessionChange_NoSession) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); TestDohStatusObserver observer; context.RegisterDohStatusObserver(&observer); context.InvalidateCachesAndPerSessionData(nullptr /* new_session */, false /* network_change */); EXPECT_EQ(observer.session_changes(), 1); EXPECT_EQ(observer.server_unavailable_notifications(), 0); context.UnregisterDohStatusObserver(&observer); } TEST_F(ResolveContextTest, SessionChange_NoDohServers) { ResolveContext context(nullptr /* url_request_context */, false /* enable_caching */); TestDohStatusObserver observer; context.RegisterDohStatusObserver(&observer); DnsConfig config = CreateDnsConfig(2 /* num_servers */, 0 /* num_doh_servers */); scoped_refptr session = CreateDnsSession(config); context.InvalidateCachesAndPerSessionData(session.get(), false /* network_change */); EXPECT_EQ(observer.session_changes(), 1); EXPECT_EQ(observer.server_unavailable_notifications(), 0); context.UnregisterDohStatusObserver(&observer); } } // namespace } // namespace net