selftests: drv-net: convert so_txtime to drv-net

In preparation for extending to pacing hardware offload, convert the
so_txtime.sh test to a drv-net test that can be run against netdevsim
and real hardware.

Also update so_txtime.c to not exit on first failure, but run to
completion and report exit code there. This helps with debugging
unexpected results, especially when processing multiple packets,
as happens in the "reverse_order" testcase.

Signed-off-by: Willem de Bruijn <willemb@google.com>

----

v6 -> v7

- update test to use new argument expect_fail
- v6 received Reviewed-by, but dropped due to above (minor) change

v5 -> v6

- fix order in tools/testing/selftests/drivers/net/config

v4 -> v5

- move qdisc setup/restore into each test
- add tc to utils.py (separate patch)
- test expected failure (separate patch)
- fix pylint
- convert fail to pass for timing errors if KSFT_MACHINE_SLOW
  (cmd does not special case KSFT_SKIP process returncode yet)

Responses to sashiko review

- The test converts per packet failure to errors, to continue
  testing other packets, but other error() cases are not in scope.
- The test starts sender and receiver at an absolute future time,
  like the original test. This assumes ~msec scale sync'ed clocks.
- The tc qdisc replace command works fine with noqueue. Tested
  manually.

v3 -> v4

- restore original qdisc after test
- drop unnecessary underscore in tap test names

v2 -> v3

- Makefile: so_txtime from YNL_GEN_FILES to TEST_GEN_FILES (Sashiko, NIPA)

v1 -> v2
- move so_txtime.c for net/lib to drivers/net (Jakub)
- fix drivers/net/config order (Jakub)
- detect passing when failure is expected (Jakub, Sashiko)
- pass pylint --disable=R (Jakub)
- only call ksft_run once (Jakub)
- do not sleep if waiting time is negative (Sashiko)
- add \n when converting error() to fprintf() (Sashiko)
- 4 space indentation, instead of 2 space
- increase sync delay from 100 to 200ms, to fix rare vng flakes

Link: https://patch.msgid.link/20260504174056.565319-4-willemdebruijn.kernel@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Willem de Bruijn
2026-05-04 13:38:34 -04:00
committed by Jakub Kicinski
parent 49bf9e5e7c
commit 5c6baef388
8 changed files with 121 additions and 118 deletions

View File

@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
napi_id_helper
psp_responder
so_txtime

View File

