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 <shuah@kernel.org>
Cc: linux-kselftest@vger.kernel.org
Cc: horms@kernel.org
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
This commit is contained in:
Ralf Lici 2025-11-19 11:56:52 +01:00 committed by Antonio Quartulli
parent 367f4b163a
commit 7b80d8a335
3 changed files with 119 additions and 1 deletions

View File

@ -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 \

View File

@ -6,6 +6,7 @@
* Author: Antonio Quartulli <antonio@openvpn.net>
*/
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
@ -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 <iface> <lport> <id_type> <peers_file>: add multiple peers as listed in the file\n");
"* new_multi_peer <iface> <lport> <id_type> <peers_file> [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<peer_id> <tx_id> <raddr> <rport> <laddr> <lport> <vpnaddr>\n");
fprintf(stderr, "\tmark: socket FW mark value\n");
fprintf(stderr,
"* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout>: 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)

View File

@ -0,0 +1,96 @@
#!/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
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