Commit 607b2ea5 authored by Kartik Hegde's avatar Kartik Hegde Committed by Commit Bot

network_diagnostics: Add HttpsLatencyRoutine

Tests whether the HTTPS latency is within established tolerance levels
for the system.

BUG=chromium:956783
TEST=unit_tests --gtest_filter=HttpsLatencyRoutineTest*

Change-Id: I7a28b103321f9c62dc21c660a60cc4c7487534a3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2378664
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@{#814409}
parent 2bf83434
...@@ -1907,6 +1907,8 @@ source_set("chromeos") { ...@@ -1907,6 +1907,8 @@ source_set("chromeos") {
"net/network_diagnostics/http_firewall_routine.h", "net/network_diagnostics/http_firewall_routine.h",
"net/network_diagnostics/http_request_manager.cc", "net/network_diagnostics/http_request_manager.cc",
"net/network_diagnostics/http_request_manager.h", "net/network_diagnostics/http_request_manager.h",
"net/network_diagnostics/https_latency_routine.cc",
"net/network_diagnostics/https_latency_routine.h",
"net/network_diagnostics/lan_connectivity_routine.cc", "net/network_diagnostics/lan_connectivity_routine.cc",
"net/network_diagnostics/lan_connectivity_routine.h", "net/network_diagnostics/lan_connectivity_routine.h",
"net/network_diagnostics/network_diagnostics.cc", "net/network_diagnostics/network_diagnostics.cc",
...@@ -3445,6 +3447,7 @@ source_set("unit_tests") { ...@@ -3445,6 +3447,7 @@ source_set("unit_tests") {
"net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc", "net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc",
"net/network_diagnostics/http_firewall_routine_unittest.cc", "net/network_diagnostics/http_firewall_routine_unittest.cc",
"net/network_diagnostics/http_request_manager_unittest.cc", "net/network_diagnostics/http_request_manager_unittest.cc",
"net/network_diagnostics/https_latency_routine_unittest.cc",
"net/network_diagnostics/lan_connectivity_routine_unittest.cc", "net/network_diagnostics/lan_connectivity_routine_unittest.cc",
"net/network_diagnostics/network_diagnostics_routine_unittest.cc", "net/network_diagnostics/network_diagnostics_routine_unittest.cc",
"net/network_diagnostics/network_diagnostics_unittest.cc", "net/network_diagnostics/network_diagnostics_unittest.cc",
......
...@@ -146,6 +146,20 @@ Problems: ...@@ -146,6 +146,20 @@ Problems:
* `kFirewallDetected`: Firewall detected. * `kFirewallDetected`: Firewall detected.
* `kPotentialFirewall`: A firewall may potentially exist. * `kPotentialFirewall`: A firewall may potentially exist.
### Google Services Routines
Tests successful communication with various Google domains.
#### HttpsLatency
Tests whether the HTTPS latency is below an acceptable threshold.
Problems:
* `kFailedDnsResolutions`: One or more DNS resolutions resulted in a failure.
* `kFailedHttpRequests`: One or more HTTPS requests resulted in a failure.
* `kSlightlyAboveThreshold`: Average HTTPS request latency is slightly above the expected threshold.
* `kSignificantlyAboveThreshold`: Average HTTPS request latency is significantly above the expected threshold.
[Network Health and Configuration]: https://docs.google.com/document/d/10DSy-jZXaRo9I9aq1UqERy76t7HkgGvInWk57pHEkzg [Network Health and Configuration]: https://docs.google.com/document/d/10DSy-jZXaRo9I9aq1UqERy76t7HkgGvInWk57pHEkzg
[network_diagnostics.mojom]: https://source.chromium.org/chromium/chromium/src/+/master:chromeos/services/network_health/public/mojom/network_diagnostics.mojom?originalUrl=https:%2F%2Fcs.chromium.org%2F [network_diagnostics.mojom]: https://source.chromium.org/chromium/chromium/src/+/master:chromeos/services/network_health/public/mojom/network_diagnostics.mojom?originalUrl=https:%2F%2Fcs.chromium.org%2F
[NetworkHealthService]: https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/chromeos/net/network_health/network_health_service.h?originalUrl=https:%2F%2Fcs.chromium.org%2F [NetworkHealthService]: https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/chromeos/net/network_health/network_health_service.h?originalUrl=https:%2F%2Fcs.chromium.org%2F
......
// 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/https_latency_routine.h"
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/time/default_tick_clock.h"
#include "chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/storage_partition.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
#include "services/network/public/mojom/network_context.mojom.h"
namespace chromeos {
namespace network_diagnostics {
namespace {
constexpr int kTotalHostsToQuery = 3;
// The length of a random eight letter prefix.
constexpr int kHostPrefixLength = 8;
constexpr int kHttpPort = 80;
constexpr char kHttpsScheme[] = "https://";
constexpr base::TimeDelta ksRequestTimeoutMs =
base::TimeDelta::FromMilliseconds(5 * 1000);
// Requests taking longer than 1000 ms are problematic.
constexpr base::TimeDelta kProblemLatencyMs =
base::TimeDelta::FromMilliseconds(1000);
// Requests lasting between 500 ms and 1000 ms are potentially problematic.
constexpr base::TimeDelta kPotentialProblemLatencyMs =
base::TimeDelta::FromMilliseconds(500);
base::TimeDelta MedianLatency(std::vector<base::TimeDelta>& latencies) {
if (latencies.size() == 0) {
return base::TimeDelta::Max();
}
std::sort(latencies.begin(), latencies.end());
if (latencies.size() % 2 != 0) {
return latencies[latencies.size() / 2];
}
auto sum =
latencies[latencies.size() / 2] + latencies[(latencies.size() + 1) / 2];
return sum / 2.0;
}
} // namespace
class HttpsLatencyRoutine::HostResolver
: public network::ResolveHostClientBase {
public:
explicit HostResolver(HttpsLatencyRoutine* https_latency_routine);
HostResolver(const HostResolver&) = delete;
HostResolver& operator=(const HostResolver&) = delete;
~HostResolver() override;
// network::mojom::ResolveHostClient:
void OnComplete(
int result,
const net::ResolveErrorInfo& resolve_error_info,
const base::Optional<net::AddressList>& resolved_addresses) override;
// Performs the DNS resolution.
void Run(const std::string& hostname);
network::mojom::NetworkContext* network_context() const {
return network_context_;
}
Profile* profile() const { return profile_; }
void set_network_context_for_testing(
network::mojom::NetworkContext* network_context) {
network_context_ = network_context;
}
void set_profile_for_testing(Profile* profile) { profile_ = profile; }
private:
void CreateHostResolver();
void OnMojoConnectionError();
Profile* profile_ = nullptr; // Unowned
network::mojom::NetworkContext* network_context_ = nullptr; // Unowned
HttpsLatencyRoutine* https_latency_routine_ = nullptr; // Unowned
mojo::Receiver<network::mojom::ResolveHostClient> receiver_{this};
mojo::Remote<network::mojom::HostResolver> host_resolver_;
};
HttpsLatencyRoutine::HostResolver::HostResolver(
HttpsLatencyRoutine* https_latency_routine)
: profile_(util::GetUserProfile()),
network_context_(
content::BrowserContext::GetDefaultStoragePartition(profile_)
->GetNetworkContext()),
https_latency_routine_(https_latency_routine) {
DCHECK(https_latency_routine_);
DCHECK(network_context_);
}
HttpsLatencyRoutine::HostResolver::~HostResolver() = default;
void HttpsLatencyRoutine::HostResolver::OnComplete(
int result,
const net::ResolveErrorInfo& resolve_error_info,
const base::Optional<net::AddressList>& resolved_addresses) {
receiver_.reset();
https_latency_routine_->OnHostResolutionComplete(result, resolve_error_info,
resolved_addresses);
}
void HttpsLatencyRoutine::HostResolver::Run(const std::string& hostname) {
if (!host_resolver_) {
CreateHostResolver();
}
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(hostname, kHttpPort),
net::NetworkIsolationKey::CreateTransient(),
std::move(parameters),
receiver_.BindNewPipeAndPassRemote());
}
void HttpsLatencyRoutine::HostResolver::CreateHostResolver() {
host_resolver_.reset();
network_context()->CreateHostResolver(
net::DnsConfigOverrides(), host_resolver_.BindNewPipeAndPassReceiver());
// Disconnect handler will be invoked if the network service crashes.
host_resolver_.set_disconnect_handler(base::BindOnce(
&HostResolver::OnMojoConnectionError, base::Unretained(this)));
}
void HttpsLatencyRoutine::HostResolver::OnMojoConnectionError() {
CreateHostResolver();
OnComplete(net::ERR_NAME_NOT_RESOLVED, net::ResolveErrorInfo(net::ERR_FAILED),
base::nullopt);
}
HttpsLatencyRoutine::HttpsLatencyRoutine()
: tick_clock_(base::DefaultTickClock::GetInstance()),
hostnames_to_query_dns_(
util::GetRandomHostsWithSchemeAndGenerate204Path(kTotalHostsToQuery,
kHostPrefixLength,
kHttpsScheme)),
hostnames_to_query_https_(hostnames_to_query_dns_),
host_resolver_(std::make_unique<HostResolver>(this)),
http_request_manager_(
std::make_unique<HttpRequestManager>(host_resolver_->profile())) {
DCHECK(http_request_manager_);
}
HttpsLatencyRoutine::~HttpsLatencyRoutine() = default;
void HttpsLatencyRoutine::RunRoutine(HttpsLatencyRoutineCallback callback) {
if (!CanRun()) {
std::move(callback).Run(verdict(), problems_);
return;
}
routine_completed_callback_ = std::move(callback);
// Before making HTTPS requests to the hosts, add the IP addresses are added
// to the DNS cache. This ensures the HTTPS latency does not include DNS
// resolution time, allowing us to identify issues with HTTPS more precisely.
AttemptNextResolution();
}
void HttpsLatencyRoutine::AnalyzeResultsAndExecuteCallback() {
base::TimeDelta median_latency = MedianLatency(latencies_);
if (!successfully_resolved_hosts_) {
set_verdict(mojom::RoutineVerdict::kProblem);
problems_.emplace_back(mojom::HttpsLatencyProblem::kFailedDnsResolutions);
} else if (failed_connection_) {
set_verdict(mojom::RoutineVerdict::kProblem);
problems_.emplace_back(mojom::HttpsLatencyProblem::kFailedHttpsRequests);
} else if (median_latency <= kProblemLatencyMs &&
median_latency > kPotentialProblemLatencyMs) {
set_verdict(mojom::RoutineVerdict::kProblem);
problems_.emplace_back(mojom::HttpsLatencyProblem::kHighLatency);
} else if (median_latency > kProblemLatencyMs) {
set_verdict(mojom::RoutineVerdict::kProblem);
problems_.emplace_back(mojom::HttpsLatencyProblem::kVeryHighLatency);
} else {
set_verdict(mojom::RoutineVerdict::kNoProblem);
}
std::move(routine_completed_callback_).Run(verdict(), problems_);
}
void HttpsLatencyRoutine::AttemptNextResolution() {
std::string hostname = hostnames_to_query_dns_.back();
hostnames_to_query_dns_.pop_back();
host_resolver_->Run(hostname);
}
void HttpsLatencyRoutine::OnHostResolutionComplete(
int result,
const net::ResolveErrorInfo& resolve_error_info,
const base::Optional<net::AddressList>& resolved_addresses) {
bool success = result == net::OK && !resolved_addresses->empty() &&
resolved_addresses.has_value();
if (!success) {
successfully_resolved_hosts_ = false;
AnalyzeResultsAndExecuteCallback();
return;
}
if (hostnames_to_query_dns_.size() > 0) {
AttemptNextResolution();
return;
}
MakeHttpsRequest();
}
void HttpsLatencyRoutine::SetNetworkContextForTesting(
network::mojom::NetworkContext* network_context) {
host_resolver_->set_network_context_for_testing(network_context);
}
void HttpsLatencyRoutine::SetProfileForTesting(Profile* profile) {
host_resolver_->set_profile_for_testing(profile);
}
void HttpsLatencyRoutine::MakeHttpsRequest() {
std::string hostname = hostnames_to_query_https_.back();
hostnames_to_query_https_.pop_back();
request_start_time_ = tick_clock_->NowTicks();
http_request_manager_->MakeRequest(
GURL(hostname), ksRequestTimeoutMs,
base::BindOnce(&HttpsLatencyRoutine::OnHttpsRequestComplete, weak_ptr()));
}
void HttpsLatencyRoutine::OnHttpsRequestComplete(bool connected) {
request_end_time_ = tick_clock_->NowTicks();
if (!connected) {
failed_connection_ = true;
AnalyzeResultsAndExecuteCallback();
return;
}
const base::TimeDelta latency = request_end_time_ - request_start_time_;
latencies_.emplace_back(latency);
if (hostnames_to_query_https_.size() > 0) {
MakeHttpsRequest();
return;
}
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_HTTPS_LATENCY_ROUTINE_H_
#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HTTPS_LATENCY_ROUTINE_H_
#include <memory>
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/net/network_diagnostics/http_request_manager.h"
#include "chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_routine.h"
#include "net/base/address_list.h"
#include "net/dns/public/resolve_error_info.h"
#include "services/network/public/cpp/resolve_host_client_base.h"
#include "services/network/public/mojom/host_resolver.mojom.h"
class HttpRequestManager;
class Profile;
namespace base {
class TickClock;
} // namespace base
namespace network {
namespace mojom {
class NetworkContext;
}
} // namespace network
namespace chromeos {
namespace network_diagnostics {
// Tests whether the HTTPS latency is within established tolerance levels for
// the system.
class HttpsLatencyRoutine : public NetworkDiagnosticsRoutine {
public:
class HostResolver;
using HttpsLatencyRoutineCallback =
mojom::NetworkDiagnosticsRoutines::HttpsLatencyCallback;
HttpsLatencyRoutine();
HttpsLatencyRoutine(const HttpsLatencyRoutine&) = delete;
HttpsLatencyRoutine& operator=(const HttpsLatencyRoutine&) = delete;
~HttpsLatencyRoutine() override;
// NetworkDiagnosticsRoutine:
void AnalyzeResultsAndExecuteCallback() override;
// Run the core logic of this routine. Set |callback| to
// |routine_completed_callback_|, which is to be executed in
// AnalyzeResultsAndExecuteCallback().
void RunRoutine(HttpsLatencyRoutineCallback callback);
// Processes the results of the DNS resolution done by |host_resolver_|.
void OnHostResolutionComplete(
int result,
const net::ResolveErrorInfo& resolve_error_info,
const base::Optional<net::AddressList>& resolved_addresses);
void SetNetworkContextForTesting(
network::mojom::NetworkContext* network_context);
void SetProfileForTesting(Profile* profile);
// HttpRequestManager setter for tests.
void set_http_request_manager_for_testing(
std::unique_ptr<HttpRequestManager> http_request_manager) {
http_request_manager_ = std::move(http_request_manager);
}
// Mimics actual time conditions.
void set_tick_clock_for_testing(const base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
}
private:
// Attempts the next DNS resolution.
void AttemptNextResolution();
// Makes a https request to the host.
void MakeHttpsRequest();
// Processes the results of an https request.
void OnHttpsRequestComplete(bool connected);
// Returns the weak pointer to |this|.
base::WeakPtr<HttpsLatencyRoutine> weak_ptr() {
return weak_factory_.GetWeakPtr();
}
bool successfully_resolved_hosts_ = true;
bool failed_connection_ = false;
const base::TickClock* tick_clock_ = nullptr; // Unowned
base::TimeTicks request_start_time_;
base::TimeTicks request_end_time_;
std::vector<std::string> hostnames_to_query_dns_;
std::vector<std::string> hostnames_to_query_https_;
std::vector<base::TimeDelta> latencies_;
std::unique_ptr<HostResolver> host_resolver_;
std::unique_ptr<HttpRequestManager> http_request_manager_;
std::vector<mojom::HttpsLatencyProblem> problems_;
HttpsLatencyRoutineCallback routine_completed_callback_;
base::WeakPtrFactory<HttpsLatencyRoutine> weak_factory_{this};
};
} // namespace network_diagnostics
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HTTPS_LATENCY_ROUTINE_H_
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#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"
#include "chrome/browser/chromeos/net/network_diagnostics/http_firewall_routine.h" #include "chrome/browser/chromeos/net/network_diagnostics/http_firewall_routine.h"
#include "chrome/browser/chromeos/net/network_diagnostics/https_latency_routine.h"
#include "chrome/browser/chromeos/net/network_diagnostics/lan_connectivity_routine.h" #include "chrome/browser/chromeos/net/network_diagnostics/lan_connectivity_routine.h"
#include "chrome/browser/chromeos/net/network_diagnostics/signal_strength_routine.h" #include "chrome/browser/chromeos/net/network_diagnostics/signal_strength_routine.h"
#include "chromeos/dbus/debug_daemon/debug_daemon_client.h" #include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
...@@ -168,5 +169,19 @@ void NetworkDiagnostics::HttpFirewall(HttpFirewallCallback callback) { ...@@ -168,5 +169,19 @@ void NetworkDiagnostics::HttpFirewall(HttpFirewallCallback callback) {
std::move(routine), std::move(callback))); std::move(routine), std::move(callback)));
} }
void NetworkDiagnostics::HttpsLatency(HttpsLatencyCallback callback) {
auto routine = std::make_unique<HttpsLatencyRoutine>();
// RunRoutine() takes a lambda callback that takes ownership of the routine.
// This ensures that the routine stays alive when it makes asynchronous mojo
// calls. The routine will be destroyed when the lambda exits.
routine->RunRoutine(base::BindOnce(
[](std::unique_ptr<HttpsLatencyRoutine> routine,
HttpsLatencyCallback callback, mojom::RoutineVerdict verdict,
const std::vector<mojom::HttpsLatencyProblem>& problems) {
std::move(callback).Run(verdict, problems);
},
std::move(routine), std::move(callback)));
}
} // namespace network_diagnostics } // namespace network_diagnostics
} // namespace chromeos } // namespace chromeos
...@@ -37,6 +37,7 @@ class NetworkDiagnostics : public mojom::NetworkDiagnosticsRoutines { ...@@ -37,6 +37,7 @@ class NetworkDiagnostics : public mojom::NetworkDiagnosticsRoutines {
void DnsLatency(DnsLatencyCallback callback) override; void DnsLatency(DnsLatencyCallback callback) override;
void DnsResolution(DnsResolutionCallback callback) override; void DnsResolution(DnsResolutionCallback callback) override;
void CaptivePortal(CaptivePortalCallback callback) override; void CaptivePortal(CaptivePortalCallback callback) override;
void HttpsLatency(HttpsLatencyCallback callback) override;
private: private:
// An unowned pointer to the DebugDaemonClient instance. // An unowned pointer to the DebugDaemonClient instance.
......
...@@ -209,6 +209,10 @@ class MockNetworkDiagnosticsRoutines : public NetworkDiagnosticsRoutines { ...@@ -209,6 +209,10 @@ class MockNetworkDiagnosticsRoutines : public NetworkDiagnosticsRoutines {
HttpFirewall, HttpFirewall,
(NetworkDiagnosticsRoutines::HttpFirewallCallback), (NetworkDiagnosticsRoutines::HttpFirewallCallback),
(override)); (override));
MOCK_METHOD(void,
HttpsLatency,
(NetworkDiagnosticsRoutines::HttpsLatencyCallback),
(override));
mojo::PendingRemote<NetworkDiagnosticsRoutines> pending_remote() { mojo::PendingRemote<NetworkDiagnosticsRoutines> pending_remote() {
if (receiver_.is_bound()) { if (receiver_.is_bound()) {
......
...@@ -99,6 +99,19 @@ enum HttpFirewallProblem { ...@@ -99,6 +99,19 @@ enum HttpFirewallProblem {
kPotentialFirewall, kPotentialFirewall,
}; };
// Problems related to the HttpsLatency routine.
[Extensible]
enum HttpsLatencyProblem {
// One or more DNS resolutions resulted in a failure.
kFailedDnsResolutions,
// One or more HTTPS requests resulted in a failure.
kFailedHttpsRequests,
// HTTPS request latency is high.
kHighLatency,
// HTTPS request latency is very high.
kVeryHighLatency,
};
// 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
...@@ -142,4 +155,9 @@ interface NetworkDiagnosticsRoutines { ...@@ -142,4 +155,9 @@ interface NetworkDiagnosticsRoutines {
// Tests whether a firewall is blocking HTTP port 80. // Tests whether a firewall is blocking HTTP port 80.
HttpFirewall() => (RoutineVerdict verdict, HttpFirewall() => (RoutineVerdict verdict,
array<HttpFirewallProblem> problems); array<HttpFirewallProblem> problems);
// Tests whether the HTTPS latency is within established tolerance levels for
// the system.
HttpsLatency() => (RoutineVerdict verdict,
array<HttpsLatencyProblem> problems);
}; };
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