Included changes:

* add Ralf Lici as ovpn reviewer
 * wait for the server-side key setup before generating TCP traffic in
   selftests
 * enable TCP_NODELAY on TCP sockets in selftests
 * drop redundant peer NULL checks in the crypto post functions
 * remove the unused work field from struct ovpn_socket
 * fix the documented return value of ovpn_bind_from_sockaddr()
 -----BEGIN PGP SIGNATURE-----
 
 iJEEABYIADkWIQQr0db7q+Rc7Zog28Fc8QQzwdnOtwUCaszhlxsUgAAAAAAEAA5t
 YW51MiwyLjUrMS4xMiwyLDIACgkQXPEEM8HZzrfcaAD9GC730T7nKpn21y7Kv9m6
 3nnfPU4j8eEI/525tizfvcgBAIT7qDgXP86BFJHQKXHpgjyNzBqK9QsU+8kwSeUd
 gCkB
 =xbNA
 -----END PGP SIGNATURE-----

Merge tag 'ovpn-net-next-20261012' of https://github.com/OpenVPN/ovpn-net-next

Antonio Quartulli says:

====================
Included changes:
 * add Ralf Lici as ovpn reviewer
 * wait for the server-side key setup before generating TCP traffic in
   selftests
 * enable TCP_NODELAY on TCP sockets in selftests
 * drop redundant peer NULL checks in the crypto post functions
 * remove the unused work field from struct ovpn_socket
 * fix the documented return value of ovpn_bind_from_sockaddr()

* tag 'ovpn-net-next-20261012' of https://github.com/OpenVPN/ovpn-net-next:
  MAINTAINERS: ovpn: add Ralf Lici as reviewer
  selftests: ovpn: wait for the TCP server key setup before traffic
  selftests: ovpn: enable TCP_NODELAY on TCP sockets
  ovpn: remove redundant peer NULL checks in crypto post functions
  ovpn: remove unused work field from struct ovpn_socket
  ovpn: Fix the return value in ovpn_bind_from_sockaddr() kernel-doc
====================

Link: https://patch.msgid.link/
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-10-19 19:16:43 -07:00
commit 17ec1b9b3f
10 changed files with 153 additions and 49 deletions

View File

@ -20537,6 +20537,7 @@ F: drivers/irqchip/irq-or1k-*
OPENVPN DATA CHANNEL OFFLOAD
M: Antonio Quartulli <antonio@openvpn.net>
R: Ralf Lici <ralf@mandelbit.com>
R: Sabrina Dubroca <sd@queasysnail.net>
L: openvpn-devel@lists.sourceforge.net (subscribers-only)
L: netdev@vger.kernel.org

View File

@ -18,7 +18,7 @@
* ovpn_bind_from_sockaddr - retrieve binding matching sockaddr
* @ss: the sockaddr to match
*
* Return: the bind matching the passed sockaddr if found, NULL otherwise
* Return: the new bind for the passed sockaddr, an ERR_PTR() on failure
*/
struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *ss)
{

View File

@ -206,8 +206,7 @@ void ovpn_decrypt_post(void *data, int ret)
drop_nocount:
if (likely(ks))
ovpn_crypto_key_slot_put(ks);
if (likely(peer))
ovpn_peer_put(peer);
ovpn_peer_put(peer);
}
/* RX path entry point: decrypt packet and forward it to the device */
@ -305,8 +304,7 @@ void ovpn_encrypt_post(void *data, int ret)
kfree_skb(skb);
if (likely(ks))
ovpn_crypto_key_slot_put(ks);
if (likely(peer))
ovpn_peer_put(peer);
ovpn_peer_put(peer);
}
static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)

View File

