Commit 7cab9650 authored by Kartik Hegde's avatar Kartik Hegde Committed by Commit Bot

network_diagnostics: Add DnsResolutionRoutine

Add a routine to test whether a DNS resolution can be completed
successfully.

BUG=chromium:956783
TEST=unit_tests --gtest_filter=DnsResolutionRoutineTest.*

Change-Id: I70f15771569e03068e10e8213c55f7656e4a49f0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2159897
Commit-Queue: Kartik Hegde <khegde@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#776162}
parent 1f641f5e
...@@ -1754,6 +1754,8 @@ source_set("chromeos") { ...@@ -1754,6 +1754,8 @@ source_set("chromeos") {
"net/dhcp_wpad_url_client.h", "net/dhcp_wpad_url_client.h",
"net/network_diagnostics/dns_latency_routine.cc", "net/network_diagnostics/dns_latency_routine.cc",
"net/network_diagnostics/dns_latency_routine.h", "net/network_diagnostics/dns_latency_routine.h",
"net/network_diagnostics/dns_resolution_routine.cc",
"net/network_diagnostics/dns_resolution_routine.h",
"net/network_diagnostics/dns_resolver_present_routine.cc", "net/network_diagnostics/dns_resolver_present_routine.cc",
"net/network_diagnostics/dns_resolver_present_routine.h", "net/network_diagnostics/dns_resolver_present_routine.h",
"net/network_diagnostics/gateway_can_be_pinged_routine.cc", "net/network_diagnostics/gateway_can_be_pinged_routine.cc",
...@@ -3100,6 +3102,7 @@ source_set("unit_tests") { ...@@ -3100,6 +3102,7 @@ source_set("unit_tests") {
"mobile/mobile_activator_unittest.cc", "mobile/mobile_activator_unittest.cc",
"net/client_cert_store_chromeos_unittest.cc", "net/client_cert_store_chromeos_unittest.cc",
"net/network_diagnostics/dns_latency_routine_unittest.cc", "net/network_diagnostics/dns_latency_routine_unittest.cc",
"net/network_diagnostics/dns_resolution_routine_unittest.cc",
"net/network_diagnostics/dns_resolver_present_routine_unittest.cc", "net/network_diagnostics/dns_resolver_present_routine_unittest.cc",
"net/network_diagnostics/gateway_can_be_pinged_routine_unittest.cc", "net/network_diagnostics/gateway_can_be_pinged_routine_unittest.cc",
"net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc", "net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc",
......
// 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 "chrome/browser/chromeos/net/network_diagnostics/dns_resolution_routine.h"
#include <iterator>
#include <utility>
#include "base/bind.h"
#include "base/optional.h"
#include "base/values.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "components/session_manager/core/session_manager.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
#include "net/base/network_isolation_key.h"
#include "services/network/public/mojom/network_context.mojom.h"
namespace chromeos {
namespace network_diagnostics {
namespace {
constexpr char kHostname[] = "ccd-testing-v4.gstatic.com";
constexpr int kHttpPort = 80;
// For an explanation of error codes, see "net/base/net_error_list.h".
constexpr int kRetryResponseCodes[] = {net::ERR_TIMED_OUT,
net::ERR_DNS_TIMED_OUT};
Profile* GetUserProfile() {
// Use sign-in profile if user has not logged in
if (session_manager::SessionManager::Get()->IsUserSessionBlocked()) {
return ProfileHelper::GetSigninProfile();
}
// Use primary profile if user is logged in
return ProfileManager::GetPrimaryUserProfile();
}
} // namespace
DnsResolutionRoutine::DnsResolutionRoutine() {
profile_ = GetUserProfile();
DCHECK(profile_);
network_context_ =
content::BrowserContext::GetDefaultStoragePartition(profile_)
->GetNetworkContext();
DCHECK(network_context_);
set_verdict(mojom::RoutineVerdict::kNotRun);
}
DnsResolutionRoutine::~DnsResolutionRoutine() = default;
void DnsResolutionRoutine::RunTest(DnsResolutionRoutineCallback callback) {
if (!CanRun()) {
std::move(callback).Run(verdict(), std::move(problems_));
return;
}
routine_completed_callback_ = std::move(callback);
CreateHostResolver();
AttemptResolution();
}
void DnsResolutionRoutine::AnalyzeResultsAndExecuteCallback() {
if (!resolved_address_received_) {
set_verdict(mojom::RoutineVerdict::kProblem);
problems_.emplace_back(mojom::DnsResolutionProblem::kFailedToResolveHost);
} else {
set_verdict(mojom::RoutineVerdict::kNoProblem);
}
std::move(routine_completed_callback_).Run(verdict(), std::move(problems_));
}
void DnsResolutionRoutine::CreateHostResolver() {
host_resolver_.reset();
network_context()->CreateHostResolver(
net::DnsConfigOverrides(), host_resolver_.BindNewPipeAndPassReceiver());
}
void DnsResolutionRoutine::OnMojoConnectionError() {
CreateHostResolver();
OnComplete(net::ERR_NAME_NOT_RESOLVED, net::ResolveErrorInfo(net::ERR_FAILED),
base::nullopt);
}
void DnsResolutionRoutine::AttemptResolution() {
DCHECK(host_resolver_);
DCHECK(!receiver_.is_bound());
network::mojom::ResolveHostParametersPtr parameters =
network::mojom::ResolveHostParameters::New();
parameters->dns_query_type = net::DnsQueryType::A;
parameters->source = net::HostResolverSource::DNS;
parameters->cache_usage =
network::mojom::ResolveHostParameters::CacheUsage::DISALLOWED;
host_resolver_->ResolveHost(net::HostPortPair(kHostname, kHttpPort),
net::NetworkIsolationKey::CreateTransient(),
std::move(parameters),
receiver_.BindNewPipeAndPassRemote());
// The host resolver is part of the network service, which may be run inside
// the browser process (in-process) or a dedicated utility process
// (out-of-process). If the network service crashes, the disconnect handler
// below will be invoked. See README in services/network for more information.
receiver_.set_disconnect_handler(base::BindOnce(
&DnsResolutionRoutine::OnMojoConnectionError, base::Unretained(this)));
}
void DnsResolutionRoutine::OnComplete(
int result,
const net::ResolveErrorInfo& resolve_error_info,
const base::Optional<net::AddressList>& resolved_addresses) {
receiver_.reset();
bool success = result == net::OK && !resolved_addresses->empty() &&
resolved_addresses.has_value();
if (success) {
resolved_address_received_ = true;
AnalyzeResultsAndExecuteCallback();
return;
}
bool retry =
std::find(std::begin(kRetryResponseCodes), std::end(kRetryResponseCodes),
result) != std::end(kRetryResponseCodes) &&
num_retries_ > 0;
if (retry) {
num_retries_--;
AttemptResolution();
} else {
AnalyzeResultsAndExecuteCallback();
}
}
} // namespace network_diagnostics
} // namespace chromeos
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_DNS_RESOLUTION_ROUTINE_H_
#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_DNS_RESOLUTION_ROUTINE_H_
#include <vector>
#include "base/callback.h"
#include "base/optional.h"
#include "chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_routine.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/cpp/resolve_host_client_base.h"
#include "services/network/public/mojom/host_resolver.mojom.h"
class Profile;
namespace network {
namespace mojom {
class NetworkContext;
}
} // namespace network
namespace chromeos {
namespace network_diagnostics {
// Tests whether a DNS resolution can be completed successfully.
class DnsResolutionRoutine : public NetworkDiagnosticsRoutine,
public network::ResolveHostClientBase {
public:
using DnsResolutionRoutineCallback =
mojom::NetworkDiagnosticsRoutines::DnsResolutionCallback;
DnsResolutionRoutine();
~DnsResolutionRoutine() override;
// NetworkDiagnosticsRoutine:
void AnalyzeResultsAndExecuteCallback() override;
// network::mojom::ResolveHostClient:
void OnComplete(
int result,
const net::ResolveErrorInfo& resolve_error_info,
const base::Optional<net::AddressList>& resolved_addresses) override;
// Run the core logic of this routine. Set |callback| to
// |routine_completed_callback_|, which is to be executed in
// AnalyzeResultsAndExecuteCallback().
void RunTest(DnsResolutionRoutineCallback callback);
void set_network_context_for_testing(
network::mojom::NetworkContext* network_context) {
network_context_ = network_context;
}
void set_profile_for_testing(Profile* profile) { profile_ = profile; }
network::mojom::NetworkContext* network_context() { return network_context_; }
Profile* profile() { return profile_; }
private:
void CreateHostResolver();
void OnMojoConnectionError();
void AttemptResolution();
// Unowned
Profile* profile_ = nullptr;
// Unowned
network::mojom::NetworkContext* network_context_ = nullptr;
static constexpr int kTotalNumRetries = 1;
int num_retries_ = kTotalNumRetries;
bool resolved_address_received_ = false;
net::AddressList resolved_addresses_;
std::vector<mojom::DnsResolutionProblem> problems_;
mojo::Receiver<network::mojom::ResolveHostClient> receiver_{this};
mojo::Remote<network::mojom::HostResolver> host_resolver_;
DnsResolutionRoutineCallback routine_completed_callback_;
DISALLOW_COPY_AND_ASSIGN(DnsResolutionRoutine);
};
} // namespace network_diagnostics
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_DNS_RESOLUTION_ROUTINE_H_
// 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 "chrome/browser/chromeos/net/network_diagnostics/dns_resolution_routine.h"
#include <deque>
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/session_manager/core/session_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "services/network/test/test_network_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
namespace chromeos {
namespace network_diagnostics {
namespace {
const int kFakePortNumber = 1234;
const char kFakeTestProfile[] = "test";
net::IPEndPoint FakeIPAddress() {
return net::IPEndPoint(net::IPAddress::IPv4Localhost(), kFakePortNumber);
}
class FakeHostResolver : public network::mojom::HostResolver {
public:
struct DnsResult {
DnsResult(int32_t result,
net::ResolveErrorInfo resolve_error_info,
base::Optional<net::AddressList> resolved_addresses)
: result(result),
resolve_error_info(resolve_error_info),
resolved_addresses(resolved_addresses) {}
int result;
net::ResolveErrorInfo resolve_error_info;
base::Optional<net::AddressList> resolved_addresses;
};
FakeHostResolver(mojo::PendingReceiver<network::mojom::HostResolver> receiver,
std::deque<DnsResult*> fake_dns_results)
: receiver_(this, std::move(receiver)),
fake_dns_results_(std::move(fake_dns_results)) {}
~FakeHostResolver() override {}
// network::mojom::HostResolver
void ResolveHost(const net::HostPortPair& host,
const net::NetworkIsolationKey& network_isolation_key,
network::mojom::ResolveHostParametersPtr optional_parameters,
mojo::PendingRemote<network::mojom::ResolveHostClient>
pending_response_client) override {
mojo::Remote<network::mojom::ResolveHostClient> response_client(
std::move(pending_response_client));
DnsResult* result = fake_dns_results_.front();
DCHECK(result);
fake_dns_results_.pop_front();
response_client->OnComplete(result->result, result->resolve_error_info,
result->resolved_addresses);
}
void MdnsListen(
const net::HostPortPair& host,
net::DnsQueryType query_type,
mojo::PendingRemote<network::mojom::MdnsListenClient> response_client,
MdnsListenCallback callback) override {
NOTREACHED();
}
private:
mojo::Receiver<network::mojom::HostResolver> receiver_;
// Use the list of fake dns results to fake different responses for multiple
// calls to the host_resolver's ResolveHost().
std::deque<DnsResult*> fake_dns_results_;
};
class FakeNetworkContext : public network::TestNetworkContext {
public:
FakeNetworkContext() = default;
explicit FakeNetworkContext(
std::deque<FakeHostResolver::DnsResult*> fake_dns_results)
: fake_dns_results_(std::move(fake_dns_results)) {}
~FakeNetworkContext() override {}
// network::TestNetworkContext:
void CreateHostResolver(
const base::Optional<net::DnsConfigOverrides>& config_overrides,
mojo::PendingReceiver<network::mojom::HostResolver> receiver) override {
ASSERT_FALSE(resolver_);
resolver_ = std::make_unique<FakeHostResolver>(
std::move(receiver), std::move(fake_dns_results_));
}
private:
std::unique_ptr<FakeHostResolver> resolver_;
std::deque<FakeHostResolver::DnsResult*> fake_dns_results_;
};
} // namespace
class DnsResolutionRoutineTest : public ::testing::Test {
public:
DnsResolutionRoutineTest()
: profile_manager_(TestingBrowserProcess::GetGlobal()) {
session_manager::SessionManager::Get()->SetSessionState(
session_manager::SessionState::LOGIN_PRIMARY);
}
void RunTest(
mojom::RoutineVerdict expected_routine_verdict,
const std::vector<mojom::DnsResolutionProblem>& expected_problems) {
dns_resolution_routine_->RunTest(base::BindOnce(
&DnsResolutionRoutineTest::CompareVerdict, weak_factory_.GetWeakPtr(),
expected_routine_verdict, expected_problems));
run_loop_.Run();
}
void CompareVerdict(
mojom::RoutineVerdict expected_verdict,
const std::vector<mojom::DnsResolutionProblem>& expected_problems,
mojom::RoutineVerdict actual_verdict,
const std::vector<mojom::DnsResolutionProblem>& actual_problems) {
DCHECK(run_loop_.running());
EXPECT_EQ(expected_verdict, actual_verdict);
EXPECT_EQ(expected_problems, actual_problems);
run_loop_.Quit();
}
void SetUpFakeProperties(
std::deque<FakeHostResolver::DnsResult*> fake_dns_results) {
ASSERT_TRUE(profile_manager_.SetUp());
fake_network_context_ =
std::make_unique<FakeNetworkContext>(std::move(fake_dns_results));
test_profile_ = profile_manager_.CreateTestingProfile(kFakeTestProfile);
}
void SetUpDnsResolutionRoutine() {
dns_resolution_routine_ = std::make_unique<DnsResolutionRoutine>();
dns_resolution_routine_->set_network_context_for_testing(
fake_network_context_.get());
dns_resolution_routine_->set_profile_for_testing(test_profile_);
}
// Sets up required properties (via fakes) and runs the test.
//
// Parameters:
// |fake_dns_results|: Represents the results of a one or more DNS
// resolutions. |expected_routine_verdict|: Represents the expected verdict
// reported by this test. |expected_problems|: Represents the expected problem
// reported by this test.
void SetUpAndRunTest(
std::deque<FakeHostResolver::DnsResult*> fake_dns_results,
mojom::RoutineVerdict expected_routine_verdict,
const std::vector<mojom::DnsResolutionProblem>& expected_problems) {
SetUpFakeProperties(std::move(fake_dns_results));
SetUpDnsResolutionRoutine();
RunTest(expected_routine_verdict, expected_problems);
}
private:
content::BrowserTaskEnvironment task_environment_;
base::RunLoop run_loop_;
session_manager::SessionManager session_manager_;
std::unique_ptr<FakeNetworkContext> fake_network_context_;
// Unowned
Profile* test_profile_;
TestingProfileManager profile_manager_;
std::unique_ptr<DnsResolutionRoutine> dns_resolution_routine_;
base::WeakPtrFactory<DnsResolutionRoutineTest> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(DnsResolutionRoutineTest);
};
// A passing routine requires an error code of net::OK and a non-empty
// net::AddressList for the DNS resolution.
TEST_F(DnsResolutionRoutineTest, TestSuccessfulResolution) {
std::deque<FakeHostResolver::DnsResult*> fake_dns_results;
auto successful_resolution = std::make_unique<FakeHostResolver::DnsResult>(
net::OK, net::ResolveErrorInfo(net::OK),
net::AddressList(FakeIPAddress()));
fake_dns_results.push_back(successful_resolution.get());
SetUpAndRunTest(std::move(fake_dns_results),
mojom::RoutineVerdict::kNoProblem, {});
}
// Set up the |fake_dns_results| to return a DnsResult with an error code
// net::ERR_NAME_NOT_RESOLVED faking a failed DNS resolution.
TEST_F(DnsResolutionRoutineTest, TestResolutionFailure) {
std::deque<FakeHostResolver::DnsResult*> fake_dns_results;
auto failed_resolution = std::make_unique<FakeHostResolver::DnsResult>(
net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED), net::AddressList());
fake_dns_results.push_back(failed_resolution.get());
SetUpAndRunTest(std::move(fake_dns_results), mojom::RoutineVerdict::kProblem,
{mojom::DnsResolutionProblem::kFailedToResolveHost});
}
// Set up the |fake_dns_results| to first return a DnsResult with an error code
// net::ERR_DNS_TIMED_OUT faking a timed out DNS resolution. On the second
// host resolution attempt, fake a net::OK resolution.
TEST_F(DnsResolutionRoutineTest, TestSuccessOnRetry) {
std::deque<FakeHostResolver::DnsResult*> fake_dns_results;
auto timed_out_resolution = std::make_unique<FakeHostResolver::DnsResult>(
net::ERR_DNS_TIMED_OUT, net::ResolveErrorInfo(net::ERR_DNS_TIMED_OUT),
net::AddressList());
fake_dns_results.push_back(timed_out_resolution.get());
auto successful_resolution = std::make_unique<FakeHostResolver::DnsResult>(
net::OK, net::ResolveErrorInfo(net::OK),
net::AddressList(FakeIPAddress()));
fake_dns_results.push_back(successful_resolution.get());
fake_dns_results.push_back(successful_resolution.get());
SetUpAndRunTest(std::move(fake_dns_results),
mojom::RoutineVerdict::kNoProblem, {});
}
} // namespace network_diagnostics
} // namespace chromeos
...@@ -66,6 +66,13 @@ enum DnsLatencyProblem { ...@@ -66,6 +66,13 @@ enum DnsLatencyProblem {
kSignificantlyAboveThreshold, kSignificantlyAboveThreshold,
}; };
// Messages related to the DnsResolution routine.
[Extensible]
enum DnsResolutionProblem {
// The routine was unable to successfully resolve the test host
kFailedToResolveHost,
};
// This interface is to be used by any clients that need to run specific // This interface is to be used by any clients that need to run specific
// network-related diagnostics. Expected clients of this interface are // network-related diagnostics. Expected clients of this interface are
// NetworkHealth, cros_healthd, and a connectivity diagnostics Web UI (to name // NetworkHealth, cros_healthd, and a connectivity diagnostics Web UI (to name
...@@ -97,4 +104,8 @@ interface NetworkDiagnosticsRoutines { ...@@ -97,4 +104,8 @@ interface NetworkDiagnosticsRoutines {
// Tests whether the DNS latency is below an acceptable threshold. // Tests whether the DNS latency is below an acceptable threshold.
DnsLatency() => (RoutineVerdict verdict, DnsLatency() => (RoutineVerdict verdict,
array<DnsLatencyProblem> problem); array<DnsLatencyProblem> problem);
// Tests whether a DNS resolution can be completed successfully.
DnsResolution() => (RoutineVerdict verdict,
array<DnsResolutionProblem> problems);
}; };
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <memory> #include <memory>
#include "chrome/browser/chromeos/net/network_diagnostics/dns_latency_routine.h" #include "chrome/browser/chromeos/net/network_diagnostics/dns_latency_routine.h"
#include "chrome/browser/chromeos/net/network_diagnostics/dns_resolution_routine.h"
#include "chrome/browser/chromeos/net/network_diagnostics/dns_resolver_present_routine.h" #include "chrome/browser/chromeos/net/network_diagnostics/dns_resolver_present_routine.h"
#include "chrome/browser/chromeos/net/network_diagnostics/gateway_can_be_pinged_routine.h" #include "chrome/browser/chromeos/net/network_diagnostics/gateway_can_be_pinged_routine.h"
#include "chrome/browser/chromeos/net/network_diagnostics/has_secure_wifi_connection_routine.h" #include "chrome/browser/chromeos/net/network_diagnostics/has_secure_wifi_connection_routine.h"
...@@ -64,5 +65,10 @@ void NetworkDiagnosticsImpl::DnsLatency(DnsLatencyCallback callback) { ...@@ -64,5 +65,10 @@ void NetworkDiagnosticsImpl::DnsLatency(DnsLatencyCallback callback) {
dns_latency_routine.RunTest(std::move(callback)); dns_latency_routine.RunTest(std::move(callback));
} }
void NetworkDiagnosticsImpl::DnsResolution(DnsResolutionCallback callback) {
DnsResolutionRoutine dns_resolution_routine;
dns_resolution_routine.RunTest(std::move(callback));
}
} // namespace network_diagnostics } // namespace network_diagnostics
} // namespace chromeos } // namespace chromeos
...@@ -32,6 +32,7 @@ class NetworkDiagnosticsImpl : public mojom::NetworkDiagnosticsRoutines { ...@@ -32,6 +32,7 @@ class NetworkDiagnosticsImpl : public mojom::NetworkDiagnosticsRoutines {
HasSecureWiFiConnectionCallback callback) override; HasSecureWiFiConnectionCallback callback) override;
void DnsResolverPresent(DnsResolverPresentCallback callback) override; void DnsResolverPresent(DnsResolverPresentCallback callback) override;
void DnsLatency(DnsLatencyCallback callback) override; void DnsLatency(DnsLatencyCallback callback) override;
void DnsResolution(DnsResolutionCallback callback) override;
private: private:
mojo::ReceiverSet<mojom::NetworkDiagnosticsRoutines> receivers_; mojo::ReceiverSet<mojom::NetworkDiagnosticsRoutines> receivers_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment