// 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();
}
}