connection_tester.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/net/connection_tester.h"
6
7#include "base/command_line.h"
8#include "base/compiler_specific.h"
9#include "base/logging.h"
10#include "base/message_loop.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/importer/firefox_proxy_settings.h"
13#include "chrome/browser/io_thread.h"
14#include "chrome/common/chrome_switches.h"
15#include "net/base/cookie_monster.h"
16#include "net/base/dnsrr_resolver.h"
17#include "net/base/host_resolver.h"
18#include "net/base/host_resolver_impl.h"
19#include "net/base/io_buffer.h"
20#include "net/base/net_errors.h"
21#include "net/base/net_util.h"
22#include "net/base/ssl_config_service_defaults.h"
23#include "net/disk_cache/disk_cache.h"
24#include "net/ftp/ftp_network_layer.h"
25#include "net/http/http_auth_handler_factory.h"
26#include "net/http/http_cache.h"
27#include "net/http/http_network_layer.h"
28#include "net/proxy/proxy_config_service_fixed.h"
29#include "net/url_request/url_request.h"
30#include "net/url_request/url_request_context.h"
31
32namespace {
33
34// ExperimentURLRequestContext ------------------------------------------------
35
36// An instance of ExperimentURLRequestContext is created for each experiment
37// run by ConnectionTester. The class initializes network dependencies according
38// to the specified "experiment".
39class ExperimentURLRequestContext : public URLRequestContext {
40 public:
41  explicit ExperimentURLRequestContext(IOThread* io_thread)
42      : io_thread_(io_thread) {}
43
44  int Init(const ConnectionTester::Experiment& experiment) {
45    int rv;
46
47    // Create a custom HostResolver for this experiment.
48    rv = CreateHostResolver(experiment.host_resolver_experiment,
49                            &host_resolver_);
50    if (rv != net::OK)
51      return rv;  // Failure.
52
53    // Create a custom ProxyService for this this experiment.
54    rv = CreateProxyService(experiment.proxy_settings_experiment,
55                            &proxy_service_);
56    if (rv != net::OK)
57      return rv;  // Failure.
58
59    // The rest of the dependencies are standard, and don't depend on the
60    // experiment being run.
61    dnsrr_resolver_ = new net::DnsRRResolver;
62    ftp_transaction_factory_ = new net::FtpNetworkLayer(host_resolver_);
63    ssl_config_service_ = new net::SSLConfigServiceDefaults;
64    http_auth_handler_factory_ = net::HttpAuthHandlerFactory::CreateDefault(
65        host_resolver_);
66    http_transaction_factory_ = new net::HttpCache(
67        net::HttpNetworkLayer::CreateFactory(host_resolver_, dnsrr_resolver_,
68            NULL /* dns_cert_checker */,
69            NULL /* ssl_host_info_factory */, proxy_service_,
70            ssl_config_service_, http_auth_handler_factory_, NULL, NULL),
71        net::HttpCache::DefaultBackend::InMemory(0));
72    // In-memory cookie store.
73    cookie_store_ = new net::CookieMonster(NULL, NULL);
74
75    return net::OK;
76  }
77
78 protected:
79  virtual ~ExperimentURLRequestContext() {
80    delete ftp_transaction_factory_;
81    delete http_transaction_factory_;
82    delete http_auth_handler_factory_;
83    delete dnsrr_resolver_;
84    delete host_resolver_;
85  }
86
87 private:
88  // Creates a host resolver for |experiment|. On success returns net::OK and
89  // fills |host_resolver| with a new pointer. Otherwise returns a network
90  // error code.
91  int CreateHostResolver(
92      ConnectionTester::HostResolverExperiment experiment,
93      net::HostResolver** host_resolver) {
94    // Create a vanilla HostResolver that disables caching.
95    const size_t kMaxJobs = 50u;
96    net::HostResolverImpl* impl =
97        new net::HostResolverImpl(NULL, NULL, kMaxJobs, NULL);
98
99    *host_resolver = impl;
100
101    // Modify it slightly based on the experiment being run.
102    switch (experiment) {
103      case ConnectionTester::HOST_RESOLVER_EXPERIMENT_PLAIN:
104        return net::OK;
105      case ConnectionTester::HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6:
106        impl->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4);
107        return net::OK;
108      case ConnectionTester::HOST_RESOLVER_EXPERIMENT_IPV6_PROBE: {
109        // Note that we don't use impl->ProbeIPv6Support() since that finishes
110        // asynchronously and may not take effect in time for the test.
111        // So instead we will probe synchronously (might take 100-200 ms).
112        net::AddressFamily family = net::IPv6Supported() ?
113            net::ADDRESS_FAMILY_UNSPECIFIED : net::ADDRESS_FAMILY_IPV4;
114        impl->SetDefaultAddressFamily(family);
115        return net::OK;
116      }
117      default:
118        NOTREACHED();
119        return net::ERR_UNEXPECTED;
120    }
121  }
122
123  // Creates a proxy config service for |experiment|. On success returns net::OK
124  // and fills |config_service| with a new pointer. Otherwise returns a network
125  // error code.
126  int CreateProxyConfigService(
127      ConnectionTester::ProxySettingsExperiment experiment,
128      scoped_ptr<net::ProxyConfigService>* config_service) {
129    switch (experiment) {
130      case ConnectionTester::PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS:
131        return CreateSystemProxyConfigService(config_service);
132      case ConnectionTester::PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS:
133        return CreateFirefoxProxyConfigService(config_service);
134      case ConnectionTester::PROXY_EXPERIMENT_USE_AUTO_DETECT:
135        config_service->reset(new net::ProxyConfigServiceFixed(
136            net::ProxyConfig::CreateAutoDetect()));
137        return net::OK;
138      case ConnectionTester::PROXY_EXPERIMENT_USE_DIRECT:
139        config_service->reset(new net::ProxyConfigServiceFixed(
140            net::ProxyConfig::CreateDirect()));
141        return net::OK;
142      default:
143        NOTREACHED();
144        return net::ERR_UNEXPECTED;
145    }
146  }
147
148  // Creates a proxy service for |experiment|. On success returns net::OK
149  // and fills |config_service| with a new pointer. Otherwise returns a network
150  // error code.
151  int CreateProxyService(
152      ConnectionTester::ProxySettingsExperiment experiment,
153      scoped_refptr<net::ProxyService>* proxy_service) {
154    // Create an appropriate proxy config service.
155    scoped_ptr<net::ProxyConfigService> config_service;
156    int rv = CreateProxyConfigService(experiment, &config_service);
157    if (rv != net::OK)
158      return rv;  // Failure.
159
160    if (CommandLine::ForCurrentProcess()->HasSwitch(
161        switches::kSingleProcess)) {
162      // We can't create a standard proxy resolver in single-process mode.
163      // Rather than falling-back to some other implementation, fail.
164      return net::ERR_NOT_IMPLEMENTED;
165    }
166
167    *proxy_service = net::ProxyService::CreateUsingV8ProxyResolver(
168        config_service.release(),
169        0u,
170        io_thread_->CreateAndRegisterProxyScriptFetcher(this),
171        host_resolver(),
172        NULL);
173
174    return net::OK;
175  }
176
177  // Creates a proxy config service that pulls from the system proxy settings.
178  // On success returns net::OK and fills |config_service| with a new pointer.
179  // Otherwise returns a network error code.
180  int CreateSystemProxyConfigService(
181      scoped_ptr<net::ProxyConfigService>* config_service) {
182#if defined(OS_LINUX)
183    // TODO(eroman): This is not supported on Linux yet, because of how
184    // construction needs ot happen on the UI thread.
185    return net::ERR_NOT_IMPLEMENTED;
186#else
187    config_service->reset(
188        net::ProxyService::CreateSystemProxyConfigService(
189            MessageLoop::current(), NULL));
190    return net::OK;
191#endif
192  }
193
194  // Creates a fixed proxy config service that is initialized using Firefox's
195  // current proxy settings. On success returns net::OK and fills
196  // |config_service| with a new pointer. Otherwise returns a network error
197  // code.
198  int CreateFirefoxProxyConfigService(
199      scoped_ptr<net::ProxyConfigService>* config_service) {
200    // Fetch Firefox's proxy settings (can fail if Firefox is not installed).
201    FirefoxProxySettings firefox_settings;
202    if (!FirefoxProxySettings::GetSettings(&firefox_settings))
203      return net::ERR_FILE_NOT_FOUND;
204
205    if (FirefoxProxySettings::SYSTEM == firefox_settings.config_type())
206      return CreateSystemProxyConfigService(config_service);
207
208    net::ProxyConfig config;
209    if (firefox_settings.ToProxyConfig(&config)) {
210      config_service->reset(new net::ProxyConfigServiceFixed(config));
211      return net::OK;
212    }
213
214    return net::ERR_FAILED;
215  }
216
217  IOThread* io_thread_;
218};
219
220}  // namespace
221
222// ConnectionTester::TestRunner ----------------------------------------------
223
224// TestRunner is a helper class for running an individual experiment. It can
225// be deleted any time after it is started, and this will abort the request.
226class ConnectionTester::TestRunner : public URLRequest::Delegate {
227 public:
228  // |tester| must remain alive throughout the TestRunner's lifetime.
229  // |tester| will be notified of completion.
230  explicit TestRunner(ConnectionTester* tester) : tester_(tester) {}
231
232  // Starts running |experiment|. Notifies tester->OnExperimentCompleted() when
233  // it is done.
234  void Run(const Experiment& experiment);
235
236  // URLRequest::Delegate implementation.
237  virtual void OnResponseStarted(URLRequest* request);
238  virtual void OnReadCompleted(URLRequest* request, int bytes_read);
239  // TODO(eroman): handle cases requiring authentication.
240
241 private:
242  // The number of bytes to read each response body chunk.
243  static const int kReadBufferSize = 1024;
244
245  // Starts reading the response's body (and keeps reading until an error or
246  // end of stream).
247  void ReadBody(URLRequest* request);
248
249  // Called when the request has completed (for both success and failure).
250  void OnResponseCompleted(URLRequest* request);
251
252  ConnectionTester* tester_;
253  scoped_ptr<URLRequest> request_;
254
255  DISALLOW_COPY_AND_ASSIGN(TestRunner);
256};
257
258void ConnectionTester::TestRunner::OnResponseStarted(URLRequest* request) {
259  if (!request->status().is_success()) {
260    OnResponseCompleted(request);
261    return;
262  }
263
264  // Start reading the body.
265  ReadBody(request);
266}
267
268void ConnectionTester::TestRunner::OnReadCompleted(URLRequest* request,
269                                                   int bytes_read) {
270  if (bytes_read <= 0) {
271    OnResponseCompleted(request);
272    return;
273  }
274
275  // Keep reading until the stream is closed. Throw the data read away.
276  ReadBody(request);
277}
278
279void ConnectionTester::TestRunner::ReadBody(URLRequest* request) {
280  // Read the response body |kReadBufferSize| bytes at a time.
281  scoped_refptr<net::IOBuffer> unused_buffer(
282      new net::IOBuffer(kReadBufferSize));
283  int num_bytes;
284  if (request->Read(unused_buffer, kReadBufferSize, &num_bytes)) {
285    OnReadCompleted(request, num_bytes);
286  } else if (!request->status().is_io_pending()) {
287    // Read failed synchronously.
288    OnResponseCompleted(request);
289  }
290}
291
292void ConnectionTester::TestRunner::OnResponseCompleted(URLRequest* request) {
293  int result = net::OK;
294  if (!request->status().is_success()) {
295    DCHECK_NE(net::ERR_IO_PENDING, request->status().os_error());
296    result = request->status().os_error();
297  }
298  tester_->OnExperimentCompleted(result);
299}
300
301void ConnectionTester::TestRunner::Run(const Experiment& experiment) {
302  // Try to create a URLRequestContext for this experiment.
303  scoped_refptr<ExperimentURLRequestContext> context(
304      new ExperimentURLRequestContext(tester_->io_thread_));
305  int rv = context->Init(experiment);
306  if (rv != net::OK) {
307    // Complete the experiment with a failure.
308    tester_->OnExperimentCompleted(rv);
309    return;
310  }
311
312  // Fetch a request using the experimental context.
313  request_.reset(new URLRequest(experiment.url, this));
314  request_->set_context(context);
315  request_->Start();
316}
317
318// ConnectionTester ----------------------------------------------------------
319
320ConnectionTester::ConnectionTester(Delegate* delegate, IOThread* io_thread)
321    : delegate_(delegate), io_thread_(io_thread) {
322  DCHECK(delegate);
323  DCHECK(io_thread);
324}
325
326ConnectionTester::~ConnectionTester() {
327  // Cancellation happens automatically by deleting test_runner_.
328}
329
330void ConnectionTester::RunAllTests(const GURL& url) {
331  // Select all possible experiments to run. (In no particular order).
332  // It is possible that some of these experiments are actually duplicates.
333  GetAllPossibleExperimentCombinations(url, &remaining_experiments_);
334
335  delegate_->OnStartConnectionTestSuite();
336  StartNextExperiment();
337}
338
339// static
340string16 ConnectionTester::ProxySettingsExperimentDescription(
341    ProxySettingsExperiment experiment) {
342  // TODO(eroman): Use proper string resources.
343  switch (experiment) {
344    case PROXY_EXPERIMENT_USE_DIRECT:
345      return ASCIIToUTF16("Don't use any proxy");
346    case PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS:
347      return ASCIIToUTF16("Use system proxy settings");
348    case PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS:
349      return ASCIIToUTF16("Use Firefox's proxy settings");
350    case PROXY_EXPERIMENT_USE_AUTO_DETECT:
351      return ASCIIToUTF16("Auto-detect proxy settings");
352    default:
353      NOTREACHED();
354      return string16();
355  }
356}
357
358// static
359string16 ConnectionTester::HostResolverExperimentDescription(
360    HostResolverExperiment experiment) {
361  // TODO(eroman): Use proper string resources.
362  switch (experiment) {
363    case HOST_RESOLVER_EXPERIMENT_PLAIN:
364      return string16();
365    case HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6:
366      return ASCIIToUTF16("Disable IPv6 host resolving");
367    case HOST_RESOLVER_EXPERIMENT_IPV6_PROBE:
368      return ASCIIToUTF16("Probe for IPv6 host resolving");
369    default:
370      NOTREACHED();
371      return string16();
372  }
373}
374
375// static
376void ConnectionTester::GetAllPossibleExperimentCombinations(
377    const GURL& url,
378    ConnectionTester::ExperimentList* list) {
379  list->clear();
380  for (size_t resolver_experiment = 0;
381       resolver_experiment < HOST_RESOLVER_EXPERIMENT_COUNT;
382       ++resolver_experiment) {
383    for (size_t proxy_experiment = 0;
384         proxy_experiment < PROXY_EXPERIMENT_COUNT;
385         ++proxy_experiment) {
386      Experiment experiment(
387          url,
388          static_cast<ProxySettingsExperiment>(proxy_experiment),
389          static_cast<HostResolverExperiment>(resolver_experiment));
390      list->push_back(experiment);
391    }
392  }
393}
394
395void ConnectionTester::StartNextExperiment() {
396  DCHECK(!remaining_experiments_.empty());
397  DCHECK(!current_test_runner_.get());
398
399  delegate_->OnStartConnectionTestExperiment(current_experiment());
400
401  current_test_runner_.reset(new TestRunner(this));
402  current_test_runner_->Run(current_experiment());
403}
404
405void ConnectionTester::OnExperimentCompleted(int result) {
406  Experiment current = current_experiment();
407
408  // Advance to the next experiment.
409  remaining_experiments_.erase(remaining_experiments_.begin());
410  current_test_runner_.reset();
411
412  // Notify the delegate of completion.
413  delegate_->OnCompletedConnectionTestExperiment(current, result);
414
415  if (remaining_experiments_.empty()) {
416    delegate_->OnCompletedConnectionTestSuite();
417  } else {
418    StartNextExperiment();
419  }
420}
421