summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/io_thread.cc13
-rw-r--r--chrome/browser/io_thread.h6
-rw-r--r--chrome/browser/io_thread_unittest.cc10
-rw-r--r--components/domain_reliability/util.cc16
-rw-r--r--net/http/http_network_session.cc4
-rw-r--r--net/http/http_network_session.h4
-rw-r--r--net/quic/quic_chromium_client_session.cc55
-rw-r--r--net/quic/quic_chromium_client_session.h21
-rw-r--r--net/quic/quic_chromium_client_session_test.cc427
-rw-r--r--net/quic/quic_connection.cc11
-rw-r--r--net/quic/quic_connection.h10
-rw-r--r--net/quic/quic_protocol.h14
-rw-r--r--net/quic/quic_stream_factory.cc247
-rw-r--r--net/quic/quic_stream_factory.h37
-rw-r--r--net/quic/quic_stream_factory_test.cc613
-rw-r--r--net/quic/quic_utils.cc4
-rw-r--r--net/quic/test_tools/quic_test_packet_maker.cc56
-rw-r--r--net/quic/test_tools/quic_test_packet_maker.h14
-rw-r--r--net/socket/socket_test_util.cc10
-rw-r--r--net/socket/socket_test_util.h6
-rw-r--r--tools/metrics/histograms/histograms.xml14
21 files changed, 1425 insertions, 167 deletions
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index fd438c0..4a012e1 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -1180,7 +1180,8 @@ void IOThread::InitializeNetworkSessionParamsFromGlobals(
&params->quic_disable_preconnect_if_0rtt);
if (!globals.quic_host_whitelist.empty())
params->quic_host_whitelist = globals.quic_host_whitelist;
-
+ globals.quic_migrate_sessions_on_network_change.CopyToIfSet(
+ &params->quic_migrate_sessions_on_network_change);
globals.origin_to_force_quic_on.CopyToIfSet(
&params->origin_to_force_quic_on);
params->enable_user_alternate_protocol_ports =
@@ -1321,6 +1322,8 @@ void IOThread::ConfigureQuicGlobals(
ShouldQuicDisablePreConnectIfZeroRtt(quic_trial_params));
globals->quic_host_whitelist =
GetQuicHostWhitelist(command_line, quic_trial_params);
+ globals->quic_migrate_sessions_on_network_change.set(
+ ShouldQuicMigrateSessionsOnNetworkChange(quic_trial_params));
}
size_t max_packet_length = GetQuicMaxPacketLength(command_line,
@@ -1598,6 +1601,14 @@ std::unordered_set<std::string> IOThread::GetQuicHostWhitelist(
return hosts;
}
+bool IOThread::ShouldQuicMigrateSessionsOnNetworkChange(
+ const VariationParameters& quic_trial_params) {
+ return base::LowerCaseEqualsASCII(
+ GetVariationParam(quic_trial_params,
+ "migrate_sessions_on_network_change"),
+ "true");
+}
+
size_t IOThread::GetQuicMaxPacketLength(
const base::CommandLine& command_line,
const VariationParameters& quic_trial_params) {
diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h
index 5c99d56..e71f26c 100644
--- a/chrome/browser/io_thread.h
+++ b/chrome/browser/io_thread.h
@@ -241,6 +241,7 @@ class IOThread : public content::BrowserThreadDelegate {
Optional<int> quic_idle_connection_timeout_seconds;
Optional<bool> quic_disable_preconnect_if_0rtt;
std::unordered_set<std::string> quic_host_whitelist;
+ Optional<bool> quic_migrate_sessions_on_network_change;
bool enable_user_alternate_protocol_ports;
// NetErrorTabHelper uses |dns_probe_service| to send DNS probes when a
// main frame load fails with a DNS error in order to provide more useful
@@ -468,6 +469,11 @@ class IOThread : public content::BrowserThreadDelegate {
const base::CommandLine& command_line,
const VariationParameters& quic_trial_params);
+ // Returns true if QUIC should migrate sessions when primary network
+ // changes.
+ static bool ShouldQuicMigrateSessionsOnNetworkChange(
+ const VariationParameters& quic_trial_params);
+
// Returns the maximum length for QUIC packets, based on any flags in
// |command_line| or the field trial. Returns 0 if there is an error
// parsing any of the options, or if the default value should be used.
diff --git a/chrome/browser/io_thread_unittest.cc b/chrome/browser/io_thread_unittest.cc
index c1d23a3..4c08ee4 100644
--- a/chrome/browser/io_thread_unittest.cc
+++ b/chrome/browser/io_thread_unittest.cc
@@ -234,6 +234,7 @@ TEST_F(IOThreadTest, EnableQuicFromFieldTrialGroup) {
EXPECT_EQ(net::kIdleConnectionTimeoutSeconds,
params.quic_idle_connection_timeout_seconds);
EXPECT_FALSE(params.quic_disable_preconnect_if_0rtt);
+ EXPECT_FALSE(params.quic_migrate_sessions_on_network_change);
EXPECT_FALSE(IOThread::ShouldEnableQuicForDataReductionProxy());
EXPECT_TRUE(params.quic_host_whitelist.empty());
}
@@ -349,6 +350,15 @@ TEST_F(IOThreadTest, QuicDisablePreConnectIfZeroRtt) {
EXPECT_TRUE(params.quic_disable_preconnect_if_0rtt);
}
+TEST_F(IOThreadTest, QuicMigrateSessionsOnNetworkChangeFromFieldTrialParams) {
+ field_trial_group_ = "Enabled";
+ field_trial_params_["migrate_sessions_on_network_change"] = "true";
+ ConfigureQuicGlobals();
+ net::HttpNetworkSession::Params params;
+ InitializeNetworkSessionParams(&params);
+ EXPECT_TRUE(params.quic_migrate_sessions_on_network_change);
+}
+
TEST_F(IOThreadTest, PacketLengthFromFieldTrialParams) {
field_trial_group_ = "Enabled";
field_trial_params_["max_packet_length"] = "1450";
diff --git a/components/domain_reliability/util.cc b/components/domain_reliability/util.cc
index f657097..2520a63 100644
--- a/components/domain_reliability/util.cc
+++ b/components/domain_reliability/util.cc
@@ -255,6 +255,22 @@ const struct QuicErrorMapping {
// tampered with.
{ net::QUIC_VERSION_NEGOTIATION_MISMATCH,
"quic.version_negotiation_mismatch" },
+
+ // Network change and connection migration errors.
+
+ // IP address changed causing connection close.
+ { net::QUIC_IP_ADDRESS_CHANGED, "quic.ip_address_changed" },
+ // Network changed, but connection had no migratable streams.
+ { net::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
+ "quic.connection_migration_no_migratable_streams" },
+ // Connection changed networks too many times.
+ { net::QUIC_CONNECTION_MIGRATION_TOO_MANY_CHANGES,
+ "quic.connection_migration_too_many_changes" },
+ // Connection migration was attempted, but there was no new network to
+ // migrate to.
+ { net::QUIC_CONNECTION_MIGRATION_NO_NEW_NETWORK,
+ "quic.connection_migration_no_new_network" },
+
// No error. Used as bound while iterating.
{ net::QUIC_LAST_ERROR, "quic.last_error"}
};
diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc
index a72b871..d3f73aa 100644
--- a/net/http/http_network_session.cc
+++ b/net/http/http_network_session.cc
@@ -127,6 +127,7 @@ HttpNetworkSession::Params::Params()
quic_close_sessions_on_ip_change(false),
quic_idle_connection_timeout_seconds(kIdleConnectionTimeoutSeconds),
quic_disable_preconnect_if_0rtt(false),
+ quic_migrate_sessions_on_network_change(false),
proxy_delegate(NULL) {
quic_supported_versions.push_back(QUIC_VERSION_25);
}
@@ -178,6 +179,7 @@ HttpNetworkSession::HttpNetworkSession(const Params& params)
params.quic_store_server_configs_in_properties,
params.quic_close_sessions_on_ip_change,
params.quic_idle_connection_timeout_seconds,
+ params.quic_migrate_sessions_on_network_change,
params.quic_connection_options),
spdy_session_pool_(params.host_resolver,
params.ssl_config_service,
@@ -337,7 +339,7 @@ void HttpNetworkSession::CloseAllConnections() {
normal_socket_pool_manager_->FlushSocketPoolsWithError(ERR_ABORTED);
websocket_socket_pool_manager_->FlushSocketPoolsWithError(ERR_ABORTED);
spdy_session_pool_.CloseCurrentSessions(ERR_ABORTED);
- quic_stream_factory_.CloseAllSessions(ERR_ABORTED);
+ quic_stream_factory_.CloseAllSessions(ERR_ABORTED, QUIC_INTERNAL_ERROR);
}
void HttpNetworkSession::CloseIdleConnections() {
diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h
index 5b1ecf4..09b4d7e 100644
--- a/net/http/http_network_session.h
+++ b/net/http/http_network_session.h
@@ -184,7 +184,9 @@ class NET_EXPORT HttpNetworkSession
bool quic_disable_preconnect_if_0rtt;
// List of hosts for which QUIC is explicitly whitelisted.
std::unordered_set<std::string> quic_host_whitelist;
-
+ // If true, active QUIC sessions may be migrated onto new IPs when network
+ // changes.
+ bool quic_migrate_sessions_on_network_change;
ProxyDelegate* proxy_delegate;
};
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc
index 5cb4978..d8000a7 100644
--- a/net/quic/quic_chromium_client_session.cc
+++ b/net/quic/quic_chromium_client_session.cc
@@ -42,6 +42,11 @@ const int k0RttHandshakeTimeoutMs = 300;
// IPv6 packets have an additional 20 bytes of overhead than IPv4 packets.
const size_t kAdditionalOverheadForIPv6 = 20;
+// Maximum number of Readers that are created for any session due to
+// connection migration. A new Reader is created every time this endpoint's
+// IP address changes.
+const size_t kMaxReadersPerQuicSession = 5;
+
// Histograms for tracking down the crashes from http://crbug.com/354669
// Note: these values must be kept in sync with the corresponding values in:
// tools/metrics/histograms/histograms.xml
@@ -180,18 +185,11 @@ QuicChromiumClientSession::QuicChromiumClientSession(
server_id_(server_id),
require_confirmation_(false),
stream_factory_(stream_factory),
- socket_(std::move(socket)),
transport_security_state_(transport_security_state),
server_info_(std::move(server_info)),
num_total_streams_(0),
task_runner_(task_runner),
net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_QUIC_SESSION)),
- packet_reader_(socket_.get(),
- clock,
- this,
- yield_after_packets,
- yield_after_duration,
- net_log_),
dns_resolution_end_time_(dns_resolution_end_time),
logger_(new QuicConnectionLogger(this,
connection_description,
@@ -200,6 +198,10 @@ QuicChromiumClientSession::QuicChromiumClientSession(
going_away_(false),
disabled_reason_(QUIC_DISABLED_NOT),
weak_factory_(this) {
+ sockets_.push_back(std::move(socket));
+ packet_readers_.push_back(make_scoped_ptr(new QuicPacketReader(
+ sockets_.back().get(), clock, this, yield_after_packets,
+ yield_after_duration, net_log_)));
crypto_stream_.reset(
crypto_client_stream_factory->CreateQuicCryptoClientStream(
server_id, this, make_scoped_ptr(new ProofVerifyContextChromium(
@@ -773,7 +775,10 @@ void QuicChromiumClientSession::OnConnectionClosed(QuicErrorCode error,
if (!callback_.is_null()) {
base::ResetAndReturn(&callback_).Run(ERR_QUIC_PROTOCOL_ERROR);
}
- socket_->Close();
+
+ for (auto& socket : sockets_) {
+ socket->Close();
+ }
QuicSession::OnConnectionClosed(error, from_peer);
DCHECK(dynamic_streams().empty());
CloseAllStreams(ERR_UNEXPECTED);
@@ -819,7 +824,9 @@ void QuicChromiumClientSession::OnProofVerifyDetailsAvailable(
}
void QuicChromiumClientSession::StartReading() {
- packet_reader_.StartReading();
+ for (auto& packet_reader : packet_readers_) {
+ packet_reader->StartReading();
+ }
}
void QuicChromiumClientSession::CloseSessionOnError(int error,
@@ -918,6 +925,12 @@ QuicChromiumClientSession::GetWeakPtr() {
void QuicChromiumClientSession::OnReadError(
int result,
const DatagramClientSocket* socket) {
+ DCHECK(socket != nullptr);
+ if (socket != GetDefaultSocket()) {
+ // Ignore read errors from old sockets that are no longer active.
+ // TODO(jri): Maybe clean up old sockets on error.
+ return;
+ }
DVLOG(1) << "Closing session on read error: " << result;
UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.ReadError", -result);
NotifyFactoryOfSessionGoingAway();
@@ -986,4 +999,28 @@ void QuicChromiumClientSession::OnConnectTimeout() {
// DCHECK_EQ(0u, GetNumOpenOutgoingStreams());
}
+bool QuicChromiumClientSession::MigrateToSocket(
+ scoped_ptr<DatagramClientSocket> socket,
+ scoped_ptr<QuicPacketReader> reader,
+ scoped_ptr<QuicPacketWriter> writer) {
+ DCHECK_EQ(sockets_.size(), packet_readers_.size());
+ if (sockets_.size() >= kMaxReadersPerQuicSession) {
+ return false;
+ }
+ // TODO(jri): Make SetQuicPacketWriter take a scoped_ptr.
+ connection()->SetQuicPacketWriter(writer.release(), /*owns_writer=*/true);
+ packet_readers_.push_back(std::move(reader));
+ sockets_.push_back(std::move(socket));
+ StartReading();
+ connection()->SendPing();
+ return true;
+}
+
+const DatagramClientSocket* QuicChromiumClientSession::GetDefaultSocket()
+ const {
+ DCHECK(sockets_.back().get() != nullptr);
+ // The most recently added socket is the currently active one.
+ return sockets_.back().get();
+}
+
} // namespace net
diff --git a/net/quic/quic_chromium_client_session.h b/net/quic/quic_chromium_client_session.h
index 37b6268..31da6cc 100644
--- a/net/quic/quic_chromium_client_session.h
+++ b/net/quic/quic_chromium_client_session.h
@@ -192,7 +192,7 @@ class NET_EXPORT_PRIVATE QuicChromiumClientSession
// Resumes a crypto handshake with the server after a timeout.
int ResumeCryptoConnect(const CompletionCallback& callback);
- // Causes the QuicConnectionHelper to start reading from the socket
+ // Causes the QuicConnectionHelper to start reading from all sockets
// and passing the data along to the QuicConnection.
void StartReading();
@@ -225,6 +225,21 @@ class NET_EXPORT_PRIVATE QuicChromiumClientSession
QuicDisabledReason disabled_reason() const { return disabled_reason_; }
+ // Migrates session onto new socket, i.e., starts reading from |socket|
+ // in addition to any previous sockets, and sets |writer| to be the new
+ // default writer. Returns true if socket was successfully added to the
+ // session and the session was successfully migrated to using the new socket.
+ // Returns false if number of migrations exceeds kMaxReadersPerQuicSession.
+ // Takes ownership of |socket|, |reader|, and |writer|.
+ bool MigrateToSocket(scoped_ptr<DatagramClientSocket> socket,
+ scoped_ptr<QuicPacketReader> reader,
+ scoped_ptr<QuicPacketWriter> writer);
+
+ // Returns current default socket. This is the socket over which all
+ // QUIC packets are sent. This default socket can change, so do not store the
+ // returned socket.
+ const DatagramClientSocket* GetDefaultSocket() const;
+
protected:
// QuicSession methods:
QuicSpdyStream* CreateIncomingDynamicStream(QuicStreamId id) override;
@@ -274,7 +289,7 @@ class NET_EXPORT_PRIVATE QuicChromiumClientSession
bool require_confirmation_;
scoped_ptr<QuicCryptoClientStream> crypto_stream_;
QuicStreamFactory* stream_factory_;
- scoped_ptr<DatagramClientSocket> socket_;
+ std::vector<scoped_ptr<DatagramClientSocket>> sockets_;
TransportSecurityState* transport_security_state_;
scoped_ptr<QuicServerInfo> server_info_;
scoped_ptr<CertVerifyResult> cert_verify_result_;
@@ -286,7 +301,7 @@ class NET_EXPORT_PRIVATE QuicChromiumClientSession
size_t num_total_streams_;
base::TaskRunner* task_runner_;
BoundNetLog net_log_;
- QuicPacketReader packet_reader_;
+ std::vector<scoped_ptr<QuicPacketReader>> packet_readers_;
base::TimeTicks dns_resolution_end_time_;
base::TimeTicks handshake_start_; // Time the handshake was started.
scoped_ptr<QuicConnectionLogger> logger_;
diff --git a/net/quic/quic_chromium_client_session_test.cc b/net/quic/quic_chromium_client_session_test.cc
index ba831ef..356b69a 100644
--- a/net/quic/quic_chromium_client_session_test.cc
+++ b/net/quic/quic_chromium_client_session_test.cc
@@ -22,13 +22,19 @@
#include "net/quic/crypto/quic_decrypter.h"
#include "net/quic/crypto/quic_encrypter.h"
#include "net/quic/crypto/quic_server_info.h"
+#include "net/quic/quic_connection_helper.h"
#include "net/quic/quic_crypto_client_stream_factory.h"
+#include "net/quic/quic_default_packet_writer.h"
#include "net/quic/quic_flags.h"
+#include "net/quic/quic_http_utils.h"
#include "net/quic/quic_packet_reader.h"
+#include "net/quic/quic_packet_writer.h"
#include "net/quic/quic_protocol.h"
#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
#include "net/quic/test_tools/quic_chromium_client_session_peer.h"
#include "net/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/quic/test_tools/quic_test_packet_maker.h"
#include "net/quic/test_tools/quic_test_utils.h"
#include "net/quic/test_tools/simple_quic_framer.h"
#include "net/socket/socket_test_util.h"
@@ -42,74 +48,106 @@ namespace net {
namespace test {
namespace {
+const IPEndPoint kIpEndPoint(IPAddressNumber(kIPv4AddressSize, 0), 0);
const char kServerHostname[] = "test.example.com";
const uint16_t kServerPort = 443;
+const size_t kMaxReadersPerQuicSession = 5;
+
+class DefaultPacketWriterFactory : public QuicConnection::PacketWriterFactory {
+ public:
+ explicit DefaultPacketWriterFactory(DatagramClientSocket* socket)
+ : socket_(socket) {}
+ ~DefaultPacketWriterFactory() override {}
+
+ QuicPacketWriter* Create(QuicConnection* connection) const override {
+ scoped_ptr<net::QuicDefaultPacketWriter> writer(
+ new net::QuicDefaultPacketWriter(socket_));
+ writer->SetConnection(connection);
+ return writer.release();
+ }
+
+ private:
+ DatagramClientSocket* socket_;
+};
class QuicChromiumClientSessionTest
: public ::testing::TestWithParam<QuicVersion> {
protected:
QuicChromiumClientSessionTest()
: crypto_config_(CryptoTestUtils::ProofVerifierForTesting()),
- connection_(new PacketSavingConnection(&helper_,
- Perspective::IS_CLIENT,
- SupportedVersions(GetParam()))),
- session_(
- connection_,
- GetSocket(),
- /*stream_factory=*/nullptr,
- QuicCryptoClientStreamFactory::GetDefaultFactory(),
- &clock_,
- &transport_security_state_,
- make_scoped_ptr((QuicServerInfo*)nullptr),
- QuicServerId(kServerHostname, kServerPort, PRIVACY_MODE_DISABLED),
- kQuicYieldAfterPacketsRead,
- QuicTime::Delta::FromMilliseconds(
- kQuicYieldAfterDurationMilliseconds),
- /*cert_verify_flags=*/0,
- DefaultQuicConfig(),
- &crypto_config_,
- "CONNECTION_UNKNOWN",
- base::TimeTicks::Now(),
- base::ThreadTaskRunnerHandle::Get().get(),
- /*socket_performance_watcher=*/nullptr,
- &net_log_) {
- session_.Initialize();
+ default_read_(new MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)),
+ socket_data_(
+ new SequencedSocketData(default_read_.get(), 1, nullptr, 0)),
+ random_(0),
+ helper_(base::ThreadTaskRunnerHandle::Get().get(), &clock_, &random_),
+ maker_(GetParam(), 0, &clock_, kServerHostname) {}
+
+ void Initialize() {
+ socket_factory_.AddSocketDataProvider(socket_data_.get());
+ scoped_ptr<DatagramClientSocket> socket =
+ socket_factory_.CreateDatagramClientSocket(DatagramSocket::DEFAULT_BIND,
+ base::Bind(&base::RandInt),
+ &net_log_, NetLog::Source());
+ socket->Connect(kIpEndPoint);
+ DefaultPacketWriterFactory writer_factory(socket.get());
+ QuicConnection* connection = new QuicConnection(
+ 0, kIpEndPoint, &helper_, writer_factory, true, Perspective::IS_CLIENT,
+ SupportedVersions(GetParam()));
+ session_.reset(new QuicChromiumClientSession(
+ connection, std::move(socket),
+ /*stream_factory=*/nullptr, &crypto_client_stream_factory_, &clock_,
+ &transport_security_state_, make_scoped_ptr((QuicServerInfo*)nullptr),
+ QuicServerId(kServerHostname, kServerPort, PRIVACY_MODE_DISABLED),
+ kQuicYieldAfterPacketsRead,
+ QuicTime::Delta::FromMilliseconds(kQuicYieldAfterDurationMilliseconds),
+ /*cert_verify_flags=*/0, DefaultQuicConfig(), &crypto_config_,
+ "CONNECTION_UNKNOWN", base::TimeTicks::Now(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ /*socket_performance_watcher=*/nullptr, &net_log_));
+
+ scoped_refptr<X509Certificate> cert(
+ ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"));
+ verify_details_.cert_verify_result.verified_cert = cert;
+ verify_details_.cert_verify_result.is_issued_by_known_root = true;
// Advance the time, because timers do not like uninitialized times.
- connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ session_->Initialize();
+ session_->StartReading();
}
void TearDown() override {
- session_.CloseSessionOnError(ERR_ABORTED, QUIC_INTERNAL_ERROR);
+ session_->CloseSessionOnError(ERR_ABORTED, QUIC_INTERNAL_ERROR);
}
- scoped_ptr<DatagramClientSocket> GetSocket() {
- socket_factory_.AddSocketDataProvider(&socket_data_);
- return socket_factory_.CreateDatagramClientSocket(
- DatagramSocket::DEFAULT_BIND, base::Bind(&base::RandInt), &net_log_,
- NetLog::Source());
+ void CompleteCryptoHandshake() {
+ ASSERT_EQ(OK, session_->CryptoConnect(false, callback_.callback()));
}
- void CompleteCryptoHandshake() {
- ASSERT_EQ(ERR_IO_PENDING,
- session_.CryptoConnect(false, callback_.callback()));
- CryptoTestUtils::FakeServerOptions server_options;
- CryptoTestUtils::HandshakeWithFakeServer(
- &helper_, connection_, session_.GetCryptoStream(), server_options);
- ASSERT_EQ(OK, callback_.WaitForResult());
+ QuicPacketWriter* CreateQuicPacketWriter(DatagramClientSocket* socket,
+ QuicConnection* connection) const {
+ scoped_ptr<QuicDefaultPacketWriter> writer(
+ new QuicDefaultPacketWriter(socket));
+ writer->SetConnection(connection);
+ return writer.release();
}
- MockConnectionHelper helper_;
QuicCryptoClientConfig crypto_config_;
- PacketSavingConnection* connection_;
TestNetLog net_log_;
+ BoundTestNetLog bound_net_log_;
MockClientSocketFactory socket_factory_;
- StaticSocketDataProvider socket_data_;
- TransportSecurityState transport_security_state_;
- QuicChromiumClientSession session_;
+ scoped_ptr<MockRead> default_read_;
+ scoped_ptr<SequencedSocketData> socket_data_;
MockClock clock_;
MockRandom random_;
+ QuicConnectionHelper helper_;
+ scoped_ptr<DefaultPacketWriterFactory> writer_factory_;
+ TransportSecurityState transport_security_state_;
+ MockCryptoClientStreamFactory crypto_client_stream_factory_;
+ scoped_ptr<QuicChromiumClientSession> session_;
QuicConnectionVisitorInterface* visitor_;
TestCompletionCallback callback_;
+ QuicTestPacketMaker maker_;
+ ProofVerifyDetailsChromium verify_details_;
};
INSTANTIATE_TEST_CASE_P(Tests,
@@ -117,43 +155,62 @@ INSTANTIATE_TEST_CASE_P(Tests,
::testing::ValuesIn(QuicSupportedVersions()));
TEST_P(QuicChromiumClientSessionTest, CryptoConnect) {
+ Initialize();
CompleteCryptoHandshake();
}
TEST_P(QuicChromiumClientSessionTest, MaxNumStreams) {
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ scoped_ptr<QuicEncryptedPacket> client_rst(maker_.MakeRstPacket(
+ 1, true, kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT));
+ MockWrite writes[] = {
+ MockWrite(ASYNC, client_rst->data(), client_rst->length(), 1)};
+ socket_data_.reset(new SequencedSocketData(reads, arraysize(reads), writes,
+ arraysize(writes)));
+
+ Initialize();
CompleteCryptoHandshake();
+ const size_t kMaxOpenStreams = session_->get_max_open_streams();
std::vector<QuicReliableClientStream*> streams;
- for (size_t i = 0; i < kDefaultMaxStreamsPerConnection; i++) {
+ for (size_t i = 0; i < kMaxOpenStreams; i++) {
QuicReliableClientStream* stream =
- session_.CreateOutgoingDynamicStream(kDefaultPriority);
+ session_->CreateOutgoingDynamicStream(kDefaultPriority);
EXPECT_TRUE(stream);
streams.push_back(stream);
}
- EXPECT_FALSE(session_.CreateOutgoingDynamicStream(kDefaultPriority));
+ EXPECT_FALSE(session_->CreateOutgoingDynamicStream(kDefaultPriority));
- EXPECT_EQ(kDefaultMaxStreamsPerConnection,
- session_.GetNumOpenOutgoingStreams());
+ EXPECT_EQ(kMaxOpenStreams, session_->GetNumOpenOutgoingStreams());
// Close a stream and ensure I can now open a new one.
QuicStreamId stream_id = streams[0]->id();
- session_.CloseStream(stream_id);
+ session_->CloseStream(stream_id);
- EXPECT_FALSE(session_.CreateOutgoingDynamicStream(kDefaultPriority));
+ EXPECT_FALSE(session_->CreateOutgoingDynamicStream(kDefaultPriority));
QuicRstStreamFrame rst1(stream_id, QUIC_STREAM_NO_ERROR, 0);
- session_.OnRstStream(rst1);
- EXPECT_EQ(kDefaultMaxStreamsPerConnection - 1,
- session_.GetNumOpenOutgoingStreams());
- EXPECT_TRUE(session_.CreateOutgoingDynamicStream(kDefaultPriority));
+ session_->OnRstStream(rst1);
+ EXPECT_EQ(kMaxOpenStreams - 1, session_->GetNumOpenOutgoingStreams());
+ EXPECT_TRUE(session_->CreateOutgoingDynamicStream(kDefaultPriority));
}
TEST_P(QuicChromiumClientSessionTest, MaxNumStreamsViaRequest) {
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ scoped_ptr<QuicEncryptedPacket> client_rst(maker_.MakeRstPacket(
+ 1, true, kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT));
+ MockWrite writes[] = {
+ MockWrite(ASYNC, client_rst->data(), client_rst->length(), 1)};
+ socket_data_.reset(new SequencedSocketData(reads, arraysize(reads), writes,
+ arraysize(writes)));
+
+ Initialize();
CompleteCryptoHandshake();
+ const size_t kMaxOpenStreams = session_->get_max_open_streams();
std::vector<QuicReliableClientStream*> streams;
- for (size_t i = 0; i < kDefaultMaxStreamsPerConnection; i++) {
+ for (size_t i = 0; i < kMaxOpenStreams; i++) {
QuicReliableClientStream* stream =
- session_.CreateOutgoingDynamicStream(kDefaultPriority);
+ session_->CreateOutgoingDynamicStream(kDefaultPriority);
EXPECT_TRUE(stream);
streams.push_back(stream);
}
@@ -162,30 +219,32 @@ TEST_P(QuicChromiumClientSessionTest, MaxNumStreamsViaRequest) {
QuicChromiumClientSession::StreamRequest stream_request;
TestCompletionCallback callback;
ASSERT_EQ(ERR_IO_PENDING,
- stream_request.StartRequest(session_.GetWeakPtr(), &stream,
+ stream_request.StartRequest(session_->GetWeakPtr(), &stream,
callback.callback()));
// Close a stream and ensure I can now open a new one.
QuicStreamId stream_id = streams[0]->id();
- session_.CloseStream(stream_id);
+ session_->CloseStream(stream_id);
QuicRstStreamFrame rst1(stream_id, QUIC_STREAM_NO_ERROR, 0);
- session_.OnRstStream(rst1);
+ session_->OnRstStream(rst1);
ASSERT_TRUE(callback.have_result());
EXPECT_EQ(OK, callback.WaitForResult());
EXPECT_TRUE(stream != nullptr);
}
TEST_P(QuicChromiumClientSessionTest, GoAwayReceived) {
+ Initialize();
CompleteCryptoHandshake();
// After receiving a GoAway, I should no longer be able to create outgoing
// streams.
- session_.connection()->OnGoAwayFrame(
+ session_->connection()->OnGoAwayFrame(
QuicGoAwayFrame(QUIC_PEER_GOING_AWAY, 1u, "Going away."));
- EXPECT_EQ(nullptr, session_.CreateOutgoingDynamicStream(kDefaultPriority));
+ EXPECT_EQ(nullptr, session_->CreateOutgoingDynamicStream(kDefaultPriority));
}
TEST_P(QuicChromiumClientSessionTest, CanPool) {
+ Initialize();
// Load a cert that is valid for:
// www.example.org
// mail.example.org
@@ -197,16 +256,17 @@ TEST_P(QuicChromiumClientSessionTest, CanPool) {
ASSERT_TRUE(details.cert_verify_result.verified_cert.get());
CompleteCryptoHandshake();
- session_.OnProofVerifyDetailsAvailable(details);
+ session_->OnProofVerifyDetailsAvailable(details);
- EXPECT_TRUE(session_.CanPool("www.example.org", PRIVACY_MODE_DISABLED));
- EXPECT_FALSE(session_.CanPool("www.example.org", PRIVACY_MODE_ENABLED));
- EXPECT_TRUE(session_.CanPool("mail.example.org", PRIVACY_MODE_DISABLED));
- EXPECT_TRUE(session_.CanPool("mail.example.com", PRIVACY_MODE_DISABLED));
- EXPECT_FALSE(session_.CanPool("mail.google.com", PRIVACY_MODE_DISABLED));
+ EXPECT_TRUE(session_->CanPool("www.example.org", PRIVACY_MODE_DISABLED));
+ EXPECT_FALSE(session_->CanPool("www.example.org", PRIVACY_MODE_ENABLED));
+ EXPECT_TRUE(session_->CanPool("mail.example.org", PRIVACY_MODE_DISABLED));
+ EXPECT_TRUE(session_->CanPool("mail.example.com", PRIVACY_MODE_DISABLED));
+ EXPECT_FALSE(session_->CanPool("mail.google.com", PRIVACY_MODE_DISABLED));
}
TEST_P(QuicChromiumClientSessionTest, ConnectionPooledWithTlsChannelId) {
+ Initialize();
// Load a cert that is valid for:
// www.example.org
// mail.example.org
@@ -218,17 +278,19 @@ TEST_P(QuicChromiumClientSessionTest, ConnectionPooledWithTlsChannelId) {
ASSERT_TRUE(details.cert_verify_result.verified_cert.get());
CompleteCryptoHandshake();
- session_.OnProofVerifyDetailsAvailable(details);
- QuicChromiumClientSessionPeer::SetHostname(&session_, "www.example.org");
- QuicChromiumClientSessionPeer::SetChannelIDSent(&session_, true);
-
- EXPECT_TRUE(session_.CanPool("www.example.org", PRIVACY_MODE_DISABLED));
- EXPECT_TRUE(session_.CanPool("mail.example.org", PRIVACY_MODE_DISABLED));
- EXPECT_FALSE(session_.CanPool("mail.example.com", PRIVACY_MODE_DISABLED));
- EXPECT_FALSE(session_.CanPool("mail.google.com", PRIVACY_MODE_DISABLED));
+ session_->OnProofVerifyDetailsAvailable(details);
+ QuicChromiumClientSessionPeer::SetHostname(session_.get(), "www.example.org");
+ QuicChromiumClientSessionPeer::SetChannelIDSent(session_.get(), true);
+
+ EXPECT_TRUE(session_->CanPool("www.example.org", PRIVACY_MODE_DISABLED));
+ EXPECT_TRUE(session_->CanPool("mail.example.org", PRIVACY_MODE_DISABLED));
+ EXPECT_FALSE(session_->CanPool("mail.example.com", PRIVACY_MODE_DISABLED));
+ EXPECT_FALSE(session_->CanPool("mail.google.com", PRIVACY_MODE_DISABLED));
}
TEST_P(QuicChromiumClientSessionTest, ConnectionNotPooledWithDifferentPin) {
+ Initialize();
+
uint8_t primary_pin = 1;
uint8_t backup_pin = 2;
uint8_t bad_pin = 3;
@@ -245,14 +307,16 @@ TEST_P(QuicChromiumClientSessionTest, ConnectionNotPooledWithDifferentPin) {
ASSERT_TRUE(details.cert_verify_result.verified_cert.get());
CompleteCryptoHandshake();
- session_.OnProofVerifyDetailsAvailable(details);
- QuicChromiumClientSessionPeer::SetHostname(&session_, "www.example.org");
- QuicChromiumClientSessionPeer::SetChannelIDSent(&session_, true);
+ session_->OnProofVerifyDetailsAvailable(details);
+ QuicChromiumClientSessionPeer::SetHostname(session_.get(), "www.example.org");
+ QuicChromiumClientSessionPeer::SetChannelIDSent(session_.get(), true);
- EXPECT_FALSE(session_.CanPool("mail.example.org", PRIVACY_MODE_DISABLED));
+ EXPECT_FALSE(session_->CanPool("mail.example.org", PRIVACY_MODE_DISABLED));
}
TEST_P(QuicChromiumClientSessionTest, ConnectionPooledWithMatchingPin) {
+ Initialize();
+
uint8_t primary_pin = 1;
uint8_t backup_pin = 2;
AddPin(&transport_security_state_, "mail.example.org", primary_pin,
@@ -268,11 +332,208 @@ TEST_P(QuicChromiumClientSessionTest, ConnectionPooledWithMatchingPin) {
ASSERT_TRUE(details.cert_verify_result.verified_cert.get());
CompleteCryptoHandshake();
- session_.OnProofVerifyDetailsAvailable(details);
- QuicChromiumClientSessionPeer::SetHostname(&session_, "www.example.org");
- QuicChromiumClientSessionPeer::SetChannelIDSent(&session_, true);
+ session_->OnProofVerifyDetailsAvailable(details);
+ QuicChromiumClientSessionPeer::SetHostname(session_.get(), "www.example.org");
+ QuicChromiumClientSessionPeer::SetChannelIDSent(session_.get(), true);
+
+ EXPECT_TRUE(session_->CanPool("mail.example.org", PRIVACY_MODE_DISABLED));
+}
+
+TEST_P(QuicChromiumClientSessionTest, MigrateToSocket) {
+ Initialize();
+ CompleteCryptoHandshake();
+
+ char data[] = "ABCD";
+ scoped_ptr<QuicEncryptedPacket> ping(
+ maker_.MakePingPacket(1, /*include_version=*/false));
+ scoped_ptr<QuicEncryptedPacket> ack_and_data_out(maker_.MakeAckAndDataPacket(
+ 2, false, 5, 1, 1, false, 0, StringPiece(data)));
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ping->data(), ping->length(), 0),
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING, 1)};
+ MockWrite writes[] = {MockWrite(SYNCHRONOUS, ping->data(), ping->length(), 2),
+ MockWrite(SYNCHRONOUS, ack_and_data_out->data(),
+ ack_and_data_out->length(), 3)};
+ StaticSocketDataProvider socket_data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create connected socket.
+ scoped_ptr<DatagramClientSocket> new_socket =
+ socket_factory_.CreateDatagramClientSocket(DatagramSocket::DEFAULT_BIND,
+ base::Bind(&base::RandInt),
+ &net_log_, NetLog::Source());
+ EXPECT_EQ(OK, new_socket->Connect(kIpEndPoint));
+
+ // Create reader and writer.
+ scoped_ptr<QuicPacketReader> new_reader(new QuicPacketReader(
+ new_socket.get(), &clock_, session_.get(), kQuicYieldAfterPacketsRead,
+ QuicTime::Delta::FromMilliseconds(kQuicYieldAfterDurationMilliseconds),
+ bound_net_log_.bound()));
+ scoped_ptr<QuicPacketWriter> new_writer(
+ CreateQuicPacketWriter(new_socket.get(), session_->connection()));
+
+ // Migrate session.
+ EXPECT_TRUE(session_->MigrateToSocket(
+ std::move(new_socket), std::move(new_reader), std::move(new_writer)));
+
+ // Write data to session.
+ struct iovec iov[1];
+ iov[0].iov_base = data;
+ iov[0].iov_len = 4;
+ session_->WritevData(5, QuicIOVector(iov, arraysize(iov), 4), 0, false,
+ MAY_FEC_PROTECT, nullptr);
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+}
+
+TEST_P(QuicChromiumClientSessionTest, MigrateToSocketMaxReaders) {
+ Initialize();
+ CompleteCryptoHandshake();
+
+ for (size_t i = 0; i < kMaxReadersPerQuicSession; ++i) {
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 1)};
+ scoped_ptr<QuicEncryptedPacket> ping_out(
+ maker_.MakePingPacket(i + 1, /*include_version=*/true));
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, ping_out->data(), ping_out->length(), i + 2)};
+ StaticSocketDataProvider socket_data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create connected socket.
+ scoped_ptr<DatagramClientSocket> new_socket =
+ socket_factory_.CreateDatagramClientSocket(DatagramSocket::DEFAULT_BIND,
+ base::Bind(&base::RandInt),
+ &net_log_, NetLog::Source());
+ EXPECT_EQ(OK, new_socket->Connect(kIpEndPoint));
+
+ // Create reader and writer.
+ scoped_ptr<QuicPacketReader> new_reader(new QuicPacketReader(
+ new_socket.get(), &clock_, session_.get(), kQuicYieldAfterPacketsRead,
+ QuicTime::Delta::FromMilliseconds(kQuicYieldAfterDurationMilliseconds),
+ bound_net_log_.bound()));
+ scoped_ptr<QuicPacketWriter> new_writer(
+ CreateQuicPacketWriter(new_socket.get(), session_->connection()));
+
+ // Migrate session.
+ if (i < kMaxReadersPerQuicSession - 1) {
+ EXPECT_TRUE(session_->MigrateToSocket(
+ std::move(new_socket), std::move(new_reader), std::move(new_writer)));
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+ } else {
+ // Max readers exceeded.
+ EXPECT_FALSE(session_->MigrateToSocket(
+ std::move(new_socket), std::move(new_reader), std::move(new_writer)));
+
+ EXPECT_FALSE(socket_data.AllReadDataConsumed());
+ EXPECT_FALSE(socket_data.AllWriteDataConsumed());
+ }
+ }
+}
+
+TEST_P(QuicChromiumClientSessionTest, MigrateToSocketReadError) {
+ scoped_ptr<QuicEncryptedPacket> ping(
+ maker_.MakePingPacket(1, /*include_version=*/true));
+ MockRead old_reads[] = {
+ MockRead(SYNCHRONOUS, ping->data(), ping->length(), 0),
+ MockRead(ASYNC, ERR_IO_PENDING, 1), // causes reading to pause.
+ MockRead(ASYNC, ERR_NETWORK_CHANGED, 2)};
+ socket_data_.reset(
+ new SequencedSocketData(old_reads, arraysize(old_reads), nullptr, 0));
+ Initialize();
+ CompleteCryptoHandshake();
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, ping->data(), ping->length(), 1)};
+ MockRead new_reads[] = {
+ MockRead(SYNCHRONOUS, ping->data(), ping->length(), 0),
+ MockRead(ASYNC, ERR_IO_PENDING, 2), // pause reading.
+ MockRead(ASYNC, ping->data(), ping->length(), 3),
+ MockRead(ASYNC, ERR_IO_PENDING, 4), // pause reading
+ MockRead(ASYNC, ERR_NETWORK_CHANGED, 5)};
+ SequencedSocketData new_socket_data(new_reads, arraysize(new_reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&new_socket_data);
+
+ // Create connected socket.
+ scoped_ptr<DatagramClientSocket> new_socket =
+ socket_factory_.CreateDatagramClientSocket(DatagramSocket::DEFAULT_BIND,
+ base::Bind(&base::RandInt),
+ &net_log_, NetLog::Source());
+ EXPECT_EQ(OK, new_socket->Connect(kIpEndPoint));
+
+ // Create reader and writer.
+ scoped_ptr<QuicPacketReader> new_reader(new QuicPacketReader(
+ new_socket.get(), &clock_, session_.get(), kQuicYieldAfterPacketsRead,
+ QuicTime::Delta::FromMilliseconds(kQuicYieldAfterDurationMilliseconds),
+ bound_net_log_.bound()));
+ scoped_ptr<QuicPacketWriter> new_writer(
+ CreateQuicPacketWriter(new_socket.get(), session_->connection()));
+
+ // Store old socket and migrate session.
+ EXPECT_TRUE(session_->MigrateToSocket(
+ std::move(new_socket), std::move(new_reader), std::move(new_writer)));
+
+ // Read error on old socket does not impact session.
+ EXPECT_TRUE(socket_data_->IsPaused());
+ socket_data_->Resume();
+ EXPECT_TRUE(session_->connection()->connected());
+ EXPECT_TRUE(new_socket_data.IsPaused());
+ new_socket_data.Resume();
+
+ // Read error on new socket causes session close.
+ EXPECT_TRUE(new_socket_data.IsPaused());
+ EXPECT_TRUE(session_->connection()->connected());
+ new_socket_data.Resume();
+ EXPECT_FALSE(session_->connection()->connected());
+
+ EXPECT_TRUE(socket_data_->AllReadDataConsumed());
+ EXPECT_TRUE(socket_data_->AllWriteDataConsumed());
+ EXPECT_TRUE(new_socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(new_socket_data.AllWriteDataConsumed());
+}
+
+TEST_P(QuicChromiumClientSessionTest, MigrateToSocketWriteError) {
+ Initialize();
+ CompleteCryptoHandshake();
- EXPECT_TRUE(session_.CanPool("mail.example.org", PRIVACY_MODE_DISABLED));
+ scoped_ptr<QuicEncryptedPacket> ping(
+ maker_.MakePingPacket(1, /*include_version=*/true));
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ MockWrite writes[] = {MockWrite(SYNCHRONOUS, ping->data(), ping->length(), 1),
+ MockWrite(SYNCHRONOUS, ERR_FAILED, 2)};
+ SequencedSocketData socket_data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create connected socket.
+ scoped_ptr<DatagramClientSocket> new_socket =
+ socket_factory_.CreateDatagramClientSocket(DatagramSocket::DEFAULT_BIND,
+ base::Bind(&base::RandInt),
+ &net_log_, NetLog::Source());
+ EXPECT_EQ(OK, new_socket->Connect(kIpEndPoint));
+
+ // Create reader and writer.
+ scoped_ptr<QuicPacketReader> new_reader(new QuicPacketReader(
+ new_socket.get(), &clock_, session_.get(), kQuicYieldAfterPacketsRead,
+ QuicTime::Delta::FromMilliseconds(kQuicYieldAfterDurationMilliseconds),
+ bound_net_log_.bound()));
+ scoped_ptr<QuicPacketWriter> new_writer(
+ CreateQuicPacketWriter(new_socket.get(), session_->connection()));
+
+ // Migrate session.
+ EXPECT_TRUE(session_->MigrateToSocket(
+ std::move(new_socket), std::move(new_reader), std::move(new_writer)));
+
+ // Write error on new socket causes session close.
+ EXPECT_TRUE(session_->connection()->connected());
+ session_->connection()->SendPing();
+ EXPECT_FALSE(session_->connection()->connected());
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
}
} // namespace
diff --git a/net/quic/quic_connection.cc b/net/quic/quic_connection.cc
index 6eabc9d..13a8be1 100644
--- a/net/quic/quic_connection.cc
+++ b/net/quic/quic_connection.cc
@@ -153,7 +153,7 @@ class PingAlarm : public QuicAlarm::Delegate {
explicit PingAlarm(QuicConnection* connection) : connection_(connection) {}
QuicTime OnAlarm() override {
- connection_->SendPing();
+ connection_->OnPingTimeout();
return QuicTime::Zero();
}
@@ -1811,10 +1811,13 @@ PeerAddressChangeType QuicConnection::DeterminePeerAddressChangeType() {
return UNKNOWN;
}
-void QuicConnection::SendPing() {
- if (retransmission_alarm_->IsSet()) {
- return;
+void QuicConnection::OnPingTimeout() {
+ if (!retransmission_alarm_->IsSet()) {
+ SendPing();
}
+}
+
+void QuicConnection::SendPing() {
packet_generator_.AddControlFrame(QuicFrame(QuicPingFrame()));
}
diff --git a/net/quic/quic_connection.h b/net/quic/quic_connection.h
index f84be90..051d109 100644
--- a/net/quic/quic_connection.h
+++ b/net/quic/quic_connection.h
@@ -389,6 +389,10 @@ class NET_EXPORT_PRIVATE QuicConnection
// Set the packet writer.
void SetQuicPacketWriter(QuicPacketWriter* writer, bool owns_writer) {
+ DCHECK(writer != nullptr);
+ if (writer_ != nullptr && owns_writer_) {
+ delete writer_;
+ }
writer_ = writer;
owns_writer_ = owns_writer;
}
@@ -505,7 +509,11 @@ class NET_EXPORT_PRIVATE QuicConnection
// Otherwise, it will reschedule the timeout alarm.
void CheckForTimeout();
- // Sends a ping, and resets the ping alarm.
+ // Called when the ping alarm fires. Causes a ping frame to be sent only
+ // if the retransmission alarm is not running.
+ void OnPingTimeout();
+
+ // Sends a ping frame.
void SendPing();
// Sets up a packet with an QuicAckFrame and sends it out.
diff --git a/net/quic/quic_protocol.h b/net/quic/quic_protocol.h
index 4a38002..2921403 100644
--- a/net/quic/quic_protocol.h
+++ b/net/quic/quic_protocol.h
@@ -639,8 +639,20 @@ enum QuicErrorCode {
// tampered with.
QUIC_VERSION_NEGOTIATION_MISMATCH = 55,
+ // IP address changed causing connection close.
+ QUIC_IP_ADDRESS_CHANGED = 78,
+
+ // Connection migration errors.
+ // Network changed, but connection had no migratable streams.
+ QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS = 79,
+ // Connection changed networks too many times.
+ QUIC_CONNECTION_MIGRATION_TOO_MANY_CHANGES = 80,
+ // Connection migration was attempted, but there was no new network to
+ // migrate to.
+ QUIC_CONNECTION_MIGRATION_NO_NEW_NETWORK = 81,
+
// No error. Used as bound while iterating.
- QUIC_LAST_ERROR = 78,
+ QUIC_LAST_ERROR = 82,
};
// Must be updated any time a QuicErrorCode is deprecated.
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index 18061d5..1eea730 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -71,6 +71,15 @@ enum CreateSessionFailure {
CREATION_ERROR_MAX
};
+enum QuicConnectionMigrationStatus {
+ MIGRATION_STATUS_NO_MIGRATABLE_STREAMS,
+ MIGRATION_STATUS_ALREADY_MIGRATED,
+ MIGRATION_STATUS_INTERNAL_ERROR,
+ MIGRATION_STATUS_TOO_MANY_CHANGES,
+ MIGRATION_STATUS_SUCCESS,
+ MIGRATION_STATUS_MAX
+};
+
// The maximum receive window sizes for QUIC sessions and streams.
const int32_t kQuicSessionMaxRecvWindowSize = 15 * 1024 * 1024; // 15 MB
const int32_t kQuicStreamMaxRecvWindowSize = 6 * 1024 * 1024; // 6 MB
@@ -83,6 +92,11 @@ void HistogramCreateSessionFailure(enum CreateSessionFailure error) {
CREATION_ERROR_MAX);
}
+void HistogramMigrationStatus(enum QuicConnectionMigrationStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.ConnectionMigration", status,
+ MIGRATION_STATUS_MAX);
+}
+
bool IsEcdsaSupported() {
#if defined(OS_WIN)
if (base::win::GetVersion() < base::win::VERSION_VISTA)
@@ -575,6 +589,7 @@ QuicStreamFactory::QuicStreamFactory(
bool store_server_configs_in_properties,
bool close_sessions_on_ip_change,
int idle_connection_timeout_seconds,
+ bool migrate_sessions_on_network_change,
const QuicTagVector& connection_options)
: require_confirmation_(true),
host_resolver_(host_resolver),
@@ -622,6 +637,9 @@ QuicStreamFactory::QuicStreamFactory(
kQuicYieldAfterDurationMilliseconds)),
store_server_configs_in_properties_(store_server_configs_in_properties),
close_sessions_on_ip_change_(close_sessions_on_ip_change),
+ migrate_sessions_on_network_change_(
+ migrate_sessions_on_network_change &&
+ NetworkChangeNotifier::AreNetworkHandlesSupported()),
port_seed_(random_generator_->RandUint64()),
check_persisted_supports_quic_(true),
has_initialized_data_(false),
@@ -660,13 +678,17 @@ QuicStreamFactory::QuicStreamFactory(
new PropertiesBasedQuicServerInfoFactory(http_server_properties_));
}
- if (close_sessions_on_ip_change_) {
+ DCHECK(
+ !(close_sessions_on_ip_change_ && migrate_sessions_on_network_change_));
+ if (migrate_sessions_on_network_change_) {
+ NetworkChangeNotifier::AddNetworkObserver(this);
+ } else if (close_sessions_on_ip_change_) {
NetworkChangeNotifier::AddIPAddressObserver(this);
}
}
QuicStreamFactory::~QuicStreamFactory() {
- CloseAllSessions(ERR_ABORTED);
+ CloseAllSessions(ERR_ABORTED, QUIC_INTERNAL_ERROR);
while (!all_sessions_.empty()) {
delete all_sessions_.begin()->first;
all_sessions_.erase(all_sessions_.begin());
@@ -676,7 +698,9 @@ QuicStreamFactory::~QuicStreamFactory() {
STLDeleteElements(&(active_jobs_[server_id]));
active_jobs_.erase(server_id);
}
- if (close_sessions_on_ip_change_) {
+ if (migrate_sessions_on_network_change_) {
+ NetworkChangeNotifier::RemoveNetworkObserver(this);
+ } else if (close_sessions_on_ip_change_) {
NetworkChangeNotifier::RemoveIPAddressObserver(this);
}
}
@@ -1116,17 +1140,15 @@ void QuicStreamFactory::CancelRequest(QuicStreamRequest* request) {
active_requests_.erase(request);
}
-void QuicStreamFactory::CloseAllSessions(int error) {
+void QuicStreamFactory::CloseAllSessions(int error, QuicErrorCode quic_error) {
while (!active_sessions_.empty()) {
size_t initial_size = active_sessions_.size();
- active_sessions_.begin()->second->CloseSessionOnError(error,
- QUIC_INTERNAL_ERROR);
+ active_sessions_.begin()->second->CloseSessionOnError(error, quic_error);
DCHECK_NE(initial_size, active_sessions_.size());
}
while (!all_sessions_.empty()) {
size_t initial_size = all_sessions_.size();
- all_sessions_.begin()->first->CloseSessionOnError(error,
- QUIC_INTERNAL_ERROR);
+ all_sessions_.begin()->first->CloseSessionOnError(error, quic_error);
DCHECK_NE(initial_size, all_sessions_.size());
}
DCHECK(all_sessions_.empty());
@@ -1159,16 +1181,115 @@ void QuicStreamFactory::ClearCachedStatesInCryptoConfig() {
}
void QuicStreamFactory::OnIPAddressChanged() {
- CloseAllSessions(ERR_NETWORK_CHANGED);
+ CloseAllSessions(ERR_NETWORK_CHANGED, QUIC_IP_ADDRESS_CHANGED);
+ set_require_confirmation(true);
+}
+
+void QuicStreamFactory::OnNetworkConnected(
+ NetworkChangeNotifier::NetworkHandle network) {}
+
+void QuicStreamFactory::OnNetworkMadeDefault(
+ NetworkChangeNotifier::NetworkHandle network) {}
+
+void QuicStreamFactory::OnNetworkDisconnected(
+ NetworkChangeNotifier::NetworkHandle network) {
+ MaybeMigrateOrCloseSessions(network, /*force_close=*/true);
set_require_confirmation(true);
}
+// This method is expected to only be called when migrating from Cellular to
+// WiFi on Android.
+void QuicStreamFactory::OnNetworkSoonToDisconnect(
+ NetworkChangeNotifier::NetworkHandle network) {
+ MaybeMigrateOrCloseSessions(network, /*force_close=*/false);
+}
+
+void QuicStreamFactory::MaybeMigrateOrCloseSessions(
+ NetworkChangeNotifier::NetworkHandle network,
+ bool force_close) {
+ DCHECK_NE(NetworkChangeNotifier::kInvalidNetworkHandle, network);
+
+ // Find a new network that sessions bound to |network| can be migrated to.
+ NetworkChangeNotifier::NetworkList network_list;
+ NetworkChangeNotifier::GetConnectedNetworks(&network_list);
+ NetworkChangeNotifier::NetworkHandle new_network =
+ NetworkChangeNotifier::kInvalidNetworkHandle;
+ for (NetworkChangeNotifier::NetworkHandle n : network_list) {
+ if (n != network) {
+ new_network = n;
+ break;
+ }
+ }
+
+ QuicStreamFactory::SessionIdMap::iterator it = all_sessions_.begin();
+ while (it != all_sessions_.end()) {
+ QuicChromiumClientSession* session = it->first;
+ QuicServerId server_id = it->second;
+ ++it;
+
+ if (session->GetDefaultSocket()->GetBoundNetwork() != network) {
+ // If session is not bound to |network|, move on.
+ HistogramMigrationStatus(MIGRATION_STATUS_ALREADY_MIGRATED);
+ continue;
+ }
+ if (session->GetNumActiveStreams() == 0) {
+ // Close idle sessions.
+ session->CloseSessionOnError(
+ ERR_NETWORK_CHANGED, QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS);
+ HistogramMigrationStatus(MIGRATION_STATUS_NO_MIGRATABLE_STREAMS);
+ continue;
+ }
+ // If session has active streams, mark it as going away.
+ OnSessionGoingAway(session);
+ if (new_network == NetworkChangeNotifier::kInvalidNetworkHandle) {
+ // No new network was found.
+ if (force_close) {
+ session->CloseSessionOnError(ERR_NETWORK_CHANGED,
+ QUIC_CONNECTION_MIGRATION_NO_NEW_NETWORK);
+ }
+ continue;
+ }
+
+ // Use OS-specified port for socket (DEFAULT_BIND) instead of
+ // using the PortSuggester since the connection is being migrated
+ // and not being newly created.
+ scoped_ptr<DatagramClientSocket> socket(
+ client_socket_factory_->CreateDatagramClientSocket(
+ DatagramSocket::DEFAULT_BIND, RandIntCallback(),
+ session->net_log().net_log(), session->net_log().source()));
+
+ QuicConnection* connection = session->connection();
+ if (ConfigureSocket(socket.get(), connection->peer_address(),
+ new_network) != OK) {
+ session->CloseSessionOnError(ERR_NETWORK_CHANGED, QUIC_INTERNAL_ERROR);
+ HistogramMigrationStatus(MIGRATION_STATUS_INTERNAL_ERROR);
+ continue;
+ }
+
+ scoped_ptr<QuicPacketReader> new_reader(new QuicPacketReader(
+ socket.get(), clock_.get(), session, yield_after_packets_,
+ yield_after_duration_, session->net_log()));
+ DefaultPacketWriterFactory packet_writer_factory(socket.get());
+ scoped_ptr<QuicPacketWriter> new_writer(
+ packet_writer_factory.Create(connection));
+
+ if (!session->MigrateToSocket(std::move(socket), std::move(new_reader),
+ std::move(new_writer))) {
+ session->CloseSessionOnError(ERR_NETWORK_CHANGED,
+ QUIC_CONNECTION_MIGRATION_TOO_MANY_CHANGES);
+ HistogramMigrationStatus(MIGRATION_STATUS_TOO_MANY_CHANGES);
+ } else {
+ HistogramMigrationStatus(MIGRATION_STATUS_SUCCESS);
+ }
+ }
+}
+
void QuicStreamFactory::OnSSLConfigChanged() {
- CloseAllSessions(ERR_CERT_DATABASE_CHANGED);
+ CloseAllSessions(ERR_CERT_DATABASE_CHANGED, QUIC_INTERNAL_ERROR);
}
void QuicStreamFactory::OnCertAdded(const X509Certificate* cert) {
- CloseAllSessions(ERR_CERT_DATABASE_CHANGED);
+ CloseAllSessions(ERR_CERT_DATABASE_CHANGED, QUIC_INTERNAL_ERROR);
}
void QuicStreamFactory::OnCACertChanged(const X509Certificate* cert) {
@@ -1181,7 +1302,7 @@ void QuicStreamFactory::OnCACertChanged(const X509Certificate* cert) {
// Since the OnCACertChanged method doesn't tell us what
// kind of change it is, we have to flush the socket
// pools to be safe.
- CloseAllSessions(ERR_CERT_DATABASE_CHANGED);
+ CloseAllSessions(ERR_CERT_DATABASE_CHANGED, QUIC_INTERNAL_ERROR);
}
bool QuicStreamFactory::HasActiveSession(const QuicServerId& server_id) const {
@@ -1195,6 +1316,63 @@ bool QuicStreamFactory::HasActiveJob(const QuicServerId& key) const {
return ContainsKey(active_jobs_, key);
}
+int QuicStreamFactory::ConfigureSocket(
+ DatagramClientSocket* socket,
+ IPEndPoint addr,
+ NetworkChangeNotifier::NetworkHandle network) {
+ if (enable_non_blocking_io_ &&
+ client_socket_factory_ == ClientSocketFactory::GetDefaultFactory()) {
+#if defined(OS_WIN)
+ static_cast<UDPClientSocket*>(socket)->UseNonBlockingIO();
+#endif
+ }
+
+ // If caller leaves network unspecified, use current default.
+ int rv;
+ if (migrate_sessions_on_network_change_) {
+ if (network == NetworkChangeNotifier::kInvalidNetworkHandle) {
+ rv = socket->BindToDefaultNetwork();
+ } else {
+ rv = socket->BindToNetwork(network);
+ }
+ if (rv != OK)
+ return rv;
+ }
+
+ rv = socket->Connect(addr);
+ if (rv != OK) {
+ HistogramCreateSessionFailure(CREATION_ERROR_CONNECTING_SOCKET);
+ return rv;
+ }
+
+ rv = socket->SetReceiveBufferSize(socket_receive_buffer_size_);
+ if (rv != OK) {
+ HistogramCreateSessionFailure(CREATION_ERROR_SETTING_RECEIVE_BUFFER);
+ return rv;
+ }
+
+ // Set a buffer large enough to contain the initial CWND's worth of packet
+ // to work around the problem with CHLO packets being sent out with the
+ // wrong encryption level, when the send buffer is full.
+ rv = socket->SetSendBufferSize(kMaxPacketSize * 20);
+ if (rv != OK) {
+ HistogramCreateSessionFailure(CREATION_ERROR_SETTING_SEND_BUFFER);
+ return rv;
+ }
+
+ socket->GetLocalAddress(&local_address_);
+ if (check_persisted_supports_quic_) {
+ check_persisted_supports_quic_ = false;
+ IPAddressNumber last_address;
+ if (http_server_properties_->GetSupportsQuic(&last_address) &&
+ last_address == local_address_.address()) {
+ require_confirmation_ = false;
+ }
+ }
+
+ return OK;
+}
+
int QuicStreamFactory::CreateSession(const QuicServerId& server_id,
int cert_verify_flags,
scoped_ptr<QuicServerInfo> server_info,
@@ -1202,6 +1380,7 @@ int QuicStreamFactory::CreateSession(const QuicServerId& server_id,
base::TimeTicks dns_resolution_end_time,
const BoundNetLog& net_log,
QuicChromiumClientSession** session) {
+ IPEndPoint addr = *address_list.begin();
bool enable_port_selection = enable_port_selection_;
if (enable_port_selection && ContainsKey(gone_away_aliases_, server_id)) {
// Disable port selection when the server is going away.
@@ -1210,33 +1389,25 @@ int QuicStreamFactory::CreateSession(const QuicServerId& server_id,
enable_port_selection = false;
gone_away_aliases_.erase(server_id);
}
-
- QuicConnectionId connection_id = random_generator_->RandUint64();
- IPEndPoint addr = *address_list.begin();
scoped_refptr<PortSuggester> port_suggester =
new PortSuggester(server_id.host_port_pair(), port_seed_);
DatagramSocket::BindType bind_type =
enable_port_selection ? DatagramSocket::RANDOM_BIND
: // Use our callback.
DatagramSocket::DEFAULT_BIND; // Use OS to randomize.
+
scoped_ptr<DatagramClientSocket> socket(
client_socket_factory_->CreateDatagramClientSocket(
bind_type, base::Bind(&PortSuggester::SuggestPort, port_suggester),
net_log.net_log(), net_log.source()));
- if (enable_non_blocking_io_ &&
- client_socket_factory_ == ClientSocketFactory::GetDefaultFactory()) {
-#if defined(OS_WIN)
- static_cast<UDPClientSocket*>(socket.get())->UseNonBlockingIO();
-#endif
- }
-
- int rv = socket->Connect(addr);
-
+ // Passing in kInvalidNetworkHandle binds socket to default network.
+ int rv = ConfigureSocket(socket.get(), addr,
+ NetworkChangeNotifier::kInvalidNetworkHandle);
if (rv != OK) {
- HistogramCreateSessionFailure(CREATION_ERROR_CONNECTING_SOCKET);
return rv;
}
+
UMA_HISTOGRAM_COUNTS("Net.QuicEphemeralPortsSuggested",
port_suggester->call_count());
if (enable_port_selection) {
@@ -1245,38 +1416,14 @@ int QuicStreamFactory::CreateSession(const QuicServerId& server_id,
DCHECK_EQ(0u, port_suggester->call_count());
}
- rv = socket->SetReceiveBufferSize(socket_receive_buffer_size_);
- if (rv != OK) {
- HistogramCreateSessionFailure(CREATION_ERROR_SETTING_RECEIVE_BUFFER);
- return rv;
- }
- // Set a buffer large enough to contain the initial CWND's worth of packet
- // to work around the problem with CHLO packets being sent out with the
- // wrong encryption level, when the send buffer is full.
- rv = socket->SetSendBufferSize(kMaxPacketSize * 20);
- if (rv != OK) {
- HistogramCreateSessionFailure(CREATION_ERROR_SETTING_SEND_BUFFER);
- return rv;
- }
-
- socket->GetLocalAddress(&local_address_);
- if (check_persisted_supports_quic_) {
- check_persisted_supports_quic_ = false;
- IPAddressNumber last_address;
- if (http_server_properties_->GetSupportsQuic(&last_address) &&
- last_address == local_address_.address()) {
- require_confirmation_ = false;
- }
- }
-
DefaultPacketWriterFactory packet_writer_factory(socket.get());
-
if (!helper_.get()) {
helper_.reset(
new QuicConnectionHelper(base::ThreadTaskRunnerHandle::Get().get(),
clock_.get(), random_generator_));
}
+ QuicConnectionId connection_id = random_generator_->RandUint64();
QuicConnection* connection = new QuicConnection(
connection_id, addr, helper_.get(), packet_writer_factory,
true /* owns_writer */, Perspective::IS_CLIENT, supported_versions_);
diff --git a/net/quic/quic_stream_factory.h b/net/quic/quic_stream_factory.h
index fa7d47a..5a63b3e 100644
--- a/net/quic/quic_stream_factory.h
+++ b/net/quic/quic_stream_factory.h
@@ -112,6 +112,7 @@ class NET_EXPORT_PRIVATE QuicStreamRequest {
// QuicChromiumClientSessions.
class NET_EXPORT_PRIVATE QuicStreamFactory
: public NetworkChangeNotifier::IPAddressObserver,
+ public NetworkChangeNotifier::NetworkObserver,
public SSLConfigService::Observer,
public CertDatabase::Observer {
public:
@@ -149,6 +150,7 @@ class NET_EXPORT_PRIVATE QuicStreamFactory
bool store_server_configs_in_properties,
bool close_sessions_on_ip_change,
int idle_connection_timeout_seconds,
+ bool migrate_sessions_on_network_change,
const QuicTagVector& connection_options);
~QuicStreamFactory() override;
@@ -208,20 +210,47 @@ class NET_EXPORT_PRIVATE QuicStreamFactory
// Cancels a pending request.
void CancelRequest(QuicStreamRequest* request);
- // Closes all current sessions.
- void CloseAllSessions(int error);
+ // Closes all current sessions with specified network and QUIC error codes.
+ void CloseAllSessions(int error, QuicErrorCode quic_error);
scoped_ptr<base::Value> QuicStreamFactoryInfoToValue() const;
// Delete all cached state objects in |crypto_config_|.
void ClearCachedStatesInCryptoConfig();
+ // Helper method that configures a DatagramClientSocket. Socket is
+ // bound to the default network if the |network| param is
+ // NetworkChangeNotifier::kInvalidNetworkHandle.
+ // Returns net_error code.
+ int ConfigureSocket(DatagramClientSocket* socket,
+ IPEndPoint addr,
+ NetworkChangeNotifier::NetworkHandle network);
+
+ // Helper method that initiates migration of active sessions
+ // currently bound to |network| to an alternate network, if one
+ // exists. Idle sessions bound to |network| are closed. If there is
+ // no alternate network to migrate active sessions onto, active
+ // sessions are closed if |force_close| is true, and continue using
+ // |network| otherwise. Sessions not bound to |network| are left unchanged.
+ void MaybeMigrateOrCloseSessions(NetworkChangeNotifier::NetworkHandle network,
+ bool force_close);
+
// NetworkChangeNotifier::IPAddressObserver methods:
// Until the servers support roaming, close all connections when the local
// IP address changes.
void OnIPAddressChanged() override;
+ // NetworkChangeNotifier::NetworkObserver methods:
+ void OnNetworkConnected(
+ NetworkChangeNotifier::NetworkHandle network) override;
+ void OnNetworkDisconnected(
+ NetworkChangeNotifier::NetworkHandle network) override;
+ void OnNetworkSoonToDisconnect(
+ NetworkChangeNotifier::NetworkHandle network) override;
+ void OnNetworkMadeDefault(
+ NetworkChangeNotifier::NetworkHandle network) override;
+
// SSLConfigService::Observer methods:
// We perform the same flushing as described above when SSL settings change.
@@ -459,6 +488,10 @@ class NET_EXPORT_PRIVATE QuicStreamFactory
// Set if all sessions should be closed when any local IP address changes.
const bool close_sessions_on_ip_change_;
+ // Set if migration should be attempted on active sessions when primary
+ // interface changes.
+ const bool migrate_sessions_on_network_change_;
+
// Each profile will (probably) have a unique port_seed_ value. This value
// is used to help seed a pseudo-random number generator (PortSuggester) so
// that we consistently (within this profile) suggest the same ephemeral
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index 477bb52..1e1e1d6 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -24,6 +24,7 @@
#include "net/quic/crypto/quic_encrypter.h"
#include "net/quic/crypto/quic_server_info.h"
#include "net/quic/quic_http_stream.h"
+#include "net/quic/quic_http_utils.h"
#include "net/quic/quic_server_id.h"
#include "net/quic/test_tools/mock_clock.h"
#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
@@ -46,6 +47,7 @@ using std::string;
using std::vector;
namespace net {
+
namespace test {
namespace {
@@ -119,6 +121,71 @@ class MockQuicServerInfoFactory : public QuicServerInfoFactory {
}
};
+class MockNetworkChangeNotifier : public NetworkChangeNotifier {
+ public:
+ MockNetworkChangeNotifier() : force_network_handles_supported_(false) {}
+
+ ConnectionType GetCurrentConnectionType() const override {
+ return CONNECTION_UNKNOWN;
+ }
+
+ void ForceNetworkHandlesSupported() {
+ force_network_handles_supported_ = true;
+ }
+
+ bool AreNetworkHandlesCurrentlySupported() const override {
+ return force_network_handles_supported_;
+ }
+
+ void SetConnectedNetworksList(const NetworkList& network_list) {
+ connected_networks_ = network_list;
+ }
+
+ void GetCurrentConnectedNetworks(NetworkList* network_list) const override {
+ network_list->clear();
+ *network_list = connected_networks_;
+ }
+
+ void NotifyNetworkSoonToDisconnect(
+ NetworkChangeNotifier::NetworkHandle network) {
+ NetworkChangeNotifier::NotifyObserversOfSpecificNetworkChange(
+ NetworkChangeNotifier::SOON_TO_DISCONNECT, network);
+ // Spin the message loop so the notification is delivered.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void NotifyNetworkDisconnected(NetworkChangeNotifier::NetworkHandle network) {
+ NetworkChangeNotifier::NotifyObserversOfSpecificNetworkChange(
+ NetworkChangeNotifier::DISCONNECTED, network);
+ // Spin the message loop so the notification is delivered.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ private:
+ bool force_network_handles_supported_;
+ NetworkChangeNotifier::NetworkList connected_networks_;
+};
+
+// Class to replace existing NetworkChangeNotifier singleton with a
+// MockNetworkChangeNotifier for a test. To use, simply create a
+// ScopedMockNetworkChangeNotifier object in the test.
+class ScopedMockNetworkChangeNotifier {
+ public:
+ ScopedMockNetworkChangeNotifier()
+ : disable_network_change_notifier_for_tests_(
+ new NetworkChangeNotifier::DisableForTest()),
+ mock_network_change_notifier_(new MockNetworkChangeNotifier()) {}
+
+ MockNetworkChangeNotifier* mock_network_change_notifier() {
+ return mock_network_change_notifier_.get();
+ }
+
+ private:
+ scoped_ptr<NetworkChangeNotifier::DisableForTest>
+ disable_network_change_notifier_for_tests_;
+ scoped_ptr<MockNetworkChangeNotifier> mock_network_change_notifier_;
+};
+
class QuicStreamFactoryTest : public ::testing::TestWithParam<TestParams> {
protected:
QuicStreamFactoryTest()
@@ -131,6 +198,7 @@ class QuicStreamFactoryTest : public ::testing::TestWithParam<TestParams> {
new ChannelIDService(new DefaultChannelIDStore(nullptr),
base::ThreadTaskRunnerHandle::Get())),
cert_transparency_verifier_(new MultiLogCTVerifier()),
+ scoped_mock_network_change_notifier_(nullptr),
factory_(nullptr),
host_port_pair_(kDefaultServerHostName, kDefaultServerPort),
privacy_mode_(PRIVACY_MODE_DISABLED),
@@ -151,7 +219,8 @@ class QuicStreamFactoryTest : public ::testing::TestWithParam<TestParams> {
delay_tcp_race_(false),
store_server_configs_in_properties_(false),
close_sessions_on_ip_change_(false),
- idle_connection_timeout_seconds_(kIdleConnectionTimeoutSeconds) {
+ idle_connection_timeout_seconds_(kIdleConnectionTimeoutSeconds),
+ migrate_sessions_on_network_change_(false) {
clock_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
}
@@ -172,11 +241,23 @@ class QuicStreamFactoryTest : public ::testing::TestWithParam<TestParams> {
threshold_public_resets_post_handshake_, receive_buffer_size_,
delay_tcp_race_, store_server_configs_in_properties_,
close_sessions_on_ip_change_, idle_connection_timeout_seconds_,
- QuicTagVector()));
+ migrate_sessions_on_network_change_, QuicTagVector()));
factory_->set_require_confirmation(false);
factory_->set_quic_server_info_factory(new MockQuicServerInfoFactory());
}
+ void InitializeConnectionMigrationTest(
+ NetworkChangeNotifier::NetworkList connected_networks) {
+ scoped_mock_network_change_notifier_.reset(
+ new ScopedMockNetworkChangeNotifier());
+ MockNetworkChangeNotifier* mock_ncn =
+ scoped_mock_network_change_notifier_->mock_network_change_notifier();
+ mock_ncn->ForceNetworkHandlesSupported();
+ mock_ncn->SetConnectedNetworksList(connected_networks);
+ migrate_sessions_on_network_change_ = true;
+ Initialize();
+ }
+
bool HasActiveSession(const HostPortPair& host_port_pair) {
return QuicStreamFactoryPeer::HasActiveSession(factory_.get(),
host_port_pair);
@@ -263,14 +344,40 @@ class QuicStreamFactoryTest : public ::testing::TestWithParam<TestParams> {
void NotifyIPAddressChanged() {
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
- // For thread safety, the NCN queues tasks to do the actual notifications,
- // so we need to spin the message loop so the notification is delivered.
+ // Spin the message loop so the notification is delivered.
base::MessageLoop::current()->RunUntilIdle();
}
+ scoped_ptr<QuicEncryptedPacket> ConstructGetRequestPacket(
+ QuicPacketNumber packet_number,
+ QuicStreamId stream_id,
+ bool should_include_version,
+ bool fin) {
+ SpdyHeaderBlock headers = maker_.GetRequestHeaders("GET", "https", "/");
+ SpdyPriority priority =
+ ConvertRequestPriorityToQuicPriority(DEFAULT_PRIORITY);
+ size_t spdy_headers_frame_len;
+ return maker_.MakeRequestHeadersPacket(
+ packet_number, stream_id, should_include_version, fin, priority,
+ headers, &spdy_headers_frame_len);
+ }
+
+ scoped_ptr<QuicEncryptedPacket> ConstructOkResponsePacket(
+ QuicPacketNumber packet_number,
+ QuicStreamId stream_id,
+ bool should_include_version,
+ bool fin) {
+ SpdyHeaderBlock headers = maker_.GetResponseHeaders("200 OK");
+ size_t spdy_headers_frame_len;
+ return maker_.MakeResponseHeadersPacket(packet_number, stream_id,
+ should_include_version, fin,
+ headers, &spdy_headers_frame_len);
+ }
+
MockHostResolver host_resolver_;
MockClientSocketFactory socket_factory_;
MockCryptoClientStreamFactory crypto_client_stream_factory_;
+ ProofVerifyDetailsChromium verify_details_;
MockRandom random_generator_;
MockClock* clock_; // Owned by factory_.
scoped_refptr<TestTaskRunner> runner_;
@@ -280,6 +387,8 @@ class QuicStreamFactoryTest : public ::testing::TestWithParam<TestParams> {
scoped_ptr<ChannelIDService> channel_id_service_;
TransportSecurityState transport_security_state_;
scoped_ptr<CTVerifier> cert_transparency_verifier_;
+ scoped_ptr<ScopedMockNetworkChangeNotifier>
+ scoped_mock_network_change_notifier_;
scoped_ptr<QuicStreamFactory> factory_;
HostPortPair host_port_pair_;
PrivacyMode privacy_mode_;
@@ -305,6 +414,7 @@ class QuicStreamFactoryTest : public ::testing::TestWithParam<TestParams> {
bool store_server_configs_in_properties_;
bool close_sessions_on_ip_change_;
int idle_connection_timeout_seconds_;
+ bool migrate_sessions_on_network_change_;
};
INSTANTIATE_TEST_CASE_P(Version,
@@ -1172,7 +1282,7 @@ TEST_P(QuicStreamFactoryTest, CloseAllSessions) {
net_log_, CompletionCallback()));
// Close the session and verify that stream saw the error.
- factory_->CloseAllSessions(ERR_INTERNET_DISCONNECTED);
+ factory_->CloseAllSessions(ERR_INTERNET_DISCONNECTED, QUIC_INTERNAL_ERROR);
EXPECT_EQ(ERR_INTERNET_DISCONNECTED,
stream->ReadResponseHeaders(callback_.callback()));
@@ -1252,6 +1362,499 @@ TEST_P(QuicStreamFactoryTest, OnIPAddressChanged) {
EXPECT_TRUE(socket_data2.AllWriteDataConsumed());
}
+TEST_P(QuicStreamFactoryTest, OnNetworkChangeSoonToDisconnect) {
+ InitializeConnectionMigrationTest(
+ {kDefaultNetworkForTests, kNewNetworkForTests});
+ ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ scoped_ptr<QuicEncryptedPacket> request_packet(
+ ConstructGetRequestPacket(1, kClientDataStreamId1, true, true));
+ MockWrite writes[] = {MockWrite(SYNCHRONOUS, request_packet->data(),
+ request_packet->length(), 1)};
+ SequencedSocketData socket_data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create request and QuicHttpStream.
+ QuicStreamRequest request(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream.get());
+
+ // Cause QUIC stream to be created.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("https://www.example.org/");
+ EXPECT_EQ(OK, stream->InitializeStream(&request_info, DEFAULT_PRIORITY,
+ net_log_, CompletionCallback()));
+
+ // Ensure that session is alive and active.
+ QuicChromiumClientSession* session =
+ QuicStreamFactoryPeer::GetActiveSession(factory_.get(), host_port_pair_);
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_TRUE(HasActiveSession(host_port_pair_));
+
+ // Send GET request on stream.
+ HttpResponseInfo response;
+ HttpRequestHeaders request_headers;
+ EXPECT_EQ(OK, stream->SendRequest(request_headers, &response,
+ callback_.callback()));
+
+ // Set up second socket data provider that is used after migration.
+ // The response to the earlier request is read on this new socket.
+ scoped_ptr<QuicEncryptedPacket> ping(
+ maker_.MakePingPacket(2, /*include_version=*/true));
+ MockWrite writes1[] = {
+ MockWrite(SYNCHRONOUS, ping->data(), ping->length(), 0)};
+ scoped_ptr<QuicEncryptedPacket> response_headers_packet(
+ ConstructOkResponsePacket(1, kClientDataStreamId1, false, false));
+ MockRead reads1[] = {MockRead(ASYNC, response_headers_packet->data(),
+ response_headers_packet->length(), 1),
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2)};
+ SequencedSocketData socket_data1(reads1, arraysize(reads1), writes1,
+ arraysize(writes1));
+ socket_factory_.AddSocketDataProvider(&socket_data1);
+
+ // Trigger connection migration. This should cause a PING frame
+ // to be emitted.
+ scoped_mock_network_change_notifier_->mock_network_change_notifier()
+ ->NotifyNetworkSoonToDisconnect(kDefaultNetworkForTests);
+
+ // The session should now be marked as going away. Ensure that
+ // while it is still alive, it is no longer active.
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_FALSE(HasActiveSession(host_port_pair_));
+ EXPECT_EQ(1u, session->GetNumActiveStreams());
+
+ // Verify that response headers on the migrated socket were delivered to the
+ // stream.
+ EXPECT_EQ(OK, stream->ReadResponseHeaders(callback_.callback()));
+ EXPECT_EQ(200, response.headers->response_code());
+
+ // Create a new request for the same destination and verify that a
+ // new session is created.
+ MockRead reads2[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ SequencedSocketData socket_data2(reads2, arraysize(reads2), nullptr, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data2);
+
+ QuicStreamRequest request2(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request2.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+ EXPECT_TRUE(stream2.get());
+
+ EXPECT_TRUE(
+ QuicStreamFactoryPeer::HasActiveSession(factory_.get(), host_port_pair_));
+ EXPECT_NE(session, QuicStreamFactoryPeer::GetActiveSession(factory_.get(),
+ host_port_pair_));
+
+ // On a DISCONNECTED notification, nothing happens to the migrated session.
+ scoped_mock_network_change_notifier_->mock_network_change_notifier()
+ ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_EQ(1u, session->GetNumActiveStreams());
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+ EXPECT_TRUE(socket_data1.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data1.AllWriteDataConsumed());
+ EXPECT_TRUE(socket_data2.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data2.AllWriteDataConsumed());
+}
+
+TEST_P(QuicStreamFactoryTest, OnNetworkChangeDisconnected) {
+ InitializeConnectionMigrationTest(
+ {kDefaultNetworkForTests, kNewNetworkForTests});
+ ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ scoped_ptr<QuicEncryptedPacket> request_packet(
+ ConstructGetRequestPacket(1, kClientDataStreamId1, true, true));
+ MockWrite writes[] = {MockWrite(SYNCHRONOUS, request_packet->data(),
+ request_packet->length(), 1)};
+ SequencedSocketData socket_data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create request and QuicHttpStream.
+ QuicStreamRequest request(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream.get());
+
+ // Cause QUIC stream to be created.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("https://www.example.org/");
+ EXPECT_EQ(OK, stream->InitializeStream(&request_info, DEFAULT_PRIORITY,
+ net_log_, CompletionCallback()));
+
+ // Ensure that session is alive and active.
+ QuicChromiumClientSession* session =
+ QuicStreamFactoryPeer::GetActiveSession(factory_.get(), host_port_pair_);
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_TRUE(HasActiveSession(host_port_pair_));
+
+ // Send GET request on stream.
+ HttpResponseInfo response_info;
+ HttpRequestHeaders request_headers;
+ EXPECT_EQ(OK, stream->SendRequest(request_headers, &response_info,
+ callback_.callback()));
+
+ // Set up second socket data provider that is used after migration.
+ scoped_ptr<QuicEncryptedPacket> ping(
+ maker_.MakePingPacket(2, /*include_version=*/true));
+ scoped_ptr<QuicEncryptedPacket> client_rst(maker_.MakeRstPacket(
+ 3, true, kClientDataStreamId1, QUIC_STREAM_CANCELLED));
+ MockWrite writes1[] = {
+ MockWrite(SYNCHRONOUS, ping->data(), ping->length(), 0)};
+ scoped_ptr<QuicEncryptedPacket> response_packet(
+ ConstructOkResponsePacket(1, kClientDataStreamId1, false, false));
+ MockRead reads1[] = {
+ MockRead(ASYNC, response_packet->data(), response_packet->length(), 1),
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2)};
+ SequencedSocketData socket_data1(reads1, arraysize(reads1), writes1,
+ arraysize(writes1));
+ socket_factory_.AddSocketDataProvider(&socket_data1);
+
+ // Trigger connection migration. This should cause a PING frame
+ // to be emitted.
+ scoped_mock_network_change_notifier_->mock_network_change_notifier()
+ ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
+
+ // The session should now be marked as going away. Ensure that
+ // while it is still alive, it is no longer active.
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_FALSE(HasActiveSession(host_port_pair_));
+ EXPECT_EQ(1u, session->GetNumActiveStreams());
+
+ // Create a new request for the same destination and verify that a
+ // new session is created.
+ MockRead reads2[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ SequencedSocketData socket_data2(reads2, arraysize(reads2), nullptr, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data2);
+
+ QuicStreamRequest request2(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request2.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+ EXPECT_TRUE(stream2.get());
+
+ EXPECT_TRUE(
+ QuicStreamFactoryPeer::HasActiveSession(factory_.get(), host_port_pair_));
+ EXPECT_NE(session, QuicStreamFactoryPeer::GetActiveSession(factory_.get(),
+ host_port_pair_));
+ EXPECT_EQ(true,
+ QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+ EXPECT_TRUE(socket_data1.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data1.AllWriteDataConsumed());
+ EXPECT_TRUE(socket_data2.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data2.AllWriteDataConsumed());
+}
+
+TEST_P(QuicStreamFactoryTest, OnNetworkChangeSoonToDisconnectNoNetworks) {
+ NetworkChangeNotifier::NetworkList no_networks(0);
+ InitializeConnectionMigrationTest(no_networks);
+ ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ scoped_ptr<QuicEncryptedPacket> client_rst(maker_.MakeRstPacket(
+ 1, true, kClientDataStreamId1, QUIC_STREAM_CANCELLED));
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, client_rst->data(), client_rst->length(), 1),
+ };
+ SequencedSocketData socket_data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create request and QuicHttpStream.
+ QuicStreamRequest request(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream.get());
+
+ // Cause QUIC stream to be created.
+ HttpRequestInfo request_info;
+ EXPECT_EQ(OK, stream->InitializeStream(&request_info, DEFAULT_PRIORITY,
+ net_log_, CompletionCallback()));
+
+ // Ensure that session is alive and active.
+ QuicChromiumClientSession* session =
+ QuicStreamFactoryPeer::GetActiveSession(factory_.get(), host_port_pair_);
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_TRUE(HasActiveSession(host_port_pair_));
+ EXPECT_EQ(1u, session->GetNumActiveStreams());
+
+ // Trigger connection migration. Since there are no networks
+ // to migrate to, this should cause the session to continue on the same
+ // socket, but be marked as going away.
+ scoped_mock_network_change_notifier_->mock_network_change_notifier()
+ ->NotifyNetworkSoonToDisconnect(kDefaultNetworkForTests);
+
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_FALSE(HasActiveSession(host_port_pair_));
+ EXPECT_EQ(1u, session->GetNumActiveStreams());
+
+ stream.reset();
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+}
+
+TEST_P(QuicStreamFactoryTest, OnNetworkChangeDisconnectedNoNetworks) {
+ NetworkChangeNotifier::NetworkList no_networks(0);
+ InitializeConnectionMigrationTest(no_networks);
+ ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ scoped_ptr<QuicEncryptedPacket> client_rst(maker_.MakeRstPacket(
+ 1, true, kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT));
+ MockWrite writes[] = {
+ MockWrite(ASYNC, client_rst->data(), client_rst->length(), 1),
+ };
+ SequencedSocketData socket_data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create request and QuicHttpStream.
+ QuicStreamRequest request(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream.get());
+
+ // Cause QUIC stream to be created.
+ HttpRequestInfo request_info;
+ EXPECT_EQ(OK, stream->InitializeStream(&request_info, DEFAULT_PRIORITY,
+ net_log_, CompletionCallback()));
+
+ // Ensure that session is alive and active.
+ QuicChromiumClientSession* session =
+ QuicStreamFactoryPeer::GetActiveSession(factory_.get(), host_port_pair_);
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_TRUE(HasActiveSession(host_port_pair_));
+
+ // Trigger connection migration. Since there are no networks
+ // to migrate to, this should cause a RST_STREAM frame to be emitted
+ // and the session to be closed.
+ scoped_mock_network_change_notifier_->mock_network_change_notifier()
+ ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
+
+ EXPECT_FALSE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_FALSE(HasActiveSession(host_port_pair_));
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+}
+
+TEST_P(QuicStreamFactoryTest, OnNetworkChangeSoonToDisconnectNoNewNetwork) {
+ InitializeConnectionMigrationTest({kDefaultNetworkForTests});
+ ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ scoped_ptr<QuicEncryptedPacket> client_rst(maker_.MakeRstPacket(
+ 1, true, kClientDataStreamId1, QUIC_STREAM_CANCELLED));
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, client_rst->data(), client_rst->length(), 1),
+ };
+ SequencedSocketData socket_data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create request and QuicHttpStream.
+ QuicStreamRequest request(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream.get());
+
+ // Cause QUIC stream to be created.
+ HttpRequestInfo request_info;
+ EXPECT_EQ(OK, stream->InitializeStream(&request_info, DEFAULT_PRIORITY,
+ net_log_, CompletionCallback()));
+
+ // Ensure that session is alive and active.
+ QuicChromiumClientSession* session =
+ QuicStreamFactoryPeer::GetActiveSession(factory_.get(), host_port_pair_);
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_TRUE(HasActiveSession(host_port_pair_));
+
+ // Trigger connection migration. Since there are no networks
+ // to migrate to, this should cause session to be continue but be marked as
+ // going away.
+ scoped_mock_network_change_notifier_->mock_network_change_notifier()
+ ->NotifyNetworkSoonToDisconnect(kDefaultNetworkForTests);
+
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_FALSE(HasActiveSession(host_port_pair_));
+ EXPECT_EQ(1u, session->GetNumActiveStreams());
+
+ stream.reset();
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+}
+
+TEST_P(QuicStreamFactoryTest, OnNetworkChangeDisconnectedNoNewNetwork) {
+ InitializeConnectionMigrationTest({kDefaultNetworkForTests});
+ ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ scoped_ptr<QuicEncryptedPacket> client_rst(maker_.MakeRstPacket(
+ 1, true, kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT));
+ MockWrite writes[] = {
+ MockWrite(ASYNC, client_rst->data(), client_rst->length(), 1),
+ };
+ SequencedSocketData socket_data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create request and QuicHttpStream.
+ QuicStreamRequest request(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream.get());
+
+ // Cause QUIC stream to be created.
+ HttpRequestInfo request_info;
+ EXPECT_EQ(OK, stream->InitializeStream(&request_info, DEFAULT_PRIORITY,
+ net_log_, CompletionCallback()));
+
+ // Ensure that session is alive and active.
+ QuicChromiumClientSession* session =
+ QuicStreamFactoryPeer::GetActiveSession(factory_.get(), host_port_pair_);
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_TRUE(HasActiveSession(host_port_pair_));
+
+ // Trigger connection migration. Since there are no networks
+ // to migrate to, this should cause a RST_STREAM frame to be emitted
+ // with QUIC_RST_ACKNOWLEDGEMENT error code, and the session will be closed.
+ scoped_mock_network_change_notifier_->mock_network_change_notifier()
+ ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
+
+ EXPECT_FALSE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_FALSE(HasActiveSession(host_port_pair_));
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+}
+
+TEST_P(QuicStreamFactoryTest, OnNetworkChangeSoonToDisconnectNoOpenStreams) {
+ InitializeConnectionMigrationTest(
+ {kDefaultNetworkForTests, kNewNetworkForTests});
+ ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ SequencedSocketData socket_data(reads, arraysize(reads), nullptr, 0u);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create request and QuicHttpStream.
+ QuicStreamRequest request(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream.get());
+
+ // Ensure that session is alive and active.
+ QuicChromiumClientSession* session =
+ QuicStreamFactoryPeer::GetActiveSession(factory_.get(), host_port_pair_);
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_TRUE(HasActiveSession(host_port_pair_));
+
+ // Trigger connection migration. Since there are no active streams,
+ // the session will be closed.
+ scoped_mock_network_change_notifier_->mock_network_change_notifier()
+ ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
+
+ EXPECT_FALSE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_FALSE(HasActiveSession(host_port_pair_));
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+}
+
+TEST_P(QuicStreamFactoryTest, OnNetworkChangeDisconnectedNoOpenStreams) {
+ InitializeConnectionMigrationTest(
+ {kDefaultNetworkForTests, kNewNetworkForTests});
+ ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+ crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+ MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0)};
+ SequencedSocketData socket_data(reads, arraysize(reads), nullptr, 0u);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ // Create request and QuicHttpStream.
+ QuicStreamRequest request(factory_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ request.Request(host_port_pair_, privacy_mode_,
+ /*cert_verify_flags=*/0, host_port_pair_.host(),
+ "GET", net_log_, callback_.callback()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream.get());
+
+ // Ensure that session is alive and active.
+ QuicChromiumClientSession* session =
+ QuicStreamFactoryPeer::GetActiveSession(factory_.get(), host_port_pair_);
+ EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_TRUE(HasActiveSession(host_port_pair_));
+
+ // Trigger connection migration. Since there are no active streams,
+ // the session will be closed.
+ scoped_mock_network_change_notifier_->mock_network_change_notifier()
+ ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
+
+ EXPECT_FALSE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+ EXPECT_FALSE(HasActiveSession(host_port_pair_));
+
+ EXPECT_TRUE(socket_data.AllReadDataConsumed());
+ EXPECT_TRUE(socket_data.AllWriteDataConsumed());
+}
+
TEST_P(QuicStreamFactoryTest, OnSSLConfigChanged) {
Initialize();
ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
diff --git a/net/quic/quic_utils.cc b/net/quic/quic_utils.cc
index 733a517..8781afbd 100644
--- a/net/quic/quic_utils.cc
+++ b/net/quic/quic_utils.cc
@@ -275,6 +275,10 @@ const char* QuicUtils::ErrorToString(QuicErrorCode error) {
RETURN_STRING_LITERAL(QUIC_FAILED_TO_SERIALIZE_PACKET);
RETURN_STRING_LITERAL(QUIC_TOO_MANY_AVAILABLE_STREAMS);
RETURN_STRING_LITERAL(QUIC_UNENCRYPTED_FEC_DATA);
+ RETURN_STRING_LITERAL(QUIC_IP_ADDRESS_CHANGED);
+ RETURN_STRING_LITERAL(QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS);
+ RETURN_STRING_LITERAL(QUIC_CONNECTION_MIGRATION_TOO_MANY_CHANGES);
+ RETURN_STRING_LITERAL(QUIC_CONNECTION_MIGRATION_NO_NEW_NETWORK);
RETURN_STRING_LITERAL(QUIC_LAST_ERROR);
// Intentionally have no default case, so we'll break the build
// if we add errors and don't put them here.
diff --git a/net/quic/test_tools/quic_test_packet_maker.cc b/net/quic/test_tools/quic_test_packet_maker.cc
index 2b9993b..25f0f43 100644
--- a/net/quic/test_tools/quic_test_packet_maker.cc
+++ b/net/quic/test_tools/quic_test_packet_maker.cc
@@ -33,6 +33,23 @@ void QuicTestPacketMaker::set_hostname(const std::string& host) {
host_.assign(host);
}
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakePingPacket(
+ QuicPacketNumber num,
+ bool include_version) {
+ QuicPacketHeader header;
+ header.public_header.connection_id = connection_id_;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = include_version;
+ header.public_header.packet_number_length = PACKET_1BYTE_PACKET_NUMBER;
+ header.packet_number = num;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.fec_group = 0;
+
+ QuicPingFrame ping;
+ return scoped_ptr<QuicEncryptedPacket>(MakePacket(header, QuicFrame(ping)));
+}
+
scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeRstPacket(
QuicPacketNumber num,
bool include_version,
@@ -232,6 +249,35 @@ scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeDataPacket(
return MakePacket(header_, QuicFrame(&frame));
}
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeAckAndDataPacket(
+ QuicPacketNumber packet_number,
+ bool include_version,
+ QuicStreamId stream_id,
+ QuicPacketNumber largest_received,
+ QuicPacketNumber least_unacked,
+ bool fin,
+ QuicStreamOffset offset,
+ base::StringPiece data) {
+ InitializeHeader(packet_number, include_version);
+
+ QuicAckFrame ack(MakeAckFrame(largest_received));
+ ack.delta_time_largest_observed = QuicTime::Delta::Zero();
+ for (QuicPacketNumber i = least_unacked; i <= largest_received; ++i) {
+ ack.received_packet_times.push_back(make_pair(i, clock_->Now()));
+ }
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&ack));
+
+ QuicStopWaitingFrame stop_waiting;
+ stop_waiting.least_unacked = least_unacked;
+ frames.push_back(QuicFrame(&stop_waiting));
+
+ QuicStreamFrame stream_frame(stream_id, fin, offset, data);
+ frames.push_back(QuicFrame(&stream_frame));
+
+ return MakeMultipleFramesPacket(header_, frames);
+}
+
scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeRequestHeadersPacket(
QuicPacketNumber packet_number,
QuicStreamId stream_id,
@@ -406,10 +452,16 @@ SpdyHeaderBlock QuicTestPacketMaker::GetResponseHeaders(
scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakePacket(
const QuicPacketHeader& header,
const QuicFrame& frame) {
- QuicFramer framer(SupportedVersions(version_), QuicTime::Zero(),
- Perspective::IS_CLIENT);
QuicFrames frames;
frames.push_back(frame);
+ return MakeMultipleFramesPacket(header, frames);
+}
+
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeMultipleFramesPacket(
+ const QuicPacketHeader& header,
+ const QuicFrames& frames) {
+ QuicFramer framer(SupportedVersions(version_), QuicTime::Zero(),
+ Perspective::IS_CLIENT);
scoped_ptr<QuicPacket> packet(
BuildUnsizedDataPacket(&framer, header, frames));
char buffer[kMaxPacketSize];
diff --git a/net/quic/test_tools/quic_test_packet_maker.h b/net/quic/test_tools/quic_test_packet_maker.h
index 9769770..cad8082 100644
--- a/net/quic/test_tools/quic_test_packet_maker.h
+++ b/net/quic/test_tools/quic_test_packet_maker.h
@@ -30,6 +30,8 @@ class QuicTestPacketMaker {
~QuicTestPacketMaker();
void set_hostname(const std::string& host);
+ scoped_ptr<QuicEncryptedPacket> MakePingPacket(QuicPacketNumber num,
+ bool include_version);
scoped_ptr<QuicEncryptedPacket> MakeRstPacket(
QuicPacketNumber num,
bool include_version,
@@ -70,6 +72,15 @@ class QuicTestPacketMaker {
bool fin,
QuicStreamOffset offset,
base::StringPiece data);
+ scoped_ptr<QuicEncryptedPacket> MakeAckAndDataPacket(
+ QuicPacketNumber packet_number,
+ bool include_version,
+ QuicStreamId stream_id,
+ QuicPacketNumber largest_received,
+ QuicPacketNumber least_unacked,
+ bool fin,
+ QuicStreamOffset offset,
+ base::StringPiece data);
// If |spdy_headers_frame_length| is non-null, it will be set to the size of
// the SPDY headers frame created for this packet.
@@ -143,6 +154,9 @@ class QuicTestPacketMaker {
private:
scoped_ptr<QuicEncryptedPacket> MakePacket(const QuicPacketHeader& header,
const QuicFrame& frame);
+ scoped_ptr<QuicEncryptedPacket> MakeMultipleFramesPacket(
+ const QuicPacketHeader& header,
+ const QuicFrames& frames);
void InitializeHeader(QuicPacketNumber packet_number,
bool should_include_version);
diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc
index f4b804b..04f5151 100644
--- a/net/socket/socket_test_util.cc
+++ b/net/socket/socket_test_util.cc
@@ -35,7 +35,6 @@
#define NET_TRACE(level, s) VLOG(level) << s << __FUNCTION__ << "() "
namespace net {
-
namespace {
inline char AsciifyHigh(char x) {
@@ -1245,6 +1244,7 @@ MockUDPClientSocket::MockUDPClientSocket(SocketDataProvider* data,
read_data_(SYNCHRONOUS, ERR_UNEXPECTED),
need_read_data_(true),
source_port_(123),
+ network_(NetworkChangeNotifier::kInvalidNetworkHandle),
pending_read_buf_(NULL),
pending_read_buf_len_(0),
net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_NONE)),
@@ -1343,16 +1343,18 @@ const BoundNetLog& MockUDPClientSocket::NetLog() const {
int MockUDPClientSocket::BindToNetwork(
NetworkChangeNotifier::NetworkHandle network) {
- return ERR_NOT_IMPLEMENTED;
+ network_ = network;
+ return OK;
}
int MockUDPClientSocket::BindToDefaultNetwork() {
- return ERR_NOT_IMPLEMENTED;
+ network_ = kDefaultNetworkForTests;
+ return OK;
}
NetworkChangeNotifier::NetworkHandle MockUDPClientSocket::GetBoundNetwork()
const {
- return NetworkChangeNotifier::kInvalidNetworkHandle;
+ return network_;
}
int MockUDPClientSocket::Connect(const IPEndPoint& address) {
diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h
index 9446f1c..a3c6df4 100644
--- a/net/socket/socket_test_util.h
+++ b/net/socket/socket_test_util.h
@@ -46,6 +46,9 @@ class RunLoop;
namespace net {
+const NetworkChangeNotifier::NetworkHandle kDefaultNetworkForTests = 1;
+const NetworkChangeNotifier::NetworkHandle kNewNetworkForTests = 2;
+
enum {
// A private network error code used by the socket test utility classes.
// If the |result| member of a MockRead is
@@ -764,6 +767,9 @@ class MockUDPClientSocket : public DatagramClientSocket, public AsyncSocket {
// Address of the "remote" peer we're connected to.
IPEndPoint peer_addr_;
+ // Network that the socket is bound to.
+ NetworkChangeNotifier::NetworkHandle network_;
+
// While an asynchronous IO is pending, we save our user-buffer state.
scoped_refptr<IOBuffer> pending_read_buf_;
int pending_read_buf_len_;
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 9483803..e41bf28 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -25711,6 +25711,12 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
</summary>
</histogram>
+<histogram name="Net.QuicSession.ConnectionMigration"
+ enum="QuicConnectionMigrationStatus">
+ <owner>jri@chromium.org</owner>
+ <summary>The result of a QUIC connection migration attempt.</summary>
+</histogram>
+
<histogram name="Net.QuicSession.ConnectionTypeFromPeer" enum="AddressFamily">
<owner>rch@chromium.org</owner>
<summary>
@@ -75041,6 +75047,14 @@ To add a new entry, add it with any value and run test to compute valid value.
<int value="5" label="FIVE_PACKETS_LOST"/>
</enum>
+<enum name="QuicConnectionMigrationStatus" type="int">
+ <int value="1" label="NO_MIGRATABLE_STREAMS"/>
+ <int value="2" label="ALREADY_MIGRATED"/>
+ <int value="3" label="INTERNAL_ERROR"/>
+ <int value="4" label="TOO_MANY_CHANGES"/>
+ <int value="5" label="SUCCESS"/>
+</enum>
+
<enum name="QuicDisabledReason" type="int">
<int value="1" label="Public reset post handshake"/>
<int value="2" label="Timeout with open streams"/>