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