selftests: drv-net: tso: add double tunneling GSO tests
Subsystems (3)
cover selftests: drv-net: tso: add double tunneling GSO tests
This series extends the TSO selftest (tso.py) to cover double-encapsulated tunnel scenarios, ensuring that hardware offload and the kernel GSO path correctly handle packets with two layers of tunnel headers.
Xu Du (4):
selftests: drv-net: tso: retry connect on EHOSTUNREACH
selftests: drv-net: tso: add helpers for double tunneling GSO
selftests: drv-net: tso: add Geneve double tunneling GSO test
selftests: drv-net: tso: expand double tunnel GSO test coverage
.../drivers/net/hw/lib/py/__init__.py | 4 +- tools/testing/selftests/drivers/net/hw/tso.py | 267 +++++++++++++++--- .../selftests/drivers/net/lib/py/__init__.py | 4 +- .../testing/selftests/net/lib/py/__init__.py | 4 +- tools/testing/selftests/net/lib/py/utils.py | 19 +- 5 files changed, 248 insertions(+), 50 deletions(-)
base-commit: 2f60df9e61aa48bf40c36254bf2e839f09cffd98
Signature · 1 line
2.53.0
RFC 1/4 selftests: drv-net: tso: retry connect on EHOSTUNREACH
The TSO test occasionally fails with "No route to host" (EHOSTUNREACH) when connecting to the remote socat listener. This can happen when the neighbor resolution has not yet completed by the time the test attempts to connect, particularly in tunnel setups where neighbor entries may take slightly longer to establish.
Add a retry loop (up to 5 attempts with 0.5s delay) around the sock.connect() call to handle this transient condition gracefully.
--- tools/testing/selftests/drivers/net/hw/tso.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)
tools/testing/selftests/drivers/net/hw/tso.py +13 -2
@@ -3,6 +3,7 @@ """Run the tools/testing/selftests/net/csum testsuite.""" +import errno import fcntl import socket import struct
@@ -47,10 +48,20 @@ def run_one_stream(cfg, ipver, remote_v4, remote_v6, should_lso): if ipver == "4": sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((remote_v4, port)) + sockaddr = (remote_v4, port) else: sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - sock.connect((remote_v6, port)) + sockaddr = (remote_v6, port) + + for attempt in range(5): + try: + sock.connect(sockaddr) + break + except OSError as e: + if e.errno == errno.EHOSTUNREACH and attempt < 4: + time.sleep(0.5) + else: + raise # Small send to make sure the connection is working. sock.send("ping".encode())
Signature · 1 line
2.53.0
RFC 2/4 selftests: drv-net: tso: add helpers for double tunneling GSO
In reply to Xu DuAdd helper functions to support double tunneling GSO testing. Since certain tunnel-specific parameters (e.g., gro-hint) are not yet supported by the standard ip-link tool, this patch adds a new helper function utilizing the ynl-cli to handle these extended netlink attributes.
As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.
--- .../drivers/net/hw/lib/py/__init__.py | 4 ++-- .../selftests/drivers/net/lib/py/__init__.py | 4 ++-- .../testing/selftests/net/lib/py/__init__.py | 4 ++-- tools/testing/selftests/net/lib/py/utils.py | 19 +++++++++++++++++-- 4 files changed, 23 insertions(+), 8 deletions(-)
tools/testing/selftests/drivers/net/hw/lib/py/__init__.py +2 -2
@@ -24,7 +24,7 @@ try: 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, tool + wait_file, tool, ynlcli 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, \
@@ -40,7 +40,7 @@ try: "CmdExitFailure", "bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool", "fd_read_timeout", "ip", "rand_port", "rand_ports", - "wait_port_listen", "wait_file", "tool", + "wait_port_listen", "wait_file", "tool", "ynlcli", "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
tools/testing/selftests/drivers/net/lib/py/__init__.py +2 -2
@@ -22,7 +22,7 @@ try: from net.lib.py import EthtoolFamily, NetdevFamily, NetshaperFamily, \ NlError, RtnlFamily, DevlinkFamily, PSPFamily, Netlink from net.lib.py import CmdExitFailure - from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \ + from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, ynlcli, \ fd_read_timeout, ip, rand_port, rand_ports, 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
@@ -36,7 +36,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", "ynlcli", "wait_port_listen", "wait_file", "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "KsftSkipEx", "KsftFailEx", "KsftXfailEx",
tools/testing/selftests/net/lib/py/__init__.py +2 -2
@@ -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, ynlcli 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", "ynlcli", "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "NetdevSim", "NetdevSimDev", "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
tools/testing/selftests/net/lib/py/utils.py +17 -2
@@ -8,6 +8,8 @@ import socket import subprocess import time +from .consts import KSRC, KSFT_DIR + class CmdInitFailure(Exception): """ Command failed to start. Only raised by bkg(). """
@@ -217,12 +219,12 @@ class defer: self.exec_only() -def tool(name, args, json=None, ns=None, host=None): +def tool(name, args, json=None, ns=None, host=None, shell=None): cmd_str = name + ' ' if json: cmd_str += '--json ' cmd_str += args - cmd_obj = cmd(cmd_str, ns=ns, host=host) + cmd_obj = cmd(cmd_str, ns=ns, host=host, shell=shell) if json: return _json.loads(cmd_obj.stdout) return cmd_obj
@@ -242,6 +244,19 @@ def ethtool(args, json=None, ns=None, host=None): return tool('ethtool', args, json=json, ns=ns, host=host) +def ynlcli(family, args, json=None, ns=None, host=None): + if (KSFT_DIR / "kselftest-list.txt").exists(): + cli = KSFT_DIR / "net/lib/ynl/pyynl/cli.py" + spec = KSFT_DIR / f"net/lib/specs/{family}.yaml" + else: + cli = KSRC / "tools/net/ynl/pyynl/cli.py" + spec = KSRC / f"Documentation/netlink/specs/{family}.yaml" + if not cli.exists(): + raise FileNotFoundError(f"cli not found at {cli}") + args = f"--spec {spec} --no-schema {args}" + return tool(cli.as_posix(), args, json=json, ns=ns, host=host, shell=True) + + def bpftrace(expr, json=None, ns=None, host=None, timeout=None): """ Run bpftrace and return map data (if json=True).
Signature · 1 line
2.53.0
RFC 4/4 selftests: drv-net: tso: expand double tunnel GSO test coverage
In reply to Xu DuAdd Geneve test variants with GRO hint and TTL inherit options to exercise additional tunnel configuration paths.
Also add double VXLAN tunnel test cases to complement the existing double Geneve coverage.
--- tools/testing/selftests/drivers/net/hw/tso.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+)
tools/testing/selftests/drivers/net/hw/tso.py +24 -0
@@ -373,6 +373,12 @@ def main() -> None: ("vxlan", "id 100 dstport 4789 udpcsum", "", ("4", "6")), (), ), + ( + "vxlan2", "4", "tx-udp_tnl-csum-segmentation", + ("vxlan", "id 100 dstport 4789 udpcsum", + "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),), + ("vxlan", "id 200 dstport 5789 udpcsum", "", ("4", "6")), + ), ( "gre", "4", "tx-gre-segmentation", ("gre", "", "", ("4", "6")),
@@ -390,6 +396,24 @@ def main() -> None: ("geneve", "id 200 dstport 6082 noudpcsum udp6zerocsumtx udp6zerocsumrx", "", ("4", "6"),), ), + ( + "geneve2_csum", "", "tx-udp_tnl-csum-segmentation", + ("geneve", "id 100 dstport 6081 udpcsum", + "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),), + ("geneve", "id 200 dstport 6082 noudpcsum", "", ("4", "6")), + ), + ( + "geneve2_grohint", "", "tx-udp_tnl-csum-segmentation", + ("geneve", "id 100 dstport 6081 udpcsum gro-hint", + "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),), + ("geneve", "id 200 dstport 6082 udpcsum", "", ("4", "6")), + ), + ( + "geneve2_inherit", "4", "tx-udp_tnl-csum-segmentation", + ("geneve", "id 100 dstport 6081 udpcsum gro-hint", + "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),), + ("geneve", "id 200 dstport 6082 udpcsum inner-proto-inherit", "", ("4", "6")), + ), ) cases = []
Signature · 1 line
2.53.0
RFC 3/4 selftests: drv-net: tso: add Geneve double tunneling GSO test
In reply to Xu DuAdd a new test case to verify GSO behavior for double-encapsulated Geneve tunnels. This ensures that hardware drivers and the network stack correctly handle nested tunnel headers during segmentation.
To maintain consistency and avoid complexity in hardware parser logic, the test case ensures that the IP versions for both the inner and outer headers remain the same.
--- tools/testing/selftests/drivers/net/hw/tso.py | 228 +++++++++++++++--- 1 file changed, 188 insertions(+), 40 deletions(-)
tools/testing/selftests/drivers/net/hw/tso.py +188 -40
@@ -5,6 +5,7 @@ import errno import fcntl +import json import socket import struct import termios
@@ -13,7 +14,29 @@ import time from lib.py import ksft_pr, ksft_run, ksft_exit, KsftSkipEx, KsftXfailEx from lib.py import ksft_eq, ksft_ge, ksft_lt from lib.py import EthtoolFamily, NetdevFamily, NetDrvEpEnv -from lib.py import bkg, cmd, defer, ethtool, ip, rand_port, wait_port_listen +from lib.py import bkg, ynlcli, defer, ethtool, ip, rand_port, wait_port_listen + + +def _convert_ip(key): + def _conv(val): + final_key = f"{key}6" if ":" in str(val) else key + return final_key, val + + return _conv + + +YNLLINK_PARAM_MAP = { + "geneve": { + "id": ("id", int), + "dstport": ("port", int), + "remote": ("", _convert_ip("remote")), + "udpcsum": ("udp-csum",), + "udp6zerocsumtx": ("udp-zero-csum6-tx",), + "udp6zerocsumrx": ("udp-zero-csum6-rx",), + "gro-hint": ("gro-hint",), + "innerprotoinherit": ("inner-proto-inherit",), + }, +} def sock_wait_drain(sock, max_wait=1000):
@@ -107,29 +130,106 @@ def run_one_stream(cfg, ipver, remote_v4, remote_v6, should_lso): 500, comment="Number of LSO wire-packets with LSO disabled") -def build_tunnel(cfg, outer_ipver, tun_info): - local_v4 = NetDrvEpEnv.nsim_v4_pfx + "1" - local_v6 = NetDrvEpEnv.nsim_v6_pfx + "1" - remote_v4 = NetDrvEpEnv.nsim_v4_pfx + "2" - remote_v6 = NetDrvEpEnv.nsim_v6_pfx + "2" - - local_addr = cfg.addr_v[outer_ipver] - remote_addr = cfg.remote_addr_v[outer_ipver] +def get_tun_ip_address(encap_tun): + pfx_v4 = NetDrvEpEnv.nsim_v4_pfx + pfx_v6 = NetDrvEpEnv.nsim_v6_pfx.rsplit("::", 1)[0] + ":" + v4_offset = 1 if encap_tun == "outer" else 17 + v6_offset = "1" if encap_tun == "outer" else "2" + local_v4 = f"{pfx_v4}{v4_offset}" + remote_v4 = f"{pfx_v4}{v4_offset + 1}" + local_v6 = f"{pfx_v6}{v6_offset}::1" + remote_v6 = f"{pfx_v6}{v6_offset}::2" + return local_v4, remote_v4, 28, local_v6, remote_v6, 80 + + +def parse_tun_args(tun_arg, tunnel_arg_dict): + data = {} + args = tun_arg.split() + i = 0 + while i < len(args): + negate = False + item = tunnel_arg_dict.get(args[i]) + if item is None and args[i].startswith("no"): + item = tunnel_arg_dict.get(args[i][2:]) + negate = True + if item and len(item) == 2: + key, conv = item + val = conv(args[i + 1]) + if isinstance(val, tuple) and len(val) == 2: + data[val[0]] = val[1] + else: + data[key] = val + i += 2 + elif item: + data[item[0]] = 0 if negate else 1 + i += 1 + else: + i += 1 + return data + + +def create_tunnel(tun_type, tun_name, tun_arg, host=None) -> None: + tunnel_arg_dict = YNLLINK_PARAM_MAP.get(tun_type) + if tunnel_arg_dict: + data = parse_tun_args(tun_arg, tunnel_arg_dict) + nl_msg = json.dumps({ + "ifname": tun_name, + "linkinfo": {"kind": tun_type, "data": data}, + }) + ynlcli("rt-link", + f"--create --excl --do newlink --json '{nl_msg}'", + host=host) + else: + ip(f"link add {tun_name} type {tun_type} {tun_arg}", host=host) + + +def build_tunnel(cfg, encap_ipver, encap_tun, tun_info): + if encap_tun == "outer": + encap_addr = cfg.addr_v[encap_ipver] + decap_addr = cfg.remote_addr_v[encap_ipver] + else: + local_v4, remote_v4, _, local_v6, remote_v6, _ = get_tun_ip_address( + "outer") + encap_addr = local_v4 if encap_ipver == "4" else local_v6 + decap_addr = remote_v4 if encap_ipver == "4" else remote_v6 tun_type = tun_info[0] - tun_arg = tun_info[1] - ip(f"link add {tun_type}-ksft type {tun_type} {tun_arg} local {local_addr} remote {remote_addr} dev {cfg.ifname}") - defer(ip, f"link del {tun_type}-ksft") - ip(f"link set dev {tun_type}-ksft up") - ip(f"addr add {local_v4}/24 dev {tun_type}-ksft") - ip(f"addr add {local_v6}/64 dev {tun_type}-ksft") - - ip(f"link add {tun_type}-ksft type {tun_type} {tun_arg} local {remote_addr} remote {local_addr} dev {cfg.remote_ifname}", - host=cfg.remote) - defer(ip, f"link del {tun_type}-ksft", host=cfg.remote) - ip(f"link set dev {tun_type}-ksft up", host=cfg.remote) - ip(f"addr add {remote_v4}/24 dev {tun_type}-ksft", host=cfg.remote) - ip(f"addr add {remote_v6}/64 dev {tun_type}-ksft", host=cfg.remote) + tun_name = f"{tun_type}-ksft" if encap_tun == "outer" else f"in-{tun_type}-ksft" + local_v4, remote_v4, mask_v4, local_v6, remote_v6, mask_v6 = get_tun_ip_address( + encap_tun + ) + tun_features = " ".join( + f + " on" for f in tun_info[2].split()) if tun_info[2] else "" + + tun_dev = cfg.ifname if encap_tun == "outer" else f"{tun_type}-ksft" + tun_arg = tun_info[1] + \ + f" local {encap_addr} remote {decap_addr} dev {tun_dev}" + + create_tunnel(tun_type, tun_name, tun_arg) + defer(ip, f"link del {tun_name}") + ip(f"link set dev {tun_name} up") + ip(f"addr add {local_v4}/{mask_v4} dev {tun_name}") + ip(f"addr add {local_v6}/{mask_v6} dev {tun_name}") + if tun_features: + ethtool(f"-K {tun_name} {tun_features}") + # pmtu can't be propagated to upper layer devices; need manual adjust + if encap_tun == "inner": + tun_mtu = 1392 if encap_ipver == "4" else 1352 + ip(f"link set dev {tun_name} mtu {tun_mtu}") + + tun_dev = cfg.remote_ifname if encap_tun == "outer" else f"{tun_type}-ksft" + tun_arg = tun_info[1] + \ + f" local {decap_addr} remote {encap_addr} dev {tun_dev}" + + create_tunnel(tun_type, tun_name, tun_arg, host=cfg.remote) + defer(ip, f"link del {tun_name}", host=cfg.remote) + ip(f"link set dev {tun_name} up", host=cfg.remote) + ip(f"addr add {remote_v4}/{mask_v4} dev {tun_name}", host=cfg.remote) + ip(f"addr add {remote_v6}/{mask_v6} dev {tun_name}", host=cfg.remote) + if tun_features: + ethtool(f"-K {tun_name} {tun_features}", host=cfg.remote) + if encap_tun == "inner": + ip(f"link set dev {tun_name} mtu {tun_mtu}", host=cfg.remote) return remote_v4, remote_v6
@@ -145,7 +245,10 @@ def restore_wanted_features(cfg): ksft_pr(f"WARNING: failure restoring wanted features: {e}") -def test_builder(name, cfg, outer_ipver, feature, tun=None, inner_ipver=None): +def test_builder( + name, cfg, outer_ipver, feature, outer_tun=None, + inner_tun=None, inner_ipver=None, +): """Construct specific tests from the common template.""" def f(cfg): cfg.require_ipver(outer_ipver)
@@ -159,8 +262,12 @@ def test_builder(name, cfg, outer_ipver, feature, tun=None, inner_ipver=None): raise KsftSkipEx(f"Device does not support {feature}") ipver = outer_ipver - if tun: - remote_v4, remote_v6 = build_tunnel(cfg, ipver, tun) + if outer_tun and inner_tun: + build_tunnel(cfg, ipver, "outer", outer_tun) + remote_v4, remote_v6 = build_tunnel(cfg, ipver, "inner", inner_tun) + ipver = inner_ipver + elif outer_tun: + remote_v4, remote_v6 = build_tunnel(cfg, ipver, "outer", outer_tun) ipver = inner_ipver else: remote_v4 = cfg.remote_addr_v["4"]
@@ -182,7 +289,11 @@ def test_builder(name, cfg, outer_ipver, feature, tun=None, inner_ipver=None): ethtool(f"-K {cfg.ifname} {feature} on") run_one_stream(cfg, ipver, remote_v4, remote_v6, should_lso=True) - f.__name__ = name + ((outer_ipver + "_") if tun else "") + "ipv" + inner_ipver + outer_tun_name = f"{outer_tun[0]}{outer_ipver}_" if outer_tun else "" + inner_tun_name = f"{inner_tun[0]}{outer_ipver}_" if inner_tun else "" + f.__name__ = ( + name + "_" if name else "" + ) + f"{outer_tun_name}{inner_tun_name}ipv{inner_ipver}" return f
@@ -242,14 +353,43 @@ def main() -> None: query_nic_features(cfg) test_info = ( - # name, v4/v6 ethtool_feature tun:(type, args, inner ip versions) - ("", "4", "tx-tcp-segmentation", None), - ("", "6", "tx-tcp6-segmentation", None), - ("vxlan", "4", "tx-udp_tnl-segmentation", ("vxlan", "id 100 dstport 4789 noudpcsum", ("4", "6"))), - ("vxlan", "6", "tx-udp_tnl-segmentation", ("vxlan", "id 100 dstport 4789 udp6zerocsumtx udp6zerocsumrx", ("4", "6"))), - ("vxlan_csum", "", "tx-udp_tnl-csum-segmentation", ("vxlan", "id 100 dstport 4789 udpcsum", ("4", "6"))), - ("gre", "4", "tx-gre-segmentation", ("gre", "", ("4", "6"))), - ("gre", "6", "tx-gre-segmentation", ("ip6gre","", ("4", "6"))), + # name, v4/v6, ethtool_feature, + # outer_tun:(type, args, ethtool_feature, inner ip versions), + # inner_tun:(type, args, ethtool_feature, inner ip versions) + ("", "4", "tx-tcp-segmentation", (), ()), + ("", "6", "tx-tcp6-segmentation", (), ()), + ( + "vxlan", "4", "tx-udp_tnl-segmentation", + ("vxlan", "id 100 dstport 4789 noudpcsum", "", ("4", "6")), (), + ), + ( + "vxlan", "6", "tx-udp_tnl-segmentation", + ("vxlan", "id 100 dstport 4789 udp6zerocsumtx udp6zerocsumrx", + "", ("4", "6")), + (), + ), + ( + "vxlan_csum", "", "tx-udp_tnl-csum-segmentation", + ("vxlan", "id 100 dstport 4789 udpcsum", "", ("4", "6")), + (), + ), + ( + "gre", "4", "tx-gre-segmentation", + ("gre", "", "", ("4", "6")), + (), + ), + ( + "gre", "6", "tx-gre-segmentation", + ("ip6gre", "", "", ("4", "6")), + (), + ), + ( + "geneve2", "4", "tx-udp_tnl-segmentation", + ("geneve", "id 100 dstport 6081 noudpcsum udp6zerocsumtx udp6zerocsumrx", + "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),), + ("geneve", "id 200 dstport 6082 noudpcsum udp6zerocsumtx udp6zerocsumrx", + "", ("4", "6"),), + ), ) cases = []
@@ -259,13 +399,21 @@ def main() -> None: if info[1] and outer_ipver != info[1]: continue - if info[3]: - cases += [ - test_builder(info[0], cfg, outer_ipver, info[2], info[3], inner_ipver) - for inner_ipver in info[3][2] - ] + outer_tun, inner_tun = info[3], info[4] + if outer_tun and inner_tun: + inner_ipvers = inner_tun[3] + cases += [test_builder(info[0], cfg, outer_ipver, info[2], + outer_tun, inner_tun, inner_ipver) + for inner_ipver in inner_ipvers] + elif outer_tun: + inner_ipvers = outer_tun[3] + cases += [test_builder(info[0], cfg, outer_ipver, info[2], + outer_tun, None, inner_ipver) + for inner_ipver in inner_ipvers] else: - cases.append(test_builder(info[0], cfg, outer_ipver, info[2], None, outer_ipver)) + cases.append( + test_builder(info[0], cfg, outer_ipver, info[2], + None, None, outer_ipver)) ksft_run(cases=cases, args=(cfg, )) ksft_exit()
Signature · 1 line
2.53.0
Jakub Kicinski
maintainerRe: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO
In reply to Xu DuQuoted 4 lines · On Tue, 7 Apr 2026 10:45:09 +0800 Xu Du wrote:
As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.
Can you explain more? Why can't you use class RtnlFamily?
Re: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO
Quoted 9 lines · Jakub Kicinski
Quoted 4 lines · On Tue, 7 Apr 2026 10:45:09 +0800 Xu Du wrote:
As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.
Can you explain more? Why can't you use class RtnlFamily?
Hi Jakub,
I want to test the gro-hint parameter functionality of the GENEVE tunnel, so I intend to use YNL for the testing. I am conducting the test between two machines using SSH type. I want to add the gro-hint parameter on both the local and remote nodes; however, I am unable to invoke class RtnlFamily on the remote node via SSH.
Signature · 3 lines
Regards, Xu
Jakub Kicinski
maintainerRe: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO
Quoted 8 lines · On Wed, 8 Apr 2026 10:04:09 +0800 Xu Du wrote:
Quoted 7 lines · Jakub Kicinski
Quoted 4 lines · On Tue, 7 Apr 2026 10:45:09 +0800 Xu Du wrote:
As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.
Quoted 7 lines
Quoted 1 line
Can you explain more? Why can't you use class RtnlFamily?
I want to test the gro-hint parameter functionality of the GENEVE tunnel, so I intend to use YNL for the testing. I am conducting the test between two machines using SSH type. I want to add the gro-hint parameter on both the local and remote nodes; however, I am unable to invoke class RtnlFamily on the remote node via SSH.
Oh. But that's not really what you're doing:
+def ynlcli(family, args, json=None, ns=None, host=None):
+ if (KSFT_DIR / "kselftest-list.txt").exists():
+ cli = KSFT_DIR / "net/lib/ynl/pyynl/cli.py"
+ spec = KSFT_DIR / f"net/lib/specs/{family}.yaml"
+ else:
+ cli = KSRC / "tools/net/ynl/pyynl/cli.py"
+ spec = KSRC / f"Documentation/netlink/specs/{family}.yaml"
+ if not cli.exists():
+ raise FileNotFoundError(f"cli not found at {cli}")
+ args = f"--spec {spec} --no-schema {args}"
+ return tool(cli.as_posix(), args, json=json, ns=ns, host=host, shell=True)
You're not deploying anything to the remote system. Are you assuming that the remote system magically has the same filesystem layout?
You can use the ynl CLI but it has to be whatever version is on the remote system. Just call ynl --family rt-link, don't dig around for the spec paths etc.
Re: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO
Quoted 32 lines · Jakub Kicinski
Quoted 8 lines · On Wed, 8 Apr 2026 10:04:09 +0800 Xu Du wrote:
Quoted 7 lines · Jakub Kicinski
Quoted 4 lines · On Tue, 7 Apr 2026 10:45:09 +0800 Xu Du wrote:
As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.
Quoted 7 lines
Quoted 1 line
Can you explain more? Why can't you use class RtnlFamily?
I want to test the gro-hint parameter functionality of the GENEVE tunnel, so I intend to use YNL for the testing. I am conducting the test between two machines using SSH type. I want to add the gro-hint parameter on both the local and remote nodes; however, I am unable to invoke class RtnlFamily on the remote node via SSH.
Oh. But that's not really what you're doing:
tools/testing/selftests/net/lib/py/utils.py +11
-0+def ynlcli(family, args, json=None, ns=None, host=None): + if (KSFT_DIR / "kselftest-list.txt").exists(): + cli = KSFT_DIR / "net/lib/ynl/pyynl/cli.py" + spec = KSFT_DIR / f"net/lib/specs/{family}.yaml" + else: + cli = KSRC / "tools/net/ynl/pyynl/cli.py" + spec = KSRC / f"Documentation/netlink/specs/{family}.yaml" + if not cli.exists(): + raise FileNotFoundError(f"cli not found at {cli}") + args = f"--spec {spec} --no-schema {args}" + return tool(cli.as_posix(), args, json=json, ns=ns, host=host, shell=True)
Quoted 8 lines
You're not deploying anything to the remote system. Are you assuming that the remote system magically has the same filesystem layout?
You can use the ynl CLI but it has to be whatever version is on the remote system. Just call ynl --family rt-link, don't dig around for the spec paths etc.
In fact, I have tested this from two different locations. The first is in tools/testing/selftests/drivers/net/hw/ using python3 tso.py, which utilizes the specs located in Documentation/netlink/specs/. The second follows the testing methodology described in the README.rst of tools/testing/selftests/drivers/net/, which uses the specs in net/lib/specs/. Based on this, I include that different processes utilize different spec locations.
I also referred to the implementation in net/lib/py/ynl.py, which employs a similar handling logic. Both using the source code repository and installing the package can meet the requirements for remote testing.
Signature · 3 lines
Regards, Xu
Jakub Kicinski
maintainerRe: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO
Quoted 29 lines · On Thu, 9 Apr 2026 15:35:21 +0800 Xu Du wrote:
Quoted 20 lines
Quoted 5 lines
I want to test the gro-hint parameter functionality of the GENEVE tunnel, so I intend to use YNL for the testing. I am conducting the test between two machines using SSH type. I want to add the gro-hint parameter on both the local and remote nodes; however, I am unable to invoke class RtnlFamily on the remote node via SSH.
Oh. But that's not really what you're doing:
tools/testing/selftests/net/lib/py/utils.py +11
-0+def ynlcli(family, args, json=None, ns=None, host=None): + if (KSFT_DIR / "kselftest-list.txt").exists(): + cli = KSFT_DIR / "net/lib/ynl/pyynl/cli.py" + spec = KSFT_DIR / f"net/lib/specs/{family}.yaml" + else: + cli = KSRC / "tools/net/ynl/pyynl/cli.py" + spec = KSRC / f"Documentation/netlink/specs/{family}.yaml" + if not cli.exists(): + raise FileNotFoundError(f"cli not found at {cli}") + args = f"--spec {spec} --no-schema {args}" + return tool(cli.as_posix(), args, json=json, ns=ns, host=host, shell=True)Quoted 8 lines
You're not deploying anything to the remote system. Are you assuming that the remote system magically has the same filesystem layout?
You can use the ynl CLI but it has to be whatever version is on the remote system. Just call ynl --family rt-link, don't dig around for the spec paths etc.
Quoted 10 lines
In fact, I have tested this from two different locations. The first is in
tools/testing/selftests/drivers/net/hw/using python3tso.py, which utilizes the specs located inDocumentation/netlink/specs/. The second follows the testing methodology described in theREADME.rstoftools/testing/selftests/drivers/net/, which uses the specs innet/lib/specs/. Based on this, I include that different processes utilize different spec locations.
I also referred to the implementation innet/lib/py/ynl.py, which employs a similar handling logic. Both using the source code repository and installing the package can meet the requirements for remote testing.
On the local system you have Python bindings.
On the remote system you can't assume KSFT_DIR or KSRC exist.
You can use ynl CLI on the remote system, like we use ip, tc etc. But then just use --family rt-link, don't try to find the filesystem path to the spec.
Is this clear now? Am I misunderstanding your misunderstanding?