Thread (45 messages) 45 messages, 6 authors, 12d ago

Re: [PATCH net-next V4 14/14] selftests: drv-net: Add PSP HW GRO conformance tests

From: netdev-bot+sashiko@kernel.org
Date: 2026-09-10 21:54:42
Also in: linux-kselftest, lkml, netdev

Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 2 · Low: 3

- [Medium] test_psp_ver_diff() in
  tools/testing/selftests/drivers/net/hw/psp_gro.py gates on the *count*…
- [Medium] test_psp_after_reconfig()
  (tools/testing/selftests/drivers/net/hw/psp_gro.py:132-141) disables…
- [Low] The new HAVE_OPENSSL probe in
  tools/testing/selftests/drivers/net/hw/Makefile (lines 16-28) decides…
- [Low] test_psp_ver_diff()
  (tools/testing/selftests/drivers/net/hw/psp_gro.py:116-122) claims to…
- [Low] The psp_gro.py module docstring (line 7: "This reuses the gro
  binary in --psp mode:") and the new Makefile comment ("psp_gro.py uses…

--

selftests: drv-net: Add PSP HW GRO conformance tests

This adds psp_gro.py, which drives the existing gro helper binary with PSP
associations obtained from a real PSP device, so the sender encrypts in
software and the receiver relies on the NIC for decrypt, decap and HW GRO.
It also plumbs a common_args parameter through gro_lib.run_test() and adds a
libcrypto probe to the hw Makefile.

No Link: tags in the commit header.
    Add PSP conformance tests using the same gro helper binary as other gro
    tests , but in --psp mode. SPIs are procured by psp_gro.py from a real
[Severity: Low]
The commit message, the new Makefile comment ("psp_gro.py uses the gro
binary in PSP mode") and the psp_gro.py docstring all describe a "--psp
mode", but the gro helper has no such option.

In tools/testing/selftests/net/lib/gro.c the only PSP entry in
long_options[] is:

	{ "psp-assoc", required_argument, NULL, OPT_PSP_ASSOC },

and psp_enabled is just a side effect of psp_parse_assoc().  _psp_args()
in psp_gro.py correctly emits --psp-assoc ver,spi,key.  Could the wording
be changed to --psp-assoc so nobody tries to pass --psp and gets an
unknown-option error from getopt_long()?
quoted hunk ↗ jump to hunk
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 6105be8e590f..daa7e52f236e 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -13,6 +13,20 @@ else
 $(warning excluding iouring tests, liburing not installed or too old)
 endif
 
+# psp_gro.py uses the gro binary in PSP mode, which requires OpenSSL.
+PKG_CONFIG ?= pkg-config
+HAVE_OPENSSL := $(shell echo 'int main(void) { return 0; }' | \
+		  $(CC) -x c - -include openssl/evp.h \
+		  $(shell $(PKG_CONFIG) --cflags --libs libcrypto 2>/dev/null \
+			  || echo -lcrypto) \
+		  -o /dev/null >/dev/null 2>&1 && echo 1)
+
+ifeq ($(HAVE_OPENSSL),1)
+COND_PROGS += psp_gro.py
+else
+$(warning excluding PSP GRO tests, libcrypto not installed)
+endif
[Severity: Low]
Is the build host the right place to test this condition?  The PSP
software encryption runs in the gro binary on the remote endpoint, not on
the build host:

gro_lib.py:_run_gro_bin()
	cfg.bin_local = cfg.net_lib_dir / "gro"
	cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
	...
	tx_cmd = f"{cfg.bin_remote} {args} --iface {cfg.remote_ifname}"
	...
	cmd(tx_cmd, host=cfg.remote)

gro is dynamically linked against libcrypto:

tools/testing/selftests/net/lib/Makefile
	$(OUTPUT)/gro: LDLIBS += $(LIBCRYPTO_LIBS)

and Remote.deploy() only scp's the executable:

tools/testing/selftests/drivers/net/lib/py/remote_ssh.py
	cmd(f"scp {what} {self.name}:{file_name}")

So on a setup where the build host has libcrypto but the remote endpoint
lacks the runtime library, psp_gro.py is installed and every case fails
with a dynamic linker error on the sender instead of skipping.  Would a
runtime check on the remote be preferable here?

[ ... ]
quoted hunk ↗ jump to hunk
diff --git a/tools/testing/selftests/drivers/net/hw/psp_gro.py b/tools/testing/selftests/drivers/net/hw/psp_gro.py
new file mode 100755
index 000000000000..3a069c944ae1
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/psp_gro.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+PSP HW GRO conformance tests.
+
+This reuses the gro binary in --psp mode:
[Severity: Low]
Same --psp naming question as in the commit message: the implemented
option in gro.c is --psp-assoc.
+The sender crafts encapsulated & SW-encrypted PSP packets with receiver's PSP
+rx-assoc, and the receiver's device decrypts and decapsulates the packets
+before an AF_PACKET tap gets to analyze them.
+
+All GRO conformance tests which could run with PSP are included.
+"""
[ ... ]
+def _setup(cfg, version):
+    """Enables PSP on the device under test."""
+    init_psp_dev(cfg)
+    require_version(cfg, version)
+
+
+def _psp_args(cfg, versions):
+    """Produces PSP associations as gro binary --psp-assoc arguments."""
+    keys = [_psp_assoc(cfg, ver) for ver in versions]
+    return [f"--psp-assoc {ver},{key['spi']:x},{key['key'].hex()}"
+            for ver, key in zip(versions, keys)]
+
+
+def _run(cfg, test_name, protocol, versions):
+    """Sets up, associates and runs one gro test case under PSP + HW GRO."""
+    _setup(cfg, max(versions))
+
+    run_test(cfg, "hw", protocol, test_name,
+             common_args=_psp_args(cfg, versions))
[ ... ]
+@ksft_variants(_ip_variants())
+def test_psp_ver_diff(cfg, protocol):
+    """Frames from two different PSP versions must not coalesce."""
+    init_psp_dev(cfg)
+    if len(cfg.psp_info['psp-versions-cap']) < 2:
+        raise KsftSkipEx("Device supports a single PSP version")
+
+    _run(cfg, "psp_ver_diff", protocol, [0, 1])
[Severity: Medium]
Does the guard here match what the test then asks for?  It counts how
many versions the device advertises, but the following call hardcodes
versions 0 and 1.

psp-versions-cap is an independent flag bitmask in
Documentation/netlink/specs/psp.yaml:

      - name: psp-versions-cap
        doc: Bitmask of PSP versions supported by the device.
        type: u32
        enum: version
        enum-as-flags: true

so the supported set is not required to be a contiguous prefix starting
at 0.  On a device advertising, say, hdr0-aes-gcm-128 (0) and
hdr0-aes-gmac-128 (2), the length check passes and then:

psp_gro.py:_run()
	_setup(cfg, max([0, 1]))     /* == 1 */

psp_lib.py:require_version()
	name = cfg.pspnl.consts["version"].entries_by_val[version].name
	if name in cfg.psp_info['psp-versions-cap']:
		return
	...
	raise KsftSkipEx("PSP version not supported", name)

which skips with a reason unrelated to the guard, and the cross-version
non-coalescing assertion never runs on a device that could run it.

The existing peer test picks the versions from the actual caps instead:

tools/testing/selftests/drivers/net/psp.py:assoc_version_mismatch()
	versions = [cfg.pspnl.consts["version"].entries[v].value for v in versions]

Would deriving the two versions from cfg.psp_info['psp-versions-cap'] be
better here?  Today mlx5 happens to advertise exactly versions 0 and 1
(psp_supported_versions[] = { GCM_128, GCM_256 }), so this only bites the
next driver that advertises a GMAC version.

[Severity: Low]
This isn't a bug, but does psp_ver_diff assert anything that psp_spi_diff
does not?  The two associations passed here differ in version and in SPI,
since each rx_assoc allocates a fresh device SPI
(psp_nl_rx_assoc_doit() -> psd->ops->rx_spi_alloc()).

On the gro.c side both cases share the same sender helper, which only
flips psp_idx:

send_psp_assoc_switch()
	create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
	psp_idx = 1;
	write_packet(fd, buf, pkt_size, daddr);
	psp_idx = 0;

and the receive expectations are byte-for-byte the same as psp_spi_diff
(correct_payload[0] = correct_payload[1] = PAYLOAD_LEN, then
check_recv_pkts(rxfd, correct_payload, 2)); only the printf text differs.
A version-only difference does not look constructible from user space, so
this may be the closest achievable approximation - it is just worth noting
that the case would pass even if the GRO path ignored the version field.

[ ... ]
+@ksft_variants(_ip_variants())
+def test_psp_after_reconfig(cfg, protocol):
+    """Verifies that decap still works after PSP off + on."""
+    _setup(cfg, 0)
+
+    cap = cfg.psp_info['psp-versions-cap']
+    cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'psp-versions-ena': []})
+    cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'psp-versions-ena': cap})
[Severity: Medium]
Should the disable here be paired with a defer() for the restore?  As
written, the only thing that re-enables PSP is the very next dev_set()
call, with nothing covering an NlError from it or a kill between the two
calls.

init_psp_dev() only registers a rollback when the cached psp_info says the
enabled set differs from the caps:

tools/testing/selftests/drivers/net/psp_lib.py:init_psp_dev()
	if cap != ena:
		cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'psp-versions-ena': cap})
		defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id,
					  'psp-versions-ena': ena})

so on a device discovered with PSP already fully enabled (cap == ena)
there is no defer at all.  If the re-enable does not happen, the NIC is
left with PSP offload off while cfg.psp_info still caches the old enabled
set, and later init_psp_dev() calls compare against that stale snapshot
and conclude nothing needs enabling - subsequent PSP tests then fail on
rx-assoc/decap in a way that looks like a driver problem.

Would registering the re-enable via defer() right after the disable, and
re-reading the device state rather than trusting the cached psp_info,
address that?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260908065244.3799142-1-tariqt%40nvidia.com
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help