selftests/net: psp: support PSP in NetDrvContEnv infrastructure

Add infrastructure to support PSP tests across network namespaces
using NetDrvContEnv with netkit pairs. This enables testing PSP device
association, where a non-PSP-capable device (e.g. netkit) in a guest
namespace is associated with a real PSP device in the host namespace,
allowing the guest to perform PSP encryption/decryption through the
host's PSP hardware.

The topology is:
  Host NS:  psp_dev_local <---> nk_host
                |                  |
                |                  | (netkit pair)
                |                  |
  Remote NS: psp_dev_peer      Guest NS: nk_guest
             (responder)             (PSP tests)

env.py:
- nk_guest_ifindex is queried after moving the device into the guest
  namespace, so tests can use it directly for dev-assoc

psp.py:
- PSP device lookup supports container environments where the PSP
  device is on the physical interface, not the test interface
- Association helpers handle dev-assoc/dev-disassoc with defer-based
  cleanup to prevent state leaks on test assertion failures
- main() tries NetDrvContEnv with primary_rx_redirect and falls back
  to NetDrvEpEnv, so existing tests continue to work without the
  container environment

Signed-off-by: Wei Wang <weibunny@fb.com>
Link: https://patch.msgid.link/20260608233118.2694144-8-weibunny.kernel@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Wei Wang 2026-06-08 16:31:15 -07:00 committed by Jakub Kicinski
parent 593e22f652
commit 1c1c2e5b1f
3 changed files with 102 additions and 4 deletions

View File

@ -8,6 +8,7 @@ CONFIG_NETCONSOLE=m
CONFIG_NETCONSOLE_DYNAMIC=y
CONFIG_NETCONSOLE_EXTENDED_LOG=y
CONFIG_NETDEVSIM=m
CONFIG_NETKIT=y
CONFIG_NET_SCH_ETF=m
CONFIG_NET_SCH_FQ=m
CONFIG_PPP=y

View File

@ -470,6 +470,9 @@ class NetDrvContEnv(NetDrvEpEnv):
self._init_ns_attached = True
ip("netns set init 0", ns=self.netns)
ip(f"link set dev {self.nk_guest_ifname} netns {self.netns.name}")
nk_guest_dev = ip(f"link show dev {self.nk_guest_ifname}",
json=True, ns=self.netns)[0]
self.nk_guest_ifindex = nk_guest_dev['ifindex']
ip(f"link set dev {self.nk_host_ifname} up")
ip(f"-6 addr add fe80::1/64 dev {self.nk_host_ifname} nodad")
ip(f"-6 route add {self.nk_guest_ipv6}/128 via fe80::2 dev {self.nk_host_ifname}")

View File

