xref: /linux/tools/testing/selftests/net/nk_qlease.py (revision 2d7e20c9886f359a4ebd4bdbba53ab2df44667d6)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4import errno
5import time
6from lib.py import (
7    ksft_run,
8    ksft_exit,
9    ksft_eq,
10    ksft_ne,
11    ksft_in,
12    ksft_not_in,
13    ksft_raises,
14)
15from lib.py import (
16    NetNS,
17    NetNSEnter,
18    EthtoolFamily,
19    NetdevFamily,
20    RtnlFamily,
21    NetdevSimDev,
22)
23from lib.py import (
24    NlError,
25    Netlink,
26    cmd,
27    defer,
28    ip,
29)
30
31
32def wait_until(cond, timeout=2.0, interval=0.05):
33    deadline = time.monotonic() + timeout
34    while not cond():
35        if time.monotonic() >= deadline:
36            return
37        time.sleep(interval)
38
39
40def create_netkit(rxqueues, mode="l2"):
41    all_links = ip("-d link show", json=True)
42    old_idxs = {
43        link["ifindex"]
44        for link in all_links
45        if link.get("linkinfo", {}).get("info_kind") == "netkit"
46    }
47
48    rtnl = RtnlFamily()
49    rtnl.newlink(
50        {
51            "linkinfo": {
52                "kind": "netkit",
53                "data": {
54                    "mode": mode,
55                    "policy": "forward",
56                    "peer-policy": "forward",
57                },
58            },
59            "num-rx-queues": rxqueues,
60        },
61        flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
62    )
63
64    all_links = ip("-d link show", json=True)
65    nk_links = [
66        link
67        for link in all_links
68        if link.get("linkinfo", {}).get("info_kind") == "netkit"
69        and link["ifindex"] not in old_idxs
70    ]
71    nk_links.sort(key=lambda x: x["ifindex"])
72    return (
73        nk_links[1]["ifname"],
74        nk_links[1]["ifindex"],
75        nk_links[0]["ifname"],
76        nk_links[0]["ifindex"],
77    )
78
79
80def create_netkit_single(rxqueues):
81    rtnl = RtnlFamily()
82    rtnl.newlink(
83        {
84            "linkinfo": {
85                "kind": "netkit",
86                "data": {
87                    "mode": "l2",
88                    "pairing": "single",
89                },
90            },
91            "num-rx-queues": rxqueues,
92        },
93        flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
94    )
95
96    all_links = ip("-d link show", json=True)
97    nk_links = [
98        link
99        for link in all_links
100        if link.get("linkinfo", {}).get("info_kind") == "netkit"
101        and "UP" not in link.get("flags", [])
102    ]
103    return nk_links[0]["ifname"], nk_links[0]["ifindex"]
104
105
106def test_remove_phys(netns) -> None:
107    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
108    defer(nsimdev.remove)
109    nsim = nsimdev.nsims[0]
110    ip(f"link set dev {nsim.ifname} up")
111
112    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
113    defer(cmd, f"ip link del dev {nk_host}", fail=False)
114
115    ip(f"link set dev {nk_guest} netns {netns.name}")
116    ip(f"link set dev {nk_host} up")
117    ip(f"link set dev {nk_guest} up", ns=netns)
118
119    src_queue = 1
120    with NetNSEnter(str(netns)):
121        netdevnl = NetdevFamily()
122        result = netdevnl.queue_create(
123            {
124                "ifindex": nk_guest_idx,
125                "type": "rx",
126                "lease": {
127                    "ifindex": nsim.ifindex,
128                    "queue": {"id": src_queue, "type": "rx"},
129                    "netns-id": 0,
130                },
131            }
132        )
133        nk_queue_id = result["id"]
134
135    netdevnl = NetdevFamily()
136    queue_info = netdevnl.queue_get(
137        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
138    )
139    ksft_in("lease", queue_info)
140    ksft_eq(queue_info["lease"]["ifindex"], nk_guest_idx)
141    ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
142
143    nsimdev.remove()
144    wait_until(lambda: cmd(f"ip link show dev {nk_host}", fail=False).ret != 0)
145    ret = cmd(f"ip link show dev {nk_host}", fail=False)
146    ksft_ne(ret.ret, 0)
147
148
149def test_double_lease(netns) -> None:
150    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
151    defer(nsimdev.remove)
152    nsim = nsimdev.nsims[0]
153    ip(f"link set dev {nsim.ifname} up")
154
155    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=3)
156    defer(cmd, f"ip link del dev {nk_host}")
157
158    ip(f"link set dev {nk_guest} netns {netns.name}")
159    ip(f"link set dev {nk_host} up")
160    ip(f"link set dev {nk_guest} up", ns=netns)
161
162    src_queue = 1
163    with NetNSEnter(str(netns)):
164        netdevnl = NetdevFamily()
165        result = netdevnl.queue_create(
166            {
167                "ifindex": nk_guest_idx,
168                "type": "rx",
169                "lease": {
170                    "ifindex": nsim.ifindex,
171                    "queue": {"id": src_queue, "type": "rx"},
172                    "netns-id": 0,
173                },
174            }
175        )
176        ksft_eq(result["id"], 1)
177
178        with ksft_raises(NlError) as e:
179            netdevnl.queue_create(
180                {
181                    "ifindex": nk_guest_idx,
182                    "type": "rx",
183                    "lease": {
184                        "ifindex": nsim.ifindex,
185                        "queue": {"id": src_queue, "type": "rx"},
186                        "netns-id": 0,
187                    },
188                }
189            )
190        ksft_eq(e.exception.nl_msg.error, -errno.EBUSY)
191
192
193def test_virtual_lessor(netns) -> None:
194    nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
195    defer(cmd, f"ip link del dev {nk_host_a}")
196    ip(f"link set dev {nk_host_a} up")
197    ip(f"link set dev {nk_guest_a} up")
198
199    nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
200    defer(cmd, f"ip link del dev {nk_host_b}")
201
202    ip(f"link set dev {nk_guest_b} netns {netns.name}")
203    ip(f"link set dev {nk_host_b} up")
204    ip(f"link set dev {nk_guest_b} up", ns=netns)
205
206    with NetNSEnter(str(netns)):
207        netdevnl = NetdevFamily()
208        with ksft_raises(NlError) as e:
209            netdevnl.queue_create(
210                {
211                    "ifindex": nk_guest_b_idx,
212                    "type": "rx",
213                    "lease": {
214                        "ifindex": nk_guest_a_idx,
215                        "queue": {"id": 0, "type": "rx"},
216                        "netns-id": 0,
217                    },
218                }
219            )
220        ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
221
222
223def test_phys_lessee(_netns) -> None:
224    nsimdev_a = NetdevSimDev(port_count=1, queue_count=2)
225    defer(nsimdev_a.remove)
226    nsim_a = nsimdev_a.nsims[0]
227    ip(f"link set dev {nsim_a.ifname} up")
228
229    nsimdev_b = NetdevSimDev(port_count=1, queue_count=2)
230    defer(nsimdev_b.remove)
231    nsim_b = nsimdev_b.nsims[0]
232    ip(f"link set dev {nsim_b.ifname} up")
233
234    netdevnl = NetdevFamily()
235    with ksft_raises(NlError) as e:
236        netdevnl.queue_create(
237            {
238                "ifindex": nsim_a.ifindex,
239                "type": "rx",
240                "lease": {
241                    "ifindex": nsim_b.ifindex,
242                    "queue": {"id": 0, "type": "rx"},
243                },
244            }
245        )
246    ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
247
248
249def test_different_lessors(netns) -> None:
250    nsimdev_a = NetdevSimDev(port_count=1, queue_count=2)
251    defer(nsimdev_a.remove)
252    nsim_a = nsimdev_a.nsims[0]
253    ip(f"link set dev {nsim_a.ifname} up")
254
255    nsimdev_b = NetdevSimDev(port_count=1, queue_count=2)
256    defer(nsimdev_b.remove)
257    nsim_b = nsimdev_b.nsims[0]
258    ip(f"link set dev {nsim_b.ifname} up")
259
260    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=3)
261    defer(cmd, f"ip link del dev {nk_host}", fail=False)
262
263    ip(f"link set dev {nk_guest} netns {netns.name}")
264    ip(f"link set dev {nk_host} up")
265    ip(f"link set dev {nk_guest} up", ns=netns)
266
267    with NetNSEnter(str(netns)):
268        netdevnl = NetdevFamily()
269        netdevnl.queue_create(
270            {
271                "ifindex": nk_guest_idx,
272                "type": "rx",
273                "lease": {
274                    "ifindex": nsim_a.ifindex,
275                    "queue": {"id": 1, "type": "rx"},
276                    "netns-id": 0,
277                },
278            }
279        )
280
281        with ksft_raises(NlError) as e:
282            netdevnl.queue_create(
283                {
284                    "ifindex": nk_guest_idx,
285                    "type": "rx",
286                    "lease": {
287                        "ifindex": nsim_b.ifindex,
288                        "queue": {"id": 1, "type": "rx"},
289                        "netns-id": 0,
290                    },
291                }
292            )
293        ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP)
294
295
296def test_queue_out_of_range(netns) -> None:
297    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
298    defer(nsimdev.remove)
299    nsim = nsimdev.nsims[0]
300    ip(f"link set dev {nsim.ifname} up")
301
302    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
303    defer(cmd, f"ip link del dev {nk_host}", fail=False)
304
305    ip(f"link set dev {nk_guest} netns {netns.name}")
306    ip(f"link set dev {nk_host} up")
307    ip(f"link set dev {nk_guest} up", ns=netns)
308
309    with NetNSEnter(str(netns)):
310        netdevnl = NetdevFamily()
311        with ksft_raises(NlError) as e:
312            netdevnl.queue_create(
313                {
314                    "ifindex": nk_guest_idx,
315                    "type": "rx",
316                    "lease": {
317                        "ifindex": nsim.ifindex,
318                        "queue": {"id": 2, "type": "rx"},
319                        "netns-id": 0,
320                    },
321                }
322            )
323        ksft_eq(e.exception.nl_msg.error, -errno.ERANGE)
324
325
326def test_resize_leased(netns) -> None:
327    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
328    defer(nsimdev.remove)
329    nsim = nsimdev.nsims[0]
330    ip(f"link set dev {nsim.ifname} up")
331
332    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
333    defer(cmd, f"ip link del dev {nk_host}", fail=False)
334
335    ip(f"link set dev {nk_guest} netns {netns.name}")
336    ip(f"link set dev {nk_host} up")
337    ip(f"link set dev {nk_guest} up", ns=netns)
338
339    with NetNSEnter(str(netns)):
340        netdevnl = NetdevFamily()
341        netdevnl.queue_create(
342            {
343                "ifindex": nk_guest_idx,
344                "type": "rx",
345                "lease": {
346                    "ifindex": nsim.ifindex,
347                    "queue": {"id": 1, "type": "rx"},
348                    "netns-id": 0,
349                },
350            }
351        )
352
353    ethnl = EthtoolFamily()
354    with ksft_raises(NlError) as e:
355        ethnl.channels_set({"header": {"dev-index": nsim.ifindex}, "combined-count": 1})
356    ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
357
358
359def test_self_lease(_netns) -> None:
360    nk_host, _, _, nk_guest_idx = create_netkit(rxqueues=2)
361    defer(cmd, f"ip link del dev {nk_host}", fail=False)
362
363    netdevnl = NetdevFamily()
364    with ksft_raises(NlError) as e:
365        netdevnl.queue_create(
366            {
367                "ifindex": nk_guest_idx,
368                "type": "rx",
369                "lease": {
370                    "ifindex": nk_guest_idx,
371                    "queue": {"id": 0, "type": "rx"},
372                },
373            }
374        )
375    ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
376
377
378def test_veth_queue_create(netns) -> None:
379    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
380    defer(nsimdev.remove)
381    nsim = nsimdev.nsims[0]
382    ip(f"link set dev {nsim.ifname} up")
383
384    ip("link add veth0 type veth peer name veth1")
385    defer(cmd, "ip link del dev veth0", fail=False)
386
387    all_links = ip("-d link show", json=True)
388    veth_peer = [
389        link
390        for link in all_links
391        if link.get("ifname") == "veth1"
392    ]
393    veth_peer_idx = veth_peer[0]["ifindex"]
394
395    ip(f"link set dev veth1 netns {netns.name}")
396    ip("link set dev veth0 up")
397    ip("link set dev veth1 up", ns=netns)
398
399    with NetNSEnter(str(netns)):
400        netdevnl = NetdevFamily()
401        with ksft_raises(NlError) as e:
402            netdevnl.queue_create(
403                {
404                    "ifindex": veth_peer_idx,
405                    "type": "rx",
406                    "lease": {
407                        "ifindex": nsim.ifindex,
408                        "queue": {"id": 1, "type": "rx"},
409                        "netns-id": 0,
410                    },
411                }
412            )
413        ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
414
415
416def test_create_tx_type(netns) -> None:
417    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
418    defer(nsimdev.remove)
419    nsim = nsimdev.nsims[0]
420    ip(f"link set dev {nsim.ifname} up")
421
422    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
423    defer(cmd, f"ip link del dev {nk_host}", fail=False)
424
425    ip(f"link set dev {nk_guest} netns {netns.name}")
426    ip(f"link set dev {nk_host} up")
427    ip(f"link set dev {nk_guest} up", ns=netns)
428
429    with NetNSEnter(str(netns)):
430        netdevnl = NetdevFamily()
431        with ksft_raises(NlError) as e:
432            netdevnl.queue_create(
433                {
434                    "ifindex": nk_guest_idx,
435                    "type": "tx",
436                    "lease": {
437                        "ifindex": nsim.ifindex,
438                        "queue": {"id": 1, "type": "rx"},
439                        "netns-id": 0,
440                    },
441                }
442            )
443        ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
444
445
446def test_create_primary(_netns) -> None:
447    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
448    defer(nsimdev.remove)
449    nsim = nsimdev.nsims[0]
450    ip(f"link set dev {nsim.ifname} up")
451
452    nk_host, nk_host_idx, _, _ = create_netkit(rxqueues=2)
453    defer(cmd, f"ip link del dev {nk_host}", fail=False)
454
455    ip(f"link set dev {nk_host} up")
456
457    netdevnl = NetdevFamily()
458    with ksft_raises(NlError) as e:
459        netdevnl.queue_create(
460            {
461                "ifindex": nk_host_idx,
462                "type": "rx",
463                "lease": {
464                    "ifindex": nsim.ifindex,
465                    "queue": {"id": 1, "type": "rx"},
466                },
467            }
468        )
469    ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP)
470
471
472def test_create_limit(netns) -> None:
473    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
474    defer(nsimdev.remove)
475    nsim = nsimdev.nsims[0]
476    ip(f"link set dev {nsim.ifname} up")
477
478    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=1)
479    defer(cmd, f"ip link del dev {nk_host}", fail=False)
480
481    ip(f"link set dev {nk_guest} netns {netns.name}")
482    ip(f"link set dev {nk_host} up")
483    ip(f"link set dev {nk_guest} up", ns=netns)
484
485    with NetNSEnter(str(netns)):
486        netdevnl = NetdevFamily()
487        with ksft_raises(NlError) as e:
488            netdevnl.queue_create(
489                {
490                    "ifindex": nk_guest_idx,
491                    "type": "rx",
492                    "lease": {
493                        "ifindex": nsim.ifindex,
494                        "queue": {"id": 1, "type": "rx"},
495                        "netns-id": 0,
496                    },
497                }
498            )
499        ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
500
501
502def test_link_flap_phys(netns) -> None:
503    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
504    defer(nsimdev.remove)
505    nsim = nsimdev.nsims[0]
506    ip(f"link set dev {nsim.ifname} up")
507
508    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
509    defer(cmd, f"ip link del dev {nk_host}")
510
511    ip(f"link set dev {nk_guest} netns {netns.name}")
512    ip(f"link set dev {nk_host} up")
513    ip(f"link set dev {nk_guest} up", ns=netns)
514
515    src_queue = 1
516    with NetNSEnter(str(netns)):
517        netdevnl = NetdevFamily()
518        result = netdevnl.queue_create(
519            {
520                "ifindex": nk_guest_idx,
521                "type": "rx",
522                "lease": {
523                    "ifindex": nsim.ifindex,
524                    "queue": {"id": src_queue, "type": "rx"},
525                    "netns-id": 0,
526                },
527            }
528        )
529        nk_queue_id = result["id"]
530
531    netdevnl = NetdevFamily()
532    queue_info = netdevnl.queue_get(
533        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
534    )
535    ksft_in("lease", queue_info)
536    ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
537
538    # Link flap the physical device
539    ip(f"link set dev {nsim.ifname} down")
540    ip(f"link set dev {nsim.ifname} up")
541
542    # Verify lease survives the flap
543    queue_info = netdevnl.queue_get(
544        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
545    )
546    ksft_in("lease", queue_info)
547    ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
548
549
550def test_queue_get_virtual(netns) -> None:
551    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
552    defer(nsimdev.remove)
553    nsim = nsimdev.nsims[0]
554    ip(f"link set dev {nsim.ifname} up")
555
556    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
557    defer(cmd, f"ip link del dev {nk_host}")
558
559    ip(f"link set dev {nk_guest} netns {netns.name}")
560    ip(f"link set dev {nk_host} up")
561    ip(f"link set dev {nk_guest} up", ns=netns)
562
563    src_queue = 1
564    with NetNSEnter(str(netns)):
565        netdevnl = NetdevFamily()
566        result = netdevnl.queue_create(
567            {
568                "ifindex": nk_guest_idx,
569                "type": "rx",
570                "lease": {
571                    "ifindex": nsim.ifindex,
572                    "queue": {"id": src_queue, "type": "rx"},
573                    "netns-id": 0,
574                },
575            }
576        )
577        nk_queue_id = result["id"]
578
579        # queue-get on virtual device's leased queue should not show lease
580        # info (lease info is only shown from the physical device's side)
581        queue_info = netdevnl.queue_get(
582            {"ifindex": nk_guest_idx, "id": nk_queue_id, "type": "rx"}
583        )
584        ksft_eq(queue_info["id"], nk_queue_id)
585        ksft_eq(queue_info["ifindex"], nk_guest_idx)
586        ksft_not_in("lease", queue_info)
587
588        # Default queue (not leased) also has no lease info
589        queue_info = netdevnl.queue_get(
590            {"ifindex": nk_guest_idx, "id": 0, "type": "rx"}
591        )
592        ksft_not_in("lease", queue_info)
593
594
595def test_remove_virt_first(netns) -> None:
596    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
597    defer(nsimdev.remove)
598    nsim = nsimdev.nsims[0]
599    ip(f"link set dev {nsim.ifname} up")
600
601    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
602
603    ip(f"link set dev {nk_guest} netns {netns.name}")
604    ip(f"link set dev {nk_host} up")
605    ip(f"link set dev {nk_guest} up", ns=netns)
606
607    src_queue = 1
608    with NetNSEnter(str(netns)):
609        netdevnl = NetdevFamily()
610        result = netdevnl.queue_create(
611            {
612                "ifindex": nk_guest_idx,
613                "type": "rx",
614                "lease": {
615                    "ifindex": nsim.ifindex,
616                    "queue": {"id": src_queue, "type": "rx"},
617                    "netns-id": 0,
618                },
619            }
620        )
621        ksft_eq(result["id"], 1)
622
623    netdevnl = NetdevFamily()
624    queue_info = netdevnl.queue_get(
625        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
626    )
627    ksft_in("lease", queue_info)
628    ksft_eq(queue_info["lease"]["queue"]["id"], result["id"])
629
630    # Delete netkit (virtual device removed first, physical stays)
631    cmd(f"ip link del dev {nk_host}")
632
633    # Verify lease is cleaned up on physical device
634    queue_info = netdevnl.queue_get(
635        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
636    )
637    ksft_not_in("lease", queue_info)
638
639
640def test_multiple_leases(netns) -> None:
641    nsimdev = NetdevSimDev(port_count=1, queue_count=3)
642    defer(nsimdev.remove)
643    nsim = nsimdev.nsims[0]
644    ip(f"link set dev {nsim.ifname} up")
645
646    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=4)
647    defer(cmd, f"ip link del dev {nk_host}", fail=False)
648
649    ip(f"link set dev {nk_guest} netns {netns.name}")
650    ip(f"link set dev {nk_host} up")
651    ip(f"link set dev {nk_guest} up", ns=netns)
652
653    with NetNSEnter(str(netns)):
654        netdevnl = NetdevFamily()
655        r1 = netdevnl.queue_create(
656            {
657                "ifindex": nk_guest_idx,
658                "type": "rx",
659                "lease": {
660                    "ifindex": nsim.ifindex,
661                    "queue": {"id": 1, "type": "rx"},
662                    "netns-id": 0,
663                },
664            }
665        )
666        r2 = netdevnl.queue_create(
667            {
668                "ifindex": nk_guest_idx,
669                "type": "rx",
670                "lease": {
671                    "ifindex": nsim.ifindex,
672                    "queue": {"id": 2, "type": "rx"},
673                    "netns-id": 0,
674                },
675            }
676        )
677
678    ksft_eq(r1["id"], 1)
679    ksft_eq(r2["id"], 2)
680
681    # Verify both leases visible on physical device
682    netdevnl = NetdevFamily()
683    q1 = netdevnl.queue_get(
684        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
685    )
686    q2 = netdevnl.queue_get(
687        {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
688    )
689    ksft_in("lease", q1)
690    ksft_in("lease", q2)
691    ksft_eq(q1["lease"]["ifindex"], nk_guest_idx)
692    ksft_eq(q2["lease"]["ifindex"], nk_guest_idx)
693    ksft_eq(q1["lease"]["queue"]["id"], r1["id"])
694    ksft_eq(q2["lease"]["queue"]["id"], r2["id"])
695
696
697def test_lease_queue_tx_type(netns) -> None:
698    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
699    defer(nsimdev.remove)
700    nsim = nsimdev.nsims[0]
701    ip(f"link set dev {nsim.ifname} up")
702
703    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
704    defer(cmd, f"ip link del dev {nk_host}", fail=False)
705
706    ip(f"link set dev {nk_guest} netns {netns.name}")
707    ip(f"link set dev {nk_host} up")
708    ip(f"link set dev {nk_guest} up", ns=netns)
709
710    with NetNSEnter(str(netns)):
711        netdevnl = NetdevFamily()
712        with ksft_raises(NlError) as e:
713            netdevnl.queue_create(
714                {
715                    "ifindex": nk_guest_idx,
716                    "type": "rx",
717                    "lease": {
718                        "ifindex": nsim.ifindex,
719                        "queue": {"id": 1, "type": "tx"},
720                        "netns-id": 0,
721                    },
722                }
723            )
724        ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
725
726
727def test_invalid_netns(netns) -> None:
728    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
729    defer(cmd, f"ip link del dev {nk_host}", fail=False)
730
731    ip(f"link set dev {nk_guest} netns {netns.name}")
732    ip(f"link set dev {nk_host} up")
733    ip(f"link set dev {nk_guest} up", ns=netns)
734
735    with NetNSEnter(str(netns)):
736        netdevnl = NetdevFamily()
737        with ksft_raises(NlError) as e:
738            netdevnl.queue_create(
739                {
740                    "ifindex": nk_guest_idx,
741                    "type": "rx",
742                    "lease": {
743                        "ifindex": 1,
744                        "queue": {"id": 0, "type": "rx"},
745                        "netns-id": 999,
746                    },
747                }
748            )
749        ksft_eq(e.exception.nl_msg.error, -errno.ENONET)
750
751
752def test_invalid_phys_ifindex(netns) -> None:
753    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
754    defer(cmd, f"ip link del dev {nk_host}", fail=False)
755
756    ip(f"link set dev {nk_guest} netns {netns.name}")
757    ip(f"link set dev {nk_host} up")
758    ip(f"link set dev {nk_guest} up", ns=netns)
759
760    with NetNSEnter(str(netns)):
761        netdevnl = NetdevFamily()
762        with ksft_raises(NlError) as e:
763            netdevnl.queue_create(
764                {
765                    "ifindex": nk_guest_idx,
766                    "type": "rx",
767                    "lease": {
768                        "ifindex": 99999,
769                        "queue": {"id": 0, "type": "rx"},
770                        "netns-id": 0,
771                    },
772                }
773            )
774        ksft_eq(e.exception.nl_msg.error, -errno.ENODEV)
775
776
777def test_multi_netkit_remove_phys(netns) -> None:
778    nsimdev = NetdevSimDev(port_count=1, queue_count=3)
779    defer(nsimdev.remove)
780    nsim = nsimdev.nsims[0]
781    ip(f"link set dev {nsim.ifname} up")
782
783    # Create two netkit pairs, each leasing a different physical queue
784    nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
785    defer(cmd, f"ip link del dev {nk_host_a}", fail=False)
786
787    nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
788    defer(cmd, f"ip link del dev {nk_host_b}", fail=False)
789
790    ip(f"link set dev {nk_guest_a} netns {netns.name}")
791    ip(f"link set dev {nk_host_a} up")
792    ip(f"link set dev {nk_guest_a} up", ns=netns)
793
794    ip(f"link set dev {nk_guest_b} netns {netns.name}")
795    ip(f"link set dev {nk_host_b} up")
796    ip(f"link set dev {nk_guest_b} up", ns=netns)
797
798    with NetNSEnter(str(netns)):
799        netdevnl = NetdevFamily()
800        netdevnl.queue_create(
801            {
802                "ifindex": nk_guest_a_idx,
803                "type": "rx",
804                "lease": {
805                    "ifindex": nsim.ifindex,
806                    "queue": {"id": 1, "type": "rx"},
807                    "netns-id": 0,
808                },
809            }
810        )
811        netdevnl.queue_create(
812            {
813                "ifindex": nk_guest_b_idx,
814                "type": "rx",
815                "lease": {
816                    "ifindex": nsim.ifindex,
817                    "queue": {"id": 2, "type": "rx"},
818                    "netns-id": 0,
819                },
820            }
821        )
822
823    # Removing the physical device should take down both netkit pairs
824    nsimdev.remove()
825    wait_until(lambda: cmd(f"ip link show dev {nk_host_a}", fail=False).ret != 0
826                       and cmd(f"ip link show dev {nk_host_b}", fail=False).ret != 0)
827    ret = cmd(f"ip link show dev {nk_host_a}", fail=False)
828    ksft_ne(ret.ret, 0)
829    ret = cmd(f"ip link show dev {nk_host_b}", fail=False)
830    ksft_ne(ret.ret, 0)
831
832
833def test_single_remove_phys(_netns) -> None:
834    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
835    defer(nsimdev.remove)
836    nsim = nsimdev.nsims[0]
837    ip(f"link set dev {nsim.ifname} up")
838
839    nk_name, nk_idx = create_netkit_single(rxqueues=2)
840    defer(cmd, f"ip link del dev {nk_name}", fail=False)
841
842    ip(f"link set dev {nk_name} up")
843
844    netdevnl = NetdevFamily()
845    netdevnl.queue_create(
846        {
847            "ifindex": nk_idx,
848            "type": "rx",
849            "lease": {
850                "ifindex": nsim.ifindex,
851                "queue": {"id": 1, "type": "rx"},
852            },
853        }
854    )
855
856    # Removing the physical device should take down the single netkit device
857    nsimdev.remove()
858    wait_until(lambda: cmd(f"ip link show dev {nk_name}", fail=False).ret != 0)
859    ret = cmd(f"ip link show dev {nk_name}", fail=False)
860    ksft_ne(ret.ret, 0)
861
862
863def test_link_flap_virt(netns) -> None:
864    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
865    defer(nsimdev.remove)
866    nsim = nsimdev.nsims[0]
867    ip(f"link set dev {nsim.ifname} up")
868
869    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
870    defer(cmd, f"ip link del dev {nk_host}")
871
872    ip(f"link set dev {nk_guest} netns {netns.name}")
873    ip(f"link set dev {nk_host} up")
874    ip(f"link set dev {nk_guest} up", ns=netns)
875
876    src_queue = 1
877    with NetNSEnter(str(netns)):
878        netdevnl = NetdevFamily()
879        result = netdevnl.queue_create(
880            {
881                "ifindex": nk_guest_idx,
882                "type": "rx",
883                "lease": {
884                    "ifindex": nsim.ifindex,
885                    "queue": {"id": src_queue, "type": "rx"},
886                    "netns-id": 0,
887                },
888            }
889        )
890        nk_queue_id = result["id"]
891
892    netdevnl = NetdevFamily()
893    queue_info = netdevnl.queue_get(
894        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
895    )
896    ksft_in("lease", queue_info)
897    ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
898
899    # Link flap the virtual (netkit) device
900    ip(f"link set dev {nk_guest} down", ns=netns)
901    ip(f"link set dev {nk_guest} up", ns=netns)
902
903    # Verify lease survives the virtual device flap
904    queue_info = netdevnl.queue_get(
905        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
906    )
907    ksft_in("lease", queue_info)
908    ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
909
910
911def test_phys_queue_no_lease(netns) -> None:
912    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
913    defer(nsimdev.remove)
914    nsim = nsimdev.nsims[0]
915    ip(f"link set dev {nsim.ifname} up")
916
917    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
918    defer(cmd, f"ip link del dev {nk_host}")
919
920    ip(f"link set dev {nk_guest} netns {netns.name}")
921    ip(f"link set dev {nk_host} up")
922    ip(f"link set dev {nk_guest} up", ns=netns)
923
924    with NetNSEnter(str(netns)):
925        netdevnl = NetdevFamily()
926        netdevnl.queue_create(
927            {
928                "ifindex": nk_guest_idx,
929                "type": "rx",
930                "lease": {
931                    "ifindex": nsim.ifindex,
932                    "queue": {"id": 1, "type": "rx"},
933                    "netns-id": 0,
934                },
935            }
936        )
937
938    # Physical queue 0 (not leased) should have no lease info
939    netdevnl = NetdevFamily()
940    queue_info = netdevnl.queue_get(
941        {"ifindex": nsim.ifindex, "id": 0, "type": "rx"}
942    )
943    ksft_not_in("lease", queue_info)
944
945    # Physical queue 1 (leased) should have lease info
946    queue_info = netdevnl.queue_get(
947        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
948    )
949    ksft_in("lease", queue_info)
950
951
952def test_same_ns_lease(_netns) -> None:
953    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
954    defer(nsimdev.remove)
955    nsim = nsimdev.nsims[0]
956    ip(f"link set dev {nsim.ifname} up")
957
958    nk_name, nk_idx = create_netkit_single(rxqueues=2)
959    defer(cmd, f"ip link del dev {nk_name}", fail=False)
960
961    ip(f"link set dev {nk_name} up")
962
963    netdevnl = NetdevFamily()
964    result = netdevnl.queue_create(
965        {
966            "ifindex": nk_idx,
967            "type": "rx",
968            "lease": {
969                "ifindex": nsim.ifindex,
970                "queue": {"id": 1, "type": "rx"},
971            },
972        }
973    )
974    ksft_eq(result["id"], 1)
975
976    # Same namespace: lease info should NOT have netns-id
977    queue_info = netdevnl.queue_get(
978        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
979    )
980    ksft_in("lease", queue_info)
981    ksft_eq(queue_info["lease"]["ifindex"], nk_idx)
982    ksft_eq(queue_info["lease"]["queue"]["id"], result["id"])
983    ksft_not_in("netns-id", queue_info["lease"])
984
985
986def test_resize_after_unlease(netns) -> None:
987    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
988    defer(nsimdev.remove)
989    nsim = nsimdev.nsims[0]
990    ip(f"link set dev {nsim.ifname} up")
991
992    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
993
994    ip(f"link set dev {nk_guest} netns {netns.name}")
995    ip(f"link set dev {nk_host} up")
996    ip(f"link set dev {nk_guest} up", ns=netns)
997
998    with NetNSEnter(str(netns)):
999        netdevnl = NetdevFamily()
1000        netdevnl.queue_create(
1001            {
1002                "ifindex": nk_guest_idx,
1003                "type": "rx",
1004                "lease": {
1005                    "ifindex": nsim.ifindex,
1006                    "queue": {"id": 1, "type": "rx"},
1007                    "netns-id": 0,
1008                },
1009            }
1010        )
1011
1012    # Resize should fail while lease is active
1013    ethnl = EthtoolFamily()
1014    with ksft_raises(NlError) as e:
1015        ethnl.channels_set({"header": {"dev-index": nsim.ifindex}, "combined-count": 1})
1016    ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
1017
1018    # Delete netkit, clearing the lease
1019    cmd(f"ip link del dev {nk_host}")
1020
1021    # Resize should now succeed
1022    ethnl.channels_set({"header": {"dev-index": nsim.ifindex}, "combined-count": 1})
1023
1024
1025def test_lease_queue_zero(netns) -> None:
1026    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1027    defer(nsimdev.remove)
1028    nsim = nsimdev.nsims[0]
1029    ip(f"link set dev {nsim.ifname} up")
1030
1031    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1032    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1033
1034    ip(f"link set dev {nk_guest} netns {netns.name}")
1035    ip(f"link set dev {nk_host} up")
1036    ip(f"link set dev {nk_guest} up", ns=netns)
1037
1038    with NetNSEnter(str(netns)):
1039        netdevnl = NetdevFamily()
1040        result = netdevnl.queue_create(
1041            {
1042                "ifindex": nk_guest_idx,
1043                "type": "rx",
1044                "lease": {
1045                    "ifindex": nsim.ifindex,
1046                    "queue": {"id": 0, "type": "rx"},
1047                    "netns-id": 0,
1048                },
1049            }
1050        )
1051        ksft_eq(result["id"], 1)
1052
1053    netdevnl = NetdevFamily()
1054    queue_info = netdevnl.queue_get(
1055        {"ifindex": nsim.ifindex, "id": 0, "type": "rx"}
1056    )
1057    ksft_in("lease", queue_info)
1058    ksft_eq(queue_info["lease"]["queue"]["id"], result["id"])
1059
1060
1061def test_release_and_reuse(netns) -> None:
1062    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1063    defer(nsimdev.remove)
1064    nsim = nsimdev.nsims[0]
1065    ip(f"link set dev {nsim.ifname} up")
1066
1067    src_queue = 1
1068
1069    # First lease
1070    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1071
1072    ip(f"link set dev {nk_guest} netns {netns.name}")
1073    ip(f"link set dev {nk_host} up")
1074    ip(f"link set dev {nk_guest} up", ns=netns)
1075
1076    with NetNSEnter(str(netns)):
1077        netdevnl = NetdevFamily()
1078        netdevnl.queue_create(
1079            {
1080                "ifindex": nk_guest_idx,
1081                "type": "rx",
1082                "lease": {
1083                    "ifindex": nsim.ifindex,
1084                    "queue": {"id": src_queue, "type": "rx"},
1085                    "netns-id": 0,
1086                },
1087            }
1088        )
1089
1090    netdevnl = NetdevFamily()
1091    queue_info = netdevnl.queue_get(
1092        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1093    )
1094    ksft_in("lease", queue_info)
1095
1096    # Delete netkit, freeing the lease
1097    cmd(f"ip link del dev {nk_host}")
1098
1099    queue_info = netdevnl.queue_get(
1100        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1101    )
1102    ksft_not_in("lease", queue_info)
1103
1104    # Re-create netkit and lease the same physical queue again
1105    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1106    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1107
1108    ip(f"link set dev {nk_guest} netns {netns.name}")
1109    ip(f"link set dev {nk_host} up")
1110    ip(f"link set dev {nk_guest} up", ns=netns)
1111
1112    with NetNSEnter(str(netns)):
1113        netdevnl = NetdevFamily()
1114        result = netdevnl.queue_create(
1115            {
1116                "ifindex": nk_guest_idx,
1117                "type": "rx",
1118                "lease": {
1119                    "ifindex": nsim.ifindex,
1120                    "queue": {"id": src_queue, "type": "rx"},
1121                    "netns-id": 0,
1122                },
1123            }
1124        )
1125        ksft_eq(result["id"], 1)
1126
1127    netdevnl = NetdevFamily()
1128    queue_info = netdevnl.queue_get(
1129        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1130    )
1131    ksft_in("lease", queue_info)
1132    ksft_eq(queue_info["lease"]["queue"]["id"], result["id"])
1133
1134
1135def test_two_netkits_same_queue(netns) -> None:
1136    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1137    defer(nsimdev.remove)
1138    nsim = nsimdev.nsims[0]
1139    ip(f"link set dev {nsim.ifname} up")
1140
1141    nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
1142    defer(cmd, f"ip link del dev {nk_host_a}", fail=False)
1143
1144    nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
1145    defer(cmd, f"ip link del dev {nk_host_b}", fail=False)
1146
1147    ip(f"link set dev {nk_guest_a} netns {netns.name}")
1148    ip(f"link set dev {nk_host_a} up")
1149    ip(f"link set dev {nk_guest_a} up", ns=netns)
1150
1151    ip(f"link set dev {nk_guest_b} netns {netns.name}")
1152    ip(f"link set dev {nk_host_b} up")
1153    ip(f"link set dev {nk_guest_b} up", ns=netns)
1154
1155    src_queue = 1
1156    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1157        netdevnl_ns.queue_create(
1158            {
1159                "ifindex": nk_guest_a_idx,
1160                "type": "rx",
1161                "lease": {
1162                    "ifindex": nsim.ifindex,
1163                    "queue": {"id": src_queue, "type": "rx"},
1164                    "netns-id": 0,
1165                },
1166            }
1167        )
1168
1169        with ksft_raises(NlError) as e:
1170            netdevnl_ns.queue_create(
1171                {
1172                    "ifindex": nk_guest_b_idx,
1173                    "type": "rx",
1174                    "lease": {
1175                        "ifindex": nsim.ifindex,
1176                        "queue": {"id": src_queue, "type": "rx"},
1177                        "netns-id": 0,
1178                    },
1179                }
1180            )
1181        ksft_eq(e.exception.nl_msg.error, -errno.EBUSY)
1182
1183
1184def test_l3_mode_lease(netns) -> None:
1185    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1186    defer(nsimdev.remove)
1187    nsim = nsimdev.nsims[0]
1188    ip(f"link set dev {nsim.ifname} up")
1189
1190    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2, mode="l3")
1191    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1192
1193    ip(f"link set dev {nk_guest} netns {netns.name}")
1194    ip(f"link set dev {nk_host} up")
1195    ip(f"link set dev {nk_guest} up", ns=netns)
1196
1197    src_queue = 1
1198    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1199        result = netdevnl_ns.queue_create(
1200            {
1201                "ifindex": nk_guest_idx,
1202                "type": "rx",
1203                "lease": {
1204                    "ifindex": nsim.ifindex,
1205                    "queue": {"id": src_queue, "type": "rx"},
1206                    "netns-id": 0,
1207                },
1208            }
1209        )
1210        ksft_eq(result["id"], 1)
1211
1212    netdevnl = NetdevFamily()
1213    queue_info = netdevnl.queue_get(
1214        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1215    )
1216    ksft_in("lease", queue_info)
1217    ksft_eq(queue_info["lease"]["ifindex"], nk_guest_idx)
1218    ksft_eq(queue_info["lease"]["queue"]["id"], result["id"])
1219
1220
1221def test_single_double_lease(_netns) -> None:
1222    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1223    defer(nsimdev.remove)
1224    nsim = nsimdev.nsims[0]
1225    ip(f"link set dev {nsim.ifname} up")
1226
1227    nk_name, nk_idx = create_netkit_single(rxqueues=3)
1228    defer(cmd, f"ip link del dev {nk_name}", fail=False)
1229
1230    ip(f"link set dev {nk_name} up")
1231
1232    netdevnl = NetdevFamily()
1233    result = netdevnl.queue_create(
1234        {
1235            "ifindex": nk_idx,
1236            "type": "rx",
1237            "lease": {
1238                "ifindex": nsim.ifindex,
1239                "queue": {"id": 1, "type": "rx"},
1240            },
1241        }
1242    )
1243    ksft_eq(result["id"], 1)
1244
1245    with ksft_raises(NlError) as e:
1246        netdevnl.queue_create(
1247            {
1248                "ifindex": nk_idx,
1249                "type": "rx",
1250                "lease": {
1251                    "ifindex": nsim.ifindex,
1252                    "queue": {"id": 1, "type": "rx"},
1253                },
1254            }
1255        )
1256    ksft_eq(e.exception.nl_msg.error, -errno.EBUSY)
1257
1258
1259def test_single_different_lessors(_netns) -> None:
1260    nsimdev_a = NetdevSimDev(port_count=1, queue_count=2)
1261    defer(nsimdev_a.remove)
1262    nsim_a = nsimdev_a.nsims[0]
1263    ip(f"link set dev {nsim_a.ifname} up")
1264
1265    nsimdev_b = NetdevSimDev(port_count=1, queue_count=2)
1266    defer(nsimdev_b.remove)
1267    nsim_b = nsimdev_b.nsims[0]
1268    ip(f"link set dev {nsim_b.ifname} up")
1269
1270    nk_name, nk_idx = create_netkit_single(rxqueues=3)
1271    defer(cmd, f"ip link del dev {nk_name}", fail=False)
1272
1273    ip(f"link set dev {nk_name} up")
1274
1275    netdevnl = NetdevFamily()
1276    netdevnl.queue_create(
1277        {
1278            "ifindex": nk_idx,
1279            "type": "rx",
1280            "lease": {
1281                "ifindex": nsim_a.ifindex,
1282                "queue": {"id": 1, "type": "rx"},
1283            },
1284        }
1285    )
1286
1287    with ksft_raises(NlError) as e:
1288        netdevnl.queue_create(
1289            {
1290                "ifindex": nk_idx,
1291                "type": "rx",
1292                "lease": {
1293                    "ifindex": nsim_b.ifindex,
1294                    "queue": {"id": 1, "type": "rx"},
1295                },
1296            }
1297        )
1298    ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP)
1299
1300
1301def test_cross_ns_netns_id(netns) -> None:
1302    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1303    defer(nsimdev.remove)
1304    nsim = nsimdev.nsims[0]
1305    ip(f"link set dev {nsim.ifname} up")
1306
1307    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1308    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1309
1310    ip(f"link set dev {nk_guest} netns {netns.name}")
1311    ip(f"link set dev {nk_host} up")
1312    ip(f"link set dev {nk_guest} up", ns=netns)
1313
1314    src_queue = 1
1315    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1316        netdevnl_ns.queue_create(
1317            {
1318                "ifindex": nk_guest_idx,
1319                "type": "rx",
1320                "lease": {
1321                    "ifindex": nsim.ifindex,
1322                    "queue": {"id": src_queue, "type": "rx"},
1323                    "netns-id": 0,
1324                },
1325            }
1326        )
1327
1328    netdevnl = NetdevFamily()
1329    queue_info = netdevnl.queue_get(
1330        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1331    )
1332    ksft_in("lease", queue_info)
1333    ksft_in("netns-id", queue_info["lease"])
1334
1335
1336def test_delete_guest_netns(_netns) -> None:
1337    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1338    defer(nsimdev.remove)
1339    nsim = nsimdev.nsims[0]
1340    ip(f"link set dev {nsim.ifname} up")
1341
1342    test_ns = NetNS()
1343    ip("netns set init 0", ns=test_ns)
1344    ip("link set lo up", ns=test_ns)
1345
1346    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1347    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1348
1349    ip(f"link set dev {nk_guest} netns {test_ns.name}")
1350    ip(f"link set dev {nk_host} up")
1351    ip(f"link set dev {nk_guest} up", ns=test_ns)
1352
1353    src_queue = 1
1354    with NetNSEnter(str(test_ns)), NetdevFamily() as netdevnl_ns:
1355        netdevnl_ns.queue_create(
1356            {
1357                "ifindex": nk_guest_idx,
1358                "type": "rx",
1359                "lease": {
1360                    "ifindex": nsim.ifindex,
1361                    "queue": {"id": src_queue, "type": "rx"},
1362                    "netns-id": 0,
1363                },
1364            }
1365        )
1366
1367    netdevnl = NetdevFamily()
1368    queue_info = netdevnl.queue_get(
1369        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1370    )
1371    ksft_in("lease", queue_info)
1372
1373    del test_ns
1374    wait_until(lambda: "lease" not in netdevnl.queue_get(
1375        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}))
1376
1377    queue_info = netdevnl.queue_get(
1378        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1379    )
1380    ksft_not_in("lease", queue_info)
1381
1382    ret = cmd(f"ip link show dev {nk_host}", fail=False)
1383    ksft_ne(ret.ret, 0)
1384
1385
1386def test_move_guest_netns(netns) -> None:
1387    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1388    defer(nsimdev.remove)
1389    nsim = nsimdev.nsims[0]
1390    ip(f"link set dev {nsim.ifname} up")
1391
1392    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1393    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1394
1395    ip(f"link set dev {nk_guest} netns {netns.name}")
1396    ip(f"link set dev {nk_host} up")
1397    ip(f"link set dev {nk_guest} up", ns=netns)
1398
1399    src_queue = 1
1400    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1401        result = netdevnl_ns.queue_create(
1402            {
1403                "ifindex": nk_guest_idx,
1404                "type": "rx",
1405                "lease": {
1406                    "ifindex": nsim.ifindex,
1407                    "queue": {"id": src_queue, "type": "rx"},
1408                    "netns-id": 0,
1409                },
1410            }
1411        )
1412        nk_queue_id = result["id"]
1413
1414    netdevnl = NetdevFamily()
1415    queue_info = netdevnl.queue_get(
1416        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1417    )
1418    ksft_in("lease", queue_info)
1419    ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
1420
1421    new_ns = NetNS()
1422    defer(new_ns.__del__)
1423    ip(f"link set dev {nk_guest} netns {new_ns.name}", ns=netns)
1424
1425    queue_info = netdevnl.queue_get(
1426        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1427    )
1428    ksft_in("lease", queue_info)
1429    ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
1430
1431
1432def test_resize_phys_no_reduction(netns) -> None:
1433    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1434    defer(nsimdev.remove)
1435    nsim = nsimdev.nsims[0]
1436    ip(f"link set dev {nsim.ifname} up")
1437
1438    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1439    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1440
1441    ip(f"link set dev {nk_guest} netns {netns.name}")
1442    ip(f"link set dev {nk_host} up")
1443    ip(f"link set dev {nk_guest} up", ns=netns)
1444
1445    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1446        netdevnl_ns.queue_create(
1447            {
1448                "ifindex": nk_guest_idx,
1449                "type": "rx",
1450                "lease": {
1451                    "ifindex": nsim.ifindex,
1452                    "queue": {"id": 1, "type": "rx"},
1453                    "netns-id": 0,
1454                },
1455            }
1456        )
1457
1458    ethnl = EthtoolFamily()
1459    ethnl.channels_set(
1460        {"header": {"dev-index": nsim.ifindex}, "combined-count": 2}
1461    )
1462
1463    netdevnl = NetdevFamily()
1464    queue_info = netdevnl.queue_get(
1465        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1466    )
1467    ksft_in("lease", queue_info)
1468
1469
1470def test_delete_one_netkit_of_two(netns) -> None:
1471    nsimdev = NetdevSimDev(port_count=1, queue_count=3)
1472    defer(nsimdev.remove)
1473    nsim = nsimdev.nsims[0]
1474    ip(f"link set dev {nsim.ifname} up")
1475
1476    nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
1477    defer(cmd, f"ip link del dev {nk_host_a}", fail=False)
1478
1479    nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
1480    defer(cmd, f"ip link del dev {nk_host_b}", fail=False)
1481
1482    ip(f"link set dev {nk_guest_a} netns {netns.name}")
1483    ip(f"link set dev {nk_host_a} up")
1484    ip(f"link set dev {nk_guest_a} up", ns=netns)
1485
1486    ip(f"link set dev {nk_guest_b} netns {netns.name}")
1487    ip(f"link set dev {nk_host_b} up")
1488    ip(f"link set dev {nk_guest_b} up", ns=netns)
1489
1490    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1491        netdevnl_ns.queue_create(
1492            {
1493                "ifindex": nk_guest_a_idx,
1494                "type": "rx",
1495                "lease": {
1496                    "ifindex": nsim.ifindex,
1497                    "queue": {"id": 1, "type": "rx"},
1498                    "netns-id": 0,
1499                },
1500            }
1501        )
1502        netdevnl_ns.queue_create(
1503            {
1504                "ifindex": nk_guest_b_idx,
1505                "type": "rx",
1506                "lease": {
1507                    "ifindex": nsim.ifindex,
1508                    "queue": {"id": 2, "type": "rx"},
1509                    "netns-id": 0,
1510                },
1511            }
1512        )
1513
1514    netdevnl = NetdevFamily()
1515    q1 = netdevnl.queue_get(
1516        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1517    )
1518    q2 = netdevnl.queue_get(
1519        {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
1520    )
1521    ksft_in("lease", q1)
1522    ksft_in("lease", q2)
1523
1524    cmd(f"ip link del dev {nk_host_a}")
1525
1526    q1 = netdevnl.queue_get(
1527        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1528    )
1529    q2 = netdevnl.queue_get(
1530        {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
1531    )
1532    ksft_not_in("lease", q1)
1533    ksft_in("lease", q2)
1534
1535
1536def test_bind_rx_leased_phys_queue(netns) -> None:
1537    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1538    defer(nsimdev.remove)
1539    nsim = nsimdev.nsims[0]
1540    ip(f"link set dev {nsim.ifname} up")
1541
1542    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1543    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1544
1545    ip(f"link set dev {nk_guest} netns {netns.name}")
1546    ip(f"link set dev {nk_host} up")
1547    ip(f"link set dev {nk_guest} up", ns=netns)
1548
1549    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1550        netdevnl_ns.queue_create(
1551            {
1552                "ifindex": nk_guest_idx,
1553                "type": "rx",
1554                "lease": {
1555                    "ifindex": nsim.ifindex,
1556                    "queue": {"id": 1, "type": "rx"},
1557                    "netns-id": 0,
1558                },
1559            }
1560        )
1561
1562    netdevnl = NetdevFamily()
1563    with ksft_raises(NlError) as e:
1564        netdevnl.bind_rx(
1565            {
1566                "ifindex": nsim.ifindex,
1567                "fd": 0,
1568                "queues": [
1569                    {"id": 0, "type": "rx"},
1570                    {"id": 1, "type": "rx"},
1571                ],
1572            }
1573        )
1574    ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP)
1575
1576
1577def test_resize_phys_shrink_past_leased(netns) -> None:
1578    nsimdev = NetdevSimDev(port_count=1, queue_count=4)
1579    defer(nsimdev.remove)
1580    nsim = nsimdev.nsims[0]
1581    ip(f"link set dev {nsim.ifname} up")
1582
1583    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1584    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1585
1586    ip(f"link set dev {nk_guest} netns {netns.name}")
1587    ip(f"link set dev {nk_host} up")
1588    ip(f"link set dev {nk_guest} up", ns=netns)
1589
1590    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1591        netdevnl_ns.queue_create(
1592            {
1593                "ifindex": nk_guest_idx,
1594                "type": "rx",
1595                "lease": {
1596                    "ifindex": nsim.ifindex,
1597                    "queue": {"id": 1, "type": "rx"},
1598                    "netns-id": 0,
1599                },
1600            }
1601        )
1602
1603    ethnl = EthtoolFamily()
1604
1605    # Shrink past the leased queue — only queue 3 removed, queue 1 untouched
1606    ethnl.channels_set(
1607        {"header": {"dev-index": nsim.ifindex}, "combined-count": 3}
1608    )
1609
1610    netdevnl = NetdevFamily()
1611    queue_info = netdevnl.queue_get(
1612        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1613    )
1614    ksft_in("lease", queue_info)
1615
1616    # Shrink further — queue 2 removed, queue 1 still untouched
1617    ethnl.channels_set(
1618        {"header": {"dev-index": nsim.ifindex}, "combined-count": 2}
1619    )
1620
1621    queue_info = netdevnl.queue_get(
1622        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1623    )
1624    ksft_in("lease", queue_info)
1625
1626    # Shrink into the leased queue — queue 1 is busy, must fail
1627    with ksft_raises(NlError) as e:
1628        ethnl.channels_set(
1629            {"header": {"dev-index": nsim.ifindex}, "combined-count": 1}
1630        )
1631    ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
1632
1633
1634def test_resize_virt_not_supported(netns) -> None:
1635    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1636    defer(nsimdev.remove)
1637    nsim = nsimdev.nsims[0]
1638    ip(f"link set dev {nsim.ifname} up")
1639
1640    nk_host, nk_host_idx, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1641    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1642
1643    ip(f"link set dev {nk_guest} netns {netns.name}")
1644    ip(f"link set dev {nk_host} up")
1645    ip(f"link set dev {nk_guest} up", ns=netns)
1646
1647    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1648        netdevnl_ns.queue_create(
1649            {
1650                "ifindex": nk_guest_idx,
1651                "type": "rx",
1652                "lease": {
1653                    "ifindex": nsim.ifindex,
1654                    "queue": {"id": 1, "type": "rx"},
1655                    "netns-id": 0,
1656                },
1657            }
1658        )
1659
1660    # Channel resize on the netkit host must fail — not supported
1661    ethnl = EthtoolFamily()
1662    with ksft_raises(NlError) as e:
1663        ethnl.channels_set(
1664            {"header": {"dev-index": nk_host_idx}, "combined-count": 1}
1665        )
1666    ksft_eq(e.exception.nl_msg.error, -errno.EOPNOTSUPP)
1667
1668    # Lease must be intact
1669    netdevnl = NetdevFamily()
1670    queue_info = netdevnl.queue_get(
1671        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1672    )
1673    ksft_in("lease", queue_info)
1674
1675
1676def test_lease_devices_down(netns) -> None:
1677    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1678    defer(nsimdev.remove)
1679    nsim = nsimdev.nsims[0]
1680
1681    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1682    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1683
1684    ip(f"link set dev {nk_guest} netns {netns.name}")
1685
1686    # Create lease while both physical and virtual devices are down
1687    src_queue = 1
1688    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1689        result = netdevnl_ns.queue_create(
1690            {
1691                "ifindex": nk_guest_idx,
1692                "type": "rx",
1693                "lease": {
1694                    "ifindex": nsim.ifindex,
1695                    "queue": {"id": src_queue, "type": "rx"},
1696                    "netns-id": 0,
1697                },
1698            }
1699        )
1700        ksft_eq(result["id"], 1)
1701
1702    # Bring devices up before queue_get: netdevsim only instantiates NAPIs in
1703    # ndo_open, and netdev-genl queue_get returns -ENOENT without a NAPI.
1704    ip(f"link set dev {nsim.ifname} up")
1705    ip(f"link set dev {nk_host} up")
1706    ip(f"link set dev {nk_guest} up", ns=netns)
1707
1708    netdevnl = NetdevFamily()
1709    queue_info = netdevnl.queue_get(
1710        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
1711    )
1712    ksft_in("lease", queue_info)
1713    ksft_eq(queue_info["lease"]["queue"]["id"], result["id"])
1714
1715
1716def test_lease_capacity_exhaustion(netns) -> None:
1717    nsimdev = NetdevSimDev(port_count=1, queue_count=4)
1718    defer(nsimdev.remove)
1719    nsim = nsimdev.nsims[0]
1720    ip(f"link set dev {nsim.ifname} up")
1721
1722    # rxqueues=3 means num_rx_queues=3, real_num_rx_queues starts at 1.
1723    # Can create 2 leased queues (real goes 1->2->3) but not a 3rd (3->4 > 3).
1724    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=3)
1725    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1726
1727    ip(f"link set dev {nk_guest} netns {netns.name}")
1728    ip(f"link set dev {nk_host} up")
1729    ip(f"link set dev {nk_guest} up", ns=netns)
1730
1731    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1732        r1 = netdevnl_ns.queue_create(
1733            {
1734                "ifindex": nk_guest_idx,
1735                "type": "rx",
1736                "lease": {
1737                    "ifindex": nsim.ifindex,
1738                    "queue": {"id": 1, "type": "rx"},
1739                    "netns-id": 0,
1740                },
1741            }
1742        )
1743        ksft_eq(r1["id"], 1)
1744
1745        r2 = netdevnl_ns.queue_create(
1746            {
1747                "ifindex": nk_guest_idx,
1748                "type": "rx",
1749                "lease": {
1750                    "ifindex": nsim.ifindex,
1751                    "queue": {"id": 2, "type": "rx"},
1752                    "netns-id": 0,
1753                },
1754            }
1755        )
1756        ksft_eq(r2["id"], 2)
1757
1758        # Third lease fails — netkit queue capacity exhausted
1759        with ksft_raises(NlError) as e:
1760            netdevnl_ns.queue_create(
1761                {
1762                    "ifindex": nk_guest_idx,
1763                    "type": "rx",
1764                    "lease": {
1765                        "ifindex": nsim.ifindex,
1766                        "queue": {"id": 3, "type": "rx"},
1767                        "netns-id": 0,
1768                    },
1769                }
1770            )
1771        ksft_eq(e.exception.nl_msg.error, -errno.EINVAL)
1772
1773    # Verify the two successful leases are intact
1774    netdevnl = NetdevFamily()
1775    q1 = netdevnl.queue_get(
1776        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1777    )
1778    q2 = netdevnl.queue_get(
1779        {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
1780    )
1781    ksft_in("lease", q1)
1782    ksft_in("lease", q2)
1783
1784
1785def test_resize_phys_up(netns) -> None:
1786    nsimdev = NetdevSimDev(port_count=1, queue_count=3)
1787    defer(nsimdev.remove)
1788    nsim = nsimdev.nsims[0]
1789    ip(f"link set dev {nsim.ifname} up")
1790
1791    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1792    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1793
1794    ip(f"link set dev {nk_guest} netns {netns.name}")
1795    ip(f"link set dev {nk_host} up")
1796    ip(f"link set dev {nk_guest} up", ns=netns)
1797
1798    # Shrink nsim first so we have room to grow
1799    ethnl = EthtoolFamily()
1800    ethnl.channels_set(
1801        {"header": {"dev-index": nsim.ifindex}, "combined-count": 2}
1802    )
1803
1804    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1805        netdevnl_ns.queue_create(
1806            {
1807                "ifindex": nk_guest_idx,
1808                "type": "rx",
1809                "lease": {
1810                    "ifindex": nsim.ifindex,
1811                    "queue": {"id": 1, "type": "rx"},
1812                    "netns-id": 0,
1813                },
1814            }
1815        )
1816
1817    # Grow channels — should succeed since leased queue is not removed
1818    ethnl.channels_set(
1819        {"header": {"dev-index": nsim.ifindex}, "combined-count": 3}
1820    )
1821
1822    netdevnl = NetdevFamily()
1823    queue_info = netdevnl.queue_get(
1824        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1825    )
1826    ksft_in("lease", queue_info)
1827
1828    # New queue 2 should exist without a lease
1829    queue_info = netdevnl.queue_get(
1830        {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
1831    )
1832    ksft_not_in("lease", queue_info)
1833
1834
1835def test_multi_ns_lease(netns) -> None:
1836    nsimdev = NetdevSimDev(port_count=1, queue_count=3)
1837    defer(nsimdev.remove)
1838    nsim = nsimdev.nsims[0]
1839    ip(f"link set dev {nsim.ifname} up")
1840
1841    ns_b = NetNS()
1842    defer(ns_b.__del__)
1843    ip("netns set init 0", ns=ns_b)
1844    ip("link set lo up", ns=ns_b)
1845
1846    # First netkit pair, guest in netns
1847    nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
1848    defer(cmd, f"ip link del dev {nk_host_a}", fail=False)
1849    ip(f"link set dev {nk_guest_a} netns {netns.name}")
1850    ip(f"link set dev {nk_host_a} up")
1851    ip(f"link set dev {nk_guest_a} up", ns=netns)
1852
1853    # Second netkit pair, guest in ns_b
1854    nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
1855    defer(cmd, f"ip link del dev {nk_host_b}", fail=False)
1856    ip(f"link set dev {nk_guest_b} netns {ns_b.name}")
1857    ip(f"link set dev {nk_host_b} up")
1858    ip(f"link set dev {nk_guest_b} up", ns=ns_b)
1859
1860    # Lease from netns
1861    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1862        result = netdevnl_ns.queue_create(
1863            {
1864                "ifindex": nk_guest_a_idx,
1865                "type": "rx",
1866                "lease": {
1867                    "ifindex": nsim.ifindex,
1868                    "queue": {"id": 1, "type": "rx"},
1869                    "netns-id": 0,
1870                },
1871            }
1872        )
1873        ksft_eq(result["id"], 1)
1874
1875    # Lease from ns_b (different namespace, same physical device)
1876    with NetNSEnter(str(ns_b)), NetdevFamily() as netdevnl_ns:
1877        result = netdevnl_ns.queue_create(
1878            {
1879                "ifindex": nk_guest_b_idx,
1880                "type": "rx",
1881                "lease": {
1882                    "ifindex": nsim.ifindex,
1883                    "queue": {"id": 2, "type": "rx"},
1884                    "netns-id": 0,
1885                },
1886            }
1887        )
1888        ksft_eq(result["id"], 1)
1889
1890    # Verify both leases from the physical side
1891    netdevnl = NetdevFamily()
1892    q1 = netdevnl.queue_get(
1893        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1894    )
1895    q2 = netdevnl.queue_get(
1896        {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
1897    )
1898    ksft_in("lease", q1)
1899    ksft_in("lease", q2)
1900    ksft_eq(q1["lease"]["ifindex"], nk_guest_a_idx)
1901    ksft_eq(q2["lease"]["ifindex"], nk_guest_b_idx)
1902
1903
1904def test_multi_ns_delete_one(netns) -> None:
1905    nsimdev = NetdevSimDev(port_count=1, queue_count=3)
1906    defer(nsimdev.remove)
1907    nsim = nsimdev.nsims[0]
1908    ip(f"link set dev {nsim.ifname} up")
1909
1910    ns_b = NetNS()
1911    ip("netns set init 0", ns=ns_b)
1912    ip("link set lo up", ns=ns_b)
1913
1914    # First netkit pair, guest in netns (ns_a)
1915    nk_host_a, _, nk_guest_a, nk_guest_a_idx = create_netkit(rxqueues=2)
1916    defer(cmd, f"ip link del dev {nk_host_a}", fail=False)
1917    ip(f"link set dev {nk_guest_a} netns {netns.name}")
1918    ip(f"link set dev {nk_host_a} up")
1919    ip(f"link set dev {nk_guest_a} up", ns=netns)
1920
1921    # Second netkit pair, guest in ns_b
1922    nk_host_b, _, nk_guest_b, nk_guest_b_idx = create_netkit(rxqueues=2)
1923    defer(cmd, f"ip link del dev {nk_host_b}", fail=False)
1924
1925    ip(f"link set dev {nk_guest_b} netns {ns_b.name}")
1926    ip(f"link set dev {nk_host_b} up")
1927    ip(f"link set dev {nk_guest_b} up", ns=ns_b)
1928
1929    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
1930        netdevnl_ns.queue_create(
1931            {
1932                "ifindex": nk_guest_a_idx,
1933                "type": "rx",
1934                "lease": {
1935                    "ifindex": nsim.ifindex,
1936                    "queue": {"id": 1, "type": "rx"},
1937                    "netns-id": 0,
1938                },
1939            }
1940        )
1941
1942    with NetNSEnter(str(ns_b)), NetdevFamily() as netdevnl_ns:
1943        netdevnl_ns.queue_create(
1944            {
1945                "ifindex": nk_guest_b_idx,
1946                "type": "rx",
1947                "lease": {
1948                    "ifindex": nsim.ifindex,
1949                    "queue": {"id": 2, "type": "rx"},
1950                    "netns-id": 0,
1951                },
1952            }
1953        )
1954
1955    netdevnl = NetdevFamily()
1956    q1 = netdevnl.queue_get(
1957        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1958    )
1959    q2 = netdevnl.queue_get(
1960        {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
1961    )
1962    ksft_in("lease", q1)
1963    ksft_in("lease", q2)
1964
1965    # Delete ns_b — destroys nk_guest_b, triggers unlease of queue 2
1966    del ns_b
1967    wait_until(lambda: "lease" not in netdevnl.queue_get(
1968        {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}))
1969
1970    # ns_a's lease on queue 1 must survive
1971    q1 = netdevnl.queue_get(
1972        {"ifindex": nsim.ifindex, "id": 1, "type": "rx"}
1973    )
1974    ksft_in("lease", q1)
1975    ksft_eq(q1["lease"]["ifindex"], nk_guest_a_idx)
1976
1977    # ns_b's lease on queue 2 must be gone
1978    q2 = netdevnl.queue_get(
1979        {"ifindex": nsim.ifindex, "id": 2, "type": "rx"}
1980    )
1981    ksft_not_in("lease", q2)
1982
1983    # nk_host_b should be gone too (phys removal cascades to netkit pair)
1984    ret = cmd(f"ip link show dev {nk_host_b}", fail=False)
1985    ksft_ne(ret.ret, 0)
1986
1987
1988def test_move_phys_netns(netns) -> None:
1989    nsimdev = NetdevSimDev(port_count=1, queue_count=2)
1990    defer(nsimdev.remove)
1991    nsim = nsimdev.nsims[0]
1992    ip(f"link set dev {nsim.ifname} up")
1993
1994    nk_host, _, nk_guest, nk_guest_idx = create_netkit(rxqueues=2)
1995    defer(cmd, f"ip link del dev {nk_host}", fail=False)
1996
1997    ip(f"link set dev {nk_guest} netns {netns.name}")
1998    ip(f"link set dev {nk_host} up")
1999    ip(f"link set dev {nk_guest} up", ns=netns)
2000
2001    src_queue = 1
2002    with NetNSEnter(str(netns)), NetdevFamily() as netdevnl_ns:
2003        nk_queue_id = netdevnl_ns.queue_create(
2004            {
2005                "ifindex": nk_guest_idx,
2006                "type": "rx",
2007                "lease": {
2008                    "ifindex": nsim.ifindex,
2009                    "queue": {"id": src_queue, "type": "rx"},
2010                    "netns-id": 0,
2011                },
2012            }
2013        )["id"]
2014
2015    netdevnl = NetdevFamily()
2016    queue_info = netdevnl.queue_get(
2017        {"ifindex": nsim.ifindex, "id": src_queue, "type": "rx"}
2018    )
2019    ksft_in("lease", queue_info)
2020
2021    # Move the physical device to a new namespace. Move it back to init_net
2022    # on cleanup before the other defers fire (new_ns deletion, nsimdev.remove)
2023    # so nsim lives in a stable namespace when they run.
2024    new_ns = NetNS()
2025    defer(new_ns.__del__)
2026    ip(f"link set dev {nsim.ifname} netns {new_ns.name}")
2027    defer(ip, f"link set dev {nsim.ifname} netns init", ns=new_ns)
2028
2029    # Physical device is now in new_ns — find its ifindex there
2030    all_links = ip("-d link show", json=True, ns=new_ns)
2031    nsim_in_new = [lnk for lnk in all_links if lnk.get("ifname") == nsim.ifname]
2032    new_ifindex = nsim_in_new[0]["ifindex"]
2033
2034    # Moving a device across netns brings it admin-down; bring it back up so
2035    # netdevsim re-creates the NAPI (netdev-genl queue_get needs it).
2036    ip(f"link set dev {nsim.ifname} up", ns=new_ns)
2037
2038    # Verify lease survived the namespace move
2039    with NetNSEnter(str(new_ns)), NetdevFamily() as netdevnl_ns:
2040        queue_info = netdevnl_ns.queue_get(
2041            {"ifindex": new_ifindex, "id": src_queue, "type": "rx"}
2042        )
2043        ksft_in("lease", queue_info)
2044        ksft_eq(queue_info["lease"]["queue"]["id"], nk_queue_id)
2045
2046
2047def main() -> None:
2048    netns = NetNS()
2049    cmd("ip netns attach init 1")
2050    ip("netns set init 0", ns=netns)
2051    ip("link set lo up", ns=netns)
2052
2053    ksft_run(
2054        [
2055            test_remove_phys,
2056            test_double_lease,
2057            test_virtual_lessor,
2058            test_phys_lessee,
2059            test_different_lessors,
2060            test_queue_out_of_range,
2061            test_resize_leased,
2062            test_self_lease,
2063            test_create_tx_type,
2064            test_create_primary,
2065            test_create_limit,
2066            test_link_flap_phys,
2067            test_queue_get_virtual,
2068            test_remove_virt_first,
2069            test_multiple_leases,
2070            test_lease_queue_tx_type,
2071            test_invalid_netns,
2072            test_invalid_phys_ifindex,
2073            test_multi_netkit_remove_phys,
2074            test_single_remove_phys,
2075            test_link_flap_virt,
2076            test_phys_queue_no_lease,
2077            test_same_ns_lease,
2078            test_resize_after_unlease,
2079            test_lease_queue_zero,
2080            test_release_and_reuse,
2081            test_veth_queue_create,
2082            test_two_netkits_same_queue,
2083            test_l3_mode_lease,
2084            test_single_double_lease,
2085            test_single_different_lessors,
2086            test_cross_ns_netns_id,
2087            test_delete_guest_netns,
2088            test_move_guest_netns,
2089            test_resize_phys_no_reduction,
2090            test_delete_one_netkit_of_two,
2091            test_bind_rx_leased_phys_queue,
2092            test_resize_phys_shrink_past_leased,
2093            test_resize_virt_not_supported,
2094            test_lease_devices_down,
2095            test_lease_capacity_exhaustion,
2096            test_resize_phys_up,
2097            test_multi_ns_lease,
2098            test_multi_ns_delete_one,
2099            test_move_phys_netns,
2100        ],
2101        args=(netns,),
2102    )
2103
2104    cmd("ip netns del init", fail=False)
2105    ksft_exit()
2106
2107
2108if __name__ == "__main__":
2109    main()
2110