diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 3e88b63f3c90b..b5eb7b818661a 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -33,6 +33,7 @@ Minor Behavior Changes * http: serve HEAD requests from cache. * http: the behavior of the *present_match* in route header matcher changed. The value of *present_match* is ignored in the past. The new behavior is *present_match* performed when value is true. absent match performed when the value is false. Please reference :ref:`present_match `. +* listener: added an option when balancing across active listeners and wildcard matching is used to return the listener that matches the IP family type associated with the listener's socket address. Any unexpected behavioral changes can be reverted by setting runtime guard ``envoy.reloadable_features.listener_wildcard_match_ip_family`` to false. * listener: respect the :ref:`connection balance config ` defined within the listener where the sockets are redirected to. Clear that field to restore the previous behavior. * tcp: switched to the new connection pool by default. Any unexpected behavioral changes can be reverted by setting runtime guard ``envoy.reloadable_features.new_tcp_connection_pool`` to false. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index b1aa82b623822..0d170d2da9111 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -75,6 +75,7 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.http2_skip_encoding_empty_trailers", "envoy.reloadable_features.improved_stream_limit_handling", "envoy.reloadable_features.internal_redirects_with_body", + "envoy.reloadable_features.listener_wildcard_match_ip_family", "envoy.reloadable_features.new_tcp_connection_pool", "envoy.reloadable_features.prefer_quic_kernel_bpf_packet_routing", "envoy.reloadable_features.preserve_downstream_scheme", diff --git a/source/server/BUILD b/source/server/BUILD index dc5d9ad5a8fb6..fe398a54f2429 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -86,6 +86,7 @@ envoy_cc_library( "//envoy/network:filter_interface", "//envoy/network:listen_socket_interface", "//envoy/network:listener_interface", + "//envoy/runtime:runtime_interface", "//envoy/server:listener_manager_interface", "//envoy/stats:timespan_interface", "//source/common/common:linked_object", diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index e13ca96dbffff..14961a5c18747 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -7,6 +7,7 @@ #include "source/common/event/deferred_task.h" #include "source/common/network/utility.h" +#include "source/common/runtime/runtime_features.h" #include "source/server/active_tcp_listener.h" namespace Envoy { @@ -191,17 +192,33 @@ ConnectionHandlerImpl::getBalancedHandlerByAddress(const Network::Address::Insta // Otherwise, we need to look for the wild card match, i.e., 0.0.0.0:[address_port]. // We do not return stopped listeners. // TODO(wattli): consolidate with previous search for more efficiency. - listener_it = - std::find_if(listeners_.begin(), listeners_.end(), - [&address](const std::pair& p) { - return absl::holds_alternative>( - p.second.typed_listener_) && - p.second.listener_->listener() != nullptr && - p.first->type() == Network::Address::Type::Ip && - p.first->ip()->port() == address.ip()->port() && - p.first->ip()->isAnyAddress(); - }); + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.listener_wildcard_match_ip_family")) { + listener_it = + std::find_if(listeners_.begin(), listeners_.end(), + [&address](const std::pair& p) { + return absl::holds_alternative>( + p.second.typed_listener_) && + p.second.listener_->listener() != nullptr && + p.first->type() == Network::Address::Type::Ip && + p.first->ip()->port() == address.ip()->port() && + p.first->ip()->isAnyAddress() && + p.first->ip()->version() == address.ip()->version(); + }); + } else { + listener_it = + std::find_if(listeners_.begin(), listeners_.end(), + [&address](const std::pair& p) { + return absl::holds_alternative>( + p.second.typed_listener_) && + p.second.listener_->listener() != nullptr && + p.first->type() == Network::Address::Type::Ip && + p.first->ip()->port() == address.ip()->port() && + p.first->ip()->isAnyAddress(); + }); + } return (listener_it != listeners_.end()) ? Network::BalancedConnectionHandlerOptRef( ActiveTcpListenerOptRef(absl::get>( diff --git a/test/server/BUILD b/test/server/BUILD index c35ef66fa63b9..39b7a0c776bb3 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -86,6 +86,7 @@ envoy_cc_test( "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", "//test/test_common:network_utility_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:threadsafe_singleton_injector_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc index b97a3d140c614..914a959d8d225 100644 --- a/test/server/connection_handler_test.cc +++ b/test/server/connection_handler_test.cc @@ -21,6 +21,7 @@ #include "test/mocks/common.h" #include "test/mocks/network/mocks.h" #include "test/test_common/network_utility.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "gmock/gmock.h" @@ -746,6 +747,162 @@ TEST_F(ConnectionHandlerTest, FallbackToWildcardListener) { EXPECT_CALL(*access_log_, log(_, _, _, _)); } +TEST_F(ConnectionHandlerTest, OldBehaviorMatchFirstWildcardListener) { + auto scoped_runtime = std::make_unique(); + + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.listener_wildcard_match_ip_family", "false"}}); + + Network::TcpListenerCallbacks* listener_callbacks1; + auto listener1 = new NiceMock(); + TestListener* test_listener1 = + addListener(1, true, true, "test_listener1", listener1, &listener_callbacks1); + Network::Address::InstanceConstSharedPtr normal_address( + new Network::Address::Ipv4Instance("127.0.0.1", 10001)); + EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(normal_address)); + handler_->addListener(absl::nullopt, *test_listener1); + + auto ipv4_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv4_any_listener_callbacks; + auto listener2 = new NiceMock(); + TestListener* ipv4_any_listener = + addListener(1, false, false, "ipv4_any_test_listener", listener2, + &ipv4_any_listener_callbacks, nullptr, nullptr, Network::Socket::Type::Stream, + std::chrono::milliseconds(15000), false, ipv4_overridden_filter_chain_manager); + Network::Address::InstanceConstSharedPtr any_address( + new Network::Address::Ipv4Instance("0.0.0.0", 80)); + EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(any_address)); + handler_->addListener(absl::nullopt, *ipv4_any_listener); + + auto ipv6_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv6_any_listener_callbacks; + auto listener3 = new NiceMock(); + TestListener* ipv6_any_listener = + addListener(1, false, false, "ipv6_any_test_listener", listener3, + &ipv6_any_listener_callbacks, nullptr, nullptr, Network::Socket::Type::Stream, + std::chrono::milliseconds(15000), false, ipv6_overridden_filter_chain_manager); + Network::Address::InstanceConstSharedPtr any_address_ipv6( + new Network::Address::Ipv6Instance("::", 80)); + EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(any_address_ipv6)); + handler_->addListener(absl::nullopt, *ipv6_any_listener); + + Network::MockListenerFilter* test_filter = new Network::MockListenerFilter(); + EXPECT_CALL(*test_filter, destroy_()); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + bool redirected = false; + EXPECT_CALL(factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + // Insert the Mock filter. + if (!redirected) { + manager.addAcceptFilter(listener_filter_matcher_, + Network::ListenerFilterPtr{test_filter}); + redirected = true; + } + return true; + })); + + Network::Address::InstanceConstSharedPtr alt_address( + new Network::Address::Ipv6Instance("::2", 80, nullptr)); + EXPECT_CALL(*test_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().addressProvider().restoreLocalAddress(alt_address); + return Network::FilterStatus::Continue; + })); + EXPECT_CALL(manager_, findFilterChain(_)).Times(0); + EXPECT_CALL(*ipv4_overridden_filter_chain_manager, findFilterChain(_)) + .WillOnce(Return(filter_chain_.get())); + EXPECT_CALL(*ipv6_overridden_filter_chain_manager, findFilterChain(_)).Times(0); + auto* connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}); + EXPECT_EQ(1UL, handler_->numConnections()); + + EXPECT_CALL(*listener3, onDestroy()); + EXPECT_CALL(*listener2, onDestroy()); + EXPECT_CALL(*listener1, onDestroy()); + EXPECT_CALL(*access_log_, log(_, _, _, _)); +} + +TEST_F(ConnectionHandlerTest, MatchIPv6WildcardListener) { + auto scoped_runtime = std::make_unique(); + + Network::TcpListenerCallbacks* listener_callbacks1; + auto listener1 = new NiceMock(); + TestListener* test_listener1 = + addListener(1, true, true, "test_listener1", listener1, &listener_callbacks1); + Network::Address::InstanceConstSharedPtr normal_address( + new Network::Address::Ipv4Instance("127.0.0.1", 10001)); + EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(normal_address)); + handler_->addListener(absl::nullopt, *test_listener1); + + auto ipv4_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv4_any_listener_callbacks; + auto listener2 = new NiceMock(); + TestListener* ipv4_any_listener = + addListener(1, false, false, "ipv4_any_test_listener", listener2, + &ipv4_any_listener_callbacks, nullptr, nullptr, Network::Socket::Type::Stream, + std::chrono::milliseconds(15000), false, ipv4_overridden_filter_chain_manager); + + Network::Address::InstanceConstSharedPtr any_address( + new Network::Address::Ipv4Instance("0.0.0.0", 80)); + EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(any_address)); + handler_->addListener(absl::nullopt, *ipv4_any_listener); + + auto ipv6_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv6_any_listener_callbacks; + auto listener3 = new NiceMock(); + TestListener* ipv6_any_listener = + addListener(1, false, false, "ipv6_any_test_listener", listener3, + &ipv6_any_listener_callbacks, nullptr, nullptr, Network::Socket::Type::Stream, + std::chrono::milliseconds(15000), false, ipv6_overridden_filter_chain_manager); + Network::Address::InstanceConstSharedPtr any_address_ipv6( + new Network::Address::Ipv6Instance("::", 80)); + EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(any_address_ipv6)); + handler_->addListener(absl::nullopt, *ipv6_any_listener); + + Network::MockListenerFilter* test_filter = new Network::MockListenerFilter(); + EXPECT_CALL(*test_filter, destroy_()); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + bool redirected = false; + EXPECT_CALL(factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + // Insert the Mock filter. + if (!redirected) { + manager.addAcceptFilter(listener_filter_matcher_, + Network::ListenerFilterPtr{test_filter}); + redirected = true; + } + return true; + })); + + Network::Address::InstanceConstSharedPtr alt_address( + new Network::Address::Ipv6Instance("::2", 80, nullptr)); + EXPECT_CALL(*test_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().addressProvider().restoreLocalAddress(alt_address); + return Network::FilterStatus::Continue; + })); + EXPECT_CALL(manager_, findFilterChain(_)).Times(0); + EXPECT_CALL(*ipv4_overridden_filter_chain_manager, findFilterChain(_)).Times(0); + EXPECT_CALL(*ipv6_overridden_filter_chain_manager, findFilterChain(_)) + .WillOnce(Return(filter_chain_.get())); + auto* connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}); + EXPECT_EQ(1UL, handler_->numConnections()); + + EXPECT_CALL(*listener3, onDestroy()); + EXPECT_CALL(*listener2, onDestroy()); + EXPECT_CALL(*listener1, onDestroy()); + EXPECT_CALL(*access_log_, log(_, _, _, _)); +} + TEST_F(ConnectionHandlerTest, WildcardListenerWithOriginalDstInbound) { Network::TcpListenerCallbacks* listener_callbacks1; auto listener1 = new NiceMock();