Skip to content

Commit

Permalink
connection: always disable reads when connection is closed with the F…
Browse files Browse the repository at this point in the history
…lushWriteAndDelay (#28)

Signed-off-by: Yan Avlasov <[email protected]>
  • Loading branch information
yanavlasov authored and PiotrSikora committed Aug 13, 2019
1 parent 84dabbf commit 79cbdca
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 27 deletions.
20 changes: 8 additions & 12 deletions source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -258,17 +258,16 @@ StreamDecoder& ConnectionManagerImpl::newStream(StreamEncoder& response_encoder,
return **streams_.begin();
}

void ConnectionManagerImpl::handleCodecException(const char* error,
Network::ConnectionCloseType close_type) {
void ConnectionManagerImpl::handleCodecException(const char* error) {
ENVOY_CONN_LOG(debug, "dispatch error: {}", read_callbacks_->connection(), error);

// In the protocol error case, we need to reset all streams now. If the close_type is
// FlushWriteAndDelay, the connection might stick around long enough for a pending stream to come
// back and try to encode. In other cases it avoids needless processing of upstream responses when
// downstream connection is closed.
// In the protocol error case, we need to reset all streams now. The connection might stick around
// long enough for a pending stream to come back and try to encode.
resetAllStreams();

read_callbacks_->connection().close(close_type);
// HTTP/1.1 codec has already sent a 400 response if possible. HTTP/2 codec has already sent
// GOAWAY.
read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWriteAndDelay);
}

Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool) {
Expand All @@ -290,14 +289,11 @@ Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool
try {
codec_->dispatch(data);
} catch (const FrameFloodException& e) {
// Abortively close flooded connections
handleCodecException(e.what(), Network::ConnectionCloseType::NoFlush);
handleCodecException(e.what());
return Network::FilterStatus::StopIteration;
} catch (const CodecProtocolException& e) {
stats_.named_.downstream_cx_protocol_error_.inc();
// HTTP/1.1 codec has already sent a 400 response if possible. HTTP/2 codec has already sent
// GOAWAY.
handleCodecException(e.what(), Network::ConnectionCloseType::FlushWriteAndDelay);
handleCodecException(e.what());
return Network::FilterStatus::StopIteration;
}

Expand Down
2 changes: 1 addition & 1 deletion source/common/http/conn_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
void onDrainTimeout();
void startDrainSequence();
Tracing::HttpTracer& tracer() { return http_context_.tracer(); }
void handleCodecException(const char* error, Network::ConnectionCloseType close_type);
void handleCodecException(const char* error);

enum class DrainState { NotDraining, Draining, Closing };

Expand Down
2 changes: 2 additions & 0 deletions source/common/network/connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ void ConnectionImpl::close(ConnectionCloseType type) {
if (!inDelayedClose()) {
initializeDelayedCloseTimer();
delayed_close_state_ = DelayedCloseState::CloseAfterFlushAndWait;
// Monitor for the peer closing the connection.
file_event_->setEnabled(enable_half_close_ ? 0 : Event::FileReadyType::Closed);
}
} else {
closeSocket(ConnectionEvent::LocalClose);
Expand Down
3 changes: 2 additions & 1 deletion test/common/http/conn_manager_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2330,7 +2330,8 @@ TEST_F(HttpConnectionManagerImplTest, FrameFloodError) {
EXPECT_CALL(filter_factory_, createFilterChain(_)).Times(0);

// FrameFloodException should result in reset of the streams followed by abortive close.
EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush));
EXPECT_CALL(filter_callbacks_.connection_,
close(Network::ConnectionCloseType::FlushWriteAndDelay));

// Kick off the incoming data.
Buffer::OwnedImpl fake_input("1234");
Expand Down
5 changes: 5 additions & 0 deletions test/common/network/connection_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1736,6 +1736,7 @@ TEST_F(PostCloseConnectionImplTest, ReadAfterCloseFlushWriteDelayIgnored) {

// Delayed connection close.
EXPECT_CALL(dispatcher_, createTimer_(_));
EXPECT_CALL(*file_event_, setEnabled(Event::FileReadyType::Closed));
connection_->close(ConnectionCloseType::FlushWriteAndDelay);

// Read event, doRead() happens on connection but no filter onData().
Expand All @@ -1760,6 +1761,10 @@ TEST_F(PostCloseConnectionImplTest, ReadAfterCloseFlushWriteDelayIgnoredWithWrit

// Delayed connection close.
EXPECT_CALL(dispatcher_, createTimer_(_));
// With half-close semantics enabled we will not wait for early close notification.
// See the `Envoy::Network::ConnectionImpl::readDisable()' method for more details.
EXPECT_CALL(*file_event_, setEnabled(0));
connection_->enableHalfClose(true);
connection_->close(ConnectionCloseType::FlushWriteAndDelay);

// Read event, doRead() happens on connection but no filter onData().
Expand Down
38 changes: 25 additions & 13 deletions test/integration/http2_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1516,8 +1516,7 @@ void Http2FloodMitigationTest::floodServer(const Http2Frame& frame, const std::s

EXPECT_LE(total_bytes_sent, TransmitThreshold) << "Flood mitigation is broken.";
EXPECT_EQ(1, test_server_->counter(flood_stat)->value());
// Verify that connection was closed abortively
EXPECT_EQ(0,
EXPECT_EQ(1,
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
}

Expand All @@ -1539,9 +1538,10 @@ void Http2FloodMitigationTest::floodServer(absl::string_view host, absl::string_
total_bytes_sent += request.size();
}
EXPECT_LE(total_bytes_sent, TransmitThreshold) << "Flood mitigation is broken.";
EXPECT_EQ(1, test_server_->counter(flood_stat)->value());
// Verify that connection was closed abortively
EXPECT_EQ(0,
if (!flood_stat.empty()) {
EXPECT_EQ(1, test_server_->counter(flood_stat)->value());
}
EXPECT_EQ(1,
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
}

Expand Down Expand Up @@ -1606,11 +1606,26 @@ TEST_P(Http2FloodMitigationTest, RST_STREAM) {
}
EXPECT_LE(total_bytes_sent, TransmitThreshold) << "Flood mitigation is broken.";
EXPECT_EQ(1, test_server_->counter("http2.outbound_control_flood")->value());
// Verify that connection was closed abortively
EXPECT_EQ(0,
EXPECT_EQ(1,
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
}

// Verify that the server stop reading downstream connection on protocol error.
TEST_P(Http2FloodMitigationTest, TooManyStreams) {
config_helper_.addConfigModifier(
[](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
-> void {
hcm.mutable_http2_protocol_options()->mutable_max_concurrent_streams()->set_value(2);
});
autonomous_upstream_ = true;
beginSession();
fake_upstreams_[0]->set_allow_unexpected_disconnects(true);

// Exceed the number of streams allowed by the server. The server should stop reading from the
// client. Verify that the client was unable to stuff a lot of data into the server.
floodServer("host", "/test/long/url", Http2Frame::ResponseStatus::_200, "");
}

TEST_P(Http2FloodMitigationTest, EmptyHeaders) {
config_helper_.addConfigModifier(
[&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
Expand All @@ -1628,8 +1643,7 @@ TEST_P(Http2FloodMitigationTest, EmptyHeaders) {
tcp_client_->waitForDisconnect();

EXPECT_EQ(1, test_server_->counter("http2.inbound_empty_frames_flood")->value());
// Verify that connection was closed abortively
EXPECT_EQ(0,
EXPECT_EQ(1,
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
}

Expand All @@ -1648,8 +1662,7 @@ TEST_P(Http2FloodMitigationTest, EmptyHeadersContinuation) {
tcp_client_->waitForDisconnect();

EXPECT_EQ(1, test_server_->counter("http2.inbound_empty_frames_flood")->value());
// Verify that connection was closed abortively
EXPECT_EQ(0,
EXPECT_EQ(1,
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
}

Expand All @@ -1669,8 +1682,7 @@ TEST_P(Http2FloodMitigationTest, EmptyData) {
tcp_client_->waitForDisconnect();

EXPECT_EQ(1, test_server_->counter("http2.inbound_empty_frames_flood")->value());
// Verify that connection was closed abortively
EXPECT_EQ(0,
EXPECT_EQ(1,
test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value());
}

Expand Down

0 comments on commit 79cbdca

Please sign in to comment.