#!/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()