From d9c1e7dc4adab28e07774ed23b137319b1c75241 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Mon, 4 May 2026 13:38:32 -0400 Subject: selftests: net: py: support cmd verifying expected failure Support negative tests, where cmd raises an exception if the command succeeded. Add optional argument expect_fail to cmd and bkg. Where fail fails the test on unexpected error, expect_fail fails it on unexpected success. Both fail on negative return code. Python subprocess may set a negative return code on process crash or timeout. Those are never anticipated failures. Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260504174056.565319-2-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/lib/py/utils.py | 39 +++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py index 6c44a3d2bbf7..b68d4607114d 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -23,6 +23,10 @@ class CmdExitFailure(Exception): self.cmd = cmd_obj +class CmdExitZeroFailure(CmdExitFailure): + """ Command succeeded (returned zero exit code), but expected failure. """ + + def fd_read_timeout(fd, timeout): rlist, _, _ = select.select([fd], [], [], timeout) if rlist: @@ -39,8 +43,9 @@ class cmd: Use bkg() instead to run a command in the background. """ - def __init__(self, comm, shell=None, fail=True, ns=None, background=False, - host=None, timeout=5, ksft_ready=None, ksft_wait=None): + def __init__(self, comm, shell=None, fail=True, expect_fail=False, ns=None, + background=False, host=None, timeout=5, ksft_ready=None, + ksft_wait=None): if ns: comm = f'ip netns exec {ns} ' + comm @@ -88,7 +93,8 @@ class cmd: self._process_terminate(terminate=terminate, timeout=1) raise CmdInitFailure("Did not receive ready message", self) if not background: - self.process(terminate=False, fail=fail, timeout=timeout) + self.process(terminate=False, fail=fail, expect_fail=expect_fail, + timeout=timeout) def _process_terminate(self, terminate, timeout): if terminate: @@ -102,7 +108,7 @@ class cmd: return stdout, stderr - def process(self, terminate=True, fail=None, timeout=5): + def process(self, terminate=True, fail=None, expect_fail=False, timeout=5): if fail is None: fail = not terminate @@ -111,10 +117,19 @@ class cmd: stdout, stderr = self._process_terminate(terminate=terminate, timeout=timeout) - if self.proc.returncode != 0 and fail: + + # Fail on unexpected test failure if fail. + # Fail on unexpected test success if expect_fail. + # Fail on negative returncode if either: + # Set by subprocess on crash or signal, this is never expected failure. + if (self.proc.returncode != 0 and fail or + (self.proc.returncode < 0 and expect_fail)): if len(stderr) > 0 and stderr[-1] == "\n": stderr = stderr[:-1] raise CmdExitFailure("Command failed", self) + elif self.proc.returncode == 0 and expect_fail: + raise CmdExitZeroFailure("Command succeeded (expected fail)", self) + def __repr__(self): def str_fmt(name, s): @@ -157,14 +172,17 @@ class bkg(cmd): with bkg("my_binary", ksft_wait=5): """ - def __init__(self, comm, shell=None, fail=None, ns=None, host=None, - exit_wait=False, ksft_ready=None, ksft_wait=None): + def __init__(self, comm, shell=None, fail=None, expect_fail=None, + ns=None, host=None, exit_wait=False, ksft_ready=None, + ksft_wait=None): super().__init__(comm, background=True, - shell=shell, fail=fail, ns=ns, host=host, - ksft_ready=ksft_ready, ksft_wait=ksft_wait) + shell=shell, fail=fail, expect_fail=expect_fail, + ns=ns, host=host, ksft_ready=ksft_ready, + ksft_wait=ksft_wait) self.terminate = not exit_wait and not ksft_wait self._exit_wait = exit_wait self.check_fail = fail + self.expect_fail = expect_fail if shell and self.terminate: print("# Warning: combining shell and terminate is risky!") @@ -179,7 +197,8 @@ class bkg(cmd): # since forcing termination silences failures with fail=None if self.proc.poll() is None: terminate = terminate or (self._exit_wait and ex_type is not None) - return self.process(terminate=terminate, fail=self.check_fail) + return self.process(terminate=terminate, fail=self.check_fail, + expect_fail=self.expect_fail) GLOBAL_DEFER_QUEUE = [] -- cgit v1.2.3 From 49bf9e5e7c7c47e21d8a082bbb311fb17b414d54 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Mon, 4 May 2026 13:38:33 -0400 Subject: selftests: net: py: add tc utility Add a wrapper similar to existing ip, ethtool, ... commands. Tc takes a slightly different syntax. Account for that. The first user is the next patch in this series, converting so_txtime to drv-net. Pacing offload is supported by selected qdiscs only. Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260504174056.565319-3-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/lib/py/__init__.py | 5 +++-- tools/testing/selftests/net/lib/py/__init__.py | 4 ++-- tools/testing/selftests/net/lib/py/utils.py | 12 +++++++++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py index 2b5ec0505672..09aac4ce67bc 100644 --- a/tools/testing/selftests/drivers/net/lib/py/__init__.py +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py @@ -23,7 +23,8 @@ try: NlError, RtnlFamily, DevlinkFamily, PSPFamily, Netlink from net.lib.py import CmdExitFailure from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \ - fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, wait_file + fd_read_timeout, ip, rand_port, rand_ports, tc, wait_port_listen, \ + wait_file from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \ @@ -36,7 +37,7 @@ try: "NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily", "Netlink", "CmdExitFailure", "bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool", - "fd_read_timeout", "ip", "rand_port", "rand_ports", + "fd_read_timeout", "ip", "rand_port", "rand_ports", "tc", "wait_port_listen", "wait_file", "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py index 7c81d86a7e97..64a8c1ed4950 100644 --- a/tools/testing/selftests/net/lib/py/__init__.py +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -14,7 +14,7 @@ from .netns import NetNS, NetNSEnter from .nsim import NetdevSim, NetdevSimDev from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \ bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \ - wait_file, tool + wait_file, tool, tc from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from .ynl import NlError, NlctrlFamily, YnlFamily, \ EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily @@ -29,7 +29,7 @@ __all__ = ["KSRC", "NetNS", "NetNSEnter", "CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer", "bpftool", "ip", "ethtool", "bpftrace", "rand_port", "rand_ports", - "wait_port_listen", "wait_file", "tool", + "wait_port_listen", "wait_file", "tool", "tc", "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "NetdevSim", "NetdevSimDev", "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError", diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py index b68d4607114d..be9408a77168 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -239,7 +239,10 @@ class defer: def tool(name, args, json=None, ns=None, host=None): cmd_str = name + ' ' if json: - cmd_str += '--json ' + if name == 'tc': + cmd_str += '-json ' + else: + cmd_str += '--json ' cmd_str += args cmd_obj = cmd(cmd_str, ns=ns, host=host) if json: @@ -257,6 +260,13 @@ def ip(args, json=None, ns=None, host=None): return tool('ip', args, json=json, host=host) +def tc(args, json=None, ns=None, host=None): + """ Helper to call tc with standard set of optional args. """ + if ns: + args = f'-netns {ns} ' + args + return tool('tc', args, json=json, host=host) + + def ethtool(args, json=None, ns=None, host=None): return tool('ethtool', args, json=json, ns=ns, host=host) -- cgit v1.2.3 From 5c6baef3885c0e16e63d432fe31aa6f7e98e18dc Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Mon, 4 May 2026 13:38:34 -0400 Subject: 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 ---- 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 --- tools/testing/selftests/drivers/net/.gitignore | 1 + tools/testing/selftests/drivers/net/Makefile | 2 + tools/testing/selftests/drivers/net/config | 2 + tools/testing/selftests/drivers/net/so_txtime.c | 532 +++++++++++++++++++++++ tools/testing/selftests/drivers/net/so_txtime.py | 96 ++++ tools/testing/selftests/net/.gitignore | 1 - tools/testing/selftests/net/Makefile | 2 - tools/testing/selftests/net/so_txtime.c | 517 ---------------------- tools/testing/selftests/net/so_txtime.sh | 110 ----- 9 files changed, 633 insertions(+), 630 deletions(-) create mode 100644 tools/testing/selftests/drivers/net/so_txtime.c create mode 100755 tools/testing/selftests/drivers/net/so_txtime.py delete mode 100644 tools/testing/selftests/net/so_txtime.c delete mode 100755 tools/testing/selftests/net/so_txtime.sh diff --git a/tools/testing/selftests/drivers/net/.gitignore b/tools/testing/selftests/drivers/net/.gitignore index 585ecb4d5dc4..e5314ce4bb2d 100644 --- a/tools/testing/selftests/drivers/net/.gitignore +++ b/tools/testing/selftests/drivers/net/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only napi_id_helper psp_responder +so_txtime diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile index b72080c6d06b..d5bf4cb638a8 100644 --- a/tools/testing/selftests/drivers/net/Makefile +++ b/tools/testing/selftests/drivers/net/Makefile @@ -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 diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index fd16994366f4..2309109a94ec 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -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 diff --git a/tools/testing/selftests/drivers/net/so_txtime.c b/tools/testing/selftests/drivers/net/so_txtime.c new file mode 100644 index 000000000000..b6930883569b --- /dev/null +++ b/tools/testing/selftests/drivers/net/so_txtime.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test the SO_TXTIME API + * + * Takes a stream of { payload, delivery time }[], to be sent across two + * processes. Start this program on two separate network namespaces or + * connected hosts, one instance in transmit mode and the other in receive + * mode using the '-r' option. Receiver will compare arrival timestamps to + * the expected stream. Sender will read transmit timestamps from the error + * queue. The streams can differ due to out-of-order delivery and drops. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest.h" + +static int cfg_clockid = CLOCK_TAI; +static uint16_t cfg_port = 8000; +static int cfg_variance_us = 4000; +static uint64_t cfg_start_time_ns; +static int cfg_mark; +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; + int64_t delay_us; +}; + +#define MAX_NUM_PKT 8 +static struct timed_send cfg_buf[MAX_NUM_PKT]; +static int cfg_num_pkt; + +static int cfg_errq_level; +static int cfg_errq_type; + +static struct sockaddr_storage cfg_dst_addr; +static struct sockaddr_storage cfg_src_addr; +static socklen_t cfg_alen; + +static uint64_t gettime_ns(clockid_t clock) +{ + struct timespec ts; + + if (clock_gettime(clock, &ts)) + error(1, errno, "gettime"); + + return ts.tv_sec * (1000ULL * 1000 * 1000) + ts.tv_nsec; +} + +static void do_send_one(int fdt, struct timed_send *ts) +{ + char control[CMSG_SPACE(sizeof(uint64_t))]; + struct msghdr msg = {0}; + struct iovec iov = {0}; + struct cmsghdr *cm; + uint64_t tdeliver; + int ret; + + iov.iov_base = &ts->data; + iov.iov_len = 1; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = (struct sockaddr *)&cfg_dst_addr; + msg.msg_namelen = cfg_alen; + + if (ts->delay_us >= 0) { + memset(control, 0, sizeof(control)); + msg.msg_control = &control; + msg.msg_controllen = sizeof(control); + + tdeliver = glob_tstart + ts->delay_us * 1000; + tdeliver_max = tdeliver_max > tdeliver ? + tdeliver_max : tdeliver; + + cm = CMSG_FIRSTHDR(&msg); + cm->cmsg_level = SOL_SOCKET; + cm->cmsg_type = SCM_TXTIME; + cm->cmsg_len = CMSG_LEN(sizeof(tdeliver)); + memcpy(CMSG_DATA(cm), &tdeliver, sizeof(tdeliver)); + } + + ret = sendmsg(fdt, &msg, 0); + if (ret == -1) + error(1, errno, "write"); + if (ret == 0) + error(1, 0, "write: 0B"); + +} + +static void do_recv_one(int fdr, struct timed_send *ts) +{ + int64_t tstop, texpect; + char rbuf[2]; + int ret; + + ret = recv(fdr, rbuf, sizeof(rbuf), 0); + if (ret == -1 && errno == EAGAIN) + error(1, EAGAIN, "recv: timeout"); + if (ret == -1) + error(1, errno, "read"); + if (ret != 1) + error(1, 0, "read: %dB", ret); + + tstop = (gettime_ns(cfg_clockid) - glob_tstart) / 1000; + texpect = ts->delay_us >= 0 ? ts->delay_us : 0; + + fprintf(stderr, "payload:%c delay:%lld expected:%lld (us)\n", + rbuf[0], (long long)tstop, (long long)texpect); + + 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")) + errors++; + } +} + +static void do_recv_verify_empty(int fdr) +{ + char rbuf[1]; + int ret; + + ret = recv(fdr, rbuf, sizeof(rbuf), 0); + if (ret != -1 || errno != EAGAIN) + error(1, 0, "recv: not empty as expected (%d, %d)", ret, errno); +} + +static int do_recv_errqueue_timeout(int fdt) +{ + char control[CMSG_SPACE(sizeof(struct sock_extended_err)) + + CMSG_SPACE(sizeof(struct sockaddr_in6))] = {0}; + char data[sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + + sizeof(struct udphdr) + 1]; + struct sock_extended_err *err; + int ret, num_tstamp = 0; + struct msghdr msg = {0}; + struct iovec iov = {0}; + struct cmsghdr *cm; + int64_t tstamp = 0; + + iov.iov_base = data; + iov.iov_len = sizeof(data); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + while (1) { + const char *reason = NULL; + + ret = recvmsg(fdt, &msg, MSG_ERRQUEUE); + if (ret == -1 && errno == EAGAIN) + break; + if (ret == -1) + error(1, errno, "errqueue"); + if (msg.msg_flags != MSG_ERRQUEUE) + error(1, 0, "errqueue: flags 0x%x\n", msg.msg_flags); + + cm = CMSG_FIRSTHDR(&msg); + if (cm->cmsg_level != cfg_errq_level || + cm->cmsg_type != cfg_errq_type) + error(1, 0, "errqueue: type 0x%x.0x%x\n", + cm->cmsg_level, cm->cmsg_type); + + err = (struct sock_extended_err *)CMSG_DATA(cm); + if (err->ee_origin != SO_EE_ORIGIN_TXTIME) + error(1, 0, "errqueue: origin 0x%x\n", err->ee_origin); + + switch (err->ee_errno) { + case ECANCELED: + if (err->ee_code != SO_EE_CODE_TXTIME_MISSED) + error(1, 0, "errqueue: unknown ECANCELED %u\n", + err->ee_code); + reason = "missed txtime"; + break; + case EINVAL: + if (err->ee_code != SO_EE_CODE_TXTIME_INVALID_PARAM) + error(1, 0, "errqueue: unknown EINVAL %u\n", + err->ee_code); + reason = "invalid txtime"; + break; + default: + error(1, 0, "errqueue: errno %u code %u\n", + err->ee_errno, err->ee_code); + } + + tstamp = ((int64_t) err->ee_data) << 32 | err->ee_info; + tstamp -= (int64_t) glob_tstart; + tstamp /= 1000 * 1000; + fprintf(stderr, "send: pkt %c at %" PRId64 "ms dropped: %s\n", + data[ret - 1], tstamp, reason); + + msg.msg_flags = 0; + msg.msg_controllen = sizeof(control); + num_tstamp++; + } + + return num_tstamp; +} + +static void recv_errqueue_msgs(int fdt) +{ + struct pollfd pfd = { .fd = fdt, .events = POLLERR }; + const int timeout_ms = 10; + int ret, num_tstamp = 0; + + do { + ret = poll(&pfd, 1, timeout_ms); + if (ret == -1) + error(1, errno, "poll"); + + if (ret && (pfd.revents & POLLERR)) + num_tstamp += do_recv_errqueue_timeout(fdt); + + if (num_tstamp == cfg_num_pkt) + break; + + } while (gettime_ns(cfg_clockid) < tdeliver_max); +} + +static void start_time_wait(void) +{ + uint64_t now; + int err; + + if (!cfg_start_time_ns) + return; + + now = gettime_ns(CLOCK_REALTIME); + 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) + error(1, errno, "usleep"); +} + +static void setsockopt_txtime(int fd) +{ + struct sock_txtime so_txtime_val = { .clockid = cfg_clockid }; + struct sock_txtime so_txtime_val_read = { 0 }; + socklen_t vallen = sizeof(so_txtime_val); + + so_txtime_val.flags = SOF_TXTIME_REPORT_ERRORS; + + if (setsockopt(fd, SOL_SOCKET, SO_TXTIME, + &so_txtime_val, sizeof(so_txtime_val))) + error(1, errno, "setsockopt txtime"); + + if (getsockopt(fd, SOL_SOCKET, SO_TXTIME, + &so_txtime_val_read, &vallen)) + error(1, errno, "getsockopt txtime"); + + if (vallen != sizeof(so_txtime_val) || + memcmp(&so_txtime_val, &so_txtime_val_read, vallen)) + error(1, 0, "getsockopt txtime: mismatch"); +} + +static int setup_tx(struct sockaddr *addr, socklen_t alen) +{ + int fd; + + fd = socket(addr->sa_family, SOCK_DGRAM, 0); + if (fd == -1) + error(1, errno, "socket t"); + + if (connect(fd, addr, alen)) + error(1, errno, "connect"); + + setsockopt_txtime(fd); + + if (cfg_mark && + setsockopt(fd, SOL_SOCKET, SO_MARK, &cfg_mark, sizeof(cfg_mark))) + error(1, errno, "setsockopt mark"); + + return fd; +} + +static int setup_rx(struct sockaddr *addr, socklen_t alen) +{ + struct timeval tv = { .tv_usec = 100 * 1000 }; + int fd; + + fd = socket(addr->sa_family, SOCK_DGRAM, 0); + if (fd == -1) + error(1, errno, "socket r"); + + if (bind(fd, addr, alen)) + error(1, errno, "bind"); + + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) + error(1, errno, "setsockopt rcv timeout"); + + return fd; +} + +static void do_test_tx(struct sockaddr *addr, socklen_t alen) +{ + int fdt, i; + + fprintf(stderr, "\nSO_TXTIME ipv%c clock %s\n", + addr->sa_family == PF_INET ? '4' : '6', + cfg_clockid == CLOCK_TAI ? "tai" : "monotonic"); + + fdt = setup_tx(addr, alen); + + start_time_wait(); + glob_tstart = gettime_ns(cfg_clockid); + + for (i = 0; i < cfg_num_pkt; i++) + do_send_one(fdt, &cfg_buf[i]); + + recv_errqueue_msgs(fdt); + + if (close(fdt)) + error(1, errno, "close t"); +} + +static void do_test_rx(struct sockaddr *addr, socklen_t alen) +{ + int fdr, i; + + fdr = setup_rx(addr, alen); + + start_time_wait(); + glob_tstart = gettime_ns(cfg_clockid); + + for (i = 0; i < cfg_num_pkt; i++) + do_recv_one(fdr, &cfg_buf[i]); + + do_recv_verify_empty(fdr); + + if (close(fdr)) + error(1, errno, "close r"); +} + +static void setup_sockaddr(int domain, const char *str_addr, + struct sockaddr_storage *sockaddr) +{ + struct sockaddr_in6 *addr6 = (void *) sockaddr; + struct sockaddr_in *addr4 = (void *) sockaddr; + + switch (domain) { + case PF_INET: + memset(addr4, 0, sizeof(*addr4)); + addr4->sin_family = AF_INET; + addr4->sin_port = htons(cfg_port); + if (str_addr && + inet_pton(AF_INET, str_addr, &(addr4->sin_addr)) != 1) + error(1, 0, "ipv4 parse error: %s", str_addr); + break; + case PF_INET6: + memset(addr6, 0, sizeof(*addr6)); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = htons(cfg_port); + if (str_addr && + inet_pton(AF_INET6, str_addr, &(addr6->sin6_addr)) != 1) + error(1, 0, "ipv6 parse error: %s", str_addr); + break; + } +} + +static int parse_io(const char *optarg, struct timed_send *array) +{ + char *arg, *tok; + int aoff = 0; + + arg = strdup(optarg); + if (!arg) + error(1, errno, "strdup"); + + while ((tok = strtok(arg, ","))) { + arg = NULL; /* only pass non-zero on first call */ + + if (aoff / 2 == MAX_NUM_PKT) + error(1, 0, "exceeds max pkt count (%d)", MAX_NUM_PKT); + + if (aoff & 1) { /* parse delay */ + array->delay_us = strtol(tok, NULL, 0) * 1000; + array++; + } else { /* parse character */ + array->data = tok[0]; + } + + aoff++; + } + + free(arg); + + return aoff / 2; +} + +static void usage(const char *progname) +{ + fprintf(stderr, "\nUsage: %s [options] \n" + "Options:\n" + " -4 only IPv4\n" + " -6 only IPv6\n" + " -c monotonic or tai (default)\n" + " -D destination IP address (server)\n" + " -S source IP address (client)\n" + " -r run rx mode\n" + " -t start time (UTC nanoseconds)\n" + " -m socket mark\n" + "\n", + progname); + exit(1); +} + +static void parse_opts(int argc, char **argv) +{ + char *daddr = NULL, *saddr = NULL; + int domain = PF_UNSPEC; + int c; + + while ((c = getopt(argc, argv, "46c:S:D:rt:m:")) != -1) { + switch (c) { + case '4': + if (domain != PF_UNSPEC) + error(1, 0, "Pass one of -4 or -6"); + domain = PF_INET; + cfg_alen = sizeof(struct sockaddr_in); + cfg_errq_level = SOL_IP; + cfg_errq_type = IP_RECVERR; + break; + case '6': + if (domain != PF_UNSPEC) + error(1, 0, "Pass one of -4 or -6"); + domain = PF_INET6; + cfg_alen = sizeof(struct sockaddr_in6); + cfg_errq_level = SOL_IPV6; + cfg_errq_type = IPV6_RECVERR; + break; + case 'c': + if (!strcmp(optarg, "tai")) + cfg_clockid = CLOCK_TAI; + else if (!strcmp(optarg, "monotonic") || + !strcmp(optarg, "mono")) + cfg_clockid = CLOCK_MONOTONIC; + else + error(1, 0, "unknown clock id %s", optarg); + break; + case 'S': + saddr = optarg; + break; + case 'D': + daddr = optarg; + break; + case 'r': + cfg_rx = true; + break; + case 't': + cfg_start_time_ns = strtoll(optarg, NULL, 0); + break; + case 'm': + cfg_mark = strtol(optarg, NULL, 0); + break; + default: + usage(argv[0]); + } + } + + if (argc - optind != 1) + usage(argv[0]); + + if (domain == PF_UNSPEC) + error(1, 0, "Pass one of -4 or -6"); + if (!daddr) + error(1, 0, "-D required\n"); + if (!cfg_rx && !saddr) + error(1, 0, "-S required\n"); + + setup_sockaddr(domain, daddr, &cfg_dst_addr); + setup_sockaddr(domain, saddr, &cfg_src_addr); + + cfg_num_pkt = parse_io(argv[optind], cfg_buf); +} + +int main(int argc, char **argv) +{ + parse_opts(argc, argv); + + if (cfg_rx) + do_test_rx((void *)&cfg_dst_addr, cfg_alen); + else + do_test_tx((void *)&cfg_src_addr, cfg_alen); + + if (errors) { + fprintf(stderr, "FAIL: %d errors\n", errors); + return KSFT_FAIL; + } + + return KSFT_PASS; +} diff --git a/tools/testing/selftests/drivers/net/so_txtime.py b/tools/testing/selftests/drivers/net/so_txtime.py new file mode 100755 index 000000000000..5b54ce76dff4 --- /dev/null +++ b/tools/testing/selftests/drivers/net/so_txtime.py @@ -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() diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore index 97ad4d551d44..02ad4c99a2b4 100644 --- a/tools/testing/selftests/net/.gitignore +++ b/tools/testing/selftests/net/.gitignore @@ -40,7 +40,6 @@ skf_net_off socket so_incoming_cpu so_netns_cookie -so_txtime so_rcv_listener stress_reuseport_listen tap diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index baa30287cf22..88c7573a8295 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -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 \ diff --git a/tools/testing/selftests/net/so_txtime.c b/tools/testing/selftests/net/so_txtime.c deleted file mode 100644 index b76df1efc2ef..000000000000 --- a/tools/testing/selftests/net/so_txtime.c +++ /dev/null @@ -1,517 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Test the SO_TXTIME API - * - * Takes a stream of { payload, delivery time }[], to be sent across two - * processes. Start this program on two separate network namespaces or - * connected hosts, one instance in transmit mode and the other in receive - * mode using the '-r' option. Receiver will compare arrival timestamps to - * the expected stream. Sender will read transmit timestamps from the error - * queue. The streams can differ due to out-of-order delivery and drops. - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int cfg_clockid = CLOCK_TAI; -static uint16_t cfg_port = 8000; -static int cfg_variance_us = 4000; -static uint64_t cfg_start_time_ns; -static int cfg_mark; -static bool cfg_rx; - -static uint64_t glob_tstart; -static uint64_t tdeliver_max; - -/* encode one timed transmission (of a 1B payload) */ -struct timed_send { - char data; - int64_t delay_us; -}; - -#define MAX_NUM_PKT 8 -static struct timed_send cfg_buf[MAX_NUM_PKT]; -static int cfg_num_pkt; - -static int cfg_errq_level; -static int cfg_errq_type; - -static struct sockaddr_storage cfg_dst_addr; -static struct sockaddr_storage cfg_src_addr; -static socklen_t cfg_alen; - -static uint64_t gettime_ns(clockid_t clock) -{ - struct timespec ts; - - if (clock_gettime(clock, &ts)) - error(1, errno, "gettime"); - - return ts.tv_sec * (1000ULL * 1000 * 1000) + ts.tv_nsec; -} - -static void do_send_one(int fdt, struct timed_send *ts) -{ - char control[CMSG_SPACE(sizeof(uint64_t))]; - struct msghdr msg = {0}; - struct iovec iov = {0}; - struct cmsghdr *cm; - uint64_t tdeliver; - int ret; - - iov.iov_base = &ts->data; - iov.iov_len = 1; - - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_name = (struct sockaddr *)&cfg_dst_addr; - msg.msg_namelen = cfg_alen; - - if (ts->delay_us >= 0) { - memset(control, 0, sizeof(control)); - msg.msg_control = &control; - msg.msg_controllen = sizeof(control); - - tdeliver = glob_tstart + ts->delay_us * 1000; - tdeliver_max = tdeliver_max > tdeliver ? - tdeliver_max : tdeliver; - - cm = CMSG_FIRSTHDR(&msg); - cm->cmsg_level = SOL_SOCKET; - cm->cmsg_type = SCM_TXTIME; - cm->cmsg_len = CMSG_LEN(sizeof(tdeliver)); - memcpy(CMSG_DATA(cm), &tdeliver, sizeof(tdeliver)); - } - - ret = sendmsg(fdt, &msg, 0); - if (ret == -1) - error(1, errno, "write"); - if (ret == 0) - error(1, 0, "write: 0B"); - -} - -static void do_recv_one(int fdr, struct timed_send *ts) -{ - int64_t tstop, texpect; - char rbuf[2]; - int ret; - - ret = recv(fdr, rbuf, sizeof(rbuf), 0); - if (ret == -1 && errno == EAGAIN) - error(1, EAGAIN, "recv: timeout"); - if (ret == -1) - error(1, errno, "read"); - if (ret != 1) - error(1, 0, "read: %dB", ret); - - tstop = (gettime_ns(cfg_clockid) - glob_tstart) / 1000; - texpect = ts->delay_us >= 0 ? ts->delay_us : 0; - - 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 (llabs(tstop - texpect) > cfg_variance_us) { - fprintf(stderr, "exceeds variance (%d us)\n", cfg_variance_us); - if (!getenv("KSFT_MACHINE_SLOW")) - exit(1); - } -} - -static void do_recv_verify_empty(int fdr) -{ - char rbuf[1]; - int ret; - - ret = recv(fdr, rbuf, sizeof(rbuf), 0); - if (ret != -1 || errno != EAGAIN) - error(1, 0, "recv: not empty as expected (%d, %d)", ret, errno); -} - -static int do_recv_errqueue_timeout(int fdt) -{ - char control[CMSG_SPACE(sizeof(struct sock_extended_err)) + - CMSG_SPACE(sizeof(struct sockaddr_in6))] = {0}; - char data[sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + - sizeof(struct udphdr) + 1]; - struct sock_extended_err *err; - int ret, num_tstamp = 0; - struct msghdr msg = {0}; - struct iovec iov = {0}; - struct cmsghdr *cm; - int64_t tstamp = 0; - - iov.iov_base = data; - iov.iov_len = sizeof(data); - - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - while (1) { - const char *reason = NULL; - - ret = recvmsg(fdt, &msg, MSG_ERRQUEUE); - if (ret == -1 && errno == EAGAIN) - break; - if (ret == -1) - error(1, errno, "errqueue"); - if (msg.msg_flags != MSG_ERRQUEUE) - error(1, 0, "errqueue: flags 0x%x\n", msg.msg_flags); - - cm = CMSG_FIRSTHDR(&msg); - if (cm->cmsg_level != cfg_errq_level || - cm->cmsg_type != cfg_errq_type) - error(1, 0, "errqueue: type 0x%x.0x%x\n", - cm->cmsg_level, cm->cmsg_type); - - err = (struct sock_extended_err *)CMSG_DATA(cm); - if (err->ee_origin != SO_EE_ORIGIN_TXTIME) - error(1, 0, "errqueue: origin 0x%x\n", err->ee_origin); - - switch (err->ee_errno) { - case ECANCELED: - if (err->ee_code != SO_EE_CODE_TXTIME_MISSED) - error(1, 0, "errqueue: unknown ECANCELED %u\n", - err->ee_code); - reason = "missed txtime"; - break; - case EINVAL: - if (err->ee_code != SO_EE_CODE_TXTIME_INVALID_PARAM) - error(1, 0, "errqueue: unknown EINVAL %u\n", - err->ee_code); - reason = "invalid txtime"; - break; - default: - error(1, 0, "errqueue: errno %u code %u\n", - err->ee_errno, err->ee_code); - } - - tstamp = ((int64_t) err->ee_data) << 32 | err->ee_info; - tstamp -= (int64_t) glob_tstart; - tstamp /= 1000 * 1000; - fprintf(stderr, "send: pkt %c at %" PRId64 "ms dropped: %s\n", - data[ret - 1], tstamp, reason); - - msg.msg_flags = 0; - msg.msg_controllen = sizeof(control); - num_tstamp++; - } - - return num_tstamp; -} - -static void recv_errqueue_msgs(int fdt) -{ - struct pollfd pfd = { .fd = fdt, .events = POLLERR }; - const int timeout_ms = 10; - int ret, num_tstamp = 0; - - do { - ret = poll(&pfd, 1, timeout_ms); - if (ret == -1) - error(1, errno, "poll"); - - if (ret && (pfd.revents & POLLERR)) - num_tstamp += do_recv_errqueue_timeout(fdt); - - if (num_tstamp == cfg_num_pkt) - break; - - } while (gettime_ns(cfg_clockid) < tdeliver_max); -} - -static void start_time_wait(void) -{ - uint64_t now; - int err; - - if (!cfg_start_time_ns) - return; - - now = gettime_ns(CLOCK_REALTIME); - if (cfg_start_time_ns < now) - return; - - err = usleep((cfg_start_time_ns - now) / 1000); - if (err) - error(1, errno, "usleep"); -} - -static void setsockopt_txtime(int fd) -{ - struct sock_txtime so_txtime_val = { .clockid = cfg_clockid }; - struct sock_txtime so_txtime_val_read = { 0 }; - socklen_t vallen = sizeof(so_txtime_val); - - so_txtime_val.flags = SOF_TXTIME_REPORT_ERRORS; - - if (setsockopt(fd, SOL_SOCKET, SO_TXTIME, - &so_txtime_val, sizeof(so_txtime_val))) - error(1, errno, "setsockopt txtime"); - - if (getsockopt(fd, SOL_SOCKET, SO_TXTIME, - &so_txtime_val_read, &vallen)) - error(1, errno, "getsockopt txtime"); - - if (vallen != sizeof(so_txtime_val) || - memcmp(&so_txtime_val, &so_txtime_val_read, vallen)) - error(1, 0, "getsockopt txtime: mismatch"); -} - -static int setup_tx(struct sockaddr *addr, socklen_t alen) -{ - int fd; - - fd = socket(addr->sa_family, SOCK_DGRAM, 0); - if (fd == -1) - error(1, errno, "socket t"); - - if (connect(fd, addr, alen)) - error(1, errno, "connect"); - - setsockopt_txtime(fd); - - if (cfg_mark && - setsockopt(fd, SOL_SOCKET, SO_MARK, &cfg_mark, sizeof(cfg_mark))) - error(1, errno, "setsockopt mark"); - - return fd; -} - -static int setup_rx(struct sockaddr *addr, socklen_t alen) -{ - struct timeval tv = { .tv_usec = 100 * 1000 }; - int fd; - - fd = socket(addr->sa_family, SOCK_DGRAM, 0); - if (fd == -1) - error(1, errno, "socket r"); - - if (bind(fd, addr, alen)) - error(1, errno, "bind"); - - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) - error(1, errno, "setsockopt rcv timeout"); - - return fd; -} - -static void do_test_tx(struct sockaddr *addr, socklen_t alen) -{ - int fdt, i; - - fprintf(stderr, "\nSO_TXTIME ipv%c clock %s\n", - addr->sa_family == PF_INET ? '4' : '6', - cfg_clockid == CLOCK_TAI ? "tai" : "monotonic"); - - fdt = setup_tx(addr, alen); - - start_time_wait(); - glob_tstart = gettime_ns(cfg_clockid); - - for (i = 0; i < cfg_num_pkt; i++) - do_send_one(fdt, &cfg_buf[i]); - - recv_errqueue_msgs(fdt); - - if (close(fdt)) - error(1, errno, "close t"); -} - -static void do_test_rx(struct sockaddr *addr, socklen_t alen) -{ - int fdr, i; - - fdr = setup_rx(addr, alen); - - start_time_wait(); - glob_tstart = gettime_ns(cfg_clockid); - - for (i = 0; i < cfg_num_pkt; i++) - do_recv_one(fdr, &cfg_buf[i]); - - do_recv_verify_empty(fdr); - - if (close(fdr)) - error(1, errno, "close r"); -} - -static void setup_sockaddr(int domain, const char *str_addr, - struct sockaddr_storage *sockaddr) -{ - struct sockaddr_in6 *addr6 = (void *) sockaddr; - struct sockaddr_in *addr4 = (void *) sockaddr; - - switch (domain) { - case PF_INET: - memset(addr4, 0, sizeof(*addr4)); - addr4->sin_family = AF_INET; - addr4->sin_port = htons(cfg_port); - if (str_addr && - inet_pton(AF_INET, str_addr, &(addr4->sin_addr)) != 1) - error(1, 0, "ipv4 parse error: %s", str_addr); - break; - case PF_INET6: - memset(addr6, 0, sizeof(*addr6)); - addr6->sin6_family = AF_INET6; - addr6->sin6_port = htons(cfg_port); - if (str_addr && - inet_pton(AF_INET6, str_addr, &(addr6->sin6_addr)) != 1) - error(1, 0, "ipv6 parse error: %s", str_addr); - break; - } -} - -static int parse_io(const char *optarg, struct timed_send *array) -{ - char *arg, *tok; - int aoff = 0; - - arg = strdup(optarg); - if (!arg) - error(1, errno, "strdup"); - - while ((tok = strtok(arg, ","))) { - arg = NULL; /* only pass non-zero on first call */ - - if (aoff / 2 == MAX_NUM_PKT) - error(1, 0, "exceeds max pkt count (%d)", MAX_NUM_PKT); - - if (aoff & 1) { /* parse delay */ - array->delay_us = strtol(tok, NULL, 0) * 1000; - array++; - } else { /* parse character */ - array->data = tok[0]; - } - - aoff++; - } - - free(arg); - - return aoff / 2; -} - -static void usage(const char *progname) -{ - fprintf(stderr, "\nUsage: %s [options] \n" - "Options:\n" - " -4 only IPv4\n" - " -6 only IPv6\n" - " -c monotonic or tai (default)\n" - " -D destination IP address (server)\n" - " -S source IP address (client)\n" - " -r run rx mode\n" - " -t start time (UTC nanoseconds)\n" - " -m socket mark\n" - "\n", - progname); - exit(1); -} - -static void parse_opts(int argc, char **argv) -{ - char *daddr = NULL, *saddr = NULL; - int domain = PF_UNSPEC; - int c; - - while ((c = getopt(argc, argv, "46c:S:D:rt:m:")) != -1) { - switch (c) { - case '4': - if (domain != PF_UNSPEC) - error(1, 0, "Pass one of -4 or -6"); - domain = PF_INET; - cfg_alen = sizeof(struct sockaddr_in); - cfg_errq_level = SOL_IP; - cfg_errq_type = IP_RECVERR; - break; - case '6': - if (domain != PF_UNSPEC) - error(1, 0, "Pass one of -4 or -6"); - domain = PF_INET6; - cfg_alen = sizeof(struct sockaddr_in6); - cfg_errq_level = SOL_IPV6; - cfg_errq_type = IPV6_RECVERR; - break; - case 'c': - if (!strcmp(optarg, "tai")) - cfg_clockid = CLOCK_TAI; - else if (!strcmp(optarg, "monotonic") || - !strcmp(optarg, "mono")) - cfg_clockid = CLOCK_MONOTONIC; - else - error(1, 0, "unknown clock id %s", optarg); - break; - case 'S': - saddr = optarg; - break; - case 'D': - daddr = optarg; - break; - case 'r': - cfg_rx = true; - break; - case 't': - cfg_start_time_ns = strtoll(optarg, NULL, 0); - break; - case 'm': - cfg_mark = strtol(optarg, NULL, 0); - break; - default: - usage(argv[0]); - } - } - - if (argc - optind != 1) - usage(argv[0]); - - if (domain == PF_UNSPEC) - error(1, 0, "Pass one of -4 or -6"); - if (!daddr) - error(1, 0, "-D required\n"); - if (!cfg_rx && !saddr) - error(1, 0, "-S required\n"); - - setup_sockaddr(domain, daddr, &cfg_dst_addr); - setup_sockaddr(domain, saddr, &cfg_src_addr); - - cfg_num_pkt = parse_io(argv[optind], cfg_buf); -} - -int main(int argc, char **argv) -{ - parse_opts(argc, argv); - - if (cfg_rx) - do_test_rx((void *)&cfg_dst_addr, cfg_alen); - else - do_test_tx((void *)&cfg_src_addr, cfg_alen); - - return 0; -} diff --git a/tools/testing/selftests/net/so_txtime.sh b/tools/testing/selftests/net/so_txtime.sh deleted file mode 100755 index 5e861ad32a42..000000000000 --- a/tools/testing/selftests/net/so_txtime.sh +++ /dev/null @@ -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 -- cgit v1.2.3