Skip to content
6 changes: 6 additions & 0 deletions envoy/network/io_handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ class IoHandle {
virtual void initializeFileEvent(Event::Dispatcher& dispatcher, Event::FileReadyCb cb,
Event::FileTriggerType trigger, uint32_t events) PURE;

/**
* Check whether the file event is initialized or not.
* @return return true if the file event is initialize, otherwise return false.
*/
virtual bool isFileEventInitialized() PURE;

/**
* Activates file events for the current underlying fd.
* @param events events that will be activated.
Expand Down
2 changes: 1 addition & 1 deletion source/common/network/io_socket_handle_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class IoSocketHandleImpl : public IoHandle, protected Logger::Loggable<Logger::I
Address::InstanceConstSharedPtr peerAddress() override;
void initializeFileEvent(Event::Dispatcher& dispatcher, Event::FileReadyCb cb,
Event::FileTriggerType trigger, uint32_t events) override;

bool isFileEventInitialized() override { return file_event_ != nullptr; }
IoHandlePtr duplicate() override;

void activateFileEvents(uint32_t events) override;
Expand Down
2 changes: 2 additions & 0 deletions source/common/quic/quic_io_handle_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class QuicIoHandleWrapper : public Network::IoHandle {
io_handle_.initializeFileEvent(dispatcher, cb, trigger, events);
}

bool isFileEventInitialized() override { return io_handle_.isFileEventInitialized(); }

Network::IoHandlePtr duplicate() override { return io_handle_.duplicate(); }

void activateFileEvents(uint32_t events) override { io_handle_.activateFileEvents(events); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ using ConfigSharedPtr = std::shared_ptr<Config>;
class Filter : public Network::ListenerFilter, Logger::Loggable<Logger::Id::filter> {
public:
Filter(const ConfigSharedPtr config);
~Filter() override {
if (cb_) {
cb_->socket().ioHandle().resetFileEvents();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It this filter guaranteed to be the owner of the file event on the ioHandle at this point, or is it possible that some other filter owns the file event?

cb_ remains set after calls to resetFileEvents() in http_inspector.cc

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it could be owned by other filter. Like we enable tls inspect and http inspect at same time, tls inspect successed, then http inspect timeout, both filter will reset the file event in the destruction. But it should be fine since the reset can be execute multiple times.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is wherever a filter that is not the TLS inspector nor HTTP inspector could own the file event. For example, the network::Connection owned by the http connection manager.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is no other filter own the file event. Since this is on the stage of accepting the connection, so the l3/l4 filter doesn't instance yet.

}
}

// Network::ListenerFilter
Network::FilterStatus onAccept(Network::ListenerFilterCallbacks& cb) override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ enum class ReadOrParseState { Done, TryAgainLater, Error };
class Filter : public Network::ListenerFilter, Logger::Loggable<Logger::Id::filter> {
public:
Filter(const ConfigSharedPtr& config) : config_(config) {}

~Filter() override {
if (cb_) {
cb_->socket().ioHandle().resetFileEvents();
}
}
// Network::ListenerFilter
Network::FilterStatus onAccept(Network::ListenerFilterCallbacks& cb) override;

Expand Down Expand Up @@ -118,7 +122,7 @@ class Filter : public Network::ListenerFilter, Logger::Loggable<Logger::Id::filt
bool parseV2Header(char* buf);
absl::optional<size_t> lenV2Address(char* buf);

Network::ListenerFilterCallbacks* cb_{};
Network::ListenerFilterCallbacks* cb_{nullptr};
Comment thread
soulxu marked this conversation as resolved.
Outdated

// The offset in buf_ that has been fully read
size_t buf_off_{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ using ConfigSharedPtr = std::shared_ptr<Config>;
class Filter : public Network::ListenerFilter, Logger::Loggable<Logger::Id::filter> {
public:
Filter(const ConfigSharedPtr config);
~Filter() override {
if (cb_) {
cb_->socket().ioHandle().resetFileEvents();
}
}

// Network::ListenerFilter
Network::FilterStatus onAccept(Network::ListenerFilterCallbacks& cb) override;
Expand All @@ -85,7 +90,7 @@ class Filter : public Network::ListenerFilter, Logger::Loggable<Logger::Id::filt
void onServername(absl::string_view name);

ConfigSharedPtr config_;
Network::ListenerFilterCallbacks* cb_;
Network::ListenerFilterCallbacks* cb_{nullptr};
Comment thread
soulxu marked this conversation as resolved.
Outdated

bssl::UniquePtr<SSL> ssl_;
uint64_t read_{0};
Expand Down
2 changes: 2 additions & 0 deletions source/extensions/io_socket/user_space/io_handle_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class IoHandleImpl final : public Network::IoHandle,

void initializeFileEvent(Event::Dispatcher& dispatcher, Event::FileReadyCb cb,
Event::FileTriggerType trigger, uint32_t events) override;
bool isFileEventInitialized() override { return user_file_event_ != nullptr; }

Network::IoHandlePtr duplicate() override;
void activateFileEvents(uint32_t events) override;
void enableFileEvents(uint32_t events) override;
Expand Down
6 changes: 2 additions & 4 deletions source/server/active_tcp_listener.cc
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,8 @@ void ActiveTcpSocket::newConnection() {
if (socket_->detectedTransportProtocol().empty()) {
socket_->setDetectedTransportProtocol("raw_buffer");
}
// TODO(lambdai): add integration test
// TODO: Address issues in wider scope. See https://github.com/envoyproxy/envoy/issues/8925
// Erase accept filter states because accept filters may not get the opportunity to clean up.
// Particularly the assigned events need to reset before assigning new events in the follow up.
// Clear the listener filter to ensure the file event registered by
// listener filter to be removed. reference PR #8922.
Comment thread
soulxu marked this conversation as resolved.
Outdated
accept_filters_.clear();
// Create a new connection on this listener.
listener_.newConnection(std::move(socket_), std::move(stream_info_));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ class HttpInspectorTest : public testing::Test {
HttpInspectorTest()
: cfg_(std::make_shared<Config>(store_)),
io_handle_(std::make_unique<Network::IoSocketHandleImpl>(42)) {}
~HttpInspectorTest() override { io_handle_->close(); }
~HttpInspectorTest() override {
io_handle_->close();
filter_.reset();
EXPECT_EQ(false, io_handle_->isFileEventInitialized());
Comment thread
soulxu marked this conversation as resolved.
Outdated
}

void init(bool include_inline_recv = true) {
filter_ = std::make_unique<Filter>(cfg_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@ class ProxyProtocolTest : public testing::TestWithParam<Network::Address::IpVers
bool bindToPort() override { return true; }
bool handOffRestoredDestinationConnections() const override { return false; }
uint32_t perConnectionBufferLimitBytes() const override { return 0; }
std::chrono::milliseconds listenerFiltersTimeout() const override { return {}; }
bool continueOnListenerFiltersTimeout() const override { return false; }
std::chrono::milliseconds listenerFiltersTimeout() const override {
return std::chrono::milliseconds(1000);
}
bool continueOnListenerFiltersTimeout() const override { return true; }
Stats::Scope& listenerScope() override { return stats_store_; }
uint64_t listenerTag() const override { return 1; }
ResourceLimit& openConnections() override { return open_connections_; }
Expand Down Expand Up @@ -210,6 +212,23 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, ProxyProtocolTest,
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
TestUtility::ipTestParamsToString);

// This test ensures the socket file event was reset after timeout, otherwise
// the assertion which avoid to create file event duplicated will be triggered.
TEST_P(ProxyProtocolTest, Timeout) {
connect();
absl::SleepFor(absl::Seconds(5));
Comment thread
soulxu marked this conversation as resolved.
Outdated
dispatcher_->run(Event::Dispatcher::RunType::NonBlock);
if (GetParam() == Envoy::Network::Address::IpVersion::v4) {
EXPECT_EQ(server_connection_->addressProvider().remoteAddress()->ip()->addressAsString(),
"127.0.0.1");
} else {
EXPECT_EQ(server_connection_->addressProvider().remoteAddress()->ip()->addressAsString(),
"::1");
}

disconnect();
}
Comment thread
soulxu marked this conversation as resolved.

TEST_P(ProxyProtocolTest, V1Basic) {
connect();
write("PROXY TCP4 1.2.3.4 253.253.253.253 65535 1234\r\nmore data");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ class TlsInspectorTest : public testing::TestWithParam<std::tuple<uint16_t, uint
TlsInspectorTest()
: cfg_(std::make_shared<Config>(store_)),
io_handle_(std::make_unique<Network::IoSocketHandleImpl>(42)) {}
~TlsInspectorTest() override { io_handle_->close(); }
~TlsInspectorTest() override {
io_handle_->close();
filter_.reset();
EXPECT_EQ(false, io_handle_->isFileEventInitialized());
}

void init() {
filter_ = std::make_unique<Filter>(cfg_);
Expand Down
84 changes: 61 additions & 23 deletions test/integration/listener_filter_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,44 +48,64 @@ class ListenerFilterIntegrationTest : public testing::TestWithParam<Network::Add
}
}

void initializeWithListenerFilter(absl::optional<bool> listener_filter_disabled = absl::nullopt) {
void initializeWithListenerFilter(bool ssl_client,
absl::optional<bool> listener_filter_disabled = absl::nullopt) {
config_helper_.renameListener("echo");
std::string tls_inspector_config = ConfigHelper::tlsInspectorFilter();
if (listener_filter_disabled.has_value()) {
tls_inspector_config = appendMatcher(tls_inspector_config, listener_filter_disabled.value());
}
config_helper_.addListenerFilter(tls_inspector_config);
config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
auto* filter_chain =
bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0);
auto* alpn = filter_chain->mutable_filter_chain_match()->add_application_protocols();
*alpn = "envoyalpn";

config_helper_.addConfigModifier([ssl_client](
envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
if (ssl_client) {
auto* filter_chain =
bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0);
auto* alpn = filter_chain->mutable_filter_chain_match()->add_application_protocols();
*alpn = "envoyalpn";
}
auto* timeout = bootstrap.mutable_static_resources()
->mutable_listeners(0)
->mutable_listener_filters_timeout();
timeout->MergeFrom(ProtobufUtil::TimeUtil::MillisecondsToDuration(1000));
bootstrap.mutable_static_resources()
->mutable_listeners(0)
->set_continue_on_listener_filters_timeout(true);
});
config_helper_.addSslConfig();
if (ssl_client) {
config_helper_.addSslConfig();
}

useListenerAccessLog("%RESPONSE_CODE_DETAILS%");
BaseIntegrationTest::initialize();

context_manager_ =
std::make_unique<Extensions::TransportSockets::Tls::ContextManagerImpl>(timeSystem());
}

void setupConnections(bool listener_filter_disabled, bool expect_connection_open) {
initializeWithListenerFilter(listener_filter_disabled);
void setupConnections(bool listener_filter_disabled, bool expect_connection_open,
bool ssl_client) {
initializeWithListenerFilter(ssl_client, listener_filter_disabled);

// Set up the SSL client.
Network::Address::InstanceConstSharedPtr address =
Ssl::getSslAddress(version_, lookupPort("echo"));
context_ = Ssl::createClientSslTransportSocketFactory({}, *context_manager_, *api_);
ssl_client_ = dispatcher_->createClientConnection(
address, Network::Address::InstanceConstSharedPtr(),
context_->createTransportSocket(
// nullptr
std::make_shared<Network::TransportSocketOptionsImpl>(
absl::string_view(""), std::vector<std::string>(),
std::vector<std::string>{"envoyalpn"})),
nullptr);
ssl_client_->addConnectionCallbacks(connect_callbacks_);
ssl_client_->connect();
Network::TransportSocketPtr transport_socket;
if (ssl_client) {
transport_socket =
context_->createTransportSocket(std::make_shared<Network::TransportSocketOptionsImpl>(
absl::string_view(""), std::vector<std::string>(),
std::vector<std::string>{"envoyalpn"}));
} else {
auto transport_socket_factory = std::make_unique<Network::RawBufferSocketFactory>();
transport_socket = transport_socket_factory->createTransportSocket(nullptr);
}
client_ = dispatcher_->createClientConnection(
address, Network::Address::InstanceConstSharedPtr(), std::move(transport_socket), nullptr);
client_->addConnectionCallbacks(connect_callbacks_);
client_->connect();
while (!connect_callbacks_.connected() && !connect_callbacks_.closed()) {
dispatcher_->run(Event::Dispatcher::RunType::NonBlock);
}
Expand All @@ -98,27 +118,45 @@ class ListenerFilterIntegrationTest : public testing::TestWithParam<Network::Add
ASSERT(connect_callbacks_.closed());
}
}

std::unique_ptr<Ssl::ContextManager> context_manager_;
Network::TransportSocketFactoryPtr context_;
ConnectionStatusCallbacks connect_callbacks_;
testing::NiceMock<Secret::MockSecretManager> secret_manager_;
Network::ClientConnectionPtr ssl_client_;
Network::ClientConnectionPtr client_;
};

// Each listener filter is enabled by default.
TEST_P(ListenerFilterIntegrationTest, AllListenerFiltersAreEnabledByDefault) {
setupConnections(/*listener_filter_disabled=*/false, /*expect_connection_open=*/true);
ssl_client_->close(Network::ConnectionCloseType::NoFlush);
setupConnections(/*listener_filter_disabled=*/false, /*expect_connection_open=*/true,
/*ssl_client=*/true);
client_->close(Network::ConnectionCloseType::NoFlush);
EXPECT_THAT(waitForAccessLog(listener_access_log_name_), testing::Eq("-"));
}

