From patchwork Wed Jul 1 15:43:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5059 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417676ejc; Wed, 1 Jul 2026 08:44:25 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ8Ej03tv7fy/Vku58mDSJIMZHQ5a3RuXYVbTObAM5mOG4qPfA1qeHlKsYC1jxKTMHp2ojO0NekSsCY=@openvpn.net X-Received: by 2002:a05:6808:30a2:b0:495:dbe8:647f with SMTP id 5614622812f47-4960f017da9mr1547223b6e.28.1782920665290; Wed, 01 Jul 2026 08:44:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920665; cv=none; d=google.com; s=arc-20260327; b=DNi8hQ4CE4MLgAIVKpwpVdZqLXdI/lQCug6DRb/ooZON4KlxVE+x1cjK1+ji5dgXy7 Hrzw0jI62pXsvcmWMtzK2gyEVqzZ57m1MFUmYmkEGDFOM2c625bSdlCu6P1qm8JUNsMf PIK2YyPtGZqa1HyzDHOajNGf42j+AB9TxYyRAydvbs1yw1a2+ghHLU4VvY5L4DpFOPOU tzGpjh62/5WZ+8oIod+sa6hvSzGQCIDL0abCt2VqgPAcc2lyiF/xROglRqrM5tNBhT5+ cG0tV2ig+METnqOUKe7ZtVaeUsOwZMqxdg+C8Y+g+crVBqSx7LMX7ud+7y1TIBfDN9r4 GA5Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding: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=n+Y1NaYpwBLOvpNDE91d3srZaGz4MTnk3NRSYdBjKF4=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=RwhO7uUazGQMee+YoQIPeN/FPpLX/+xlE3XlHU8d8zLf8b69L8GCefBwtuwIsWN3/0 GrCYaqXwRXncnt6fz2TcxBlT1/GTejZrfOhpZxkDqddJoMw6NKVoCmZeCYqOo7f171ug 8c1xI0kXWvWxxMIkGdcia3r9u37m7bSze6RdI1nJWEC4cJT8mvUIqzHHyq9i55TksnDP qL8L1aWnw8lOeU8AepWT06vZrGqCW/lNJvOEpHNPlQyymDJanKkrWqGnVcrMPqA7l4YL MXZqtVtDYP4sKkyILm3KrOkrklzj9ksIL1XSzImbvfJZzdYLrPiEDLBeVtVMgmQrk80v 3xaA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b="hw/xhHJI"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="V073L/5V"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=TItlvd3e; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="f/WM1x5H"; 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-496069e99d3si1772994b6e.29.2026.07.01.08.44.24 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:25 -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="hw/xhHJI"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="V073L/5V"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=TItlvd3e; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="f/WM1x5H"; 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: 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:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=n+Y1NaYpwBLOvpNDE91d3srZaGz4MTnk3NRSYdBjKF4=; b=hw/xhHJI62D0vBxUCM0pH9HMEK BONlONwmM4Cri7Lt9ZPgUIUqAczh71NJa8i4jXT19XcRrTs4NUn4HT7dWzfXlhbSDl6VZ6v/tHCjQ wZJJGvJCLACH0qOqHRATq8P2pO5kdm3NREggM0LnQ6ox7k/BcbAuB9IFvY1v2+nbmQAU=; Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wex6s-0007ik-J4; Wed, 01 Jul 2026 15:44:22 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wex6p-0007iO-Rn for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:19 +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=1xgMd0lCWAnlzoh4FGvPJA3/hB9p/9I2EyfjuCFl4hs=; b=V073L/5VZJ923Z8HkVTM9N028g a9PeieHtTPFXx+pyCRI0S6Y8SGBcrUDmbOlG2LBiAPKM/YX9UOwV2QbOlkl6AtevHXwBkT0KyjVJb MweVrHyOiWLXhb8c/JSgoR8wa8eHpbf2rbRrWKcX6ZvFjHDirjPwpUR4bc94vvyFimZw=; 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=1xgMd0lCWAnlzoh4FGvPJA3/hB9p/9I2EyfjuCFl4hs=; b=TItlvd3ekmubNQYNOcQOwMhvQQ OhW1mj9U2p2+6VF9z4QR2wkCma7q7X8K6+04Do9vymkGF3TovvREUzQlvzVBVuycw0bUU74L3xnRX 6d23y/qcj+D0A2O0gto3QBxqjkzaf12vMRlyiDuG4PGP/RXxPfErd+ljaJIiKNqHFiUs=; Received: from mout-b-106.mailbox.org ([195.10.208.46]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wex6l-0002AE-GE for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:19 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [10.196.197.202]) (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-106.mailbox.org (Postfix) with ESMTPS id 4gr47X48YgzDsWl; Wed, 1 Jul 2026 17:44:08 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920648; 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=1xgMd0lCWAnlzoh4FGvPJA3/hB9p/9I2EyfjuCFl4hs=; b=f/WM1x5HiOFAVAs6hewp/9xn2VhYZVIaGH9MHh3an7liTJP6CYRwDcE3wf1GRnkMtQWSS5 7W/dTvF4t7fiyT7hM4EJrObVFIkUnO0rR3Hmf8s0+RJ9Hf8VPDh2gRMKqUkPvu7A/LV8BS +a8D4j7qUDv+dcynsE07KWoMBHnHyTNbCg7DE0gj9Ylzx71+iyE7imJDoBiV+ncPKwGGfg eWm2eW4UlCqvbX822CaVS9vM8fWDOxdq3W1DxHX83r0JE3nVS7hwxqO1G3NonIyWKwukSX 8n3+Tpzn03pnS0ut6LhPbmFu9ARVVNc/1cPn8ZGRvXI/GKYUCivcjI0di+ajfQ== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:29 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 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's key installation command with an explicit key type so that the tests can install either direct keys or epoch keys. Use the same selector for the TCP connect helper when it auto-insta [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [195.10.208.46 listed in wl.mailspike.net] X-Headers-End: 1wex6l-0002AE-GE Subject: [Openvpn-devel] [RFC ovpn net-next 14/14] selftests: ovpn: exercise epoch keys 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869527819642416378 X-GMAIL-MSGID: 1869527819642416378 Extend ovpn-cli's key installation command with an explicit key type so that the tests can install either direct keys or epoch keys. Use the same selector for the TCP connect helper when it auto-installs a key. Add epoch wrappers around the existing UDP and TCP data-path tests. The packet capture filter also checks epoch 1 in DATA_V2 packet IDs, so the tests verify netlink key installation and the epoch packet layout without adding a separate topology. This intentionally does not cover epoch rotation or future-key refill: the selftests use normal production limits, so the short traffic run does not consume enough packet-ID or AEAD budget to advance epochs. Signed-off-by: Ralf Lici --- tools/testing/selftests/net/ovpn/Makefile | 2 + tools/testing/selftests/net/ovpn/common.sh | 30 +++-- tools/testing/selftests/net/ovpn/ovpn-cli.c | 116 +++++++++++++++--- .../selftests/net/ovpn/test-epoch-tcp.sh | 11 ++ .../testing/selftests/net/ovpn/test-epoch.sh | 10 ++ tools/testing/selftests/net/ovpn/test-mark.sh | 3 +- tools/testing/selftests/net/ovpn/test.sh | 12 +- 7 files changed, 154 insertions(+), 30 deletions(-) create mode 100755 tools/testing/selftests/net/ovpn/test-epoch-tcp.sh create mode 100755 tools/testing/selftests/net/ovpn/test-epoch.sh diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile index 169f0464ac3a..515aeac945a4 100644 --- a/tools/testing/selftests/net/ovpn/Makefile +++ b/tools/testing/selftests/net/ovpn/Makefile @@ -36,6 +36,8 @@ TEST_PROGS := \ test-chachapoly.sh \ test-close-socket-tcp.sh \ test-close-socket.sh \ + test-epoch-tcp.sh \ + test-epoch.sh \ test-float.sh \ test-large-mtu.sh \ test-mark.sh \ diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 2d844eb3aa6e..de5ff9a9e97a 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -12,6 +12,7 @@ OVPN_TCP_PEERS_FILE=${OVPN_TCP_PEERS_FILE:-tcp_peers.txt} OVPN_CLI=${OVPN_CLI:-${OVPN_COMMON_DIR}/ovpn-cli} OVPN_YNL=${OVPN_YNL:-${OVPN_COMMON_DIR}/../../../../net/ynl/pyynl/cli.py} OVPN_ALG=${OVPN_ALG:-aes} +OVPN_KEY_TYPE=${OVPN_KEY_TYPE:-direct} OVPN_PROTO=${OVPN_PROTO:-UDP} OVPN_FLOAT=${OVPN_FLOAT:-0} OVPN_SYMMETRIC_ID=${OVPN_SYMMETRIC_ID:-0} @@ -184,14 +185,26 @@ ovpn_build_capture_filter() { # 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}" + printf "ip6 and ip6[6] = 17 and ip6[48:4] = %s" \ + "${1}" + if [ "${OVPN_KEY_TYPE}" == "epoch" ]; then + printf " and ip6[52:2] = 0x0001" + fi else printf "ip and udp[8:4] = %s" "${1}" + if [ "${OVPN_KEY_TYPE}" == "epoch" ]; then + printf " and udp[12:2] = 0x0001" + fi 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}" + printf "ip and tcp[(((tcp[12] & 0xf0) >> 2) + 2):4] = %s" \ + "${1}" + if [ "${OVPN_KEY_TYPE}" == "epoch" ]; then + printf " and tcp[(((tcp[12] & 0xf0) >> 2) + 6):2] = " + printf "0x0001" + fi fi } @@ -222,8 +235,8 @@ ovpn_add_peer() { for p in $(seq 1 ${OVPN_NUM_PEERS}); do ip netns exec "${server_ns}" ${OVPN_CLI} \ - new_key tun0 ${p} 1 0 ${OVPN_ALG} 0 \ - data64.key + new_key tun0 ${p} 1 0 ${OVPN_ALG} \ + ${OVPN_KEY_TYPE} 0 data64.key done else peer_ns="ovpn_peer${1}" @@ -245,7 +258,8 @@ ovpn_add_peer() { tun${1} ${PEER_ID} ${TX_ID} ${LPORT} ${RADDR} \ ${RPORT} ip netns exec "${peer_ns}" ${OVPN_CLI} new_key tun${1} \ - ${PEER_ID} 1 0 ${OVPN_ALG} 1 data64.key + ${PEER_ID} 1 0 ${OVPN_ALG} ${OVPN_KEY_TYPE} \ + 1 data64.key fi else if [ ${1} -eq 0 ]; then @@ -254,7 +268,8 @@ ovpn_add_peer() { for p in $(seq 1 ${OVPN_NUM_PEERS}); do ip netns exec "${server_ns}" \ ${OVPN_CLI} new_key tun0 ${p} \ - 1 0 ${OVPN_ALG} 0 data64.key + 1 0 ${OVPN_ALG} \ + ${OVPN_KEY_TYPE} 0 data64.key done }) & sleep 5 @@ -269,7 +284,8 @@ ovpn_add_peer() { TX_ID=${1} fi ip netns exec "${peer_ns}" ${OVPN_CLI} connect tun${1} \ - ${PEER_ID} ${TX_ID} 10.10.${1}.1 1 data64.key + ${PEER_ID} ${TX_ID} 10.10.${1}.1 1 \ + ${OVPN_KEY_TYPE} data64.key fi fi } diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index d40953375c86..90bfcb6d39e2 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -61,6 +61,11 @@ enum ovpn_key_direction { KEY_DIR_OUT, }; +enum ovpn_key_type { + KEY_TYPE_DIRECT = 0, + KEY_TYPE_EPOCH, +}; + #define KEY_LEN (256 / 8) #define NONCE_LEN 8 @@ -130,6 +135,7 @@ struct ovpn_ctx { __u32 keepalive_interval; __u32 keepalive_timeout; + enum ovpn_key_type key_type; enum ovpn_key_direction key_dir; enum ovpn_key_slot key_slot; int key_id; @@ -451,6 +457,18 @@ static int ovpn_parse_cipher(const char *cipher, struct ovpn_ctx *ctx) return 0; } +static int ovpn_parse_key_type(const char *type, struct ovpn_ctx *ctx) +{ + if (strcmp(type, "direct") == 0) + ctx->key_type = KEY_TYPE_DIRECT; + else if (strcmp(type, "epoch") == 0) + ctx->key_type = KEY_TYPE_EPOCH; + else + return -ENOTSUP; + + return 0; +} + static int ovpn_parse_key_direction(const char *dir, struct ovpn_ctx *ctx) { int in_dir; @@ -921,9 +939,38 @@ static int ovpn_get_peer(struct ovpn_ctx *ovpn) return ret; } +static int ovpn_put_direct_key(struct nl_msg *msg, int attr, const __u8 *key, + const __u8 *nonce) +{ + struct nlattr *key_dir; + + key_dir = nla_nest_start(msg, attr); + NLA_PUT(msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, key); + NLA_PUT(msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, nonce); + nla_nest_end(msg, key_dir); + + return 0; +nla_put_failure: + return -1; +} + +static int ovpn_put_epoch_key(struct nl_msg *msg, int attr, const __u8 *key) +{ + struct nlattr *epoch; + + epoch = nla_nest_start(msg, attr); + NLA_PUT(msg, OVPN_A_EPOCH_KEY, KEY_LEN, key); + NLA_PUT_U32(msg, OVPN_A_EPOCH_CIPHER_KEY_LEN, KEY_LEN); + nla_nest_end(msg, epoch); + + return 0; +nla_put_failure: + return -1; +} + static int ovpn_new_key(struct ovpn_ctx *ovpn) { - struct nlattr *keyconf, *key_dir; + struct nlattr *keyconf; struct nl_ctx *ctx; int ret = -1; @@ -937,15 +984,37 @@ static int ovpn_new_key(struct ovpn_ctx *ovpn) NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_KEY_ID, ovpn->key_id); NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_CIPHER_ALG, ovpn->cipher); - key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_ENCRYPT_DIR); - NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_enc); - NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce); - nla_nest_end(ctx->nl_msg, key_dir); + switch (ovpn->key_type) { + case KEY_TYPE_DIRECT: + ret = ovpn_put_direct_key(ctx->nl_msg, + OVPN_A_KEYCONF_ENCRYPT_DIR, + ovpn->key_enc, ovpn->nonce); + if (ret) + goto nla_put_failure; - key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_DECRYPT_DIR); - NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_dec); - NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce); - nla_nest_end(ctx->nl_msg, key_dir); + ret = ovpn_put_direct_key(ctx->nl_msg, + OVPN_A_KEYCONF_DECRYPT_DIR, + ovpn->key_dec, ovpn->nonce); + if (ret) + goto nla_put_failure; + break; + case KEY_TYPE_EPOCH: + ret = ovpn_put_epoch_key(ctx->nl_msg, + OVPN_A_KEYCONF_ENCRYPT_EPOCH, + ovpn->key_enc); + if (ret) + goto nla_put_failure; + + ret = ovpn_put_epoch_key(ctx->nl_msg, + OVPN_A_KEYCONF_DECRYPT_EPOCH, + ovpn->key_dec); + if (ret) + goto nla_put_failure; + break; + default: + ret = -EINVAL; + goto nla_put_failure; + } nla_nest_end(ctx->nl_msg, keyconf); @@ -1691,7 +1760,7 @@ static void usage(const char *cmd) "\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_type key_file]: start connecting peer of TCP-based VPN session\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tpeer_id: peer ID found in data packets received from this peer\n"); @@ -1699,8 +1768,8 @@ static void usage(const char *cmd) "\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, "\tkey_type: key type, supported: direct, epoch\n"); + fprintf(stderr, "\tkey_file: file containing the pre-shared key\n"); fprintf(stderr, "* new_peer [vpnaddr]: add new peer\n"); @@ -1748,7 +1817,7 @@ static void usage(const char *cmd) "\tpeer_id: peer ID of the peer to query. All peers are returned if omitted\n"); fprintf(stderr, - "* new_key : set data channel key\n"); + "* new_key : set data channel key\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tpeer_id: peer ID of the peer to configure the key for\n"); @@ -1756,6 +1825,7 @@ static void usage(const char *cmd) fprintf(stderr, "\tkey_id: an ID from 0 to 7\n"); fprintf(stderr, "\tcipher: cipher to use, supported: aes (AES-GCM), chachapoly (CHACHA20POLY1305)\n"); + fprintf(stderr, "\tkey_type: key type, supported: direct, epoch\n"); fprintf(stderr, "\tkey_dir: key direction, must 0 on one host and 1 on the other\n"); fprintf(stderr, "\tkey_file: file containing the pre-shared key\n"); @@ -2247,12 +2317,19 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) } if (argc > 7) { + if (argc < 9) + return -EINVAL; + 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[7], ovpn); + ret = ovpn_parse_key_type(argv[7], ovpn); + if (ret < 0) + return -1; + + ret = ovpn_parse_key(argv[8], ovpn); if (ret) return -1; } @@ -2351,7 +2428,7 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) } break; case CMD_NEW_KEY: - if (argc < 9) + if (argc < 10) return -EINVAL; ovpn->peer_id = strtoul(argv[3], NULL, 10); @@ -2374,11 +2451,15 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) if (ret < 0) return -1; - ret = ovpn_parse_key_direction(argv[7], ovpn); + ret = ovpn_parse_key_type(argv[7], ovpn); + if (ret < 0) + return -1; + + ret = ovpn_parse_key_direction(argv[8], ovpn); if (ret < 0) return -1; - ret = ovpn_parse_key(argv[8], ovpn); + ret = ovpn_parse_key(argv[9], ovpn); if (ret) return -1; break; @@ -2442,6 +2523,7 @@ int main(int argc, char *argv[]) memset(&ovpn, 0, sizeof(ovpn)); ovpn.sa_family = AF_UNSPEC; ovpn.cipher = OVPN_CIPHER_ALG_NONE; + ovpn.key_type = KEY_TYPE_DIRECT; ovpn.cmd = ovpn_parse_cmd(argv[1]); if (ovpn.cmd == CMD_INVALID) { diff --git a/tools/testing/selftests/net/ovpn/test-epoch-tcp.sh b/tools/testing/selftests/net/ovpn/test-epoch-tcp.sh new file mode 100755 index 000000000000..ec7b243d1923 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-epoch-tcp.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2026 OpenVPN, Inc. +# +# Author: Ralf Lici +# Antonio Quartulli + +OVPN_KEY_TYPE="epoch" +OVPN_PROTO="TCP" + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test-epoch.sh b/tools/testing/selftests/net/ovpn/test-epoch.sh new file mode 100755 index 000000000000..17641dad87c4 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-epoch.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2026 OpenVPN, Inc. +# +# Author: Ralf Lici +# Antonio Quartulli + +OVPN_KEY_TYPE="epoch" + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test-mark.sh b/tools/testing/selftests/net/ovpn/test-mark.sh index 7c1d56e9c525..43b4798fefde 100755 --- a/tools/testing/selftests/net/ovpn/test-mark.sh +++ b/tools/testing/selftests/net/ovpn/test-mark.sh @@ -43,7 +43,8 @@ ovpn_mark_prepare_network() { 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 \ - "${p}" 1 0 "${OVPN_ALG}" 0 data64.key + "${p}" 1 0 "${OVPN_ALG}" "${OVPN_KEY_TYPE}" \ + 0 data64.key done for p in $(seq 1 3); do diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index 9b5610837032..a30842903fdb 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -67,14 +67,15 @@ ovpn_run_basic_traffic() { local tcpdump_timeout="1.5s" for p in $(seq 1 ${OVPN_NUM_PEERS}); do - # The first part of the data packet header consists of: + # The tcpdump filters match the cleartext data packet prefix: # - 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: + # - 24 bits for peer-id: # - with asymmetric ID: "${p}" one way and "${p} + 9" the # other way # - with symmetric ID: "${p}" both ways + # - epoch keys only: 2 bytes for epoch ("1" at this point) header1=$(printf "0x4800000%x" ${p}) header2=$(printf "0x4800000%x" $((p + OVPN_ID_OFFSET))) raddr="" @@ -152,11 +153,12 @@ ovpn_run_key_rollover() { 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 \ - ${p} 2 1 ${OVPN_ALG} 0 data64.key + ${p} 2 1 ${OVPN_ALG} ${OVPN_KEY_TYPE} \ + 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} \ - $((p + OVPN_ID_OFFSET)) 2 1 ${OVPN_ALG} 1 \ - data64.key + $((p + OVPN_ID_OFFSET)) 2 1 ${OVPN_ALG} \ + ${OVPN_KEY_TYPE} 1 data64.key ovpn_cmd_ok "swap keys on peer${p}" \ ip netns exec "${peer_ns}" ${OVPN_CLI} swap_keys \ tun${p} $((p + OVPN_ID_OFFSET))