Skip to content

Commit

Permalink
listener: match rebalancer to listener IP family type (envoyproxy#16914)
Browse files Browse the repository at this point in the history
When getting a rebalancer by address and a wild card match is being
used, the first match in the list is returned. However, if there are
listeners with addresses "0.0.0.0" and "::" then the first active
listener found will be used, irrespective of the IP family type. Change
the behavior to always return the listener of the same IP family type as
the rebalancer.

Fixes envoyproxy#16804

Co-authored-by: [email protected]
Signed-off-by: Jacob Delgado <[email protected]>
  • Loading branch information
jacob-delgado authored Jul 7, 2021
1 parent 9fee580 commit 19e7879
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Minor Behavior Changes
``envoy.reloadable_features.no_chunked_encoding_header_for_304`` to false.
* 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
<envoy_v3_api_field_config.route.v3.HeaderMatcher.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 <envoy_v3_api_field_config.listener.v3.Listener.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.
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 @@ -76,6 +76,7 @@ constexpr const char* runtime_features[] = {
"envoy.reloadable_features.http_upstream_wait_connect_response",
"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.no_chunked_encoding_header_for_304",
"envoy.reloadable_features.prefer_quic_kernel_bpf_packet_routing",
Expand Down
1 change: 1 addition & 0 deletions source/server/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
39 changes: 28 additions & 11 deletions source/server/connection_handler_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Network::Address::InstanceConstSharedPtr,
ConnectionHandlerImpl::ActiveListenerDetails>& p) {
return absl::holds_alternative<std::reference_wrapper<ActiveTcpListener>>(
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<Network::Address::InstanceConstSharedPtr,
ConnectionHandlerImpl::ActiveListenerDetails>& p) {
return absl::holds_alternative<std::reference_wrapper<ActiveTcpListener>>(
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<Network::Address::InstanceConstSharedPtr,
ConnectionHandlerImpl::ActiveListenerDetails>& p) {
return absl::holds_alternative<std::reference_wrapper<ActiveTcpListener>>(
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<std::reference_wrapper<ActiveTcpListener>>(
Expand Down
1 change: 1 addition & 0 deletions test/server/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
157 changes: 157 additions & 0 deletions test/server/connection_handler_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -746,6 +747,162 @@ TEST_F(ConnectionHandlerTest, FallbackToWildcardListener) {
EXPECT_CALL(*access_log_, log(_, _, _, _));
}

TEST_F(ConnectionHandlerTest, OldBehaviorMatchFirstWildcardListener) {
auto scoped_runtime = std::make_unique<TestScopedRuntime>();

Runtime::LoaderSingleton::getExisting()->mergeValues(
{{"envoy.reloadable_features.listener_wildcard_match_ip_family", "false"}});

Network::TcpListenerCallbacks* listener_callbacks1;
auto listener1 = new NiceMock<Network::MockListener>();
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<NiceMock<Network::MockFilterChainManager>>();
Network::TcpListenerCallbacks* ipv4_any_listener_callbacks;
auto listener2 = new NiceMock<Network::MockListener>();
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<NiceMock<Network::MockFilterChainManager>>();
Network::TcpListenerCallbacks* ipv6_any_listener_callbacks;
auto listener3 = new NiceMock<Network::MockListener>();
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<Network::MockConnectionSocket>();
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<Network::MockServerConnection>();
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<TestScopedRuntime>();

Network::TcpListenerCallbacks* listener_callbacks1;
auto listener1 = new NiceMock<Network::MockListener>();
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<NiceMock<Network::MockFilterChainManager>>();
Network::TcpListenerCallbacks* ipv4_any_listener_callbacks;
auto listener2 = new NiceMock<Network::MockListener>();
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<NiceMock<Network::MockFilterChainManager>>();
Network::TcpListenerCallbacks* ipv6_any_listener_callbacks;
auto listener3 = new NiceMock<Network::MockListener>();
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<Network::MockConnectionSocket>();
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<Network::MockServerConnection>();
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<Network::MockListener>();
Expand Down

0 comments on commit 19e7879

Please sign in to comment.