diff options
-rw-r--r-- | chrome/browser/io_thread.cc | 13 | ||||
-rw-r--r-- | chrome/browser/io_thread.h | 6 | ||||
-rw-r--r-- | chrome/browser/io_thread_unittest.cc | 10 | ||||
-rw-r--r-- | components/domain_reliability/util.cc | 16 | ||||
-rw-r--r-- | net/http/http_network_session.cc | 4 | ||||
-rw-r--r-- | net/http/http_network_session.h | 4 | ||||
-rw-r--r-- | net/quic/quic_chromium_client_session.cc | 55 | ||||
-rw-r--r-- | net/quic/quic_chromium_client_session.h | 21 | ||||
-rw-r--r-- | net/quic/quic_chromium_client_session_test.cc | 427 | ||||
-rw-r--r-- | net/quic/quic_connection.cc | 11 | ||||
-rw-r--r-- | net/quic/quic_connection.h | 10 | ||||
-rw-r--r-- | net/quic/quic_protocol.h | 14 | ||||
-rw-r--r-- | net/quic/quic_stream_factory.cc | 247 | ||||
-rw-r--r-- | net/quic/quic_stream_factory.h | 37 | ||||
-rw-r--r-- | net/quic/quic_stream_factory_test.cc | 613 | ||||
-rw-r--r-- | net/quic/quic_utils.cc | 4 | ||||
-rw-r--r-- | net/quic/test_tools/quic_test_packet_maker.cc | 56 | ||||
-rw-r--r-- | net/quic/test_tools/quic_test_packet_maker.h | 14 | ||||
-rw-r--r-- | net/socket/socket_test_util.cc | 10 | ||||
-rw-r--r-- | net/socket/socket_test_util.h | 6 | ||||
-rw-r--r-- | tools/metrics/histograms/histograms.xml | 14 |
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( ¶ms->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( + ¶ms->quic_migrate_sessions_on_network_change); globals.origin_to_force_quic_on.CopyToIfSet( ¶ms->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(¶ms); + 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"/> |