* [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc
@ 2026-03-04 0:00 Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 2/9] selftests/net: Export Netlink class via lib.py Wei Wang
` (7 more replies)
0 siblings, 8 replies; 21+ messages in thread
From: Wei Wang @ 2026-03-04 0:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang
The main purpose of this feature is to associate virtual devices like
veth or netkit with a real PSP device, so we could provide PSP
functionality to the application running with virtual devices.
A typical deployment that works with this feature is as follows:
Host Namespace:
psp_dev_local ←──physically linked──→ psp_dev_peer
(PSP device)
│
│ BPF on psp_dev_local ingress: bpf_redirect_peer() to nk_guest
│
nk_host / veth_host
│
│ BPF on nk_host ingress: bpf_redirect_neigh() to psp_dev_local
│
Guest Namespace (netns):
│
nk_guest / veth_guest
★ PSP application run here
Remote Namespace (_netns):
psp_dev_peer
★ PSP server application runs here
Note:
The general requirement for this feature to work:
For PSP to work correctly, the egress device at validate_xmit_skb()
time must have psp_dev matching the association's psd. Any device
stacking or traffic redirection that changes the egress device will
cause either:
1. TX validation failure (SKB_DROP_REASON_PSP_OUTPUT) - fail-safe
2. RX policy failure after tx-assoc - packets without PSP extension
are rejected by receiver expecting encrypted traffic
Here are a few examples that this feature would not work:
- Bonding with load balancing in round-robin, XOR, 802.3ad mode across
multiple PSP devices, or mixed PSP and non-PSP devices
- Bonding with active-backup mode might work without PSP migration for
failover case.
- ipvlan/macvlan in bridge mode would not work given packets are
loopbacked locally without going through the PSP device.
The first 4 patchs are from this patchset:
https://lore.kernel.org/netdev/20260302053315.1919859-1-dw@davidwei.uk/
We base the newly added test cases on top of env NetDrvContEnv from this
patchset.
Changes since v1:
- Update the first 4 patches to reflect the latest changes in
https://lore.kernel.org/netdev/20260302053315.1919859-1-dw@davidwei.uk/
- Update patch 9 to add a param to NetDrvContEnv to control the loading
of the tx forwarding bpf program
David Wei (4):
selftests/net: Add bpf skb forwarding program
selftests/net: Export Netlink class via lib.py
selftests/net: Add env for container based tests
selftests/net: Add netkit container ping test
Wei Wang (5):
psp: add unprivileged version of psp_device_get_locked
psp: Add new netlink cmd for dev-assoc and dev-disassoc
psp: add a new netdev event for dev unregister
selftests/net: Add bpf skb forwarding program
selftest/net: psp: Add test for dev-assoc/disassoc
Documentation/netlink/specs/psp.yaml | 66 ++-
include/net/psp/types.h | 15 +
include/uapi/linux/psp.h | 13 +
net/psp/psp-nl-gen.c | 34 +-
net/psp/psp-nl-gen.h | 4 +
net/psp/psp.h | 2 +-
net/psp/psp_main.c | 86 ++-
net/psp/psp_nl.c | 297 +++++++++-
.../testing/selftests/drivers/net/README.rst | 38 ++
.../testing/selftests/drivers/net/hw/Makefile | 1 +
tools/testing/selftests/drivers/net/hw/config | 3 +
.../drivers/net/hw/lib/py/__init__.py | 7 +-
.../selftests/drivers/net/hw/nk_forward.bpf.c | 49 ++
.../selftests/drivers/net/hw/nk_netns.py | 23 +
.../drivers/net/hw/nk_redirect.bpf.c | 60 ++
.../selftests/drivers/net/lib/py/__init__.py | 11 +-
.../selftests/drivers/net/lib/py/env.py | 260 +++++++++
tools/testing/selftests/drivers/net/psp.py | 525 +++++++++++++++++-
.../testing/selftests/net/lib/py/__init__.py | 4 +-
tools/testing/selftests/net/lib/py/ynl.py | 4 +-
20 files changed, 1464 insertions(+), 38 deletions(-)
create mode 100644 tools/testing/selftests/drivers/net/hw/nk_forward.bpf.c
create mode 100755 tools/testing/selftests/drivers/net/hw/nk_netns.py
create mode 100644 tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 net-next 2/9] selftests/net: Export Netlink class via lib.py
2026-03-04 0:00 [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
@ 2026-03-04 0:00 ` Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 3/9] selftests/net: Add env for container based tests Wei Wang
` (6 subsequent siblings)
7 siblings, 0 replies; 21+ messages in thread
From: Wei Wang @ 2026-03-04 0:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang
From: David Wei <dw@davidwei.uk>
Making rtnl newlink calls requires constants defined in Netlink class in
pyynl. Export it.
Signed-off-by: David Wei <dw@davidwei.uk>
---
tools/testing/selftests/drivers/net/lib/py/__init__.py | 4 ++--
tools/testing/selftests/net/lib/py/__init__.py | 4 ++--
tools/testing/selftests/net/lib/py/ynl.py | 4 ++--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
index 5872d114f142..e5ef5e949c12 100644
--- a/tools/testing/selftests/drivers/net/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
@@ -19,7 +19,7 @@ try:
# Import one by one to avoid pylint false positives
from net.lib.py import NetNS, NetNSEnter, NetdevSimDev
from net.lib.py import EthtoolFamily, NetdevFamily, NetshaperFamily, \
- NlError, RtnlFamily, DevlinkFamily, PSPFamily
+ NlError, RtnlFamily, DevlinkFamily, PSPFamily, Netlink
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
@@ -31,7 +31,7 @@ try:
__all__ = ["NetNS", "NetNSEnter", "NetdevSimDev",
"EthtoolFamily", "NetdevFamily", "NetshaperFamily",
- "NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily",
+ "NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily", "Netlink",
"CmdExitFailure",
"bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool",
"fd_read_timeout", "ip", "rand_port", "rand_ports",
diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index a584e7f806a4..54e84282781c 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py
@@ -16,7 +16,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \
wait_file, tool
from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily
-from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily
+from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink
__all__ = ["KSRC",
"KsftFailEx", "KsftSkipEx", "KsftXfailEx", "ksft_pr", "ksft_eq",
@@ -31,4 +31,4 @@ __all__ = ["KSRC",
"NetdevSim", "NetdevSimDev",
"NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
"YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily",
- "RtnlAddrFamily"]
+ "RtnlAddrFamily", "Netlink"]
diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py
index 32c223e93b2c..a52b067f7589 100644
--- a/tools/testing/selftests/net/lib/py/ynl.py
+++ b/tools/testing/selftests/net/lib/py/ynl.py
@@ -13,14 +13,14 @@ try:
SPEC_PATH = KSFT_DIR / "net/lib/specs"
sys.path.append(tools_full_path.as_posix())
- from net.lib.ynl.pyynl.lib import YnlFamily, NlError
+ from net.lib.ynl.pyynl.lib import YnlFamily, NlError, Netlink
else:
# Running in tree
tools_full_path = KSRC / "tools"
SPEC_PATH = KSRC / "Documentation/netlink/specs"
sys.path.append(tools_full_path.as_posix())
- from net.ynl.pyynl.lib import YnlFamily, NlError
+ from net.ynl.pyynl.lib import YnlFamily, NlError, Netlink
except ModuleNotFoundError as e:
ksft_pr("Failed importing `ynl` library from kernel sources")
ksft_pr(str(e))
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 net-next 3/9] selftests/net: Add env for container based tests
2026-03-04 0:00 [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 2/9] selftests/net: Export Netlink class via lib.py Wei Wang
@ 2026-03-04 0:00 ` Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 4/9] selftests/net: Add netkit container ping test Wei Wang
` (5 subsequent siblings)
7 siblings, 0 replies; 21+ messages in thread
From: Wei Wang @ 2026-03-04 0:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang, Daniel Borkmann
From: David Wei <dw@davidwei.uk>
Add an env NetDrvContEnv for container based selftests. This automates
the setup of a netns, netkit pair with one inside the netns, and a BPF
program that forwards skbs from the NETIF host inside the container.
Currently only netkit is used, but other virtual netdevs e.g. veth can
be used too.
Expect netkit container datapath selftests to have a publicly routable
IP prefix to assign to netkit in a container, such that packets will
land on eth0. The BPF skb forward program will then forward such packets
from the host netns to the container netns.
Signed-off-by: David Wei <dw@davidwei.uk>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
.../testing/selftests/drivers/net/README.rst | 38 ++++
.../drivers/net/hw/lib/py/__init__.py | 7 +-
.../selftests/drivers/net/lib/py/__init__.py | 7 +-
.../selftests/drivers/net/lib/py/env.py | 208 ++++++++++++++++++
4 files changed, 254 insertions(+), 6 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst
index eb838ae94844..24bf4e10024f 100644
--- a/tools/testing/selftests/drivers/net/README.rst
+++ b/tools/testing/selftests/drivers/net/README.rst
@@ -62,6 +62,44 @@ LOCAL_V4, LOCAL_V6, REMOTE_V4, REMOTE_V6
Local and remote endpoint IP addresses.
+LOCAL_PREFIX_V6
+~~~~~~~~~~~~~~~
+
+Local IP prefix/subnet which can be used to allocate extra IP addresses (for
+network name spaces behind macvlan, veth, netkit devices). DUT must be
+reachable using these addresses from the endpoint.
+
+LOCAL_PREFIX_V6 must NOT match LOCAL_V6.
+
+Example:
+ NETIF = "eth0"
+ LOCAL_V6 = "2001:db8::1"
+ REMOTE_V6 = "2001:db8::2"
+ LOCAL_PREFIX_V6 = "fd00:cafe::"
+
+ +-----------------------------+ +------------------------------+
+ dst | INIT NS | | TEST NS |
+ fd00: | +-------------+ | | |
+ cafe:: | | NETIF | | bpf | |
+ 2:2 +---|>| 2001:db8::1 | |redirect| +-------------------------+ |
+ | | | |-------------|--------|>| Netkit | |
+ | | +-------------+ | _peer | | nk_guest | |
+ | | +-------------+ Netkit pair | | | fe80::2/64 | |
+ | | | Netkit |.............|........|>| fd00:cafe::2:2/64 | |
+ | | | nk_host | | | +-------------------------+ |
+ | | | fe80::1/64 | | | |
+ | | +-------------+ | | route: |
+ | | | | default |
+ | | route: | | via fe80::1 dev nk_guest |
+ | | fd00:cafe::2:2/128 | +------------------------------+
+ | | via fe80::2 dev nk_host |
+ | +-----------------------------+
+ |
+ | +---------------+
+ | | REMOTE |
+ +---| 2001:db8::2 |
+ +---------------+
+
REMOTE_TYPE
~~~~~~~~~~~
diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
index 1971577d47e9..b8d9ae282390 100644
--- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
@@ -3,6 +3,7 @@
"""
Driver test environment (hardware-only tests).
NetDrvEnv and NetDrvEpEnv are the main environment classes.
+NetDrvContEnv extends NetDrvEpEnv with netkit container support.
Former is for local host only tests, latter creates / connects
to a remote endpoint. See NIPA wiki for more information about
running and writing driver tests.
@@ -30,7 +31,7 @@ try:
from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \
ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
from drivers.net.lib.py import GenerateTraffic, Remote, Iperf3Runner
- from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv
+ from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv
__all__ = ["NetNS", "NetNSEnter", "NetdevSimDev",
"EthtoolFamily", "NetdevFamily", "NetshaperFamily",
@@ -45,8 +46,8 @@ try:
"ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt",
"ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
"ksft_not_none", "ksft_not_none",
- "NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote",
- "Iperf3Runner"]
+ "NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic",
+ "Remote", "Iperf3Runner"]
except ModuleNotFoundError as e:
print("Failed importing `net` library from kernel sources")
print(str(e))
diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
index e5ef5e949c12..374d4f08dd05 100644
--- a/tools/testing/selftests/drivers/net/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
@@ -3,6 +3,7 @@
"""
Driver test environment.
NetDrvEnv and NetDrvEpEnv are the main environment classes.
+NetDrvContEnv extends NetDrvEpEnv with netkit container support.
Former is for local host only tests, latter creates / connects
to a remote endpoint. See NIPA wiki for more information about
running and writing driver tests.
@@ -43,12 +44,12 @@ try:
"ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
"ksft_not_none", "ksft_not_none"]
- from .env import NetDrvEnv, NetDrvEpEnv
+ from .env import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv
from .load import GenerateTraffic, Iperf3Runner
from .remote import Remote
- __all__ += ["NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote",
- "Iperf3Runner"]
+ __all__ += ["NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic",
+ "Remote", "Iperf3Runner"]
except ModuleNotFoundError as e:
print("Failed importing `net` library from kernel sources")
print(str(e))
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index 41cc248ac848..36d98e1df15b 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -1,13 +1,16 @@
# SPDX-License-Identifier: GPL-2.0
+import ipaddress
import os
import time
+import json
from pathlib import Path
from lib.py import KsftSkipEx, KsftXfailEx
from lib.py import ksft_setup, wait_file
from lib.py import cmd, ethtool, ip, CmdExitFailure
from lib.py import NetNS, NetdevSimDev
from .remote import Remote
+from . import bpftool, RtnlFamily, Netlink
class NetDrvEnvBase:
@@ -289,3 +292,208 @@ class NetDrvEpEnv(NetDrvEnvBase):
data.get('stats-block-usecs', 0) / 1000 / 1000
time.sleep(self._stats_settle_time)
+
+
+class NetDrvContEnv(NetDrvEpEnv):
+ """
+ Class for an environment with a netkit pair setup for forwarding traffic
+ between the physical interface and a network namespace.
+ NETIF = "eth0"
+ LOCAL_V6 = "2001:db8::1"
+ REMOTE_V6 = "2001:db8::2"
+ LOCAL_PREFIX_V6 = "fd00:cafe::"
+
+ +-----------------------------+ +------------------------------+
+ dst | INIT NS | | TEST NS |
+ fd00: | +-------------+ | | |
+ cafe:: | | NETIF | | bpf | |
+ 2:2 +---|>| 2001:db8::1 | |redirect| +-------------------------+ |
+ | | | |-------------|--------|>| Netkit | |
+ | | +-------------+ | _peer | | nk_guest | |
+ | | +-------------+ Netkit pair | | | fe80::2/64 | |
+ | | | Netkit |.............|........|>| fd00:cafe::2:2/64 | |
+ | | | nk_host | | | +-------------------------+ |
+ | | | fe80::1/64 | | | |
+ | | +-------------+ | | route: |
+ | | | | default |
+ | | route: | | via fe80::1 dev nk_guest |
+ | | fd00:cafe::2:2/128 | +------------------------------+
+ | | via fe80::2 dev nk_host |
+ | +-----------------------------+
+ |
+ | +---------------+
+ | | REMOTE |
+ +---| 2001:db8::2 |
+ +---------------+
+ """
+
+ def __init__(self, src_path, rxqueues=1, **kwargs):
+ self.netns = None
+ self._nk_host_ifname = None
+ self._nk_guest_ifname = None
+ self._tc_clsact_added = False
+ self._tc_attached = False
+ self._bpf_prog_pref = None
+ self._bpf_prog_id = None
+ self._init_ns_attached = False
+ self._old_fwd = None
+ self._old_accept_ra = None
+
+ super().__init__(src_path, **kwargs)
+
+ self.require_ipver("6")
+ local_prefix = self.env.get("LOCAL_PREFIX_V6")
+ if not local_prefix:
+ raise KsftSkipEx("LOCAL_PREFIX_V6 required")
+
+ try:
+ net = ipaddress.IPv6Network(local_prefix, strict=False)
+ except ValueError:
+ net = ipaddress.IPv6Network(f"{local_prefix}::/64", strict=False)
+ self.ipv6_prefix = str(net.network_address)
+ self.nk_host_ipv6 = f"{self.ipv6_prefix}2:1"
+ self.nk_guest_ipv6 = f"{self.ipv6_prefix}2:2"
+
+ local_v6 = ipaddress.IPv6Address(self.addr_v["6"])
+ if local_v6 in net:
+ raise KsftSkipEx("LOCAL_V6 must not fall within LOCAL_PREFIX_V6")
+
+ rtnl = RtnlFamily()
+ rtnl.newlink(
+ {
+ "linkinfo": {
+ "kind": "netkit",
+ "data": {
+ "mode": "l2",
+ "policy": "forward",
+ "peer-policy": "forward",
+ },
+ },
+ "num-rx-queues": rxqueues,
+ },
+ flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
+ )
+
+ all_links = ip("-d link show", json=True)
+ netkit_links = [link for link in all_links
+ if link.get('linkinfo', {}).get('info_kind') == 'netkit'
+ and 'UP' not in link.get('flags', [])]
+
+ if len(netkit_links) != 2:
+ raise KsftSkipEx("Failed to create netkit pair")
+
+ netkit_links.sort(key=lambda x: x['ifindex'])
+ self._nk_host_ifname = netkit_links[1]['ifname']
+ self._nk_guest_ifname = netkit_links[0]['ifname']
+ self.nk_host_ifindex = netkit_links[1]['ifindex']
+ self.nk_guest_ifindex = netkit_links[0]['ifindex']
+
+ self._setup_ns()
+ self._attach_bpf()
+
+ def __del__(self):
+ if self._tc_attached:
+ cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}")
+ self._tc_attached = False
+
+ if self._tc_clsact_added:
+ cmd(f"tc qdisc del dev {self.ifname} clsact")
+ self._tc_clsact_added = False
+
+ if self._nk_host_ifname:
+ cmd(f"ip link del dev {self._nk_host_ifname}")
+ self._nk_host_ifname = None
+ self._nk_guest_ifname = None
+
+ if self._init_ns_attached:
+ cmd("ip netns del init", fail=False)
+ self._init_ns_attached = False
+
+ if self.netns:
+ del self.netns
+ self.netns = None
+
+ if self._old_fwd is not None:
+ with open("/proc/sys/net/ipv6/conf/all/forwarding", "w") as f:
+ f.write(self._old_fwd)
+ self._old_fwd = None
+ if self._old_accept_ra is not None:
+ with open("/proc/sys/net/ipv6/conf/all/accept_ra", "w") as f:
+ f.write(self._old_accept_ra)
+ self._old_accept_ra = None
+
+ super().__del__()
+
+ def _setup_ns(self):
+ fwd_path = "/proc/sys/net/ipv6/conf/all/forwarding"
+ ra_path = "/proc/sys/net/ipv6/conf/all/accept_ra"
+ with open(fwd_path) as f:
+ self._old_fwd = f.read().strip()
+ with open(ra_path) as f:
+ self._old_accept_ra = f.read().strip()
+ with open(fwd_path, "w") as f:
+ f.write("1")
+ with open(ra_path, "w") as f:
+ f.write("2")
+
+ self.netns = NetNS()
+ cmd("ip netns attach init 1")
+ 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}")
+ 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}")
+
+ ip("link set lo up", ns=self.netns)
+ ip(f"link set dev {self._nk_guest_ifname} up", ns=self.netns)
+ ip(f"-6 addr add fe80::2/64 dev {self._nk_guest_ifname}", ns=self.netns)
+ ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self._nk_guest_ifname} nodad", ns=self.netns)
+ ip(f"-6 route add default via fe80::1 dev {self._nk_guest_ifname}", ns=self.netns)
+
+ def _tc_ensure_clsact(self):
+ qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout)
+ for q in qdisc:
+ if q['kind'] == 'clsact':
+ return
+ cmd(f"tc qdisc add dev {self.ifname} clsact")
+ self._tc_clsact_added = True
+
+ def _get_bpf_prog_ids(self):
+ filter = json.loads(cmd(f"tc -j filter show dev {self.ifname} ingress").stdout)
+ for bpf in filter:
+ if 'options' not in bpf:
+ continue
+ if bpf['options']['bpf_name'].startswith('nk_forward.bpf'):
+ return (bpf['pref'], bpf['options']['prog']['id'])
+ if self._bpf_prog_pref is None:
+ raise Exception("Failed to get BPF prog ID")
+
+ def _attach_bpf(self):
+ bpf_obj = self.test_dir / "nk_forward.bpf.o"
+ if not bpf_obj.exists():
+ raise KsftSkipEx("BPF prog not found")
+
+ self._tc_ensure_clsact()
+ cmd(f"tc filter add dev {self.ifname} ingress bpf obj {bpf_obj} sec tc/ingress direct-action")
+ self._tc_attached = True
+
+ (self._bpf_prog_pref, self._bpf_prog_id) = self._get_bpf_prog_ids()
+ prog_info = bpftool(f"prog show id {self._bpf_prog_id}", json=True)
+ map_ids = prog_info.get("map_ids", [])
+
+ bss_map_id = None
+ for map_id in map_ids:
+ map_info = bpftool(f"map show id {map_id}", json=True)
+ if map_info.get("name").endswith("bss"):
+ bss_map_id = map_id
+
+ if bss_map_id is None:
+ raise Exception("Failed to find .bss map")
+
+ ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix)
+ ipv6_bytes = ipv6_addr.packed
+ ifindex_bytes = self.nk_host_ifindex.to_bytes(4, byteorder='little')
+ value = ipv6_bytes + ifindex_bytes
+ value_hex = ' '.join(f'{b:02x}' for b in value)
+ bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 net-next 4/9] selftests/net: Add netkit container ping test
2026-03-04 0:00 [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 2/9] selftests/net: Export Netlink class via lib.py Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 3/9] selftests/net: Add env for container based tests Wei Wang
@ 2026-03-04 0:00 ` Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked Wei Wang
` (4 subsequent siblings)
7 siblings, 0 replies; 21+ messages in thread
From: Wei Wang @ 2026-03-04 0:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang, Daniel Borkmann
From: David Wei <dw@davidwei.uk>
Add a basic ping test using NetDrvContEnv that sets up a netkit pair,
with one end in a netns. Use LOCAL_PREFIX_V6 and nk_forward BPF program
to ping from a remote host to the netkit in netns.
Signed-off-by: David Wei <dw@davidwei.uk>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
.../testing/selftests/drivers/net/hw/Makefile | 1 +
tools/testing/selftests/drivers/net/hw/config | 3 +++
.../selftests/drivers/net/hw/nk_netns.py | 23 +++++++++++++++++++
3 files changed, 27 insertions(+)
create mode 100755 tools/testing/selftests/drivers/net/hw/nk_netns.py
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index a64140333a46..91df028abfc0 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -32,6 +32,7 @@ TEST_PROGS = \
irq.py \
loopback.sh \
nic_timestamp.py \
+ nk_netns.py \
pp_alloc_fail.py \
rss_api.py \
rss_ctx.py \
diff --git a/tools/testing/selftests/drivers/net/hw/config b/tools/testing/selftests/drivers/net/hw/config
index 2307aa001be1..b6524040418e 100644
--- a/tools/testing/selftests/drivers/net/hw/config
+++ b/tools/testing/selftests/drivers/net/hw/config
@@ -1,3 +1,4 @@
+CONFIG_BPF_SYSCALL=y
CONFIG_FAIL_FUNCTION=y
CONFIG_FAULT_INJECTION=y
CONFIG_FAULT_INJECTION_DEBUG_FS=y
@@ -5,6 +6,8 @@ CONFIG_FUNCTION_ERROR_INJECTION=y
CONFIG_IO_URING=y
CONFIG_IPV6=y
CONFIG_IPV6_GRE=y
+CONFIG_NETKIT=y
+CONFIG_NET_CLS_BPF=y
CONFIG_NET_IPGRE=y
CONFIG_NET_IPGRE_DEMUX=y
CONFIG_UDMABUF=y
diff --git a/tools/testing/selftests/drivers/net/hw/nk_netns.py b/tools/testing/selftests/drivers/net/hw/nk_netns.py
new file mode 100755
index 000000000000..afa8638195d8
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_netns.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+from lib.py import ksft_run, ksft_exit
+from lib.py import NetDrvContEnv
+from lib.py import cmd
+
+
+def test_ping(cfg) -> None:
+ cfg.require_ipver("6")
+
+ cmd(f"ping -c 1 -W5 {cfg.nk_guest_ipv6}", host=cfg.remote)
+ cmd(f"ping -c 1 -W5 {cfg.remote_addr_v['6']}", ns=cfg.netns)
+
+
+def main() -> None:
+ with NetDrvContEnv(__file__) as cfg:
+ ksft_run([test_ping], args=(cfg,))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
2026-03-04 0:00 [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
` (2 preceding siblings ...)
2026-03-04 0:00 ` [PATCH v2 net-next 4/9] selftests/net: Add netkit container ping test Wei Wang
@ 2026-03-04 0:00 ` Wei Wang
2026-03-04 15:00 ` Daniel Zahka
2026-03-04 16:25 ` Willem de Bruijn
2026-03-04 0:00 ` [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc Wei Wang
` (3 subsequent siblings)
7 siblings, 2 replies; 21+ messages in thread
From: Wei Wang @ 2026-03-04 0:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang
Add a place holder function called psp_device_get_locked_unpriv() which
will be used for commands that are unprivileged and are used for
exisiting commands like dev-dump, dev-get, rx-assoc, tx-assoc.
Commands including dev-add/delete/change-ntf, key-rotate would keep
using the privileged version.
Following commit will be implementing the unprivileged version check.
Signed-off-by: Wei Wang <weibunny@fb.com>
---
Documentation/netlink/specs/psp.yaml | 2 +-
net/psp/psp-nl-gen.c | 2 +-
net/psp/psp-nl-gen.h | 2 ++
net/psp/psp.h | 2 +-
net/psp/psp_main.c | 3 ++-
net/psp/psp_nl.c | 28 +++++++++++++++++++++-------
6 files changed, 28 insertions(+), 11 deletions(-)
diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml
index f3a57782d2cf..2ef94f3503c8 100644
--- a/Documentation/netlink/specs/psp.yaml
+++ b/Documentation/netlink/specs/psp.yaml
@@ -170,7 +170,7 @@ operations:
- ifindex
- psp-versions-cap
- psp-versions-ena
- pre: psp-device-get-locked
+ pre: psp-device-get-locked-unpriv
post: psp-device-unlock
dump:
reply: *dev-all
diff --git a/net/psp/psp-nl-gen.c b/net/psp/psp-nl-gen.c
index 22a48d0fa378..106607a201d8 100644
--- a/net/psp/psp-nl-gen.c
+++ b/net/psp/psp-nl-gen.c
@@ -57,7 +57,7 @@ static const struct nla_policy psp_get_stats_nl_policy[PSP_A_STATS_DEV_ID + 1] =
static const struct genl_split_ops psp_nl_ops[] = {
{
.cmd = PSP_CMD_DEV_GET,
- .pre_doit = psp_device_get_locked,
+ .pre_doit = psp_device_get_locked_unpriv,
.doit = psp_nl_dev_get_doit,
.post_doit = psp_device_unlock,
.policy = psp_dev_get_nl_policy,
diff --git a/net/psp/psp-nl-gen.h b/net/psp/psp-nl-gen.h
index 599c5f1c82f2..7abad086be1e 100644
--- a/net/psp/psp-nl-gen.h
+++ b/net/psp/psp-nl-gen.h
@@ -15,6 +15,8 @@
/* Common nested types */
extern const struct nla_policy psp_keys_nl_policy[PSP_A_KEYS_SPI + 1];
+int psp_device_get_locked_unpriv(const struct genl_split_ops *ops,
+ struct sk_buff *skb, struct genl_info *info);
int psp_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info);
int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
diff --git a/net/psp/psp.h b/net/psp/psp.h
index 9f19137593a0..0e4ca03de869 100644
--- a/net/psp/psp.h
+++ b/net/psp/psp.h
@@ -14,7 +14,7 @@ extern struct xarray psp_devs;
extern struct mutex psp_devs_lock;
void psp_dev_free(struct psp_dev *psd);
-int psp_dev_check_access(struct psp_dev *psd, struct net *net);
+int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool unpriv);
void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd);
diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c
index d4c04c923c5a..27390b5cc89d 100644
--- a/net/psp/psp_main.c
+++ b/net/psp/psp_main.c
@@ -27,10 +27,11 @@ struct mutex psp_devs_lock;
* psp_dev_check_access() - check if user in a given net ns can access PSP dev
* @psd: PSP device structure user is trying to access
* @net: net namespace user is in
+ * @unpriv: whether the caller is unprivileged
*
* Return: 0 if PSP device should be visible in @net, errno otherwise.
*/
-int psp_dev_check_access(struct psp_dev *psd, struct net *net)
+int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool unpriv)
{
if (dev_net(psd->main_netdev) == net)
return 0;
diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c
index 6afd7707ec12..8e0e4a853f9b 100644
--- a/net/psp/psp_nl.c
+++ b/net/psp/psp_nl.c
@@ -41,7 +41,8 @@ static int psp_nl_reply_send(struct sk_buff *rsp, struct genl_info *info)
/* Device stuff */
static struct psp_dev *
-psp_device_get_and_lock(struct net *net, struct nlattr *dev_id)
+psp_device_get_and_lock(struct net *net, struct nlattr *dev_id,
+ bool unpriv)
{
struct psp_dev *psd;
int err;
@@ -56,7 +57,7 @@ psp_device_get_and_lock(struct net *net, struct nlattr *dev_id)
mutex_lock(&psd->lock);
mutex_unlock(&psp_devs_lock);
- err = psp_dev_check_access(psd, net);
+ err = psp_dev_check_access(psd, net, unpriv);
if (err) {
mutex_unlock(&psd->lock);
return ERR_PTR(err);
@@ -72,7 +73,20 @@ int psp_device_get_locked(const struct genl_split_ops *ops,
return -EINVAL;
info->user_ptr[0] = psp_device_get_and_lock(genl_info_net(info),
- info->attrs[PSP_A_DEV_ID]);
+ info->attrs[PSP_A_DEV_ID],
+ false);
+ return PTR_ERR_OR_ZERO(info->user_ptr[0]);
+}
+
+int psp_device_get_locked_unpriv(const struct genl_split_ops *ops,
+ struct sk_buff *skb, struct genl_info *info)
+{
+ if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_ID))
+ return -EINVAL;
+
+ info->user_ptr[0] = psp_device_get_and_lock(genl_info_net(info),
+ info->attrs[PSP_A_DEV_ID],
+ true);
return PTR_ERR_OR_ZERO(info->user_ptr[0]);
}
@@ -160,7 +174,7 @@ static int
psp_nl_dev_get_dumpit_one(struct sk_buff *rsp, struct netlink_callback *cb,
struct psp_dev *psd)
{
- if (psp_dev_check_access(psd, sock_net(rsp->sk)))
+ if (psp_dev_check_access(psd, sock_net(rsp->sk), true))
return 0;
return psp_nl_dev_fill(psd, rsp, genl_info_dump(cb));
@@ -305,7 +319,7 @@ int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
psd = psp_dev_get_for_sock(socket->sk);
if (psd) {
- err = psp_dev_check_access(psd, genl_info_net(info));
+ err = psp_dev_check_access(psd, genl_info_net(info), true);
if (err) {
psp_dev_put(psd);
psd = NULL;
@@ -330,7 +344,7 @@ int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
psp_dev_put(psd);
} else {
- psd = psp_device_get_and_lock(genl_info_net(info), id);
+ psd = psp_device_get_and_lock(genl_info_net(info), id, true);
if (IS_ERR(psd)) {
err = PTR_ERR(psd);
goto err_sock_put;
@@ -573,7 +587,7 @@ static int
psp_nl_stats_get_dumpit_one(struct sk_buff *rsp, struct netlink_callback *cb,
struct psp_dev *psd)
{
- if (psp_dev_check_access(psd, sock_net(rsp->sk)))
+ if (psp_dev_check_access(psd, sock_net(rsp->sk), true))
return 0;
return psp_nl_stats_fill(psd, rsp, genl_info_dump(cb));
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc
2026-03-04 0:00 [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
` (3 preceding siblings ...)
2026-03-04 0:00 ` [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked Wei Wang
@ 2026-03-04 0:00 ` Wei Wang
2026-03-04 15:20 ` Daniel Zahka
2026-03-04 16:17 ` Daniel Zahka
2026-03-04 0:00 ` [PATCH v2 net-next 7/9] psp: add a new netdev event for dev unregister Wei Wang
` (2 subsequent siblings)
7 siblings, 2 replies; 21+ messages in thread
From: Wei Wang @ 2026-03-04 0:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang
The main purpose of this cmd is to be able to associcate a
non-psp-capable device (e.g. veth) with a psp device.
One use case is if we create a pair of veth/netkit, and assign 1 end
inside a netns, while leaving the other end within the default netns,
with a real PSP device, e.g. netdevsim or a physical PSP-capable NIC.
With this command, we could associate the veth/netkit inside the netns
with PSP device, so the virtual device could act as PSP-capable device
to initiate PSP connections, and performs PSP encryption/decryption on
the real PSP device.
Signed-off-by: Wei Wang <weibunny@fb.com>
---
Documentation/netlink/specs/psp.yaml | 64 ++++++-
include/net/psp/types.h | 15 ++
include/uapi/linux/psp.h | 13 ++
net/psp/psp-nl-gen.c | 32 ++++
net/psp/psp-nl-gen.h | 2 +
net/psp/psp_main.c | 21 ++-
net/psp/psp_nl.c | 269 ++++++++++++++++++++++++++-
7 files changed, 407 insertions(+), 9 deletions(-)
diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml
index 2ef94f3503c8..0e096d79789c 100644
--- a/Documentation/netlink/specs/psp.yaml
+++ b/Documentation/netlink/specs/psp.yaml
@@ -13,6 +13,17 @@ definitions:
hdr0-aes-gmac-128, hdr0-aes-gmac-256]
attribute-sets:
+ -
+ name: assoc-dev-info
+ attributes:
+ -
+ name: ifindex
+ doc: ifindex of an associated network device.
+ type: u32
+ -
+ name: nsid
+ doc: Network namespace ID of the associated device.
+ type: s32
-
name: dev
attributes:
@@ -24,7 +35,9 @@ attribute-sets:
min: 1
-
name: ifindex
- doc: ifindex of the main netdevice linked to the PSP device.
+ doc: |
+ ifindex of the main netdevice linked to the PSP device,
+ or the ifindex to associate with the PSP device.
type: u32
-
name: psp-versions-cap
@@ -38,6 +51,25 @@ attribute-sets:
type: u32
enum: version
enum-as-flags: true
+ -
+ name: assoc-list
+ doc: List of associated virtual devices.
+ type: nest
+ nested-attributes: assoc-dev-info
+ multi-attr: true
+ -
+ name: nsid
+ doc: Network namespace ID for the device to associate/disassociate.
+ type: s32
+ -
+ name: by-association
+ doc: |
+ Flag indicating the PSP device is an associated device from a
+ different network namespace.
+ Present when in associated namespace, absent when in primary/host
+ namespace.
+ type: flag
+
-
name: assoc
attributes:
@@ -170,6 +202,8 @@ operations:
- ifindex
- psp-versions-cap
- psp-versions-ena
+ - assoc-list
+ - by-association
pre: psp-device-get-locked-unpriv
post: psp-device-unlock
dump:
@@ -271,6 +305,34 @@ operations:
post: psp-device-unlock
dump:
reply: *stats-all
+ -
+ name: dev-assoc
+ doc: Associate a network device with a PSP device.
+ attribute-set: dev
+ do:
+ request:
+ attributes:
+ - id
+ - ifindex
+ - nsid
+ reply:
+ attributes: []
+ pre: psp-device-get-locked-unpriv
+ post: psp-device-unlock
+ -
+ name: dev-disassoc
+ doc: Disassociate a network device from a PSP device.
+ attribute-set: dev
+ do:
+ request:
+ attributes:
+ - id
+ - ifindex
+ - nsid
+ reply:
+ attributes: []
+ pre: psp-device-get-locked-unpriv
+ post: psp-device-unlock
mcast-groups:
list:
diff --git a/include/net/psp/types.h b/include/net/psp/types.h
index 25a9096d4e7d..4bd432ed107a 100644
--- a/include/net/psp/types.h
+++ b/include/net/psp/types.h
@@ -5,6 +5,7 @@
#include <linux/mutex.h>
#include <linux/refcount.h>
+#include <net/net_trackers.h>
struct netlink_ext_ack;
@@ -43,9 +44,22 @@ struct psp_dev_config {
u32 versions;
};
+/**
+ * struct psp_assoc_dev - wrapper for associated net_device
+ * @dev_list: list node for psp_dev::assoc_dev_list
+ * @assoc_dev: the associated net_device
+ * @dev_tracker: tracker for the net_device reference
+ */
+struct psp_assoc_dev {
+ struct list_head dev_list;
+ struct net_device *assoc_dev;
+ netdevice_tracker dev_tracker;
+};
+
/**
* struct psp_dev - PSP device struct
* @main_netdev: original netdevice of this PSP device
+ * @assoc_dev_list: list of psp_assoc_dev entries associated with this PSP device
* @ops: driver callbacks
* @caps: device capabilities
* @drv_priv: driver priv pointer
@@ -67,6 +81,7 @@ struct psp_dev_config {
*/
struct psp_dev {
struct net_device *main_netdev;
+ struct list_head assoc_dev_list;
struct psp_dev_ops *ops;
struct psp_dev_caps *caps;
diff --git a/include/uapi/linux/psp.h b/include/uapi/linux/psp.h
index a3a336488dc3..1c8899cd4da5 100644
--- a/include/uapi/linux/psp.h
+++ b/include/uapi/linux/psp.h
@@ -17,11 +17,22 @@ enum psp_version {
PSP_VERSION_HDR0_AES_GMAC_256,
};
+enum {
+ PSP_A_ASSOC_DEV_INFO_IFINDEX = 1,
+ PSP_A_ASSOC_DEV_INFO_NSID,
+
+ __PSP_A_ASSOC_DEV_INFO_MAX,
+ PSP_A_ASSOC_DEV_INFO_MAX = (__PSP_A_ASSOC_DEV_INFO_MAX - 1)
+};
+
enum {
PSP_A_DEV_ID = 1,
PSP_A_DEV_IFINDEX,
PSP_A_DEV_PSP_VERSIONS_CAP,
PSP_A_DEV_PSP_VERSIONS_ENA,
+ PSP_A_DEV_ASSOC_LIST,
+ PSP_A_DEV_NSID,
+ PSP_A_DEV_BY_ASSOCIATION,
__PSP_A_DEV_MAX,
PSP_A_DEV_MAX = (__PSP_A_DEV_MAX - 1)
@@ -74,6 +85,8 @@ enum {
PSP_CMD_RX_ASSOC,
PSP_CMD_TX_ASSOC,
PSP_CMD_GET_STATS,
+ PSP_CMD_DEV_ASSOC,
+ PSP_CMD_DEV_DISASSOC,
__PSP_CMD_MAX,
PSP_CMD_MAX = (__PSP_CMD_MAX - 1)
diff --git a/net/psp/psp-nl-gen.c b/net/psp/psp-nl-gen.c
index 106607a201d8..753cbe993399 100644
--- a/net/psp/psp-nl-gen.c
+++ b/net/psp/psp-nl-gen.c
@@ -53,6 +53,20 @@ static const struct nla_policy psp_get_stats_nl_policy[PSP_A_STATS_DEV_ID + 1] =
[PSP_A_STATS_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
};
+/* PSP_CMD_DEV_ASSOC - do */
+static const struct nla_policy psp_dev_assoc_nl_policy[PSP_A_DEV_NSID + 1] = {
+ [PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
+ [PSP_A_DEV_IFINDEX] = { .type = NLA_U32, },
+ [PSP_A_DEV_NSID] = { .type = NLA_S32, },
+};
+
+/* PSP_CMD_DEV_DISASSOC - do */
+static const struct nla_policy psp_dev_disassoc_nl_policy[PSP_A_DEV_NSID + 1] = {
+ [PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
+ [PSP_A_DEV_IFINDEX] = { .type = NLA_U32, },
+ [PSP_A_DEV_NSID] = { .type = NLA_S32, },
+};
+
/* Ops table for psp */
static const struct genl_split_ops psp_nl_ops[] = {
{
@@ -119,6 +133,24 @@ static const struct genl_split_ops psp_nl_ops[] = {
.dumpit = psp_nl_get_stats_dumpit,
.flags = GENL_CMD_CAP_DUMP,
},
+ {
+ .cmd = PSP_CMD_DEV_ASSOC,
+ .pre_doit = psp_device_get_locked_unpriv,
+ .doit = psp_nl_dev_assoc_doit,
+ .post_doit = psp_device_unlock,
+ .policy = psp_dev_assoc_nl_policy,
+ .maxattr = PSP_A_DEV_NSID,
+ .flags = GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = PSP_CMD_DEV_DISASSOC,
+ .pre_doit = psp_device_get_locked_unpriv,
+ .doit = psp_nl_dev_disassoc_doit,
+ .post_doit = psp_device_unlock,
+ .policy = psp_dev_disassoc_nl_policy,
+ .maxattr = PSP_A_DEV_NSID,
+ .flags = GENL_CMD_CAP_DO,
+ },
};
static const struct genl_multicast_group psp_nl_mcgrps[] = {
diff --git a/net/psp/psp-nl-gen.h b/net/psp/psp-nl-gen.h
index 7abad086be1e..d3b331888f9c 100644
--- a/net/psp/psp-nl-gen.h
+++ b/net/psp/psp-nl-gen.h
@@ -33,6 +33,8 @@ int psp_nl_rx_assoc_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_tx_assoc_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_get_stats_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_get_stats_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info);
+int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info);
enum {
PSP_NLGRP_MGMT,
diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c
index 27390b5cc89d..a7635cae0d83 100644
--- a/net/psp/psp_main.c
+++ b/net/psp/psp_main.c
@@ -33,8 +33,18 @@ struct mutex psp_devs_lock;
*/
int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool unpriv)
{
+ struct psp_assoc_dev *entry;
+
if (dev_net(psd->main_netdev) == net)
return 0;
+
+ if (unpriv) {
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ if (dev_net(entry->assoc_dev) == net)
+ return 0;
+ }
+ }
+
return -ENOENT;
}
@@ -70,6 +80,7 @@ psp_dev_create(struct net_device *netdev,
return ERR_PTR(-ENOMEM);
psd->main_netdev = netdev;
+ INIT_LIST_HEAD(&psd->assoc_dev_list);
psd->ops = psd_ops;
psd->caps = psd_caps;
psd->drv_priv = priv_ptr;
@@ -117,6 +128,7 @@ void psp_dev_free(struct psp_dev *psd)
*/
void psp_dev_unregister(struct psp_dev *psd)
{
+ struct psp_assoc_dev *entry, *entry_tmp;
struct psp_assoc *pas, *next;
mutex_lock(&psp_devs_lock);
@@ -136,6 +148,14 @@ void psp_dev_unregister(struct psp_dev *psd)
list_for_each_entry_safe(pas, next, &psd->stale_assocs, assocs_list)
psp_dev_tx_key_del(psd, pas);
+ list_for_each_entry_safe(entry, entry_tmp, &psd->assoc_dev_list,
+ dev_list) {
+ list_del(&entry->dev_list);
+ rcu_assign_pointer(entry->assoc_dev->psp_dev, NULL);
+ netdev_put(entry->assoc_dev, &entry->dev_tracker);
+ kfree(entry);
+ }
+
rcu_assign_pointer(psd->main_netdev->psp_dev, NULL);
psd->ops = NULL;
@@ -357,5 +377,4 @@ static int __init psp_init(void)
return genl_register_family(&psp_nl_family);
}
-
subsys_initcall(psp_init);
diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c
index 8e0e4a853f9b..40d702e34064 100644
--- a/net/psp/psp_nl.c
+++ b/net/psp/psp_nl.c
@@ -2,6 +2,7 @@
#include <linux/ethtool.h>
#include <linux/skbuff.h>
+#include <linux/net_namespace.h>
#include <linux/xarray.h>
#include <net/genetlink.h>
#include <net/psp.h>
@@ -38,6 +39,58 @@ static int psp_nl_reply_send(struct sk_buff *rsp, struct genl_info *info)
return genlmsg_reply(rsp, info);
}
+static bool psp_nl_has_listeners_any_ns(struct psp_dev *psd, unsigned int group)
+{
+ struct psp_assoc_dev *entry;
+
+ if (genl_has_listeners(&psp_nl_family, dev_net(psd->main_netdev),
+ group))
+ return true;
+
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ if (genl_has_listeners(&psp_nl_family,
+ dev_net(entry->assoc_dev), group))
+ return true;
+ }
+
+ return false;
+}
+
+static void psp_nl_multicast_all_ns(struct psp_dev *psd, struct sk_buff *ntf,
+ unsigned int group)
+{
+ struct psp_assoc_dev *entry;
+ struct sk_buff *ntf_copy;
+ struct xarray sent_nets;
+ struct net *main_net;
+
+ main_net = dev_net(psd->main_netdev);
+ xa_init(&sent_nets);
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ struct net *assoc_net = dev_net(entry->assoc_dev);
+ int ret;
+
+ if (net_eq(assoc_net, main_net))
+ continue;
+
+ ret = xa_insert(&sent_nets, (unsigned long)assoc_net, assoc_net,
+ GFP_KERNEL);
+ if (ret == -EBUSY)
+ continue;
+
+ ntf_copy = skb_clone(ntf, GFP_KERNEL);
+ if (!ntf_copy)
+ break;
+
+ genlmsg_multicast_netns(&psp_nl_family, assoc_net, ntf_copy, 0,
+ group, GFP_KERNEL);
+ }
+ xa_destroy(&sent_nets);
+ /* Send to main device netns */
+ genlmsg_multicast_netns(&psp_nl_family, main_net, ntf, 0, group,
+ GFP_KERNEL);
+}
+
/* Device stuff */
static struct psp_dev *
@@ -102,11 +155,74 @@ psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb,
sockfd_put(socket);
}
+static bool psp_has_assoc_dev_in_ns(struct psp_dev *psd, struct net *net)
+{
+ struct psp_assoc_dev *entry;
+
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ if (dev_net(entry->assoc_dev) == net)
+ return true;
+ }
+
+ return false;
+}
+
+static int psp_nl_fill_assoc_dev_list(struct psp_dev *psd, struct sk_buff *rsp,
+ struct net *cur_net,
+ struct net *filter_net)
+{
+ struct psp_assoc_dev *entry;
+ struct net *dev_net_ns;
+ struct nlattr *nest;
+ int nsid;
+
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ dev_net_ns = dev_net(entry->assoc_dev);
+
+ if (filter_net && dev_net_ns != filter_net)
+ continue;
+
+ /* When filtering by namespace, all devices are in the caller's
+ * namespace so nsid is always NETNSA_NSID_NOT_ASSIGNED (-1).
+ * Otherwise, calculate the nsid relative to cur_net.
+ */
+ nsid = filter_net ? NETNSA_NSID_NOT_ASSIGNED :
+ peernet2id_alloc(cur_net, dev_net_ns,
+ GFP_KERNEL);
+
+ nest = nla_nest_start(rsp, PSP_A_DEV_ASSOC_LIST);
+ if (!nest)
+ return -1;
+
+ if (nla_put_u32(rsp, PSP_A_ASSOC_DEV_INFO_IFINDEX,
+ entry->assoc_dev->ifindex) ||
+ nla_put_s32(rsp, PSP_A_ASSOC_DEV_INFO_NSID, nsid)) {
+ nla_nest_cancel(rsp, nest);
+ return -1;
+ }
+
+ nla_nest_end(rsp, nest);
+ }
+
+ return 0;
+}
+
static int
psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp,
const struct genl_info *info)
{
+ struct net *cur_net;
void *hdr;
+ int err;
+
+ cur_net = genl_info_net(info);
+
+ /* Skip this device if we're in an associated netns but have no
+ * associated devices in cur_net
+ */
+ if (cur_net != dev_net(psd->main_netdev) &&
+ !psp_has_assoc_dev_in_ns(psd, cur_net))
+ return 0;
hdr = genlmsg_iput(rsp, info);
if (!hdr)
@@ -118,6 +234,22 @@ psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp,
nla_put_u32(rsp, PSP_A_DEV_PSP_VERSIONS_ENA, psd->config.versions))
goto err_cancel_msg;
+ if (cur_net == dev_net(psd->main_netdev)) {
+ /* Primary device - dump assoc list */
+ err = psp_nl_fill_assoc_dev_list(psd, rsp, cur_net, NULL);
+ if (err)
+ goto err_cancel_msg;
+ } else {
+ /* In netns: set by-association flag and dump filtered
+ * assoc list containing only devices in cur_net
+ */
+ if (nla_put_flag(rsp, PSP_A_DEV_BY_ASSOCIATION))
+ goto err_cancel_msg;
+ err = psp_nl_fill_assoc_dev_list(psd, rsp, cur_net, cur_net);
+ if (err)
+ goto err_cancel_msg;
+ }
+
genlmsg_end(rsp, hdr);
return 0;
@@ -131,8 +263,7 @@ void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd)
struct genl_info info;
struct sk_buff *ntf;
- if (!genl_has_listeners(&psp_nl_family, dev_net(psd->main_netdev),
- PSP_NLGRP_MGMT))
+ if (!psp_nl_has_listeners_any_ns(psd, PSP_NLGRP_MGMT))
return;
ntf = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
@@ -140,13 +271,13 @@ void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd)
return;
genl_info_init_ntf(&info, &psp_nl_family, cmd);
+ genl_info_net_set(&info, dev_net(psd->main_netdev));
if (psp_nl_dev_fill(psd, ntf, &info)) {
nlmsg_free(ntf);
return;
}
- genlmsg_multicast_netns(&psp_nl_family, dev_net(psd->main_netdev), ntf,
- 0, PSP_NLGRP_MGMT, GFP_KERNEL);
+ psp_nl_multicast_all_ns(psd, ntf, PSP_NLGRP_MGMT);
}
int psp_nl_dev_get_doit(struct sk_buff *req, struct genl_info *info)
@@ -243,8 +374,8 @@ int psp_nl_dev_set_doit(struct sk_buff *skb, struct genl_info *info)
int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info)
{
struct psp_dev *psd = info->user_ptr[0];
- struct genl_info ntf_info;
struct sk_buff *ntf, *rsp;
+ struct genl_info ntf_info;
u8 prev_gen;
int err;
@@ -280,8 +411,9 @@ int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info)
psd->stats.rotations++;
nlmsg_end(ntf, (struct nlmsghdr *)ntf->data);
- genlmsg_multicast_netns(&psp_nl_family, dev_net(psd->main_netdev), ntf,
- 0, PSP_NLGRP_USE, GFP_KERNEL);
+
+ psp_nl_multicast_all_ns(psd, ntf, PSP_NLGRP_USE);
+
return psp_nl_reply_send(rsp, info);
err_free_ntf:
@@ -291,6 +423,129 @@ int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info)
return err;
}
+int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct psp_dev *psd = info->user_ptr[0];
+ struct psp_assoc_dev *psp_assoc_dev;
+ struct net_device *assoc_dev;
+ u32 assoc_ifindex;
+ struct sk_buff *rsp;
+ struct net *net;
+ int nsid;
+
+ if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_IFINDEX) ||
+ GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_NSID))
+ return -EINVAL;
+
+ nsid = nla_get_s32(info->attrs[PSP_A_DEV_NSID]);
+ net = get_net_ns_by_id(genl_info_net(info), nsid);
+ if (!net) {
+ NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_NSID]);
+ return -EINVAL;
+ }
+
+ psp_assoc_dev = kzalloc(sizeof(*psp_assoc_dev), GFP_KERNEL);
+ if (!psp_assoc_dev) {
+ put_net(net);
+ return -ENOMEM;
+ }
+
+ assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]);
+ assoc_dev = netdev_get_by_index(net, assoc_ifindex,
+ &psp_assoc_dev->dev_tracker,
+ GFP_KERNEL);
+ if (!assoc_dev) {
+ put_net(net);
+ kfree(psp_assoc_dev);
+ NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]);
+ return -ENODEV;
+ }
+
+ /* Check if device is already associated with a PSP device */
+ if (rcu_access_pointer(assoc_dev->psp_dev)) {
+ NL_SET_ERR_MSG(info->extack,
+ "Device already associated with a PSP device");
+ netdev_put(assoc_dev, &psp_assoc_dev->dev_tracker);
+ put_net(net);
+ kfree(psp_assoc_dev);
+ return -EBUSY;
+ }
+
+ psp_assoc_dev->assoc_dev = assoc_dev;
+ rsp = psp_nl_reply_new(info);
+ if (!rsp) {
+ netdev_put(assoc_dev, &psp_assoc_dev->dev_tracker);
+ put_net(net);
+ kfree(psp_assoc_dev);
+ return -ENOMEM;
+ }
+
+ rcu_assign_pointer(assoc_dev->psp_dev, psd);
+ list_add_tail(&psp_assoc_dev->dev_list, &psd->assoc_dev_list);
+
+ put_net(net);
+
+ psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
+
+ return psp_nl_reply_send(rsp, info);
+}
+
+int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct psp_assoc_dev *entry, *found = NULL;
+ struct psp_dev *psd = info->user_ptr[0];
+ u32 assoc_ifindex;
+ struct sk_buff *rsp;
+ struct net *net;
+ int nsid;
+
+ if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_IFINDEX) ||
+ GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_NSID))
+ return -EINVAL;
+
+ nsid = nla_get_s32(info->attrs[PSP_A_DEV_NSID]);
+ net = get_net_ns_by_id(genl_info_net(info), nsid);
+ if (!net) {
+ NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_NSID]);
+ return -EINVAL;
+ }
+
+ assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]);
+
+ /* Search the association list by ifindex and netns */
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ if (entry->assoc_dev->ifindex == assoc_ifindex &&
+ dev_net(entry->assoc_dev) == net) {
+ found = entry;
+ break;
+ }
+ }
+
+ if (!found) {
+ put_net(net);
+ NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]);
+ return -ENODEV;
+ }
+
+ rsp = psp_nl_reply_new(info);
+ if (!rsp) {
+ put_net(net);
+ return -ENOMEM;
+ }
+
+ /* Remove from the association list */
+ list_del(&found->dev_list);
+ rcu_assign_pointer(found->assoc_dev->psp_dev, NULL);
+ netdev_put(found->assoc_dev, &found->dev_tracker);
+ kfree(found);
+
+ put_net(net);
+
+ psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
+
+ return psp_nl_reply_send(rsp, info);
+}
+
/* Key etc. */
int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 net-next 7/9] psp: add a new netdev event for dev unregister
2026-03-04 0:00 [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
` (4 preceding siblings ...)
2026-03-04 0:00 ` [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc Wei Wang
@ 2026-03-04 0:00 ` Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 8/9] selftests/net: Add bpf skb forwarding program Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 9/9] selftest/net: psp: Add test for dev-assoc/disassoc Wei Wang
7 siblings, 0 replies; 21+ messages in thread
From: Wei Wang @ 2026-03-04 0:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang
Add a new netdev event for dev unregister and handle the removal of this
dev from psp->assoc_dev_list.
Signed-off-by: Wei Wang <weibunny@fb.com>
---
net/psp/psp_main.c | 64 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 63 insertions(+), 1 deletion(-)
diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c
index a7635cae0d83..83b2b3abbe6f 100644
--- a/net/psp/psp_main.c
+++ b/net/psp/psp_main.c
@@ -371,10 +371,72 @@ int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv)
}
EXPORT_SYMBOL(psp_dev_rcv);
+static void psp_dev_disassoc_one(struct psp_dev *psd, struct net_device *dev)
+{
+ struct psp_assoc_dev *entry, *tmp;
+
+ list_for_each_entry_safe(entry, tmp, &psd->assoc_dev_list, dev_list) {
+ if (entry->assoc_dev == dev) {
+ list_del(&entry->dev_list);
+ rcu_assign_pointer(entry->assoc_dev->psp_dev, NULL);
+ netdev_put(entry->assoc_dev, &entry->dev_tracker);
+ kfree(entry);
+ return;
+ }
+ }
+}
+
+static int psp_netdev_event(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct psp_dev *psd;
+
+ if (event != NETDEV_UNREGISTER)
+ return NOTIFY_DONE;
+
+ rcu_read_lock();
+ psd = rcu_dereference(dev->psp_dev);
+ if (psd && psp_dev_tryget(psd)) {
+ rcu_read_unlock();
+ mutex_lock(&psd->lock);
+ psp_dev_disassoc_one(psd, dev);
+ mutex_unlock(&psd->lock);
+ psp_dev_put(psd);
+ } else {
+ rcu_read_unlock();
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block psp_netdev_notifier = {
+ .notifier_call = psp_netdev_event,
+};
+
static int __init psp_init(void)
{
+ int err;
+
mutex_init(&psp_devs_lock);
+ err = register_netdevice_notifier(&psp_netdev_notifier);
+ if (err)
+ return err;
+
+ err = genl_register_family(&psp_nl_family);
+ if (err) {
+ unregister_netdevice_notifier(&psp_netdev_notifier);
+ return err;
+ }
+
+ return 0;
+}
- return genl_register_family(&psp_nl_family);
+static void __exit psp_exit(void)
+{
+ genl_unregister_family(&psp_nl_family);
+ unregister_netdevice_notifier(&psp_netdev_notifier);
}
+
subsys_initcall(psp_init);
+module_exit(psp_exit);
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 net-next 8/9] selftests/net: Add bpf skb forwarding program
2026-03-04 0:00 [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
` (5 preceding siblings ...)
2026-03-04 0:00 ` [PATCH v2 net-next 7/9] psp: add a new netdev event for dev unregister Wei Wang
@ 2026-03-04 0:00 ` Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 9/9] selftest/net: psp: Add test for dev-assoc/disassoc Wei Wang
7 siblings, 0 replies; 21+ messages in thread
From: Wei Wang @ 2026-03-04 0:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang, Bobby Eshleman
Add nk_redirect.bpf.c, a BPF program that forwards skbs matching some IPv6
prefix received on eth0 ifindex to a specified dev ifindex.
bpf_redirect_neigh() is used to make sure neighbor lookup is performed
and proper MAC addr is being used.
Signed-off-by: Wei Wang <weibunny@fb.com>
Reviewed-by: Bobby Eshleman <bobbyeshleman@meta.com>
Tested-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
.../drivers/net/hw/nk_redirect.bpf.c | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c
diff --git a/tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c b/tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c
new file mode 100644
index 000000000000..7ac9ffd50f15
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BPF program for redirecting traffic using bpf_redirect_neigh().
+ * Unlike bpf_redirect() which preserves L2 headers, bpf_redirect_neigh()
+ * performs neighbor lookup and fills in the correct L2 addresses for the
+ * target interface. This is necessary when redirecting across different
+ * device types (e.g., from netdevsim to netkit).
+ */
+#include <linux/bpf.h>
+#include <linux/pkt_cls.h>
+#include <linux/if_ether.h>
+#include <linux/ipv6.h>
+#include <linux/in6.h>
+#include <bpf/bpf_endian.h>
+#include <bpf/bpf_helpers.h>
+
+#define TC_ACT_OK 0
+#define ETH_P_IPV6 0x86DD
+
+#define ctx_ptr(field) ((void *)(long)(field))
+
+#define v6_p64_equal(a, b) (a.s6_addr32[0] == b.s6_addr32[0] && \
+ a.s6_addr32[1] == b.s6_addr32[1])
+
+volatile __u32 redirect_ifindex;
+volatile __u8 ipv6_prefix[16];
+
+SEC("tc/ingress")
+int tc_redirect(struct __sk_buff *skb)
+{
+ void *data_end = ctx_ptr(skb->data_end);
+ void *data = ctx_ptr(skb->data);
+ struct in6_addr *match_prefix;
+ struct ipv6hdr *ip6h;
+ struct ethhdr *eth;
+
+ match_prefix = (struct in6_addr *)ipv6_prefix;
+
+ if (skb->protocol != bpf_htons(ETH_P_IPV6))
+ return TC_ACT_OK;
+
+ eth = data;
+ if ((void *)(eth + 1) > data_end)
+ return TC_ACT_OK;
+
+ ip6h = data + sizeof(struct ethhdr);
+ if ((void *)(ip6h + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (!v6_p64_equal(ip6h->daddr, (*match_prefix)))
+ return TC_ACT_OK;
+
+ /*
+ * Use bpf_redirect_neigh() to perform neighbor lookup and fill in
+ * correct L2 addresses for the target interface.
+ */
+ return bpf_redirect_neigh(redirect_ifindex, NULL, 0, 0);
+}
+
+char __license[] SEC("license") = "GPL";
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 net-next 9/9] selftest/net: psp: Add test for dev-assoc/disassoc
2026-03-04 0:00 [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
` (6 preceding siblings ...)
2026-03-04 0:00 ` [PATCH v2 net-next 8/9] selftests/net: Add bpf skb forwarding program Wei Wang
@ 2026-03-04 0:00 ` Wei Wang
2026-03-06 21:53 ` Jakub Kicinski
7 siblings, 1 reply; 21+ messages in thread
From: Wei Wang @ 2026-03-04 0:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang
Add a new param to NetDrvContEnv to add an additional bpf redirect
program on nk_host to redirect traffic to the psp_dev_local.
The topology looks like this:
Host NS: psp_dev_local <---> nk_host
| |
| | (netkit pair)
| |
Remote NS: psp_dev_peer Guest NS: nk_guest
(responder) (PSP tests)
Add following tests for dev-assoc/dev-disassoc functionality:
1. Test the output of `./tools/net/ynl/pyynl/cli.py --spec
Documentation/netlink/specs/psp.yaml --dump dev-get` in both default and
the guest netns.
2. Test the case where we associate netkit with psp_dev_local, and
send PSP traffic from nk_guest to psp_dev_peer in 2 different netns.
3. Test to make sure the key rotation notification is sent to the netns
for associated dev as well
4. Test to make sure the dev change notification is sent to the netns
for associated dev as well
5. Test the deletion of nk_guest in client netns, and proper cleanup in
the assoc-list for psp dev.
Signed-off-by: Wei Wang <weibunny@fb.com>
---
.../selftests/drivers/net/lib/py/__init__.py | 4 +-
.../selftests/drivers/net/lib/py/env.py | 54 +-
tools/testing/selftests/drivers/net/psp.py | 525 +++++++++++++++++-
3 files changed, 574 insertions(+), 9 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
index 374d4f08dd05..552f36db95ce 100644
--- a/tools/testing/selftests/drivers/net/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
@@ -48,8 +48,8 @@ try:
from .load import GenerateTraffic, Iperf3Runner
from .remote import Remote
- __all__ += ["NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic",
- "Remote", "Iperf3Runner"]
+ __all__ += ["NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv",
+ "GenerateTraffic", "Remote", "Iperf3Runner"]
except ModuleNotFoundError as e:
print("Failed importing `net` library from kernel sources")
print(str(e))
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index 36d98e1df15b..299fb4b0b8dd 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -2,6 +2,7 @@
import ipaddress
import os
+import re
import time
import json
from pathlib import Path
@@ -327,7 +328,7 @@ class NetDrvContEnv(NetDrvEpEnv):
+---------------+
"""
- def __init__(self, src_path, rxqueues=1, **kwargs):
+ def __init__(self, src_path, rxqueues=1, install_tx_redirect_bpf=False, **kwargs):
self.netns = None
self._nk_host_ifname = None
self._nk_guest_ifname = None
@@ -338,6 +339,8 @@ class NetDrvContEnv(NetDrvEpEnv):
self._init_ns_attached = False
self._old_fwd = None
self._old_accept_ra = None
+ self._nk_host_tc_attached = False
+ self._nk_host_bpf_prog_pref = None
super().__init__(src_path, **kwargs)
@@ -391,7 +394,13 @@ class NetDrvContEnv(NetDrvEpEnv):
self._setup_ns()
self._attach_bpf()
+ if install_tx_redirect_bpf:
+ self._attach_tx_redirect_bpf()
+
def __del__(self):
+ if self._nk_host_tc_attached:
+ cmd(f"tc filter del dev {self._nk_host_ifname} ingress pref {self._nk_host_bpf_prog_pref}", fail=False)
+ self._nk_host_tc_attached = False
if self._tc_attached:
cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}")
self._tc_attached = False
@@ -497,3 +506,46 @@ class NetDrvContEnv(NetDrvEpEnv):
value = ipv6_bytes + ifindex_bytes
value_hex = ' '.join(f'{b:02x}' for b in value)
bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
+
+ def _attach_tx_redirect_bpf(self):
+ """
+ Attach BPF program on nk_host ingress to redirect TX traffic.
+
+ Packets from nk_guest destined for the nsim network arrive at nk_host
+ via the netkit pair. This BPF program redirects them to the physical
+ interface so they can reach the remote peer.
+ """
+ bpf_obj = self.test_dir / "nk_redirect.bpf.o"
+ if not bpf_obj.exists():
+ raise KsftSkipEx("BPF prog nk_redirect.bpf.o not found")
+
+ cmd(f"tc qdisc add dev {self._nk_host_ifname} clsact")
+
+ cmd(f"tc filter add dev {self._nk_host_ifname} ingress bpf obj {bpf_obj} sec tc/ingress direct-action")
+ self._nk_host_tc_attached = True
+
+ tc_info = cmd(f"tc filter show dev {self._nk_host_ifname} ingress").stdout
+ match = re.search(r'pref (\d+).*nk_redirect\.bpf.*id (\d+)', tc_info)
+ if not match:
+ raise Exception("Failed to get TX redirect BPF prog ID")
+ self._nk_host_bpf_prog_pref = int(match.group(1))
+ nk_host_bpf_prog_id = int(match.group(2))
+
+ prog_info = bpftool(f"prog show id {nk_host_bpf_prog_id}", json=True)
+ map_ids = prog_info.get("map_ids", [])
+
+ bss_map_id = None
+ for map_id in map_ids:
+ map_info = bpftool(f"map show id {map_id}", json=True)
+ if map_info.get("name").endswith("bss"):
+ bss_map_id = map_id
+
+ if bss_map_id is None:
+ raise Exception("Failed to find TX redirect BPF .bss map")
+
+ ipv6_addr = ipaddress.IPv6Address(self.nsim_v6_pfx)
+ ipv6_bytes = ipv6_addr.packed
+ ifindex_bytes = self.ifindex.to_bytes(4, byteorder='little')
+ value = ipv6_bytes + ifindex_bytes
+ value_hex = ' '.join(f'{b:02x}' for b in value)
+ bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py
index 864d9fce1094..7c47a55c7aea 100755
--- a/tools/testing/selftests/drivers/net/psp.py
+++ b/tools/testing/selftests/drivers/net/psp.py
@@ -5,6 +5,7 @@
import errno
import fcntl
+import os
import socket
import struct
import termios
@@ -15,8 +16,10 @@ from lib.py import ksft_run, ksft_exit, ksft_pr
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 KsftSkipEx
-from lib.py import NetDrvEpEnv, PSPFamily, NlError
-from lib.py import bkg, rand_port, wait_port_listen
+from lib.py import NetDrvContEnv, PSPFamily, NlError
+from lib.py import NetNSEnter
+from lib.py import bkg, cmd, rand_port, wait_port_listen
+from lib.py import ip
def _get_outq(s):
@@ -117,11 +120,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 (nsim_local) 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
@@ -394,6 +399,387 @@ def _data_basic_send(cfg, version, ipver):
_close_psp_conn(cfg, s)
+def _data_basic_send_netkit_psp_assoc(cfg, version, ipver):
+ """
+ Test basic data send with netkit interface associated with PSP dev.
+ """
+
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ # Test connectivity in both directions before PSP operations
+ remote_addr = cfg.remote_addr_v["6"] # nsim_peer address (2001:db8::2)
+ nk_guest_addr = cfg.nk_guest_ipv6 # nk_guest address (2001:db9::2:2)
+
+ # Check if assoc-list contains nk_guest
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
+
+ if 'assoc-list' in dev_info:
+ found = False
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] == nk_guest_ifindex and assoc['nsid'] == cfg.psp_dev_peer_nsid:
+ found = True
+ break
+ ksft_true(found, "Associated device not found in dev_get() response")
+ else:
+ raise RuntimeError("No assoc-list in dev_get() response after association")
+
+ # Enter guest namespace (netns) to run PSP test
+ with NetNSEnter(cfg.netns.name):
+ cfg.pspnl = PSPFamily()
+
+ s = _make_psp_conn(cfg, version, ipver)
+
+ rx_assoc = cfg.pspnl.rx_assoc({"version": version,
+ "dev-id": cfg.psp_dev_id,
+ "sock-fd": s.fileno()})
+ rx = rx_assoc['rx-key']
+ tx = _spi_xchg(s, rx)
+
+ cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
+ "version": version,
+ "tx-key": tx,
+ "sock-fd": s.fileno()})
+
+ data_len = _send_careful(cfg, s, 100)
+ _check_data_rx(cfg, data_len)
+ _close_psp_conn(cfg, s)
+
+ # Clean up - back in host namespace
+ cfg.pspnl = PSPFamily()
+ cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _key_rotation_notify_multi_ns_netkit(cfg, version, ipver):
+ """ Test key rotation notifications across multiple namespaces using netkit """
+ import threading
+
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ results = {'main_ns': None, 'peer_ns': None}
+
+ def listen_in_namespace(ns_name, result_key):
+ """Listen for key rotation notifications in a namespace"""
+ try:
+ if ns_name:
+ ctx = NetNSEnter(ns_name)
+ else:
+ from contextlib import nullcontext
+ ctx = nullcontext()
+
+ with ctx:
+ pspnl = PSPFamily()
+ pspnl.ntf_subscribe('use')
+
+ collected_notifications = []
+ for i in range(100):
+ pspnl.check_ntf()
+
+ try:
+ while True:
+ msg = pspnl.async_msg_queue.get_nowait()
+ collected_notifications.append(msg['msg'])
+ except:
+ pass
+
+ if collected_notifications:
+ results[result_key] = collected_notifications
+ break
+
+ time.sleep(0.1)
+ else:
+ # Timeout - no notification received
+ results[result_key] = []
+ except Exception as e:
+ results[result_key] = {'error': str(e)}
+
+ # Create listener threads
+ # Main namespace listener
+ main_ns_thread = threading.Thread(
+ target=listen_in_namespace,
+ args=(None, 'main_ns'),
+ name='main_ns_listener'
+ )
+ # Guest namespace listener (netkit peer)
+ guest_ns_thread = threading.Thread(
+ target=listen_in_namespace,
+ args=(cfg.netns.name, 'peer_ns'),
+ name='guest_ns_listener'
+ )
+
+ main_ns_thread.start()
+ guest_ns_thread.start()
+
+ time.sleep(0.5)
+
+ # Trigger key rotation on the PSP device
+ cfg.pspnl.key_rotate({"id": psp_dev_id_for_assoc})
+
+ main_ns_thread.join(timeout=15)
+ guest_ns_thread.join(timeout=15)
+
+ # Verify notifications were received in both namespaces
+ ksft_not_none(results['main_ns'], "No result from main namespace listener")
+ ksft_not_none(results['peer_ns'], "No result from guest namespace listener")
+
+ # Check for errors in listeners
+ if isinstance(results['main_ns'], dict) and 'error' in results['main_ns']:
+ raise RuntimeError(f"Main NS listener error: {results['main_ns']['error']}")
+ if isinstance(results['peer_ns'], dict) and 'error' in results['peer_ns']:
+ raise RuntimeError(f"Guest NS listener error: {results['peer_ns']['error']}")
+
+ # Verify that both namespaces received at least one notification
+ ksft_gt(len(results['main_ns']), 0, "No key rotation notification received in main namespace")
+ ksft_gt(len(results['peer_ns']), 0, "No key rotation notification received in guest namespace")
+
+ # Verify the notification contains the expected dev_id
+ main_ns_has_dev = any(
+ ntf.get('id') == psp_dev_id_for_assoc
+ for ntf in results['main_ns']
+ )
+ guest_ns_has_dev = any(
+ ntf.get('id') == psp_dev_id_for_assoc
+ for ntf in results['peer_ns']
+ )
+
+ ksft_true(main_ns_has_dev, "Key rotation notification for correct device not found in main namespace")
+ ksft_true(guest_ns_has_dev, "Key rotation notification for correct device not found in guest namespace")
+
+ # Clean up
+ cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _dev_change_notify_multi_ns_netkit(cfg, version, ipver):
+ """ Test dev_change notifications across multiple namespaces using netkit """
+ import threading
+
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ results = {'main_ns': None, 'peer_ns': None}
+
+ def listen_in_namespace(ns_name, result_key):
+ """Listen for dev_change notifications in a namespace"""
+ try:
+ if ns_name:
+ ctx = NetNSEnter(ns_name)
+ else:
+ from contextlib import nullcontext
+ ctx = nullcontext()
+
+ with ctx:
+ pspnl = PSPFamily()
+ pspnl.ntf_subscribe('mgmt')
+
+ collected_notifications = []
+ for i in range(100):
+ pspnl.check_ntf()
+
+ try:
+ while True:
+ msg = pspnl.async_msg_queue.get_nowait()
+ collected_notifications.append(msg['msg'])
+ except:
+ pass
+
+ if collected_notifications:
+ results[result_key] = collected_notifications
+ break
+
+ time.sleep(0.1)
+ else:
+ # Timeout - no notification received
+ results[result_key] = []
+ except Exception as e:
+ results[result_key] = {'error': str(e)}
+
+ # Create listener threads
+ # Main namespace listener
+ main_ns_thread = threading.Thread(
+ target=listen_in_namespace,
+ args=(None, 'main_ns'),
+ name='main_ns_listener'
+ )
+ # Guest namespace listener (netkit peer)
+ guest_ns_thread = threading.Thread(
+ target=listen_in_namespace,
+ args=(cfg.netns.name, 'peer_ns'),
+ name='guest_ns_listener'
+ )
+
+ main_ns_thread.start()
+ guest_ns_thread.start()
+
+ time.sleep(1.0) # Give threads time to subscribe
+
+ # Trigger dev_change by calling dev_set (notification is always sent)
+ cfg.pspnl.dev_set({'id': psp_dev_id_for_assoc, 'psp-versions-ena': cfg.psp_info['psp-versions-cap']})
+
+ main_ns_thread.join(timeout=15)
+ guest_ns_thread.join(timeout=15)
+
+ # Verify notifications were received in both namespaces
+ ksft_not_none(results['main_ns'], "No result from main namespace listener")
+ ksft_not_none(results['peer_ns'], "No result from guest namespace listener")
+
+ # Check for errors in listeners
+ if isinstance(results['main_ns'], dict) and 'error' in results['main_ns']:
+ raise RuntimeError(f"Main NS listener error: {results['main_ns']['error']}")
+ if isinstance(results['peer_ns'], dict) and 'error' in results['peer_ns']:
+ raise RuntimeError(f"Guest NS listener error: {results['peer_ns']['error']}")
+
+ # Verify that both namespaces received at least one notification
+ ksft_gt(len(results['main_ns']), 0, "No dev_change notification received in main namespace")
+ ksft_gt(len(results['peer_ns']), 0, "No dev_change notification received in guest namespace")
+
+ # Verify the notification contains the expected dev_id
+ main_ns_has_dev = any(
+ ntf.get('id') == psp_dev_id_for_assoc
+ for ntf in results['main_ns']
+ )
+ guest_ns_has_dev = any(
+ ntf.get('id') == psp_dev_id_for_assoc
+ for ntf in results['peer_ns']
+ )
+
+ ksft_true(main_ns_has_dev, "Dev_change notification for correct device not found in main namespace")
+ ksft_true(guest_ns_has_dev, "Dev_change notification for correct device not found in guest namespace")
+
+ # Clean up
+ cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _psp_dev_get_check_netkit_psp_assoc(cfg, version, ipver):
+ """ Check psp dev-get output with netkit interface associated with PSP dev """
+
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ # Check 1: In default netns, verify dev-get has correct ifindex and assoc-list
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
+
+ # Verify the PSP device has the correct ifindex
+ ksft_eq(dev_info['ifindex'], cfg.psp_ifindex)
+
+ # Verify assoc-list exists and contains the associated nk_guest with correct ifindex and nsid
+ ksft_true('assoc-list' in dev_info, "No assoc-list in dev_get() response after association")
+ found = False
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] == nk_guest_ifindex and assoc['nsid'] == cfg.psp_dev_peer_nsid:
+ found = True
+ break
+ ksft_true(found, "Associated device not found in assoc-list with correct ifindex and nsid")
+
+ # Check 2: In guest netns, verify dev-get has assoc-list with nk_guest device
+ with NetNSEnter(cfg.netns.name):
+ peer_pspnl = PSPFamily()
+
+ # Dump all devices in the guest namespace
+ peer_devices = peer_pspnl.dev_get({}, dump=True)
+
+ # Find the device with by-association flag
+ peer_dev = None
+ for dev in peer_devices:
+ if dev.get('by-association'):
+ peer_dev = dev
+ break
+
+ ksft_not_none(peer_dev, "No PSP device found with by-association flag in guest netns")
+
+ # Verify assoc-list contains the nk_guest device
+ ksft_true('assoc-list' in peer_dev and len(peer_dev['assoc-list']) > 0,
+ "Guest device should have assoc-list with local devices")
+
+ # Verify the assoc-list contains nk_guest ifindex with nsid=-1 (same namespace)
+ found = False
+ for assoc in peer_dev['assoc-list']:
+ if assoc['ifindex'] == nk_guest_ifindex:
+ ksft_eq(assoc['nsid'], -1,
+ "nsid should be -1 (NETNSA_NSID_NOT_ASSIGNED) for same-namespace device")
+ found = True
+ break
+ ksft_true(found, "nk_guest ifindex not found in assoc-list")
+
+ # Clean up
+ cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _psp_dev_assoc_cleanup_on_netkit_del(cfg):
+ """ Test that assoc-list is cleared when associated netkit interface is deleted """
+ import subprocess
+
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ # Verify assoc-list exists in default netns
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
+ ksft_true('assoc-list' in dev_info, "No assoc-list after association")
+ found = False
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] == nk_guest_ifindex and assoc['nsid'] == cfg.psp_dev_peer_nsid:
+ found = True
+ break
+ ksft_true(found, "Associated device not found in assoc-list")
+
+ # Delete the netkit interface in the guest namespace
+ ns_name = cfg.netns.name
+ subprocess.run(f"ip netns exec {ns_name} ip link del {cfg._nk_guest_ifname}", shell=True, check=True)
+
+ # Mark netkit as already deleted so cleanup won't try to delete it again
+ # (deleting nk_guest also removes nk_host since they're a pair)
+ cfg._nk_host_ifname = None
+ cfg._nk_guest_ifname = None
+
+ # Verify assoc-list is gone in default netns after netkit deletion
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
+ ksft_true('assoc-list' not in dev_info or len(dev_info['assoc-list']) == 0,
+ "assoc-list should be empty after netkit deletion")
+
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
def __bad_xfer_do(cfg, s, tx, version='hdr0-aes-gcm-128'):
# Make sure we accept the ACK for the SPI before we seal with the bad assoc
_check_data_outq(s, 0)
@@ -591,13 +977,107 @@ def ipver_test_builder(name, test_func, ipver):
return test_case
+def _get_nsid(ns_name):
+ """Get the nsid for a namespace."""
+ nsid = 0
+ try:
+ list_result = cmd("ip netns list-id", shell=True)
+ list_output = list_result.stdout if hasattr(list_result, 'stdout') else str(list_result)
+
+ for line in list_output.split('\n'):
+ if ns_name in line and 'nsid' in line:
+ parts = line.split()
+ if 'nsid' in parts:
+ nsid_idx = parts.index('nsid')
+ if nsid_idx + 1 < len(parts):
+ try:
+ nsid = int(parts[nsid_idx + 1])
+ break
+ except ValueError:
+ pass
+ except Exception as e:
+ raise KsftSkipEx(f"Failed to query nsid: {e}")
+ return nsid
+
+
+def _setup_psp_attributes(cfg):
+ """
+ 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:
+ # nsim case: PSP device is nsim_local (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 nsim_peer (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 _setup_psp_routes(cfg):
+ """
+ Set up routes for cross-namespace connectivity.
+
+ Traffic flows:
+ 1. remote (_netns) -> nk_guest (netns):
+ nsim_peer -> nsim_local -> BPF redirect -> nk_host -> nk_guest
+ Needs: route in _netns to nk_v6_pfx/64 via nsim_local
+
+ 2. nk_guest (netns) -> remote (_netns):
+ nk_guest -> nk_host -> nsim_local -> nsim_peer
+ Needs: route in netns to nsim_v6_pfx/64 via nk_host
+ """
+ # In _netns (remote namespace): add route to nk_guest prefix via nsim_local
+ # nsim_peer can reach nsim_local via the link, then traffic goes through BPF
+ ip(f"-6 route add {cfg.nk_v6_pfx}/64 via {cfg.nsim_v6_pfx}1 dev {cfg.psp_dev_peer_ifname}",
+ ns=cfg._netns)
+
+ # In netns (guest namespace): add route to nsim_peer prefix
+ # nk_guest default route goes to nk_host, but we need explicit route to nsim_v6_pfx/64
+ ip(f"-6 route add {cfg.nsim_v6_pfx}/64 via fe80::1 dev {cfg._nk_guest_ifname}",
+ ns=cfg.netns)
+
+
def main() -> None:
""" Ksft boiler plate main """
- with NetDrvEpEnv(__file__) as cfg:
+ # Use a different prefix for netkit guest to avoid conflict with nsim prefix
+ nk_v6_pfx = "2001:db9::"
+
+ # Set LOCAL_PREFIX_V6 to a DIFFERENT prefix than nsim to avoid BPF
+ # redirecting psp_responder traffic. The BPF only redirects traffic
+ # matching LOCAL_PREFIX_V6, so nsim traffic (2001:db8::) won't be affected.
+ if "LOCAL_PREFIX_V6" not in os.environ:
+ os.environ["LOCAL_PREFIX_V6"] = nk_v6_pfx
+
+ with NetDrvContEnv(__file__, install_tx_redirect_bpf=True) as cfg:
cfg.pspnl = PSPFamily()
+ cfg.nk_v6_pfx = nk_v6_pfx
+
+ # Set up PSP-specific attributes and routes
+ _setup_psp_attributes(cfg)
+ _setup_psp_routes(cfg)
# Set up responder and communication sock
+ # psp_responder runs in _netns (remote namespace with nsim_peer)
responder = cfg.remote.deploy("psp_responder")
cfg.comm_port = rand_port()
@@ -622,9 +1102,42 @@ def main() -> None:
ipver_test_builder("data_mss_adjust", _data_mss_adjust, ipver)
for ipver in ("4", "6")
]
+ cases += [
+ psp_ip_ver_test_builder(
+ "data_basic_send_netkit_psp_assoc",
+ _data_basic_send_netkit_psp_assoc, version, "6"
+ )
+ for version in range(0, 4)
+ ]
+ cases += [
+ psp_ip_ver_test_builder(
+ "key_rotation_notify_multi_ns_netkit",
+ _key_rotation_notify_multi_ns_netkit, version, "6"
+ )
+ for version in range(0, 4)
+ ]
+ cases += [
+ psp_ip_ver_test_builder(
+ "dev_change_notify_multi_ns_netkit",
+ _dev_change_notify_multi_ns_netkit, version, "6"
+ )
+ for version in range(0, 4)
+ ]
+ cases += [
+ psp_ip_ver_test_builder(
+ "psp_dev_get_check_netkit_psp_assoc",
+ _psp_dev_get_check_netkit_psp_assoc, version, "6"
+ )
+ for version in range(0, 4)
+ ]
+ # Run netkit deletion test only once at the end since it destroys the netkit
+ def psp_dev_assoc_cleanup_on_netkit_del_test(cfg):
+ _psp_dev_assoc_cleanup_on_netkit_del(cfg)
+ psp_dev_assoc_cleanup_on_netkit_del_test.__name__ = "psp_dev_assoc_cleanup_on_netkit_del"
+ cases.append(psp_dev_assoc_cleanup_on_netkit_del_test)
ksft_run(cases=cases, globs=globals(),
- case_pfx={"dev_", "data_", "assoc_", "removal_"},
+ case_pfx={"dev_", "data_", "assoc_", "removal_", "key_", "psp_dev_"},
args=(cfg, ))
cfg.comm_sock.send(b"exit\0")
--
2.47.3
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
2026-03-04 0:00 ` [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked Wei Wang
@ 2026-03-04 15:00 ` Daniel Zahka
2026-03-04 16:25 ` Willem de Bruijn
1 sibling, 0 replies; 21+ messages in thread
From: Daniel Zahka @ 2026-03-04 15:00 UTC (permalink / raw)
To: Wei Wang, netdev, Jakub Kicinski, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
On 3/3/26 7:00 PM, Wei Wang wrote:
> Add a place holder function called psp_device_get_locked_unpriv() which
> will be used for commands that are unprivileged and are used for
> exisiting commands like dev-dump, dev-get, rx-assoc, tx-assoc.
> Commands including dev-add/delete/change-ntf, key-rotate would keep
> using the privileged version.
It looks to me like get-stats is a command that is still using the
privileged version. Is that intentional?
> @@ -72,7 +73,20 @@ int psp_device_get_locked(const struct genl_split_ops *ops,
> return -EINVAL;
>
> info->user_ptr[0] = psp_device_get_and_lock(genl_info_net(info),
> - info->attrs[PSP_A_DEV_ID]);
> + info->attrs[PSP_A_DEV_ID],
> + false);
> + return PTR_ERR_OR_ZERO(info->user_ptr[0]);
> +}
> +
> +int psp_device_get_locked_unpriv(const struct genl_split_ops *ops,
> + struct sk_buff *skb, struct genl_info *info)
> +{
> + if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_ID))
> + return -EINVAL;
> +
> + info->user_ptr[0] = psp_device_get_and_lock(genl_info_net(info),
> + info->attrs[PSP_A_DEV_ID],
> + true);
> return PTR_ERR_OR_ZERO(info->user_ptr[0]);
> }
nit: psp_device_get_locked() and psp_device_get_locked_unpriv() are
entirely identical modulo true/false. You could probably have something
like __psp_device_get_locked() with the common code.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc
2026-03-04 0:00 ` [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc Wei Wang
@ 2026-03-04 15:20 ` Daniel Zahka
2026-03-04 16:17 ` Daniel Zahka
1 sibling, 0 replies; 21+ messages in thread
From: Daniel Zahka @ 2026-03-04 15:20 UTC (permalink / raw)
To: Wei Wang, netdev, Jakub Kicinski, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
On 3/3/26 7:00 PM, Wei Wang wrote:
> +int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> + struct psp_dev *psd = info->user_ptr[0];
> + struct psp_assoc_dev *psp_assoc_dev;
> + struct net_device *assoc_dev;
> + u32 assoc_ifindex;
> + struct sk_buff *rsp;
> + struct net *net;
> + int nsid;
> +
> + if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_IFINDEX) ||
> + GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_NSID))
> + return -EINVAL;
> +
> + nsid = nla_get_s32(info->attrs[PSP_A_DEV_NSID]);
> + net = get_net_ns_by_id(genl_info_net(info), nsid);
I think this assumes assoc_dev is not in genl_info_net(info). Perhaps
this could be made to work by adding a self referencing nsid, but I
think it might be less complicated to have a path that deals with this
case directly.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc
2026-03-04 0:00 ` [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc Wei Wang
2026-03-04 15:20 ` Daniel Zahka
@ 2026-03-04 16:17 ` Daniel Zahka
2026-03-04 17:28 ` Wei Wang
1 sibling, 1 reply; 21+ messages in thread
From: Daniel Zahka @ 2026-03-04 16:17 UTC (permalink / raw)
To: Wei Wang, netdev, Jakub Kicinski, Willem de Bruijn, David Wei,
Andrew Lunn, David S. Miller, Eric Dumazet
On 3/3/26 7:00 PM, Wei Wang wrote:
> +int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> + struct psp_assoc_dev *entry, *found = NULL;
> + struct psp_dev *psd = info->user_ptr[0];
> + u32 assoc_ifindex;
> + struct sk_buff *rsp;
> + struct net *net;
> + int nsid;
> +
> + if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_IFINDEX) ||
> + GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_NSID))
> + return -EINVAL;
> +
> + nsid = nla_get_s32(info->attrs[PSP_A_DEV_NSID]);
> + net = get_net_ns_by_id(genl_info_net(info), nsid);
> + if (!net) {
> + NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_NSID]);
> + return -EINVAL;
> + }
> +
> + assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]);
> +
> + /* Search the association list by ifindex and netns */
> + list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
> + if (entry->assoc_dev->ifindex == assoc_ifindex &&
> + dev_net(entry->assoc_dev) == net) {
> + found = entry;
> + break;
> + }
> + }
> +
> + if (!found) {
> + put_net(net);
> + NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]);
> + return -ENODEV;
> + }
> +
> + rsp = psp_nl_reply_new(info);
> + if (!rsp) {
> + put_net(net);
> + return -ENOMEM;
> + }
> +
> + /* Remove from the association list */
> + list_del(&found->dev_list);
> + rcu_assign_pointer(found->assoc_dev->psp_dev, NULL);
> + netdev_put(found->assoc_dev, &found->dev_tracker);
> + kfree(found);
in the case where found was the last associated device in its netns in
psd->assoc_dev_list, listeners in that netns will not receive any
notification that a psp device has gone away.
> +
> + put_net(net);
> +
> + psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
2026-03-04 0:00 ` [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked Wei Wang
2026-03-04 15:00 ` Daniel Zahka
@ 2026-03-04 16:25 ` Willem de Bruijn
2026-03-04 17:42 ` Wei Wang
1 sibling, 1 reply; 21+ messages in thread
From: Willem de Bruijn @ 2026-03-04 16:25 UTC (permalink / raw)
To: Wei Wang, netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn,
David Wei, Andrew Lunn, David S. Miller, Eric Dumazet
Cc: Wei Wang
Wei Wang wrote:
> Add a place holder function called psp_device_get_locked_unpriv() which
> will be used for commands that are unprivileged and are used for
> exisiting commands like dev-dump, dev-get, rx-assoc, tx-assoc.
> Commands including dev-add/delete/change-ntf, key-rotate would keep
> using the privileged version.
>
> Following commit will be implementing the unprivileged version check.
Can you define what unprivileged means concretely in this context.
From the follow-on patch, it seems to only affect
psp_dev_check_access, where it additionally allows some operations
from other netns if that netns has an associated device.
Can you give a concise reason for which operations to allow from
another netns and which to deny. Also as forward looking heuristic for
when new operations may be added.
Btw minor typo in first sentence of next commit: associcate.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc
2026-03-04 16:17 ` Daniel Zahka
@ 2026-03-04 17:28 ` Wei Wang
0 siblings, 0 replies; 21+ messages in thread
From: Wei Wang @ 2026-03-04 17:28 UTC (permalink / raw)
To: daniel.zahka@gmail.com, netdev@vger.kernel.org, kuba@kernel.org,
Willem de Bruijn, David Wei, Andrew Lunn, David S. Miller,
Eric Dumazet
________________________________________
From: Daniel Zahka <daniel.zahka@gmail.com>
Sent: Wednesday, March 4, 2026 8:17 AM
To: Wei Wang; netdev@vger.kernel.org; kuba@kernel.org; Willem de Bruijn; David Wei; Andrew Lunn; David S. Miller; Eric Dumazet
Subject: Re: [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc
On 3/3/26 7:00 PM, Wei Wang wrote:
>> +int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info)
>> +{
>> + struct psp_assoc_dev *entry, *found = NULL;
>> + struct psp_dev *psd = info->user_ptr[0];
>> + u32 assoc_ifindex;
>> + struct sk_buff *rsp;
>> + struct net *net;
>> + int nsid;
>> +
>> + if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_IFINDEX) ||
>> + GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_NSID))
>> + return -EINVAL;
>> +
>> + nsid = nla_get_s32(info->attrs[PSP_A_DEV_NSID]);
>> + net = get_net_ns_by_id(genl_info_net(info), nsid);
>> + if (!net) {
>> + NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_NSID]);
>> + return -EINVAL;
>> + }
>> +
>> + assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]);
>> +
>> + /* Search the association list by ifindex and netns */
>> + list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
>> + if (entry->assoc_dev->ifindex == assoc_ifindex &&
>> + dev_net(entry->assoc_dev) == net) {
>> + found = entry;
>> + break;
>> + }
>> + }
>> +
>> + put_net(net);
>> + NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]);
>> + return -ENODEV;
>> + }
>> +
>> + rsp = psp_nl_reply_new(info);
>> + if (!rsp) {
>> + put_net(net);
>> + return -ENOMEM;
>> + }
>> +
>> + /* Remove from the association list */
>> + list_del(&found->dev_list);
>> + rcu_assign_pointer(found->assoc_dev->psp_dev, NULL);
>> + netdev_put(found->assoc_dev, &found->dev_tracker);
>> + kfree(found);
>
> in the case where found was the last associated device in its netns in
> psd->assoc_dev_list, listeners in that netns will not receive any
> notification that a psp device has gone away.
You are right! Thanks for catching this. Will fix in the next version.
>> +
>> + put_net(net);
>> +
>> + psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
2026-03-04 16:25 ` Willem de Bruijn
@ 2026-03-04 17:42 ` Wei Wang
2026-03-04 18:01 ` Daniel Zahka
0 siblings, 1 reply; 21+ messages in thread
From: Wei Wang @ 2026-03-04 17:42 UTC (permalink / raw)
To: Willem de Bruijn, netdev@vger.kernel.org, kuba@kernel.org,
daniel.zahka@gmail.com, David Wei, Andrew Lunn, David S. Miller,
Eric Dumazet
________________________________________
From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
Sent: Wednesday, March 4, 2026 8:25 AM
To: Wei Wang; netdev@vger.kernel.org; kuba@kernel.org; daniel.zahka@gmail.com; Willem de Bruijn; David Wei; Andrew Lunn; David S. Miller; Eric Dumazet
Cc: Wei Wang
Subject: Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
>
> Wei Wang wrote:
> > Add a place holder function called psp_device_get_locked_unpriv() which
> > will be used for commands that are unprivileged and are used for
> > exisiting commands like dev-dump, dev-get, rx-assoc, tx-assoc.
> > Commands including dev-add/delete/change-ntf, key-rotate would keep
> > using the privileged version.
> >
> > Following commit will be implementing the unprivileged version check.
>
> Can you define what unprivileged means concretely in this context.
>
> From the follow-on patch, it seems to only affect
> psp_dev_check_access, where it additionally allows some operations
> from other netns if that netns has an associated device.
>
> Can you give a concise reason for which operations to allow from
> another netns and which to deny. Also as forward looking heuristic for
> when new operations may be added.
>
> Btw minor typo in first sentence of next commit: associcate.
Thanks for the comment!
Yes. "unprivileged" means access from the main netns as well as any netns with an associated device and is used for commands like dev-dump, dev-get, rx-assoc, tx-assoc, which are user-controlled functionalities of the psd.
While "privileged" means access only from the main netns and is used for dev-add/delete/change-ntf, key-rotate. This is more like the admin-types of operations of the psd.
I will update the commit msg in the next version to make it more clear.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
2026-03-04 17:42 ` Wei Wang
@ 2026-03-04 18:01 ` Daniel Zahka
2026-03-04 18:03 ` Daniel Zahka
0 siblings, 1 reply; 21+ messages in thread
From: Daniel Zahka @ 2026-03-04 18:01 UTC (permalink / raw)
To: Wei Wang, Willem de Bruijn, netdev@vger.kernel.org,
kuba@kernel.org, David Wei, Andrew Lunn, David S. Miller,
Eric Dumazet
On 3/4/26 12:42 PM, Wei Wang wrote:
> _______________________________________
> From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
> Sent: Wednesday, March 4, 2026 8:25 AM
> To: Wei Wang; netdev@vger.kernel.org; kuba@kernel.org; daniel.zahka@gmail.com; Willem de Bruijn; David Wei; Andrew Lunn; David S. Miller; Eric Dumazet
> Cc: Wei Wang
> Subject: Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
>
>> Can you give a concise reason for which operations to allow from
>> another netns and which to deny. Also as forward looking heuristic for
>> when new operations may be added.
>>
>> Btw minor typo in first sentence of next commit: associcate.
> Thanks for the comment!
> Yes. "unprivileged" means access from the main netns as well as any netns with an associated device and is used for commands like dev-dump, dev-get, rx-assoc, tx-assoc, which are user-controlled functionalities of the psd.
> While "privileged" means access only from the main netns and is used for dev-add/delete/change-ntf, key-rotate. This is more like the admin-types of operations of the psd.
> I will update the commit msg in the next version to make it more clear.
I think Willem's question is more: what criteria did you use to decide
if each operation is privileged or not? I think one reasonable answer
would be: operations that have side effects on the psp dev's state, e.g.
key rotations or changing the enabled psp versions.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
2026-03-04 18:01 ` Daniel Zahka
@ 2026-03-04 18:03 ` Daniel Zahka
2026-03-04 22:31 ` Wei Wang
0 siblings, 1 reply; 21+ messages in thread
From: Daniel Zahka @ 2026-03-04 18:03 UTC (permalink / raw)
To: Wei Wang, Willem de Bruijn, netdev@vger.kernel.org,
kuba@kernel.org, David Wei, Andrew Lunn, David S. Miller,
Eric Dumazet
On 3/4/26 1:01 PM, Daniel Zahka wrote:
>
>
> On 3/4/26 12:42 PM, Wei Wang wrote:
>> _______________________________________
>> From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
>> Sent: Wednesday, March 4, 2026 8:25 AM
>> To: Wei Wang; netdev@vger.kernel.org; kuba@kernel.org;
>> daniel.zahka@gmail.com; Willem de Bruijn; David Wei; Andrew Lunn;
>> David S. Miller; Eric Dumazet
>> Cc: Wei Wang
>> Subject: Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of
>> psp_device_get_locked
>>
>>> Can you give a concise reason for which operations to allow from
>>> another netns and which to deny. Also as forward looking heuristic for
>>> when new operations may be added.
>>>
>>> Btw minor typo in first sentence of next commit: associcate.
>> Thanks for the comment!
>> Yes. "unprivileged" means access from the main netns as well as any
>> netns with an associated device and is used for commands like
>> dev-dump, dev-get, rx-assoc, tx-assoc, which are user-controlled
>> functionalities of the psd.
>> While "privileged" means access only from the main netns and is used
>> for dev-add/delete/change-ntf, key-rotate. This is more like the
>> admin-types of operations of the psd.
>> I will update the commit msg in the next version to make it more clear.
>
> I think Willem's question is more: what criteria did you use to decide
> if each operation is privileged or not? I think one reasonable answer
> would be: operations that have side effects on the psp dev's state,
> e.g. key rotations or changing the enabled psp versions.
well, now that I sent that I realize that criteria is so broad to
include things like rx-assoc and tx-assoc, which obviously need to be
unprivileged :(
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
2026-03-04 18:03 ` Daniel Zahka
@ 2026-03-04 22:31 ` Wei Wang
2026-03-04 23:41 ` Willem de Bruijn
0 siblings, 1 reply; 21+ messages in thread
From: Wei Wang @ 2026-03-04 22:31 UTC (permalink / raw)
To: daniel.zahka@gmail.com, Willem de Bruijn, netdev@vger.kernel.org,
kuba@kernel.org, David Wei, Andrew Lunn, David S. Miller,
Eric Dumazet
________________________________________
From: Daniel Zahka <daniel.zahka@gmail.com>
Sent: Wednesday, March 4, 2026 10:03 AM
To: Wei Wang; Willem de Bruijn; netdev@vger.kernel.org; kuba@kernel.org; David Wei; Andrew Lunn; David S. Miller; Eric Dumazet
Subject: Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
> On 3/4/26 1:01 PM, Daniel Zahka wrote:
> >
> >
> > On 3/4/26 12:42 PM, Wei Wang wrote:
> >> _______________________________________
> >> From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
> >> Sent: Wednesday, March 4, 2026 8:25 AM
> >> To: Wei Wang; netdev@vger.kernel.org; kuba@kernel.org;
> >> daniel.zahka@gmail.com; Willem de Bruijn; David Wei; Andrew Lunn;
> >> David S. Miller; Eric Dumazet
> >> Cc: Wei Wang
> >> Subject: Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of
> >> psp_device_get_locked
> >>
> >>> Can you give a concise reason for which operations to allow from
> >>> another netns and which to deny. Also as forward looking heuristic for
> >>> when new operations may be added.
> >>>
> >>> Btw minor typo in first sentence of next commit: associcate.
> >> Thanks for the comment!
> >> Yes. "unprivileged" means access from the main netns as well as any
> >> netns with an associated device and is used for commands like
> >> dev-dump, dev-get, rx-assoc, tx-assoc, which are user-controlled
> >> functionalities of the psd.
> >> While "privileged" means access only from the main netns and is used
> >> for dev-add/delete/change-ntf, key-rotate. This is more like the
> >> admin-types of operations of the psd.
> >> I will update the commit msg in the next version to make it more clear.
> >
> > I think Willem's question is more: what criteria did you use to decide
> > if each operation is privileged or not? I think one reasonable answer
> > would be: operations that have side effects on the psp dev's state,
> > e.g. key rotations or changing the enabled psp versions.
Agree. And the current set of privileged operations should only include: dev-set, key-rotation.
All others should be unprivileged I think...
(The get-stats command which I should make sure they are called with unpriv = true. I will change that in the next version.)
>
> well, now that I sent that I realize that criteria is so broad to
> include things like rx-assoc and tx-assoc, which obviously need to be
> unprivileged :(
Yes. tx-assoc and rx-assoc are currently implemented as unprivileged. They call psp_assoc_device_get_locked() which currently calls psp_dev_check_access() with unpriv = true.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
2026-03-04 22:31 ` Wei Wang
@ 2026-03-04 23:41 ` Willem de Bruijn
2026-03-06 21:34 ` Jakub Kicinski
0 siblings, 1 reply; 21+ messages in thread
From: Willem de Bruijn @ 2026-03-04 23:41 UTC (permalink / raw)
To: Wei Wang, daniel.zahka@gmail.com, Willem de Bruijn,
netdev@vger.kernel.org, kuba@kernel.org, David Wei, Andrew Lunn,
David S. Miller, Eric Dumazet
> > >> From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
> > >> Sent: Wednesday, March 4, 2026 8:25 AM
> > >> To: Wei Wang; netdev@vger.kernel.org; kuba@kernel.org;
> > >> daniel.zahka@gmail.com; Willem de Bruijn; David Wei; Andrew Lunn;
> > >> David S. Miller; Eric Dumazet
> > >> Cc: Wei Wang
> > >> Subject: Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of
> > >> psp_device_get_locked
> > >>
> > >>> Can you give a concise reason for which operations to allow from
> > >>> another netns and which to deny. Also as forward looking heuristic for
> > >>> when new operations may be added.
> > >>>
> > >>> Btw minor typo in first sentence of next commit: associcate.
> > >> Thanks for the comment!
> > >> Yes. "unprivileged" means access from the main netns as well as any
> > >> netns with an associated device and is used for commands like
> > >> dev-dump, dev-get, rx-assoc, tx-assoc, which are user-controlled
> > >> functionalities of the psd.
> > >> While "privileged" means access only from the main netns and is used
> > >> for dev-add/delete/change-ntf, key-rotate. This is more like the
> > >> admin-types of operations of the psd.
> > >> I will update the commit msg in the next version to make it more clear.
> > >
> > > I think Willem's question is more: what criteria did you use to decide
> > > if each operation is privileged or not? I think one reasonable answer
> > > would be: operations that have side effects on the psp dev's state,
> > > e.g. key rotations or changing the enabled psp versions.
>
> Agree. And the current set of privileged operations should only include: dev-set, key-rotation.
> All others should be unprivileged I think...
> (The get-stats command which I should make sure they are called with unpriv = true. I will change that in the next version.)
>
> >
> > well, now that I sent that I realize that criteria is so broad to
> > include things like rx-assoc and tx-assoc, which obviously need to be
> > unprivileged :(
>
> Yes. tx-assoc and rx-assoc are currently implemented as unprivileged. They call psp_assoc_device_get_locked() which currently calls psp_dev_check_access() with unpriv = true.
Consider a more descriptive monitor than unprivileged too. As is, it's
not clear what privilege or capability is implied.
Perhaps just assoc_allowed?
(also please check your email: plain-text only)
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked
2026-03-04 23:41 ` Willem de Bruijn
@ 2026-03-06 21:34 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-03-06 21:34 UTC (permalink / raw)
To: Willem de Bruijn
Cc: Wei Wang, daniel.zahka@gmail.com, netdev@vger.kernel.org,
David Wei, Andrew Lunn, David S. Miller, Eric Dumazet
On Wed, 04 Mar 2026 18:41:09 -0500 Willem de Bruijn wrote:
> [...]
> [...]
> [...]
> [...]
> >
> > Agree. And the current set of privileged operations should only include: dev-set, key-rotation.
> > All others should be unprivileged I think...
> > (The get-stats command which I should make sure they are called with unpriv = true. I will change that in the next version.)
> >
> > >
> > > well, now that I sent that I realize that criteria is so broad to
> > > include things like rx-assoc and tx-assoc, which obviously need to be
> > > unprivileged :(
> >
> > Yes. tx-assoc and rx-assoc are currently implemented as unprivileged. They call psp_assoc_device_get_locked() which currently calls psp_dev_check_access() with unpriv = true.
>
> Consider a more descriptive monitor than unprivileged too. As is, it's
> not clear what privilege or capability is implied.
>
> Perhaps just assoc_allowed?
Sorry for chiming in late. Maybe things would be more intuitive if
we flipped the logic and called the arg/functions "admin" instead
"unpriv" ?
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 net-next 9/9] selftest/net: psp: Add test for dev-assoc/disassoc
2026-03-04 0:00 ` [PATCH v2 net-next 9/9] selftest/net: psp: Add test for dev-assoc/disassoc Wei Wang
@ 2026-03-06 21:53 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-03-06 21:53 UTC (permalink / raw)
To: Wei Wang
Cc: netdev, Daniel Zahka, Willem de Bruijn, David Wei, Andrew Lunn,
David S. Miller, Eric Dumazet
On Tue, 3 Mar 2026 16:00:49 -0800 Wei Wang wrote:
> +def _data_basic_send_netkit_psp_assoc(cfg, version, ipver):
> + """
> + Test basic data send with netkit interface associated with PSP dev.
> + """
> +
> + _init_psp_dev(cfg, True)
> + psp_dev_id_for_assoc = cfg.psp_dev_id
> +
> + # Associate PSP device with nk_guest interface (in guest namespace)
> + nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
dev info is one of the few things the env class should have loaded for
us. Please add it if it's missing, we have it for other envs (env.dev,
env.remote_dev, nk should presumably be env.nk_dev ? or env.cont_dev ?)
> + nk_guest_ifindex = nk_guest_dev['ifindex']
> +
> + cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
Please wrap lines at 80 chars.
> + # Check if assoc-list contains nk_guest
> + dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
> +
> + if 'assoc-list' in dev_info:
> + found = False
> + for assoc in dev_info['assoc-list']:
> + if assoc['ifindex'] == nk_guest_ifindex and assoc['nsid'] == cfg.psp_dev_peer_nsid:
> + found = True
> + break
Should it not be the _only_ assoc? Maybe let's start with that
assumption and complicate the tests later if needed?
The other tests are rotating the keys and generally assuming full
control over PSP so I don't think we should cater to the case of
"something else is using PSP on the base system" here either.
Let's also check how the attributes look from inside the netns
and how they look if the netkit and netdevsim are in the same netns?
(unless you already have that. this patch is a bit enormous :()
> + ksft_true(found, "Associated device not found in dev_get() response")
> + else:
> + raise RuntimeError("No assoc-list in dev_get() response after association")
KsftFailEx() ?
> + # Enter guest namespace (netns) to run PSP test
> + with NetNSEnter(cfg.netns.name):
> + cfg.pspnl = PSPFamily()
> +
> + s = _make_psp_conn(cfg, version, ipver)
> +
> + rx_assoc = cfg.pspnl.rx_assoc({"version": version,
> + "dev-id": cfg.psp_dev_id,
> + "sock-fd": s.fileno()})
> + rx = rx_assoc['rx-key']
> + tx = _spi_xchg(s, rx)
> +
> + cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
> + "version": version,
> + "tx-key": tx,
> + "sock-fd": s.fileno()})
> +
> + data_len = _send_careful(cfg, s, 100)
> + _check_data_rx(cfg, data_len)
> + _close_psp_conn(cfg, s)
> +
> + # Clean up - back in host namespace
> + cfg.pspnl = PSPFamily()
> + cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
> +
> + del cfg.psp_dev_id
> + del cfg.psp_info
> +
> +
> +def _key_rotation_notify_multi_ns_netkit(cfg, version, ipver):
> + """ Test key rotation notifications across multiple namespaces using netkit """
> + import threading
I don't think we need threading for this test.. ?
If we open a socket while in one netns and then switch the netns back
the previously opened socket should still be in the netns? So we can
just ntf_check() directly from the main thread?
> + _init_psp_dev(cfg, True)
> + psp_dev_id_for_assoc = cfg.psp_dev_id
> +
> + # Associate PSP device with nk_guest interface (in guest namespace)
> + nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
> + nk_guest_ifindex = nk_guest_dev['ifindex']
> +
> + cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
> +
> + results = {'main_ns': None, 'peer_ns': None}
> +
> + def listen_in_namespace(ns_name, result_key):
> + """Listen for key rotation notifications in a namespace"""
> + try:
> + if ns_name:
> + ctx = NetNSEnter(ns_name)
> + else:
> + from contextlib import nullcontext
> + ctx = nullcontext()
> +
> + with ctx:
> + pspnl = PSPFamily()
> + pspnl.ntf_subscribe('use')
> +
> + collected_notifications = []
> + for i in range(100):
> + pspnl.check_ntf()
> +
> + try:
> + while True:
> + msg = pspnl.async_msg_queue.get_nowait()
> + collected_notifications.append(msg['msg'])
> + except:
> + pass
> +
> + if collected_notifications:
> + results[result_key] = collected_notifications
> + break
> +
> + time.sleep(0.1)
> + else:
> + # Timeout - no notification received
> + results[result_key] = []
> + except Exception as e:
> + results[result_key] = {'error': str(e)}
> +
> + # Create listener threads
> + # Main namespace listener
> + main_ns_thread = threading.Thread(
> + target=listen_in_namespace,
> + args=(None, 'main_ns'),
> + name='main_ns_listener'
> + )
> + # Guest namespace listener (netkit peer)
> + guest_ns_thread = threading.Thread(
> + target=listen_in_namespace,
> + args=(cfg.netns.name, 'peer_ns'),
> + name='guest_ns_listener'
> + )
> +
> + main_ns_thread.start()
> + guest_ns_thread.start()
> +
> + time.sleep(0.5)
> +
> + # Trigger key rotation on the PSP device
> + cfg.pspnl.key_rotate({"id": psp_dev_id_for_assoc})
> +
> + main_ns_thread.join(timeout=15)
> + guest_ns_thread.join(timeout=15)
> +
> + # Verify notifications were received in both namespaces
> + ksft_not_none(results['main_ns'], "No result from main namespace listener")
> + ksft_not_none(results['peer_ns'], "No result from guest namespace listener")
> +
> + # Check for errors in listeners
> + if isinstance(results['main_ns'], dict) and 'error' in results['main_ns']:
> + raise RuntimeError(f"Main NS listener error: {results['main_ns']['error']}")
> + if isinstance(results['peer_ns'], dict) and 'error' in results['peer_ns']:
> + raise RuntimeError(f"Guest NS listener error: {results['peer_ns']['error']}")
> +
> + # Verify that both namespaces received at least one notification
> + ksft_gt(len(results['main_ns']), 0, "No key rotation notification received in main namespace")
> + ksft_gt(len(results['peer_ns']), 0, "No key rotation notification received in guest namespace")
> +
> + # Verify the notification contains the expected dev_id
> + main_ns_has_dev = any(
> + ntf.get('id') == psp_dev_id_for_assoc
> + for ntf in results['main_ns']
> + )
> + guest_ns_has_dev = any(
> + ntf.get('id') == psp_dev_id_for_assoc
> + for ntf in results['peer_ns']
> + )
> +
> + ksft_true(main_ns_has_dev, "Key rotation notification for correct device not found in main namespace")
> + ksft_true(guest_ns_has_dev, "Key rotation notification for correct device not found in guest namespace")
> +
> + # Clean up
> + cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
> + del cfg.psp_dev_id
> + del cfg.psp_info
> +
> +
> +def _dev_change_notify_multi_ns_netkit(cfg, version, ipver):
> + """ Test dev_change notifications across multiple namespaces using netkit """
> + import threading
> +
> + _init_psp_dev(cfg, True)
> + psp_dev_id_for_assoc = cfg.psp_dev_id
> +
> + # Associate PSP device with nk_guest interface (in guest namespace)
> + nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
> + nk_guest_ifindex = nk_guest_dev['ifindex']
> +
> + cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
> +
> + results = {'main_ns': None, 'peer_ns': None}
> +
> + def listen_in_namespace(ns_name, result_key):
> + """Listen for dev_change notifications in a namespace"""
> + try:
> + if ns_name:
> + ctx = NetNSEnter(ns_name)
> + else:
> + from contextlib import nullcontext
> + ctx = nullcontext()
> +
> + with ctx:
> + pspnl = PSPFamily()
> + pspnl.ntf_subscribe('mgmt')
> +
> + collected_notifications = []
> + for i in range(100):
> + pspnl.check_ntf()
> +
> + try:
> + while True:
> + msg = pspnl.async_msg_queue.get_nowait()
> + collected_notifications.append(msg['msg'])
> + except:
> + pass
> +
> + if collected_notifications:
> + results[result_key] = collected_notifications
> + break
> +
> + time.sleep(0.1)
> + else:
> + # Timeout - no notification received
> + results[result_key] = []
> + except Exception as e:
> + results[result_key] = {'error': str(e)}
> +
> + # Create listener threads
> + # Main namespace listener
> + main_ns_thread = threading.Thread(
> + target=listen_in_namespace,
> + args=(None, 'main_ns'),
> + name='main_ns_listener'
> + )
> + # Guest namespace listener (netkit peer)
> + guest_ns_thread = threading.Thread(
> + target=listen_in_namespace,
> + args=(cfg.netns.name, 'peer_ns'),
> + name='guest_ns_listener'
> + )
> +
> + main_ns_thread.start()
> + guest_ns_thread.start()
> +
> + time.sleep(1.0) # Give threads time to subscribe
> +
> + # Trigger dev_change by calling dev_set (notification is always sent)
> + cfg.pspnl.dev_set({'id': psp_dev_id_for_assoc, 'psp-versions-ena': cfg.psp_info['psp-versions-cap']})
> +
> + main_ns_thread.join(timeout=15)
> + guest_ns_thread.join(timeout=15)
> +
> + # Verify notifications were received in both namespaces
> + ksft_not_none(results['main_ns'], "No result from main namespace listener")
> + ksft_not_none(results['peer_ns'], "No result from guest namespace listener")
> +
> + # Check for errors in listeners
> + if isinstance(results['main_ns'], dict) and 'error' in results['main_ns']:
> + raise RuntimeError(f"Main NS listener error: {results['main_ns']['error']}")
> + if isinstance(results['peer_ns'], dict) and 'error' in results['peer_ns']:
> + raise RuntimeError(f"Guest NS listener error: {results['peer_ns']['error']}")
> +
> + # Verify that both namespaces received at least one notification
> + ksft_gt(len(results['main_ns']), 0, "No dev_change notification received in main namespace")
> + ksft_gt(len(results['peer_ns']), 0, "No dev_change notification received in guest namespace")
> +
> + # Verify the notification contains the expected dev_id
> + main_ns_has_dev = any(
> + ntf.get('id') == psp_dev_id_for_assoc
> + for ntf in results['main_ns']
> + )
> + guest_ns_has_dev = any(
> + ntf.get('id') == psp_dev_id_for_assoc
> + for ntf in results['peer_ns']
> + )
> +
> + ksft_true(main_ns_has_dev, "Dev_change notification for correct device not found in main namespace")
> + ksft_true(guest_ns_has_dev, "Dev_change notification for correct device not found in guest namespace")
> +
> + # Clean up
> + cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
> + del cfg.psp_dev_id
> + del cfg.psp_info
> +
> +
> +def _psp_dev_get_check_netkit_psp_assoc(cfg, version, ipver):
> + """ Check psp dev-get output with netkit interface associated with PSP dev """
> +
> + _init_psp_dev(cfg, True)
> + psp_dev_id_for_assoc = cfg.psp_dev_id
> +
> + # Associate PSP device with nk_guest interface (in guest namespace)
> + nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
> + nk_guest_ifindex = nk_guest_dev['ifindex']
> +
> + cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
> +
> + # Check 1: In default netns, verify dev-get has correct ifindex and assoc-list
> + dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
> +
> + # Verify the PSP device has the correct ifindex
> + ksft_eq(dev_info['ifindex'], cfg.psp_ifindex)
> +
> + # Verify assoc-list exists and contains the associated nk_guest with correct ifindex and nsid
> + ksft_true('assoc-list' in dev_info, "No assoc-list in dev_get() response after association")
> + found = False
> + for assoc in dev_info['assoc-list']:
> + if assoc['ifindex'] == nk_guest_ifindex and assoc['nsid'] == cfg.psp_dev_peer_nsid:
> + found = True
> + break
> + ksft_true(found, "Associated device not found in assoc-list with correct ifindex and nsid")
> +
> + # Check 2: In guest netns, verify dev-get has assoc-list with nk_guest device
> + with NetNSEnter(cfg.netns.name):
> + peer_pspnl = PSPFamily()
> +
> + # Dump all devices in the guest namespace
> + peer_devices = peer_pspnl.dev_get({}, dump=True)
> +
> + # Find the device with by-association flag
> + peer_dev = None
> + for dev in peer_devices:
> + if dev.get('by-association'):
> + peer_dev = dev
> + break
> +
> + ksft_not_none(peer_dev, "No PSP device found with by-association flag in guest netns")
> +
> + # Verify assoc-list contains the nk_guest device
> + ksft_true('assoc-list' in peer_dev and len(peer_dev['assoc-list']) > 0,
> + "Guest device should have assoc-list with local devices")
> +
> + # Verify the assoc-list contains nk_guest ifindex with nsid=-1 (same namespace)
> + found = False
> + for assoc in peer_dev['assoc-list']:
> + if assoc['ifindex'] == nk_guest_ifindex:
> + ksft_eq(assoc['nsid'], -1,
> + "nsid should be -1 (NETNSA_NSID_NOT_ASSIGNED) for same-namespace device")
> + found = True
> + break
> + ksft_true(found, "nk_guest ifindex not found in assoc-list")
> +
> + # Clean up
> + cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
> +
> + del cfg.psp_dev_id
> + del cfg.psp_info
> +
> +
> +def _psp_dev_assoc_cleanup_on_netkit_del(cfg):
> + """ Test that assoc-list is cleared when associated netkit interface is deleted """
> + import subprocess
use cmd()? feels like this code is AI generated
please review it carefully before repost
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2026-03-06 21:53 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-04 0:00 [PATCH v2 net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 2/9] selftests/net: Export Netlink class via lib.py Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 3/9] selftests/net: Add env for container based tests Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 4/9] selftests/net: Add netkit container ping test Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 5/9] psp: add unprivileged version of psp_device_get_locked Wei Wang
2026-03-04 15:00 ` Daniel Zahka
2026-03-04 16:25 ` Willem de Bruijn
2026-03-04 17:42 ` Wei Wang
2026-03-04 18:01 ` Daniel Zahka
2026-03-04 18:03 ` Daniel Zahka
2026-03-04 22:31 ` Wei Wang
2026-03-04 23:41 ` Willem de Bruijn
2026-03-06 21:34 ` Jakub Kicinski
2026-03-04 0:00 ` [PATCH v2 net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc Wei Wang
2026-03-04 15:20 ` Daniel Zahka
2026-03-04 16:17 ` Daniel Zahka
2026-03-04 17:28 ` Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 7/9] psp: add a new netdev event for dev unregister Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 8/9] selftests/net: Add bpf skb forwarding program Wei Wang
2026-03-04 0:00 ` [PATCH v2 net-next 9/9] selftest/net: psp: Add test for dev-assoc/disassoc Wei Wang
2026-03-06 21:53 ` Jakub Kicinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox