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.