@@ -7,6 +7,7 @@ TEST_INCLUDES := $(wildcard lib/py/*.py) \
TEST_GEN_FILES := \
napi_id_helper \
so_txtime \
# end of TEST_GEN_FILES
TEST_PROGS := \
@@ -21,6 +22,7 @@ TEST_PROGS := \
queues.py \
ring_reconfig.py \
shaper.py \
so_txtime.py \
stats.py \
xdp.py \
# end of TEST_PROGS

View File

@@ -8,5 +8,7 @@ CONFIG_NETCONSOLE=m
CONFIG_NETCONSOLE_DYNAMIC=y
CONFIG_NETCONSOLE_EXTENDED_LOG=y
CONFIG_NETDEVSIM=m
CONFIG_NET_SCH_ETF=m
CONFIG_NET_SCH_FQ=m
CONFIG_VLAN_8021Q=m
CONFIG_XDP_SOCKETS=y

View File

@@ -33,6 +33,8 @@
#include <unistd.h>
#include <poll.h>
#include "kselftest.h"
static int cfg_clockid = CLOCK_TAI;
static uint16_t cfg_port = 8000;
static int cfg_variance_us = 4000;
@@ -43,6 +45,8 @@ static bool cfg_rx;
static uint64_t glob_tstart;
static uint64_t tdeliver_max;
static int errors;
/* encode one timed transmission (of a 1B payload) */
struct timed_send {
char data;
@@ -131,13 +135,15 @@ static void do_recv_one(int fdr, struct timed_send *ts)
fprintf(stderr, "payload:%c delay:%lld expected:%lld (us)\n",
rbuf[0], (long long)tstop, (long long)texpect);
if (rbuf[0] != ts->data)
error(1, 0, "payload mismatch. expected %c", ts->data);
if (rbuf[0] != ts->data) {
fprintf(stderr, "payload mismatch. expected %c\n", ts->data);
errors++;
}
if (llabs(tstop - texpect) > cfg_variance_us) {
fprintf(stderr, "exceeds variance (%d us)\n", cfg_variance_us);
if (!getenv("KSFT_MACHINE_SLOW"))
exit(1);
errors++;
}
}
@@ -255,8 +261,12 @@ static void start_time_wait(void)
return;
now = gettime_ns(CLOCK_REALTIME);
if (cfg_start_time_ns < now)
if (cfg_start_time_ns < now) {
fprintf(stderr, "FAIL: start time already passed\n");
if (!getenv("KSFT_MACHINE_SLOW"))
errors++;
return;
}
err = usleep((cfg_start_time_ns - now) / 1000);
if (err)
@@ -513,5 +523,10 @@ int main(int argc, char **argv)
else
do_test_tx((void *)&cfg_src_addr, cfg_alen);
return 0;
if (errors) {
fprintf(stderr, "FAIL: %d errors\n", errors);
return KSFT_FAIL;
}
return KSFT_PASS;
}

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
"""Regression tests for the SO_TXTIME interface.
Test delivery time in FQ and ETF qdiscs.
"""
import time
from lib.py import ksft_exit, ksft_run, ksft_variants
from lib.py import KsftNamedVariant, KsftSkipEx
from lib.py import NetDrvEpEnv, bkg, cmd, defer, tc
def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success):
"""Main function. Run so_txtime as sender and receiver."""
bin_path = cfg.test_dir / "so_txtime"
tstart = time.time_ns() + 200_000_000
cmd_addr = f"-S {cfg.addr_v[ipver]} -D {cfg.remote_addr_v[ipver]}"
cmd_base = f"{bin_path} -{ipver} -c {clockid} -t {tstart} {cmd_addr}"
cmd_rx = f"{cmd_base} {args_rx} -r"
cmd_tx = f"{cmd_base} {args_tx}"
with bkg(cmd_rx, host=cfg.remote, fail=expect_success,
expect_fail=(not expect_success), exit_wait=True):
cmd(cmd_tx)
def _qdisc_setup(ifname, qdisc, optargs=""):
"""Replace root qdisc. Restore the original after the test.
If the original is mq, children will be of type default_qdisc.
"""
orig = tc(f"qdisc show dev {ifname} root", json=True)[0].get("kind", None)
defer(tc, f"qdisc replace dev {ifname} root {orig}")
tc(f"qdisc replace dev {ifname} root {qdisc} {optargs}")
def _test_variants_mono():
for ipver in ["4", "6"]:
for testcase in [
["no_delay", "a,-1", "a,-1"],
["zero_delay", "a,0", "a,0"],
["one_pkt", "a,10", "a,10"],
["in_order", "a,10,b,20", "a,10,b,20"],
["reverse_order", "a,20,b,10", "b,20,a,20"],
]:
name = f"v{ipver}_{testcase[0]}"
yield KsftNamedVariant(name, ipver, testcase[1], testcase[2])
@ksft_variants(_test_variants_mono())
def test_so_txtime_mono(cfg, ipver, args_tx, args_rx):
"""Run all variants of monotonic (fq) tests."""
_qdisc_setup(cfg.ifname, "fq")
test_so_txtime(cfg, "mono", ipver, args_tx, args_rx, True)
def _test_variants_etf():
for ipver in ["4", "6"]:
for testcase in [
["no_delay", "a,-1", "a,-1", False],
["zero_delay", "a,0", "a,0", False],
["one_pkt", "a,10", "a,10", True],
["in_order", "a,10,b,20", "a,10,b,20", True],
["reverse_order", "a,20,b,10", "b,10,a,20", True],
]:
name = f"v{ipver}_{testcase[0]}"
yield KsftNamedVariant(
name, ipver, testcase[1], testcase[2], testcase[3]
)
@ksft_variants(_test_variants_etf())
def test_so_txtime_etf(cfg, ipver, args_tx, args_rx, expect_fail):
"""Run all variants of etf tests."""
try:
_qdisc_setup(cfg.ifname, "etf", "clockid CLOCK_TAI delta 400000")
except Exception as e:
raise KsftSkipEx("tc does not support qdisc etf. skipping") from e
test_so_txtime(cfg, "tai", ipver, args_tx, args_rx, expect_fail)
def main() -> None:
"""Boilerplate ksft main."""
with NetDrvEpEnv(__file__) as cfg:
ksft_run([test_so_txtime_mono, test_so_txtime_etf], args=(cfg,))
ksft_exit()
if __name__ == "__main__":
main()

View File

@@ -40,7 +40,6 @@ skf_net_off
socket
so_incoming_cpu
so_netns_cookie
so_txtime
so_rcv_listener
stress_reuseport_listen
tap

View File

