-
Notifications
You must be signed in to change notification settings - Fork 5.5k
udp: prevent crashing the envoy if udpListener is empty #11914
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
Changes from 10 commits
b256299
2982aa4
33dd0de
4456676
f2c3740
3666790
5feb051
9474d9a
93db712
f02e1d1
3a17841
57a6351
542e065
6e78449
f99cd24
bd49c08
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 |
|---|---|---|
|
|
@@ -361,7 +361,16 @@ class ActiveRawUdpListener : public Network::UdpListenerCallbacks, | |
| Network::Listener* listener() override { return udp_listener_.get(); } | ||
| void pauseListening() override { udp_listener_->disable(); } | ||
| void resumeListening() override { udp_listener_->enable(); } | ||
| void shutdownListener() override { udp_listener_.reset(); } | ||
| void shutdownListener() override { | ||
| // The read_filter_ should be deleted before the udp_listener_ is deleted. | ||
| if (read_filter_.get() != nullptr && udp_listener_.get() == nullptr) { | ||
| throw Envoy::EnvoyException( | ||
|
Contributor
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. Does it have to be an exception? If it's just added for the sake of test, I think it's better to written the test in a way that without read_filter_.reset() the test would crash.
Contributor
Author
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. Ok. I will change that code and the test to as you mentioned. :) |
||
| fmt::format("The read_filter should be deleted before the udp_listener is deleted: {} ", | ||
| config_->name())); | ||
| } | ||
| read_filter_.reset(); | ||
| udp_listener_.reset(); | ||
| } | ||
|
|
||
| // Network::UdpListenerFilterManager | ||
| void addReadFilter(Network::UdpListenerReadFilterPtr&& filter) override; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,16 @@ namespace Envoy { | |
| namespace Server { | ||
| namespace { | ||
|
|
||
| class MockUpstreamUdpFilter : public Network::UdpListenerReadFilter { | ||
|
Contributor
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. No interface is mocked. rename it to: UnimplementedUpstreamUdpFilter?
Contributor
Author
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. Ok. It is better to rename it to other one. |
||
| public: | ||
| MockUpstreamUdpFilter(Network::UdpReadFilterCallbacks& callbacks) | ||
| : UdpListenerReadFilter(callbacks) {} | ||
|
|
||
| // Network::UdpListenerReadFilter | ||
| void onData(Network::UdpRecvData&) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } | ||
| void onReceiveError(Api::IoError::IoErrorCode) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } | ||
| }; | ||
|
|
||
| class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable<Logger::Id::main> { | ||
| public: | ||
| ConnectionHandlerTest() | ||
|
|
@@ -138,13 +148,14 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable<L | |
| std::chrono::milliseconds listener_filters_timeout = std::chrono::milliseconds(15000), | ||
| bool continue_on_listener_filters_timeout = false, | ||
| std::shared_ptr<NiceMock<Network::MockFilterChainManager>> overridden_filter_chain_manager = | ||
| nullptr) { | ||
| nullptr, | ||
| bool return_null_listener = false) { | ||
| listeners_.emplace_back(std::make_unique<TestListener>( | ||
| *this, tag, bind_to_port, hand_off_restored_destination_connections, name, socket_type, | ||
| listener_filters_timeout, continue_on_listener_filters_timeout, socket_factory_, | ||
| overridden_filter_chain_manager)); | ||
| EXPECT_CALL(*socket_factory_, socketType()).WillOnce(Return(socket_type)); | ||
| if (listener == nullptr) { | ||
| if (listener == nullptr && return_null_listener == false) { | ||
| // Expecting listener config in place update. | ||
| // If so, dispatcher would not create new network listener. | ||
| return listeners_.back().get(); | ||
|
|
@@ -1016,6 +1027,32 @@ TEST_F(ConnectionHandlerTest, ListenerFilterWorks) { | |
| EXPECT_CALL(*listener, onDestroy()); | ||
| } | ||
|
|
||
| // The read_filter should be deleted before the udp_listener is deleted. | ||
|
Contributor
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 you add a UDP proxy filter test to reproduce the crash you encountered and verify that the crash is gone with the fix?
Contributor
Author
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. I think that the read filter is reset so that any packets cannot be received that send by upstream by the my fix. Do you have any ideas to simulate without call the file_event_cb_ directly on the UDP proxy filter test?
Contributor
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. How about any integration test which don't simulate file_event_cb_ but actually sends packet to that socket? @mattklein123 about ideas of how to write regression test for this crash as Matt knows better about that test code.
Contributor
Author
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. I checked the integration test but I cannot find a way to test my patch. |
||
| TEST_F(ConnectionHandlerTest, ShutdownUdpListener) { | ||
| InSequence s; | ||
|
|
||
| TestListener* test_listener = addListener(1, true, false, "test_listener", nullptr, nullptr, | ||
| nullptr, nullptr, Network::Socket::Type::Datagram, | ||
| std::chrono::milliseconds(), false, nullptr, true); | ||
|
|
||
| EXPECT_CALL(factory_, createUdpListenerFilterChain(_, _)) | ||
| .WillOnce(Invoke([&](Network::UdpListenerFilterManager& udp_listener, | ||
| Network::UdpReadFilterCallbacks& callbacks) -> bool { | ||
| udp_listener.addReadFilter(std::make_unique<MockUpstreamUdpFilter>(callbacks)); | ||
| return true; | ||
| })); | ||
| EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(local_address_)); | ||
| handler_->addListener(absl::nullopt, *test_listener); | ||
|
|
||
| try { | ||
| handler_->stopListeners(); | ||
| FAIL(); | ||
| } catch (const Envoy::EnvoyException& e) { | ||
| EXPECT_THAT(e.what(), | ||
| HasSubstr("The read_filter should be deleted before the udp_listener is deleted")); | ||
| } | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace Server | ||
| } // namespace Envoy | ||
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.
@danzh2010 @chadr123 sorry I haven't been tracking this PR. Can you describe in more detail what the actual problem is? I would like to understand this better, which will help build a test, but also make this comment better. My very quick understanding is that this is the line that is crashing? Is that right?
envoy/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc
Line 247 in 8452f4b
So the issue is a race condition in which the listener is removed, but before we destroy the filter during deferred deletion. Is that right? So the fix here is to make sure that when the listener is shutdown/destroyed the filter is also destroyed?
Yeah it might not be possible to have an integration test for this case but I will determine that once we make this comment more complete in terms of what is going on here. Thank you!
/wait
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.
Yes. All your understanding is right.
So, the filter should be deleted before listener deletion because the filter refers to the listener.
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 thanks. I agree I don't see how it will be possible to make an integration test for this. Please make the comment more robust and we can ship. Thank you!
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.
How about this?
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.
Let's do this (reflow as appropriate):
Thank you!
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. I have changed the comments now. :)