// Copyright (c) 2011 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/command_line.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/task.h" #include "base/threading/thread_restrictions.h" #include "base/utf_string_conversions.h" #include "chrome/browser/importer/firefox_proxy_settings.h" #include "chrome/common/chrome_switches.h" #include "net/base/cert_verifier.h" #include "net/base/cookie_monster.h" #include "net/base/dnsrr_resolver.h" #include "net/base/host_resolver.h" #include "net/base/host_resolver_impl.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/base/ssl_config_service_defaults.h" #include "net/ftp/ftp_network_layer.h" #include "net/http/http_auth_handler_factory.h" #include "net/http/http_cache.h" #include "net/http/http_network_session.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/proxy/proxy_script_fetcher_impl.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" 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) : proxy_request_context_(proxy_request_context) {} int Init(const ConnectionTester::Experiment& experiment) { int rv; // Create a custom HostResolver for this experiment. net::HostResolver* host_resolver_tmp = NULL; rv = CreateHostResolver(experiment.host_resolver_experiment, &host_resolver_tmp); if (rv != net::OK) return rv; // Failure. set_host_resolver(host_resolver_tmp); // Create a custom ProxyService for this this experiment. scoped_refptr<net::ProxyService> proxy_service_tmp = NULL; rv = CreateProxyService(experiment.proxy_settings_experiment, &proxy_service_tmp); if (rv != net::OK) return rv; // Failure. set_proxy_service(proxy_service_tmp); // The rest of the dependencies are standard, and don't depend on the // experiment being run. set_cert_verifier(new net::CertVerifier); set_dnsrr_resolver(new net::DnsRRResolver); set_ftp_transaction_factory(new net::FtpNetworkLayer(host_resolver_tmp)); set_ssl_config_service(new net::SSLConfigServiceDefaults); set_http_auth_handler_factory(net::HttpAuthHandlerFactory::CreateDefault( host_resolver_tmp)); net::HttpNetworkSession::Params session_params; session_params.host_resolver = host_resolver_tmp; session_params.dnsrr_resolver = dnsrr_resolver(); session_params.cert_verifier = cert_verifier(); session_params.proxy_service = proxy_service_tmp; session_params.http_auth_handler_factory = http_auth_handler_factory(); session_params.ssl_config_service = ssl_config_service(); scoped_refptr<net::HttpNetworkSession> network_session( new net::HttpNetworkSession(session_params)); set_http_transaction_factory(new net::HttpCache( network_session, net::HttpCache::DefaultBackend::InMemory(0))); // In-memory cookie store. set_cookie_store(new net::CookieMonster(NULL, NULL)); return net::OK; } protected: virtual ~ExperimentURLRequestContext() { delete ftp_transaction_factory(); delete http_transaction_factory(); delete http_auth_handler_factory(); delete dnsrr_resolver(); delete cert_verifier(); delete host_resolver(); } 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, net::HostResolver** host_resolver) { // Create a vanilla HostResolver that disables caching. const size_t kMaxJobs = 50u; net::HostResolverImpl* impl = new net::HostResolverImpl(NULL, NULL, kMaxJobs, NULL); *host_resolver = impl; // Modify it slightly based on the experiment being run. switch (experiment) { case ConnectionTester::HOST_RESOLVER_EXPERIMENT_PLAIN: return net::OK; case ConnectionTester::HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6: impl->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4); return net::OK; case ConnectionTester::HOST_RESOLVER_EXPERIMENT_IPV6_PROBE: { // Note that we don't use impl->ProbeIPv6Support() since that finishes // asynchronously and may not take effect in time for the test. // So instead we will probe synchronously (might take 100-200 ms). net::AddressFamily family = net::IPv6Supported() ? net::ADDRESS_FAMILY_UNSPECIFIED : net::ADDRESS_FAMILY_IPV4; impl->SetDefaultAddressFamily(family); return net::OK; } default: NOTREACHED(); return net::ERR_UNEXPECTED; } } // 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) { scoped_ptr<base::ThreadRestrictions::ScopedAllowIO> allow_io; switch (experiment) { case ConnectionTester::PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS: return CreateSystemProxyConfigService(config_service); case ConnectionTester::PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS: // http://crbug.com/67664: This call can lead to blocking IO on the IO // thread. This is a bug and should be fixed. allow_io.reset(new base::ThreadRestrictions::ScopedAllowIO); return CreateFirefoxProxyConfigService(config_service); 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; } } // Creates a proxy service for |experiment|. On success returns net::OK // and fills |config_service| with a new pointer. Otherwise returns a network // error code. int CreateProxyService( ConnectionTester::ProxySettingsExperiment experiment, scoped_refptr<net::ProxyService>* proxy_service) { // Create an appropriate proxy config service. scoped_ptr<net::ProxyConfigService> config_service; int rv = CreateProxyConfigService(experiment, &config_service); if (rv != net::OK) return rv; // Failure. 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; } *proxy_service = net::ProxyService::CreateUsingV8ProxyResolver( config_service.release(), 0u, new net::ProxyScriptFetcherImpl(proxy_request_context_), host_resolver(), NULL); 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) // 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( MessageLoop::current(), NULL)); return net::OK; #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) { // Fetch Firefox's proxy settings (can fail if Firefox is not installed). FirefoxProxySettings firefox_settings; if (!FirefoxProxySettings::GetSettings(&firefox_settings)) return net::ERR_FILE_NOT_FOUND; if (FirefoxProxySettings::SYSTEM == firefox_settings.config_type()) return CreateSystemProxyConfigService(config_service); net::ProxyConfig config; if (firefox_settings.ToProxyConfig(&config)) { config_service->reset(new net::ProxyConfigServiceFixed(config)); return net::OK; } return net::ERR_FAILED; } const scoped_refptr<net::URLRequestContext> proxy_request_context_; }; } // 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. explicit TestRunner(ConnectionTester* tester) : tester_(tester), ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {} // 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); virtual void OnReadCompleted(net::URLRequest* request, int bytes_read); // 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<net::URLRequest> request_; ScopedRunnableMethodFactory<TestRunner> method_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, 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().os_error()); result = request->status().os_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). MessageLoop::current()->PostTask( FROM_HERE, method_factory_.NewRunnableMethod( &TestRunner::OnExperimentCompletedWithResult, result)); } void ConnectionTester::TestRunner::OnExperimentCompletedWithResult(int result) { tester_->OnExperimentCompleted(result); } void ConnectionTester::TestRunner::Run(const Experiment& experiment) { // Try to create a net::URLRequestContext for this experiment. scoped_refptr<ExperimentURLRequestContext> context( new ExperimentURLRequestContext(tester_->proxy_request_context_)); int rv = context->Init(experiment); if (rv != net::OK) { // Complete the experiment with a failure. tester_->OnExperimentCompleted(rv); return; } // Fetch a request using the experimental context. request_.reset(new net::URLRequest(experiment.url, this)); request_->set_context(context); request_->Start(); } // ConnectionTester ---------------------------------------------------------- ConnectionTester::ConnectionTester( Delegate* delegate, net::URLRequestContext* proxy_request_context) : delegate_(delegate), proxy_request_context_(proxy_request_context) { 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 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 string16(); } } // static string16 ConnectionTester::HostResolverExperimentDescription( HostResolverExperiment experiment) { // TODO(eroman): Use proper string resources. switch (experiment) { case HOST_RESOLVER_EXPERIMENT_PLAIN: return 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 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)); 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(); } }