From: Jiayuan Chen <jiayuan.chen@linux.dev>
To: netdev@vger.kernel.org
Cc: Jiayuan Chen <jiayuan.chen@linux.dev>,
Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Shuah Khan <shuah@kernel.org>,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org
Subject: [RFC PATCH net-next 2/2] selftests: netdevsim: add a kTLS device offload test
Date: Tue, 28 Jul 2026 20:56:41 +0800 [thread overview]
Message-ID: <20260728125658.390500-3-jiayuan.chen@linux.dev> (raw)
In-Reply-To: <20260728125658.390500-1-jiayuan.chen@linux.dev>
Run kTLS over a linked netdevsim pair, one port per netns. The test
reads /proc/net/tls_stat around the setsockopt() calls and fails unless
both directions landed on the device path, so it cannot quietly pass on
the software one. It then does a bulk transfer both ways, small
MSG_MORE writes, splice() with TLS_TX_ZEROCOPY_RO, and a run with the
record limit at its minimum so that whole records pack several to a
segment, checking the payload each time, plus the ethtool off/on path.
# ./tls.sh
PASS: tls-hw-tx-offload advertised and on by default
PASS: tls-hw-rx-offload advertised and on by default
PASS: per-port debugfs tls file exists
PASS: offloaded TLS data transfer
PASS: tx and rx contexts installed on both ports
PASS: both ends used the device path
PASS: no silent fallback to the software path
PASS: no decrypt errors
PASS: driver counted offloaded packets both ways
PASS: all offload contexts released on close
PASS: no device contexts left behind
PASS: offload declined once the feature is off
PASS: software path used when offload is off
PASS: offload works again after re-enabling
passed: 14 failed: 0
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
.../drivers/net/netdevsim/.gitignore | 2 +
.../selftests/drivers/net/netdevsim/Makefile | 7 +
.../selftests/drivers/net/netdevsim/tls.sh | 271 ++++++++++
.../drivers/net/netdevsim/tls_offload.c | 498 ++++++++++++++++++
4 files changed, 778 insertions(+)
create mode 100644 tools/testing/selftests/drivers/net/netdevsim/.gitignore
create mode 100755 tools/testing/selftests/drivers/net/netdevsim/tls.sh
create mode 100644 tools/testing/selftests/drivers/net/netdevsim/tls_offload.c
diff --git a/tools/testing/selftests/drivers/net/netdevsim/.gitignore b/tools/testing/selftests/drivers/net/netdevsim/.gitignore
new file mode 100644
index 000000000000..9d4f57cb2baa
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/netdevsim/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+tls_offload
diff --git a/tools/testing/selftests/drivers/net/netdevsim/Makefile b/tools/testing/selftests/drivers/net/netdevsim/Makefile
index 9808c2fbae9e..5beb4ed64eb5 100644
--- a/tools/testing/selftests/drivers/net/netdevsim/Makefile
+++ b/tools/testing/selftests/drivers/net/netdevsim/Makefile
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: GPL-2.0+ OR MIT
+CFLAGS += $(KHDR_INCLUDES)
+
TEST_PROGS := \
devlink.sh \
devlink_in_netns.sh \
@@ -15,9 +17,14 @@ TEST_PROGS := \
peer.sh \
psample.sh \
tc-mq-visibility.sh \
+ tls.sh \
udp_tunnel_nic.sh \
# end of TEST_PROGS
+TEST_GEN_FILES := \
+ tls_offload
+# end of TEST_GEN_FILES
+
TEST_FILES := \
ethtool-common.sh
# end of TEST_FILES
diff --git a/tools/testing/selftests/drivers/net/netdevsim/tls.sh b/tools/testing/selftests/drivers/net/netdevsim/tls.sh
new file mode 100755
index 000000000000..399e2c4d382d
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/netdevsim/tls.sh
@@ -0,0 +1,271 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Exercise netdevsim's emulated kTLS device offload over a linked
+# netdevsim pair, one port per network namespace.
+#
+# shellcheck disable=SC2154 # ksft_skip comes from lib.sh
+
+lib_dir=$(dirname "$0")
+# shellcheck source=./../../../net/lib.sh
+# shellcheck disable=SC1091
+source "$lib_dir"/../../../net/lib.sh
+
+NSIM_DEV_1_ID=$((256 + RANDOM % 256))
+NSIM_DEV_1_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_1_ID
+NSIM_DEV_2_ID=$((512 + RANDOM % 256))
+NSIM_DEV_2_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_2_ID
+
+NSIM_DEV_SYS_NEW=/sys/bus/netdevsim/new_device
+NSIM_DEV_SYS_DEL=/sys/bus/netdevsim/del_device
+NSIM_DEV_SYS_LINK=/sys/bus/netdevsim/link_device
+
+DEBUGFS=/sys/kernel/debug/netdevsim
+NSIM_DEV_1_TLS=$DEBUGFS/netdevsim$NSIM_DEV_1_ID/ports/0/tls
+NSIM_DEV_2_TLS=$DEBUGFS/netdevsim$NSIM_DEV_2_ID/ports/0/tls
+
+SRV_IP=192.168.13.1
+CLI_IP=192.168.13.2
+PORT=4433
+
+SYNCDIR=
+BIN=$lib_dir/tls_offload
+
+num_pass=0
+num_fail=0
+
+check()
+{
+ local msg="$1"
+ local ret="$2"
+
+ if [ "$ret" -eq 0 ]; then
+ echo "PASS: $msg"
+ num_pass=$((num_pass + 1))
+ else
+ echo "FAIL: $msg"
+ num_fail=$((num_fail + 1))
+ fi
+}
+
+# shellcheck disable=SC2317,SC2329 # invoked from the EXIT trap
+cleanup()
+{
+ ip netns del nscl 2>/dev/null
+ ip netns del nssv 2>/dev/null
+ echo "$NSIM_DEV_2_ID" > "$NSIM_DEV_SYS_DEL" 2>/dev/null
+ echo "$NSIM_DEV_1_ID" > "$NSIM_DEV_SYS_DEL" 2>/dev/null
+ [ -n "$SYNCDIR" ] && rm -rf "$SYNCDIR"
+}
+
+setup()
+{
+ set -e
+
+ echo "$NSIM_DEV_1_ID" > "$NSIM_DEV_SYS_NEW"
+ echo "$NSIM_DEV_2_ID" > "$NSIM_DEV_SYS_NEW"
+ udevadm settle 2>/dev/null || sleep 1
+
+ NSIM_DEV_1_NAME=$(find "$NSIM_DEV_1_SYS"/net -maxdepth 1 -type d ! \
+ -path "$NSIM_DEV_1_SYS"/net -exec basename {} \;)
+ NSIM_DEV_2_NAME=$(find "$NSIM_DEV_2_SYS"/net -maxdepth 1 -type d ! \
+ -path "$NSIM_DEV_2_SYS"/net -exec basename {} \;)
+
+ ip netns add nssv
+ ip netns add nscl
+
+ ip link set "$NSIM_DEV_1_NAME" netns nssv
+ ip link set "$NSIM_DEV_2_NAME" netns nscl
+
+ ip netns exec nssv ip addr add "$SRV_IP/24" dev "$NSIM_DEV_1_NAME"
+ ip netns exec nscl ip addr add "$CLI_IP/24" dev "$NSIM_DEV_2_NAME"
+
+ ip netns exec nssv ip link set dev "$NSIM_DEV_1_NAME" up
+ ip netns exec nscl ip link set dev "$NSIM_DEV_2_NAME" up
+
+ NSIM_DEV_1_FD=$((256 + RANDOM % 256))
+ exec {NSIM_DEV_1_FD}</var/run/netns/nssv
+ NSIM_DEV_1_IFIDX=$(ip netns exec nssv \
+ cat /sys/class/net/"$NSIM_DEV_1_NAME"/ifindex)
+
+ NSIM_DEV_2_FD=$((256 + RANDOM % 256))
+ exec {NSIM_DEV_2_FD}</var/run/netns/nscl
+ NSIM_DEV_2_IFIDX=$(ip netns exec nscl \
+ cat /sys/class/net/"$NSIM_DEV_2_NAME"/ifindex)
+
+ echo "$NSIM_DEV_1_FD:$NSIM_DEV_1_IFIDX $NSIM_DEV_2_FD:$NSIM_DEV_2_IFIDX" \
+ > "$NSIM_DEV_SYS_LINK"
+
+ SYNCDIR=$(mktemp -d)
+ set +e
+}
+
+feature()
+{
+ local netns="$1"
+ local dev="$2"
+ local feat="$3"
+
+ ip netns exec "$netns" ethtool -k "$dev" 2>/dev/null | \
+ sed -n "s/^$feat: \([a-z]*\).*/\1/p"
+}
+
+dbg_field()
+{
+ sed -n "s/.*\<$2=\([0-9]*\).*/\1/p" "$1" | head -1
+}
+
+tls_stat()
+{
+ ip netns exec "$1" cat /proc/net/tls_stat | \
+ sed -n "s/^$2 \([0-9]*\)/\1/p"
+}
+
+# Both ends park once their offload is installed and before any data is
+# sent, so the driver's context count can be sampled without racing the
+# transfer. Pass "nosample" when the offload is expected to be refused,
+# since then neither end ever reaches the barrier.
+run_pair()
+{
+ local sample="${1:-sample}"
+ local srv_rc cli_rc waited=0
+
+ rm -f "$SYNCDIR"/*.ready "$SYNCDIR"/go
+ CONNS_1=0
+ CONNS_2=0
+
+ ip netns exec nssv "$BIN" server "$SRV_IP" "$PORT" "$SYNCDIR" &
+ local srv_pid=$!
+ ip netns exec nscl "$BIN" client "$SRV_IP" "$PORT" "$SYNCDIR" &
+ local cli_pid=$!
+
+ if [ "$sample" = "sample" ]; then
+ while [ ! -e "$SYNCDIR/server.ready" ] ||
+ [ ! -e "$SYNCDIR/client.ready" ]; do
+ [ "$waited" -ge 200 ] && break
+ sleep 0.05
+ waited=$((waited + 1))
+ done
+ CONNS_1=$(dbg_field "$NSIM_DEV_1_TLS" count)
+ CONNS_2=$(dbg_field "$NSIM_DEV_2_TLS" count)
+ : "${CONNS_1:=0}"
+ : "${CONNS_2:=0}"
+ fi
+ touch "$SYNCDIR/go"
+
+ wait "$srv_pid"; srv_rc=$?
+ wait "$cli_pid"; cli_rc=$?
+
+ [ "$srv_rc" -eq 0 ] && [ "$cli_rc" -eq 0 ]
+}
+
+###
+### Code start
+###
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo "SKIP: need root"
+ exit "$ksft_skip"
+fi
+
+if ! command -v ethtool >/dev/null; then
+ echo "SKIP: ethtool not found"
+ exit "$ksft_skip"
+fi
+
+if [ ! -x "$BIN" ]; then
+ echo "SKIP: $BIN not built"
+ exit "$ksft_skip"
+fi
+
+modprobe netdevsim 2>/dev/null
+if [ ! -d /sys/bus/netdevsim ]; then
+ echo "SKIP: netdevsim not available"
+ exit "$ksft_skip"
+fi
+
+modprobe tls 2>/dev/null
+if [ ! -e /proc/net/tls_stat ]; then
+ echo "SKIP: kernel TLS not available"
+ exit "$ksft_skip"
+fi
+
+trap cleanup EXIT
+setup
+
+# The offload has to be advertised, and on by default like the other
+# netdevsim crypto offloads.
+for f in tls-hw-tx-offload tls-hw-rx-offload; do
+ [ "$(feature nssv "$NSIM_DEV_1_NAME" "$f")" = "on" ]
+ check "$f advertised and on by default" $?
+done
+
+[ -e "$NSIM_DEV_1_TLS" ]
+check "per-port debugfs tls file exists" $?
+
+# Main data path run.
+run_pair
+check "offloaded TLS data transfer" $?
+
+# Sampled at the barrier, so each port must be holding exactly the TX and
+# the RX context of its own socket.
+[ "$CONNS_1" -eq 2 ] && [ "$CONNS_2" -eq 2 ]
+check "tx and rx contexts installed on both ports" $?
+
+# Both ends must have gone through the device path, not the SW fallback.
+[ "$(tls_stat nssv TlsTxDevice)" -ge 1 ] && \
+ [ "$(tls_stat nssv TlsRxDevice)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsTxDevice)" -ge 1 ] && \
+ [ "$(tls_stat nscl TlsRxDevice)" -ge 1 ]
+check "both ends used the device path" $?
+
+[ "$(tls_stat nssv TlsTxSw)" -eq 0 ] && [ "$(tls_stat nscl TlsTxSw)" -eq 0 ]
+check "no silent fallback to the software path" $?
+
+[ "$(tls_stat nssv TlsDecryptError)" -eq 0 ] && \
+ [ "$(tls_stat nscl TlsDecryptError)" -eq 0 ]
+check "no decrypt errors" $?
+
+# The driver must have seen the records go by in both directions.
+[ "$(dbg_field "$NSIM_DEV_1_TLS" tx_packets)" -ge 1 ] && \
+ [ "$(dbg_field "$NSIM_DEV_1_TLS" rx_packets)" -ge 1 ] && \
+ [ "$(dbg_field "$NSIM_DEV_2_TLS" tx_packets)" -ge 1 ] && \
+ [ "$(dbg_field "$NSIM_DEV_2_TLS" rx_packets)" -ge 1 ]
+check "driver counted offloaded packets both ways" $?
+
+# Sockets are closed by now, so every context must have been given back.
+[ "$(dbg_field "$NSIM_DEV_1_TLS" count)" -eq 0 ] && \
+ [ "$(dbg_field "$NSIM_DEV_2_TLS" count)" -eq 0 ]
+check "all offload contexts released on close" $?
+
+[ "$(tls_stat nssv TlsCurrTxDevice)" -eq 0 ] && \
+ [ "$(tls_stat nssv TlsCurrRxDevice)" -eq 0 ]
+check "no device contexts left behind" $?
+
+# Turning the feature off has to make the offload refuse the connection;
+# the test binary insists on the device path, so it must now fail.
+ip netns exec nssv ethtool -K "$NSIM_DEV_1_NAME" tls-hw-tx-offload off
+ip netns exec nssv ethtool -K "$NSIM_DEV_1_NAME" tls-hw-rx-offload off
+ip netns exec nscl ethtool -K "$NSIM_DEV_2_NAME" tls-hw-tx-offload off
+ip netns exec nscl ethtool -K "$NSIM_DEV_2_NAME" tls-hw-rx-offload off
+
+run_pair nosample
+rc=$?
+[ "$rc" -ne 0 ]
+check "offload declined once the feature is off" $?
+
+[ "$(tls_stat nssv TlsTxSw)" -ge 1 ]
+check "software path used when offload is off" $?
+
+ip netns exec nssv ethtool -K "$NSIM_DEV_1_NAME" tls-hw-tx-offload on
+ip netns exec nssv ethtool -K "$NSIM_DEV_1_NAME" tls-hw-rx-offload on
+ip netns exec nscl ethtool -K "$NSIM_DEV_2_NAME" tls-hw-tx-offload on
+ip netns exec nscl ethtool -K "$NSIM_DEV_2_NAME" tls-hw-rx-offload on
+
+run_pair
+check "offload works again after re-enabling" $?
+
+echo
+echo "passed: $num_pass failed: $num_fail"
+[ "$num_fail" -eq 0 ] && exit 0
+exit 1
diff --git a/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c b/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c
new file mode 100644
index 000000000000..ebfb4bfcd4f4
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/netdevsim/tls_offload.c
@@ -0,0 +1,498 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kTLS device offload data path exercise, driven by tls.sh.
+ *
+ * One instance runs as the server and one as the client, each in its own
+ * network namespace, connected back to back by a linked netdevsim pair.
+ * Both ends enable kTLS and rely on netdevsim's emulated TLS offload, so
+ * every record travels through net/tls/tls_device.c rather than the
+ * software path.
+ *
+ * The two processes rendezvous through a shared directory so that neither
+ * side sends before the other has installed its RX offload.
+ */
+
+#define _GNU_SOURCE
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/tls.h>
+
+#ifndef SOL_TLS
+#define SOL_TLS 282
+#endif
+
+#ifndef TCP_ULP
+#define TCP_ULP 31
+#endif
+
+#define BULK_LEN (200 * 1024)
+#define MORE_FRAGS 64
+#define SPLICE_FRAG_LEN 4096
+#define SPLICE_FRAGS 8
+
+/* TLS_MIN_RECORD_SIZE_LIM and TLS_MAX_PAYLOAD_SIZE, which are not uapi. */
+#define REC_LIM_MIN 64
+#define REC_LIM_MAX 16384
+
+#define SMALL_RECS 100
+#define SMALL_LEN (REC_LIM_MIN * SMALL_RECS)
+
+#define SYNC_TIMEOUT_MS 20000
+#define CONNECT_TIMEOUT_MS 20000
+
+static const char *role;
+
+static void die(const char *what)
+{
+ fprintf(stderr, "%s: %s: %s\n", role, what, strerror(errno));
+ exit(1);
+}
+
+static void fail(const char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s: ", role);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+static void msleep(unsigned int ms)
+{
+ struct timespec ts = {
+ .tv_sec = ms / 1000,
+ .tv_nsec = (ms % 1000) * 1000000L,
+ };
+
+ nanosleep(&ts, NULL);
+}
+
+/* /proc/net/tls_stat is per netns, so both ends can check that their own
+ * connection really landed on the device path.
+ */
+static unsigned long read_tls_stat(const char *name)
+{
+ char line[256];
+ unsigned long val;
+ FILE *f;
+
+ f = fopen("/proc/net/tls_stat", "r");
+ if (!f)
+ die("open /proc/net/tls_stat");
+
+ while (fgets(line, sizeof(line), f)) {
+ char key[64];
+
+ if (sscanf(line, "%63s %lu", key, &val) != 2)
+ continue;
+ if (!strcmp(key, name)) {
+ fclose(f);
+ return val;
+ }
+ }
+
+ fclose(f);
+ fail("%s not found in /proc/net/tls_stat", name);
+ return 0;
+}
+
+static void fill_pattern(char *buf, size_t len, unsigned int seed)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = (char)(seed + i * 31 + (i >> 8) * 7);
+}
+
+static void check_pattern(const char *buf, size_t len, unsigned int seed,
+ const char *what)
+{
+ char *want = malloc(len);
+ size_t i;
+
+ if (!want)
+ die("malloc");
+
+ fill_pattern(want, len, seed);
+ for (i = 0; i < len; i++) {
+ if (buf[i] != want[i])
+ fail("%s: payload mismatch at byte %zu: got 0x%02x want 0x%02x",
+ what, i, (unsigned char)buf[i],
+ (unsigned char)want[i]);
+ }
+
+ free(want);
+}
+
+static void write_all(int fd, const char *buf, size_t len)
+{
+ size_t done = 0;
+
+ while (done < len) {
+ ssize_t n = send(fd, buf + done, len - done, 0);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ die("send");
+ }
+ done += n;
+ }
+}
+
+static void read_all(int fd, char *buf, size_t len)
+{
+ size_t done = 0;
+
+ while (done < len) {
+ ssize_t n = recv(fd, buf + done, len - done, 0);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ die("recv");
+ }
+ if (n == 0)
+ fail("peer closed after %zu of %zu bytes", done, len);
+ done += n;
+ }
+}
+
+static void enable_ktls(int fd)
+{
+ struct tls12_crypto_info_aes_gcm_128 ci = {};
+ unsigned long tx_before, rx_before;
+
+ tx_before = read_tls_stat("TlsTxDevice");
+ rx_before = read_tls_stat("TlsRxDevice");
+
+ if (setsockopt(fd, IPPROTO_TCP, TCP_ULP, "tls", sizeof("tls")))
+ die("setsockopt(TCP_ULP, tls)");
+
+ ci.info.version = TLS_1_2_VERSION;
+ ci.info.cipher_type = TLS_CIPHER_AES_GCM_128;
+ memset(ci.iv, 'i', sizeof(ci.iv));
+ memset(ci.key, 'k', sizeof(ci.key));
+ memset(ci.salt, 's', sizeof(ci.salt));
+ memset(ci.rec_seq, 0, sizeof(ci.rec_seq));
+
+ if (setsockopt(fd, SOL_TLS, TLS_TX, &ci, sizeof(ci)))
+ die("setsockopt(TLS_TX)");
+ if (setsockopt(fd, SOL_TLS, TLS_RX, &ci, sizeof(ci)))
+ die("setsockopt(TLS_RX)");
+
+ /* The whole point of the exercise: refuse to silently fall back to
+ * the software path, otherwise the test would pass without ever
+ * touching tls_device.c.
+ */
+ if (read_tls_stat("TlsTxDevice") != tx_before + 1)
+ fail("TX did not land on the device path (TlsTxDevice %lu -> %lu)",
+ tx_before, read_tls_stat("TlsTxDevice"));
+ if (read_tls_stat("TlsRxDevice") != rx_before + 1)
+ fail("RX did not land on the device path (TlsRxDevice %lu -> %lu)",
+ rx_before, read_tls_stat("TlsRxDevice"));
+}
+
+static void sync_path(char *out, size_t len, const char *dir, const char *who)
+{
+ if ((size_t)snprintf(out, len, "%s/%s.ready", dir, who) >= len)
+ fail("sync dir path too long");
+}
+
+static void rendezvous(const char *dir, const char *me, const char *peer)
+{
+ char mine[PATH_MAX], theirs[PATH_MAX];
+ unsigned int waited = 0;
+ int fd;
+
+ sync_path(mine, sizeof(mine), dir, me);
+ sync_path(theirs, sizeof(theirs), dir, peer);
+
+ fd = open(mine, O_CREAT | O_WRONLY, 0600);
+ if (fd < 0)
+ die("create sync file");
+ close(fd);
+
+ while (access(theirs, F_OK)) {
+ if (waited >= SYNC_TIMEOUT_MS)
+ fail("timed out waiting for %s", peer);
+ msleep(20);
+ waited += 20;
+ }
+}
+
+/* Both ends stop here with their offload installed and no data sent yet,
+ * so that the driver state can be inspected from the outside.
+ */
+static void wait_for_go(const char *dir)
+{
+ unsigned int waited = 0;
+ char go[PATH_MAX];
+
+ if ((size_t)snprintf(go, sizeof(go), "%s/go", dir) >= sizeof(go))
+ fail("sync dir path too long");
+
+ while (access(go, F_OK)) {
+ if (waited >= SYNC_TIMEOUT_MS)
+ fail("timed out waiting for go");
+ msleep(20);
+ waited += 20;
+ }
+}
+
+/* Small writes with MSG_MORE accumulate into one open record before it is
+ * pushed, which is the interesting part of tls_push_data().
+ */
+static void send_msg_more(int fd, unsigned int seed)
+{
+ char buf[MORE_FRAGS + 1];
+ int i;
+
+ fill_pattern(buf, sizeof(buf), seed);
+
+ for (i = 0; i < MORE_FRAGS; i++) {
+ if (send(fd, buf + i, 1, MSG_MORE) != 1)
+ die("send(MSG_MORE)");
+ }
+ if (send(fd, buf + MORE_FRAGS, 1, 0) != 1)
+ die("send(last)");
+}
+
+/* splice() reaches tls_push_data() with MSG_SPLICE_PAGES once
+ * TLS_TX_ZEROCOPY_RO is enabled, which is a distinct fragment path.
+ */
+static void send_splice(int fd, unsigned int seed)
+{
+ char buf[SPLICE_FRAG_LEN];
+ int val = 1;
+ int i;
+
+ if (setsockopt(fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, &val, sizeof(val)))
+ die("setsockopt(TLS_TX_ZEROCOPY_RO)");
+
+ for (i = 0; i < SPLICE_FRAGS; i++) {
+ int p[2];
+
+ fill_pattern(buf, sizeof(buf), seed + i * SPLICE_FRAG_LEN);
+
+ if (pipe(p))
+ die("pipe");
+ if (write(p[1], buf, sizeof(buf)) != sizeof(buf))
+ die("write to pipe");
+ if (splice(p[0], NULL, fd, NULL, sizeof(buf),
+ i == SPLICE_FRAGS - 1 ? 0 : SPLICE_F_MORE) !=
+ sizeof(buf))
+ die("splice");
+ close(p[0]);
+ close(p[1]);
+ }
+
+ val = 0;
+ if (setsockopt(fd, SOL_TLS, TLS_TX_ZEROCOPY_RO, &val, sizeof(val)))
+ die("setsockopt(TLS_TX_ZEROCOPY_RO off)");
+}
+
+/* A record can be up to 16K, so normally one segment carries a piece of a
+ * single record. Shrinking the limit puts a dozen or so whole records in
+ * every segment instead, which is the multi-record path through the driver.
+ */
+static void send_small_records(int fd, unsigned int seed)
+{
+ char buf[SMALL_LEN];
+ uint16_t limit;
+
+ limit = REC_LIM_MIN;
+ if (setsockopt(fd, SOL_TLS, TLS_TX_MAX_PAYLOAD_LEN, &limit,
+ sizeof(limit)))
+ die("setsockopt(TLS_TX_MAX_PAYLOAD_LEN)");
+
+ fill_pattern(buf, sizeof(buf), seed);
+ write_all(fd, buf, sizeof(buf));
+
+ limit = REC_LIM_MAX;
+ if (setsockopt(fd, SOL_TLS, TLS_TX_MAX_PAYLOAD_LEN, &limit,
+ sizeof(limit)))
+ die("setsockopt(TLS_TX_MAX_PAYLOAD_LEN restore)");
+}
+
+#define SEED_C2S_BULK 0x11
+#define SEED_S2C_BULK 0x22
+#define SEED_C2S_MORE 0x33
+#define SEED_C2S_SPLICE 0x44
+#define SEED_C2S_SMALL 0x55
+
+static void run_client(int fd)
+{
+ char *buf = malloc(BULK_LEN);
+
+ if (!buf)
+ die("malloc");
+
+ fill_pattern(buf, BULK_LEN, SEED_C2S_BULK);
+ write_all(fd, buf, BULK_LEN);
+
+ read_all(fd, buf, BULK_LEN);
+ check_pattern(buf, BULK_LEN, SEED_S2C_BULK, "server -> client bulk");
+
+ send_msg_more(fd, SEED_C2S_MORE);
+ send_splice(fd, SEED_C2S_SPLICE);
+ send_small_records(fd, SEED_C2S_SMALL);
+
+ /* Wait for the server's verdict before tearing anything down. */
+ read_all(fd, buf, 1);
+ if (buf[0] != 'k')
+ fail("server reported a failure");
+
+ free(buf);
+}
+
+static void run_server(int fd)
+{
+ size_t splice_len = (size_t)SPLICE_FRAG_LEN * SPLICE_FRAGS;
+ char *buf = malloc(BULK_LEN);
+ char more[MORE_FRAGS + 1];
+ char *sbuf;
+ char ok = 'k';
+ int i;
+
+ sbuf = malloc(splice_len);
+ if (!buf || !sbuf)
+ die("malloc");
+
+ read_all(fd, buf, BULK_LEN);
+ check_pattern(buf, BULK_LEN, SEED_C2S_BULK, "client -> server bulk");
+
+ fill_pattern(buf, BULK_LEN, SEED_S2C_BULK);
+ write_all(fd, buf, BULK_LEN);
+
+ read_all(fd, more, sizeof(more));
+ check_pattern(more, sizeof(more), SEED_C2S_MORE, "client -> server MSG_MORE");
+
+ read_all(fd, sbuf, splice_len);
+ for (i = 0; i < SPLICE_FRAGS; i++)
+ check_pattern(sbuf + (size_t)i * SPLICE_FRAG_LEN,
+ SPLICE_FRAG_LEN, SEED_C2S_SPLICE +
+ i * SPLICE_FRAG_LEN, "client -> server splice");
+
+ read_all(fd, buf, SMALL_LEN);
+ check_pattern(buf, SMALL_LEN, SEED_C2S_SMALL,
+ "client -> server small records");
+
+ write_all(fd, &ok, 1);
+
+ free(sbuf);
+ free(buf);
+}
+
+static int do_server(const char *ip, int port, const char *syncdir)
+{
+ struct sockaddr_in sa = {};
+ int lfd, fd, one = 1;
+
+ lfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (lfd < 0)
+ die("socket");
+ if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)))
+ die("SO_REUSEADDR");
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ if (inet_pton(AF_INET, ip, &sa.sin_addr) != 1)
+ fail("bad bind address %s", ip);
+
+ if (bind(lfd, (struct sockaddr *)&sa, sizeof(sa)))
+ die("bind");
+ if (listen(lfd, 1))
+ die("listen");
+
+ fd = accept(lfd, NULL, NULL);
+ if (fd < 0)
+ die("accept");
+ close(lfd);
+
+ enable_ktls(fd);
+ rendezvous(syncdir, "server", "client");
+ wait_for_go(syncdir);
+
+ run_server(fd);
+
+ close(fd);
+ return 0;
+}
+
+static int do_client(const char *ip, int port, const char *syncdir)
+{
+ struct sockaddr_in sa = {};
+ unsigned int waited = 0;
+ int fd;
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ if (inet_pton(AF_INET, ip, &sa.sin_addr) != 1)
+ fail("bad server address %s", ip);
+
+ for (;;) {
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0)
+ die("socket");
+ if (!connect(fd, (struct sockaddr *)&sa, sizeof(sa)))
+ break;
+ close(fd);
+ if (waited >= CONNECT_TIMEOUT_MS)
+ die("connect");
+ msleep(20);
+ waited += 20;
+ }
+
+ enable_ktls(fd);
+ rendezvous(syncdir, "client", "server");
+ wait_for_go(syncdir);
+
+ run_client(fd);
+
+ close(fd);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int port;
+
+ if (argc != 5) {
+ fprintf(stderr,
+ "usage: %s server|client <ip> <port> <syncdir>\n",
+ argv[0]);
+ return 2;
+ }
+
+ role = argv[1];
+ port = atoi(argv[3]);
+
+ if (!strcmp(role, "server"))
+ return do_server(argv[2], port, argv[4]);
+ if (!strcmp(role, "client"))
+ return do_client(argv[2], port, argv[4]);
+
+ fprintf(stderr, "unknown role %s\n", role);
+ return 2;
+}
--
2.43.0
prev parent reply other threads:[~2026-07-28 12:57 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-28 12:56 [RFC PATCH net-next 0/2] netdevsim: add TLS device offload emulation and test Jiayuan Chen
2026-07-28 12:56 ` [RFC PATCH net-next 1/2] netdevsim: add TLS device offload emulation Jiayuan Chen
2026-07-28 12:56 ` Jiayuan Chen [this message]
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=20260728125658.390500-3-jiayuan.chen@linux.dev \
--to=jiayuan.chen@linux.dev \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shuah@kernel.org \
/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.