Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Bug Fixes

* ext_authz: fix the ext_authz http filter to correctly set response flags to ``UAEX`` when a connection is denied.
* ext_authz: fix the ext_authz network filter to correctly set response flag and code details to ``UAEX`` when a connection is denied.
* hcm: stop processing the response if encoding it has caused downstream reset. The fix is guarded by ``envoy.reloadable_features.handle_stream_reset_during_hcm_encoding``.
* listener: fixed issue where more than one listener could listen on the same port if using reuse port, thus randomly accepting connections on different listeners. This configuration is now rejected.
* thrift_proxy: do not close downstream connections when an upstream connection overflow happens.
* thrift_proxy: fix the thrift_proxy connection manager to correctly report success/error response metrics when performing :ref:`payload passthrough <envoy_v3_api_field_extensions.filters.network.thrift_proxy.v3.ThriftProxy.payload_passthrough>`.
Expand Down
4 changes: 4 additions & 0 deletions source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,10 @@ void ConnectionManagerImpl::ActiveStream::onResetStream(StreamResetReason reset_
filter_manager_.streamInfo().setResponseCodeDetails(
StreamInfo::ResponseCodeDetails::get().Overload);
}
if (Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.handle_stream_reset_during_hcm_encoding")) {
filter_manager_.onDownstreamReset();
}

connection_manager_.doDeferredStreamDestroy(*this);
}
Expand Down
16 changes: 15 additions & 1 deletion source/common/http/filter_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1013,11 +1013,16 @@ void FilterManager::sendDirectLocalReply(
state_.non_100_response_headers_encoded_ = true;
filter_manager_callbacks_.encodeHeaders(*filter_manager_callbacks_.responseHeaders(),
end_stream);

if (state_.saw_downstream_reset_) {
return;
}
maybeEndEncode(end_stream);
},
[&](Buffer::Instance& data, bool end_stream) -> void {
filter_manager_callbacks_.encodeData(data, end_stream);
if (state_.saw_downstream_reset_) {
return;
}
maybeEndEncode(end_stream);
}},
Utility::LocalReplyData{state_.is_grpc_request_, code, body, grpc_status, is_head_request});
Expand Down Expand Up @@ -1151,6 +1156,9 @@ void FilterManager::encodeHeaders(ActiveStreamEncoderFilter* filter, ResponseHea
const bool modified_end_stream = (end_stream && continue_data_entry == encoder_filters_.end());
state_.non_100_response_headers_encoded_ = true;
filter_manager_callbacks_.encodeHeaders(headers, modified_end_stream);
if (state_.saw_downstream_reset_) {
return;
}
maybeEndEncode(modified_end_stream);

if (!modified_end_stream) {
Expand Down Expand Up @@ -1290,6 +1298,9 @@ void FilterManager::encodeData(ActiveStreamEncoderFilter* filter, Buffer::Instan

const bool modified_end_stream = end_stream && trailers_added_entry == encoder_filters_.end();
filter_manager_callbacks_.encodeData(data, modified_end_stream);
if (state_.saw_downstream_reset_) {
return;
}
maybeEndEncode(modified_end_stream);

// If trailers were adding during encodeData we need to trigger decodeTrailers in order
Expand Down Expand Up @@ -1332,6 +1343,9 @@ void FilterManager::encodeTrailers(ActiveStreamEncoderFilter* filter,
}

filter_manager_callbacks_.encodeTrailers(trailers);
if (state_.saw_downstream_reset_) {
return;
}
maybeEndEncode(true);
}

Expand Down
6 changes: 5 additions & 1 deletion source/common/http/filter_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,8 @@ class FilterManager : public ScopeTrackedObject,

void contextOnContinue(ScopeTrackedObjectStack& tracked_object_stack);

void onDownstreamReset() { state_.saw_downstream_reset_ = true; }

private:
// Indicates which filter to start the iteration with.
enum class FilterIterationStartState { AlwaysStartFromNext, CanStartFromCurrent };
Expand Down Expand Up @@ -1065,7 +1067,8 @@ class FilterManager : public ScopeTrackedObject,
: remote_complete_(false), local_complete_(false), has_1xx_headers_(false),
created_filter_chain_(false), is_head_request_(false), is_grpc_request_(false),
non_100_response_headers_encoded_(false), under_on_local_reply_(false),
decoder_filter_chain_aborted_(false), encoder_filter_chain_aborted_(false) {}
decoder_filter_chain_aborted_(false), encoder_filter_chain_aborted_(false),
saw_downstream_reset_(false) {}

uint32_t filter_call_state_{0};

Expand All @@ -1088,6 +1091,7 @@ class FilterManager : public ScopeTrackedObject,
// True when the filter chain iteration was aborted with local reply.
bool decoder_filter_chain_aborted_ : 1;
bool encoder_filter_chain_aborted_ : 1;
bool saw_downstream_reset_ : 1;

// The following 3 members are booleans rather than part of the space-saving bitfield as they
// are passed as arguments to functions expecting bools. Extend State using the bitfield
Expand Down
1 change: 1 addition & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ constexpr const char* runtime_features[] = {
"envoy.reloadable_features.disable_tls_inspector_injection",
"envoy.reloadable_features.fix_added_trailers",
"envoy.reloadable_features.grpc_bridge_stats_disabled",
"envoy.reloadable_features.handle_stream_reset_during_hcm_encoding",
"envoy.reloadable_features.hash_multiple_header_values",
"envoy.reloadable_features.health_check.graceful_goaway_handling",
"envoy.reloadable_features.http2_consume_stream_refused_errors",
Expand Down
122 changes: 122 additions & 0 deletions test/common/http/conn_manager_impl_test_2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2882,5 +2882,127 @@ TEST_F(HttpConnectionManagerImplTest, RequestRejectedViaIPDetection) {
EXPECT_EQ(1U, stats_.named_.downstream_rq_rejected_via_ip_detection_.value());
}

TEST_F(HttpConnectionManagerImplTest, DisconnectDuringEncodeHeader) {
setup(false, "envoy-server-test");
setupFilterChain(1, 0);

EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true))
.WillOnce(Return(FilterHeadersStatus::StopIteration));
startRequest(/*end_stream=*/true);

EXPECT_CALL(response_encoder_, encodeHeaders(_, true))
.WillOnce(Invoke([&](const ResponseHeaderMap& headers, bool) -> void {
EXPECT_NE(nullptr, headers.Server());
EXPECT_EQ("envoy-server-test", headers.getServerValue());
conn_manager_->onEvent(Network::ConnectionEvent::LocalClose);
}));
EXPECT_CALL(*decoder_filters_[0], onStreamComplete());
EXPECT_CALL(*decoder_filters_[0], onDestroy());

ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}};
decoder_filters_[0]->callbacks_->streamInfo().setResponseCodeDetails("");
decoder_filters_[0]->callbacks_->encodeHeaders(std::move(response_headers), true, "details");
}

