selftests: ovpn: validate peer VPN addresses

Exercise peer VPN address validation through both peer creation and
update. Check missing, unspecified, duplicate, multicast, broadcast,
loopback, IPv4-compatible and IPv4-mapped addresses.

Temporarily configure a peer with both address families to verify that
either family can be cleared while the other remains configured, then
restore the original addresses before running the existing traffic
tests.

Extend ovpn-cli peer updates with an optional VPN address and preserve
peer creation errors so the negative tests can observe rejected netlink
requests.

Signed-off-by: Ralf Lici <ralf@mandelbit.com>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
This commit is contained in:
Ralf Lici 2026-08-28 15:00:10 +02:00 committed by Antonio Quartulli
parent 5940f3407b
commit 0062080268
3 changed files with 123 additions and 19 deletions

View File

@ -136,6 +136,19 @@ ovpn_create_ns() {
ip netns add "ovpn_peer${1}"
}
ovpn_peer_vpn_addr() {
local peer="$1"
local file
if [ "${OVPN_PROTO}" == "UDP" ]; then
file="${OVPN_UDP_PEERS_FILE}"
else
file="${OVPN_TCP_PEERS_FILE}"
fi
awk -v peer="${peer}" '$1 == peer {print $NF; exit}' "${file}"
}
ovpn_setup_ns() {
local peer="ovpn_peer${1}"
local server_ns="ovpn_peer0"

View File

@ -650,6 +650,26 @@ static int ovpn_connect(struct ovpn_ctx *ovpn)
return ret;
}
static int ovpn_nl_put_vpn_addr(struct nl_msg *msg,
const struct ovpn_ctx *ovpn)
{
if (!ovpn->peer_ip_set)
return 0;
switch (ovpn->peer_ip.in4.sin_family) {
case AF_INET:
return nla_put_u32(msg, OVPN_A_PEER_VPN_IPV4,
ovpn->peer_ip.in4.sin_addr.s_addr);
case AF_INET6:
return nla_put(msg, OVPN_A_PEER_VPN_IPV6,
sizeof(struct in6_addr),
&ovpn->peer_ip.in6.sin6_addr);
default:
fprintf(stderr, "Invalid family for peer address\n");
return -EAFNOSUPPORT;
}
}
static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp)
{
struct nlattr *attr;
@ -691,22 +711,9 @@ static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp)
}
}
if (ovpn->peer_ip_set) {
switch (ovpn->peer_ip.in4.sin_family) {
case AF_INET:
NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4,
ovpn->peer_ip.in4.sin_addr.s_addr);
break;
case AF_INET6:
NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6,
sizeof(struct in6_addr),
&ovpn->peer_ip.in6.sin6_addr);
break;
default:
fprintf(stderr, "Invalid family for peer address\n");
goto nla_put_failure;
}
}
ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn);
if (ret)
goto nla_put_failure;
nla_nest_end(ctx->nl_msg, attr);
@ -732,6 +739,10 @@ static int ovpn_set_peer(struct ovpn_ctx *ovpn)
ovpn->keepalive_interval);
NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT,
ovpn->keepalive_timeout);
ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn);
if (ret)
goto nla_put_failure;
nla_nest_end(ctx->nl_msg, attr);
ret = ovpn_nl_msg_send(ctx, NULL);
@ -1730,13 +1741,14 @@ static void usage(const char *cmd)
fprintf(stderr, "\tmark: socket FW mark value\n");
fprintf(stderr,
"* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout>: set peer attributes\n");
"* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout> [vpnaddr]: set peer attributes\n");
fprintf(stderr, "\tiface: ovpn interface name\n");
fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n");
fprintf(stderr,
"\tkeepalive_interval: interval for sending ping messages\n");
fprintf(stderr,
"\tkeepalive_timeout: time after which a peer is timed out\n");
fprintf(stderr, "\tvpnaddr: peer VPN IP\n");
fprintf(stderr, "* del_peer <iface> <peer_id>: delete peer\n");
fprintf(stderr, "\tiface: ovpn interface name\n");
@ -2090,6 +2102,8 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn)
return ret;
ret = ovpn_new_peer(ovpn, false);
if (ret < 0)
return ret;
ovpn_waitbg();
break;
case CMD_NEW_MULTI_PEER:
@ -2331,6 +2345,12 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[])
"keepalive interval value out of range\n");
return -1;
}
if (argc > 6) {
ret = ovpn_parse_remote(ovpn, NULL, NULL, argv[6]);
if (ret < 0)
return -1;
}
break;
case CMD_DEL_PEER:
if (argc < 4)

