| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: GPL-2.0 | |
| import time | |
| from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_true | |
| from lib.py import ip | |
| from lib.py import NetNS, NetNSEnter | |
| from lib.py import RtnlFamily | |
| LINK_NETNSID = 100 | |
| LINK_NETNSID2 = 200 | |
| def test_event() -> None: | |
| with NetNS() as ns1, NetNS() as ns2: | |
| with NetNSEnter(str(ns2)): | |
| rtnl = RtnlFamily() | |
| rtnl.ntf_subscribe("rtnlgrp-link") | |
| ip(f"netns set {ns2} {LINK_NETNSID}", ns=str(ns1)) | |
| ip(f"link add netns {ns1} link-netnsid {LINK_NETNSID} dummy1 type dummy") | |
| ip(f"link add netns {ns1} dummy2 type dummy", ns=str(ns2)) | |
| ip("link del dummy1", ns=str(ns1)) | |
| ip("link del dummy2", ns=str(ns1)) | |
| time.sleep(1) | |
| rtnl.check_ntf() | |
| ksft_true(rtnl.async_msg_queue.empty(), | |
| "Received unexpected link notification") | |
| def test_event_all_nsid() -> None: | |
| """NETLINK_LISTEN_ALL_NSID notifications: local events must not | |
| carry nsid even with a self-referential mapping. Remote events | |
| must carry the correct nsid.""" | |
| with NetNS() as ns1, NetNS() as ns2: | |
| net1, net2 = str(ns1), str(ns2) | |
| with NetNSEnter(net1): | |
| rtnl = RtnlFamily() | |
| rtnl.ntf_listen_all_nsid() | |
| rtnl.ntf_subscribe("rtnlgrp-link") | |
| # Case 1: no nsid assigned, local event, no nsid expected. | |
| ip("link add dummy-lo type dummy", ns=net1) | |
| # Case 2: self-referential nsid, local event, still no nsid. | |
| ip(f"netns set {net1} {LINK_NETNSID}", ns=net1) | |
| ip("link add dummy-sr type dummy", ns=net1) | |
| # Case 3: remote event, nsid present. | |
| ip(f"netns set {net2} {LINK_NETNSID2}", ns=net1) | |
| ip("link add dummy-re type dummy", ns=net2) | |
| # Collect the three newlink events, ignoring unrelated noise. | |
| events = {} | |
| for msg in rtnl.poll_ntf(duration=1): | |
| if msg['name'] == 'getlink': | |
| ifname = msg['msg'].get('ifname') | |
| if ifname in ('dummy-lo', 'dummy-sr', 'dummy-re'): | |
| events[ifname] = msg | |
| if len(events) == 3: | |
| break | |
| ksft_true('dummy-lo' in events, "missing local event") | |
| ksft_true(events['dummy-lo'].get('nsid') is None, | |
| "local event without nsid should not carry nsid") | |
| ksft_true('dummy-sr' in events, "missing self-ref event") | |
| ksft_true(events['dummy-sr'].get('nsid') is None, | |
| "local event with self-ref nsid should not carry nsid") | |
| ksft_true('dummy-re' in events, "missing remote event") | |
| ksft_eq(events['dummy-re'].get('nsid'), LINK_NETNSID2, | |
| "remote event should carry nsid") | |
| ip("link del dummy-lo", ns=net1) | |
| ip("link del dummy-sr", ns=net1) | |
| ip("link del dummy-re", ns=net2) | |
| def validate_link_netns(netns, ifname, link_netnsid) -> bool: | |
| link_info = ip(f"-d link show dev {ifname}", ns=netns, json=True) | |
| if not link_info: | |
| return False | |
| return link_info[0].get("link_netnsid") == link_netnsid | |
| def test_link_net() -> None: | |
| configs = [ | |
| # type, common args, type args, fallback to dev_net | |
| ("ipvlan", "link dummy1", "", False), | |
| ("macsec", "link dummy1", "", False), | |
| ("macvlan", "link dummy1", "", False), | |
| ("macvtap", "link dummy1", "", False), | |
| ("vlan", "link dummy1", "id 100", False), | |
| ("gre", "", "local 192.0.2.1", True), | |
| ("vti", "", "local 192.0.2.1", True), | |
| ("ipip", "", "local 192.0.2.1", True), | |
| ("ip6gre", "", "local 2001:db8::1", True), | |
| ("ip6tnl", "", "local 2001:db8::1", True), | |
| ("vti6", "", "local 2001:db8::1", True), | |
| ("sit", "", "local 192.0.2.1", True), | |
| ("xfrm", "", "if_id 1", True), | |
| ] | |
| with NetNS() as ns1, NetNS() as ns2, NetNS() as ns3: | |
| net1, net2, net3 = str(ns1), str(ns2), str(ns3) | |
| # prepare link netnsid and a dummy link needed by certain drivers | |
| ip(f"netns set {net3} {LINK_NETNSID}", ns=str(net2)) | |
| ip("link add dummy1 type dummy", ns=net3) | |
| cases = [ | |
| # source, "netns", "link-netns", expected link-netns | |
| (net3, None, None, None, None), | |
| (net3, net2, None, None, LINK_NETNSID), | |
| (net2, None, net3, LINK_NETNSID, LINK_NETNSID), | |
| (net1, net2, net3, LINK_NETNSID, LINK_NETNSID), | |
| ] | |
| for src_net, netns, link_netns, exp1, exp2 in cases: | |
| tgt_net = netns or src_net | |
| for typ, cargs, targs, fb_dev_net in configs: | |
| cmd = "link add" | |
| if netns: | |
| cmd += f" netns {netns}" | |
| if link_netns: | |
| cmd += f" link-netns {link_netns}" | |
| cmd += f" {cargs} foo type {typ} {targs}" | |
| ip(cmd, ns=src_net) | |
| if fb_dev_net: | |
| ksft_true(validate_link_netns(tgt_net, "foo", exp1), | |
| f"{typ} link_netns validation failed") | |
| else: | |
| ksft_true(validate_link_netns(tgt_net, "foo", exp2), | |
| f"{typ} link_netns validation failed") | |
| ip(f"link del foo", ns=tgt_net) | |
| def test_peer_net() -> None: | |
| types = [ | |
| "vxcan", | |
| "netkit", | |
| "veth", | |
| ] | |
| with NetNS() as ns1, NetNS() as ns2, NetNS() as ns3, NetNS() as ns4: | |
| net1, net2, net3, net4 = str(ns1), str(ns2), str(ns3), str(ns4) | |
| ip(f"netns set {net3} {LINK_NETNSID}", ns=str(net2)) | |
| cases = [ | |
| # source, "netns", "link-netns", "peer netns", expected | |
| (net1, None, None, None, None), | |
| (net1, net2, None, None, None), | |
| (net2, None, net3, None, LINK_NETNSID), | |
| (net1, net2, net3, None, None), | |
| (net2, None, None, net3, LINK_NETNSID), | |
| (net1, net2, None, net3, LINK_NETNSID), | |
| (net2, None, net2, net3, LINK_NETNSID), | |
| (net1, net2, net4, net3, LINK_NETNSID), | |
| ] | |
| for src_net, netns, link_netns, peer_netns, exp in cases: | |
| tgt_net = netns or src_net | |
| for typ in types: | |
| cmd = "link add" | |
| if netns: | |
| cmd += f" netns {netns}" | |
| if link_netns: | |
| cmd += f" link-netns {link_netns}" | |
| cmd += f" foo type {typ}" | |
| if peer_netns: | |
| cmd += f" peer netns {peer_netns}" | |
| ip(cmd, ns=src_net) | |
| ksft_true(validate_link_netns(tgt_net, "foo", exp), | |
| f"{typ} peer_netns validation failed") | |
| ip(f"link del foo", ns=tgt_net) | |
| def main() -> None: | |
| ksft_run([ | |
| test_event, | |
| test_event_all_nsid, | |
| test_link_net, | |
| test_peer_net, | |
| ]) | |
| ksft_exit() | |
| if __name__ == "__main__": | |
| main() |