Skip to content

Commit

Permalink
quic: finer reset error code mapping (#35053)
Browse files Browse the repository at this point in the history
Commit Message: change the mapping between
`quic::QuicRstStreamErrorCode` and `StreamResetReason` to be more
specific.

Currently all Envoy reset() will error will by default mapped to
`quic::QUIC_BAD_APPLICATION_PAYLOAD`. This change will enumerate through
all enum values of StreamResetReason to specifically map them to a quic
reset error code. This can enforce developers to manually pick a proper
quic reset error for each newly added StreamResetReason values.

This change also change the mapping from QUICHE level stream reset error
`quic::QuicRstStreamErrorCode` to `StreamResetReason` to a finer
granular.

Risk Level: low, only affect error reporting
Testing: existing test passed
Docs Changes: N/A
Release Notes: N/A
Platform Specific Features: N/A

---------

Signed-off-by: Dan Zhang <danzh@google.com>
Co-authored-by: Dan Zhang <danzh@google.com>
  • Loading branch information
danzh2010 and danzh1989 committed Jul 12, 2024
1 parent 31624bd commit 5b33739
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 19 deletions.
2 changes: 1 addition & 1 deletion mobile/test/common/integration/client_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,7 @@ TEST_P(ClientIntegrationTest, CancelDuringResponse) {
ASSERT_TRUE(
waitForCounterGe("http3.upstream.tx.quic_connection_close_error_code_QUIC_NO_ERROR", 1));
ASSERT_TRUE(waitForCounterGe(
"http3.upstream.tx.quic_reset_stream_error_code_QUIC_STREAM_CANCELLED", 1));
"http3.upstream.tx.quic_reset_stream_error_code_QUIC_STREAM_REQUEST_REJECTED", 1));
}
}

Expand Down
40 changes: 34 additions & 6 deletions source/common/quic/envoy_quic_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,24 @@ quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetRea
case Http::StreamResetReason::ConnectionTimeout:
case Http::StreamResetReason::ConnectionTermination:
return quic::QUIC_STREAM_CONNECTION_ERROR;
case Http::StreamResetReason::ConnectError:
return quic::QUIC_STREAM_CONNECT_ERROR;
case Http::StreamResetReason::LocalReset:
return quic::QUIC_STREAM_REQUEST_REJECTED;
case Http::StreamResetReason::OverloadManager:
return quic::QUIC_STREAM_CANCELLED;
default:
return quic::QUIC_BAD_APPLICATION_PAYLOAD;
case Http::StreamResetReason::ProtocolError:
return quic::QUIC_STREAM_GENERAL_PROTOCOL_ERROR;
case Http::StreamResetReason::Overflow:
IS_ENVOY_BUG("Resource overflow shouldn't be propergated to QUIC network stack");
break;
case Http::StreamResetReason::RemoteRefusedStreamReset:
case Http::StreamResetReason::RemoteReset:
IS_ENVOY_BUG("Remote reset shouldn't be initiated by self.");
}

ENVOY_LOG_MISC(error, absl::StrCat("Unknown reset reason: ", reason));
return quic::QUIC_STREAM_UNKNOWN_APPLICATION_ERROR_CODE;
}

Http::StreamResetReason quicRstErrorToEnvoyLocalResetReason(quic::QuicRstStreamErrorCode rst_err) {
Expand All @@ -90,10 +102,15 @@ Http::StreamResetReason quicRstErrorToEnvoyLocalResetReason(quic::QuicRstStreamE
return Http::StreamResetReason::LocalRefusedStreamReset;
case quic::QUIC_STREAM_CONNECTION_ERROR:
return Http::StreamResetReason::LocalConnectionFailure;
case quic::QUIC_BAD_APPLICATION_PAYLOAD:
return Http::StreamResetReason::ProtocolError;
default:
case quic::QUIC_STREAM_NO_ERROR:
case quic::QUIC_STREAM_EXCESSIVE_LOAD:
case quic::QUIC_HEADERS_TOO_LARGE:
case quic::QUIC_STREAM_REQUEST_REJECTED:
return Http::StreamResetReason::LocalReset;
case quic::QUIC_STREAM_CANCELLED:
return Http::StreamResetReason::OverloadManager;
default:
return Http::StreamResetReason::ProtocolError;
}
}

