| // Copyright (c) 2010 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/io_thread.h" |
| #include "base/command_line.h" |
| #include "base/leak_tracker.h" |
| #include "base/logging.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_thread.h" |
| #include "chrome/browser/gpu_process_host.h" |
| #include "chrome/browser/net/chrome_net_log.h" |
| #include "chrome/browser/net/predictor_api.h" |
| #include "chrome/browser/net/passive_log_collector.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/net/url_fetcher.h" |
| #include "net/base/mapped_host_resolver.h" |
| #include "net/base/host_cache.h" |
| #include "net/base/host_resolver.h" |
| #include "net/base/host_resolver_impl.h" |
| #include "net/base/net_util.h" |
| #include "net/http/http_auth_filter.h" |
| #include "net/http/http_auth_handler_factory.h" |
| #include "net/http/http_auth_handler_negotiate.h" |
| |
| namespace { |
| |
| net::HostResolver* CreateGlobalHostResolver() { |
| const CommandLine& command_line = *CommandLine::ForCurrentProcess(); |
| |
| size_t parallelism = net::HostResolver::kDefaultParallelism; |
| |
| // Use the concurrency override from the command-line, if any. |
| if (command_line.HasSwitch(switches::kHostResolverParallelism)) { |
| std::string s = |
| command_line.GetSwitchValueASCII(switches::kHostResolverParallelism); |
| |
| // Parse the switch (it should be a positive integer formatted as decimal). |
| int n; |
| if (StringToInt(s, &n) && n > 0) { |
| parallelism = static_cast<size_t>(n); |
| } else { |
| LOG(ERROR) << "Invalid switch for host resolver parallelism: " << s; |
| } |
| } |
| |
| net::HostResolver* global_host_resolver = |
| net::CreateSystemHostResolver(parallelism); |
| |
| // Determine if we should disable IPv6 support. |
| if (!command_line.HasSwitch(switches::kEnableIPv6)) { |
| if (command_line.HasSwitch(switches::kDisableIPv6)) { |
| global_host_resolver->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4); |
| } else { |
| net::HostResolverImpl* host_resolver_impl = |
| global_host_resolver->GetAsHostResolverImpl(); |
| if (host_resolver_impl != NULL) { |
| // (optionally) Use probe to decide if support is warranted. |
| bool use_ipv6_probe = true; |
| |
| #if defined(OS_WIN) |
| // Measure impact of probing to allow IPv6. |
| // Some users report confused OS handling of IPv6, leading to large |
| // latency. If we can show that IPv6 is not supported, then disabliing |
| // it will work around such problems. This is the test of the probe. |
| const FieldTrial::Probability kDivisor = 100; |
| const FieldTrial::Probability kProbability = 50; // 50% probability. |
| FieldTrial* trial = new FieldTrial("IPv6_Probe", kDivisor); |
| int skip_group = trial->AppendGroup("_IPv6_probe_skipped", |
| kProbability); |
| trial->AppendGroup("_IPv6_probe_done", |
| FieldTrial::kAllRemainingProbability); |
| use_ipv6_probe = (trial->group() != skip_group); |
| #endif |
| |
| if (use_ipv6_probe) |
| host_resolver_impl->ProbeIPv6Support(); |
| } |
| } |
| } |
| |
| // If hostname remappings were specified on the command-line, layer these |
| // rules on top of the real host resolver. This allows forwarding all requests |
| // through a designated test server. |
| if (!command_line.HasSwitch(switches::kHostResolverRules)) |
| return global_host_resolver; |
| |
| net::MappedHostResolver* remapped_resolver = |
| new net::MappedHostResolver(global_host_resolver); |
| remapped_resolver->SetRulesFromString( |
| command_line.GetSwitchValueASCII(switches::kHostResolverRules)); |
| return remapped_resolver; |
| } |
| |
| class LoggingNetworkChangeObserver |
| : public net::NetworkChangeNotifier::Observer { |
| public: |
| // |net_log| must remain valid throughout our lifetime. |
| explicit LoggingNetworkChangeObserver(net::NetLog* net_log) |
| : net_log_(net_log) { |
| net::NetworkChangeNotifier::AddObserver(this); |
| } |
| |
| ~LoggingNetworkChangeObserver() { |
| net::NetworkChangeNotifier::RemoveObserver(this); |
| } |
| |
| virtual void OnIPAddressChanged() { |
| LOG(INFO) << "Observed a change to the network IP addresses"; |
| |
| net::NetLog::Source global_source; |
| |
| // TODO(eroman): We shouldn't need to assign an ID to this source, since |
| // conceptually it is the "global event stream". However |
| // currently the javascript does a grouping on source id, so |
| // the display will look weird if we don't give it one. |
| global_source.id = net_log_->NextID(); |
| |
| net_log_->AddEntry(net::NetLog::TYPE_NETWORK_IP_ADDRESSSES_CHANGED, |
| base::TimeTicks::Now(), |
| global_source, |
| net::NetLog::PHASE_NONE, |
| NULL); |
| } |
| |
| private: |
| net::NetLog* net_log_; |
| DISALLOW_COPY_AND_ASSIGN(LoggingNetworkChangeObserver); |
| }; |
| |
| } // namespace |
| |
| // The IOThread object must outlive any tasks posted to the IO thread before the |
| // Quit task. |
| DISABLE_RUNNABLE_METHOD_REFCOUNT(IOThread); |
| |
| IOThread::IOThread() |
| : BrowserProcessSubThread(ChromeThread::IO), |
| globals_(NULL), |
| speculative_interceptor_(NULL), |
| prefetch_observer_(NULL), |
| predictor_(NULL) {} |
| |
| IOThread::~IOThread() { |
| // We cannot rely on our base class to stop the thread since we want our |
| // CleanUp function to run. |
| Stop(); |
| DCHECK(!globals_); |
| } |
| |
| IOThread::Globals* IOThread::globals() { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); |
| return globals_; |
| } |
| |
| void IOThread::InitNetworkPredictor( |
| bool prefetching_enabled, |
| base::TimeDelta max_dns_queue_delay, |
| size_t max_concurrent, |
| const chrome_common_net::UrlList& startup_urls, |
| ListValue* referral_list, |
| bool preconnect_enabled) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| message_loop()->PostTask( |
| FROM_HERE, |
| NewRunnableMethod( |
| this, |
| &IOThread::InitNetworkPredictorOnIOThread, |
| prefetching_enabled, max_dns_queue_delay, max_concurrent, |
| startup_urls, referral_list, preconnect_enabled)); |
| } |
| |
| void IOThread::ChangedToOnTheRecord() { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| message_loop()->PostTask( |
| FROM_HERE, |
| NewRunnableMethod( |
| this, |
| &IOThread::ChangedToOnTheRecordOnIOThread)); |
| } |
| |
| void IOThread::Init() { |
| BrowserProcessSubThread::Init(); |
| |
| DCHECK(!globals_); |
| globals_ = new Globals; |
| |
| globals_->net_log.reset(new ChromeNetLog()); |
| |
| // Add an observer that will emit network change events to the ChromeNetLog. |
| // Assuming NetworkChangeNotifier dispatches in FIFO order, we should be |
| // logging the network change before other IO thread consumers respond to it. |
| network_change_observer_.reset( |
| new LoggingNetworkChangeObserver(globals_->net_log.get())); |
| |
| globals_->host_resolver = CreateGlobalHostResolver(); |
| globals_->http_auth_handler_factory.reset(CreateDefaultAuthHandlerFactory( |
| globals_->host_resolver)); |
| } |
| |
| void IOThread::CleanUp() { |
| // This must be reset before the ChromeNetLog is destroyed. |
| network_change_observer_.reset(); |
| |
| // If any child processes are still running, terminate them and |
| // and delete the BrowserChildProcessHost instances to release whatever |
| // IO thread only resources they are referencing. |
| BrowserChildProcessHost::TerminateAll(); |
| |
| // Not initialized in Init(). May not be initialized. |
| if (predictor_) { |
| predictor_->Shutdown(); |
| |
| // TODO(willchan): Stop reference counting Predictor. It's owned by |
| // IOThread now. |
| predictor_->Release(); |
| predictor_ = NULL; |
| chrome_browser_net::FreePredictorResources(); |
| } |
| |
| // Deletion will unregister this interceptor. |
| delete speculative_interceptor_; |
| speculative_interceptor_ = NULL; |
| |
| // Not initialized in Init(). May not be initialized. |
| if (prefetch_observer_) { |
| globals_->host_resolver->RemoveObserver(prefetch_observer_); |
| delete prefetch_observer_; |
| prefetch_observer_ = NULL; |
| } |
| |
| // TODO(eroman): hack for http://crbug.com/15513 |
| if (globals_->host_resolver->GetAsHostResolverImpl()) { |
| globals_->host_resolver.get()->GetAsHostResolverImpl()->Shutdown(); |
| } |
| |
| // We will delete the NetLog as part of CleanUpAfterMessageLoopDestruction() |
| // in case any of the message loop destruction observers try to access it. |
| deferred_net_log_to_delete_.reset(globals_->net_log.release()); |
| |
| delete globals_; |
| globals_ = NULL; |
| |
| // URLRequest instances must NOT outlive the IO thread. |
| base::LeakTracker<URLRequest>::CheckForLeaks(); |
| |
| BrowserProcessSubThread::CleanUp(); |
| } |
| |
| void IOThread::CleanUpAfterMessageLoopDestruction() { |
| // TODO(eroman): get rid of this special case for 39723. If we could instead |
| // have a method that runs after the message loop destruction obsevers have |
| // run, but before the message loop itself is destroyed, we could safely |
| // combine the two cleanups. |
| deferred_net_log_to_delete_.reset(); |
| BrowserProcessSubThread::CleanUpAfterMessageLoopDestruction(); |
| } |
| |
| net::HttpAuthHandlerFactory* IOThread::CreateDefaultAuthHandlerFactory( |
| net::HostResolver* resolver) { |
| net::HttpAuthFilterWhitelist* auth_filter = NULL; |
| |
| // Get the whitelist information from the command line, create an |
| // HttpAuthFilterWhitelist, and attach it to the HttpAuthHandlerFactory. |
| const CommandLine& command_line = *CommandLine::ForCurrentProcess(); |
| |
| if (command_line.HasSwitch(switches::kAuthServerWhitelist)) { |
| std::string auth_server_whitelist = |
| command_line.GetSwitchValueASCII(switches::kAuthServerWhitelist); |
| |
| // Create a whitelist filter. |
| auth_filter = new net::HttpAuthFilterWhitelist(); |
| auth_filter->SetWhitelist(auth_server_whitelist); |
| } |
| |
| // Set the flag that enables or disables the Negotiate auth handler. |
| static const bool kNegotiateAuthEnabledDefault = true; |
| |
| bool negotiate_auth_enabled = kNegotiateAuthEnabledDefault; |
| if (command_line.HasSwitch(switches::kExperimentalEnableNegotiateAuth)) { |
| std::string enable_negotiate_auth = command_line.GetSwitchValueASCII( |
| switches::kExperimentalEnableNegotiateAuth); |
| // Enabled if no value, or value is 'true'. Disabled otherwise. |
| negotiate_auth_enabled = |
| enable_negotiate_auth.empty() || |
| (StringToLowerASCII(enable_negotiate_auth) == "true"); |
| } |
| |
| net::HttpAuthHandlerRegistryFactory* registry_factory = |
| net::HttpAuthHandlerFactory::CreateDefault(); |
| |
| globals_->url_security_manager.reset( |
| net::URLSecurityManager::Create(auth_filter)); |
| |
| // Add the security manager to the auth factories that need it. |
| registry_factory->SetURLSecurityManager("ntlm", |
| globals_->url_security_manager.get()); |
| registry_factory->SetURLSecurityManager("negotiate", |
| globals_->url_security_manager.get()); |
| if (negotiate_auth_enabled) { |
| // Configure the Negotiate settings for the Kerberos SPN. |
| // TODO(cbentzel): Read the related IE registry settings on Windows builds. |
| // TODO(cbentzel): Ugly use of static_cast here. |
| net::HttpAuthHandlerNegotiate::Factory* negotiate_factory = |
| static_cast<net::HttpAuthHandlerNegotiate::Factory*>( |
| registry_factory->GetSchemeFactory("negotiate")); |
| DCHECK(negotiate_factory); |
| negotiate_factory->set_host_resolver(resolver); |
| if (command_line.HasSwitch(switches::kDisableAuthNegotiateCnameLookup)) |
| negotiate_factory->set_disable_cname_lookup(true); |
| if (command_line.HasSwitch(switches::kEnableAuthNegotiatePort)) |
| negotiate_factory->set_use_port(true); |
| } else { |
| // Disable the Negotiate authentication handler. |
| registry_factory->RegisterSchemeFactory("negotiate", NULL); |
| } |
| return registry_factory; |
| } |
| |
| void IOThread::InitNetworkPredictorOnIOThread( |
| bool prefetching_enabled, |
| base::TimeDelta max_dns_queue_delay, |
| size_t max_concurrent, |
| const chrome_common_net::UrlList& startup_urls, |
| ListValue* referral_list, |
| bool preconnect_enabled) { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); |
| CHECK(!predictor_); |
| |
| chrome_browser_net::EnablePredictor(prefetching_enabled); |
| |
| predictor_ = new chrome_browser_net::Predictor( |
| globals_->host_resolver, |
| max_dns_queue_delay, |
| max_concurrent, |
| preconnect_enabled); |
| predictor_->AddRef(); |
| |
| // TODO(jar): Until connection notification and DNS observation handling are |
| // properly combined into a learning model, we'll only use one observation |
| // mechanism or the other. |
| if (preconnect_enabled) { |
| DCHECK(!speculative_interceptor_); |
| speculative_interceptor_ = new chrome_browser_net::ConnectInterceptor; |
| } else { |
| DCHECK(!prefetch_observer_); |
| prefetch_observer_ = chrome_browser_net::CreateResolverObserver(); |
| globals_->host_resolver->AddObserver(prefetch_observer_); |
| } |
| |
| FinalizePredictorInitialization( |
| predictor_, prefetch_observer_, startup_urls, referral_list); |
| } |
| |
| void IOThread::ChangedToOnTheRecordOnIOThread() { |
| DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); |
| |
| if (predictor_) { |
| // Destroy all evidence of our OTR session. |
| predictor_->Predictor::DiscardAllResults(); |
| } |
| |
| // Clear the host cache to avoid showing entries from the OTR session |
| // in about:net-internals. |
| if (globals_->host_resolver->GetAsHostResolverImpl()) { |
| net::HostCache* host_cache = |
| globals_->host_resolver.get()->GetAsHostResolverImpl()->cache(); |
| if (host_cache) |
| host_cache->clear(); |
| } |
| // Clear all of the passively logged data. |
| // TODO(eroman): this is a bit heavy handed, really all we need to do is |
| // clear the data pertaining to off the record context. |
| globals_->net_log->passive_collector()->Clear(); |
| } |