@ -24,7 +24,6 @@ struct ovpn_peer;
* @peer: unique peer transmitting over this socket (TCP only)
* @sk: the low level sock object
* @refcount: amount of contexts currently referencing this object
* @work: member used to schedule release routine (it may block)
* @tcp_tx_work: work for deferring outgoing packet processing (TCP only)
*/
struct ovpn_socket {
@ -38,7 +37,6 @@ struct ovpn_socket {
struct sock *sk;
struct kref refcount;
struct work_struct work;
struct work_struct tcp_tx_work;
};

View File

@ -42,6 +42,7 @@ TEST_PROGS := \
test-symmetric-id-float.sh \
test-symmetric-id-tcp.sh \
test-symmetric-id.sh \
test-tcp-nagle.sh \
test-tcp.sh \
test.sh \
# end of TEST_PROGS

View File

@ -191,20 +191,14 @@ ovpn_setup_ns() {
ovpn_build_capture_filter() {
# match the first four bytes of the openvpn data payload
if [ "${OVPN_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
# 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
# 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 udp[8:4] = %s" "${1}"
fi
}
@ -262,14 +256,19 @@ ovpn_add_peer() {
fi
else
if [ ${1} -eq 0 ]; then
OVPN_TCP_KEYS_LOG=$(mktemp)
(ip netns exec "${server_ns}" ${OVPN_CLI} listen tun0 \
1 ${M_ID} ${OVPN_TCP_PEERS_FILE} && {
rc=0
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} 0 data64.key \
|| rc=1
done
}) &
exit ${rc}
}) >"${OVPN_TCP_KEYS_LOG}" 2>&1 &
OVPN_TCP_KEYS_PID=$!
sleep 5
else
peer_ns="ovpn_peer${1}"
@ -287,6 +286,58 @@ ovpn_add_peer() {
fi
}
# In TCP mode the server accepts the peers and installs their data keys from a
# background subshell. "listen" returns once a connection has been accepted for
# every entry of the peers file, so the key installation runs after the last
# ovpn_add_peer() call. Wait for that subshell before generating any traffic,
# otherwise the first packets may race the key installation and get dropped for
# lack of a key.
#
# The wait is bounded: both the accept loop and the subsequent receive in
# ovpn-cli are unbounded, so a peer that never connects (for instance when
# OVPN_NUM_PEERS is smaller than the number of entries in the peers file) would
# otherwise hang the whole test instead of failing.
OVPN_TCP_KEYS_TIMEOUT=${OVPN_TCP_KEYS_TIMEOUT:-30}
ovpn_wait_tcp_keys() {
local deadline rc
[ -n "${OVPN_TCP_KEYS_PID:-}" ] || return 0
deadline=$((SECONDS + OVPN_TCP_KEYS_TIMEOUT))
while kill -0 "${OVPN_TCP_KEYS_PID}" 2>/dev/null; do
if [ "${SECONDS}" -ge "${deadline}" ]; then
printf '%s\n' \
"server-side key setup still running after ${OVPN_TCP_KEYS_TIMEOUT}s"
kill "${OVPN_TCP_KEYS_PID}" 2>/dev/null
wait "${OVPN_TCP_KEYS_PID}" 2>/dev/null
ovpn_dump_tcp_keys_log
return 1
fi
sleep 0.2
done
wait "${OVPN_TCP_KEYS_PID}"
rc=$?
# ovpn_cmd_run() has already removed the temporary file this subshell
# inherited, so without this its output would be lost and a failed
# new_key would only show up later as a ping failure.
if [ "${rc}" -ne 0 ]; then
ovpn_dump_tcp_keys_log
fi
rm -f "${OVPN_TCP_KEYS_LOG}"
return "${rc}"
}
ovpn_dump_tcp_keys_log() {
[ -s "${OVPN_TCP_KEYS_LOG:-}" ] || return 0
printf '%s\n' "--- server-side key setup output ---"
cat "${OVPN_TCP_KEYS_LOG}"
}
ovpn_compare_ntfs() {
local diff_rc=0
local diff_file

View File

@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
@ -16,6 +17,7 @@
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <time.h>
#include <linux/ovpn.h>
@ -470,6 +472,33 @@ static int ovpn_parse_key_direction(const char *dir, struct ovpn_ctx *ctx)
return 0;
}
/* openvpn 2.7.6 and later always set TCP_NODELAY, so mirror that by
* default. Peers running 2.7.0 to 2.7.5 still leave Nagle enabled, and
* mixed-version deployments will keep doing so for a while, so let the
* selftests exercise that configuration too by setting OVPN_TCP_NODELAY=0.
*/
static bool ovpn_tcp_nodelay_wanted(void)
{
const char *env = getenv("OVPN_TCP_NODELAY");
return !env || strcmp(env, "0");
}
static int ovpn_tcp_nodelay(int socket)
{
int opt = 1;
int ret;
if (!ovpn_tcp_nodelay_wanted())
return 0;
ret = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
if (ret < 0)
perror("setsockopt for TCP_NODELAY");
return ret;
}
static int ovpn_socket(struct ovpn_ctx *ctx, sa_family_t family, int proto)
{
struct sockaddr_storage local_sock = { 0 };
@ -606,6 +635,12 @@ static int ovpn_accept(struct ovpn_ctx *ctx)
goto err;
}
if (ovpn_tcp_nodelay(ret) < 0) {
close(ret);
ret = -1;
goto err;
}
return ret;
err:
close(ctx->socket);
@ -623,6 +658,10 @@ static int ovpn_connect(struct ovpn_ctx *ovpn)
return -1;
}
ret = ovpn_tcp_nodelay(s);
if (ret < 0)
goto err;
switch (ovpn->remote.in4.sin_family) {
case AF_INET:
socklen = sizeof(struct sockaddr_in);

View File

@ -37,6 +37,8 @@ ovpn_prepare_network() {
ovpn_cmd_ok "register peer${p} in overlay" ovpn_add_peer "${p}"
done
ovpn_cmd_ok "wait for server-side key setup" ovpn_wait_tcp_keys
for p in $(seq 1 ${OVPN_NUM_PEERS}); do
peer_ns="ovpn_peer${p}"
ovpn_cmd_ok "set peer0 timeout for peer ${p}" \

View File

@ -0,0 +1,12 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2025 OpenVPN, Inc.
#
# Author: Antonio Quartulli <antonio@openvpn.net>
# Peers running openvpn 2.7.0 to 2.7.5 leave Nagle enabled on their TCP
# sockets. Keep exercising that configuration so it does not silently break.
OVPN_PROTO="TCP"
export OVPN_TCP_NODELAY=0
source test.sh

View File

@ -45,6 +45,8 @@ ovpn_prepare_network() {
ovpn_cmd_ok "register peer${p} in overlay" ovpn_add_peer "${p}"
done
ovpn_cmd_ok "wait for server-side key setup" ovpn_wait_tcp_keys
for p in $(seq 1 ${OVPN_NUM_PEERS}); do
peer_ns="ovpn_peer${p}"
ovpn_cmd_ok "set peer0 timeout for peer ${p}" \
@ -137,35 +139,33 @@ 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:
# - 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 + OVPN_ID_OFFSET)))
raddr=""
if [ "${OVPN_PROTO}" == "UDP" ]; then
# The first part of the data packet header consists of:
# - 5 bits for opcode ("9" for DATA_V2)
# - 3 bits for key-id ("0" at this point)
# - 3 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 + OVPN_ID_OFFSET)))
raddr=$(awk "NR == ${p} {print \$3}" \
"${OVPN_UDP_PEERS_FILE}")
peer_ns="ovpn_peer${p}"
timeout ${tcpdump_timeout} ip netns exec "${peer_ns}" \
tcpdump --immediate-mode -p -ni veth${p} -c 1 \
"$(ovpn_build_capture_filter "${header1}" "${raddr}")" \
>/dev/null 2>&1 &
tcpdump_pid1=$!
timeout ${tcpdump_timeout} ip netns exec "${peer_ns}" \
tcpdump --immediate-mode -p -ni veth${p} -c 1 \
"$(ovpn_build_capture_filter "${header2}" "${raddr}")" \
>/dev/null 2>&1 &
tcpdump_pid2=$!
sleep 0.3
fi
peer_ns="ovpn_peer${p}"
timeout ${tcpdump_timeout} ip netns exec "${peer_ns}" \
tcpdump --immediate-mode -p -ni veth${p} -c 1 \
"$(ovpn_build_capture_filter "${header1}" "${raddr}")" \
>/dev/null 2>&1 &
tcpdump_pid1=$!
timeout ${tcpdump_timeout} ip netns exec "${peer_ns}" \
tcpdump --immediate-mode -p -ni veth${p} -c 1 \
"$(ovpn_build_capture_filter "${header2}" "${raddr}")" \
>/dev/null 2>&1 &
tcpdump_pid2=$!
sleep 0.3
ovpn_cmd_ok "send baseline traffic to peer ${p}" \
ip netns exec ovpn_peer0 \
ping -qfc 100 -w 3 5.5.5.$((p + 1))
@ -173,8 +173,10 @@ ovpn_run_basic_traffic() {
ip netns exec ovpn_peer0 \
ping -qfc 100 -s 3000 -w 3 5.5.5.$((p + 1))
wait "${tcpdump_pid1}" || return 1
wait "${tcpdump_pid2}" || return 1
if [ "${OVPN_PROTO}" == "UDP" ]; then
wait "${tcpdump_pid1}" || return 1
wait "${tcpdump_pid2}" || return 1
fi
done
}