Expand All @@ -102,9 +119,20 @@ Http::StreamResetReason quicRstErrorToEnvoyRemoteResetReason(quic::QuicRstStream
case quic::QUIC_REFUSED_STREAM:
return Http::StreamResetReason::RemoteRefusedStreamReset;
case quic::QUIC_STREAM_CONNECTION_ERROR:
return Http::StreamResetReason::ConnectionTermination;
case quic::QUIC_STREAM_CONNECT_ERROR:
return Http::StreamResetReason::ConnectError;
default:
case quic::QUIC_STREAM_NO_ERROR:
case quic::QUIC_STREAM_REQUEST_REJECTED:
case quic::QUIC_STREAM_UNKNOWN_APPLICATION_ERROR_CODE:
case quic::QUIC_STREAM_EXCESSIVE_LOAD:
case quic::QUIC_HEADERS_TOO_LARGE:
return Http::StreamResetReason::RemoteReset;
case quic::QUIC_STREAM_CANCELLED:
return Http::StreamResetReason::OverloadManager;
case quic::QUIC_STREAM_GENERAL_PROTOCOL_ERROR:
default:
return Http::StreamResetReason::ProtocolError;
}
}

Expand Down
4 changes: 2 additions & 2 deletions test/common/quic/envoy_quic_client_session_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ TEST_P(EnvoyQuicClientSessionTest, OnResetFrame) {
quic::QuicStreamId stream_id = stream.id();
quic::QuicRstStreamFrame rst1(/*control_frame_id=*/1u, stream_id,
quic::QUIC_ERROR_PROCESSING_STREAM, /*bytes_written=*/0u);
EXPECT_CALL(stream_callbacks, onResetStream(Http::StreamResetReason::RemoteReset, _));
EXPECT_CALL(stream_callbacks, onResetStream(Http::StreamResetReason::ProtocolError, _));
envoy_quic_session_->OnRstStream(rst1);

EXPECT_EQ(
Expand All @@ -294,7 +294,7 @@ TEST_P(EnvoyQuicClientSessionTest, SendResetFrame) {

// IETF bi-directional stream.
quic::QuicStreamId stream_id = stream.id();
EXPECT_CALL(stream_callbacks, onResetStream(Http::StreamResetReason::LocalReset, _));
EXPECT_CALL(stream_callbacks, onResetStream(Http::StreamResetReason::ProtocolError, _));
EXPECT_CALL(*quic_connection_, SendControlFrame(_));
envoy_quic_session_->ResetStream(stream_id, quic::QUIC_ERROR_PROCESSING_STREAM);

Expand Down
18 changes: 18 additions & 0 deletions test/common/quic/envoy_quic_utils_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "test/mocks/api/mocks.h"
#include "test/test_common/threadsafe_singleton_injector.h"
#include "test/test_common/utility.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"
Expand Down Expand Up @@ -264,5 +265,22 @@ TEST(EnvoyQuicUtilsTest, HeaderMapMaxSizeLimit) {
EXPECT_EQ(response_trailer->maxHeadersKb(), 60);
}

TEST(EnvoyQuicUtilsTest, EnvoyResetReasonToQuicResetErrorCodeImpossibleCases) {
EXPECT_ENVOY_BUG(envoyResetReasonToQuicRstError(Http::StreamResetReason::Overflow),
"Resource overflow ");
EXPECT_ENVOY_BUG(
envoyResetReasonToQuicRstError(Http::StreamResetReason::RemoteRefusedStreamReset),
"Remote reset ");
}

TEST(EnvoyQuicUtilsTest, QuicResetErrorToEnvoyResetReason) {
EXPECT_EQ(quicRstErrorToEnvoyLocalResetReason(quic::QUIC_STREAM_NO_ERROR),
Http::StreamResetReason::LocalReset);
EXPECT_EQ(quicRstErrorToEnvoyRemoteResetReason(quic::QUIC_STREAM_CONNECTION_ERROR),
Http::StreamResetReason::ConnectionTermination);
EXPECT_EQ(quicRstErrorToEnvoyRemoteResetReason(quic::QUIC_STREAM_CONNECT_ERROR),
Http::StreamResetReason::ConnectError);
}

} // namespace Quic
} // namespace Envoy
5 changes: 4 additions & 1 deletion test/integration/buffer_accounting_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,10 @@ TEST_P(ProtocolsBufferWatermarksTest, ResettingStreamUnregistersAccount) {
ASSERT_TRUE(codec_client_->waitForDisconnect(std::chrono::milliseconds(10000)));
} else {
ASSERT_TRUE(response1->waitForReset());
EXPECT_EQ(response1->resetReason(), Http::StreamResetReason::RemoteReset);
EXPECT_EQ(response1->resetReason(),
(std::get<0>(GetParam()).downstream_protocol == Http::CodecType::HTTP2
? Http::StreamResetReason::RemoteReset
: Http::StreamResetReason::OverloadManager));
}

// Wait for the upstream request to receive the reset to avoid a race when
Expand Down
2 changes: 1 addition & 1 deletion test/integration/multiplexed_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2875,7 +2875,7 @@ TEST_P(MultiplexedIntegrationTest, InconsistentContentLength) {
ASSERT_TRUE(response->waitForReset());
// http3.inconsistent_content_length.
if (downstreamProtocol() == Http::CodecType::HTTP3) {
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
EXPECT_EQ(Http::StreamResetReason::ProtocolError, response->resetReason());
EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("inconsistent_content_length"));
} else if (GetParam().http2_implementation == Http2Impl::Oghttp2) {
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
Expand Down
38 changes: 30 additions & 8 deletions test/integration/protocol_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1826,7 +1826,10 @@ TEST_P(DownstreamProtocolIntegrationTest, HeadersWithUnderscoresCauseRequestReje
ASSERT_TRUE(response->waitForReset());
codec_client_->close();
ASSERT_TRUE(response->reset());
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
EXPECT_EQ((downstream_protocol_ == Http::CodecType::HTTP3
? Http::StreamResetReason::ProtocolError
: Http::StreamResetReason::RemoteReset),
response->resetReason());
}
EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("unexpected_underscore"));
}
Expand Down Expand Up @@ -1864,7 +1867,10 @@ TEST_P(DownstreamProtocolIntegrationTest, TrailerWithUnderscoresCauseRequestReje
ASSERT_TRUE(response->waitForReset());
codec_client_->close();
ASSERT_TRUE(response->reset());
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
EXPECT_EQ((downstream_protocol_ == Http::CodecType::HTTP3
? Http::StreamResetReason::ProtocolError
: Http::StreamResetReason::RemoteReset),
response->resetReason());
}
EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("unexpected_underscore"));
}
Expand Down Expand Up @@ -2202,7 +2208,10 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidContentLengthAllowed) {
EXPECT_EQ("400", response->headers().getStatusValue());
} else {
ASSERT_TRUE(response->reset());
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
EXPECT_EQ((downstream_protocol_ == Http::CodecType::HTTP3
? Http::StreamResetReason::ProtocolError
: Http::StreamResetReason::RemoteReset),
response->resetReason());
}
}

