Send HTTP/3 GOAWAY frame when closing connection. Protected by FLAGS_quic_reloadable_flag_quic_send_goaway_with_connection_close. PiperOrigin-RevId: 337371228 Change-Id: Ic810995552c911a4ef978208c397237f2848cc82
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc index 36a122e..2d883b0 100644 --- a/quic/core/http/quic_spdy_session.cc +++ b/quic/core/http/quic_spdy_session.cc
@@ -1359,6 +1359,13 @@ } } +void QuicSpdySession::BeforeConnectionCloseSent() { + if (GetQuicReloadableFlag(quic_send_goaway_with_connection_close) && + VersionUsesHttp3(transport_version()) && IsEncryptionEstablished()) { + SendHttp3GoAway(); + } +} + void QuicSpdySession::OnCanCreateNewOutgoingStream(bool unidirectional) { if (unidirectional && VersionUsesHttp3(transport_version())) { MaybeInitializeHttp3UnidirectionalStreams();
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h index bd9500c..2a6ca31 100644 --- a/quic/core/http/quic_spdy_session.h +++ b/quic/core/http/quic_spdy_session.h
@@ -467,6 +467,9 @@ // Initializes HTTP/3 unidirectional streams if not yet initialzed. virtual void MaybeInitializeHttp3UnidirectionalStreams(); + // QuicConnectionVisitorInterface method. + void BeforeConnectionCloseSent() override; + private: friend class test::QuicSpdySessionPeer;
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc index 345d1b5..76dba5c 100644 --- a/quic/core/http/quic_spdy_session_test.cc +++ b/quic/core/http/quic_spdy_session_test.cc
@@ -23,6 +23,7 @@ #include "net/third_party/quiche/src/quic/core/quic_data_writer.h" #include "net/third_party/quiche/src/quic/core/quic_packets.h" #include "net/third_party/quiche/src/quic/core/quic_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/quic/core/quic_utils.h" #include "net/third_party/quiche/src/quic/core/quic_versions.h" #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h" @@ -3061,6 +3062,63 @@ session_.OnStreamFrame(data3); } +TEST_P(QuicSpdySessionTestServer, Http3GoAwayWhenClosingConnection) { + if (!VersionUsesHttp3(transport_version())) { + return; + } + + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_.set_debug_visitor(&debug_visitor); + + EXPECT_CALL(debug_visitor, OnSettingsFrameSent(_)); + CompleteHandshake(); + + QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0); + + // Create stream by receiving some data (CreateIncomingStream() would not + // update the session's largest peer created stream ID). + const size_t headers_payload_length = 10; + std::unique_ptr<char[]> headers_buffer; + QuicByteCount headers_frame_header_length = + HttpEncoder::SerializeHeadersFrameHeader(headers_payload_length, + &headers_buffer); + absl::string_view headers_frame_header(headers_buffer.get(), + headers_frame_header_length); + EXPECT_CALL(debug_visitor, + OnHeadersFrameReceived(stream_id, headers_payload_length)); + session_.OnStreamFrame( + QuicStreamFrame(stream_id, false, 0, headers_frame_header)); + + EXPECT_EQ(stream_id, QuicSessionPeer::GetLargestPeerCreatedStreamId( + &session_, /*unidirectional = */ false)); + + if (GetQuicReloadableFlag(quic_send_goaway_with_connection_close)) { + if (GetQuicReloadableFlag(quic_fix_http3_goaway_stream_id)) { + // Stream with stream_id is already received and potentially processed, + // therefore a GOAWAY frame is sent with the next stream ID. + EXPECT_CALL(debug_visitor, + OnGoAwayFrameSent(stream_id + QuicUtils::StreamIdDelta( + transport_version()))); + } else { + // GOAWAY frame stream id is incorrect, ignore. + EXPECT_CALL(debug_visitor, OnGoAwayFrameSent(_)); + } + } + + // Close connection. + EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _)) + .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 0))); + EXPECT_CALL(*connection_, CloseConnection(QUIC_NO_ERROR, _, _)) + .WillOnce( + Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); + EXPECT_CALL(*connection_, SendConnectionClosePacket(QUIC_NO_ERROR, _)) + .WillOnce(Invoke(connection_, + &MockQuicConnection::ReallySendConnectionClosePacket)); + connection_->CloseConnection( + QUIC_NO_ERROR, "closing connection", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); +} + TEST_P(QuicSpdySessionTestClient, SendInitialMaxPushIdIfSet) { if (!VersionUsesHttp3(transport_version())) { return;
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index 52992d0..4dec406 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -3787,6 +3787,11 @@ packet_creator_.FlushAckFrame(frames); } + if (level == ENCRYPTION_FORWARD_SECURE && + perspective_ == Perspective::IS_SERVER) { + visitor_->BeforeConnectionCloseSent(); + } + auto* frame = new QuicConnectionCloseFrame(transport_version(), error, details, framer_.current_received_frame_type());
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h index 3e74191..afcbe13 100644 --- a/quic/core/quic_connection.h +++ b/quic/core/quic_connection.h
@@ -204,6 +204,11 @@ // Called to generate an encrypter for the same key phase of the last // decrypter returned by AdvanceKeysAndCreateCurrentOneRttDecrypter(). virtual std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() = 0; + + // Called when connection is being closed right before a CONNECTION_CLOSE + // frame is serialized, but only on the server and only if forward secure + // encryption has already been established. + virtual void BeforeConnectionCloseSent() = 0; }; // Interface which gets callbacks from the QuicConnection at interesting
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 178eb2f..62f4b8a 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -1479,6 +1479,9 @@ host.FromString("1.1.1.1"); QuicSocketAddress self_address(host, 123); EXPECT_CALL(visitor_, AllowSelfAddressChange()).WillOnce(Return(false)); + if (version().handshake_protocol == PROTOCOL_TLS1_3) { + EXPECT_CALL(visitor_, BeforeConnectionCloseSent()); + } EXPECT_CALL(visitor_, OnConnectionClosed(_, _)); ProcessFramePacketWithAddresses(MakeCryptoFrame(), self_address, kPeerAddress, ENCRYPTION_INITIAL); @@ -7401,6 +7404,9 @@ frame1_.data_buffer = data->data(); frame1_.data_length = data->length(); + if (version().handshake_protocol == PROTOCOL_TLS1_3) { + EXPECT_CALL(visitor_, BeforeConnectionCloseSent()); + } EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)); ForceProcessFramePacket(QuicFrame(frame1_)); @@ -7657,6 +7663,9 @@ } set_perspective(Perspective::IS_SERVER); + if (version().handshake_protocol == PROTOCOL_TLS1_3) { + EXPECT_CALL(visitor_, BeforeConnectionCloseSent()); + } EXPECT_CALL(visitor_, OnConnectionClosed(_, _)); const QuicErrorCode kQuicErrorCode = QUIC_INTERNAL_ERROR; connection_.CloseConnection( @@ -9823,6 +9832,9 @@ } set_perspective(Perspective::IS_SERVER); EXPECT_CALL(visitor_, OnHandshakeDoneReceived()).Times(0); + if (version().handshake_protocol == PROTOCOL_TLS1_3) { + EXPECT_CALL(visitor_, BeforeConnectionCloseSent()); + } EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)) .WillOnce(Invoke(this, &QuicConnectionTest::SaveConnectionCloseFrame)); QuicFrames frames; @@ -11008,6 +11020,9 @@ EXPECT_TRUE(connection_.connected()); EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet()); + if (version().handshake_protocol == PROTOCOL_TLS1_3) { + EXPECT_CALL(visitor_, BeforeConnectionCloseSent()); + } EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)); EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h index 3c04c77..e22bd23 100644 --- a/quic/core/quic_session.h +++ b/quic/core/quic_session.h
@@ -140,6 +140,7 @@ std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter() override; std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override; + void BeforeConnectionCloseSent() override {} // QuicStreamFrameDataProducer WriteStreamDataResult WriteStreamData(QuicStreamId id,
diff --git a/quic/test_tools/quic_session_peer.h b/quic/test_tools/quic_session_peer.h index 9a6fdef..fdcb443 100644 --- a/quic/test_tools/quic_session_peer.h +++ b/quic/test_tools/quic_session_peer.h
@@ -82,6 +82,10 @@ static void SetPerspective(QuicSession* session, Perspective perspective); static size_t GetNumOpenDynamicStreams(QuicSession* session); static size_t GetNumDrainingStreams(QuicSession* session); + static QuicStreamId GetLargestPeerCreatedStreamId(QuicSession* session, + bool unidirectional) { + return session->GetLargestPeerCreatedStreamId(unidirectional); + } }; } // namespace test
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index 3539628..a55f9a8 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -589,6 +589,7 @@ CreateCurrentOneRttEncrypter, (), (override)); + MOCK_METHOD(void, BeforeConnectionCloseSent, (), (override)); }; class MockQuicConnectionHelper : public QuicConnectionHelperInterface { @@ -733,6 +734,11 @@ QuicConnection::CloseConnection(error, details, connection_close_behavior); } + void ReallySendConnectionClosePacket(QuicErrorCode error, + const std::string& details) { + QuicConnection::SendConnectionClosePacket(error, details); + } + void ReallyProcessUdpPacket(const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address, const QuicReceivedPacket& packet) {
diff --git a/quic/test_tools/simulator/quic_endpoint.h b/quic/test_tools/simulator/quic_endpoint.h index 7968900..7f5bef2 100644 --- a/quic/test_tools/simulator/quic_endpoint.h +++ b/quic/test_tools/simulator/quic_endpoint.h
@@ -101,6 +101,7 @@ std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override { return nullptr; } + void BeforeConnectionCloseSent() override {} // End QuicConnectionVisitorInterface implementation.