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
27using base::TimeDelta;
28
29namespace 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.
34const int TransportConnectJobHelper::kIPv6FallbackTimerInMs = 300;
35
36namespace {
37
38// Returns true iff all addresses in |list| are in the IPv6 family.
39bool 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|.
52static 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.
56static base::LazyInstance<base::TimeTicks>::Leaky
57    g_last_connect_time = LAZY_INSTANCE_INITIALIZER;
58
59TransportSocketParams::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
80TransportSocketParams::~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.
91static const int kTransportConnectJobTimeoutInSeconds = 240;  // 4 minutes.
92
93TransportConnectJobHelper::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
104TransportConnectJobHelper::~TransportConnectJobHelper() {}
105
106int 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
115int 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
134base::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
194TransportConnectJob::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
210TransportConnectJob::~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
215LoadState 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
231void 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
240int TransportConnectJob::DoResolveHost() {
241  return helper_.DoResolveHost(priority(), net_log());
242}
243
244int TransportConnectJob::DoResolveHostComplete(int result) {
245  return helper_.DoResolveHostComplete(result, net_log());
246}
247
248int 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
302int 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
360void 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
386void 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
414int TransportConnectJob::ConnectInternal() {
415  return helper_.DoConnectInternal(this);
416}
417
418scoped_ptr<ConnectJob>
419TransportClientSocketPool::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
434base::TimeDelta
435    TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout()
436    const {
437  return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds);
438}
439
440TransportClientSocketPool::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
455TransportClientSocketPool::~TransportClientSocketPool() {}
456
457int 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
473void 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
485void 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
504void TransportClientSocketPool::CancelRequest(
505    const std::string& group_name,
506    ClientSocketHandle* handle) {
507  base_.CancelRequest(group_name, handle);
508}
509
510void 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
517void TransportClientSocketPool::FlushWithError(int error) {
518  base_.FlushWithError(error);
519}
520
521void TransportClientSocketPool::CloseIdleSockets() {
522  base_.CloseIdleSockets();
523}
524
525int TransportClientSocketPool::IdleSocketCount() const {
526  return base_.idle_socket_count();
527}
528
529int TransportClientSocketPool::IdleSocketCountInGroup(
530    const std::string& group_name) const {
531  return base_.IdleSocketCountInGroup(group_name);
532}
533
534LoadState TransportClientSocketPool::GetLoadState(
535    const std::string& group_name, const ClientSocketHandle* handle) const {
536  return base_.GetLoadState(group_name, handle);
537}
538
539base::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
546base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const {
547  return base_.ConnectionTimeout();
548}
549
550ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const {
551  return base_.histograms();
552}
553
554bool TransportClientSocketPool::IsStalled() const {
555  return base_.IsStalled();
556}
557
558void TransportClientSocketPool::AddHigherLayeredPool(
559    HigherLayeredPool* higher_pool) {
560  base_.AddHigherLayeredPool(higher_pool);
561}
562
563void TransportClientSocketPool::RemoveHigherLayeredPool(
564    HigherLayeredPool* higher_pool) {
565  base_.RemoveHigherLayeredPool(higher_pool);
566}
567
568}  // namespace net
569