// 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 "base/metrics/field_trial.h" #include "base/metrics/histogram.h" #include "base/strings/string_number_conversions.h" #include "net/base/ip_endpoint.h" #include "net/base/net_util.h" #include "net/dns/dns_client.h" #include "net/dns/dns_config_service.h" #include "net/dns/dns_protocol.h" using base::FieldTrialList; using base::StringToInt; using chrome_common_net::DnsProbeStatus; using net::DnsClient; using net::DnsConfig; using net::IPAddressNumber; using net::ParseIPLiteralToNumber; 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 char kGooglePublicDns1[] = "8.8.8.8"; const char kGooglePublicDns2[] = "8.8.4.4"; net::IPEndPoint MakeDnsEndPoint(const std::string& dns_ip_literal) { IPAddressNumber dns_ip_number; bool rv = ParseIPLiteralToNumber(dns_ip_literal, &dns_ip_number); DCHECK(rv); return net::IPEndPoint(dns_ip_number, net::dns_protocol::kDefaultPort); } 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 chrome_common_net::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 chrome_common_net::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 chrome_common_net::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 chrome_common_net::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 chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE; } void HistogramProbe(DnsProbeStatus status, base::TimeDelta elapsed) { DCHECK(chrome_common_net::DnsProbeStatusIsFinished(status)); UMA_HISTOGRAM_ENUMERATION("DnsProbe.ProbeResult", status, chrome_common_net::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( scoped_ptr system_client) { system_runner_.SetClient(system_client.Pass()); } void DnsProbeService::SetPublicClientForTesting( scoped_ptr public_client) { public_runner_.SetClient(public_client.Pass()); } 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; scoped_ptr system_client(DnsClient::CreateClient(NULL)); system_client->SetConfig(system_config); system_runner_.SetClient(system_client.Pass()); } void DnsProbeService::SetPublicClientToGooglePublicDns() { DnsConfig public_config; public_config.nameservers.push_back(MakeDnsEndPoint(kGooglePublicDns1)); public_config.nameservers.push_back(MakeDnsEndPoint(kGooglePublicDns2)); public_config.attempts = 1; public_config.randomize_ports = false; scoped_ptr public_client(DnsClient::CreateClient(NULL)); public_client->SetConfig(public_config); public_runner_.SetClient(public_client.Pass()); } 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(chrome_common_net::DnsProbeStatusIsFinished(cached_result_)); DCHECK(!pending_callbacks_.empty()); std::vector callbacks; callbacks.swap(pending_callbacks_); for (std::vector::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_ = chrome_common_net::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