diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index a90cfde6ddc66..82edfba8270e5 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -211,6 +211,7 @@ envoy_cc_library( ":quic_filter_manager_connection_lib", "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", + "//source/common/http:codes_lib", "//source/common/http:header_map_lib", "//source/common/http:header_utility_lib", "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index 79e442ea8628b..d296af0bd5033 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -19,8 +19,11 @@ #include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "common/buffer/buffer_impl.h" +#include "common/http/codes.h" #include "common/http/header_map_impl.h" #include "common/http/header_utility.h" +#include "common/http/utility.h" +#include "common/common/enum_to_int.h" #include "common/common/assert.h" namespace Envoy { @@ -132,18 +135,39 @@ void EnvoyQuicClientStream::switchStreamBlockState(bool should_block) { void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { - if (rst_sent()) { + if (read_side_closed()) { return; } quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); ASSERT(headers_decompressed() && !header_list.empty()); - response_decoder_->decodeHeaders( - quicHeadersToEnvoyHeaders(header_list), - /*end_stream=*/fin); + ENVOY_STREAM_LOG(debug, "Received headers: {}.", *this, header_list.DebugString()); if (fin) { end_stream_decoded_ = true; } + std::unique_ptr headers = + quicHeadersToEnvoyHeaders(header_list); + const uint64_t status = Http::Utility::getResponseStatus(*headers); + if (Http::CodeUtility::is1xx(status)) { + if (status == enumToInt(Http::Code::SwitchingProtocols)) { + // HTTP3 doesn't support the HTTP Upgrade mechanism or 101 (Switching Protocols) status code. + Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD); + return; + } + + // These are Informational 1xx headers, not the actual response headers. + set_headers_decompressed(false); + } + + if (status == enumToInt(Http::Code::Continue) && !decoded_100_continue_) { + // This is 100 Continue, only decode it once to support Expect:100-Continue header. + decoded_100_continue_ = true; + response_decoder_->decode100ContinueHeaders(std::move(headers)); + } else if (status != enumToInt(Http::Code::Continue)) { + response_decoder_->decodeHeaders(std::move(headers), + /*end_stream=*/fin); + } + ConsumeHeaderList(); } @@ -151,6 +175,9 @@ void EnvoyQuicClientStream::OnBodyAvailable() { ASSERT(FinishedReadingHeaders()); ASSERT(read_disable_counter_ == 0); ASSERT(!in_decode_data_callstack_); + if (read_side_closed()) { + return; + } in_decode_data_callstack_ = true; Buffer::InstancePtr buffer = std::make_unique(); @@ -178,13 +205,13 @@ void EnvoyQuicClientStream::OnBodyAvailable() { // already delivered it or decodeTrailers will be called. bool skip_decoding = (buffer->length() == 0 && !fin_read_and_no_trailers) || end_stream_decoded_; if (!skip_decoding) { - response_decoder_->decodeData(*buffer, fin_read_and_no_trailers); if (fin_read_and_no_trailers) { end_stream_decoded_ = true; } + response_decoder_->decodeData(*buffer, fin_read_and_no_trailers); } - if (!sequencer()->IsClosed()) { + if (!sequencer()->IsClosed() || read_side_closed()) { in_decode_data_callstack_ = false; if (read_disable_counter_ > 0) { // If readDisable() was ever called during decodeData() and it meant to disable @@ -204,6 +231,10 @@ void EnvoyQuicClientStream::OnBodyAvailable() { void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { + if (read_side_closed()) { + return; + } + ENVOY_STREAM_LOG(debug, "Received trailers: {}.", *this, header_list.DebugString()); quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); ASSERT(trailers_decompressed()); if (session()->connection()->connected() && !rst_sent()) { @@ -215,9 +246,9 @@ void EnvoyQuicClientStream::maybeDecodeTrailers() { if (sequencer()->IsClosed() && !FinishedReadingTrailers()) { ASSERT(!received_trailers().empty()); // Only decode trailers after finishing decoding body. + end_stream_decoded_ = true; response_decoder_->decodeTrailers( spdyHeaderBlockToEnvoyHeaders(received_trailers())); - end_stream_decoded_ = true; MarkTrailersConsumed(); } } @@ -236,7 +267,9 @@ void EnvoyQuicClientStream::Reset(quic::QuicRstStreamErrorCode error) { void EnvoyQuicClientStream::OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) { quic::QuicSpdyClientStream::OnConnectionClosed(error, source); - runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); + if (!end_stream_decoded_) { + runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); + } } void EnvoyQuicClientStream::OnClose() { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h index 2702b5f8fe790..8c2b9c6517f34 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h @@ -72,6 +72,8 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, void maybeDecodeTrailers(); Http::ResponseDecoder* response_decoder_{nullptr}; + + bool decoded_100_continue_{false}; }; } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index c7d25b8d47da9..311966d60ce6a 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -150,18 +150,18 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { // TODO(danzh) Fix in QUICHE. If the stream has been reset in the call stack, // OnInitialHeadersComplete() shouldn't be called. - if (rst_sent()) { + if (read_side_closed()) { return; } quic::QuicSpdyServerStreamBase::OnInitialHeadersComplete(fin, frame_len, header_list); ASSERT(headers_decompressed() && !header_list.empty()); - - request_decoder_->decodeHeaders( - quicHeadersToEnvoyHeaders(header_list), - /*end_stream=*/fin); + ENVOY_STREAM_LOG(debug, "Received headers: {}.", *this, header_list.DebugString()); if (fin) { end_stream_decoded_ = true; } + request_decoder_->decodeHeaders( + quicHeadersToEnvoyHeaders(header_list), + /*end_stream=*/fin); ConsumeHeaderList(); } @@ -169,6 +169,9 @@ void EnvoyQuicServerStream::OnBodyAvailable() { ASSERT(FinishedReadingHeaders()); ASSERT(read_disable_counter_ == 0); ASSERT(!in_decode_data_callstack_); + if (read_side_closed()) { + return; + } in_decode_data_callstack_ = true; Buffer::InstancePtr buffer = std::make_unique(); @@ -195,13 +198,13 @@ void EnvoyQuicServerStream::OnBodyAvailable() { // already delivered it or decodeTrailers will be called. bool skip_decoding = (buffer->length() == 0 && !fin_read_and_no_trailers) || end_stream_decoded_; if (!skip_decoding) { - request_decoder_->decodeData(*buffer, fin_read_and_no_trailers); if (fin_read_and_no_trailers) { end_stream_decoded_ = true; } + request_decoder_->decodeData(*buffer, fin_read_and_no_trailers); } - if (!sequencer()->IsClosed()) { + if (!sequencer()->IsClosed() || read_side_closed()) { in_decode_data_callstack_ = false; if (read_disable_counter_ > 0) { // If readDisable() was ever called during decodeData() and it meant to disable @@ -221,6 +224,10 @@ void EnvoyQuicServerStream::OnBodyAvailable() { void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { + if (read_side_closed()) { + return; + } + ENVOY_STREAM_LOG(debug, "Received trailers: {}.", *this, received_trailers().DebugString()); quic::QuicSpdyServerStreamBase::OnTrailingHeadersComplete(fin, frame_len, header_list); ASSERT(trailers_decompressed()); if (session()->connection()->connected() && !rst_sent()) { @@ -228,13 +235,18 @@ void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len } } +void EnvoyQuicServerStream::OnHeadersTooLarge() { + ENVOY_STREAM_LOG(debug, "Headers too large.", *this); + quic::QuicSpdyServerStreamBase::OnHeadersTooLarge(); +} + void EnvoyQuicServerStream::maybeDecodeTrailers() { if (sequencer()->IsClosed() && !FinishedReadingTrailers()) { ASSERT(!received_trailers().empty()); // Only decode trailers after finishing decoding body. + end_stream_decoded_ = true; request_decoder_->decodeTrailers( spdyHeaderBlockToEnvoyHeaders(received_trailers())); - end_stream_decoded_ = true; MarkTrailersConsumed(); } } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h index acd4138db3986..b6be2edd602ad 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h @@ -66,6 +66,7 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, const quic::QuicHeaderList& header_list) override; void OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) override; + void OnHeadersTooLarge() override; private: QuicFilterManagerConnectionImpl* filterManagerConnection(); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index 01871dcfff99d..7b1df7c2e81d5 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -96,7 +96,7 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { return bodyToHttp3StreamPayload(body); } - size_t receiveResponse(const std::string& payload, bool fin) { + size_t receiveResponse(const std::string& payload, bool fin, size_t offset = 0) { EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) { EXPECT_EQ("200", headers->getStatusValue()); @@ -110,18 +110,18 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { if (quic::VersionUsesHttp3(quic_version_.transport_version)) { std::string data = absl::StrCat(spdyHeaderToHttp3StreamPayload(spdy_response_headers_), bodyToStreamPayload(payload)); - quic::QuicStreamFrame frame(stream_id_, fin, 0, data); + quic::QuicStreamFrame frame(stream_id_, fin, offset, data); quic_stream_->OnStreamFrame(frame); EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); - return data.length(); + return offset + data.length(); } quic_stream_->OnStreamHeaderList(/*fin=*/false, response_headers_.uncompressed_header_bytes(), response_headers_); - quic::QuicStreamFrame frame(stream_id_, fin, 0, payload); + quic::QuicStreamFrame frame(stream_id_, fin, offset, payload); quic_stream_->OnStreamFrame(frame); EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); - return payload.length(); + return offset + payload.length(); } protected: @@ -208,6 +208,71 @@ TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { } } +TEST_P(EnvoyQuicClientStreamTest, PostRequestAnd100Continue) { + const auto result = quic_stream_->encodeHeaders(request_headers_, false); + EXPECT_TRUE(result.ok()); + + EXPECT_CALL(stream_decoder_, decode100ContinueHeaders_(_)) + .WillOnce(Invoke([this](const Http::ResponseHeaderMapPtr& headers) { + EXPECT_EQ("100", headers->getStatusValue()); + EXPECT_EQ("0", headers->get(Http::LowerCaseString("i"))[0]->value().getStringView()); + quic_stream_->encodeData(request_body_, true); + })); + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) { + EXPECT_EQ("103", headers->getStatusValue()); + EXPECT_EQ("1", headers->get(Http::LowerCaseString("i"))[0]->value().getStringView()); + })); + size_t offset = 0; + size_t i = 0; + // Receive several 10x headers, only the first 100 Continue header should be + // delivered. + for (const std::string& status : {"100", "103", "100"}) { + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + spdy::SpdyHeaderBlock continue_header; + continue_header[":status"] = status; + continue_header["i"] = absl::StrCat("", i++); + std::string data = spdyHeaderToHttp3StreamPayload(continue_header); + quic::QuicStreamFrame frame(stream_id_, false, offset, data); + quic_stream_->OnStreamFrame(frame); + offset += data.length(); + } else { + quic::QuicHeaderList continue_header; + continue_header.OnHeaderBlockStart(); + continue_header.OnHeader(":status", status); + continue_header.OnHeader("i", absl::StrCat("", i++)); + continue_header.OnHeaderBlockEnd(0, 0); + quic_stream_->OnStreamHeaderList(/*fin=*/false, continue_header.uncompressed_header_bytes(), + continue_header); + } + } + + receiveResponse(response_body_, true, offset); +} + +TEST_P(EnvoyQuicClientStreamTest, ResetUpon101SwitchProtocol) { + const auto result = quic_stream_->encodeHeaders(request_headers_, false); + EXPECT_TRUE(result.ok()); + + EXPECT_CALL(stream_callbacks_, onResetStream(Http::StreamResetReason::LocalReset, _)); + // Receive several 10x headers, only the first 100 Continue header should be + // delivered. + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + spdy::SpdyHeaderBlock continue_header; + continue_header[":status"] = "101"; + std::string data = spdyHeaderToHttp3StreamPayload(continue_header); + quic::QuicStreamFrame frame(stream_id_, false, 0u, data); + quic_stream_->OnStreamFrame(frame); + } else { + quic::QuicHeaderList continue_header; + continue_header.OnHeaderBlockStart(); + continue_header.OnHeader(":status", "101"); + continue_header.OnHeaderBlockEnd(0, 0); + quic_stream_->OnStreamHeaderList(/*fin=*/false, continue_header.uncompressed_header_bytes(), + continue_header); + } +} + TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { if (quic::VersionUsesHttp3(quic_version_.transport_version)) { EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); @@ -226,15 +291,7 @@ TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { // Trailer should be delivered to HCM later after body arrives. quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); - std::string data = response_body_; - if (quic::VersionUsesHttp3(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::QuicByteCount data_frame_header_length = - quic::HttpEncoder::SerializeDataFrameHeader(response_body_.length(), &data_buffer); - absl::string_view data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, response_body_); - } - quic::QuicStreamFrame frame(stream_id_, false, 0, data); + quic::QuicStreamFrame frame(stream_id_, false, 0, response_body_); EXPECT_CALL(stream_decoder_, decodeData(_, _)) .Times(testing::AtMost(2)) .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { @@ -403,5 +460,116 @@ TEST_P(EnvoyQuicClientStreamTest, ReceiveResetStream) { EXPECT_TRUE(quic_stream_->rst_received()); } +TEST_P(EnvoyQuicClientStreamTest, CloseConnectionDuringDecodingHeader) { + const auto result = quic_stream_->encodeHeaders(request_headers_, false); + EXPECT_TRUE(result.ok()); + quic_stream_->encodeData(request_body_, true); + + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/!quic::VersionUsesHttp3( + quic_version_.transport_version))) + .WillOnce(Invoke([this](const Http::ResponseHeaderMapPtr&, bool) { + quic_connection_->CloseConnection( + quic::QUIC_NO_ERROR, "Closed in decodeHeaders", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + })); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + // onResetStream() callback should be triggered because end_stream is + // not decoded with header. + EXPECT_CALL(stream_callbacks_, + onResetStream(Http::StreamResetReason::ConnectionTermination, _)); + std::string data = spdyHeaderToHttp3StreamPayload(spdy_response_headers_); + quic::QuicStreamFrame frame(stream_id_, true, 0, data); + quic_stream_->OnStreamFrame(frame); + } else { + // onResetStream() callback shouldn't be triggered because end_stream is + // already decoded. + quic_stream_->OnStreamHeaderList(/*fin=*/true, response_headers_.uncompressed_header_bytes(), + response_headers_); + } +} + +TEST_P(EnvoyQuicClientStreamTest, CloseConnectionDuringDecodingDataWithEndStream) { + const auto result = quic_stream_->encodeHeaders(request_headers_, false); + EXPECT_TRUE(result.ok()); + quic_stream_->encodeData(request_body_, true); + + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)); + EXPECT_CALL(stream_decoder_, decodeData(_, true)) + .WillOnce(Invoke([this](Buffer::Instance&, bool) { + // onResetStream() callback shouldn't be triggered. + quic_connection_->CloseConnection( + quic::QUIC_NO_ERROR, "Closed in decodeDdata", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + })); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string data = absl::StrCat(spdyHeaderToHttp3StreamPayload(spdy_response_headers_), + bodyToStreamPayload(response_body_)); + quic::QuicStreamFrame frame(stream_id_, true, 0, data); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/false, response_headers_.uncompressed_header_bytes(), + response_headers_); + + quic::QuicStreamFrame frame(stream_id_, true, 0, response_body_); + quic_stream_->OnStreamFrame(frame); + } +} + +TEST_P(EnvoyQuicClientStreamTest, CloseConnectionDuringDecodingDataWithTrailer) { + const auto result = quic_stream_->encodeHeaders(request_headers_, false); + EXPECT_TRUE(result.ok()); + quic_stream_->encodeData(request_body_, true); + + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)); + EXPECT_CALL(stream_decoder_, decodeData(_, false)) + .WillOnce(Invoke([this](Buffer::Instance&, bool) { + // onResetStream() and decodeTrailers() shouldn't be triggered. + quic_connection_->CloseConnection( + quic::QUIC_NO_ERROR, "Closed in decodeDdata", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + EXPECT_TRUE(quic_stream_->read_side_closed()); + })); + EXPECT_CALL(stream_callbacks_, onResetStream(Http::StreamResetReason::ConnectionTermination, _)); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string data = absl::StrCat(spdyHeaderToHttp3StreamPayload(spdy_response_headers_), + bodyToStreamPayload(response_body_), + spdyHeaderToHttp3StreamPayload(spdy_trailers_)); + quic::QuicStreamFrame frame(stream_id_, true, 0, data); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/false, response_headers_.uncompressed_header_bytes(), + response_headers_); + + quic::QuicStreamFrame frame(stream_id_, /*fin=*/false, 0, response_body_); + quic_stream_->OnStreamFrame(frame); + quic_stream_->OnStreamHeaderList( + /*fin=*/!quic::VersionUsesHttp3(quic_version_.transport_version), + trailers_.uncompressed_header_bytes(), trailers_); + } +} + +TEST_P(EnvoyQuicClientStreamTest, CloseConnectionDuringDecodingTrailer) { + const auto result = quic_stream_->encodeHeaders(request_headers_, true); + EXPECT_TRUE(result.ok()); + + size_t offset = receiveResponse(response_body_, false); + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) + .WillOnce(Invoke([this](const Http::ResponseTrailerMapPtr&) { + // onResetStream() callback shouldn't be triggered. + quic_connection_->CloseConnection( + quic::QUIC_NO_ERROR, "Closed in decodeTrailers", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + })); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_trailers_); + quic::QuicStreamFrame frame(stream_id_, true, offset, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList( + /*fin=*/!quic::VersionUsesHttp3(quic_version_.transport_version), + trailers_.uncompressed_header_bytes(), trailers_); + } +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 3e37ba3e1d39d..15e986255dbbe 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -502,5 +502,68 @@ TEST_P(EnvoyQuicServerStreamTest, HeadersContributeToWatermarkIquic) { EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } +TEST_P(EnvoyQuicServerStreamTest, RequestHeaderTooLarge) { + // Bump stream flow control window to allow request headers larger than 16K. + quic::QuicWindowUpdateFrame window_update1(quic::kInvalidControlFrameId, quic_stream_->id(), + 32 * 1024); + quic_stream_->OnWindowUpdateFrame(window_update1); + + EXPECT_CALL(stream_callbacks_, onResetStream(Http::StreamResetReason::LocalReset, _)); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + spdy::SpdyHeaderBlock spdy_headers; + spdy_headers[":authority"] = host_; + spdy_headers[":method"] = "POST"; + spdy_headers[":path"] = "/"; + // This header exceeds max header size limit and should cause stream reset. + spdy_headers["long_header"] = std::string(16 * 1024 + 1, 'a'); + std::string payload = absl::StrCat(spdyHeaderToHttp3StreamPayload(spdy_headers), + bodyToStreamPayload(request_body_)); + quic::QuicStreamFrame frame(stream_id_, false, 0, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic::QuicHeaderList request_headers; + request_headers.set_max_header_list_size(16 * 1024); + request_headers.OnHeaderBlockStart(); + request_headers.OnHeader(":authority", host_); + request_headers.OnHeader(":method", "POST"); + request_headers.OnHeader(":path", "/"); + request_headers.OnHeader("long_header", std::string(16 * 1024 + 1, 'a')); + request_headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, + /*compressed_header_bytes=*/0); + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers.uncompressed_header_bytes(), + request_headers); + } + EXPECT_TRUE(quic_stream_->rst_sent()); +} + +TEST_P(EnvoyQuicServerStreamTest, RequestTrailerTooLarge) { + // Bump stream flow control window to allow request headers larger than 16K. + quic::QuicWindowUpdateFrame window_update1(quic::kInvalidControlFrameId, quic_stream_->id(), + 20 * 1024); + size_t offset = receiveRequest(request_body_, false, request_body_.size() * 2); + + quic_stream_->OnWindowUpdateFrame(window_update1); + + EXPECT_CALL(stream_callbacks_, onResetStream(Http::StreamResetReason::LocalReset, _)); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + spdy::SpdyHeaderBlock spdy_trailers; + // This header exceeds max header size limit and should cause stream reset. + spdy_trailers["long_header"] = std::string(16 * 1024 + 1, 'a'); + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_trailers); + quic::QuicStreamFrame frame(stream_id_, false, offset, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic::QuicHeaderList spdy_trailers; + spdy_trailers.set_max_header_list_size(16 * 1024); + spdy_trailers.OnHeaderBlockStart(); + spdy_trailers.OnHeader("long_header", std::string(16 * 1024 + 1, 'a')); + spdy_trailers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, + /*compressed_header_bytes=*/0); + quic_stream_->OnStreamHeaderList(/*fin=*/true, spdy_trailers.uncompressed_header_bytes(), + spdy_trailers); + } + EXPECT_TRUE(quic_stream_->rst_sent()); +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 4c6309620f3da..e84e01c170f69 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -537,5 +537,39 @@ TEST_P(QuicHttpIntegrationTest, RequestResponseWithTrailers) { /*response_trailers_present=*/true); } +// Multiple 1xx before the request completes. +TEST_P(QuicHttpIntegrationTest, EnvoyProxyingEarlyMultiple1xx) { + testEnvoyProxying1xx(/*continue_before_upstream_complete=*/true, /*with_encoder_filter=*/false, + /*with_multiple_1xx_headers=*/true); +} + +// HTTP3 doesn't support 101 SwitchProtocol response code, the client should +// reset the request. +TEST_P(QuicHttpIntegrationTest, Reset101SwitchProtocolResponse) { + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.set_proxy_100_continue(true); }); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto encoder_decoder = + codec_client_->startRequest(Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/dynamo/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"expect", "100-continue"}}); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + + // Wait for the request headers to be received upstream. + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "101"}}, false); + response->waitForReset(); + codec_client_->close(); + EXPECT_FALSE(response->complete()); +} + } // namespace Quic } // namespace Envoy