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