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()?

> 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?

[ ... ]

> 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

Reply via email to