public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests
@ 2026-04-13 22:08 Daniel Borkmann
  2026-04-13 22:08 ` [PATCH net-next v2 1/3] tools/ynl: Make YnlFamily closeable as a context manager Daniel Borkmann
                   ` (4 more replies)
  0 siblings, 5 replies; 11+ messages in thread
From: Daniel Borkmann @ 2026-04-13 22:08 UTC (permalink / raw)
  To: netdev; +Cc: kuba, dw, pabeni, razor

This is a set of follow-ups addressing [0]:

- Split netdevsim tests from HW tests in nk_qlease and move the SW
  tests under selftests/net/
- Remove multiple ksft_run()s to fix the recently enforced hard-fail
- Move all the setup inside the test cases for the ones under
  selftests/net/ (I'll defer the HW ones to David)
- Add more test coverage related to queue leasing behavior and corner
  cases, so now we have 45 tests in nk_qlease.py with netdevsim
  which does not need special HW

  [0] https://lore.kernel.org/netdev/20260409181950.7e099b6c@kernel.org

v1->v2:
 - Fixed ruff ambiguous variable name
 - Fixed https://sashiko.dev/#/patchset/20260413114011.588162-1-daniel%40iogearbox.net
   findings except the one in patch 1 since if something goes wrong
   there, then the test fails and socket gets cleaned up via cyclic
   GC run anyway

Daniel Borkmann (3):
  tools/ynl: Make YnlFamily closeable as a context manager
  selftests/net: Split netdevsim tests from HW tests in nk_qlease
  selftests/net: Add additional test coverage in nk_qlease

 tools/net/ynl/pyynl/lib/ynl.py                |   10 +
 .../selftests/drivers/net/hw/nk_qlease.py     | 1142 ---------
 tools/testing/selftests/net/Makefile          |    1 +
 tools/testing/selftests/net/nk_qlease.py      | 2109 +++++++++++++++++
 4 files changed, 2120 insertions(+), 1142 deletions(-)
 create mode 100755 tools/testing/selftests/net/nk_qlease.py

-- 
2.43.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH net-next v2 1/3] tools/ynl: Make YnlFamily closeable as a context manager
  2026-04-13 22:08 [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
@ 2026-04-13 22:08 ` Daniel Borkmann
  2026-04-14  5:57   ` Nikolay Aleksandrov
  2026-04-13 22:08 ` [PATCH net-next v2 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease Daniel Borkmann
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: Daniel Borkmann @ 2026-04-13 22:08 UTC (permalink / raw)
  To: netdev; +Cc: kuba, dw, pabeni, razor

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 <daniel@iogearbox.net>
---
 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)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next v2 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease
  2026-04-13 22:08 [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
  2026-04-13 22:08 ` [PATCH net-next v2 1/3] tools/ynl: Make YnlFamily closeable as a context manager Daniel Borkmann
@ 2026-04-13 22:08 ` Daniel Borkmann
  2026-04-14  5:58   ` Nikolay Aleksandrov
  2026-04-13 22:08 ` [PATCH net-next v2 3/3] selftests/net: Add additional test coverage " Daniel Borkmann
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: Daniel Borkmann @ 2026-04-13 22:08 UTC (permalink / raw)
  To: netdev; +Cc: kuba, dw, pabeni, razor

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 <daniel@iogearbox.net>
Link: https://lore.kernel.org/netdev/20260409181950.7e099b6c@kernel.org
---
 .../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()
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next v2 3/3] selftests/net: Add additional test coverage in nk_qlease
  2026-04-13 22:08 [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
  2026-04-13 22:08 ` [PATCH net-next v2 1/3] tools/ynl: Make YnlFamily closeable as a context manager Daniel Borkmann
  2026-04-13 22:08 ` [PATCH net-next v2 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease Daniel Borkmann
@ 2026-04-13 22:08 ` Daniel Borkmann
  2026-04-14  5:59   ` Nikolay Aleksandrov
  2026-04-14  2:12 ` [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Jakub Kicinski
  2026-04-14 15:50 ` patchwork-bot+netdevbpf
  4 siblings, 1 reply; 11+ messages in thread
From: Daniel Borkmann @ 2026-04-13 22:08 UTC (permalink / raw)
  To: netdev; +Cc: kuba, dw, pabeni, razor

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 <daniel@iogearbox.net>
---
 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,),
     )
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests
  2026-04-13 22:08 [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
                   ` (2 preceding siblings ...)
  2026-04-13 22:08 ` [PATCH net-next v2 3/3] selftests/net: Add additional test coverage " Daniel Borkmann
@ 2026-04-14  2:12 ` Jakub Kicinski
  2026-04-14  7:33   ` Daniel Borkmann
  2026-04-14 15:50 ` patchwork-bot+netdevbpf
  4 siblings, 1 reply; 11+ messages in thread
From: Jakub Kicinski @ 2026-04-14  2:12 UTC (permalink / raw)
  To: Daniel Borkmann; +Cc: netdev, dw, pabeni, razor

On Tue, 14 Apr 2026 00:08:03 +0200 Daniel Borkmann wrote:
> This is a set of follow-ups addressing [0]:
> 
> - Split netdevsim tests from HW tests in nk_qlease and move the SW
>   tests under selftests/net/
> - Remove multiple ksft_run()s to fix the recently enforced hard-fail
> - Move all the setup inside the test cases for the ones under
>   selftests/net/ (I'll defer the HW ones to David)
> - Add more test coverage related to queue leasing behavior and corner
>   cases, so now we have 45 tests in nk_qlease.py with netdevsim
>   which does not need special HW

LGTM, thanks!

I'll let it run overnight in the CI to shake out any latent flakiness
(and the crash which I think is from Stan's series).

Could you cook up one more follow up to enable VETH in the config?
We're getting:

# # Exception| Traceback (most recent call last):
# # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/ksft.py", line 420, in ksft_run
# # Exception|     func(*args)
# # Exception|     ~~~~^^^^^^^
# # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/drivers/net/hw/./nk_qlease.py", line 393, in test_veth_queue_create
# # Exception|     ip("link add veth0 type veth peer name veth1")
# # Exception|     ~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 238, in ip
# # Exception|     return tool('ip', args, json=json, host=host)
# # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 225, in tool
# # Exception|     cmd_obj = cmd(cmd_str, ns=ns, host=host)
# # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 91, in __init__
# # Exception|     self.process(terminate=False, fail=fail, timeout=timeout)
# # Exception|     ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 117, in process
# # Exception|     raise CmdExitFailure("Command failed", self)
# # Exception| net.lib.py.utils.CmdExitFailure: Command failed
# # Exception| CMD: ip link add veth0 type veth peer name veth1
# # Exception|   EXIT: 2
# # Exception|   STDERR: Error: Unknown device type.
# # Exception| 
# not ok 27 nk_qlease.test_veth_queue_create


I guess you can post it without waiting for this to be merged, it won't
conflict.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v2 1/3] tools/ynl: Make YnlFamily closeable as a context manager
  2026-04-13 22:08 ` [PATCH net-next v2 1/3] tools/ynl: Make YnlFamily closeable as a context manager Daniel Borkmann
@ 2026-04-14  5:57   ` Nikolay Aleksandrov
  0 siblings, 0 replies; 11+ messages in thread
From: Nikolay Aleksandrov @ 2026-04-14  5:57 UTC (permalink / raw)
  To: Daniel Borkmann, netdev; +Cc: kuba, dw, pabeni

On 4/14/26 01:08, Daniel Borkmann wrote:
> 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 <daniel@iogearbox.net>
> ---
>  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)

Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v2 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease
  2026-04-13 22:08 ` [PATCH net-next v2 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease Daniel Borkmann
@ 2026-04-14  5:58   ` Nikolay Aleksandrov
  0 siblings, 0 replies; 11+ messages in thread
From: Nikolay Aleksandrov @ 2026-04-14  5:58 UTC (permalink / raw)
  To: Daniel Borkmann, netdev; +Cc: kuba, dw, pabeni

On 4/14/26 01:08, Daniel Borkmann wrote:
> 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 <daniel@iogearbox.net>
> Link: https://lore.kernel.org/netdev/20260409181950.7e099b6c@kernel.org
> ---
>  .../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
> 

Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v2 3/3] selftests/net: Add additional test coverage in nk_qlease
  2026-04-13 22:08 ` [PATCH net-next v2 3/3] selftests/net: Add additional test coverage " Daniel Borkmann
@ 2026-04-14  5:59   ` Nikolay Aleksandrov
  0 siblings, 0 replies; 11+ messages in thread
From: Nikolay Aleksandrov @ 2026-04-14  5:59 UTC (permalink / raw)
  To: Daniel Borkmann, netdev; +Cc: kuba, dw, pabeni

On 4/14/26 01:08, Daniel Borkmann wrote:
> 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 <daniel@iogearbox.net>
> ---
>  tools/testing/selftests/net/nk_qlease.py | 951 ++++++++++++++++++++++-
>  1 file changed, 946 insertions(+), 5 deletions(-)
> 

Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests
  2026-04-14  2:12 ` [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Jakub Kicinski
@ 2026-04-14  7:33   ` Daniel Borkmann
  2026-04-14  7:51     ` Daniel Borkmann
  0 siblings, 1 reply; 11+ messages in thread
From: Daniel Borkmann @ 2026-04-14  7:33 UTC (permalink / raw)
  To: Jakub Kicinski; +Cc: netdev, dw, pabeni, razor

On 4/14/26 4:12 AM, Jakub Kicinski wrote:
> On Tue, 14 Apr 2026 00:08:03 +0200 Daniel Borkmann wrote:
>> This is a set of follow-ups addressing [0]:
>>
>> - Split netdevsim tests from HW tests in nk_qlease and move the SW
>>    tests under selftests/net/
>> - Remove multiple ksft_run()s to fix the recently enforced hard-fail
>> - Move all the setup inside the test cases for the ones under
>>    selftests/net/ (I'll defer the HW ones to David)
>> - Add more test coverage related to queue leasing behavior and corner
>>    cases, so now we have 45 tests in nk_qlease.py with netdevsim
>>    which does not need special HW
> 
> LGTM, thanks!
> 
> I'll let it run overnight in the CI to shake out any latent flakiness
> (and the crash which I think is from Stan's series).
> 
> Could you cook up one more follow up to enable VETH in the config?
> We're getting:
> 
> # # Exception| Traceback (most recent call last):
> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/ksft.py", line 420, in ksft_run
> # # Exception|     func(*args)
> # # Exception|     ~~~~^^^^^^^
> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/drivers/net/hw/./nk_qlease.py", line 393, in test_veth_queue_create
> # # Exception|     ip("link add veth0 type veth peer name veth1")
> # # Exception|     ~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 238, in ip
> # # Exception|     return tool('ip', args, json=json, host=host)
> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 225, in tool
> # # Exception|     cmd_obj = cmd(cmd_str, ns=ns, host=host)
> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 91, in __init__
> # # Exception|     self.process(terminate=False, fail=fail, timeout=timeout)
> # # Exception|     ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 117, in process
> # # Exception|     raise CmdExitFailure("Command failed", self)
> # # Exception| net.lib.py.utils.CmdExitFailure: Command failed
> # # Exception| CMD: ip link add veth0 type veth peer name veth1
> # # Exception|   EXIT: 2
> # # Exception|   STDERR: Error: Unknown device type.
> # # Exception|
> # not ok 27 nk_qlease.test_veth_queue_create
> 
> I guess you can post it without waiting for this to be merged, it won't
> conflict.

Ack, will take a look! Thanks!

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests
  2026-04-14  7:33   ` Daniel Borkmann
@ 2026-04-14  7:51     ` Daniel Borkmann
  0 siblings, 0 replies; 11+ messages in thread
From: Daniel Borkmann @ 2026-04-14  7:51 UTC (permalink / raw)
  To: Jakub Kicinski; +Cc: netdev, dw, pabeni, razor

On 4/14/26 9:33 AM, Daniel Borkmann wrote:
> On 4/14/26 4:12 AM, Jakub Kicinski wrote:
>> On Tue, 14 Apr 2026 00:08:03 +0200 Daniel Borkmann wrote:
>>> This is a set of follow-ups addressing [0]:
>>>
>>> - Split netdevsim tests from HW tests in nk_qlease and move the SW
>>>    tests under selftests/net/
>>> - Remove multiple ksft_run()s to fix the recently enforced hard-fail
>>> - Move all the setup inside the test cases for the ones under
>>>    selftests/net/ (I'll defer the HW ones to David)
>>> - Add more test coverage related to queue leasing behavior and corner
>>>    cases, so now we have 45 tests in nk_qlease.py with netdevsim
>>>    which does not need special HW
>>
>> LGTM, thanks!
>>
>> I'll let it run overnight in the CI to shake out any latent flakiness
>> (and the crash which I think is from Stan's series).
>>
>> Could you cook up one more follow up to enable VETH in the config?
>> We're getting:
>>
>> # # Exception| Traceback (most recent call last):
>> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/ksft.py", line 420, in ksft_run
>> # # Exception|     func(*args)
>> # # Exception|     ~~~~^^^^^^^
>> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/drivers/net/hw/./nk_qlease.py", line 393, in test_veth_queue_create
>> # # Exception|     ip("link add veth0 type veth peer name veth1")
>> # # Exception|     ~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 238, in ip
>> # # Exception|     return tool('ip', args, json=json, host=host)
>> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 225, in tool
>> # # Exception|     cmd_obj = cmd(cmd_str, ns=ns, host=host)
>> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 91, in __init__
>> # # Exception|     self.process(terminate=False, fail=fail, timeout=timeout)
>> # # Exception|     ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>> # # Exception|   File "/srv/vmksft/testing/wt-24/tools/testing/selftests/net/lib/py/utils.py", line 117, in process
>> # # Exception|     raise CmdExitFailure("Command failed", self)
>> # # Exception| net.lib.py.utils.CmdExitFailure: Command failed
>> # # Exception| CMD: ip link add veth0 type veth peer name veth1
>> # # Exception|   EXIT: 2
>> # # Exception|   STDERR: Error: Unknown device type.
>> # # Exception|
>> # not ok 27 nk_qlease.test_veth_queue_create
>>
>> I guess you can post it without waiting for this to be merged, it won't
>> conflict.
> 
> Ack, will take a look! Thanks!

After this series here, there is no veth test left anymore under
tools/testing/selftests/drivers/net/hw/ and they moved over to the
tools/testing/selftests/net/nk_qlease.py which already has the needed
CONFIG_VETH=y (in tools/testing/selftests/net/config).

Stan's series was run where this one here is not in the tree yet, so
if we would add CONFIG_VETH=y into tools/testing/selftests/drivers/net/hw/config
it would be unnecessary - I presume we don't want to add in that case.

Thanks,
Daniel

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests
  2026-04-13 22:08 [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
                   ` (3 preceding siblings ...)
  2026-04-14  2:12 ` [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Jakub Kicinski
@ 2026-04-14 15:50 ` patchwork-bot+netdevbpf
  4 siblings, 0 replies; 11+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-04-14 15:50 UTC (permalink / raw)
  To: Daniel Borkmann; +Cc: netdev, kuba, dw, pabeni, razor

Hello:

This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Tue, 14 Apr 2026 00:08:03 +0200 you wrote:
> This is a set of follow-ups addressing [0]:
> 
> - Split netdevsim tests from HW tests in nk_qlease and move the SW
>   tests under selftests/net/
> - Remove multiple ksft_run()s to fix the recently enforced hard-fail
> - Move all the setup inside the test cases for the ones under
>   selftests/net/ (I'll defer the HW ones to David)
> - Add more test coverage related to queue leasing behavior and corner
>   cases, so now we have 45 tests in nk_qlease.py with netdevsim
>   which does not need special HW
> 
> [...]

Here is the summary with links:
  - [net-next,v2,1/3] tools/ynl: Make YnlFamily closeable as a context manager
    https://git.kernel.org/netdev/net-next/c/4a6fe5fe6004
  - [net-next,v2,2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease
    https://git.kernel.org/netdev/net-next/c/e254ffb9502c
  - [net-next,v2,3/3] selftests/net: Add additional test coverage in nk_qlease
    https://git.kernel.org/netdev/net-next/c/1e822171ba9b

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-04-14 15:50 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-13 22:08 [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
2026-04-13 22:08 ` [PATCH net-next v2 1/3] tools/ynl: Make YnlFamily closeable as a context manager Daniel Borkmann
2026-04-14  5:57   ` Nikolay Aleksandrov
2026-04-13 22:08 ` [PATCH net-next v2 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease Daniel Borkmann
2026-04-14  5:58   ` Nikolay Aleksandrov
2026-04-13 22:08 ` [PATCH net-next v2 3/3] selftests/net: Add additional test coverage " Daniel Borkmann
2026-04-14  5:59   ` Nikolay Aleksandrov
2026-04-14  2:12 ` [PATCH net-next v2 0/3] Follow-ups to nk_qlease net selftests Jakub Kicinski
2026-04-14  7:33   ` Daniel Borkmann
2026-04-14  7:51     ` Daniel Borkmann
2026-04-14 15:50 ` patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox