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 22 using base::TimeDelta; 23 24 namespace 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. 29 const int TransportConnectJob::kIPv6FallbackTimerInMs = 300; 30 31 namespace { 32 33 bool 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 48 bool 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 60 TransportSocketParams::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 74 TransportSocketParams::~TransportSocketParams() {} 75 76 void 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 89 bool TransportSocketParams::getUID(uid_t *uid) const { 90 if (!valid_uid_) { 91 return false; 92 } 93 *uid = calling_uid_; 94 return true; 95 } 96 97 void 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. 112 static const int kTransportConnectJobTimeoutInSeconds = 240; // 4 minutes. 113 114 TransportConnectJob::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 135 TransportConnectJob::~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 140 LoadState 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 155 void 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 186 void TransportConnectJob::OnIOComplete(int result) { 187 int rv = DoLoop(result); 188 if (rv != ERR_IO_PENDING) 189 NotifyDelegateOfCompletion(rv); // Deletes |this| 190 } 191 192 int 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 224 int TransportConnectJob::DoResolveHost() { 225 next_state_ = STATE_RESOLVE_HOST_COMPLETE; 226 return resolver_.Resolve(params_->destination(), &addresses_, &callback_, 227 net_log()); 228 } 229 230 int TransportConnectJob::DoResolveHostComplete(int result) { 231 if (result == OK) 232 next_state_ = STATE_TRANSPORT_CONNECT; 233 return result; 234 } 235 236 int 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 263 int 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 316 void 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 350 void 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 396 int TransportConnectJob::ConnectInternal() { 397 next_state_ = STATE_RESOLVE_HOST; 398 start_time_ = base::TimeTicks::Now(); 399 return DoLoop(OK); 400 } 401 402 ConnectJob* 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 416 base::TimeDelta 417 TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout() 418 const { 419 return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds); 420 } 421 422 TransportClientSocketPool::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 438 TransportClientSocketPool::~TransportClientSocketPool() {} 439 440 int 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 463 void 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 483 void TransportClientSocketPool::CancelRequest( 484 const std::string& group_name, 485 ClientSocketHandle* handle) { 486 base_.CancelRequest(group_name, handle); 487 } 488 489 void TransportClientSocketPool::ReleaseSocket( 490 const std::string& group_name, 491 ClientSocket* socket, 492 int id) { 493 base_.ReleaseSocket(group_name, socket, id); 494 } 495 496 void TransportClientSocketPool::Flush() { 497 base_.Flush(); 498 } 499 500 void TransportClientSocketPool::CloseIdleSockets() { 501 base_.CloseIdleSockets(); 502 } 503 504 int TransportClientSocketPool::IdleSocketCount() const { 505 return base_.idle_socket_count(); 506 } 507 508 int TransportClientSocketPool::IdleSocketCountInGroup( 509 const std::string& group_name) const { 510 return base_.IdleSocketCountInGroup(group_name); 511 } 512 513 LoadState TransportClientSocketPool::GetLoadState( 514 const std::string& group_name, const ClientSocketHandle* handle) const { 515 return base_.GetLoadState(group_name, handle); 516 } 517 518 DictionaryValue* 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 525 base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const { 526 return base_.ConnectionTimeout(); 527 } 528 529 ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const { 530 return base_.histograms(); 531 } 532 533 } // namespace net 534