| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <list> |
| |
| #include "base/functional/callback.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "base/threading/platform_thread.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/network_service_util.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/shell/browser/shell.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.h" |
| #include "net/base/address_list.h" |
| #include "net/base/ip_address.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/network_interfaces.h" |
| #include "net/base/request_priority.h" |
| #include "net/dns/host_resolver_system_task.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/dns/public/host_resolver_results.h" |
| #include "net/dns/public/resolve_error_info.h" |
| #include "net/dns/public/secure_dns_policy.h" |
| #include "sandbox/policy/features.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/simple_host_resolver.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/perf/perf_result_reporter.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| const int kHttpPort = 80; |
| |
| const char kHostname1[] = "hostname1"; |
| const char kIpAddress1[] = "127.0.0.2"; |
| const char kHostname2[] = "hostname2"; |
| const char kIpAddress2[] = "127.0.0.3"; |
| const char kFailHostname[] = "failhostname"; |
| |
| using ResolveHostFuture = |
| base::test::TestFuture<int, |
| const net::ResolveErrorInfo&, |
| const net::AddressList&, |
| const net::HostResolverEndpointResults&>; |
| |
| } // namespace |
| |
| class SystemDnsResolverBrowserTest : public content::ContentBrowserTest { |
| public: |
| SystemDnsResolverBrowserTest() = default; |
| |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| host_resolver()->AddRule(kHostname1, kIpAddress1); |
| host_resolver()->AddRule(kHostname2, kIpAddress2); |
| host_resolver()->AddSimulatedFailure(kFailHostname, 0); |
| host_resolver()->AddRule(net::GetHostName(), "127.0.0.1"); |
| |
| resolver_ = |
| network::SimpleHostResolver::Create(shell() |
| ->web_contents() |
| ->GetBrowserContext() |
| ->GetDefaultStoragePartition() |
| ->GetNetworkContext()); |
| } |
| |
| void TearDownOnMainThread() override { |
| // Has to be torn down before its network context (which is a raw_ptr). |
| resolver_.reset(); |
| |
| ContentBrowserTest::TearDownOnMainThread(); |
| } |
| |
| void ResolveHostname( |
| std::string hostname, |
| network::SimpleHostResolver::ResolveHostCallback callback) { |
| network::mojom::ResolveHostParametersPtr parameters = |
| network::mojom::ResolveHostParameters::New(); |
| parameters->initial_priority = net::RequestPriority::HIGHEST; |
| // Use the SYSTEM resolver, and don't allow the cache or attempt DoH. |
| parameters->source = net::HostResolverSource::SYSTEM; |
| parameters->cache_usage = |
| network::mojom::ResolveHostParameters::CacheUsage::DISALLOWED; |
| parameters->secure_dns_policy = network::mojom::SecureDnsPolicy::DISABLE; |
| |
| mojo::PendingReceiver<network::mojom::ResolveHostClient> receiver; |
| resolver_->ResolveHost(network::mojom::HostResolverHost::NewHostPortPair( |
| net::HostPortPair(hostname, kHttpPort)), |
| net::NetworkAnonymizationKey::CreateTransient(), |
| std::move(parameters), std::move(callback)); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| std::unique_ptr<network::SimpleHostResolver> resolver_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SystemDnsResolverBrowserTest, |
| NetworkServiceResolvesOneHostname) { |
| ResolveHostFuture future; |
| ResolveHostname(kHostname1, future.GetCallback()); |
| |
| const auto& addr_list1 = future.Get<net::AddressList>(); |
| |
| if (GetContentClientForTesting() |
| ->browser() |
| ->ShouldRunOutOfProcessSystemDnsResolution()) { |
| // If system DNS resolution runs in the browser process, check here that the |
| // resolver received the correct number of resolves. |
| EXPECT_EQ(host_resolver()->NumResolvesForHostPattern(kHostname1), 1u); |
| } |
| |
| ASSERT_FALSE(addr_list1.empty()); |
| net::IPAddress address1; |
| EXPECT_TRUE(address1.AssignFromIPLiteral(kIpAddress1)); |
| EXPECT_EQ(addr_list1.front(), |
| net::IPEndPoint(net::IPAddress(address1), kHttpPort)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SystemDnsResolverBrowserTest, |
| NetworkServiceResolvesTwoHostnames) { |
| ResolveHostFuture future1; |
| ResolveHostname(kHostname1, future1.GetCallback()); |
| ResolveHostFuture future2; |
| ResolveHostname(kHostname2, future2.GetCallback()); |
| |
| const auto& addr_list1 = future1.Get<net::AddressList>(); |
| const auto& addr_list2 = future2.Get<net::AddressList>(); |
| |
| if (GetContentClientForTesting() |
| ->browser() |
| ->ShouldRunOutOfProcessSystemDnsResolution()) { |
| // If system DNS resolution runs in the browser process, check here that the |
| // resolver received the correct number of resolves. |
| EXPECT_EQ(host_resolver()->NumResolvesForHostPattern(kHostname1), 1u); |
| EXPECT_EQ(host_resolver()->NumResolvesForHostPattern(kHostname2), 1u); |
| } |
| |
| ASSERT_FALSE(addr_list1.empty()); |
| net::IPAddress address1; |
| EXPECT_TRUE(address1.AssignFromIPLiteral(kIpAddress1)); |
| EXPECT_EQ(addr_list1[0], |
| net::IPEndPoint(net::IPAddress(address1), kHttpPort)); |
| |
| ASSERT_FALSE(addr_list2.empty()); |
| net::IPAddress address2; |
| EXPECT_TRUE(address2.AssignFromIPLiteral(kIpAddress2)); |
| EXPECT_EQ(addr_list2[0], |
| net::IPEndPoint(net::IPAddress(address2), kHttpPort)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SystemDnsResolverBrowserTest, |
| NetworkServiceFailsResolvingBadHostname) { |
| ResolveHostFuture future; |
| ResolveHostname(kFailHostname, future.GetCallback()); |
| |
| auto [result, resolve_error_info, resolved_addresses, endpoints] = |
| future.Take(); |
| |
| if (GetContentClientForTesting() |
| ->browser() |
| ->ShouldRunOutOfProcessSystemDnsResolution()) { |
| // If system DNS resolution runs in the browser process, check here that |
| // the resolver received the correct number of resolves. |
| EXPECT_EQ(host_resolver()->NumResolvesForHostPattern(kFailHostname), 1u); |
| } |
| |
| EXPECT_EQ(resolve_error_info.error, net::ERR_NAME_NOT_RESOLVED); |
| EXPECT_EQ(result, net::ERR_NAME_NOT_RESOLVED); |
| } |
| |
| // Check if the system's own host name resolves, which is a slightly different |
| // code path from normal resolution. |
| IN_PROC_BROWSER_TEST_F(SystemDnsResolverBrowserTest, |
| NetworkServiceResolvesOwnHostname) { |
| base::test::TestFuture<const net::AddressList&, /*os_error_result=*/int, |
| /*net_error_result=*/int> |
| future; |
| |
| // Systems with an in-process network service (e.g. some Android) will not |
| // have a network_service_test(). |
| std::unique_ptr<net::HostResolverSystemTask> system_task; |
| if (IsInProcessNetworkService()) { |
| system_task = net::HostResolverSystemTask::CreateForOwnHostname( |
| net::AddressFamily::ADDRESS_FAMILY_UNSPECIFIED, 0); |
| system_task->Start(future.GetCallback()); |
| } else { |
| network_service_test()->ResolveOwnHostnameWithSystemDns( |
| future.GetCallback()); |
| } |
| |
| auto [addr_list, os_error_result, net_error_result] = future.Take(); |
| |
| if (GetContentClientForTesting() |
| ->browser() |
| ->ShouldRunOutOfProcessSystemDnsResolution()) { |
| // If system DNS resolution runs in the browser process, check here that the |
| // resolver received the correct number of resolves. |
| EXPECT_EQ(host_resolver()->NumResolvesForHostPattern(net::GetHostName()), |
| 1u); |
| } |
| |
| ASSERT_EQ(addr_list.size(), 1u); |
| net::IPAddress address; |
| EXPECT_TRUE(address.AssignFromIPLiteral("127.0.0.1")); |
| EXPECT_EQ(addr_list[0].address(), address); |
| } |
| |
| class SystemDnsResolverPerfTest : public content::ContentBrowserTest { |
| public: |
| SystemDnsResolverPerfTest() { |
| SetAllowNetworkAccessToHostResolutions(); |
| } |
| |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| resolver_ = |
| network::SimpleHostResolver::Create(shell() |
| ->web_contents() |
| ->GetBrowserContext() |
| ->GetDefaultStoragePartition() |
| ->GetNetworkContext()); |
| } |
| |
| void TearDownOnMainThread() override { |
| // Has to be torn down before its network context (which is a raw_ptr). |
| resolver_.reset(); |
| |
| ContentBrowserTest::TearDownOnMainThread(); |
| } |
| |
| void ResolveAHost(network::SimpleHostResolver::ResolveHostCallback callback) { |
| network::mojom::ResolveHostParametersPtr parameters = |
| network::mojom::ResolveHostParameters::New(); |
| parameters->initial_priority = net::RequestPriority::HIGHEST; |
| // Use the SYSTEM resolver, and don't allow the cache or attempt DoH. |
| parameters->source = net::HostResolverSource::SYSTEM; |
| parameters->cache_usage = |
| network::mojom::ResolveHostParameters::CacheUsage::DISALLOWED; |
| parameters->secure_dns_policy = network::mojom::SecureDnsPolicy::DISABLE; |
| |
| mojo::PendingReceiver<network::mojom::ResolveHostClient> receiver; |
| resolver_->ResolveHost(network::mojom::HostResolverHost::NewHostPortPair( |
| net::HostPortPair("google.com", kHttpPort)), |
| net::NetworkAnonymizationKey::CreateTransient(), |
| std::move(parameters), std::move(callback)); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| std::unique_ptr<network::SimpleHostResolver> resolver_; |
| }; |
| |
| namespace { |
| constexpr char kMetricPrefixSystemDnsResolver[] = "SystemDnsResolver."; |
| constexpr char kMetricTimePerSystemDnsResolution[] = "time_per_resolution"; |
| |
| const size_t kNumResolutions = 5000; |
| std::atomic<bool> g_finished = false; |
| |
| void PostAnotherTask() { |
| base::PlatformThread::Sleep(base::Seconds(1)); |
| if (!g_finished) { |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&PostAnotherTask)); |
| } |
| } |
| |
| perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) { |
| perf_test::PerfResultReporter reporter(kMetricPrefixSystemDnsResolver, |
| story_name); |
| reporter.RegisterImportantMetric(kMetricTimePerSystemDnsResolution, "ms"); |
| return reporter; |
| } |
| } // namespace |
| |
| // This benchmark just sends a bunch SYSTEM dns requests to the network service. |
| // If out-of-process system DNS resolution is enabled, it will send those |
| // requests back to the browser. So this benchmark adds some mild UI thread |
| // contention. |
| // TODO(crbug.com/1312224, crbug.com/1320192): this can probably be removed when |
| // out-of-process system DNS resolution fully launches. |
| IN_PROC_BROWSER_TEST_F(SystemDnsResolverPerfTest, MANUAL_ResolveManyHostnames) { |
| std::vector<ResolveHostFuture> futures(kNumResolutions); |
| std::vector<net::AddressList> results(kNumResolutions); |
| |
| // Simulate UI thread busyness: |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&PostAnotherTask)); |
| base::TimeTicks start = base::TimeTicks::Now(); |
| for (ResolveHostFuture& future : futures) { |
| ResolveAHost(future.GetCallback()); |
| } |
| for (size_t i = 0; i < kNumResolutions; i++) { |
| results[i] = futures[i].Get<net::AddressList>(); |
| } |
| base::TimeDelta duration = base::TimeTicks::Now() - start; |
| |
| auto reporter = SetUpReporter("SystemDnsResolution"); |
| reporter.AddResult( |
| kMetricTimePerSystemDnsResolution, |
| duration.InMilliseconds() / static_cast<double>(kNumResolutions)); |
| |
| // Verify there are results. |
| for (const net::AddressList& result : results) { |
| ASSERT_GT(result.size(), 0u); |
| } |
| |
| g_finished = true; |
| } |
| |
| } // namespace content |