Expand Down Expand Up @@ -2255,7 +2264,10 @@ TEST_P(DownstreamProtocolIntegrationTest, MultipleContentLengthsAllowed) {
EXPECT_EQ("400", response->headers().getStatusValue());
} else {
ASSERT_TRUE(response->reset());
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
EXPECT_EQ((downstream_protocol_ == Http::CodecType::HTTP3
? Http::StreamResetReason::ProtocolError
: Http::StreamResetReason::RemoteReset),
response->resetReason());
}
}

Expand Down Expand Up @@ -4384,7 +4396,10 @@ TEST_P(DownstreamProtocolIntegrationTest, ContentLengthSmallerThanPayload) {
// Inconsistency in content-length header and the actually body length should be treated as a
// stream error.
ASSERT_TRUE(response->waitForReset());
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
EXPECT_EQ((downstreamProtocol() == Http::CodecType::HTTP3
? Http::StreamResetReason::ProtocolError
: Http::StreamResetReason::RemoteReset),
response->resetReason());
}
}

Expand Down Expand Up @@ -4413,7 +4428,9 @@ TEST_P(DownstreamProtocolIntegrationTest, ContentLengthLargerThanPayload) {
// Inconsistency in content-length header and the actually body length should be treated as a
// stream error.
ASSERT_TRUE(response->waitForReset());
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
EXPECT_EQ((downstreamProtocol() == Http::CodecType::HTTP3 ? Http::StreamResetReason::ProtocolError
: Http::StreamResetReason::RemoteReset),
response->resetReason());
}

class NoUdpGso : public Api::OsSysCallsImpl {
Expand Down Expand Up @@ -4671,7 +4688,10 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidRequestHeaderNameStreamError) {
test_server_->waitForCounterGe("http.config_test.downstream_rq_4xx", 1);
} else {
// H/2 codec does not send 400 on protocol errors
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
EXPECT_EQ((downstream_protocol_ == Http::CodecType::HTTP3
? Http::StreamResetReason::ProtocolError
: Http::StreamResetReason::RemoteReset),
response->resetReason());
}
}

Expand Down Expand Up @@ -4943,7 +4963,9 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidTrailerStreamError) {
ASSERT_TRUE(response->waitForReset());
codec_client_->close();
ASSERT_TRUE(response->reset());
EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());
EXPECT_EQ((downstreamProtocol() == Http::CodecType::HTTP3 ? Http::StreamResetReason::ProtocolError
: Http::StreamResetReason::RemoteReset),
response->resetReason());
if (!use_universal_header_validator_) {
// TODO(#24620) UHV does not include the DPE prefix in the downstream protocol error reasons
if (downstreamProtocol() != Http::CodecType::HTTP3) {
Expand Down

0 comments on commit 5b33739

Please sign in to comment.