-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
http: adding response code details for downstream HTTP/1.1 codec errors #9286
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -367,6 +367,10 @@ void ConnectionManagerImpl::resetAllStreams( | |
stream.onResetStream(StreamResetReason::ConnectionTermination, absl::string_view()); | ||
if (response_flag.has_value()) { | ||
stream.stream_info_.setResponseFlag(response_flag.value()); | ||
if (response_flag.value() == StreamInfo::ResponseFlag::DownstreamProtocolError) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. micro optimization: you already checked that is |
||
stream.stream_info_.setResponseCodeDetails( | ||
stream.response_encoder_->getStream().responseDetails()); | ||
} | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,17 @@ namespace Http { | |
namespace Http1 { | ||
namespace { | ||
|
||
struct Http1ResponseCodeDetailValues { | ||
const std::string TooManyHeaders = "http1.too_many_headers"; | ||
const std::string HeadersTooLarge = "http1.headers_too_large"; | ||
const std::string HttpCodecError = "http1.codec_error"; | ||
const std::string InvalidCharacters = "http1.invalid_characters"; | ||
const std::string ConnectionHeaderSanitization = "http1.connection_header_bad"; | ||
const std::string InvalidUrl = "http1.invalid_url"; | ||
}; | ||
|
||
using Http1ResponseCodeDetails = ConstSingleton<Http1ResponseCodeDetailValues>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These can all be Also, honestly since there's no initialization order fiasco here involved. |
||
|
||
const StringUtil::CaseUnorderedSet& caseUnorderdSetContainingUpgradeAndHttp2Settings() { | ||
CONSTRUCT_ON_FIRST_USE(StringUtil::CaseUnorderedSet, | ||
Http::Headers::get().ConnectionValues.Upgrade, | ||
|
@@ -381,7 +392,7 @@ void ConnectionImpl::completeLastHeader() { | |
// Check if the number of headers exceeds the limit. | ||
if (current_header_map_->size() > max_headers_count_) { | ||
error_code_ = Http::Code::RequestHeaderFieldsTooLarge; | ||
sendProtocolError(); | ||
sendProtocolError(Http1ResponseCodeDetails::get().TooManyHeaders); | ||
throw CodecProtocolException("headers size exceeds limit"); | ||
} | ||
|
||
|
@@ -442,7 +453,7 @@ void ConnectionImpl::dispatch(Buffer::Instance& data) { | |
size_t ConnectionImpl::dispatchSlice(const char* slice, size_t len) { | ||
ssize_t rc = http_parser_execute(&parser_, &settings_, slice, len); | ||
if (HTTP_PARSER_ERRNO(&parser_) != HPE_OK && HTTP_PARSER_ERRNO(&parser_) != HPE_PAUSED) { | ||
sendProtocolError(); | ||
sendProtocolError(Http1ResponseCodeDetails::get().HttpCodecError); | ||
throw CodecProtocolException("http/1.1 protocol error: " + | ||
std::string(http_errno_name(HTTP_PARSER_ERRNO(&parser_)))); | ||
} | ||
|
@@ -475,7 +486,7 @@ void ConnectionImpl::onHeaderValue(const char* data, size_t length) { | |
if (!Http::HeaderUtility::headerIsValid(header_value)) { | ||
ENVOY_CONN_LOG(debug, "invalid header value: {}", connection_, header_value); | ||
error_code_ = Http::Code::BadRequest; | ||
sendProtocolError(); | ||
sendProtocolError(Http1ResponseCodeDetails::get().InvalidCharacters); | ||
throw CodecProtocolException("http/1.1 protocol error: header value contains invalid chars"); | ||
} | ||
} else if (header_value.find('\0') != absl::string_view::npos) { | ||
|
@@ -496,7 +507,7 @@ void ConnectionImpl::onHeaderValue(const char* data, size_t length) { | |
if (total > (max_headers_kb_ * 1024)) { | ||
|
||
error_code_ = Http::Code::RequestHeaderFieldsTooLarge; | ||
sendProtocolError(); | ||
sendProtocolError(Http1ResponseCodeDetails::get().HeadersTooLarge); | ||
throw CodecProtocolException("headers size exceeds limit"); | ||
} | ||
} | ||
|
@@ -543,7 +554,7 @@ int ConnectionImpl::onHeadersCompleteBase() { | |
ENVOY_CONN_LOG(debug, "Invalid nominated headers in Connection: {}", connection_, | ||
header_value); | ||
error_code_ = Http::Code::BadRequest; | ||
sendProtocolError(); | ||
sendProtocolError(Http1ResponseCodeDetails::get().ConnectionHeaderSanitization); | ||
} | ||
} | ||
|
||
|
@@ -631,7 +642,7 @@ void ServerConnectionImpl::handlePath(HeaderMapImpl& headers, unsigned int metho | |
|
||
Utility::Url absolute_url; | ||
if (!absolute_url.initialize(active_request_->request_url_.getStringView())) { | ||
sendProtocolError(); | ||
sendProtocolError(Http1ResponseCodeDetails::get().InvalidUrl); | ||
throw CodecProtocolException("http/1.1 protocol error: invalid url in request line"); | ||
} | ||
// RFC7230#5.7 | ||
|
@@ -738,7 +749,10 @@ void ServerConnectionImpl::onResetStream(StreamResetReason reason) { | |
active_request_.reset(); | ||
} | ||
|
||
void ServerConnectionImpl::sendProtocolError() { | ||
void ServerConnectionImpl::sendProtocolError(absl::string_view details) { | ||
if (active_request_) { | ||
active_request_->response_encoder_.setDetails(details); | ||
} | ||
// We do this here because we may get a protocol error before we have a logical stream. Higher | ||
// layers can only operate on streams, so there is no coherent way to allow them to send an error | ||
// "out of band." On one hand this is kind of a hack but on the other hand it normalizes HTTP/1.1 | ||
|
@@ -875,6 +889,12 @@ void ClientConnectionImpl::onResetStream(StreamResetReason reason) { | |
} | ||
} | ||
|
||
void ClientConnectionImpl::sendProtocolError(absl::string_view details) { | ||
if (request_encoder_) { | ||
request_encoder_->setDetails(details); | ||
} | ||
} | ||
|
||
void ClientConnectionImpl::onAboveHighWatermark() { | ||
// This should never happen without an active stream/request. | ||
ASSERT(!pending_responses_.empty()); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1349,6 +1349,7 @@ TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { | |
// the 60th 1kb header should induce overflow | ||
buffer = Buffer::OwnedImpl(fmt::format("big: {}\r\n", long_string)); | ||
EXPECT_THROW_WITH_MESSAGE(codec_->dispatch(buffer), EnvoyException, "headers size exceeds limit"); | ||
EXPECT_EQ("http1.headers_too_large", response_encoder->getStream().responseDetails()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we get tests that cover each of the new details? |
||
} | ||
|
||
// Tests that the 101th request header causes overflow with the default max number of request | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning this error this way, WDYT about just throwing this out with the
CodecProtocolException
? I think this would lead to clearer logic since in the HCM it can just be passed into the code that resets all of the streams?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I started with that, but it didn't work the way I wanted to, because if one H2 stream has bad headers, I only wanted that particular stream to be tagged with bad headers - the others would maybe eventually get flagged with "you shared a connection with a bad stream" but shouldn't be tagged with the error that wasn't theirs. Only the codec knows which stream causes the problem so it has to be on a per-stream basis to be tagged correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK that makes sense. Can you leave a comment trail about that thought process?