From 4a6fe5fe60040c31c25767ca815a06fab35c1eb7 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Tue, 14 Apr 2026 00:08:04 +0200 Subject: [PATCH 1/3] tools/ynl: Make YnlFamily closeable as a context manager YnlFamily opens an AF_NETLINK socket in __init__ but has no way to release it other than leaving it to the GC. YnlFamily holds a self reference cycle through SpecFamily's self.family = self in its super().__init__() call, so refcount GC cannot reclaim it and the socket stays open until the cyclic GC runs. If a test creates a guest netns, instantiates a YnlFamily inside it via NetNSEnter(), performs some test case work via Ynl, and then deletes the netns, then the 'ip netns del' only drops the mount binding and cleanup_net in the kernel never runs, so any subsequent test case assertions that objects got cleaned up would fail given this only gets triggered later via cyclic GC run. Add an explicit close() that closes the netlink socket and wire up the __enter__/__exit__ so callers can scope the instance deterministically via 'with YnlFamily(...) as ynl: ...'. Signed-off-by: Daniel Borkmann Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260413220809.604592-2-daniel@iogearbox.net Signed-off-by: Jakub Kicinski --- tools/net/ynl/pyynl/lib/ynl.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py index 9c078599cea0..f63c6f828735 100644 --- a/tools/net/ynl/pyynl/lib/ynl.py +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -731,6 +731,16 @@ class YnlFamily(SpecFamily): bound_f = functools.partial(self._op, op_name) setattr(self, op.ident_name, bound_f) + def close(self): + if self.sock is not None: + self.sock.close() + self.sock = None + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + self.close() def ntf_subscribe(self, mcast_name): mcast_id = self.nlproto.get_mcast_id(mcast_name, self.mcast_groups) From e254ffb9502c8b4c7f8712c34ae6590796825260 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Tue, 14 Apr 2026 00:08:05 +0200 Subject: [PATCH 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease As pointed out in 3d2c3d2eea9a ("selftests: net: py: explicitly forbid multiple ksft_run() calls"), ksft_run() cannot be called multiple times. Move the netdevsim-based queue lease tests to selftests/net/ so that each file has exactly one ksft_run() call. The HW tests (io_uring ZC RX, queue attrs, XDP with MP, destroy) remain in selftests/drivers/net/hw/. Fixes: 65d657d80684 ("selftests/net: Add queue leasing tests with netkit") Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/netdev/20260409181950.7e099b6c@kernel.org Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260413220809.604592-3-daniel@iogearbox.net Signed-off-by: Jakub Kicinski --- .../selftests/drivers/net/hw/nk_qlease.py | 1142 ---------------- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/nk_qlease.py | 1168 +++++++++++++++++ 3 files changed, 1169 insertions(+), 1142 deletions(-) create mode 100755 tools/testing/selftests/net/nk_qlease.py diff --git a/tools/testing/selftests/drivers/net/hw/nk_qlease.py b/tools/testing/selftests/drivers/net/hw/nk_qlease.py index 2bc5ffe96c7d..aa83dc321328 100755 --- a/tools/testing/selftests/drivers/net/hw/nk_qlease.py +++ b/tools/testing/selftests/drivers/net/hw/nk_qlease.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 -import errno import re import time import threading @@ -10,23 +9,17 @@ from lib.py import ( ksft_run, ksft_exit, ksft_eq, - ksft_ne, ksft_in, ksft_not_in, ksft_raises, ) from lib.py import ( NetDrvContEnv, - NetNS, NetNSEnter, EthtoolFamily, NetdevFamily, - RtnlFamily, - NetdevSimDev, ) from lib.py import ( - NlError, - Netlink, bkg, cmd, defer, @@ -46,1100 +39,6 @@ def set_flow_rule(cfg): return int(values) -def create_netkit(rxqueues): - all_links = ip("-d link show", json=True) - old_idxs = { - link["ifindex"] - for link in all_links - if link.get("linkinfo", {}).get("info_kind") == "netkit" - } - - 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) - nk_links = [ - link - for link in all_links - if link.get("linkinfo", {}).get("info_kind") == "netkit" - and link["ifindex"] not in old_idxs - ] - nk_links.sort(key=lambda x: x["ifindex"]) - return ( - nk_links[1]["ifname"], - nk_links[1]["ifindex"], - nk_links[0]["ifname"], - nk_links[0]["ifindex"], - ) - - -def create_netkit_single(rxqueues): - rtnl = RtnlFamily() - rtnl.newlink( - { - "linkinfo": { - "kind": "netkit", - "data": { - "mode": "l2", - "pairing": "single", - }, - }, - "num-rx-queues": rxqueues, - }, - flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL], - ) - - all_links = ip("-d link show", json=True) - nk_links = [ - link - for link in all_links - if link.get("linkinfo", {}).get("info_kind") == "netkit" - and "UP" not in link.get("flags", []) - ] - return nk_links[0]["ifname"], nk_links[0]["ifindex"] - - -def test_remove_phys(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - src_queue = 1 - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - result = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": src_queue, "type": "rx"}, - "netns-id": 0, - }, - } - ) - nk_queue_id = result["id"] - - netdevnl = NetdevFamily() - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_in("lease", queue_info) - ksft_eq(queue_info["lease"]["ifindex"], nk_guest_idx) - ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) - - nsimdev.remove() - time.sleep(0.1) - ret = cmd(f"ip link show dev {nk_host}", fail=False) - ksft_ne(ret.ret, 0) - - -def test_double_lease(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=3) - defer(cmd, f"ip link del dev {nk_host}") - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - src_queue = 1 - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - result = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": src_queue, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(result["id"], 1) - - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": src_queue, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EBUSY) - - -def test_virtual_lessor(netns) -> None: - nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host_a}") - ip(f"link set dev {nk_host_a} up") - ip(f"link set dev {nk_guest_a} up") - - nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host_b}") - - ip(f"link set dev {nk_guest_b} netns {netns.name}") - ip(f"link set dev {nk_host_b} up") - ip(f"link set dev {nk_guest_b} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_b_idx, - "type": "rx", - "lease": { - "ifindex": nk_guest_a_idx, - "queue": {"id": 0, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) - - -def test_phys_lessee(_netns) -> None: - nsimdev_a = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev_a.remove) - nsim_a = nsimdev_a.nsims[0] - ip(f"link set dev {nsim_a.ifname} up") - - nsimdev_b = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev_b.remove) - nsim_b = nsimdev_b.nsims[0] - ip(f"link set dev {nsim_b.ifname} up") - - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nsim_a.ifindex, - "type": "rx", - "lease": { - "ifindex": nsim_b.ifindex, - "queue": {"id": 0, "type": "rx"}, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) - - -def test_different_lessors(netns) -> None: - nsimdev_a = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev_a.remove) - nsim_a = nsimdev_a.nsims[0] - ip(f"link set dev {nsim_a.ifname} up") - - nsimdev_b = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev_b.remove) - nsim_b = nsimdev_b.nsims[0] - ip(f"link set dev {nsim_b.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=3) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim_a.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim_b.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP) - - -def test_queue_out_of_range(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 2, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.ERANGE) - - -def test_resize_leased(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - - ethnl = EthtoolFamily() - with ksft_raises(NlError) as e: - ethnl.channels_set({"header": {"dev-index": nsim.ifindex}, "combined-count": 1}) - ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) - - -def test_self_lease(_netns) -> None: - nk_host, _, _, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nk_guest_idx, - "queue": {"id": 0, "type": "rx"}, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) - - -def test_veth_queue_create(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - ip("link add veth0 type veth peer name veth1") - defer(cmd, "ip link del dev veth0", fail=False) - - all_links = ip("-d link show", json=True) - veth_peer = [ - link - for link in all_links - if link.get("ifname") == "veth1" - ] - veth_peer_idx = veth_peer[0]["ifindex"] - - ip(f"link set dev veth1 netns {netns.name}") - ip("link set dev veth0 up") - ip("link set dev veth1 up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": veth_peer_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) - - -def test_create_tx_type(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "tx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) - - -def test_create_primary(_netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, nk_host_idx, _, _ = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_host} up") - - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_host_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP) - - -def test_create_limit(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=1) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) - - -def test_link_flap_phys(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}") - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - src_queue = 1 - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - result = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": src_queue, "type": "rx"}, - "netns-id": 0, - }, - } - ) - nk_queue_id = result["id"] - - netdevnl = NetdevFamily() - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_in("lease", queue_info) - ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) - - # Link flap the physical device - ip(f"link set dev {nsim.ifname} down") - ip(f"link set dev {nsim.ifname} up") - - # Verify lease survives the flap - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_in("lease", queue_info) - ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) - - -def test_queue_get_virtual(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}") - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - src_queue = 1 - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - result = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": src_queue, "type": "rx"}, - "netns-id": 0, - }, - } - ) - nk_queue_id = result["id"] - - # queue-get on virtual device's leased queue should not show lease - # info (lease info is only shown from the physical device's side) - queue_info = netdevnl.queue_get( - {"ifindex": nk_guest_idx, "id": nk_queue_id, "type": "rx"} - ) - ksft_eq(queue_info["id"], nk_queue_id) - ksft_eq(queue_info["ifindex"], nk_guest_idx) - ksft_not_in("lease", queue_info) - - # Default queue (not leased) also has no lease info - queue_info = netdevnl.queue_get( - {"ifindex": nk_guest_idx, "id": 0, "type": "rx"} - ) - ksft_not_in("lease", queue_info) - - -def test_remove_virt_first(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - src_queue = 1 - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - result = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": src_queue, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(result["id"], 1) - - netdevnl = NetdevFamily() - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_in("lease", queue_info) - ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) - - # Delete netkit (virtual device removed first, physical stays) - cmd(f"ip link del dev {nk_host}") - - # Verify lease is cleaned up on physical device - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_not_in("lease", queue_info) - - -def test_multiple_leases(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=3) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=4) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - r1 = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - r2 = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 2, "type": "rx"}, - "netns-id": 0, - }, - } - ) - - ksft_eq(r1["id"], 1) - ksft_eq(r2["id"], 2) - - # Verify both leases visible on physical device - netdevnl = NetdevFamily() - q1 = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} - ) - q2 = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": 2, "type": "rx"} - ) - ksft_in("lease", q1) - ksft_in("lease", q2) - ksft_eq(q1["lease"]["ifindex"], nk_guest_idx) - ksft_eq(q2["lease"]["ifindex"], nk_guest_idx) - ksft_eq(q1["lease"]["queue"]["id"], r1["id"]) - ksft_eq(q2["lease"]["queue"]["id"], r2["id"]) - - -def test_lease_queue_tx_type(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "tx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) - - -def test_invalid_netns(netns) -> None: - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": 1, - "queue": {"id": 0, "type": "rx"}, - "netns-id": 999, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.ENONET) - - -def test_invalid_phys_ifindex(netns) -> None: - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - with ksft_raises(NlError) as e: - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": 99999, - "queue": {"id": 0, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(e.exception.nl_msg.error, -errno.ENODEV) - - -def test_multi_netkit_remove_phys(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=3) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - # Create two netkit pairs, each leasing a different physical queue - nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host_a}", fail=False) - - nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host_b}", fail=False) - - ip(f"link set dev {nk_guest_a} netns {netns.name}") - ip(f"link set dev {nk_host_a} up") - ip(f"link set dev {nk_guest_a} up", ns=netns) - - ip(f"link set dev {nk_guest_b} netns {netns.name}") - ip(f"link set dev {nk_host_b} up") - ip(f"link set dev {nk_guest_b} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - netdevnl.queue_create( - { - "ifindex": nk_guest_a_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - netdevnl.queue_create( - { - "ifindex": nk_guest_b_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 2, "type": "rx"}, - "netns-id": 0, - }, - } - ) - - # Removing the physical device should take down both netkit pairs - nsimdev.remove() - time.sleep(0.1) - ret = cmd(f"ip link show dev {nk_host_a}", fail=False) - ksft_ne(ret.ret, 0) - ret = cmd(f"ip link show dev {nk_host_b}", fail=False) - ksft_ne(ret.ret, 0) - - -def test_single_remove_phys(_netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_name, nk_idx = create_netkit_single(rxqueues=2) - defer(cmd, f"ip link del dev {nk_name}", fail=False) - - ip(f"link set dev {nk_name} up") - - netdevnl = NetdevFamily() - netdevnl.queue_create( - { - "ifindex": nk_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - }, - } - ) - - # Removing the physical device should take down the single netkit device - nsimdev.remove() - time.sleep(0.1) - ret = cmd(f"ip link show dev {nk_name}", fail=False) - ksft_ne(ret.ret, 0) - - -def test_link_flap_virt(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}") - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - src_queue = 1 - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - result = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": src_queue, "type": "rx"}, - "netns-id": 0, - }, - } - ) - nk_queue_id = result["id"] - - netdevnl = NetdevFamily() - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_in("lease", queue_info) - ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) - - # Link flap the virtual (netkit) device - ip(f"link set dev {nk_guest} down", ns=netns) - ip(f"link set dev {nk_guest} up", ns=netns) - - # Verify lease survives the virtual device flap - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_in("lease", queue_info) - ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) - - -def test_phys_queue_no_lease(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}") - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - - # Physical queue 0 (not leased) should have no lease info - netdevnl = NetdevFamily() - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": 0, "type": "rx"} - ) - ksft_not_in("lease", queue_info) - - # Physical queue 1 (leased) should have lease info - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} - ) - ksft_in("lease", queue_info) - - -def test_same_ns_lease(_netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_name, nk_idx = create_netkit_single(rxqueues=2) - defer(cmd, f"ip link del dev {nk_name}", fail=False) - - ip(f"link set dev {nk_name} up") - - netdevnl = NetdevFamily() - result = netdevnl.queue_create( - { - "ifindex": nk_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - }, - } - ) - ksft_eq(result["id"], 1) - - # Same namespace: lease info should NOT have netns-id - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} - ) - ksft_in("lease", queue_info) - ksft_eq(queue_info["lease"]["ifindex"], nk_idx) - ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) - ksft_not_in("netns-id", queue_info["lease"]) - - -def test_resize_after_unlease(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 1, "type": "rx"}, - "netns-id": 0, - }, - } - ) - - # Resize should fail while lease is active - ethnl = EthtoolFamily() - with ksft_raises(NlError) as e: - ethnl.channels_set({"header": {"dev-index": nsim.ifindex}, "combined-count": 1}) - ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) - - # Delete netkit, clearing the lease - cmd(f"ip link del dev {nk_host}") - - # Resize should now succeed - ethnl.channels_set({"header": {"dev-index": nsim.ifindex}, "combined-count": 1}) - - -def test_lease_queue_zero(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - result = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": 0, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(result["id"], 1) - - netdevnl = NetdevFamily() - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": 0, "type": "rx"} - ) - ksft_in("lease", queue_info) - ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) - - -def test_release_and_reuse(netns) -> None: - nsimdev = NetdevSimDev(port_count=1, queue_count=2) - defer(nsimdev.remove) - nsim = nsimdev.nsims[0] - ip(f"link set dev {nsim.ifname} up") - - src_queue = 1 - - # First lease - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": src_queue, "type": "rx"}, - "netns-id": 0, - }, - } - ) - - netdevnl = NetdevFamily() - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_in("lease", queue_info) - - # Delete netkit, freeing the lease - cmd(f"ip link del dev {nk_host}") - - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_not_in("lease", queue_info) - - # Re-create netkit and lease the same physical queue again - nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) - defer(cmd, f"ip link del dev {nk_host}", fail=False) - - ip(f"link set dev {nk_guest} netns {netns.name}") - ip(f"link set dev {nk_host} up") - ip(f"link set dev {nk_guest} up", ns=netns) - - with NetNSEnter(str(netns)): - netdevnl = NetdevFamily() - result = netdevnl.queue_create( - { - "ifindex": nk_guest_idx, - "type": "rx", - "lease": { - "ifindex": nsim.ifindex, - "queue": {"id": src_queue, "type": "rx"}, - "netns-id": 0, - }, - } - ) - ksft_eq(result["id"], 1) - - netdevnl = NetdevFamily() - queue_info = netdevnl.queue_get( - {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} - ) - ksft_in("lease", queue_info) - ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) - - def test_iou_zcrx(cfg) -> None: cfg.require_ipver("6") ethnl = EthtoolFamily() @@ -1324,47 +223,6 @@ def test_destroy(cfg) -> None: def main() -> None: - netns = NetNS() - cmd("ip netns attach init 1") - ip("netns set init 0", ns=netns) - ip("link set lo up", ns=netns) - - ksft_run( - [ - test_remove_phys, - test_double_lease, - test_virtual_lessor, - test_phys_lessee, - test_different_lessors, - test_queue_out_of_range, - test_resize_leased, - test_self_lease, - test_create_tx_type, - test_create_primary, - test_create_limit, - test_link_flap_phys, - test_queue_get_virtual, - test_remove_virt_first, - test_multiple_leases, - test_lease_queue_tx_type, - test_invalid_netns, - test_invalid_phys_ifindex, - test_multi_netkit_remove_phys, - test_single_remove_phys, - test_link_flap_virt, - test_phys_queue_no_lease, - test_same_ns_lease, - test_resize_after_unlease, - test_lease_queue_zero, - test_release_and_reuse, - test_veth_queue_create, - ], - args=(netns,), - ) - - cmd("ip netns del init", fail=False) - del netns - with NetDrvContEnv(__file__, rxqueues=2) as cfg: cfg.bin_local = path.abspath( path.dirname(__file__) + "/../../../drivers/net/hw/iou-zcrx" diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 231245a95879..a275ed584026 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -65,6 +65,7 @@ TEST_PROGS := \ netdevice.sh \ netns-name.sh \ netns-sysctl.sh \ + nk_qlease.py \ nl_netdev.py \ nl_nlctrl.py \ pmtu.sh \ diff --git a/tools/testing/selftests/net/nk_qlease.py b/tools/testing/selftests/net/nk_qlease.py new file mode 100755 index 000000000000..6ed4fb5e90f6 --- /dev/null +++ b/tools/testing/selftests/net/nk_qlease.py @@ -0,0 +1,1168 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +import errno +import time +from lib.py import ( + ksft_run, + ksft_exit, + ksft_eq, + ksft_ne, + ksft_in, + ksft_not_in, + ksft_raises, +) +from lib.py import ( + NetNS, + NetNSEnter, + EthtoolFamily, + NetdevFamily, + RtnlFamily, + NetdevSimDev, +) +from lib.py import ( + NlError, + Netlink, + cmd, + defer, + ip, +) + +def create_netkit(rxqueues): + all_links = ip("-d link show", json=True) + old_idxs = { + link["ifindex"] + for link in all_links + if link.get("linkinfo", {}).get("info_kind") == "netkit" + } + + 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) + nk_links = [ + link + for link in all_links + if link.get("linkinfo", {}).get("info_kind") == "netkit" + and link["ifindex"] not in old_idxs + ] + nk_links.sort(key=lambda x: x["ifindex"]) + return ( + nk_links[1]["ifname"], + nk_links[1]["ifindex"], + nk_links[0]["ifname"], + nk_links[0]["ifindex"], + ) + + +def create_netkit_single(rxqueues): + rtnl = RtnlFamily() + rtnl.newlink( + { + "linkinfo": { + "kind": "netkit", + "data": { + "mode": "l2", + "pairing": "single", + }, + }, + "num-rx-queues": rxqueues, + }, + flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL], + ) + + all_links = ip("-d link show", json=True) + nk_links = [ + link + for link in all_links + if link.get("linkinfo", {}).get("info_kind") == "netkit" + and "UP" not in link.get("flags", []) + ] + return nk_links[0]["ifname"], nk_links[0]["ifindex"] + +def test_remove_phys(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + nk_queue_id = result["id"] + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["ifindex"], nk_guest_idx) + ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) + + nsimdev.remove() + time.sleep(0.1) + ret = cmd(f"ip link show dev {nk_host}", fail=False) + ksft_ne(ret.ret, 0) + + +def test_double_lease(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=3) + defer(cmd, f"ip link del dev {nk_host}") + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(result["id"], 1) + + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EBUSY) + + +def test_virtual_lessor(netns) -> None: + nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_a}") + ip(f"link set dev {nk_host_a} up") + ip(f"link set dev {nk_guest_a} up") + + nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_b}") + + ip(f"link set dev {nk_guest_b} netns {netns.name}") + ip(f"link set dev {nk_host_b} up") + ip(f"link set dev {nk_guest_b} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_b_idx, + "type": "rx", + "lease": { + "ifindex": nk_guest_a_idx, + "queue": {"id": 0, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + +def test_phys_lessee(_netns) -> None: + nsimdev_a = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev_a.remove) + nsim_a = nsimdev_a.nsims[0] + ip(f"link set dev {nsim_a.ifname} up") + + nsimdev_b = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev_b.remove) + nsim_b = nsimdev_b.nsims[0] + ip(f"link set dev {nsim_b.ifname} up") + + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nsim_a.ifindex, + "type": "rx", + "lease": { + "ifindex": nsim_b.ifindex, + "queue": {"id": 0, "type": "rx"}, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + +def test_different_lessors(netns) -> None: + nsimdev_a = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev_a.remove) + nsim_a = nsimdev_a.nsims[0] + ip(f"link set dev {nsim_a.ifname} up") + + nsimdev_b = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev_b.remove) + nsim_b = nsimdev_b.nsims[0] + ip(f"link set dev {nsim_b.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=3) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim_a.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim_b.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP) + + +def test_queue_out_of_range(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 2, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.ERANGE) + + +def test_resize_leased(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + ethnl = EthtoolFamily() + with ksft_raises(NlError) as e: + ethnl.channels_set({"header": {"dev-index": nsim.ifindex}, "combined-count": 1}) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + +def test_self_lease(_netns) -> None: + nk_host, _, _, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nk_guest_idx, + "queue": {"id": 0, "type": "rx"}, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + +def test_veth_queue_create(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + ip("link add veth0 type veth peer name veth1") + defer(cmd, "ip link del dev veth0", fail=False) + + all_links = ip("-d link show", json=True) + veth_peer = [ + link + for link in all_links + if link.get("ifname") == "veth1" + ] + veth_peer_idx = veth_peer[0]["ifindex"] + + ip(f"link set dev veth1 netns {netns.name}") + ip("link set dev veth0 up") + ip("link set dev veth1 up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": veth_peer_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + +def test_create_tx_type(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "tx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + +def test_create_primary(_netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, nk_host_idx, _, _ = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_host} up") + + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_host_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP) + + +def test_create_limit(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=1) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + +def test_link_flap_phys(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}") + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + nk_queue_id = result["id"] + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) + + # Link flap the physical device + ip(f"link set dev {nsim.ifname} down") + ip(f"link set dev {nsim.ifname} up") + + # Verify lease survives the flap + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) + + +def test_queue_get_virtual(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}") + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + nk_queue_id = result["id"] + + # queue-get on virtual device's leased queue should not show lease + # info (lease info is only shown from the physical device's side) + queue_info = netdevnl.queue_get( + {"ifindex": nk_guest_idx, "id": nk_queue_id, "type": "rx"} + ) + ksft_eq(queue_info["id"], nk_queue_id) + ksft_eq(queue_info["ifindex"], nk_guest_idx) + ksft_not_in("lease", queue_info) + + # Default queue (not leased) also has no lease info + queue_info = netdevnl.queue_get( + {"ifindex": nk_guest_idx, "id": 0, "type": "rx"} + ) + ksft_not_in("lease", queue_info) + + +def test_remove_virt_first(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(result["id"], 1) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) + + # Delete netkit (virtual device removed first, physical stays) + cmd(f"ip link del dev {nk_host}") + + # Verify lease is cleaned up on physical device + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_not_in("lease", queue_info) + + +def test_multiple_leases(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=3) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=4) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + r1 = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + r2 = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 2, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + ksft_eq(r1["id"], 1) + ksft_eq(r2["id"], 2) + + # Verify both leases visible on physical device + netdevnl = NetdevFamily() + q1 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + q2 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 2, "type": "rx"} + ) + ksft_in("lease", q1) + ksft_in("lease", q2) + ksft_eq(q1["lease"]["ifindex"], nk_guest_idx) + ksft_eq(q2["lease"]["ifindex"], nk_guest_idx) + ksft_eq(q1["lease"]["queue"]["id"], r1["id"]) + ksft_eq(q2["lease"]["queue"]["id"], r2["id"]) + + +def test_lease_queue_tx_type(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "tx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + +def test_invalid_netns(netns) -> None: + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": 1, + "queue": {"id": 0, "type": "rx"}, + "netns-id": 999, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.ENONET) + + +def test_invalid_phys_ifindex(netns) -> None: + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": 99999, + "queue": {"id": 0, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.ENODEV) + + +def test_multi_netkit_remove_phys(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=3) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + # Create two netkit pairs, each leasing a different physical queue + nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_a}", fail=False) + + nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_b}", fail=False) + + ip(f"link set dev {nk_guest_a} netns {netns.name}") + ip(f"link set dev {nk_host_a} up") + ip(f"link set dev {nk_guest_a} up", ns=netns) + + ip(f"link set dev {nk_guest_b} netns {netns.name}") + ip(f"link set dev {nk_host_b} up") + ip(f"link set dev {nk_guest_b} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + netdevnl.queue_create( + { + "ifindex": nk_guest_a_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + netdevnl.queue_create( + { + "ifindex": nk_guest_b_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 2, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + # Removing the physical device should take down both netkit pairs + nsimdev.remove() + time.sleep(0.1) + ret = cmd(f"ip link show dev {nk_host_a}", fail=False) + ksft_ne(ret.ret, 0) + ret = cmd(f"ip link show dev {nk_host_b}", fail=False) + ksft_ne(ret.ret, 0) + + +def test_single_remove_phys(_netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_name, nk_idx = create_netkit_single(rxqueues=2) + defer(cmd, f"ip link del dev {nk_name}", fail=False) + + ip(f"link set dev {nk_name} up") + + netdevnl = NetdevFamily() + netdevnl.queue_create( + { + "ifindex": nk_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + }, + } + ) + + # Removing the physical device should take down the single netkit device + nsimdev.remove() + time.sleep(0.1) + ret = cmd(f"ip link show dev {nk_name}", fail=False) + ksft_ne(ret.ret, 0) + + +def test_link_flap_virt(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}") + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + nk_queue_id = result["id"] + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) + + # Link flap the virtual (netkit) device + ip(f"link set dev {nk_guest} down", ns=netns) + ip(f"link set dev {nk_guest} up", ns=netns) + + # Verify lease survives the virtual device flap + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) + + +def test_phys_queue_no_lease(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}") + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + # Physical queue 0 (not leased) should have no lease info + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 0, "type": "rx"} + ) + ksft_not_in("lease", queue_info) + + # Physical queue 1 (leased) should have lease info + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + ksft_in("lease", queue_info) + + +def test_same_ns_lease(_netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_name, nk_idx = create_netkit_single(rxqueues=2) + defer(cmd, f"ip link del dev {nk_name}", fail=False) + + ip(f"link set dev {nk_name} up") + + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + }, + } + ) + ksft_eq(result["id"], 1) + + # Same namespace: lease info should NOT have netns-id + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["ifindex"], nk_idx) + ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) + ksft_not_in("netns-id", queue_info["lease"]) + + +def test_resize_after_unlease(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + # Resize should fail while lease is active + ethnl = EthtoolFamily() + with ksft_raises(NlError) as e: + ethnl.channels_set({"header": {"dev-index": nsim.ifindex}, "combined-count": 1}) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + # Delete netkit, clearing the lease + cmd(f"ip link del dev {nk_host}") + + # Resize should now succeed + ethnl.channels_set({"header": {"dev-index": nsim.ifindex}, "combined-count": 1}) + + +def test_lease_queue_zero(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 0, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(result["id"], 1) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 0, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) + + +def test_release_and_reuse(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + src_queue = 1 + + # First lease + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + + # Delete netkit, freeing the lease + cmd(f"ip link del dev {nk_host}") + + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_not_in("lease", queue_info) + + # Re-create netkit and lease the same physical queue again + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)): + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(result["id"], 1) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) + + +def main() -> None: + netns = NetNS() + cmd("ip netns attach init 1") + ip("netns set init 0", ns=netns) + ip("link set lo up", ns=netns) + + ksft_run( + [ + test_remove_phys, + test_double_lease, + test_virtual_lessor, + test_phys_lessee, + test_different_lessors, + test_queue_out_of_range, + test_resize_leased, + test_self_lease, + test_create_tx_type, + test_create_primary, + test_create_limit, + test_link_flap_phys, + test_queue_get_virtual, + test_remove_virt_first, + test_multiple_leases, + test_lease_queue_tx_type, + test_invalid_netns, + test_invalid_phys_ifindex, + test_multi_netkit_remove_phys, + test_single_remove_phys, + test_link_flap_virt, + test_phys_queue_no_lease, + test_same_ns_lease, + test_resize_after_unlease, + test_lease_queue_zero, + test_release_and_reuse, + test_veth_queue_create, + ], + args=(netns,), + ) + + cmd("ip netns del init", fail=False) + ksft_exit() + + +if __name__ == "__main__": + main() From 1e822171ba9bca7a5d2371bc10358340835bdad3 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Tue, 14 Apr 2026 00:08:06 +0200 Subject: [PATCH 3/3] selftests/net: Add additional test coverage in nk_qlease Add further netkit queue-lease coverage for netns lifecycle of the guest and physical halves, channel resize across active leases, single-device and multi-lessee scenarios, L3 mode operation, lease capacity exhaustion, and corner-cases of e.g. queue-create rejection paths. Also make the tests more robust by removing the time.sleep(0.1) after netns deletion and turn them into a wait loop. Full test run: # ./nk_qlease.py TAP version 13 1..45 ok 1 nk_qlease.test_remove_phys ok 2 nk_qlease.test_double_lease ok 3 nk_qlease.test_virtual_lessor ok 4 nk_qlease.test_phys_lessee ok 5 nk_qlease.test_different_lessors ok 6 nk_qlease.test_queue_out_of_range ok 7 nk_qlease.test_resize_leased ok 8 nk_qlease.test_self_lease ok 9 nk_qlease.test_create_tx_type ok 10 nk_qlease.test_create_primary ok 11 nk_qlease.test_create_limit ok 12 nk_qlease.test_link_flap_phys ok 13 nk_qlease.test_queue_get_virtual ok 14 nk_qlease.test_remove_virt_first ok 15 nk_qlease.test_multiple_leases ok 16 nk_qlease.test_lease_queue_tx_type ok 17 nk_qlease.test_invalid_netns ok 18 nk_qlease.test_invalid_phys_ifindex ok 19 nk_qlease.test_multi_netkit_remove_phys ok 20 nk_qlease.test_single_remove_phys ok 21 nk_qlease.test_link_flap_virt ok 22 nk_qlease.test_phys_queue_no_lease ok 23 nk_qlease.test_same_ns_lease ok 24 nk_qlease.test_resize_after_unlease ok 25 nk_qlease.test_lease_queue_zero ok 26 nk_qlease.test_release_and_reuse ok 27 nk_qlease.test_veth_queue_create ok 28 nk_qlease.test_two_netkits_same_queue ok 29 nk_qlease.test_l3_mode_lease ok 30 nk_qlease.test_single_double_lease ok 31 nk_qlease.test_single_different_lessors ok 32 nk_qlease.test_cross_ns_netns_id ok 33 nk_qlease.test_delete_guest_netns ok 34 nk_qlease.test_move_guest_netns ok 35 nk_qlease.test_resize_phys_no_reduction ok 36 nk_qlease.test_delete_one_netkit_of_two ok 37 nk_qlease.test_bind_rx_leased_phys_queue ok 38 nk_qlease.test_resize_phys_shrink_past_leased ok 39 nk_qlease.test_resize_virt_not_supported ok 40 nk_qlease.test_lease_devices_down ok 41 nk_qlease.test_lease_capacity_exhaustion ok 42 nk_qlease.test_resize_phys_up ok 43 nk_qlease.test_multi_ns_lease ok 44 nk_qlease.test_multi_ns_delete_one ok 45 nk_qlease.test_move_phys_netns # Totals: pass:45 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Daniel Borkmann Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260413220809.604592-4-daniel@iogearbox.net Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/nk_qlease.py | 951 ++++++++++++++++++++++- 1 file changed, 946 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/nk_qlease.py b/tools/testing/selftests/net/nk_qlease.py index 6ed4fb5e90f6..a84a73ff4eda 100755 --- a/tools/testing/selftests/net/nk_qlease.py +++ b/tools/testing/selftests/net/nk_qlease.py @@ -28,7 +28,16 @@ from lib.py import ( ip, ) -def create_netkit(rxqueues): + +def wait_until(cond, timeout=2.0, interval=0.05): + deadline = time.monotonic() + timeout + while not cond(): + if time.monotonic() >= deadline: + return + time.sleep(interval) + + +def create_netkit(rxqueues, mode="l2"): all_links = ip("-d link show", json=True) old_idxs = { link["ifindex"] @@ -42,7 +51,7 @@ def create_netkit(rxqueues): "linkinfo": { "kind": "netkit", "data": { - "mode": "l2", + "mode": mode, "policy": "forward", "peer-policy": "forward", }, @@ -93,6 +102,7 @@ def create_netkit_single(rxqueues): ] return nk_links[0]["ifname"], nk_links[0]["ifindex"] + def test_remove_phys(netns) -> None: nsimdev = NetdevSimDev(port_count=1, queue_count=2) defer(nsimdev.remove) @@ -131,7 +141,7 @@ def test_remove_phys(netns) -> None: ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) nsimdev.remove() - time.sleep(0.1) + wait_until(lambda: cmd(f"ip link show dev {nk_host}", fail=False).ret != 0) ret = cmd(f"ip link show dev {nk_host}", fail=False) ksft_ne(ret.ret, 0) @@ -812,7 +822,8 @@ def test_multi_netkit_remove_phys(netns) -> None: # Removing the physical device should take down both netkit pairs nsimdev.remove() - time.sleep(0.1) + wait_until(lambda: cmd(f"ip link show dev {nk_host_a}", fail=False).ret != 0 + and cmd(f"ip link show dev {nk_host_b}", fail=False).ret != 0) ret = cmd(f"ip link show dev {nk_host_a}", fail=False) ksft_ne(ret.ret, 0) ret = cmd(f"ip link show dev {nk_host_b}", fail=False) @@ -844,7 +855,7 @@ def test_single_remove_phys(_netns) -> None: # Removing the physical device should take down the single netkit device nsimdev.remove() - time.sleep(0.1) + wait_until(lambda: cmd(f"ip link show dev {nk_name}", fail=False).ret != 0) ret = cmd(f"ip link show dev {nk_name}", fail=False) ksft_ne(ret.ret, 0) @@ -1121,6 +1132,918 @@ def test_release_and_reuse(netns) -> None: ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) +def test_two_netkits_same_queue(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_a}", fail=False) + + nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_b}", fail=False) + + ip(f"link set dev {nk_guest_a} netns {netns.name}") + ip(f"link set dev {nk_host_a} up") + ip(f"link set dev {nk_guest_a} up", ns=netns) + + ip(f"link set dev {nk_guest_b} netns {netns.name}") + ip(f"link set dev {nk_host_b} up") + ip(f"link set dev {nk_guest_b} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_a_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + with ksft_raises(NlError) as e: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_b_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EBUSY) + + +def test_l3_mode_lease(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2, mode="l3") + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + result = netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(result["id"], 1) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["ifindex"], nk_guest_idx) + ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) + + +def test_single_double_lease(_netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_name, nk_idx = create_netkit_single(rxqueues=3) + defer(cmd, f"ip link del dev {nk_name}", fail=False) + + ip(f"link set dev {nk_name} up") + + netdevnl = NetdevFamily() + result = netdevnl.queue_create( + { + "ifindex": nk_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + }, + } + ) + ksft_eq(result["id"], 1) + + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EBUSY) + + +def test_single_different_lessors(_netns) -> None: + nsimdev_a = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev_a.remove) + nsim_a = nsimdev_a.nsims[0] + ip(f"link set dev {nsim_a.ifname} up") + + nsimdev_b = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev_b.remove) + nsim_b = nsimdev_b.nsims[0] + ip(f"link set dev {nsim_b.ifname} up") + + nk_name, nk_idx = create_netkit_single(rxqueues=3) + defer(cmd, f"ip link del dev {nk_name}", fail=False) + + ip(f"link set dev {nk_name} up") + + netdevnl = NetdevFamily() + netdevnl.queue_create( + { + "ifindex": nk_idx, + "type": "rx", + "lease": { + "ifindex": nsim_a.ifindex, + "queue": {"id": 1, "type": "rx"}, + }, + } + ) + + with ksft_raises(NlError) as e: + netdevnl.queue_create( + { + "ifindex": nk_idx, + "type": "rx", + "lease": { + "ifindex": nsim_b.ifindex, + "queue": {"id": 1, "type": "rx"}, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP) + + +def test_cross_ns_netns_id(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_in("netns-id", queue_info["lease"]) + + +def test_delete_guest_netns(_netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + test_ns = NetNS() + ip("netns set init 0", ns=test_ns) + ip("link set lo up", ns=test_ns) + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {test_ns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=test_ns) + + src_queue = 1 + with NetNSEnter(str(test_ns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + + del test_ns + wait_until(lambda: "lease" not in netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"})) + + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_not_in("lease", queue_info) + + ret = cmd(f"ip link show dev {nk_host}", fail=False) + ksft_ne(ret.ret, 0) + + +def test_move_guest_netns(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + result = netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + nk_queue_id = result["id"] + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) + + new_ns = NetNS() + defer(new_ns.__del__) + ip(f"link set dev {nk_guest} netns {new_ns.name}", ns=netns) + + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) + + +def test_resize_phys_no_reduction(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + ethnl = EthtoolFamily() + ethnl.channels_set( + {"header": {"dev-index": nsim.ifindex}, "combined-count": 2} + ) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + ksft_in("lease", queue_info) + + +def test_delete_one_netkit_of_two(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=3) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_a}", fail=False) + + nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_b}", fail=False) + + ip(f"link set dev {nk_guest_a} netns {netns.name}") + ip(f"link set dev {nk_host_a} up") + ip(f"link set dev {nk_guest_a} up", ns=netns) + + ip(f"link set dev {nk_guest_b} netns {netns.name}") + ip(f"link set dev {nk_host_b} up") + ip(f"link set dev {nk_guest_b} up", ns=netns) + + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_a_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_b_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 2, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + netdevnl = NetdevFamily() + q1 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + q2 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 2, "type": "rx"} + ) + ksft_in("lease", q1) + ksft_in("lease", q2) + + cmd(f"ip link del dev {nk_host_a}") + + q1 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + q2 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 2, "type": "rx"} + ) + ksft_not_in("lease", q1) + ksft_in("lease", q2) + + +def test_bind_rx_leased_phys_queue(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + netdevnl = NetdevFamily() + with ksft_raises(NlError) as e: + netdevnl.bind_rx( + { + "ifindex": nsim.ifindex, + "fd": 0, + "queues": [ + {"id": 0, "type": "rx"}, + {"id": 1, "type": "rx"}, + ], + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP) + + +def test_resize_phys_shrink_past_leased(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=4) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + ethnl = EthtoolFamily() + + # Shrink past the leased queue — only queue 3 removed, queue 1 untouched + ethnl.channels_set( + {"header": {"dev-index": nsim.ifindex}, "combined-count": 3} + ) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + ksft_in("lease", queue_info) + + # Shrink further — queue 2 removed, queue 1 still untouched + ethnl.channels_set( + {"header": {"dev-index": nsim.ifindex}, "combined-count": 2} + ) + + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + ksft_in("lease", queue_info) + + # Shrink into the leased queue — queue 1 is busy, must fail + with ksft_raises(NlError) as e: + ethnl.channels_set( + {"header": {"dev-index": nsim.ifindex}, "combined-count": 1} + ) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + +def test_resize_virt_not_supported(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, nk_host_idx, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + # Channel resize on the netkit host must fail — not supported + ethnl = EthtoolFamily() + with ksft_raises(NlError) as e: + ethnl.channels_set( + {"header": {"dev-index": nk_host_idx}, "combined-count": 1} + ) + ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP) + + # Lease must be intact + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + ksft_in("lease", queue_info) + + +def test_lease_devices_down(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + + # Create lease while both physical and virtual devices are down + src_queue = 1 + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + result = netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(result["id"], 1) + + # Bring devices up before queue_get: netdevsim only instantiates NAPIs in + # ndo_open, and netdev-genl queue_get returns -ENOENT without a NAPI. + ip(f"link set dev {nsim.ifname} up") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], result["id"]) + + +def test_lease_capacity_exhaustion(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=4) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + # rxqueues=3 means num_rx_queues=3, real_num_rx_queues starts at 1. + # Can create 2 leased queues (real goes 1->2->3) but not a 3rd (3->4 > 3). + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=3) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + r1 = netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(r1["id"], 1) + + r2 = netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 2, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(r2["id"], 2) + + # Third lease fails — netkit queue capacity exhausted + with ksft_raises(NlError) as e: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 3, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(e.exception.nl_msg.error, -errno.EINVAL) + + # Verify the two successful leases are intact + netdevnl = NetdevFamily() + q1 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + q2 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 2, "type": "rx"} + ) + ksft_in("lease", q1) + ksft_in("lease", q2) + + +def test_resize_phys_up(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=3) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + # Shrink nsim first so we have room to grow + ethnl = EthtoolFamily() + ethnl.channels_set( + {"header": {"dev-index": nsim.ifindex}, "combined-count": 2} + ) + + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + # Grow channels — should succeed since leased queue is not removed + ethnl.channels_set( + {"header": {"dev-index": nsim.ifindex}, "combined-count": 3} + ) + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + ksft_in("lease", queue_info) + + # New queue 2 should exist without a lease + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 2, "type": "rx"} + ) + ksft_not_in("lease", queue_info) + + +def test_multi_ns_lease(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=3) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + ns_b = NetNS() + defer(ns_b.__del__) + ip("netns set init 0", ns=ns_b) + ip("link set lo up", ns=ns_b) + + # First netkit pair, guest in netns + nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_a}", fail=False) + ip(f"link set dev {nk_guest_a} netns {netns.name}") + ip(f"link set dev {nk_host_a} up") + ip(f"link set dev {nk_guest_a} up", ns=netns) + + # Second netkit pair, guest in ns_b + nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_b}", fail=False) + ip(f"link set dev {nk_guest_b} netns {ns_b.name}") + ip(f"link set dev {nk_host_b} up") + ip(f"link set dev {nk_guest_b} up", ns=ns_b) + + # Lease from netns + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + result = netdevnl_ns.queue_create( + { + "ifindex": nk_guest_a_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(result["id"], 1) + + # Lease from ns_b (different namespace, same physical device) + with NetNSEnter(str(ns_b)), NetdevFamily() as netdevnl_ns: + result = netdevnl_ns.queue_create( + { + "ifindex": nk_guest_b_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 2, "type": "rx"}, + "netns-id": 0, + }, + } + ) + ksft_eq(result["id"], 1) + + # Verify both leases from the physical side + netdevnl = NetdevFamily() + q1 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + q2 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 2, "type": "rx"} + ) + ksft_in("lease", q1) + ksft_in("lease", q2) + ksft_eq(q1["lease"]["ifindex"], nk_guest_a_idx) + ksft_eq(q2["lease"]["ifindex"], nk_guest_b_idx) + + +def test_multi_ns_delete_one(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=3) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + ns_b = NetNS() + ip("netns set init 0", ns=ns_b) + ip("link set lo up", ns=ns_b) + + # First netkit pair, guest in netns (ns_a) + nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_a}", fail=False) + ip(f"link set dev {nk_guest_a} netns {netns.name}") + ip(f"link set dev {nk_host_a} up") + ip(f"link set dev {nk_guest_a} up", ns=netns) + + # Second netkit pair, guest in ns_b + nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host_b}", fail=False) + + ip(f"link set dev {nk_guest_b} netns {ns_b.name}") + ip(f"link set dev {nk_host_b} up") + ip(f"link set dev {nk_guest_b} up", ns=ns_b) + + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_a_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 1, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + with NetNSEnter(str(ns_b)), NetdevFamily() as netdevnl_ns: + netdevnl_ns.queue_create( + { + "ifindex": nk_guest_b_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": 2, "type": "rx"}, + "netns-id": 0, + }, + } + ) + + netdevnl = NetdevFamily() + q1 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + q2 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 2, "type": "rx"} + ) + ksft_in("lease", q1) + ksft_in("lease", q2) + + # Delete ns_b — destroys nk_guest_b, triggers unlease of queue 2 + del ns_b + wait_until(lambda: "lease" not in netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 2, "type": "rx"})) + + # ns_a's lease on queue 1 must survive + q1 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 1, "type": "rx"} + ) + ksft_in("lease", q1) + ksft_eq(q1["lease"]["ifindex"], nk_guest_a_idx) + + # ns_b's lease on queue 2 must be gone + q2 = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": 2, "type": "rx"} + ) + ksft_not_in("lease", q2) + + # nk_host_b should be gone too (phys removal cascades to netkit pair) + ret = cmd(f"ip link show dev {nk_host_b}", fail=False) + ksft_ne(ret.ret, 0) + + +def test_move_phys_netns(netns) -> None: + nsimdev = NetdevSimDev(port_count=1, queue_count=2) + defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + ip(f"link set dev {nsim.ifname} up") + + nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2) + defer(cmd, f"ip link del dev {nk_host}", fail=False) + + ip(f"link set dev {nk_guest} netns {netns.name}") + ip(f"link set dev {nk_host} up") + ip(f"link set dev {nk_guest} up", ns=netns) + + src_queue = 1 + with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns: + nk_queue_id = netdevnl_ns.queue_create( + { + "ifindex": nk_guest_idx, + "type": "rx", + "lease": { + "ifindex": nsim.ifindex, + "queue": {"id": src_queue, "type": "rx"}, + "netns-id": 0, + }, + } + )["id"] + + netdevnl = NetdevFamily() + queue_info = netdevnl.queue_get( + {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + + # Move the physical device to a new namespace. Move it back to init_net + # on cleanup before the other defers fire (new_ns deletion, nsimdev.remove) + # so nsim lives in a stable namespace when they run. + new_ns = NetNS() + defer(new_ns.__del__) + ip(f"link set dev {nsim.ifname} netns {new_ns.name}") + defer(ip, f"link set dev {nsim.ifname} netns init", ns=new_ns) + + # Physical device is now in new_ns — find its ifindex there + all_links = ip("-d link show", json=True, ns=new_ns) + nsim_in_new = [lnk for lnk in all_links if lnk.get("ifname") == nsim.ifname] + new_ifindex = nsim_in_new[0]["ifindex"] + + # Moving a device across netns brings it admin-down; bring it back up so + # netdevsim re-creates the NAPI (netdev-genl queue_get needs it). + ip(f"link set dev {nsim.ifname} up", ns=new_ns) + + # Verify lease survived the namespace move + with NetNSEnter(str(new_ns)), NetdevFamily() as netdevnl_ns: + queue_info = netdevnl_ns.queue_get( + {"ifindex": new_ifindex, "id": src_queue, "type": "rx"} + ) + ksft_in("lease", queue_info) + ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id) + + def main() -> None: netns = NetNS() cmd("ip netns attach init 1") @@ -1156,6 +2079,24 @@ def main() -> None: test_lease_queue_zero, test_release_and_reuse, test_veth_queue_create, + test_two_netkits_same_queue, + test_l3_mode_lease, + test_single_double_lease, + test_single_different_lessors, + test_cross_ns_netns_id, + test_delete_guest_netns, + test_move_guest_netns, + test_resize_phys_no_reduction, + test_delete_one_netkit_of_two, + test_bind_rx_leased_phys_queue, + test_resize_phys_shrink_past_leased, + test_resize_virt_not_supported, + test_lease_devices_down, + test_lease_capacity_exhaustion, + test_resize_phys_up, + test_multi_ns_lease, + test_multi_ns_delete_one, + test_move_phys_netns, ], args=(netns,), )