blob: 014f34de048464c483d32a7e7e3e9ee42660c050 [file] [log] [blame]
// Copyright 2014 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 "components/domain_reliability/monitor.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/profiler/scoped_tracker.h"
#include "base/single_thread_task_runner.h"
#include "base/task_runner.h"
#include "base/time/time.h"
#include "components/domain_reliability/baked_in_configs.h"
#include "net/base/ip_endpoint.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
namespace domain_reliability {
namespace {
int URLRequestStatusToNetError(const net::URLRequestStatus& status) {
switch (status.status()) {
case net::URLRequestStatus::SUCCESS:
return net::OK;
case net::URLRequestStatus::IO_PENDING:
return net::ERR_IO_PENDING;
case net::URLRequestStatus::CANCELED:
return net::ERR_ABORTED;
case net::URLRequestStatus::FAILED:
return status.error();
default:
NOTREACHED();
return net::ERR_UNEXPECTED;
}
}
// Updates the status, chrome_error, and server_ip fields of |beacon| from
// the endpoint and result of |attempt|. If there is no matching status for
// the result, returns false (which means the attempt should not result in a
// beacon being reported).
bool UpdateBeaconFromAttempt(DomainReliabilityBeacon* beacon,
const net::ConnectionAttempt& attempt) {
if (!GetDomainReliabilityBeaconStatus(
attempt.result, beacon->http_response_code, &beacon->status)) {
return false;
}
beacon->chrome_error = attempt.result;
if (!attempt.endpoint.address().empty())
beacon->server_ip = attempt.endpoint.ToString();
else
beacon->server_ip = "";
return true;
}
// TODO(ttuttle): This function is absurd. See if |socket_address| in
// HttpResponseInfo can become an IPEndPoint.
bool ConvertHostPortPairToIPEndPoint(const net::HostPortPair& host_port_pair,
net::IPEndPoint* ip_endpoint_out) {
net::IPAddressNumber ip_address_number;
if (!net::ParseIPLiteralToNumber(host_port_pair.host(), &ip_address_number))
return false;
*ip_endpoint_out = net::IPEndPoint(ip_address_number, host_port_pair.port());
return true;
}
} // namespace
DomainReliabilityMonitor::DomainReliabilityMonitor(
const std::string& upload_reporter_string,
const scoped_refptr<base::SingleThreadTaskRunner>& pref_thread,
const scoped_refptr<base::SingleThreadTaskRunner>& network_thread)
: time_(new ActualTime()),
upload_reporter_string_(upload_reporter_string),
scheduler_params_(
DomainReliabilityScheduler::Params::GetFromFieldTrialsOrDefaults()),
dispatcher_(time_.get()),
context_manager_(this),
pref_task_runner_(pref_thread),
network_task_runner_(network_thread),
moved_to_network_thread_(false),
discard_uploads_set_(false),
weak_factory_(this) {
DCHECK(OnPrefThread());
net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
}
DomainReliabilityMonitor::DomainReliabilityMonitor(
const std::string& upload_reporter_string,
const scoped_refptr<base::SingleThreadTaskRunner>& pref_thread,
const scoped_refptr<base::SingleThreadTaskRunner>& network_thread,
scoped_ptr<MockableTime> time)
: time_(time.Pass()),
upload_reporter_string_(upload_reporter_string),
scheduler_params_(
DomainReliabilityScheduler::Params::GetFromFieldTrialsOrDefaults()),
dispatcher_(time_.get()),
context_manager_(this),
pref_task_runner_(pref_thread),
network_task_runner_(network_thread),
moved_to_network_thread_(false),
discard_uploads_set_(false),
weak_factory_(this) {
DCHECK(OnPrefThread());
net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
}
DomainReliabilityMonitor::~DomainReliabilityMonitor() {
if (moved_to_network_thread_)
DCHECK(OnNetworkThread());
else
DCHECK(OnPrefThread());
net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
}
void DomainReliabilityMonitor::MoveToNetworkThread() {
DCHECK(OnPrefThread());
DCHECK(!moved_to_network_thread_);
moved_to_network_thread_ = true;
}
void DomainReliabilityMonitor::InitURLRequestContext(
net::URLRequestContext* url_request_context) {
DCHECK(OnNetworkThread());
DCHECK(moved_to_network_thread_);
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter =
new net::TrivialURLRequestContextGetter(url_request_context,
network_task_runner_);
InitURLRequestContext(url_request_context_getter);
}
void DomainReliabilityMonitor::InitURLRequestContext(
const scoped_refptr<net::URLRequestContextGetter>&
url_request_context_getter) {
DCHECK(OnNetworkThread());
DCHECK(moved_to_network_thread_);
// Make sure the URLRequestContext actually lives on what was declared to be
// the network thread.
DCHECK(url_request_context_getter->GetNetworkTaskRunner()->
RunsTasksOnCurrentThread());
uploader_ = DomainReliabilityUploader::Create(time_.get(),
url_request_context_getter);
}
void DomainReliabilityMonitor::AddBakedInConfigs() {
// TODO(ttuttle): Remove ScopedTracker below once crbug.com/436671 is fixed.
tracked_objects::ScopedTracker tracking_profile(
FROM_HERE_WITH_EXPLICIT_FUNCTION(
"436671 DomainReliabilityMonitor::AddBakedInConfigs"));
DCHECK(OnNetworkThread());
DCHECK(moved_to_network_thread_);
base::Time now = base::Time::Now();
for (size_t i = 0; kBakedInJsonConfigs[i]; ++i) {
base::StringPiece json(kBakedInJsonConfigs[i]);
scoped_ptr<const DomainReliabilityConfig> config =
DomainReliabilityConfig::FromJSON(json);
if (!config) {
continue;
} else if (config->IsExpired(now)) {
LOG(WARNING) << "Baked-in Domain Reliability config for "
<< config->domain << " is expired.";
continue;
}
context_manager_.AddContextForConfig(config.Pass());
}
}
void DomainReliabilityMonitor::SetDiscardUploads(bool discard_uploads) {
DCHECK(OnNetworkThread());
DCHECK(moved_to_network_thread_);
DCHECK(uploader_);
uploader_->set_discard_uploads(discard_uploads);
discard_uploads_set_ = true;
}
void DomainReliabilityMonitor::OnBeforeRedirect(net::URLRequest* request) {
DCHECK(OnNetworkThread());
DCHECK(discard_uploads_set_);
// Record the redirect itself in addition to the final request.
OnRequestLegComplete(RequestInfo(*request));
}
void DomainReliabilityMonitor::OnCompleted(net::URLRequest* request,
bool started) {
DCHECK(OnNetworkThread());
DCHECK(discard_uploads_set_);
if (!started)
return;
RequestInfo request_info(*request);
OnRequestLegComplete(request_info);
if (request_info.response_info.network_accessed) {
// A request was just using the network, so now is a good time to run any
// pending and eligible uploads.
dispatcher_.RunEligibleTasks();
}
}
void DomainReliabilityMonitor::OnNetworkChanged(
net::NetworkChangeNotifier::ConnectionType type) {
last_network_change_time_ = time_->NowTicks();
}
void DomainReliabilityMonitor::ClearBrowsingData(
DomainReliabilityClearMode mode) {
DCHECK(OnNetworkThread());
switch (mode) {
case CLEAR_BEACONS:
context_manager_.ClearBeaconsInAllContexts();
break;
case CLEAR_CONTEXTS:
context_manager_.RemoveAllContexts();
break;
case MAX_CLEAR_MODE:
NOTREACHED();
}
}
scoped_ptr<base::Value> DomainReliabilityMonitor::GetWebUIData() const {
DCHECK(OnNetworkThread());
scoped_ptr<base::DictionaryValue> data_value(new base::DictionaryValue());
data_value->Set("contexts", context_manager_.GetWebUIData());
return data_value.Pass();
}
DomainReliabilityContext* DomainReliabilityMonitor::AddContextForTesting(
scoped_ptr<const DomainReliabilityConfig> config) {
DCHECK(OnNetworkThread());
return context_manager_.AddContextForConfig(config.Pass());
}
scoped_ptr<DomainReliabilityContext>
DomainReliabilityMonitor::CreateContextForConfig(
scoped_ptr<const DomainReliabilityConfig> config) {
DCHECK(OnNetworkThread());
DCHECK(config);
DCHECK(config->IsValid());
return make_scoped_ptr(new DomainReliabilityContext(
time_.get(),
scheduler_params_,
upload_reporter_string_,
&last_network_change_time_,
&dispatcher_,
uploader_.get(),
config.Pass()));
}
DomainReliabilityMonitor::RequestInfo::RequestInfo() {}
DomainReliabilityMonitor::RequestInfo::RequestInfo(
const net::URLRequest& request)
: url(request.url()),
status(request.status()),
response_info(request.response_info()),
load_flags(request.load_flags()),
is_upload(DomainReliabilityUploader::URLRequestIsUpload(request)) {
request.GetLoadTimingInfo(&load_timing_info);
request.GetConnectionAttempts(&connection_attempts);
}
DomainReliabilityMonitor::RequestInfo::~RequestInfo() {}
// static
bool DomainReliabilityMonitor::RequestInfo::ShouldReportRequest(
const DomainReliabilityMonitor::RequestInfo& request) {
// Don't report requests for Domain Reliability uploads or that weren't
// supposed to send cookies.
if (request.is_upload)
return false;
if (request.load_flags & net::LOAD_DO_NOT_SEND_COOKIES)
return false;
// Report requests that accessed the network or failed with an error code
// that Domain Reliability is interested in.
if (request.response_info.network_accessed)
return true;
if (URLRequestStatusToNetError(request.status) != net::OK)
return true;
return false;
}
void DomainReliabilityMonitor::OnRequestLegComplete(
const RequestInfo& request) {
// Check these again because unit tests call this directly.
DCHECK(OnNetworkThread());
DCHECK(discard_uploads_set_);
if (!RequestInfo::ShouldReportRequest(request))
return;
int response_code;
if (request.response_info.headers.get())
response_code = request.response_info.headers->response_code();
else
response_code = -1;
net::IPEndPoint url_request_endpoint;
// If response was cached, socket address will be from the serialized
// response info in the cache, so don't report it.
// TODO(ttuttle): Plumb out the "current" socket address so we can always
// report it.
if (!request.response_info.was_cached &&
!request.response_info.was_fetched_via_proxy) {
ConvertHostPortPairToIPEndPoint(request.response_info.socket_address,
&url_request_endpoint);
}
net::ConnectionAttempt url_request_attempt(
url_request_endpoint, URLRequestStatusToNetError(request.status));
DomainReliabilityBeacon beacon;
beacon.protocol = GetDomainReliabilityProtocol(
request.response_info.connection_info,
request.response_info.ssl_info.is_valid());
beacon.http_response_code = response_code;
beacon.start_time = request.load_timing_info.request_start;
beacon.elapsed = time_->NowTicks() - beacon.start_time;
beacon.was_proxied = request.response_info.was_fetched_via_proxy;
beacon.domain = request.url.host();
// This is not foolproof -- it's possible that we'll see the same error twice
// (e.g. an SSL error during connection on one attempt, and then an error
// that maps to the same code during a read).
// TODO(ttuttle): Find a way for this code to reliably tell whether we
// eventually established a connection or not.
bool url_request_attempt_is_duplicate = false;
for (const auto& attempt : request.connection_attempts) {
if (attempt.result == url_request_attempt.result)
url_request_attempt_is_duplicate = true;
if (!UpdateBeaconFromAttempt(&beacon, attempt))
continue;
context_manager_.RouteBeacon(request.url, beacon);
}
if (url_request_attempt_is_duplicate)
return;
if (!UpdateBeaconFromAttempt(&beacon, url_request_attempt))
return;
context_manager_.RouteBeacon(request.url, beacon);
}
base::WeakPtr<DomainReliabilityMonitor>
DomainReliabilityMonitor::MakeWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace domain_reliability