View File

@ -56,6 +56,76 @@ ovpn_prepare_network() {
done
}
ovpn_new_test_peer() {
local peer_id="$1"
shift
ip netns exec ovpn_peer0 "${OVPN_CLI}" new_peer tun0 \
"${peer_id}" none 65000 10.10.1.2 1 "$@"
}
ovpn_set_peer_vpn_addr() {
ip netns exec ovpn_peer0 "${OVPN_CLI}" set_peer tun0 \
"$1" 60 120 "$2"
}
ovpn_run_vpn_addr_validation() {
local addr
local peer1_addr4
local test_peer_id=$((OVPN_NUM_PEERS + 1))
local test_peer_addr6="2001:db8::2"
# Do not include 0.0.0.0 or :: here. They are invalid on creation, but
# clear one address family on update and are valid if the other remains.
local -a invalid_addrs=(
"127.0.0.1"
"224.0.0.1"
"255.255.255.255"
"::1"
"::192.0.2.1"
"::ffff:192.0.2.1"
"ff02::1"
)
peer1_addr4=$(ovpn_peer_vpn_addr 1)
ovpn_cmd_fail "reject peer without VPN address" \
ovpn_new_test_peer "${test_peer_id}"
for addr in "0.0.0.0" "::" "${invalid_addrs[@]}"; do
ovpn_cmd_fail "reject new peer VPN address ${addr}" \
ovpn_new_test_peer "${test_peer_id}" "${addr}"
done
ovpn_cmd_fail "reject duplicate IPv4 address on peer creation" \
ovpn_new_test_peer "${test_peer_id}" "${peer1_addr4}"
ovpn_cmd_fail "reject clearing the last peer VPN address" \
ovpn_set_peer_vpn_addr 1 0.0.0.0
for addr in "${invalid_addrs[@]}"; do
ovpn_cmd_fail "reject updated peer VPN address ${addr}" \
ovpn_set_peer_vpn_addr 1 "${addr}"
done
ovpn_cmd_fail "reject duplicate IPv4 address on peer update" \
ovpn_set_peer_vpn_addr 2 "${peer1_addr4}"
ovpn_cmd_ok "add peer IPv6 address" \
ovpn_set_peer_vpn_addr 1 "${test_peer_addr6}"
ovpn_cmd_fail "reject duplicate IPv6 address on peer creation" \
ovpn_new_test_peer "${test_peer_id}" "${test_peer_addr6}"
ovpn_cmd_fail "reject duplicate IPv6 address on peer update" \
ovpn_set_peer_vpn_addr 2 "${test_peer_addr6}"
ovpn_cmd_ok "clear peer IPv4 address" \
ovpn_set_peer_vpn_addr 1 0.0.0.0
ovpn_cmd_fail "reject clearing the remaining peer IPv6 address" \
ovpn_set_peer_vpn_addr 1 ::
ovpn_cmd_ok "restore peer IPv4 address" \
ovpn_set_peer_vpn_addr 1 "${peer1_addr4}"
ovpn_cmd_ok "clear peer IPv6 address" \
ovpn_set_peer_vpn_addr 1 ::
}
ovpn_run_basic_traffic() {
local p
local header1
@ -293,15 +363,16 @@ trap ovpn_stage_err ERR
ktap_print_header
if [ "${OVPN_FLOAT}" == "1" ]; then
ktap_set_plan 13
ktap_set_plan 14
else
ktap_set_plan 12
ktap_set_plan 13
fi
ovpn_cleanup
modprobe -q ovpn || true
ovpn_run_stage "setup network topology" ovpn_prepare_network
ovpn_run_stage "validate peer VPN addresses" ovpn_run_vpn_addr_validation
ovpn_run_stage "run baseline data traffic" ovpn_run_basic_traffic
ovpn_run_stage "run LAN traffic behind peer1" ovpn_run_lan_traffic
[ "${OVPN_FLOAT}" == "1" ] && ovpn_run_stage "run floating peer checks" \