|  | // 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/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" | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | // 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() { | 
|  | DCHECK(resolver_); | 
|  | DCHECK(resolver_->GetHostCache()); | 
|  |  | 
|  | resolver_->GetHostCache()->OnNetworkChange(); | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | 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, | 
|  | // 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}}", | 
|  | // Data reduction proxy key. | 
|  | "", | 
|  | // Data reduction proxy. | 
|  | "", | 
|  | // Fallback data reduction proxy. | 
|  | "", | 
|  | // Data reduction proxy secure proxy check URL. | 
|  | "", | 
|  | // 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 |