// 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/connection_tester.h" #include "base/bind.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/thread_task_runner_handle.h" #include "base/threading/thread_restrictions.h" #include "chrome/common/chrome_switches.h" #include "content/public/browser/browser_thread.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/base/request_priority.h" #include "net/cert/cert_verifier.h" #include "net/cookies/cookie_monster.h" #include "net/dns/host_resolver.h" #include "net/http/http_auth_handler_factory.h" #include "net/http/http_cache.h" #include "net/http/http_network_session.h" #include "net/http/http_server_properties_impl.h" #include "net/http/transport_security_state.h" #include "net/proxy/dhcp_proxy_script_fetcher_factory.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/proxy/proxy_script_fetcher_impl.h" #include "net/proxy/proxy_service.h" #include "net/proxy/proxy_service_v8.h" #include "net/ssl/ssl_config_service_defaults.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_storage.h" #if !defined(OS_ANDROID) && !defined(OS_IOS) #include "chrome/browser/net/firefox_proxy_settings.h" #endif namespace { // ExperimentURLRequestContext ------------------------------------------------ // An instance of ExperimentURLRequestContext is created for each experiment // run by ConnectionTester. The class initializes network dependencies according // to the specified "experiment". class ExperimentURLRequestContext : public net::URLRequestContext { public: explicit ExperimentURLRequestContext( net::URLRequestContext* proxy_request_context) : #if !defined(OS_IOS) proxy_request_context_(proxy_request_context), #endif storage_(this), weak_factory_(this) {} virtual ~ExperimentURLRequestContext() {} // Creates a proxy config service for |experiment|. On success returns net::OK // and fills |config_service| with a new pointer. Otherwise returns a network // error code. int CreateProxyConfigService( ConnectionTester::ProxySettingsExperiment experiment, scoped_ptr<net::ProxyConfigService>* config_service, base::Callback<void(int)> callback) { switch (experiment) { case ConnectionTester::PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS: return CreateSystemProxyConfigService(config_service); case ConnectionTester::PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS: return CreateFirefoxProxyConfigService(config_service, callback); case ConnectionTester::PROXY_EXPERIMENT_USE_AUTO_DETECT: config_service->reset(new net::ProxyConfigServiceFixed( net::ProxyConfig::CreateAutoDetect())); return net::OK; case ConnectionTester::PROXY_EXPERIMENT_USE_DIRECT: config_service->reset(new net::ProxyConfigServiceFixed( net::ProxyConfig::CreateDirect())); return net::OK; default: NOTREACHED(); return net::ERR_UNEXPECTED; } } int Init(const ConnectionTester::Experiment& experiment, scoped_ptr<net::ProxyConfigService>* proxy_config_service, net::NetLog* net_log) { int rv; // Create a custom HostResolver for this experiment. scoped_ptr<net::HostResolver> host_resolver_tmp; rv = CreateHostResolver(experiment.host_resolver_experiment, &host_resolver_tmp); if (rv != net::OK) return rv; // Failure. storage_.set_host_resolver(host_resolver_tmp.Pass()); // Create a custom ProxyService for this this experiment. scoped_ptr<net::ProxyService> experiment_proxy_service; rv = CreateProxyService(experiment.proxy_settings_experiment, proxy_config_service, &experiment_proxy_service); if (rv != net::OK) return rv; // Failure. storage_.set_proxy_service(experiment_proxy_service.release()); // The rest of the dependencies are standard, and don't depend on the // experiment being run. storage_.set_cert_verifier(net::CertVerifier::CreateDefault()); storage_.set_transport_security_state(new net::TransportSecurityState); storage_.set_ssl_config_service(new net::SSLConfigServiceDefaults); storage_.set_http_auth_handler_factory( net::HttpAuthHandlerFactory::CreateDefault(host_resolver())); storage_.set_http_server_properties( scoped_ptr<net::HttpServerProperties>( new net::HttpServerPropertiesImpl())); net::HttpNetworkSession::Params session_params; session_params.host_resolver = host_resolver(); session_params.cert_verifier = cert_verifier(); session_params.transport_security_state = transport_security_state(); session_params.proxy_service = proxy_service(); session_params.ssl_config_service = ssl_config_service(); session_params.http_auth_handler_factory = http_auth_handler_factory(); session_params.http_server_properties = http_server_properties(); session_params.net_log = net_log; scoped_refptr<net::HttpNetworkSession> network_session( new net::HttpNetworkSession(session_params)); storage_.set_http_transaction_factory(new net::HttpCache( network_session.get(), net::HttpCache::DefaultBackend::InMemory(0))); // In-memory cookie store. storage_.set_cookie_store(new net::CookieMonster(NULL, NULL)); return net::OK; } private: // Creates a host resolver for |experiment|. On success returns net::OK and // fills |host_resolver| with a new pointer. Otherwise returns a network // error code. int CreateHostResolver( ConnectionTester::HostResolverExperiment experiment, scoped_ptr<net::HostResolver>* host_resolver) { // Create a vanilla HostResolver that disables caching. const size_t kMaxJobs = 50u; const size_t kMaxRetryAttempts = 4u; net::HostResolver::Options options; options.max_concurrent_resolves = kMaxJobs; options.max_retry_attempts = kMaxRetryAttempts; options.enable_caching = false; scoped_ptr<net::HostResolver> resolver( net::HostResolver::CreateSystemResolver(options, NULL /* NetLog */)); // Modify it slightly based on the experiment being run. switch (experiment) { case ConnectionTester::HOST_RESOLVER_EXPERIMENT_PLAIN: break; case ConnectionTester::HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6: resolver->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4); break; case ConnectionTester::HOST_RESOLVER_EXPERIMENT_IPV6_PROBE: { // The system HostResolver will probe by default. break; } default: NOTREACHED(); return net::ERR_UNEXPECTED; } host_resolver->swap(resolver); return net::OK; } // Creates a proxy service for |experiment|. On success returns net::OK // and fills |experiment_proxy_service| with a new pointer. Otherwise returns // a network error code. int CreateProxyService( ConnectionTester::ProxySettingsExperiment experiment, scoped_ptr<net::ProxyConfigService>* proxy_config_service, scoped_ptr<net::ProxyService>* experiment_proxy_service) { if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kSingleProcess)) { // We can't create a standard proxy resolver in single-process mode. // Rather than falling-back to some other implementation, fail. return net::ERR_NOT_IMPLEMENTED; } net::DhcpProxyScriptFetcherFactory dhcp_factory; if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableDhcpWpad)) { dhcp_factory.set_enabled(false); } #if defined(OS_IOS) experiment_proxy_service->reset( net::ProxyService::CreateUsingSystemProxyResolver( proxy_config_service->release(), 0u, NULL)); #else experiment_proxy_service->reset( net::CreateProxyServiceUsingV8ProxyResolver( proxy_config_service->release(), new net::ProxyScriptFetcherImpl(proxy_request_context_), dhcp_factory.Create(proxy_request_context_), host_resolver(), NULL, NULL)); #endif return net::OK; } // Creates a proxy config service that pulls from the system proxy settings. // On success returns net::OK and fills |config_service| with a new pointer. // Otherwise returns a network error code. int CreateSystemProxyConfigService( scoped_ptr<net::ProxyConfigService>* config_service) { #if defined(OS_LINUX) || defined(OS_OPENBSD) // TODO(eroman): This is not supported on Linux yet, because of how // construction needs ot happen on the UI thread. return net::ERR_NOT_IMPLEMENTED; #else config_service->reset(net::ProxyService::CreateSystemProxyConfigService( base::ThreadTaskRunnerHandle::Get().get(), NULL)); return net::OK; #endif } #if !defined(OS_ANDROID) && !defined(OS_IOS) static int FirefoxProxySettingsTask( FirefoxProxySettings* firefox_settings) { if (!FirefoxProxySettings::GetSettings(firefox_settings)) return net::ERR_FILE_NOT_FOUND; return net::OK; } void FirefoxProxySettingsReply( scoped_ptr<net::ProxyConfigService>* config_service, FirefoxProxySettings* firefox_settings, base::Callback<void(int)> callback, int rv) { if (rv == net::OK) { if (FirefoxProxySettings::SYSTEM == firefox_settings->config_type()) { rv = CreateSystemProxyConfigService(config_service); } else { net::ProxyConfig config; if (firefox_settings->ToProxyConfig(&config)) config_service->reset(new net::ProxyConfigServiceFixed(config)); else rv = net::ERR_FAILED; } } callback.Run(rv); } #endif // Creates a fixed proxy config service that is initialized using Firefox's // current proxy settings. On success returns net::OK and fills // |config_service| with a new pointer. Otherwise returns a network error // code. int CreateFirefoxProxyConfigService( scoped_ptr<net::ProxyConfigService>* config_service, base::Callback<void(int)> callback) { #if defined(OS_ANDROID) || defined(OS_IOS) // Chrome on Android and iOS do not support Firefox settings. return net::ERR_NOT_IMPLEMENTED; #else // Fetch Firefox's proxy settings (can fail if Firefox is not installed). FirefoxProxySettings* ff_settings = new FirefoxProxySettings(); base::Callback<int(void)> task = base::Bind( &FirefoxProxySettingsTask, ff_settings); base::Callback<void(int)> reply = base::Bind( &ExperimentURLRequestContext::FirefoxProxySettingsReply, weak_factory_.GetWeakPtr(), config_service, base::Owned(ff_settings), callback); if (!content::BrowserThread::PostTaskAndReplyWithResult<int>( content::BrowserThread::FILE, FROM_HERE, task, reply)) return net::ERR_FAILED; return net::ERR_IO_PENDING; #endif } #if !defined(OS_IOS) net::URLRequestContext* const proxy_request_context_; #endif net::URLRequestContextStorage storage_; base::WeakPtrFactory<ExperimentURLRequestContext> weak_factory_; }; } // namespace // ConnectionTester::TestRunner ---------------------------------------------- // TestRunner is a helper class for running an individual experiment. It can // be deleted any time after it is started, and this will abort the request. class ConnectionTester::TestRunner : public net::URLRequest::Delegate { public: // |tester| must remain alive throughout the TestRunner's lifetime. // |tester| will be notified of completion. TestRunner(ConnectionTester* tester, net::NetLog* net_log) : tester_(tester), net_log_(net_log), weak_factory_(this) {} // Finish running |experiment| once a ProxyConfigService has been created. // In the case of a FirefoxProxyConfigService, this will be called back // after disk access has completed. void ProxyConfigServiceCreated( const Experiment& experiment, scoped_ptr<net::ProxyConfigService>* proxy_config_service, int status); // Starts running |experiment|. Notifies tester->OnExperimentCompleted() when // it is done. void Run(const Experiment& experiment); // Overridden from net::URLRequest::Delegate: virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; virtual void OnReadCompleted(net::URLRequest* request, int bytes_read) OVERRIDE; // TODO(eroman): handle cases requiring authentication. private: // The number of bytes to read each response body chunk. static const int kReadBufferSize = 1024; // Starts reading the response's body (and keeps reading until an error or // end of stream). void ReadBody(net::URLRequest* request); // Called when the request has completed (for both success and failure). void OnResponseCompleted(net::URLRequest* request); void OnExperimentCompletedWithResult(int result); ConnectionTester* tester_; scoped_ptr<ExperimentURLRequestContext> request_context_; scoped_ptr<net::URLRequest> request_; net::NetLog* net_log_; base::WeakPtrFactory<TestRunner> weak_factory_; DISALLOW_COPY_AND_ASSIGN(TestRunner); }; void ConnectionTester::TestRunner::OnResponseStarted(net::URLRequest* request) { if (!request->status().is_success()) { OnResponseCompleted(request); return; } // Start reading the body. ReadBody(request); } void ConnectionTester::TestRunner::OnReadCompleted(net::URLRequest* request, int bytes_read) { if (bytes_read <= 0) { OnResponseCompleted(request); return; } // Keep reading until the stream is closed. Throw the data read away. ReadBody(request); } void ConnectionTester::TestRunner::ReadBody(net::URLRequest* request) { // Read the response body |kReadBufferSize| bytes at a time. scoped_refptr<net::IOBuffer> unused_buffer( new net::IOBuffer(kReadBufferSize)); int num_bytes; if (request->Read(unused_buffer.get(), kReadBufferSize, &num_bytes)) { OnReadCompleted(request, num_bytes); } else if (!request->status().is_io_pending()) { // Read failed synchronously. OnResponseCompleted(request); } } void ConnectionTester::TestRunner::OnResponseCompleted( net::URLRequest* request) { int result = net::OK; if (!request->status().is_success()) { DCHECK_NE(net::ERR_IO_PENDING, request->status().error()); result = request->status().error(); } // Post a task to notify the parent rather than handling it right away, // to avoid re-entrancy problems with URLRequest. (Don't want the caller // to end up deleting the URLRequest while in the middle of processing). base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&TestRunner::OnExperimentCompletedWithResult, weak_factory_.GetWeakPtr(), result)); } void ConnectionTester::TestRunner::OnExperimentCompletedWithResult(int result) { tester_->OnExperimentCompleted(result); } void ConnectionTester::TestRunner::ProxyConfigServiceCreated( const Experiment& experiment, scoped_ptr<net::ProxyConfigService>* proxy_config_service, int status) { if (status == net::OK) status = request_context_->Init(experiment, proxy_config_service, net_log_); if (status != net::OK) { tester_->OnExperimentCompleted(status); return; } // Fetch a request using the experimental context. request_ = request_context_->CreateRequest( experiment.url, net::DEFAULT_PRIORITY, this); request_->Start(); } void ConnectionTester::TestRunner::Run(const Experiment& experiment) { // Try to create a net::URLRequestContext for this experiment. request_context_.reset( new ExperimentURLRequestContext(tester_->proxy_request_context_)); scoped_ptr<net::ProxyConfigService>* proxy_config_service = new scoped_ptr<net::ProxyConfigService>(); base::Callback<void(int)> config_service_callback = base::Bind( &TestRunner::ProxyConfigServiceCreated, weak_factory_.GetWeakPtr(), experiment, base::Owned(proxy_config_service)); int rv = request_context_->CreateProxyConfigService( experiment.proxy_settings_experiment, proxy_config_service, config_service_callback); if (rv != net::ERR_IO_PENDING) ProxyConfigServiceCreated(experiment, proxy_config_service, rv); } // ConnectionTester ---------------------------------------------------------- ConnectionTester::ConnectionTester( Delegate* delegate, net::URLRequestContext* proxy_request_context, net::NetLog* net_log) : delegate_(delegate), proxy_request_context_(proxy_request_context), net_log_(net_log) { DCHECK(delegate); DCHECK(proxy_request_context); } ConnectionTester::~ConnectionTester() { // Cancellation happens automatically by deleting test_runner_. } void ConnectionTester::RunAllTests(const GURL& url) { // Select all possible experiments to run. (In no particular order). // It is possible that some of these experiments are actually duplicates. GetAllPossibleExperimentCombinations(url, &remaining_experiments_); delegate_->OnStartConnectionTestSuite(); StartNextExperiment(); } // static base::string16 ConnectionTester::ProxySettingsExperimentDescription( ProxySettingsExperiment experiment) { // TODO(eroman): Use proper string resources. switch (experiment) { case PROXY_EXPERIMENT_USE_DIRECT: return ASCIIToUTF16("Don't use any proxy"); case PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS: return ASCIIToUTF16("Use system proxy settings"); case PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS: return ASCIIToUTF16("Use Firefox's proxy settings"); case PROXY_EXPERIMENT_USE_AUTO_DETECT: return ASCIIToUTF16("Auto-detect proxy settings"); default: NOTREACHED(); return base::string16(); } } // static base::string16 ConnectionTester::HostResolverExperimentDescription( HostResolverExperiment experiment) { // TODO(eroman): Use proper string resources. switch (experiment) { case HOST_RESOLVER_EXPERIMENT_PLAIN: return base::string16(); case HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6: return ASCIIToUTF16("Disable IPv6 host resolving"); case HOST_RESOLVER_EXPERIMENT_IPV6_PROBE: return ASCIIToUTF16("Probe for IPv6 host resolving"); default: NOTREACHED(); return base::string16(); } } // static void ConnectionTester::GetAllPossibleExperimentCombinations( const GURL& url, ConnectionTester::ExperimentList* list) { list->clear(); for (size_t resolver_experiment = 0; resolver_experiment < HOST_RESOLVER_EXPERIMENT_COUNT; ++resolver_experiment) { for (size_t proxy_experiment = 0; proxy_experiment < PROXY_EXPERIMENT_COUNT; ++proxy_experiment) { Experiment experiment( url, static_cast<ProxySettingsExperiment>(proxy_experiment), static_cast<HostResolverExperiment>(resolver_experiment)); list->push_back(experiment); } } } void ConnectionTester::StartNextExperiment() { DCHECK(!remaining_experiments_.empty()); DCHECK(!current_test_runner_.get()); delegate_->OnStartConnectionTestExperiment(current_experiment()); current_test_runner_.reset(new TestRunner(this, net_log_)); current_test_runner_->Run(current_experiment()); } void ConnectionTester::OnExperimentCompleted(int result) { Experiment current = current_experiment(); // Advance to the next experiment. remaining_experiments_.erase(remaining_experiments_.begin()); current_test_runner_.reset(); // Notify the delegate of completion. delegate_->OnCompletedConnectionTestExperiment(current, result); if (remaining_experiments_.empty()) { delegate_->OnCompletedConnectionTestSuite(); } else { StartNextExperiment(); } }