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 "net/socket/transport_client_socket_pool.h" 6 7#include "base/compiler_specific.h" 8#include "base/logging.h" 9#include "base/message_loop.h" 10#include "base/metrics/histogram.h" 11#include "base/string_util.h" 12#include "base/time.h" 13#include "net/base/ip_endpoint.h" 14#include "net/base/net_log.h" 15#include "net/base/net_errors.h" 16#include "net/base/sys_addrinfo.h" 17#include "net/socket/client_socket_factory.h" 18#include "net/socket/client_socket_handle.h" 19#include "net/socket/client_socket_pool_base.h" 20#include "net/socket/tcp_client_socket.h" 21 22using base::TimeDelta; 23 24namespace net { 25 26// TODO(willchan): Base this off RTT instead of statically setting it. Note we 27// choose a timeout that is different from the backup connect job timer so they 28// don't synchronize. 29const int TransportConnectJob::kIPv6FallbackTimerInMs = 300; 30 31namespace { 32 33bool AddressListStartsWithIPv6AndHasAnIPv4Addr(const AddressList& addrlist) { 34 const struct addrinfo* ai = addrlist.head(); 35 if (ai->ai_family != AF_INET6) 36 return false; 37 38 ai = ai->ai_next; 39 while (ai) { 40 if (ai->ai_family != AF_INET6) 41 return true; 42 ai = ai->ai_next; 43 } 44 45 return false; 46} 47 48bool AddressListOnlyContainsIPv6Addresses(const AddressList& addrlist) { 49 DCHECK(addrlist.head()); 50 for (const struct addrinfo* ai = addrlist.head(); ai; ai = ai->ai_next) { 51 if (ai->ai_family != AF_INET6) 52 return false; 53 } 54 55 return true; 56} 57 58} // namespace 59 60TransportSocketParams::TransportSocketParams( 61 const HostPortPair& host_port_pair, 62 RequestPriority priority, 63 const GURL& referrer, 64 bool disable_resolver_cache, 65 bool ignore_limits) 66 : destination_(host_port_pair), ignore_limits_(ignore_limits) 67#ifdef ANDROID 68 , valid_uid_(false), calling_uid_(0) 69#endif 70 { 71 Initialize(priority, referrer, disable_resolver_cache); 72} 73 74TransportSocketParams::~TransportSocketParams() {} 75 76void TransportSocketParams::Initialize(RequestPriority priority, 77 const GURL& referrer, 78 bool disable_resolver_cache) { 79 // The referrer is used by the DNS prefetch system to correlate resolutions 80 // with the page that triggered them. It doesn't impact the actual addresses 81 // that we resolve to. 82 destination_.set_referrer(referrer); 83 destination_.set_priority(priority); 84 if (disable_resolver_cache) 85 destination_.set_allow_cached_response(false); 86} 87 88#ifdef ANDROID 89bool TransportSocketParams::getUID(uid_t *uid) const { 90 if (!valid_uid_) { 91 return false; 92 } 93 *uid = calling_uid_; 94 return true; 95} 96 97void TransportSocketParams::setUID(uid_t uid) { 98 valid_uid_ = true; 99 calling_uid_ = uid; 100} 101#endif 102 103// TransportConnectJobs will time out after this many seconds. Note this is 104// the total time, including both host resolution and TCP connect() times. 105// 106// TODO(eroman): The use of this constant needs to be re-evaluated. The time 107// needed for TCPClientSocketXXX::Connect() can be arbitrarily long, since 108// the address list may contain many alternatives, and most of those may 109// timeout. Even worse, the per-connect timeout threshold varies greatly 110// between systems (anywhere from 20 seconds to 190 seconds). 111// See comment #12 at http://crbug.com/23364 for specifics. 112static const int kTransportConnectJobTimeoutInSeconds = 240; // 4 minutes. 113 114TransportConnectJob::TransportConnectJob( 115 const std::string& group_name, 116 const scoped_refptr<TransportSocketParams>& params, 117 base::TimeDelta timeout_duration, 118 ClientSocketFactory* client_socket_factory, 119 HostResolver* host_resolver, 120 Delegate* delegate, 121 NetLog* net_log) 122 : ConnectJob(group_name, timeout_duration, delegate, 123 BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)), 124 params_(params), 125 client_socket_factory_(client_socket_factory), 126 ALLOW_THIS_IN_INITIALIZER_LIST( 127 callback_(this, 128 &TransportConnectJob::OnIOComplete)), 129 resolver_(host_resolver), 130 ALLOW_THIS_IN_INITIALIZER_LIST( 131 fallback_callback_( 132 this, 133 &TransportConnectJob::DoIPv6FallbackTransportConnectComplete)) {} 134 135TransportConnectJob::~TransportConnectJob() { 136 // We don't worry about cancelling the host resolution and TCP connect, since 137 // ~SingleRequestHostResolver and ~ClientSocket will take care of it. 138} 139 140LoadState TransportConnectJob::GetLoadState() const { 141 switch (next_state_) { 142 case STATE_RESOLVE_HOST: 143 case STATE_RESOLVE_HOST_COMPLETE: 144 return LOAD_STATE_RESOLVING_HOST; 145 case STATE_TRANSPORT_CONNECT: 146 case STATE_TRANSPORT_CONNECT_COMPLETE: 147 return LOAD_STATE_CONNECTING; 148 default: 149 NOTREACHED(); 150 return LOAD_STATE_IDLE; 151 } 152} 153 154// static 155void TransportConnectJob::MakeAddrListStartWithIPv4(AddressList* addrlist) { 156 if (addrlist->head()->ai_family != AF_INET6) 157 return; 158 bool has_ipv4 = false; 159 for (const struct addrinfo* ai = addrlist->head(); ai; ai = ai->ai_next) { 160 if (ai->ai_family != AF_INET6) { 161 has_ipv4 = true; 162 break; 163 } 164 } 165 if (!has_ipv4) 166 return; 167 168 struct addrinfo* head = CreateCopyOfAddrinfo(addrlist->head(), true); 169 struct addrinfo* tail = head; 170 while (tail->ai_next) 171 tail = tail->ai_next; 172 char* canonname = head->ai_canonname; 173 head->ai_canonname = NULL; 174 while (head->ai_family == AF_INET6) { 175 tail->ai_next = head; 176 tail = head; 177 head = head->ai_next; 178 tail->ai_next = NULL; 179 } 180 head->ai_canonname = canonname; 181 182 addrlist->Copy(head, true); 183 FreeCopyOfAddrinfo(head); 184} 185 186void TransportConnectJob::OnIOComplete(int result) { 187 int rv = DoLoop(result); 188 if (rv != ERR_IO_PENDING) 189 NotifyDelegateOfCompletion(rv); // Deletes |this| 190} 191 192int TransportConnectJob::DoLoop(int result) { 193 DCHECK_NE(next_state_, STATE_NONE); 194 195 int rv = result; 196 do { 197 State state = next_state_; 198 next_state_ = STATE_NONE; 199 switch (state) { 200 case STATE_RESOLVE_HOST: 201 DCHECK_EQ(OK, rv); 202 rv = DoResolveHost(); 203 break; 204 case STATE_RESOLVE_HOST_COMPLETE: 205 rv = DoResolveHostComplete(rv); 206 break; 207 case STATE_TRANSPORT_CONNECT: 208 DCHECK_EQ(OK, rv); 209 rv = DoTransportConnect(); 210 break; 211 case STATE_TRANSPORT_CONNECT_COMPLETE: 212 rv = DoTransportConnectComplete(rv); 213 break; 214 default: 215 NOTREACHED(); 216 rv = ERR_FAILED; 217 break; 218 } 219 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); 220 221 return rv; 222} 223 224int TransportConnectJob::DoResolveHost() { 225 next_state_ = STATE_RESOLVE_HOST_COMPLETE; 226 return resolver_.Resolve(params_->destination(), &addresses_, &callback_, 227 net_log()); 228} 229 230int TransportConnectJob::DoResolveHostComplete(int result) { 231 if (result == OK) 232 next_state_ = STATE_TRANSPORT_CONNECT; 233 return result; 234} 235 236int TransportConnectJob::DoTransportConnect() { 237 next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE; 238 transport_socket_.reset(client_socket_factory_->CreateTransportClientSocket( 239 addresses_, net_log().net_log(), net_log().source())); 240 connect_start_time_ = base::TimeTicks::Now(); 241 242#ifdef ANDROID 243 uid_t calling_uid = 0; 244 bool valid_uid = params_->getUID(&calling_uid); 245#endif 246 247 int rv = transport_socket_->Connect(&callback_ 248#ifdef ANDROID 249 , params_->ignore_limits() 250 , valid_uid 251 , calling_uid 252#endif 253 ); 254 if (rv == ERR_IO_PENDING && 255 AddressListStartsWithIPv6AndHasAnIPv4Addr(addresses_)) { 256 fallback_timer_.Start( 257 base::TimeDelta::FromMilliseconds(kIPv6FallbackTimerInMs), 258 this, &TransportConnectJob::DoIPv6FallbackTransportConnect); 259 } 260 return rv; 261} 262 263int TransportConnectJob::DoTransportConnectComplete(int result) { 264 if (result == OK) { 265 bool is_ipv4 = addresses_.head()->ai_family != AF_INET6; 266 DCHECK(connect_start_time_ != base::TimeTicks()); 267 DCHECK(start_time_ != base::TimeTicks()); 268 base::TimeTicks now = base::TimeTicks::Now(); 269 base::TimeDelta total_duration = now - start_time_; 270 UMA_HISTOGRAM_CUSTOM_TIMES( 271 "Net.DNS_Resolution_And_TCP_Connection_Latency2", 272 total_duration, 273 base::TimeDelta::FromMilliseconds(1), 274 base::TimeDelta::FromMinutes(10), 275 100); 276 277 base::TimeDelta connect_duration = now - connect_start_time_; 278 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", 279 connect_duration, 280 base::TimeDelta::FromMilliseconds(1), 281 base::TimeDelta::FromMinutes(10), 282 100); 283 284 if (is_ipv4) { 285 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_No_Race", 286 connect_duration, 287 base::TimeDelta::FromMilliseconds(1), 288 base::TimeDelta::FromMinutes(10), 289 100); 290 } else { 291 if (AddressListOnlyContainsIPv6Addresses(addresses_)) { 292 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Solo", 293 connect_duration, 294 base::TimeDelta::FromMilliseconds(1), 295 base::TimeDelta::FromMinutes(10), 296 100); 297 } else { 298 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Raceable", 299 connect_duration, 300 base::TimeDelta::FromMilliseconds(1), 301 base::TimeDelta::FromMinutes(10), 302 100); 303 } 304 } 305 set_socket(transport_socket_.release()); 306 fallback_timer_.Stop(); 307 } else { 308 // Be a bit paranoid and kill off the fallback members to prevent reuse. 309 fallback_transport_socket_.reset(); 310 fallback_addresses_.reset(); 311 } 312 313 return result; 314} 315 316void TransportConnectJob::DoIPv6FallbackTransportConnect() { 317 // The timer should only fire while we're waiting for the main connect to 318 // succeed. 319 if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) { 320 NOTREACHED(); 321 return; 322 } 323 324 DCHECK(!fallback_transport_socket_.get()); 325 DCHECK(!fallback_addresses_.get()); 326 327 fallback_addresses_.reset(new AddressList(addresses_)); 328 MakeAddrListStartWithIPv4(fallback_addresses_.get()); 329 fallback_transport_socket_.reset( 330 client_socket_factory_->CreateTransportClientSocket( 331 *fallback_addresses_, net_log().net_log(), net_log().source())); 332 fallback_connect_start_time_ = base::TimeTicks::Now(); 333 334#ifdef ANDROID 335 uid_t calling_uid = 0; 336 bool valid_uid = params_->getUID(&calling_uid); 337#endif 338 339 int rv = fallback_transport_socket_->Connect(&fallback_callback_ 340#ifdef ANDROID 341 , params_->ignore_limits() 342 , valid_uid 343 , calling_uid 344#endif 345 ); 346 if (rv != ERR_IO_PENDING) 347 DoIPv6FallbackTransportConnectComplete(rv); 348} 349 350void TransportConnectJob::DoIPv6FallbackTransportConnectComplete(int result) { 351 // This should only happen when we're waiting for the main connect to succeed. 352 if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) { 353 NOTREACHED(); 354 return; 355 } 356 357 DCHECK_NE(ERR_IO_PENDING, result); 358 DCHECK(fallback_transport_socket_.get()); 359 DCHECK(fallback_addresses_.get()); 360 361 if (result == OK) { 362 DCHECK(fallback_connect_start_time_ != base::TimeTicks()); 363 DCHECK(start_time_ != base::TimeTicks()); 364 base::TimeTicks now = base::TimeTicks::Now(); 365 base::TimeDelta total_duration = now - start_time_; 366 UMA_HISTOGRAM_CUSTOM_TIMES( 367 "Net.DNS_Resolution_And_TCP_Connection_Latency2", 368 total_duration, 369 base::TimeDelta::FromMilliseconds(1), 370 base::TimeDelta::FromMinutes(10), 371 100); 372 373 base::TimeDelta connect_duration = now - fallback_connect_start_time_; 374 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", 375 connect_duration, 376 base::TimeDelta::FromMilliseconds(1), 377 base::TimeDelta::FromMinutes(10), 378 100); 379 380 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_Wins_Race", 381 connect_duration, 382 base::TimeDelta::FromMilliseconds(1), 383 base::TimeDelta::FromMinutes(10), 384 100); 385 set_socket(fallback_transport_socket_.release()); 386 next_state_ = STATE_NONE; 387 transport_socket_.reset(); 388 } else { 389 // Be a bit paranoid and kill off the fallback members to prevent reuse. 390 fallback_transport_socket_.reset(); 391 fallback_addresses_.reset(); 392 } 393 NotifyDelegateOfCompletion(result); // Deletes |this| 394} 395 396int TransportConnectJob::ConnectInternal() { 397 next_state_ = STATE_RESOLVE_HOST; 398 start_time_ = base::TimeTicks::Now(); 399 return DoLoop(OK); 400} 401 402ConnectJob* 403 TransportClientSocketPool::TransportConnectJobFactory::NewConnectJob( 404 const std::string& group_name, 405 const PoolBase::Request& request, 406 ConnectJob::Delegate* delegate) const { 407 return new TransportConnectJob(group_name, 408 request.params(), 409 ConnectionTimeout(), 410 client_socket_factory_, 411 host_resolver_, 412 delegate, 413 net_log_); 414} 415 416base::TimeDelta 417 TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout() 418 const { 419 return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds); 420} 421 422TransportClientSocketPool::TransportClientSocketPool( 423 int max_sockets, 424 int max_sockets_per_group, 425 ClientSocketPoolHistograms* histograms, 426 HostResolver* host_resolver, 427 ClientSocketFactory* client_socket_factory, 428 NetLog* net_log) 429 : base_(max_sockets, max_sockets_per_group, histograms, 430 base::TimeDelta::FromSeconds( 431 ClientSocketPool::unused_idle_socket_timeout()), 432 base::TimeDelta::FromSeconds(kUsedIdleSocketTimeout), 433 new TransportConnectJobFactory(client_socket_factory, 434 host_resolver, net_log)) { 435 base_.EnableConnectBackupJobs(); 436} 437 438TransportClientSocketPool::~TransportClientSocketPool() {} 439 440int TransportClientSocketPool::RequestSocket( 441 const std::string& group_name, 442 const void* params, 443 RequestPriority priority, 444 ClientSocketHandle* handle, 445 CompletionCallback* callback, 446 const BoundNetLog& net_log) { 447 const scoped_refptr<TransportSocketParams>* casted_params = 448 static_cast<const scoped_refptr<TransportSocketParams>*>(params); 449 450 if (net_log.IsLoggingAllEvents()) { 451 // TODO(eroman): Split out the host and port parameters. 452 net_log.AddEvent( 453 NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET, 454 make_scoped_refptr(new NetLogStringParameter( 455 "host_and_port", 456 casted_params->get()->destination().host_port_pair().ToString()))); 457 } 458 459 return base_.RequestSocket(group_name, *casted_params, priority, handle, 460 callback, net_log); 461} 462 463void TransportClientSocketPool::RequestSockets( 464 const std::string& group_name, 465 const void* params, 466 int num_sockets, 467 const BoundNetLog& net_log) { 468 const scoped_refptr<TransportSocketParams>* casted_params = 469 static_cast<const scoped_refptr<TransportSocketParams>*>(params); 470 471 if (net_log.IsLoggingAllEvents()) { 472 // TODO(eroman): Split out the host and port parameters. 473 net_log.AddEvent( 474 NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS, 475 make_scoped_refptr(new NetLogStringParameter( 476 "host_and_port", 477 casted_params->get()->destination().host_port_pair().ToString()))); 478 } 479 480 base_.RequestSockets(group_name, *casted_params, num_sockets, net_log); 481} 482 483void TransportClientSocketPool::CancelRequest( 484 const std::string& group_name, 485 ClientSocketHandle* handle) { 486 base_.CancelRequest(group_name, handle); 487} 488 489void TransportClientSocketPool::ReleaseSocket( 490 const std::string& group_name, 491 ClientSocket* socket, 492 int id) { 493 base_.ReleaseSocket(group_name, socket, id); 494} 495 496void TransportClientSocketPool::Flush() { 497 base_.Flush(); 498} 499 500void TransportClientSocketPool::CloseIdleSockets() { 501 base_.CloseIdleSockets(); 502} 503 504int TransportClientSocketPool::IdleSocketCount() const { 505 return base_.idle_socket_count(); 506} 507 508int TransportClientSocketPool::IdleSocketCountInGroup( 509 const std::string& group_name) const { 510 return base_.IdleSocketCountInGroup(group_name); 511} 512 513LoadState TransportClientSocketPool::GetLoadState( 514 const std::string& group_name, const ClientSocketHandle* handle) const { 515 return base_.GetLoadState(group_name, handle); 516} 517 518DictionaryValue* TransportClientSocketPool::GetInfoAsValue( 519 const std::string& name, 520 const std::string& type, 521 bool include_nested_pools) const { 522 return base_.GetInfoAsValue(name, type); 523} 524 525base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const { 526 return base_.ConnectionTimeout(); 527} 528 529ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const { 530 return base_.histograms(); 531} 532 533} // namespace net 534