From: Antonio Quartulli <antonio@openvpn.net>
To: netdev@vger.kernel.org
Cc: Marco Baffo <marco@mandelbit.com>,
Sabrina Dubroca <sd@queasysnail.net>,
Ralf Lici <ralf@mandelbit.com>, Jakub Kicinski <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>,
Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Neal Cardwell <ncardwell@google.com>,
Antonio Quartulli <antonio@openvpn.net>
Subject: [PATCH net-next 4/6] selftests: ovpn: enable TCP_NODELAY on TCP sockets
Date: Mon, 12 Oct 2026 15:38:01 +0200 [thread overview]
Message-ID: <20261012133809.2477063-5-antonio@openvpn.net> (raw)
In-Reply-To: <20261012133809.2477063-1-antonio@openvpn.net>
From: Marco Baffo <marco@mandelbit.com>
Since openvpn 2.7.6, userspace unconditionally sets TCP_NODELAY on its
TCP sockets. Do the same in ovpn-cli by default, so that the TCP
selftests match that configuration.
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 the option
can be turned off with OVPN_TCP_NODELAY=0. test-tcp-nagle.sh uses it to
keep running the TCP suite in that configuration.
The TCP peer ID capture assumes that every TCP segment starts with an
ovpn length prefix followed by a data header. TCP does not preserve
record boundaries, and enabling TCP_NODELAY makes this check unreliable.
Restrict the capture-based peer ID check to UDP.
Suggested-by: Neal Cardwell <ncardwell@google.com>
Signed-off-by: Marco Baffo <marco@mandelbit.com>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
tools/testing/selftests/net/ovpn/Makefile | 1 +
tools/testing/selftests/net/ovpn/common.sh | 20 +++----
tools/testing/selftests/net/ovpn/ovpn-cli.c | 39 ++++++++++++++
.../selftests/net/ovpn/test-tcp-nagle.sh | 12 +++++
tools/testing/selftests/net/ovpn/test.sh | 54 +++++++++----------
5 files changed, 86 insertions(+), 40 deletions(-)
create mode 100755 tools/testing/selftests/net/ovpn/test-tcp-nagle.sh
diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile
index 169f0464ac3a..35e4c9d4fc68 100644
--- a/tools/testing/selftests/net/ovpn/Makefile
+++ b/tools/testing/selftests/net/ovpn/Makefile
@@ -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
diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh
index 5e9c81e885e6..2f7851b82343 100644
--- a/tools/testing/selftests/net/ovpn/common.sh
+++ b/tools/testing/selftests/net/ovpn/common.sh
@@ -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
}
diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c
index 3b612a8a18fe..00b80bc5157f 100644
--- a/tools/testing/selftests/net/ovpn/ovpn-cli.c
+++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c
@@ -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);
diff --git a/tools/testing/selftests/net/ovpn/test-tcp-nagle.sh b/tools/testing/selftests/net/ovpn/test-tcp-nagle.sh
new file mode 100755
index 000000000000..2d94d8e14d3d
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/test-tcp-nagle.sh
@@ -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
diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh
index 392109d5e14e..e2e6ffdd29bb 100755
--- a/tools/testing/selftests/net/ovpn/test.sh
+++ b/tools/testing/selftests/net/ovpn/test.sh
@@ -137,35 +137,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 +171,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
}
--
2.55.0
next prev parent reply other threads:[~2026-10-12 13:38 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 13:37 [PATCH net-next 0/6] pull request: ovpn 2026-10-12 Antonio Quartulli
2026-10-12 13:37 ` [PATCH net-next 1/6] ovpn: Fix the return value in ovpn_bind_from_sockaddr() kernel-doc Antonio Quartulli
2026-10-12 13:37 ` [PATCH net-next 2/6] ovpn: remove unused work field from struct ovpn_socket Antonio Quartulli
2026-10-12 13:38 ` [PATCH net-next 3/6] ovpn: remove redundant peer NULL checks in crypto post functions Antonio Quartulli
2026-10-12 13:38 ` Antonio Quartulli [this message]
2026-10-13 14:39 ` [PATCH net-next 4/6] selftests: ovpn: enable TCP_NODELAY on TCP sockets netdev-bot+sashiko
2026-10-13 20:17 ` Antonio Quartulli
2026-10-12 13:38 ` [PATCH net-next 5/6] selftests: ovpn: wait for the TCP server key setup before traffic Antonio Quartulli
2026-10-13 14:39 ` netdev-bot+sashiko
2026-10-12 13:38 ` [PATCH net-next 6/6] MAINTAINERS: ovpn: add Ralf Lici as reviewer Antonio Quartulli
2026-10-20 2:17 ` [PATCH net-next 0/6] pull request: ovpn 2026-10-12 Jakub Kicinski
2026-10-20 2:20 ` patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261012133809.2477063-5-antonio@openvpn.net \
--to=antonio@openvpn.net \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=kuba@kernel.org \
--cc=marco@mandelbit.com \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=ralf@mandelbit.com \
--cc=sd@queasysnail.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.