Support GOAWAY in HTTP/3. Currently the GOAWAY is used and sent in the same way as gQUIC GOAWAY does, which means the content of GOAWAY frame is not used. I will follow up with more CLs to implement the real IETF usage where stream larger than the goaway id is not allowed. gfe-relnote: protected by disabled v99 flag. PiperOrigin-RevId: 275339124 Change-Id: I082f579f501b534cce7ef2b83c94dee5a2091e85
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc index 4d78106..c949fa7 100644 --- a/quic/core/http/quic_receive_control_stream.cc +++ b/quic/core/http/quic_receive_control_stream.cc
@@ -9,6 +9,7 @@ #include "net/third_party/quiche/src/quic/core/http/http_constants.h" #include "net/third_party/quiche/src/quic/core/http/http_decoder.h" #include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" namespace quic { @@ -65,9 +66,15 @@ return false; } - bool OnGoAwayFrame(const GoAwayFrame& /*frame*/) override { - CloseConnectionOnWrongFrame("Goaway"); - return false; + bool OnGoAwayFrame(const GoAwayFrame& frame) override { + QuicSpdySession* spdy_session = + static_cast<QuicSpdySession*>(stream_->session()); + if (spdy_session->perspective() == Perspective::IS_SERVER) { + CloseConnectionOnWrongFrame("Go Away"); + return false; + } + spdy_session->OnHttp3GoAway(frame.stream_id); + return true; } bool OnSettingsFrameStart(QuicByteCount header_length) override {
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc index 73b30b3..c8df8a0 100644 --- a/quic/core/http/quic_receive_control_stream_test.cc +++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -5,6 +5,7 @@ #include "net/third_party/quiche/src/quic/core/http/quic_receive_control_stream.h" #include "net/third_party/quiche/src/quic/core/http/http_constants.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/platform/api/quic_ptr_util.h" #include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" @@ -214,11 +215,12 @@ } TEST_P(QuicReceiveControlStreamTest, ReceiveWrongFrame) { - GoAwayFrame goaway; - goaway.stream_id = 0x1; + DuplicatePushFrame dup; + dup.push_id = 0x1; HttpEncoder encoder; std::unique_ptr<char[]> buffer; - QuicByteCount header_length = encoder.SerializeGoAwayFrame(goaway, &buffer); + QuicByteCount header_length = + encoder.SerializeDuplicatePushFrame(dup, &buffer); std::string data = std::string(buffer.get(), header_length); QuicStreamFrame frame(receive_control_stream_->id(), false, 1, data); @@ -245,6 +247,28 @@ EXPECT_EQ(1u, stream_->precedence().spdy3_priority()); } +TEST_P(QuicReceiveControlStreamTest, ReceiveGoAwayFrame) { + GoAwayFrame goaway; + goaway.stream_id = 0x00; + HttpEncoder encoder; + + std::unique_ptr<char[]> buffer; + QuicByteCount header_length = encoder.SerializeGoAwayFrame(goaway, &buffer); + std::string data = std::string(buffer.get(), header_length); + + QuicStreamFrame frame(receive_control_stream_->id(), false, 1, data); + EXPECT_FALSE(session_.http3_goaway_received()); + + if (perspective() == Perspective::IS_SERVER) { + EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _)); + } + + receive_control_stream_->OnStreamFrame(frame); + if (perspective() == Perspective::IS_CLIENT) { + EXPECT_TRUE(session_.http3_goaway_received()); + } +} + TEST_P(QuicReceiveControlStreamTest, PushPromiseOnControlStreamShouldClose) { PushPromiseFrame push_promise; push_promise.push_id = 0x01;
diff --git a/quic/core/http/quic_send_control_stream.cc b/quic/core/http/quic_send_control_stream.cc index da7f79e..9891d66 100644 --- a/quic/core/http/quic_send_control_stream.cc +++ b/quic/core/http/quic_send_control_stream.cc
@@ -3,11 +3,15 @@ // found in the LICENSE file. #include "net/third_party/quiche/src/quic/core/http/quic_send_control_stream.h" +#include <memory> #include "net/third_party/quiche/src/quic/core/http/http_constants.h" #include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h" #include "net/third_party/quiche/src/quic/core/quic_session.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/platform/api/quic_arraysize.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" namespace quic { @@ -90,4 +94,22 @@ /*fin = */ false, nullptr); } +void QuicSendControlStream::SendGoAway(QuicStreamId stream_id) { + QuicConnection::ScopedPacketFlusher flusher(session()->connection()); + + MaybeSendSettingsFrame(); + GoAwayFrame frame; + // If the peer hasn't created any stream yet. Use stream id 0 to indicate no + // request is accepted. + if (stream_id == + QuicUtils::GetInvalidStreamId(session()->transport_version())) { + stream_id = 0; + } + frame.stream_id = stream_id; + std::unique_ptr<char[]> buffer; + QuicByteCount frame_length = encoder_.SerializeGoAwayFrame(frame, &buffer); + WriteOrBufferData(QuicStringPiece(buffer.get(), frame_length), false, + nullptr); +} + } // namespace quic
diff --git a/quic/core/http/quic_send_control_stream.h b/quic/core/http/quic_send_control_stream.h index aa8fff1..414066d 100644 --- a/quic/core/http/quic_send_control_stream.h +++ b/quic/core/http/quic_send_control_stream.h
@@ -7,6 +7,7 @@ #include "net/third_party/quiche/src/quic/core/http/http_encoder.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/platform/api/quic_export.h" namespace quic { @@ -42,6 +43,9 @@ // Send |Priority| on this stream. It must be sent after settings. void WritePriority(const PriorityFrame& priority); + // Serialize a GOAWAY frame from |stream_id| and send it on this stream. + void SendGoAway(QuicStreamId stream_id); + // The send control stream is write unidirectional, so this method should // never be called. void OnDataAvailable() override { QUIC_NOTREACHED(); }
diff --git a/quic/core/http/quic_spdy_client_session.cc b/quic/core/http/quic_spdy_client_session.cc index c6f99ff..da3336d 100644 --- a/quic/core/http/quic_spdy_client_session.cc +++ b/quic/core/http/quic_spdy_client_session.cc
@@ -53,6 +53,9 @@ QUIC_DLOG(INFO) << "Encryption not active so no outgoing stream created."; return false; } + bool goaway_received = VersionUsesHttp3(transport_version()) + ? http3_goaway_received() + : QuicSession::goaway_received(); if (!GetQuicReloadableFlag(quic_use_common_stream_check) && !VersionHasIetfQuicFrames(transport_version())) { if (GetNumOpenOutgoingStreams() >= @@ -61,14 +64,14 @@ << "Already " << GetNumOpenOutgoingStreams() << " open."; return false; } - if (goaway_received() && respect_goaway_) { + if (goaway_received && respect_goaway_) { QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. " << "Already received goaway."; return false; } return true; } - if (goaway_received() && respect_goaway_) { + if (goaway_received && respect_goaway_) { QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. " << "Already received goaway."; return false; @@ -132,7 +135,10 @@ QUIC_BUG << "ShouldCreateIncomingStream called when disconnected"; return false; } - if (goaway_received() && respect_goaway_) { + bool goaway_received = quic::VersionUsesHttp3(transport_version()) + ? http3_goaway_received() + : QuicSession::goaway_received(); + if (goaway_received && respect_goaway_) { QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. " << "Already received goaway."; return false;
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc index 1372df0..5e4c9b6 100644 --- a/quic/core/http/quic_spdy_session.cc +++ b/quic/core/http/quic_spdy_session.cc
@@ -11,6 +11,8 @@ #include "net/third_party/quiche/src/quic/core/http/http_constants.h" #include "net/third_party/quiche/src/quic/core/http/quic_headers_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_error_codes.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_bug_tracker.h" @@ -347,7 +349,9 @@ spdy_framer_visitor_(new SpdyFramerVisitor(this)), max_allowed_push_id_(0), destruction_indicator_(123456789), - debug_visitor_(nullptr) { + debug_visitor_(nullptr), + http3_goaway_received_(false), + http3_goaway_sent_(false) { h2_deframer_.set_visitor(spdy_framer_visitor_.get()); h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get()); spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get()); @@ -531,6 +535,26 @@ send_control_stream_->WritePriority(priority); } +void QuicSpdySession::OnHttp3GoAway(QuicStreamId stream_id) { + DCHECK_EQ(perspective(), Perspective::IS_CLIENT); + if (!QuicUtils::IsBidirectionalStreamId(stream_id) || + IsIncomingStream(stream_id)) { + CloseConnectionWithDetails( + QUIC_INVALID_STREAM_ID, + "GOAWAY's last stream id has to point to a request stream"); + return; + } + http3_goaway_received_ = true; +} + +void QuicSpdySession::SendHttp3GoAway() { + DCHECK_EQ(perspective(), Perspective::IS_SERVER); + DCHECK(VersionUsesHttp3(transport_version())); + http3_goaway_sent_ = true; + send_control_stream_->SendGoAway( + GetLargestPeerCreatedStreamId(/*unidirectional = */ false)); +} + void QuicSpdySession::WritePushPromise(QuicStreamId original_stream_id, QuicStreamId promised_stream_id, SpdyHeaderBlock headers) {
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h index 0eb3751..52bab28 100644 --- a/quic/core/http/quic_spdy_session.h +++ b/quic/core/http/quic_spdy_session.h
@@ -153,6 +153,13 @@ // Writes a HTTP/3 PRIORITY frame to the peer. void WriteH3Priority(const PriorityFrame& priority); + // Process received HTTP/3 GOAWAY frame. This method should only be called on + // the client side. + virtual void OnHttp3GoAway(QuicStreamId stream_id); + + // Write the GOAWAY |frame| on control stream. + void SendHttp3GoAway(); + // Write |headers| for |promised_stream_id| on |original_stream_id| in a // PUSH_PROMISE frame to peer. virtual void WritePushPromise(QuicStreamId original_stream_id, @@ -237,6 +244,10 @@ Http3DebugVisitor* debug_visitor() { return debug_visitor_; } + bool http3_goaway_received() const { return http3_goaway_received_; } + + bool http3_goaway_sent() const { return http3_goaway_sent_; } + // Log header compression ratio histogram. // |using_qpack| is true for QPACK, false for HPACK. // |is_sent| is true for sent headers, false for received ones. @@ -416,6 +427,11 @@ // Not owned by the session. Http3DebugVisitor* debug_visitor_; + + // If the endpoint has received HTTP/3 GOAWAY frame. + bool http3_goaway_received_; + // If the endpoint has sent HTTP/3 GOAWAY frame. + bool http3_goaway_sent_; }; } // namespace quic
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc index 89f2d04..c4eb3c7 100644 --- a/quic/core/http/quic_spdy_session_test.cc +++ b/quic/core/http/quic_spdy_session_test.cc
@@ -978,7 +978,7 @@ TEST_P(QuicSpdySessionTestServer, SendGoAway) { if (VersionHasIetfQuicFrames(transport_version())) { - // GoAway frames are not in version 99 + // HTTP/3 GOAWAY has different semantic and thus has its own test. return; } connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); @@ -1001,10 +1001,24 @@ EXPECT_TRUE(session_.GetOrCreateStream(kTestStreamId)); } +TEST_P(QuicSpdySessionTestServer, SendHttp3GoAway) { + if (!VersionUsesHttp3(transport_version())) { + return; + } + connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + + session_.SendHttp3GoAway(); + EXPECT_TRUE(session_.http3_goaway_sent()); + + const QuicStreamId kTestStreamId = + GetNthClientInitiatedBidirectionalStreamId(transport_version(), 0); + EXPECT_CALL(*connection_, OnStreamReset(kTestStreamId, _)).Times(0); + EXPECT_TRUE(session_.GetOrCreateStream(kTestStreamId)); +} + TEST_P(QuicSpdySessionTestServer, DoNotSendGoAwayTwice) { if (VersionHasIetfQuicFrames(transport_version())) { - // TODO(b/118808809): Enable this test for version 99 when GOAWAY is - // supported. + // HTTP/3 GOAWAY doesn't have such restriction. return; } EXPECT_CALL(*connection_, SendControlFrame(_)) @@ -1016,8 +1030,7 @@ TEST_P(QuicSpdySessionTestServer, InvalidGoAway) { if (VersionHasIetfQuicFrames(transport_version())) { - // TODO(b/118808809): Enable this test for version 99 when GOAWAY is - // supported. + // HTTP/3 GOAWAY has different semantics and thus has its own test. return; } QuicGoAwayFrame go_away(kInvalidControlFrameId, QUIC_PEER_GOING_AWAY, @@ -2589,6 +2602,20 @@ session_.OnStreamFrame(frame); } +TEST_P(QuicSpdySessionTestClient, InvalidHttp3GoAway) { + if (!VersionUsesHttp3(transport_version())) { + return; + } + EXPECT_CALL( + *connection_, + CloseConnection( + QUIC_INVALID_STREAM_ID, + "GOAWAY's last stream id has to point to a request stream", _)); + QuicStreamId stream_id = + GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 0); + session_.OnHttp3GoAway(stream_id); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc index b6e4b83..e27cc31 100644 --- a/quic/core/quic_session.cc +++ b/quic/core/quic_session.cc
@@ -1476,6 +1476,13 @@ largest_peer_created_stream_id); } +QuicStreamId QuicSession::GetLargestPeerCreatedStreamId( + bool unidirectional) const { + // This method is only used in IETF QUIC. + DCHECK(VersionHasIetfQuicFrames(transport_version())); + return v99_streamid_manager_.GetLargestPeerCreatedStreamId(unidirectional); +} + bool QuicSession::IsClosedStream(QuicStreamId id) { DCHECK_NE(QuicUtils::GetInvalidStreamId(transport_version()), id); if (IsOpenStream(id)) {
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h index 9a2f3aa..23f1031 100644 --- a/quic/core/quic_session.h +++ b/quic/core/quic_session.h
@@ -23,6 +23,7 @@ #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_stream_frame_data_producer.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h" #include "net/third_party/quiche/src/quic/core/session_notifier_interface.h" #include "net/third_party/quiche/src/quic/core/uber_quic_stream_id_manager.h" @@ -598,6 +599,10 @@ bool IsHandshakeConfirmed() const { return is_handshake_confirmed_; } + // Return the largest peer created stream id depending on directionality + // indicated by |unidirectional|. + QuicStreamId GetLargestPeerCreatedStreamId(bool unidirectional) const; + private: friend class test::QuicSessionPeer;
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc index 9eb4fed..20b591a 100644 --- a/quic/core/quic_session_test.cc +++ b/quic/core/quic_session_test.cc
@@ -1309,7 +1309,7 @@ TEST_P(QuicSessionTestServer, SendGoAway) { if (VersionHasIetfQuicFrames(transport_version())) { - // GoAway frames are not in version 99 + // In IETF QUIC, GOAWAY lives up in the HTTP layer. return; } connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); @@ -1334,8 +1334,7 @@ TEST_P(QuicSessionTestServer, DoNotSendGoAwayTwice) { if (VersionHasIetfQuicFrames(transport_version())) { - // TODO(b/118808809): Enable this test for version 99 when GOAWAY is - // supported. + // In IETF QUIC, GOAWAY lives up in the HTTP layer. return; } EXPECT_CALL(*connection_, SendControlFrame(_)) @@ -1347,8 +1346,7 @@ TEST_P(QuicSessionTestServer, InvalidGoAway) { if (VersionHasIetfQuicFrames(transport_version())) { - // TODO(b/118808809): Enable this test for version 99 when GOAWAY is - // supported. + // In IETF QUIC, GOAWAY lives up in the HTTP layer. return; } QuicGoAwayFrame go_away(kInvalidControlFrameId, QUIC_PEER_GOING_AWAY,
diff --git a/quic/core/quic_stream_id_manager.h b/quic/core/quic_stream_id_manager.h index 1ac2947..4cb9a88 100644 --- a/quic/core/quic_stream_id_manager.h +++ b/quic/core/quic_stream_id_manager.h
@@ -153,6 +153,10 @@ largest_peer_created_stream_id_ = largest_peer_created_stream_id; } + QuicStreamId largest_peer_created_stream_id() const { + return largest_peer_created_stream_id_; + } + // These are the limits for outgoing and incoming streams, // respectively. For incoming there are two limits, what has // been advertised to the peer and what is actually available.
diff --git a/quic/core/uber_quic_stream_id_manager.cc b/quic/core/uber_quic_stream_id_manager.cc index f258384..3fb5be8 100644 --- a/quic/core/uber_quic_stream_id_manager.cc +++ b/quic/core/uber_quic_stream_id_manager.cc
@@ -135,6 +135,14 @@ largest_peer_created_stream_id); } +QuicStreamId UberQuicStreamIdManager::GetLargestPeerCreatedStreamId( + bool unidirectional) const { + if (unidirectional) { + return unidirectional_stream_id_manager_.largest_peer_created_stream_id(); + } + return bidirectional_stream_id_manager_.largest_peer_created_stream_id(); +} + QuicStreamId UberQuicStreamIdManager::next_outgoing_bidirectional_stream_id() const { return bidirectional_stream_id_manager_.next_outgoing_stream_id();
diff --git a/quic/core/uber_quic_stream_id_manager.h b/quic/core/uber_quic_stream_id_manager.h index a725fdd..75fcb9d 100644 --- a/quic/core/uber_quic_stream_id_manager.h +++ b/quic/core/uber_quic_stream_id_manager.h
@@ -73,6 +73,8 @@ void SetLargestPeerCreatedStreamId( QuicStreamId largest_peer_created_stream_id); + QuicStreamId GetLargestPeerCreatedStreamId(bool unidirectional) const; + QuicStreamId next_outgoing_bidirectional_stream_id() const; QuicStreamId next_outgoing_unidirectional_stream_id() const;
diff --git a/quic/test_tools/quic_test_server.cc b/quic/test_tools/quic_test_server.cc index a893830..81b54d4 100644 --- a/quic/test_tools/quic_test_server.cc +++ b/quic/test_tools/quic_test_server.cc
@@ -230,12 +230,20 @@ quic_simple_server_backend) {} void ImmediateGoAwaySession::OnStreamFrame(const QuicStreamFrame& frame) { - SendGoAway(QUIC_PEER_GOING_AWAY, ""); + if (VersionUsesHttp3(transport_version())) { + SendHttp3GoAway(); + } else { + SendGoAway(QUIC_PEER_GOING_AWAY, ""); + } QuicSimpleServerSession::OnStreamFrame(frame); } void ImmediateGoAwaySession::OnCryptoFrame(const QuicCryptoFrame& frame) { - SendGoAway(QUIC_PEER_GOING_AWAY, ""); + // In IETF QUIC, GOAWAY lives up in HTTP/3 layer. Even if it's a immediate + // goaway session, goaway shouldn't be sent when crypto frame is received. + if (!VersionUsesHttp3(transport_version())) { + SendGoAway(QUIC_PEER_GOING_AWAY, ""); + } QuicSimpleServerSession::OnCryptoFrame(frame); }