// The tls_inspector is disabled. The ALPN won't be sniffed out and no filter chain is matched.
TEST_P(ListenerFilterIntegrationTest, DisabledTlsInspectorFailsFilterChainFind) {
setupConnections(/*listener_filter_disabled=*/true, /*expect_connection_open=*/false);
setupConnections(/*listener_filter_disabled=*/true, /*expect_connection_open=*/false,
/*ssl_client=*/true);
EXPECT_THAT(waitForAccessLog(listener_access_log_name_),
testing::Eq(StreamInfo::ResponseCodeDetails::get().FilterChainNotFound));
}

// trigger the tls inspect filter timeout, and continue create new connection after timeout
TEST_P(ListenerFilterIntegrationTest, ContinueOnListenerTimeout) {
setupConnections(/*listener_filter_disabled=*/false, /*expect_connection_open=*/true,
/*ssl_client=*/false);
// The length of tls hello message is defined as `TLS_MAX_CLIENT_HELLO = 64 * 1024`
// if tls inspect filter doesn't read the max length of hello message data, it
// will continue wait. Then the listener filter timeout timer will be triggered.
Buffer::OwnedImpl buffer("fake data");
client_->write(buffer, false);
// the timeout is set as one seconds, sleep 5 to trigger the timeout.
absl::SleepFor(absl::Seconds(5));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use simulated time to avoid such a long sleep?

Also, see comments above regarding additional expectations we could add in order to ensure that this test case goes down the expected code paths.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me see how to simulate time in integration test. If there is way, I should be able fix the proxy filter unittest also.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the proxy filter unitest to use SimulatedTimeSystemHelper. But for the integration test, I saw the BaseIntegrationTest is using the real time-system.

Event::GlobalTimeSystem time_system_;

so I thought the integration is preferred to run at real timesystem?

client_->close(Network::ConnectionCloseType::NoFlush);
EXPECT_THAT(waitForAccessLog(listener_access_log_name_), testing::Eq("-"));
}

INSTANTIATE_TEST_SUITE_P(IpVersions, ListenerFilterIntegrationTest,
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
TestUtility::ipTestParamsToString);
Expand Down
1 change: 1 addition & 0 deletions test/mocks/network/io_handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class MockIoHandle : public IoHandle {
MOCK_METHOD(absl::optional<std::chrono::milliseconds>, lastRoundTripTime, ());
MOCK_METHOD(Api::SysCallIntResult, ioctl,
(unsigned long, void*, unsigned long, void*, unsigned long, unsigned long*));
MOCK_METHOD(bool, isFileEventInitialized, ());
};

} // namespace Network
Expand Down