@@ -83,7 +83,6 @@ TEST_PROGS := \
rxtimestamp.sh \
sctp_vrf.sh \
skf_net_off.sh \
so_txtime.sh \
srv6_end_dt46_l3vpn_test.sh \
srv6_end_dt4_l3vpn_test.sh \
srv6_end_dt6_l3vpn_test.sh \
@@ -157,7 +156,6 @@ TEST_GEN_FILES := \
skf_net_off \
so_netns_cookie \
so_rcv_listener \
so_txtime \
socket \
stress_reuseport_listen \
tcp_fastopen_backup_key \

View File

@@ -1,110 +0,0 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Regression tests for the SO_TXTIME interface
set -e
readonly ksft_skip=4
readonly DEV="veth0"
readonly BIN="./so_txtime"
readonly RAND="$(mktemp -u XXXXXX)"
readonly NSPREFIX="ns-${RAND}"
readonly NS1="${NSPREFIX}1"
readonly NS2="${NSPREFIX}2"
readonly SADDR4='192.168.1.1'
readonly DADDR4='192.168.1.2'
readonly SADDR6='fd::1'
readonly DADDR6='fd::2'
cleanup() {
ip netns del "${NS2}"
ip netns del "${NS1}"
}
trap cleanup EXIT
# Create virtual ethernet pair between network namespaces
ip netns add "${NS1}"
ip netns add "${NS2}"
ip link add "${DEV}" netns "${NS1}" type veth \
peer name "${DEV}" netns "${NS2}"
# Bring the devices up
ip -netns "${NS1}" link set "${DEV}" up
ip -netns "${NS2}" link set "${DEV}" up
# Set fixed MAC addresses on the devices
ip -netns "${NS1}" link set dev "${DEV}" address 02:02:02:02:02:02
ip -netns "${NS2}" link set dev "${DEV}" address 06:06:06:06:06:06
# Add fixed IP addresses to the devices
ip -netns "${NS1}" addr add 192.168.1.1/24 dev "${DEV}"
ip -netns "${NS2}" addr add 192.168.1.2/24 dev "${DEV}"
ip -netns "${NS1}" addr add fd::1/64 dev "${DEV}" nodad
ip -netns "${NS2}" addr add fd::2/64 dev "${DEV}" nodad
run_test() {
local readonly IP="$1"
local readonly CLOCK="$2"
local readonly TXARGS="$3"
local readonly RXARGS="$4"
if [[ "${IP}" == "4" ]]; then
local readonly SADDR="${SADDR4}"
local readonly DADDR="${DADDR4}"
elif [[ "${IP}" == "6" ]]; then
local readonly SADDR="${SADDR6}"
local readonly DADDR="${DADDR6}"
else
echo "Invalid IP version ${IP}"
exit 1
fi
local readonly START="$(date +%s%N --date="+ 0.1 seconds")"
ip netns exec "${NS2}" "${BIN}" -"${IP}" -c "${CLOCK}" -t "${START}" -S "${SADDR}" -D "${DADDR}" "${RXARGS}" -r &
ip netns exec "${NS1}" "${BIN}" -"${IP}" -c "${CLOCK}" -t "${START}" -S "${SADDR}" -D "${DADDR}" "${TXARGS}"
wait "$!"
}
do_test() {
run_test $@
[ $? -ne 0 ] && ret=1
}
do_fail_test() {
run_test $@
[ $? -eq 0 ] && ret=1
}
ip netns exec "${NS1}" tc qdisc add dev "${DEV}" root fq
set +e
ret=0
do_test 4 mono a,-1 a,-1
do_test 6 mono a,0 a,0
do_test 6 mono a,10 a,10
do_test 4 mono a,10,b,20 a,10,b,20
do_test 6 mono a,20,b,10 b,20,a,20
if ip netns exec "${NS1}" tc qdisc replace dev "${DEV}" root etf clockid CLOCK_TAI delta 400000; then
do_fail_test 4 tai a,-1 a,-1
do_fail_test 6 tai a,0 a,0
do_test 6 tai a,10 a,10
do_test 4 tai a,10,b,20 a,10,b,20
do_test 6 tai a,20,b,10 b,10,a,20
else
echo "tc ($(tc -V)) does not support qdisc etf. skipping"
[ $ret -eq 0 ] && ret=$ksft_skip
fi
if [ $ret -eq 0 ]; then
echo OK. All tests passed
elif [[ $ret -ne $ksft_skip && -n "$KSFT_MACHINE_SLOW" ]]; then
echo "Ignoring errors due to slow environment" 1>&2
ret=0
fi
exit $ret