TEST_F(HttpConnectionManagerImplTest, DisconnectDuringEncodeBody) {
setup(false, "envoy-server-test");
setupFilterChain(1, 0);

EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true))
.WillOnce(Return(FilterHeadersStatus::StopIteration));
startRequest(/*end_stream=*/true);

EXPECT_CALL(response_encoder_, encodeHeaders(_, false))
.WillOnce(Invoke([&](const ResponseHeaderMap& headers, bool) -> void {
EXPECT_NE(nullptr, headers.Server());
EXPECT_EQ("envoy-server-test", headers.getServerValue());
}));
EXPECT_CALL(response_encoder_, encodeData(_, true))
.WillOnce(Invoke([&](Buffer::Instance&, bool) -> void {
conn_manager_->onEvent(Network::ConnectionEvent::LocalClose);
}));
EXPECT_CALL(*decoder_filters_[0], onStreamComplete());
EXPECT_CALL(*decoder_filters_[0], onDestroy());

ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}};
decoder_filters_[0]->callbacks_->streamInfo().setResponseCodeDetails("");
decoder_filters_[0]->callbacks_->encodeHeaders(std::move(response_headers), false, "details");
Buffer::OwnedImpl response_body("response");
decoder_filters_[0]->callbacks_->encodeData(response_body, true);
}

