Main objective of this series is to convert the gro.sh and toeplitz.sh tests to be "NIPA-compatible" - meaning make use of the Python env, which lets us run the tests against either netdevsim or a real device.
The tests seem to have been written with a different flow in mind. Namely they source different bash "setup" scripts depending on arguments passed to the test. While I have nothing against the use of bash and the overall architecture - the existing code needs quite a bit of work (don't assume MAC/IP addresses, support remote endpoint over SSH). If I'm the one fixing it, I'd rather convert them to our "simplistic" Python.
This series rewrites the tests in Python while addressing their shortcomings. The functionality of running the test over loopback on a real device is retained but with a different method of invocation (see the last patch).
Once again we are dealing with a script which run over a variety of protocols (combination of [ipv4, ipv6, ipip] x [tcp, udp]). The first 4 patches add support for test variants to our scripts. We use the term "variant" in the same sense as the C kselftest_harness.h - variant is just a set of static input arguments.
Note that neither GRO nor the Toeplitz test fully passes for me on any HW I have access to. But this is unrelated to the conversion. This series is not making any real functional changes to the tests, it is limited to improving the "test harness" scripts.
Jakub Kicinski (12): selftests: net: py: coding style improvements selftests: net: py: extract the case generation logic selftests: net: py: add test variants selftests: drv-net: xdp: use variants for qstat tests selftests: net: relocate gro and toeplitz tests to drivers/net selftests: net: py: support ksft ready without wait selftests: net: py: read ip link info about remote dev netdevsim: pass packets thru GRO on Rx selftests: drv-net: add a Python version of the GRO test selftests: drv-net: hw: convert the Toeplitz test to Python netdevsim: add loopback support selftests: net: remove old setup_* scripts
tools/testing/selftests/drivers/net/Makefile | 2 + .../testing/selftests/drivers/net/hw/Makefile | 6 +- tools/testing/selftests/net/Makefile | 7 - tools/testing/selftests/net/lib/Makefile | 1 + drivers/net/netdevsim/netdev.c | 26 ++- .../testing/selftests/{ => drivers}/net/gro.c | 5 +- .../{net => drivers/net/hw}/toeplitz.c | 7 +- .../testing/selftests/drivers/net/.gitignore | 1 + tools/testing/selftests/drivers/net/gro.py | 161 ++++++++++++++ .../selftests/drivers/net/hw/.gitignore | 3 +- .../drivers/net/hw/lib/py/__init__.py | 4 +- .../selftests/drivers/net/hw/toeplitz.py | 208 ++++++++++++++++++ .../selftests/drivers/net/lib/py/__init__.py | 4 +- .../selftests/drivers/net/lib/py/env.py | 2 + tools/testing/selftests/drivers/net/xdp.py | 42 ++-- tools/testing/selftests/net/.gitignore | 2 - tools/testing/selftests/net/gro.sh | 105 --------- .../selftests/net/lib/ksft_setup_loopback.sh | 111 ++++++++++ .../testing/selftests/net/lib/py/__init__.py | 5 +- tools/testing/selftests/net/lib/py/ksft.py | 93 ++++++-- tools/testing/selftests/net/lib/py/nsim.py | 2 +- tools/testing/selftests/net/lib/py/utils.py | 20 +- tools/testing/selftests/net/setup_loopback.sh | 120 ---------- tools/testing/selftests/net/setup_veth.sh | 45 ---- tools/testing/selftests/net/toeplitz.sh | 199 ----------------- .../testing/selftests/net/toeplitz_client.sh | 28 --- 26 files changed, 631 insertions(+), 578 deletions(-) rename tools/testing/selftests/{ => drivers}/net/gro.c (99%) rename tools/testing/selftests/{net => drivers/net/hw}/toeplitz.c (99%) create mode 100755 tools/testing/selftests/drivers/net/gro.py create mode 100755 tools/testing/selftests/drivers/net/hw/toeplitz.py delete mode 100755 tools/testing/selftests/net/gro.sh create mode 100755 tools/testing/selftests/net/lib/ksft_setup_loopback.sh delete mode 100644 tools/testing/selftests/net/setup_loopback.sh delete mode 100644 tools/testing/selftests/net/setup_veth.sh delete mode 100755 tools/testing/selftests/net/toeplitz.sh delete mode 100755 tools/testing/selftests/net/toeplitz_client.sh
We're about to add more features here and finding new issues with old ones in place is hard. Address ruff checks: - bare exceptions - f-string with no params - unused import
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/net/lib/py/ksft.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py index 83b1574f7719..56dd9bd060cd 100644 --- a/tools/testing/selftests/net/lib/py/ksft.py +++ b/tools/testing/selftests/net/lib/py/ksft.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: GPL-2.0
-import builtins import functools import inspect import signal @@ -163,7 +162,7 @@ KSFT_DISRUPTIVE = True entry = global_defer_queue.pop() try: entry.exec_only() - except: + except Exception: ksft_pr(f"Exception while handling defer / cleanup (callback {i} of {qlen_start})!") tb = traceback.format_exc() for line in tb.strip().split('\n'): @@ -181,7 +180,7 @@ KSFT_DISRUPTIVE = True @functools.wraps(func) def wrapper(*args, **kwargs): if not KSFT_DISRUPTIVE: - raise KsftSkipEx(f"marked as disruptive") + raise KsftSkipEx("marked as disruptive") return func(*args, **kwargs) return wrapper
@@ -199,7 +198,7 @@ KSFT_DISRUPTIVE = True return False try: return bool(int(value)) - except: + except Exception: raise Exception(f"failed to parse {name}")
if "DISRUPTIVE" in env:
In preparation for adding test variants move the test case collection logic to a dedicated function. New helper returns
(function, args, name, )
tuples. The main test loop can simply run them, not much logic or discernment needed.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/net/lib/py/ksft.py | 31 +++++++++++++++------- 1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py index 56dd9bd060cd..52c42c313cf2 100644 --- a/tools/testing/selftests/net/lib/py/ksft.py +++ b/tools/testing/selftests/net/lib/py/ksft.py @@ -135,7 +135,7 @@ KSFT_DISRUPTIVE = True time.sleep(sleep)
-def ktap_result(ok, cnt=1, case="", comment=""): +def ktap_result(ok, cnt=1, case_name="", comment=""): global KSFT_RESULT_ALL KSFT_RESULT_ALL = KSFT_RESULT_ALL and ok
@@ -145,8 +145,8 @@ KSFT_DISRUPTIVE = True res += "ok " res += str(cnt) + " " res += KSFT_MAIN_NAME - if case: - res += "." + str(case.__name__) + if case_name: + res += "." + case_name if comment: res += " # " + comment print(res, flush=True) @@ -219,9 +219,13 @@ KSFT_DISRUPTIVE = True ksft_pr(f"Ignoring SIGTERM (cnt: {term_cnt}), already exiting...")
-def ksft_run(cases=None, globs=None, case_pfx=None, args=()): - cases = cases or [] +def _ksft_generate_test_cases(cases, globs, case_pfx, args): + """Generate a flat list of (func, args, name) tuples"""
+ cases = cases or [] + test_cases = [] + + # If using the globs method find all relevant functions if globs and case_pfx: for key, value in globs.items(): if not callable(value): @@ -231,6 +235,15 @@ KSFT_DISRUPTIVE = True cases.append(value) break
+ for func in cases: + test_cases.append((func, args, func.__name__)) + + return test_cases + + +def ksft_run(cases=None, globs=None, case_pfx=None, args=()): + test_cases = _ksft_generate_test_cases(cases, globs, case_pfx, args) + global term_cnt term_cnt = 0 prev_sigterm = signal.signal(signal.SIGTERM, _ksft_intr) @@ -238,19 +251,19 @@ KSFT_DISRUPTIVE = True totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0}
print("TAP version 13", flush=True) - print("1.." + str(len(cases)), flush=True) + print("1.." + str(len(test_cases)), flush=True)
global KSFT_RESULT cnt = 0 stop = False - for case in cases: + for func, args, name in test_cases: KSFT_RESULT = True cnt += 1 comment = "" cnt_key = ""
try: - case(*args) + func(*args) except KsftSkipEx as e: comment = "SKIP " + str(e) cnt_key = 'skip' @@ -272,7 +285,7 @@ KSFT_DISRUPTIVE = True if not cnt_key: cnt_key = 'pass' if KSFT_RESULT else 'fail'
- ktap_result(KSFT_RESULT, cnt, case, comment=comment) + ktap_result(KSFT_RESULT, cnt, name, comment=comment) totals[cnt_key] += 1
if stop:
There's a lot of cases where we try to re-run the same code with different parameters. We currently need to either use a generator method or create a "main" case implementation which then gets called by trivial case functions:
def _test(x, y, z): ...
def case_int(): _test(1, 2, 3)
def case_str(): _test('a', 'b', 'c')
Add support for variants, similar to kselftests_harness.h and a lot of other frameworks. Variants can be added as decorator to test functions:
@ksft_variants([(1, 2, 3), ('a', 'b', 'c')]) def case(x, y, z): ...
ksft_run() will auto-generate case names: case.1_2_3 case.a_b_c
Because the names may not always be pretty (and to avoid forcing classes to implement case-friendly __str__()) add a wrapper class KsftNamedVariant which lets the user specify the name for the variant.
Note that ksft_run's args are still supported. ksft_run splices args and variant params together.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- .../drivers/net/hw/lib/py/__init__.py | 4 +- .../selftests/drivers/net/lib/py/__init__.py | 4 +- .../testing/selftests/net/lib/py/__init__.py | 5 +- tools/testing/selftests/net/lib/py/ksft.py | 57 ++++++++++++++++++- 4 files changed, 63 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py index fb010a48a5a1..0c61debf86fb 100644 --- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py +++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py @@ -25,7 +25,7 @@ KSFT_DIR = (Path(__file__).parent / "../../../../..").resolve() fd_read_timeout, ip, rand_port, wait_port_listen, wait_file from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \ - ksft_setup + ksft_setup, ksft_variants, KsftNamedVariant from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \ ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none from drivers.net.lib.py import GenerateTraffic, Remote @@ -40,7 +40,7 @@ KSFT_DIR = (Path(__file__).parent / "../../../../..").resolve() "wait_port_listen", "wait_file", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run", - "ksft_setup", + "ksft_setup", "ksft_variants", "KsftNamedVariant", "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt", "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt", "ksft_not_none", "ksft_not_none", diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py index b0c6300150fb..d9d035634a31 100644 --- a/tools/testing/selftests/drivers/net/lib/py/__init__.py +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py @@ -25,7 +25,7 @@ KSFT_DIR = (Path(__file__).parent / "../../../..").resolve() fd_read_timeout, ip, rand_port, wait_port_listen, wait_file from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \ - ksft_setup + ksft_setup, ksft_variants, KsftNamedVariant from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \ ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
@@ -38,7 +38,7 @@ KSFT_DIR = (Path(__file__).parent / "../../../..").resolve() "wait_port_listen", "wait_file", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run", - "ksft_setup", + "ksft_setup", "ksft_variants", "KsftNamedVariant", "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt", "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt", "ksft_not_none", "ksft_not_none"] diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py index 97b7cf2b20eb..40f9ce307dd1 100644 --- a/tools/testing/selftests/net/lib/py/__init__.py +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -8,7 +8,8 @@ from .consts import KSRC from .ksft import KsftFailEx, KsftSkipEx, KsftXfailEx, ksft_pr, ksft_eq, \ ksft_ne, ksft_true, ksft_not_none, ksft_in, ksft_not_in, ksft_is, \ ksft_ge, ksft_gt, ksft_lt, ksft_raises, ksft_busy_wait, \ - ktap_result, ksft_disruptive, ksft_setup, ksft_run, ksft_exit + ktap_result, ksft_disruptive, ksft_setup, ksft_run, ksft_exit, \ + ksft_variants, KsftNamedVariant from .netns import NetNS, NetNSEnter from .nsim import NetdevSim, NetdevSimDev from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \ @@ -21,7 +22,7 @@ __all__ = ["KSRC", "ksft_ne", "ksft_true", "ksft_not_none", "ksft_in", "ksft_not_in", "ksft_is", "ksft_ge", "ksft_gt", "ksft_lt", "ksft_raises", "ksft_busy_wait", "ktap_result", "ksft_disruptive", "ksft_setup", - "ksft_run", "ksft_exit", + "ksft_run", "ksft_exit", "ksft_variants", "KsftNamedVariant", "NetNS", "NetNSEnter", "CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer", "bpftool", "ip", "ethtool", "bpftrace", "rand_port", diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py index 52c42c313cf2..47e0af210bee 100644 --- a/tools/testing/selftests/net/lib/py/ksft.py +++ b/tools/testing/selftests/net/lib/py/ksft.py @@ -185,6 +185,49 @@ KSFT_DISRUPTIVE = True return wrapper
+class KsftNamedVariant: + """ Named string name + argument list tuple for @ksft_variants """ + + def __init__(self, name, *params): + self.params = params + self.name = name or "_".join([str(x) for x in self.params]) + + +def ksft_variants(params): + """ + Decorator defining the sets of inputs for a test. + The parameters will be included in the name of the resulting sub-case. + Parameters can be either single object, tuple or a KsftNamedVariant. + The argument can be a list or a generator. + + Example: + + @ksft_variants([ + (1, "a"), + (2, "b"), + KsftNamedVariant("three", 3, "c"), + ]) + def my_case(cfg, a, b): + pass # ... + + ksft_run(cases=[my_case], args=(cfg, )) + + Will generate cases: + my_case.1_a + my_case.2_b + my_case.three + """ + + def decorator(func): + @functools.wraps(func) + def wrapper(): + return func + wrapper.ksft_variants = params + wrapper.original_func = func + return wrapper + return decorator + + def ksft_setup(env): """ Setup test framework global state from the environment. @@ -236,7 +279,19 @@ KSFT_DISRUPTIVE = True break
for func in cases: - test_cases.append((func, args, func.__name__)) + if hasattr(func, 'ksft_variants'): + # Parametrized test - create case for each param + for param in func.ksft_variants: + if not isinstance(param, KsftNamedVariant): + if not isinstance(param, tuple): + param = (param, ) + param = KsftNamedVariant(None, *param) + + test_cases.append((func.original_func, + (*args, *param.params), + func.__name__ + "." + param.name)) + else: + test_cases.append((func, args, func.__name__))
return test_cases
Use just-added ksft variants for XDP qstat tests.
While at it correct the number of packets, we're sending 1000 packets now.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/drivers/net/xdp.py | 42 ++++++++-------------- 1 file changed, 14 insertions(+), 28 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/selftests/drivers/net/xdp.py index 834a37ae7d0d..e54df158dfe9 100755 --- a/tools/testing/selftests/drivers/net/xdp.py +++ b/tools/testing/selftests/drivers/net/xdp.py @@ -12,6 +12,7 @@ from dataclasses import dataclass from enum import Enum
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_ne, ksft_pr +from lib.py import KsftNamedVariant, ksft_variants from lib.py import KsftFailEx, NetDrvEpEnv from lib.py import EthtoolFamily, NetdevFamily, NlError from lib.py import bkg, cmd, rand_port, wait_port_listen @@ -672,7 +673,18 @@ from lib.py import ip, bpftool, defer _validate_res(res, offset_lst, pkt_sz_lst)
-def _test_xdp_native_ifc_stats(cfg, act): +@ksft_variants([ + KsftNamedVariant("pass", XDPAction.PASS), + KsftNamedVariant("drop", XDPAction.DROP), + KsftNamedVariant("tx", XDPAction.TX), +]) +def test_xdp_native_qstats(cfg, act): + """ + Send 1000 messages. Expect XDP action specified in @act. + Make sure the packets were counted to interface level qstats + (Rx, and Tx if act is TX). + """ + cfg.require_cmd("socat")
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) @@ -733,30 +745,6 @@ from lib.py import ip, bpftool, defer ksft_ge(after['tx-packets'], before['tx-packets'])
-def test_xdp_native_qstats_pass(cfg): - """ - Send 2000 messages, expect XDP_PASS, make sure the packets were counted - to interface level qstats (Rx). - """ - _test_xdp_native_ifc_stats(cfg, XDPAction.PASS) - - -def test_xdp_native_qstats_drop(cfg): - """ - Send 2000 messages, expect XDP_DROP, make sure the packets were counted - to interface level qstats (Rx). - """ - _test_xdp_native_ifc_stats(cfg, XDPAction.DROP) - - -def test_xdp_native_qstats_tx(cfg): - """ - Send 2000 messages, expect XDP_TX, make sure the packets were counted - to interface level qstats (Rx and Tx) - """ - _test_xdp_native_ifc_stats(cfg, XDPAction.TX) - - def main(): """ Main function to execute the XDP tests. @@ -781,9 +769,7 @@ from lib.py import ip, bpftool, defer test_xdp_native_adjst_tail_shrnk_data, test_xdp_native_adjst_head_grow_data, test_xdp_native_adjst_head_shrnk_data, - test_xdp_native_qstats_pass, - test_xdp_native_qstats_drop, - test_xdp_native_qstats_tx, + test_xdp_native_qstats, ], args=(cfg,)) ksft_exit()
The GRO test can run on a real device or a veth. The Toeplitz hash test can only run on a real device. Move them from net/ to drivers/net/ and drivers/net/hw/ respectively.
There are two scripts which set up the environment for these tests setup_loopback.sh and setup_veth.sh. Move those scripts to net/lib. The paths to the setup files are a little ugly but they will be deleted shortly.
toeplitz_client.sh is not a test in itself, but rather a helper to send traffic, so add it to TEST_FILES rather than TEST_PROGS.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/drivers/net/Makefile | 2 ++ tools/testing/selftests/drivers/net/hw/Makefile | 7 ++++++- tools/testing/selftests/net/Makefile | 7 ------- tools/testing/selftests/net/lib/Makefile | 2 ++ tools/testing/selftests/{ => drivers}/net/gro.c | 2 +- tools/testing/selftests/{net => drivers/net/hw}/toeplitz.c | 2 +- tools/testing/selftests/drivers/net/.gitignore | 1 + tools/testing/selftests/{ => drivers}/net/gro.sh | 4 ++-- tools/testing/selftests/drivers/net/hw/.gitignore | 3 ++- .../testing/selftests/{net => drivers/net/hw}/toeplitz.sh | 2 +- .../selftests/{net => drivers/net/hw}/toeplitz_client.sh | 0 tools/testing/selftests/net/.gitignore | 2 -- tools/testing/selftests/net/{ => lib}/setup_loopback.sh | 0 tools/testing/selftests/net/{ => lib}/setup_veth.sh | 0 14 files changed, 18 insertions(+), 16 deletions(-) rename tools/testing/selftests/{ => drivers}/net/gro.c (99%) rename tools/testing/selftests/{net => drivers/net/hw}/toeplitz.c (99%) rename tools/testing/selftests/{ => drivers}/net/gro.sh (95%) rename tools/testing/selftests/{net => drivers/net/hw}/toeplitz.sh (98%) rename tools/testing/selftests/{net => drivers/net/hw}/toeplitz_client.sh (100%) rename tools/testing/selftests/net/{ => lib}/setup_loopback.sh (100%) rename tools/testing/selftests/net/{ => lib}/setup_veth.sh (100%)
diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile index 33f4816216ec..7083a8707c4e 100644 --- a/tools/testing/selftests/drivers/net/Makefile +++ b/tools/testing/selftests/drivers/net/Makefile @@ -6,10 +6,12 @@ TEST_INCLUDES := $(wildcard lib/py/*.py) \ ../../net/lib.sh \
TEST_GEN_FILES := \ + gro \ napi_id_helper \ # end of TEST_GEN_FILES
TEST_PROGS := \ + gro.sh \ hds.py \ napi_id.py \ napi_threaded.py \ diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index 8133d1a0051c..c9dced8c934a 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -1,6 +1,9 @@ # SPDX-License-Identifier: GPL-2.0+ OR MIT
-TEST_GEN_FILES = iou-zcrx +TEST_GEN_FILES := \ + iou-zcrx \ + toeplitz \ +# end of TEST_GEN_FILES
TEST_PROGS = \ csum.py \ @@ -21,12 +24,14 @@ TEST_PROGS = \ rss_ctx.py \ rss_flow_label.py \ rss_input_xfrm.py \ + toeplitz.sh \ tso.py \ xsk_reconfig.py \ #
TEST_FILES := \ ethtool_lib.sh \ + toeplitz_client.sh \ #
TEST_INCLUDES := \ diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index b5127e968108..b66ba04f19d9 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -38,7 +38,6 @@ TEST_PROGS := \ fq_band_pktlimit.sh \ gre_gso.sh \ gre_ipv6_lladdr.sh \ - gro.sh \ icmp.sh \ icmp_redirect.sh \ io_uring_zerocopy_tx.sh \ @@ -121,8 +120,6 @@ TEST_PROGS := \ # end of TEST_PROGS
TEST_PROGS_EXTENDED := \ - toeplitz.sh \ - toeplitz_client.sh \ xfrm_policy_add_speed.sh \ # end of TEST_PROGS_EXTENDED
@@ -130,7 +127,6 @@ TEST_GEN_FILES := \ bind_bhash \ cmsg_sender \ fin_ack_lat \ - gro \ hwtstamp_config \ io_uring_zerocopy_tx \ ioam6_parser \ @@ -159,7 +155,6 @@ TEST_GEN_FILES := \ tcp_mmap \ tfo \ timestamping \ - toeplitz \ txring_overwrite \ txtimestamp \ udpgso \ @@ -193,8 +188,6 @@ TEST_FILES := \ in_netns.sh \ lib.sh \ settings \ - setup_loopback.sh \ - setup_veth.sh \ # end of TEST_FILES
# YNL files, must be before "include ..lib.mk" diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile index ce795bc0a1af..c10796933d42 100644 --- a/tools/testing/selftests/net/lib/Makefile +++ b/tools/testing/selftests/net/lib/Makefile @@ -8,6 +8,8 @@ CFLAGS += -I../../ TEST_FILES := \ ../../../../net/ynl \ ../../../../../Documentation/netlink/specs \ + setup_loopback.sh \ + setup_veth.sh \ # end of TEST_FILES
TEST_GEN_FILES := \ diff --git a/tools/testing/selftests/net/gro.c b/tools/testing/selftests/drivers/net/gro.c similarity index 99% rename from tools/testing/selftests/net/gro.c rename to tools/testing/selftests/drivers/net/gro.c index cfc39f70635d..9b9be0cf8f7f 100644 --- a/tools/testing/selftests/net/gro.c +++ b/tools/testing/selftests/drivers/net/gro.c @@ -57,7 +57,7 @@ #include <string.h> #include <unistd.h>
-#include "../kselftest.h" +#include "../../kselftest.h"
#define DPORT 8000 #define SPORT 1500 diff --git a/tools/testing/selftests/net/toeplitz.c b/tools/testing/selftests/drivers/net/hw/toeplitz.c similarity index 99% rename from tools/testing/selftests/net/toeplitz.c rename to tools/testing/selftests/drivers/net/hw/toeplitz.c index 9ba03164d73a..bf74aa25345d 100644 --- a/tools/testing/selftests/net/toeplitz.c +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.c @@ -52,7 +52,7 @@ #include <sys/types.h> #include <unistd.h>
-#include "../kselftest.h" +#include "../../../kselftest.h"
#define TOEPLITZ_KEY_MIN_LEN 40 #define TOEPLITZ_KEY_MAX_LEN 60 diff --git a/tools/testing/selftests/drivers/net/.gitignore b/tools/testing/selftests/drivers/net/.gitignore index 585ecb4d5dc4..3633c7a3ed65 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 +gro napi_id_helper psp_responder diff --git a/tools/testing/selftests/net/gro.sh b/tools/testing/selftests/drivers/net/gro.sh similarity index 95% rename from tools/testing/selftests/net/gro.sh rename to tools/testing/selftests/drivers/net/gro.sh index 4c5144c6f652..bd3cf6d02eda 100755 --- a/tools/testing/selftests/net/gro.sh +++ b/tools/testing/selftests/drivers/net/gro.sh @@ -90,9 +90,9 @@ while getopts "i:t:p:" opt; do done
if [ -n "$dev" ]; then - source setup_loopback.sh + source $(dirname $0)/../../net/lib/setup_loopback.sh else - source setup_veth.sh + source $(dirname $0)/../../net/lib/setup_veth.sh fi
setup diff --git a/tools/testing/selftests/drivers/net/hw/.gitignore b/tools/testing/selftests/drivers/net/hw/.gitignore index 6942bf575497..8feb7493b3ed 100644 --- a/tools/testing/selftests/drivers/net/hw/.gitignore +++ b/tools/testing/selftests/drivers/net/hw/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only iou-zcrx -ncdevmem +ncdevmme +toeplitz diff --git a/tools/testing/selftests/net/toeplitz.sh b/tools/testing/selftests/drivers/net/hw/toeplitz.sh similarity index 98% rename from tools/testing/selftests/net/toeplitz.sh rename to tools/testing/selftests/drivers/net/hw/toeplitz.sh index 8ff172f7bb1b..d236b666dd3b 100755 --- a/tools/testing/selftests/net/toeplitz.sh +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.sh @@ -11,7 +11,7 @@ # invoke as ./toeplitz.sh (-i <iface>) -u|-t -4|-6 \ # [(-rss -irq_prefix <irq-pattern-prefix>)|(-rps <rps_map>)]
-source setup_loopback.sh +source $(dirname $0)/../../../net/lib/setup_loopback.sh readonly SERVER_IP4="192.168.1.200/24" readonly SERVER_IP6="fda8::1/64" readonly SERVER_MAC="aa:00:00:00:00:02" diff --git a/tools/testing/selftests/net/toeplitz_client.sh b/tools/testing/selftests/drivers/net/hw/toeplitz_client.sh similarity index 100% rename from tools/testing/selftests/net/toeplitz_client.sh rename to tools/testing/selftests/drivers/net/hw/toeplitz_client.sh diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore index 439101b518ee..0b7a3e64db00 100644 --- a/tools/testing/selftests/net/.gitignore +++ b/tools/testing/selftests/net/.gitignore @@ -7,7 +7,6 @@ cmsg_sender diag_uid epoll_busy_poll fin_ack_lat -gro hwtstamp_config io_uring_zerocopy_tx ioam6_parser @@ -56,7 +55,6 @@ tcp_port_share tfo timestamping tls -toeplitz tools tun txring_overwrite diff --git a/tools/testing/selftests/net/setup_loopback.sh b/tools/testing/selftests/net/lib/setup_loopback.sh similarity index 100% rename from tools/testing/selftests/net/setup_loopback.sh rename to tools/testing/selftests/net/lib/setup_loopback.sh diff --git a/tools/testing/selftests/net/setup_veth.sh b/tools/testing/selftests/net/lib/setup_veth.sh similarity index 100% rename from tools/testing/selftests/net/setup_veth.sh rename to tools/testing/selftests/net/lib/setup_veth.sh
Jakub Kicinski wrote:
The GRO test can run on a real device or a veth. The Toeplitz hash test can only run on a real device. Move them from net/ to drivers/net/ and drivers/net/hw/ respectively.
There are two scripts which set up the environment for these tests setup_loopback.sh and setup_veth.sh. Move those scripts to net/lib. The paths to the setup files are a little ugly but they will be deleted shortly.
toeplitz_client.sh is not a test in itself, but rather a helper to send traffic, so add it to TEST_FILES rather than TEST_PROGS.
Signed-off-by: Jakub Kicinski kuba@kernel.org diff --git a/tools/testing/selftests/drivers/net/hw/.gitignore b/tools/testing/selftests/drivers/net/hw/.gitignore index 6942bf575497..8feb7493b3ed 100644 --- a/tools/testing/selftests/drivers/net/hw/.gitignore +++ b/tools/testing/selftests/drivers/net/hw/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only iou-zcrx -ncdevmem +ncdevmme
modified neighboring line
+toeplitz
There's a common synchronization problem when a script (Python test) uses a C program to set up some state (usually start a receiving process for traffic). The script needs to know when the process has fully initialized. The inverse of the problem exists for shutting the process down - we need a reliable way to tell the process to exit.
We added helpers to do this safely in commit 71477137994f ("selftests: drv-net: add a way to wait for a local process") unfortunately the two operations (wait for init, and shutdown) are controlled by a single parameter (ksft_wait). Add support for using ksft_ready without using the second fd for exit.
This is useful for programs which wait for a specific number of packets to rx so exit_wait is a good match, but we still need to wait for init.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/net/lib/py/utils.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py index cb40ecef9456..106ee1f2df86 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -32,7 +32,7 @@ import time 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_wait=None): + host=None, timeout=5, ksft_ready=None, ksft_wait=None): if ns: comm = f'ip netns exec {ns} ' + comm
@@ -52,21 +52,25 @@ import time # ksft_wait lets us wait for the background process to fully start, # we pass an FD to the child process, and wait for it to write back. # Similarly term_fd tells child it's time to exit. - pass_fds = () + pass_fds = [] env = os.environ.copy() if ksft_wait is not None: - rfd, ready_fd = os.pipe() wait_fd, self.ksft_term_fd = os.pipe() - pass_fds = (ready_fd, wait_fd, ) - env["KSFT_READY_FD"] = str(ready_fd) + pass_fds.append(wait_fd) env["KSFT_WAIT_FD"] = str(wait_fd) + ksft_ready = True # ksft_wait implies ready + if ksft_ready is not None: + rfd, ready_fd = os.pipe() + pass_fds.append(ready_fd) + env["KSFT_READY_FD"] = str(ready_fd)
self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, pass_fds=pass_fds, env=env) if ksft_wait is not None: - os.close(ready_fd) os.close(wait_fd) + if ksft_ready is not None: + os.close(ready_fd) msg = fd_read_timeout(rfd, ksft_wait) os.close(rfd) if not msg: @@ -116,10 +120,10 @@ import time with bkg("my_binary", ksft_wait=5): """ def __init__(self, comm, shell=None, fail=None, ns=None, host=None, - exit_wait=False, ksft_wait=None): + exit_wait=False, ksft_ready=None, ksft_wait=None): super().__init__(comm, background=True, shell=shell, fail=fail, ns=ns, host=host, - ksft_wait=ksft_wait) + 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
We're already saving the info about the local dev in env.dev for the tests, save remote dev as well. This is more symmetric, env generally provides the same info for local and remote end.
While at it make sure that we reliably get the detailed info about the local dev. nsim used to read the dev info without -d.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/drivers/net/lib/py/env.py | 2 ++ tools/testing/selftests/net/lib/py/nsim.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index 01be3d9b9720..8b644fd84ff2 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -168,6 +168,8 @@ from .remote import Remote
# resolve remote interface name self.remote_ifname = self.resolve_remote_ifc() + self.remote_dev = ip("-d link show dev " + self.remote_ifname, + host=self.remote, json=True)[0]
self._required_cmd = {}
diff --git a/tools/testing/selftests/net/lib/py/nsim.py b/tools/testing/selftests/net/lib/py/nsim.py index 1a8cbe9acc48..7c640ed64c0b 100644 --- a/tools/testing/selftests/net/lib/py/nsim.py +++ b/tools/testing/selftests/net/lib/py/nsim.py @@ -27,7 +27,7 @@ from .utils import cmd, ip self.port_index = port_index self.ns = ns self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index) - ret = ip("-j link show dev %s" % ifname, ns=ns) + ret = ip("-d -j link show dev %s" % ifname, ns=ns) self.dev = json.loads(ret.stdout)[0] self.ifindex = self.dev["ifindex"]
To replace veth in software GRO testing with netdevsim we need GRO support in netdevsim. Luckily we already have NAPI support so this change is trivial (comapred to veth).
Signed-off-by: Jakub Kicinski kuba@kernel.org --- drivers/net/netdevsim/netdev.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c index fa1d97885caa..2b713db16cd0 100644 --- a/drivers/net/netdevsim/netdev.c +++ b/drivers/net/netdevsim/netdev.c @@ -433,13 +433,8 @@ static int nsim_rcv(struct nsim_rq *rq, int budget) }
/* skb might be discard at netif_receive_skb, save the len */ - skblen = skb->len; - skb_mark_napi_id(skb, &rq->napi); - ret = netif_receive_skb(skb); - if (ret == NET_RX_SUCCESS) - dev_dstats_rx_add(dev, skblen); - else - dev_dstats_rx_dropped(dev); + dev_dstats_rx_add(dev, skb->len); + napi_gro_receive(&rq->napi, skb); }
nsim_start_peer_tx_queue(dev, rq);
Jakub Kicinski wrote:
To replace veth in software GRO testing with netdevsim we need GRO support in netdevsim. Luckily we already have NAPI support so this change is trivial (comapred to veth).
compared
Signed-off-by: Jakub Kicinski kuba@kernel.org
Rewrite the existing gro.sh test in Python. The conversion not exact, the changes are related to integrating the test with our "remote endpoint" paradigm. The test now reads the IP addresses from the user config. It resolves the MAC address (including running over Layer 3 networks).
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/drivers/net/Makefile | 2 +- tools/testing/selftests/drivers/net/gro.c | 3 + tools/testing/selftests/drivers/net/gro.py | 161 +++++++++++++++++++ tools/testing/selftests/drivers/net/gro.sh | 105 ------------ 4 files changed, 165 insertions(+), 106 deletions(-) create mode 100755 tools/testing/selftests/drivers/net/gro.py delete mode 100755 tools/testing/selftests/drivers/net/gro.sh
diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile index 7083a8707c4e..f5c71d993750 100644 --- a/tools/testing/selftests/drivers/net/Makefile +++ b/tools/testing/selftests/drivers/net/Makefile @@ -11,7 +11,7 @@ TEST_GEN_FILES := \ # end of TEST_GEN_FILES
TEST_PROGS := \ - gro.sh \ + gro.py \ hds.py \ napi_id.py \ napi_threaded.py \ diff --git a/tools/testing/selftests/drivers/net/gro.c b/tools/testing/selftests/drivers/net/gro.c index 9b9be0cf8f7f..995b492f5bcb 100644 --- a/tools/testing/selftests/drivers/net/gro.c +++ b/tools/testing/selftests/drivers/net/gro.c @@ -58,6 +58,7 @@ #include <unistd.h>
#include "../../kselftest.h" +#include "../../net/lib/ksft.h"
#define DPORT 8000 #define SPORT 1500 @@ -1127,6 +1128,8 @@ static void gro_receiver(void) set_timeout(rxfd); bind_packetsocket(rxfd);
+ ksft_ready(); + memset(correct_payload, 0, sizeof(correct_payload));
if (strcmp(testname, "data") == 0) { diff --git a/tools/testing/selftests/drivers/net/gro.py b/tools/testing/selftests/drivers/net/gro.py new file mode 100755 index 000000000000..a27f8106eb63 --- /dev/null +++ b/tools/testing/selftests/drivers/net/gro.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +""" +GRO (Generic Receive Offload) conformance tests. + +Validates that GRO coalescing works correctly by running the gro +binary in different configurations and checking for correct packet +coalescing behavior. + +Test cases: + - data: Data packets with same size/headers and correct seq numbers coalesce + - ack: Pure ACK packets do not coalesce + - flags: Packets with PSH, SYN, URG, RST flags do not coalesce + - tcp: Packets with incorrect checksum, non-consecutive seqno don't coalesce + - ip: Packets with different ECN, TTL, TOS, or IP options don't coalesce + - large: Packets larger than GRO_MAX_SIZE don't coalesce +""" + +import os +from lib.py import ksft_run, ksft_exit, ksft_pr +from lib.py import NetDrvEpEnv, KsftXfailEx +from lib.py import cmd, defer, bkg, ip +from lib.py import ksft_variants + + +def _resolve_dmac(cfg, ipver): + """ + Find the destination MAC address remote host should use to send packets + towards the local host. I may be a router / gateway address. + """ + + attr = "dmac" + ipver + # Cache the response across test cases + if hasattr(cfg, attr): + return getattr(cfg, attr) + + route = ip(f"-{ipver} route get {cfg.addr_v[ipver]}", + json=True, host=cfg.remote)[0] + gw = route.get("gateway") + # Local L2 segment, address directly + if not gw: + setattr(cfg, attr, cfg.dev['address']) + return getattr(cfg, attr) + + # ping to make sure neighbor is resolved, + # bind to an interface, for v6 the GW is likely link local + cmd(f"ping -c1 -W0 -I{cfg.remote_ifname} {gw}", host=cfg.remote) + + neigh = ip(f"neigh get {gw} dev {cfg.remote_ifname}", + json=True, host=cfg.remote)[0] + setattr(cfg, attr, neigh['lladdr']) + return getattr(cfg, attr) + + +def _write_defer_restore(cfg, path, val, defer_undo=False): + with open(path, "r", encoding="utf-8") as fp: + orig_val = fp.read().strip() + if str(val) == orig_val: + return + with open(path, "w", encoding="utf-8") as fp: + fp.write(val) + if defer_undo: + defer(_write_defer_restore, cfg, path, orig_val) + + +def _set_mtu_restore(dev, mtu, host): + if dev['mtu'] < mtu: + ip(f"link set dev {dev['ifname']} mtu {mtu}", host=host) + defer(ip, f"link set dev {dev['ifname']} mtu {dev['mtu']}", host=host) + + +def _setup(cfg): + """ Setup hardware loopback mode for GRO testing. """ + + if not hasattr(cfg, "bin_remote"): + cfg.bin_local = cfg.test_dir / "gro" + cfg.bin_remote = cfg.remote.deploy(cfg.bin_local) + + flush_path = f"/sys/class/net/{cfg.ifname}/gro_flush_timeout" + irq_path = f"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs" + + _write_defer_restore(cfg, flush_path, "200000", defer_undo=True) + _write_defer_restore(cfg, irq_path, "10", defer_undo=True) + + try: + # Disable TSO for local tests + cfg.require_nsim() # will raise KsftXfailEx if not running on nsim + + cmd(f"ethtool -K {cfg.ifname} gro on tso off") + cmd(f"ethtool -K {cfg.remote_ifname} gro on tso off", host=cfg.remote) + except KsftXfailEx: + pass + +def _gro_variants(): + """Generator that yields all combinations of protocol and test types.""" + + for protocol in ["ipv4", "ipv6", "ipip"]: + for test_name in ["data", "ack", "flags", "tcp", "ip", "large"]: + yield protocol, test_name + + +@ksft_variants(_gro_variants()) +def test(cfg, protocol, test_name): + """Run a single GRO test with retries.""" + + ipver = "6" if protocol[-1] == "6" else "4" + cfg.require_ipver(ipver) + + _setup(cfg) + + # "large" test needs at least 4k MTU + if test_name == "large": + _set_mtu_restore(cfg.dev, 4096, None) + _set_mtu_restore(cfg.remote_dev, 4096, cfg.remote) + + base_cmd_args = [ + f"--{protocol}", + f"--dmac {_resolve_dmac(cfg, ipver)}", + f"--smac {cfg.remote_dev['address']}", + f"--daddr {cfg.addr_v[ipver]}", + f"--saddr {cfg.remote_addr_v[ipver]}", + f"--test {test_name}", + "--verbose" + ] + base_args = " ".join(base_cmd_args) + + max_retries = 6 + for attempt in range(max_retries): + rx_cmd = f"{cfg.bin_local} {base_args} --rx --iface {cfg.ifname}" + tx_cmd = f"{cfg.bin_remote} {base_args} --iface {cfg.remote_ifname}" + + fail_now = attempt >= max_retries - 1 + + with bkg(rx_cmd, ksft_ready=True, exit_wait=True, + fail=fail_now) as rx_proc: + cmd(tx_cmd, host=cfg.remote) + + if rx_proc.ret == 0: + return + + ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# ')) + ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# ')) + + if test_name == "large" and os.environ.get("KSFT_MACHINE_SLOW"): + ksft_pr(f"Ignoring {protocol}/{test_name} failure due to slow environment") + return + + ksft_pr(f"Attempt {attempt + 1}/{max_retries} failed, retrying...") + + +def main() -> None: + """ Ksft boiler plate main """ + + with NetDrvEpEnv(__file__) as cfg: + ksft_run(cases=[test], args=(cfg,)) + ksft_exit() + + +if __name__ == "__main__": + main() diff --git a/tools/testing/selftests/drivers/net/gro.sh b/tools/testing/selftests/drivers/net/gro.sh deleted file mode 100755 index bd3cf6d02eda..000000000000 --- a/tools/testing/selftests/drivers/net/gro.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: GPL-2.0 - -readonly SERVER_MAC="aa:00:00:00:00:02" -readonly CLIENT_MAC="aa:00:00:00:00:01" -readonly TESTS=("data" "ack" "flags" "tcp" "ip" "large") -readonly PROTOS=("ipv4" "ipv6" "ipip") -dev="" -test="all" -proto="ipv4" - -run_test() { - local server_pid=0 - local exit_code=0 - local protocol=$1 - local test=$2 - local ARGS=( "--${protocol}" "--dmac" "${SERVER_MAC}" \ - "--smac" "${CLIENT_MAC}" "--test" "${test}" "--verbose" ) - - setup_ns - # Each test is run 6 times to deflake, because given the receive timing, - # not all packets that should coalesce will be considered in the same flow - # on every try. - for tries in {1..6}; do - # Actual test starts here - ip netns exec $server_ns ./gro "${ARGS[@]}" "--rx" "--iface" "server" \ - 1>>log.txt & - server_pid=$! - sleep 0.5 # to allow for socket init - ip netns exec $client_ns ./gro "${ARGS[@]}" "--iface" "client" \ - 1>>log.txt - wait "${server_pid}" - exit_code=$? - if [[ ${test} == "large" && -n "${KSFT_MACHINE_SLOW}" && \ - ${exit_code} -ne 0 ]]; then - echo "Ignoring errors due to slow environment" 1>&2 - exit_code=0 - fi - if [[ "${exit_code}" -eq 0 ]]; then - break; - fi - done - cleanup_ns - echo ${exit_code} -} - -run_all_tests() { - local failed_tests=() - for proto in "${PROTOS[@]}"; do - for test in "${TESTS[@]}"; do - echo "running test ${proto} ${test}" >&2 - exit_code=$(run_test $proto $test) - if [[ "${exit_code}" -ne 0 ]]; then - failed_tests+=("${proto}_${test}") - fi; - done; - done - if [[ ${#failed_tests[@]} -ne 0 ]]; then - echo "failed tests: ${failed_tests[*]}. \ - Please see log.txt for more logs" - exit 1 - else - echo "All Tests Succeeded!" - fi; -} - -usage() { - echo "Usage: $0 \ - [-i <DEV>] \ - [-t data|ack|flags|tcp|ip|large] \ - [-p <ipv4|ipv6>]" 1>&2; - exit 1; -} - -while getopts "i:t:p:" opt; do - case "${opt}" in - i) - dev="${OPTARG}" - ;; - t) - test="${OPTARG}" - ;; - p) - proto="${OPTARG}" - ;; - *) - usage - ;; - esac -done - -if [ -n "$dev" ]; then - source $(dirname $0)/../../net/lib/setup_loopback.sh -else - source $(dirname $0)/../../net/lib/setup_veth.sh -fi - -setup -trap cleanup EXIT -if [[ "${test}" == "all" ]]; then - run_all_tests -else - exit_code=$(run_test "${proto}" "${test}") - exit $exit_code -fi;
Jakub Kicinski wrote:
Rewrite the existing gro.sh test in Python. The conversion not exact, the changes are related to integrating the test with our "remote endpoint" paradigm. The test now reads the IP addresses from the user config. It resolves the MAC address (including running over Layer 3 networks).
Signed-off-by: Jakub Kicinski kuba@kernel.org
tools/testing/selftests/drivers/net/Makefile | 2 +- tools/testing/selftests/drivers/net/gro.c | 3 + tools/testing/selftests/drivers/net/gro.py | 161 +++++++++++++++++++ tools/testing/selftests/drivers/net/gro.sh | 105 ------------ 4 files changed, 165 insertions(+), 106 deletions(-) create mode 100755 tools/testing/selftests/drivers/net/gro.py delete mode 100755 tools/testing/selftests/drivers/net/gro.sh
diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile index 7083a8707c4e..f5c71d993750 100644 --- a/tools/testing/selftests/drivers/net/Makefile +++ b/tools/testing/selftests/drivers/net/Makefile @@ -11,7 +11,7 @@ TEST_GEN_FILES := \ # end of TEST_GEN_FILES TEST_PROGS := \
- gro.sh \
- gro.py \ hds.py \ napi_id.py \ napi_threaded.py \
diff --git a/tools/testing/selftests/drivers/net/gro.c b/tools/testing/selftests/drivers/net/gro.c index 9b9be0cf8f7f..995b492f5bcb 100644 --- a/tools/testing/selftests/drivers/net/gro.c +++ b/tools/testing/selftests/drivers/net/gro.c @@ -58,6 +58,7 @@ #include <unistd.h> #include "../../kselftest.h" +#include "../../net/lib/ksft.h" #define DPORT 8000 #define SPORT 1500 @@ -1127,6 +1128,8 @@ static void gro_receiver(void) set_timeout(rxfd); bind_packetsocket(rxfd);
- ksft_ready();
- memset(correct_payload, 0, sizeof(correct_payload));
if (strcmp(testname, "data") == 0) { diff --git a/tools/testing/selftests/drivers/net/gro.py b/tools/testing/selftests/drivers/net/gro.py new file mode 100755 index 000000000000..a27f8106eb63 --- /dev/null +++ b/tools/testing/selftests/drivers/net/gro.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0
+""" +GRO (Generic Receive Offload) conformance tests.
+Validates that GRO coalescing works correctly by running the gro +binary in different configurations and checking for correct packet +coalescing behavior.
+Test cases:
- data: Data packets with same size/headers and correct seq numbers coalesce
- ack: Pure ACK packets do not coalesce
- flags: Packets with PSH, SYN, URG, RST flags do not coalesce
- tcp: Packets with incorrect checksum, non-consecutive seqno don't coalesce
- ip: Packets with different ECN, TTL, TOS, or IP options don't coalesce
- large: Packets larger than GRO_MAX_SIZE don't coalesce
+"""
+import os +from lib.py import ksft_run, ksft_exit, ksft_pr +from lib.py import NetDrvEpEnv, KsftXfailEx +from lib.py import cmd, defer, bkg, ip +from lib.py import ksft_variants
+def _resolve_dmac(cfg, ipver):
- """
- Find the destination MAC address remote host should use to send packets
- towards the local host. I may be a router / gateway address.
I -> It
- """
- attr = "dmac" + ipver
- # Cache the response across test cases
- if hasattr(cfg, attr):
return getattr(cfg, attr)- route = ip(f"-{ipver} route get {cfg.addr_v[ipver]}",
json=True, host=cfg.remote)[0]- gw = route.get("gateway")
- # Local L2 segment, address directly
- if not gw:
setattr(cfg, attr, cfg.dev['address'])return getattr(cfg, attr)- # ping to make sure neighbor is resolved,
- # bind to an interface, for v6 the GW is likely link local
- cmd(f"ping -c1 -W0 -I{cfg.remote_ifname} {gw}", host=cfg.remote)
- neigh = ip(f"neigh get {gw} dev {cfg.remote_ifname}",
json=True, host=cfg.remote)[0]- setattr(cfg, attr, neigh['lladdr'])
- return getattr(cfg, attr)
Rewrite the existing toeplitz.sh test in Python. The conversion is a lot less exact than the GRO one. We use Netlink APIs to get the device RSS and IRQ information. We expect that the device has neither RPS nor RFS configure, and set RPS up as part of the test.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- .../testing/selftests/drivers/net/hw/Makefile | 3 +- .../selftests/drivers/net/hw/toeplitz.c | 5 + .../selftests/drivers/net/hw/toeplitz.py | 208 ++++++++++++++++++ .../selftests/drivers/net/hw/toeplitz.sh | 199 ----------------- .../drivers/net/hw/toeplitz_client.sh | 28 --- 5 files changed, 214 insertions(+), 229 deletions(-) create mode 100755 tools/testing/selftests/drivers/net/hw/toeplitz.py delete mode 100755 tools/testing/selftests/drivers/net/hw/toeplitz.sh delete mode 100755 tools/testing/selftests/drivers/net/hw/toeplitz_client.sh
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index c9dced8c934a..1760238e9d4f 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -24,14 +24,13 @@ TEST_PROGS = \ rss_ctx.py \ rss_flow_label.py \ rss_input_xfrm.py \ - toeplitz.sh \ + toeplitz.py \ tso.py \ xsk_reconfig.py \ #
TEST_FILES := \ ethtool_lib.sh \ - toeplitz_client.sh \ #
TEST_INCLUDES := \ diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.c b/tools/testing/selftests/drivers/net/hw/toeplitz.c index bf74aa25345d..afc5f910b006 100644 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.c +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.c @@ -53,6 +53,7 @@ #include <unistd.h>
#include "../../../kselftest.h" +#include "../../../net/lib/ksft.h"
#define TOEPLITZ_KEY_MIN_LEN 40 #define TOEPLITZ_KEY_MAX_LEN 60 @@ -576,6 +577,10 @@ int main(int argc, char **argv) fd_sink = setup_sink();
setup_rings(); + + /* Signal to test framework that we're ready to receive */ + ksft_ready(); + process_rings(); cleanup_rings();
diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.py b/tools/testing/selftests/drivers/net/hw/toeplitz.py new file mode 100755 index 000000000000..34476c431e39 --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +""" +Toeplitz Rx hashing test: + - rxhash (the hash value calculation itself); + - RSS mapping from rxhash to rx queue; + - RPS mapping from rxhash to cpu. +""" + +import glob +import os +import socket +from lib.py import ksft_run, ksft_exit, ksft_pr +from lib.py import NetDrvEpEnv, EthtoolFamily, NetdevFamily +from lib.py import cmd, bkg, rand_port, defer +from lib.py import ksft_in +from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx + + +def _check_rps_and_rfs_not_configured(cfg): + """Verify that RPS is not already configured.""" + + for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"): + with open(rps_file, "r", encoding="utf-8") as fp: + val = fp.read().strip() + if set(val) - {"0", ","}: + raise KsftSkipEx(f"RPS already configured on {rps_file}: {val}") + + rfs_file = "/proc/sys/net/core/rps_sock_flow_entries" + with open(rfs_file, "r", encoding="utf-8") as fp: + val = fp.read().strip() + if val != "0": + raise KsftSkipEx(f"RFS already configured {rfs_file}: {val}") + + +def _get_rss_key(cfg): + """ + Read the RSS key from the device. + Return a string in the traditional %02x:%02x:%02x:.. format. + """ + + rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}}) + return ':'.join(f'{b:02x}' for b in rss["hkey"]) + + +def _get_cpu_for_irq(irq): + with open(f"/proc/irq/{irq}/smp_affinity_list", "r", + encoding="utf-8") as fp: + data = fp.read().strip() + if "," in data or "-" in data: + raise KsftFailEx(f"IRQ{irq} is not mapped to a single core: {data}") + return int(data) + + +def _get_irq_cpus(cfg): + """ + Read the list of IRQs for the device Rx queues. + """ + queues = cfg.netnl.queue_get({"ifindex": cfg.ifindex}, dump=True) + napis = cfg.netnl.napi_get({"ifindex": cfg.ifindex}, dump=True) + + # Remap into ID-based dicts + napis = {n["id"]: n for n in napis} + queues = {f"{q['type']}{q['id']}": q for q in queues} + + cpus = [] + for rx in range(9999): + name = f"rx{rx}" + if name not in queues: + break + cpus.append(_get_cpu_for_irq(napis[queues[name]["napi-id"]]["irq"])) + + return cpus + + +def _get_unused_cpus(cfg, count=2): + """ + Get CPUs that are not used by Rx queues. + Returns a list of at least 'count' CPU numbers. + """ + + # Get CPUs used by Rx queues + rx_cpus = set(_get_irq_cpus(cfg)) + + # Get total number of CPUs + num_cpus = os.cpu_count() + + # Find unused CPUs + unused_cpus = [cpu for cpu in range(num_cpus) if cpu not in rx_cpus] + + if len(unused_cpus) < count: + raise KsftSkipEx(f"Need at {count} CPUs not used by Rx queues, found {len(unused_cpus)}") + + return unused_cpus[:count] + + +def _configure_rps(cfg, rps_cpus): + """Configure RPS for all Rx queues.""" + + mask = 0 + for cpu in rps_cpus: + mask |= (1 << cpu) + mask = hex(mask)[2:] + + # Set RPS bitmap for all rx queues + for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"): + cmd(f"echo {mask} > {rps_file}", shell=True) + + return mask + + +def _send_traffic(cfg, proto_flag, ipver, port): + """Send 20 packets of requested type.""" + + # Determine protocol and IP version for socat + if proto_flag == "-u": + proto = "UDP" + else: + proto = "TCP" + + baddr = f"[{cfg.addr_v['6']}]" if ipver == "6" else cfg.addr_v["4"] + + # Run socat in a loop to send traffic periodically + # Use sh -c with a loop similar to toeplitz_client.sh + socat_cmd = f""" + for i in `seq 20`; do + echo "msg $i" | socat -{ipver} -t 0.1 - {proto}:{baddr}:{port}; + sleep 0.001; + done + """ + + cmd(socat_cmd, shell=True, host=cfg.remote) + + +def _test_variants(): + for grp in ["", "rss", "rps"]: + for l4 in ["tcp", "udp"]: + for l3 in ["4", "6"]: + name = f"{l4}_ipv{l3}" + if grp: + name = f"{grp}_{name}" + yield KsftNamedVariant(name, "-" + l4[0], l3, grp) + + +@ksft_variants(_test_variants()) +def test(cfg, proto_flag, ipver, grp): + """Run a single toeplitz test.""" + + cfg.require_ipver(ipver) + + # Check that rxhash is enabled + ksft_in("receive-hashing: on", cmd(f"ethtool -k {cfg.ifname}").stdout) + + port = rand_port(socket.SOCK_DGRAM) + key = _get_rss_key(cfg) + + toeplitz_path = cfg.test_dir / "toeplitz" + rx_cmd = [ + str(toeplitz_path), + "-" + ipver, + proto_flag, + "-d", str(port), + "-i", cfg.ifname, + "-k", key, + "-T", "1000", + "-s", + "-v" + ] + + if grp: + _check_rps_and_rfs_not_configured(cfg) + if grp == "rss": + irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)]) + rx_cmd += ["-C", irq_cpus] + ksft_pr(f"RSS using CPUs: {irq_cpus}") + elif grp == "rps": + # Get CPUs not used by Rx queues and configure them for RPS + rps_cpus = _get_unused_cpus(cfg, count=2) + rps_mask = _configure_rps(cfg, rps_cpus) + defer(_configure_rps, cfg, []) + rx_cmd += ["-r", rps_mask] + ksft_pr(f"RPS using CPUs: {rps_cpus}, mask: {rps_mask}") + + # Run rx in background, it will exit once it has seen enough packets + with bkg(" ".join(rx_cmd), ksft_ready=True, exit_wait=True) as rx_proc: + while rx_proc.proc.poll() is None: + _send_traffic(cfg, proto_flag, ipver, port) + + # Check rx result + ksft_pr("Receiver output:") + ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# ')) + if rx_proc.stderr: + ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# ')) + + +def main() -> None: + """Ksft boilerplate main.""" + + with NetDrvEpEnv(__file__) as cfg: + cfg.ethnl = EthtoolFamily() + cfg.netnl = NetdevFamily() + ksft_run(cases=[test], args=(cfg,)) + ksft_exit() + + +if __name__ == "__main__": + main() diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.sh b/tools/testing/selftests/drivers/net/hw/toeplitz.sh deleted file mode 100755 index d236b666dd3b..000000000000 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.sh +++ /dev/null @@ -1,199 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: GPL-2.0 -# -# extended toeplitz test: test rxhash plus, optionally, either (1) rss mapping -# from rxhash to rx queue ('-rss') or (2) rps mapping from rxhash to cpu -# ('-rps <rps_map>') -# -# irq-pattern-prefix can be derived from /sys/kernel/irq/*/action, -# which is a driver-specific encoding. -# -# invoke as ./toeplitz.sh (-i <iface>) -u|-t -4|-6 \ -# [(-rss -irq_prefix <irq-pattern-prefix>)|(-rps <rps_map>)] - -source $(dirname $0)/../../../net/lib/setup_loopback.sh -readonly SERVER_IP4="192.168.1.200/24" -readonly SERVER_IP6="fda8::1/64" -readonly SERVER_MAC="aa:00:00:00:00:02" - -readonly CLIENT_IP4="192.168.1.100/24" -readonly CLIENT_IP6="fda8::2/64" -readonly CLIENT_MAC="aa:00:00:00:00:01" - -PORT=8000 -KEY="$(</proc/sys/net/core/netdev_rss_key)" -TEST_RSS=false -RPS_MAP="" -PROTO_FLAG="" -IP_FLAG="" -DEV="eth0" - -# Return the number of rxqs among which RSS is configured to spread packets. -# This is determined by reading the RSS indirection table using ethtool. -get_rss_cfg_num_rxqs() { - echo $(ethtool -x "${DEV}" | - grep -E [[:space:]]+[0-9]+:[[:space:]]+ | - cut -d: -f2- | - awk '{$1=$1};1' | - tr ' ' '\n' | - sort -u | - wc -l) -} - -# Return a list of the receive irq handler cpus. -# The list is ordered by the irqs, so first rxq-0 cpu, then rxq-1 cpu, etc. -# Reads /sys/kernel/irq/ in order, so algorithm depends on -# irq_{rxq-0} < irq_{rxq-1}, etc. -get_rx_irq_cpus() { - CPUS="" - # sort so that irq 2 is read before irq 10 - SORTED_IRQS=$(for i in /sys/kernel/irq/*; do echo $i; done | sort -V) - # Consider only as many queues as RSS actually uses. We assume that - # if RSS_CFG_NUM_RXQS=N, then RSS uses rxqs 0-(N-1). - RSS_CFG_NUM_RXQS=$(get_rss_cfg_num_rxqs) - RXQ_COUNT=0 - - for i in ${SORTED_IRQS} - do - [[ "${RXQ_COUNT}" -lt "${RSS_CFG_NUM_RXQS}" ]] || break - # lookup relevant IRQs by action name - [[ -e "$i/actions" ]] || continue - cat "$i/actions" | grep -q "${IRQ_PATTERN}" || continue - irqname=$(<"$i/actions") - - # does the IRQ get called - irqcount=$(cat "$i/per_cpu_count" | tr -d '0,') - [[ -n "${irqcount}" ]] || continue - - # lookup CPU - irq=$(basename "$i") - cpu=$(cat "/proc/irq/$irq/smp_affinity_list") - - if [[ -z "${CPUS}" ]]; then - CPUS="${cpu}" - else - CPUS="${CPUS},${cpu}" - fi - RXQ_COUNT=$((RXQ_COUNT+1)) - done - - echo "${CPUS}" -} - -get_disable_rfs_cmd() { - echo "echo 0 > /proc/sys/net/core/rps_sock_flow_entries;" -} - -get_set_rps_bitmaps_cmd() { - CMD="" - for i in /sys/class/net/${DEV}/queues/rx-*/rps_cpus - do - CMD="${CMD} echo $1 > ${i};" - done - - echo "${CMD}" -} - -get_disable_rps_cmd() { - echo "$(get_set_rps_bitmaps_cmd 0)" -} - -die() { - echo "$1" - exit 1 -} - -check_nic_rxhash_enabled() { - local -r pattern="receive-hashing:\ on" - - ethtool -k "${DEV}" | grep -q "${pattern}" || die "rxhash must be enabled" -} - -parse_opts() { - local prog=$0 - shift 1 - - while [[ "$1" =~ "-" ]]; do - if [[ "$1" = "-irq_prefix" ]]; then - shift - IRQ_PATTERN="^$1-[0-9]*$" - elif [[ "$1" = "-u" || "$1" = "-t" ]]; then - PROTO_FLAG="$1" - elif [[ "$1" = "-4" ]]; then - IP_FLAG="$1" - SERVER_IP="${SERVER_IP4}" - CLIENT_IP="${CLIENT_IP4}" - elif [[ "$1" = "-6" ]]; then - IP_FLAG="$1" - SERVER_IP="${SERVER_IP6}" - CLIENT_IP="${CLIENT_IP6}" - elif [[ "$1" = "-rss" ]]; then - TEST_RSS=true - elif [[ "$1" = "-rps" ]]; then - shift - RPS_MAP="$1" - elif [[ "$1" = "-i" ]]; then - shift - DEV="$1" - else - die "Usage: ${prog} (-i <iface>) -u|-t -4|-6 \ - [(-rss -irq_prefix <irq-pattern-prefix>)|(-rps <rps_map>)]" - fi - shift - done -} - -setup() { - setup_loopback_environment "${DEV}" - - # Set up server_ns namespace and client_ns namespace - setup_macvlan_ns "${DEV}" $server_ns server \ - "${SERVER_MAC}" "${SERVER_IP}" - setup_macvlan_ns "${DEV}" $client_ns client \ - "${CLIENT_MAC}" "${CLIENT_IP}" -} - -cleanup() { - cleanup_macvlan_ns $server_ns server $client_ns client - cleanup_loopback "${DEV}" -} - -parse_opts $0 $@ - -setup -trap cleanup EXIT - -check_nic_rxhash_enabled - -# Actual test starts here -if [[ "${TEST_RSS}" = true ]]; then - # RPS/RFS must be disabled because they move packets between cpus, - # which breaks the PACKET_FANOUT_CPU identification of RSS decisions. - eval "$(get_disable_rfs_cmd) $(get_disable_rps_cmd)" \ - ip netns exec $server_ns ./toeplitz "${IP_FLAG}" "${PROTO_FLAG}" \ - -d "${PORT}" -i "${DEV}" -k "${KEY}" -T 1000 \ - -C "$(get_rx_irq_cpus)" -s -v & -elif [[ ! -z "${RPS_MAP}" ]]; then - eval "$(get_disable_rfs_cmd) $(get_set_rps_bitmaps_cmd ${RPS_MAP})" \ - ip netns exec $server_ns ./toeplitz "${IP_FLAG}" "${PROTO_FLAG}" \ - -d "${PORT}" -i "${DEV}" -k "${KEY}" -T 1000 \ - -r "0x${RPS_MAP}" -s -v & -else - ip netns exec $server_ns ./toeplitz "${IP_FLAG}" "${PROTO_FLAG}" \ - -d "${PORT}" -i "${DEV}" -k "${KEY}" -T 1000 -s -v & -fi - -server_pid=$! - -ip netns exec $client_ns ./toeplitz_client.sh "${PROTO_FLAG}" \ - "${IP_FLAG}" "${SERVER_IP%%/*}" "${PORT}" & - -client_pid=$! - -wait "${server_pid}" -exit_code=$? -kill -9 "${client_pid}" -if [[ "${exit_code}" -eq 0 ]]; then - echo "Test Succeeded!" -fi -exit "${exit_code}" diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz_client.sh b/tools/testing/selftests/drivers/net/hw/toeplitz_client.sh deleted file mode 100755 index 2fef34f4aba1..000000000000 --- a/tools/testing/selftests/drivers/net/hw/toeplitz_client.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: GPL-2.0 -# -# A simple program for generating traffic for the toeplitz test. -# -# This program sends packets periodically for, conservatively, 20 seconds. The -# intent is for the calling program to kill this program once it is no longer -# needed, rather than waiting for the 20 second expiration. - -send_traffic() { - expiration=$((SECONDS+20)) - while [[ "${SECONDS}" -lt "${expiration}" ]] - do - if [[ "${PROTO}" == "-u" ]]; then - echo "msg $i" | nc "${IPVER}" -u -w 0 "${ADDR}" "${PORT}" - else - echo "msg $i" | nc "${IPVER}" -w 0 "${ADDR}" "${PORT}" - fi - sleep 0.001 - done -} - -PROTO=$1 -IPVER=$2 -ADDR=$3 -PORT=$4 - -send_traffic
Jakub Kicinski wrote:
Rewrite the existing toeplitz.sh test in Python. The conversion is a lot less exact than the GRO one. We use Netlink APIs to get the device RSS and IRQ information. We expect that the device has neither RPS nor RFS configure, and set RPS up as part of
configure -> configured
the test.
Signed-off-by: Jakub Kicinski kuba@kernel.org
Support device loopback. Apparently this mode has been historically supported by the toeplitz test and I don't have any HW which lets me test the conversion..
Signed-off-by: Jakub Kicinski kuba@kernel.org --- drivers/net/netdevsim/netdev.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c index 2b713db16cd0..6927c1962277 100644 --- a/drivers/net/netdevsim/netdev.c +++ b/drivers/net/netdevsim/netdev.c @@ -133,15 +133,21 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev) if (!nsim_ipsec_tx(ns, skb)) goto out_drop_any;
- peer_ns = rcu_dereference(ns->peer); - if (!peer_ns) - goto out_drop_any; + /* Check if loopback mode is enabled */ + if (dev->features & NETIF_F_LOOPBACK) { + peer_ns = ns; + peer_dev = dev; + } else { + peer_ns = rcu_dereference(ns->peer); + if (!peer_ns) + goto out_drop_any; + peer_dev = peer_ns->netdev; + }
dr = nsim_do_psp(skb, ns, peer_ns, &psp_ext); if (dr) goto out_drop_free;
- peer_dev = peer_ns->netdev; rxq = skb_get_queue_mapping(skb); if (rxq >= peer_dev->num_rx_queues) rxq = rxq % peer_dev->num_rx_queues; @@ -976,7 +982,8 @@ static void nsim_setup(struct net_device *dev) NETIF_F_FRAGLIST | NETIF_F_HW_CSUM | NETIF_F_LRO | - NETIF_F_TSO; + NETIF_F_TSO | + NETIF_F_LOOPBACK; dev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS; dev->max_mtu = ETH_MAX_MTU; dev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_HW_OFFLOAD;
gro.sh and toeplitz.sh used to source in two setup scripts depending on whether the test was expected to be run against veth or a real device. veth testing is replaced by netdevsim and existing "remote endpoint" support in our Python tests. Add a script which sets up loopback mode.
The usage is a little bit more complicated than running the scripts used to be. Testing used to work like this:
./../gro.sh -i eth0 ...
now the "setup script" has to be run explicitly:
NETIF=eth0 ./../ksft_setup_loopback.sh ./../gro.sh
But the functionality itself is retained.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/net/lib/Makefile | 3 +- .../selftests/net/lib/ksft_setup_loopback.sh | 111 ++++++++++++++++ .../selftests/net/lib/setup_loopback.sh | 120 ------------------ tools/testing/selftests/net/lib/setup_veth.sh | 45 ------- 4 files changed, 112 insertions(+), 167 deletions(-) create mode 100755 tools/testing/selftests/net/lib/ksft_setup_loopback.sh delete mode 100644 tools/testing/selftests/net/lib/setup_loopback.sh delete mode 100644 tools/testing/selftests/net/lib/setup_veth.sh
diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile index c10796933d42..5339f56329e1 100644 --- a/tools/testing/selftests/net/lib/Makefile +++ b/tools/testing/selftests/net/lib/Makefile @@ -8,8 +8,7 @@ CFLAGS += -I../../ TEST_FILES := \ ../../../../net/ynl \ ../../../../../Documentation/netlink/specs \ - setup_loopback.sh \ - setup_veth.sh \ + ksft_setup_loopback.sh \ # end of TEST_FILES
TEST_GEN_FILES := \ diff --git a/tools/testing/selftests/net/lib/ksft_setup_loopback.sh b/tools/testing/selftests/net/lib/ksft_setup_loopback.sh new file mode 100755 index 000000000000..3defbb1919c5 --- /dev/null +++ b/tools/testing/selftests/net/lib/ksft_setup_loopback.sh @@ -0,0 +1,111 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# Setup script for running ksft tests over a real interface in loopback mode. +# This scripts replaces the historical setup_loopback.sh. It puts +# a (presumably) real hardware interface into loopback mode, creates macvlan +# interfaces on top and places them in a network namespace for isolation. +# +# NETIF env variable must be exported to indicate the real target device. +# Note that the test will override NETIF with one of the macvlans, the +# actual ksft test will only see the macvlans. +# +# Example use: +# export NETIF=eth0 +# ./net/lib/ksft_setup_loopback.sh ./drivers/net/gro.py + +if [ -z "$NETIF" ]; then + echo "Error: NETIF variable not set" + exit 1 +fi +if ! [ -d "/sys/class/net/$NETIF" ]; then + echo "Error: Can't find $NETIF, invalid netdevice" + exit 1 +fi + +# Save original settings for cleanup +readonly FLUSH_PATH="/sys/class/net/${NETIF}/gro_flush_timeout" +readonly IRQ_PATH="/sys/class/net/${NETIF}/napi_defer_hard_irqs" +FLUSH_TIMEOUT="$(< "${FLUSH_PATH}")" +readonly FLUSH_TIMEOUT +HARD_IRQS="$(< "${IRQ_PATH}")" +readonly HARD_IRQS + +SERVER_NS=$(mktemp -u server-XXXXXXXX) +readonly SERVER_NS +CLIENT_NS=$(mktemp -u client-XXXXXXXX) +readonly CLIENT_NS +readonly SERVER_MAC="aa:00:00:00:00:02" +readonly CLIENT_MAC="aa:00:00:00:00:01" + +# ksft expects addresses to communicate with remote +export LOCAL_V6=2001:db8:1::1 +export REMOTE_V6=2001:db8:1::2 + +cleanup() { + local exit_code=$? + + echo "Cleaning up..." + + # Remove macvlan interfaces and namespaces + ip -netns "${SERVER_NS}" link del dev server 2>/dev/null || true + ip netns del "${SERVER_NS}" 2>/dev/null || true + ip -netns "${CLIENT_NS}" link del dev client 2>/dev/null || true + ip netns del "${CLIENT_NS}" 2>/dev/null || true + + # Disable loopback + ethtool -K "${NETIF}" loopback off 2>/dev/null || true + sleep 1 + + echo "${FLUSH_TIMEOUT}" >"${FLUSH_PATH}" + echo "${HARD_IRQS}" >"${IRQ_PATH}" + + exit $exit_code +} + +trap cleanup EXIT INT TERM + +# Enable loopback mode +echo "Enabling loopback on ${NETIF}..." +ethtool -K "${NETIF}" loopback on || { + echo "Failed to enable loopback mode" + exit 1 +} +# The interface may need time to get carrier back, but selftests +# will wait for carrier, so no need to wait / sleep here. + +# Use timer on host to trigger the network stack +# Also disable device interrupt to not depend on NIC interrupt +# Reduce test flakiness caused by unexpected interrupts +echo 100000 >"${FLUSH_PATH}" +echo 50 >"${IRQ_PATH}" + +# Create server namespace with macvlan +ip netns add "${SERVER_NS}" +ip link add link "${NETIF}" dev server address "${SERVER_MAC}" type macvlan +ip link set dev server netns "${SERVER_NS}" +ip -netns "${SERVER_NS}" link set dev server up +ip -netns "${SERVER_NS}" addr add $LOCAL_V6/64 dev server +ip -netns "${SERVER_NS}" link set dev lo up + +# Create client namespace with macvlan +ip netns add "${CLIENT_NS}" +ip link add link "${NETIF}" dev client address "${CLIENT_MAC}" type macvlan +ip link set dev client netns "${CLIENT_NS}" +ip -netns "${CLIENT_NS}" link set dev client up +ip -netns "${CLIENT_NS}" addr add $REMOTE_V6/64 dev client +ip -netns "${CLIENT_NS}" link set dev lo up + +echo "Setup complete!" +echo " Device: ${NETIF}" +echo " Server NS: ${SERVER_NS}" +echo " Client NS: ${CLIENT_NS}" +echo "" + +# Setup environment variables for tests +export NETIF=server +export REMOTE_TYPE=netns +export REMOTE_ARGS="${CLIENT_NS}" + +# Run the command +ip netns exec "${SERVER_NS}" "$@" diff --git a/tools/testing/selftests/net/lib/setup_loopback.sh b/tools/testing/selftests/net/lib/setup_loopback.sh deleted file mode 100644 index 2070b57849de..000000000000 --- a/tools/testing/selftests/net/lib/setup_loopback.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: GPL-2.0 - -readonly FLUSH_PATH="/sys/class/net/${dev}/gro_flush_timeout" -readonly IRQ_PATH="/sys/class/net/${dev}/napi_defer_hard_irqs" -readonly FLUSH_TIMEOUT="$(< ${FLUSH_PATH})" -readonly HARD_IRQS="$(< ${IRQ_PATH})" -readonly server_ns=$(mktemp -u server-XXXXXXXX) -readonly client_ns=$(mktemp -u client-XXXXXXXX) - -netdev_check_for_carrier() { - local -r dev="$1" - - for i in {1..5}; do - carrier="$(cat /sys/class/net/${dev}/carrier)" - if [[ "${carrier}" -ne 1 ]] ; then - echo "carrier not ready yet..." >&2 - sleep 1 - else - echo "carrier ready" >&2 - break - fi - done - echo "${carrier}" -} - -# Assumes that there is no existing ipvlan device on the physical device -setup_loopback_environment() { - local dev="$1" - - # Fail hard if cannot turn on loopback mode for current NIC - ethtool -K "${dev}" loopback on || exit 1 - sleep 1 - - # Check for the carrier - carrier=$(netdev_check_for_carrier ${dev}) - if [[ "${carrier}" -ne 1 ]] ; then - echo "setup_loopback_environment failed" - exit 1 - fi -} - -setup_macvlan_ns(){ - local -r link_dev="$1" - local -r ns_name="$2" - local -r ns_dev="$3" - local -r ns_mac="$4" - local -r addr="$5" - - ip link add link "${link_dev}" dev "${ns_dev}" \ - address "${ns_mac}" type macvlan - exit_code=$? - if [[ "${exit_code}" -ne 0 ]]; then - echo "setup_macvlan_ns failed" - exit $exit_code - fi - - [[ -e /var/run/netns/"${ns_name}" ]] || ip netns add "${ns_name}" - ip link set dev "${ns_dev}" netns "${ns_name}" - ip -netns "${ns_name}" link set dev "${ns_dev}" up - if [[ -n "${addr}" ]]; then - ip -netns "${ns_name}" addr add dev "${ns_dev}" "${addr}" - fi - - sleep 1 -} - -cleanup_macvlan_ns(){ - while (( $# >= 2 )); do - ns_name="$1" - ns_dev="$2" - ip -netns "${ns_name}" link del dev "${ns_dev}" - ip netns del "${ns_name}" - shift 2 - done -} - -cleanup_loopback(){ - local -r dev="$1" - - ethtool -K "${dev}" loopback off - sleep 1 - - # Check for the carrier - carrier=$(netdev_check_for_carrier ${dev}) - if [[ "${carrier}" -ne 1 ]] ; then - echo "setup_loopback_environment failed" - exit 1 - fi -} - -setup_interrupt() { - # Use timer on host to trigger the network stack - # Also disable device interrupt to not depend on NIC interrupt - # Reduce test flakiness caused by unexpected interrupts - echo 100000 >"${FLUSH_PATH}" - echo 50 >"${IRQ_PATH}" -} - -setup_ns() { - # Set up server_ns namespace and client_ns namespace - setup_macvlan_ns "${dev}" ${server_ns} server "${SERVER_MAC}" - setup_macvlan_ns "${dev}" ${client_ns} client "${CLIENT_MAC}" -} - -cleanup_ns() { - cleanup_macvlan_ns ${server_ns} server ${client_ns} client -} - -setup() { - setup_loopback_environment "${dev}" - setup_interrupt -} - -cleanup() { - cleanup_loopback "${dev}" - - echo "${FLUSH_TIMEOUT}" >"${FLUSH_PATH}" - echo "${HARD_IRQS}" >"${IRQ_PATH}" -} diff --git a/tools/testing/selftests/net/lib/setup_veth.sh b/tools/testing/selftests/net/lib/setup_veth.sh deleted file mode 100644 index 152bf4c65747..000000000000 --- a/tools/testing/selftests/net/lib/setup_veth.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: GPL-2.0 - -readonly server_ns=$(mktemp -u server-XXXXXXXX) -readonly client_ns=$(mktemp -u client-XXXXXXXX) - -setup_veth_ns() { - local -r link_dev="$1" - local -r ns_name="$2" - local -r ns_dev="$3" - local -r ns_mac="$4" - - [[ -e /var/run/netns/"${ns_name}" ]] || ip netns add "${ns_name}" - echo 200000 > "/sys/class/net/${ns_dev}/gro_flush_timeout" - echo 1 > "/sys/class/net/${ns_dev}/napi_defer_hard_irqs" - ip link set dev "${ns_dev}" netns "${ns_name}" mtu 65535 - ip -netns "${ns_name}" link set dev "${ns_dev}" up - - ip netns exec "${ns_name}" ethtool -K "${ns_dev}" gro on tso off -} - -setup_ns() { - # Set up server_ns namespace and client_ns namespace - ip link add name server type veth peer name client - - setup_veth_ns "${dev}" ${server_ns} server "${SERVER_MAC}" - setup_veth_ns "${dev}" ${client_ns} client "${CLIENT_MAC}" -} - -cleanup_ns() { - local ns_name - - for ns_name in ${client_ns} ${server_ns}; do - [[ -e /var/run/netns/"${ns_name}" ]] && ip netns del "${ns_name}" - done -} - -setup() { - # no global init setup step needed - : -} - -cleanup() { - cleanup_ns -}
Jakub Kicinski wrote:
Main objective of this series is to convert the gro.sh and toeplitz.sh tests to be "NIPA-compatible" - meaning make use of the Python env, which lets us run the tests against either netdevsim or a real device.
The tests seem to have been written with a different flow in mind. Namely they source different bash "setup" scripts depending on arguments passed to the test. While I have nothing against the use of bash and the overall architecture - the existing code needs quite a bit of work (don't assume MAC/IP addresses, support remote endpoint over SSH). If I'm the one fixing it, I'd rather convert them to our "simplistic" Python.
This series rewrites the tests in Python while addressing their shortcomings. The functionality of running the test over loopback on a real device is retained but with a different method of invocation (see the last patch).
Once again we are dealing with a script which run over a variety of protocols (combination of [ipv4, ipv6, ipip] x [tcp, udp]). The first 4 patches add support for test variants to our scripts. We use the term "variant" in the same sense as the C kselftest_harness.h - variant is just a set of static input arguments.
Note that neither GRO nor the Toeplitz test fully passes for me on any HW I have access to. But this is unrelated to the conversion.
You observed the same failures with the old and new tests? Are they deterministic failures or flakes.
This series is not making any real functional changes to the tests, it is limited to improving the "test harness" scripts.
Jakub Kicinski (12): selftests: net: py: coding style improvements selftests: net: py: extract the case generation logic selftests: net: py: add test variants selftests: drv-net: xdp: use variants for qstat tests selftests: net: relocate gro and toeplitz tests to drivers/net selftests: net: py: support ksft ready without wait selftests: net: py: read ip link info about remote dev netdevsim: pass packets thru GRO on Rx selftests: drv-net: add a Python version of the GRO test selftests: drv-net: hw: convert the Toeplitz test to Python netdevsim: add loopback support selftests: net: remove old setup_* scripts
Thanks for converting these tests!
No significant actionable comments, just a few trivial typos.
On Mon, 17 Nov 2025 21:11:31 -0500 Willem de Bruijn wrote:
Note that neither GRO nor the Toeplitz test fully passes for me on any HW I have access to. But this is unrelated to the conversion.
You observed the same failures with the old and new tests? Are they deterministic failures or flakes.
Deterministic for Toeplitz - all NICs I have calculate the Rx hash the same as the test for at least one of traffic types. But none of them exactly as the test is expecting. One IIRC also uses non-standard RSS indir table pattern by default. The indirection table will be a trivial fix.
For HW-GRO I investigated less closely I mostly focused on making sure netdevsim is solid as a replacement for veth. There was more flakiness on HW (admittedly I was running inter-dc-building). But the failures looked rather sus - the test was reporting that packets which were not supposed to be coalesced got coalesced.
BTW it's slightly inconvenient that we disable HW-GRO when normal GRO is disabled :( Makes it quite hard to run the test to check device behavior. My current plan is to rely on device counters to check whether traffic is getting coalesced but better ideas most welcome :(
This series is not making any real functional changes to the tests, it is limited to improving the "test harness" scripts.
No significant actionable comments, just a few trivial typos.
Thanks!
Jakub Kicinski wrote:
On Mon, 17 Nov 2025 21:11:31 -0500 Willem de Bruijn wrote:
Note that neither GRO nor the Toeplitz test fully passes for me on any HW I have access to. But this is unrelated to the conversion.
You observed the same failures with the old and new tests? Are they deterministic failures or flakes.
Deterministic for Toeplitz - all NICs I have calculate the Rx hash the same as the test for at least one of traffic types. But none of them exactly as the test is expecting. One IIRC also uses non-standard RSS indir table pattern by default. The indirection table will be a trivial fix.
Ugh yes we've had a bug open for ages internally to add indirection table parsing to the test:
The (upstream) RSS test is too simplistic: it calculates
queue_id = hash % num_queues
Real RSS uses an indirection table:
queue_id = indir_table[hash % indir_table_len]
For HW-GRO I investigated less closely I mostly focused on making sure netdevsim is solid as a replacement for veth. There was more flakiness on HW (admittedly I was running inter-dc-building). But the failures looked rather sus - the test was reporting that packets which were not supposed to be coalesced got coalesced.
The reverse is a known cause of flakiness, due to the context closure timer firing. But unexpected coalescing definitely seems suspicious.
BTW it's slightly inconvenient that we disable HW-GRO when normal GRO is disabled :( Makes it quite hard to run the test to check device behavior. My current plan is to rely on device counters to check whether traffic is getting coalesced but better ideas most welcome :(
We probably have to maintain this behavior, but could add an override to enable only HW-GRO.
Alternatively, just for measurement, a bpf fentry program. But that is a lot more complex than reading the counters, which is sufficient signal.
This series is not making any real functional changes to the tests, it is limited to improving the "test harness" scripts.
No significant actionable comments, just a few trivial typos.
Thanks!
linux-kselftest-mirror@lists.linaro.org