普通文本  |  544行  |  20.02 KB

// 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 "content/public/browser/cookie_store_factory.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/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"
#include "net/url_request/url_request_job_factory_impl.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(
        content::CreateCookieStore(content::CookieStoreConfig()));
    // Creating a new job factory avoids added ProtocolHandlers and
    // layered URLRequestInterceptingJobFactories.
    storage_.set_job_factory(new net::URLRequestJobFactoryImpl());

    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 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, NULL);
  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 base::ASCIIToUTF16("Don't use any proxy");
    case PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS:
      return base::ASCIIToUTF16("Use system proxy settings");
    case PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS:
      return base::ASCIIToUTF16("Use Firefox's proxy settings");
    case PROXY_EXPERIMENT_USE_AUTO_DETECT:
      return base::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 base::ASCIIToUTF16("Disable IPv6 host resolving");
    case HOST_RESOLVER_EXPERIMENT_IPV6_PROBE:
      return base::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();
  }
}