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.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 f91665748dde..cdfc2d72fb39 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; @@ -353,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]; @@ -617,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--; @@ -631,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); } diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 315648a770d0..13ff5188cca3 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) @@ -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) @@ -749,6 +854,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") @@ -794,25 +903,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": { @@ -824,25 +926,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, @@ -853,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() @@ -863,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.""" @@ -981,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,