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 "net/socket/transport_client_socket_pool.h" 6 7 #include <algorithm> 8 9 #include "base/compiler_specific.h" 10 #include "base/lazy_instance.h" 11 #include "base/logging.h" 12 #include "base/message_loop/message_loop.h" 13 #include "base/metrics/histogram.h" 14 #include "base/strings/string_util.h" 15 #include "base/synchronization/lock.h" 16 #include "base/time/time.h" 17 #include "base/values.h" 18 #include "net/base/ip_endpoint.h" 19 #include "net/base/net_errors.h" 20 #include "net/base/net_log.h" 21 #include "net/socket/client_socket_factory.h" 22 #include "net/socket/client_socket_handle.h" 23 #include "net/socket/client_socket_pool_base.h" 24 #include "net/socket/socket_net_log_params.h" 25 #include "net/socket/tcp_client_socket.h" 26 27 using base::TimeDelta; 28 29 namespace net { 30 31 // TODO(willchan): Base this off RTT instead of statically setting it. Note we 32 // choose a timeout that is different from the backup connect job timer so they 33 // don't synchronize. 34 const int TransportConnectJobHelper::kIPv6FallbackTimerInMs = 300; 35 36 namespace { 37 38 // Returns true iff all addresses in |list| are in the IPv6 family. 39 bool AddressListOnlyContainsIPv6(const AddressList& list) { 40 DCHECK(!list.empty()); 41 for (AddressList::const_iterator iter = list.begin(); iter != list.end(); 42 ++iter) { 43 if (iter->GetFamily() != ADDRESS_FAMILY_IPV6) 44 return false; 45 } 46 return true; 47 } 48 49 } // namespace 50 51 // This lock protects |g_last_connect_time|. 52 static base::LazyInstance<base::Lock>::Leaky 53 g_last_connect_time_lock = LAZY_INSTANCE_INITIALIZER; 54 55 // |g_last_connect_time| has the last time a connect() call is made. 56 static base::LazyInstance<base::TimeTicks>::Leaky 57 g_last_connect_time = LAZY_INSTANCE_INITIALIZER; 58 59 TransportSocketParams::TransportSocketParams( 60 const HostPortPair& host_port_pair, 61 bool disable_resolver_cache, 62 bool ignore_limits, 63 const OnHostResolutionCallback& host_resolution_callback, 64 CombineConnectAndWritePolicy combine_connect_and_write_if_supported) 65 : destination_(host_port_pair), 66 ignore_limits_(ignore_limits), 67 host_resolution_callback_(host_resolution_callback), 68 combine_connect_and_write_(combine_connect_and_write_if_supported) { 69 if (disable_resolver_cache) 70 destination_.set_allow_cached_response(false); 71 // combine_connect_and_write currently translates to TCP FastOpen. 72 // Enable TCP FastOpen if user wants it. 73 if (combine_connect_and_write_ == COMBINE_CONNECT_AND_WRITE_DEFAULT) { 74 IsTCPFastOpenUserEnabled() ? combine_connect_and_write_ = 75 COMBINE_CONNECT_AND_WRITE_DESIRED : 76 COMBINE_CONNECT_AND_WRITE_PROHIBITED; 77 } 78 } 79 80 TransportSocketParams::~TransportSocketParams() {} 81 82 // TransportConnectJobs will time out after this many seconds. Note this is 83 // the total time, including both host resolution and TCP connect() times. 84 // 85 // TODO(eroman): The use of this constant needs to be re-evaluated. The time 86 // needed for TCPClientSocketXXX::Connect() can be arbitrarily long, since 87 // the address list may contain many alternatives, and most of those may 88 // timeout. Even worse, the per-connect timeout threshold varies greatly 89 // between systems (anywhere from 20 seconds to 190 seconds). 90 // See comment #12 at http://crbug.com/23364 for specifics. 91 static const int kTransportConnectJobTimeoutInSeconds = 240; // 4 minutes. 92 93 TransportConnectJobHelper::TransportConnectJobHelper( 94 const scoped_refptr<TransportSocketParams>& params, 95 ClientSocketFactory* client_socket_factory, 96 HostResolver* host_resolver, 97 LoadTimingInfo::ConnectTiming* connect_timing) 98 : params_(params), 99 client_socket_factory_(client_socket_factory), 100 resolver_(host_resolver), 101 next_state_(STATE_NONE), 102 connect_timing_(connect_timing) {} 103 104 TransportConnectJobHelper::~TransportConnectJobHelper() {} 105 106 int TransportConnectJobHelper::DoResolveHost(RequestPriority priority, 107 const BoundNetLog& net_log) { 108 next_state_ = STATE_RESOLVE_HOST_COMPLETE; 109 connect_timing_->dns_start = base::TimeTicks::Now(); 110 111 return resolver_.Resolve( 112 params_->destination(), priority, &addresses_, on_io_complete_, net_log); 113 } 114 115 int TransportConnectJobHelper::DoResolveHostComplete( 116 int result, 117 const BoundNetLog& net_log) { 118 connect_timing_->dns_end = base::TimeTicks::Now(); 119 // Overwrite connection start time, since for connections that do not go 120 // through proxies, |connect_start| should not include dns lookup time. 121 connect_timing_->connect_start = connect_timing_->dns_end; 122 123 if (result == OK) { 124 // Invoke callback, and abort if it fails. 125 if (!params_->host_resolution_callback().is_null()) 126 result = params_->host_resolution_callback().Run(addresses_, net_log); 127 128 if (result == OK) 129 next_state_ = STATE_TRANSPORT_CONNECT; 130 } 131 return result; 132 } 133 134 base::TimeDelta TransportConnectJobHelper::HistogramDuration( 135 ConnectionLatencyHistogram race_result) { 136 DCHECK(!connect_timing_->connect_start.is_null()); 137 DCHECK(!connect_timing_->dns_start.is_null()); 138 base::TimeTicks now = base::TimeTicks::Now(); 139 base::TimeDelta total_duration = now - connect_timing_->dns_start; 140 UMA_HISTOGRAM_CUSTOM_TIMES("Net.DNS_Resolution_And_TCP_Connection_Latency2", 141 total_duration, 142 base::TimeDelta::FromMilliseconds(1), 143 base::TimeDelta::FromMinutes(10), 144 100); 145 146 base::TimeDelta connect_duration = now - connect_timing_->connect_start; 147 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", 148 connect_duration, 149 base::TimeDelta::FromMilliseconds(1), 150 base::TimeDelta::FromMinutes(10), 151 100); 152 153 switch (race_result) { 154 case CONNECTION_LATENCY_IPV4_WINS_RACE: 155 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_Wins_Race", 156 connect_duration, 157 base::TimeDelta::FromMilliseconds(1), 158 base::TimeDelta::FromMinutes(10), 159 100); 160 break; 161 162 case CONNECTION_LATENCY_IPV4_NO_RACE: 163 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_No_Race", 164 connect_duration, 165 base::TimeDelta::FromMilliseconds(1), 166 base::TimeDelta::FromMinutes(10), 167 100); 168 break; 169 170 case CONNECTION_LATENCY_IPV6_RACEABLE: 171 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Raceable", 172 connect_duration, 173 base::TimeDelta::FromMilliseconds(1), 174 base::TimeDelta::FromMinutes(10), 175 100); 176 break; 177 178 case CONNECTION_LATENCY_IPV6_SOLO: 179 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Solo", 180 connect_duration, 181 base::TimeDelta::FromMilliseconds(1), 182 base::TimeDelta::FromMinutes(10), 183 100); 184 break; 185 186 default: 187 NOTREACHED(); 188 break; 189 } 190 191 return connect_duration; 192 } 193 194 TransportConnectJob::TransportConnectJob( 195 const std::string& group_name, 196 RequestPriority priority, 197 const scoped_refptr<TransportSocketParams>& params, 198 base::TimeDelta timeout_duration, 199 ClientSocketFactory* client_socket_factory, 200 HostResolver* host_resolver, 201 Delegate* delegate, 202 NetLog* net_log) 203 : ConnectJob(group_name, timeout_duration, priority, delegate, 204 BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)), 205 helper_(params, client_socket_factory, host_resolver, &connect_timing_), 206 interval_between_connects_(CONNECT_INTERVAL_GT_20MS) { 207 helper_.SetOnIOComplete(this); 208 } 209 210 TransportConnectJob::~TransportConnectJob() { 211 // We don't worry about cancelling the host resolution and TCP connect, since 212 // ~SingleRequestHostResolver and ~StreamSocket will take care of it. 213 } 214 215 LoadState TransportConnectJob::GetLoadState() const { 216 switch (helper_.next_state()) { 217 case TransportConnectJobHelper::STATE_RESOLVE_HOST: 218 case TransportConnectJobHelper::STATE_RESOLVE_HOST_COMPLETE: 219 return LOAD_STATE_RESOLVING_HOST; 220 case TransportConnectJobHelper::STATE_TRANSPORT_CONNECT: 221 case TransportConnectJobHelper::STATE_TRANSPORT_CONNECT_COMPLETE: 222 return LOAD_STATE_CONNECTING; 223 case TransportConnectJobHelper::STATE_NONE: 224 return LOAD_STATE_IDLE; 225 } 226 NOTREACHED(); 227 return LOAD_STATE_IDLE; 228 } 229 230 // static 231 void TransportConnectJob::MakeAddressListStartWithIPv4(AddressList* list) { 232 for (AddressList::iterator i = list->begin(); i != list->end(); ++i) { 233 if (i->GetFamily() == ADDRESS_FAMILY_IPV4) { 234 std::rotate(list->begin(), i, list->end()); 235 break; 236 } 237 } 238 } 239 240 int TransportConnectJob::DoResolveHost() { 241 return helper_.DoResolveHost(priority(), net_log()); 242 } 243 244 int TransportConnectJob::DoResolveHostComplete(int result) { 245 return helper_.DoResolveHostComplete(result, net_log()); 246 } 247 248 int TransportConnectJob::DoTransportConnect() { 249 base::TimeTicks now = base::TimeTicks::Now(); 250 base::TimeTicks last_connect_time; 251 { 252 base::AutoLock lock(g_last_connect_time_lock.Get()); 253 last_connect_time = g_last_connect_time.Get(); 254 *g_last_connect_time.Pointer() = now; 255 } 256 if (last_connect_time.is_null()) { 257 interval_between_connects_ = CONNECT_INTERVAL_GT_20MS; 258 } else { 259 int64 interval = (now - last_connect_time).InMilliseconds(); 260 if (interval <= 10) 261 interval_between_connects_ = CONNECT_INTERVAL_LE_10MS; 262 else if (interval <= 20) 263 interval_between_connects_ = CONNECT_INTERVAL_LE_20MS; 264 else 265 interval_between_connects_ = CONNECT_INTERVAL_GT_20MS; 266 } 267 268 helper_.set_next_state( 269 TransportConnectJobHelper::STATE_TRANSPORT_CONNECT_COMPLETE); 270 transport_socket_ = 271 helper_.client_socket_factory()->CreateTransportClientSocket( 272 helper_.addresses(), net_log().net_log(), net_log().source()); 273 274 // If the list contains IPv6 and IPv4 addresses, the first address will 275 // be IPv6, and the IPv4 addresses will be tried as fallback addresses, 276 // per "Happy Eyeballs" (RFC 6555). 277 bool try_ipv6_connect_with_ipv4_fallback = 278 helper_.addresses().front().GetFamily() == ADDRESS_FAMILY_IPV6 && 279 !AddressListOnlyContainsIPv6(helper_.addresses()); 280 281 // Enable TCP FastOpen if indicated by transport socket params. 282 // Note: We currently do not turn on TCP FastOpen for destinations where 283 // we try a TCP connect over IPv6 with fallback to IPv4. 284 if (!try_ipv6_connect_with_ipv4_fallback && 285 helper_.params()->combine_connect_and_write() == 286 TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DESIRED) { 287 transport_socket_->EnableTCPFastOpenIfSupported(); 288 } 289 290 int rv = transport_socket_->Connect(helper_.on_io_complete()); 291 if (rv == ERR_IO_PENDING && try_ipv6_connect_with_ipv4_fallback) { 292 fallback_timer_.Start( 293 FROM_HERE, 294 base::TimeDelta::FromMilliseconds( 295 TransportConnectJobHelper::kIPv6FallbackTimerInMs), 296 this, 297 &TransportConnectJob::DoIPv6FallbackTransportConnect); 298 } 299 return rv; 300 } 301 302 int TransportConnectJob::DoTransportConnectComplete(int result) { 303 if (result == OK) { 304 bool is_ipv4 = 305 helper_.addresses().front().GetFamily() == ADDRESS_FAMILY_IPV4; 306 TransportConnectJobHelper::ConnectionLatencyHistogram race_result = 307 TransportConnectJobHelper::CONNECTION_LATENCY_UNKNOWN; 308 if (is_ipv4) { 309 race_result = TransportConnectJobHelper::CONNECTION_LATENCY_IPV4_NO_RACE; 310 } else { 311 if (AddressListOnlyContainsIPv6(helper_.addresses())) { 312 race_result = TransportConnectJobHelper::CONNECTION_LATENCY_IPV6_SOLO; 313 } else { 314 race_result = 315 TransportConnectJobHelper::CONNECTION_LATENCY_IPV6_RACEABLE; 316 } 317 } 318 base::TimeDelta connect_duration = helper_.HistogramDuration(race_result); 319 switch (interval_between_connects_) { 320 case CONNECT_INTERVAL_LE_10MS: 321 UMA_HISTOGRAM_CUSTOM_TIMES( 322 "Net.TCP_Connection_Latency_Interval_LessThanOrEqual_10ms", 323 connect_duration, 324 base::TimeDelta::FromMilliseconds(1), 325 base::TimeDelta::FromMinutes(10), 326 100); 327 break; 328 case CONNECT_INTERVAL_LE_20MS: 329 UMA_HISTOGRAM_CUSTOM_TIMES( 330 "Net.TCP_Connection_Latency_Interval_LessThanOrEqual_20ms", 331 connect_duration, 332 base::TimeDelta::FromMilliseconds(1), 333 base::TimeDelta::FromMinutes(10), 334 100); 335 break; 336 case CONNECT_INTERVAL_GT_20MS: 337 UMA_HISTOGRAM_CUSTOM_TIMES( 338 "Net.TCP_Connection_Latency_Interval_GreaterThan_20ms", 339 connect_duration, 340 base::TimeDelta::FromMilliseconds(1), 341 base::TimeDelta::FromMinutes(10), 342 100); 343 break; 344 default: 345 NOTREACHED(); 346 break; 347 } 348 349 SetSocket(transport_socket_.Pass()); 350 fallback_timer_.Stop(); 351 } else { 352 // Be a bit paranoid and kill off the fallback members to prevent reuse. 353 fallback_transport_socket_.reset(); 354 fallback_addresses_.reset(); 355 } 356 357 return result; 358 } 359 360 void TransportConnectJob::DoIPv6FallbackTransportConnect() { 361 // The timer should only fire while we're waiting for the main connect to 362 // succeed. 363 if (helper_.next_state() != 364 TransportConnectJobHelper::STATE_TRANSPORT_CONNECT_COMPLETE) { 365 NOTREACHED(); 366 return; 367 } 368 369 DCHECK(!fallback_transport_socket_.get()); 370 DCHECK(!fallback_addresses_.get()); 371 372 fallback_addresses_.reset(new AddressList(helper_.addresses())); 373 MakeAddressListStartWithIPv4(fallback_addresses_.get()); 374 fallback_transport_socket_ = 375 helper_.client_socket_factory()->CreateTransportClientSocket( 376 *fallback_addresses_, net_log().net_log(), net_log().source()); 377 fallback_connect_start_time_ = base::TimeTicks::Now(); 378 int rv = fallback_transport_socket_->Connect( 379 base::Bind( 380 &TransportConnectJob::DoIPv6FallbackTransportConnectComplete, 381 base::Unretained(this))); 382 if (rv != ERR_IO_PENDING) 383 DoIPv6FallbackTransportConnectComplete(rv); 384 } 385 386 void TransportConnectJob::DoIPv6FallbackTransportConnectComplete(int result) { 387 // This should only happen when we're waiting for the main connect to succeed. 388 if (helper_.next_state() != 389 TransportConnectJobHelper::STATE_TRANSPORT_CONNECT_COMPLETE) { 390 NOTREACHED(); 391 return; 392 } 393 394 DCHECK_NE(ERR_IO_PENDING, result); 395 DCHECK(fallback_transport_socket_.get()); 396 DCHECK(fallback_addresses_.get()); 397 398 if (result == OK) { 399 DCHECK(!fallback_connect_start_time_.is_null()); 400 connect_timing_.connect_start = fallback_connect_start_time_; 401 helper_.HistogramDuration( 402 TransportConnectJobHelper::CONNECTION_LATENCY_IPV4_WINS_RACE); 403 SetSocket(fallback_transport_socket_.Pass()); 404 helper_.set_next_state(TransportConnectJobHelper::STATE_NONE); 405 transport_socket_.reset(); 406 } else { 407 // Be a bit paranoid and kill off the fallback members to prevent reuse. 408 fallback_transport_socket_.reset(); 409 fallback_addresses_.reset(); 410 } 411 NotifyDelegateOfCompletion(result); // Deletes |this| 412 } 413 414 int TransportConnectJob::ConnectInternal() { 415 return helper_.DoConnectInternal(this); 416 } 417 418 scoped_ptr<ConnectJob> 419 TransportClientSocketPool::TransportConnectJobFactory::NewConnectJob( 420 const std::string& group_name, 421 const PoolBase::Request& request, 422 ConnectJob::Delegate* delegate) const { 423 return scoped_ptr<ConnectJob>( 424 new TransportConnectJob(group_name, 425 request.priority(), 426 request.params(), 427 ConnectionTimeout(), 428 client_socket_factory_, 429 host_resolver_, 430 delegate, 431 net_log_)); 432 } 433 434 base::TimeDelta 435 TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout() 436 const { 437 return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds); 438 } 439 440 TransportClientSocketPool::TransportClientSocketPool( 441 int max_sockets, 442 int max_sockets_per_group, 443 ClientSocketPoolHistograms* histograms, 444 HostResolver* host_resolver, 445 ClientSocketFactory* client_socket_factory, 446 NetLog* net_log) 447 : base_(NULL, max_sockets, max_sockets_per_group, histograms, 448 ClientSocketPool::unused_idle_socket_timeout(), 449 ClientSocketPool::used_idle_socket_timeout(), 450 new TransportConnectJobFactory(client_socket_factory, 451 host_resolver, net_log)) { 452 base_.EnableConnectBackupJobs(); 453 } 454 455 TransportClientSocketPool::~TransportClientSocketPool() {} 456 457 int TransportClientSocketPool::RequestSocket( 458 const std::string& group_name, 459 const void* params, 460 RequestPriority priority, 461 ClientSocketHandle* handle, 462 const CompletionCallback& callback, 463 const BoundNetLog& net_log) { 464 const scoped_refptr<TransportSocketParams>* casted_params = 465 static_cast<const scoped_refptr<TransportSocketParams>*>(params); 466 467 NetLogTcpClientSocketPoolRequestedSocket(net_log, casted_params); 468 469 return base_.RequestSocket(group_name, *casted_params, priority, handle, 470 callback, net_log); 471 } 472 473 void TransportClientSocketPool::NetLogTcpClientSocketPoolRequestedSocket( 474 const BoundNetLog& net_log, 475 const scoped_refptr<TransportSocketParams>* casted_params) { 476 if (net_log.IsLogging()) { 477 // TODO(eroman): Split out the host and port parameters. 478 net_log.AddEvent( 479 NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET, 480 CreateNetLogHostPortPairCallback( 481 &casted_params->get()->destination().host_port_pair())); 482 } 483 } 484 485 void TransportClientSocketPool::RequestSockets( 486 const std::string& group_name, 487 const void* params, 488 int num_sockets, 489 const BoundNetLog& net_log) { 490 const scoped_refptr<TransportSocketParams>* casted_params = 491 static_cast<const scoped_refptr<TransportSocketParams>*>(params); 492 493 if (net_log.IsLogging()) { 494 // TODO(eroman): Split out the host and port parameters. 495 net_log.AddEvent( 496 NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS, 497 CreateNetLogHostPortPairCallback( 498 &casted_params->get()->destination().host_port_pair())); 499 } 500 501 base_.RequestSockets(group_name, *casted_params, num_sockets, net_log); 502 } 503 504 void TransportClientSocketPool::CancelRequest( 505 const std::string& group_name, 506 ClientSocketHandle* handle) { 507 base_.CancelRequest(group_name, handle); 508 } 509 510 void TransportClientSocketPool::ReleaseSocket( 511 const std::string& group_name, 512 scoped_ptr<StreamSocket> socket, 513 int id) { 514 base_.ReleaseSocket(group_name, socket.Pass(), id); 515 } 516 517 void TransportClientSocketPool::FlushWithError(int error) { 518 base_.FlushWithError(error); 519 } 520 521 void TransportClientSocketPool::CloseIdleSockets() { 522 base_.CloseIdleSockets(); 523 } 524 525 int TransportClientSocketPool::IdleSocketCount() const { 526 return base_.idle_socket_count(); 527 } 528 529 int TransportClientSocketPool::IdleSocketCountInGroup( 530 const std::string& group_name) const { 531 return base_.IdleSocketCountInGroup(group_name); 532 } 533 534 LoadState TransportClientSocketPool::GetLoadState( 535 const std::string& group_name, const ClientSocketHandle* handle) const { 536 return base_.GetLoadState(group_name, handle); 537 } 538 539 base::DictionaryValue* TransportClientSocketPool::GetInfoAsValue( 540 const std::string& name, 541 const std::string& type, 542 bool include_nested_pools) const { 543 return base_.GetInfoAsValue(name, type); 544 } 545 546 base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const { 547 return base_.ConnectionTimeout(); 548 } 549 550 ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const { 551 return base_.histograms(); 552 } 553 554 bool TransportClientSocketPool::IsStalled() const { 555 return base_.IsStalled(); 556 } 557 558 void TransportClientSocketPool::AddHigherLayeredPool( 559 HigherLayeredPool* higher_pool) { 560 base_.AddHigherLayeredPool(higher_pool); 561 } 562 563 void TransportClientSocketPool::RemoveHigherLayeredPool( 564 HigherLayeredPool* higher_pool) { 565 base_.RemoveHigherLayeredPool(higher_pool); 566 } 567 568 } // namespace net 569