Commit 3b9e4292 authored by Kartik Hegde's avatar Kartik Hegde Committed by Commit Bot

network_diagnostics: Add HttpFirewall Routine

Tests whether a firewall is blocking HTTP port 80.

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

Change-Id: I9e847e433417dd2f2cca551041d7bf4ab5ad032e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2303464
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@{#799403}
parent acc03fa7
......@@ -69,6 +69,7 @@ source_set("chromeos") {
"//ash/public/cpp",
"//ash/public/cpp/external_arc",
"//ash/public/mojom",
"//base/util/ranges:ranges",
"//base/util/timer",
"//build:branding_buildflags",
"//chrome/app:command_ids",
......@@ -1858,6 +1859,8 @@ source_set("chromeos") {
"net/network_diagnostics/gateway_can_be_pinged_routine.h",
"net/network_diagnostics/has_secure_wifi_connection_routine.cc",
"net/network_diagnostics/has_secure_wifi_connection_routine.h",
"net/network_diagnostics/http_firewall_routine.cc",
"net/network_diagnostics/http_firewall_routine.h",
"net/network_diagnostics/lan_connectivity_routine.cc",
"net/network_diagnostics/lan_connectivity_routine.h",
"net/network_diagnostics/network_diagnostics.cc",
......@@ -3310,6 +3313,7 @@ source_set("unit_tests") {
"net/network_diagnostics/dns_resolver_present_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/http_firewall_routine_unittest.cc",
"net/network_diagnostics/lan_connectivity_routine_unittest.cc",
"net/network_diagnostics/network_diagnostics_routine_unittest.cc",
"net/network_diagnostics/network_diagnostics_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/http_firewall_routine.h"
#include <iterator>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/no_destructor.h"
#include "base/optional.h"
#include "base/rand_util.h"
#include "base/sequence_checker.h"
#include "base/time/default_tick_clock.h"
#include "base/util/ranges/algorithm.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 "net/socket/client_socket_factory.h"
#include "net/socket/transport_client_socket.h"
#include "services/network/public/mojom/network_context.mojom.h"
namespace chromeos {
namespace network_diagnostics {
namespace {
constexpr int kHttpPort = 80;
// The threshold describing the number of DNS resolution failures permitted.
// E.g. If 10 host resolution attempts are made, any less than eight
// successfully resolved hosts would result in a problem.
constexpr double kDnsResolutionSuccessRateThreshold = 0.8;
constexpr int kTotalAdditionalHostsToQuery = 3;
// The length of a random eight letter prefix obtained by the characters from
// |kPossibleChars|.
constexpr int kHostPrefixLength = 8;
constexpr char kHostSuffix[] = "-ccd-testing-v4.metric.gstatic.com";
// The threshold describing number of socket connection failures permitted. E.g.
// If connections to 10 sockets are attempted, any more than two failures would
// result in a problem.
constexpr double kSocketConnectionFailureRateThreshold = 0.2;
// 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};
const std::string GetRandomString(int length) {
std::string prefix;
for (int i = 0; i < length; i++) {
prefix += ('a' + base::RandInt(0, 25));
}
return prefix;
}
// Returns a list of random prefixes to prepend to |kHostSuffix| to create a
// complete hostname. By including a random prefix, we ensure with a very high
// probability that the DNS queries are done on unique hosts.
std::vector<std::string> GetHostnamesToQuery() {
static const base::NoDestructor<std::vector<std::string>> fixed_hostnames(
{"www.google.com", "mail.google.com", "drive.google.com",
"accounts.google.com", "plus.google.com", "groups.google.com"});
std::vector<std::string> hostnames_to_query(fixed_hostnames->begin(),
fixed_hostnames->end());
for (int i = 0; i < kTotalAdditionalHostsToQuery; i++) {
hostnames_to_query.emplace_back(GetRandomString(kHostPrefixLength) +
kHostSuffix);
}
return hostnames_to_query;
}
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
class HttpFirewallRoutine::HostResolver
: public network::ResolveHostClientBase {
public:
explicit HostResolver(HttpFirewallRoutine* http_firewall_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() { return network_context_; }
Profile* profile() { 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
HttpFirewallRoutine* http_firewall_routine_; // Unowned
mojo::Receiver<network::mojom::ResolveHostClient> receiver_{this};
mojo::Remote<network::mojom::HostResolver> host_resolver_;
};
HttpFirewallRoutine::HostResolver::HostResolver(
HttpFirewallRoutine* http_firewall_routine)
: http_firewall_routine_(http_firewall_routine) {
DCHECK(http_firewall_routine);
profile_ = GetUserProfile();
network_context_ =
content::BrowserContext::GetDefaultStoragePartition(profile_)
->GetNetworkContext();
DCHECK(network_context_);
}
HttpFirewallRoutine::HostResolver::~HostResolver() = default;
void HttpFirewallRoutine::HostResolver::OnComplete(
int result,
const net::ResolveErrorInfo& resolve_error_info,
const base::Optional<net::AddressList>& resolved_addresses) {
receiver_.reset();
http_firewall_routine_->OnHostResolutionComplete(result, resolve_error_info,
resolved_addresses);
}
void HttpFirewallRoutine::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());
receiver_.set_disconnect_handler(base::BindOnce(
&HostResolver::OnMojoConnectionError, base::Unretained(this)));
}
void HttpFirewallRoutine::HostResolver::CreateHostResolver() {
host_resolver_.reset();
network_context()->CreateHostResolver(
net::DnsConfigOverrides(), host_resolver_.BindNewPipeAndPassReceiver());
}
void HttpFirewallRoutine::HostResolver::OnMojoConnectionError() {
CreateHostResolver();
OnComplete(net::ERR_NAME_NOT_RESOLVED, net::ResolveErrorInfo(net::ERR_FAILED),
base::nullopt);
}
HttpFirewallRoutine::HttpFirewallRoutine() {
DETACH_FROM_SEQUENCE(sequence_checker_);
host_resolver_ = std::make_unique<HostResolver>(this);
hostnames_to_query_ = GetHostnamesToQuery();
num_hostnames_to_query_ = hostnames_to_query_.size();
client_socket_factory_ = net::ClientSocketFactory::GetDefaultFactory();
set_verdict(mojom::RoutineVerdict::kNotRun);
}
HttpFirewallRoutine::~HttpFirewallRoutine() {
hostnames_to_query_.clear();
}
void HttpFirewallRoutine::RunRoutine(HttpFirewallRoutineCallback callback) {
if (!CanRun()) {
std::move(callback).Run(verdict(), std::move(problems_));
return;
}
routine_completed_callback_ = std::move(callback);
AttemptNextResolution();
}
void HttpFirewallRoutine::AnalyzeResultsAndExecuteCallback() {
double dns_resolution_success_rate =
static_cast<double>(resolved_addresses_.size()) /
static_cast<double>(num_hostnames_to_query_);
double socket_connections_failure_rate =
static_cast<double>(socket_connection_failures_) /
static_cast<double>(num_hostnames_to_query_);
if (dns_resolution_success_rate < kDnsResolutionSuccessRateThreshold) {
set_verdict(mojom::RoutineVerdict::kProblem);
problems_.emplace_back(
mojom::HttpFirewallProblem::kDnsResolutionFailuresAboveThreshold);
} else if (socket_connections_failure_rate <
kSocketConnectionFailureRateThreshold) {
set_verdict(mojom::RoutineVerdict::kNoProblem);
} else if (socket_connection_failures_ == num_hostnames_to_query_) {
set_verdict(mojom::RoutineVerdict::kProblem);
problems_.emplace_back(mojom::HttpFirewallProblem::kFirewallDetected);
} else {
// It cannot be conclusively determined whether a firewall exists; however,
// since reaching this case means socket_connection_failure_rate >
// kSocketConnectionFailureRateThreshold, a firewall could potentially
// exist.
set_verdict(mojom::RoutineVerdict::kProblem);
problems_.emplace_back(mojom::HttpFirewallProblem::kPotentialFirewall);
}
std::move(routine_completed_callback_).Run(verdict(), std::move(problems_));
}
void HttpFirewallRoutine::AttemptNextResolution() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::string hostname = hostnames_to_query_.back();
hostnames_to_query_.pop_back();
host_resolver_->Run(hostname);
}
void HttpFirewallRoutine::OnHostResolutionComplete(
int result,
const net::ResolveErrorInfo& resolve_error_info,
const base::Optional<net::AddressList>& resolved_addresses) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool success = result == net::OK && !resolved_addresses->empty() &&
resolved_addresses.has_value();
if (success) {
resolved_addresses_.emplace_back(resolved_addresses.value());
}
if (hostnames_to_query_.size() > 0) {
AttemptNextResolution();
} else {
AttemptSocketConnections();
}
}
void HttpFirewallRoutine::SetNetworkContextForTesting(
network::mojom::NetworkContext* network_context) {
host_resolver_->set_network_context_for_testing(network_context);
}
void HttpFirewallRoutine::SetProfileForTesting(Profile* profile) {
host_resolver_->set_profile_for_testing(profile);
}
void HttpFirewallRoutine::AttemptSocketConnections() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Verify whether the number of failed DNS resolutions is below the threshold.
double dns_resolution_success_rate =
static_cast<double>(resolved_addresses_.size()) /
static_cast<double>(num_hostnames_to_query_);
if (dns_resolution_success_rate < kDnsResolutionSuccessRateThreshold) {
AnalyzeResultsAndExecuteCallback();
}
// Create a socket for each address and port combination.
for (auto& resolved_address : resolved_addresses_) {
// Ensure the IP addresses stored for every address list (denoted by
// resolved_address) is using port 80.
resolved_address =
net::AddressList::CopyWithPort(resolved_address, kHttpPort);
sockets_.emplace_back(client_socket_factory_->CreateTransportClientSocket(
resolved_address, nullptr, net_log_.net_log(), net_log_.source()));
}
// Connect the sockets.
for (int i = 0; i < static_cast<int>(sockets_.size()); i++) {
Connect(i);
}
}
void HttpFirewallRoutine::Connect(int socket_index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int result = sockets_[socket_index]->Connect(
base::BindOnce(&HttpFirewallRoutine::OnSocketConnected,
base::Unretained(this), socket_index));
if (result != net::ERR_IO_PENDING) {
OnSocketConnected(socket_index, result);
}
}
void HttpFirewallRoutine::OnSocketConnected(int socket_index, int result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto* iter = util::ranges::find(kRetryResponseCodes, result);
if (iter != std::end(kRetryResponseCodes) && num_retries_ > 0) {
num_retries_--;
// Disconnect the socket in case there is any data in the incoming buffer.
sockets_[socket_index]->Disconnect();
Connect(socket_index);
return;
}
if (result < 0) {
socket_connection_failures_++;
}
num_tcp_connections_attempted_++;
if (num_tcp_connections_attempted_ == num_hostnames_to_query_) {
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_HTTP_FIREWALL_ROUTINE_H_
#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HTTP_FIREWALL_ROUTINE_H_
#include <memory>
#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 "net/base/address_list.h"
#include "net/log/net_log_with_source.h"
#include "services/network/public/cpp/resolve_host_client_base.h"
#include "services/network/public/mojom/host_resolver.mojom.h"
class Profile;
namespace net {
class ClientSocketFactory;
class TransportClientSocket;
} // namespace net
namespace network {
namespace mojom {
class NetworkContext;
}
} // namespace network
namespace chromeos {
namespace network_diagnostics {
// Tests whether a firewall is blocking HTTP port 80.
class HttpFirewallRoutine : public NetworkDiagnosticsRoutine {
public:
class HostResolver;
using HttpFirewallRoutineCallback =
mojom::NetworkDiagnosticsRoutines::HttpFirewallCallback;
HttpFirewallRoutine();
HttpFirewallRoutine(const HttpFirewallRoutine&) = delete;
HttpFirewallRoutine& operator=(const HttpFirewallRoutine&) = delete;
~HttpFirewallRoutine() 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(HttpFirewallRoutineCallback 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);
void set_client_socket_factory_for_testing(
net::ClientSocketFactory* client_socket_factory) {
client_socket_factory_ = client_socket_factory;
}
net::ClientSocketFactory* client_socket_factory() {
return client_socket_factory_;
}
private:
void AttemptNextResolution();
void AttemptSocketConnections();
void Connect(int socket_index);
void OnSocketConnected(int result, int socket_index);
// Unowned
net::ClientSocketFactory* client_socket_factory_ = nullptr;
int num_hostnames_to_query_;
std::vector<std::string> hostnames_to_query_;
static constexpr int kTotalNumRetries = 3;
int num_retries_ = kTotalNumRetries;
int socket_connection_failures_ = 0;
int num_tcp_connections_attempted_ = 0;
net::NetLogWithSource net_log_;
std::vector<std::unique_ptr<net::TransportClientSocket>> sockets_;
std::vector<net::AddressList> resolved_addresses_;
std::vector<mojom::HttpFirewallProblem> problems_;
std::unique_ptr<HostResolver> host_resolver_;
HttpFirewallRoutineCallback routine_completed_callback_;
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace network_diagnostics
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HTTP_FIREWALL_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/http_firewall_routine.h"
#include <deque>
#include <memory>
#include <utility>
#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 "net/socket/client_socket_factory.h"
#include "net/socket/socket_test_util.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 {
// The number of hosts the the routine tries to open socket connections to (if
// DNS resolution is successful). Based on GetHostnamesToQuery() in
// http_firewall_routine.cc.
const int kTotalHosts = 9;
// Represents a fake port number of a fake ip address returned by the
// FakeHostResolver.
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_;
};
class MockTCPSocket : public net::MockTCPClientSocket {
public:
explicit MockTCPSocket(net::SocketDataProvider* socket_data_provider)
: net::MockTCPClientSocket(net::AddressList(),
nullptr,
socket_data_provider) {}
MockTCPSocket(const MockTCPSocket&) = delete;
MockTCPSocket& operator=(const MockTCPSocket&) = delete;
int Connect(net::CompletionOnceCallback callback) override {
return net::MockTCPClientSocket::Connect(std::move(callback));
}
};
class FakeClientSocketFactory : public net::ClientSocketFactory {
public:
FakeClientSocketFactory(
std::deque<net::SocketDataProvider*> fake_socket_data_providers)
: socket_data_providers_(fake_socket_data_providers) {}
FakeClientSocketFactory(const FakeClientSocketFactory&) = delete;
FakeClientSocketFactory& operator=(const FakeClientSocketFactory&) = delete;
std::unique_ptr<net::DatagramClientSocket> CreateDatagramClientSocket(
net::DatagramSocket::BindType bind_type,
net::NetLog* net_log,
const net::NetLogSource& source) override {
NOTIMPLEMENTED();
return nullptr;
}
std::unique_ptr<net::TransportClientSocket> CreateTransportClientSocket(
const net::AddressList& addresses,
std::unique_ptr<net::SocketPerformanceWatcher> socket_performance_watcher,
net::NetLog* net_log,
const net::NetLogSource& source) override {
net::SocketDataProvider* socket_data_provider =
socket_data_providers_.front();
socket_data_providers_.pop_front();
return std::make_unique<MockTCPSocket>(socket_data_provider);
}
std::unique_ptr<net::SSLClientSocket> CreateSSLClientSocket(
net::SSLClientContext* context,
std::unique_ptr<net::StreamSocket> stream_socket,
const net::HostPortPair& host_and_port,
const net::SSLConfig& ssl_config) override {
NOTIMPLEMENTED();
return nullptr;
}
std::unique_ptr<net::ProxyClientSocket> CreateProxyClientSocket(
std::unique_ptr<net::StreamSocket> stream_socket,
const std::string& user_agent,
const net::HostPortPair& endpoint,
const net::ProxyServer& proxy_server,
net::HttpAuthController* http_auth_controller,
bool tunnel,
bool using_spdy,
net::NextProto negotiated_protocol,
net::ProxyDelegate* proxy_delegate,
const net::NetworkTrafficAnnotationTag& traffic_annotation) override {
NOTIMPLEMENTED();
return nullptr;
}
private:
std::deque<net::SocketDataProvider*> socket_data_providers_;
};
} // namespace
class HttpFirewallRoutineTest : public ::testing::Test {
public:
HttpFirewallRoutineTest()
: profile_manager_(TestingBrowserProcess::GetGlobal()) {
session_manager::SessionManager::Get()->SetSessionState(
session_manager::SessionState::LOGIN_PRIMARY);
}
HttpFirewallRoutineTest(const HttpFirewallRoutineTest&) = delete;
HttpFirewallRoutineTest& operator=(const HttpFirewallRoutineTest&) = delete;
void RunRoutine(
mojom::RoutineVerdict expected_routine_verdict,
const std::vector<mojom::HttpFirewallProblem>& expected_problems) {
http_firewall_routine_->RunRoutine(base::BindOnce(
&HttpFirewallRoutineTest::CompareVerdict, weak_factory_.GetWeakPtr(),
expected_routine_verdict, expected_problems));
run_loop_.Run();
}
void CompareVerdict(
mojom::RoutineVerdict expected_verdict,
const std::vector<mojom::HttpFirewallProblem>& expected_problems,
mojom::RoutineVerdict actual_verdict,
const std::vector<mojom::HttpFirewallProblem>& 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,
std::deque<net::SocketDataProvider*> fake_socket_data_providers) {
ASSERT_TRUE(profile_manager_.SetUp());
fake_network_context_ =
std::make_unique<FakeNetworkContext>(std::move(fake_dns_results));
test_profile_ = profile_manager_.CreateTestingProfile(kFakeTestProfile);
fake_client_socket_factory_ = std::make_unique<FakeClientSocketFactory>(
std::move(fake_socket_data_providers));
}
void SetUpHttpFirewallRoutine() {
http_firewall_routine_ = std::make_unique<HttpFirewallRoutine>();
http_firewall_routine_->SetNetworkContextForTesting(
fake_network_context_.get());
http_firewall_routine_->SetProfileForTesting(test_profile_);
http_firewall_routine_->set_client_socket_factory_for_testing(
fake_client_socket_factory_.get());
}
// Sets up required properties (via fakes) and runs the test.
//
// Parameters:
// |fake_dns_results|: Represents the results of a one or more DNS
// resolutions.
// |fake_socket_data_providers|: Represents the socket data provider for one
// or more TCP sockets.
// |expected_routine_verdict|: Represents the expected verdict
// reported by this test.
// |expected_problems|: Represents the expected problem
// reported by this test.
void SetUpAndRunRoutine(
std::deque<FakeHostResolver::DnsResult*> fake_dns_results,
std::deque<net::SocketDataProvider*> fake_socket_data_providers,
mojom::RoutineVerdict expected_routine_verdict,
const std::vector<mojom::HttpFirewallProblem>& expected_problems) {
SetUpFakeProperties(std::move(fake_dns_results),
std::move(fake_socket_data_providers));
SetUpHttpFirewallRoutine();
RunRoutine(expected_routine_verdict, expected_problems);
}
FakeClientSocketFactory* fake_client_socket_factory() {
return fake_client_socket_factory_.get();
}
private:
content::BrowserTaskEnvironment task_environment_;
base::RunLoop run_loop_;
session_manager::SessionManager session_manager_;
std::unique_ptr<FakeNetworkContext> fake_network_context_;
std::unique_ptr<FakeClientSocketFactory> fake_client_socket_factory_;
// Unowned
Profile* test_profile_;
TestingProfileManager profile_manager_;
std::unique_ptr<HttpFirewallRoutine> http_firewall_routine_;
base::WeakPtrFactory<HttpFirewallRoutineTest> weak_factory_{this};
};
TEST_F(HttpFirewallRoutineTest, TestDnsResolutionFailuresAboveThreshold) {
std::deque<FakeHostResolver::DnsResult*> fake_dns_results;
std::deque<net::SocketDataProvider*> fake_socket_data_providers;
std::vector<std::unique_ptr<FakeHostResolver::DnsResult>> resolutions;
std::vector<std::unique_ptr<net::SocketDataProvider>> providers;
// kTotalHosts = 9
for (int i = 0; i < kTotalHosts; i++) {
std::unique_ptr<FakeHostResolver::DnsResult> resolution;
if (i < 2) {
resolution = std::make_unique<FakeHostResolver::DnsResult>(
net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED),
net::AddressList());
} else {
// Having seven successful resolutions out of nine puts us below the
// threshold needed to attempt socket connections.
resolution = std::make_unique<FakeHostResolver::DnsResult>(
net::OK, net::ResolveErrorInfo(net::OK),
net::AddressList(FakeIPAddress()));
}
fake_dns_results.push_back(resolution.get());
resolutions.emplace_back(std::move(resolution));
auto socket_data_provider = std::make_unique<net::SequencedSocketData>();
socket_data_provider->set_connect_data(
net::MockConnect(net::IoMode::ASYNC, net::ERR_FAILED));
fake_socket_data_providers.push_back(socket_data_provider.get());
providers.emplace_back(std::move(socket_data_provider));
}
SetUpAndRunRoutine(
std::move(fake_dns_results), std::move(fake_socket_data_providers),
mojom::RoutineVerdict::kProblem,
{mojom::HttpFirewallProblem::kDnsResolutionFailuresAboveThreshold});
}
TEST_F(HttpFirewallRoutineTest, TestFirewallDetection) {
std::deque<FakeHostResolver::DnsResult*> fake_dns_results;
std::deque<net::SocketDataProvider*> fake_socket_data_providers;
std::vector<std::unique_ptr<FakeHostResolver::DnsResult>> resolutions;
std::vector<std::unique_ptr<net::SocketDataProvider>> providers;
// kTotalHosts = 9
for (int i = 0; i < kTotalHosts; i++) {
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());
resolutions.emplace_back(std::move(successful_resolution));
auto socket_data_provider = std::make_unique<net::SequencedSocketData>();
// Firewall detection requires all connect attempts to fail.
socket_data_provider->set_connect_data(
net::MockConnect(net::IoMode::ASYNC, net::ERR_FAILED));
fake_socket_data_providers.push_back(socket_data_provider.get());
providers.emplace_back(std::move(socket_data_provider));
}
SetUpAndRunRoutine(std::move(fake_dns_results),
std::move(fake_socket_data_providers),
mojom::RoutineVerdict::kProblem,
{mojom::HttpFirewallProblem::kFirewallDetected});
}
TEST_F(HttpFirewallRoutineTest, TestPotentialFirewallDetection) {
std::deque<FakeHostResolver::DnsResult*> fake_dns_results;
std::deque<net::SocketDataProvider*> fake_socket_data_providers;
std::vector<std::unique_ptr<FakeHostResolver::DnsResult>> resolutions;
std::vector<std::unique_ptr<net::SocketDataProvider>> providers;
// kTotalHosts = 9
for (int i = 0; i < kTotalHosts; i++) {
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());
resolutions.emplace_back(std::move(successful_resolution));
auto socket_data_provider = std::make_unique<net::SequencedSocketData>();
if (i < 5) {
socket_data_provider->set_connect_data(
net::MockConnect(net::IoMode::ASYNC, net::OK));
} else {
// Having five connection failures and four successful connections signals
// a potential firewall.
socket_data_provider->set_connect_data(
net::MockConnect(net::IoMode::ASYNC, net::ERR_FAILED));
}
fake_socket_data_providers.push_back(socket_data_provider.get());
providers.emplace_back(std::move(socket_data_provider));
}
SetUpAndRunRoutine(std::move(fake_dns_results),
std::move(fake_socket_data_providers),
mojom::RoutineVerdict::kProblem,
{mojom::HttpFirewallProblem::kPotentialFirewall});
}
TEST_F(HttpFirewallRoutineTest, TestNoFirewallIssues) {
std::deque<FakeHostResolver::DnsResult*> fake_dns_results;
std::deque<net::SocketDataProvider*> fake_socket_data_providers;
std::vector<std::unique_ptr<FakeHostResolver::DnsResult>> resolutions;
std::vector<std::unique_ptr<net::SocketDataProvider>> providers;
// kTotalHosts = 9
for (int i = 0; i < kTotalHosts; i++) {
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());
resolutions.emplace_back(std::move(successful_resolution));
auto socket_data_provider = std::make_unique<net::SequencedSocketData>();
if (i < 8) {
socket_data_provider->set_connect_data(
net::MockConnect(net::IoMode::ASYNC, net::OK));
} else {
// Having one connection failure and eight successful connections puts us
// above the required threshold.
socket_data_provider->set_connect_data(
net::MockConnect(net::IoMode::ASYNC, net::ERR_FAILED));
}
fake_socket_data_providers.push_back(socket_data_provider.get());
providers.emplace_back(std::move(socket_data_provider));
}
SetUpAndRunRoutine(std::move(fake_dns_results),
std::move(fake_socket_data_providers),
mojom::RoutineVerdict::kNoProblem, {});
}
TEST_F(HttpFirewallRoutineTest, TestContinousRetries) {
std::deque<FakeHostResolver::DnsResult*> fake_dns_results;
std::deque<net::SocketDataProvider*> fake_socket_data_providers;
std::vector<std::unique_ptr<FakeHostResolver::DnsResult>> resolutions;
std::vector<std::unique_ptr<net::SocketDataProvider>> providers;
// kTotalHosts = 9
for (int i = 0; i < kTotalHosts; i++) {
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());
resolutions.emplace_back(std::move(successful_resolution));
auto socket_data_provider = std::make_unique<net::SequencedSocketData>();
if (i < 8) {
socket_data_provider->set_connect_data(
net::MockConnect(net::IoMode::ASYNC, net::OK));
} else {
// Having one socket that continuously retries until failure and eight
// sockets that make successful connections puts us above the required
// threshold.
socket_data_provider->set_connect_data(
net::MockConnect(net::IoMode::ASYNC, net::ERR_TIMED_OUT));
}
fake_socket_data_providers.push_back(socket_data_provider.get());
providers.emplace_back(std::move(socket_data_provider));
}
SetUpAndRunRoutine(std::move(fake_dns_results),
std::move(fake_socket_data_providers),
mojom::RoutineVerdict::kNoProblem, {});
}
// TODO(khegde): Eventually add unit tests that includes more scenarios with
// retries. This is tough to mock out because each MockTCPClient is initialized
// with a SocketDataProvider*, which cannot be modified during the lifetime of
// the socket. SocketDataProvider* sets the MockConnection that fakes the result
// of the socket's connection attempt. As a result, the same socket cannot be
// used to fake a retry failure, followed by either a successful or unsuccessful
// connection attempt.
} // namespace network_diagnostics
} // namespace chromeos
......@@ -14,6 +14,7 @@
#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/has_secure_wifi_connection_routine.h"
#include "chrome/browser/chromeos/net/network_diagnostics/http_firewall_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 "chromeos/dbus/debug_daemon/debug_daemon_client.h"
......@@ -153,5 +154,19 @@ void NetworkDiagnostics::CaptivePortal(CaptivePortalCallback callback) {
std::move(routine), std::move(callback)));
}
void NetworkDiagnostics::HttpFirewall(HttpFirewallCallback callback) {
auto routine = std::make_unique<HttpFirewallRoutine>();
// 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<HttpFirewallRoutine> routine,
HttpFirewallCallback callback, mojom::RoutineVerdict verdict,
const std::vector<mojom::HttpFirewallProblem>& problems) {
std::move(callback).Run(verdict, std::move(problems));
},
std::move(routine), std::move(callback)));
}
} // namespace network_diagnostics
} // namespace chromeos
......@@ -30,6 +30,7 @@ class NetworkDiagnostics : public mojom::NetworkDiagnosticsRoutines {
void LanConnectivity(LanConnectivityCallback callback) override;
void SignalStrength(SignalStrengthCallback callback) override;
void GatewayCanBePinged(GatewayCanBePingedCallback callback) override;
void HttpFirewall(HttpFirewallCallback callback) override;
void HasSecureWiFiConnection(
HasSecureWiFiConnectionCallback callback) override;
void DnsResolverPresent(DnsResolverPresentCallback callback) override;
......
......@@ -205,6 +205,10 @@ class MockNetworkDiagnosticsRoutines : public NetworkDiagnosticsRoutines {
CaptivePortal,
(NetworkDiagnosticsRoutines::CaptivePortalCallback),
(override));
MOCK_METHOD(void,
HttpFirewall,
(NetworkDiagnosticsRoutines::HttpFirewallCallback),
(override));
mojo::PendingRemote<NetworkDiagnosticsRoutines> pending_remote() {
if (receiver_.is_bound()) {
......
......@@ -88,6 +88,17 @@ enum CaptivePortalProblem {
kCaptivePortalState,
};
// Problems related to the HttpFirewall routine.
[Extensible]
enum HttpFirewallProblem {
// DNS resolution failures above threshold.
kDnsResolutionFailuresAboveThreshold,
// Firewall detected.
kFirewallDetected,
// A firewall may potentially exist.
kPotentialFirewall,
};
// This interface is to be used by any clients that need to run specific
// network-related diagnostics. Expected clients of this interface are
// NetworkHealth, cros_healthd, and a connectivity diagnostics Web UI (to name
......@@ -127,4 +138,8 @@ interface NetworkDiagnosticsRoutines {
// Tests whether the internet connection is behind a captive portal.
CaptivePortal() => (RoutineVerdict verdict,
array<CaptivePortalProblem> problems);
// Tests whether a firewall is blocking HTTP port 80.
HttpFirewall() => (RoutineVerdict verdict,
array<HttpFirewallProblem> 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