| // Copyright 2016 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 "components/cronet/stale_host_resolver.h" |
| |
| #include "base/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/values.h" |
| #include "components/cronet/url_request_context_config.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/cert/cert_verifier.h" |
| #include "net/dns/host_resolver_proc.h" |
| #include "net/http/http_network_session.h" |
| #include "net/log/net_log.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/proxy/proxy_config.h" |
| #include "net/proxy/proxy_config_service_fixed.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_builder.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if defined(OS_ANDROID) |
| #include "net/android/network_change_notifier_factory_android.h" |
| #endif |
| |
| namespace cronet { |
| |
| namespace { |
| |
| const char kHostname[] = "example.com"; |
| const char kCacheAddress[] = "1.1.1.1"; |
| const char kNetworkAddress[] = "2.2.2.2"; |
| const char kUninitializedAddress[] = "3.3.3.3"; |
| const int kCacheEntryTTLSec = 300; |
| |
| const int kNoStaleDelaySec = 0; |
| const int kLongStaleDelaySec = 3600; |
| const uint16_t kPort = 12345; |
| |
| const int kAgeFreshSec = 0; |
| const int kAgeExpiredSec = kCacheEntryTTLSec * 2; |
| |
| // How long to wait for resolve calls to return. If the tests are working |
| // correctly, we won't end up waiting this long -- it's just a backup. |
| const int kWaitTimeoutSec = 1; |
| |
| net::AddressList MakeAddressList(const char* ip_address_str) { |
| net::IPAddress address; |
| bool rv = address.AssignFromIPLiteral(ip_address_str); |
| DCHECK(rv); |
| |
| net::AddressList address_list; |
| address_list.push_back(net::IPEndPoint(address, 0u)); |
| return address_list; |
| } |
| |
| class MockHostResolverProc : public net::HostResolverProc { |
| public: |
| MockHostResolverProc() : HostResolverProc(nullptr) {} |
| |
| int Resolve(const std::string& hostname, |
| net::AddressFamily address_family, |
| net::HostResolverFlags host_resolver_flags, |
| net::AddressList* address_list, |
| int* os_error) override { |
| *address_list = MakeAddressList(kNetworkAddress); |
| return net::OK; |
| } |
| |
| protected: |
| ~MockHostResolverProc() override {} |
| }; |
| |
| class StaleHostResolverTest : public testing::Test { |
| protected: |
| StaleHostResolverTest() |
| : mock_proc_(new MockHostResolverProc()), |
| resolver_(nullptr), |
| resolve_pending_(false), |
| resolve_complete_(false) {} |
| |
| ~StaleHostResolverTest() override {} |
| |
| void SetStaleDelay(int stale_delay_sec) { |
| DCHECK(!resolver_); |
| |
| options_.delay = base::TimeDelta::FromSeconds(stale_delay_sec); |
| } |
| |
| void SetStaleUsability(int max_expired_time_sec, |
| int max_stale_uses, |
| bool allow_other_network) { |
| DCHECK(!resolver_); |
| |
| options_.max_expired_time = |
| base::TimeDelta::FromSeconds(max_expired_time_sec); |
| options_.max_stale_uses = max_stale_uses; |
| options_.allow_other_network = allow_other_network; |
| } |
| |
| void CreateResolver() { |
| DCHECK(!resolver_); |
| |
| std::unique_ptr<net::HostResolverImpl> inner_resolver( |
| net::HostResolver::CreateDefaultResolverImpl(nullptr)); |
| |
| net::HostResolverImpl::ProcTaskParams proc_params(mock_proc_.get(), 1u); |
| inner_resolver->set_proc_params_for_test(proc_params); |
| |
| stale_resolver_ = base::WrapUnique( |
| new StaleHostResolver(std::move(inner_resolver), options_)); |
| resolver_ = stale_resolver_.get(); |
| } |
| |
| void DestroyResolver() { |
| DCHECK(stale_resolver_); |
| |
| stale_resolver_.reset(); |
| resolver_ = nullptr; |
| } |
| |
| void SetResolver(net::HostResolver* resolver) { |
| DCHECK(!resolver_); |
| |
| resolver_ = resolver; |
| } |
| |
| void ClearResolver() { |
| DCHECK(resolver_); |
| DCHECK(!stale_resolver_); |
| |
| resolver_ = nullptr; |
| } |
| |
| void CreateNetworkChangeNotifier() { |
| #if defined(OS_ANDROID) |
| net::NetworkChangeNotifier::SetFactory( |
| new net::NetworkChangeNotifierFactoryAndroid()); |
| #endif |
| net::NetworkChangeNotifier::Create(); |
| } |
| |
| // Creates a cache entry for |kHostname| that is |age_sec| seconds old. |
| void CreateCacheEntry(int age_sec) { |
| DCHECK(resolver_); |
| DCHECK(resolver_->GetHostCache()); |
| |
| base::TimeDelta ttl(base::TimeDelta::FromSeconds(kCacheEntryTTLSec)); |
| net::HostCache::Key key(kHostname, net::ADDRESS_FAMILY_IPV4, 0); |
| net::HostCache::Entry entry(net::OK, MakeAddressList(kCacheAddress), ttl); |
| base::TimeDelta age = base::TimeDelta::FromSeconds(age_sec); |
| base::TimeTicks then = base::TimeTicks::Now() - age; |
| resolver_->GetHostCache()->Set(key, entry, then, ttl); |
| } |
| |
| void OnNetworkChange() { |
| // Real network changes on Android will send both notifications. |
| net::NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); |
| net::NetworkChangeNotifier::NotifyObserversOfDNSChangeForTests(); |
| base::RunLoop().RunUntilIdle(); // Wait for notification. |
| } |
| |
| void LookupStale() { |
| DCHECK(resolver_); |
| DCHECK(resolver_->GetHostCache()); |
| |
| net::HostCache::Key key(kHostname, net::ADDRESS_FAMILY_IPV4, 0); |
| base::TimeTicks now = base::TimeTicks::Now(); |
| const net::HostCache::Entry* entry; |
| net::HostCache::EntryStaleness stale; |
| entry = resolver_->GetHostCache()->LookupStale(key, now, &stale); |
| EXPECT_TRUE(entry); |
| EXPECT_TRUE(stale.is_stale()); |
| } |
| |
| void Resolve() { |
| DCHECK(resolver_); |
| EXPECT_FALSE(resolve_pending_); |
| |
| net::HostResolver::RequestInfo info(net::HostPortPair(kHostname, kPort)); |
| info.set_address_family(net::ADDRESS_FAMILY_IPV4); |
| |
| resolve_pending_ = true; |
| resolve_complete_ = false; |
| resolve_addresses_ = MakeAddressList(kUninitializedAddress); |
| resolve_error_ = net::ERR_UNEXPECTED; |
| |
| int rv = |
| resolver_->Resolve(info, net::DEFAULT_PRIORITY, &resolve_addresses_, |
| base::Bind(&StaleHostResolverTest::OnResolveComplete, |
| base::Unretained(this)), |
| &request_, net::NetLogWithSource()); |
| if (rv != net::ERR_IO_PENDING) { |
| resolve_pending_ = false; |
| resolve_complete_ = true; |
| resolve_error_ = rv; |
| } |
| } |
| |
| void WaitForResolve() { |
| if (!resolve_pending_) |
| return; |
| |
| base::RunLoop run_loop; |
| |
| // Run until resolve completes or timeout. |
| resolve_closure_ = run_loop.QuitWhenIdleClosure(); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitWhenIdleClosure(), |
| base::TimeDelta::FromSeconds(kWaitTimeoutSec)); |
| run_loop.Run(); |
| } |
| |
| void WaitForIdle() { |
| base::RunLoop run_loop; |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, run_loop.QuitWhenIdleClosure()); |
| run_loop.Run(); |
| } |
| |
| void Cancel() { |
| DCHECK(resolver_); |
| EXPECT_TRUE(resolve_pending_); |
| |
| delete request_.release(); |
| |
| resolve_pending_ = false; |
| } |
| |
| void OnResolveComplete(int error) { |
| EXPECT_TRUE(resolve_pending_); |
| |
| request_.reset(); |
| |
| resolve_error_ = error; |
| resolve_pending_ = false; |
| resolve_complete_ = true; |
| |
| if (!resolve_closure_.is_null()) |
| base::ResetAndReturn(&resolve_closure_).Run(); |
| } |
| |
| bool resolve_complete() const { return resolve_complete_; } |
| int resolve_error() const { return resolve_error_; } |
| const net::AddressList& resolve_addresses() const { |
| return resolve_addresses_; |
| } |
| |
| private: |
| // Needed for HostResolver to run HostResolverProc callbacks. |
| base::MessageLoopForIO message_loop_for_io_; |
| scoped_refptr<MockHostResolverProc> mock_proc_; |
| |
| net::HostResolver* resolver_; |
| StaleHostResolver::StaleOptions options_; |
| std::unique_ptr<StaleHostResolver> stale_resolver_; |
| |
| base::TimeTicks now_; |
| std::unique_ptr<net::HostResolver::Request> request_; |
| bool resolve_pending_; |
| bool resolve_complete_; |
| net::AddressList resolve_addresses_; |
| int resolve_error_; |
| |
| base::Closure resolve_closure_; |
| }; |
| |
| // Make sure that test harness can be created and destroyed without crashing. |
| TEST_F(StaleHostResolverTest, Null) {} |
| |
| // Make sure that resolver can be created and destroyed without crashing. |
| TEST_F(StaleHostResolverTest, Create) { |
| CreateResolver(); |
| } |
| |
| TEST_F(StaleHostResolverTest, Network) { |
| CreateResolver(); |
| |
| Resolve(); |
| WaitForResolve(); |
| |
| EXPECT_TRUE(resolve_complete()); |
| EXPECT_EQ(net::OK, resolve_error()); |
| EXPECT_EQ(1u, resolve_addresses().size()); |
| EXPECT_EQ(kNetworkAddress, resolve_addresses()[0].ToStringWithoutPort()); |
| } |
| |
| TEST_F(StaleHostResolverTest, FreshCache) { |
| CreateResolver(); |
| CreateCacheEntry(kAgeFreshSec); |
| |
| Resolve(); |
| |
| EXPECT_TRUE(resolve_complete()); |
| EXPECT_EQ(net::OK, resolve_error()); |
| EXPECT_EQ(1u, resolve_addresses().size()); |
| EXPECT_EQ(kCacheAddress, resolve_addresses()[0].ToStringWithoutPort()); |
| |
| WaitForIdle(); |
| } |
| |
| TEST_F(StaleHostResolverTest, StaleCache) { |
| SetStaleDelay(kNoStaleDelaySec); |
| CreateResolver(); |
| CreateCacheEntry(kAgeExpiredSec); |
| |
| Resolve(); |
| WaitForResolve(); |
| |
| EXPECT_TRUE(resolve_complete()); |
| EXPECT_EQ(net::OK, resolve_error()); |
| EXPECT_EQ(1u, resolve_addresses().size()); |
| EXPECT_EQ(kCacheAddress, resolve_addresses()[0].ToStringWithoutPort()); |
| } |
| |
| TEST_F(StaleHostResolverTest, NetworkWithStaleCache) { |
| SetStaleDelay(kLongStaleDelaySec); |
| CreateResolver(); |
| CreateCacheEntry(kAgeExpiredSec); |
| |
| Resolve(); |
| WaitForResolve(); |
| |
| EXPECT_TRUE(resolve_complete()); |
| EXPECT_EQ(net::OK, resolve_error()); |
| EXPECT_EQ(1u, resolve_addresses().size()); |
| EXPECT_EQ(kNetworkAddress, resolve_addresses()[0].ToStringWithoutPort()); |
| } |
| |
| TEST_F(StaleHostResolverTest, CancelWithNoCache) { |
| SetStaleDelay(kNoStaleDelaySec); |
| CreateResolver(); |
| |
| Resolve(); |
| |
| Cancel(); |
| |
| EXPECT_FALSE(resolve_complete()); |
| |
| // Make sure there's no lingering |OnResolveComplete()| callback waiting. |
| WaitForIdle(); |
| } |
| |
| TEST_F(StaleHostResolverTest, CancelWithStaleCache) { |
| SetStaleDelay(kLongStaleDelaySec); |
| CreateResolver(); |
| CreateCacheEntry(kAgeExpiredSec); |
| |
| Resolve(); |
| |
| Cancel(); |
| |
| EXPECT_FALSE(resolve_complete()); |
| |
| // Make sure there's no lingering |OnResolveComplete()| callback waiting. |
| WaitForIdle(); |
| } |
| |
| // CancelWithFreshCache makes no sense; the request would've returned |
| // synchronously. |
| |
| TEST_F(StaleHostResolverTest, StaleUsability) { |
| const struct { |
| int max_expired_time_sec; |
| int max_stale_uses; |
| bool allow_other_network; |
| |
| int age_sec; |
| int stale_use; |
| int network_changes; |
| |
| bool usable; |
| } kUsabilityTestCases[] = { |
| // Fresh data always accepted. |
| {0, 0, true, -1, 1, 0, true}, |
| {1, 1, false, -1, 1, 0, true}, |
| |
| // Unlimited expired time accepts non-zero time. |
| {0, 0, true, 1, 1, 0, true}, |
| |
| // Limited expired time accepts before but not after limit. |
| {2, 0, true, 1, 1, 0, true}, |
| {2, 0, true, 3, 1, 0, false}, |
| |
| // Unlimited stale uses accepts first and later uses. |
| {2, 0, true, 1, 1, 0, true}, |
| {2, 0, true, 1, 9, 0, true}, |
| |
| // Limited stale uses accepts up to and including limit. |
| {2, 2, true, 1, 1, 0, true}, |
| {2, 2, true, 1, 2, 0, true}, |
| {2, 2, true, 1, 3, 0, false}, |
| {2, 2, true, 1, 9, 0, false}, |
| |
| // Allowing other networks accepts zero or more network changes. |
| {2, 0, true, 1, 1, 0, true}, |
| {2, 0, true, 1, 1, 1, true}, |
| {2, 0, true, 1, 1, 9, true}, |
| |
| // Disallowing other networks only accepts zero network changes. |
| {2, 0, false, 1, 1, 0, true}, |
| {2, 0, false, 1, 1, 1, false}, |
| {2, 0, false, 1, 1, 9, false}, |
| }; |
| |
| SetStaleDelay(kNoStaleDelaySec); |
| CreateNetworkChangeNotifier(); |
| |
| for (size_t i = 0; i < arraysize(kUsabilityTestCases); ++i) { |
| const auto& test_case = kUsabilityTestCases[i]; |
| |
| SetStaleUsability(test_case.max_expired_time_sec, test_case.max_stale_uses, |
| test_case.allow_other_network); |
| CreateResolver(); |
| CreateCacheEntry(kCacheEntryTTLSec + test_case.age_sec); |
| for (int j = 0; j < test_case.network_changes; ++j) |
| OnNetworkChange(); |
| for (int j = 0; j < test_case.stale_use - 1; ++j) |
| LookupStale(); |
| |
| Resolve(); |
| WaitForResolve(); |
| EXPECT_TRUE(resolve_complete()) << i; |
| EXPECT_EQ(net::OK, resolve_error()) << i; |
| EXPECT_EQ(1u, resolve_addresses().size()) << i; |
| { |
| const char* expected = test_case.usable ? kCacheAddress : kNetworkAddress; |
| EXPECT_EQ(expected, resolve_addresses()[0].ToStringWithoutPort()) << i; |
| } |
| |
| DestroyResolver(); |
| } |
| } |
| |
| TEST_F(StaleHostResolverTest, CreatedByContext) { |
| URLRequestContextConfig config( |
| // Enable QUIC. |
| true, |
| // QUIC User Agent ID. |
| "Default QUIC User Agent ID", |
| // Enable SPDY. |
| true, |
| // Enable SDCH. |
| false, |
| // Enable Brotli. |
| false, |
| // Type of http cache. |
| URLRequestContextConfig::HttpCacheType::DISK, |
| // Max size of http cache in bytes. |
| 1024000, |
| // Disable caching for HTTP responses. Other information may be stored in |
| // the cache. |
| false, |
| // Storage path for http cache and cookie storage. |
| "/data/data/org.chromium.net/app_cronet_test/test_storage", |
| // User-Agent request header field. |
| "fake agent", |
| // JSON encoded experimental options. |
| "{\"AsyncDNS\":{\"enable\":false}," |
| "\"StaleDNS\":{\"enable\":true," |
| "\"delay_ms\":0," |
| "\"max_expired_time_ms\":0," |
| "\"max_stale_uses\":0}}", |
| // MockCertVerifier to use for testing purposes. |
| std::unique_ptr<net::CertVerifier>(), |
| // Enable network quality estimator. |
| false, |
| // Enable Public Key Pinning bypass for local trust anchors. |
| true, |
| // Certificate verifier cache data. |
| ""); |
| |
| net::URLRequestContextBuilder builder; |
| net::NetLog net_log; |
| config.ConfigureURLRequestContextBuilder(&builder, &net_log, nullptr); |
| // Set a ProxyConfigService to avoid DCHECK failure when building. |
| builder.set_proxy_config_service(base::WrapUnique( |
| new net::ProxyConfigServiceFixed(net::ProxyConfig::CreateDirect()))); |
| std::unique_ptr<net::URLRequestContext> context(builder.Build()); |
| |
| // Duplicate StaleCache test case to ensure StaleHostResolver was created: |
| |
| // Note: Experimental config above sets 0ms stale delay. |
| SetResolver(context->host_resolver()); |
| CreateCacheEntry(kAgeExpiredSec); |
| |
| Resolve(); |
| EXPECT_FALSE(resolve_complete()); |
| WaitForResolve(); |
| |
| EXPECT_TRUE(resolve_complete()); |
| EXPECT_EQ(net::OK, resolve_error()); |
| EXPECT_EQ(1u, resolve_addresses().size()); |
| EXPECT_EQ(kCacheAddress, resolve_addresses()[0].ToStringWithoutPort()); |
| } |
| |
| } // namespace |
| |
| } // namespace cronet |