summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-19 17:51:12 +0000
committerwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-19 17:51:12 +0000
commit70bd03d5a0e63545043b32286404b6aadf10ba75 (patch)
treef3f35cf5e0d221d06f3942ef59bdb0fc7ee8667f
parent36e0d1896ee038e157cde8df4164746caef16336 (diff)
downloadchromium_src-70bd03d5a0e63545043b32286404b6aadf10ba75.zip
chromium_src-70bd03d5a0e63545043b32286404b6aadf10ba75.tar.gz
chromium_src-70bd03d5a0e63545043b32286404b6aadf10ba75.tar.bz2
Merge 84251, 85083, 85188, 85375.
Add a fallback socket connect() for IPv6. When a hostname has both IPv6 and IPv4 addresses, and the IPv6 address is listed first, we start a timer (300ms) (deliberately chosen to be different from the backup connect job). If the timer fires, that means the IPv6 connect() hasn't completed yet, and we start a second socket connect() where we give it the same AddressList, except we move all IPv6 addresses that are in front of the first IPv4 address to the end. That way, we will use the first IPv4 address. We will race these two connect()s and pass the first one to complete to ConnectJob::set_socket(). Adds 4 new TCP connection latency histograms to assess the new behavior: IPv6 raceable (includes both when it races and doesn't, which are distinguished by whether or not the samples exceed the race timeout of 300ms) IPv6 solo (no IPv4 addresses to race against) IPv4 no race (IPv4 is the first address, so we're not racing) IPv4 wins race (IPv4 raced and won, even though it started behind). BUG=81686 TEST=On Linux, drop ip6 packets via `sudo ip6tables -A OUTPUT -p tcp -j DROP`. Then test against a site with both IPv6 and IPv4 addresses (such as www.ripe.net). It should load without hitting 20s connect() timeouts on the IPv6 addresses. Review URL: http://codereview.chromium.org/7029049 git-svn-id: svn://svn.chromium.org/chrome/branches/742/src@85934 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/base/address_list.cc58
-rw-r--r--net/base/net_util.cc53
-rw-r--r--net/base/net_util.h10
-rw-r--r--net/socket/transport_client_socket_pool.cc197
-rw-r--r--net/socket/transport_client_socket_pool.h41
-rw-r--r--net/socket/transport_client_socket_pool_unittest.cc307
6 files changed, 578 insertions, 88 deletions
diff --git a/net/base/address_list.cc b/net/base/address_list.cc
index 83df606..1804736 100644
--- a/net/base/address_list.cc
+++ b/net/base/address_list.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -22,56 +22,6 @@ char* do_strdup(const char* src) {
#endif
}
-// Make a copy of |info| (the dynamically-allocated parts are copied as well).
-// If |recursive| is true, chained entries via ai_next are copied too.
-// Copy returned by this function should be deleted using
-// DeleteCopyOfAddrinfo(), and NOT freeaddrinfo().
-struct addrinfo* CreateCopyOfAddrinfo(const struct addrinfo* info,
- bool recursive) {
- DCHECK(info);
- struct addrinfo* copy = new addrinfo;
-
- // Copy all the fields (some of these are pointers, we will fix that next).
- memcpy(copy, info, sizeof(addrinfo));
-
- // ai_canonname is a NULL-terminated string.
- if (info->ai_canonname) {
- copy->ai_canonname = do_strdup(info->ai_canonname);
- }
-
- // ai_addr is a buffer of length ai_addrlen.
- if (info->ai_addr) {
- copy->ai_addr = reinterpret_cast<sockaddr *>(new char[info->ai_addrlen]);
- memcpy(copy->ai_addr, info->ai_addr, info->ai_addrlen);
- }
-
- // Recursive copy.
- if (recursive && info->ai_next)
- copy->ai_next = CreateCopyOfAddrinfo(info->ai_next, recursive);
- else
- copy->ai_next = NULL;
-
- return copy;
-}
-
-// Free an addrinfo that was created by CreateCopyOfAddrinfo().
-void FreeMyAddrinfo(struct addrinfo* info) {
- DCHECK(info);
- if (info->ai_canonname)
- free(info->ai_canonname); // Allocated by strdup.
-
- if (info->ai_addr)
- delete [] reinterpret_cast<char*>(info->ai_addr);
-
- struct addrinfo* next = info->ai_next;
-
- delete info;
-
- // Recursive free.
- if (next)
- FreeMyAddrinfo(next);
-}
-
// Assign the port for all addresses in the list.
void SetPortRecursive(struct addrinfo* info, int port) {
uint16* port_field = GetPortFieldFromAddrinfo(info);
@@ -283,12 +233,12 @@ AddressList::Data::Data(struct addrinfo* ai, bool is_system_created)
}
AddressList::Data::~Data() {
- // Call either freeaddrinfo(head), or FreeMyAddrinfo(head), depending who
- // created the data.
+ // Call either freeaddrinfo(head), or FreeCopyOfAddrinfo(head), depending on
+ // who created the data.
if (is_system_created)
freeaddrinfo(head);
else
- FreeMyAddrinfo(head);
+ FreeCopyOfAddrinfo(head);
}
} // namespace net
diff --git a/net/base/net_util.cc b/net/base/net_util.cc
index a6fe220..38035ca 100644
--- a/net/base/net_util.cc
+++ b/net/base/net_util.cc
@@ -1120,6 +1120,14 @@ std::wstring FormatUrlInternal(const GURL& url,
return url_string;
}
+char* do_strdup(const char* src) {
+#if defined(OS_WIN)
+ return _strdup(src);
+#else
+ return strdup(src);
+#endif
+}
+
} // namespace
const FormatUrlType kFormatUrlOmitNothing = 0;
@@ -2182,6 +2190,51 @@ bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number,
return true;
}
+struct addrinfo* CreateCopyOfAddrinfo(const struct addrinfo* info,
+ bool recursive) {
+ DCHECK(info);
+ struct addrinfo* copy = new addrinfo;
+
+ // Copy all the fields (some of these are pointers, we will fix that next).
+ memcpy(copy, info, sizeof(addrinfo));
+
+ // ai_canonname is a NULL-terminated string.
+ if (info->ai_canonname) {
+ copy->ai_canonname = do_strdup(info->ai_canonname);
+ }
+
+ // ai_addr is a buffer of length ai_addrlen.
+ if (info->ai_addr) {
+ copy->ai_addr = reinterpret_cast<sockaddr *>(new char[info->ai_addrlen]);
+ memcpy(copy->ai_addr, info->ai_addr, info->ai_addrlen);
+ }
+
+ // Recursive copy.
+ if (recursive && info->ai_next)
+ copy->ai_next = CreateCopyOfAddrinfo(info->ai_next, recursive);
+ else
+ copy->ai_next = NULL;
+
+ return copy;
+}
+
+void FreeCopyOfAddrinfo(struct addrinfo* info) {
+ DCHECK(info);
+ if (info->ai_canonname)
+ free(info->ai_canonname); // Allocated by strdup.
+
+ if (info->ai_addr)
+ delete [] reinterpret_cast<char*>(info->ai_addr);
+
+ struct addrinfo* next = info->ai_next;
+
+ delete info;
+
+ // Recursive free.
+ if (next)
+ FreeCopyOfAddrinfo(next);
+}
+
// Returns the port field of the sockaddr in |info|.
uint16* GetPortFieldFromAddrinfo(struct addrinfo* info) {
const struct addrinfo* const_info = info;
diff --git a/net/base/net_util.h b/net/base/net_util.h
index 0ff3369..df99469 100644
--- a/net/base/net_util.h
+++ b/net/base/net_util.h
@@ -441,6 +441,16 @@ bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number,
const IPAddressNumber& ip_prefix,
size_t prefix_length_in_bits);
+// Makes a copy of |info|. The dynamically-allocated parts are copied as well.
+// If |recursive| is true, chained entries via ai_next are copied too.
+// The copy returned by this function should be freed using
+// FreeCopyOfAddrinfo(), and NOT freeaddrinfo().
+struct addrinfo* CreateCopyOfAddrinfo(const struct addrinfo* info,
+ bool recursive);
+
+// Frees an addrinfo that was created by CreateCopyOfAddrinfo().
+void FreeCopyOfAddrinfo(struct addrinfo* info);
+
// Returns the port field of the sockaddr in |info|.
const uint16* GetPortFieldFromAddrinfo(const struct addrinfo* info);
uint16* GetPortFieldFromAddrinfo(struct addrinfo* info);
diff --git a/net/socket/transport_client_socket_pool.cc b/net/socket/transport_client_socket_pool.cc
index 54a817d..eb14983 100644
--- a/net/socket/transport_client_socket_pool.cc
+++ b/net/socket/transport_client_socket_pool.cc
@@ -10,8 +10,10 @@
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/time.h"
+#include "net/base/ip_endpoint.h"
#include "net/base/net_log.h"
#include "net/base/net_errors.h"
+#include "net/base/sys_addrinfo.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/client_socket_pool_base.h"
@@ -21,10 +23,46 @@ using base::TimeDelta;
namespace net {
-TransportSocketParams::TransportSocketParams(const HostPortPair& host_port_pair,
- RequestPriority priority, const GURL& referrer,
- bool disable_resolver_cache,
- bool ignore_limits)
+// TODO(willchan): Base this off RTT instead of statically setting it. Note we
+// choose a timeout that is different from the backup connect job timer so they
+// don't synchronize.
+const int TransportConnectJob::kIPv6FallbackTimerInMs = 300;
+
+namespace {
+
+bool AddressListStartsWithIPv6AndHasAnIPv4Addr(const AddressList& addrlist) {
+ const struct addrinfo* ai = addrlist.head();
+ if (ai->ai_family != AF_INET6)
+ return false;
+
+ ai = ai->ai_next;
+ while (ai) {
+ if (ai->ai_family != AF_INET6)
+ return true;
+ ai = ai->ai_next;
+ }
+
+ return false;
+}
+
+bool AddressListOnlyContainsIPv6Addresses(const AddressList& addrlist) {
+ DCHECK(addrlist.head());
+ for (const struct addrinfo* ai = addrlist.head(); ai; ai = ai->ai_next) {
+ if (ai->ai_family != AF_INET6)
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+TransportSocketParams::TransportSocketParams(
+ const HostPortPair& host_port_pair,
+ RequestPriority priority,
+ const GURL& referrer,
+ bool disable_resolver_cache,
+ bool ignore_limits)
: destination_(host_port_pair), ignore_limits_(ignore_limits) {
Initialize(priority, referrer, disable_resolver_cache);
}
@@ -32,8 +70,8 @@ TransportSocketParams::TransportSocketParams(const HostPortPair& host_port_pair,
TransportSocketParams::~TransportSocketParams() {}
void TransportSocketParams::Initialize(RequestPriority priority,
- const GURL& referrer,
- bool disable_resolver_cache) {
+ const GURL& referrer,
+ bool disable_resolver_cache) {
// The referrer is used by the DNS prefetch system to correlate resolutions
// with the page that triggered them. It doesn't impact the actual addresses
// that we resolve to.
@@ -69,7 +107,11 @@ TransportConnectJob::TransportConnectJob(
ALLOW_THIS_IN_INITIALIZER_LIST(
callback_(this,
&TransportConnectJob::OnIOComplete)),
- resolver_(host_resolver) {}
+ resolver_(host_resolver),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ fallback_callback_(
+ this,
+ &TransportConnectJob::DoIPv6FallbackTransportConnectComplete)) {}
TransportConnectJob::~TransportConnectJob() {
// We don't worry about cancelling the host resolution and TCP connect, since
@@ -90,6 +132,38 @@ LoadState TransportConnectJob::GetLoadState() const {
}
}
+// static
+void TransportConnectJob::MakeAddrListStartWithIPv4(AddressList* addrlist) {
+ if (addrlist->head()->ai_family != AF_INET6)
+ return;
+ bool has_ipv4 = false;
+ for (const struct addrinfo* ai = addrlist->head(); ai; ai = ai->ai_next) {
+ if (ai->ai_family != AF_INET6) {
+ has_ipv4 = true;
+ break;
+ }
+ }
+ if (!has_ipv4)
+ return;
+
+ struct addrinfo* head = CreateCopyOfAddrinfo(addrlist->head(), true);
+ struct addrinfo* tail = head;
+ while (tail->ai_next)
+ tail = tail->ai_next;
+ char* canonname = head->ai_canonname;
+ head->ai_canonname = NULL;
+ while (head->ai_family == AF_INET6) {
+ tail->ai_next = head;
+ tail = head;
+ head = head->ai_next;
+ tail->ai_next = NULL;
+ }
+ head->ai_canonname = canonname;
+
+ addrlist->Copy(head, true);
+ FreeCopyOfAddrinfo(head);
+}
+
void TransportConnectJob::OnIOComplete(int result) {
int rv = DoLoop(result);
if (rv != ERR_IO_PENDING)
@@ -142,14 +216,22 @@ int TransportConnectJob::DoResolveHostComplete(int result) {
int TransportConnectJob::DoTransportConnect() {
next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
- set_socket(client_socket_factory_->CreateTransportClientSocket(
+ transport_socket_.reset(client_socket_factory_->CreateTransportClientSocket(
addresses_, net_log().net_log(), net_log().source()));
connect_start_time_ = base::TimeTicks::Now();
- return socket()->Connect(&callback_);
+ int rv = transport_socket_->Connect(&callback_);
+ if (rv == ERR_IO_PENDING &&
+ AddressListStartsWithIPv6AndHasAnIPv4Addr(addresses_)) {
+ fallback_timer_.Start(
+ base::TimeDelta::FromMilliseconds(kIPv6FallbackTimerInMs),
+ this, &TransportConnectJob::DoIPv6FallbackTransportConnect);
+ }
+ return rv;
}
int TransportConnectJob::DoTransportConnectComplete(int result) {
if (result == OK) {
+ bool is_ipv4 = addresses_.head()->ai_family != AF_INET6;
DCHECK(connect_start_time_ != base::TimeTicks());
DCHECK(start_time_ != base::TimeTicks());
base::TimeTicks now = base::TimeTicks::Now();
@@ -167,14 +249,107 @@ int TransportConnectJob::DoTransportConnectComplete(int result) {
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMinutes(10),
100);
+
+ if (is_ipv4) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_No_Race",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ } else {
+ if (AddressListOnlyContainsIPv6Addresses(addresses_)) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Solo",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ } else {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Raceable",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ }
+ }
+ set_socket(transport_socket_.release());
+ fallback_timer_.Stop();
} else {
- // Delete the socket on error.
- set_socket(NULL);
+ // Be a bit paranoid and kill off the fallback members to prevent reuse.
+ fallback_transport_socket_.reset();
+ fallback_addresses_.reset();
}
return result;
}
+void TransportConnectJob::DoIPv6FallbackTransportConnect() {
+ // The timer should only fire while we're waiting for the main connect to
+ // succeed.
+ if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) {
+ NOTREACHED();
+ return;
+ }
+
+ DCHECK(!fallback_transport_socket_.get());
+ DCHECK(!fallback_addresses_.get());
+
+ fallback_addresses_.reset(new AddressList(addresses_));
+ MakeAddrListStartWithIPv4(fallback_addresses_.get());
+ fallback_transport_socket_.reset(
+ client_socket_factory_->CreateTransportClientSocket(
+ *fallback_addresses_, net_log().net_log(), net_log().source()));
+ fallback_connect_start_time_ = base::TimeTicks::Now();
+ int rv = fallback_transport_socket_->Connect(&fallback_callback_);
+ if (rv != ERR_IO_PENDING)
+ DoIPv6FallbackTransportConnectComplete(rv);
+}
+
+void TransportConnectJob::DoIPv6FallbackTransportConnectComplete(int result) {
+ // This should only happen when we're waiting for the main connect to succeed.
+ if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) {
+ NOTREACHED();
+ return;
+ }
+
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(fallback_transport_socket_.get());
+ DCHECK(fallback_addresses_.get());
+
+ if (result == OK) {
+ DCHECK(fallback_connect_start_time_ != base::TimeTicks());
+ DCHECK(start_time_ != base::TimeTicks());
+ base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeDelta total_duration = now - start_time_;
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.DNS_Resolution_And_TCP_Connection_Latency2",
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+
+ base::TimeDelta connect_duration = now - fallback_connect_start_time_;
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_Wins_Race",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ set_socket(fallback_transport_socket_.release());
+ next_state_ = STATE_NONE;
+ transport_socket_.reset();
+ } else {
+ // Be a bit paranoid and kill off the fallback members to prevent reuse.
+ fallback_transport_socket_.reset();
+ fallback_addresses_.reset();
+ }
+ NotifyDelegateOfCompletion(result); // Deletes |this|
+}
+
int TransportConnectJob::ConnectInternal() {
next_state_ = STATE_RESOLVE_HOST;
start_time_ = base::TimeTicks::Now();
diff --git a/net/socket/transport_client_socket_pool.h b/net/socket/transport_client_socket_pool.h
index 1661ed6..bf630f6 100644
--- a/net/socket/transport_client_socket_pool.h
+++ b/net/socket/transport_client_socket_pool.h
@@ -48,21 +48,36 @@ class TransportSocketParams : public base::RefCounted<TransportSocketParams> {
};
// TransportConnectJob handles the host resolution necessary for socket creation
-// and the transport (likely TCP) connect.
+// and the transport (likely TCP) connect. TransportConnectJob also has fallback
+// logic for IPv6 connect() timeouts (which may happen due to networks / routers
+// with broken IPv6 support). Those timeouts take 20s, so rather than make the
+// user wait 20s for the timeout to fire, we use a fallback timer
+// (kIPv6FallbackTimerInMs) and start a connect() to a IPv4 address if the timer
+// fires. Then we race the IPv4 connect() against the IPv6 connect() (which has
+// a headstart) and return the one that completes first to the socket pool.
class TransportConnectJob : public ConnectJob {
public:
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);
+ const scoped_refptr<TransportSocketParams>& params,
+ base::TimeDelta timeout_duration,
+ ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ Delegate* delegate,
+ NetLog* net_log);
virtual ~TransportConnectJob();
// ConnectJob methods.
virtual LoadState GetLoadState() const;
+ // Makes |addrlist| start with an IPv4 address if |addrlist| contains any
+ // IPv4 address.
+ //
+ // WARNING: this method should only be used to implement the prefer-IPv4
+ // hack. It is a public method for the unit tests.
+ static void MakeAddrListStartWithIPv4(AddressList* addrlist);
+
+ static const int kIPv6FallbackTimerInMs;
+
private:
enum State {
STATE_RESOLVE_HOST,
@@ -82,6 +97,10 @@ class TransportConnectJob : public ConnectJob {
int DoTransportConnect();
int DoTransportConnectComplete(int result);
+ // Not part of the state machine.
+ void DoIPv6FallbackTransportConnect();
+ void DoIPv6FallbackTransportConnectComplete(int result);
+
// Begins the host resolution and the TCP connect. Returns OK on success
// and ERR_IO_PENDING if it cannot immediately service the request.
// Otherwise, it returns a net error code.
@@ -100,6 +119,14 @@ class TransportConnectJob : public ConnectJob {
// The time the connect was started (after DNS finished).
base::TimeTicks connect_start_time_;
+ scoped_ptr<ClientSocket> transport_socket_;
+
+ scoped_ptr<ClientSocket> fallback_transport_socket_;
+ scoped_ptr<AddressList> fallback_addresses_;
+ CompletionCallbackImpl<TransportConnectJob> fallback_callback_;
+ base::TimeTicks fallback_connect_start_time_;
+ base::OneShotTimer<TransportConnectJob> fallback_timer_;
+
DISALLOW_COPY_AND_ASSIGN(TransportConnectJob);
};
diff --git a/net/socket/transport_client_socket_pool_unittest.cc b/net/socket/transport_client_socket_pool_unittest.cc
index ae937e9..204c86d 100644
--- a/net/socket/transport_client_socket_pool_unittest.cc
+++ b/net/socket/transport_client_socket_pool_unittest.cc
@@ -6,10 +6,14 @@
#include "base/callback.h"
#include "base/compiler_specific.h"
+#include "base/logging.h"
#include "base/message_loop.h"
#include "base/threading/platform_thread.h"
+#include "net/base/ip_endpoint.h"
#include "net/base/mock_host_resolver.h"
#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/sys_addrinfo.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/client_socket.h"
#include "net/socket/client_socket_factory.h"
@@ -29,9 +33,23 @@ const int kMaxSockets = 32;
const int kMaxSocketsPerGroup = 6;
const net::RequestPriority kDefaultPriority = LOW;
+void SetIPv4Address(IPEndPoint* address) {
+ IPAddressNumber number;
+ CHECK(ParseIPLiteralToNumber("1.1.1.1", &number));
+ *address = IPEndPoint(number, 80);
+}
+
+void SetIPv6Address(IPEndPoint* address) {
+ IPAddressNumber number;
+ CHECK(ParseIPLiteralToNumber("1:abcd::3:4:ff", &number));
+ *address = IPEndPoint(number, 80);
+}
+
class MockClientSocket : public ClientSocket {
public:
- MockClientSocket() : connected_(false) {}
+ MockClientSocket(const AddressList& addrlist)
+ : connected_(false),
+ addrlist_(addrlist) {}
// ClientSocket methods:
virtual int Connect(CompletionCallback* callback) {
@@ -51,7 +69,13 @@ class MockClientSocket : public ClientSocket {
return ERR_UNEXPECTED;
}
virtual int GetLocalAddress(IPEndPoint* address) const {
- return ERR_UNEXPECTED;
+ if (!connected_)
+ return ERR_SOCKET_NOT_CONNECTED;
+ if (addrlist_.head()->ai_family == AF_INET)
+ SetIPv4Address(address);
+ else
+ SetIPv6Address(address);
+ return OK;
}
virtual const BoundNetLog& NetLog() const {
return net_log_;
@@ -76,12 +100,13 @@ class MockClientSocket : public ClientSocket {
private:
bool connected_;
+ const AddressList addrlist_;
BoundNetLog net_log_;
};
class MockFailingClientSocket : public ClientSocket {
public:
- MockFailingClientSocket() {}
+ MockFailingClientSocket(const AddressList& addrlist) : addrlist_(addrlist) {}
// ClientSocket methods:
virtual int Connect(CompletionCallback* callback) {
@@ -125,6 +150,7 @@ class MockFailingClientSocket : public ClientSocket {
virtual bool SetSendBufferSize(int32 size) { return true; }
private:
+ const AddressList addrlist_;
BoundNetLog net_log_;
};
@@ -134,12 +160,17 @@ class MockPendingClientSocket : public ClientSocket {
// or fail.
// |should_stall| indicates that this socket should never connect.
// |delay_ms| is the delay, in milliseconds, before simulating a connect.
- MockPendingClientSocket(bool should_connect, bool should_stall, int delay_ms)
+ MockPendingClientSocket(
+ const AddressList& addrlist,
+ bool should_connect,
+ bool should_stall,
+ int delay_ms)
: method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
should_connect_(should_connect),
should_stall_(should_stall),
delay_ms_(delay_ms),
- is_connected_(false) {}
+ is_connected_(false),
+ addrlist_(addrlist) {}
// ClientSocket methods:
virtual int Connect(CompletionCallback* callback) {
@@ -162,7 +193,13 @@ class MockPendingClientSocket : public ClientSocket {
return ERR_UNEXPECTED;
}
virtual int GetLocalAddress(IPEndPoint* address) const {
- return ERR_UNEXPECTED;
+ if (!is_connected_)
+ return ERR_SOCKET_NOT_CONNECTED;
+ if (addrlist_.head()->ai_family == AF_INET)
+ SetIPv4Address(address);
+ else
+ SetIPv6Address(address);
+ return OK;
}
virtual const BoundNetLog& NetLog() const {
return net_log_;
@@ -205,6 +242,7 @@ class MockPendingClientSocket : public ClientSocket {
bool should_stall_;
int delay_ms_;
bool is_connected_;
+ const AddressList addrlist_;
BoundNetLog net_log_;
};
@@ -224,7 +262,8 @@ class MockClientSocketFactory : public ClientSocketFactory {
MockClientSocketFactory()
: allocation_count_(0), client_socket_type_(MOCK_CLIENT_SOCKET),
client_socket_types_(NULL), client_socket_index_(0),
- client_socket_index_max_(0) {}
+ client_socket_index_max_(0),
+ delay_ms_(ClientSocketPool::kMaxConnectRetryIntervalMs) {}
virtual ClientSocket* CreateTransportClientSocket(
const AddressList& addresses,
@@ -240,21 +279,20 @@ class MockClientSocketFactory : public ClientSocketFactory {
switch (type) {
case MOCK_CLIENT_SOCKET:
- return new MockClientSocket();
+ return new MockClientSocket(addresses);
case MOCK_FAILING_CLIENT_SOCKET:
- return new MockFailingClientSocket();
+ return new MockFailingClientSocket(addresses);
case MOCK_PENDING_CLIENT_SOCKET:
- return new MockPendingClientSocket(true, false, 0);
+ return new MockPendingClientSocket(addresses, true, false, 0);
case MOCK_PENDING_FAILING_CLIENT_SOCKET:
- return new MockPendingClientSocket(false, false, 0);
+ return new MockPendingClientSocket(addresses, false, false, 0);
case MOCK_DELAYED_CLIENT_SOCKET:
- return new MockPendingClientSocket(true, false,
- ClientSocketPool::kMaxConnectRetryIntervalMs);
+ return new MockPendingClientSocket(addresses, true, false, delay_ms_);
case MOCK_STALLED_CLIENT_SOCKET:
- return new MockPendingClientSocket(true, true, 0);
+ return new MockPendingClientSocket(addresses, true, true, 0);
default:
NOTREACHED();
- return new MockClientSocket();
+ return new MockClientSocket(addresses);
}
}
@@ -289,12 +327,15 @@ class MockClientSocketFactory : public ClientSocketFactory {
client_socket_index_max_ = num_types;
}
+ void set_delay_ms(int delay_ms) { delay_ms_ = delay_ms; }
+
private:
int allocation_count_;
ClientSocketType client_socket_type_;
ClientSocketType* client_socket_types_;
int client_socket_index_;
int client_socket_index_max_;
+ int delay_ms_;
};
class TransportClientSocketPoolTest : public testing::Test {
@@ -355,6 +396,89 @@ class TransportClientSocketPoolTest : public testing::Test {
ClientSocketPoolTest test_base_;
};
+TEST(TransportConnectJobTest, MakeAddrListStartWithIPv4) {
+ IPAddressNumber ip_number;
+ ASSERT_TRUE(ParseIPLiteralToNumber("192.168.1.1", &ip_number));
+ AddressList addrlist_v4_1(ip_number, 80, false);
+ ASSERT_TRUE(ParseIPLiteralToNumber("192.168.1.2", &ip_number));
+ AddressList addrlist_v4_2(ip_number, 80, false);
+ ASSERT_TRUE(ParseIPLiteralToNumber("2001:4860:b006::64", &ip_number));
+ AddressList addrlist_v6_1(ip_number, 80, false);
+ ASSERT_TRUE(ParseIPLiteralToNumber("2001:4860:b006::66", &ip_number));
+ AddressList addrlist_v6_2(ip_number, 80, false);
+
+ AddressList addrlist;
+ const struct addrinfo* ai;
+
+ // Test 1: IPv4 only. Expect no change.
+ addrlist.Copy(addrlist_v4_1.head(), true);
+ addrlist.Append(addrlist_v4_2.head());
+ TransportConnectJob::MakeAddrListStartWithIPv4(&addrlist);
+ ai = addrlist.head();
+ EXPECT_EQ(AF_INET, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET, ai->ai_family);
+ EXPECT_TRUE(ai->ai_next == NULL);
+
+ // Test 2: IPv6 only. Expect no change.
+ addrlist.Copy(addrlist_v6_1.head(), true);
+ addrlist.Append(addrlist_v6_2.head());
+ TransportConnectJob::MakeAddrListStartWithIPv4(&addrlist);
+ ai = addrlist.head();
+ EXPECT_EQ(AF_INET6, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET6, ai->ai_family);
+ EXPECT_TRUE(ai->ai_next == NULL);
+
+ // Test 3: IPv4 then IPv6. Expect no change.
+ addrlist.Copy(addrlist_v4_1.head(), true);
+ addrlist.Append(addrlist_v4_2.head());
+ addrlist.Append(addrlist_v6_1.head());
+ addrlist.Append(addrlist_v6_2.head());
+ TransportConnectJob::MakeAddrListStartWithIPv4(&addrlist);
+ ai = addrlist.head();
+ EXPECT_EQ(AF_INET, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET6, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET6, ai->ai_family);
+ EXPECT_TRUE(ai->ai_next == NULL);
+
+ // Test 4: IPv6, IPv4, IPv6, IPv4. Expect first IPv6 moved to the end.
+ addrlist.Copy(addrlist_v6_1.head(), true);
+ addrlist.Append(addrlist_v4_1.head());
+ addrlist.Append(addrlist_v6_2.head());
+ addrlist.Append(addrlist_v4_2.head());
+ TransportConnectJob::MakeAddrListStartWithIPv4(&addrlist);
+ ai = addrlist.head();
+ EXPECT_EQ(AF_INET, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET6, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET6, ai->ai_family);
+ EXPECT_TRUE(ai->ai_next == NULL);
+
+ // Test 5: IPv6, IPv6, IPv4, IPv4. Expect first two IPv6's moved to the end.
+ addrlist.Copy(addrlist_v6_1.head(), true);
+ addrlist.Append(addrlist_v6_2.head());
+ addrlist.Append(addrlist_v4_1.head());
+ addrlist.Append(addrlist_v4_2.head());
+ TransportConnectJob::MakeAddrListStartWithIPv4(&addrlist);
+ ai = addrlist.head();
+ EXPECT_EQ(AF_INET, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET6, ai->ai_family);
+ ai = ai->ai_next;
+ EXPECT_EQ(AF_INET6, ai->ai_family);
+ EXPECT_TRUE(ai->ai_next == NULL);
+}
+
TEST_F(TransportClientSocketPoolTest, Basic) {
TestCompletionCallback callback;
ClientSocketHandle handle;
@@ -787,7 +911,7 @@ TEST_F(TransportClientSocketPoolTest, BackupSocketConnect) {
// Wait for the backup socket timer to fire.
base::PlatformThread::Sleep(
- ClientSocketPool::kMaxConnectRetryIntervalMs * 2);
+ ClientSocketPool::kMaxConnectRetryIntervalMs + 50);
// Let the appropriate socket connect.
MessageLoop::current()->RunAllPending();
@@ -935,6 +1059,157 @@ TEST_F(TransportClientSocketPoolTest, BackupSocketFailAfterDelay) {
host_resolver_->set_synchronous_mode(false);
}
+// Test the case of the IPv6 address stalling, and falling back to the IPv4
+// socket which finishes first.
+TEST_F(TransportClientSocketPoolTest, IPv6FallbackSocketIPv4FinishesFirst) {
+ // Create a pool without backup jobs.
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+ TransportClientSocketPool pool(kMaxSockets,
+ kMaxSocketsPerGroup,
+ histograms_.get(),
+ host_resolver_.get(),
+ &client_socket_factory_,
+ NULL);
+
+ MockClientSocketFactory::ClientSocketType case_types[] = {
+ // This is the IPv6 socket.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET,
+ // This is the IPv4 socket.
+ MockClientSocketFactory::MOCK_PENDING_CLIENT_SOCKET
+ };
+
+ client_socket_factory_.set_client_socket_types(case_types, 2);
+
+ // Resolve an AddressList with a IPv6 address first and then a IPv4 address.
+ host_resolver_->rules()->AddIPLiteralRule(
+ "*", "2:abcd::3:4:ff,2.2.2.2", "");
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, &callback, &pool, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ IPEndPoint endpoint;
+ handle.socket()->GetLocalAddress(&endpoint);
+ EXPECT_EQ(kIPv4AddressSize, endpoint.address().size());
+ EXPECT_EQ(2, client_socket_factory_.allocation_count());
+}
+
+// Test the case of the IPv6 address being slow, thus falling back to trying to
+// connect to the IPv4 address, but having the connect to the IPv6 address
+// finish first.
+TEST_F(TransportClientSocketPoolTest, IPv6FallbackSocketIPv6FinishesFirst) {
+ // Create a pool without backup jobs.
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+ TransportClientSocketPool pool(kMaxSockets,
+ kMaxSocketsPerGroup,
+ histograms_.get(),
+ host_resolver_.get(),
+ &client_socket_factory_,
+ NULL);
+
+ MockClientSocketFactory::ClientSocketType case_types[] = {
+ // This is the IPv6 socket.
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET,
+ // This is the IPv4 socket.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET
+ };
+
+ client_socket_factory_.set_client_socket_types(case_types, 2);
+ client_socket_factory_.set_delay_ms(
+ TransportConnectJob::kIPv6FallbackTimerInMs + 50);
+
+ // Resolve an AddressList with a IPv6 address first and then a IPv4 address.
+ host_resolver_->rules()->AddIPLiteralRule(
+ "*", "2:abcd::3:4:ff,2.2.2.2", "");
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, &callback, &pool, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ IPEndPoint endpoint;
+ handle.socket()->GetLocalAddress(&endpoint);
+ EXPECT_EQ(kIPv6AddressSize, endpoint.address().size());
+ EXPECT_EQ(2, client_socket_factory_.allocation_count());
+}
+
+TEST_F(TransportClientSocketPoolTest, IPv6NoIPv4AddressesToFallbackTo) {
+ // Create a pool without backup jobs.
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+ TransportClientSocketPool pool(kMaxSockets,
+ kMaxSocketsPerGroup,
+ histograms_.get(),
+ host_resolver_.get(),
+ &client_socket_factory_,
+ NULL);
+
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET);
+
+ // Resolve an AddressList with only IPv6 addresses.
+ host_resolver_->rules()->AddIPLiteralRule(
+ "*", "2:abcd::3:4:ff,3:abcd::3:4:ff", "");
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, &callback, &pool, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ IPEndPoint endpoint;
+ handle.socket()->GetLocalAddress(&endpoint);
+ EXPECT_EQ(kIPv6AddressSize, endpoint.address().size());
+ EXPECT_EQ(1, client_socket_factory_.allocation_count());
+}
+
+TEST_F(TransportClientSocketPoolTest, IPv4HasNoFallback) {
+ // Create a pool without backup jobs.
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+ TransportClientSocketPool pool(kMaxSockets,
+ kMaxSocketsPerGroup,
+ histograms_.get(),
+ host_resolver_.get(),
+ &client_socket_factory_,
+ NULL);
+
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET);
+
+ // Resolve an AddressList with only IPv4 addresses.
+ host_resolver_->rules()->AddIPLiteralRule(
+ "*", "1.1.1.1", "");
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, &callback, &pool, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ IPEndPoint endpoint;
+ handle.socket()->GetLocalAddress(&endpoint);
+ EXPECT_EQ(kIPv4AddressSize, endpoint.address().size());
+ EXPECT_EQ(1, client_socket_factory_.allocation_count());
+}
+
} // namespace
} // namespace net