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: 7+ 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-12 13:38 ` [PATCH net-next 5/6] selftests: ovpn: wait for the TCP server key setup before traffic Antonio Quartulli
2026-10-12 13:38 ` [PATCH net-next 6/6] MAINTAINERS: ovpn: add Ralf Lici as reviewer Antonio Quartulli
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox