From a8e136b496259e689942d295d06cc4e517418cc4 Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 11 Nov 2025 22:34:51 +0100 Subject: selftests: ovpn: allow compiling ovpn-cli.c with mbedtls3 mbedtls 3 installs headers and calls the shared object differently than version 2, therefore we must now rely on pkgconfig to fill the right C/LDFLAGS. Moreover the mbedtls3 library expects any base64 file to have their content on one line. Since this change does no break older versions, let's change the sample key file format and make mbedtls3 happy. Cc: Shuah Khan Cc: linux-kselftest@vger.kernel.org Cc: horms@kernel.org Signed-off-by: Antonio Quartulli --- tools/testing/selftests/net/ovpn/Makefile | 16 +++++++++++----- tools/testing/selftests/net/ovpn/data64.key | 6 +----- 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile index dbe0388c8512..e59271a25d76 100644 --- a/tools/testing/selftests/net/ovpn/Makefile +++ b/tools/testing/selftests/net/ovpn/Makefile @@ -2,19 +2,25 @@ # Copyright (C) 2020-2025 OpenVPN, Inc. # CFLAGS = -pedantic -Wextra -Wall -Wl,--no-as-needed -g -O0 -ggdb $(KHDR_INCLUDES) +CFLAGS += $(shell pkg-config --cflags mbedcrypto-3 mbedtls-3 2>/dev/null) + VAR_CFLAGS = $(shell pkg-config --cflags libnl-3.0 libnl-genl-3.0 2>/dev/null) ifeq ($(VAR_CFLAGS),) VAR_CFLAGS = -I/usr/include/libnl3 endif CFLAGS += $(VAR_CFLAGS) +MTLS_LDLIBS= $(shell pkg-config --libs mbedcrypto-3 mbedtls-3 2>/dev/null) +ifeq ($(MTLS_LDLIBS),) +MTLS_LDLIBS = -lmbedtls -lmbedcrypto +endif +LDLIBS += $(MTLS_LDLIBS) -LDLIBS = -lmbedtls -lmbedcrypto -VAR_LDLIBS = $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0 2>/dev/null) -ifeq ($(VAR_LDLIBS),) -VAR_LDLIBS = -lnl-genl-3 -lnl-3 +NL_LDLIBS = $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0 2>/dev/null) +ifeq ($(NL_LDLIBS),) +NL_LDLIBS = -lnl-genl-3 -lnl-3 endif -LDLIBS += $(VAR_LDLIBS) +LDLIBS += $(NL_LDLIBS) TEST_FILES = common.sh diff --git a/tools/testing/selftests/net/ovpn/data64.key b/tools/testing/selftests/net/ovpn/data64.key index a99e88c4e290..d04febcdf5a2 100644 --- a/tools/testing/selftests/net/ovpn/data64.key +++ b/tools/testing/selftests/net/ovpn/data64.key @@ -1,5 +1 @@ -jRqMACN7d7/aFQNT8S7jkrBD8uwrgHbG5OQZP2eu4R1Y7tfpS2bf5RHv06Vi163CGoaIiTX99R3B -ia9ycAH8Wz1+9PWv51dnBLur9jbShlgZ2QHLtUc4a/gfT7zZwULXuuxdLnvR21DDeMBaTbkgbai9 -uvAa7ne1liIgGFzbv+Bas4HDVrygxIxuAnP5Qgc3648IJkZ0QEXPF+O9f0n5+QIvGCxkAUVx+5K6 -KIs+SoeWXnAopELmoGSjUpFtJbagXK82HfdqpuUxT2Tnuef0/14SzVE/vNleBNu2ZbyrSAaah8tE -BofkPJUBFY+YQcfZNM5Dgrw3i+Bpmpq/gpdg5w== +jRqMACN7d7/aFQNT8S7jkrBD8uwrgHbG5OQZP2eu4R1Y7tfpS2bf5RHv06Vi163CGoaIiTX99R3Bia9ycAH8Wz1+9PWv51dnBLur9jbShlgZ2QHLtUc4a/gfT7zZwULXuuxdLnvR21DDeMBaTbkgbai9uvAa7ne1liIgGFzbv+Bas4HDVrygxIxuAnP5Qgc3648IJkZ0QEXPF+O9f0n5+QIvGCxkAUVx+5K6KIs+SoeWXnAopELmoGSjUpFtJbagXK82HfdqpuUxT2Tnuef0/14SzVE/vNleBNu2ZbyrSAaah8tEBofkPJUBFY+YQcfZNM5Dgrw3i+Bpmpq/gpdg5w== -- cgit v1.2.3 From c841b676da98638f5ed8d3f2f449ddd02d9921aa Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 14 Nov 2025 11:39:40 +0100 Subject: ovpn: notify userspace on client float event Send a netlink notification when a client updates its remote UDP endpoint. The notification includes the new IP address, port, and scope ID (for IPv6). Cc: linux-kselftest@vger.kernel.org Cc: horms@kernel.org Cc: shuah@kernel.org Cc: donald.hunter@gmail.com Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca --- Documentation/netlink/specs/ovpn.yaml | 6 +++ drivers/net/ovpn/netlink.c | 82 +++++++++++++++++++++++++++++ drivers/net/ovpn/netlink.h | 2 + drivers/net/ovpn/peer.c | 2 + include/uapi/linux/ovpn.h | 1 + tools/testing/selftests/net/ovpn/ovpn-cli.c | 3 ++ 6 files changed, 96 insertions(+) (limited to 'tools/testing') diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml index 1b91045cee2e..0d0c028bf96f 100644 --- a/Documentation/netlink/specs/ovpn.yaml +++ b/Documentation/netlink/specs/ovpn.yaml @@ -502,6 +502,12 @@ operations: - ifindex - keyconf + - + name: peer-float-ntf + doc: Notification about a peer floating (changing its remote UDP endpoint) + notify: peer-get + mcgrp: peers + mcast-groups: list: - diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index fed0e46b32a3..e10d7f9a28f5 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -1203,6 +1203,88 @@ err_free_msg: return ret; } +/** + * ovpn_nl_peer_float_notify - notify userspace about peer floating + * @peer: the floated peer + * @ss: sockaddr representing the new remote endpoint + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_nl_peer_float_notify(struct ovpn_peer *peer, + const struct sockaddr_storage *ss) +{ + struct ovpn_socket *sock; + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa; + struct sk_buff *msg; + struct nlattr *attr; + int ret = -EMSGSIZE; + void *hdr; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &ovpn_nl_family, 0, + OVPN_CMD_PEER_FLOAT_NTF); + if (!hdr) { + ret = -ENOBUFS; + goto err_free_msg; + } + + if (nla_put_u32(msg, OVPN_A_IFINDEX, peer->ovpn->dev->ifindex)) + goto err_cancel_msg; + + attr = nla_nest_start(msg, OVPN_A_PEER); + if (!attr) + goto err_cancel_msg; + + if (nla_put_u32(msg, OVPN_A_PEER_ID, peer->id)) + goto err_cancel_msg; + + if (ss->ss_family == AF_INET) { + sa = (struct sockaddr_in *)ss; + if (nla_put_in_addr(msg, OVPN_A_PEER_REMOTE_IPV4, + sa->sin_addr.s_addr) || + nla_put_net16(msg, OVPN_A_PEER_REMOTE_PORT, sa->sin_port)) + goto err_cancel_msg; + } else if (ss->ss_family == AF_INET6) { + sa6 = (struct sockaddr_in6 *)ss; + if (nla_put_in6_addr(msg, OVPN_A_PEER_REMOTE_IPV6, + &sa6->sin6_addr) || + nla_put_u32(msg, OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID, + sa6->sin6_scope_id) || + nla_put_net16(msg, OVPN_A_PEER_REMOTE_PORT, sa6->sin6_port)) + goto err_cancel_msg; + } else { + ret = -EAFNOSUPPORT; + goto err_cancel_msg; + } + + nla_nest_end(msg, attr); + genlmsg_end(msg, hdr); + + rcu_read_lock(); + sock = rcu_dereference(peer->sock); + if (!sock) { + ret = -EINVAL; + goto err_unlock; + } + genlmsg_multicast_netns(&ovpn_nl_family, sock_net(sock->sk), msg, + 0, OVPN_NLGRP_PEERS, GFP_ATOMIC); + rcu_read_unlock(); + + return 0; + +err_unlock: + rcu_read_unlock(); +err_cancel_msg: + genlmsg_cancel(msg, hdr); +err_free_msg: + nlmsg_free(msg); + return ret; +} + /** * ovpn_nl_key_swap_notify - notify userspace peer's key must be renewed * @peer: the peer whose key needs to be renewed diff --git a/drivers/net/ovpn/netlink.h b/drivers/net/ovpn/netlink.h index 8615dfc3c472..11ee7c681885 100644 --- a/drivers/net/ovpn/netlink.h +++ b/drivers/net/ovpn/netlink.h @@ -13,6 +13,8 @@ int ovpn_nl_register(void); void ovpn_nl_unregister(void); int ovpn_nl_peer_del_notify(struct ovpn_peer *peer); +int ovpn_nl_peer_float_notify(struct ovpn_peer *peer, + const struct sockaddr_storage *ss); int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id); #endif /* _NET_OVPN_NETLINK_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 3716a1d82801..4e145b4497e6 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -287,6 +287,8 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) spin_unlock_bh(&peer->lock); + ovpn_nl_peer_float_notify(peer, &ss); + /* rehashing is required only in MP mode as P2P has one peer * only and thus there is no hashtable */ diff --git a/include/uapi/linux/ovpn.h b/include/uapi/linux/ovpn.h index 959b41def61f..0cce0d58b830 100644 --- a/include/uapi/linux/ovpn.h +++ b/include/uapi/linux/ovpn.h @@ -100,6 +100,7 @@ enum { OVPN_CMD_KEY_SWAP, OVPN_CMD_KEY_SWAP_NTF, OVPN_CMD_KEY_DEL, + OVPN_CMD_PEER_FLOAT_NTF, __OVPN_CMD_MAX, OVPN_CMD_MAX = (__OVPN_CMD_MAX - 1) diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index 0f3babf19fd0..7178abae1b2f 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -1516,6 +1516,9 @@ static int ovpn_handle_msg(struct nl_msg *msg, void *arg) case OVPN_CMD_PEER_DEL_NTF: fprintf(stdout, "received CMD_PEER_DEL_NTF\n"); break; + case OVPN_CMD_PEER_FLOAT_NTF: + fprintf(stdout, "received CMD_PEER_FLOAT_NTF\n"); + break; case OVPN_CMD_KEY_SWAP_NTF: fprintf(stdout, "received CMD_KEY_SWAP_NTF\n"); break; -- cgit v1.2.3 From 77de28cd7cf172e782319a144bf64e693794d78b Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 13 Jun 2025 17:55:39 +0200 Subject: selftests: ovpn: add notification parsing and matching To verify that netlink notifications are correctly emitted and contain the expected fields, this commit uses the tools/net/ynl/pyynl/cli.py script to create multicast listeners. These listeners record the captured notifications to a JSON file, which is later compared to the expected output. Cc: linux-kselftest@vger.kernel.org Cc: shuah@kernel.org Cc: horms@kernel.org Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- tools/testing/selftests/net/ovpn/Makefile | 9 +++++- tools/testing/selftests/net/ovpn/common.sh | 34 ++++++++++++++++++++-- .../selftests/net/ovpn/json/peer0-float.json | 9 ++++++ tools/testing/selftests/net/ovpn/json/peer0.json | 6 ++++ .../selftests/net/ovpn/json/peer1-float.json | 1 + tools/testing/selftests/net/ovpn/json/peer1.json | 1 + .../selftests/net/ovpn/json/peer2-float.json | 1 + tools/testing/selftests/net/ovpn/json/peer2.json | 1 + .../selftests/net/ovpn/json/peer3-float.json | 1 + tools/testing/selftests/net/ovpn/json/peer3.json | 1 + .../selftests/net/ovpn/json/peer4-float.json | 1 + tools/testing/selftests/net/ovpn/json/peer4.json | 1 + .../selftests/net/ovpn/json/peer5-float.json | 1 + tools/testing/selftests/net/ovpn/json/peer5.json | 1 + .../selftests/net/ovpn/json/peer6-float.json | 1 + tools/testing/selftests/net/ovpn/json/peer6.json | 1 + tools/testing/selftests/net/ovpn/tcp_peers.txt | 1 + tools/testing/selftests/net/ovpn/test.sh | 8 +++++ 18 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 tools/testing/selftests/net/ovpn/json/peer0-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer0.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer1-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer1.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer2-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer2.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer3-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer3.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer4-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer4.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer5-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer5.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer6-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer6.json (limited to 'tools/testing') diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile index e59271a25d76..88891d9f5c56 100644 --- a/tools/testing/selftests/net/ovpn/Makefile +++ b/tools/testing/selftests/net/ovpn/Makefile @@ -23,7 +23,14 @@ endif LDLIBS += $(NL_LDLIBS) -TEST_FILES = common.sh +TEST_FILES = \ + common.sh \ + data64.key \ + json \ + tcp_peers.txt \ + udp_peers.txt \ + ../../../../net/ynl/pyynl/cli.py \ +# end of TEST_FILES TEST_PROGS := \ test-chachapoly.sh \ diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 88869c675d03..df0a541148fe 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -7,12 +7,18 @@ UDP_PEERS_FILE=${UDP_PEERS_FILE:-udp_peers.txt} TCP_PEERS_FILE=${TCP_PEERS_FILE:-tcp_peers.txt} OVPN_CLI=${OVPN_CLI:-./ovpn-cli} +YNL_CLI=${YNL_CLI:-../../../../net/ynl/pyynl/cli.py} ALG=${ALG:-aes} PROTO=${PROTO:-UDP} FLOAT=${FLOAT:-0} +JQ_FILTER='map(select(.msg.peer | has("remote-ipv6") | not)) | + map(del(.msg.ifindex)) | sort_by(.msg.peer.id)[]' LAN_IP="11.11.11.11" +declare -A tmp_jsons=() +declare -A listener_pids=() + create_ns() { ip netns add peer${1} } @@ -48,6 +54,14 @@ setup_ns() { ip -n peer${1} link set tun${1} up } +setup_listener() { + file=$(mktemp) + PYTHONUNBUFFERED=1 ip netns exec peer${p} ${YNL_CLI} --family ovpn \ + --subscribe peers --output-json --duration 40 > ${file} & + listener_pids[$1]=$! + tmp_jsons[$1]="${file}" +} + add_peer() { if [ "${PROTO}" == "UDP" ]; then if [ ${1} -eq 0 ]; then @@ -82,6 +96,24 @@ add_peer() { fi } +compare_ntfs() { + if [ ${#tmp_jsons[@]} -gt 0 ]; then + [ "$FLOAT" == 1 ] && suffix="-float" + expected="json/peer${1}${suffix}.json" + received="${tmp_jsons[$1]}" + + kill -TERM ${listener_pids[$1]} || true + wait ${listener_pids[$1]} || true + printf "Checking notifications for peer ${1}... " + if diff <(jq -s "${JQ_FILTER}" ${expected}) \ + <(jq -s "${JQ_FILTER}" ${received}); then + echo "OK" + fi + + rm -f ${received} || true + fi +} + cleanup() { # some ovpn-cli processes sleep in background so they need manual poking killall $(basename ${OVPN_CLI}) 2>/dev/null || true @@ -104,5 +136,3 @@ if [ "${PROTO}" == "UDP" ]; then else NUM_PEERS=${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')} fi - - diff --git a/tools/testing/selftests/net/ovpn/json/peer0-float.json b/tools/testing/selftests/net/ovpn/json/peer0-float.json new file mode 100644 index 000000000000..682fa58ad4ea --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer0-float.json @@ -0,0 +1,9 @@ +{"name": "peer-float-ntf", "msg": {"ifindex": 0, "peer": {"id": 1, "remote-ipv4": "10.10.1.3", "remote-port": 1}}} +{"name": "peer-float-ntf", "msg": {"ifindex": 0, "peer": {"id": 2, "remote-ipv4": "10.10.2.3", "remote-port": 1}}} +{"name": "peer-float-ntf", "msg": {"ifindex": 0, "peer": {"id": 3, "remote-ipv4": "10.10.3.3", "remote-port": 1}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 1}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 2}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 3}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 4}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 5}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 6}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer0.json b/tools/testing/selftests/net/ovpn/json/peer0.json new file mode 100644 index 000000000000..7c46a33d5ecd --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer0.json @@ -0,0 +1,6 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 1}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 2}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 3}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 4}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 5}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 6}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer1-float.json b/tools/testing/selftests/net/ovpn/json/peer1-float.json new file mode 120000 index 000000000000..d28c328d1452 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer1-float.json @@ -0,0 +1 @@ +peer1.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer1.json b/tools/testing/selftests/net/ovpn/json/peer1.json new file mode 100644 index 000000000000..5da4ea9d51fb --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer1.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 1}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer2-float.json b/tools/testing/selftests/net/ovpn/json/peer2-float.json new file mode 120000 index 000000000000..b9f09980aaa0 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer2-float.json @@ -0,0 +1 @@ +peer2.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer2.json b/tools/testing/selftests/net/ovpn/json/peer2.json new file mode 100644 index 000000000000..8f6db4f8c2ac --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer2.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 2}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer3-float.json b/tools/testing/selftests/net/ovpn/json/peer3-float.json new file mode 120000 index 000000000000..2700b55bcf2e --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer3-float.json @@ -0,0 +1 @@ +peer3.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer3.json b/tools/testing/selftests/net/ovpn/json/peer3.json new file mode 100644 index 000000000000..bdabd6fa2e64 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer3.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 3}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer4-float.json b/tools/testing/selftests/net/ovpn/json/peer4-float.json new file mode 120000 index 000000000000..460f6c14cd60 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer4-float.json @@ -0,0 +1 @@ +peer4.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer4.json b/tools/testing/selftests/net/ovpn/json/peer4.json new file mode 100644 index 000000000000..c3734bb9251b --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer4.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 4}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer5-float.json b/tools/testing/selftests/net/ovpn/json/peer5-float.json new file mode 120000 index 000000000000..0f725c50ce19 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer5-float.json @@ -0,0 +1 @@ +peer5.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer5.json b/tools/testing/selftests/net/ovpn/json/peer5.json new file mode 100644 index 000000000000..46c4a348299d --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer5.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 5}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer6-float.json b/tools/testing/selftests/net/ovpn/json/peer6-float.json new file mode 120000 index 000000000000..4d9ded3e0a84 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer6-float.json @@ -0,0 +1 @@ +peer6.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer6.json b/tools/testing/selftests/net/ovpn/json/peer6.json new file mode 100644 index 000000000000..aa30f2cff625 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer6.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 6}}} diff --git a/tools/testing/selftests/net/ovpn/tcp_peers.txt b/tools/testing/selftests/net/ovpn/tcp_peers.txt index d753eebe8716..b8f3cb33eaa2 100644 --- a/tools/testing/selftests/net/ovpn/tcp_peers.txt +++ b/tools/testing/selftests/net/ovpn/tcp_peers.txt @@ -3,3 +3,4 @@ 3 5.5.5.4 4 5.5.5.5 5 5.5.5.6 +6 5.5.5.7 diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index e8acdc303307..c2904342ec57 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -17,6 +17,10 @@ for p in $(seq 0 ${NUM_PEERS}); do create_ns ${p} done +for p in $(seq 0 ${NUM_PEERS}); do + setup_listener ${p} +done + for p in $(seq 0 ${NUM_PEERS}); do setup_ns ${p} 5.5.5.$((${p} + 1))/24 ${MTU} done @@ -112,6 +116,10 @@ for p in $(seq 3 ${NUM_PEERS}); do done sleep 5 +for p in $(seq 0 ${NUM_PEERS}); do + compare_ntfs ${p} +done + cleanup modprobe -r ovpn || true -- cgit v1.2.3 From 367f4b163a8cff20d0cb06eb265a1bf1c6652bd9 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Wed, 23 Jul 2025 14:35:34 +0200 Subject: selftests: ovpn: check asymmetric peer-id Extend the base test to verify that the correct peer-id is set in data packet headers. This is done by capturing ping packets with tcpdump during the initial exchange and matching the first portion of the header against the expected sequence for every connection. Cc: Shuah Khan Cc: linux-kselftest@vger.kernel.org Cc: horms@kernel.org Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- tools/testing/selftests/net/ovpn/Makefile | 3 + tools/testing/selftests/net/ovpn/common.sh | 69 +++++++++-- .../selftests/net/ovpn/json/peer0-symm-float.json | 1 + .../selftests/net/ovpn/json/peer0-symm.json | 1 + .../selftests/net/ovpn/json/peer1-symm-float.json | 1 + .../selftests/net/ovpn/json/peer1-symm.json | 1 + tools/testing/selftests/net/ovpn/json/peer1.json | 2 +- .../selftests/net/ovpn/json/peer2-symm-float.json | 1 + .../selftests/net/ovpn/json/peer2-symm.json | 1 + tools/testing/selftests/net/ovpn/json/peer2.json | 2 +- .../selftests/net/ovpn/json/peer3-symm-float.json | 1 + .../selftests/net/ovpn/json/peer3-symm.json | 1 + tools/testing/selftests/net/ovpn/json/peer3.json | 2 +- .../selftests/net/ovpn/json/peer4-symm-float.json | 1 + .../selftests/net/ovpn/json/peer4-symm.json | 1 + tools/testing/selftests/net/ovpn/json/peer4.json | 2 +- .../selftests/net/ovpn/json/peer5-symm-float.json | 1 + .../selftests/net/ovpn/json/peer5-symm.json | 1 + tools/testing/selftests/net/ovpn/json/peer5.json | 2 +- .../selftests/net/ovpn/json/peer6-symm-float.json | 1 + .../selftests/net/ovpn/json/peer6-symm.json | 1 + tools/testing/selftests/net/ovpn/json/peer6.json | 2 +- tools/testing/selftests/net/ovpn/ovpn-cli.c | 128 +++++++++++++++------ tools/testing/selftests/net/ovpn/tcp_peers.txt | 12 +- .../selftests/net/ovpn/test-close-socket.sh | 2 +- .../selftests/net/ovpn/test-symmetric-id-float.sh | 11 ++ .../selftests/net/ovpn/test-symmetric-id-tcp.sh | 11 ++ .../selftests/net/ovpn/test-symmetric-id.sh | 10 ++ tools/testing/selftests/net/ovpn/test.sh | 68 ++++++++--- tools/testing/selftests/net/ovpn/udp_peers.txt | 12 +- 30 files changed, 271 insertions(+), 81 deletions(-) create mode 120000 tools/testing/selftests/net/ovpn/json/peer0-symm-float.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer0-symm.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer1-symm-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer1-symm.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer2-symm-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer2-symm.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer3-symm-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer3-symm.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer4-symm-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer4-symm.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer5-symm-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer5-symm.json create mode 120000 tools/testing/selftests/net/ovpn/json/peer6-symm-float.json create mode 100644 tools/testing/selftests/net/ovpn/json/peer6-symm.json create mode 100755 tools/testing/selftests/net/ovpn/test-symmetric-id-float.sh create mode 100755 tools/testing/selftests/net/ovpn/test-symmetric-id-tcp.sh create mode 100755 tools/testing/selftests/net/ovpn/test-symmetric-id.sh (limited to 'tools/testing') diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile index 88891d9f5c56..ce9f79c4f892 100644 --- a/tools/testing/selftests/net/ovpn/Makefile +++ b/tools/testing/selftests/net/ovpn/Makefile @@ -38,6 +38,9 @@ TEST_PROGS := \ test-close-socket.sh \ test-float.sh \ test-large-mtu.sh \ + test-symmetric-id-float.sh \ + test-symmetric-id-tcp.sh \ + test-symmetric-id.sh \ test-tcp.sh \ test.sh \ # end of TEST_PROGS diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index df0a541148fe..4c08f756e63a 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -11,6 +11,9 @@ YNL_CLI=${YNL_CLI:-../../../../net/ynl/pyynl/cli.py} ALG=${ALG:-aes} PROTO=${PROTO:-UDP} FLOAT=${FLOAT:-0} +SYMMETRIC_ID=${SYMMETRIC_ID:-0} + +export ID_OFFSET=$(( 9 * (SYMMETRIC_ID == 0) )) JQ_FILTER='map(select(.msg.peer | has("remote-ipv6") | not)) | map(del(.msg.ifindex)) | sort_by(.msg.peer.id)[]' @@ -54,6 +57,25 @@ setup_ns() { ip -n peer${1} link set tun${1} up } +build_capture_filter() { + # match the first four bytes of the openvpn data payload + if [ "${PROTO}" == "UDP" ]; then + # For UDP, libpcap transport indexing only works for IPv4, so + # use an explicit IPv4 or IPv6 expression based on the peer + # address. The IPv6 branch assumes there are no extension + # headers in the outer packet. + if [[ "${2}" == *:* ]]; then + printf "ip6 and ip6[6] = 17 and ip6[48:4] = %s" "${1}" + else + printf "ip and udp[8:4] = %s" "${1}" + fi + else + # openvpn over TCP prepends a 2-byte packet length ahead of the + # DATA_V2 opcode, so skip it before matching the payload header + printf "ip and tcp[(((tcp[12] & 0xf0) >> 2) + 2):4] = %s" "${1}" + fi +} + setup_listener() { file=$(mktemp) PYTHONUNBUFFERED=1 ip netns exec peer${p} ${YNL_CLI} --family ovpn \ @@ -63,26 +85,39 @@ setup_listener() { } add_peer() { + labels=("ASYMM" "SYMM") + M_ID=${labels[SYMMETRIC_ID]} + if [ "${PROTO}" == "UDP" ]; then if [ ${1} -eq 0 ]; then - ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE} + ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 \ + ${M_ID} ${UDP_PEERS_FILE} for p in $(seq 1 ${NUM_PEERS}); do ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 \ data64.key done else - RADDR=$(awk "NR == ${1} {print \$2}" ${UDP_PEERS_FILE}) - RPORT=$(awk "NR == ${1} {print \$3}" ${UDP_PEERS_FILE}) - LPORT=$(awk "NR == ${1} {print \$5}" ${UDP_PEERS_FILE}) - ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} ${1} ${LPORT} \ - ${RADDR} ${RPORT} - ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${1} 1 0 ${ALG} 1 \ - data64.key + if [ "${SYMMETRIC_ID}" -eq 1 ]; then + PEER_ID=${1} + TX_ID="none" + else + PEER_ID=$(awk "NR == ${1} {print \$2}" \ + ${UDP_PEERS_FILE}) + TX_ID=${1} + fi + RADDR=$(awk "NR == ${1} {print \$3}" ${UDP_PEERS_FILE}) + RPORT=$(awk "NR == ${1} {print \$4}" ${UDP_PEERS_FILE}) + LPORT=$(awk "NR == ${1} {print \$6}" ${UDP_PEERS_FILE}) + ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} \ + ${PEER_ID} ${TX_ID} ${LPORT} ${RADDR} ${RPORT} + ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} \ + ${PEER_ID} 1 0 ${ALG} 1 data64.key fi else if [ ${1} -eq 0 ]; then - (ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${TCP_PEERS_FILE} && { + (ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${M_ID} \ + ${TCP_PEERS_FILE} && { for p in $(seq 1 ${NUM_PEERS}); do ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 \ ${ALG} 0 data64.key @@ -90,15 +125,25 @@ add_peer() { }) & sleep 5 else - ip netns exec peer${1} ${OVPN_CLI} connect tun${1} ${1} 10.10.${1}.1 1 \ - data64.key + if [ "${SYMMETRIC_ID}" -eq 1 ]; then + PEER_ID=${1} + TX_ID="none" + else + PEER_ID=$(awk "NR == ${1} {print \$2}" \ + ${TCP_PEERS_FILE}) + TX_ID=${1} + fi + ip netns exec peer${1} ${OVPN_CLI} connect tun${1} \ + ${PEER_ID} ${TX_ID} 10.10.${1}.1 1 data64.key fi fi } compare_ntfs() { if [ ${#tmp_jsons[@]} -gt 0 ]; then - [ "$FLOAT" == 1 ] && suffix="-float" + suffix="" + [ "${SYMMETRIC_ID}" -eq 1 ] && suffix="${suffix}-symm" + [ "$FLOAT" == 1 ] && suffix="${suffix}-float" expected="json/peer${1}${suffix}.json" received="${tmp_jsons[$1]}" diff --git a/tools/testing/selftests/net/ovpn/json/peer0-symm-float.json b/tools/testing/selftests/net/ovpn/json/peer0-symm-float.json new file mode 120000 index 000000000000..e31a5bd59863 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer0-symm-float.json @@ -0,0 +1 @@ +peer0-float.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer0-symm.json b/tools/testing/selftests/net/ovpn/json/peer0-symm.json new file mode 120000 index 000000000000..57a163048eed --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer0-symm.json @@ -0,0 +1 @@ +peer0.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer1-symm-float.json b/tools/testing/selftests/net/ovpn/json/peer1-symm-float.json new file mode 120000 index 000000000000..b3615dcc523d --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer1-symm-float.json @@ -0,0 +1 @@ +peer1-symm.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer1-symm.json b/tools/testing/selftests/net/ovpn/json/peer1-symm.json new file mode 100644 index 000000000000..5da4ea9d51fb --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer1-symm.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 1}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer1.json b/tools/testing/selftests/net/ovpn/json/peer1.json index 5da4ea9d51fb..1009d26dc14a 100644 --- a/tools/testing/selftests/net/ovpn/json/peer1.json +++ b/tools/testing/selftests/net/ovpn/json/peer1.json @@ -1 +1 @@ -{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 1}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 10}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer2-symm-float.json b/tools/testing/selftests/net/ovpn/json/peer2-symm-float.json new file mode 120000 index 000000000000..28a895cb5170 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer2-symm-float.json @@ -0,0 +1 @@ +peer2-symm.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer2-symm.json b/tools/testing/selftests/net/ovpn/json/peer2-symm.json new file mode 100644 index 000000000000..8f6db4f8c2ac --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer2-symm.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 2}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer2.json b/tools/testing/selftests/net/ovpn/json/peer2.json index 8f6db4f8c2ac..44e9fad2b622 100644 --- a/tools/testing/selftests/net/ovpn/json/peer2.json +++ b/tools/testing/selftests/net/ovpn/json/peer2.json @@ -1 +1 @@ -{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 2}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 11}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer3-symm-float.json b/tools/testing/selftests/net/ovpn/json/peer3-symm-float.json new file mode 120000 index 000000000000..ee8b9719c2fd --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer3-symm-float.json @@ -0,0 +1 @@ +peer3-symm.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer3-symm.json b/tools/testing/selftests/net/ovpn/json/peer3-symm.json new file mode 100644 index 000000000000..bdabd6fa2e64 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer3-symm.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 3}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer3.json b/tools/testing/selftests/net/ovpn/json/peer3.json index bdabd6fa2e64..d4be8ba130ae 100644 --- a/tools/testing/selftests/net/ovpn/json/peer3.json +++ b/tools/testing/selftests/net/ovpn/json/peer3.json @@ -1 +1 @@ -{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 3}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 12}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer4-symm-float.json b/tools/testing/selftests/net/ovpn/json/peer4-symm-float.json new file mode 120000 index 000000000000..7d34ff7305da --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer4-symm-float.json @@ -0,0 +1 @@ +peer4-symm.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer4-symm.json b/tools/testing/selftests/net/ovpn/json/peer4-symm.json new file mode 100644 index 000000000000..c3734bb9251b --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer4-symm.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 4}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer4.json b/tools/testing/selftests/net/ovpn/json/peer4.json index c3734bb9251b..67d27e2d48ac 100644 --- a/tools/testing/selftests/net/ovpn/json/peer4.json +++ b/tools/testing/selftests/net/ovpn/json/peer4.json @@ -1 +1 @@ -{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 4}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 13}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer5-symm-float.json b/tools/testing/selftests/net/ovpn/json/peer5-symm-float.json new file mode 120000 index 000000000000..afc0f5f9f13b --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer5-symm-float.json @@ -0,0 +1 @@ +peer5-symm.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer5-symm.json b/tools/testing/selftests/net/ovpn/json/peer5-symm.json new file mode 100644 index 000000000000..46c4a348299d --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer5-symm.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 5}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer5.json b/tools/testing/selftests/net/ovpn/json/peer5.json index 46c4a348299d..ecd9bd0b2f37 100644 --- a/tools/testing/selftests/net/ovpn/json/peer5.json +++ b/tools/testing/selftests/net/ovpn/json/peer5.json @@ -1 +1 @@ -{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 5}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 14}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer6-symm-float.json b/tools/testing/selftests/net/ovpn/json/peer6-symm-float.json new file mode 120000 index 000000000000..e39203204d8c --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer6-symm-float.json @@ -0,0 +1 @@ +peer6-symm.json \ No newline at end of file diff --git a/tools/testing/selftests/net/ovpn/json/peer6-symm.json b/tools/testing/selftests/net/ovpn/json/peer6-symm.json new file mode 100644 index 000000000000..aa30f2cff625 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/json/peer6-symm.json @@ -0,0 +1 @@ +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 6}}} diff --git a/tools/testing/selftests/net/ovpn/json/peer6.json b/tools/testing/selftests/net/ovpn/json/peer6.json index aa30f2cff625..7fded29c5804 100644 --- a/tools/testing/selftests/net/ovpn/json/peer6.json +++ b/tools/testing/selftests/net/ovpn/json/peer6.json @@ -1 +1 @@ -{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 6}}} +{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 15}}} diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index 7178abae1b2f..085446471397 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -103,7 +103,7 @@ struct ovpn_ctx { sa_family_t sa_family; - unsigned long peer_id; + unsigned long peer_id, tx_id; unsigned long lport; union { @@ -133,6 +133,8 @@ struct ovpn_ctx { enum ovpn_key_slot key_slot; int key_id; + bool asymm_id; + const char *peers_file; }; @@ -649,6 +651,8 @@ static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp) attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER); NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id); + if (ovpn->asymm_id) + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_TX_ID, ovpn->tx_id); NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_SOCKET, ovpn->socket); if (!is_tcp) { @@ -767,6 +771,10 @@ static int ovpn_handle_peer(struct nl_msg *msg, void (*arg)__always_unused) fprintf(stderr, "* Peer %u\n", nla_get_u32(pattrs[OVPN_A_PEER_ID])); + if (pattrs[OVPN_A_PEER_TX_ID]) + fprintf(stderr, "\tTX peer ID %u\n", + nla_get_u32(pattrs[OVPN_A_PEER_TX_ID])); + if (pattrs[OVPN_A_PEER_SOCKET_NETNSID]) fprintf(stderr, "\tsocket NetNS ID: %d\n", nla_get_s32(pattrs[OVPN_A_PEER_SOCKET_NETNSID])); @@ -1657,41 +1665,57 @@ static void usage(const char *cmd) fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, - "* listen [ipv6]: listen for incoming peer TCP connections\n"); + "* listen [ipv6]: listen for incoming peer TCP connections\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tlport: TCP port to listen to\n"); + fprintf(stderr, "\tid_type:\n"); + fprintf(stderr, + "\t\t- SYMM for ignoring the TX peer ID from the peers_file\n"); + fprintf(stderr, + "\t\t- ASYMM for using the TX peer ID from the peers_file\n"); fprintf(stderr, "\tpeers_file: file containing one peer per line: Line format:\n"); - fprintf(stderr, "\t\t \n"); + fprintf(stderr, "\t\t \n"); fprintf(stderr, "\tipv6: whether the socket should listen to the IPv6 wildcard address\n"); fprintf(stderr, - "* connect [key_file]: start connecting peer of TCP-based VPN session\n"); + "* connect [key_file]: start connecting peer of TCP-based VPN session\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); - fprintf(stderr, "\tpeer_id: peer ID of the connecting peer\n"); + fprintf(stderr, + "\tpeer_id: peer ID found in data packets received from this peer\n"); + fprintf(stderr, + "\ttx_id: peer ID to be used when sending to this peer, 'none' for symmetric peer ID\n"); fprintf(stderr, "\traddr: peer IP address to connect to\n"); fprintf(stderr, "\trport: peer TCP port to connect to\n"); fprintf(stderr, "\tkey_file: file containing the symmetric key for encryption\n"); fprintf(stderr, - "* new_peer [vpnaddr]: add new peer\n"); + "* new_peer [vpnaddr]: add new peer\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); - fprintf(stderr, "\tlport: local UDP port to bind to\n"); fprintf(stderr, - "\tpeer_id: peer ID to be used in data packets to/from this peer\n"); + "\tpeer_id: peer ID found in data packets received from this peer\n"); + fprintf(stderr, + "\ttx_id: peer ID to be used when sending to this peer, 'none' for symmetric peer ID\n"); + fprintf(stderr, "\tlport: local UDP port to bind to\n"); fprintf(stderr, "\traddr: peer IP address\n"); fprintf(stderr, "\trport: peer UDP port\n"); fprintf(stderr, "\tvpnaddr: peer VPN IP\n"); fprintf(stderr, - "* new_multi_peer : add multiple peers as listed in the file\n"); + "* new_multi_peer : add multiple peers as listed in the file\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tlport: local UDP port to bind to\n"); + fprintf(stderr, "\tid_type:\n"); + fprintf(stderr, + "\t\t- SYMM for ignoring the TX peer ID from the peers_file\n"); + fprintf(stderr, + "\t\t- ASYMM for using the TX peer ID from the peers_file\n"); fprintf(stderr, "\tpeers_file: text file containing one peer per line. Line format:\n"); - fprintf(stderr, "\t\t \n"); + fprintf(stderr, + "\t\t \n"); fprintf(stderr, "* set_peer : set peer attributes\n"); @@ -1804,15 +1828,23 @@ out: } static int ovpn_parse_new_peer(struct ovpn_ctx *ovpn, const char *peer_id, - const char *raddr, const char *rport, - const char *vpnip) + const char *tx_id, const char *raddr, + const char *rport, const char *vpnip) { ovpn->peer_id = strtoul(peer_id, NULL, 10); if (errno == ERANGE || ovpn->peer_id > PEER_ID_UNDEF) { - fprintf(stderr, "peer ID value out of range\n"); + fprintf(stderr, "rx peer ID value out of range\n"); return -1; } + if (ovpn->asymm_id) { + ovpn->tx_id = strtoul(tx_id, NULL, 10); + if (errno == ERANGE || ovpn->tx_id > PEER_ID_UNDEF) { + fprintf(stderr, "tx peer ID value out of range\n"); + return -1; + } + } + return ovpn_parse_remote(ovpn, raddr, rport, vpnip); } @@ -1939,8 +1971,8 @@ static void ovpn_waitbg(void) static int ovpn_run_cmd(struct ovpn_ctx *ovpn) { - char peer_id[10], vpnip[INET6_ADDRSTRLEN], laddr[128], lport[10]; - char raddr[128], rport[10]; + char peer_id[10], tx_id[10], vpnip[INET6_ADDRSTRLEN], laddr[128]; + char lport[10], raddr[128], rport[10]; int n, ret; FILE *fp; @@ -1967,7 +1999,8 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) int num_peers = 0; - while ((n = fscanf(fp, "%s %s\n", peer_id, vpnip)) == 2) { + while ((n = fscanf(fp, "%s %s %s\n", peer_id, tx_id, + vpnip)) == 3) { struct ovpn_ctx peer_ctx = { 0 }; if (num_peers == MAX_PEERS) { @@ -1977,6 +2010,7 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) peer_ctx.ifindex = ovpn->ifindex; peer_ctx.sa_family = ovpn->sa_family; + peer_ctx.asymm_id = ovpn->asymm_id; peer_ctx.socket = ovpn_accept(ovpn); if (peer_ctx.socket < 0) { @@ -1987,8 +2021,8 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) /* store peer sockets to test TCP I/O */ ovpn->cli_sockets[num_peers] = peer_ctx.socket; - ret = ovpn_parse_new_peer(&peer_ctx, peer_id, NULL, - NULL, vpnip); + ret = ovpn_parse_new_peer(&peer_ctx, peer_id, tx_id, + NULL, NULL, vpnip); if (ret < 0) { fprintf(stderr, "error while parsing line\n"); return -1; @@ -2056,16 +2090,17 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) return -1; } - while ((n = fscanf(fp, "%s %s %s %s %s %s\n", peer_id, laddr, - lport, raddr, rport, vpnip)) == 6) { + while ((n = fscanf(fp, "%s %s %s %s %s %s %s\n", peer_id, tx_id, + laddr, lport, raddr, rport, vpnip)) == 7) { struct ovpn_ctx peer_ctx = { 0 }; peer_ctx.ifindex = ovpn->ifindex; peer_ctx.socket = ovpn->socket; peer_ctx.sa_family = AF_UNSPEC; + peer_ctx.asymm_id = ovpn->asymm_id; - ret = ovpn_parse_new_peer(&peer_ctx, peer_id, raddr, - rport, vpnip); + ret = ovpn_parse_new_peer(&peer_ctx, peer_id, tx_id, + raddr, rport, vpnip); if (ret < 0) { fprintf(stderr, "error while parsing line\n"); return -1; @@ -2161,7 +2196,7 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) case CMD_DEL_IFACE: break; case CMD_LISTEN: - if (argc < 5) + if (argc < 6) return -EINVAL; ovpn->lport = strtoul(argv[3], NULL, 10); @@ -2170,55 +2205,67 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) return -1; } - ovpn->peers_file = argv[4]; + if (strcmp(argv[4], "SYMM") == 0) { + ovpn->asymm_id = false; + } else if (strcmp(argv[4], "ASYMM") == 0) { + ovpn->asymm_id = true; + } else { + fprintf(stderr, "Cannot parse id type: %s\n", argv[4]); + return -1; + } + + ovpn->peers_file = argv[5]; ovpn->sa_family = AF_INET; - if (argc > 5 && !strcmp(argv[5], "ipv6")) + if (argc > 6 && !strcmp(argv[6], "ipv6")) ovpn->sa_family = AF_INET6; break; case CMD_CONNECT: - if (argc < 6) + if (argc < 7) return -EINVAL; ovpn->sa_family = AF_INET; + ovpn->asymm_id = strcmp(argv[4], "none"); ret = ovpn_parse_new_peer(ovpn, argv[3], argv[4], argv[5], - NULL); + argv[6], NULL); if (ret < 0) { fprintf(stderr, "Cannot parse remote peer data\n"); return -1; } - if (argc > 6) { + if (argc > 7) { ovpn->key_slot = OVPN_KEY_SLOT_PRIMARY; ovpn->key_id = 0; ovpn->cipher = OVPN_CIPHER_ALG_AES_GCM; ovpn->key_dir = KEY_DIR_OUT; - ret = ovpn_parse_key(argv[6], ovpn); + ret = ovpn_parse_key(argv[7], ovpn); if (ret) return -1; } break; case CMD_NEW_PEER: - if (argc < 7) + if (argc < 8) return -EINVAL; - ovpn->lport = strtoul(argv[4], NULL, 10); + ovpn->asymm_id = strcmp(argv[4], "none"); + + ovpn->lport = strtoul(argv[5], NULL, 10); if (errno == ERANGE || ovpn->lport > 65535) { fprintf(stderr, "lport value out of range\n"); return -1; } - const char *vpnip = (argc > 7) ? argv[7] : NULL; + const char *vpnip = (argc > 8) ? argv[8] : NULL; - ret = ovpn_parse_new_peer(ovpn, argv[3], argv[5], argv[6], - vpnip); + ret = ovpn_parse_new_peer(ovpn, argv[3], argv[4], argv[6], + argv[7], vpnip); if (ret < 0) return -1; break; case CMD_NEW_MULTI_PEER: - if (argc < 5) + if (argc < 6) return -EINVAL; ovpn->lport = strtoul(argv[3], NULL, 10); @@ -2227,7 +2274,16 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) return -1; } - ovpn->peers_file = argv[4]; + if (!strcmp(argv[4], "SYMM")) { + ovpn->asymm_id = false; + } else if (!strcmp(argv[4], "ASYMM")) { + ovpn->asymm_id = true; + } else { + fprintf(stderr, "Cannot parse id type: %s\n", argv[4]); + return -1; + } + + ovpn->peers_file = argv[5]; break; case CMD_SET_PEER: if (argc < 6) diff --git a/tools/testing/selftests/net/ovpn/tcp_peers.txt b/tools/testing/selftests/net/ovpn/tcp_peers.txt index b8f3cb33eaa2..3cb67b560705 100644 --- a/tools/testing/selftests/net/ovpn/tcp_peers.txt +++ b/tools/testing/selftests/net/ovpn/tcp_peers.txt @@ -1,6 +1,6 @@ -1 5.5.5.2 -2 5.5.5.3 -3 5.5.5.4 -4 5.5.5.5 -5 5.5.5.6 -6 5.5.5.7 +1 10 5.5.5.2 +2 11 5.5.5.3 +3 12 5.5.5.4 +4 13 5.5.5.5 +5 14 5.5.5.6 +6 15 5.5.5.7 diff --git a/tools/testing/selftests/net/ovpn/test-close-socket.sh b/tools/testing/selftests/net/ovpn/test-close-socket.sh index 5e48a8b67928..0d09df14fe8e 100755 --- a/tools/testing/selftests/net/ovpn/test-close-socket.sh +++ b/tools/testing/selftests/net/ovpn/test-close-socket.sh @@ -27,7 +27,7 @@ done for p in $(seq 1 ${NUM_PEERS}); do ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120 - ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120 + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} $((${p}+9)) 60 120 done sleep 1 diff --git a/tools/testing/selftests/net/ovpn/test-symmetric-id-float.sh b/tools/testing/selftests/net/ovpn/test-symmetric-id-float.sh new file mode 100755 index 000000000000..b3711a81b463 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-symmetric-id-float.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2025 OpenVPN, Inc. +# +# Author: Ralf Lici +# Antonio Quartulli + +SYMMETRIC_ID="1" +FLOAT="1" + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test-symmetric-id-tcp.sh b/tools/testing/selftests/net/ovpn/test-symmetric-id-tcp.sh new file mode 100755 index 000000000000..188cafb67b2f --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-symmetric-id-tcp.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2025 OpenVPN, Inc. +# +# Author: Ralf Lici +# Antonio Quartulli + +PROTO="TCP" +SYMMETRIC_ID=1 + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test-symmetric-id.sh b/tools/testing/selftests/net/ovpn/test-symmetric-id.sh new file mode 100755 index 000000000000..35b119c72e4f --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-symmetric-id.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2025 OpenVPN, Inc. +# +# Author: Ralf Lici +# Antonio Quartulli + +SYMMETRIC_ID="1" + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index c2904342ec57..b60e94a4094e 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -31,14 +31,45 @@ done for p in $(seq 1 ${NUM_PEERS}); do ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120 - ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120 + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} \ + $((${p}+ID_OFFSET)) 60 120 done sleep 1 +TCPDUMP_TIMEOUT="1.5s" for p in $(seq 1 ${NUM_PEERS}); do + # The first part of the data packet header consists of: + # - TCP only: 2 bytes for the packet length + # - 5 bits for opcode ("9" for DATA_V2) + # - 3 bits for key-id ("0" at this point) + # - 12 bytes for peer-id: + # - with asymmetric ID: "${p}" one way and "${p} + 9" the other way + # - with symmetric ID: "${p}" both ways + HEADER1=$(printf "0x4800000%x" ${p}) + HEADER2=$(printf "0x4800000%x" $((${p} + ID_OFFSET))) + RADDR="" + if [ "${PROTO}" == "UDP" ]; then + RADDR=$(awk "NR == ${p} {print \$3}" ${UDP_PEERS_FILE}) + fi + + timeout ${TCPDUMP_TIMEOUT} ip netns exec peer${p} \ + tcpdump --immediate-mode -p -ni veth${p} -c 1 \ + "$(build_capture_filter "${HEADER1}" "${RADDR}")" \ + >/dev/null 2>&1 & + TCPDUMP_PID1=$! + timeout ${TCPDUMP_TIMEOUT} ip netns exec peer${p} \ + tcpdump --immediate-mode -p -ni veth${p} -c 1 \ + "$(build_capture_filter "${HEADER2}" "${RADDR}")" \ + >/dev/null 2>&1 & + TCPDUMP_PID2=$! + + sleep 0.3 ip netns exec peer0 ping -qfc 500 -w 3 5.5.5.$((${p} + 1)) ip netns exec peer0 ping -qfc 500 -s 3000 -w 3 5.5.5.$((${p} + 1)) + + wait ${TCPDUMP_PID1} + wait ${TCPDUMP_PID2} done # ping LAN behind client 1 @@ -61,9 +92,12 @@ ip netns exec peer1 iperf3 -Z -t 3 -c 5.5.5.1 echo "Adding secondary key and then swap:" for p in $(seq 1 ${NUM_PEERS}); do - ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 2 1 ${ALG} 0 data64.key - ip netns exec peer${p} ${OVPN_CLI} new_key tun${p} ${p} 2 1 ${ALG} 1 data64.key - ip netns exec peer${p} ${OVPN_CLI} swap_keys tun${p} ${p} + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 2 1 ${ALG} 0 \ + data64.key + ip netns exec peer${p} ${OVPN_CLI} new_key tun${p} \ + $((${p} + ID_OFFSET)) 2 1 ${ALG} 1 data64.key + ip netns exec peer${p} ${OVPN_CLI} swap_keys tun${p} \ + $((${p} + ID_OFFSET)) done sleep 1 @@ -75,17 +109,19 @@ ip netns exec peer1 ${OVPN_CLI} get_peer tun1 echo "Querying peer 1:" ip netns exec peer0 ${OVPN_CLI} get_peer tun0 1 -echo "Querying non-existent peer 10:" -ip netns exec peer0 ${OVPN_CLI} get_peer tun0 10 || true +echo "Querying non-existent peer 20:" +ip netns exec peer0 ${OVPN_CLI} get_peer tun0 20 || true echo "Deleting peer 1:" ip netns exec peer0 ${OVPN_CLI} del_peer tun0 1 -ip netns exec peer1 ${OVPN_CLI} del_peer tun1 1 +ip netns exec peer1 ${OVPN_CLI} del_peer tun1 $((1 + ID_OFFSET)) echo "Querying keys:" for p in $(seq 2 ${NUM_PEERS}); do - ip netns exec peer${p} ${OVPN_CLI} get_key tun${p} ${p} 1 - ip netns exec peer${p} ${OVPN_CLI} get_key tun${p} ${p} 2 + ip netns exec peer${p} ${OVPN_CLI} get_key tun${p} \ + $((${p} + ID_OFFSET)) 1 + ip netns exec peer${p} ${OVPN_CLI} get_key tun${p} \ + $((${p} + ID_OFFSET)) 2 done echo "Deleting peer while sending traffic:" @@ -94,25 +130,29 @@ sleep 2 ip netns exec peer0 ${OVPN_CLI} del_peer tun0 2 # following command fails in TCP mode # (both ends get conn reset when one peer disconnects) -ip netns exec peer2 ${OVPN_CLI} del_peer tun2 2 || true +ip netns exec peer2 ${OVPN_CLI} del_peer tun2 $((2 + ID_OFFSET)) || true echo "Deleting keys:" for p in $(seq 3 ${NUM_PEERS}); do - ip netns exec peer${p} ${OVPN_CLI} del_key tun${p} ${p} 1 - ip netns exec peer${p} ${OVPN_CLI} del_key tun${p} ${p} 2 + ip netns exec peer${p} ${OVPN_CLI} del_key tun${p} \ + $((${p} + ID_OFFSET)) 1 + ip netns exec peer${p} ${OVPN_CLI} del_key tun${p} \ + $((${p} + ID_OFFSET)) 2 done echo "Setting timeout to 3s MP:" for p in $(seq 3 ${NUM_PEERS}); do ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 3 3 || true - ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 0 0 + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} \ + $((${p} + ID_OFFSET)) 0 0 done # wait for peers to timeout sleep 5 echo "Setting timeout to 3s P2P:" for p in $(seq 3 ${NUM_PEERS}); do - ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 3 3 + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} \ + $((${p} + ID_OFFSET)) 3 3 done sleep 5 diff --git a/tools/testing/selftests/net/ovpn/udp_peers.txt b/tools/testing/selftests/net/ovpn/udp_peers.txt index e9773ddf875c..93de6465353c 100644 --- a/tools/testing/selftests/net/ovpn/udp_peers.txt +++ b/tools/testing/selftests/net/ovpn/udp_peers.txt @@ -1,6 +1,6 @@ -1 10.10.1.1 1 10.10.1.2 1 5.5.5.2 -2 10.10.2.1 1 10.10.2.2 1 5.5.5.3 -3 10.10.3.1 1 10.10.3.2 1 5.5.5.4 -4 fd00:0:0:4::1 1 fd00:0:0:4::2 1 5.5.5.5 -5 fd00:0:0:5::1 1 fd00:0:0:5::2 1 5.5.5.6 -6 fd00:0:0:6::1 1 fd00:0:0:6::2 1 5.5.5.7 +1 10 10.10.1.1 1 10.10.1.2 1 5.5.5.2 +2 11 10.10.2.1 1 10.10.2.2 1 5.5.5.3 +3 12 10.10.3.1 1 10.10.3.2 1 5.5.5.4 +4 13 fd00:0:0:4::1 1 fd00:0:0:4::2 1 5.5.5.5 +5 14 fd00:0:0:5::1 1 fd00:0:0:5::2 1 5.5.5.6 +6 15 fd00:0:0:6::1 1 fd00:0:0:6::2 1 5.5.5.7 -- cgit v1.2.3 From 7b80d8a33500bd8ae5081c053f0447b502581d79 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Wed, 19 Nov 2025 11:56:52 +0100 Subject: selftests: ovpn: add test for the FW mark feature Add a selftest to verify that the FW mark socket option is correctly supported and its value propagated by ovpn. The test adds and removes nftables DROP rules based on the mark value, and checks that the rule counter aligns with the number of lost ping packets. Cc: Shuah Khan Cc: linux-kselftest@vger.kernel.org Cc: horms@kernel.org Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- tools/testing/selftests/net/ovpn/Makefile | 1 + tools/testing/selftests/net/ovpn/ovpn-cli.c | 23 ++++++- tools/testing/selftests/net/ovpn/test-mark.sh | 96 +++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100755 tools/testing/selftests/net/ovpn/test-mark.sh (limited to 'tools/testing') diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile index ce9f79c4f892..169f0464ac3a 100644 --- a/tools/testing/selftests/net/ovpn/Makefile +++ b/tools/testing/selftests/net/ovpn/Makefile @@ -38,6 +38,7 @@ TEST_PROGS := \ test-close-socket.sh \ test-float.sh \ test-large-mtu.sh \ + test-mark.sh \ test-symmetric-id-float.sh \ test-symmetric-id-tcp.sh \ test-symmetric-id.sh \ diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index 085446471397..d40953375c86 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 @@ -133,6 +134,7 @@ struct ovpn_ctx { enum ovpn_key_slot key_slot; int key_id; + uint32_t mark; bool asymm_id; const char *peers_file; @@ -523,6 +525,15 @@ static int ovpn_socket(struct ovpn_ctx *ctx, sa_family_t family, int proto) return ret; } + if (ctx->mark != 0) { + ret = setsockopt(s, SOL_SOCKET, SO_MARK, (void *)&ctx->mark, + sizeof(ctx->mark)); + if (ret < 0) { + perror("setsockopt for SO_MARK"); + return ret; + } + } + if (family == AF_INET6) { opt = 0; if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &opt, @@ -1704,7 +1715,7 @@ static void usage(const char *cmd) fprintf(stderr, "\tvpnaddr: peer VPN IP\n"); fprintf(stderr, - "* new_multi_peer : add multiple peers as listed in the file\n"); + "* new_multi_peer [mark]: add multiple peers as listed in the file\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tlport: local UDP port to bind to\n"); fprintf(stderr, "\tid_type:\n"); @@ -1716,6 +1727,7 @@ static void usage(const char *cmd) "\tpeers_file: text file containing one peer per line. Line format:\n"); fprintf(stderr, "\t\t \n"); + fprintf(stderr, "\tmark: socket FW mark value\n"); fprintf(stderr, "* set_peer : set peer attributes\n"); @@ -2284,6 +2296,15 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) } ovpn->peers_file = argv[5]; + + ovpn->mark = 0; + if (argc > 6) { + ovpn->mark = strtoul(argv[6], NULL, 10); + if (errno == ERANGE || ovpn->mark > UINT32_MAX) { + fprintf(stderr, "mark value out of range\n"); + return -1; + } + } break; case CMD_SET_PEER: if (argc < 6) diff --git a/tools/testing/selftests/net/ovpn/test-mark.sh b/tools/testing/selftests/net/ovpn/test-mark.sh new file mode 100755 index 000000000000..8534428ed3eb --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-mark.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020-2025 OpenVPN, Inc. +# +# Author: Ralf Lici +# Antonio Quartulli + +#set -x +set -e + +MARK=1056 + +source ./common.sh + +cleanup + +modprobe -q ovpn || true + +for p in $(seq 0 "${NUM_PEERS}"); do + create_ns "${p}" +done + +for p in $(seq 0 3); do + setup_ns "${p}" 5.5.5.$((p + 1))/24 +done + +# add peer0 with mark +ip netns exec peer0 "${OVPN_CLI}" new_multi_peer tun0 1 ASYMM \ + "${UDP_PEERS_FILE}" \ + ${MARK} +for p in $(seq 1 3); do + ip netns exec peer0 "${OVPN_CLI}" new_key tun0 "${p}" 1 0 "${ALG}" 0 \ + data64.key +done + +for p in $(seq 1 3); do + add_peer "${p}" +done + +for p in $(seq 1 3); do + ip netns exec peer0 "${OVPN_CLI}" set_peer tun0 "${p}" 60 120 + ip netns exec peer"${p}" "${OVPN_CLI}" set_peer tun"${p}" \ + $((p + 9)) 60 120 +done + +sleep 1 + +for p in $(seq 1 3); do + ip netns exec peer0 ping -qfc 500 -w 3 5.5.5.$((p + 1)) +done + +echo "Adding an nftables drop rule based on mark value ${MARK}" +ip netns exec peer0 nft flush ruleset +ip netns exec peer0 nft 'add table inet filter' +ip netns exec peer0 nft 'add chain inet filter output { + type filter hook output priority 0; + policy accept; +}' +ip netns exec peer0 nft add rule inet filter output \ + meta mark == ${MARK} \ + counter drop + +DROP_COUNTER=$(ip netns exec peer0 nft list chain inet filter output \ + | sed -n 's/.*packets \([0-9]*\).*/\1/p') +sleep 1 + +# ping should fail +for p in $(seq 1 3); do + PING_OUTPUT=$(ip netns exec peer0 ping \ + -qfc 500 -w 1 5.5.5.$((p + 1)) 2>&1) && exit 1 + echo "${PING_OUTPUT}" + LOST_PACKETS=$(echo "$PING_OUTPUT" \ + | awk '/packets transmitted/ { print $1 }') + # increment the drop counter by the amount of lost packets + DROP_COUNTER=$((DROP_COUNTER + LOST_PACKETS)) +done + +# check if the final nft counter matches our counter +TOTAL_COUNT=$(ip netns exec peer0 nft list chain inet filter output \ + | sed -n 's/.*packets \([0-9]*\).*/\1/p') +if [ "${DROP_COUNTER}" -ne "${TOTAL_COUNT}" ]; then + echo "Expected ${TOTAL_COUNT} drops, got ${DROP_COUNTER}" + exit 1 +fi + +echo "Removing the drop rule" +ip netns exec peer0 nft flush ruleset +sleep 1 + +for p in $(seq 1 3); do + ip netns exec peer0 ping -qfc 500 -w 3 5.5.5.$((p + 1)) +done + +cleanup + +modprobe -r ovpn || true -- cgit v1.2.3