blob: f98a462d31720dae8ba6e55c66285271b8bf9813 [file] [log] [blame]
// 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