diff options
author | willchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-12 21:27:09 +0000 |
---|---|---|
committer | willchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-12 21:27:09 +0000 |
commit | 9a3822e8eeabbb82119da7eafaae24cd625dc1e2 (patch) | |
tree | 5af6b9afbd9c45525fba91a4d6b05b84a7787f06 /net | |
parent | 652257791aa8aa96c53edd18edf2bd8c4dcaec1b (diff) | |
download | chromium_src-9a3822e8eeabbb82119da7eafaae24cd625dc1e2.zip chromium_src-9a3822e8eeabbb82119da7eafaae24cd625dc1e2.tar.gz chromium_src-9a3822e8eeabbb82119da7eafaae24cd625dc1e2.tar.bz2 |
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/6969028
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@85188 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/socket/transport_client_socket_pool.cc | 164 | ||||
-rw-r--r-- | net/socket/transport_client_socket_pool.h | 14 | ||||
-rw-r--r-- | net/socket/transport_client_socket_pool_unittest.cc | 223 |
3 files changed, 374 insertions, 27 deletions
diff --git a/net/socket/transport_client_socket_pool.cc b/net/socket/transport_client_socket_pool.cc index 76ade36..a75b426 100644 --- a/net/socket/transport_client_socket_pool.cc +++ b/net/socket/transport_client_socket_pool.cc @@ -10,6 +10,7 @@ #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" @@ -22,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); } @@ -33,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. @@ -70,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 @@ -175,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(); @@ -200,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 4626e05..2f52afc 100644 --- a/net/socket/transport_client_socket_pool.h +++ b/net/socket/transport_client_socket_pool.h @@ -70,6 +70,8 @@ class TransportConnectJob : public ConnectJob { // 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, @@ -89,6 +91,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. @@ -107,6 +113,14 @@ class TransportConnectJob : public ConnectJob { // The time the connect was started (after DNS finished). base::TimeTicks connect_start_time_; + scoped_ptr<StreamSocket> transport_socket_; + + scoped_ptr<StreamSocket> 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 49c42de..a4b345d 100644 --- a/net/socket/transport_client_socket_pool_unittest.cc +++ b/net/socket/transport_client_socket_pool_unittest.cc @@ -6,10 +6,13 @@ #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_factory.h" @@ -30,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 StreamSocket { public: - MockClientSocket() : connected_(false) {} + MockClientSocket(const AddressList& addrlist) + : connected_(false), + addrlist_(addrlist) {} // StreamSocket methods: virtual int Connect(CompletionCallback* callback) { @@ -52,7 +69,13 @@ class MockClientSocket : public StreamSocket { 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_; @@ -77,12 +100,13 @@ class MockClientSocket : public StreamSocket { private: bool connected_; + const AddressList addrlist_; BoundNetLog net_log_; }; class MockFailingClientSocket : public StreamSocket { public: - MockFailingClientSocket() {} + MockFailingClientSocket(const AddressList& addrlist) : addrlist_(addrlist) {} // StreamSocket methods: virtual int Connect(CompletionCallback* callback) { @@ -126,6 +150,7 @@ class MockFailingClientSocket : public StreamSocket { virtual bool SetSendBufferSize(int32 size) { return true; } private: + const AddressList addrlist_; BoundNetLog net_log_; }; @@ -135,12 +160,17 @@ class MockPendingClientSocket : public StreamSocket { // 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) {} // StreamSocket methods: virtual int Connect(CompletionCallback* callback) { @@ -163,7 +193,13 @@ class MockPendingClientSocket : public StreamSocket { 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_; @@ -206,6 +242,7 @@ class MockPendingClientSocket : public StreamSocket { bool should_stall_; int delay_ms_; bool is_connected_; + const AddressList addrlist_; BoundNetLog net_log_; }; @@ -225,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 StreamSocket* CreateTransportClientSocket( const AddressList& addresses, @@ -241,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); } } @@ -290,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 { @@ -871,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(); @@ -1019,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 |