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
AddressListStartsWithIPv6AndHasAnIPv4Addr(const AddressList & addrlist)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
AddressListOnlyContainsIPv6Addresses(const AddressList & addrlist)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
TransportSocketParams(const HostPortPair & host_port_pair,RequestPriority priority,const GURL & referrer,bool disable_resolver_cache,bool ignore_limits)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
~TransportSocketParams()74 TransportSocketParams::~TransportSocketParams() {}
75
Initialize(RequestPriority priority,const GURL & referrer,bool disable_resolver_cache)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
getUID(uid_t * uid) const89 bool TransportSocketParams::getUID(uid_t *uid) const {
90 if (!valid_uid_) {
91 return false;
92 }
93 *uid = calling_uid_;
94 return true;
95 }
96
setUID(uid_t uid)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
TransportConnectJob(const std::string & group_name,const scoped_refptr<TransportSocketParams> & params,base::TimeDelta timeout_duration,ClientSocketFactory * client_socket_factory,HostResolver * host_resolver,Delegate * delegate,NetLog * net_log)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
~TransportConnectJob()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
GetLoadState() const140 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
MakeAddrListStartWithIPv4(AddressList * addrlist)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
OnIOComplete(int result)186 void TransportConnectJob::OnIOComplete(int result) {
187 int rv = DoLoop(result);
188 if (rv != ERR_IO_PENDING)
189 NotifyDelegateOfCompletion(rv); // Deletes |this|
190 }
191
DoLoop(int result)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
DoResolveHost()224 int TransportConnectJob::DoResolveHost() {
225 next_state_ = STATE_RESOLVE_HOST_COMPLETE;
226 return resolver_.Resolve(params_->destination(), &addresses_, &callback_,
227 net_log());
228 }
229
DoResolveHostComplete(int result)230 int TransportConnectJob::DoResolveHostComplete(int result) {
231 if (result == OK)
232 next_state_ = STATE_TRANSPORT_CONNECT;
233 return result;
234 }
235
DoTransportConnect()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
DoTransportConnectComplete(int result)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
DoIPv6FallbackTransportConnect()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
DoIPv6FallbackTransportConnectComplete(int result)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
ConnectInternal()396 int TransportConnectJob::ConnectInternal() {
397 next_state_ = STATE_RESOLVE_HOST;
398 start_time_ = base::TimeTicks::Now();
399 return DoLoop(OK);
400 }
401
402 ConnectJob*
NewConnectJob(const std::string & group_name,const PoolBase::Request & request,ConnectJob::Delegate * delegate) const403 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
ConnectionTimeout() const417 TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout()
418 const {
419 return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds);
420 }
421
TransportClientSocketPool(int max_sockets,int max_sockets_per_group,ClientSocketPoolHistograms * histograms,HostResolver * host_resolver,ClientSocketFactory * client_socket_factory,NetLog * net_log)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
~TransportClientSocketPool()438 TransportClientSocketPool::~TransportClientSocketPool() {}
439
RequestSocket(const std::string & group_name,const void * params,RequestPriority priority,ClientSocketHandle * handle,CompletionCallback * callback,const BoundNetLog & net_log)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
RequestSockets(const std::string & group_name,const void * params,int num_sockets,const BoundNetLog & net_log)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
CancelRequest(const std::string & group_name,ClientSocketHandle * handle)483 void TransportClientSocketPool::CancelRequest(
484 const std::string& group_name,
485 ClientSocketHandle* handle) {
486 base_.CancelRequest(group_name, handle);
487 }
488
ReleaseSocket(const std::string & group_name,ClientSocket * socket,int id)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
Flush()496 void TransportClientSocketPool::Flush() {
497 base_.Flush();
498 }
499
CloseIdleSockets()500 void TransportClientSocketPool::CloseIdleSockets() {
501 base_.CloseIdleSockets();
502 }
503
IdleSocketCount() const504 int TransportClientSocketPool::IdleSocketCount() const {
505 return base_.idle_socket_count();
506 }
507
IdleSocketCountInGroup(const std::string & group_name) const508 int TransportClientSocketPool::IdleSocketCountInGroup(
509 const std::string& group_name) const {
510 return base_.IdleSocketCountInGroup(group_name);
511 }
512
GetLoadState(const std::string & group_name,const ClientSocketHandle * handle) const513 LoadState TransportClientSocketPool::GetLoadState(
514 const std::string& group_name, const ClientSocketHandle* handle) const {
515 return base_.GetLoadState(group_name, handle);
516 }
517
GetInfoAsValue(const std::string & name,const std::string & type,bool include_nested_pools) const518 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
ConnectionTimeout() const525 base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const {
526 return base_.ConnectionTimeout();
527 }
528
histograms() const529 ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const {
530 return base_.histograms();
531 }
532
533 } // namespace net
534