diff options
-rw-r--r-- | net/base/net_error_details.h | 9 | ||||
-rw-r--r-- | net/quic/quic_chromium_client_session.cc | 11 | ||||
-rw-r--r-- | net/quic/quic_chromium_client_session.h | 8 | ||||
-rw-r--r-- | net/quic/quic_http_stream.cc | 9 | ||||
-rw-r--r-- | net/quic/quic_http_stream.h | 5 | ||||
-rw-r--r-- | net/quic/quic_network_transaction_unittest.cc | 80 | ||||
-rw-r--r-- | net/quic/quic_stream_factory_test.cc | 40 | ||||
-rw-r--r-- | net/quic/test_tools/quic_test_packet_maker.cc | 22 | ||||
-rw-r--r-- | net/quic/test_tools/quic_test_packet_maker.h | 3 |
9 files changed, 180 insertions, 7 deletions
diff --git a/net/base/net_error_details.h b/net/base/net_error_details.h index 47efebe..7bd5baa 100644 --- a/net/base/net_error_details.h +++ b/net/base/net_error_details.h @@ -16,12 +16,14 @@ struct NET_EXPORT NetErrorDetails { NetErrorDetails() : quic_broken(false), quic_connection_error(QUIC_NO_ERROR), - connection_info(HttpResponseInfo::CONNECTION_INFO_UNKNOWN) {} + connection_info(HttpResponseInfo::CONNECTION_INFO_UNKNOWN), + quic_port_migration_detected(false) {} NetErrorDetails(bool quic_broken, QuicErrorCode quic_connection_error) : quic_broken(quic_broken), quic_connection_error(quic_connection_error), - connection_info(HttpResponseInfo::CONNECTION_INFO_UNKNOWN) {} + connection_info(HttpResponseInfo::CONNECTION_INFO_UNKNOWN), + quic_port_migration_detected(false) {} // True if all QUIC alternative services are marked broken for the origin. bool quic_broken; @@ -31,6 +33,9 @@ struct NET_EXPORT NetErrorDetails { // Will be discarded by upper layers if the connection type can be fetched // from response header from the server. HttpResponseInfo::ConnectionInfo connection_info; + // True if receives a GoAway frame from the server due to connection + // migration with port change. + bool quic_port_migration_detected; }; } // namespace net diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc index 15ec708..85d06ae 100644 --- a/net/quic/quic_chromium_client_session.cc +++ b/net/quic/quic_chromium_client_session.cc @@ -205,6 +205,7 @@ QuicChromiumClientSession::QuicChromiumClientSession( std::move(socket_performance_watcher), net_log_)), going_away_(false), + port_migration_detected_(false), disabled_reason_(QUIC_DISABLED_NOT), token_binding_signatures_(kTokenBindingSignatureMapSize), weak_factory_(this) { @@ -370,7 +371,7 @@ void QuicChromiumClientSession::OnStreamFrame(const QuicStreamFrame& frame) { void QuicChromiumClientSession::AddObserver(Observer* observer) { if (going_away_) { RecordUnexpectedObservers(ADD_OBSERVER); - observer->OnSessionClosed(ERR_UNEXPECTED); + observer->OnSessionClosed(ERR_UNEXPECTED, port_migration_detected_); return; } @@ -760,6 +761,7 @@ void QuicChromiumClientSession::OnCryptoHandshakeMessageReceived( void QuicChromiumClientSession::OnGoAway(const QuicGoAwayFrame& frame) { QuicSession::OnGoAway(frame); NotifyFactoryOfSessionGoingAway(); + port_migration_detected_ = frame.error_code == QUIC_ERROR_MIGRATING_PORT; } void QuicChromiumClientSession::OnRstStream(const QuicRstStreamFrame& frame) { @@ -961,7 +963,7 @@ void QuicChromiumClientSession::CloseAllObservers(int net_error) { while (!observers_.empty()) { Observer* observer = *observers_.begin(); observers_.erase(observer); - observer->OnSessionClosed(net_error); + observer->OnSessionClosed(net_error, port_migration_detected_); } } @@ -1098,6 +1100,11 @@ bool QuicChromiumClientSession::MigrateToSocket( return true; } +void QuicChromiumClientSession::PopulateNetErrorDetails( + NetErrorDetails* details) { + details->quic_port_migration_detected = port_migration_detected_; +} + const DatagramClientSocket* QuicChromiumClientSession::GetDefaultSocket() const { DCHECK(sockets_.back().get() != nullptr); diff --git a/net/quic/quic_chromium_client_session.h b/net/quic/quic_chromium_client_session.h index c4ef293..5cc31d3 100644 --- a/net/quic/quic_chromium_client_session.h +++ b/net/quic/quic_chromium_client_session.h @@ -20,6 +20,7 @@ #include "base/memory/scoped_ptr.h" #include "base/time/time.h" #include "net/base/completion_callback.h" +#include "net/base/net_error_details.h" #include "net/base/socket_performance_watcher.h" #include "net/cert/ct_verify_result.h" #include "net/proxy/proxy_server.h" @@ -71,7 +72,7 @@ class NET_EXPORT_PRIVATE QuicChromiumClientSession public: virtual ~Observer() {} virtual void OnCryptoHandshakeConfirmed() = 0; - virtual void OnSessionClosed(int error) = 0; + virtual void OnSessionClosed(int error, bool port_migration_detected) = 0; }; // A helper class used to manage a request to create a stream. @@ -248,6 +249,9 @@ class NET_EXPORT_PRIVATE QuicChromiumClientSession scoped_ptr<QuicChromiumPacketReader> reader, scoped_ptr<QuicPacketWriter> writer); + // Populates network error details for this session. + void PopulateNetErrorDetails(NetErrorDetails* details); + // 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. @@ -328,6 +332,8 @@ class NET_EXPORT_PRIVATE QuicChromiumClientSession // True when the session is going away, and streams may no longer be created // on this session. Existing stream will continue to be processed. bool going_away_; + // True when the session receives a go away from server due to port migration. + bool port_migration_detected_; QuicDisabledReason disabled_reason_; TokenBindingSignatureMap token_binding_signatures_; base::WeakPtrFactory<QuicChromiumClientSession> weak_factory_; diff --git a/net/quic/quic_http_stream.cc b/net/quic/quic_http_stream.cc index 5578bd6..55f3296 100644 --- a/net/quic/quic_http_stream.cc +++ b/net/quic/quic_http_stream.cc @@ -46,6 +46,7 @@ QuicHttpStream::QuicHttpStream( closed_stream_sent_bytes_(0), user_buffer_len_(0), quic_connection_error_(QUIC_NO_ERROR), + port_migration_detected_(false), found_promise_(false), push_handle_(nullptr), weak_factory_(this) { @@ -437,6 +438,11 @@ void QuicHttpStream::PopulateNetErrorDetails(NetErrorDetails* details) { details->connection_info = HttpResponseInfo::CONNECTION_INFO_QUIC1_SPDY3; if (was_handshake_confirmed_) details->quic_connection_error = quic_connection_error_; + if (session_) { + session_->PopulateNetErrorDetails(details); + } else { + details->quic_port_migration_detected = port_migration_detected_; + } } void QuicHttpStream::SetPriority(RequestPriority priority) { @@ -514,9 +520,10 @@ void QuicHttpStream::OnCryptoHandshakeConfirmed() { was_handshake_confirmed_ = true; } -void QuicHttpStream::OnSessionClosed(int error) { +void QuicHttpStream::OnSessionClosed(int error, bool port_migration_detected) { Close(false); session_error_ = error; + port_migration_detected_ = port_migration_detected; session_.reset(); } diff --git a/net/quic/quic_http_stream.h b/net/quic/quic_http_stream.h index 81aacb0..a88a6e1 100644 --- a/net/quic/quic_http_stream.h +++ b/net/quic/quic_http_stream.h @@ -79,7 +79,7 @@ class NET_EXPORT_PRIVATE QuicHttpStream // QuicChromiumClientSession::Observer implementation void OnCryptoHandshakeConfirmed() override; - void OnSessionClosed(int error) override; + void OnSessionClosed(int error, bool port_migration_detected) override; // QuicClientPushPromiseIndex::Delegate implementation bool CheckVary(const SpdyHeaderBlock& client_request, @@ -193,6 +193,9 @@ class NET_EXPORT_PRIVATE QuicHttpStream // SSLInfo from the underlying QuicSession. SSLInfo ssl_info_; + // True when this stream receives a go away from server due to port migration. + bool port_migration_detected_; + bool found_promise_; // |QuicClientPromisedInfo| owns this. It will be set when |Try()| // is asynchronous, i.e. it returned QUIC_PENDING, and remains valid diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc index b5a8d6d..16847aa 100644 --- a/net/quic/quic_network_transaction_unittest.cc +++ b/net/quic/quic_network_transaction_unittest.cc @@ -233,6 +233,13 @@ class QuicNetworkTransactionTest return maker_.MakeConnectionClosePacket(num); } + scoped_ptr<QuicEncryptedPacket> ConstructGoAwayPacket( + QuicPacketNumber num, + QuicErrorCode error_code, + std::string reason_phrase) { + return maker_.MakeGoAwayPacket(num, error_code, reason_phrase); + } + scoped_ptr<QuicEncryptedPacket> ConstructAckPacket( QuicPacketNumber largest_received, QuicPacketNumber least_unacked) { @@ -240,6 +247,18 @@ class QuicNetworkTransactionTest least_unacked, true); } + scoped_ptr<QuicEncryptedPacket> ConstructAckAndRstPacket( + QuicPacketNumber num, + QuicStreamId stream_id, + QuicRstStreamErrorCode error_code, + QuicPacketNumber largest_received, + QuicPacketNumber ack_least_unacked, + QuicPacketNumber stop_least_unacked) { + return maker_.MakeAckAndRstPacket(num, false, stream_id, error_code, + largest_received, ack_least_unacked, + stop_least_unacked, true); + } + scoped_ptr<QuicEncryptedPacket> ConstructAckPacket( QuicPacketNumber largest_received, QuicPacketNumber least_unacked, @@ -915,6 +934,67 @@ TEST_P(QuicNetworkTransactionTest, UseAlternativeServiceQuicSupportedVersion) { SendRequestAndExpectQuicResponse("hello!"); } +TEST_P(QuicNetworkTransactionTest, GoAwayWithConnectionMigrationOnPortsOnly) { + MockQuicData mock_quic_data; + mock_quic_data.AddWrite( + ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true, + GetRequestHeaders("GET", "https", "/"))); + mock_quic_data.AddRead(ConstructResponseHeadersPacket( + 1, kClientDataStreamId1, false, false, GetResponseHeaders("200 OK"))); + // Read a GoAway packet with + // QuicErrorCode: QUIC_ERROR_MIGRATING_PORT from the peer. + mock_quic_data.AddRead( + ConstructGoAwayPacket(2, QUIC_ERROR_MIGRATING_PORT, + "connection migration with port change only")); + mock_quic_data.AddWrite(ConstructAckPacket(2, 1)); + mock_quic_data.AddRead( + ConstructDataPacket(3, kClientDataStreamId1, false, true, 0, "hello!")); + mock_quic_data.AddWrite(ConstructAckAndRstPacket( + 3, kClientDataStreamId1, QUIC_STREAM_CANCELLED, 3, 3, 1)); + mock_quic_data.AddRead(ASYNC, ERR_IO_PENDING); // No more data to read + mock_quic_data.AddRead(ASYNC, 0); // EOF + + mock_quic_data.AddSocketDataToFactory(&socket_factory_); + + // The non-alternate protocol job needs to hang in order to guarantee that + // the alternate-protocol job will "win". + AddHangingNonAlternateProtocolSocketData(); + + // In order for a new QUIC session to be established via alternate-protocol + // without racing an HTTP connection, we need the host resolution to happen + // synchronously. Of course, even though QUIC *could* perform a 0-RTT + // connection to the the server, in this test we require confirmation + // before encrypting so the HTTP job will still start. + host_resolver_.set_synchronous_mode(true); + host_resolver_.rules()->AddIPLiteralRule("mail.example.org", "192.168.0.1", + ""); + HostResolver::RequestInfo info(HostPortPair("mail.example.org", 443)); + AddressList address; + host_resolver_.Resolve(info, DEFAULT_PRIORITY, &address, CompletionCallback(), + nullptr, net_log_.bound()); + + CreateSession(); + session_->quic_stream_factory()->set_require_confirmation(true); + AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT); + + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); + TestCompletionCallback callback; + int rv = trans->Start(&request_, callback.callback(), net_log_.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + crypto_client_stream_factory_.last_stream()->SendOnCryptoHandshakeEvent( + QuicSession::HANDSHAKE_CONFIRMED); + EXPECT_EQ(OK, callback.WaitForResult()); + + // Check whether this transaction is correctly marked as received a go-away + // because of migrating port. + NetErrorDetails details; + EXPECT_FALSE(details.quic_port_migration_detected); + trans->PopulateNetErrorDetails(&details); + EXPECT_TRUE(details.quic_port_migration_detected); +} + TEST_P(QuicNetworkTransactionTest, DoNotUseAlternativeServiceQuicUnsupportedVersion) { std::string altsvc_header = base::StringPrintf( diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc index 762a2d7..c01c3dc 100644 --- a/net/quic/quic_stream_factory_test.cc +++ b/net/quic/quic_stream_factory_test.cc @@ -603,6 +603,46 @@ TEST_P(QuicStreamFactoryTest, GoAway) { EXPECT_TRUE(socket_data.AllWriteDataConsumed()); } +TEST_P(QuicStreamFactoryTest, GoAwayForConnectionMigrationWithPortOnly) { + Initialize(); + 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, 0); + socket_factory_.AddSocketDataProvider(&socket_data); + + QuicStreamRequest request(factory_.get()); + EXPECT_EQ(ERR_IO_PENDING, + request.Request(host_port_pair_, privacy_mode_, + /*cert_verify_flags=*/0, url_, "GET", net_log_, + callback_.callback())); + + EXPECT_EQ(OK, callback_.WaitForResult()); + scoped_ptr<QuicHttpStream> stream = request.ReleaseStream(); + EXPECT_TRUE(stream.get()); + + QuicChromiumClientSession* session = + QuicStreamFactoryPeer::GetActiveSession(factory_.get(), host_port_pair_); + + session->OnGoAway( + QuicGoAwayFrame(QUIC_ERROR_MIGRATING_PORT, 0, + "peer connection migration due to port change only")); + NetErrorDetails details; + EXPECT_FALSE(details.quic_port_migration_detected); + session->PopulateNetErrorDetails(&details); + EXPECT_TRUE(details.quic_port_migration_detected); + details.quic_port_migration_detected = false; + stream->PopulateNetErrorDetails(&details); + EXPECT_TRUE(details.quic_port_migration_detected); + + EXPECT_FALSE( + QuicStreamFactoryPeer::HasActiveSession(factory_.get(), host_port_pair_)); + + EXPECT_TRUE(socket_data.AllReadDataConsumed()); + EXPECT_TRUE(socket_data.AllWriteDataConsumed()); +} + TEST_P(QuicStreamFactoryTest, Pooling) { Initialize(); ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails(); diff --git a/net/quic/test_tools/quic_test_packet_maker.cc b/net/quic/test_tools/quic_test_packet_maker.cc index ebd2021..0bb671c 100644 --- a/net/quic/test_tools/quic_test_packet_maker.cc +++ b/net/quic/test_tools/quic_test_packet_maker.cc @@ -190,6 +190,28 @@ scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeConnectionClosePacket( return scoped_ptr<QuicEncryptedPacket>(MakePacket(header, QuicFrame(&close))); } +scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeGoAwayPacket( + QuicPacketNumber num, + QuicErrorCode error_code, + std::string reason_phrase) { + QuicPacketHeader header; + header.public_header.connection_id = connection_id_; + header.public_header.reset_flag = false; + header.public_header.version_flag = false; + 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; + + QuicGoAwayFrame goaway; + goaway.error_code = error_code; + goaway.last_good_stream_id = 0; + goaway.reason_phrase = reason_phrase; + return scoped_ptr<QuicEncryptedPacket>( + MakePacket(header, QuicFrame(&goaway))); +} + // Sets both least_unacked fields in stop waiting frame and ack frame // to be |least_unacked|. scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeAckPacket( diff --git a/net/quic/test_tools/quic_test_packet_maker.h b/net/quic/test_tools/quic_test_packet_maker.h index 2b2499fa..3527cdf 100644 --- a/net/quic/test_tools/quic_test_packet_maker.h +++ b/net/quic/test_tools/quic_test_packet_maker.h @@ -56,6 +56,9 @@ class QuicTestPacketMaker { std::string& quic_error_details); scoped_ptr<QuicEncryptedPacket> MakeConnectionClosePacket( QuicPacketNumber num); + scoped_ptr<QuicEncryptedPacket> MakeGoAwayPacket(QuicPacketNumber num, + QuicErrorCode error_code, + std::string reason_phrase); scoped_ptr<QuicEncryptedPacket> MakeAckPacket( QuicPacketNumber packet_number, QuicPacketNumber largest_received, |