summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-04-14 18:17:06 +0300
committerJakub Kicinski <kuba@kernel.org>2026-04-14 18:17:06 +0300
commit2d7e20c9886f359a4ebd4bdbba53ab2df44667d6 (patch)
treef0ddac6f418d7ff552f8d3d79a01221ec268d985
parent34e1a98ff2a87cf4b8de3ccebe9d45273f014aeb (diff)
parent1e822171ba9bca7a5d2371bc10358340835bdad3 (diff)
downloadlinux-2d7e20c9886f359a4ebd4bdbba53ab2df44667d6.tar.xz
Merge branch 'follow-ups-to-nk_qlease-net-selftests'
Daniel Borkmann says: ==================== Follow-ups to nk_qlease net selftests 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 ==================== Link: https://patch.msgid.link/20260413220809.604592-1-daniel@iogearbox.net Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--tools/net/ynl/pyynl/lib/ynl.py10
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/nk_qlease.py1142
-rw-r--r--tools/testing/selftests/net/Makefile1
-rwxr-xr-xtools/testing/selftests/net/nk_qlease.py2109
4 files changed, 2120 insertions, 1142 deletions
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)
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..a84a73ff4eda
--- /dev/null
+++ b/tools/testing/selftests/net/nk_qlease.py
@@ -0,0 +1,2109 @@
+#!/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 wait_until(cond, timeout=2.0, interval=0.05):
+ deadline = time.monotonic() + timeout
+ while not cond():
+ if time.monotonic() >= deadline:
+ return
+ time.sleep(interval)
+
+
+def create_netkit(rxqueues, mode="l2"):
+ all_links = ip("-d link show", json=True)
+ old_idxs = {
+ link["ifindex"]
+ for link in all_links
+ if link.get("linkinfo", {}).get("info_kind") == "netkit"
+ }
+
+ rtnl = RtnlFamily()
+ rtnl.newlink(
+ {
+ "linkinfo": {
+ "kind": "netkit",
+ "data": {
+ "mode": mode,
+ "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()
+ wait_until(lambda: cmd(f"ip link show dev {nk_host}", fail=False).ret != 0)
+ ret = cmd(f"ip link show dev {nk_host}", fail=False)
+ ksft_ne(ret.ret, 0)
+
+
+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()
+ wait_until(lambda: cmd(f"ip link show dev {nk_host_a}", fail=False).ret != 0
+ and cmd(f"ip link show dev {nk_host_b}", fail=False).ret != 0)
+ ret = cmd(f"ip link show dev {nk_host_a}", fail=False)
+ ksft_ne(ret.ret, 0)
+ ret = cmd(f"ip link show dev {nk_host_b}", fail=False)
+ 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()
+ wait_until(lambda: cmd(f"ip link show dev {nk_name}", fail=False).ret != 0)
+ ret = cmd(f"ip link show dev {nk_name}", fail=False)
+ ksft_ne(ret.ret, 0)
+
+
+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_two_netkits_same_queue(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host_a}", fail=False)
+
+ nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host_b}", fail=False)
+
+ ip(f"link set dev {nk_guest_a} netns {netns.name}")
+ ip(f"link set dev {nk_host_a} up")
+ ip(f"link set dev {nk_guest_a} up", ns=netns)
+
+ ip(f"link set dev {nk_guest_b} netns {netns.name}")
+ ip(f"link set dev {nk_host_b} up")
+ ip(f"link set dev {nk_guest_b} up", ns=netns)
+
+ src_queue = 1
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_a_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ with ksft_raises(NlError) as e:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_b_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ ksft_eq(e.exception.nl_msg.error, -errno.EBUSY)
+
+
+def test_l3_mode_lease(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2, mode="l3")
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ src_queue = 1
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ result = netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ ksft_eq(result["id"], 1)
+
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+ ksft_eq(queue_info["lease"]["ifindex"], nk_guest_idx)
+ ksft_eq(queue_info["lease"]["queue"]["id"], result["id"])
+
+
+def test_single_double_lease(_netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_name, nk_idx = create_netkit_single(rxqueues=3)
+ defer(cmd, f"ip link del dev {nk_name}", fail=False)
+
+ ip(f"link set dev {nk_name} up")
+
+ netdevnl = NetdevFamily()
+ result = netdevnl.queue_create(
+ {
+ "ifindex": nk_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ },
+ }
+ )
+ ksft_eq(result["id"], 1)
+
+ with ksft_raises(NlError) as e:
+ netdevnl.queue_create(
+ {
+ "ifindex": nk_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ },
+ }
+ )
+ ksft_eq(e.exception.nl_msg.error, -errno.EBUSY)
+
+
+def test_single_different_lessors(_netns) -> None:
+ nsimdev_a = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev_a.remove)
+ nsim_a = nsimdev_a.nsims[0]
+ ip(f"link set dev {nsim_a.ifname} up")
+
+ nsimdev_b = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev_b.remove)
+ nsim_b = nsimdev_b.nsims[0]
+ ip(f"link set dev {nsim_b.ifname} up")
+
+ nk_name, nk_idx = create_netkit_single(rxqueues=3)
+ defer(cmd, f"ip link del dev {nk_name}", fail=False)
+
+ ip(f"link set dev {nk_name} up")
+
+ netdevnl = NetdevFamily()
+ netdevnl.queue_create(
+ {
+ "ifindex": nk_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim_a.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ },
+ }
+ )
+
+ with ksft_raises(NlError) as e:
+ netdevnl.queue_create(
+ {
+ "ifindex": nk_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim_b.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ },
+ }
+ )
+ ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP)
+
+
+def test_cross_ns_netns_id(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ src_queue = 1
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+ ksft_in("netns-id", queue_info["lease"])
+
+
+def test_delete_guest_netns(_netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ test_ns = NetNS()
+ ip("netns set init 0", ns=test_ns)
+ ip("link set lo up", ns=test_ns)
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {test_ns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=test_ns)
+
+ src_queue = 1
+ with NetNSEnter(str(test_ns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+
+ del test_ns
+ wait_until(lambda: "lease" not in netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}))
+
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
+ )
+ ksft_not_in("lease", queue_info)
+
+ ret = cmd(f"ip link show dev {nk_host}", fail=False)
+ ksft_ne(ret.ret, 0)
+
+
+def test_move_guest_netns(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ src_queue = 1
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ result = netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ nk_queue_id = result["id"]
+
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+ ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
+
+ new_ns = NetNS()
+ defer(new_ns.__del__)
+ ip(f"link set dev {nk_guest} netns {new_ns.name}", ns=netns)
+
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+ ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
+
+
+def test_resize_phys_no_reduction(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ ethnl = EthtoolFamily()
+ ethnl.channels_set(
+ {"header": {"dev-index": nsim.ifindex}, "combined-count": 2}
+ )
+
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+
+
+def test_delete_one_netkit_of_two(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=3)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host_a}", fail=False)
+
+ nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host_b}", fail=False)
+
+ ip(f"link set dev {nk_guest_a} netns {netns.name}")
+ ip(f"link set dev {nk_host_a} up")
+ ip(f"link set dev {nk_guest_a} up", ns=netns)
+
+ ip(f"link set dev {nk_guest_b} netns {netns.name}")
+ ip(f"link set dev {nk_host_b} up")
+ ip(f"link set dev {nk_guest_b} up", ns=netns)
+
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_a_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_b_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 2, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ netdevnl = NetdevFamily()
+ q1 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ q2 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
+ )
+ ksft_in("lease", q1)
+ ksft_in("lease", q2)
+
+ cmd(f"ip link del dev {nk_host_a}")
+
+ q1 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ q2 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
+ )
+ ksft_not_in("lease", q1)
+ ksft_in("lease", q2)
+
+
+def test_bind_rx_leased_phys_queue(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ netdevnl = NetdevFamily()
+ with ksft_raises(NlError) as e:
+ netdevnl.bind_rx(
+ {
+ "ifindex": nsim.ifindex,
+ "fd": 0,
+ "queues": [
+ {"id": 0, "type": "rx"},
+ {"id": 1, "type": "rx"},
+ ],
+ }
+ )
+ ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP)
+
+
+def test_resize_phys_shrink_past_leased(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=4)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ ethnl = EthtoolFamily()
+
+ # Shrink past the leased queue — only queue 3 removed, queue 1 untouched
+ ethnl.channels_set(
+ {"header": {"dev-index": nsim.ifindex}, "combined-count": 3}
+ )
+
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+
+ # Shrink further — queue 2 removed, queue 1 still untouched
+ ethnl.channels_set(
+ {"header": {"dev-index": nsim.ifindex}, "combined-count": 2}
+ )
+
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+
+ # Shrink into the leased queue — queue 1 is busy, must fail
+ with ksft_raises(NlError) as e:
+ ethnl.channels_set(
+ {"header": {"dev-index": nsim.ifindex}, "combined-count": 1}
+ )
+ ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
+
+
+def test_resize_virt_not_supported(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host, nk_host_idx, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ # Channel resize on the netkit host must fail — not supported
+ ethnl = EthtoolFamily()
+ with ksft_raises(NlError) as e:
+ ethnl.channels_set(
+ {"header": {"dev-index": nk_host_idx}, "combined-count": 1}
+ )
+ ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP)
+
+ # Lease must be intact
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+
+
+def test_lease_devices_down(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+
+ # Create lease while both physical and virtual devices are down
+ src_queue = 1
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ result = netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ ksft_eq(result["id"], 1)
+
+ # Bring devices up before queue_get: netdevsim only instantiates NAPIs in
+ # ndo_open, and netdev-genl queue_get returns -ENOENT without a NAPI.
+ ip(f"link set dev {nsim.ifname} up")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+ ksft_eq(queue_info["lease"]["queue"]["id"], result["id"])
+
+
+def test_lease_capacity_exhaustion(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=4)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ # rxqueues=3 means num_rx_queues=3, real_num_rx_queues starts at 1.
+ # Can create 2 leased queues (real goes 1->2->3) but not a 3rd (3->4 > 3).
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=3)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ r1 = netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ ksft_eq(r1["id"], 1)
+
+ r2 = netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 2, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ ksft_eq(r2["id"], 2)
+
+ # Third lease fails — netkit queue capacity exhausted
+ with ksft_raises(NlError) as e:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 3, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
+
+ # Verify the two successful leases are intact
+ netdevnl = NetdevFamily()
+ q1 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ q2 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
+ )
+ ksft_in("lease", q1)
+ ksft_in("lease", q2)
+
+
+def test_resize_phys_up(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=3)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ # Shrink nsim first so we have room to grow
+ ethnl = EthtoolFamily()
+ ethnl.channels_set(
+ {"header": {"dev-index": nsim.ifindex}, "combined-count": 2}
+ )
+
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ # Grow channels — should succeed since leased queue is not removed
+ ethnl.channels_set(
+ {"header": {"dev-index": nsim.ifindex}, "combined-count": 3}
+ )
+
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+
+ # New queue 2 should exist without a lease
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
+ )
+ ksft_not_in("lease", queue_info)
+
+
+def test_multi_ns_lease(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=3)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ ns_b = NetNS()
+ defer(ns_b.__del__)
+ ip("netns set init 0", ns=ns_b)
+ ip("link set lo up", ns=ns_b)
+
+ # First netkit pair, guest in netns
+ nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host_a}", fail=False)
+ ip(f"link set dev {nk_guest_a} netns {netns.name}")
+ ip(f"link set dev {nk_host_a} up")
+ ip(f"link set dev {nk_guest_a} up", ns=netns)
+
+ # Second netkit pair, guest in ns_b
+ nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host_b}", fail=False)
+ ip(f"link set dev {nk_guest_b} netns {ns_b.name}")
+ ip(f"link set dev {nk_host_b} up")
+ ip(f"link set dev {nk_guest_b} up", ns=ns_b)
+
+ # Lease from netns
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ result = netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_a_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ ksft_eq(result["id"], 1)
+
+ # Lease from ns_b (different namespace, same physical device)
+ with NetNSEnter(str(ns_b)), NetdevFamily() as netdevnl_ns:
+ result = netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_b_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 2, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+ ksft_eq(result["id"], 1)
+
+ # Verify both leases from the physical side
+ netdevnl = NetdevFamily()
+ q1 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ q2 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
+ )
+ ksft_in("lease", q1)
+ ksft_in("lease", q2)
+ ksft_eq(q1["lease"]["ifindex"], nk_guest_a_idx)
+ ksft_eq(q2["lease"]["ifindex"], nk_guest_b_idx)
+
+
+def test_multi_ns_delete_one(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=3)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ ns_b = NetNS()
+ ip("netns set init 0", ns=ns_b)
+ ip("link set lo up", ns=ns_b)
+
+ # First netkit pair, guest in netns (ns_a)
+ nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host_a}", fail=False)
+ ip(f"link set dev {nk_guest_a} netns {netns.name}")
+ ip(f"link set dev {nk_host_a} up")
+ ip(f"link set dev {nk_guest_a} up", ns=netns)
+
+ # Second netkit pair, guest in ns_b
+ nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host_b}", fail=False)
+
+ ip(f"link set dev {nk_guest_b} netns {ns_b.name}")
+ ip(f"link set dev {nk_host_b} up")
+ ip(f"link set dev {nk_guest_b} up", ns=ns_b)
+
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_a_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 1, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ with NetNSEnter(str(ns_b)), NetdevFamily() as netdevnl_ns:
+ netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_b_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": 2, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )
+
+ netdevnl = NetdevFamily()
+ q1 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ q2 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
+ )
+ ksft_in("lease", q1)
+ ksft_in("lease", q2)
+
+ # Delete ns_b — destroys nk_guest_b, triggers unlease of queue 2
+ del ns_b
+ wait_until(lambda: "lease" not in netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}))
+
+ # ns_a's lease on queue 1 must survive
+ q1 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
+ )
+ ksft_in("lease", q1)
+ ksft_eq(q1["lease"]["ifindex"], nk_guest_a_idx)
+
+ # ns_b's lease on queue 2 must be gone
+ q2 = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
+ )
+ ksft_not_in("lease", q2)
+
+ # nk_host_b should be gone too (phys removal cascades to netkit pair)
+ ret = cmd(f"ip link show dev {nk_host_b}", fail=False)
+ ksft_ne(ret.ret, 0)
+
+
+def test_move_phys_netns(netns) -> None:
+ nsimdev = NetdevSimDev(port_count=1, queue_count=2)
+ defer(nsimdev.remove)
+ nsim = nsimdev.nsims[0]
+ ip(f"link set dev {nsim.ifname} up")
+
+ nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
+ defer(cmd, f"ip link del dev {nk_host}", fail=False)
+
+ ip(f"link set dev {nk_guest} netns {netns.name}")
+ ip(f"link set dev {nk_host} up")
+ ip(f"link set dev {nk_guest} up", ns=netns)
+
+ src_queue = 1
+ with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
+ nk_queue_id = netdevnl_ns.queue_create(
+ {
+ "ifindex": nk_guest_idx,
+ "type": "rx",
+ "lease": {
+ "ifindex": nsim.ifindex,
+ "queue": {"id": src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ }
+ )["id"]
+
+ netdevnl = NetdevFamily()
+ queue_info = netdevnl.queue_get(
+ {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+
+ # Move the physical device to a new namespace. Move it back to init_net
+ # on cleanup before the other defers fire (new_ns deletion, nsimdev.remove)
+ # so nsim lives in a stable namespace when they run.
+ new_ns = NetNS()
+ defer(new_ns.__del__)
+ ip(f"link set dev {nsim.ifname} netns {new_ns.name}")
+ defer(ip, f"link set dev {nsim.ifname} netns init", ns=new_ns)
+
+ # Physical device is now in new_ns — find its ifindex there
+ all_links = ip("-d link show", json=True, ns=new_ns)
+ nsim_in_new = [lnk for lnk in all_links if lnk.get("ifname") == nsim.ifname]
+ new_ifindex = nsim_in_new[0]["ifindex"]
+
+ # Moving a device across netns brings it admin-down; bring it back up so
+ # netdevsim re-creates the NAPI (netdev-genl queue_get needs it).
+ ip(f"link set dev {nsim.ifname} up", ns=new_ns)
+
+ # Verify lease survived the namespace move
+ with NetNSEnter(str(new_ns)), NetdevFamily() as netdevnl_ns:
+ queue_info = netdevnl_ns.queue_get(
+ {"ifindex": new_ifindex, "id": src_queue, "type": "rx"}
+ )
+ ksft_in("lease", queue_info)
+ ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
+
+
+def main() -> None:
+ netns = NetNS()
+ cmd("ip netns attach init 1")
+ 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,
+ 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,),
+ )
+
+ cmd("ip netns del init", fail=False)
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()