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