TEST_F(HttpConnectionManagerImplTest, DisconnectDuringEncodeTrailer) {
setup(false, "envoy-server-test");
setupFilterChain(1, 0);

EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true))
.WillOnce(Return(FilterHeadersStatus::StopIteration));
startRequest(/*end_stream=*/true);

EXPECT_CALL(response_encoder_, encodeHeaders(_, false))
.WillOnce(Invoke([&](const ResponseHeaderMap& headers, bool) -> void {
EXPECT_NE(nullptr, headers.Server());
EXPECT_EQ("envoy-server-test", headers.getServerValue());
}));
EXPECT_CALL(response_encoder_, encodeData(_, false));
EXPECT_CALL(response_encoder_, encodeTrailers(_))
.WillOnce(Invoke([&](const Http::ResponseTrailerMap&) -> void {
conn_manager_->onEvent(Network::ConnectionEvent::LocalClose);
}));
EXPECT_CALL(*decoder_filters_[0], onStreamComplete());
EXPECT_CALL(*decoder_filters_[0], onDestroy());

ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}};
decoder_filters_[0]->callbacks_->streamInfo().setResponseCodeDetails("");
decoder_filters_[0]->callbacks_->encodeHeaders(std::move(response_headers), false, "details");
Buffer::OwnedImpl response_body("response");
decoder_filters_[0]->callbacks_->encodeData(response_body, false);
decoder_filters_[0]->callbacks_->encodeTrailers(
ResponseTrailerMapPtr{new TestResponseTrailerMapImpl{{"some", "trailer"}}});
}

TEST_F(HttpConnectionManagerImplTest, DirectLocalReplyCausesDisconnect) {
initial_buffer_limit_ = 10;
setup(false, "");
setUpEncoderAndDecoder(false, false);
sendRequestHeadersAndData();

// Start the response without processing the request headers through all
// filters.
ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}};
EXPECT_CALL(*encoder_filters_[1], encodeHeaders(_, false))
.WillOnce(Return(FilterHeadersStatus::StopIteration));
decoder_filters_[0]->callbacks_->streamInfo().setResponseCodeDetails("");
decoder_filters_[0]->callbacks_->encodeHeaders(std::move(response_headers), false, "details");

// Now overload the buffer with response data. The filter returns
// StopIterationAndBuffer, which will trigger an early response.

expectOnDestroy();
Buffer::OwnedImpl fake_response("A long enough string to go over watermarks");
// Fake response starts doing through the filter.
EXPECT_CALL(*encoder_filters_[1], encodeData(_, false))
.WillOnce(Return(FilterDataStatus::StopIterationAndBuffer));
std::string response_body;
// The 500 goes directly to the encoder.
EXPECT_CALL(response_encoder_, encodeHeaders(_, false))
.WillOnce(Invoke([&](const ResponseHeaderMap& headers, bool) -> FilterHeadersStatus {
// Make sure this is a 500
EXPECT_EQ("500", headers.getStatusValue());
// Make sure Envoy standard sanitization has been applied.
EXPECT_TRUE(headers.Date() != nullptr);
EXPECT_EQ("response_payload_too_large",
decoder_filters_[0]->callbacks_->streamInfo().responseCodeDetails().value());
return FilterHeadersStatus::Continue;
}));
EXPECT_CALL(response_encoder_, encodeData(_, true))
.WillOnce(Invoke([&](Buffer::Instance&, bool) -> void {
conn_manager_->onEvent(Network::ConnectionEvent::LocalClose);
}));
decoder_filters_[0]->callbacks_->encodeData(fake_response, false);

EXPECT_EQ(1U, stats_.named_.rs_too_large_.value());
}

} // namespace Http
} // namespace Envoy
3 changes: 1 addition & 2 deletions test/integration/protocol_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3556,8 +3556,7 @@ TEST_P(DownstreamProtocolIntegrationTest, HandleSocketFail) {
Network::IoSocketError* ebadf = Network::IoSocketError::getIoSocketEbadfInstance();
socket_swap.write_matcher_->setSourcePort(lookupPort("http"));
socket_swap.write_matcher_->setWriteOverride(ebadf);
// TODO(danzh) set to true.
upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false);
upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true);

if (downstreamProtocol() == Http::CodecType::HTTP3) {
// For HTTP/3 since the packets are black holed, there is no client side
Expand Down