From fbcd35eb1871fb7df3a5dbb0ffa05f15b83207a0 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 12 Sep 2026 13:04:21 -0700 Subject: [PATCH 1/6] selftests: drv-net: psp: fix linter issues Fix ruff 0.16 warnings: C403 Unnecessary list comprehension (rewrite as a set comprehension) Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260912200426.121025-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/psp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 315648a770d0..5e87fb50c348 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -525,11 +525,11 @@ def data_stale_key(cfg): def __nsim_psp_rereg(cfg): # The PSP dev ID will change, remember what was there before - before = set([x['id'] for x in cfg.pspnl.dev_get({}, dump=True)]) + before = {x['id'] for x in cfg.pspnl.dev_get({}, dump=True)} cfg._ns.nsims[0].dfs_write('psp_rereg', '1') - after = set([x['id'] for x in cfg.pspnl.dev_get({}, dump=True)]) + after = {x['id'] for x in cfg.pspnl.dev_get({}, dump=True)} new_devs = list(after - before) ksft_eq(len(new_devs), 1) From cf3302111b1a5c2b56127579ca51bdd20c70c0fe Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 12 Sep 2026 13:04:22 -0700 Subject: [PATCH 2/6] psp: don't report the main netdevice's ifindex to associated namespaces PSP device is visible in a netns if any of the devices (eg. netkit) are associated with that PSP device. In the main netns we show all the associated netdevs + their netns id. In the "container" netns we show only the local devices. But we were listing the main netdev in all cases, even though it's meaningless outside of the main netns. Report ifindex only in the main netdevice's namespace. Absence is already unambiguous, the by-association flag is set exactly in the messages which no longer carry the ifindex. Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260912200426.121025-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/psp.yaml | 2 ++ net/psp/psp_nl.c | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml index e9c2ee7e28e0..f3266763c325 100644 --- a/Documentation/netlink/specs/psp.yaml +++ b/Documentation/netlink/specs/psp.yaml @@ -38,6 +38,8 @@ attribute-sets: doc: | ifindex of the main netdevice linked to the PSP device, or the ifindex to associate with the PSP device. + Only reported to the network namespace the main netdevice + lives in, an ifindex has no meaning outside of it. type: u32 - name: psp-versions-cap diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c index f91665748dde..b57366b5e032 100644 --- a/net/psp/psp_nl.c +++ b/net/psp/psp_nl.c @@ -294,13 +294,16 @@ psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp, return -EMSGSIZE; if (nla_put_u32(rsp, PSP_A_DEV_ID, psd->id) || - nla_put_u32(rsp, PSP_A_DEV_IFINDEX, psd->main_netdev->ifindex) || nla_put_u32(rsp, PSP_A_DEV_PSP_VERSIONS_CAP, psd->caps->versions) || 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 */ + /* Primary device - report the netdev, dump assoc list. */ + if (nla_put_u32(rsp, PSP_A_DEV_IFINDEX, + psd->main_netdev->ifindex)) + goto err_cancel_msg; + err = psp_nl_fill_assoc_dev_list(psd, rsp, cur_net, NULL); if (err) goto err_cancel_msg; From 4d5d9f3a19682450858ff93ba3beaae65784ee3d Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 12 Sep 2026 13:04:23 -0700 Subject: [PATCH 3/6] selftests: drv-net: psp: check the ifindex an associated netns sees dev-get must not report the ifindex outside of the main netns. The dev-get checks for an associated namespace are already there, add the "no main ifindex" assertion. Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260912200426.121025-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/psp.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 5e87fb50c348..43780efb84ed 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -749,6 +749,10 @@ def _psp_dev_get_check_netkit_psp_assoc(cfg): ksft_not_none(peer_dev, "No PSP device found with by-association flag in guest netns") + # ifindex of the main netdevice means nothing in this namespace + ksft_true('ifindex' not in peer_dev, + "ifindex reported to an associated namespace") + # 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") From fb3ddbe2b5774f97a428bfb26711d68b966fcd0a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 12 Sep 2026 13:04:24 -0700 Subject: [PATCH 4/6] psp: notify about a disassociation once it has happened The dev-change-ntf generated by dev-disassoc was built before the association was unlinked, so the assoc-list it carried still contained the device which was going away, and nothing corrected it afterwards. We don't really expect associations to change during a lifetime of a netns but this is still wrong. Netlink listeners need to be able to tell the current "state of the world" based on notifications. The notification had to be sent early because psp_nl_multicast_per_ns() derives the set of namespaces to notify from the association list, so a namespace losing its last associated device becomes unreachable once the entry is gone. In that case - get the netns from the netdev itself, and send that namespace a dev-del-ntf. If the netns had multiple associated netdevs and only one disassoc'd we'll still send a change notification, just with a correct list. Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260912200426.121025-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- net/psp/psp.h | 1 + net/psp/psp_main.c | 16 +++++++++++----- net/psp/psp_nl.c | 44 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/net/psp/psp.h b/net/psp/psp.h index bbb39e2f5b0a..b123c2427905 100644 --- a/net/psp/psp.h +++ b/net/psp/psp.h @@ -19,6 +19,7 @@ bool psp_has_assoc_dev_in_ns(struct psp_dev *psd, struct net *net); int psp_attach_netdev_notifier(void); void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd); +void psp_nl_notify_disassoc(struct psp_dev *psd, struct net *net); struct psp_assoc *psp_assoc_create(struct psp_dev *psd); struct psp_dev *psp_dev_get_for_sock(struct sock *sk); diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c index 91473f96ad21..273b010d2355 100644 --- a/net/psp/psp_main.c +++ b/net/psp/psp_main.c @@ -408,7 +408,7 @@ 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) +static bool psp_dev_disassoc_one(struct psp_dev *psd, struct net_device *dev) { struct psp_assoc_dev *entry; @@ -419,9 +419,11 @@ static void psp_dev_disassoc_one(struct psp_dev *psd, struct net_device *dev) rcu_assign_pointer(entry->assoc_dev->psp_dev, NULL); netdev_put(entry->assoc_dev, &entry->dev_tracker); kfree(entry); - return; + return true; } } + + return false; } static int psp_netdev_event(struct notifier_block *nb, unsigned long event, @@ -438,9 +440,13 @@ static int psp_netdev_event(struct notifier_block *nb, unsigned long event, if (psd && psp_dev_tryget(psd)) { rcu_read_unlock(); mutex_lock(&psd->lock); - if (psp_dev_is_registered(psd)) - psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF); - psp_dev_disassoc_one(psd, dev); + /* Nothing to report if the device was never on the list, + * dev-assoc may have failed after publishing dev->psp_dev, + * and this is also the main netdevice's path. + */ + if (psp_dev_disassoc_one(psd, dev) && + psp_dev_is_registered(psd)) + psp_nl_notify_disassoc(psd, dev_net(dev)); mutex_unlock(&psd->lock); psp_dev_put(psd); } else { diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c index b57366b5e032..cdfc2d72fb39 100644 --- a/net/psp/psp_nl.c +++ b/net/psp/psp_nl.c @@ -356,6 +356,40 @@ void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd) psp_nl_build_dev_ntf, &cmd); } +/** + * psp_nl_notify_disassoc() - notify about a device losing an association + * @psd: PSP device (must be locked) + * @net: netns of the netdevice which got disassociated + * + * Must be called once @psd no longer has the association, so that the + * notifications carry the state after the change. + */ +void psp_nl_notify_disassoc(struct psp_dev *psd, struct net *net) +{ + struct sk_buff *ntf; + bool still_visible; + u32 cmd; + + lockdep_assert_held(&psd->lock); + + psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF); + + /* psp_nl_notify_dev() reaches the main netdevice's netns and every + * netns which still has an associated device. If @net is neither, + * the device is gone from @net and we should send a delete ntf. + */ + still_visible = !psp_dev_check_access(psd, net, false); + if (still_visible || !maybe_get_net(net)) + return; + + cmd = PSP_CMD_DEV_DEL_NTF; + ntf = psp_nl_build_dev_ntf(psd, net, &cmd); + if (ntf) + genlmsg_multicast_netns(&psp_nl_family, net, ntf, 0, + PSP_NLGRP_MGMT, GFP_KERNEL); + put_net(net); +} + int psp_nl_dev_get_doit(struct sk_buff *req, struct genl_info *info) { struct psp_dev *psd = info->user_ptr[0]; @@ -620,13 +654,6 @@ int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info) return -ENOMEM; } - put_net(net); - - /* Notify before removal so listeners in the disassociated namespace - * still receive the notification. - */ - psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF); - /* Remove from the association list */ list_del(&found->dev_list); psd->assoc_dev_cnt--; @@ -634,6 +661,9 @@ int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info) netdev_put(found->assoc_dev, &found->dev_tracker); kfree(found); + psp_nl_notify_disassoc(psd, net); + put_net(net); + return psp_nl_reply_send(rsp, info); } From b711865f68ce1cd5e832f7859260e9db73d2ed7c Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 12 Sep 2026 13:04:25 -0700 Subject: [PATCH 5/6] selftests: drv-net: psp: factor out creating a netkit in the test netns The netkit removal test builds a disposable netkit pair and moves its peer into the test namespace. The next commit needs a second associated device there, so move that to a helper. No functional change, other than looking the new peer up among all netkit devices rather than the two the environment created, which is what makes it reusable. Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260912200426.121025-6-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/psp.py | 60 ++++++++++++---------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 43780efb84ed..d44d2c83473e 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -798,25 +798,18 @@ def _dev_assoc_no_nsid(cfg): ksft_true(not found, "Device should not be in assoc-list after disassociation") -def _psp_dev_assoc_cleanup_on_netkit_del(cfg): - """Test that assoc-list is cleared when associated netkit is deleted. +def _add_netkit_guest(cfg, host_name, guest_name): + """Create a netkit pair and move its peer into the test namespace. - Creates a disposable netkit pair for this test to avoid destroying - the shared environment. + Returns the peer's ifindex there and the defer() deleting the pair. """ - _init_psp_dev(cfg, True) - defer(delattr, cfg, 'psp_dev_id') - defer(delattr, cfg, 'psp_info') + existing = {link['ifindex'] for link in ip("-d link show", json=True) + if link.get('linkinfo', {}).get('info_kind') == 'netkit'} - existing = {cfg.nk_host_ifindex, cfg.nk_guest_ifindex} - - # Create a temporary netkit pair - tmp_host_name = "tmp_nk_host" - tmp_guest_name = "tmp_nk_guest" rtnl = RtnlFamily() rtnl.newlink( { - "ifname": tmp_host_name, + "ifname": host_name, "linkinfo": { "kind": "netkit", "data": { @@ -828,25 +821,38 @@ def _psp_dev_assoc_cleanup_on_netkit_del(cfg): }, flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL], ) - cleanup_netkit = defer(ip, f"link del {tmp_host_name}") + cleanup = defer(ip, f"link del {host_name}") # Find the peer by diffing against existing netkit ifindexes all_links = ip("-d link show", json=True) - tmp_peer = [link for link in all_links - if link.get('linkinfo', {}).get('info_kind') == 'netkit' - and link['ifindex'] not in existing - and link['ifname'] != tmp_host_name] - ksft_eq(len(tmp_peer), 1, - "Failed to find temporary netkit peer") - guest_name = tmp_peer[0]['ifname'] + peer = [link for link in all_links + if link.get('linkinfo', {}).get('info_kind') == 'netkit' + and link['ifindex'] not in existing + and link['ifname'] != host_name] + ksft_eq(len(peer), 1, "Failed to find the new netkit peer") # Rename and move guest end into the test namespace - ip(f"link set dev {guest_name} name {tmp_guest_name}") - ip(f"link set dev {tmp_guest_name} netns {cfg.netns.name}") - tmp_guest_dev = ip(f"link show dev {tmp_guest_name}", - json=True, ns=cfg.netns)[0] - tmp_guest_ifindex = tmp_guest_dev['ifindex'] - ip(f"link set dev {tmp_guest_name} up", ns=cfg.netns) + ip(f"link set dev {peer[0]['ifname']} name {guest_name}") + ip(f"link set dev {guest_name} netns {cfg.netns.name}") + guest_dev = ip(f"link show dev {guest_name}", json=True, ns=cfg.netns)[0] + ip(f"link set dev {guest_name} up", ns=cfg.netns) + + return guest_dev['ifindex'], cleanup + + +def _psp_dev_assoc_cleanup_on_netkit_del(cfg): + """Test that assoc-list is cleared when associated netkit is deleted. + + Creates a disposable netkit pair for this test to avoid destroying + the shared environment. + """ + _init_psp_dev(cfg, True) + defer(delattr, cfg, 'psp_dev_id') + defer(delattr, cfg, 'psp_info') + + tmp_host_name = "tmp_nk_host" + tmp_guest_ifindex, cleanup_netkit = _add_netkit_guest(cfg, tmp_host_name, + "tmp_nk_guest") # Associate PSP device with the temporary guest interface cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id, From 4d3c1d963c6fd104bf408ea66c52a939d93697bd Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 12 Sep 2026 13:04:26 -0700 Subject: [PATCH 6/6] selftests: drv-net: psp: check the PSP disassociation notifications The main namespace must see a change which no longer lists the device, and the namespace which lost its last association must see the device go away. Check that on both paths which generate the notifications, dev-disassoc and netdevice removal, and check that a namespace which still has another association is only told about the change. Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260912200426.121025-7-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/psp.py | 112 +++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index d44d2c83473e..13ff5188cca3 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -723,6 +723,111 @@ def _dev_change_notify_multi_ns_netkit(cfg): f" in {label} namespace") +def _subscribe_mgmt(cfg): + """Listen on the mgmt group in the guest and in the main namespace.""" + # Listener in the guest namespace; socket stays bound to that ns + with NetNSEnter(cfg.netns.name): + peer_pspnl = PSPFamily() + peer_pspnl.ntf_subscribe('mgmt') + + main_pspnl = PSPFamily() + main_pspnl.ntf_subscribe('mgmt') + + return main_pspnl, peer_pspnl + + +def _get_dev_ntf(cfg, pspnl, label): + """Wait for the next notification about the PSP device under test.""" + for ntf in pspnl.poll_ntf(duration=10): + if ntf['msg'].get('id') == cfg.psp_dev_id: + return ntf + raise KsftFailEx(f"No notification received in the {label} namespace") + + +def _check_disassoc_ntf(cfg, main_pspnl, peer_pspnl, ifindex): + """Check the notifications for a netns losing its last association.""" + ntf = _get_dev_ntf(cfg, main_pspnl, "main") + ksft_eq(ntf['name'], 'dev-change-ntf') + for assoc in ntf['msg'].get('assoc-list', []): + if assoc['nsid'] != cfg.psp_dev_peer_nsid: + continue + ksft_ne(assoc['ifindex'], ifindex, + "Disassociated device still listed in the notification") + + # The device is gone as far as the disassociated namespace is concerned + ntf = _get_dev_ntf(cfg, peer_pspnl, "guest") + ksft_eq(ntf['name'], 'dev-del-ntf') + ksft_true('ifindex' not in ntf['msg'], + "ifindex reported to an associated namespace") + + +def _dev_disassoc_notify_multi_ns_netkit(cfg): + """ Test the notifications dev-disassoc generates in both namespaces """ + _init_psp_dev(cfg, True) + defer(delattr, cfg, 'psp_dev_id') + defer(delattr, cfg, 'psp_info') + + cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id, + 'ifindex': cfg.nk_guest_ifindex, + 'nsid': cfg.psp_dev_peer_nsid}) + defer(_try_disassoc, cfg, cfg.psp_dev_id, cfg.nk_guest_ifindex, + cfg.psp_dev_peer_nsid) + + main_pspnl, peer_pspnl = _subscribe_mgmt(cfg) + + cfg.pspnl.dev_disassoc({'id': cfg.psp_dev_id, + 'ifindex': cfg.nk_guest_ifindex, + 'nsid': cfg.psp_dev_peer_nsid}) + + _check_disassoc_ntf(cfg, main_pspnl, peer_pspnl, cfg.nk_guest_ifindex) + + +def _dev_disassoc_notify_one_of_two_netkit(cfg): + """Test the notifications with two netkits associated in one netns. + + Disassociating the first netkit leaves the PSP device visible in the + guest namespace, generates a dev-change-ntf. + Disassociating the second one takes the device out of its view, + generates 'dev-del-ntf'. + """ + _init_psp_dev(cfg, True) + defer(delattr, cfg, 'psp_dev_id') + defer(delattr, cfg, 'psp_info') + + tmp_ifindex, _ = _add_netkit_guest(cfg, "tmp_nk_host", "tmp_nk_guest") + + for ifindex in [cfg.nk_guest_ifindex, tmp_ifindex]: + cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id, 'ifindex': ifindex, + 'nsid': cfg.psp_dev_peer_nsid}) + defer(_try_disassoc, cfg, cfg.psp_dev_id, ifindex, + cfg.psp_dev_peer_nsid) + + main_pspnl, peer_pspnl = _subscribe_mgmt(cfg) + + # One of the two goes away, the device stays visible in the guest netns + cfg.pspnl.dev_disassoc({'id': cfg.psp_dev_id, 'ifindex': tmp_ifindex, + 'nsid': cfg.psp_dev_peer_nsid}) + + ntf = _get_dev_ntf(cfg, main_pspnl, "main") + ksft_eq(ntf['name'], 'dev-change-ntf') + + ntf = _get_dev_ntf(cfg, peer_pspnl, "guest") + ksft_eq(ntf['name'], 'dev-change-ntf') + found = False + for assoc in ntf['msg'].get('assoc-list', []): + ksft_ne(assoc['ifindex'], tmp_ifindex, + "Disassociated device still listed in the notification") + found |= assoc['ifindex'] == cfg.nk_guest_ifindex + ksft_true(found, "Remaining association missing from the notification") + + # And now the last one, the device disappears from the guest netns + cfg.pspnl.dev_disassoc({'id': cfg.psp_dev_id, + 'ifindex': cfg.nk_guest_ifindex, + 'nsid': cfg.psp_dev_peer_nsid}) + + _check_disassoc_ntf(cfg, main_pspnl, peer_pspnl, cfg.nk_guest_ifindex) + + def _psp_dev_get_check_netkit_psp_assoc(cfg): """ Check psp dev-get output with netkit interface associated with PSP dev """ _assoc_nk_guest(cfg) @@ -863,6 +968,9 @@ def _psp_dev_assoc_cleanup_on_netkit_del(cfg): _check_assoc_list(cfg, cfg.psp_dev_id, tmp_guest_ifindex, cfg.psp_dev_peer_nsid) + # Removing the netdevice is notified like a disassociation + main_pspnl, peer_pspnl = _subscribe_mgmt(cfg) + # Delete the temporary netkit pair (deleting one end removes both) ip(f"link del {tmp_host_name}") cleanup_netkit.cancel() @@ -873,6 +981,8 @@ def _psp_dev_assoc_cleanup_on_netkit_del(cfg): or len(dev_info['assoc-list']) == 0, "assoc-list should be empty after netkit deletion") + _check_disassoc_ntf(cfg, main_pspnl, peer_pspnl, tmp_guest_ifindex) + def _try_disassoc(cfg, psp_dev_id, ifindex, nsid=None): """Best-effort disassociate, ignoring errors if already removed.""" @@ -991,6 +1101,8 @@ def main() -> None: data_basic_send_netkit_psp_assoc, _key_rotation_notify_multi_ns_netkit, _dev_change_notify_multi_ns_netkit, + _dev_disassoc_notify_multi_ns_netkit, + _dev_disassoc_notify_one_of_two_netkit, _psp_dev_get_check_netkit_psp_assoc, _dev_assoc_no_nsid, _psp_dev_assoc_cleanup_on_netkit_del,