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);
}