blob: 22a2c410b872870e2092e3abad926029dcfb36b1 [file] [log] [blame]
// Copyright (c) 2012 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/net/dns_probe_service.h"
#include <stdint.h>
#include <utility>
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/dns/dns_client.h"
#include "net/dns/dns_config.h"
#include "net/dns/public/dns_protocol.h"
using base::FieldTrialList;
using base::StringToInt;
using error_page::DnsProbeStatus;
using net::DnsClient;
using net::DnsConfig;
using net::NetworkChangeNotifier;
namespace chrome_browser_net {
namespace {
// How long the DnsProbeService will cache the probe result for.
// If it's older than this and we get a probe request, the service expires it
// and starts a new probe.
const int kMaxResultAgeMs = 5000;
// The public DNS servers used by the DnsProbeService to verify internet
// connectivity.
const uint8_t kGooglePublicDns1[] = {8, 8, 8, 8};
const uint8_t kGooglePublicDns2[] = {8, 8, 4, 4};
DnsProbeStatus EvaluateResults(DnsProbeRunner::Result system_result,
DnsProbeRunner::Result public_result) {
// If the system DNS is working, assume the domain doesn't exist.
if (system_result == DnsProbeRunner::CORRECT)
return error_page::DNS_PROBE_FINISHED_NXDOMAIN;
// If the system DNS is unknown (e.g. on Android), but the public server is
// reachable, assume the domain doesn't exist.
if (system_result == DnsProbeRunner::UNKNOWN &&
public_result == DnsProbeRunner::CORRECT) {
return error_page::DNS_PROBE_FINISHED_NXDOMAIN;
}
// If the system DNS is not working but another public server is, assume the
// DNS config is bad (or perhaps the DNS servers are down or broken).
if (public_result == DnsProbeRunner::CORRECT)
return error_page::DNS_PROBE_FINISHED_BAD_CONFIG;
// If the system DNS is not working and another public server is unreachable,
// assume the internet connection is down (note that system DNS may be a
// router on the LAN, so it may be reachable but returning errors.)
if (public_result == DnsProbeRunner::UNREACHABLE)
return error_page::DNS_PROBE_FINISHED_NO_INTERNET;
// Otherwise: the system DNS is not working and another public server is
// responding but with errors or incorrect results. This is an awkward case;
// an invasive captive portal or a restrictive firewall may be intercepting
// or rewriting DNS traffic, or the public server may itself be failing or
// down.
return error_page::DNS_PROBE_FINISHED_INCONCLUSIVE;
}
void HistogramProbe(DnsProbeStatus status, base::TimeDelta elapsed) {
DCHECK(error_page::DnsProbeStatusIsFinished(status));
UMA_HISTOGRAM_ENUMERATION("DnsProbe.ProbeResult", status,
error_page::DNS_PROBE_MAX);
UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.ProbeDuration", elapsed);
}
} // namespace
DnsProbeService::DnsProbeService()
: state_(STATE_NO_RESULT) {
NetworkChangeNotifier::AddDNSObserver(this);
SetSystemClientToCurrentConfig();
SetPublicClientToGooglePublicDns();
}
DnsProbeService::~DnsProbeService() {
NetworkChangeNotifier::RemoveDNSObserver(this);
}
void DnsProbeService::ProbeDns(const DnsProbeService::ProbeCallback& callback) {
pending_callbacks_.push_back(callback);
if (CachedResultIsExpired())
ClearCachedResult();
switch (state_) {
case STATE_NO_RESULT:
StartProbes();
break;
case STATE_RESULT_CACHED:
CallCallbacks();
break;
case STATE_PROBE_RUNNING:
// Do nothing; probe is already running, and will call the callback.
break;
}
}
void DnsProbeService::OnDNSChanged() {
ClearCachedResult();
SetSystemClientToCurrentConfig();
}
void DnsProbeService::OnInitialDNSConfigRead() {
OnDNSChanged();
}
void DnsProbeService::SetSystemClientForTesting(
std::unique_ptr<DnsClient> system_client) {
system_runner_.SetClient(std::move(system_client));
}
void DnsProbeService::SetPublicClientForTesting(
std::unique_ptr<DnsClient> public_client) {
public_runner_.SetClient(std::move(public_client));
}
void DnsProbeService::ClearCachedResultForTesting() {
ClearCachedResult();
}
void DnsProbeService::SetSystemClientToCurrentConfig() {
DnsConfig system_config;
NetworkChangeNotifier::GetDnsConfig(&system_config);
system_config.search.clear();
system_config.attempts = 1;
system_config.randomize_ports = false;
std::unique_ptr<DnsClient> system_client(DnsClient::CreateClient(NULL));
system_client->SetConfig(system_config);
system_runner_.SetClient(std::move(system_client));
}
void DnsProbeService::SetPublicClientToGooglePublicDns() {
DnsConfig public_config;
public_config.nameservers.push_back(net::IPEndPoint(
net::IPAddress(kGooglePublicDns1), net::dns_protocol::kDefaultPort));
public_config.nameservers.push_back(net::IPEndPoint(
net::IPAddress(kGooglePublicDns2), net::dns_protocol::kDefaultPort));
public_config.attempts = 1;
public_config.randomize_ports = false;
std::unique_ptr<DnsClient> public_client(DnsClient::CreateClient(NULL));
public_client->SetConfig(public_config);
public_runner_.SetClient(std::move(public_client));
}
void DnsProbeService::StartProbes() {
DCHECK_EQ(STATE_NO_RESULT, state_);
DCHECK(!system_runner_.IsRunning());
DCHECK(!public_runner_.IsRunning());
const base::Closure callback = base::Bind(&DnsProbeService::OnProbeComplete,
base::Unretained(this));
system_runner_.RunProbe(callback);
public_runner_.RunProbe(callback);
probe_start_time_ = base::Time::Now();
state_ = STATE_PROBE_RUNNING;
DCHECK(system_runner_.IsRunning());
DCHECK(public_runner_.IsRunning());
}
void DnsProbeService::OnProbeComplete() {
DCHECK_EQ(STATE_PROBE_RUNNING, state_);
if (system_runner_.IsRunning() || public_runner_.IsRunning())
return;
cached_result_ = EvaluateResults(system_runner_.result(),
public_runner_.result());
state_ = STATE_RESULT_CACHED;
HistogramProbe(cached_result_, base::Time::Now() - probe_start_time_);
CallCallbacks();
}
void DnsProbeService::CallCallbacks() {
DCHECK_EQ(STATE_RESULT_CACHED, state_);
DCHECK(error_page::DnsProbeStatusIsFinished(cached_result_));
DCHECK(!pending_callbacks_.empty());
std::vector<ProbeCallback> callbacks;
callbacks.swap(pending_callbacks_);
for (std::vector<ProbeCallback>::const_iterator i = callbacks.begin();
i != callbacks.end(); ++i) {
i->Run(cached_result_);
}
}
void DnsProbeService::ClearCachedResult() {
if (state_ == STATE_RESULT_CACHED) {
state_ = STATE_NO_RESULT;
cached_result_ = error_page::DNS_PROBE_MAX;
}
}
bool DnsProbeService::CachedResultIsExpired() const {
if (state_ != STATE_RESULT_CACHED)
return false;
const base::TimeDelta kMaxResultAge =
base::TimeDelta::FromMilliseconds(kMaxResultAgeMs);
return base::Time::Now() - probe_start_time_ > kMaxResultAge;
}
} // namespace chrome_browser_net