Attention is currently required from: plaisthos.
Hello plaisthos,
I'd like you to do a code review.
Please visit
http://gerrit.openvpn.net/c/openvpn/+/1521?usp=email
to review the following change.
Change subject: Add support for user defined network namespace
......................................................................
Add support for user defined network namespace
Introduce the --netns option to allow moving the tunnel interface
into a user-defined network namespace.
This allows the VPN data plane to be isolated from the main
OpenVPN process namespace.
The current netlink library integration supports interface creation
and deletion in a target namespace. However, subsequent configuration
operations (e.g. address or mtu set) are executed in the caller's
namespace, as they rely on the default netlink socket context.
As a result, interface-related configuration performed after creation
may be applied in the wrong namespace.
Introduce helper functions to temporarily switch the process to the
requested network namespace using setns(2), execute the required
netlink operations, and then restore the original namespace.
The namespace switch is temporary and scoped to each netlink
operation. Once the operation completes, the original namespace
is restored to preserve the process execution context.
Note: This feature is Linux-only and depends on setns(2).
It is not compatible (yet) with Data Channel Offload (DCO).
Change-Id: I8b0d1cad7062856abcc40c4e16ec93b45295bbd3
Signed-off-by: Gianmarco De Gregori <[email protected]>
---
M doc/man-sections/vpn-network-options.rst
M src/openvpn/dco.c
M src/openvpn/networking.h
M src/openvpn/networking_sitnl.c
M src/openvpn/networking_sitnl.h
M src/openvpn/options.c
M src/openvpn/options.h
M src/openvpn/tun.c
8 files changed, 707 insertions(+), 46 deletions(-)
git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/21/1521/1
diff --git a/doc/man-sections/vpn-network-options.rst
b/doc/man-sections/vpn-network-options.rst
index 33ebedb..cd54cd4 100644
--- a/doc/man-sections/vpn-network-options.rst
+++ b/doc/man-sections/vpn-network-options.rst
@@ -304,6 +304,12 @@
Specify the link layer address, more commonly known as the MAC address.
Only applied to TAP devices.
+--netns name
+ Move the created tunnel interface into the specified network
+ namespace. The namespace must already exist.
+
+ (Supported on Linux only, on other platforms this is a no-op).
+
--persist-tun
Don't close and reopen TUN/TAP device or run up/down scripts across
:code:`SIGUSR1` or ``--ping-restart`` restarts.
diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index 26b8645..879590f 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -388,6 +388,12 @@
ret);
}
}
+
+ if (o->netns)
+ {
+ msg(msglevel, "Note: --netns not supported by ovpn-dco, disabling data
channel offload.");
+ return false;
+ }
#endif /* if defined(_WIN32) */
#if defined(HAVE_LIBCAPNG)
diff --git a/src/openvpn/networking.h b/src/openvpn/networking.h
index bce0c19..526dd49 100644
--- a/src/openvpn/networking.h
+++ b/src/openvpn/networking.h
@@ -42,7 +42,7 @@
/* Only the iproute2 backend implements these functions,
* the rest can rely on these stubs
*/
-#if !defined(ENABLE_IPROUTE)
+#if !defined(ENABLE_IPROUTE) && !defined(ENABLE_SITNL) ||
defined(TARGET_ANDROID)
static inline int
net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
{
@@ -63,7 +63,7 @@
{
(void)ctx;
}
-#endif /* !defined(ENABLE_IPROUTE) */
+#endif /* !defined(ENABLE_IPROUTE) && !defined(ENABLE_SITNL) ||
defined(TARGET_ANDROID) */
#if defined(ENABLE_SITNL) || defined(ENABLE_IPROUTE)
diff --git a/src/openvpn/networking_sitnl.c b/src/openvpn/networking_sitnl.c
index a5b2905..120ee6c 100644
--- a/src/openvpn/networking_sitnl.c
+++ b/src/openvpn/networking_sitnl.c
@@ -33,14 +33,17 @@
#include "networking.h"
#include "proto.h"
#include "route.h"
+#include "openvpn.h"
#include <errno.h>
#include <string.h>
#include <unistd.h>
+#include <sched.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
+#include <linux/net_namespace.h>
#define SNDBUF_SIZE (1024 * 2)
#define RCVBUF_SIZE (1024 * 4)
@@ -65,6 +68,12 @@
_nest->rta_len = (unsigned short)((void *)sitnl_nlmsg_tail(_msg) -
(void *)_nest); \
}
+#define SITNL_NETNS_RTA(r) \
+ ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))))
+
+#define NETNS_RUN_DIR "/var/run/netns"
+#define NETNS_ORIG_DIR "/proc/self/ns/net"
+
/* This function was originally implemented as a macro, but compiling with
* gcc and -O3 was getting confused about the math and thus raising
* security warnings on subsequent memcpy() calls.
@@ -121,6 +130,16 @@
char buf[256];
};
+/**
+ * Network namespace NSID request message
+ */
+struct sitnl_nsid_req
+{
+ struct nlmsghdr n;
+ struct rtgenmsg g;
+ char buf[256];
+};
+
typedef int (*sitnl_parse_reply_cb)(struct nlmsghdr *msg, void *arg);
/**
@@ -165,6 +184,101 @@
return 0;
}
+#if defined(TARGET_LINUX) && defined(ENABLE_SITNL)
+
+static int netns_id_from_name_sitnl(const char *name);
+static int set_netns_id_from_name_sitnl(const char *name);
+
+int
+net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
+{
+ struct stat st;
+ char path[PATH_MAX];
+
+ ctx->netns = NULL;
+
+ if (c && c->options.netns)
+ {
+ snprintf(path, sizeof(path), "%s/%s", NETNS_RUN_DIR, c->options.netns);
+
+ if (stat(path, &st) != 0)
+ {
+ msg(M_WARN, "%s: Network namespace %s does not exist\n", __func__,
path);
+ return -1;
+ }
+
+ ctx->netns = c->options.netns;
+ }
+
+ ctx->gc = gc_new();
+ return 0;
+}
+
+void
+net_ctx_reset(openvpn_net_ctx_t *ctx)
+{
+ gc_reset(&ctx->gc);
+}
+
+void
+net_ctx_free(openvpn_net_ctx_t *ctx)
+{
+ gc_free(&ctx->gc);
+}
+
+int
+netns_switch(const char *name)
+{
+ char net_path[PATH_MAX];
+ int orig_fd, netns_fd;
+
+ /* Save current netns descriptor */
+ orig_fd = open(NETNS_ORIG_DIR, O_RDONLY | O_CLOEXEC);
+ if (orig_fd < 0)
+ {
+ msg(M_WARN, "%s: Cannot open original network namespace\n", __func__);
+ return -1;
+ }
+
+ snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
+ netns_fd = open(net_path, O_RDONLY | O_CLOEXEC);
+ if (netns_fd < 0)
+ {
+ msg(M_WARN, "%s: Cannot open network namespace \"%s\"\n", __func__,
name);
+ close(orig_fd);
+ return -1;
+ }
+
+ if (setns(netns_fd, CLONE_NEWNET) < 0)
+ {
+ msg(M_WARN, "%s: setting the network namespace \"%s\" failed\n",
__func__, name);
+ close(netns_fd);
+ close(orig_fd);
+ return -1;
+ }
+
+ close(netns_fd);
+ return orig_fd;
+}
+
+int
+netns_restore(int orig_fd)
+{
+ if (orig_fd < 0)
+ return -1;
+
+ if (setns(orig_fd, CLONE_NEWNET) < 0)
+ {
+ msg(M_WARN, "%s: Cannot restore original network namespace\n",
__func__);
+ close(orig_fd);
+ return -1;
+ }
+
+ close(orig_fd);
+ return 0;
+}
+#endif /* defined (TARGET_LINUX) && defined (ENABLE_SITNL) */
+
/**
* Open RTNL socket
*/
@@ -454,6 +568,90 @@
return ret;
}
+/**
+ * Netlink callback to extract the interface index from a RTM_NEWLINK message.
+ *
+ * This function is used as a Netlink response handler. It inspects incoming
+ * Netlink messages and, when a RTM_NEWLINK message is received, copies the
+ * contained `struct ifinfomsg` into the caller-provided result structure.
+ *
+ * @param n Netlink message header received from the kernel.
+ * @param arg Pointer to a `struct sitnl_link_req` used to store the result.
+ *
+ * @return 0 to stop Netlink message processing once the interface is found,
+ * 1 to continue processing other messages.
+ */
+static int
+sitnl_link_get_ifindex(struct nlmsghdr *n, void *arg)
+{
+ struct sitnl_link_req *res = arg;
+ struct ifinfomsg *ifi;
+
+ if (n->nlmsg_type != RTM_NEWLINK)
+ return 1;
+
+ ifi = NLMSG_DATA(n);
+ res->i = *ifi;
+
+ return 0;
+}
+
+/**
+ * Retrieve the interface index of a network device in a specific network
namespace.
+ *
+ * Sends an RTM_GETLINK Netlink request targeting the given network namespace
ID
+ * and interface name, and extracts the resulting interface index from the
kernel
+ * response.
+ *
+ * On failure, this function returns 0, matching the behavior of
+ * `if_nametoindex()`.
+ *
+ * @param ifname Name of the network interface.
+ * @param netnsid Target network namespace ID.
+ *
+ * @return Interface index on success, or 0 on error.
+ */
+static int
+get_ifindex_in_netns(const char *ifname, int netnsid)
+{
+ struct sitnl_link_req req;
+ struct sitnl_link_req res;
+ int ret = -1, ifindex = 0;
+
+ CLEAR(req);
+ CLEAR(res);
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+ req.n.nlmsg_type = RTM_GETLINK;
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+
+ SITNL_ADDATTR(&req.n, sizeof(req), IFLA_TARGET_NETNSID, &netnsid, 4);
+ SITNL_ADDATTR(&req.n, sizeof(req), IFLA_IFNAME, ifname, strlen(ifname) +
1);
+
+ ret = sitnl_send(&req.n, 0, 0, sitnl_link_get_ifindex, &res);
+
+ if (ret < 0)
+ {
+ goto err;
+ }
+
+ ifindex = res.i.ifi_index;
+
+err:
+ return ifindex;
+}
+
+int
+openvpn_if_nametoindex(const char *ifname, int netnsid)
+{
+ if (netnsid < 0)
+ {
+ return if_nametoindex(ifname);
+ }
+
+ return get_ifindex_in_netns(ifname, netnsid);
+}
+
typedef struct
{
size_t addr_size;
@@ -624,6 +822,53 @@
return ret;
}
+int
+net_iface_move_netns(const char *iface, const char *netns)
+{
+ struct sitnl_link_req req = {};
+ int ret = -1, ifindex;
+ char net_path[PATH_MAX];
+
+ ASSERT(iface);
+ ASSERT(netns);
+
+ snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, netns);
+
+ ifindex = if_nametoindex(iface);
+ if (ifindex == 0)
+ {
+ msg(M_ERR, "%s: if_nametoindex(%s) failed", __func__, iface);
+ goto err;
+ }
+
+ int netns_fd = open(net_path, O_RDONLY);
+ if (netns_fd < 0)
+ {
+ msg(M_ERR, "%s: open(%s) failed: %s", __func__, netns,
strerror(errno));
+ goto err;
+ }
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+ req.n.nlmsg_type = RTM_SETLINK;
+ req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+ req.i.ifi_family = AF_UNSPEC;
+ req.i.ifi_index = ifindex;
+
+ SITNL_ADDATTR(&req.n, sizeof(req), IFLA_NET_NS_FD, &netns_fd,
sizeof(netns_fd));
+
+ msg(D_ROUTE, "%s: move %s to netns %s", __func__, iface, netns);
+
+ ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+
+err:
+ if (netns_fd >= 0)
+ {
+ close(netns_fd);
+ }
+ return ret;
+}
+
/* used by iproute2 implementation too */
int
net_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst, in_addr_t
*best_gw,
@@ -661,17 +906,23 @@
net_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)
{
struct sitnl_link_req req;
- int ifindex;
+ int ifindex, orig = -1, ret = -1;
+ const char *netns = ctx ? ctx->netns : NULL;
CLEAR(req);
+ if (netns)
+ {
+ orig = netns_switch(netns);
+ }
+
if (!iface)
{
msg(M_WARN, "%s: passed NULL interface", __func__);
return -EINVAL;
}
- ifindex = if_nametoindex(iface);
+ ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (ifindex == 0)
{
msg(M_WARN, "%s: rtnl: cannot get ifindex for %s: %s", __func__,
iface, strerror(errno));
@@ -696,18 +947,35 @@
msg(M_INFO, "%s: set %s %s", __func__, iface, up ? "up" : "down");
- return sitnl_send(&req.n, 0, 0, NULL, NULL);
+ ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+ if (ret < 0)
+ {
+ goto err;
+ }
+ if (orig >= 0)
+ {
+ netns_restore(orig);
+ }
+
+err:
+ return ret;
}
int
net_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface, uint32_t mtu)
{
struct sitnl_link_req req;
- int ifindex, ret = -1;
+ int ifindex, orig = -1, ret = -1;
+ const char *netns = ctx ? ctx->netns : NULL;
CLEAR(req);
- ifindex = if_nametoindex(iface);
+ if (netns)
+ {
+ orig = netns_switch(netns);
+ }
+
+ ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (ifindex == 0)
{
msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
iface);
@@ -726,6 +994,11 @@
msg(M_INFO, "%s: mtu %u for %s", __func__, mtu, iface);
ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+
+ if (orig >= 0)
+ {
+ netns_restore(orig);
+ }
err:
return ret;
}
@@ -734,11 +1007,18 @@
net_addr_ll_set(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,
uint8_t *addr)
{
struct sitnl_link_req req;
- int ifindex, ret = -1;
+ int ifindex, orig = -1, ret = -1;
+ const char *netns = ctx ? ctx->netns : NULL;
CLEAR(req);
- ifindex = if_nametoindex(iface);
+ if (netns)
+ {
+ orig = netns_switch(netns);
+ }
+
+
+ ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (ifindex == 0)
{
msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
iface);
@@ -757,20 +1037,32 @@
msg(M_INFO, "%s: lladdr " MAC_FMT " for %s", __func__,
MAC_PRINT_ARG(addr), iface);
ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+
+ if (orig >= 0)
+ {
+ netns_restore(orig);
+ }
err:
return ret;
}
static int
-sitnl_addr_set(uint16_t cmd, uint16_t flags, int ifindex, sa_family_t
af_family,
+sitnl_addr_set(openvpn_net_ctx_t *ctx, uint16_t cmd, uint16_t flags, int
ifindex, sa_family_t af_family,
const inet_address_t *local, const inet_address_t *remote, int
prefixlen)
{
struct sitnl_addr_req req;
uint32_t size;
- int ret = -EINVAL;
+ int ret = -EINVAL, orig = -1;
+ const char *netns = ctx ? ctx->netns : NULL;
CLEAR(req);
+ if (netns)
+ {
+ orig = netns_switch(netns);
+ }
+
+
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
req.n.nlmsg_type = cmd;
req.n.nlmsg_flags = NLM_F_REQUEST | flags;
@@ -820,19 +1112,25 @@
}
ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+
if (ret == -EEXIST)
{
ret = 0;
}
+ if (orig >= 0)
+ {
+ netns_restore(orig);
+ }
err:
return ret;
}
static int
-sitnl_addr_ptp_add(sa_family_t af_family, const char *iface, const
inet_address_t *local,
+sitnl_addr_ptp_add(openvpn_net_ctx_t *ctx, sa_family_t af_family, const char
*iface, const inet_address_t *local,
const inet_address_t *remote)
{
int ifindex;
+ const char *netns = ctx ? ctx->netns : NULL;
switch (af_family)
{
@@ -850,21 +1148,22 @@
return -EINVAL;
}
- ifindex = if_nametoindex(iface);
+ ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (ifindex == 0)
{
msg(M_WARN, "%s: cannot get ifindex for %s: %s", __func__, np(iface),
strerror(errno));
return -ENOENT;
}
- return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex,
af_family, local,
+ return sitnl_addr_set(ctx, RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE,
ifindex, af_family, local,
remote, 0);
}
static int
-sitnl_addr_ptp_del(sa_family_t af_family, const char *iface, const
inet_address_t *local)
+sitnl_addr_ptp_del(openvpn_net_ctx_t *ctx, sa_family_t af_family, const char
*iface, const inet_address_t *local)
{
int ifindex;
+ const char *netns = ctx ? ctx->netns : NULL;
switch (af_family)
{
@@ -882,26 +1181,33 @@
return -EINVAL;
}
- ifindex = if_nametoindex(iface);
+ ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (ifindex == 0)
{
msg(M_WARN | M_ERRNO, "%s: cannot get ifindex for %s", __func__,
iface);
return -ENOENT;
}
- return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, local, NULL, 0);
+ return sitnl_addr_set(ctx, RTM_DELADDR, 0, ifindex, af_family, local,
NULL, 0);
}
static int
-sitnl_route_set(uint16_t cmd, uint16_t flags, int ifindex, sa_family_t
af_family, const void *dst,
+sitnl_route_set(openvpn_net_ctx_t *ctx, uint16_t cmd, uint16_t flags, int
ifindex, sa_family_t af_family, const void *dst,
int prefixlen, const void *gw, enum rt_class_t table, int
metric,
enum rt_scope_t scope, unsigned char protocol, unsigned char
type)
{
struct sitnl_route_req req;
- int ret = -1, size;
+ int ret = -1, size, orig = -1;
+ const char *netns = ctx ? ctx->netns : NULL;
CLEAR(req);
+ if (netns)
+ {
+ orig = netns_switch(netns);
+ }
+
+
switch (af_family)
{
case AF_INET:
@@ -959,14 +1265,20 @@
}
ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+
+ if (orig >= 0)
+ {
+ netns_restore(orig);
+ }
err:
return ret;
}
static int
-sitnl_addr_add(sa_family_t af_family, const char *iface, const inet_address_t
*addr, int prefixlen)
+sitnl_addr_add(openvpn_net_ctx_t *ctx, sa_family_t af_family, const char
*iface, const inet_address_t *addr, int prefixlen)
{
int ifindex;
+ const char *netns = ctx ? ctx->netns : NULL;
switch (af_family)
{
@@ -984,20 +1296,21 @@
return -EINVAL;
}
- ifindex = if_nametoindex(iface);
+ ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (ifindex == 0)
{
msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
iface);
return -ENOENT;
}
- return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex,
af_family, addr, NULL,
+ return sitnl_addr_set(ctx, RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE,
ifindex, af_family, addr, NULL,
prefixlen);
}
static int
-sitnl_addr_del(sa_family_t af_family, const char *iface, inet_address_t *addr,
int prefixlen)
+sitnl_addr_del(openvpn_net_ctx_t *ctx, sa_family_t af_family, const char
*iface, inet_address_t *addr, int prefixlen)
{
+ const char *netns = ctx ? ctx->netns : NULL;
int ifindex;
switch (af_family)
@@ -1016,14 +1329,14 @@
return -EINVAL;
}
- ifindex = if_nametoindex(iface);
+ ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (ifindex == 0)
{
msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
iface);
return -ENOENT;
}
- return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, addr, NULL,
prefixlen);
+ return sitnl_addr_set(ctx, RTM_DELADDR, 0, ifindex, af_family, addr, NULL,
prefixlen);
}
int
@@ -1042,7 +1355,7 @@
msg(M_INFO, "%s: %s/%d dev %s", __func__, inet_ntop(AF_INET,
&addr_v4.ipv4, buf, sizeof(buf)),
prefixlen, iface);
- return sitnl_addr_add(AF_INET, iface, &addr_v4, prefixlen);
+ return sitnl_addr_add(ctx, AF_INET, iface, &addr_v4, prefixlen);
}
int
@@ -1062,7 +1375,7 @@
msg(M_INFO, "%s: %s/%d dev %s", __func__, inet_ntop(AF_INET6,
&addr_v6.ipv6, buf, sizeof(buf)),
prefixlen, iface);
- return sitnl_addr_add(AF_INET6, iface, &addr_v6, prefixlen);
+ return sitnl_addr_add(ctx, AF_INET6, iface, &addr_v6, prefixlen);
}
int
@@ -1081,7 +1394,7 @@
msg(M_INFO, "%s: %s dev %s", __func__, inet_ntop(AF_INET, &addr_v4.ipv4,
buf, sizeof(buf)),
iface);
- return sitnl_addr_del(AF_INET, iface, &addr_v4, prefixlen);
+ return sitnl_addr_del(ctx, AF_INET, iface, &addr_v4, prefixlen);
}
int
@@ -1101,7 +1414,7 @@
msg(M_INFO, "%s: %s/%d dev %s", __func__, inet_ntop(AF_INET6,
&addr_v6.ipv6, buf, sizeof(buf)),
prefixlen, iface);
- return sitnl_addr_del(AF_INET6, iface, &addr_v6, prefixlen);
+ return sitnl_addr_del(ctx, AF_INET6, iface, &addr_v6, prefixlen);
}
int
@@ -1129,7 +1442,7 @@
inet_ntop(AF_INET, &local_v4.ipv4, buf1, sizeof(buf1)),
inet_ntop(AF_INET, &remote_v4.ipv4, buf2, sizeof(buf2)), iface);
- return sitnl_addr_ptp_add(AF_INET, iface, &local_v4, &remote_v4);
+ return sitnl_addr_ptp_add(ctx, AF_INET, iface, &local_v4, &remote_v4);
}
int
@@ -1150,19 +1463,20 @@
msg(M_INFO, "%s: %s dev %s", __func__, inet_ntop(AF_INET, &local_v4.ipv4,
buf, sizeof(buf)),
iface);
- return sitnl_addr_ptp_del(AF_INET, iface, &local_v4);
+ return sitnl_addr_ptp_del(ctx, AF_INET, iface, &local_v4);
}
static int
-sitnl_route_add(const char *iface, sa_family_t af_family, const void *dst, int
prefixlen,
+sitnl_route_add(openvpn_net_ctx_t *ctx, const char *iface, sa_family_t
af_family, const void *dst, int prefixlen,
const void *gw, uint32_t table, int metric)
{
+ const char *netns = ctx ? ctx->netns : NULL;
enum rt_scope_t scope = RT_SCOPE_UNIVERSE;
int ifindex = 0;
if (iface)
{
- ifindex = if_nametoindex(iface);
+ ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (ifindex == 0)
{
msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifindex for %s",
__func__, iface);
@@ -1180,7 +1494,7 @@
scope = RT_SCOPE_LINK;
}
- return sitnl_route_set(RTM_NEWROUTE, NLM_F_CREATE, ifindex, af_family,
dst, prefixlen, gw,
+ return sitnl_route_set(ctx, RTM_NEWROUTE, NLM_F_CREATE, ifindex,
af_family, dst, prefixlen, gw,
table, metric, scope, RTPROT_BOOT, RTN_UNICAST);
}
@@ -1209,7 +1523,7 @@
inet_ntop(AF_INET, &dst_be, dst_str, sizeof(dst_str)), prefixlen,
inet_ntop(AF_INET, &gw_be, gw_str, sizeof(gw_str)), np(iface), table,
metric);
- return sitnl_route_add(iface, AF_INET, dst_ptr, prefixlen, gw_ptr, table,
metric);
+ return sitnl_route_add(ctx, iface, AF_INET, dst_ptr, prefixlen, gw_ptr,
table, metric);
}
int
@@ -1235,18 +1549,19 @@
inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)), prefixlen,
inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)), np(iface),
table, metric);
- return sitnl_route_add(iface, AF_INET6, dst, prefixlen, gw, table, metric);
+ return sitnl_route_add(ctx, iface, AF_INET6, dst, prefixlen, gw, table,
metric);
}
static int
-sitnl_route_del(const char *iface, sa_family_t af_family, inet_address_t *dst,
int prefixlen,
+sitnl_route_del(openvpn_net_ctx_t *ctx, const char *iface, sa_family_t
af_family, inet_address_t *dst, int prefixlen,
inet_address_t *gw, uint32_t table, int metric)
{
+ const char *netns = ctx ? ctx->netns : NULL;
int ifindex = 0;
if (iface)
{
- ifindex = if_nametoindex(iface);
+ ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (ifindex == 0)
{
msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifindex for %s",
__func__, iface);
@@ -1259,7 +1574,7 @@
table = RT_TABLE_MAIN;
}
- return sitnl_route_set(RTM_DELROUTE, 0, ifindex, af_family, dst,
prefixlen, gw, table, metric,
+ return sitnl_route_set(ctx, RTM_DELROUTE, 0, ifindex, af_family, dst,
prefixlen, gw, table, metric,
RT_SCOPE_NOWHERE, 0, 0);
}
@@ -1286,7 +1601,7 @@
inet_ntop(AF_INET, &dst_v4.ipv4, dst_str, sizeof(dst_str)), prefixlen,
inet_ntop(AF_INET, &gw_v4.ipv4, gw_str, sizeof(gw_str)), np(iface),
table, metric);
- return sitnl_route_del(iface, AF_INET, &dst_v4, prefixlen, &gw_v4, table,
metric);
+ return sitnl_route_del(ctx, iface, AF_INET, &dst_v4, prefixlen, &gw_v4,
table, metric);
}
int
@@ -1312,7 +1627,7 @@
inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)), prefixlen,
inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)), np(iface),
table, metric);
- return sitnl_route_del(iface, AF_INET6, &dst_v6, prefixlen, &gw_v6, table,
metric);
+ return sitnl_route_del(ctx, iface, AF_INET6, &dst_v6, prefixlen, &gw_v6,
table, metric);
}
@@ -1320,10 +1635,16 @@
net_iface_new(openvpn_net_ctx_t *ctx, const char *iface, const char *type,
void *arg)
{
struct sitnl_link_req req = {};
- int ret = -1;
+ int ret = -1, orig = -1;
+ const char *netns = ctx ? ctx->netns : NULL;
ASSERT(iface);
+ if (netns)
+ {
+ orig = netns_switch(netns);
+ }
+
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
req.n.nlmsg_type = RTM_NEWLINK;
@@ -1348,6 +1669,11 @@
msg(D_ROUTE, "%s: add %s type %s", __func__, iface, type);
ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+
+ if (orig >= 0)
+ {
+ netns_restore(orig);
+ }
err:
return ret;
}
@@ -1424,17 +1750,189 @@
return 0;
}
+/**
+ * Netlink callback to extract and store a network namespace ID (NSID).
+ *
+ * This function processes Netlink responses to RTM_GETNSID requests.
+ * If a NETNSA_NSID attribute is present, its value is copied into the
+ * caller-provided storage.
+ *
+ * If a Netlink error message is received, the contained error code is
+ * returned directly.
+ *
+ * @param n Netlink message header received from the kernel.
+ * @param arg Pointer to an integer where the NSID will be stored.
+ *
+ * @return 0 on success, a negative error code on failure.
+ */
+static int
+sitnl_nsid_save(struct nlmsghdr *n, void *arg)
+{
+ int *nsid = arg;
+ struct rtgenmsg *g = NLMSG_DATA(n);
+ struct rtattr *tb[NETNSA_MAX + 1];
+ int ret;
+
+ if (n->nlmsg_type == NLMSG_ERROR)
+ {
+ struct nlmsgerr *err = NLMSG_DATA(n);
+ return err->error;
+ }
+
+ ret = sitnl_parse_rtattr(tb, NETNSA_MAX, SITNL_NETNS_RTA(g),
NLMSG_PAYLOAD(n, sizeof(*g)));
+
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ if (!tb[NETNSA_NSID])
+ {
+ return -ENOENT;
+ }
+
+ memcpy(nsid, RTA_DATA(tb[NETNSA_NSID]), sizeof(*nsid));
+ return 0;
+}
+
+/**
+ * Retrieve the network namespace ID (NSID) associated with a namespace name.
+ *
+ * This function opens the network namespace identified by the given path
+ * and sends an RTM_GETNSID Netlink request to retrieve its assigned NSID.
+ *
+ * @param name Name of the network namespace.
+ *
+ * @return The network namespace ID on success, or a negative value on failure.
+ */
+static int
+netns_id_from_name_sitnl(const char *name)
+{
+ struct sitnl_nsid_req req;
+
+ int ret = -1, netnsid = -1;
+ CLEAR(req);
+
+ int netns_fd = open(name, O_RDONLY);
+ if (netns_fd < 0)
+ {
+ goto err;
+ }
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.g));
+ req.n.nlmsg_type = RTM_GETNSID;
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.g.rtgen_family = AF_UNSPEC;
+
+ SITNL_ADDATTR(&req.n, sizeof(req), NETNSA_FD, &netns_fd, 4);
+
+ ret = sitnl_send(&req.n, 0, 0, sitnl_nsid_save, &netnsid);
+
+ if (ret < 0)
+ {
+ goto err;
+ }
+
+err:
+ if (netns_fd >= 0)
+ {
+ close(netns_fd);
+ }
+
+ return netnsid;
+}
+
+/**
+ * Assign a network namespace ID (NSID) to a namespace.
+ *
+ * This function sends an RTM_NEWNSID Netlink request to associate a
+ * NSID with the network namespace identified by the given path.
+ *
+ * @param name Name of the network namespace.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+static int
+set_netns_id_from_name_sitnl(const char *name)
+{
+ struct sitnl_nsid_req req;
+
+ int ret = -1, netnsid = -1;
+ CLEAR(req);
+
+ int netns_fd = open(name, O_RDONLY);
+ if (netns_fd < 0)
+ {
+ goto err;
+ }
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.g));
+ req.n.nlmsg_type = RTM_NEWNSID;
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.g.rtgen_family = AF_UNSPEC;
+
+ SITNL_ADDATTR(&req.n, sizeof(req), NETNSA_FD, &netns_fd, 4);
+ SITNL_ADDATTR(&req.n, sizeof(req), NETNSA_NSID, &netnsid, 4);
+
+ ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+
+ if (ret < 0)
+ {
+ goto err;
+ }
+
+err:
+ if (netns_fd >= 0)
+ {
+ close(netns_fd);
+ }
+
+ return ret;
+}
+
+int
+get_or_create_netnsid_sitnl(const char *name)
+{
+ char net_path[PATH_MAX];
+ int netnsid;
+ snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
+
+ /* First get */
+ netnsid = netns_id_from_name_sitnl(net_path);
+ if (netnsid >= 0)
+ return netnsid;
+
+ /* No netnsid yet? Try to assign one */
+ netnsid = set_netns_id_from_name_sitnl(net_path);
+ if (netnsid < 0)
+ return -1;
+
+ /* Second get */
+ netnsid = netns_id_from_name_sitnl(net_path);
+ if (netnsid >= 0)
+ return netnsid;
+
+ return -1;
+}
+
int
net_iface_type(openvpn_net_ctx_t *ctx, const char *iface, char
type[IFACE_TYPE_LEN_MAX])
{
+ const char *netns = ctx ? ctx->netns : NULL;
struct sitnl_link_req req = {};
- int ifindex = if_nametoindex(iface);
+ int ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
+ int orig = -1;
if (!ifindex)
{
return -errno;
}
+ if (netns)
+ {
+ orig = netns_switch(netns);
+ }
+
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
req.n.nlmsg_flags = NLM_F_REQUEST;
req.n.nlmsg_type = RTM_GETLINK;
@@ -1453,20 +1951,32 @@
msg(D_ROUTE, "%s: type of %s: %s", __func__, iface, type);
+ if (orig >= 0)
+ {
+ netns_restore(orig);
+ }
+
return 0;
}
int
net_iface_del(openvpn_net_ctx_t *ctx, const char *iface)
{
+ int ret = -1, orig = -1;
struct sitnl_link_req req = {};
- int ifindex = if_nametoindex(iface);
+ const char *netns = ctx ? ctx->netns : NULL;
+ int ifindex = openvpn_if_nametoindex(iface,
get_or_create_netnsid_sitnl(netns));
if (!ifindex)
{
return -errno;
}
+ if (netns)
+ {
+ orig = netns_switch(netns);
+ }
+
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
req.n.nlmsg_flags = NLM_F_REQUEST;
req.n.nlmsg_type = RTM_DELLINK;
@@ -1476,7 +1986,20 @@
msg(D_ROUTE, "%s: delete %s", __func__, iface);
- return sitnl_send(&req.n, 0, 0, NULL, NULL);
+ ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+
+ if (ret < 0)
+ {
+ goto err;
+ }
+
+ if (orig >= 0)
+ {
+ netns_restore(orig);
+ }
+
+err:
+ return ret;
}
#endif /* !ENABLE_SITNL */
diff --git a/src/openvpn/networking_sitnl.h b/src/openvpn/networking_sitnl.h
index 481cc36..731a0e4 100644
--- a/src/openvpn/networking_sitnl.h
+++ b/src/openvpn/networking_sitnl.h
@@ -21,7 +21,111 @@
#ifndef NETWORKING_SITNL_H_
#define NETWORKING_SITNL_H_
+#include "env_set.h"
+
typedef char openvpn_net_iface_t;
-typedef void *openvpn_net_ctx_t;
+
+struct openvpn_net_ctx
+{
+ const char *netns;
+ struct gc_arena gc;
+};
+
+typedef struct openvpn_net_ctx openvpn_net_ctx_t;
+
+/**
+ * @brief Switch the current thread to the specified network namespace.
+ *
+ * This function changes the calling thread's network namespace to the one
+ * identified by @p name. The current (original) network namespace file
+ * descriptor is saved and returned, so it can later be restored using
+ * netns_restore().
+ *
+ * The switch is performed using setns(2). This approach is required because
+ * the netlink library does not support performing operations in an arbitrary
+ * target network namespace, except for interface creation and deletion.
+ * Therefore, in order to execute generic netlink operations inside a
+ * specific network namespace, the thread must temporarily enter
+ * that namespace via setns().
+ *
+ * @param name Name of the target network namespace (as found under
+ * NETNS_RUN_DIR, e.g. /var/run/netns/<name>).
+ *
+ * @return On success, returns a file descriptor referring to the original
+ * network namespace. This descriptor must be passed to
+ * netns_restore() to switch back.
+ * @return -1 on failure (an error is logged and no namespace switch is kept).
+ *
+ * @note The returned file descriptor must be closed by calling
+ * netns_restore(). Failing to do so may leak file descriptors.
+ */
+int netns_switch(const char *name);
+
+/**
+ * @brief Restore the previously saved network namespace.
+ *
+ * This function restores the network namespace saved by netns_switch()
+ * using the file descriptor returned by that function.
+ *
+ * The restoration is performed using setns(2), switching the calling
+ * thread back to its original network namespace.
+ *
+ * @param orig_fd File descriptor of the original network namespace,
+ * as returned by netns_switch().
+ *
+ * @return 0 on success.
+ * @return -1 on failure (an error is logged and the file descriptor
+ * is closed).
+ *
+ * @note This function closes @p orig_fd in all cases.
+ * After calling this function, @p orig_fd must not be reused.
+ */
+int netns_restore(int orig_fd);
+
+/**
+ * Resolve a network interface name to its interface index.
+ *
+ * If a valid network namespace ID is provided, the lookup is performed inside
+ * that network namespace using Netlink. Otherwise, this function falls back
+ * to the standard `if_nametoindex()` call in the current namespace.
+ *
+ * @param ifname Name of the network interface.
+ * @param netnsid Network namespace ID, or a negative value to use the current
+ * namespace.
+ *
+ * @return Interface index on success, or 0 on error.
+ */
+int openvpn_if_nametoindex(const char *ifname, int netnsid);
+
+/**
+ * Move a network interface to a different network namespace.
+ *
+ * This function moves the specified network interface from the current
+ * network namespace into the target network namespace identified by name.
+ * The operation is performed via a RTM_SETLINK Netlink request using the
+ * IFLA_NET_NS_FD attribute.
+ *
+ * The target network namespace is expected to exist under NETNS_RUN_DIR
+ * (/var/run/netns).
+ *
+ * @param iface Name of the network interface to move.
+ * @param netns Name of the target network namespace.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+int net_iface_move_netns(const char *iface, const char *netns);
+
+/**
+ * Retrieve or create a network namespace ID (NSID) for a given namespace.
+ *
+ * This function first attempts to retrieve the NSID associated with the
+ * specified network namespace. If no NSID is currently assigned, it
+ * requests the kernel to create one and then retries the lookup.
+ *
+ * @param name Name of the network namespace.
+ *
+ * @return The network namespace ID on success, or -1 on failure.
+ */
+int get_or_create_netnsid_sitnl(const char *name);
#endif /* NETWORKING_SITNL_H_ */
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 2bca647..7b7681a 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -6473,6 +6473,16 @@
VERIFY_PERMISSION(OPT_P_GENERAL);
options->mtu_test = true;
}
+#if defined(TARGET_LINUX)
+ else if (streq(p[0], "netns") && p[1] && !p[2])
+ {
+#ifndef ENABLE_SITNL
+ msg(M_WARN, "NOTE: --netns is supported only on Linux when SITNL is
built-in");
+#endif
+ VERIFY_PERMISSION(OPT_P_GENERAL);
+ options->netns = p[1];
+ }
+#endif
else if (streq(p[0], "nice") && p[1] && !p[2])
{
VERIFY_PERMISSION(OPT_P_NICE);
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 16cfdb5..a19a84c 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -316,6 +316,7 @@
struct dns_options dns_options;
+ const char *netns;
bool remote_random;
const char *ipchange;
const char *dev;
diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
index e38df3e..e3c46ea 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -2169,6 +2169,17 @@
set_nonblock(tt->fd);
set_cloexec(tt->fd);
tt->actual_name = string_alloc(ifr.ifr_name, NULL);
+
+#if defined(TARGET_LINUX) && defined(ENABLE_SITNL)
+ if (ctx->netns)
+ {
+ int ret = net_iface_move_netns(ifr.ifr_name, ctx->netns);
+ if (ret < 0)
+ {
+ msg(M_WARN | M_ERRNO, "Note: Cannot move dev %s to network
namespace %s", ifr.ifr_name, ctx->netns);
+ }
+ }
+#endif /* if defined (TARGET_LINUX) && defined(ENABLE_SITNL) */
}
return;
}
--
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1521?usp=email
To unsubscribe, or for help writing mail filters, visit
http://gerrit.openvpn.net/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I8b0d1cad7062856abcc40c4e16ec93b45295bbd3
Gerrit-Change-Number: 1521
Gerrit-PatchSet: 1
Gerrit-Owner: its_Giaan <[email protected]>
Gerrit-Reviewer: plaisthos <[email protected]>
Gerrit-CC: openvpn-devel <[email protected]>
Gerrit-Attention: plaisthos <[email protected]>
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel