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