// Copyright (c) 2006-2008 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_global.h" #include #include #include "base/command_line.h" #include "base/singleton.h" #include "base/stats_counters.h" #include "base/string_util.h" #include "base/thread.h" #include "base/values.h" #include "chrome/browser/browser.h" #include "chrome/browser/net/dns_host_info.h" #include "chrome/browser/net/referrer.h" #include "chrome/browser/profile.h" #include "chrome/browser/session_startup_pref.h" #include "chrome/common/notification_registrar.h" #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" #include "chrome/common/chrome_switches.h" #include "net/base/fixed_host_resolver.h" #include "net/base/host_resolver.h" #include "net/base/host_resolver_impl.h" using base::Time; using base::TimeDelta; namespace chrome_browser_net { static void DiscardAllPrefetchState(); static void DnsMotivatedPrefetch(const std::string& hostname, DnsHostInfo::ResolutionMotivation motivation); static void DnsPrefetchMotivatedList( const NameList& hostnames, DnsHostInfo::ResolutionMotivation motivation); // static const size_t DnsPrefetcherInit::kMaxConcurrentLookups = 8; // static const int DnsPrefetcherInit::kMaxQueueingDelayMs = 1000; // Host resolver shared by DNS prefetcher, and the main URLRequestContext. static net::HostResolver* global_host_resolver = NULL; //------------------------------------------------------------------------------ // This section contains all the globally accessable API entry points for the // DNS Prefetching feature. //------------------------------------------------------------------------------ // Status of prefetch feature, controlling whether any prefetching is done. static bool dns_prefetch_enabled = true; // Cached inverted copy of the off_the_record pref. static bool on_the_record_switch = true; // Enable/disable Dns prefetch activity (either via command line, or via pref). void EnableDnsPrefetch(bool enable) { dns_prefetch_enabled = enable; } void OnTheRecord(bool enable) { if (on_the_record_switch == enable) return; on_the_record_switch = enable; if (on_the_record_switch) DiscardAllPrefetchState(); // Destroy all evidence of our OTR session. } void RegisterPrefs(PrefService* local_state) { local_state->RegisterListPref(prefs::kDnsStartupPrefetchList); local_state->RegisterListPref(prefs::kDnsHostReferralList); } void RegisterUserPrefs(PrefService* user_prefs) { user_prefs->RegisterBooleanPref(prefs::kDnsPrefetchingEnabled, true); } // When enabled, we use the following instance to service all requests in the // browser process. static DnsMaster* dns_master; // This API is only used in the browser process. // It is called from an IPC message originating in the renderer. It currently // includes both Page-Scan, and Link-Hover prefetching. // TODO(jar): Separate out link-hover prefetching, and page-scan results. void DnsPrefetchList(const NameList& hostnames) { DnsPrefetchMotivatedList(hostnames, DnsHostInfo::PAGE_SCAN_MOTIVATED); } static void DnsPrefetchMotivatedList( const NameList& hostnames, DnsHostInfo::ResolutionMotivation motivation) { if (!dns_prefetch_enabled || NULL == dns_master) return; dns_master->ResolveList(hostnames, motivation); } // This API is used by the autocomplete popup box (where URLs are typed). void DnsPrefetchUrl(const GURL& url) { if (!dns_prefetch_enabled || NULL == dns_master) return; if (url.is_valid()) DnsMotivatedPrefetch(url.host(), DnsHostInfo::OMNIBOX_MOTIVATED); } static void DnsMotivatedPrefetch(const std::string& hostname, DnsHostInfo::ResolutionMotivation motivation) { if (!dns_prefetch_enabled || NULL == dns_master || !hostname.size()) return; dns_master->Resolve(hostname, motivation); } //------------------------------------------------------------------------------ // This section intermingles prefetch results with actual browser HTTP // network activity. It supports calculating of the benefit of a prefetch, as // well as recording what prefetched hostname resolutions might be potentially // helpful during the next chrome-startup. //------------------------------------------------------------------------------ // This function determines if there was a saving by prefetching the hostname // for which the navigation_info is supplied. static bool AccruePrefetchBenefits(const GURL& referrer, DnsHostInfo* navigation_info) { if (!dns_prefetch_enabled || NULL == dns_master) return false; return dns_master->AccruePrefetchBenefits(referrer, navigation_info); } // When we navigate, we may know in advance some other domains that will need to // be resolved. This function initiates those side effects. static void NavigatingTo(const std::string& host_name) { if (!dns_prefetch_enabled || NULL == dns_master) return; dns_master->NavigatingTo(host_name); } // The observer class needs to connect starts and finishes of HTTP network // resolutions. We use the following type for that map. typedef std::map ObservedResolutionMap; // There will only be one instance ever created of the following Observer // class. As a result, we get away with using static members for data local // to that instance (to better comply with a google style guide exemption). class PrefetchObserver : public net::HostResolver::Observer { public: PrefetchObserver(); ~PrefetchObserver(); virtual void OnStartResolution( int request_id, const net::HostResolver::RequestInfo& request_info); virtual void OnFinishResolutionWithStatus( int request_id, bool was_resolved, const net::HostResolver::RequestInfo& request_info); virtual void OnCancelResolution( int request_id, const net::HostResolver::RequestInfo& request_info); static void DnsGetFirstResolutionsHtml(std::string* output); static void SaveStartupListAsPref(PrefService* local_state); private: static void StartupListAppend(const DnsHostInfo& navigation_info); // We avoid using member variables to better comply with the style guide. // We had permission to instantiate only a very minimal class as a global // data item, so we avoid putting members in that class. // There is really only one instance of this class, and it would have been // much simpler to use member variables than these static members. static Lock* lock; // Map of pending resolutions seen by observer. static ObservedResolutionMap* resolutions; // List of the first N hostname resolutions observed in this run. static Results* first_resolutions; // The number of hostnames we'll save for prefetching at next startup. static const size_t kStartupResolutionCount = 10; }; //------------------------------------------------------------------------------ // Member definitions for above Observer class. PrefetchObserver::PrefetchObserver() { DCHECK(!lock && !resolutions && !first_resolutions); lock = new Lock; resolutions = new ObservedResolutionMap; first_resolutions = new Results; } PrefetchObserver::~PrefetchObserver() { DCHECK(lock && resolutions && first_resolutions); delete first_resolutions; first_resolutions = NULL; delete resolutions; resolutions = NULL; delete lock; lock = NULL; } void PrefetchObserver::OnStartResolution( int request_id, const net::HostResolver::RequestInfo& request_info) { if (request_info.is_speculative()) return; // One of our own requests. DCHECK_NE(0U, request_info.hostname().length()); DnsHostInfo navigation_info; navigation_info.SetHostname(request_info.hostname()); navigation_info.SetStartedState(); NavigatingTo(request_info.hostname()); AutoLock auto_lock(*lock); // This entry will be deleted either by OnFinishResolutionWithStatus(), or // by OnCancelResolution(). (*resolutions)[request_id] = navigation_info; } void PrefetchObserver::OnFinishResolutionWithStatus( int request_id, bool was_resolved, const net::HostResolver::RequestInfo& request_info) { if (request_info.is_speculative()) return; // One of our own requests. DnsHostInfo navigation_info; size_t startup_count; { AutoLock auto_lock(*lock); ObservedResolutionMap::iterator it = resolutions->find(request_id); if (resolutions->end() == it) { DCHECK(false); return; } navigation_info = it->second; resolutions->erase(it); startup_count = first_resolutions->size(); } navigation_info.SetFinishedState(was_resolved); // Get timing info AccruePrefetchBenefits(request_info.referrer(), &navigation_info); if (kStartupResolutionCount <= startup_count || !was_resolved) return; // TODO(jar): Don't add host to our list if it is a non-linked lookup, and // instead rely on Referrers to pull this in automatically with the enclosing // page load (once we start to persist elements of our referrer tree). StartupListAppend(navigation_info); } void PrefetchObserver::OnCancelResolution( int request_id, const net::HostResolver::RequestInfo& request_info) { if (request_info.is_speculative()) return; // One of our own requests. // Remove the entry from |resolutions| that was added by OnStartResolution(). AutoLock auto_lock(*lock); ObservedResolutionMap::iterator it = resolutions->find(request_id); if (resolutions->end() == it) { DCHECK(false); return; } resolutions->erase(it); } // static void PrefetchObserver::StartupListAppend(const DnsHostInfo& navigation_info) { if (!on_the_record_switch || NULL == dns_master) return; AutoLock auto_lock(*lock); if (kStartupResolutionCount <= first_resolutions->size()) return; // Someone just added the last item. std::string host_name = navigation_info.hostname(); if (first_resolutions->find(host_name) != first_resolutions->end()) return; // We already have this hostname listed. (*first_resolutions)[host_name] = navigation_info; } // static void PrefetchObserver::SaveStartupListAsPref(PrefService* local_state) { ListValue* startup_list = local_state->GetMutableList(prefs::kDnsStartupPrefetchList); DCHECK(startup_list); if (!startup_list) return; startup_list->Clear(); DCHECK_EQ(0u, startup_list->GetSize()); AutoLock auto_lock(*lock); for (Results::iterator it = first_resolutions->begin(); it != first_resolutions->end(); it++) { const std::string hostname = it->first; startup_list->Append(Value::CreateStringValue(hostname)); } } // static void PrefetchObserver::DnsGetFirstResolutionsHtml(std::string* output) { DnsHostInfo::DnsInfoTable resolution_list; { AutoLock auto_lock(*lock); for (Results::iterator it(first_resolutions->begin()); it != first_resolutions->end(); it++) { resolution_list.push_back(it->second); } } DnsHostInfo::GetHtmlTable(resolution_list, "Future startups will prefetch DNS records for ", false, output); } // static Lock* PrefetchObserver::lock = NULL; // static ObservedResolutionMap* PrefetchObserver::resolutions = NULL; // static Results* PrefetchObserver::first_resolutions = NULL; //------------------------------------------------------------------------------ // Support observer to detect opening and closing of OffTheRecord windows. class OffTheRecordObserver : public NotificationObserver { public: void Register() { // TODO(pkasting): This test should not be necessary. See crbug.com/12475. if (registrar_.IsEmpty()) { registrar_.Add(this, NotificationType::BROWSER_CLOSED, NotificationService::AllSources()); registrar_.Add(this, NotificationType::BROWSER_OPENED, NotificationService::AllSources()); } } void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::BROWSER_OPENED: if (!Source(source)->profile()->IsOffTheRecord()) break; { AutoLock lock(lock_); ++count_off_the_record_windows_; } OnTheRecord(false); break; case NotificationType::BROWSER_CLOSED: if (!Source(source)->profile()->IsOffTheRecord()) break; // Ignore ordinary windows. { AutoLock lock(lock_); DCHECK_LT(0, count_off_the_record_windows_); if (0 >= count_off_the_record_windows_) // Defensive coding. break; if (--count_off_the_record_windows_) break; // Still some windows are incognito. } // Release lock. OnTheRecord(true); break; default: break; } } private: friend struct DefaultSingletonTraits; OffTheRecordObserver() : lock_(), count_off_the_record_windows_(0) { } ~OffTheRecordObserver() { } NotificationRegistrar registrar_; Lock lock_; int count_off_the_record_windows_; DISALLOW_COPY_AND_ASSIGN(OffTheRecordObserver); }; //------------------------------------------------------------------------------ // This section supports the about:dns page. //------------------------------------------------------------------------------ // Provide global support for the about:dns page. void DnsPrefetchGetHtmlInfo(std::string* output) { output->append("About DNS" // We'd like the following no-cache... but it doesn't work. // "" ""); if (!dns_prefetch_enabled || NULL == dns_master) { output->append("Dns Prefetching is disabled."); } else { if (!on_the_record_switch) { output->append("Incognito mode is active in a window."); } else { dns_master->GetHtmlInfo(output); PrefetchObserver::DnsGetFirstResolutionsHtml(output); dns_master->GetHtmlReferrerLists(output); } } output->append(""); } //------------------------------------------------------------------------------ // This section intializes and tears down global DNS prefetch services. //------------------------------------------------------------------------------ // Note: We have explicit permission to create the following global static // object (in opposition to Google style rules). By making it a static, we // can ensure its deletion. static PrefetchObserver dns_resolution_observer; void InitDnsPrefetch(TimeDelta max_queue_delay, size_t max_concurrent, PrefService* user_prefs) { // Use a large shutdown time so that UI tests (that instigate lookups, and // then try to shutdown the browser) don't instigate the CHECK about // "some slaves have not finished" const TimeDelta kAllowableShutdownTime(TimeDelta::FromSeconds(10)); DCHECK(NULL == dns_master); if (!dns_master) { // Have the DnsMaster issue resolve requests through a global HostResolver // that is shared by the main URLRequestContext, and lives on the IO thread. dns_master = new DnsMaster(GetGlobalHostResolver(), max_queue_delay, max_concurrent); dns_master->AddRef(); // We did the initialization, so we should prime the pump, and set up // the DNS resolution system to run. Singleton::get()->Register(); if (user_prefs) { bool enabled = user_prefs->GetBoolean(prefs::kDnsPrefetchingEnabled); EnableDnsPrefetch(enabled); } DLOG(INFO) << "DNS Prefetch service started"; // Start observing real HTTP stack resolutions. // TODO(eroman): really this should be called from IO thread (since that is // where the host resolver lives). Since this occurs before requests have // started it is not a race yet. GetGlobalHostResolver()->AddObserver(&dns_resolution_observer); } } void EnsureDnsPrefetchShutdown() { if (NULL != dns_master) { dns_master->Shutdown(); // Stop observing DNS resolutions. Note that dns_master holds a reference // to the global host resolver, so is guaranteed to be live. GetGlobalHostResolver()->RemoveObserver(&dns_resolution_observer); // TODO(eroman): temp hack for http://crbug.com/15513 GetGlobalHostResolver()->Shutdown(); } // TODO(eroman): This is a hack so the in process browser tests work if // BrowserMain() is to be called again. global_host_resolver = NULL; } void FreeDnsPrefetchResources() { DCHECK(NULL != dns_master); dns_master->Release(); dns_master = NULL; } static void DiscardAllPrefetchState() { if (!dns_master) return; dns_master->DiscardAllResults(); } //------------------------------------------------------------------------------ net::HostResolver* GetGlobalHostResolver() { // Called from UI thread. if (!global_host_resolver) { const CommandLine& command_line = *CommandLine::ForCurrentProcess(); // The FixedHostResolver allows us to send all network requests through // a designated test server. if (command_line.HasSwitch(switches::kFixedServer)) { std::string host_and_port = WideToASCII(command_line.GetSwitchValue(switches::kFixedServer)); global_host_resolver = new net::FixedHostResolver(host_and_port); return global_host_resolver; } global_host_resolver = net::CreateSystemHostResolver(); if (command_line.HasSwitch(switches::kDisableIPv6)) global_host_resolver->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4); } return global_host_resolver; } //------------------------------------------------------------------------------ // Functions to handle saving of hostnames from one session to the next, to // expedite startup times. void SaveHostNamesForNextStartup(PrefService* local_state) { if (!dns_prefetch_enabled) return; PrefetchObserver::SaveStartupListAsPref(local_state); } void DnsPrefetchHostNamesAtStartup(PrefService* user_prefs, PrefService* local_state) { NameList hostnames; // Prefetch DNS for hostnames we learned about during last session. // This may catch secondary hostnames, pulled in by the homepages. It will // also catch more of the "primary" home pages, since that was (presumably) // rendered first (and will be rendered first this time too). ListValue* startup_list = local_state->GetMutableList(prefs::kDnsStartupPrefetchList); if (startup_list) { for (ListValue::iterator it = startup_list->begin(); it != startup_list->end(); it++) { std::string hostname; (*it)->GetAsString(&hostname); hostnames.push_back(hostname); } } // Prepare for any static home page(s) the user has in prefs. The user may // have a LOT of tab's specified, so we may as well try to warm them all. SessionStartupPref tab_start_pref = SessionStartupPref::GetStartupPref(user_prefs); if (SessionStartupPref::URLS == tab_start_pref.type) { for (size_t i = 0; i < tab_start_pref.urls.size(); i++) { GURL gurl = tab_start_pref.urls[i]; if (gurl.is_valid() && !gurl.host().empty()) hostnames.push_back(gurl.host()); } } if (hostnames.size() > 0) DnsPrefetchMotivatedList(hostnames, DnsHostInfo::STARTUP_LIST_MOTIVATED); else // Start a thread. DnsMotivatedPrefetch(std::string("www.google.com"), DnsHostInfo::STARTUP_LIST_MOTIVATED); } //------------------------------------------------------------------------------ // Functions to persist and restore host references, that are used to direct DNS // prefetch of names (probably) used in subresources when the major resource is // navigated towards. void SaveSubresourceReferrers(PrefService* local_state) { if (NULL == dns_master) return; ListValue* referral_list = local_state->GetMutableList(prefs::kDnsHostReferralList); dns_master->SerializeReferrers(referral_list); } void RestoreSubresourceReferrers(PrefService* local_state) { if (NULL == dns_master) return; ListValue* referral_list = local_state->GetMutableList(prefs::kDnsHostReferralList); dns_master->DeserializeReferrers(*referral_list); } void TrimSubresourceReferrers() { if (NULL == dns_master) return; dns_master->TrimReferrers(); } //------------------------------------------------------------------------------ // Methods for the helper class that is used to startup and teardown the whole // DNS prefetch system. DnsPrefetcherInit::DnsPrefetcherInit(PrefService* user_prefs, PrefService* local_state) { // Set up a field trial to see what disabling DNS pre-resolution does to // latency of page loads. FieldTrial::Probability kDivisor = 100; // For each option (i.e., non-default), we have a fixed probability. FieldTrial::Probability kProbabilityPerGroup = 10; // 10% probability. trial_ = new FieldTrial("DnsImpact", kDivisor); // First option is to disable prefetching completely. int disabled_prefetch = trial_->AppendGroup("_disabled_prefetch", kProbabilityPerGroup); // Set congestion detection at 250, 500, or 750ms, rather than the 1 second // default. int max_250ms_prefetch = trial_->AppendGroup("_max_250ms_queue_prefetch", kProbabilityPerGroup); int max_500ms_prefetch = trial_->AppendGroup("_max_500ms_queue_prefetch", kProbabilityPerGroup); int max_750ms_prefetch = trial_->AppendGroup("_max_750ms_queue_prefetch", kProbabilityPerGroup); // Set congestion detection at 2 seconds instead of the 1 second default. int max_2s_prefetch = trial_->AppendGroup("_max_2s_queue_prefetch", kProbabilityPerGroup); trial_->AppendGroup("_default_enabled_prefetch", FieldTrial::kAllRemainingProbability); if (trial_->group() != disabled_prefetch) { // Initialize the DNS prefetch system. size_t max_concurrent = kMaxConcurrentLookups; int max_queueing_delay_ms = kMaxQueueingDelayMs; if (trial_->group() == max_250ms_prefetch) max_queueing_delay_ms = 250; else if (trial_->group() == max_500ms_prefetch) max_queueing_delay_ms = 500; else if (trial_->group() == max_750ms_prefetch) max_queueing_delay_ms = 750; else if (trial_->group() == max_2s_prefetch) max_queueing_delay_ms = 2000; TimeDelta max_queueing_delay( TimeDelta::FromMilliseconds(max_queueing_delay_ms)); DCHECK(!dns_master); InitDnsPrefetch(max_queueing_delay, max_concurrent, user_prefs); DCHECK(dns_master); // Will be checked in destructor. chrome_browser_net::DnsPrefetchHostNamesAtStartup(user_prefs, local_state); chrome_browser_net::RestoreSubresourceReferrers(local_state); } } DnsPrefetcherInit::~DnsPrefetcherInit() { if (dns_master) FreeDnsPrefetchResources(); } } // namespace chrome_browser_net