diff --git a/api/envoy/admin/v2alpha/listeners.proto b/api/envoy/admin/v2alpha/listeners.proto
index ee385de793c72..fa305c796aef1 100644
--- a/api/envoy/admin/v2alpha/listeners.proto
+++ b/api/envoy/admin/v2alpha/listeners.proto
@@ -26,7 +26,7 @@ message ListenerStatus {
// Name of the listener
string name = 1;
- // The actual local address that the listener is listening on. If a listener was configured
+ // The actual local addresses that the listener is listening on. If a listener was configured
// to listen on port 0, then this address has the port that was allocated by the OS.
api.v2.core.Address local_address = 2;
}
diff --git a/api/envoy/admin/v3/listeners.proto b/api/envoy/admin/v3/listeners.proto
index 86e35890bd67b..383706b6c2f14 100644
--- a/api/envoy/admin/v3/listeners.proto
+++ b/api/envoy/admin/v3/listeners.proto
@@ -31,7 +31,7 @@ message ListenerStatus {
// Name of the listener
string name = 1;
- // The actual local address that the listener is listening on. If a listener was configured
+ // The actual local addresses that the listener is listening on. If a listener was configured
// to listen on port 0, then this address has the port that was allocated by the OS.
- config.core.v3.Address local_address = 2;
+ repeated config.core.v3.Address local_addresses = 2;
}
diff --git a/api/envoy/config/listener/v3/listener.proto b/api/envoy/config/listener/v3/listener.proto
index d8982b0a97a7d..cce4b8609d707 100644
--- a/api/envoy/config/listener/v3/listener.proto
+++ b/api/envoy/config/listener/v3/listener.proto
@@ -38,7 +38,7 @@ message ListenerCollection {
repeated xds.core.v3.CollectionEntry entries = 1;
}
-// [#next-free-field: 33]
+// [#next-free-field: 34]
message Listener {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Listener";
@@ -107,8 +107,15 @@ message Listener {
// that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on
// Linux as the actual port will be allocated by the OS.
// Required unless *api_listener* or *listener_specifier* is populated.
+ // TODO (soulxu): deprecated the `address` field after `addresses` implemented.
core.v3.Address address = 2;
+ // A list of addresses that the listener should listen on. In general, the address must be unique, though
+ // that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on
+ // Linux as the actual port will be allocated by the OS. For multiple addresses in single listener,
+ // all addresses are the same protocol, and multiple internal addresses don't support now.
+ repeated core.v3.Address addresses = 33;
+
// Optional prefix to use on listener stats. If empty, the stats will be rooted at
// `listener.
.`. If non-empty, stats will be rooted at
// `listener..`.
diff --git a/envoy/network/connection_handler.h b/envoy/network/connection_handler.h
index 1b5ff88200158..03e970ca4d1a5 100644
--- a/envoy/network/connection_handler.h
+++ b/envoy/network/connection_handler.h
@@ -13,6 +13,8 @@
#include "source/common/common/interval_value.h"
+#include "address.h"
+
namespace Envoy {
namespace Network {
@@ -178,10 +180,12 @@ class TcpConnectionHandler : public virtual ConnectionHandler {
/**
* Obtain the rebalancer of the tcp listener.
* @param listener_tag supplies the tag of the tcp listener that was passed to addListener().
+ * @param address is used to query the address specific handler.
* @return BalancedConnectionHandlerOptRef the balancer attached to the listener. `nullopt` if
* listener doesn't exist or rebalancer doesn't exist.
*/
- virtual BalancedConnectionHandlerOptRef getBalancedHandlerByTag(uint64_t listener_tag) PURE;
+ virtual BalancedConnectionHandlerOptRef
+ getBalancedHandlerByTag(uint64_t listener_tag, const Network::Address::Instance& address) PURE;
/**
* Obtain the rebalancer of the tcp listener.
@@ -199,11 +203,12 @@ class TcpConnectionHandler : public virtual ConnectionHandler {
class UdpConnectionHandler : public virtual ConnectionHandler {
public:
/**
- * Get the ``UdpListenerCallbacks`` associated with ``listener_tag``. This will be
+ * Get the ``UdpListenerCallbacks`` associated with ``listener_tag`` and ``address``. This will be
* absl::nullopt for non-UDP listeners and for ``listener_tag`` values that have already been
* removed.
*/
- virtual UdpListenerCallbacksOptRef getUdpListenerCallbacks(uint64_t listener_tag) PURE;
+ virtual UdpListenerCallbacksOptRef
+ getUdpListenerCallbacks(uint64_t listener_tag, const Network::Address::Instance& address) PURE;
};
/**
@@ -219,6 +224,7 @@ class ActiveUdpListenerFactory {
* @param runtime the runtime for this server.
* @param worker_index The index of the worker this listener is being created on.
* @param parent is the owner of the created ActiveListener objects.
+ * @param listen_socket_ptr is the UDP socket.
* @param dispatcher is used to create actual UDP listener.
* @param config provides information needed to create ActiveUdpListener and
* UdpListener objects.
@@ -226,8 +232,9 @@ class ActiveUdpListenerFactory {
*/
virtual ConnectionHandler::ActiveUdpListenerPtr
createActiveUdpListener(Runtime::Loader& runtime, uint32_t worker_index,
- UdpConnectionHandler& parent, Event::Dispatcher& dispatcher,
- Network::ListenerConfig& config) PURE;
+ UdpConnectionHandler& parent,
+ Network::SocketSharedPtr&& listen_socket_ptr,
+ Event::Dispatcher& dispatcher, Network::ListenerConfig& config) PURE;
/**
* @return true if the UDP passing through listener doesn't form stateful connections.
diff --git a/envoy/network/listener.h b/envoy/network/listener.h
index 2a4c3f97908ce..bf11bcd5e3403 100644
--- a/envoy/network/listener.h
+++ b/envoy/network/listener.h
@@ -20,6 +20,8 @@
#include "source/common/common/interval_value.h"
+#include "address.h"
+
namespace Envoy {
namespace Network {
@@ -95,9 +97,11 @@ class UdpListenerConfig {
virtual UdpPacketWriterFactory& packetWriterFactory() PURE;
/**
+ * @param address is used to query the address specific router.
* @return the UdpListenerWorkerRouter for this listener.
*/
- virtual UdpListenerWorkerRouter& listenerWorkerRouter() PURE;
+ virtual UdpListenerWorkerRouter&
+ listenerWorkerRouter(const Network::Address::Instance& address) PURE;
/**
* @return the configuration for the listener.
@@ -148,14 +152,14 @@ class ListenerConfig {
/**
* @return ListenSocketFactory& the factory to create listen socket.
*/
- virtual ListenSocketFactory& listenSocketFactory() PURE;
+ virtual const std::vector& listenSocketFactories() PURE;
/**
* @return bool specifies whether the listener should actually listen on the port.
* A listener that doesn't listen on a port can only receive connections
* redirected from other listeners.
*/
- virtual bool bindToPort() PURE;
+ virtual bool bindToPort() const PURE;
/**
* @return bool if a connection should be handed off to another Listener after the original
@@ -215,10 +219,11 @@ class ListenerConfig {
virtual envoy::config::core::v3::TrafficDirection direction() const PURE;
/**
+ * @param address is used for query the address specific connection balancer.
* @return the connection balancer for this listener. All listeners have a connection balancer,
* though the implementation may be a NOP balancer.
*/
- virtual ConnectionBalancer& connectionBalancer() PURE;
+ virtual ConnectionBalancer& connectionBalancer(const Network::Address::Instance& address) PURE;
/**
* Open connection resources for this listener.
diff --git a/source/common/network/utility.h b/source/common/network/utility.h
index 54cff85a81d17..9916e4a02e9a0 100644
--- a/source/common/network/utility.h
+++ b/source/common/network/utility.h
@@ -418,5 +418,14 @@ class Utility {
static absl::uint128 flipOrder(const absl::uint128& input);
};
+/**
+ * Log formatter for a list of addresses.
+ */
+struct AddressStrFormatter {
+ void operator()(std::string* out, const Network::Address::InstanceConstSharedPtr& instance) {
+ out->append(instance->asString());
+ }
+};
+
} // namespace Network
} // namespace Envoy
diff --git a/source/common/quic/active_quic_listener.cc b/source/common/quic/active_quic_listener.cc
index 17775ef19f663..8cf1db5699bdf 100644
--- a/source/common/quic/active_quic_listener.cc
+++ b/source/common/quic/active_quic_listener.cc
@@ -26,21 +26,7 @@ bool ActiveQuicListenerFactory::disable_kernel_bpf_packet_routing_for_test_ = fa
ActiveQuicListener::ActiveQuicListener(
Runtime::Loader& runtime, uint32_t worker_index, uint32_t concurrency,
Event::Dispatcher& dispatcher, Network::UdpConnectionHandler& parent,
- Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config,
- bool kernel_worker_routing, const envoy::config::core::v3::RuntimeFeatureFlag& enabled,
- QuicStatNames& quic_stat_names, uint32_t packets_received_to_connection_count_ratio,
- EnvoyQuicCryptoServerStreamFactoryInterface& crypto_server_stream_factory,
- EnvoyQuicProofSourceFactoryInterface& proof_source_factory)
- : ActiveQuicListener(runtime, worker_index, concurrency, dispatcher, parent,
- listener_config.listenSocketFactory().getListenSocket(worker_index),
- listener_config, quic_config, kernel_worker_routing, enabled,
- quic_stat_names, packets_received_to_connection_count_ratio,
- crypto_server_stream_factory, proof_source_factory) {}
-
-ActiveQuicListener::ActiveQuicListener(
- Runtime::Loader& runtime, uint32_t worker_index, uint32_t concurrency,
- Event::Dispatcher& dispatcher, Network::UdpConnectionHandler& parent,
- Network::SocketSharedPtr listen_socket, Network::ListenerConfig& listener_config,
+ Network::SocketSharedPtr&& listen_socket, Network::ListenerConfig& listener_config,
const quic::QuicConfig& quic_config, bool kernel_worker_routing,
const envoy::config::core::v3::RuntimeFeatureFlag& enabled, QuicStatNames& quic_stat_names,
uint32_t packets_to_read_to_connection_count_ratio,
@@ -338,11 +324,12 @@ ActiveQuicListenerFactory::ActiveQuicListenerFactory(
Network::ConnectionHandler::ActiveUdpListenerPtr ActiveQuicListenerFactory::createActiveUdpListener(
Runtime::Loader& runtime, uint32_t worker_index, Network::UdpConnectionHandler& parent,
- Event::Dispatcher& disptacher, Network::ListenerConfig& config) {
+ Network::SocketSharedPtr&& listen_socket_ptr, Event::Dispatcher& disptacher,
+ Network::ListenerConfig& config) {
ASSERT(crypto_server_stream_factory_.has_value());
return std::make_unique(
- runtime, worker_index, concurrency_, disptacher, parent, config, quic_config_,
- kernel_worker_routing_, enabled_, quic_stat_names_,
+ runtime, worker_index, concurrency_, disptacher, parent, std::move(listen_socket_ptr), config,
+ quic_config_, kernel_worker_routing_, enabled_, quic_stat_names_,
packets_to_read_to_connection_count_ratio_, crypto_server_stream_factory_.value(),
proof_source_factory_.value());
}
diff --git a/source/common/quic/active_quic_listener.h b/source/common/quic/active_quic_listener.h
index 6feda1abc7c22..b8a1b3a58d118 100644
--- a/source/common/quic/active_quic_listener.h
+++ b/source/common/quic/active_quic_listener.h
@@ -30,17 +30,7 @@ class ActiveQuicListener : public Envoy::Server::ActiveUdpListenerBase,
ActiveQuicListener(Runtime::Loader& runtime, uint32_t worker_index, uint32_t concurrency,
Event::Dispatcher& dispatcher, Network::UdpConnectionHandler& parent,
- Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config,
- bool kernel_worker_routing,
- const envoy::config::core::v3::RuntimeFeatureFlag& enabled,
- QuicStatNames& quic_stat_names,
- uint32_t packets_to_read_to_connection_count_ratio,
- EnvoyQuicCryptoServerStreamFactoryInterface& crypto_server_stream_factory,
- EnvoyQuicProofSourceFactoryInterface& proof_source_factory);
-
- ActiveQuicListener(Runtime::Loader& runtime, uint32_t worker_index, uint32_t concurrency,
- Event::Dispatcher& dispatcher, Network::UdpConnectionHandler& parent,
- Network::SocketSharedPtr listen_socket,
+ Network::SocketSharedPtr&& listen_socket,
Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config,
bool kernel_worker_routing,
const envoy::config::core::v3::RuntimeFeatureFlag& enabled,
@@ -108,8 +98,9 @@ class ActiveQuicListenerFactory : public Network::ActiveUdpListenerFactory,
// Network::ActiveUdpListenerFactory.
Network::ConnectionHandler::ActiveUdpListenerPtr
createActiveUdpListener(Runtime::Loader& runtime, uint32_t worker_index,
- Network::UdpConnectionHandler& parent, Event::Dispatcher& disptacher,
- Network::ListenerConfig& config) override;
+ Network::UdpConnectionHandler& parent,
+ Network::SocketSharedPtr&& listen_socket_ptr,
+ Event::Dispatcher& disptacher, Network::ListenerConfig& config) override;
bool isTransportConnectionless() const override { return false; }
const Network::Socket::OptionsSharedPtr& socketOptions() const override { return options_; }
diff --git a/source/server/active_raw_udp_listener_config.cc b/source/server/active_raw_udp_listener_config.cc
index e149dadf00716..dca62508ed01e 100644
--- a/source/server/active_raw_udp_listener_config.cc
+++ b/source/server/active_raw_udp_listener_config.cc
@@ -15,10 +15,11 @@ ActiveRawUdpListenerFactory::ActiveRawUdpListenerFactory(uint32_t concurrency)
Network::ConnectionHandler::ActiveUdpListenerPtr
ActiveRawUdpListenerFactory::createActiveUdpListener(Runtime::Loader&, uint32_t worker_index,
Network::UdpConnectionHandler& parent,
+ Network::SocketSharedPtr&& listen_socket_ptr,
Event::Dispatcher& dispatcher,
Network::ListenerConfig& config) {
- return std::make_unique(worker_index, concurrency_, parent, dispatcher,
- config);
+ return std::make_unique(worker_index, concurrency_, parent,
+ std::move(listen_socket_ptr), dispatcher, config);
}
} // namespace Server
diff --git a/source/server/active_raw_udp_listener_config.h b/source/server/active_raw_udp_listener_config.h
index 4b79d9ed6d4f5..a94e31e0c1fe3 100644
--- a/source/server/active_raw_udp_listener_config.h
+++ b/source/server/active_raw_udp_listener_config.h
@@ -11,8 +11,9 @@ class ActiveRawUdpListenerFactory : public Network::ActiveUdpListenerFactory {
Network::ConnectionHandler::ActiveUdpListenerPtr
createActiveUdpListener(Runtime::Loader&, uint32_t worker_index,
- Network::UdpConnectionHandler& parent, Event::Dispatcher& disptacher,
- Network::ListenerConfig& config) override;
+ Network::UdpConnectionHandler& parent,
+ Network::SocketSharedPtr&& listen_socket_ptr,
+ Event::Dispatcher& disptacher, Network::ListenerConfig& config) override;
bool isTransportConnectionless() const override { return true; }
const Network::Socket::OptionsSharedPtr& socketOptions() const override { return options_; }
diff --git a/source/server/active_tcp_listener.cc b/source/server/active_tcp_listener.cc
index 78b435c0aad62..1542730837d9e 100644
--- a/source/server/active_tcp_listener.cc
+++ b/source/server/active_tcp_listener.cc
@@ -14,28 +14,32 @@ namespace Server {
ActiveTcpListener::ActiveTcpListener(Network::TcpConnectionHandler& parent,
Network::ListenerConfig& config, Runtime::Loader& runtime,
- uint32_t worker_index)
- : OwnedActiveStreamListenerBase(parent, parent.dispatcher(),
- parent.dispatcher().createListener(
- config.listenSocketFactory().getListenSocket(worker_index),
- *this, runtime, config.bindToPort(),
- config.ignoreGlobalConnLimit()),
- config),
- tcp_conn_handler_(parent) {
- config.connectionBalancer().registerHandler(*this);
+ Network::SocketSharedPtr&& socket,
+ Network::Address::InstanceConstSharedPtr& address,
+ Network::ConnectionBalancer& connection_balancer)
+ : OwnedActiveStreamListenerBase(
+ parent, parent.dispatcher(),
+ parent.dispatcher().createListener(std::move(socket), *this, runtime, config.bindToPort(),
+ config.ignoreGlobalConnLimit()),
+ config),
+ tcp_conn_handler_(parent), connection_balancer_(connection_balancer), address_(address) {
+ connection_balancer_.registerHandler(*this);
}
ActiveTcpListener::ActiveTcpListener(Network::TcpConnectionHandler& parent,
Network::ListenerPtr&& listener,
- Network::ListenerConfig& config, Runtime::Loader&)
+ Network::Address::InstanceConstSharedPtr& address,
+ Network::ListenerConfig& config,
+ Network::ConnectionBalancer& connection_balancer,
+ Runtime::Loader&)
: OwnedActiveStreamListenerBase(parent, parent.dispatcher(), std::move(listener), config),
- tcp_conn_handler_(parent) {
- config.connectionBalancer().registerHandler(*this);
+ tcp_conn_handler_(parent), connection_balancer_(connection_balancer), address_(address) {
+ connection_balancer_.registerHandler(*this);
}
ActiveTcpListener::~ActiveTcpListener() {
is_deleting_ = true;
- config_->connectionBalancer().unregisterHandler(*this);
+ connection_balancer_.unregisterHandler(*this);
// Purge sockets that have not progressed to connections. This should only happen when
// a listener filter stops iteration and never resumes.
@@ -65,7 +69,6 @@ ActiveTcpListener::~ActiveTcpListener() {
void ActiveTcpListener::updateListenerConfig(Network::ListenerConfig& config) {
ENVOY_LOG(trace, "replacing listener ", config_->listenerTag(), " by ", config.listenerTag());
- ASSERT(&config_->connectionBalancer() == &config.connectionBalancer());
config_ = &config;
}
@@ -98,7 +101,7 @@ void ActiveTcpListener::onAcceptWorker(Network::ConnectionSocketPtr&& socket,
bool rebalanced) {
if (!rebalanced) {
Network::BalancedConnectionHandler& target_handler =
- config_->connectionBalancer().pickTargetHandler(*this);
+ connection_balancer_.pickTargetHandler(*this);
if (&target_handler != this) {
target_handler.post(std::move(socket));
return;
@@ -153,10 +156,11 @@ void ActiveTcpListener::post(Network::ConnectionSocketPtr&& socket) {
RebalancedSocketSharedPtr socket_to_rebalance = std::make_shared();
socket_to_rebalance->socket = std::move(socket);
- dispatcher().post([socket_to_rebalance, tag = config_->listenerTag(),
+ dispatcher().post([socket_to_rebalance, address = address_, tag = config_->listenerTag(),
&tcp_conn_handler = tcp_conn_handler_,
handoff = config_->handOffRestoredDestinationConnections()]() {
- auto balanced_handler = tcp_conn_handler.getBalancedHandlerByTag(tag);
+ // `getBalancedHandlerByAddress` only support the IP address but there can be unix socket also.
+ auto balanced_handler = tcp_conn_handler.getBalancedHandlerByTag(tag, *address);
if (balanced_handler.has_value()) {
balanced_handler->get().onAcceptWorker(std::move(socket_to_rebalance->socket), handoff, true);
return;
diff --git a/source/server/active_tcp_listener.h b/source/server/active_tcp_listener.h
index ba3fc15fbdb63..f0a0ae49bd4a9 100644
--- a/source/server/active_tcp_listener.h
+++ b/source/server/active_tcp_listener.h
@@ -27,9 +27,13 @@ class ActiveTcpListener final : public Network::TcpListenerCallbacks,
public Network::BalancedConnectionHandler {
public:
ActiveTcpListener(Network::TcpConnectionHandler& parent, Network::ListenerConfig& config,
- Runtime::Loader& runtime, uint32_t worker_index);
+ Runtime::Loader& runtime, Network::SocketSharedPtr&& socket,
+ Network::Address::InstanceConstSharedPtr& address,
+ Network::ConnectionBalancer& connection_balancer);
ActiveTcpListener(Network::TcpConnectionHandler& parent, Network::ListenerPtr&& listener,
- Network::ListenerConfig& config, Runtime::Loader& runtime);
+ Network::Address::InstanceConstSharedPtr& address,
+ Network::ListenerConfig& config,
+ Network::ConnectionBalancer& connection_balancer, Runtime::Loader& runtime);
~ActiveTcpListener() override;
bool listenerConnectionLimitReached() const {
@@ -81,6 +85,9 @@ class ActiveTcpListener final : public Network::TcpListenerCallbacks,
// The number of connections currently active on this listener. This is typically used for
// connection balancing across per-handler listeners.
std::atomic num_listener_connections_{};
+
+ Network::ConnectionBalancer& connection_balancer_;
+ Network::Address::InstanceConstSharedPtr address_;
};
using ActiveTcpListenerOptRef = absl::optional>;
diff --git a/source/server/active_udp_listener.cc b/source/server/active_udp_listener.cc
index e0d4c64f73f89..ed5195be40055 100644
--- a/source/server/active_udp_listener.cc
+++ b/source/server/active_udp_listener.cc
@@ -18,13 +18,15 @@ ActiveUdpListenerBase::ActiveUdpListenerBase(uint32_t worker_index, uint32_t con
: ActiveListenerImplBase(parent, config), worker_index_(worker_index),
concurrency_(concurrency), parent_(parent), listen_socket_(listen_socket),
udp_listener_(std::move(listener)),
- udp_stats_({ALL_UDP_LISTENER_STATS(POOL_COUNTER_PREFIX(config->listenerScope(), "udp"))}) {
+ udp_stats_({ALL_UDP_LISTENER_STATS(POOL_COUNTER_PREFIX(config->listenerScope(), "udp"))}),
+ udp_listener_worker_router_(config_->udpListenerConfig()->listenerWorkerRouter(
+ *listen_socket.connectionInfoProvider().localAddress())) {
ASSERT(worker_index_ < concurrency_);
- config_->udpListenerConfig()->listenerWorkerRouter().registerWorkerForListener(*this);
+ udp_listener_worker_router_.registerWorkerForListener(*this);
}
ActiveUdpListenerBase::~ActiveUdpListenerBase() {
- config_->udpListenerConfig()->listenerWorkerRouter().unregisterWorkerForListener(*this);
+ udp_listener_worker_router_.unregisterWorkerForListener(*this);
}
void ActiveUdpListenerBase::post(Network::UdpRecvData&& data) {
@@ -38,13 +40,14 @@ void ActiveUdpListenerBase::post(Network::UdpRecvData&& data) {
auto data_to_post = std::make_shared();
*data_to_post = std::move(data);
- udp_listener_->dispatcher().post(
- [data_to_post, tag = config_->listenerTag(), &parent = parent_]() {
- Network::UdpListenerCallbacksOptRef listener = parent.getUdpListenerCallbacks(tag);
- if (listener.has_value()) {
- listener->get().onDataWorker(std::move(*data_to_post));
- }
- });
+ auto address = listen_socket_.connectionInfoProvider().localAddress();
+ udp_listener_->dispatcher().post([data_to_post, tag = config_->listenerTag(), &parent = parent_,
+ address]() {
+ Network::UdpListenerCallbacksOptRef listener = parent.getUdpListenerCallbacks(tag, *address);
+ if (listener.has_value()) {
+ listener->get().onDataWorker(std::move(*data_to_post));
+ }
+ });
}
void ActiveUdpListenerBase::onData(Network::UdpRecvData&& data) {
@@ -59,18 +62,10 @@ void ActiveUdpListenerBase::onData(Network::UdpRecvData&& data) {
if (dest == worker_index_) {
onDataWorker(std::move(data));
} else {
- config_->udpListenerConfig()->listenerWorkerRouter().deliver(dest, std::move(data));
+ udp_listener_worker_router_.deliver(dest, std::move(data));
}
}
-ActiveRawUdpListener::ActiveRawUdpListener(uint32_t worker_index, uint32_t concurrency,
- Network::UdpConnectionHandler& parent,
- Event::Dispatcher& dispatcher,
- Network::ListenerConfig& config)
- : ActiveRawUdpListener(worker_index, concurrency, parent,
- config.listenSocketFactory().getListenSocket(worker_index), dispatcher,
- config) {}
-
ActiveRawUdpListener::ActiveRawUdpListener(uint32_t worker_index, uint32_t concurrency,
Network::UdpConnectionHandler& parent,
Network::SocketSharedPtr listen_socket_ptr,
diff --git a/source/server/active_udp_listener.h b/source/server/active_udp_listener.h
index dff9406c44366..9691d66825d6a 100644
--- a/source/server/active_udp_listener.h
+++ b/source/server/active_udp_listener.h
@@ -55,6 +55,7 @@ class ActiveUdpListenerBase : public ActiveListenerImplBase,
Network::Socket& listen_socket_;
Network::UdpListenerPtr udp_listener_;
UdpListenerStats udp_stats_;
+ Network::UdpListenerWorkerRouter& udp_listener_worker_router_;
};
/**
@@ -65,9 +66,6 @@ class ActiveRawUdpListener : public ActiveUdpListenerBase,
public Network::UdpReadFilterCallbacks,
Logger::Loggable {
public:
- ActiveRawUdpListener(uint32_t worker_index, uint32_t concurrency,
- Network::UdpConnectionHandler& parent, Event::Dispatcher& dispatcher,
- Network::ListenerConfig& config);
ActiveRawUdpListener(uint32_t worker_index, uint32_t concurrency,
Network::UdpConnectionHandler& parent,
Network::SocketSharedPtr listen_socket_ptr, Event::Dispatcher& dispatcher,
diff --git a/source/server/admin/admin.cc b/source/server/admin/admin.cc
index 629b8fcda0171..9efe33c00a191 100644
--- a/source/server/admin/admin.cc
+++ b/source/server/admin/admin.cc
@@ -128,7 +128,7 @@ void AdminImpl::startHttpListener(const std::list&
socket_ = std::make_shared(address, socket_options, true);
RELEASE_ASSERT(0 == socket_->ioHandle().listen(ENVOY_TCP_BACKLOG_SIZE).return_value_,
"listen() failed on admin listener");
- socket_factory_ = std::make_unique(socket_);
+ socket_factories_.emplace_back(std::make_unique(socket_));
listener_ = std::make_unique(*this, std::move(listener_scope));
ENVOY_LOG(info, "admin address: {}",
socket().connectionInfoProvider().localAddress()->asString());
diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h
index 151d10ba6eee9..b09df29494840 100644
--- a/source/server/admin/admin.h
+++ b/source/server/admin/admin.h
@@ -389,10 +389,10 @@ class AdminImpl : public Admin,
// Network::ListenerConfig
Network::FilterChainManager& filterChainManager() override { return parent_; }
Network::FilterChainFactory& filterChainFactory() override { return parent_; }
- Network::ListenSocketFactory& listenSocketFactory() override {
- return *parent_.socket_factory_;
+ const std::vector& listenSocketFactories() override {
+ return parent_.socket_factories_;
}
- bool bindToPort() override { return true; }
+ bool bindToPort() const override { return true; }
bool handOffRestoredDestinationConnections() const override { return false; }
uint32_t perConnectionBufferLimitBytes() const override { return 0; }
std::chrono::milliseconds listenerFiltersTimeout() const override { return {}; }
@@ -409,7 +409,9 @@ class AdminImpl : public Admin,
envoy::config::core::v3::TrafficDirection direction() const override {
return envoy::config::core::v3::UNSPECIFIED;
}
- Network::ConnectionBalancer& connectionBalancer() override { return connection_balancer_; }
+ Network::ConnectionBalancer& connectionBalancer(const Network::Address::Instance&) override {
+ return connection_balancer_;
+ }
ResourceLimit& openConnections() override { return open_connections_; }
const std::vector& accessLogs() const override {
return empty_access_logs_;
@@ -495,7 +497,7 @@ class AdminImpl : public Admin,
ConfigTrackerImpl config_tracker_;
const Network::FilterChainSharedPtr admin_filter_chain_;
Network::SocketSharedPtr socket_;
- Network::ListenSocketFactoryPtr socket_factory_;
+ std::vector socket_factories_;
AdminListenerPtr listener_;
const AdminInternalAddressConfig internal_address_config_;
const LocalReply::LocalReplyPtr local_reply_;
diff --git a/source/server/admin/listeners_handler.cc b/source/server/admin/listeners_handler.cc
index 9e6ddbbc746bc..e588132be8144 100644
--- a/source/server/admin/listeners_handler.cc
+++ b/source/server/admin/listeners_handler.cc
@@ -57,16 +57,20 @@ void ListenersHandler::writeListenersAsJson(Buffer::Instance& response) {
for (const auto& listener : server_.listenerManager().listeners()) {
envoy::admin::v3::ListenerStatus& listener_status = *listeners.add_listener_statuses();
listener_status.set_name(listener.get().name());
- Network::Utility::addressToProtobufAddress(*listener.get().listenSocketFactory().localAddress(),
- *listener_status.mutable_local_address());
+ for (auto& socket_factory : listener.get().listenSocketFactories()) {
+ auto address = listener_status.add_local_addresses();
+ Network::Utility::addressToProtobufAddress(*socket_factory->localAddress(), *address);
+ }
}
response.add(MessageUtil::getJsonStringFromMessageOrError(listeners, true)); // pretty-print
}
void ListenersHandler::writeListenersAsText(Buffer::Instance& response) {
for (const auto& listener : server_.listenerManager().listeners()) {
- response.add(fmt::format("{}::{}\n", listener.get().name(),
- listener.get().listenSocketFactory().localAddress()->asString()));
+ for (auto& socket_factory : listener.get().listenSocketFactories()) {
+ response.add(fmt::format("{}::{}\n", listener.get().name(),
+ socket_factory->localAddress()->asString()));
+ }
}
}
diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc
index 94699da0ed53a..448d2cd3eab28 100644
--- a/source/server/connection_handler_impl.cc
+++ b/source/server/connection_handler_impl.cc
@@ -36,12 +36,16 @@ void ConnectionHandlerImpl::addListener(absl::optional overridden_list
ActiveListenerDetailsOptRef listener_detail =
findActiveListenerByTag(overridden_listener.value());
ASSERT(listener_detail.has_value());
- listener_detail->get().listener_->updateListenerConfig(config);
+ listener_detail->get().invokeListenerMethod(
+ [&config](Network::ConnectionHandler::ActiveListener& listener) {
+ listener.updateListenerConfig(config);
+ });
return;
}
- auto details = std::make_shared();
+ auto details = std::make_unique();
if (config.internalListenerConfig().has_value()) {
+ auto pre_address_details = std::make_shared();
// Ensure the this ConnectionHandlerImpl link to the thread local registry. Ideally this step
// should be done only once. However, an extra phase and interface is overkill.
Network::InternalListenerRegistry& internal_listener_registry =
@@ -53,90 +57,99 @@ void ConnectionHandlerImpl::addListener(absl::optional overridden_list
if (overridden_listener.has_value()) {
if (auto iter = listener_map_by_tag_.find(overridden_listener.value());
iter != listener_map_by_tag_.end()) {
- iter->second->internalListener()->get().updateListenerConfig(config);
+ iter->second->invokeListenerMethod(
+ [&config](Network::ConnectionHandler::ActiveListener& listener) {
+ listener.updateListenerConfig(config);
+ });
return;
}
IS_ENVOY_BUG("unexpected");
}
auto internal_listener = std::make_unique(*this, dispatcher(), config);
- details->typed_listener_ = *internal_listener;
- details->listener_ = std::move(internal_listener);
- } else if (config.listenSocketFactory().socketType() == Network::Socket::Type::Stream) {
+ // The internal address doesn't support multiple addresses.
+ ASSERT(config.listenSocketFactories().size() == 1);
+ details->addActiveListener(config, config.listenSocketFactories()[0]->localAddress(),
+ listener_reject_fraction_, disable_listeners_,
+ std::move(internal_listener));
+ } else if (config.listenSocketFactories()[0]->socketType() == Network::Socket::Type::Stream) {
if (!support_udp_in_place_filter_chain_update && overridden_listener.has_value()) {
if (auto iter = listener_map_by_tag_.find(overridden_listener.value());
iter != listener_map_by_tag_.end()) {
- iter->second->tcpListener()->get().updateListenerConfig(config);
+ iter->second->invokeListenerMethod(
+ [&config](Network::ConnectionHandler::ActiveListener& listener) {
+ listener.updateListenerConfig(config);
+ });
return;
}
IS_ENVOY_BUG("unexpected");
}
- // worker_index_ doesn't have a value on the main thread for the admin server.
- auto tcp_listener = std::make_unique(
- *this, config, runtime, worker_index_.has_value() ? *worker_index_ : 0);
- details->typed_listener_ = *tcp_listener;
- details->listener_ = std::move(tcp_listener);
+ for (auto& socket_factory : config.listenSocketFactories()) {
+ auto address = socket_factory->localAddress();
+ // worker_index_ doesn't have a value on the main thread for the admin server.
+ details->addActiveListener(
+ config, address, listener_reject_fraction_, disable_listeners_,
+ std::make_unique(
+ *this, config, runtime,
+ socket_factory->getListenSocket(worker_index_.has_value() ? *worker_index_ : 0),
+ address, config.connectionBalancer(*address)));
+ }
} else {
ASSERT(config.udpListenerConfig().has_value(), "UDP listener factory is not initialized.");
ASSERT(worker_index_.has_value());
- ConnectionHandler::ActiveUdpListenerPtr udp_listener =
- config.udpListenerConfig()->listenerFactory().createActiveUdpListener(
- runtime, *worker_index_, *this, dispatcher_, config);
- details->typed_listener_ = *udp_listener;
- details->listener_ = std::move(udp_listener);
- }
-
- if (disable_listeners_) {
- details->listener_->pauseListening();
- }
- if (auto* listener = details->listener_->listener(); listener != nullptr) {
- listener->setRejectFraction(listener_reject_fraction_);
+ for (auto& socket_factory : config.listenSocketFactories()) {
+ auto address = socket_factory->localAddress();
+ details->addActiveListener(
+ config, address, listener_reject_fraction_, disable_listeners_,
+ config.udpListenerConfig()->listenerFactory().createActiveUdpListener(
+ runtime, *worker_index_, *this, socket_factory->getListenSocket(*worker_index_),
+ dispatcher_, config));
+ }
}
- details->listener_tag_ = config.listenerTag();
- details->address_ = config.listenSocketFactory().localAddress();
-
ASSERT(!listener_map_by_tag_.contains(config.listenerTag()));
- listener_map_by_tag_.emplace(config.listenerTag(), details);
- // This map only store the new listener.
- if (absl::holds_alternative>(
- details->typed_listener_)) {
- tcp_listener_map_by_address_.insert_or_assign(
- config.listenSocketFactory().localAddress()->asStringView(), details);
-
- auto& address = details->address_;
- // If the address is Ipv6 and isn't v6only, parse out the ipv4 compatible address from the Ipv6
- // address and put an item to the map. Then this allows the `getBalancedHandlerByAddress`
- // can match the Ipv4 request to Ipv4-mapped address also.
- if (address->type() == Network::Address::Type::Ip &&
- address->ip()->version() == Network::Address::IpVersion::v6 &&
- !address->ip()->ipv6()->v6only()) {
- if (address->ip()->isAnyAddress()) {
- // Since both "::" with ipv4_compat and "0.0.0.0" can be supported.
- // If there already one and it isn't shutdown for compatible addr,
- // then won't insert a new one.
- auto ipv4_any_address = Network::Address::Ipv4Instance(address->ip()->port()).asString();
- auto ipv4_any_listener = tcp_listener_map_by_address_.find(ipv4_any_address);
- if (ipv4_any_listener == tcp_listener_map_by_address_.end() ||
- ipv4_any_listener->second->listener_->listener() == nullptr) {
- tcp_listener_map_by_address_.insert_or_assign(ipv4_any_address, details);
- }
- } else {
- auto v4_compatible_addr = address->ip()->ipv6()->v4CompatibleAddress();
- // Remove this check when runtime flag
- // `envoy.reloadable_features.strict_check_on_ipv4_compat` deprecated.
- // If this isn't a valid Ipv4-mapped address, then do nothing.
- if (v4_compatible_addr != nullptr) {
- tcp_listener_map_by_address_.insert_or_assign(v4_compatible_addr->asStringView(),
- details);
+ for (auto& per_address_details : details->per_address_details_) {
+ // This map only store the new listener.
+ if (absl::holds_alternative>(
+ per_address_details->typed_listener_)) {
+ tcp_listener_map_by_address_.insert_or_assign(per_address_details->address_->asStringView(),
+ per_address_details);
+
+ auto& address = per_address_details->address_;
+ // If the address is Ipv6 and isn't v6only, parse out the ipv4 compatible address from the
+ // Ipv6 address and put an item to the map. Then this allows the `getBalancedHandlerByAddress`
+ // can match the Ipv4 request to Ipv4-mapped address also.
+ if (address->type() == Network::Address::Type::Ip &&
+ address->ip()->version() == Network::Address::IpVersion::v6 &&
+ !address->ip()->ipv6()->v6only()) {
+ if (address->ip()->isAnyAddress()) {
+ // Since both "::" with ipv4_compat and "0.0.0.0" can be supported.
+ // If there already one and it isn't shutdown for compatible addr,
+ // then won't insert a new one.
+ auto ipv4_any_address = Network::Address::Ipv4Instance(address->ip()->port()).asString();
+ auto ipv4_any_listener = tcp_listener_map_by_address_.find(ipv4_any_address);
+ if (ipv4_any_listener == tcp_listener_map_by_address_.end() ||
+ ipv4_any_listener->second->listener_->listener() == nullptr) {
+ tcp_listener_map_by_address_.insert_or_assign(ipv4_any_address, per_address_details);
+ }
+ } else {
+ auto v4_compatible_addr = address->ip()->ipv6()->v4CompatibleAddress();
+ // Remove this check when runtime flag
+ // `envoy.reloadable_features.strict_check_on_ipv4_compat` deprecated.
+ // If this isn't a valid Ipv4-mapped address, then do nothing.
+ if (v4_compatible_addr != nullptr) {
+ tcp_listener_map_by_address_.insert_or_assign(v4_compatible_addr->asStringView(),
+ per_address_details);
+ }
}
}
+ } else if (absl::holds_alternative>(
+ per_address_details->typed_listener_)) {
+ internal_listener_map_by_address_.insert_or_assign(
+ per_address_details->address_->asStringView(), per_address_details);
}
- } else if (absl::holds_alternative>(
- details->typed_listener_)) {
- internal_listener_map_by_address_.insert_or_assign(
- config.listenSocketFactory().localAddress()->asStringView(), details);
}
+ listener_map_by_tag_.emplace(config.listenerTag(), std::move(details));
}
void ConnectionHandlerImpl::removeListeners(uint64_t listener_tag) {
@@ -144,54 +157,61 @@ void ConnectionHandlerImpl::removeListeners(uint64_t listener_tag) {
listener_iter != listener_map_by_tag_.end()) {
// listener_map_by_address_ may already update to the new listener. Compare it with the one
// which find from listener_map_by_tag_, only delete it when it is same listener.
- auto& address = listener_iter->second->address_;
- auto address_view = address->asStringView();
- if (tcp_listener_map_by_address_.contains(address_view) &&
- tcp_listener_map_by_address_[address_view]->listener_tag_ ==
- listener_iter->second->listener_tag_) {
- tcp_listener_map_by_address_.erase(address_view);
-
- // If the address is Ipv6 and isn't v6only, delete the corresponding Ipv4 item from the map.
- if (address->type() == Network::Address::Type::Ip &&
- address->ip()->version() == Network::Address::IpVersion::v6 &&
- !address->ip()->ipv6()->v6only()) {
- if (address->ip()->isAnyAddress()) {
- auto ipv4_any_addr_iter = tcp_listener_map_by_address_.find(
- Network::Address::Ipv4Instance(address->ip()->port()).asStringView());
- // Since both "::" with ipv4_compat and "0.0.0.0" can be supported, ensure they are same
- // listener by tag.
- if (ipv4_any_addr_iter != tcp_listener_map_by_address_.end() &&
- ipv4_any_addr_iter->second->listener_tag_ == listener_iter->second->listener_tag_) {
- tcp_listener_map_by_address_.erase(ipv4_any_addr_iter);
- }
- } else {
- auto v4_compatible_addr = address->ip()->ipv6()->v4CompatibleAddress();
- // Remove this check when runtime flag
- // `envoy.reloadable_features.strict_check_on_ipv4_compat` deprecated.
- if (v4_compatible_addr != nullptr) {
- // both "::FFFF:" with ipv4_compat and "" isn't valid case,
- // remove the v4 compatible addr item directly.
- tcp_listener_map_by_address_.erase(v4_compatible_addr->asStringView());
+ for (auto& per_address_details : listener_iter->second->per_address_details_) {
+ auto& address = per_address_details->address_;
+ auto address_view = address->asStringView();
+ if (tcp_listener_map_by_address_.contains(address_view) &&
+ tcp_listener_map_by_address_[address_view]->listener_tag_ ==
+ per_address_details->listener_tag_) {
+ tcp_listener_map_by_address_.erase(address_view);
+
+ // If the address is Ipv6 and isn't v6only, delete the corresponding Ipv4 item from the map.
+ if (address->type() == Network::Address::Type::Ip &&
+ address->ip()->version() == Network::Address::IpVersion::v6 &&
+ !address->ip()->ipv6()->v6only()) {
+ if (address->ip()->isAnyAddress()) {
+ auto ipv4_any_addr_iter = tcp_listener_map_by_address_.find(
+ Network::Address::Ipv4Instance(address->ip()->port()).asStringView());
+ // Since both "::" with ipv4_compat and "0.0.0.0" can be supported, ensure they are same
+ // listener by tag.
+ if (ipv4_any_addr_iter != tcp_listener_map_by_address_.end() &&
+ ipv4_any_addr_iter->second->listener_tag_ == per_address_details->listener_tag_) {
+ tcp_listener_map_by_address_.erase(ipv4_any_addr_iter);
+ }
+ } else {
+ auto v4_compatible_addr = address->ip()->ipv6()->v4CompatibleAddress();
+ // Remove this check when runtime flag
+ // `envoy.reloadable_features.strict_check_on_ipv4_compat` deprecated.
+ if (v4_compatible_addr != nullptr) {
+ // both "::FFFF:" with ipv4_compat and "" isn't valid case,
+ // remove the v4 compatible addr item directly.
+ tcp_listener_map_by_address_.erase(v4_compatible_addr->asStringView());
+ }
}
}
+ } else if (internal_listener_map_by_address_.contains(address_view) &&
+ internal_listener_map_by_address_[address_view]->listener_tag_ ==
+ per_address_details->listener_tag_) {
+ internal_listener_map_by_address_.erase(address_view);
}
- } else if (internal_listener_map_by_address_.contains(address_view) &&
- internal_listener_map_by_address_[address_view]->listener_tag_ ==
- listener_iter->second->listener_tag_) {
- internal_listener_map_by_address_.erase(address_view);
}
listener_map_by_tag_.erase(listener_iter);
}
}
Network::UdpListenerCallbacksOptRef
-ConnectionHandlerImpl::getUdpListenerCallbacks(uint64_t listener_tag) {
+ConnectionHandlerImpl::getUdpListenerCallbacks(uint64_t listener_tag,
+ const Network::Address::Instance& address) {
auto listener = findActiveListenerByTag(listener_tag);
if (listener.has_value()) {
// If the tag matches this must be a UDP listener.
- auto udp_listener = listener->get().udpListener();
- ASSERT(udp_listener.has_value());
- return udp_listener;
+ for (auto& details : listener->get().per_address_details_) {
+ if (*details->address_ == address) {
+ auto udp_listener = details->udpListener();
+ ASSERT(udp_listener.has_value());
+ return details->udpListener();
+ }
+ }
}
return absl::nullopt;
@@ -202,7 +222,10 @@ void ConnectionHandlerImpl::removeFilterChains(
std::function completion) {
if (auto listener_it = listener_map_by_tag_.find(listener_tag);
listener_it != listener_map_by_tag_.end()) {
- listener_it->second->listener_->onFilterChainDraining(filter_chains);
+ listener_it->second->invokeListenerMethod(
+ [&filter_chains](Network::ConnectionHandler::ActiveListener& listener) {
+ listener.onFilterChainDraining(filter_chains);
+ });
}
// Reach here if the target listener is found or the target listener was removed by a full
@@ -213,44 +236,55 @@ void ConnectionHandlerImpl::removeFilterChains(
void ConnectionHandlerImpl::stopListeners(uint64_t listener_tag) {
if (auto iter = listener_map_by_tag_.find(listener_tag); iter != listener_map_by_tag_.end()) {
- if (iter->second->listener_->listener() != nullptr) {
- iter->second->listener_->shutdownListener();
- }
+ iter->second->invokeListenerMethod([](Network::ConnectionHandler::ActiveListener& listener) {
+ if (listener.listener() != nullptr) {
+ listener.shutdownListener();
+ }
+ });
}
}
void ConnectionHandlerImpl::stopListeners() {
for (auto& iter : listener_map_by_tag_) {
- if (iter.second->listener_->listener() != nullptr) {
- iter.second->listener_->shutdownListener();
- }
+ iter.second->invokeListenerMethod([](Network::ConnectionHandler::ActiveListener& listener) {
+ if (listener.listener() != nullptr) {
+ listener.shutdownListener();
+ }
+ });
}
}
void ConnectionHandlerImpl::disableListeners() {
disable_listeners_ = true;
for (auto& iter : listener_map_by_tag_) {
- if (iter.second->listener_->listener() != nullptr) {
- iter.second->listener_->pauseListening();
- }
+ iter.second->invokeListenerMethod([](Network::ConnectionHandler::ActiveListener& listener) {
+ if (listener.listener() != nullptr) {
+ listener.pauseListening();
+ }
+ });
}
}
void ConnectionHandlerImpl::enableListeners() {
disable_listeners_ = false;
for (auto& iter : listener_map_by_tag_) {
- if (iter.second->listener_->listener() != nullptr) {
- iter.second->listener_->resumeListening();
- }
+ iter.second->invokeListenerMethod([](Network::ConnectionHandler::ActiveListener& listener) {
+ if (listener.listener() != nullptr) {
+ listener.resumeListening();
+ }
+ });
}
}
void ConnectionHandlerImpl::setListenerRejectFraction(UnitFloat reject_fraction) {
listener_reject_fraction_ = reject_fraction;
for (auto& iter : listener_map_by_tag_) {
- if (iter.second->listener_->listener() != nullptr) {
- iter.second->listener_->listener()->setRejectFraction(reject_fraction);
- }
+ iter.second->invokeListenerMethod(
+ [&reject_fraction](Network::ConnectionHandler::ActiveListener& listener) {
+ if (listener.listener() != nullptr) {
+ listener.listener()->setRejectFraction(reject_fraction);
+ }
+ });
}
}
@@ -265,19 +299,19 @@ ConnectionHandlerImpl::findByAddress(const Network::Address::InstanceConstShared
}
ConnectionHandlerImpl::ActiveTcpListenerOptRef
-ConnectionHandlerImpl::ActiveListenerDetails::tcpListener() {
+ConnectionHandlerImpl::PerAddressActiveListenerDetails::tcpListener() {
auto* val = absl::get_if>(&typed_listener_);
return (val != nullptr) ? absl::make_optional(*val) : absl::nullopt;
}
ConnectionHandlerImpl::UdpListenerCallbacksOptRef
-ConnectionHandlerImpl::ActiveListenerDetails::udpListener() {
+ConnectionHandlerImpl::PerAddressActiveListenerDetails::udpListener() {
auto* val = absl::get_if>(&typed_listener_);
return (val != nullptr) ? absl::make_optional(*val) : absl::nullopt;
}
ConnectionHandlerImpl::ActiveInternalListenerOptRef
-ConnectionHandlerImpl::ActiveListenerDetails::internalListener() {
+ConnectionHandlerImpl::PerAddressActiveListenerDetails::internalListener() {
auto* val = absl::get_if>(&typed_listener_);
return (val != nullptr) ? absl::make_optional(*val) : absl::nullopt;
}
@@ -291,14 +325,18 @@ ConnectionHandlerImpl::findActiveListenerByTag(uint64_t listener_tag) {
}
Network::BalancedConnectionHandlerOptRef
-ConnectionHandlerImpl::getBalancedHandlerByTag(uint64_t listener_tag) {
+ConnectionHandlerImpl::getBalancedHandlerByTag(uint64_t listener_tag,
+ const Network::Address::Instance& address) {
auto active_listener = findActiveListenerByTag(listener_tag);
if (active_listener.has_value()) {
- ASSERT(absl::holds_alternative>(
- active_listener->get().typed_listener_) &&
- active_listener->get().listener_->listener() != nullptr);
- return Network::BalancedConnectionHandlerOptRef(
- active_listener->get().tcpListener().value().get());
+ for (auto& details : active_listener->get().per_address_details_) {
+ if (*details->address_ == address) {
+ ASSERT(absl::holds_alternative>(
+ details->typed_listener_) &&
+ details->listener_->listener() != nullptr);
+ return {details->tcpListener().value().get()};
+ }
+ }
}
return absl::nullopt;
}
@@ -313,11 +351,10 @@ ConnectionHandlerImpl::getBalancedHandlerByAddress(const Network::Address::Insta
if (auto listener_it = tcp_listener_map_by_address_.find(address.asStringView());
listener_it != tcp_listener_map_by_address_.end() &&
listener_it->second->listener_->listener() != nullptr) {
- return Network::BalancedConnectionHandlerOptRef(
- listener_it->second->tcpListener().value().get());
+ return {listener_it->second->tcpListener().value().get()};
}
- OptRef details;
+ OptRef details;
// 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.
diff --git a/source/server/connection_handler_impl.h b/source/server/connection_handler_impl.h
index 52a365ce7c689..4acc71cdca851 100644
--- a/source/server/connection_handler_impl.h
+++ b/source/server/connection_handler_impl.h
@@ -60,19 +60,23 @@ class ConnectionHandlerImpl : public Network::TcpConnectionHandler,
// Network::TcpConnectionHandler
Event::Dispatcher& dispatcher() override { return dispatcher_; }
- Network::BalancedConnectionHandlerOptRef getBalancedHandlerByTag(uint64_t listener_tag) override;
+ Network::BalancedConnectionHandlerOptRef
+ getBalancedHandlerByTag(uint64_t listener_tag,
+ const Network::Address::Instance& address) override;
Network::BalancedConnectionHandlerOptRef
getBalancedHandlerByAddress(const Network::Address::Instance& address) override;
// Network::UdpConnectionHandler
- Network::UdpListenerCallbacksOptRef getUdpListenerCallbacks(uint64_t listener_tag) override;
+ Network::UdpListenerCallbacksOptRef
+ getUdpListenerCallbacks(uint64_t listener_tag,
+ const Network::Address::Instance& address) override;
// Network::InternalListenerManager
Network::InternalListenerOptRef
findByAddress(const Network::Address::InstanceConstSharedPtr& listen_address) override;
private:
- struct ActiveListenerDetails {
+ struct PerAddressActiveListenerDetails {
// Strong pointer to the listener, whether TCP, UDP, QUIC, etc.
Network::ConnectionHandler::ActiveListenerPtr listener_;
Network::Address::InstanceConstSharedPtr address_;
@@ -88,6 +92,39 @@ class ConnectionHandlerImpl : public Network::TcpConnectionHandler,
UdpListenerCallbacksOptRef udpListener();
ActiveInternalListenerOptRef internalListener();
};
+
+ struct ActiveListenerDetails {
+ std::vector> per_address_details_;
+
+ using ListenerMethodFn = std::function;
+
+ void invokeListenerMethod(ListenerMethodFn fn) {
+ std::for_each(per_address_details_.begin(), per_address_details_.end(),
+ [&fn](std::shared_ptr& details) {
+ fn(*details->listener_);
+ });
+ }
+
+ template
+ void addActiveListener(Network::ListenerConfig& config,
+ const Network::Address::InstanceConstSharedPtr& address,
+ UnitFloat& listener_reject_fraction, bool disable_listeners,
+ ActiveListener&& listener) {
+ auto pre_address_details = std::make_shared();
+ pre_address_details->typed_listener_ = *listener;
+ pre_address_details->listener_ = std::move(listener);
+ pre_address_details->address_ = address;
+ if (disable_listeners) {
+ pre_address_details->listener_->pauseListening();
+ }
+ if (auto* listener = pre_address_details->listener_->listener(); listener != nullptr) {
+ listener->setRejectFraction(listener_reject_fraction);
+ }
+ pre_address_details->listener_tag_ = config.listenerTag();
+ per_address_details_.emplace_back(pre_address_details);
+ }
+ };
+
using ActiveListenerDetailsOptRef = absl::optional>;
ActiveListenerDetailsOptRef findActiveListenerByTag(uint64_t listener_tag);
@@ -95,10 +132,10 @@ class ConnectionHandlerImpl : public Network::TcpConnectionHandler,
const absl::optional worker_index_;
Event::Dispatcher& dispatcher_;
const std::string per_handler_stat_prefix_;
- absl::flat_hash_map> listener_map_by_tag_;
- absl::flat_hash_map>
+ absl::flat_hash_map> listener_map_by_tag_;
+ absl::flat_hash_map>
tcp_listener_map_by_address_;
- absl::flat_hash_map>
+ absl::flat_hash_map>
internal_listener_map_by_address_;
std::atomic num_handler_connections_{};
diff --git a/source/server/filter_chain_manager_impl.cc b/source/server/filter_chain_manager_impl.cc
index f8b12bc525873..6dde01909da41 100644
--- a/source/server/filter_chain_manager_impl.cc
+++ b/source/server/filter_chain_manager_impl.cc
@@ -10,6 +10,7 @@
#include "source/common/network/matching/data_impl.h"
#include "source/common/network/matching/inputs.h"
#include "source/common/network/socket_interface.h"
+#include "source/common/network/utility.h"
#include "source/common/protobuf/utility.h"
#include "source/server/configuration_impl.h"
@@ -194,10 +195,10 @@ bool PerFilterChainFactoryContextImpl::isQuicListener() const {
}
FilterChainManagerImpl::FilterChainManagerImpl(
- const Network::Address::InstanceConstSharedPtr& address,
+ const std::vector& addresses,
Configuration::FactoryContext& factory_context, Init::Manager& init_manager,
const FilterChainManagerImpl& parent_manager)
- : address_(address), parent_context_(factory_context), origin_(&parent_manager),
+ : addresses_(addresses), parent_context_(factory_context), origin_(&parent_manager),
init_manager_(init_manager) {}
bool FilterChainManagerImpl::isWildcardServerName(const std::string& name) {
@@ -220,17 +221,19 @@ void FilterChainManagerImpl::addFilterChains(
for (const auto& filter_chain : filter_chain_span) {
const auto& filter_chain_match = filter_chain->filter_chain_match();
if (!filter_chain_match.address_suffix().empty() || filter_chain_match.has_suffix_len()) {
- throw EnvoyException(fmt::format("error adding listener '{}': filter chain '{}' contains "
- "unimplemented fields",
- address_->asString(), filter_chain->name()));
+ throw EnvoyException(fmt::format(
+ "error adding listener '{}': filter chain '{}' contains "
+ "unimplemented fields",
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter()), filter_chain->name()));
}
if (!filter_chain_matcher) {
const auto& matching_iter = filter_chains.find(filter_chain_match);
if (matching_iter != filter_chains.end()) {
- throw EnvoyException(fmt::format("error adding listener '{}': filter chain '{}' has "
- "the same matching rules defined as '{}'",
- address_->asString(), filter_chain->name(),
- matching_iter->second));
+ throw EnvoyException(
+ fmt::format("error adding listener '{}': filter chain '{}' has "
+ "the same matching rules defined as '{}'",
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter()),
+ filter_chain->name(), matching_iter->second));
}
filter_chains.insert({filter_chain_match, filter_chain->name()});
}
@@ -250,14 +253,14 @@ void FilterChainManagerImpl::addFilterChains(
if (filter_chain->name().empty()) {
throw EnvoyException(fmt::format(
"error adding listener '{}': \"name\" field is required when using a listener matcher",
- address_->asString()));
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
}
auto [_, inserted] =
filter_chains_by_name.try_emplace(filter_chain->name(), filter_chain_impl);
if (!inserted) {
- throw EnvoyException(
- fmt::format("error adding listener '{}': \"name\" field is duplicated with value '{}'",
- address_->asString(), filter_chain->name()));
+ throw EnvoyException(fmt::format(
+ "error adding listener '{}': \"name\" field is duplicated with value '{}'",
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter()), filter_chain->name()));
}
if (filter_chain->has_filter_chain_match()) {
ENVOY_LOG(debug, "filter chain match in chain '{}' is ignored", filter_chain->name());
@@ -288,7 +291,7 @@ void FilterChainManagerImpl::addFilterChains(
throw EnvoyException(
fmt::format("error adding listener '{}': partial wildcards are not supported in "
"\"server_names\"",
- address_->asString()));
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
}
server_names.push_back(absl::AsciiStrToLower(server_name));
}
@@ -512,9 +515,10 @@ void FilterChainManagerImpl::addFilterChainForSourcePorts(
// If we got here and found already configured branch, then it means that this FilterChainMatch
// is a duplicate, and that there is some overlap in the repeated fields with already processed
// FilterChainMatches.
- throw EnvoyException(fmt::format("error adding listener '{}': multiple filter chains with "
- "overlapping matching rules are defined",
- address_->asString()));
+ throw EnvoyException(
+ fmt::format("error adding listener '{}': multiple filter chains with "
+ "overlapping matching rules are defined",
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
}
}
diff --git a/source/server/filter_chain_manager_impl.h b/source/server/filter_chain_manager_impl.h
index e3322602c35f1..3d802cdba1cb0 100644
--- a/source/server/filter_chain_manager_impl.h
+++ b/source/server/filter_chain_manager_impl.h
@@ -199,12 +199,12 @@ class FilterChainManagerImpl : public Network::FilterChainManager,
using FcContextMap =
absl::flat_hash_map;
- FilterChainManagerImpl(const Network::Address::InstanceConstSharedPtr& address,
+ FilterChainManagerImpl(const std::vector& addresses,
Configuration::FactoryContext& factory_context,
Init::Manager& init_manager)
- : address_(address), parent_context_(factory_context), init_manager_(init_manager) {}
+ : addresses_(addresses), parent_context_(factory_context), init_manager_(init_manager) {}
- FilterChainManagerImpl(const Network::Address::InstanceConstSharedPtr& address,
+ FilterChainManagerImpl(const std::vector& addresses,
Configuration::FactoryContext& factory_context,
Init::Manager& init_manager, const FilterChainManagerImpl& parent_manager);
@@ -379,7 +379,7 @@ class FilterChainManagerImpl : public Network::FilterChainManager,
// and application protocols, using structures defined above.
DestinationPortsMap destination_ports_map_;
- const Network::Address::InstanceConstSharedPtr address_;
+ const std::vector& addresses_;
// This is the reference to a factory context which all the generations of listener share.
Configuration::FactoryContext& parent_context_;
std::list> factory_contexts_;
diff --git a/source/server/hot_restarting_parent.cc b/source/server/hot_restarting_parent.cc
index a94b762722a65..7c289fa14364b 100644
--- a/source/server/hot_restarting_parent.cc
+++ b/source/server/hot_restarting_parent.cc
@@ -108,23 +108,24 @@ HotRestartingParent::Internal::getListenSocketsForChild(const HotRestartMessage:
Network::Utility::resolveUrl(request.pass_listen_socket().address());
for (const auto& listener : server_->listenerManager().listeners()) {
- Network::ListenSocketFactory& socket_factory = listener.get().listenSocketFactory();
- if (*socket_factory.localAddress() == *addr && listener.get().bindToPort()) {
- StatusOr socket_type =
- Network::Utility::socketTypeFromUrl(request.pass_listen_socket().address());
- // socketTypeFromUrl should return a valid value since resolveUrl returned a valid address.
- ASSERT(socket_type.ok());
-
- if (socket_factory.socketType() == *socket_type) {
- // worker_index() will default to 0 if not set which is the behavior before this field
- // was added. Thus, this should be safe for both roll forward and roll back.
- if (request.pass_listen_socket().worker_index() < server_->options().concurrency()) {
- wrapped_reply.mutable_reply()->mutable_pass_listen_socket()->set_fd(
- socket_factory.getListenSocket(request.pass_listen_socket().worker_index())
- ->ioHandle()
- .fdDoNotUse());
+ for (auto& socket_factory : listener.get().listenSocketFactories()) {
+ if (*socket_factory->localAddress() == *addr && listener.get().bindToPort()) {
+ StatusOr socket_type =
+ Network::Utility::socketTypeFromUrl(request.pass_listen_socket().address());
+ // socketTypeFromUrl should return a valid value since resolveUrl returned a valid address.
+ ASSERT(socket_type.ok());
+
+ if (socket_factory->socketType() == *socket_type) {
+ // worker_index() will default to 0 if not set which is the behavior before this field
+ // was added. Thus, this should be safe for both roll forward and roll back.
+ if (request.pass_listen_socket().worker_index() < server_->options().concurrency()) {
+ wrapped_reply.mutable_reply()->mutable_pass_listen_socket()->set_fd(
+ socket_factory->getListenSocket(request.pass_listen_socket().worker_index())
+ ->ioHandle()
+ .fdDoNotUse());
+ }
+ break;
}
- break;
}
}
}
diff --git a/source/server/listener_impl.cc b/source/server/listener_impl.cc
index 957c62694279f..bc6340d2ee153 100644
--- a/source/server/listener_impl.cc
+++ b/source/server/listener_impl.cc
@@ -217,11 +217,12 @@ ListenerFactoryContextBaseImpl::ListenerFactoryContextBaseImpl(
const envoy::config::listener::v3::Listener& config, DrainManagerPtr drain_manager)
: server_(server), metadata_(config.metadata()), typed_metadata_(config.metadata()),
direction_(config.traffic_direction()), global_scope_(server.stats().createScope("")),
- listener_scope_(server_.stats().createScope(
- fmt::format("listener.{}.",
- !config.stat_prefix().empty()
- ? config.stat_prefix()
- : Network::Address::resolveProtoAddress(config.address())->asString()))),
+ listener_scope_(server_.stats().createScope(fmt::format(
+ "listener.{}.", !config.stat_prefix().empty()
+ ? config.stat_prefix()
+ : Network::Address::resolveProtoAddress(
+ config.has_address() ? config.address() : config.addresses(0))
+ ->asString()))),
validation_visitor_(validation_visitor), drain_manager_(std::move(drain_manager)),
is_quic_(config.udp_listener_config().has_quic_options()) {}
@@ -297,7 +298,10 @@ ListenerImpl::ListenerImpl(const envoy::config::listener::v3::Listener& config,
const std::string& version_info, ListenerManagerImpl& parent,
const std::string& name, bool added_via_api, bool workers_started,
uint64_t hash, uint32_t concurrency)
- : parent_(parent), address_(Network::Address::resolveProtoAddress(config.address())),
+ : parent_(parent),
+ socket_type_(config.has_address()
+ ? Network::Utility::protobufAddressSocketType(config.address())
+ : Network::Utility::protobufAddressSocketType(config.addresses(0))),
bind_to_port_(shouldBindToPort(config)), mptcp_enabled_(config.enable_mptcp()),
hand_off_restored_destination_connections_(
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, use_original_dst, false)),
@@ -322,8 +326,6 @@ ListenerImpl::ListenerImpl(const envoy::config::listener::v3::Listener& config,
listener_factory_context_(std::make_shared(
parent.server_, validation_visitor_, config, this, *this,
parent.factory_.createDrainManager(config.drain_type()))),
- filter_chain_manager_(address_, listener_factory_context_->parentFactoryContext(),
- initManager()),
reuse_port_(getReusePortOrDefault(parent_.server_, config_)),
cx_limit_runtime_key_("envoy.resource_limits.listener." + config_.name() +
".connection_limit"),
@@ -348,20 +350,26 @@ ListenerImpl::ListenerImpl(const envoy::config::listener::v3::Listener& config,
parent_.server_.singletonManager(), parent_.server_.threadLocal(),
validation_visitor_, parent_.server_.api(), parent_.server_.options(),
parent_.server_.accessLogManager())),
- quic_stat_names_(parent_.quicStatNames()) {
-
- if ((address_->type() == Network::Address::Type::Ip &&
- config.address().socket_address().ipv4_compat()) &&
- (address_->ip()->version() != Network::Address::IpVersion::v6 ||
- (!address_->ip()->isAnyAddress() &&
- address_->ip()->ipv6()->v4CompatibleAddress() == nullptr))) {
- if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_check_on_ipv4_compat")) {
- throw EnvoyException(fmt::format(
- "Only IPv6 address '::' or valid IPv4-mapped IPv6 address can set ipv4_compat: {}",
- address_->asStringView()));
- } else {
- ENVOY_LOG(warn, "An invalid IPv4-mapped IPv6 address is used when ipv4_compat is set: {}",
- address_->asStringView());
+ quic_stat_names_(parent_.quicStatNames()), concurrency_(concurrency) {
+ // All the addresses should be same socket type, so get the first address's socket type is enough.
+ auto socket_type = config.has_address()
+ ? Network::Utility::protobufAddressSocketType(config.address())
+ : Network::Utility::protobufAddressSocketType(config.addresses(0));
+ if (config.has_address()) {
+ auto address = Network::Address::resolveProtoAddress(config.address());
+ checkIpv4CompatAddress(address, config.address());
+ addresses_.emplace_back(address);
+ } else if (config.addresses_size() > 0) {
+ for (auto i = 0; i < config.addresses_size(); i++) {
+ if (socket_type != Network::Utility::protobufAddressSocketType(config.addresses(i))) {
+ throw EnvoyException(
+ fmt::format("listener {}: has different socket type. The listener only "
+ "support same socket type for all the addresses.",
+ name_));
+ }
+ auto address = Network::Address::resolveProtoAddress(config.addresses(i));
+ checkIpv4CompatAddress(address, config.addresses(i));
+ addresses_.emplace_back(address);
}
}
@@ -374,17 +382,19 @@ ListenerImpl::ListenerImpl(const envoy::config::listener::v3::Listener& config,
cx_limit_runtime_key_, config_.name());
}
+ filter_chain_manager_ = std::make_unique(
+ addresses_, listener_factory_context_->parentFactoryContext(), initManager()),
+
buildAccessLog();
- auto socket_type = Network::Utility::protobufAddressSocketType(config.address());
- validateConfig(socket_type);
+ validateConfig();
// buildUdpListenerFactory() must come before buildListenSocketOptions() because the UDP
// listener factory can provide additional options.
- buildUdpListenerFactory(socket_type, concurrency);
- buildListenSocketOptions(socket_type);
- createListenerFilterFactories(socket_type);
- validateFilterChains(socket_type);
+ buildUdpListenerFactory(concurrency);
+ buildListenSocketOptions();
+ createListenerFilterFactories();
+ validateFilterChains();
buildFilterChains();
- if (socket_type != Network::Socket::Type::Datagram) {
+ if (socket_type_ != Network::Socket::Type::Datagram) {
buildSocketOptions();
buildOriginalDstListenerFilter();
buildProxyProtocolListenerFilter();
@@ -404,8 +414,8 @@ ListenerImpl::ListenerImpl(ListenerImpl& origin,
const std::string& version_info, ListenerManagerImpl& parent,
const std::string& name, bool added_via_api, bool workers_started,
uint64_t hash)
- : parent_(parent), address_(origin.address_), bind_to_port_(shouldBindToPort(config)),
- mptcp_enabled_(config.enable_mptcp()),
+ : parent_(parent), socket_type_(origin.socket_type_), addresses_(origin.addresses_),
+ bind_to_port_(shouldBindToPort(config)), mptcp_enabled_(config.enable_mptcp()),
hand_off_restored_destination_connections_(
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, use_original_dst, false)),
per_connection_buffer_limit_bytes_(
@@ -427,11 +437,12 @@ ListenerImpl::ListenerImpl(ListenerImpl& origin,
PROTOBUF_GET_MS_OR_DEFAULT(config, listener_filters_timeout, 15000)),
continue_on_listener_filters_timeout_(config.continue_on_listener_filters_timeout()),
udp_listener_config_(origin.udp_listener_config_),
- connection_balancer_(origin.connection_balancer_),
+ connection_balancers_(origin.connection_balancers_),
listener_factory_context_(std::make_shared(
origin.listener_factory_context_->listener_factory_context_base_, this, *this)),
- filter_chain_manager_(address_, origin.listener_factory_context_->parentFactoryContext(),
- initManager(), origin.filter_chain_manager_),
+ filter_chain_manager_(std::make_unique(
+ addresses_, origin.listener_factory_context_->parentFactoryContext(), initManager(),
+ *origin.filter_chain_manager_)),
reuse_port_(origin.reuse_port_),
local_init_watcher_(fmt::format("Listener-local-init-watcher {}", name),
[this] {
@@ -441,14 +452,13 @@ ListenerImpl::ListenerImpl(ListenerImpl& origin,
transport_factory_context_(origin.transport_factory_context_),
quic_stat_names_(parent_.quicStatNames()) {
buildAccessLog();
- auto socket_type = Network::Utility::protobufAddressSocketType(config.address());
- validateConfig(socket_type);
- buildListenSocketOptions(socket_type);
- createListenerFilterFactories(socket_type);
- validateFilterChains(socket_type);
+ validateConfig();
+ buildListenSocketOptions();
+ createListenerFilterFactories();
+ validateFilterChains();
buildFilterChains();
buildInternalListener();
- if (socket_type == Network::Socket::Type::Stream) {
+ if (socket_type_ == Network::Socket::Type::Stream) {
// Apply the options below only for TCP.
buildSocketOptions();
buildOriginalDstListenerFilter();
@@ -457,15 +467,35 @@ ListenerImpl::ListenerImpl(ListenerImpl& origin,
}
}
-void ListenerImpl::validateConfig(Network::Socket::Type socket_type) {
+void ListenerImpl::checkIpv4CompatAddress(const Network::Address::InstanceConstSharedPtr& address,
+ const envoy::config::core::v3::Address& proto_address) {
+ if ((address->type() == Network::Address::Type::Ip &&
+ proto_address.socket_address().ipv4_compat()) &&
+ (address->ip()->version() != Network::Address::IpVersion::v6 ||
+ (!address->ip()->isAnyAddress() &&
+ address->ip()->ipv6()->v4CompatibleAddress() == nullptr))) {
+ if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_check_on_ipv4_compat")) {
+ throw EnvoyException(fmt::format(
+ "Only IPv6 address '::' or valid IPv4-mapped IPv6 address can set ipv4_compat: {}",
+ address->asStringView()));
+ } else {
+ ENVOY_LOG(warn, "An invalid IPv4-mapped IPv6 address is used when ipv4_compat is set: {}",
+ address->asStringView());
+ }
+ }
+}
+
+void ListenerImpl::validateConfig() {
if (mptcp_enabled_) {
- if (socket_type != Network::Socket::Type::Stream) {
+ if (socket_type_ != Network::Socket::Type::Stream) {
throw EnvoyException(
fmt::format("listener {}: enable_mptcp can only be used with TCP listeners", name_));
}
- if (address_->type() != Network::Address::Type::Ip) {
- throw EnvoyException(
- fmt::format("listener {}: enable_mptcp can only be used with IP addresses", name_));
+ for (auto& address : addresses_) {
+ if (address->type() != Network::Address::Type::Ip) {
+ throw EnvoyException(
+ fmt::format("listener {}: enable_mptcp can only be used with IP addresses", name_));
+ }
}
if (!Api::OsSysCallsSingleton::get().supportsMptcp()) {
throw EnvoyException(fmt::format(
@@ -484,11 +514,12 @@ void ListenerImpl::buildAccessLog() {
}
void ListenerImpl::buildInternalListener() {
- if (config_.address().has_envoy_internal_address()) {
+ if (config_.has_address() ? config_.address().has_envoy_internal_address()
+ : config_.addresses(0).has_envoy_internal_address()) {
if (config_.has_api_listener()) {
throw EnvoyException(
fmt::format("error adding listener '{}': internal address cannot be used in api listener",
- address_->asString()));
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
}
if ((config_.has_connection_balance_config() &&
config_.connection_balance_config().has_exact_balance()) ||
@@ -499,11 +530,12 @@ void ListenerImpl::buildInternalListener() {
(config_.has_transparent() && config_.transparent().value())) {
throw EnvoyException(
fmt::format("error adding listener '{}': has unsupported tcp listener feature",
- address_->asString()));
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
}
if (!config_.socket_options().empty()) {
- throw EnvoyException(fmt::format("error adding listener '{}': does not support socket option",
- address_->asString()));
+ throw EnvoyException(
+ fmt::format("error adding listener '{}': does not support socket option",
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
}
std::shared_ptr internal_listener_registry =
parent_.server_.singletonManager().getTyped(
@@ -511,22 +543,35 @@ void ListenerImpl::buildInternalListener() {
if (internal_listener_registry == nullptr) {
throw EnvoyException(
fmt::format("error adding listener '{}': internal listener registry is not initialized.",
- address_->asString()));
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
}
internal_listener_config_ =
std::make_unique(*internal_listener_registry);
} else {
if (config_.has_internal_listener()) {
- throw EnvoyException(fmt::format("error adding listener '{}': address is not an internal "
- "address but an internal listener config is provided",
- address_->asString()));
+ throw EnvoyException(
+ fmt::format("error adding listener '{}': address is not an internal "
+ "address but an internal listener config is provided",
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
}
}
}
-void ListenerImpl::buildUdpListenerFactory(Network::Socket::Type socket_type,
- uint32_t concurrency) {
- if (socket_type != Network::Socket::Type::Datagram) {
+void ListenerImpl::buildUdpListenerWorkerRouter(const Network::Address::Instance& address,
+ uint32_t concurrency) {
+ if (socket_type_ != Network::Socket::Type::Datagram) {
+ return;
+ }
+ auto iter = udp_listener_config_->listener_worker_routers_.find(address.asString());
+ if (iter != udp_listener_config_->listener_worker_routers_.end()) {
+ return;
+ }
+ udp_listener_config_->listener_worker_routers_.emplace(
+ address.asString(), std::make_unique(concurrency));
+}
+
+void ListenerImpl::buildUdpListenerFactory(uint32_t concurrency) {
+ if (socket_type_ != Network::Socket::Type::Datagram) {
return;
}
if (!reuse_port_ && concurrency > 1) {
@@ -562,14 +607,12 @@ void ListenerImpl::buildUdpListenerFactory(Network::Socket::Type socket_type,
udp_listener_config_->listener_factory_ =
std::make_unique(concurrency);
}
- udp_listener_config_->listener_worker_router_ =
- std::make_unique(concurrency);
if (udp_listener_config_->writer_factory_ == nullptr) {
udp_listener_config_->writer_factory_ = std::make_unique();
}
}
-void ListenerImpl::buildListenSocketOptions(Network::Socket::Type socket_type) {
+void ListenerImpl::buildListenSocketOptions() {
// The process-wide `signal()` handling may fail to handle SIGPIPE if overridden
// in the process (i.e., on a mobile client). Some OSes support handling it at the socket layer:
if (ENVOY_SOCKET_SO_NOSIGPIPE.hasValue()) {
@@ -588,7 +631,7 @@ void ListenerImpl::buildListenSocketOptions(Network::Socket::Type socket_type) {
addListenSocketOptions(
Network::SocketOptionFactory::buildLiteralOptions(config_.socket_options()));
}
- if (socket_type == Network::Socket::Type::Datagram) {
+ if (socket_type_ == Network::Socket::Type::Datagram) {
// Needed for recvmsg to return destination address in IP header.
addListenSocketOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions());
// Needed to return receive buffer overflown indicator.
@@ -606,9 +649,9 @@ void ListenerImpl::buildListenSocketOptions(Network::Socket::Type socket_type) {
}
}
-void ListenerImpl::createListenerFilterFactories(Network::Socket::Type socket_type) {
+void ListenerImpl::createListenerFilterFactories() {
if (!config_.listener_filters().empty()) {
- switch (socket_type) {
+ switch (socket_type_) {
case Network::Socket::Type::Datagram:
udp_listener_filter_factories_ = parent_.factory_.createUdpListenerFilterFactoryList(
config_.listener_filters(), *listener_factory_context_);
@@ -621,23 +664,25 @@ void ListenerImpl::createListenerFilterFactories(Network::Socket::Type socket_ty
}
}
-void ListenerImpl::validateFilterChains(Network::Socket::Type socket_type) {
+void ListenerImpl::validateFilterChains() {
if (config_.filter_chains().empty() && !config_.has_default_filter_chain() &&
- (socket_type == Network::Socket::Type::Stream ||
+ (socket_type_ == Network::Socket::Type::Stream ||
!udp_listener_config_->listener_factory_->isTransportConnectionless())) {
// If we got here, this is a tcp listener or connection-oriented udp listener, so ensure there
// is a filter chain specified
- throw EnvoyException(fmt::format("error adding listener '{}': no filter chains specified",
- address_->asString()));
+ throw EnvoyException(
+ fmt::format("error adding listener '{}': no filter chains specified",
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
} else if (udp_listener_config_ != nullptr &&
!udp_listener_config_->listener_factory_->isTransportConnectionless()) {
// Early fail if any filter chain doesn't have transport socket configured.
if (anyFilterChain(config_, [](const auto& filter_chain) {
return !filter_chain.has_transport_socket();
})) {
- throw EnvoyException(fmt::format("error adding listener '{}': no transport socket "
- "specified for connection oriented UDP listener",
- address_->asString()));
+ throw EnvoyException(
+ fmt::format("error adding listener '{}': no transport socket "
+ "specified for connection oriented UDP listener",
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter())));
}
} else if (Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.udp_listener_updates_filter_chain_in_place") &&
@@ -647,23 +692,24 @@ void ListenerImpl::validateFilterChains(Network::Socket::Type socket_type) {
throw EnvoyException(fmt::format("error adding listener '{}': {} filter chain(s) specified for "
"connection-less UDP listener.",
- address_->asString(), config_.filter_chains_size()));
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter()),
+ config_.filter_chains_size()));
}
}
void ListenerImpl::buildFilterChains() {
transport_factory_context_->setInitManager(*dynamic_init_manager_);
ListenerFilterChainFactoryBuilder builder(*this, *transport_factory_context_);
- filter_chain_manager_.addFilterChains(
+ filter_chain_manager_->addFilterChains(
config_.has_filter_chain_matcher() ? &config_.filter_chain_matcher() : nullptr,
config_.filter_chains(),
config_.has_default_filter_chain() ? &config_.default_filter_chain() : nullptr, builder,
- filter_chain_manager_);
+ *filter_chain_manager_);
}
-void ListenerImpl::buildSocketOptions() {
- // TCP specific setup.
- if (connection_balancer_ == nullptr) {
+void ListenerImpl::buildConnectionBalancer(const Network::Address::Instance& address) {
+ auto iter = connection_balancers_.find(address.asString());
+ if (iter == connection_balancers_.end() && socket_type_ == Network::Socket::Type::Stream) {
#ifdef WIN32
// On Windows we use the exact connection balancer to dispatch connections
// from worker 1 to all workers. This is a perf hit but it is the only way
@@ -675,19 +721,24 @@ void ListenerImpl::buildSocketOptions() {
"Envoy is running on Windows."
"ExactBalance is used to load balance connections between workers on Windows.",
config_.name());
- connection_balancer_ = std::make_shared();
+ connection_balancers_.emplace(address.asString(),
+ std::make_shared());
#else
// Not in place listener update.
if (config_.has_connection_balance_config()) {
// Currently exact balance is the only supported type and there are no options.
ASSERT(config_.connection_balance_config().has_exact_balance());
- connection_balancer_ = std::make_shared();
+ connection_balancers_.emplace(address.asString(),
+ std::make_shared());
} else {
- connection_balancer_ = std::make_shared();
+ connection_balancers_.emplace(address.asString(),
+ std::make_shared());
}
#endif
}
+}
+void ListenerImpl::buildSocketOptions() {
if (config_.has_tcp_fast_open_queue_length()) {
addListenSocketOptions(Network::SocketOptionFactory::buildTcpFastOpenOptions(
config_.tcp_fast_open_queue_length().value()));
@@ -827,7 +878,7 @@ void ListenerImpl::createUdpListenerFilterChain(Network::UdpListenerFilterManage
void ListenerImpl::debugLog(const std::string& message) {
UNREFERENCED_PARAMETER(message);
ENVOY_LOG(debug, "{}: name={}, hash={}, tag={}, address={}", message, name_, hash_, listener_tag_,
- address_->asString());
+ absl::StrJoin(addresses_, ",", Network::AddressStrFormatter()));
}
void ListenerImpl::initialize() {
@@ -853,9 +904,10 @@ ListenerImpl::~ListenerImpl() {
Init::Manager& ListenerImpl::initManager() { return *dynamic_init_manager_; }
-void ListenerImpl::setSocketFactory(Network::ListenSocketFactoryPtr&& socket_factory) {
- ASSERT(!socket_factory_);
- socket_factory_ = std::move(socket_factory);
+void ListenerImpl::addSocketFactory(Network::ListenSocketFactoryPtr&& socket_factory) {
+ buildConnectionBalancer(*socket_factory->localAddress());
+ buildUdpListenerWorkerRouter(*socket_factory->localAddress(), concurrency_);
+ socket_factories_.emplace_back(std::move(socket_factory));
}
bool ListenerImpl::supportUpdateFilterChain(const envoy::config::listener::v3::Listener& config,
@@ -868,9 +920,11 @@ bool ListenerImpl::supportUpdateFilterChain(const envoy::config::listener::v3::L
if (!Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.udp_listener_updates_filter_chain_in_place") &&
- (Network::Utility::protobufAddressSocketType(config_.address()) !=
+ (Network::Utility::protobufAddressSocketType(config_.has_address() ? config_.address()
+ : config_.addresses(0)) !=
Network::Socket::Type::Stream ||
- Network::Utility::protobufAddressSocketType(config.address()) !=
+ Network::Utility::protobufAddressSocketType(config.has_address() ? config.address()
+ : config.addresses(0)) !=
Network::Socket::Type::Stream)) {
return false;
}
@@ -901,10 +955,10 @@ ListenerImpl::newListenerWithFilterChain(const envoy::config::listener::v3::List
void ListenerImpl::diffFilterChain(const ListenerImpl& another_listener,
std::function callback) {
- for (const auto& message_and_filter_chain : filter_chain_manager_.filterChainsByMessage()) {
- if (another_listener.filter_chain_manager_.filterChainsByMessage().find(
+ for (const auto& message_and_filter_chain : filter_chain_manager_->filterChainsByMessage()) {
+ if (another_listener.filter_chain_manager_->filterChainsByMessage().find(
message_and_filter_chain.first) ==
- another_listener.filter_chain_manager_.filterChainsByMessage().end()) {
+ another_listener.filter_chain_manager_->filterChainsByMessage().end()) {
// The filter chain exists in `this` listener but not in the listener passed in.
callback(*message_and_filter_chain.second);
}
@@ -912,11 +966,11 @@ void ListenerImpl::diffFilterChain(const ListenerImpl& another_listener,
// Filter chain manager maintains an optional default filter chain besides the filter chains
// indexed by message.
if (auto eq = MessageUtil();
- filter_chain_manager_.defaultFilterChainMessage().has_value() &&
- (!another_listener.filter_chain_manager_.defaultFilterChainMessage().has_value() ||
- !eq(*another_listener.filter_chain_manager_.defaultFilterChainMessage(),
- *filter_chain_manager_.defaultFilterChainMessage()))) {
- callback(*filter_chain_manager_.defaultFilterChain());
+ filter_chain_manager_->defaultFilterChainMessage().has_value() &&
+ (!another_listener.filter_chain_manager_->defaultFilterChainMessage().has_value() ||
+ !eq(*another_listener.filter_chain_manager_->defaultFilterChainMessage(),
+ *filter_chain_manager_->defaultFilterChainMessage()))) {
+ callback(*filter_chain_manager_->defaultFilterChain());
}
}
@@ -945,7 +999,8 @@ bool ListenerImpl::getReusePortOrDefault(Server::Instance& server,
}();
#ifndef __linux__
- const auto socket_type = Network::Utility::protobufAddressSocketType(config.address());
+ const auto socket_type = Network::Utility::protobufAddressSocketType(
+ config.has_address() ? config.address() : config.addresses(0));
if (initial_reuse_port_value && socket_type == Network::Socket::Type::Stream) {
// reuse_port is the default on Linux for TCP. On other platforms even if set it is disabled
// and the user is warned. For UDP it's always the default even if not effective.
@@ -961,9 +1016,59 @@ bool ListenerImpl::getReusePortOrDefault(Server::Instance& server,
}
bool ListenerImpl::hasCompatibleAddress(const ListenerImpl& other) const {
- return *address() == *other.address() &&
- Network::Utility::protobufAddressSocketType(config_.address()) ==
- Network::Utility::protobufAddressSocketType(other.config_.address());
+ if ((socket_type_ != other.socket_type_) || (addresses_.size() != other.addresses().size())) {
+ return false;
+ }
+
+ // Since there can be multiple zero port addresses, create a copy
+ // of other's addresses, remove it for any finding to avoid matching same zero port
+ // address to the same one.
+ auto other_addresses = other.addresses();
+ for (auto& addr : addresses()) {
+ auto iter = std::find_if(other_addresses.begin(), other_addresses.end(),
+ [&addr](const Network::Address::InstanceConstSharedPtr& other_addr) {
+ return *addr == *other_addr;
+ });
+ if (iter == other_addresses.end()) {
+ return false;
+ }
+ other_addresses.erase(iter);
+ }
+ return true;
+}
+
+bool ListenerImpl::hasDuplicatedAddress(const ListenerImpl& other) const {
+ if (socket_type_ != other.socket_type_) {
+ return false;
+ }
+ // For listeners that do not bind or listeners that do not bind to port 0 we must check to make
+ // sure we are not duplicating the address. This avoids ambiguity about which non-binding
+ // listener is used or even worse for the binding to port != 0 and reuse port case multiple
+ // different listeners receiving connections destined for the same port.
+ for (auto& other_addr : other.addresses()) {
+ if (other_addr->ip() == nullptr ||
+ (other_addr->ip() != nullptr && (other_addr->ip()->port() != 0 || !bindToPort()))) {
+ if (find_if(addresses_.begin(), addresses_.end(),
+ [&other_addr](const Network::Address::InstanceConstSharedPtr& addr) {
+ return *other_addr == *addr;
+ }) != addresses_.end()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void ListenerImpl::cloneSocketFactoryFrom(const ListenerImpl& other) {
+ for (auto& socket_factory : other.getSocketFactories()) {
+ addSocketFactory(socket_factory->clone());
+ }
+}
+
+void ListenerImpl::closeAllSockets() {
+ for (auto& socket_factory : socket_factories_) {
+ socket_factory->closeAllSockets();
+ }
}
bool ListenerMessageUtil::filterChainOnlyChange(const envoy::config::listener::v3::Listener& lhs,
diff --git a/source/server/listener_impl.h b/source/server/listener_impl.h
index 8e2eee26d7bfe..ee6e800140f57 100644
--- a/source/server/listener_impl.h
+++ b/source/server/listener_impl.h
@@ -282,15 +282,19 @@ class ListenerImpl final : public Network::ListenerConfig,
bool blockUpdate(uint64_t new_hash) { return new_hash == hash_ || !added_via_api_; }
bool blockRemove() { return !added_via_api_; }
- Network::Address::InstanceConstSharedPtr address() const { return address_; }
+ const std::vector& addresses() const {
+ return addresses_;
+ }
const envoy::config::listener::v3::Listener& config() const { return config_; }
- const Network::ListenSocketFactory& getSocketFactory() const { return *socket_factory_; }
+ const std::vector& getSocketFactories() const {
+ return socket_factories_;
+ }
void debugLog(const std::string& message);
void initialize();
DrainManager& localDrainManager() const {
return listener_factory_context_->listener_factory_context_base_->drainManager();
}
- void setSocketFactory(Network::ListenSocketFactoryPtr&& socket_factory);
+ void addSocketFactory(Network::ListenSocketFactoryPtr&& socket_factory);
void setSocketAndOptions(const Network::SocketSharedPtr& socket);
const Network::Socket::OptionsSharedPtr& listenSocketOptions() { return listen_socket_options_; }
const std::string& versionInfo() const { return version_info_; }
@@ -300,12 +304,16 @@ class ListenerImpl final : public Network::ListenerConfig,
// Check whether a new listener can share sockets with this listener.
bool hasCompatibleAddress(const ListenerImpl& other) const;
+ // Check whether a new listener has duplicated listening address this listener.
+ bool hasDuplicatedAddress(const ListenerImpl& other) const;
// Network::ListenerConfig
- Network::FilterChainManager& filterChainManager() override { return filter_chain_manager_; }
+ Network::FilterChainManager& filterChainManager() override { return *filter_chain_manager_; }
Network::FilterChainFactory& filterChainFactory() override { return *this; }
- Network::ListenSocketFactory& listenSocketFactory() override { return *socket_factory_; }
- bool bindToPort() override { return bind_to_port_; }
+ const std::vector& listenSocketFactories() override {
+ return socket_factories_;
+ }
+ bool bindToPort() const override { return bind_to_port_; }
bool mptcpEnabled() { return mptcp_enabled_; }
bool handOffRestoredDestinationConnections() const override {
return hand_off_restored_destination_connections_;
@@ -330,7 +338,12 @@ class ListenerImpl final : public Network::ListenerConfig,
return internal_listener_config_ != nullptr ? *internal_listener_config_
: Network::InternalListenerConfigOptRef();
}
- Network::ConnectionBalancer& connectionBalancer() override { return *connection_balancer_; }
+ Network::ConnectionBalancer&
+ connectionBalancer(const Network::Address::Instance& address) override {
+ auto balancer = connection_balancers_.find(address.asString());
+ ASSERT(balancer != connection_balancers_.end());
+ return *balancer->second;
+ }
ResourceLimit& openConnections() override { return *open_connections_; }
const std::vector& accessLogs() const override {
return access_logs_;
@@ -349,6 +362,11 @@ class ListenerImpl final : public Network::ListenerConfig,
}
}
+ void cloneSocketFactoryFrom(const ListenerImpl& other);
+ void closeAllSockets();
+
+ Network::Socket::Type socketType() const { return socket_type_; }
+
// Network::FilterChainFactory
bool createNetworkFilterChain(Network::Connection& connection,
const std::vector& factories) override;
@@ -366,15 +384,18 @@ class ListenerImpl final : public Network::ListenerConfig,
// Network::UdpListenerConfig
Network::ActiveUdpListenerFactory& listenerFactory() override { return *listener_factory_; }
Network::UdpPacketWriterFactory& packetWriterFactory() override { return *writer_factory_; }
- Network::UdpListenerWorkerRouter& listenerWorkerRouter() override {
- return *listener_worker_router_;
+ Network::UdpListenerWorkerRouter&
+ listenerWorkerRouter(const Network::Address::Instance& address) override {
+ auto iter = listener_worker_routers_.find(address.asString());
+ ASSERT(iter != listener_worker_routers_.end());
+ return *iter->second;
}
const envoy::config::listener::v3::UdpListenerConfig& config() override { return config_; }
const envoy::config::listener::v3::UdpListenerConfig config_;
Network::ActiveUdpListenerFactoryPtr listener_factory_;
Network::UdpPacketWriterFactoryPtr writer_factory_;
- Network::UdpListenerWorkerRouterPtr listener_worker_router_;
+ absl::flat_hash_map listener_worker_routers_;
};
class InternalListenerConfigImpl : public Network::InternalListenerConfig {
@@ -400,15 +421,20 @@ class ListenerImpl final : public Network::ListenerConfig,
// Helpers for constructor.
void buildAccessLog();
void buildInternalListener();
- void validateConfig(Network::Socket::Type socket_type);
- void buildUdpListenerFactory(Network::Socket::Type socket_type, uint32_t concurrency);
- void buildListenSocketOptions(Network::Socket::Type socket_type);
- void createListenerFilterFactories(Network::Socket::Type socket_type);
- void validateFilterChains(Network::Socket::Type socket_type);
+ void validateConfig();
+ void buildUdpListenerWorkerRouter(const Network::Address::Instance& address,
+ uint32_t concurrency);
+ void buildUdpListenerFactory(uint32_t concurrency);
+ void buildListenSocketOptions();
+ void createListenerFilterFactories();
+ void validateFilterChains();
void buildFilterChains();
+ void buildConnectionBalancer(const Network::Address::Instance& address);
void buildSocketOptions();
void buildOriginalDstListenerFilter();
void buildProxyProtocolListenerFilter();
+ void checkIpv4CompatAddress(const Network::Address::InstanceConstSharedPtr& address,
+ const envoy::config::core::v3::Address& proto_address);
void addListenSocketOptions(const Network::Socket::OptionsSharedPtr& options) {
ensureSocketOptions();
@@ -416,9 +442,10 @@ class ListenerImpl final : public Network::ListenerConfig,
}
ListenerManagerImpl& parent_;
- Network::Address::InstanceConstSharedPtr address_;
+ const Network::Socket::Type socket_type_;
+ std::vector addresses_;
- Network::ListenSocketFactoryPtr socket_factory_;
+ std::vector socket_factories_;
const bool bind_to_port_;
const bool mptcp_enabled_;
const bool hand_off_restored_destination_connections_;
@@ -448,9 +475,9 @@ class ListenerImpl final : public Network::ListenerConfig,
const bool continue_on_listener_filters_timeout_;
std::shared_ptr udp_listener_config_;
std::unique_ptr internal_listener_config_;
- Network::ConnectionBalancerSharedPtr connection_balancer_;
+ absl::flat_hash_map connection_balancers_;
std::shared_ptr listener_factory_context_;
- FilterChainManagerImpl filter_chain_manager_;
+ std::unique_ptr filter_chain_manager_;
const bool reuse_port_;
// Per-listener connection limits are only specified via runtime.
@@ -468,6 +495,7 @@ class ListenerImpl final : public Network::ListenerConfig,
transport_factory_context_;
Quic::QuicStatNames& quic_stat_names_;
+ uint32_t concurrency_;
// to access ListenerManagerImpl::factory_.
friend class ListenerFilterChainFactoryBuilder;
diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc
index 1d675eb8422db..8e5e6f8f519eb 100644
--- a/source/server/listener_manager_impl.cc
+++ b/source/server/listener_manager_impl.cc
@@ -335,11 +335,40 @@ ListenerManagerStats ListenerManagerImpl::generateStats(Stats::Scope& scope) {
bool ListenerManagerImpl::addOrUpdateListener(const envoy::config::listener::v3::Listener& config,
const std::string& version_info, bool added_via_api) {
+ std::string name;
+ if (!config.name().empty()) {
+ name = config.name();
+ } else {
+ name = server_.api().randomGenerator().uuid();
+ }
+
+ // TODO(soulxu): remove this validation to the `PGV` when deprecate the old `address` field.
+ if (config.has_address() && config.addresses_size() > 0) {
+ throw EnvoyException(
+ fmt::format("listener {}: only one of `address` and `addresses` can be set.", name));
+ } else if (!config.has_address() && config.addresses_size() == 0) {
+ throw EnvoyException(
+ fmt::format("listener {}: one of `address` and `addresses` must be set.", name));
+ }
+
+ // There is no use-case for multiple internal addresses yet. This can be supported
+ // when the use-case show up in the future.
+ if (!config.has_address()) {
+ if (config.addresses(0).has_envoy_internal_address() && config.addresses_size() > 1) {
+ throw EnvoyException(
+ fmt::format("listener {}: internal address doesn't support multiple addresses.", name));
+ }
+ }
+
if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.internal_address")) {
RELEASE_ASSERT(
- !config.address().has_envoy_internal_address(),
+ !(config.has_address() ? config.address().has_envoy_internal_address()
+ : config.addresses(0).has_envoy_internal_address()),
fmt::format("listener {} has envoy internal address {}. This runtime feature is disabled.",
- config.name(), config.address().envoy_internal_address().DebugString()));
+ config.name(),
+ config.has_address()
+ ? config.address().envoy_internal_address().DebugString()
+ : config.addresses(0).envoy_internal_address().DebugString()));
}
// TODO(junr03): currently only one ApiListener can be installed via bootstrap to avoid having to
// build a collection of listeners, and to have to be able to warm and drain the listeners. In the
@@ -357,13 +386,6 @@ bool ListenerManagerImpl::addOrUpdateListener(const envoy::config::listener::v3:
}
}
- std::string name;
- if (!config.name().empty()) {
- name = config.name();
- } else {
- name = server_.api().randomGenerator().uuid();
- }
-
auto it = error_state_tracker_.find(name);
TRY_ASSERT_MAIN_THREAD {
return addOrUpdateListenerInternal(config, version_info, added_via_api, name);
@@ -447,18 +469,18 @@ bool ListenerManagerImpl::addOrUpdateListenerInternal(
ASSERT(workers_started_);
new_listener->debugLog("update warming listener");
if (!(*existing_warming_listener)->hasCompatibleAddress(*new_listener)) {
- setNewOrDrainingSocketFactory(name, config.address(), *new_listener);
+ setNewOrDrainingSocketFactory(name, *new_listener);
} else {
- new_listener->setSocketFactory((*existing_warming_listener)->getSocketFactory().clone());
+ new_listener->cloneSocketFactoryFrom(**existing_warming_listener);
}
*existing_warming_listener = std::move(new_listener);
} else if (existing_active_listener != active_listeners_.end()) {
// In this case we have no warming listener, so what we do depends on whether workers
// have been started or not.
if (!(*existing_active_listener)->hasCompatibleAddress(*new_listener)) {
- setNewOrDrainingSocketFactory(name, config.address(), *new_listener);
+ setNewOrDrainingSocketFactory(name, *new_listener);
} else {
- new_listener->setSocketFactory((*existing_active_listener)->getSocketFactory().clone());
+ new_listener->cloneSocketFactoryFrom(**existing_active_listener);
}
if (workers_started_) {
new_listener->debugLog("add warming listener");
@@ -470,7 +492,7 @@ bool ListenerManagerImpl::addOrUpdateListenerInternal(
} else {
// We have no warming or active listener so we need to make a new one. What we do depends on
// whether workers have been started or not.
- setNewOrDrainingSocketFactory(name, config.address(), *new_listener);
+ setNewOrDrainingSocketFactory(name, *new_listener);
if (workers_started_) {
new_listener->debugLog("add warming listener");
warming_listeners_.emplace_back(std::move(new_listener));
@@ -493,10 +515,10 @@ bool ListenerManagerImpl::addOrUpdateListenerInternal(
return true;
}
-bool ListenerManagerImpl::hasListenerWithCompatibleAddress(const ListenerList& list,
+bool ListenerManagerImpl::hasListenerWithDuplicatedAddress(const ListenerList& list,
const ListenerImpl& listener) {
for (const auto& existing_listener : list) {
- if (existing_listener->hasCompatibleAddress(listener)) {
+ if (existing_listener->hasDuplicatedAddress(listener)) {
return true;
}
}
@@ -592,7 +614,9 @@ ListenerManagerImpl::listeners(ListenerState state) {
bool ListenerManagerImpl::doFinalPreWorkerListenerInit(ListenerImpl& listener) {
TRY_ASSERT_MAIN_THREAD {
- listener.listenSocketFactory().doFinalPreWorkerInit();
+ for (auto& socket_factory : listener.listenSocketFactories()) {
+ socket_factory->doFinalPreWorkerInit();
+ }
return true;
}
END_TRY
@@ -966,19 +990,13 @@ Network::DrainableFilterChainSharedPtr ListenerFilterChainFactoryBuilder::buildF
return filter_chain_res;
}
-void ListenerManagerImpl::setNewOrDrainingSocketFactory(
- const std::string& name, const envoy::config::core::v3::Address& proto_address,
- ListenerImpl& listener) {
- // For listeners that do not bind or listeners that do not bind to port 0 we must check to make
- // sure we are not duplicating the address. This avoids ambiguity about which non-binding
- // listener is used or even worse for the binding to port != 0 and reuse port case multiple
- // different listeners receiving connections destined for the same port.
- if ((!listener.bindToPort() || listener.config().address().socket_address().port_value() != 0) &&
- (hasListenerWithCompatibleAddress(warming_listeners_, listener) ||
- hasListenerWithCompatibleAddress(active_listeners_, listener))) {
+void ListenerManagerImpl::setNewOrDrainingSocketFactory(const std::string& name,
+ ListenerImpl& listener) {
+ if (hasListenerWithDuplicatedAddress(warming_listeners_, listener) ||
+ hasListenerWithDuplicatedAddress(active_listeners_, listener)) {
const std::string message =
fmt::format("error adding listener: '{}' has duplicate address '{}' as existing listener",
- name, listener.address()->asString());
+ name, absl::StrJoin(listener.addresses(), ",", Network::AddressStrFormatter()));
ENVOY_LOG(warn, "{}", message);
throw EnvoyException(message);
}
@@ -987,43 +1005,45 @@ void ListenerManagerImpl::setNewOrDrainingSocketFactory(
// the same address we are configured for. This is an edge case, but
// may happen if a listener is removed and then added back with a same or different name and
// intended to listen on the same address. This should work and not fail.
- const Network::ListenSocketFactory* draining_listen_socket_factory = nullptr;
- auto existing_draining_listener = std::find_if(
- draining_listeners_.cbegin(), draining_listeners_.cend(),
- [&listener](const DrainingListener& draining_listener) {
- return draining_listener.listener_->listenSocketFactory().getListenSocket(0)->isOpen() &&
- listener.hasCompatibleAddress(*draining_listener.listener_);
- });
+ const ListenerImpl* draining_listener_ptr = nullptr;
+ auto existing_draining_listener =
+ std::find_if(draining_listeners_.cbegin(), draining_listeners_.cend(),
+ [&listener](const DrainingListener& draining_listener) {
+ return draining_listener.listener_->listenSocketFactories()[0]
+ ->getListenSocket(0)
+ ->isOpen() &&
+ listener.hasCompatibleAddress(*draining_listener.listener_);
+ });
if (existing_draining_listener != draining_listeners_.cend()) {
- draining_listen_socket_factory = &existing_draining_listener->listener_->getSocketFactory();
existing_draining_listener->listener_->debugLog("clones listener sockets");
+ draining_listener_ptr = existing_draining_listener->listener_.get();
} else {
auto existing_draining_filter_chain = std::find_if(
draining_filter_chains_manager_.cbegin(), draining_filter_chains_manager_.cend(),
[&listener](const DrainingFilterChainsManager& draining_filter_chain) {
return draining_filter_chain.getDrainingListener()
- .listenSocketFactory()
- .getListenSocket(0)
+ .listenSocketFactories()[0]
+ ->getListenSocket(0)
->isOpen() &&
listener.hasCompatibleAddress(draining_filter_chain.getDrainingListener());
});
if (existing_draining_filter_chain != draining_filter_chains_manager_.cend()) {
- draining_listen_socket_factory =
- &existing_draining_filter_chain->getDrainingListener().getSocketFactory();
existing_draining_filter_chain->getDrainingListener().debugLog("clones listener socket");
+ draining_listener_ptr = &existing_draining_filter_chain->getDrainingListener();
}
}
- listener.setSocketFactory(draining_listen_socket_factory != nullptr
- ? draining_listen_socket_factory->clone()
- : createListenSocketFactory(proto_address, listener));
+ if (draining_listener_ptr != nullptr) {
+ listener.cloneSocketFactoryFrom(*draining_listener_ptr);
+ } else {
+ createListenSocketFactory(listener);
+ }
}
-Network::ListenSocketFactoryPtr ListenerManagerImpl::createListenSocketFactory(
- const envoy::config::core::v3::Address& proto_address, ListenerImpl& listener) {
- Network::Socket::Type socket_type = Network::Utility::protobufAddressSocketType(proto_address);
+void ListenerManagerImpl::createListenSocketFactory(ListenerImpl& listener) {
+ Network::Socket::Type socket_type = listener.socketType();
ListenerComponentFactory::BindType bind_type = ListenerComponentFactory::BindType::NoBind;
if (listener.bindToPort()) {
bind_type = listener.reusePort() ? ListenerComponentFactory::BindType::ReusePort
@@ -1032,9 +1052,11 @@ Network::ListenSocketFactoryPtr ListenerManagerImpl::createListenSocketFactory(
TRY_ASSERT_MAIN_THREAD {
Network::SocketCreationOptions creation_options;
creation_options.mptcp_enabled_ = listener.mptcpEnabled();
- return std::make_unique(
- factory_, listener.address(), socket_type, listener.listenSocketOptions(), listener.name(),
- listener.tcpBacklogSize(), bind_type, creation_options, server_.options().concurrency());
+ for (auto& address : listener.addresses()) {
+ listener.addSocketFactory(std::make_unique(
+ factory_, address, socket_type, listener.listenSocketOptions(), listener.name(),
+ listener.tcpBacklogSize(), bind_type, creation_options, server_.options().concurrency()));
+ }
}
END_TRY
catch (const EnvoyException& e) {
@@ -1052,7 +1074,7 @@ void ListenerManagerImpl::maybeCloseSocketsForListener(ListenerImpl& listener) {
// already waiting for long timeout. However, connection-oriented UDP listeners shouldn't
// close the socket because they need to receive packets for existing connections via the
// listen sockets.
- listener.listenSocketFactory().closeAllSockets();
+ listener.closeAllSockets();
// In case of this listener was in-place updated previously and in the filter chains draining
// procedure, so close the sockets for the previous draining listener.
@@ -1060,7 +1082,7 @@ void ListenerManagerImpl::maybeCloseSocketsForListener(ListenerImpl& listener) {
// A listener can be in-place updated multiple times, so there may
// have multiple draining listeners with same tag.
if (manager.getDrainingListenerTag() == listener.listenerTag()) {
- manager.getDrainingListener().listenSocketFactory().closeAllSockets();
+ manager.getDrainingListener().closeAllSockets();
}
}
}
diff --git a/source/server/listener_manager_impl.h b/source/server/listener_manager_impl.h
index f2baedbd66a66..1744f3cc617ff 100644
--- a/source/server/listener_manager_impl.h
+++ b/source/server/listener_manager_impl.h
@@ -235,7 +235,7 @@ class ListenerManagerImpl : public ListenerManager, Logger::LoggableaddOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions());
ASSERT_TRUE(Network::Socket::applyOptions(listen_socket_->options(), *listen_socket_,
envoy::config::core::v3::SocketOption::STATE_BOUND));
-
- ON_CALL(listener_config_, listenSocketFactory()).WillByDefault(ReturnRef(socket_factory_));
- ON_CALL(socket_factory_, getListenSocket(_)).WillByDefault(Return(listen_socket_));
+ socket_factories_.emplace_back(std::make_unique>());
+ ON_CALL(listener_config_, listenSocketFactories()).WillByDefault(ReturnRef(socket_factories_));
+ ON_CALL(*static_cast*>(socket_factories_[0].get()),
+ getListenSocket(_))
+ .WillByDefault(Return(listen_socket_));
// Use UdpGsoBatchWriter to perform non-batched writes for the purpose of this test, if it is
// supported.
@@ -120,7 +122,9 @@ class ActiveQuicListenerTest : public testing::TestWithParam(listener_factory_->createActiveUdpListener(
- scoped_runtime_.loader(), 0, connection_handler_, *dispatcher_, listener_config_));
+ scoped_runtime_.loader(), 0, connection_handler_,
+ listener_config_.listenSocketFactories()[0]->getListenSocket(0), *dispatcher_,
+ listener_config_));
quic_dispatcher_ = ActiveQuicListenerPeer::quicDispatcher(*quic_listener_);
quic::QuicCryptoServerConfig& crypto_config =
ActiveQuicListenerPeer::cryptoConfig(*quic_listener_);
@@ -291,7 +295,7 @@ class ActiveQuicListenerTest : public testing::TestWithParam quic_listener_;
Network::ActiveUdpListenerFactoryPtr listener_factory_;
- NiceMock socket_factory_;
+ std::vector socket_factories_;
EnvoyQuicDispatcher* quic_dispatcher_;
NiceMock tls_;
diff --git a/test/config/integration/BUILD b/test/config/integration/BUILD
index 94c9f35bd99d5..0d7fbf3b4a36d 100644
--- a/test/config/integration/BUILD
+++ b/test/config/integration/BUILD
@@ -9,6 +9,7 @@ envoy_package()
exports_files([
"server.yaml",
+ "server_multiple_addresses.yaml",
"server_unix_listener.yaml",
])
@@ -36,6 +37,7 @@ filegroup(
name = "server_config_files",
srcs = [
"server.yaml",
+ "server_multiple_addresses.yaml",
"server_unix_listener.yaml",
],
)
diff --git a/test/config/integration/server_multiple_addresses.yaml b/test/config/integration/server_multiple_addresses.yaml
new file mode 100644
index 0000000000000..bc146892e9959
--- /dev/null
+++ b/test/config/integration/server_multiple_addresses.yaml
@@ -0,0 +1,180 @@
+static_resources:
+ listeners:
+ - addresses:
+ - socket_address:
+ address: "{{ ip_loopback_address }}"
+ port_value: 0
+ - socket_address:
+ address: "{{ ip_loopback_address }}"
+ port_value: 0
+ enable_reuse_port: {{ enable_reuse_port }}
+ filter_chains:
+ - filters:
+ - name: http
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+ drain_timeout: 5s
+ route_config:
+ virtual_hosts:
+ - require_tls: all
+ routes:
+ - route: {cluster: cluster_1}
+ match: {prefix: "/"}
+ domains:
+ - www.redirect.com
+ name: redirect
+ - routes:
+ - match: {prefix: "/"}
+ route:
+ cluster: cluster_1
+ - match: {prefix: "/test/long/url"}
+ route:
+ rate_limits:
+ - actions:
+ - destination_cluster: {}
+ cluster: cluster_1
+ - match: {prefix: "/test/"}
+ route: {cluster: cluster_2}
+ - match: {prefix: "/websocket/test"}
+ route:
+ prefix_rewrite: "/websocket"
+ cluster: cluster_1
+ domains:
+ - "*"
+ name: integration
+ codec_type: http1
+ stat_prefix: router
+ http_filters:
+ - name: health
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck
+ pass_through_mode: false
+ - name: envoy.filters.http.router
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+ access_log:
+ - name: accesslog
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
+ path: "{{ null_device_path }}"
+ filter:
+ or_filter:
+ filters:
+ - status_code_filter:
+ comparison:
+ op: GE
+ value:
+ default_value: 500
+ runtime_key: access_log.access_error.status
+ - duration_filter:
+ comparison:
+ op: GE
+ value:
+ default_value: 1000
+ runtime_key: access_log.access_error.duration
+ clusters:
+ - name: cluster_1
+ connect_timeout: 5s
+ load_assignment:
+ cluster_name: cluster_1
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: "{{ ip_loopback_address }}"
+ port_value: "{{ upstream_0 }}"
+ dns_lookup_family: "{{ dns_lookup_family }}"
+ - name: cluster_2
+ type: STRICT_DNS
+ connect_timeout: 5s
+ load_assignment:
+ cluster_name: cluster_2
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: localhost
+ port_value: "{{ upstream_1 }}"
+ dns_lookup_family: "{{ dns_lookup_family }}"
+ - name: cluster_3
+ connect_timeout: 5s
+ per_connection_buffer_limit_bytes: 1024
+ load_assignment:
+ cluster_name: cluster_3
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: "{{ ip_loopback_address }}"
+ port_value: "{{ upstream_0 }}"
+ dns_lookup_family: "{{ dns_lookup_family }}"
+ - name: statsd
+ type: STRICT_DNS
+ connect_timeout: 5s
+ load_assignment:
+ cluster_name: statsd
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: localhost
+ port_value: 4
+ dns_lookup_family: "{{ dns_lookup_family }}"
+ - name: redis
+ type: STRICT_DNS
+ connect_timeout: 5s
+ lb_policy: RING_HASH
+ load_assignment:
+ cluster_name: redis
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: localhost
+ port_value: 4
+ dns_lookup_family: "{{ dns_lookup_family }}"
+ outlier_detection: {}
+dynamic_resources: {}
+cluster_manager: {}
+flags_path: "/invalid_flags"
+stats_sinks:
+- name: local_stats
+ typed_config:
+ "@type": type.googleapis.com/envoy.config.metrics.v3.StatsdSink
+ address:
+ socket_address:
+ address: "{{ ip_loopback_address }}"
+ port_value: 8125
+- name: tcp_stats
+ typed_config:
+ "@type": type.googleapis.com/envoy.config.metrics.v3.StatsdSink
+ tcp_cluster_name: statsd
+layered_runtime:
+ layers:
+ - name: root
+ disk_layer:
+ symlink_root: "{{ test_tmpdir }}/test/common/runtime/test_data/current"
+ subdirectory: envoy
+ - name: override
+ disk_layer:
+ symlink_root: "{{ test_tmpdir }}/test/common/runtime/test_data/current"
+ subdirectory: envoy_override
+ append_service_cluster: true
+ - name: admin
+ admin_layer: {}
+admin:
+ access_log:
+ - name: envoy.access_loggers.file
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
+ path: "{{ null_device_path }}"
+ profile_path: "{{ test_tmpdir }}/envoy.prof"
+ address:
+ socket_address:
+ address: "{{ ip_loopback_address }}"
+ port_value: 0
diff --git a/test/config/utility.cc b/test/config/utility.cc
index 619050253de55..3af72afc8161b 100644
--- a/test/config/utility.cc
+++ b/test/config/utility.cc
@@ -73,7 +73,20 @@ std::string ConfigHelper::baseConfigNoListeners() {
Platform::null_device_path, Platform::null_device_path);
}
-std::string ConfigHelper::baseConfig() {
+std::string ConfigHelper::baseConfig(bool multiple_addresses) {
+ if (multiple_addresses) {
+ return absl::StrCat(baseConfigNoListeners(), R"EOF(
+ listeners:
+ - name: listener_0
+ addresses:
+ - socket_address:
+ address: 127.0.0.1
+ port_value: 0
+ - socket_address:
+ address: 127.0.0.1
+ port_value: 0
+)EOF");
+ }
return absl::StrCat(baseConfigNoListeners(), R"EOF(
listeners:
- name: listener_0
@@ -84,8 +97,9 @@ std::string ConfigHelper::baseConfig() {
)EOF");
}
-std::string ConfigHelper::baseUdpListenerConfig(std::string listen_address) {
- return fmt::format(R"EOF(
+std::string ConfigHelper::baseUdpListenerConfig(std::string listen_address,
+ bool multiple_addresses) {
+ std::string config = fmt::format(R"EOF(
admin:
access_log:
- name: envoy.access_loggers.file
@@ -108,6 +122,26 @@ std::string ConfigHelper::baseUdpListenerConfig(std::string listen_address) {
socket_address:
address: 127.0.0.1
port_value: 0
+)EOF",
+ Platform::null_device_path);
+
+ if (multiple_addresses) {
+ return absl::StrCat(config, fmt::format(R"EOF(
+ listeners:
+ name: listener_0
+ addresses:
+ - socket_address:
+ address: {}
+ port_value: 0
+ protocol: udp
+ - socket_address:
+ address: {}
+ port_value: 0
+ protocol: udp
+)EOF",
+ listen_address, listen_address));
+ }
+ return absl::StrCat(config, fmt::format(R"EOF(
listeners:
name: listener_0
address:
@@ -116,7 +150,7 @@ std::string ConfigHelper::baseUdpListenerConfig(std::string listen_address) {
port_value: 0
protocol: udp
)EOF",
- Platform::null_device_path, listen_address);
+ listen_address));
}
std::string ConfigHelper::tcpProxyConfig() {
@@ -177,11 +211,11 @@ name: "envoy.filters.listener.tls_inspector"
)EOF";
}
-std::string ConfigHelper::httpProxyConfig(bool downstream_use_quic) {
+std::string ConfigHelper::httpProxyConfig(bool downstream_use_quic, bool multiple_addresses) {
if (downstream_use_quic) {
- return quicHttpProxyConfig();
+ return quicHttpProxyConfig(multiple_addresses);
}
- return absl::StrCat(baseConfig(), fmt::format(R"EOF(
+ return absl::StrCat(baseConfig(multiple_addresses), fmt::format(R"EOF(
filter_chains:
filters:
name: http
@@ -213,14 +247,15 @@ std::string ConfigHelper::httpProxyConfig(bool downstream_use_quic) {
domains: "*"
name: route_config_0
)EOF",
- Platform::null_device_path));
+ Platform::null_device_path));
}
// TODO(danzh): For better compatibility with HTTP integration test framework,
// it's better to combine with HTTP_PROXY_CONFIG, and use config modifiers to
// specify quic specific things.
-std::string ConfigHelper::quicHttpProxyConfig() {
- return absl::StrCat(baseUdpListenerConfig("127.0.0.1"), fmt::format(R"EOF(
+std::string ConfigHelper::quicHttpProxyConfig(bool multiple_addresses) {
+ return absl::StrCat(baseUdpListenerConfig("127.0.0.1", multiple_addresses),
+ fmt::format(R"EOF(
filter_chains:
transport_socket:
name: envoy.transport_sockets.quic
@@ -256,7 +291,7 @@ std::string ConfigHelper::quicHttpProxyConfig() {
udp_listener_config:
quic_options: {{}}
)EOF",
- Platform::null_device_path));
+ Platform::null_device_path));
}
std::string ConfigHelper::defaultBufferFilter() {
@@ -685,23 +720,37 @@ ConfigHelper::ConfigHelper(const Network::Address::IpVersion version, Api::Api&
auto* static_resources = bootstrap_.mutable_static_resources();
for (int i = 0; i < static_resources->listeners_size(); ++i) {
auto* listener = static_resources->mutable_listeners(i);
- if (listener->mutable_address()->has_envoy_internal_address()) {
- ENVOY_LOG_MISC(
- debug, "Listener {} has internal address {}. Will not reset to loop back socket address.",
- i, listener->mutable_address()->envoy_internal_address().server_listener_name());
- continue;
- }
- if (listener->mutable_address()->has_pipe()) {
- ENVOY_LOG_MISC(debug,
- "Listener {} has pipe address {}. Will not reset to loop back socket address.",
- i, listener->mutable_address()->pipe().path());
- continue;
- }
- auto* listener_socket_addr = listener->mutable_address()->mutable_socket_address();
- if (listener_socket_addr->address() == "0.0.0.0" || listener_socket_addr->address() == "::") {
- listener_socket_addr->set_address(Network::Test::getAnyAddressString(version));
+ if (listener->addresses_size() == 0) {
+ if (listener->mutable_address()->has_envoy_internal_address()) {
+ ENVOY_LOG_MISC(
+ debug,
+ "Listener {} has internal address {}. Will not reset to loop back socket address.", i,
+ listener->mutable_address()->envoy_internal_address().server_listener_name());
+ continue;
+ }
+ if (listener->mutable_address()->has_pipe()) {
+ ENVOY_LOG_MISC(
+ debug, "Listener {} has pipe address {}. Will not reset to loop back socket address.",
+ i, listener->mutable_address()->pipe().path());
+ continue;
+ }
+
+ auto* listener_socket_addr = listener->mutable_address()->mutable_socket_address();
+ if (listener_socket_addr->address() == "0.0.0.0" || listener_socket_addr->address() == "::") {
+ listener_socket_addr->set_address(Network::Test::getAnyAddressString(version));
+ } else {
+ listener_socket_addr->set_address(Network::Test::getLoopbackAddressString(version));
+ }
} else {
- listener_socket_addr->set_address(Network::Test::getLoopbackAddressString(version));
+ for (int i = 0; i < listener->addresses_size(); i++) {
+ auto* listener_socket_addr = listener->mutable_addresses(i)->mutable_socket_address();
+ if (listener_socket_addr->address() == "0.0.0.0" ||
+ listener_socket_addr->address() == "::") {
+ listener_socket_addr->set_address(Network::Test::getAnyAddressString(version));
+ } else {
+ listener_socket_addr->set_address(Network::Test::getLoopbackAddressString(version));
+ }
+ }
}
}
diff --git a/test/config/utility.h b/test/config/utility.h
index 4cfca969e90ff..040aed8120b2c 100644
--- a/test/config/utility.h
+++ b/test/config/utility.h
@@ -138,7 +138,7 @@ class ConfigHelper {
// By default, this runs with an L7 proxy config, but config can be set to TCP_PROXY_CONFIG
// to test L4 proxying.
ConfigHelper(const Network::Address::IpVersion version, Api::Api& api,
- const std::string& config = httpProxyConfig(false));
+ const std::string& config = httpProxyConfig(false, false));
static void
initializeTls(const ServerSslOptions& options,
@@ -154,10 +154,11 @@ class ConfigHelper {
static std::string baseConfigNoListeners();
// A basic configuration (admin port, cluster_0, one listener) with no network filters.
- static std::string baseConfig();
+ static std::string baseConfig(bool multiple_addresses = false);
// A basic configuration (admin port, cluster_0, one udp listener) with no network filters.
- static std::string baseUdpListenerConfig(std::string listen_address = "0.0.0.0");
+ static std::string baseUdpListenerConfig(std::string listen_address = "0.0.0.0",
+ bool multiple_addresses = false);
// A string for a tls inspector listener filter which can be used with addListenerFilter()
static std::string tlsInspectorFilter(bool enable_ja3_fingerprinting = false);
@@ -168,9 +169,10 @@ class ConfigHelper {
// A basic configuration for L4 proxying.
static std::string tcpProxyConfig();
// A basic configuration for L7 proxying.
- static std::string httpProxyConfig(bool downstream_use_quic = false);
+ static std::string httpProxyConfig(bool downstream_use_quic = false,
+ bool multiple_addresses = false);
// A basic configuration for L7 proxying with QUIC transport.
- static std::string quicHttpProxyConfig();
+ static std::string quicHttpProxyConfig(bool multiple_addresses = false);
// A string for a basic buffer filter, which can be used with prependFilter()
static std::string defaultBufferFilter();
// A string for a small buffer filter, which can be used with prependFilter()
diff --git a/test/extensions/common/proxy_protocol/proxy_protocol_regression_test.cc b/test/extensions/common/proxy_protocol/proxy_protocol_regression_test.cc
index fe07517d8c84d..c4e86e7c3b085 100644
--- a/test/extensions/common/proxy_protocol/proxy_protocol_regression_test.cc
+++ b/test/extensions/common/proxy_protocol/proxy_protocol_regression_test.cc
@@ -46,10 +46,16 @@ class ProxyProtocolRegressionTest : public testing::TestWithParam());
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ socketType())
+ .WillOnce(Return(Network::Socket::Type::Stream));
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ localAddress())
.WillRepeatedly(ReturnRef(socket_->connectionInfoProvider().localAddress()));
- EXPECT_CALL(socket_factory_, getListenSocket(_)).WillOnce(Return(socket_));
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ getListenSocket(_))
+ .WillOnce(Return(socket_));
connection_handler_->addListener(absl::nullopt, *this, runtime_);
conn_ = dispatcher_->createClientConnection(socket_->connectionInfoProvider().localAddress(),
Network::Address::InstanceConstSharedPtr(),
@@ -60,8 +66,10 @@ class ProxyProtocolRegressionTest : public testing::TestWithParam& listenSocketFactories() override {
+ return socket_factories_;
+ }
+ bool bindToPort() const override { return true; }
bool handOffRestoredDestinationConnections() const override { return false; }
uint32_t perConnectionBufferLimitBytes() const override { return 0; }
std::chrono::milliseconds listenerFiltersTimeout() const override { return {}; }
@@ -79,7 +87,9 @@ class ProxyProtocolRegressionTest : public testing::TestWithParam& accessLogs() const override {
return empty_access_logs_;
}
@@ -166,7 +176,7 @@ class ProxyProtocolRegressionTest : public testing::TestWithParam socket_;
- Network::MockListenSocketFactory socket_factory_;
+ std::vector socket_factories_;
Network::NopConnectionBalancerImpl connection_balancer_;
Network::ConnectionHandlerPtr connection_handler_;
Network::MockFilterChainFactory factory_;
diff --git a/test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.cc b/test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.cc
index 88241d66d935d..06edc6a57b87c 100644
--- a/test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.cc
+++ b/test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.cc
@@ -35,10 +35,16 @@ ListenerFilterWithDataFuzzer::ListenerFilterWithDataFuzzer()
connection_handler_(new Server::ConnectionHandlerImpl(*dispatcher_, absl::nullopt)),
name_("proxy"), filter_chain_(Network::Test::createEmptyFilterChainWithRawBufferSockets()),
init_manager_(nullptr) {
- EXPECT_CALL(socket_factory_, socketType()).WillOnce(Return(Network::Socket::Type::Stream));
- EXPECT_CALL(socket_factory_, localAddress())
+ socket_factories_.emplace_back(std::make_unique());
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ socketType())
+ .WillOnce(Return(Network::Socket::Type::Stream));
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ localAddress())
.WillRepeatedly(ReturnRef(socket_->connectionInfoProvider().localAddress()));
- EXPECT_CALL(socket_factory_, getListenSocket(_)).WillOnce(Return(socket_));
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ getListenSocket(_))
+ .WillOnce(Return(socket_));
connection_handler_->addListener(absl::nullopt, *this, runtime_);
conn_ = dispatcher_->createClientConnection(socket_->connectionInfoProvider().localAddress(),
Network::Address::InstanceConstSharedPtr(),
diff --git a/test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.h b/test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.h
index 9a52da99823c0..4dc25ce0c30a3 100644
--- a/test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.h
+++ b/test/extensions/filters/listener/common/fuzz/listener_filter_fuzzer.h
@@ -48,8 +48,10 @@ class ListenerFilterWithDataFuzzer : public Network::ListenerConfig,
// Network::ListenerConfig
Network::FilterChainManager& filterChainManager() override { return *this; }
Network::FilterChainFactory& filterChainFactory() override { return factory_; }
- Network::ListenSocketFactory& listenSocketFactory() override { return socket_factory_; }
- bool bindToPort() override { return true; }
+ const std::vector& listenSocketFactories() override {
+ return socket_factories_;
+ }
+ bool bindToPort() const override { return true; }
bool handOffRestoredDestinationConnections() const override { return false; }
uint32_t perConnectionBufferLimitBytes() const override { return 0; }
std::chrono::milliseconds listenerFiltersTimeout() const override { return {}; }
@@ -67,7 +69,9 @@ class ListenerFilterWithDataFuzzer : public Network::ListenerConfig,
envoy::config::core::v3::TrafficDirection direction() const override {
return envoy::config::core::v3::UNSPECIFIED;
}
- Network::ConnectionBalancer& connectionBalancer() override { return connection_balancer_; }
+ Network::ConnectionBalancer& connectionBalancer(const Network::Address::Instance&) override {
+ return connection_balancer_;
+ }
const std::vector& accessLogs() const override {
return empty_access_logs_;
}
@@ -98,7 +102,7 @@ class ListenerFilterWithDataFuzzer : public Network::ListenerConfig,
BasicResourceLimitImpl open_connections_;
Event::DispatcherPtr dispatcher_;
std::shared_ptr socket_;
- Network::MockListenSocketFactory socket_factory_;
+ std::vector socket_factories_;
Network::NopConnectionBalancerImpl connection_balancer_;
Network::ConnectionHandlerPtr connection_handler_;
Network::MockFilterChainFactory factory_;
diff --git a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc
index afeba9505b0ac..53c031efef6cf 100644
--- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc
+++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc
@@ -63,10 +63,16 @@ class ProxyProtocolTest : public testing::TestWithParam());
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ socketType())
+ .WillOnce(Return(Network::Socket::Type::Stream));
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ localAddress())
.WillRepeatedly(ReturnRef(socket_->connectionInfoProvider().localAddress()));
- EXPECT_CALL(socket_factory_, getListenSocket(_)).WillOnce(Return(socket_));
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ getListenSocket(_))
+ .WillOnce(Return(socket_));
connection_handler_->addListener(absl::nullopt, *this, runtime_);
conn_ = dispatcher_->createClientConnection(socket_->connectionInfoProvider().localAddress(),
Network::Address::InstanceConstSharedPtr(),
@@ -77,8 +83,10 @@ class ProxyProtocolTest : public testing::TestWithParam& listenSocketFactories() override {
+ return socket_factories_;
+ }
+ bool bindToPort() const override { return true; }
bool handOffRestoredDestinationConnections() const override { return false; }
uint32_t perConnectionBufferLimitBytes() const override { return 0; }
std::chrono::milliseconds listenerFiltersTimeout() const override { return {}; }
@@ -96,7 +104,9 @@ class ProxyProtocolTest : public testing::TestWithParam& accessLogs() const override {
return empty_access_logs_;
}
@@ -198,7 +208,7 @@ class ProxyProtocolTest : public testing::TestWithParam socket_;
- Network::MockListenSocketFactory socket_factory_;
+ std::vector socket_factories_;
Network::NopConnectionBalancerImpl connection_balancer_;
Network::ConnectionHandlerPtr connection_handler_;
Network::MockFilterChainFactory factory_;
@@ -1745,10 +1755,16 @@ class WildcardProxyProtocolTest : public testing::TestWithParam());
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ socketType())
+ .WillOnce(Return(Network::Socket::Type::Stream));
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ localAddress())
.WillRepeatedly(ReturnRef(socket_->connectionInfoProvider().localAddress()));
- EXPECT_CALL(socket_factory_, getListenSocket(_)).WillOnce(Return(socket_));
+ EXPECT_CALL(*static_cast(socket_factories_[0].get()),
+ getListenSocket(_))
+ .WillOnce(Return(socket_));
connection_handler_->addListener(absl::nullopt, *this, runtime_);
conn_ = dispatcher_->createClientConnection(local_dst_address_,
Network::Address::InstanceConstSharedPtr(),
@@ -1769,8 +1785,10 @@ class WildcardProxyProtocolTest : public testing::TestWithParam& listenSocketFactories() override {
+ return socket_factories_;
+ }
+ bool bindToPort() const override { return true; }
bool handOffRestoredDestinationConnections() const override { return false; }
uint32_t perConnectionBufferLimitBytes() const override { return 0; }
std::chrono::milliseconds listenerFiltersTimeout() const override { return {}; }
@@ -1788,7 +1806,9 @@ class WildcardProxyProtocolTest : public testing::TestWithParam& accessLogs() const override {
return empty_access_logs_;
}
@@ -1849,7 +1869,7 @@ class WildcardProxyProtocolTest : public testing::TestWithParam socket_factories_;
std::shared_ptr socket_;
Network::Address::InstanceConstSharedPtr local_dst_address_;
Network::NopConnectionBalancerImpl connection_balancer_;
diff --git a/test/extensions/filters/network/echo/echo_integration_test.cc b/test/extensions/filters/network/echo/echo_integration_test.cc
index e18b7cec2ee52..0416bc36cd5b7 100644
--- a/test/extensions/filters/network/echo/echo_integration_test.cc
+++ b/test/extensions/filters/network/echo/echo_integration_test.cc
@@ -78,8 +78,8 @@ name: new_listener
.listenerManager()
.listeners()[1]
.get()
- .listenSocketFactory()
- .localAddress()
+ .listenSocketFactories()[0]
+ ->localAddress()
->ip()
->port();
diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc
index 90a7e4d570a5d..9cab0691b21a7 100644
--- a/test/integration/base_integration_test.cc
+++ b/test/integration/base_integration_test.cc
@@ -348,11 +348,16 @@ void BaseIntegrationTest::registerTestServerPorts(const std::vector
auto listener_it = listeners.cbegin();
auto port_it = port_names.cbegin();
- for (; port_it != port_names.end() && listener_it != listeners.end(); ++port_it, ++listener_it) {
- const auto listen_addr = listener_it->get().listenSocketFactory().localAddress();
- if (listen_addr->type() == Network::Address::Type::Ip) {
- ENVOY_LOG(debug, "registered '{}' as port {}.", *port_it, listen_addr->ip()->port());
- registerPort(*port_it, listen_addr->ip()->port());
+ for (; port_it != port_names.end() && listener_it != listeners.end(); ++listener_it) {
+ auto socket_factory_it = listener_it->get().listenSocketFactories().begin();
+ for (; socket_factory_it != listener_it->get().listenSocketFactories().end() &&
+ port_it != port_names.end();
+ ++socket_factory_it, ++port_it) {
+ const auto listen_addr = (*socket_factory_it)->localAddress();
+ if (listen_addr->type() == Network::Address::Type::Ip) {
+ ENVOY_LOG(debug, "registered '{}' as port {}.", *port_it, listen_addr->ip()->port());
+ registerPort(*port_it, listen_addr->ip()->port());
+ }
}
}
const auto admin_addr =
diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc
index 5312bdd4c7141..e392418f08566 100644
--- a/test/integration/fake_upstream.cc
+++ b/test/integration/fake_upstream.cc
@@ -561,7 +561,6 @@ FakeUpstream::FakeUpstream(Network::TransportSocketFactoryPtr&& transport_socket
: http_type_(config.upstream_protocol_), http2_options_(config.http2_options_),
http3_options_(config.http3_options_), quic_options_(config.quic_options_),
socket_(Network::SocketSharedPtr(listen_socket.release())),
- socket_factory_(std::make_unique(socket_)),
api_(Api::createApiForTest(stats_store_)), time_system_(config.time_system_),
dispatcher_(api_->allocateDispatcher("fake_upstream")),
handler_(new Server::ConnectionHandlerImpl(*dispatcher_, 0)), config_(config),
@@ -571,6 +570,7 @@ FakeUpstream::FakeUpstream(Network::TransportSocketFactoryPtr&& transport_socket
stats_scope_(stats_store_.createScope("test_server_scope")) {
ENVOY_LOG(info, "starting fake server at {}. UDP={} codec={}", localAddress()->asString(),
config.udp_fake_upstream_.has_value(), FakeHttpConnection::typeToString(http_type_));
+ socket_factories_.emplace_back(std::make_unique(socket_));
if (config.udp_fake_upstream_.has_value() &&
config.udp_fake_upstream_->max_rx_datagram_size_.has_value()) {
listener_.udp_listener_config_.config_.mutable_downstream_socket_config()
@@ -624,7 +624,7 @@ void FakeUpstream::createUdpListenerFilterChain(Network::UdpListenerFilterManage
}
void FakeUpstream::threadRoutine() {
- socket_factory_->doFinalPreWorkerInit();
+ socket_factories_[0]->doFinalPreWorkerInit();
handler_->addListener(absl::nullopt, listener_, runtime_);
server_initialized_.setReady();
dispatcher_->run(Event::Dispatcher::RunType::Block);
diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h
index 1f49cfacea48d..bc3fd3f5b9a87 100644
--- a/test/integration/fake_upstream.h
+++ b/test/integration/fake_upstream.h
@@ -760,7 +760,8 @@ class FakeUpstream : Logger::Loggable,
// Network::UdpListenerConfig
Network::ActiveUdpListenerFactory& listenerFactory() override { return *listener_factory_; }
Network::UdpPacketWriterFactory& packetWriterFactory() override { return *writer_factory_; }
- Network::UdpListenerWorkerRouter& listenerWorkerRouter() override {
+ Network::UdpListenerWorkerRouter&
+ listenerWorkerRouter(const Network::Address::Instance&) override {
return listener_worker_router_;
}
const envoy::config::listener::v3::UdpListenerConfig& config() override { return config_; }
@@ -794,10 +795,10 @@ class FakeUpstream : Logger::Loggable,
// Network::ListenerConfig
Network::FilterChainManager& filterChainManager() override { return parent_; }
Network::FilterChainFactory& filterChainFactory() override { return parent_; }
- Network::ListenSocketFactory& listenSocketFactory() override {
- return *parent_.socket_factory_;
+ const std::vector& listenSocketFactories() override {
+ return parent_.socket_factories_;
}
- bool bindToPort() override { return true; }
+ bool bindToPort() const override { return true; }
bool handOffRestoredDestinationConnections() const override { return false; }
uint32_t perConnectionBufferLimitBytes() const override { return 0; }
std::chrono::milliseconds listenerFiltersTimeout() const override { return {}; }
@@ -806,10 +807,10 @@ class FakeUpstream : Logger::Loggable,
uint64_t listenerTag() const override { return 0; }
const std::string& name() const override { return name_; }
Network::UdpListenerConfigOptRef udpListenerConfig() override { return udp_listener_config_; }
- Network::InternalListenerConfigOptRef internalListenerConfig() override {
- return Network::InternalListenerConfigOptRef();
+ Network::InternalListenerConfigOptRef internalListenerConfig() override { return {}; }
+ Network::ConnectionBalancer& connectionBalancer(const Network::Address::Instance&) override {
+ return connection_balancer_;
}
- Network::ConnectionBalancer& connectionBalancer() override { return connection_balancer_; }
envoy::config::core::v3::TrafficDirection direction() const override {
return envoy::config::core::v3::UNSPECIFIED;
}
@@ -845,7 +846,7 @@ class FakeUpstream : Logger::Loggable,
const envoy::config::core::v3::Http3ProtocolOptions http3_options_;
envoy::config::listener::v3::QuicProtocolOptions quic_options_;
Network::SocketSharedPtr socket_;
- Network::ListenSocketFactoryPtr socket_factory_;
+ std::vector socket_factories_;
ConditionalInitializer server_initialized_;
// Guards any objects which can be altered both in the upstream thread and the
// main test thread.
diff --git a/test/integration/hotrestart_test.sh b/test/integration/hotrestart_test.sh
index 5c26beaae5299..baf23e8cc8374 100755
--- a/test/integration/hotrestart_test.sh
+++ b/test/integration/hotrestart_test.sh
@@ -75,6 +75,19 @@ sed -e "s#{{ upstream_. }}#0#g" "${TEST_SRCDIR}/envoy"/test/config/integration/s
cat > "${HOT_RESTART_JSON_REUSE_PORT}"
JSON_TEST_ARRAY+=("${HOT_RESTART_JSON_REUSE_PORT}")
+# Test reuse_port listener with multiple addresses.
+HOT_RESTART_JSON_REUSE_PORT_MULTI_ADDRESSES="${TEST_TMPDIR}"/hot_restart_v4_multiple_addresses.yaml
+echo "building ${HOT_RESTART_JSON_V4} ..."
+sed -e "s#{{ upstream_. }}#0#g" "${TEST_SRCDIR}/envoy"/test/config/integration/server_multiple_addresses.yaml | \
+ sed -e "s#{{ test_rundir }}#$TEST_SRCDIR/envoy#" | \
+ sed -e "s#{{ test_tmpdir }}#$TEST_TMPDIR#" | \
+ sed -e "s#{{ ip_loopback_address }}#127.0.0.1#" | \
+ sed -e "s#{{ enable_reuse_port }}#true#" | \
+ sed -e "s#{{ dns_lookup_family }}#V4_ONLY#" | \
+ sed -e "s#{{ null_device_path }}#/dev/null#" | \
+ cat > "${HOT_RESTART_JSON_REUSE_PORT_MULTI_ADDRESSES}"
+JSON_TEST_ARRAY+=("${HOT_RESTART_JSON_REUSE_PORT_MULTI_ADDRESSES}")
+
# Shared memory size varies by architecture
SHARED_MEMORY_SIZE="104"
[[ "$(uname -m)" == "aarch64" ]] && SHARED_MEMORY_SIZE="120"
@@ -93,8 +106,9 @@ function run_testsuite() {
fi
start_test validation
+
check "${ENVOY_BIN}" -c "${HOT_RESTART_JSON}" --mode validate --service-cluster cluster \
- --service-node node --disable-hot-restart
+ --service-node node --disable-hot-restart -l debug
BASE_ID_PATH=$(mktemp 'envoy_test_base_id.XXXXXX')
echo "Selected dynamic base id path ${BASE_ID_PATH}"
diff --git a/test/integration/integration_admin_test.cc b/test/integration/integration_admin_test.cc
index 93f9e7c55e584..2678ea0607cc7 100644
--- a/test/integration/integration_admin_test.cc
+++ b/test/integration/integration_admin_test.cc
@@ -325,10 +325,11 @@ TEST_P(IntegrationAdminTest, Admin) {
auto listeners = test_server_->server().listenerManager().listeners();
auto listener_it = listeners.cbegin();
for (; listener_it != listeners.end(); ++listener_it) {
- EXPECT_THAT(response->body(),
- HasSubstr(fmt::format(
- "{}::{}", listener_it->get().name(),
- listener_it->get().listenSocketFactory().localAddress()->asString())));
+ for (auto& socket_factory : listener_it->get().listenSocketFactories()) {
+ EXPECT_THAT(response->body(),
+ HasSubstr(fmt::format("{}::{}", listener_it->get().name(),
+ socket_factory->localAddress()->asString())));
+ }
}
EXPECT_EQ("200", request("admin", "GET", "/listeners?format=json", response));
@@ -341,12 +342,16 @@ TEST_P(IntegrationAdminTest, Admin) {
listener_it = listeners.cbegin();
for (; listener_info_it != listener_info.end() && listener_it != listeners.end();
++listener_info_it, ++listener_it) {
- auto local_address = (*listener_info_it)->getObject("local_address");
- auto socket_address = local_address->getObject("socket_address");
- EXPECT_EQ(listener_it->get().listenSocketFactory().localAddress()->ip()->addressAsString(),
- socket_address->getString("address"));
- EXPECT_EQ(listener_it->get().listenSocketFactory().localAddress()->ip()->port(),
- socket_address->getInteger("port_value"));
+ std::vector local_addresses =
+ (*listener_info_it)->getObjectArray("local_addresses");
+ for (std::vector::size_type i = 0; i < local_addresses.size(); i++) {
+ auto socket_address = local_addresses[i]->getObject("socket_address");
+ EXPECT_EQ(
+ listener_it->get().listenSocketFactories()[i]->localAddress()->ip()->addressAsString(),
+ socket_address->getString("address"));
+ EXPECT_EQ(listener_it->get().listenSocketFactories()[i]->localAddress()->ip()->port(),
+ socket_address->getInteger("port_value"));
+ }
}
EXPECT_EQ("200", request("admin", "GET", "/config_dump", response));
diff --git a/test/integration/integration_admin_test.h b/test/integration/integration_admin_test.h
index 7d036b98a7230..f149d9f362805 100644
--- a/test/integration/integration_admin_test.h
+++ b/test/integration/integration_admin_test.h
@@ -15,17 +15,26 @@ namespace Envoy {
class IntegrationAdminTest : public HttpProtocolIntegrationTest {
public:
void initialize() override {
- config_helper_.addConfigModifier(
- [](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void {
- auto& hist_settings =
- *bootstrap.mutable_stats_config()->mutable_histogram_bucket_settings();
- envoy::config::metrics::v3::HistogramBucketSettings* setting = hist_settings.Add();
- setting->mutable_match()->set_suffix("upstream_cx_connect_ms");
- setting->mutable_buckets()->Add(1);
- setting->mutable_buckets()->Add(2);
- setting->mutable_buckets()->Add(3);
- setting->mutable_buckets()->Add(4);
- });
+ config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap)
+ -> void {
+ auto listener_config = bootstrap.mutable_static_resources()->mutable_listeners(0);
+ listener_config->clear_address();
+ auto address1 = listener_config->add_addresses();
+ envoy::config::core::v3::SocketAddress& socket_address1 = *address1->mutable_socket_address();
+ socket_address1.set_address("127.0.0.1");
+ socket_address1.set_port_value(0);
+ auto address2 = listener_config->add_addresses();
+ envoy::config::core::v3::SocketAddress& socket_address2 = *address2->mutable_socket_address();
+ socket_address2.set_address("127.0.0.2");
+ socket_address2.set_port_value(0);
+ auto& hist_settings = *bootstrap.mutable_stats_config()->mutable_histogram_bucket_settings();
+ envoy::config::metrics::v3::HistogramBucketSettings* setting = hist_settings.Add();
+ setting->mutable_match()->set_suffix("upstream_cx_connect_ms");
+ setting->mutable_buckets()->Add(1);
+ setting->mutable_buckets()->Add(2);
+ setting->mutable_buckets()->Add(3);
+ setting->mutable_buckets()->Add(4);
+ });
HttpIntegrationTest::initialize();
}
diff --git a/test/integration/listener_lds_integration_test.cc b/test/integration/listener_lds_integration_test.cc
index 7226abde41ba1..3a35b93411044 100644
--- a/test/integration/listener_lds_integration_test.cc
+++ b/test/integration/listener_lds_integration_test.cc
@@ -26,18 +26,21 @@ using testing::StartsWith;
namespace Envoy {
namespace {
-class ListenerIntegrationTest : public HttpIntegrationTest,
- public Grpc::GrpcClientIntegrationParamTest {
-protected:
+class ListenerIntegrationTestBase : public HttpIntegrationTest {
+public:
struct FakeUpstreamInfo {
FakeHttpConnectionPtr connection_;
FakeUpstream* upstream_{};
absl::flat_hash_map stream_by_resource_name_;
};
- ListenerIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, ipVersion()) {}
+ ListenerIntegrationTestBase(Network::Address::IpVersion version, const std::string& config)
+ : HttpIntegrationTest(Http::CodecType::HTTP1, version, config) {}
+
+ ~ListenerIntegrationTestBase() override { resetConnections(); }
- ~ListenerIntegrationTest() override { resetConnections(); }
+ virtual void setUpGrpcRds() PURE;
+ virtual void setUpGrpcLds() PURE;
void initialize() override {
// We want to use the GRPC based LDS.
@@ -59,43 +62,13 @@ class ListenerIntegrationTest : public HttpIntegrationTest,
ConfigHelper::setHttp2(*rds_cluster);
});
- config_helper_.addConfigModifier(
- [this](
- envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
- http_connection_manager) {
- auto* rds_config = http_connection_manager.mutable_rds();
- rds_config->set_route_config_name(route_table_name_);
- rds_config->mutable_config_source()->set_resource_api_version(
- envoy::config::core::v3::ApiVersion::V3);
- envoy::config::core::v3::ApiConfigSource* rds_api_config_source =
- rds_config->mutable_config_source()->mutable_api_config_source();
- rds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC);
- rds_api_config_source->set_transport_api_version(envoy::config::core::v3::V3);
- envoy::config::core::v3::GrpcService* grpc_service =
- rds_api_config_source->add_grpc_services();
- setGrpcService(*grpc_service, "rds_cluster", getRdsFakeUpstream().localAddress());
- });
-
+ setUpGrpcRds();
// Note this has to be the last modifier as it nuke static_resource listeners.
setUpGrpcLds();
+ ENVOY_LOG_MISC(debug, "listener config: {}", listener_config_.DebugString());
+
HttpIntegrationTest::initialize();
}
- void setUpGrpcLds() {
- config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
- listener_config_.Swap(bootstrap.mutable_static_resources()->mutable_listeners(0));
- listener_config_.set_name(listener_name_);
- ENVOY_LOG_MISC(debug, "listener config: {}", listener_config_.DebugString());
- bootstrap.mutable_static_resources()->mutable_listeners()->Clear();
- auto* lds_config_source = bootstrap.mutable_dynamic_resources()->mutable_lds_config();
- lds_config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3);
- auto* lds_api_config_source = lds_config_source->mutable_api_config_source();
- lds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC);
- lds_api_config_source->set_transport_api_version(envoy::config::core::v3::V3);
- envoy::config::core::v3::GrpcService* grpc_service =
- lds_api_config_source->add_grpc_services();
- setGrpcService(*grpc_service, "lds_cluster", getLdsFakeUpstream().localAddress());
- });
- }
void createUpstreams() override {
HttpIntegrationTest::createUpstreams();
@@ -194,9 +167,100 @@ class ListenerIntegrationTest : public HttpIntegrationTest,
FakeUpstreamInfo rds_upstream_info_;
};
+class ListenerIntegrationTest : public ListenerIntegrationTestBase,
+ public Grpc::GrpcClientIntegrationParamTest {
+public:
+ ListenerIntegrationTest()
+ : ListenerIntegrationTestBase(ipVersion(),
+ ConfigHelper::httpProxyConfig(/*downstream_use_quic=*/false,
+ /*multiple_addresses=*/false)) {}
+
+ void setUpGrpcRds() override {
+ config_helper_.addConfigModifier(
+ [this](
+ envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
+ http_connection_manager) {
+ auto* rds_config = http_connection_manager.mutable_rds();
+ rds_config->set_route_config_name(route_table_name_);
+ rds_config->mutable_config_source()->set_resource_api_version(
+ envoy::config::core::v3::ApiVersion::V3);
+ envoy::config::core::v3::ApiConfigSource* rds_api_config_source =
+ rds_config->mutable_config_source()->mutable_api_config_source();
+ rds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC);
+ rds_api_config_source->set_transport_api_version(envoy::config::core::v3::V3);
+ envoy::config::core::v3::GrpcService* grpc_service =
+ rds_api_config_source->add_grpc_services();
+ setGrpcService(*grpc_service, "rds_cluster", getRdsFakeUpstream().localAddress());
+ });
+ }
+
+ void setUpGrpcLds() override {
+ config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
+ listener_config_.Swap(bootstrap.mutable_static_resources()->mutable_listeners(0));
+ listener_config_.set_name(listener_name_);
+ bootstrap.mutable_static_resources()->mutable_listeners()->Clear();
+ auto* lds_config_source = bootstrap.mutable_dynamic_resources()->mutable_lds_config();
+ lds_config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3);
+ auto* lds_api_config_source = lds_config_source->mutable_api_config_source();
+ lds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC);
+ lds_api_config_source->set_transport_api_version(envoy::config::core::v3::V3);
+ envoy::config::core::v3::GrpcService* grpc_service =
+ lds_api_config_source->add_grpc_services();
+ setGrpcService(*grpc_service, "lds_cluster", getLdsFakeUpstream().localAddress());
+ });
+ }
+};
+
+class ListenerMultiAddressesIntegrationTest : public ListenerIntegrationTestBase,
+ public Grpc::GrpcClientIntegrationParamTest {
+public:
+ ListenerMultiAddressesIntegrationTest()
+ : ListenerIntegrationTestBase(ipVersion(),
+ ConfigHelper::httpProxyConfig(/*downstream_use_quic=*/false,
+ /*multiple_addresses=*/true)) {}
+
+ void setUpGrpcRds() override {
+ config_helper_.addConfigModifier(
+ [this](
+ envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
+ http_connection_manager) {
+ auto* rds_config = http_connection_manager.mutable_rds();
+ rds_config->set_route_config_name(route_table_name_);
+ rds_config->mutable_config_source()->set_resource_api_version(
+ envoy::config::core::v3::ApiVersion::V3);
+ envoy::config::core::v3::ApiConfigSource* rds_api_config_source =
+ rds_config->mutable_config_source()->mutable_api_config_source();
+ rds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC);
+ rds_api_config_source->set_transport_api_version(envoy::config::core::v3::V3);
+ envoy::config::core::v3::GrpcService* grpc_service =
+ rds_api_config_source->add_grpc_services();
+ setGrpcService(*grpc_service, "rds_cluster", getRdsFakeUpstream().localAddress());
+ });
+ }
+
+ void setUpGrpcLds() override {
+ config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
+ listener_config_.Swap(bootstrap.mutable_static_resources()->mutable_listeners(0));
+ listener_config_.set_name(listener_name_);
+ bootstrap.mutable_static_resources()->mutable_listeners()->Clear();
+ auto* lds_config_source = bootstrap.mutable_dynamic_resources()->mutable_lds_config();
+ lds_config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3);
+ auto* lds_api_config_source = lds_config_source->mutable_api_config_source();
+ lds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC);
+ lds_api_config_source->set_transport_api_version(envoy::config::core::v3::V3);
+ envoy::config::core::v3::GrpcService* grpc_service =
+ lds_api_config_source->add_grpc_services();
+ setGrpcService(*grpc_service, "lds_cluster", getLdsFakeUpstream().localAddress());
+ });
+ }
+};
+
INSTANTIATE_TEST_SUITE_P(IpVersionsAndGrpcTypes, ListenerIntegrationTest,
GRPC_CLIENT_INTEGRATION_PARAMS);
+INSTANTIATE_TEST_SUITE_P(IpVersionsAndGrpcTypes, ListenerMultiAddressesIntegrationTest,
+ GRPC_CLIENT_INTEGRATION_PARAMS);
+
// Tests that an update with an unknown filter config proto is rejected.
TEST_P(ListenerIntegrationTest, CleanlyRejectsUnknownFilterConfigProto) {
on_server_init_function_ = [&]() {
@@ -373,6 +437,67 @@ TEST_P(ListenerIntegrationTest, RemoveLastUninitializedListener) {
EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized);
}
+TEST_P(ListenerMultiAddressesIntegrationTest, BasicSuccessWithMultiAddresses) {
+ on_server_init_function_ = [&]() {
+ createLdsStream();
+ sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "1");
+ createRdsStream(route_table_name_);
+ };
+ initialize();
+ test_server_->waitForCounterGe("listener_manager.lds.update_success", 1);
+ // testing-listener-0 is not initialized as we haven't pushed any RDS yet.
+ EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing);
+ // Workers not started, the LDS added listener 0 is in active_listeners_ list.
+ EXPECT_EQ(test_server_->server().listenerManager().listeners().size(), 1);
+ registerTestServerPorts({"address1", "address2"});
+
+ const std::string route_config_tmpl = R"EOF(
+ name: {}
+ virtual_hosts:
+ - name: integration
+ domains: ["*"]
+ routes:
+ - match: {{ prefix: "/" }}
+ route: {{ cluster: {} }}
+)EOF";
+ sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1");
+ test_server_->waitForCounterGe(
+ fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1);
+ // Now testing-listener-0 finishes initialization, Server initManager will be ready.
+ EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized);
+
+ test_server_->waitUntilListenersReady();
+ // NOTE: The line above doesn't tell you if listener is up and listening.
+ test_server_->waitForCounterGe("listener_manager.listener_create_success", 1);
+ // Request is sent to cluster_0.
+
+ int response_size = 800;
+ int request_size = 10;
+ Http::TestResponseHeaderMapImpl response_headers{{":status", "200"},
+ {"server_id", "cluster_0, backend_0"}};
+
+ codec_client_ = makeHttpConnection(lookupPort("address1"));
+ auto response = sendRequestAndWaitForResponse(
+ Http::TestResponseHeaderMapImpl{
+ {":method", "GET"}, {":path", "/"}, {":authority", "host"}, {":scheme", "http"}},
+ request_size, response_headers, response_size, /*cluster_0*/ 0);
+ verifyResponse(std::move(response), "200", response_headers, std::string(response_size, 'a'));
+ EXPECT_TRUE(upstream_request_->complete());
+ EXPECT_EQ(request_size, upstream_request_->bodyLength());
+ codec_client_->close();
+ // Wait for the client to be disconnected.
+ ASSERT_TRUE(codec_client_->waitForDisconnect());
+
+ codec_client_ = makeHttpConnection(lookupPort("address2"));
+ auto response2 = sendRequestAndWaitForResponse(
+ Http::TestResponseHeaderMapImpl{
+ {":method", "GET"}, {":path", "/"}, {":authority", "host"}, {":scheme", "http"}},
+ request_size, response_headers, response_size, /*cluster_0*/ 0);
+ verifyResponse(std::move(response2), "200", response_headers, std::string(response_size, 'a'));
+ EXPECT_TRUE(upstream_request_->complete());
+ EXPECT_EQ(request_size, upstream_request_->bodyLength());
+}
+
// Tests that a LDS adding listener works as expected.
TEST_P(ListenerIntegrationTest, BasicSuccess) {
on_server_init_function_ = [&]() {
@@ -490,6 +615,162 @@ TEST_P(ListenerIntegrationTest, MultipleLdsUpdatesSharingListenSocketFactory) {
}
}
+// This is multiple addresses version test for the above one.
+TEST_P(ListenerMultiAddressesIntegrationTest,
+ MultipleLdsUpdatesSharingListenSocketFactoryWithMultiAddresses) {
+ on_server_init_function_ = [&]() {
+ createLdsStream();
+ sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "1");
+ createRdsStream(route_table_name_);
+ };
+ initialize();
+ test_server_->waitForCounterGe("listener_manager.lds.update_success", 1);
+ // testing-listener-0 is not initialized as we haven't pushed any RDS yet.
+ EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing);
+ // Workers not started, the LDS added listener 0 is in active_listeners_ list.
+ EXPECT_EQ(test_server_->server().listenerManager().listeners().size(), 1);
+ registerTestServerPorts({"address1", "address2"});
+
+ const std::string route_config_tmpl = R"EOF(
+ name: {}
+ virtual_hosts:
+ - name: integration
+ domains: ["*"]
+ routes:
+ - match: {{ prefix: "/" }}
+ route: {{ cluster: {} }}
+)EOF";
+ sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1");
+ test_server_->waitForCounterGe(
+ fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1);
+ // Now testing-listener-0 finishes initialization, Server initManager will be ready.
+ EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized);
+
+ test_server_->waitUntilListenersReady();
+ // NOTE: The line above doesn't tell you if listener is up and listening.
+ test_server_->waitForCounterGe("listener_manager.listener_create_success", 1);
+ // Make a connection to the listener from version 1.
+ codec_client_ = makeHttpConnection(lookupPort("address1"));
+
+ for (int version = 2; version <= 10; version++) {
+ // Touch the metadata to get a different hash.
+ (*(*listener_config_.mutable_metadata()->mutable_filter_metadata())["random_filter_name"]
+ .mutable_fields())["random_key"]
+ .set_number_value(version);
+ sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)},
+ absl::StrCat(version));
+ sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"),
+ absl::StrCat(version));
+
+ test_server_->waitForCounterGe("listener_manager.listener_create_success", version);
+
+ // Wait for the client to be disconnected.
+ ASSERT_TRUE(codec_client_->waitForDisconnect());
+
+ const uint32_t response_size = 800;
+ const uint32_t request_size = 10;
+ Http::TestResponseHeaderMapImpl response_headers{{":status", "200"},
+ {"server_id", "cluster_0, backend_0"}};
+
+ // Make a new connection to the new listener's first address.
+ codec_client_ = makeHttpConnection(lookupPort("address1"));
+ auto response = sendRequestAndWaitForResponse(
+ Http::TestResponseHeaderMapImpl{
+ {":method", "GET"}, {":path", "/"}, {":authority", "host"}, {":scheme", "http"}},
+ request_size, response_headers, response_size, /*cluster_0*/ 0);
+ verifyResponse(std::move(response), "200", response_headers, std::string(response_size, 'a'));
+ EXPECT_TRUE(upstream_request_->complete());
+ EXPECT_EQ(request_size, upstream_request_->bodyLength());
+ codec_client_->close();
+ // Wait for the client to be disconnected.
+ ASSERT_TRUE(codec_client_->waitForDisconnect());
+
+ // Make a new connection to the new listener's second address.
+ codec_client_ = makeHttpConnection(lookupPort("address2"));
+ auto response2 = sendRequestAndWaitForResponse(
+ Http::TestResponseHeaderMapImpl{
+ {":method", "GET"}, {":path", "/"}, {":authority", "host"}, {":scheme", "http"}},
+ request_size, response_headers, response_size, /*cluster_0*/ 0);
+ verifyResponse(std::move(response2), "200", response_headers, std::string(response_size, 'a'));
+ EXPECT_TRUE(upstream_request_->complete());
+ EXPECT_EQ(request_size, upstream_request_->bodyLength());
+ }
+}
+
+TEST_P(ListenerMultiAddressesIntegrationTest, MultipleAddressesListenerInPlaceUpdate) {
+ on_server_init_function_ = [&]() {
+ createLdsStream();
+ sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "1");
+ createRdsStream(route_table_name_);
+ };
+ setDrainTime(std::chrono::seconds(30));
+ initialize();
+ test_server_->waitForCounterGe("listener_manager.lds.update_success", 1);
+ // testing-listener-0 is not initialized as we haven't pushed any RDS yet.
+ EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing);
+ // Workers not started, the LDS added listener 0 is in active_listeners_ list.
+ EXPECT_EQ(test_server_->server().listenerManager().listeners().size(), 1);
+ registerTestServerPorts({"address1", "address2"});
+
+ const std::string route_config_tmpl = R"EOF(
+ name: {}
+ virtual_hosts:
+ - name: integration
+ domains: ["*"]
+ routes:
+ - match: {{ prefix: "/" }}
+ route: {{ cluster: {} }}
+)EOF";
+ sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1");
+ test_server_->waitForCounterGe(
+ fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1);
+ // Now testing-listener-0 finishes initialization, Server initManager will be ready.
+ EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized);
+
+ test_server_->waitUntilListenersReady();
+ // NOTE: The line above doesn't tell you if listener is up and listening.
+ test_server_->waitForCounterGe("listener_manager.listener_create_success", 1);
+
+ // Trigger a listener in-place updating.
+ listener_config_.mutable_filter_chains(0)->mutable_filters(0)->set_name("http_filter");
+ sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2");
+ sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "2");
+
+ test_server_->waitForCounterGe("listener_manager.listener_create_success", 2);
+ test_server_->waitForCounterEq("listener_manager.listener_in_place_updated", 1);
+ test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 1);
+
+ const uint32_t response_size = 800;
+ const uint32_t request_size = 10;
+ Http::TestResponseHeaderMapImpl response_headers{{":status", "200"},
+ {"server_id", "cluster_0, backend_0"}};
+
+ // Make a new connection to the new listener's first address.
+ codec_client_ = makeHttpConnection(lookupPort("address1"));
+
+ auto response1 = sendRequestAndWaitForResponse(
+ Http::TestResponseHeaderMapImpl{
+ {":method", "GET"}, {":path", "/"}, {":authority", "host"}, {":scheme", "http"}},
+ request_size, response_headers, response_size, /*cluster_0*/ 0);
+ verifyResponse(std::move(response1), "200", response_headers, std::string(response_size, 'a'));
+ EXPECT_TRUE(upstream_request_->complete());
+ EXPECT_EQ(request_size, upstream_request_->bodyLength());
+ codec_client_->close();
+ // Wait for the client to be disconnected.
+ ASSERT_TRUE(codec_client_->waitForDisconnect());
+
+ // Make a new connection to the new listener's second address.
+ codec_client_ = makeHttpConnection(lookupPort("address2"));
+
+ auto response2 = sendRequestAndWaitForResponse(
+ Http::TestResponseHeaderMapImpl{
+ {":method", "GET"}, {":path", "/"}, {":authority", "host"}, {":scheme", "http"}},
+ request_size, response_headers, response_size, /*cluster_0*/ 0);
+ verifyResponse(std::move(response2), "200", response_headers, std::string(response_size, 'a'));
+ EXPECT_TRUE(upstream_request_->complete());
+ EXPECT_EQ(request_size, upstream_request_->bodyLength());
+}
+
// Create a listener, then do an in-place update for the listener.
// Remove the listener before the filter chain draining is done,
// then expect the connection will be reset.
diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc
index 8511b172d97b0..056fcef762d59 100644
--- a/test/integration/quic_http_integration_test.cc
+++ b/test/integration/quic_http_integration_test.cc
@@ -131,16 +131,14 @@ class TestEnvoyQuicClientConnection : public EnvoyQuicClientConnection {
};
// A test that sets up its own client connection with customized quic version and connection ID.
-class QuicHttpIntegrationTest : public HttpIntegrationTest,
- public testing::TestWithParam {
+class QuicHttpIntegrationTestBase : public HttpIntegrationTest {
public:
- QuicHttpIntegrationTest()
- : HttpIntegrationTest(Http::CodecType::HTTP3, GetParam(),
- ConfigHelper::quicHttpProxyConfig()),
+ QuicHttpIntegrationTestBase(Network::Address::IpVersion version, std::string config)
+ : HttpIntegrationTest(Http::CodecType::HTTP3, version, config),
supported_versions_(quic::CurrentSupportedHttp3Versions()), conn_helper_(*dispatcher_),
alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) {}
- ~QuicHttpIntegrationTest() override {
+ ~QuicHttpIntegrationTestBase() override {
cleanupUpstreamAndDownstream();
// Release the client before destroying |conn_helper_|. No such need once |conn_helper_| is
// moved into a client connection factory in the base test class.
@@ -288,13 +286,13 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest,
}
constexpr auto timeout_first = std::chrono::seconds(15);
constexpr auto timeout_subsequent = std::chrono::milliseconds(10);
- if (GetParam() == Network::Address::IpVersion::v4) {
+ if (version_ == Network::Address::IpVersion::v4) {
test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_cx_total", 8u, timeout_first);
} else {
test_server_->waitForCounterEq("listener.[__1]_0.downstream_cx_total", 8u, timeout_first);
}
for (size_t i = 0; i < concurrency_; ++i) {
- if (GetParam() == Network::Address::IpVersion::v4) {
+ if (version_ == Network::Address::IpVersion::v4) {
test_server_->waitForGaugeEq(
fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_active", i), 1u,
timeout_subsequent);
@@ -347,10 +345,118 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest,
bool validation_failure_on_path_response_{false};
};
+class QuicHttpIntegrationTest : public QuicHttpIntegrationTestBase,
+ public testing::TestWithParam {
+public:
+ QuicHttpIntegrationTest()
+ : QuicHttpIntegrationTestBase(GetParam(), ConfigHelper::quicHttpProxyConfig()) {}
+};
+
+class QuicHttpMultiAddressesIntegrationTest
+ : public QuicHttpIntegrationTestBase,
+ public testing::TestWithParam {
+public:
+ QuicHttpMultiAddressesIntegrationTest()
+ : QuicHttpIntegrationTestBase(GetParam(), ConfigHelper::quicHttpProxyConfig(true)) {}
+
+ void testMultipleQuicConnections() {
+ // Enabling SO_REUSEPORT with 8 workers. Unfortunately this setting makes the test rarely flaky
+ // if it is configured to run with --runs_per_test=N where N > 1 but without --jobs=1.
+ setConcurrency(8);
+ initialize();
+ std::vector addresses({"address1", "address2"});
+ registerTestServerPorts(addresses);
+ std::vector codec_clients1;
+ std::vector codec_clients2;
+ for (size_t i = 1; i <= concurrency_; ++i) {
+ // The BPF filter and ActiveQuicListener::destination() look at the 1st word of connection id
+ // in the packet header. And currently all QUIC versions support >= 8 bytes connection id. So
+ // create connections with the first 4 bytes of connection id different from each
+ // other so they should be evenly distributed.
+ designated_connection_ids_.push_back(quic::test::TestConnectionId(i << 32));
+ // TODO(sunjayBhatia,wrowe): deserialize this, establishing all connections in parallel
+ // (Expected to save ~14s each across 6 tests on Windows)
+ codec_clients1.push_back(makeHttpConnection(lookupPort("address1")));
+ designated_connection_ids_.push_back(quic::test::TestConnectionId(i << 32));
+ codec_clients2.push_back(makeHttpConnection(lookupPort("address2")));
+ }
+ constexpr auto timeout_first = std::chrono::seconds(15);
+ constexpr auto timeout_subsequent = std::chrono::milliseconds(10);
+ if (version_ == Network::Address::IpVersion::v4) {
+ test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_cx_total", 16u,
+ timeout_first);
+ } else {
+ test_server_->waitForCounterEq("listener.[__1]_0.downstream_cx_total", 16u, timeout_first);
+ }
+ for (size_t i = 0; i < concurrency_; ++i) {
+ if (version_ == Network::Address::IpVersion::v4) {
+ test_server_->waitForGaugeEq(
+ fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_active", i), 2u,
+ timeout_subsequent);
+ test_server_->waitForCounterEq(
+ fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_total", i), 2u,
+ timeout_subsequent);
+ } else {
+ test_server_->waitForGaugeEq(
+ fmt::format("listener.[__1]_0.worker_{}.downstream_cx_active", i), 2u,
+ timeout_subsequent);
+ test_server_->waitForCounterEq(
+ fmt::format("listener.[__1]_0.worker_{}.downstream_cx_total", i), 2u,
+ timeout_subsequent);
+ }
+ }
+ for (size_t i = 0; i < concurrency_; ++i) {
+ fake_upstream_connection_ = nullptr;
+ upstream_request_ = nullptr;
+
+ auto encoder_decoder = codec_clients1[i]->startRequest(
+ Http::TestRequestHeaderMapImpl{{":method", "GET"},
+ {":path", "/test/long/url"},
+ {":scheme", "http"},
+ {":authority", "host"}});
+ auto& request_encoder = encoder_decoder.first;
+ auto response = std::move(encoder_decoder.second);
+ codec_clients1[i]->sendData(request_encoder, 1000, true);
+ waitForNextUpstreamRequest();
+ upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"},
+ {"set-cookie", "foo"},
+ {"set-cookie", "bar"}},
+ true);
+
+ ASSERT_TRUE(response->waitForEndStream());
+ EXPECT_TRUE(response->complete());
+ codec_clients1[i]->close();
+
+ auto encoder_decoder2 = codec_clients2[i]->startRequest(
+ Http::TestRequestHeaderMapImpl{{":method", "GET"},
+ {":path", "/test/long/url"},
+ {":scheme", "http"},
+ {":authority", "host"}});
+ auto& request_encoder2 = encoder_decoder2.first;
+ auto response2 = std::move(encoder_decoder2.second);
+ codec_clients2[i]->sendData(request_encoder2, 1000, true);
+ waitForNextUpstreamRequest();
+ upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"},
+ {"set-cookie", "foo"},
+ {"set-cookie", "bar"}},
+ true);
+
+ ASSERT_TRUE(response2->waitForEndStream());
+ EXPECT_TRUE(response2->complete());
+ codec_clients2[i]->close();
+ }
+ }
+};
+
INSTANTIATE_TEST_SUITE_P(QuicHttpIntegrationTests, QuicHttpIntegrationTest,
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
TestUtility::ipTestParamsToString);
+INSTANTIATE_TEST_SUITE_P(QuicHttpMultiAddressesIntegrationTest,
+ QuicHttpMultiAddressesIntegrationTest,
+ testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
+ TestUtility::ipTestParamsToString);
+
TEST_P(QuicHttpIntegrationTest, GetRequestAndEmptyResponse) {
testRouterHeaderOnlyRequestAndResponse();
}
@@ -419,7 +525,7 @@ TEST_P(QuicHttpIntegrationTest, ZeroRtt) {
EXPECT_TRUE(quic_session->ssl()->peerCertificateValidated());
// Close the second connection.
codec_client_->close();
- if (GetParam() == Network::Address::IpVersion::v4) {
+ if (version_ == Network::Address::IpVersion::v4) {
test_server_->waitForCounterEq(
"listener.127.0.0.1_0.http3.downstream.rx.quic_connection_close_error_"
"code_QUIC_NO_ERROR",
@@ -503,6 +609,11 @@ TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsDefaultMode) {
testMultipleQuicConnections();
}
+// Ensure multiple quic connections work, regardless of platform BPF support
+TEST_P(QuicHttpMultiAddressesIntegrationTest, MultipleQuicConnectionsDefaultMode) {
+ testMultipleQuicConnections();
+}
+
TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsNoBPF) {
// Note: This setting is a no-op on platforms without BPF
class DisableBpf {
@@ -515,6 +626,18 @@ TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsNoBPF) {
testMultipleQuicConnections();
}
+TEST_P(QuicHttpMultiAddressesIntegrationTest, MultipleQuicConnectionsNoBPF) {
+ // Note: This setting is a no-op on platforms without BPF
+ class DisableBpf {
+ public:
+ DisableBpf() { ActiveQuicListenerFactory::setDisableKernelBpfPacketRoutingForTest(true); }
+ ~DisableBpf() { ActiveQuicListenerFactory::setDisableKernelBpfPacketRoutingForTest(false); }
+ };
+
+ DisableBpf disable;
+ testMultipleQuicConnections();
+}
+
// Tests that the packets from a connection with CID longer than 8 bytes are routed to the same
// worker.
TEST_P(QuicHttpIntegrationTest, MultiWorkerWithLongConnectionId) {
diff --git a/test/integration/server.cc b/test/integration/server.cc
index 2985c30d69be8..0af5bec20d082 100644
--- a/test/integration/server.cc
+++ b/test/integration/server.cc
@@ -132,7 +132,7 @@ void IntegrationTestServer::start(
if (tap_path) {
std::vector ports;
for (auto listener : server().listenerManager().listeners()) {
- const auto listen_addr = listener.get().listenSocketFactory().localAddress();
+ const auto listen_addr = listener.get().listenSocketFactories()[0]->localAddress();
if (listen_addr->type() == Network::Address::Type::Ip) {
ports.push_back(listen_addr->ip()->port());
}
diff --git a/test/mocks/network/mocks.cc b/test/mocks/network/mocks.cc
index f27c1738ed368..ba72b0ea1f01a 100644
--- a/test/mocks/network/mocks.cc
+++ b/test/mocks/network/mocks.cc
@@ -27,18 +27,20 @@ namespace Network {
MockUdpListenerConfig::MockUdpListenerConfig()
: udp_listener_worker_router_(std::make_unique(1)) {
- ON_CALL(*this, listenerWorkerRouter()).WillByDefault(ReturnRef(*udp_listener_worker_router_));
+ ON_CALL(*this, listenerWorkerRouter(_)).WillByDefault(ReturnRef(*udp_listener_worker_router_));
ON_CALL(*this, config()).WillByDefault(ReturnRef(config_));
}
MockUdpListenerConfig::~MockUdpListenerConfig() = default;
MockListenerConfig::MockListenerConfig()
: socket_(std::make_shared>()) {
+ socket_factories_.emplace_back(std::make_unique());
ON_CALL(*this, filterChainFactory()).WillByDefault(ReturnRef(filter_chain_factory_));
- ON_CALL(*this, listenSocketFactory()).WillByDefault(ReturnRef(socket_factory_));
- ON_CALL(socket_factory_, localAddress())
+ ON_CALL(*this, listenSocketFactories()).WillByDefault(ReturnRef(socket_factories_));
+ ON_CALL(*static_cast(socket_factories_[0].get()), localAddress())
.WillByDefault(ReturnRef(socket_->connectionInfoProvider().localAddress()));
- ON_CALL(socket_factory_, getListenSocket(_)).WillByDefault(Return(socket_));
+ ON_CALL(*static_cast(socket_factories_[0].get()), getListenSocket(_))
+ .WillByDefault(Return(socket_));
ON_CALL(*this, listenerScope()).WillByDefault(ReturnRef(scope_));
ON_CALL(*this, name()).WillByDefault(ReturnRef(name_));
}
diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h
index 27c5799b00f9a..b45245fff7912 100644
--- a/test/mocks/network/mocks.h
+++ b/test/mocks/network/mocks.h
@@ -412,7 +412,7 @@ class MockUdpListenerConfig : public UdpListenerConfig {
MOCK_METHOD(ActiveUdpListenerFactory&, listenerFactory, ());
MOCK_METHOD(UdpPacketWriterFactory&, packetWriterFactory, ());
- MOCK_METHOD(UdpListenerWorkerRouter&, listenerWorkerRouter, ());
+ MOCK_METHOD(UdpListenerWorkerRouter&, listenerWorkerRouter, (const Network::Address::Instance&));
MOCK_METHOD(const envoy::config::listener::v3::UdpListenerConfig&, config, ());
UdpListenerWorkerRouterPtr udp_listener_worker_router_;
@@ -426,8 +426,8 @@ class MockListenerConfig : public ListenerConfig {
MOCK_METHOD(FilterChainManager&, filterChainManager, ());
MOCK_METHOD(FilterChainFactory&, filterChainFactory, ());
- MOCK_METHOD(ListenSocketFactory&, listenSocketFactory, ());
- MOCK_METHOD(bool, bindToPort, ());
+ MOCK_METHOD(std::vector&, listenSocketFactories, ());
+ MOCK_METHOD(bool, bindToPort, (), (const));
MOCK_METHOD(bool, handOffRestoredDestinationConnections, (), (const));
MOCK_METHOD(uint32_t, perConnectionBufferLimitBytes, (), (const));
MOCK_METHOD(std::chrono::milliseconds, listenerFiltersTimeout, (), (const));
@@ -437,7 +437,7 @@ class MockListenerConfig : public ListenerConfig {
MOCK_METHOD(const std::string&, name, (), (const));
MOCK_METHOD(Network::UdpListenerConfigOptRef, udpListenerConfig, ());
MOCK_METHOD(InternalListenerConfigOptRef, internalListenerConfig, ());
- MOCK_METHOD(ConnectionBalancer&, connectionBalancer, ());
+ MOCK_METHOD(ConnectionBalancer&, connectionBalancer, (const Network::Address::Instance&));
MOCK_METHOD(ResourceLimit&, openConnections, ());
MOCK_METHOD(uint32_t, tcpBacklogSize, (), (const));
MOCK_METHOD(Init::Manager&, initManager, ());
@@ -452,7 +452,7 @@ class MockListenerConfig : public ListenerConfig {
}
testing::NiceMock filter_chain_factory_;
- MockListenSocketFactory socket_factory_;
+ std::vector socket_factories_;
SocketSharedPtr socket_;
Stats::IsolatedStoreImpl scope_;
std::string name_;
diff --git a/test/mocks/server/worker.cc b/test/mocks/server/worker.cc
index f20c6564b90a5..2e2559853ea43 100644
--- a/test/mocks/server/worker.cc
+++ b/test/mocks/server/worker.cc
@@ -17,7 +17,7 @@ MockWorker::MockWorker() {
Network::ListenerConfig& config,
AddListenerCompletion completion, Runtime::Loader&) -> void {
UNREFERENCED_PARAMETER(overridden_listener);
- config.listenSocketFactory().getListenSocket(0);
+ config.listenSocketFactories()[0]->getListenSocket(0);
EXPECT_EQ(nullptr, add_listener_completion_);
add_listener_completion_ = completion;
}));
diff --git a/test/server/active_tcp_listener_test.cc b/test/server/active_tcp_listener_test.cc
index 159b04338efaa..bf19d9a19b156 100644
--- a/test/server/active_tcp_listener_test.cc
+++ b/test/server/active_tcp_listener_test.cc
@@ -36,7 +36,7 @@ class MockTcpConnectionHandler : public Network::TcpConnectionHandler,
public:
MOCK_METHOD(Event::Dispatcher&, dispatcher, ());
MOCK_METHOD(Network::BalancedConnectionHandlerOptRef, getBalancedHandlerByTag,
- (uint64_t listener_tag));
+ (uint64_t listener_tag, const Network::Address::Instance&));
MOCK_METHOD(Network::BalancedConnectionHandlerOptRef, getBalancedHandlerByAddress,
(const Network::Address::Instance& address));
};
@@ -51,7 +51,6 @@ class ActiveTcpListenerTest : public testing::Test, protected Logger::Loggable>();
EXPECT_CALL(*generic_listener_, onDestroy());
- generic_active_listener_ = std::make_unique(
- conn_handler_, std::move(generic_listener_), listener_config_, runtime_);
+ Network::Address::InstanceConstSharedPtr address(
+ new Network::Address::Ipv4Instance("127.0.0.1", 10001));
+ generic_active_listener_ =
+ std::make_unique(conn_handler_, std::move(generic_listener_), address,
+ listener_config_, balancer_, runtime_);
generic_active_listener_->incNumConnections();
generic_accepted_socket_ = std::make_unique>();
EXPECT_CALL(*generic_accepted_socket_, ioHandle()).WillRepeatedly(ReturnRef(io_handle_));
@@ -90,8 +92,11 @@ class ActiveTcpListenerTest : public testing::Test, protected Logger::Loggable>();
EXPECT_CALL(*generic_listener_, onDestroy());
- generic_active_listener_ = std::make_unique(
- conn_handler_, std::move(generic_listener_), listener_config_, runtime_);
+ Network::Address::InstanceConstSharedPtr address(
+ new Network::Address::Ipv4Instance("127.0.0.1", 10001));
+ generic_active_listener_ =
+ std::make_unique(conn_handler_, std::move(generic_listener_), address,
+ listener_config_, balancer_, runtime_);
generic_active_listener_->incNumConnections();
generic_accepted_socket_ = std::make_unique>();
EXPECT_CALL(*generic_accepted_socket_, ioHandle()).WillRepeatedly(ReturnRef(io_handle_));
@@ -220,8 +225,11 @@ TEST_F(ActiveTcpListenerTest, ListenerFilterWithInspectDataMultipleFilters) {
auto listener = std::make_unique>();
EXPECT_CALL(*listener, onDestroy());
- auto active_listener = std::make_unique(conn_handler_, std::move(listener),
- listener_config_, runtime_);
+ Network::NopConnectionBalancerImpl balancer;
+ Network::Address::InstanceConstSharedPtr address(
+ new Network::Address::Ipv4Instance("127.0.0.1", 10001));
+ auto active_listener = std::make_unique(
+ conn_handler_, std::move(listener), address, listener_config_, balancer, runtime_);
auto accepted_socket = std::make_unique>();
EXPECT_CALL(*accepted_socket, ioHandle()).WillRepeatedly(ReturnRef(io_handle_));
@@ -309,8 +317,11 @@ TEST_F(ActiveTcpListenerTest, ListenerFilterWithInspectDataMultipleFilters2) {
auto listener = std::make_unique>();
EXPECT_CALL(*listener, onDestroy());
- auto active_listener = std::make_unique(conn_handler_, std::move(listener),
- listener_config_, runtime_);
+ Network::NopConnectionBalancerImpl balancer;
+ Network::Address::InstanceConstSharedPtr address(
+ new Network::Address::Ipv4Instance("127.0.0.1", 10001));
+ auto active_listener = std::make_unique(
+ conn_handler_, std::move(listener), address, listener_config_, balancer, runtime_);
auto accepted_socket = std::make_unique>();
EXPECT_CALL(*accepted_socket, ioHandle()).WillRepeatedly(ReturnRef(io_handle_));
@@ -477,7 +488,6 @@ TEST_F(ActiveTcpListenerTest, RedirectedRebalancer) {
Network::Address::InstanceConstSharedPtr normal_address(
new Network::Address::Ipv4Instance("127.0.0.1", 10001));
EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(normal_address));
- EXPECT_CALL(listener_config1, connectionBalancer()).WillRepeatedly(ReturnRef(balancer1));
EXPECT_CALL(listener_config1, listenerScope).Times(testing::AnyNumber());
EXPECT_CALL(listener_config1, listenerFiltersTimeout());
EXPECT_CALL(listener_config1, continueOnListenerFiltersTimeout());
@@ -488,8 +498,9 @@ TEST_F(ActiveTcpListenerTest, RedirectedRebalancer) {
auto mock_listener_will_be_moved1 = std::make_unique();
auto& listener1 = *mock_listener_will_be_moved1;
- auto active_listener1 = std::make_unique(
- conn_handler_, std::move(mock_listener_will_be_moved1), listener_config1, runtime_);
+ auto active_listener1 =
+ std::make_unique(conn_handler_, std::move(mock_listener_will_be_moved1),
+ normal_address, listener_config1, balancer1, runtime_);
NiceMock listener_config2;
Network::MockConnectionBalancer balancer2;
@@ -500,7 +511,6 @@ TEST_F(ActiveTcpListenerTest, RedirectedRebalancer) {
new Network::Address::Ipv4Instance("127.0.0.2", 20002));
EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(alt_address));
EXPECT_CALL(listener_config2, listenerFiltersTimeout());
- EXPECT_CALL(listener_config2, connectionBalancer()).WillRepeatedly(ReturnRef(balancer2));
EXPECT_CALL(listener_config2, listenerScope).Times(testing::AnyNumber());
EXPECT_CALL(listener_config2, handOffRestoredDestinationConnections())
.WillRepeatedly(Return(false));
@@ -509,8 +519,9 @@ TEST_F(ActiveTcpListenerTest, RedirectedRebalancer) {
EXPECT_CALL(listener_config2, openConnections()).WillRepeatedly(ReturnRef(resource_limit_));
auto mock_listener_will_be_moved2 = std::make_unique();
auto& listener2 = *mock_listener_will_be_moved2;
- auto active_listener2 = std::make_shared(
- conn_handler_, std::move(mock_listener_will_be_moved2), listener_config2, runtime_);
+ auto active_listener2 =
+ std::make_shared(conn_handler_, std::move(mock_listener_will_be_moved2),
+ alt_address, listener_config2, balancer2, runtime_);
auto* test_filter = new NiceMock();
EXPECT_CALL(*test_filter, destroy_());
@@ -582,6 +593,89 @@ TEST_F(ActiveTcpListenerTest, RedirectedRebalancer) {
EXPECT_CALL(listener2, onDestroy());
active_listener2.reset();
}
+
+TEST_F(ActiveTcpListenerTest, Rebalance) {
+ NiceMock listener_config1;
+ NiceMock balancer1;
+ EXPECT_CALL(balancer1, registerHandler(_)).Times(2);
+ EXPECT_CALL(balancer1, unregisterHandler(_)).Times(2);
+
+ Network::Address::InstanceConstSharedPtr normal_address(
+ new Network::Address::Ipv4Instance("127.0.0.1", 10001));
+ EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(normal_address));
+ EXPECT_CALL(listener_config1, listenerScope).Times(testing::AnyNumber());
+ EXPECT_CALL(listener_config1, listenerFiltersTimeout());
+ EXPECT_CALL(listener_config1, continueOnListenerFiltersTimeout());
+ EXPECT_CALL(listener_config1, filterChainManager()).WillRepeatedly(ReturnRef(manager_));
+ EXPECT_CALL(listener_config1, openConnections()).WillRepeatedly(ReturnRef(resource_limit_));
+ EXPECT_CALL(listener_config1, handOffRestoredDestinationConnections())
+ .WillRepeatedly(Return(true));
+
+ auto mock_listener_will_be_moved1 = std::make_unique();
+ auto& listener1 = *mock_listener_will_be_moved1;
+ auto active_listener1 =
+ std::make_unique(conn_handler_, std::move(mock_listener_will_be_moved1),
+ normal_address, listener_config1, balancer1, runtime_);
+
+ NiceMock listener_config2;
+
+ EXPECT_CALL(*socket_factory_, localAddress()).WillRepeatedly(ReturnRef(normal_address));
+ EXPECT_CALL(listener_config2, listenerFiltersTimeout());
+ EXPECT_CALL(listener_config2, listenerScope).Times(testing::AnyNumber());
+ EXPECT_CALL(listener_config2, handOffRestoredDestinationConnections())
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(listener_config2, continueOnListenerFiltersTimeout());
+ EXPECT_CALL(listener_config2, filterChainManager()).WillRepeatedly(ReturnRef(manager_));
+ EXPECT_CALL(listener_config2, openConnections()).WillRepeatedly(ReturnRef(resource_limit_));
+ auto mock_listener_will_be_moved2 = std::make_unique();
+ auto& listener2 = *mock_listener_will_be_moved2;
+ auto active_listener2 =
+ std::make_shared(conn_handler_, std::move(mock_listener_will_be_moved2),
+ normal_address, listener_config2, balancer1, runtime_);
+ Network::MockConnectionSocket* accepted_socket = new NiceMock();
+
+ // active_listener1 re-balance. Set the balance target to the the active_listener2.
+ EXPECT_CALL(balancer1, pickTargetHandler(_))
+ .WillOnce(testing::DoAll(testing::WithArg<0>(Invoke([&active_listener2](auto&) {
+ active_listener2->incNumConnections();
+ })),
+ ReturnRef(*active_listener2)));
+
+ EXPECT_CALL(conn_handler_, getBalancedHandlerByTag)
+ .WillOnce(Invoke([&normal_address,
+ &active_listener2](uint64_t, const Network::Address::Instance& address) {
+ EXPECT_EQ(address, *normal_address);
+ return Network::BalancedConnectionHandlerOptRef(*active_listener2);
+ }));
+ auto filter_factory_callback = std::make_shared>();
+ auto transport_socket_factory = Network::Test::createRawBufferSocketFactory();
+ filter_chain_ = std::make_shared>();
+
+ EXPECT_CALL(conn_handler_, incNumConnections());
+ EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get()));
+ EXPECT_CALL(*filter_chain_, transportSocketFactory)
+ .WillOnce(testing::ReturnRef(*transport_socket_factory));
+ EXPECT_CALL(*filter_chain_, networkFilterFactories).WillOnce(ReturnRef(*filter_factory_callback));
+ EXPECT_CALL(listener_config2, filterChainFactory())
+ .WillRepeatedly(ReturnRef(filter_chain_factory_));
+
+ auto* connection = new NiceMock();
+ EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection));
+ EXPECT_CALL(filter_chain_factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true));
+ active_listener1->onAccept(Network::ConnectionSocketPtr{accepted_socket});
+
+ // Verify per-listener connection stats.
+ EXPECT_EQ(1UL, conn_handler_.numConnections());
+
+ EXPECT_CALL(conn_handler_, decNumConnections());
+ connection->close(Network::ConnectionCloseType::NoFlush);
+
+ EXPECT_CALL(listener1, onDestroy());
+ active_listener1.reset();
+ EXPECT_CALL(listener2, onDestroy());
+ active_listener2.reset();
+}
+
} // namespace
} // namespace Server
} // namespace Envoy
diff --git a/test/server/active_udp_listener_test.cc b/test/server/active_udp_listener_test.cc
index 7337c0d2e5550..ba537c0a4d61d 100644
--- a/test/server/active_udp_listener_test.cc
+++ b/test/server/active_udp_listener_test.cc
@@ -30,7 +30,7 @@ class MockUdpConnectionHandler : public Network::UdpConnectionHandler,
public:
MOCK_METHOD(Event::Dispatcher&, dispatcher, ());
MOCK_METHOD(Network::UdpListenerCallbacksOptRef, getUdpListenerCallbacks,
- (uint64_t listener_tag));
+ (uint64_t listener_tag, const Network::Address::Instance& address));
};
class ActiveUdpListenerTest : public testing::TestWithParam,
@@ -50,9 +50,12 @@ class ActiveUdpListenerTest : public testing::TestWithParamaddOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions());
ASSERT_TRUE(Network::Socket::applyOptions(listen_socket_->options(), *listen_socket_,
envoy::config::core::v3::SocketOption::STATE_BOUND));
-
- ON_CALL(socket_factory_, getListenSocket(_)).WillByDefault(Return(listen_socket_));
- EXPECT_CALL(listener_config_, listenSocketFactory()).WillRepeatedly(ReturnRef(socket_factory_));
+ socket_factories_.emplace_back(std::make_unique());
+ ON_CALL(*static_cast(socket_factories_[0].get()),
+ getListenSocket(_))
+ .WillByDefault(Return(listen_socket_));
+ EXPECT_CALL(listener_config_, listenSocketFactories())
+ .WillRepeatedly(ReturnRef(socket_factories_));
// Use UdpGsoBatchWriter to perform non-batched writes for the purpose of this test, if it is
// supported.
@@ -77,8 +80,8 @@ class ActiveUdpListenerTest : public testing::TestWithParam(0, 1, conn_handler_, dispatcher_, listener_config_);
+ active_listener_ = std::make_unique(0, 1, conn_handler_, listen_socket_,
+ dispatcher_, listener_config_);
}
std::string listener_stat_prefix_{"listener_stat_prefix"};
@@ -87,7 +90,7 @@ class ActiveUdpListenerTest : public testing::TestWithParam socket_factory_;
+ std::vector socket_factories_;
Stats::IsolatedStoreImpl scope_;
NiceMock udp_listener_config_;
NiceMock udp_packet_writer_factory_;
diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc
index 9fba04c607ca2..859d558e30aa7 100644
--- a/test/server/connection_handler_test.cc
+++ b/test/server/connection_handler_test.cc
@@ -74,7 +74,7 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable> filter_chain_manager = nullptr,
uint32_t tcp_backlog_size = ENVOY_TCP_BACKLOG_SIZE,
Network::ConnectionBalancerSharedPtr connection_balancer = nullptr,
- bool ignore_global_conn_limit = false)
+ bool ignore_global_conn_limit = false, int num_of_socket_factories = 1)
: parent_(parent), socket_(std::make_shared>()),
tag_(tag), bind_to_port_(bind_to_port), tcp_backlog_size_(tcp_backlog_size),
hand_off_restored_destination_connections_(hand_off_restored_destination_connections),
@@ -85,6 +85,9 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable());
+ }
envoy::config::listener::v3::UdpListenerConfig udp_config;
udp_listener_config_ = std::make_unique(udp_config);
udp_listener_config_->listener_factory_ =
@@ -100,7 +103,8 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable(socket_factories_[index].get());
+ }
Network::FilterChainFactory& filterChainFactory() override { return parent_.factory_; }
- Network::ListenSocketFactory& listenSocketFactory() override { return socket_factory_; }
- bool bindToPort() override { return bind_to_port_; }
+ const std::vector& listenSocketFactories() override {
+ return socket_factories_;
+ }
+ bool bindToPort() const override { return bind_to_port_; }
bool handOffRestoredDestinationConnections() const override {
return hand_off_restored_destination_connections_;
}
@@ -177,7 +187,9 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable& accessLogs() const override {
return access_logs_;
}
@@ -192,7 +204,7 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable> socket_;
- Network::MockListenSocketFactory socket_factory_;
+ std::vector socket_factories_;
uint64_t tag_;
bool bind_to_port_;
const uint32_t tcp_backlog_size_;
@@ -254,6 +266,7 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable connection_balancer = nullptr,
Network::BalancedConnectionHandler** balanced_connection_handler = nullptr,
Network::Socket::Type socket_type = Network::Socket::Type::Stream,
@@ -273,8 +286,15 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggablesocket_factory_, socketType()).WillOnce(Return(socket_type));
- EXPECT_CALL(listeners_.back()->socket_factory_, getListenSocket(_))
+ EXPECT_CALL(listeners_.back()->socketFactory(), socketType()).WillOnce(Return(socket_type));
+ if (address == nullptr) {
+ EXPECT_CALL(listeners_.back()->socketFactory(), localAddress())
+ .WillRepeatedly(ReturnRef(local_address_));
+ } else {
+ EXPECT_CALL(listeners_.back()->socketFactory(), localAddress())
+ .WillRepeatedly(ReturnRef(address));
+ }
+ EXPECT_CALL(listeners_.back()->socketFactory(), getListenSocket(_))
.WillOnce(Return(listeners_.back()->socket_));
if (socket_type == Network::Socket::Type::Stream) {
EXPECT_CALL(dispatcher_, createListener_(_, _, _, _, _))
@@ -307,6 +327,43 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable& mock_listeners,
+ std::vector& addresses,
+ Network::TcpListenerCallbacks** listener_callbacks = nullptr, bool disable_listener = false) {
+ listeners_.emplace_back(std::make_unique(
+ *this, tag, bind_to_port, hand_off_restored_destination_connections, name,
+ Network::Socket::Type::Stream, std::chrono::milliseconds(15000), false, access_log_,
+ nullptr, ENVOY_TCP_BACKLOG_SIZE, nullptr, false, mock_listeners.size()));
+
+ EXPECT_CALL(listeners_.back()->socketFactory(0), socketType())
+ .WillOnce(Return(Network::Socket::Type::Stream));
+ for (std::vector::size_type i = 0; i < mock_listeners.size(); i++) {
+ EXPECT_CALL(listeners_.back()->socketFactory(i), localAddress())
+ .WillRepeatedly(ReturnRef(addresses[i]));
+ EXPECT_CALL(listeners_.back()->socketFactory(i), getListenSocket(_))
+ .WillOnce(Return(listeners_.back()->socket_));
+
+ EXPECT_CALL(dispatcher_, createListener_(_, _, _, _, _))
+ .WillOnce(Invoke([i, &mock_listeners, listener_callbacks](
+ Network::SocketSharedPtr&&, Network::TcpListenerCallbacks& cb,
+ Runtime::Loader&, bool, bool) -> Network::Listener* {
+ if (listener_callbacks != nullptr) {
+ *listener_callbacks = &cb;
+ }
+ return mock_listeners[i];
+ }))
+ .RetiresOnSaturation();
+
+ if (disable_listener) {
+ EXPECT_CALL(*static_cast(mock_listeners[i]), disable());
+ }
+ }
+
+ return listeners_.back().get();
+ }
+
TestListener* addInternalListener(
uint64_t tag, const std::string& name,
std::chrono::milliseconds listener_filters_timeout = std::chrono::milliseconds(15000),
@@ -332,7 +389,7 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggableip()->port());
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
+ EXPECT_CALL(test_listener->socketFactory(), localAddress())
.WillRepeatedly(ReturnRef(any_address));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
@@ -387,7 +444,8 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable();
Network::BalancedConnectionHandler* current_handler;
TestListener* test_listener =
- addListener(1, true, false, "test_listener", listener, &listener_callbacks,
+ addListener(1, true, false, "test_listener", listener, &listener_callbacks, nullptr,
connection_balancer, ¤t_handler);
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
// Fake a balancer posting a connection to us.
@@ -435,24 +491,20 @@ TEST_F(ConnectionHandlerTest, RemoveListenerDuringRebalance) {
TEST_F(ConnectionHandlerTest, ListenerConnectionLimitEnforced) {
Network::TcpListenerCallbacks* listener_callbacks1;
auto listener1 = new NiceMock();
- TestListener* test_listener1 =
- addListener(1, false, false, "test_listener1", listener1, &listener_callbacks1);
Network::Address::InstanceConstSharedPtr normal_address(
new Network::Address::Ipv4Instance("127.0.0.1", 10001));
- EXPECT_CALL(test_listener1->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(normal_address));
+ TestListener* test_listener1 = addListener(1, false, false, "test_listener1", listener1,
+ &listener_callbacks1, normal_address);
// Only allow a single connection on this listener.
test_listener1->setMaxConnections(1);
handler_->addListener(absl::nullopt, *test_listener1, runtime_);
auto listener2 = new NiceMock();
Network::TcpListenerCallbacks* listener_callbacks2;
- TestListener* test_listener2 =
- addListener(2, false, false, "test_listener2", listener2, &listener_callbacks2);
Network::Address::InstanceConstSharedPtr alt_address(
new Network::Address::Ipv4Instance("127.0.0.2", 20002));
- EXPECT_CALL(test_listener2->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(alt_address));
+ TestListener* test_listener2 =
+ addListener(2, false, false, "test_listener2", listener2, &listener_callbacks2, alt_address);
// Do not allow any connections on this listener.
test_listener2->setMaxConnections(0);
handler_->addListener(absl::nullopt, *test_listener2, runtime_);
@@ -524,8 +576,6 @@ TEST_F(ConnectionHandlerTest, RemoveListener) {
auto listener = new NiceMock();
TestListener* test_listener =
addListener(1, true, false, "test_listener", listener, &listener_callbacks);
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
Network::MockConnectionSocket* connection = new NiceMock();
@@ -548,6 +598,47 @@ TEST_F(ConnectionHandlerTest, RemoveListener) {
handler_->removeListeners(0);
}
+TEST_F(ConnectionHandlerTest, RemoveListenerWithMultiAddrs) {
+ InSequence s;
+
+ Network::TcpListenerCallbacks* listener_callbacks;
+ auto listener1 = new NiceMock();
+ auto listener2 = new NiceMock();
+ std::vector mock_listeners;
+ mock_listeners.emplace_back(listener1);
+ mock_listeners.emplace_back(listener2);
+ Network::Address::InstanceConstSharedPtr address1(
+ new Network::Address::Ipv4Instance("127.0.0.1", 80, nullptr));
+ Network::Address::InstanceConstSharedPtr address2(
+ new Network::Address::Ipv4Instance("127.0.0.2", 80, nullptr));
+ std::vector addresses;
+ addresses.emplace_back(address1);
+ addresses.emplace_back(address2);
+ TestListener* test_listener = addMultiAddrsListener(
+ 1, true, false, "test_listener", mock_listeners, addresses, &listener_callbacks);
+ handler_->addListener(absl::nullopt, *test_listener, runtime_);
+
+ Network::MockConnectionSocket* connection = new NiceMock();
+ EXPECT_CALL(*access_log_, log(_, _, _, _));
+ listener_callbacks->onAccept(Network::ConnectionSocketPtr{connection});
+ EXPECT_EQ(0UL, handler_->numConnections());
+
+ // Test stop/remove of not existent listener.
+ handler_->stopListeners(0);
+ handler_->removeListeners(0);
+
+ EXPECT_CALL(*listener1, onDestroy());
+ EXPECT_CALL(*listener2, onDestroy());
+ handler_->stopListeners(1);
+ EXPECT_CALL(dispatcher_, clearDeferredDeleteList()).Times(2);
+ handler_->removeListeners(1);
+ EXPECT_EQ(0UL, handler_->numConnections());
+
+ // Test stop/remove of not existent listener.
+ handler_->stopListeners(0);
+ handler_->removeListeners(0);
+}
+
TEST_F(ConnectionHandlerTest, DisableListener) {
InSequence s;
@@ -555,8 +646,6 @@ TEST_F(ConnectionHandlerTest, DisableListener) {
auto listener = new NiceMock();
TestListener* test_listener =
addListener(1, false, false, "test_listener", listener, &listener_callbacks);
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
EXPECT_CALL(*listener, disable());
@@ -565,6 +654,32 @@ TEST_F(ConnectionHandlerTest, DisableListener) {
handler_->disableListeners();
}
+TEST_F(ConnectionHandlerTest, DisableListenerWithMultiAddrs) {
+ Network::TcpListenerCallbacks* listener_callbacks;
+ auto listener1 = new NiceMock();
+ auto listener2 = new NiceMock();
+ std::vector mock_listeners;
+ mock_listeners.emplace_back(listener1);
+ mock_listeners.emplace_back(listener2);
+ Network::Address::InstanceConstSharedPtr address1(
+ new Network::Address::Ipv4Instance("127.0.0.1", 80, nullptr));
+ Network::Address::InstanceConstSharedPtr address2(
+ new Network::Address::Ipv4Instance("127.0.0.2", 80, nullptr));
+ std::vector addresses;
+ addresses.emplace_back(address1);
+ addresses.emplace_back(address2);
+ TestListener* test_listener = addMultiAddrsListener(
+ 1, false, false, "test_listener", mock_listeners, addresses, &listener_callbacks);
+ handler_->addListener(absl::nullopt, *test_listener, runtime_);
+
+ EXPECT_CALL(*listener1, disable());
+ EXPECT_CALL(*listener2, disable());
+ EXPECT_CALL(*listener1, onDestroy());
+ EXPECT_CALL(*listener2, onDestroy());
+
+ handler_->disableListeners();
+}
+
// Envoy doesn't have such case yet, just ensure the code won't break with it.
TEST_F(ConnectionHandlerTest, StopAndDisableStoppedListener) {
InSequence s;
@@ -573,8 +688,6 @@ TEST_F(ConnectionHandlerTest, StopAndDisableStoppedListener) {
auto listener = new NiceMock();
TestListener* test_listener =
addListener(1, false, false, "test_listener", listener, &listener_callbacks);
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
EXPECT_CALL(*listener, onDestroy());
@@ -595,14 +708,35 @@ TEST_F(ConnectionHandlerTest, AddDisabledListener) {
TestListener* test_listener =
addListener(1, false, false, "test_listener", listener, &listener_callbacks);
EXPECT_CALL(*listener, disable());
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
EXPECT_CALL(*listener, onDestroy());
handler_->disableListeners();
handler_->addListener(absl::nullopt, *test_listener, runtime_);
}
+TEST_F(ConnectionHandlerTest, AddDisabledListenerWithMultiAddrs) {
+ Network::TcpListenerCallbacks* listener_callbacks;
+ auto listener1 = new NiceMock();
+ auto listener2 = new NiceMock();
+ std::vector mock_listeners;
+ mock_listeners.emplace_back(listener1);
+ mock_listeners.emplace_back(listener2);
+ Network::Address::InstanceConstSharedPtr address1(
+ new Network::Address::Ipv4Instance("127.0.0.1", 80, nullptr));
+ Network::Address::InstanceConstSharedPtr address2(
+ new Network::Address::Ipv4Instance("127.0.0.2", 80, nullptr));
+ std::vector addresses;
+ addresses.emplace_back(address1);
+ addresses.emplace_back(address2);
+ TestListener* test_listener = addMultiAddrsListener(
+ 1, false, false, "test_listener", mock_listeners, addresses, &listener_callbacks, true);
+ EXPECT_CALL(*listener1, onDestroy());
+ EXPECT_CALL(*listener2, onDestroy());
+
+ handler_->disableListeners();
+ handler_->addListener(absl::nullopt, *test_listener, runtime_);
+}
+
TEST_F(ConnectionHandlerTest, SetListenerRejectFraction) {
InSequence s;
@@ -610,8 +744,6 @@ TEST_F(ConnectionHandlerTest, SetListenerRejectFraction) {
auto listener = new NiceMock();
TestListener* test_listener =
addListener(1, false, false, "test_listener", listener, &listener_callbacks);
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
EXPECT_CALL(*listener, setRejectFraction(UnitFloat(0.1234f)));
@@ -628,8 +760,6 @@ TEST_F(ConnectionHandlerTest, AddListenerSetRejectFraction) {
TestListener* test_listener =
addListener(1, false, false, "test_listener", listener, &listener_callbacks);
EXPECT_CALL(*listener, setRejectFraction(UnitFloat(0.12345f)));
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
EXPECT_CALL(*listener, onDestroy());
handler_->setListenerRejectFraction(UnitFloat(0.12345f));
@@ -644,8 +774,6 @@ TEST_F(ConnectionHandlerTest, SetsTransportSocketConnectTimeout) {
TestListener* test_listener =
addListener(1, false, false, "test_listener", listener, &listener_callbacks);
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
auto server_connection = new NiceMock();
@@ -670,8 +798,6 @@ TEST_F(ConnectionHandlerTest, DestroyCloseConnections) {
auto listener = new NiceMock();
TestListener* test_listener =
addListener(1, true, false, "test_listener", listener, &listener_callbacks);
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
Network::MockConnectionSocket* connection = new NiceMock();
@@ -691,8 +817,6 @@ TEST_F(ConnectionHandlerTest, CloseDuringFilterChainCreate) {
auto listener = new NiceMock();
TestListener* test_listener =
addListener(1, true, false, "test_listener", listener, &listener_callbacks);
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get()));
@@ -716,8 +840,6 @@ TEST_F(ConnectionHandlerTest, CloseConnectionOnEmptyFilterChain) {
auto listener = new NiceMock();
TestListener* test_listener =
addListener(1, true, false, "test_listener", listener, &listener_callbacks);
- EXPECT_CALL(test_listener->socket_factory_, localAddress())
- .WillRepeatedly(ReturnRef(local_address_));
handler_->addListener(absl::nullopt, *test_listener, runtime_);
EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get()));
@@ -737,22 +859,18 @@ TEST_F(ConnectionHandlerTest, CloseConnectionOnEmptyFilterChain) {
TEST_F(ConnectionHandlerTest, NormalRedirect) {
Network::TcpListenerCallbacks* listener_callbacks1;
auto listener1 = new NiceMock