* [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests
@ 2026-04-13 11:40 Daniel Borkmann
2026-04-13 11:40 ` [PATCH net-next 1/3] tools/ynl: Make YnlFamily closeable as a context manager Daniel Borkmann
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Daniel Borkmann @ 2026-04-13 11:40 UTC (permalink / raw)
To: kuba; +Cc: pabeni, dw, razor, netdev
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
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 | 2097 +++++++++++++++++
4 files changed, 2108 insertions(+), 1142 deletions(-)
create mode 100755 tools/testing/selftests/net/nk_qlease.py
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next 1/3] tools/ynl: Make YnlFamily closeable as a context manager
2026-04-13 11:40 [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
@ 2026-04-13 11:40 ` Daniel Borkmann
2026-04-13 11:40 ` [PATCH net-next 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease Daniel Borkmann
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Daniel Borkmann @ 2026-04-13 11:40 UTC (permalink / raw)
To: kuba; +Cc: pabeni, dw, razor, netdev
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] 6+ messages in thread
* [PATCH net-next 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease
2026-04-13 11:40 [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
2026-04-13 11:40 ` [PATCH net-next 1/3] tools/ynl: Make YnlFamily closeable as a context manager Daniel Borkmann
@ 2026-04-13 11:40 ` Daniel Borkmann
2026-04-13 11:40 ` [PATCH net-next 3/3] selftests/net: Add additional test coverage " Daniel Borkmann
2026-04-13 13:02 ` [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
3 siblings, 0 replies; 6+ messages in thread
From: Daniel Borkmann @ 2026-04-13 11:40 UTC (permalink / raw)
To: kuba; +Cc: pabeni, dw, razor, netdev
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] 6+ messages in thread
* [PATCH net-next 3/3] selftests/net: Add additional test coverage in nk_qlease
2026-04-13 11:40 [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
2026-04-13 11:40 ` [PATCH net-next 1/3] tools/ynl: Make YnlFamily closeable as a context manager Daniel Borkmann
2026-04-13 11:40 ` [PATCH net-next 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease Daniel Borkmann
@ 2026-04-13 11:40 ` Daniel Borkmann
2026-04-13 13:02 ` [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
3 siblings, 0 replies; 6+ messages in thread
From: Daniel Borkmann @ 2026-04-13 11:40 UTC (permalink / raw)
To: kuba; +Cc: pabeni, dw, razor, netdev
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.
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 | 933 ++++++++++++++++++++++-
1 file changed, 931 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/net/nk_qlease.py b/tools/testing/selftests/net/nk_qlease.py
index 6ed4fb5e90f6..df35c82bccfc 100755
--- a/tools/testing/selftests/net/nk_qlease.py
+++ b/tools/testing/selftests/net/nk_qlease.py
@@ -28,7 +28,8 @@ from lib.py import (
ip,
)
-def create_netkit(rxqueues):
+
+def create_netkit(rxqueues, mode="l2"):
all_links = ip("-d link show", json=True)
old_idxs = {
link["ifindex"]
@@ -42,7 +43,7 @@ def create_netkit(rxqueues):
"linkinfo": {
"kind": "netkit",
"data": {
- "mode": "l2",
+ "mode": mode,
"policy": "forward",
"peer-policy": "forward",
},
@@ -93,6 +94,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)
@@ -1121,6 +1123,915 @@ 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)
+
+ 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
+ time.sleep(0.1)
+
+ 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:
+ r_a = 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(r_a["id"], 1)
+
+ # Lease from ns_b (different namespace, same physical device)
+ with NetNSEnter(str(ns_b)), NetdevFamily() as netdevnl_ns:
+ r_b = 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(r_b["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)
+
+ 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
+ time.sleep(0.1)
+
+ # 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:
+ 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)
+
+ # 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 = [l for l in all_links if l.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 +2067,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] 6+ messages in thread
* Re: [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests
2026-04-13 11:40 [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
` (2 preceding siblings ...)
2026-04-13 11:40 ` [PATCH net-next 3/3] selftests/net: Add additional test coverage " Daniel Borkmann
@ 2026-04-13 13:02 ` Daniel Borkmann
2026-04-13 21:32 ` Jakub Kicinski
3 siblings, 1 reply; 6+ messages in thread
From: Daniel Borkmann @ 2026-04-13 13:02 UTC (permalink / raw)
To: kuba; +Cc: pabeni, dw, razor, netdev
On 4/13/26 1:40 PM, 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
>
> [0] https://lore.kernel.org/netdev/20260409181950.7e099b6c@kernel.org
Few comments on the sashiko and ruff review [1,2]:
- re: "the socket would stay open until the cyclic garbage collector runs"
imho that is fine since this would mean there's an error somewhere and
test does not run as expected / would fail, and socket is still being
closed eventually
- re "del test_ns" with sleep to wait for cleanup_net.. was done similarly
as in already merged patches, I can think of a different/better way with
a wait loop where applicable to remove any potential for flakiness
- The other things flagged by Gemini also make sense
- Missed the ruff one in "[E741] Ambiguous variable name: `l`" will fix
I'm planning to address these in a v2 of the series, but as per netdev rule
will wait 24h before resend unless you'd like me to explicitly resend earlier
(given merge win timing).
Thanks,
Daniel
[1] https://sashiko.dev/#/patchset/20260413114011.588162-1-daniel%40iogearbox.net
[2] https://patchwork.kernel.org/project/netdevbpf/list/?series=1080682
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests
2026-04-13 13:02 ` [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
@ 2026-04-13 21:32 ` Jakub Kicinski
0 siblings, 0 replies; 6+ messages in thread
From: Jakub Kicinski @ 2026-04-13 21:32 UTC (permalink / raw)
To: Daniel Borkmann; +Cc: pabeni, dw, razor, netdev
On Mon, 13 Apr 2026 15:02:22 +0200 Daniel Borkmann wrote:
> I'm planning to address these in a v2 of the series, but as per netdev rule
> will wait 24h before resend unless you'd like me to explicitly resend earlier
> (given merge win timing).
Please go ahead with v2 as soon as it's ready.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-04-13 21:32 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-13 11:40 [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
2026-04-13 11:40 ` [PATCH net-next 1/3] tools/ynl: Make YnlFamily closeable as a context manager Daniel Borkmann
2026-04-13 11:40 ` [PATCH net-next 2/3] selftests/net: Split netdevsim tests from HW tests in nk_qlease Daniel Borkmann
2026-04-13 11:40 ` [PATCH net-next 3/3] selftests/net: Add additional test coverage " Daniel Borkmann
2026-04-13 13:02 ` [PATCH net-next 0/3] Follow-ups to nk_qlease net selftests Daniel Borkmann
2026-04-13 21:32 ` Jakub Kicinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox