1 // Copyright (c) 2012 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/bind.h"
8 #include "base/command_line.h"
9 #include "base/compiler_specific.h"
10 #include "base/logging.h"
11 #include "base/memory/weak_ptr.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/thread_task_runner_handle.h"
15 #include "base/threading/thread_restrictions.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/cookie_store_factory.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/request_priority.h"
23 #include "net/cert/cert_verifier.h"
24 #include "net/dns/host_resolver.h"
25 #include "net/http/http_auth_handler_factory.h"
26 #include "net/http/http_cache.h"
27 #include "net/http/http_network_session.h"
28 #include "net/http/http_server_properties_impl.h"
29 #include "net/http/transport_security_state.h"
30 #include "net/proxy/dhcp_proxy_script_fetcher_factory.h"
31 #include "net/proxy/proxy_config_service_fixed.h"
32 #include "net/proxy/proxy_script_fetcher_impl.h"
33 #include "net/proxy/proxy_service.h"
34 #include "net/proxy/proxy_service_v8.h"
35 #include "net/ssl/ssl_config_service_defaults.h"
36 #include "net/url_request/url_request.h"
37 #include "net/url_request/url_request_context.h"
38 #include "net/url_request/url_request_context_storage.h"
39 #include "net/url_request/url_request_job_factory_impl.h"
40
41 #if !defined(OS_ANDROID) && !defined(OS_IOS)
42 #include "chrome/browser/net/firefox_proxy_settings.h"
43 #endif
44
45 namespace {
46
47 // ExperimentURLRequestContext ------------------------------------------------
48
49 // An instance of ExperimentURLRequestContext is created for each experiment
50 // run by ConnectionTester. The class initializes network dependencies according
51 // to the specified "experiment".
52 class ExperimentURLRequestContext : public net::URLRequestContext {
53 public:
ExperimentURLRequestContext(net::URLRequestContext * proxy_request_context)54 explicit ExperimentURLRequestContext(
55 net::URLRequestContext* proxy_request_context) :
56 #if !defined(OS_IOS)
57 proxy_request_context_(proxy_request_context),
58 #endif
59 storage_(this),
60 weak_factory_(this) {}
61
~ExperimentURLRequestContext()62 virtual ~ExperimentURLRequestContext() {}
63
64 // Creates a proxy config service for |experiment|. On success returns net::OK
65 // and fills |config_service| with a new pointer. Otherwise returns a network
66 // error code.
CreateProxyConfigService(ConnectionTester::ProxySettingsExperiment experiment,scoped_ptr<net::ProxyConfigService> * config_service,base::Callback<void (int)> callback)67 int CreateProxyConfigService(
68 ConnectionTester::ProxySettingsExperiment experiment,
69 scoped_ptr<net::ProxyConfigService>* config_service,
70 base::Callback<void(int)> callback) {
71 switch (experiment) {
72 case ConnectionTester::PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS:
73 return CreateSystemProxyConfigService(config_service);
74 case ConnectionTester::PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS:
75 return CreateFirefoxProxyConfigService(config_service, callback);
76 case ConnectionTester::PROXY_EXPERIMENT_USE_AUTO_DETECT:
77 config_service->reset(new net::ProxyConfigServiceFixed(
78 net::ProxyConfig::CreateAutoDetect()));
79 return net::OK;
80 case ConnectionTester::PROXY_EXPERIMENT_USE_DIRECT:
81 config_service->reset(new net::ProxyConfigServiceFixed(
82 net::ProxyConfig::CreateDirect()));
83 return net::OK;
84 default:
85 NOTREACHED();
86 return net::ERR_UNEXPECTED;
87 }
88 }
89
Init(const ConnectionTester::Experiment & experiment,scoped_ptr<net::ProxyConfigService> * proxy_config_service,net::NetLog * net_log)90 int Init(const ConnectionTester::Experiment& experiment,
91 scoped_ptr<net::ProxyConfigService>* proxy_config_service,
92 net::NetLog* net_log) {
93 int rv;
94
95 // Create a custom HostResolver for this experiment.
96 scoped_ptr<net::HostResolver> host_resolver_tmp;
97 rv = CreateHostResolver(experiment.host_resolver_experiment,
98 &host_resolver_tmp);
99 if (rv != net::OK)
100 return rv; // Failure.
101 storage_.set_host_resolver(host_resolver_tmp.Pass());
102
103 // Create a custom ProxyService for this this experiment.
104 scoped_ptr<net::ProxyService> experiment_proxy_service;
105 rv = CreateProxyService(experiment.proxy_settings_experiment,
106 proxy_config_service, &experiment_proxy_service);
107 if (rv != net::OK)
108 return rv; // Failure.
109 storage_.set_proxy_service(experiment_proxy_service.release());
110
111 // The rest of the dependencies are standard, and don't depend on the
112 // experiment being run.
113 storage_.set_cert_verifier(net::CertVerifier::CreateDefault());
114 storage_.set_transport_security_state(new net::TransportSecurityState);
115 storage_.set_ssl_config_service(new net::SSLConfigServiceDefaults);
116 storage_.set_http_auth_handler_factory(
117 net::HttpAuthHandlerFactory::CreateDefault(host_resolver()));
118 storage_.set_http_server_properties(
119 scoped_ptr<net::HttpServerProperties>(
120 new net::HttpServerPropertiesImpl()));
121
122 net::HttpNetworkSession::Params session_params;
123 session_params.host_resolver = host_resolver();
124 session_params.cert_verifier = cert_verifier();
125 session_params.transport_security_state = transport_security_state();
126 session_params.proxy_service = proxy_service();
127 session_params.ssl_config_service = ssl_config_service();
128 session_params.http_auth_handler_factory = http_auth_handler_factory();
129 session_params.http_server_properties = http_server_properties();
130 session_params.net_log = net_log;
131 scoped_refptr<net::HttpNetworkSession> network_session(
132 new net::HttpNetworkSession(session_params));
133 storage_.set_http_transaction_factory(new net::HttpCache(
134 network_session.get(), net::HttpCache::DefaultBackend::InMemory(0)));
135 // In-memory cookie store.
136 storage_.set_cookie_store(
137 content::CreateCookieStore(content::CookieStoreConfig()));
138 // Creating a new job factory avoids added ProtocolHandlers and
139 // layered URLRequestInterceptingJobFactories.
140 storage_.set_job_factory(new net::URLRequestJobFactoryImpl());
141
142 return net::OK;
143 }
144
145 private:
146 // Creates a host resolver for |experiment|. On success returns net::OK and
147 // fills |host_resolver| with a new pointer. Otherwise returns a network
148 // error code.
CreateHostResolver(ConnectionTester::HostResolverExperiment experiment,scoped_ptr<net::HostResolver> * host_resolver)149 int CreateHostResolver(
150 ConnectionTester::HostResolverExperiment experiment,
151 scoped_ptr<net::HostResolver>* host_resolver) {
152 // Create a vanilla HostResolver that disables caching.
153 const size_t kMaxJobs = 50u;
154 const size_t kMaxRetryAttempts = 4u;
155 net::HostResolver::Options options;
156 options.max_concurrent_resolves = kMaxJobs;
157 options.max_retry_attempts = kMaxRetryAttempts;
158 options.enable_caching = false;
159 scoped_ptr<net::HostResolver> resolver(
160 net::HostResolver::CreateSystemResolver(options, NULL /* NetLog */));
161
162 // Modify it slightly based on the experiment being run.
163 switch (experiment) {
164 case ConnectionTester::HOST_RESOLVER_EXPERIMENT_PLAIN:
165 break;
166 case ConnectionTester::HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6:
167 resolver->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4);
168 break;
169 case ConnectionTester::HOST_RESOLVER_EXPERIMENT_IPV6_PROBE: {
170 // The system HostResolver will probe by default.
171 break;
172 }
173 default:
174 NOTREACHED();
175 return net::ERR_UNEXPECTED;
176 }
177 host_resolver->swap(resolver);
178 return net::OK;
179 }
180
181 // Creates a proxy service for |experiment|. On success returns net::OK
182 // and fills |experiment_proxy_service| with a new pointer. Otherwise returns
183 // a network error code.
CreateProxyService(ConnectionTester::ProxySettingsExperiment experiment,scoped_ptr<net::ProxyConfigService> * proxy_config_service,scoped_ptr<net::ProxyService> * experiment_proxy_service)184 int CreateProxyService(
185 ConnectionTester::ProxySettingsExperiment experiment,
186 scoped_ptr<net::ProxyConfigService>* proxy_config_service,
187 scoped_ptr<net::ProxyService>* experiment_proxy_service) {
188 if (CommandLine::ForCurrentProcess()->HasSwitch(
189 switches::kSingleProcess)) {
190 // We can't create a standard proxy resolver in single-process mode.
191 // Rather than falling-back to some other implementation, fail.
192 return net::ERR_NOT_IMPLEMENTED;
193 }
194
195 net::DhcpProxyScriptFetcherFactory dhcp_factory;
196
197 #if defined(OS_IOS)
198 experiment_proxy_service->reset(
199 net::ProxyService::CreateUsingSystemProxyResolver(
200 proxy_config_service->release(), 0u, NULL));
201 #else
202 experiment_proxy_service->reset(
203 net::CreateProxyServiceUsingV8ProxyResolver(
204 proxy_config_service->release(),
205 new net::ProxyScriptFetcherImpl(proxy_request_context_),
206 dhcp_factory.Create(proxy_request_context_),
207 host_resolver(),
208 NULL,
209 NULL));
210 #endif
211
212 return net::OK;
213 }
214
215 // Creates a proxy config service that pulls from the system proxy settings.
216 // On success returns net::OK and fills |config_service| with a new pointer.
217 // Otherwise returns a network error code.
CreateSystemProxyConfigService(scoped_ptr<net::ProxyConfigService> * config_service)218 int CreateSystemProxyConfigService(
219 scoped_ptr<net::ProxyConfigService>* config_service) {
220 #if defined(OS_LINUX) || defined(OS_OPENBSD)
221 // TODO(eroman): This is not supported on Linux yet, because of how
222 // construction needs ot happen on the UI thread.
223 return net::ERR_NOT_IMPLEMENTED;
224 #else
225 config_service->reset(net::ProxyService::CreateSystemProxyConfigService(
226 base::ThreadTaskRunnerHandle::Get().get(), NULL));
227 return net::OK;
228 #endif
229 }
230
231 #if !defined(OS_ANDROID) && !defined(OS_IOS)
FirefoxProxySettingsTask(FirefoxProxySettings * firefox_settings)232 static int FirefoxProxySettingsTask(
233 FirefoxProxySettings* firefox_settings) {
234 if (!FirefoxProxySettings::GetSettings(firefox_settings))
235 return net::ERR_FILE_NOT_FOUND;
236 return net::OK;
237 }
238
FirefoxProxySettingsReply(scoped_ptr<net::ProxyConfigService> * config_service,FirefoxProxySettings * firefox_settings,base::Callback<void (int)> callback,int rv)239 void FirefoxProxySettingsReply(
240 scoped_ptr<net::ProxyConfigService>* config_service,
241 FirefoxProxySettings* firefox_settings,
242 base::Callback<void(int)> callback,
243 int rv) {
244 if (rv == net::OK) {
245 if (FirefoxProxySettings::SYSTEM == firefox_settings->config_type()) {
246 rv = CreateSystemProxyConfigService(config_service);
247 } else {
248 net::ProxyConfig config;
249 if (firefox_settings->ToProxyConfig(&config))
250 config_service->reset(new net::ProxyConfigServiceFixed(config));
251 else
252 rv = net::ERR_FAILED;
253 }
254 }
255 callback.Run(rv);
256 }
257 #endif
258
259 // Creates a fixed proxy config service that is initialized using Firefox's
260 // current proxy settings. On success returns net::OK and fills
261 // |config_service| with a new pointer. Otherwise returns a network error
262 // code.
CreateFirefoxProxyConfigService(scoped_ptr<net::ProxyConfigService> * config_service,base::Callback<void (int)> callback)263 int CreateFirefoxProxyConfigService(
264 scoped_ptr<net::ProxyConfigService>* config_service,
265 base::Callback<void(int)> callback) {
266 #if defined(OS_ANDROID) || defined(OS_IOS)
267 // Chrome on Android and iOS do not support Firefox settings.
268 return net::ERR_NOT_IMPLEMENTED;
269 #else
270 // Fetch Firefox's proxy settings (can fail if Firefox is not installed).
271 FirefoxProxySettings* ff_settings = new FirefoxProxySettings();
272 base::Callback<int(void)> task = base::Bind(
273 &FirefoxProxySettingsTask, ff_settings);
274 base::Callback<void(int)> reply = base::Bind(
275 &ExperimentURLRequestContext::FirefoxProxySettingsReply,
276 weak_factory_.GetWeakPtr(), config_service,
277 base::Owned(ff_settings), callback);
278 if (!content::BrowserThread::PostTaskAndReplyWithResult<int>(
279 content::BrowserThread::FILE, FROM_HERE, task, reply))
280 return net::ERR_FAILED;
281 return net::ERR_IO_PENDING;
282 #endif
283 }
284
285 #if !defined(OS_IOS)
286 net::URLRequestContext* const proxy_request_context_;
287 #endif
288 net::URLRequestContextStorage storage_;
289 base::WeakPtrFactory<ExperimentURLRequestContext> weak_factory_;
290 };
291
292 } // namespace
293
294 // ConnectionTester::TestRunner ----------------------------------------------
295
296 // TestRunner is a helper class for running an individual experiment. It can
297 // be deleted any time after it is started, and this will abort the request.
298 class ConnectionTester::TestRunner : public net::URLRequest::Delegate {
299 public:
300 // |tester| must remain alive throughout the TestRunner's lifetime.
301 // |tester| will be notified of completion.
TestRunner(ConnectionTester * tester,net::NetLog * net_log)302 TestRunner(ConnectionTester* tester, net::NetLog* net_log)
303 : tester_(tester),
304 net_log_(net_log),
305 weak_factory_(this) {}
306
307 // Finish running |experiment| once a ProxyConfigService has been created.
308 // In the case of a FirefoxProxyConfigService, this will be called back
309 // after disk access has completed.
310 void ProxyConfigServiceCreated(
311 const Experiment& experiment,
312 scoped_ptr<net::ProxyConfigService>* proxy_config_service, int status);
313
314 // Starts running |experiment|. Notifies tester->OnExperimentCompleted() when
315 // it is done.
316 void Run(const Experiment& experiment);
317
318 // Overridden from net::URLRequest::Delegate:
319 virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE;
320 virtual void OnReadCompleted(net::URLRequest* request,
321 int bytes_read) OVERRIDE;
322 // TODO(eroman): handle cases requiring authentication.
323
324 private:
325 // The number of bytes to read each response body chunk.
326 static const int kReadBufferSize = 1024;
327
328 // Starts reading the response's body (and keeps reading until an error or
329 // end of stream).
330 void ReadBody(net::URLRequest* request);
331
332 // Called when the request has completed (for both success and failure).
333 void OnResponseCompleted(net::URLRequest* request);
334 void OnExperimentCompletedWithResult(int result);
335
336 ConnectionTester* tester_;
337 scoped_ptr<ExperimentURLRequestContext> request_context_;
338 scoped_ptr<net::URLRequest> request_;
339 net::NetLog* net_log_;
340
341 base::WeakPtrFactory<TestRunner> weak_factory_;
342
343 DISALLOW_COPY_AND_ASSIGN(TestRunner);
344 };
345
OnResponseStarted(net::URLRequest * request)346 void ConnectionTester::TestRunner::OnResponseStarted(net::URLRequest* request) {
347 if (!request->status().is_success()) {
348 OnResponseCompleted(request);
349 return;
350 }
351
352 // Start reading the body.
353 ReadBody(request);
354 }
355
OnReadCompleted(net::URLRequest * request,int bytes_read)356 void ConnectionTester::TestRunner::OnReadCompleted(net::URLRequest* request,
357 int bytes_read) {
358 if (bytes_read <= 0) {
359 OnResponseCompleted(request);
360 return;
361 }
362
363 // Keep reading until the stream is closed. Throw the data read away.
364 ReadBody(request);
365 }
366
ReadBody(net::URLRequest * request)367 void ConnectionTester::TestRunner::ReadBody(net::URLRequest* request) {
368 // Read the response body |kReadBufferSize| bytes at a time.
369 scoped_refptr<net::IOBuffer> unused_buffer(
370 new net::IOBuffer(kReadBufferSize));
371 int num_bytes;
372 if (request->Read(unused_buffer.get(), kReadBufferSize, &num_bytes)) {
373 OnReadCompleted(request, num_bytes);
374 } else if (!request->status().is_io_pending()) {
375 // Read failed synchronously.
376 OnResponseCompleted(request);
377 }
378 }
379
OnResponseCompleted(net::URLRequest * request)380 void ConnectionTester::TestRunner::OnResponseCompleted(
381 net::URLRequest* request) {
382 int result = net::OK;
383 if (!request->status().is_success()) {
384 DCHECK_NE(net::ERR_IO_PENDING, request->status().error());
385 result = request->status().error();
386 }
387
388 // Post a task to notify the parent rather than handling it right away,
389 // to avoid re-entrancy problems with URLRequest. (Don't want the caller
390 // to end up deleting the URLRequest while in the middle of processing).
391 base::MessageLoop::current()->PostTask(
392 FROM_HERE,
393 base::Bind(&TestRunner::OnExperimentCompletedWithResult,
394 weak_factory_.GetWeakPtr(), result));
395 }
396
OnExperimentCompletedWithResult(int result)397 void ConnectionTester::TestRunner::OnExperimentCompletedWithResult(int result) {
398 tester_->OnExperimentCompleted(result);
399 }
400
ProxyConfigServiceCreated(const Experiment & experiment,scoped_ptr<net::ProxyConfigService> * proxy_config_service,int status)401 void ConnectionTester::TestRunner::ProxyConfigServiceCreated(
402 const Experiment& experiment,
403 scoped_ptr<net::ProxyConfigService>* proxy_config_service,
404 int status) {
405 if (status == net::OK)
406 status = request_context_->Init(experiment,
407 proxy_config_service,
408 net_log_);
409 if (status != net::OK) {
410 tester_->OnExperimentCompleted(status);
411 return;
412 }
413 // Fetch a request using the experimental context.
414 request_ = request_context_->CreateRequest(
415 experiment.url, net::DEFAULT_PRIORITY, this, NULL);
416 request_->Start();
417 }
418
Run(const Experiment & experiment)419 void ConnectionTester::TestRunner::Run(const Experiment& experiment) {
420 // Try to create a net::URLRequestContext for this experiment.
421 request_context_.reset(
422 new ExperimentURLRequestContext(tester_->proxy_request_context_));
423 scoped_ptr<net::ProxyConfigService>* proxy_config_service =
424 new scoped_ptr<net::ProxyConfigService>();
425 base::Callback<void(int)> config_service_callback =
426 base::Bind(
427 &TestRunner::ProxyConfigServiceCreated, weak_factory_.GetWeakPtr(),
428 experiment, base::Owned(proxy_config_service));
429 int rv = request_context_->CreateProxyConfigService(
430 experiment.proxy_settings_experiment,
431 proxy_config_service, config_service_callback);
432 if (rv != net::ERR_IO_PENDING)
433 ProxyConfigServiceCreated(experiment, proxy_config_service, rv);
434 }
435
436 // ConnectionTester ----------------------------------------------------------
437
ConnectionTester(Delegate * delegate,net::URLRequestContext * proxy_request_context,net::NetLog * net_log)438 ConnectionTester::ConnectionTester(
439 Delegate* delegate,
440 net::URLRequestContext* proxy_request_context,
441 net::NetLog* net_log)
442 : delegate_(delegate),
443 proxy_request_context_(proxy_request_context),
444 net_log_(net_log) {
445 DCHECK(delegate);
446 DCHECK(proxy_request_context);
447 }
448
~ConnectionTester()449 ConnectionTester::~ConnectionTester() {
450 // Cancellation happens automatically by deleting test_runner_.
451 }
452
RunAllTests(const GURL & url)453 void ConnectionTester::RunAllTests(const GURL& url) {
454 // Select all possible experiments to run. (In no particular order).
455 // It is possible that some of these experiments are actually duplicates.
456 GetAllPossibleExperimentCombinations(url, &remaining_experiments_);
457
458 delegate_->OnStartConnectionTestSuite();
459 StartNextExperiment();
460 }
461
462 // static
ProxySettingsExperimentDescription(ProxySettingsExperiment experiment)463 base::string16 ConnectionTester::ProxySettingsExperimentDescription(
464 ProxySettingsExperiment experiment) {
465 // TODO(eroman): Use proper string resources.
466 switch (experiment) {
467 case PROXY_EXPERIMENT_USE_DIRECT:
468 return base::ASCIIToUTF16("Don't use any proxy");
469 case PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS:
470 return base::ASCIIToUTF16("Use system proxy settings");
471 case PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS:
472 return base::ASCIIToUTF16("Use Firefox's proxy settings");
473 case PROXY_EXPERIMENT_USE_AUTO_DETECT:
474 return base::ASCIIToUTF16("Auto-detect proxy settings");
475 default:
476 NOTREACHED();
477 return base::string16();
478 }
479 }
480
481 // static
HostResolverExperimentDescription(HostResolverExperiment experiment)482 base::string16 ConnectionTester::HostResolverExperimentDescription(
483 HostResolverExperiment experiment) {
484 // TODO(eroman): Use proper string resources.
485 switch (experiment) {
486 case HOST_RESOLVER_EXPERIMENT_PLAIN:
487 return base::string16();
488 case HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6:
489 return base::ASCIIToUTF16("Disable IPv6 host resolving");
490 case HOST_RESOLVER_EXPERIMENT_IPV6_PROBE:
491 return base::ASCIIToUTF16("Probe for IPv6 host resolving");
492 default:
493 NOTREACHED();
494 return base::string16();
495 }
496 }
497
498 // static
GetAllPossibleExperimentCombinations(const GURL & url,ConnectionTester::ExperimentList * list)499 void ConnectionTester::GetAllPossibleExperimentCombinations(
500 const GURL& url,
501 ConnectionTester::ExperimentList* list) {
502 list->clear();
503 for (size_t resolver_experiment = 0;
504 resolver_experiment < HOST_RESOLVER_EXPERIMENT_COUNT;
505 ++resolver_experiment) {
506 for (size_t proxy_experiment = 0;
507 proxy_experiment < PROXY_EXPERIMENT_COUNT;
508 ++proxy_experiment) {
509 Experiment experiment(
510 url,
511 static_cast<ProxySettingsExperiment>(proxy_experiment),
512 static_cast<HostResolverExperiment>(resolver_experiment));
513 list->push_back(experiment);
514 }
515 }
516 }
517
StartNextExperiment()518 void ConnectionTester::StartNextExperiment() {
519 DCHECK(!remaining_experiments_.empty());
520 DCHECK(!current_test_runner_.get());
521
522 delegate_->OnStartConnectionTestExperiment(current_experiment());
523
524 current_test_runner_.reset(new TestRunner(this, net_log_));
525 current_test_runner_->Run(current_experiment());
526 }
527
OnExperimentCompleted(int result)528 void ConnectionTester::OnExperimentCompleted(int result) {
529 Experiment current = current_experiment();
530
531 // Advance to the next experiment.
532 remaining_experiments_.erase(remaining_experiments_.begin());
533 current_test_runner_.reset();
534
535 // Notify the delegate of completion.
536 delegate_->OnCompletedConnectionTestExperiment(current, result);
537
538 if (remaining_experiments_.empty()) {
539 delegate_->OnCompletedConnectionTestSuite();
540 } else {
541 StartNextExperiment();
542 }
543 }
544