From: Ralf Lici ralf@mandelbit.com
Add a selftest to verify that when a socket is bound to a device, UDP traffic from ovpn is correctly routed through the specified interface.
The test sets up a P2P session between two peers in separate network namespaces, connected via two veth pairs. It binds to both veth interfaces and uses tcpdump to confirm that traffic flows through the expected paths.
Cc: Shuah Khan shuah@kernel.org Signed-off-by: Ralf Lici ralf@mandelbit.com Signed-off-by: Antonio Quartulli antonio@openvpn.net --- tools/testing/selftests/net/ovpn/Makefile | 1 + tools/testing/selftests/net/ovpn/common.sh | 6 +- tools/testing/selftests/net/ovpn/ovpn-cli.c | 39 +++++-- tools/testing/selftests/net/ovpn/test-bind.sh | 103 ++++++++++++++++++ tools/testing/selftests/net/ovpn/test-mark.sh | 2 +- 5 files changed, 137 insertions(+), 14 deletions(-) create mode 100755 tools/testing/selftests/net/ovpn/test-bind.sh
diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile index 7c87c95d957e..f219d87e2c44 100644 --- a/tools/testing/selftests/net/ovpn/Makefile +++ b/tools/testing/selftests/net/ovpn/Makefile @@ -26,6 +26,7 @@ LDLIBS += $(NL_LDLIBS) TEST_FILES = common.sh
TEST_PROGS := \ + test-bind.sh \ test-chachapoly.sh \ test-close-socket-tcp.sh \ test-close-socket.sh \ diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index d926413c9f16..c802e4e50054 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -66,9 +66,11 @@ setup_listener() { }
add_peer() { + dev=${2:-"any"} + 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 ${dev} 1 ${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 \ @@ -79,7 +81,7 @@ add_peer() { 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} ${TX_ID} ${1} \ + ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} ${dev} ${TX_ID} ${1} \ ${LPORT} ${RADDR} ${RPORT} ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${TX_ID} 1 0 \ ${ALG} 1 data64.key diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index 4df596d29b8c..6d84380c76ad 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -135,6 +135,7 @@ struct ovpn_ctx { int key_id;
uint32_t mark; + const char *bind_dev;
const char *peers_file; }; @@ -542,6 +543,14 @@ static int ovpn_socket(struct ovpn_ctx *ctx, sa_family_t family, int proto) } }
+ if (ctx->bind_dev) { + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ctx->bind_dev, + strlen(ctx->bind_dev) + 1) != 0) { + perror("setsockopt for SO_BINDTODEVICE"); + return -1; + } + } + ret = bind(s, (struct sockaddr *)&local_sock, sock_len); if (ret < 0) { perror("cannot bind socket"); @@ -1693,8 +1702,10 @@ static void usage(const char *cmd) "\tkey_file: file containing the symmetric key for encryption\n");
fprintf(stderr, - "* new_peer <iface> <peer_id> <tx_id> <lport> <raddr> <rport> [vpnaddr]: add new peer\n"); + "* new_peer <iface> <dev> <peer_id> <tx_id> <lport> <raddr> <rport> [vpnaddr]: add new peer\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, + "\tdev: transport interface name to bind to, supports 'any'\n"); fprintf(stderr, "\tlport: local UDP port to bind to\n"); fprintf(stderr, "\tpeer_id: peer ID found in data packets received from this peer\n"); @@ -1705,8 +1716,10 @@ static void usage(const char *cmd) fprintf(stderr, "\tvpnaddr: peer VPN IP\n");
fprintf(stderr, - "* new_multi_peer <iface> <lport> <peers_file> [mark]: add multiple peers as listed in the file\n"); + "* new_multi_peer <iface> <dev> <lport> <peers_file> [mark]: add multiple peers as listed in the file\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, + "\tdev: transport interface name to bind to, supports 'any'\n"); fprintf(stderr, "\tlport: local UDP port to bind to\n"); fprintf(stderr, "\tpeers_file: text file containing one peer per line. Line format:\n"); @@ -2227,37 +2240,41 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) } break; case CMD_NEW_PEER: - if (argc < 7) + if (argc < 8) return -EINVAL;
- ovpn->lport = strtoul(argv[5], NULL, 10); + ovpn->bind_dev = strcmp(argv[3], "any") == 0 ? NULL : argv[3]; + + ovpn->lport = strtoul(argv[6], NULL, 10); if (errno == ERANGE || ovpn->lport > 65535) { fprintf(stderr, "lport value out of range\n"); return -1; }
- const char *vpnip = (argc > 8) ? argv[8] : NULL; + const char *vpnip = (argc > 9) ? argv[9] : NULL;
- ret = ovpn_parse_new_peer(ovpn, argv[3], argv[4], argv[6], argv[7], + ret = ovpn_parse_new_peer(ovpn, argv[4], argv[5], argv[7], argv[8], 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); + ovpn->bind_dev = strcmp(argv[3], "any") == 0 ? NULL : argv[3]; + + ovpn->lport = strtoul(argv[4], NULL, 10); if (errno == ERANGE || ovpn->lport > 65535) { fprintf(stderr, "lport value out of range\n"); return -1; }
- ovpn->peers_file = argv[4]; + ovpn->peers_file = argv[5];
ovpn->mark = 0; - if (argc > 5) { - ovpn->mark = strtoul(argv[5], NULL, 10); + 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; diff --git a/tools/testing/selftests/net/ovpn/test-bind.sh b/tools/testing/selftests/net/ovpn/test-bind.sh new file mode 100755 index 000000000000..fd7c3c8fdf63 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-bind.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020-2025 OpenVPN, Inc. +# +# Author: Ralf Lici ralf@mandelbit.com +# Antonio Quartulli antonio@openvpn.net + +#set -x +set -e + +PROTO=UDP +source ./common.sh + +cleanup + +modprobe -q ovpn || true + +# setup a P2P session between peer1 and peer2 + +ip netns add peer1 +ip netns add peer2 + +ip link add veth1 netns peer1 type veth peer name veth1 netns peer2 +ip link add veth2 netns peer1 type veth peer name veth2 netns peer2 + +ip -n peer1 addr add 10.10.10.1/24 dev veth1 +ip -n peer1 link set veth1 up + +ip -n peer1 addr add 20.20.20.1/24 dev veth2 +ip -n peer1 link set veth2 up + +ip -n peer2 addr add 10.10.10.2/24 dev veth1 +ip -n peer2 link set veth1 up + +ip -n peer2 addr add 20.20.20.2/24 dev veth2 +ip -n peer2 link set veth2 up + +ip netns exec peer1 ${OVPN_CLI} new_iface tun1 P2P +ip netns exec peer2 ${OVPN_CLI} new_iface tun2 P2P + +ip -n peer1 addr add 5.5.5.1 dev tun1 +ip -n peer1 link set tun1 up +ip -n peer2 addr add 5.5.5.2 dev tun2 +ip -n peer2 link set tun2 up + +ip -n peer1 route add 5.5.5.0/24 dev tun1 +ip -n peer2 route add 5.5.5.0/24 dev tun2 + +run_bind_test() { + dev1=${1} + dev2=${2} + raddr4_peer1=${3} + raddr4_peer2=${4} + + touch /tmp/ovpn-bind1.log + touch /tmp/ovpn-bind2.log + + ip netns exec peer1 ${OVPN_CLI} del_peer tun1 1 2>/dev/null || true + ip netns exec peer2 ${OVPN_CLI} del_peer tun2 10 2>/dev/null || true + + # close any active socket + killall $(basename ${OVPN_CLI}) 2>/dev/null || true + + ip netns exec peer1 ${OVPN_CLI} new_peer tun1 ${dev1} 1 10 1 ${raddr4_peer1} 1 + ip netns exec peer1 ${OVPN_CLI} new_key tun1 1 1 0 ${ALG} 0 data64.key + ip netns exec peer2 ${OVPN_CLI} new_peer tun2 ${dev2} 10 1 1 ${raddr4_peer2} 1 + ip netns exec peer2 ${OVPN_CLI} new_key tun2 10 1 0 ${ALG} 1 data64.key + + ip netns exec peer1 ${OVPN_CLI} set_peer tun1 1 60 120 + ip netns exec peer2 ${OVPN_CLI} set_peer tun2 10 60 120 + + timeout 2 ip netns exec peer1 tcpdump -i veth1 "${PROTO,,}" port 1 -n -q > /tmp/ovpn-bind1.log & + tcpdump1_pid=$! + timeout 2 ip netns exec peer1 tcpdump -i veth2 "${PROTO,,}" port 1 -n -q > /tmp/ovpn-bind2.log & + tcpdump2_pid=$! + sleep 0.5 + + ip netns exec peer1 ping -qfc 50 -w 1 5.5.5.2 + + wait ${tcpdump1_pid} || true + wait ${tcpdump2_pid} || true +} + +run_bind_test veth1 any 10.10.10.2 10.10.10.1 +[ "$(grep -c -i udp /tmp/ovpn-bind1.log)" -ge 100 ] +[ "$(grep -c -i udp /tmp/ovpn-bind2.log)" -eq 0 ] + +run_bind_test veth2 any 20.20.20.2 20.20.20.1 +[ "$(grep -c -i udp /tmp/ovpn-bind2.log)" -ge 100 ] +[ "$(grep -c -i udp /tmp/ovpn-bind1.log)" -eq 0 ] + +run_bind_test any veth1 10.10.10.2 10.10.10.1 +[ "$(grep -c -i udp /tmp/ovpn-bind1.log)" -ge 100 ] +[ "$(grep -c -i udp /tmp/ovpn-bind2.log)" -eq 0 ] + +run_bind_test any veth2 20.20.20.2 20.20.20.1 +[ "$(grep -c -i udp /tmp/ovpn-bind2.log)" -ge 100 ] +[ "$(grep -c -i udp /tmp/ovpn-bind1.log)" -eq 0 ] + +cleanup + +modprobe -r ovpn || true + diff --git a/tools/testing/selftests/net/ovpn/test-mark.sh b/tools/testing/selftests/net/ovpn/test-mark.sh index a4bfe938118d..c2600bb22e2c 100755 --- a/tools/testing/selftests/net/ovpn/test-mark.sh +++ b/tools/testing/selftests/net/ovpn/test-mark.sh @@ -26,7 +26,7 @@ for p in $(seq 0 3); do done
# add peer0 with mark -ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE} ${MARK} +ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 any 1 ${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