* [PATCH net-next v2 0/3] selftests: drv-net: add a simple TSO test
@ 2025-02-14 23:46 Jakub Kicinski
2025-02-14 23:46 ` [PATCH net-next v2 1/3] selftests: drv-net: resolve remote interface name Jakub Kicinski
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Jakub Kicinski @ 2025-02-14 23:46 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms,
willemdebruijn.kernel, petrm, stfomichev, Jakub Kicinski
Add a simple test for exercising TSO over tunnels.
v2:
- [patch 1] check for IP being on multiple ifcs
- [patch 3] lower max noise
- [patch 3] mention header overhead in the comment
- [patch 3] fix the basic v4 TSO feature name
- [patch 3] also run a stream with just GSO partial for tunnels
v1: https://lore.kernel.org/20250213003454.1333711-1-kuba@kernel.org
Jakub Kicinski (3):
selftests: drv-net: resolve remote interface name
selftests: drv-net: get detailed interface info
selftests: drv-net: add a simple TSO test
.../testing/selftests/drivers/net/hw/Makefile | 1 +
tools/testing/selftests/drivers/net/hw/tso.py | 234 ++++++++++++++++++
.../selftests/drivers/net/lib/py/env.py | 19 +-
3 files changed, 252 insertions(+), 2 deletions(-)
create mode 100755 tools/testing/selftests/drivers/net/hw/tso.py
--
2.48.1
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH net-next v2 1/3] selftests: drv-net: resolve remote interface name 2025-02-14 23:46 [PATCH net-next v2 0/3] selftests: drv-net: add a simple TSO test Jakub Kicinski @ 2025-02-14 23:46 ` Jakub Kicinski 2025-02-15 14:50 ` Willem de Bruijn 2025-02-17 11:42 ` Petr Machata 2025-02-14 23:46 ` [PATCH net-next v2 2/3] selftests: drv-net: get detailed interface info Jakub Kicinski 2025-02-14 23:46 ` [PATCH net-next v2 3/3] selftests: drv-net: add a simple TSO test Jakub Kicinski 2 siblings, 2 replies; 10+ messages in thread From: Jakub Kicinski @ 2025-02-14 23:46 UTC (permalink / raw) To: davem Cc: netdev, edumazet, pabeni, andrew+netdev, horms, willemdebruijn.kernel, petrm, stfomichev, Jakub Kicinski Find out and record in env the name of the interface which remote host will use for the IP address provided via config. Interface name is useful for mausezahn and for setting up tunnels. Signed-off-by: Jakub Kicinski <kuba@kernel.org> --- v2: - check for IP being on multiple ifcs v1: https://lore.kernel.org/20250213003454.1333711-2-kuba@kernel.org --- tools/testing/selftests/drivers/net/lib/py/env.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index 886b4904613c..55d6b3d992b6 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -154,6 +154,9 @@ from .remote import Remote self.ifname = self.dev['ifname'] self.ifindex = self.dev['ifindex'] + # resolve remote interface name + self.remote_ifname = self.resolve_remote_ifc() + self._required_cmd = {} def create_local(self): @@ -200,6 +203,18 @@ from .remote import Remote raise Exception("Invalid environment, missing configuration:", missing, "Please see tools/testing/selftests/drivers/net/README.rst") + def resolve_remote_ifc(self): + v4 = v6 = None + if self.remote_v4: + v4 = ip("addr show to " + self.remote_v4, json=True, host=self.remote) + if self.remote_v6: + v6 = ip("addr show to " + self.remote_v6, json=True, host=self.remote) + if v4 and v6 and v4[0]["ifname"] != v6[0]["ifname"]: + raise Exception("Can't resolve remote interface name, v4 and v6 don't match") + if (v4 and len(v4) > 1) or (v6 and len(v6) > 1): + raise Exception("Can't resolve remote interface name, multiple interfaces match") + return v6[0]["ifname"] if v6 else v4[0]["ifname"] + def __enter__(self): return self -- 2.48.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v2 1/3] selftests: drv-net: resolve remote interface name 2025-02-14 23:46 ` [PATCH net-next v2 1/3] selftests: drv-net: resolve remote interface name Jakub Kicinski @ 2025-02-15 14:50 ` Willem de Bruijn 2025-02-17 11:42 ` Petr Machata 1 sibling, 0 replies; 10+ messages in thread From: Willem de Bruijn @ 2025-02-15 14:50 UTC (permalink / raw) To: Jakub Kicinski, davem Cc: netdev, edumazet, pabeni, andrew+netdev, horms, willemdebruijn.kernel, petrm, stfomichev, Jakub Kicinski Jakub Kicinski wrote: > Find out and record in env the name of the interface which remote host > will use for the IP address provided via config. > > Interface name is useful for mausezahn and for setting up tunnels. > > Signed-off-by: Jakub Kicinski <kuba@kernel.org> Reviewed-by: Willem de Bruijn <willemb@google.com> ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v2 1/3] selftests: drv-net: resolve remote interface name 2025-02-14 23:46 ` [PATCH net-next v2 1/3] selftests: drv-net: resolve remote interface name Jakub Kicinski 2025-02-15 14:50 ` Willem de Bruijn @ 2025-02-17 11:42 ` Petr Machata 1 sibling, 0 replies; 10+ messages in thread From: Petr Machata @ 2025-02-17 11:42 UTC (permalink / raw) To: Jakub Kicinski Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, willemdebruijn.kernel, petrm, stfomichev Jakub Kicinski <kuba@kernel.org> writes: > Find out and record in env the name of the interface which remote host > will use for the IP address provided via config. > > Interface name is useful for mausezahn and for setting up tunnels. > > Signed-off-by: Jakub Kicinski <kuba@kernel.org> Reviewed-by: Petr Machata <petrm@nvidia.com> ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v2 2/3] selftests: drv-net: get detailed interface info 2025-02-14 23:46 [PATCH net-next v2 0/3] selftests: drv-net: add a simple TSO test Jakub Kicinski 2025-02-14 23:46 ` [PATCH net-next v2 1/3] selftests: drv-net: resolve remote interface name Jakub Kicinski @ 2025-02-14 23:46 ` Jakub Kicinski 2025-02-15 14:50 ` Willem de Bruijn 2025-02-14 23:46 ` [PATCH net-next v2 3/3] selftests: drv-net: add a simple TSO test Jakub Kicinski 2 siblings, 1 reply; 10+ messages in thread From: Jakub Kicinski @ 2025-02-14 23:46 UTC (permalink / raw) To: davem Cc: netdev, edumazet, pabeni, andrew+netdev, horms, willemdebruijn.kernel, petrm, stfomichev, Jakub Kicinski We already record output of ip link for NETIF in env for easy access. Record the detailed version. TSO test will want to know the max tso size. Reviewed-by: Petr Machata <petrm@nvidia.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org> --- tools/testing/selftests/drivers/net/lib/py/env.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index 55d6b3d992b6..128acff4f753 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -64,7 +64,7 @@ from .remote import Remote self._ns = None if 'NETIF' in self.env: - self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0] + self.dev = ip("-d link show dev " + self.env['NETIF'], json=True)[0] else: self._ns = NetdevSimDev(**kwargs) self.dev = self._ns.nsims[0].dev @@ -118,7 +118,7 @@ from .remote import Remote raise KsftXfailEx("Test only works on netdevsim") self._check_env() - self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0] + self.dev = ip("-d link show dev " + self.env['NETIF'], json=True)[0] self.v4 = self.env.get("LOCAL_V4") self.v6 = self.env.get("LOCAL_V6") -- 2.48.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v2 2/3] selftests: drv-net: get detailed interface info 2025-02-14 23:46 ` [PATCH net-next v2 2/3] selftests: drv-net: get detailed interface info Jakub Kicinski @ 2025-02-15 14:50 ` Willem de Bruijn 0 siblings, 0 replies; 10+ messages in thread From: Willem de Bruijn @ 2025-02-15 14:50 UTC (permalink / raw) To: Jakub Kicinski, davem Cc: netdev, edumazet, pabeni, andrew+netdev, horms, willemdebruijn.kernel, petrm, stfomichev, Jakub Kicinski Jakub Kicinski wrote: > We already record output of ip link for NETIF in env for easy access. > Record the detailed version. TSO test will want to know the max tso size. > > Reviewed-by: Petr Machata <petrm@nvidia.com> > Signed-off-by: Jakub Kicinski <kuba@kernel.org> Reviewed-by: Willem de Bruijn <willemb@google.com> ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v2 3/3] selftests: drv-net: add a simple TSO test 2025-02-14 23:46 [PATCH net-next v2 0/3] selftests: drv-net: add a simple TSO test Jakub Kicinski 2025-02-14 23:46 ` [PATCH net-next v2 1/3] selftests: drv-net: resolve remote interface name Jakub Kicinski 2025-02-14 23:46 ` [PATCH net-next v2 2/3] selftests: drv-net: get detailed interface info Jakub Kicinski @ 2025-02-14 23:46 ` Jakub Kicinski 2025-02-15 14:49 ` Willem de Bruijn 2025-02-15 14:50 ` Willem de Bruijn 2 siblings, 2 replies; 10+ messages in thread From: Jakub Kicinski @ 2025-02-14 23:46 UTC (permalink / raw) To: davem Cc: netdev, edumazet, pabeni, andrew+netdev, horms, willemdebruijn.kernel, petrm, stfomichev, Jakub Kicinski Add a simple test for TSO. Send a few MB of data and check device stats to verify that the device was performing segmentation. Do the same thing over a few tunnel types. Injecting GSO packets directly would give us more ability to test corner cases, but perhaps starting simple is good enough? # ./ksft-net-drv/drivers/net/hw/tso.py # Detected qstat for LSO wire-packets KTAP version 1 1..14 ok 1 tso.ipv4 # SKIP Test requires IPv4 connectivity ok 2 tso.vxlan4_ipv4 # SKIP Test requires IPv4 connectivity ok 3 tso.vxlan6_ipv4 # SKIP Test requires IPv4 connectivity ok 4 tso.vxlan_csum4_ipv4 # SKIP Test requires IPv4 connectivity ok 5 tso.vxlan_csum6_ipv4 # SKIP Test requires IPv4 connectivity ok 6 tso.gre4_ipv4 # SKIP Test requires IPv4 connectivity ok 7 tso.gre6_ipv4 # SKIP Test requires IPv4 connectivity ok 8 tso.ipv6 ok 9 tso.vxlan4_ipv6 ok 10 tso.vxlan6_ipv6 ok 11 tso.vxlan_csum4_ipv6 ok 12 tso.vxlan_csum6_ipv6 ok 13 tso.gre4_ipv6 ok 14 tso.gre6_ipv6 # Totals: pass:7 fail:0 xfail:0 xpass:0 skip:7 error:0 Note that the test currently depends on the driver reporting the LSO count via qstat, which appears to be relatively rare (virtio, cisco/enic, sfc/efc; but virtio needs host support). Signed-off-by: Jakub Kicinski <kuba@kernel.org> --- v2: - lower max noise - mention header overhead in the comment - fix the basic v4 TSO feature name - also run a stream with just GSO partial for tunnels v1: https://lore.kernel.org/20250213003454.1333711-4-kuba@kernel.org --- .../testing/selftests/drivers/net/hw/Makefile | 1 + tools/testing/selftests/drivers/net/hw/tso.py | 234 ++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100755 tools/testing/selftests/drivers/net/hw/tso.py diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index 21ba64ce1e34..ae783e18be83 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -15,6 +15,7 @@ TEST_PROGS = \ nic_performance.py \ pp_alloc_fail.py \ rss_ctx.py \ + tso.py \ # TEST_FILES := \ diff --git a/tools/testing/selftests/drivers/net/hw/tso.py b/tools/testing/selftests/drivers/net/hw/tso.py new file mode 100755 index 000000000000..346b257f7160 --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/tso.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +"""Run the tools/testing/selftests/net/csum testsuite.""" + +import fcntl +import socket +import struct +import termios +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 + + +def sock_wait_drain(sock, max_wait=1000): + """Wait for all pending write data on the socket to get ACKed.""" + for _ in range(max_wait): + one = b'\0' * 4 + outq = fcntl.ioctl(sock.fileno(), termios.TIOCOUTQ, one) + outq = struct.unpack("I", outq)[0] + if outq == 0: + break + time.sleep(0.01) + ksft_eq(outq, 0) + + +def tcp_sock_get_retrans(sock): + """Get the number of retransmissions for the TCP socket.""" + info = sock.getsockopt(socket.SOL_TCP, socket.TCP_INFO, 512) + return struct.unpack("I", info[100:104])[0] + + +def run_one_stream(cfg, ipv4, remote_v4, remote_v6, should_lso): + cfg.require_cmd("socat", remote=True) + + port = rand_port() + listen_cmd = f"socat -{cfg.addr_ipver} -t 2 -u TCP-LISTEN:{port},reuseport /dev/null,ignoreeof" + + with bkg(listen_cmd, host=cfg.remote) as nc: + wait_port_listen(port, host=cfg.remote) + + if ipv4: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((remote_v4, port)) + else: + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + sock.connect((remote_v6, port)) + + # Small send to make sure the connection is working. + sock.send("ping".encode()) + sock_wait_drain(sock) + + # Send 4MB of data, record the LSO packet count. + qstat_old = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] + buf = b"0" * 1024 * 1024 * 4 + sock.send(buf) + sock_wait_drain(sock) + qstat_new = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] + + # No math behind the 10 here, but try to catch cases where + # TCP falls back to non-LSO. + ksft_lt(tcp_sock_get_retrans(sock), 10) + sock.close() + + # Check that at least 90% of the data was sent as LSO packets. + # System noise may cause false negatives. Also header overheads + # will add up to 5% of extra packes... The check is best effort. + total_lso_wire = len(buf) * 0.90 // cfg.dev["mtu"] + total_lso_super = len(buf) * 0.90 // cfg.dev["tso_max_size"] + if should_lso: + if cfg.have_stat_super_count: + ksft_ge(qstat_new['tx-hw-gso-packets'] - + qstat_old['tx-hw-gso-packets'], + total_lso_super, + comment="Number of LSO super-packets with LSO enabled") + if cfg.have_stat_wire_count: + ksft_ge(qstat_new['tx-hw-gso-wire-packets'] - + qstat_old['tx-hw-gso-wire-packets'], + total_lso_wire, + comment="Number of LSO wire-packets with LSO enabled") + else: + if cfg.have_stat_super_count: + ksft_lt(qstat_new['tx-hw-gso-packets'] - + qstat_old['tx-hw-gso-packets'], + 15, comment="Number of LSO super-packets with LSO disabled") + if cfg.have_stat_wire_count: + ksft_lt(qstat_new['tx-hw-gso-wire-packets'] - + qstat_old['tx-hw-gso-wire-packets'], + 500, comment="Number of LSO wire-packets with LSO disabled") + + +def build_tunnel(cfg, outer_ipv4, 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" + + if outer_ipv4: + local_addr = cfg.v4 + remote_addr = cfg.remote_v4 + else: + local_addr = cfg.v6 + remote_addr = cfg.remote_v6 + + tun_type = tun_info[0] + tun_arg = tun_info[2] + 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) + + return remote_v4, remote_v6 + + +def test_builder(name, cfg, ipv4, feature, tun=None, inner_ipv4=None): + """Construct specific tests from the common template.""" + def f(cfg): + if ipv4: + cfg.require_v4() + else: + cfg.require_v6() + + if not cfg.have_stat_super_count and \ + not cfg.have_stat_wire_count: + raise KsftSkipEx(f"Device does not support LSO queue stats") + + if tun: + remote_v4, remote_v6 = build_tunnel(cfg, ipv4, tun) + else: + remote_v4 = cfg.remote_v4 + remote_v6 = cfg.remote_v6 + + tun_partial = tun and tun[1] + has_gso_partial = tun and 'tx-gso-partial' in cfg.features + + # First test without the feature enabled. + ethtool(f"-K {cfg.ifname} {feature} off") + if has_gso_partial: + ethtool(f"-K {cfg.ifname} tx-gso-partial off") + run_one_stream(cfg, ipv4, remote_v4, remote_v6, should_lso=False) + + # Now test with the feature enabled. + # For compatible tunnels only - just GSO partial, not specific feature. + if has_gso_partial: + ethtool(f"-K {cfg.ifname} tx-gso-partial on") + run_one_stream(cfg, ipv4, remote_v4, remote_v6, + should_lso=tun_partial) + + # Full feature enabled. + if feature in cfg.features: + ethtool(f"-K {cfg.ifname} {feature} on") + run_one_stream(cfg, ipv4, remote_v4, remote_v6, should_lso=True) + else: + raise KsftXfailEx(f"Device does not support {feature}") + + if tun: + name += ("4" if inner_ipv4 else "6") + "_" + if ipv4: + f.__name__ = name + "ipv4" + else: + f.__name__ = name + "ipv6" + return f + + +def query_nic_features(cfg) -> None: + """Query and cache the NIC features.""" + cfg.features = set() + + cfg.have_stat_super_count = False + cfg.have_stat_wire_count = False + + features = cfg.ethnl.features_get({"header": {"dev-index": cfg.ifindex}}) + for f in features["active"]["bits"]["bit"]: + cfg.features.add(f["name"]) + for f in features["hw"]["bits"]["bit"]: + cfg.features.add(f["name"]) + + stats = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True) + if stats: + if 'tx-hw-gso-packets' in stats[0]: + ksft_pr("Detected qstat for LSO super-packets") + cfg.have_stat_super_count = True + if 'tx-hw-gso-wire-packets' in stats[0]: + ksft_pr("Detected qstat for LSO wire-packets") + cfg.have_stat_wire_count = True + + +def main() -> None: + with NetDrvEpEnv(__file__, nsim_test=False) as cfg: + cfg.ethnl = EthtoolFamily() + cfg.netnl = NetdevFamily() + + query_nic_features(cfg) + + test_info = ( + # name, skip v4/v6 ethtool_feature tun:(type, partial, args) + ("", False, "tx-tcp-segmentation", None), + ("", True, "tx-tcp6-segmentation", None), + ("vxlan", None, "tx-udp_tnl-segmentation", ("vxlan", True, "id 100 dstport 4789 noudpcsum")), + ("vxlan_csum", None, "tx-udp_tnl-csum-segmentation", ("vxlan", False, "id 100 dstport 4789 udpcsum")), + ("gre", False, "tx-gre-segmentation", ("ipgre", False, "")), + ("gre", True, "tx-gre-segmentation", ("ip6gre", False, "")), + ) + + cases = [] + for outer_ipv4 in [True, False]: + for info in test_info: + # Skip if test which only works for a specific IP version + if outer_ipv4 == info[1]: + continue + + cases.append(test_builder(info[0], cfg, outer_ipv4, info[2], + tun=info[3], inner_ipv4=True)) + if info[3]: + cases.append(test_builder(info[0], cfg, outer_ipv4, info[2], + tun=info[3], inner_ipv4=False)) + + ksft_run(cases=cases, args=(cfg, )) + ksft_exit() + + +if __name__ == "__main__": + main() -- 2.48.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v2 3/3] selftests: drv-net: add a simple TSO test 2025-02-14 23:46 ` [PATCH net-next v2 3/3] selftests: drv-net: add a simple TSO test Jakub Kicinski @ 2025-02-15 14:49 ` Willem de Bruijn 2025-02-15 16:31 ` Jakub Kicinski 2025-02-15 14:50 ` Willem de Bruijn 1 sibling, 1 reply; 10+ messages in thread From: Willem de Bruijn @ 2025-02-15 14:49 UTC (permalink / raw) To: Jakub Kicinski, davem Cc: netdev, edumazet, pabeni, andrew+netdev, horms, willemdebruijn.kernel, petrm, stfomichev, Jakub Kicinski Jakub Kicinski wrote: > Add a simple test for TSO. Send a few MB of data and check device > stats to verify that the device was performing segmentation. > Do the same thing over a few tunnel types. > > Injecting GSO packets directly would give us more ability to test > corner cases, but perhaps starting simple is good enough? > > # ./ksft-net-drv/drivers/net/hw/tso.py > # Detected qstat for LSO wire-packets > KTAP version 1 > 1..14 > ok 1 tso.ipv4 # SKIP Test requires IPv4 connectivity > ok 2 tso.vxlan4_ipv4 # SKIP Test requires IPv4 connectivity > ok 3 tso.vxlan6_ipv4 # SKIP Test requires IPv4 connectivity > ok 4 tso.vxlan_csum4_ipv4 # SKIP Test requires IPv4 connectivity > ok 5 tso.vxlan_csum6_ipv4 # SKIP Test requires IPv4 connectivity > ok 6 tso.gre4_ipv4 # SKIP Test requires IPv4 connectivity > ok 7 tso.gre6_ipv4 # SKIP Test requires IPv4 connectivity > ok 8 tso.ipv6 > ok 9 tso.vxlan4_ipv6 > ok 10 tso.vxlan6_ipv6 > ok 11 tso.vxlan_csum4_ipv6 > ok 12 tso.vxlan_csum6_ipv6 > ok 13 tso.gre4_ipv6 > ok 14 tso.gre6_ipv6 > # Totals: pass:7 fail:0 xfail:0 xpass:0 skip:7 error:0 > > Note that the test currently depends on the driver reporting > the LSO count via qstat, which appears to be relatively rare > (virtio, cisco/enic, sfc/efc; but virtio needs host support). > > Signed-off-by: Jakub Kicinski <kuba@kernel.org> > --- > v2: > - lower max noise > - mention header overhead in the comment > - fix the basic v4 TSO feature name > - also run a stream with just GSO partial for tunnels > v1: https://lore.kernel.org/20250213003454.1333711-4-kuba@kernel.org > --- > .../testing/selftests/drivers/net/hw/Makefile | 1 + > tools/testing/selftests/drivers/net/hw/tso.py | 234 ++++++++++++++++++ > 2 files changed, 235 insertions(+) > create mode 100755 tools/testing/selftests/drivers/net/hw/tso.py > > diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile > index 21ba64ce1e34..ae783e18be83 100644 > --- a/tools/testing/selftests/drivers/net/hw/Makefile > +++ b/tools/testing/selftests/drivers/net/hw/Makefile > @@ -15,6 +15,7 @@ TEST_PROGS = \ > nic_performance.py \ > pp_alloc_fail.py \ > rss_ctx.py \ > + tso.py \ > # > > TEST_FILES := \ > diff --git a/tools/testing/selftests/drivers/net/hw/tso.py b/tools/testing/selftests/drivers/net/hw/tso.py > new file mode 100755 > index 000000000000..346b257f7160 > --- /dev/null > +++ b/tools/testing/selftests/drivers/net/hw/tso.py > @@ -0,0 +1,234 @@ > +#!/usr/bin/env python3 > +# SPDX-License-Identifier: GPL-2.0 > + > +"""Run the tools/testing/selftests/net/csum testsuite.""" > + > +import fcntl > +import socket > +import struct > +import termios > +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 > + > + > +def sock_wait_drain(sock, max_wait=1000): > + """Wait for all pending write data on the socket to get ACKed.""" > + for _ in range(max_wait): > + one = b'\0' * 4 > + outq = fcntl.ioctl(sock.fileno(), termios.TIOCOUTQ, one) > + outq = struct.unpack("I", outq)[0] > + if outq == 0: > + break > + time.sleep(0.01) > + ksft_eq(outq, 0) > + > + > +def tcp_sock_get_retrans(sock): > + """Get the number of retransmissions for the TCP socket.""" > + info = sock.getsockopt(socket.SOL_TCP, socket.TCP_INFO, 512) > + return struct.unpack("I", info[100:104])[0] > + > + > +def run_one_stream(cfg, ipv4, remote_v4, remote_v6, should_lso): > + cfg.require_cmd("socat", remote=True) > + > + port = rand_port() > + listen_cmd = f"socat -{cfg.addr_ipver} -t 2 -u TCP-LISTEN:{port},reuseport /dev/null,ignoreeof" > + > + with bkg(listen_cmd, host=cfg.remote) as nc: > + wait_port_listen(port, host=cfg.remote) > + > + if ipv4: > + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) > + sock.connect((remote_v4, port)) > + else: > + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) > + sock.connect((remote_v6, port)) > + > + # Small send to make sure the connection is working. > + sock.send("ping".encode()) > + sock_wait_drain(sock) > + > + # Send 4MB of data, record the LSO packet count. > + qstat_old = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] > + buf = b"0" * 1024 * 1024 * 4 > + sock.send(buf) > + sock_wait_drain(sock) > + qstat_new = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] > + > + # No math behind the 10 here, but try to catch cases where > + # TCP falls back to non-LSO. > + ksft_lt(tcp_sock_get_retrans(sock), 10) > + sock.close() > + > + # Check that at least 90% of the data was sent as LSO packets. > + # System noise may cause false negatives. Also header overheads > + # will add up to 5% of extra packes... The check is best effort. > + total_lso_wire = len(buf) * 0.90 // cfg.dev["mtu"] > + total_lso_super = len(buf) * 0.90 // cfg.dev["tso_max_size"] > + if should_lso: > + if cfg.have_stat_super_count: > + ksft_ge(qstat_new['tx-hw-gso-packets'] - > + qstat_old['tx-hw-gso-packets'], > + total_lso_super, > + comment="Number of LSO super-packets with LSO enabled") > + if cfg.have_stat_wire_count: > + ksft_ge(qstat_new['tx-hw-gso-wire-packets'] - > + qstat_old['tx-hw-gso-wire-packets'], > + total_lso_wire, > + comment="Number of LSO wire-packets with LSO enabled") > + else: > + if cfg.have_stat_super_count: > + ksft_lt(qstat_new['tx-hw-gso-packets'] - > + qstat_old['tx-hw-gso-packets'], > + 15, comment="Number of LSO super-packets with LSO disabled") > + if cfg.have_stat_wire_count: > + ksft_lt(qstat_new['tx-hw-gso-wire-packets'] - > + qstat_old['tx-hw-gso-wire-packets'], > + 500, comment="Number of LSO wire-packets with LSO disabled") > + > + > +def build_tunnel(cfg, outer_ipv4, 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" > + > + if outer_ipv4: > + local_addr = cfg.v4 > + remote_addr = cfg.remote_v4 > + else: > + local_addr = cfg.v6 > + remote_addr = cfg.remote_v6 > + > + tun_type = tun_info[0] > + tun_arg = tun_info[2] > + 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) > + > + return remote_v4, remote_v6 > + > + > +def test_builder(name, cfg, ipv4, feature, tun=None, inner_ipv4=None): > + """Construct specific tests from the common template.""" > + def f(cfg): > + if ipv4: > + cfg.require_v4() > + else: > + cfg.require_v6() > + > + if not cfg.have_stat_super_count and \ > + not cfg.have_stat_wire_count: > + raise KsftSkipEx(f"Device does not support LSO queue stats") > + > + if tun: > + remote_v4, remote_v6 = build_tunnel(cfg, ipv4, tun) > + else: > + remote_v4 = cfg.remote_v4 > + remote_v6 = cfg.remote_v6 > + > + tun_partial = tun and tun[1] > + has_gso_partial = tun and 'tx-gso-partial' in cfg.features > + > + # First test without the feature enabled. > + ethtool(f"-K {cfg.ifname} {feature} off") > + if has_gso_partial: > + ethtool(f"-K {cfg.ifname} tx-gso-partial off") > + run_one_stream(cfg, ipv4, remote_v4, remote_v6, should_lso=False) > + > + # Now test with the feature enabled. > + # For compatible tunnels only - just GSO partial, not specific feature. > + if has_gso_partial: > + ethtool(f"-K {cfg.ifname} tx-gso-partial on") > + run_one_stream(cfg, ipv4, remote_v4, remote_v6, > + should_lso=tun_partial) > + > + # Full feature enabled. > + if feature in cfg.features: > + ethtool(f"-K {cfg.ifname} {feature} on") > + run_one_stream(cfg, ipv4, remote_v4, remote_v6, should_lso=True) > + else: > + raise KsftXfailEx(f"Device does not support {feature}") > + > + if tun: > + name += ("4" if inner_ipv4 else "6") + "_" > + if ipv4: > + f.__name__ = name + "ipv4" > + else: > + f.__name__ = name + "ipv6" > + return f > + > + > +def query_nic_features(cfg) -> None: > + """Query and cache the NIC features.""" > + cfg.features = set() > + > + cfg.have_stat_super_count = False > + cfg.have_stat_wire_count = False > + > + features = cfg.ethnl.features_get({"header": {"dev-index": cfg.ifindex}}) > + for f in features["active"]["bits"]["bit"]: > + cfg.features.add(f["name"]) > + for f in features["hw"]["bits"]["bit"]: > + cfg.features.add(f["name"]) > + > + stats = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True) > + if stats: > + if 'tx-hw-gso-packets' in stats[0]: > + ksft_pr("Detected qstat for LSO super-packets") > + cfg.have_stat_super_count = True > + if 'tx-hw-gso-wire-packets' in stats[0]: > + ksft_pr("Detected qstat for LSO wire-packets") > + cfg.have_stat_wire_count = True > + > + > +def main() -> None: > + with NetDrvEpEnv(__file__, nsim_test=False) as cfg: > + cfg.ethnl = EthtoolFamily() > + cfg.netnl = NetdevFamily() > + > + query_nic_features(cfg) > + > + test_info = ( > + # name, skip v4/v6 ethtool_feature tun:(type, partial, args) > + ("", False, "tx-tcp-segmentation", None), > + ("", True, "tx-tcp6-segmentation", None), > + ("vxlan", None, "tx-udp_tnl-segmentation", ("vxlan", True, "id 100 dstport 4789 noudpcsum")), > + ("vxlan_csum", None, "tx-udp_tnl-csum-segmentation", ("vxlan", False, "id 100 dstport 4789 udpcsum")), > + ("gre", False, "tx-gre-segmentation", ("ipgre", False, "")), > + ("gre", True, "tx-gre-segmentation", ("ip6gre", False, "")), > + ) > + > + cases = [] > + for outer_ipv4 in [True, False]: > + for info in test_info: > + # Skip if test which only works for a specific IP version > + if outer_ipv4 == info[1]: Only if need to respin: using ternary True, False, None for skip_ipv6, skip_ipv4, skip_neither is a bit non-obvious. Use strings? ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v2 3/3] selftests: drv-net: add a simple TSO test 2025-02-15 14:49 ` Willem de Bruijn @ 2025-02-15 16:31 ` Jakub Kicinski 0 siblings, 0 replies; 10+ messages in thread From: Jakub Kicinski @ 2025-02-15 16:31 UTC (permalink / raw) To: Willem de Bruijn Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, petrm, stfomichev On Sat, 15 Feb 2025 09:49:26 -0500 Willem de Bruijn wrote: > > + cases = [] > > + for outer_ipv4 in [True, False]: > > + for info in test_info: > > + # Skip if test which only works for a specific IP version > > + if outer_ipv4 == info[1]: > > Only if need to respin: > > using ternary True, False, None for skip_ipv6, skip_ipv4, skip_neither > is a bit non-obvious. Use strings? Now that you say it.. I think using ipver with values "4" "6" could clean up a few things. I'll poke a little bit on Monday, and probably respin. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v2 3/3] selftests: drv-net: add a simple TSO test 2025-02-14 23:46 ` [PATCH net-next v2 3/3] selftests: drv-net: add a simple TSO test Jakub Kicinski 2025-02-15 14:49 ` Willem de Bruijn @ 2025-02-15 14:50 ` Willem de Bruijn 1 sibling, 0 replies; 10+ messages in thread From: Willem de Bruijn @ 2025-02-15 14:50 UTC (permalink / raw) To: Jakub Kicinski, davem Cc: netdev, edumazet, pabeni, andrew+netdev, horms, willemdebruijn.kernel, petrm, stfomichev, Jakub Kicinski Jakub Kicinski wrote: > Add a simple test for TSO. Send a few MB of data and check device > stats to verify that the device was performing segmentation. > Do the same thing over a few tunnel types. > > Injecting GSO packets directly would give us more ability to test > corner cases, but perhaps starting simple is good enough? > > # ./ksft-net-drv/drivers/net/hw/tso.py > # Detected qstat for LSO wire-packets > KTAP version 1 > 1..14 > ok 1 tso.ipv4 # SKIP Test requires IPv4 connectivity > ok 2 tso.vxlan4_ipv4 # SKIP Test requires IPv4 connectivity > ok 3 tso.vxlan6_ipv4 # SKIP Test requires IPv4 connectivity > ok 4 tso.vxlan_csum4_ipv4 # SKIP Test requires IPv4 connectivity > ok 5 tso.vxlan_csum6_ipv4 # SKIP Test requires IPv4 connectivity > ok 6 tso.gre4_ipv4 # SKIP Test requires IPv4 connectivity > ok 7 tso.gre6_ipv4 # SKIP Test requires IPv4 connectivity > ok 8 tso.ipv6 > ok 9 tso.vxlan4_ipv6 > ok 10 tso.vxlan6_ipv6 > ok 11 tso.vxlan_csum4_ipv6 > ok 12 tso.vxlan_csum6_ipv6 > ok 13 tso.gre4_ipv6 > ok 14 tso.gre6_ipv6 > # Totals: pass:7 fail:0 xfail:0 xpass:0 skip:7 error:0 > > Note that the test currently depends on the driver reporting > the LSO count via qstat, which appears to be relatively rare > (virtio, cisco/enic, sfc/efc; but virtio needs host support). > > Signed-off-by: Jakub Kicinski <kuba@kernel.org> Reviewed-by: Willem de Bruijn <willemb@google.com> ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-02-17 11:43 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-02-14 23:46 [PATCH net-next v2 0/3] selftests: drv-net: add a simple TSO test Jakub Kicinski 2025-02-14 23:46 ` [PATCH net-next v2 1/3] selftests: drv-net: resolve remote interface name Jakub Kicinski 2025-02-15 14:50 ` Willem de Bruijn 2025-02-17 11:42 ` Petr Machata 2025-02-14 23:46 ` [PATCH net-next v2 2/3] selftests: drv-net: get detailed interface info Jakub Kicinski 2025-02-15 14:50 ` Willem de Bruijn 2025-02-14 23:46 ` [PATCH net-next v2 3/3] selftests: drv-net: add a simple TSO test Jakub Kicinski 2025-02-15 14:49 ` Willem de Bruijn 2025-02-15 16:31 ` Jakub Kicinski 2025-02-15 14:50 ` Willem de Bruijn
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).