Skip to content

Commit d1723fd

Browse files
authored
listener: Support for Linux network namespaces (#39517)
This changes the listener to make use of the `network_namespace_filepath` field of the `SocketAddress` that specifies the address/port to listen on. When the field is specified, the underlying sockets used to listen are created in the network namespace referenced by the filepath. This allows the Envoy process to listen for connections from _other_ network namespaces, but to proxy those connections through the Envoy's network namespace. ### What is a network namespace? Each Linux network namespace has its own independent network devices, IP addresses, routing tables, and firewall rules. This isolation allows multiple processes or containers to run on the same host machine with their own distinct network configurations, preventing conflicts. ### Motivation Currently, Envoy listeners bind to network interfaces on the host's default network namespace. In environments where Envoy needs to operate within a specific, isolated network namespace (like a container with a custom network configuration or a mesh sidecar within a k8s pod), this change allows a single Envoy process to act like a sidecar for multiple pods. Since network namespaces are a Linux feature, there is no change for other OSes. Risk Level: Low. Only matters if the field is configured and only on Linux. Testing: Unit testing w/ mocks. Direct verification or integration testing requires privileges. Docs Changes: Proto docs updated. Release Notes: Done Platform Specific Features: Linux, only. Progress toward issue #38947 --------- Signed-off-by: Tony Allen <txallen@google.com> Signed-off-by: Tony Allen <tony@allen.gg>
1 parent 5a80c0e commit d1723fd

File tree

24 files changed

+318
-62
lines changed

24 files changed

+318
-62
lines changed

api/envoy/config/core/v3/address.proto

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,18 @@ message SocketAddress {
9898
// IPv6 space as ``::FFFF:<IPv4-address>``.
9999
bool ipv4_compat = 6;
100100

101-
// The Linux network namespace to bind the socket to. If this is set, Envoy will
102-
// create the socket in the specified network namespace. Only supported on Linux.
103-
// [#not-implemented-hide:]
101+
// Filepath that specifies the Linux network namespace this socket will be created in (see ``man 7
102+
// network_namespaces``). If this field is set, Envoy will create the socket in the specified
103+
// network namespace.
104+
//
105+
// .. note::
106+
// Setting this parameter requires Envoy to run with the ``CAP_NET_ADMIN`` capability.
107+
//
108+
// .. note::
109+
// Currently only used for Listener sockets.
110+
//
111+
// .. attention::
112+
// Network namespaces are only configurable on Linux. Otherwise, this field has no effect.
104113
string network_namespace_filepath = 7;
105114
}
106115

changelogs/current.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ new_features:
160160
Included the asterisk ``*`` in the match pattern when using the * or ** operators in the URL template.
161161
This behavioral change can be temporarily reverted by setting runtime guard
162162
``envoy.reloadable_features.uri_template_match_on_asterisk`` to ``false``.
163+
- area: socket
164+
change: |
165+
Added ``network_namespace_filepath`` to ``SocketAddress``. Currently only used by listeners.
163166
- area: rbac filter
164167
change: |
165168
allow listed ``FilterStateInput`` to be used with the xDS matcher in the HTTP RBAC filter.

envoy/api/os_sys_calls.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
#include "envoy/common/pure.h"
1313
#include "envoy/network/address.h"
1414

15-
#include "absl/types/optional.h"
16-
1715
namespace Envoy {
1816
namespace Api {
1917

envoy/api/os_sys_calls_linux.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ class LinuxOsSysCalls {
1616
public:
1717
virtual ~LinuxOsSysCalls() = default;
1818

19+
/**
20+
* @see man 2 setns
21+
*/
22+
virtual SysCallIntResult setns(int fd, int nstype) const PURE;
23+
1924
/**
2025
* @see sched_getaffinity (man 2 sched_getaffinity)
2126
*/

envoy/network/address.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ class Instance {
239239
* @return SocketInterface to be used with the address.
240240
*/
241241
virtual const Network::SocketInterface& socketInterface() const PURE;
242+
243+
/**
244+
* @return filepath of the network namespace for the address.
245+
*/
246+
virtual absl::optional<std::string> networkNamespace() const PURE;
242247
};
243248

244249
/*

mobile/library/common/network/synthetic_address_impl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class SyntheticAddressImpl : public Instance {
5454
return SocketInterfaceSingleton::get();
5555
}
5656

57+
absl::optional<std::string> networkNamespace() const override { return absl::nullopt; }
58+
5759
private:
5860
const std::string address_{"synthetic"};
5961
};

source/common/api/posix/os_sys_calls_impl_linux.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,10 @@ SysCallIntResult LinuxOsSysCallsImpl::sched_getaffinity(pid_t pid, size_t cpuset
1717
return {rc, errno};
1818
}
1919

20+
SysCallIntResult LinuxOsSysCallsImpl::setns(int fd, int nstype) const {
21+
const int rc = ::setns(fd, nstype);
22+
return {rc, errno};
23+
}
24+
2025
} // namespace Api
2126
} // namespace Envoy

source/common/api/posix/os_sys_calls_impl_linux.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class LinuxOsSysCallsImpl : public LinuxOsSysCalls {
1515
public:
1616
// Api::LinuxOsSysCalls
1717
SysCallIntResult sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t* mask) override;
18+
SysCallIntResult setns(int fd, int nstype) const override;
1819
};
1920

2021
using LinuxOsSysCallsSingleton = ThreadSafeSingleton<LinuxOsSysCallsImpl>;

source/common/listener_manager/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ envoy_cc_library(
3636
"//envoy/server:transport_socket_config_interface",
3737
"//envoy/server:worker_interface",
3838
"//source/common/access_log:access_log_lib",
39+
"//source/common/api:os_sys_calls_lib",
3940
"//source/common/common:basic_resource_lib",
4041
"//source/common/common:empty_string",
4142
"//source/common/config:utility_lib",

source/common/listener_manager/listener_impl.cc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,15 +1155,17 @@ bool ListenerImpl::hasDuplicatedAddress(const ListenerImpl& other) const {
11551155
return false;
11561156
}
11571157
// For listeners that do not bind or listeners that do not bind to port 0 we must check to make
1158-
// sure we are not duplicating the address. This avoids ambiguity about which non-binding
1159-
// listener is used or even worse for the binding to port != 0 and reuse port case multiple
1160-
// different listeners receiving connections destined for the same port.
1158+
// sure we are not duplicating the address in the same network namespace. This avoids ambiguity
1159+
// about which non-binding listener is used or even worse for the binding to port != 0 and reuse
1160+
// port case multiple different listeners receiving connections destined for the same port.
11611161
for (auto& other_addr : other.addresses()) {
11621162
if (other_addr->ip() == nullptr ||
11631163
(other_addr->ip() != nullptr && (other_addr->ip()->port() != 0 || !bindToPort()))) {
11641164
if (find_if(addresses_.begin(), addresses_.end(),
11651165
[&other_addr](const Network::Address::InstanceConstSharedPtr& addr) {
1166-
return *other_addr == *addr;
1166+
const bool same_netns =
1167+
addr->networkNamespace() == other_addr->networkNamespace();
1168+
return same_netns && *other_addr == *addr;
11671169
}) != addresses_.end()) {
11681170
return true;
11691171
}

0 commit comments

Comments
 (0)