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