From patchwork Wed Jul 29 15:38:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 5178 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:fd0b:b0:87d:ab56:3700 with SMTP id cw11csp2127946mac; Wed, 29 Jul 2026 08:39:13 -0700 (PDT) X-Forwarded-Encrypted: i=2; AHgh+RonzgxYjs8QqcAmfSZaoK4d9zu5AcbDu9/e8GL8CqPOVo4wEI4z/vrZrVBkr1MMXa9CVrTdcxDJNUI=@openvpn.net X-Received: by 2002:a05:6808:1908:b0:49a:8f0d:cdbb with SMTP id 5614622812f47-4ad5bd29b67mr5053705b6e.31.1785339553197; Wed, 29 Jul 2026 08:39:13 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1785339553; cv=none; d=google.com; s=arc-20260327; b=QJGNd1n44bMuJ9HJJGLp3PVnsrH9uqzHxNvfr0CL4XUjdLQakNiC80q7b72ysyCklI V4gApH339zdZM14JkEWUO0VdYM1u5OzkkNFwK4Eu+RIl7yVRENZkxgdgzyiZcUWuPpkc WRFkEBWRFCEMHx566V0PDZ2H5PjPVJPGElJPMsn5BpsxLWCqzKl3/4MGuk6NaTGelc5U tXeCVbgK7WwnngYZz8x3eleh0StnrKdb0oOzWoiW0r5AsGBrH34tHGHcJ1zqwesdmvkR krOVLWQjSIjJgNRrtna+h898aMiQVBDivPeu+PP8jC/BFhdGbeCN38L/lbU0ecgp4sZv 0KJQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:cc:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=NaZ6fRZO//M758KgOqdUF8Zu+57xxcnp0A2y/71pXpo=; fh=BsMg/B0Yb/hS/rzP5Npz4luh0IleZm8REk1XWiWRt2A=; b=Z7rcq+Hgr80zRRwWh6F04121HoU7SIKEWZ0MEuncQh3J0a5TiRc1KhvynpQBlANm3G QaDrRcjPdDbJasx0i9HEYUuItPURzgc0EU5PWcOnYf0Ddk0L5aK6NQFXbbp2lsXAlbxh rXZlW5SJik0Sy9IJ7vPuCoDGqn90u7U74MH4mMx1hDDo14CdFhB1/eDHqyogdyRkG6a8 9fUIgNFwoQQ0TtKbkuSq3JvUlaNzS7ImjzbJGeJgAiBbEWEgf+aWjSy+Tf1e9692gdxp Ih5Z5s843eWLTvrKCNFLFwQYkP0mttH8I5qatElieqodQQcoz2c0vRNYvFOeZ8UTi1zH Or+Q==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=VRTVEpNO; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=cCJ3LIRK; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=EuvxwFH4; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=tta8xAJK; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 5614622812f47-4ad6eca6eb9si1775827b6e.13.2026.07.29.08.39.12 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 29 Jul 2026 08:39:13 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=VRTVEpNO; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=cCJ3LIRK; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=EuvxwFH4; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=tta8xAJK; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type:Cc: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=NaZ6fRZO//M758KgOqdUF8Zu+57xxcnp0A2y/71pXpo=; b=VRTVEpNOJ2/R6pydtPuajtDgRC Bozl/eWhXiITBoLGHC+w5px1xGm7WLdOmidzAO3UlzIT19OZtg0BFJIwbjuO9zPm5osSWw0H4mD0r 1ajOKp/Dx/Sv4V+pB6xZXwxNkR7Q2z0k4zOYJqVeyC+Ys0qRVR52bH4bplYOkJnejwF8=; Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wp6NE-00008M-Lj; Wed, 29 Jul 2026 15:39:10 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wp6ND-00008D-44 for openvpn-devel@lists.sourceforge.net; Wed, 29 Jul 2026 15:39:08 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=DLxemB7r739PuPvMKLKCiNyWpGf3rKMq5Dl9+FxEFNg=; b=cCJ3LIRK6/Yijcz4lC7PraoBUQ Ry6v2z3vOMXba9lQe9scGq0V+cOpkX+r224YoeTsaqFF6limsU81GPNYmXizx3WFf8y047onlqwZM SkQ5XlaP23NjkIDttRmS/Oh+Gtbb1kEx79GSvdfu/ETqAReULYhnJWN34IMbf0/7ULrI=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=DLxemB7r739PuPvMKLKCiNyWpGf3rKMq5Dl9+FxEFNg=; b=EuvxwFH43ODRQBl+Fnqg+sYk8s XViZO194EzPJT5SjiMCFq4NWjlDg5L7Tn6vWhMHVUpEcfssmk2O65gErIVGv7TqycPa6wIhyY8Kz4 VgU6dB6TvNp6GzTrU2VQ3AS3YxV4MEvUuk5Fsm2lvi5zQBB7aG9yhDKojkxYcYeAl+EE=; Received: from mout-b-201.mailbox.org ([195.10.208.61]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wp6NB-0001Oy-Vb for openvpn-devel@lists.sourceforge.net; Wed, 29 Jul 2026 15:39:08 +0000 Received: from smtp1.mailbox.org (smtp1.mailbox.org [IPv6:2001:67c:2050:b231:465::1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-201.mailbox.org (Postfix) with ESMTPS id 4h9Ghd5wg9zLlqy; Wed, 29 Jul 2026 17:38:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1785339537; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=DLxemB7r739PuPvMKLKCiNyWpGf3rKMq5Dl9+FxEFNg=; b=tta8xAJKR/9/oAx30wKfl1xGS/bCJj3QqhW/Dx8Xrz9nuSS5qf394RO78VeCapgYbNKs3k zSb52U3DkZa83yWPuUhynPgNw3+I59BAea2+LRjr+COIv6hs70j4F7qYLWcpqx0yFOrRjv Z38O2ag7zDAALYN6/CjkO56Ye5b5niYC1PtMxx9fdJoeO4R8JOwtYBCh1ocFNQ4aXJadhn D6LhBCW6ChqUH7/idyeWgXb0EgLTY0KRhsJP+1TBWp9gENUxM14TTkm09EQAH/nKmb9wkn 4W2avaPy6exdoLGjCsa/1wgg93fkb+0WSM5/qbPX1r4Dp1qb44cfbLAqZyy+rw== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::1 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Wed, 29 Jul 2026 17:38:14 +0200 Message-ID: <20260729153815.2637139-3-marco@mandelbit.com> In-Reply-To: <20260729153815.2637139-1-marco@mandelbit.com> References: <20260729153815.2637139-1-marco@mandelbit.com> MIME-Version: 1.0 X-Rspamd-Queue-Id: 4h9Ghd5wg9zLlqy X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Extend ovpn-cli with -n to resolve an interface in a named network namespace and include OVPN_A_TARGET_NETNSID in peer and key requests. Use -n for existing peer and key operations that do not create transport sockets, exercising foreign-netns requests throughout the test suite. Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-Headers-End: 1wp6NB-0001Oy-Vb Subject: [Openvpn-devel] [PATCH ovpn net-next v3 3/4] selftests: ovpn: test operations in a foreign netns X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Antonio Quartulli Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1872064206934645760 X-GMAIL-MSGID: 1872064206934645760 Extend ovpn-cli with -n to resolve an interface in a named network namespace and include OVPN_A_TARGET_NETNSID in peer and key requests. Use -n for existing peer and key operations that do not create transport sockets, exercising foreign-netns requests throughout the test suite. Signed-off-by: Marco Baffo --- Changes in v3: - None. Changes in v2: - Used "ovpn-cli -n" instead of "ip netns exec ovpn-cli" where possible. - Removed additional tests since we modified the existing ones to exercise the new code path. tools/testing/selftests/net/ovpn/common.sh | 8 +- tools/testing/selftests/net/ovpn/ovpn-cli.c | 266 +++++++++++++++++- .../selftests/net/ovpn/test-close-socket.sh | 4 +- tools/testing/selftests/net/ovpn/test-mark.sh | 6 +- tools/testing/selftests/net/ovpn/test.sh | 50 ++-- 5 files changed, 289 insertions(+), 45 deletions(-) diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 2d844eb3aa6e..4f2510fb6d7f 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -221,7 +221,7 @@ ovpn_add_peer() { ${OVPN_UDP_PEERS_FILE} for p in $(seq 1 ${OVPN_NUM_PEERS}); do - ip netns exec "${server_ns}" ${OVPN_CLI} \ + "${OVPN_CLI}" -n "${server_ns}" \ new_key tun0 ${p} 1 0 ${OVPN_ALG} 0 \ data64.key done @@ -244,7 +244,7 @@ ovpn_add_peer() { ip netns exec "${peer_ns}" ${OVPN_CLI} new_peer \ tun${1} ${PEER_ID} ${TX_ID} ${LPORT} ${RADDR} \ ${RPORT} - ip netns exec "${peer_ns}" ${OVPN_CLI} new_key tun${1} \ + "${OVPN_CLI}" -n "${peer_ns}" new_key tun${1} \ ${PEER_ID} 1 0 ${OVPN_ALG} 1 data64.key fi else @@ -252,8 +252,8 @@ ovpn_add_peer() { (ip netns exec "${server_ns}" ${OVPN_CLI} listen tun0 \ 1 ${M_ID} ${OVPN_TCP_PEERS_FILE} && { for p in $(seq 1 ${OVPN_NUM_PEERS}); do - ip netns exec "${server_ns}" \ - ${OVPN_CLI} new_key tun0 ${p} \ + "${OVPN_CLI}" -n "${server_ns}" \ + new_key tun0 ${p} \ 1 0 ${OVPN_ALG} 0 data64.key done }) & diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index d40953375c86..6ea39b3ecc29 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -6,6 +6,7 @@ * Author: Antonio Quartulli */ +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include @@ -121,6 +123,8 @@ struct ovpn_ctx { unsigned int ifindex; char ifname[IFNAMSIZ]; + const char *target_netns_name; + int target_netnsid; enum ovpn_mode mode; bool mode_set; @@ -224,6 +228,10 @@ static struct nl_ctx *nl_ctx_alloc_flags(struct ovpn_ctx *ovpn, int cmd, if (ovpn->ifindex > 0) NLA_PUT_U32(ctx->nl_msg, OVPN_A_IFINDEX, ovpn->ifindex); + if (ovpn->target_netnsid >= 0) + NLA_PUT_S32(ctx->nl_msg, OVPN_A_TARGET_NETNSID, + ovpn->target_netnsid); + return ctx; nla_put_failure: err_msg: @@ -1365,12 +1373,196 @@ static int ovpn_rt_send(struct nlmsghdr *payload, pid_t peer, return ret; } +static int ovpn_parse_netnsid(struct nlmsghdr *msg, void *arg) +{ + struct rtgenmsg *gen; + struct rtattr *attr; + int len; + + if (msg->nlmsg_type != RTM_NEWNSID || + msg->nlmsg_len < NLMSG_LENGTH(sizeof(*gen))) + return -EINVAL; + + gen = NLMSG_DATA(msg); + len = NLMSG_PAYLOAD(msg, sizeof(*gen)); + attr = (struct rtattr *)((char *)gen + NLMSG_ALIGN(sizeof(*gen))); + + for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { + if (attr->rta_type != NETNSA_NSID) + continue; + + if (RTA_PAYLOAD(attr) < sizeof(int32_t)) + return -EINVAL; + + memcpy(arg, RTA_DATA(attr), sizeof(int32_t)); + return 0; + } + + return -ENOENT; +} + +struct ovpn_netns_req { + struct nlmsghdr n; + struct rtgenmsg g; + char buf[64]; +}; + +static int ovpn_get_netnsid(int netns_fd, int *netnsid) +{ + struct ovpn_netns_req req = { 0 }; + uint32_t fd = netns_fd; + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.g)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETNSID; + req.g.rtgen_family = AF_UNSPEC; + + if (ovpn_addattr(&req.n, sizeof(req), NETNSA_FD, &fd, + sizeof(fd)) < 0) + return -EMSGSIZE; + + *netnsid = NETNSA_NSID_NOT_ASSIGNED; + return ovpn_rt_send(&req.n, 0, 0, ovpn_parse_netnsid, netnsid); +} + +static int ovpn_assign_netnsid(int netns_fd) +{ + /* NETNSA_NSID_NOT_ASSIGNED asks the kernel to allocate an NSID. */ + int32_t netnsid = NETNSA_NSID_NOT_ASSIGNED; + struct ovpn_netns_req req = { 0 }; + uint32_t fd = netns_fd; + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.g)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_NEWNSID; + req.g.rtgen_family = AF_UNSPEC; + + if (ovpn_addattr(&req.n, sizeof(req), NETNSA_NSID, &netnsid, + sizeof(netnsid)) < 0 || + ovpn_addattr(&req.n, sizeof(req), NETNSA_FD, &fd, + sizeof(fd)) < 0) + return -EMSGSIZE; + + return ovpn_rt_send(&req.n, 0, 0, NULL, NULL); +} + +/* Get the target NSID relative to the current netns, assigning one if necessary. */ +static int ovpn_get_or_assign_netnsid(int netns_fd) +{ + int netnsid, ret; + + ret = ovpn_get_netnsid(netns_fd, &netnsid); + if (ret < 0) + return ret; + + if (netnsid >= 0) + return netnsid; + + ret = ovpn_assign_netnsid(netns_fd); + if (ret < 0 && ret != -EEXIST) + return ret; + + ret = ovpn_get_netnsid(netns_fd, &netnsid); + if (ret < 0) + return ret; + + if (netnsid < 0) + return -ENOENT; + + return netnsid; +} + +static int ovpn_parse_ifindex(struct nlmsghdr *msg, void *arg) +{ + struct ifinfomsg *ifinfo; + + if (msg->nlmsg_type != RTM_NEWLINK || + msg->nlmsg_len < NLMSG_LENGTH(sizeof(*ifinfo))) + return -EINVAL; + + ifinfo = NLMSG_DATA(msg); + if (ifinfo->ifi_index <= 0) + return -EINVAL; + + *(unsigned int *)arg = ifinfo->ifi_index; + + return 0; +} + struct ovpn_link_req { struct nlmsghdr n; struct ifinfomsg i; char buf[256]; }; +static int ovpn_get_ifindex(int netnsid, const char *ifname, + unsigned int *ifindex) +{ + struct ovpn_link_req req = { 0 }; + int32_t id = netnsid; + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETLINK; + req.i.ifi_family = AF_UNSPEC; + + if (ovpn_addattr(&req.n, sizeof(req), IFLA_TARGET_NETNSID, &id, + sizeof(id)) < 0 || + ovpn_addattr(&req.n, sizeof(req), IFLA_IFNAME, ifname, + strlen(ifname) + 1) < 0) + return -EMSGSIZE; + + return ovpn_rt_send(&req.n, 0, 0, ovpn_parse_ifindex, ifindex); +} + +#define NETNS_RUN_DIR "/var/run/netns" + +static int ovpn_resolve_netns(struct ovpn_ctx *ovpn) +{ + int target_fd, ret; + char path[PATH_MAX]; + int len; + + if (!ovpn->target_netns_name[0] || + strchr(ovpn->target_netns_name, '/')) { + fprintf(stderr, "invalid network namespace name: %s\n", + ovpn->target_netns_name); + return -EINVAL; + } + + len = snprintf(path, sizeof(path), "%s/%s", NETNS_RUN_DIR, + ovpn->target_netns_name); + if (len < 0 || len >= (int)sizeof(path)) + return -ENAMETOOLONG; + + target_fd = open(path, O_RDONLY); + if (target_fd < 0) { + ret = errno; + fprintf(stderr, "cannot open network namespace %s: %s\n", + ovpn->target_netns_name, strerror(ret)); + return -ret; + } + + ovpn->target_netnsid = ovpn_get_or_assign_netnsid(target_fd); + if (ovpn->target_netnsid < 0) { + ret = ovpn->target_netnsid; + fprintf(stderr, "cannot resolve network namespace ID for %s\n", + ovpn->target_netns_name); + goto out; + } + + ret = ovpn_get_ifindex(ovpn->target_netnsid, ovpn->ifname, + &ovpn->ifindex); + if (ret < 0) { + fprintf(stderr, "cannot find interface %s in namespace %s\n", + ovpn->ifname, ovpn->target_netns_name); + } +out: + close(target_fd); + + return ret; +} + static int ovpn_new_iface(struct ovpn_ctx *ovpn) { struct rtattr *linkinfo, *data; @@ -1662,8 +1854,10 @@ static int ovpn_listen_mcast(void) static void usage(const char *cmd) { fprintf(stderr, - "Usage %s [arguments..]\n", + "Usage %s [-n ] [arguments..]\n", cmd); + fprintf(stderr, + "\t-n netns: run peer/key operations on an interface in the named network namespace\n"); fprintf(stderr, "where can be one of the following\n\n"); fprintf(stderr, "* new_iface [mode]: create new ovpn interface\n"); @@ -1966,6 +2160,26 @@ static enum ovpn_cmd ovpn_parse_cmd(const char *cmd) return CMD_INVALID; } +static bool ovpn_cmd_supports_target_netns(enum ovpn_cmd cmd) +{ + switch (cmd) { + case CMD_LISTEN: + case CMD_CONNECT: + case CMD_NEW_PEER: + case CMD_NEW_MULTI_PEER: + case CMD_SET_PEER: + case CMD_DEL_PEER: + case CMD_GET_PEER: + case CMD_NEW_KEY: + case CMD_DEL_KEY: + case CMD_GET_KEY: + case CMD_SWAP_KEYS: + return true; + default: + return false; + } +} + /* Send process to background and waits for signal. * * This helper is called at the end of commands @@ -2021,6 +2235,7 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) } peer_ctx.ifindex = ovpn->ifindex; + peer_ctx.target_netnsid = ovpn->target_netnsid; peer_ctx.sa_family = ovpn->sa_family; peer_ctx.asymm_id = ovpn->asymm_id; @@ -2107,6 +2322,7 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) struct ovpn_ctx peer_ctx = { 0 }; peer_ctx.ifindex = ovpn->ifindex; + peer_ctx.target_netnsid = ovpn->target_netnsid; peer_ctx.socket = ovpn->socket; peer_ctx.sa_family = AF_UNSPEC; peer_ctx.asymm_id = ovpn->asymm_id; @@ -2179,13 +2395,19 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) strscpy(ovpn->ifname, argv[2], IFNAMSIZ - 1); ovpn->ifname[IFNAMSIZ - 1] = '\0'; - /* all commands, except NEW_IFNAME, needs an ifindex */ + /* all commands, except NEW_IFACE, need an ifindex */ if (ovpn->cmd != CMD_NEW_IFACE) { - ovpn->ifindex = if_nametoindex(ovpn->ifname); - if (!ovpn->ifindex) { - fprintf(stderr, "cannot find interface: %s\n", - strerror(errno)); - return -1; + if (ovpn->target_netns_name) { + ret = ovpn_resolve_netns(ovpn); + if (ret < 0) + return ret; + } else { + ovpn->ifindex = if_nametoindex(ovpn->ifname); + if (!ovpn->ifindex) { + fprintf(stderr, "cannot find interface: %s\n", + strerror(errno)); + return -1; + } } } @@ -2432,29 +2654,51 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) int main(int argc, char *argv[]) { struct ovpn_ctx ovpn; + const char *program = argv[0]; int ret; + memset(&ovpn, 0, sizeof(ovpn)); + if (argc > 1 && !strcmp(argv[1], "-n")) { + if (argc < 4) { + fprintf(stderr, + "Error: -n requires a namespace and a command.\n\n"); + usage(program); + return -EINVAL; + } + + ovpn.target_netns_name = argv[2]; + argc -= 2; + argv += 2; + } + if (argc < 2) { - usage(argv[0]); + usage(program); return -1; } - memset(&ovpn, 0, sizeof(ovpn)); + ovpn.target_netnsid = NETNSA_NSID_NOT_ASSIGNED; ovpn.sa_family = AF_UNSPEC; ovpn.cipher = OVPN_CIPHER_ALG_NONE; ovpn.cmd = ovpn_parse_cmd(argv[1]); if (ovpn.cmd == CMD_INVALID) { fprintf(stderr, "Error: unknown command.\n\n"); - usage(argv[0]); + usage(program); return -1; } + if (ovpn.target_netns_name && + !ovpn_cmd_supports_target_netns(ovpn.cmd)) { + fprintf(stderr, "Error: -n is not supported for command %s.\n", + argv[1]); + return -EOPNOTSUPP; + } + ret = ovpn_parse_cmd_args(&ovpn, argc, argv); if (ret < 0) { fprintf(stderr, "Error: invalid arguments.\n\n"); if (ret == -EINVAL) - usage(argv[0]); + usage(program); return ret; } diff --git a/tools/testing/selftests/net/ovpn/test-close-socket.sh b/tools/testing/selftests/net/ovpn/test-close-socket.sh index ec9a51bbf3c9..0e4e33b2973f 100755 --- a/tools/testing/selftests/net/ovpn/test-close-socket.sh +++ b/tools/testing/selftests/net/ovpn/test-close-socket.sh @@ -40,10 +40,10 @@ ovpn_prepare_network() { for p in $(seq 1 ${OVPN_NUM_PEERS}); do peer_ns="ovpn_peer${p}" ovpn_cmd_ok "set peer0 timeout for peer ${p}" \ - ip netns exec ovpn_peer0 ${OVPN_CLI} set_peer tun0 \ + "${OVPN_CLI}" -n ovpn_peer0 set_peer tun0 \ ${p} 60 120 ovpn_cmd_ok "set peer${p} timeout for peer ${p}" \ - ip netns exec "${peer_ns}" ${OVPN_CLI} set_peer \ + "${OVPN_CLI}" -n "${peer_ns}" set_peer \ tun${p} $((p + OVPN_ID_OFFSET)) 60 120 done } diff --git a/tools/testing/selftests/net/ovpn/test-mark.sh b/tools/testing/selftests/net/ovpn/test-mark.sh index 7c1d56e9c525..8a9ca52f86be 100755 --- a/tools/testing/selftests/net/ovpn/test-mark.sh +++ b/tools/testing/selftests/net/ovpn/test-mark.sh @@ -42,7 +42,7 @@ ovpn_mark_prepare_network() { ASYMM "${OVPN_UDP_PEERS_FILE}" "${MARK}" for p in $(seq 1 3); do ovpn_cmd_ok "install server key for peer ${p}" \ - ip netns exec ovpn_peer0 "${OVPN_CLI}" new_key tun0 \ + "${OVPN_CLI}" -n ovpn_peer0 new_key tun0 \ "${p}" 1 0 "${OVPN_ALG}" 0 data64.key done @@ -53,10 +53,10 @@ ovpn_mark_prepare_network() { for p in $(seq 1 3); do peer_ns="ovpn_peer${p}" ovpn_cmd_ok "set peer0 timeout for peer ${p}" \ - ip netns exec ovpn_peer0 "${OVPN_CLI}" set_peer tun0 \ + "${OVPN_CLI}" -n ovpn_peer0 set_peer tun0 \ "${p}" 60 120 ovpn_cmd_ok "set peer${p} timeout for peer ${p}" \ - ip netns exec "${peer_ns}" "${OVPN_CLI}" set_peer \ + "${OVPN_CLI}" -n "${peer_ns}" set_peer \ tun"${p}" $((p + OVPN_ID_OFFSET)) 60 120 done } diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index 9b5610837032..468dd7985c8f 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -48,10 +48,10 @@ ovpn_prepare_network() { for p in $(seq 1 ${OVPN_NUM_PEERS}); do peer_ns="ovpn_peer${p}" ovpn_cmd_ok "set peer0 timeout for peer ${p}" \ - ip netns exec ovpn_peer0 ${OVPN_CLI} set_peer tun0 \ + "${OVPN_CLI}" -n ovpn_peer0 set_peer tun0 \ ${p} 60 120 ovpn_cmd_ok "set peer${p} timeout for peer ${p}" \ - ip netns exec "${peer_ns}" ${OVPN_CLI} set_peer \ + "${OVPN_CLI}" -n "${peer_ns}" set_peer \ tun${p} $((p + OVPN_ID_OFFSET)) 60 120 done } @@ -151,14 +151,14 @@ ovpn_run_key_rollover() { for p in $(seq 1 ${OVPN_NUM_PEERS}); do peer_ns="ovpn_peer${p}" ovpn_cmd_ok "add secondary key on peer0 for peer ${p}" \ - ip netns exec ovpn_peer0 ${OVPN_CLI} new_key tun0 \ + "${OVPN_CLI}" -n ovpn_peer0 new_key tun0 \ ${p} 2 1 ${OVPN_ALG} 0 data64.key ovpn_cmd_ok "add secondary key on peer${p} for peer ${p}" \ - ip netns exec "${peer_ns}" ${OVPN_CLI} new_key tun${p} \ + "${OVPN_CLI}" -n "${peer_ns}" new_key tun${p} \ $((p + OVPN_ID_OFFSET)) 2 1 ${OVPN_ALG} 1 \ data64.key ovpn_cmd_ok "swap keys on peer${p}" \ - ip netns exec "${peer_ns}" ${OVPN_CLI} swap_keys \ + "${OVPN_CLI}" -n "${peer_ns}" swap_keys \ tun${p} $((p + OVPN_ID_OFFSET)) done } @@ -166,22 +166,22 @@ ovpn_run_key_rollover() { ovpn_run_queries() { ovpn_log "Querying all peers:" - ovpn_cmd_ok "query all peers from peer0" \ - ip netns exec ovpn_peer0 ${OVPN_CLI} get_peer tun0 - ovpn_cmd_ok "query all peers from peer1" \ - ip netns exec ovpn_peer1 ${OVPN_CLI} get_peer tun1 + ovpn_cmd_ok "query all peers on peer0" \ + "${OVPN_CLI}" -n ovpn_peer0 get_peer tun0 + ovpn_cmd_ok "query all peers on peer1" \ + "${OVPN_CLI}" -n ovpn_peer1 get_peer tun1 ovpn_log "Querying peer 1:" - ovpn_cmd_ok "query peer 1 from peer0" \ - ip netns exec ovpn_peer0 ${OVPN_CLI} get_peer tun0 1 + ovpn_cmd_ok "query peer 1 on peer0" \ + "${OVPN_CLI}" -n ovpn_peer0 get_peer tun0 1 } ovpn_query_peer_missing() { ovpn_log "Querying non-existent peer 20:" ovpn_cmd_fail "query missing peer 20 on peer0" \ - ip netns exec ovpn_peer0 ${OVPN_CLI} get_peer tun0 20 + "${OVPN_CLI}" -n ovpn_peer0 get_peer tun0 20 } ovpn_run_peer_cleanup() { @@ -191,9 +191,9 @@ ovpn_run_peer_cleanup() { ovpn_log "Deleting peer 1:" ovpn_cmd_ok "delete peer1 on peer0" \ - ip netns exec ovpn_peer0 ${OVPN_CLI} del_peer tun0 1 + "${OVPN_CLI}" -n ovpn_peer0 del_peer tun0 1 ovpn_cmd_ok "delete peer1 on peer1" \ - ip netns exec ovpn_peer1 ${OVPN_CLI} del_peer tun1 \ + "${OVPN_CLI}" -n ovpn_peer1 del_peer tun1 \ $((1 + OVPN_ID_OFFSET)) ovpn_log "Querying keys:" @@ -201,10 +201,10 @@ ovpn_run_peer_cleanup() { for p in $(seq 2 ${OVPN_NUM_PEERS}); do peer_ns="ovpn_peer${p}" ovpn_cmd_ok "query peer${p} key 1" \ - ip netns exec "${peer_ns}" ${OVPN_CLI} get_key tun${p} \ + "${OVPN_CLI}" -n "${peer_ns}" get_key tun${p} \ $((p + OVPN_ID_OFFSET)) 1 ovpn_cmd_ok "query peer${p} key 2" \ - ip netns exec "${peer_ns}" ${OVPN_CLI} get_key tun${p} \ + "${OVPN_CLI}" -n "${peer_ns}" get_key tun${p} \ $((p + OVPN_ID_OFFSET)) 2 done } @@ -217,16 +217,16 @@ ovpn_run_traffic_delete_peer() { ovpn_run_bg ping_pid ip netns exec ovpn_peer2 ping -qf -w 4 5.5.5.1 sleep 2 ovpn_cmd_ok "delete peer0 peer 2" \ - ip netns exec ovpn_peer0 ${OVPN_CLI} del_peer tun0 2 + "${OVPN_CLI}" -n ovpn_peer0 del_peer tun0 2 if [ "${OVPN_PROTO}" == "TCP" ]; then # In TCP mode this command is expected to fail for both peers. ovpn_cmd_mayfail "delete peer2 peer 2 (TCP non-fatal)" \ - ip netns exec ovpn_peer2 ${OVPN_CLI} del_peer tun2 \ + "${OVPN_CLI}" -n ovpn_peer2 del_peer tun2 \ $((2 + OVPN_ID_OFFSET)) else - ovpn_cmd_ok "delete peer2 peer 2" ip netns exec ovpn_peer2 \ - ${OVPN_CLI} del_peer tun2 $((2 + OVPN_ID_OFFSET)) + ovpn_cmd_ok "delete peer2 peer 2" "${OVPN_CLI}" -n ovpn_peer2 \ + del_peer tun2 $((2 + OVPN_ID_OFFSET)) fi wait "${ping_pid}" || true @@ -241,10 +241,10 @@ ovpn_run_key_cleanup() { for p in $(seq 3 ${OVPN_NUM_PEERS}); do peer_ns="ovpn_peer${p}" ovpn_cmd_ok "delete key 1 for peer${p}" \ - ip netns exec "${peer_ns}" ${OVPN_CLI} del_key tun${p} \ + "${OVPN_CLI}" -n "${peer_ns}" del_key tun${p} \ $((p + OVPN_ID_OFFSET)) 1 ovpn_cmd_ok "delete key 2 for peer${p}" \ - ip netns exec "${peer_ns}" ${OVPN_CLI} del_key tun${p} \ + "${OVPN_CLI}" -n "${peer_ns}" del_key tun${p} \ $((p + OVPN_ID_OFFSET)) 2 done } @@ -258,11 +258,11 @@ ovpn_run_timeouts() { for p in $(seq 3 ${OVPN_NUM_PEERS}); do # Non-fatal: this may fail in some protocol modes. ovpn_cmd_mayfail "set peer0 timeout for peer ${p} (non-fatal)" \ - ip netns exec ovpn_peer0 ${OVPN_CLI} set_peer tun0 \ + "${OVPN_CLI}" -n ovpn_peer0 set_peer tun0 \ ${p} 3 3 peer_ns="ovpn_peer${p}" ovpn_cmd_ok "disable timeout on peer${p} while peer0 adjusts \ - state" ip netns exec "${peer_ns}" ${OVPN_CLI} set_peer \ + state" "${OVPN_CLI}" -n "${peer_ns}" set_peer \ tun${p} $((p + OVPN_ID_OFFSET)) 0 0 done # wait for peers to timeout @@ -273,7 +273,7 @@ ovpn_run_timeouts() { for p in $(seq 3 ${OVPN_NUM_PEERS}); do peer_ns="ovpn_peer${p}" ovpn_cmd_ok "set peer${p} P2P timeout" \ - ip netns exec "${peer_ns}" ${OVPN_CLI} set_peer \ + "${OVPN_CLI}" -n "${peer_ns}" set_peer \ tun${p} $((p + OVPN_ID_OFFSET)) 3 3 done sleep 5