@ -5,6 +5,7 @@
import errno
import fcntl
import os
import socket
import struct
import termios
@ -16,8 +17,10 @@ from lib.py import ksft_true, ksft_eq, ksft_ne, ksft_gt, ksft_raises
from lib.py import ksft_not_none
from lib.py import ksft_variants, KsftNamedVariant
from lib.py import KsftSkipEx
from lib.py import NetDrvEpEnv, PSPFamily, NlError
from lib.py import NetDrvEpEnv, NetDrvContEnv
from lib.py import NlError, PSPFamily
from lib.py import bkg, rand_port, wait_port_listen
from lib.py import ip
def _get_outq(s):
@ -118,11 +121,13 @@ def _get_stat(cfg, key):
# Test case boiler plate
#
def _init_psp_dev(cfg):
def _init_psp_dev(cfg, use_psp_ifindex=False):
if not hasattr(cfg, 'psp_dev_id'):
# Figure out which local device we are testing against
# For NetDrvContEnv: use psp_ifindex instead of ifindex
target_ifindex = cfg.psp_ifindex if use_psp_ifindex else cfg.ifindex
for dev in cfg.pspnl.dev_get({}, dump=True):
if dev['ifindex'] == cfg.ifindex:
if dev['ifindex'] == target_ifindex:
cfg.psp_info = dev
cfg.psp_dev_id = cfg.psp_info['id']
break
@ -597,13 +602,102 @@ def data_mss_adjust(cfg, ipver):
_data_mss_adjust(cfg, ipver)
def _try_disassoc(cfg, psp_dev_id, ifindex, nsid=None):
"""Best-effort disassociate, ignoring errors if already removed."""
try:
params = {'id': psp_dev_id, 'ifindex': ifindex}
if nsid is not None:
params['nsid'] = nsid
cfg.pspnl.dev_disassoc(params)
except NlError:
pass
def _assoc_nk_guest(cfg):
"""Associate nk_guest with PSP device and register cleanup via defer()."""
_init_psp_dev(cfg, True)
cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id,
'ifindex': cfg.nk_guest_ifindex,
'nsid': cfg.psp_dev_peer_nsid})
defer(_disassoc_nk_guest, cfg,
cfg.psp_dev_id, cfg.nk_guest_ifindex)
def _disassoc_nk_guest(cfg, psp_dev_id, nk_guest_ifindex):
"""Disassociate nk_guest and reset cfg PSP state."""
pspnl = PSPFamily()
pspnl.dev_disassoc({'id': psp_dev_id, 'ifindex': nk_guest_ifindex,
'nsid': cfg.psp_dev_peer_nsid})
cfg.pspnl = pspnl
del cfg.psp_dev_id
del cfg.psp_info
def _get_nsid(ns_name):
"""Get the nsid for a namespace."""
for entry in ip("netns list-id", json=True):
if entry.get("name") == str(ns_name):
return entry["nsid"]
raise KsftSkipEx(f"nsid not found for namespace {ns_name}")
def _setup_psp_attributes(cfg):
# pylint: disable=protected-access
"""
Set up PSP-specific attributes on the environment.
This sets attributes needed for PSP tests based on whether we're using
netdevsim or a real NIC.
"""
if cfg._ns is not None:
# netdevsim case: PSP device is the local dev (in host namespace)
cfg.psp_dev = cfg._ns.nsims[0].dev
cfg.psp_ifname = cfg.psp_dev['ifname']
cfg.psp_ifindex = cfg.psp_dev['ifindex']
# PSP peer device is the remote dev (in _netns, where psp_responder runs)
cfg.psp_dev_peer = cfg._ns_peer.nsims[0].dev
cfg.psp_dev_peer_ifname = cfg.psp_dev_peer['ifname']
cfg.psp_dev_peer_ifindex = cfg.psp_dev_peer['ifindex']
else:
# Real NIC case: PSP device is the local interface
cfg.psp_dev = cfg.dev
cfg.psp_ifname = cfg.ifname
cfg.psp_ifindex = cfg.ifindex
# PSP peer device is the remote interface
cfg.psp_dev_peer = cfg.remote_dev
cfg.psp_dev_peer_ifname = cfg.remote_ifname
cfg.psp_dev_peer_ifindex = cfg.remote_ifindex
# Get nsid for the guest namespace (netns) where nk_guest is
cfg.psp_dev_peer_nsid = _get_nsid(cfg.netns.name)
def main() -> None:
""" Ksft boiler plate main """
with NetDrvEpEnv(__file__) as cfg:
# Make sure LOCAL_PREFIX_V6 is set
if "LOCAL_PREFIX_V6" not in os.environ:
os.environ["LOCAL_PREFIX_V6"] = "2001:db8:2::"
try:
env = NetDrvContEnv(__file__, primary_rx_redirect=True)
has_cont = True
except KsftSkipEx:
env = NetDrvEpEnv(__file__)
has_cont = False
with env as cfg:
cfg.pspnl = PSPFamily()
if has_cont:
_setup_psp_attributes(cfg)
# Set up responder and communication sock
# psp_responder runs in _netns (remote namespace with psp_dev_peer)
responder = cfg.remote.deploy("psp_responder")
cfg.comm_port = rand_port()