xref: /freebsd-14.2/tests/atf_python/sys/net/vnet.py (revision 09d61b28)
18eb2bee6SAlexander V. Chernikov#!/usr/local/bin/python3
2cfc9cf9bSAlexander V. Chernikovimport copy
3cfc9cf9bSAlexander V. Chernikovimport ipaddress
48eb2bee6SAlexander V. Chernikovimport os
5584ad412SAlexander V. Chernikovimport re
68eb2bee6SAlexander V. Chernikovimport socket
7cfc9cf9bSAlexander V. Chernikovimport sys
88eb2bee6SAlexander V. Chernikovimport time
9584ad412SAlexander V. Chernikovfrom multiprocessing import connection
10cfc9cf9bSAlexander V. Chernikovfrom multiprocessing import Pipe
11cfc9cf9bSAlexander V. Chernikovfrom multiprocessing import Process
12cfc9cf9bSAlexander V. Chernikovfrom typing import Dict
138eb2bee6SAlexander V. Chernikovfrom typing import List
14cfc9cf9bSAlexander V. Chernikovfrom typing import NamedTuple
158eb2bee6SAlexander V. Chernikov
16cfc9cf9bSAlexander V. Chernikovfrom atf_python.sys.net.tools import ToolsHelper
17f63825ffSAlexander V. Chernikovfrom atf_python.utils import BaseTest
18f63825ffSAlexander V. Chernikovfrom atf_python.utils import libc
198eb2bee6SAlexander V. Chernikov
20cfc9cf9bSAlexander V. Chernikov
21cfc9cf9bSAlexander V. Chernikovdef run_cmd(cmd: str, verbose=True) -> str:
228eb2bee6SAlexander V. Chernikov    print("run: '{}'".format(cmd))
238eb2bee6SAlexander V. Chernikov    return os.popen(cmd).read()
248eb2bee6SAlexander V. Chernikov
258eb2bee6SAlexander V. Chernikov
26f63825ffSAlexander V. Chernikovdef get_topology_id(test_id: str) -> str:
27f63825ffSAlexander V. Chernikov    """
28f63825ffSAlexander V. Chernikov    Gets a unique topology id based on the pytest test_id.
29f63825ffSAlexander V. Chernikov      "test_ip6_output.py::TestIP6Output::test_output6_pktinfo[ipandif]" ->
30f63825ffSAlexander V. Chernikov      "TestIP6Output:test_output6_pktinfo[ipandif]"
31f63825ffSAlexander V. Chernikov    """
32f63825ffSAlexander V. Chernikov    return ":".join(test_id.split("::")[-2:])
33f63825ffSAlexander V. Chernikov
34f63825ffSAlexander V. Chernikov
35cfc9cf9bSAlexander V. Chernikovdef convert_test_name(test_name: str) -> str:
36cfc9cf9bSAlexander V. Chernikov    """Convert test name to a string that can be used in the file/jail names"""
37cfc9cf9bSAlexander V. Chernikov    ret = ""
38cfc9cf9bSAlexander V. Chernikov    for char in test_name:
39f63825ffSAlexander V. Chernikov        if char.isalnum() or char in ("_", "-", ":"):
40cfc9cf9bSAlexander V. Chernikov            ret += char
41cfc9cf9bSAlexander V. Chernikov        elif char in ("["):
42cfc9cf9bSAlexander V. Chernikov            ret += "_"
43cfc9cf9bSAlexander V. Chernikov    return ret
448eb2bee6SAlexander V. Chernikov
45cfc9cf9bSAlexander V. Chernikov
46cfc9cf9bSAlexander V. Chernikovclass VnetInterface(object):
478eb2bee6SAlexander V. Chernikov    # defines from net/if_types.h
488eb2bee6SAlexander V. Chernikov    IFT_LOOP = 0x18
498eb2bee6SAlexander V. Chernikov    IFT_ETHER = 0x06
508eb2bee6SAlexander V. Chernikov
51cfc9cf9bSAlexander V. Chernikov    def __init__(self, iface_alias: str, iface_name: str):
528eb2bee6SAlexander V. Chernikov        self.name = iface_name
53cfc9cf9bSAlexander V. Chernikov        self.alias = iface_alias
548eb2bee6SAlexander V. Chernikov        self.vnet_name = ""
558eb2bee6SAlexander V. Chernikov        self.jailed = False
56cfc9cf9bSAlexander V. Chernikov        self.addr_map: Dict[str, Dict] = {"inet6": {}, "inet": {}}
57cfc9cf9bSAlexander V. Chernikov        self.prefixes4: List[List[str]] = []
58cfc9cf9bSAlexander V. Chernikov        self.prefixes6: List[List[str]] = []
598eb2bee6SAlexander V. Chernikov        if iface_name.startswith("lo"):
608eb2bee6SAlexander V. Chernikov            self.iftype = self.IFT_LOOP
618eb2bee6SAlexander V. Chernikov        else:
628eb2bee6SAlexander V. Chernikov            self.iftype = self.IFT_ETHER
638eb2bee6SAlexander V. Chernikov
648eb2bee6SAlexander V. Chernikov    @property
658eb2bee6SAlexander V. Chernikov    def ifindex(self):
668eb2bee6SAlexander V. Chernikov        return socket.if_nametoindex(self.name)
678eb2bee6SAlexander V. Chernikov
68cfc9cf9bSAlexander V. Chernikov    @property
69cfc9cf9bSAlexander V. Chernikov    def first_ipv6(self):
70cfc9cf9bSAlexander V. Chernikov        d = self.addr_map["inet6"]
71cfc9cf9bSAlexander V. Chernikov        return d[next(iter(d))]
72cfc9cf9bSAlexander V. Chernikov
73cfc9cf9bSAlexander V. Chernikov    @property
74cfc9cf9bSAlexander V. Chernikov    def first_ipv4(self):
75cfc9cf9bSAlexander V. Chernikov        d = self.addr_map["inet"]
76cfc9cf9bSAlexander V. Chernikov        return d[next(iter(d))]
77cfc9cf9bSAlexander V. Chernikov
788eb2bee6SAlexander V. Chernikov    def set_vnet(self, vnet_name: str):
798eb2bee6SAlexander V. Chernikov        self.vnet_name = vnet_name
808eb2bee6SAlexander V. Chernikov
818eb2bee6SAlexander V. Chernikov    def set_jailed(self, jailed: bool):
828eb2bee6SAlexander V. Chernikov        self.jailed = jailed
838eb2bee6SAlexander V. Chernikov
84cfc9cf9bSAlexander V. Chernikov    def run_cmd(
85cfc9cf9bSAlexander V. Chernikov        self,
86cfc9cf9bSAlexander V. Chernikov        cmd,
87cfc9cf9bSAlexander V. Chernikov        verbose=False,
88cfc9cf9bSAlexander V. Chernikov    ):
898eb2bee6SAlexander V. Chernikov        if self.vnet_name and not self.jailed:
908eb2bee6SAlexander V. Chernikov            cmd = "jexec {} {}".format(self.vnet_name, cmd)
91cfc9cf9bSAlexander V. Chernikov        return run_cmd(cmd, verbose)
928eb2bee6SAlexander V. Chernikov
938eb2bee6SAlexander V. Chernikov    @classmethod
94cfc9cf9bSAlexander V. Chernikov    def setup_loopback(cls, vnet_name: str):
95cfc9cf9bSAlexander V. Chernikov        lo = VnetInterface("", "lo0")
96cfc9cf9bSAlexander V. Chernikov        lo.set_vnet(vnet_name)
974856aeaaSJose Luis Duran        lo.setup_addr("127.0.0.1/8")
98cfc9cf9bSAlexander V. Chernikov        lo.turn_up()
99cfc9cf9bSAlexander V. Chernikov
100cfc9cf9bSAlexander V. Chernikov    @classmethod
101cfc9cf9bSAlexander V. Chernikov    def create_iface(cls, alias_name: str, iface_name: str) -> List["VnetInterface"]:
1028eb2bee6SAlexander V. Chernikov        name = run_cmd("/sbin/ifconfig {} create".format(iface_name)).rstrip()
1038eb2bee6SAlexander V. Chernikov        if not name:
1048eb2bee6SAlexander V. Chernikov            raise Exception("Unable to create iface {}".format(iface_name))
105cfc9cf9bSAlexander V. Chernikov        ret = [cls(alias_name, name)]
1068eb2bee6SAlexander V. Chernikov        if name.startswith("epair"):
107cfc9cf9bSAlexander V. Chernikov            ret.append(cls(alias_name, name[:-1] + "b"))
108cfc9cf9bSAlexander V. Chernikov        return ret
1098eb2bee6SAlexander V. Chernikov
110cfc9cf9bSAlexander V. Chernikov    def setup_addr(self, _addr: str):
111cfc9cf9bSAlexander V. Chernikov        addr = ipaddress.ip_interface(_addr)
112cfc9cf9bSAlexander V. Chernikov        if addr.version == 6:
1138eb2bee6SAlexander V. Chernikov            family = "inet6"
1147064c94aSAlexander V. Chernikov            cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
1158eb2bee6SAlexander V. Chernikov        else:
1168eb2bee6SAlexander V. Chernikov            family = "inet"
1177064c94aSAlexander V. Chernikov            if self.addr_map[family]:
1187064c94aSAlexander V. Chernikov                cmd = "/sbin/ifconfig {} alias {}".format(self.name, addr)
1197064c94aSAlexander V. Chernikov            else:
1208eb2bee6SAlexander V. Chernikov                cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
1218eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1227064c94aSAlexander V. Chernikov        self.addr_map[family][str(addr.ip)] = addr
1238eb2bee6SAlexander V. Chernikov
124cfc9cf9bSAlexander V. Chernikov    def delete_addr(self, _addr: str):
125cfc9cf9bSAlexander V. Chernikov        addr = ipaddress.ip_address(_addr)
126cfc9cf9bSAlexander V. Chernikov        if addr.version == 6:
127cfc9cf9bSAlexander V. Chernikov            family = "inet6"
1288eb2bee6SAlexander V. Chernikov            cmd = "/sbin/ifconfig {} inet6 {} delete".format(self.name, addr)
1298eb2bee6SAlexander V. Chernikov        else:
130cfc9cf9bSAlexander V. Chernikov            family = "inet"
1318eb2bee6SAlexander V. Chernikov            cmd = "/sbin/ifconfig {} -alias {}".format(self.name, addr)
1328eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
133cfc9cf9bSAlexander V. Chernikov        del self.addr_map[family][str(addr)]
1348eb2bee6SAlexander V. Chernikov
1358eb2bee6SAlexander V. Chernikov    def turn_up(self):
1368eb2bee6SAlexander V. Chernikov        cmd = "/sbin/ifconfig {} up".format(self.name)
1378eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1388eb2bee6SAlexander V. Chernikov
1398eb2bee6SAlexander V. Chernikov    def enable_ipv6(self):
1408eb2bee6SAlexander V. Chernikov        cmd = "/usr/sbin/ndp -i {} -disabled".format(self.name)
1418eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1428eb2bee6SAlexander V. Chernikov
143cfc9cf9bSAlexander V. Chernikov    def has_tentative(self) -> bool:
144cfc9cf9bSAlexander V. Chernikov        """True if an interface has some addresses in tenative state"""
145cfc9cf9bSAlexander V. Chernikov        cmd = "/sbin/ifconfig {} inet6".format(self.name)
146cfc9cf9bSAlexander V. Chernikov        out = self.run_cmd(cmd, verbose=False)
147cfc9cf9bSAlexander V. Chernikov        for line in out.splitlines():
148cfc9cf9bSAlexander V. Chernikov            if "tentative" in line:
1498eb2bee6SAlexander V. Chernikov                return True
1508eb2bee6SAlexander V. Chernikov        return False
1518eb2bee6SAlexander V. Chernikov
1528eb2bee6SAlexander V. Chernikov
153cfc9cf9bSAlexander V. Chernikovclass IfaceFactory(object):
154cfc9cf9bSAlexander V. Chernikov    INTERFACES_FNAME = "created_ifaces.lst"
155f3065e76SAlexander V. Chernikov    AUTODELETE_TYPES = ("epair", "gif", "gre", "lo", "tap", "tun")
156cfc9cf9bSAlexander V. Chernikov
157f63825ffSAlexander V. Chernikov    def __init__(self):
158cfc9cf9bSAlexander V. Chernikov        self.file_name = self.INTERFACES_FNAME
159cfc9cf9bSAlexander V. Chernikov
160cfc9cf9bSAlexander V. Chernikov    def _register_iface(self, iface_name: str):
161cfc9cf9bSAlexander V. Chernikov        with open(self.file_name, "a") as f:
162cfc9cf9bSAlexander V. Chernikov            f.write(iface_name + "\n")
163cfc9cf9bSAlexander V. Chernikov
16420ea7f26SAlexander V. Chernikov    def _list_ifaces(self) -> List[str]:
16520ea7f26SAlexander V. Chernikov        ret: List[str] = []
1668eb2bee6SAlexander V. Chernikov        try:
167cfc9cf9bSAlexander V. Chernikov            with open(self.file_name, "r") as f:
1688eb2bee6SAlexander V. Chernikov                for line in f:
16920ea7f26SAlexander V. Chernikov                    ret.append(line.strip())
17020ea7f26SAlexander V. Chernikov        except OSError:
17120ea7f26SAlexander V. Chernikov            pass
17220ea7f26SAlexander V. Chernikov        return ret
17320ea7f26SAlexander V. Chernikov
17420ea7f26SAlexander V. Chernikov    def create_iface(self, alias_name: str, iface_name: str) -> List[VnetInterface]:
17520ea7f26SAlexander V. Chernikov        ifaces = VnetInterface.create_iface(alias_name, iface_name)
17620ea7f26SAlexander V. Chernikov        for iface in ifaces:
17720ea7f26SAlexander V. Chernikov            if not self.is_autodeleted(iface.name):
17820ea7f26SAlexander V. Chernikov                self._register_iface(iface.name)
17920ea7f26SAlexander V. Chernikov        return ifaces
18020ea7f26SAlexander V. Chernikov
18120ea7f26SAlexander V. Chernikov    @staticmethod
18220ea7f26SAlexander V. Chernikov    def is_autodeleted(iface_name: str) -> bool:
18320ea7f26SAlexander V. Chernikov        iface_type = re.split(r"\d+", iface_name)[0]
18420ea7f26SAlexander V. Chernikov        return iface_type in IfaceFactory.AUTODELETE_TYPES
18520ea7f26SAlexander V. Chernikov
18620ea7f26SAlexander V. Chernikov    def cleanup_vnet_interfaces(self, vnet_name: str) -> List[str]:
18720ea7f26SAlexander V. Chernikov        """Destroys"""
18820ea7f26SAlexander V. Chernikov        ifaces_lst = ToolsHelper.get_output(
18920ea7f26SAlexander V. Chernikov            "/usr/sbin/jexec {} ifconfig -l".format(vnet_name)
19020ea7f26SAlexander V. Chernikov        )
19120ea7f26SAlexander V. Chernikov        for iface_name in ifaces_lst.split():
19220ea7f26SAlexander V. Chernikov            if not self.is_autodeleted(iface_name):
19320ea7f26SAlexander V. Chernikov                if iface_name not in self._list_ifaces():
19420ea7f26SAlexander V. Chernikov                    print("Skipping interface {}:{}".format(vnet_name, iface_name))
19520ea7f26SAlexander V. Chernikov                    continue
19620ea7f26SAlexander V. Chernikov            run_cmd(
19720ea7f26SAlexander V. Chernikov                "/usr/sbin/jexec {} ifconfig {} destroy".format(vnet_name, iface_name)
19820ea7f26SAlexander V. Chernikov            )
19920ea7f26SAlexander V. Chernikov
20020ea7f26SAlexander V. Chernikov    def cleanup(self):
20120ea7f26SAlexander V. Chernikov        try:
202cfc9cf9bSAlexander V. Chernikov            os.unlink(self.INTERFACES_FNAME)
20320ea7f26SAlexander V. Chernikov        except OSError:
2048eb2bee6SAlexander V. Chernikov            pass
2058eb2bee6SAlexander V. Chernikov
2068eb2bee6SAlexander V. Chernikov
207cfc9cf9bSAlexander V. Chernikovclass VnetInstance(object):
208cfc9cf9bSAlexander V. Chernikov    def __init__(
209cfc9cf9bSAlexander V. Chernikov        self, vnet_alias: str, vnet_name: str, jid: int, ifaces: List[VnetInterface]
210cfc9cf9bSAlexander V. Chernikov    ):
211cfc9cf9bSAlexander V. Chernikov        self.name = vnet_name
212cfc9cf9bSAlexander V. Chernikov        self.alias = vnet_alias  # reference in the test topology
213cfc9cf9bSAlexander V. Chernikov        self.jid = jid
214cfc9cf9bSAlexander V. Chernikov        self.ifaces = ifaces
215cfc9cf9bSAlexander V. Chernikov        self.iface_alias_map = {}  # iface.alias: iface
216cfc9cf9bSAlexander V. Chernikov        self.iface_map = {}  # iface.name: iface
2178eb2bee6SAlexander V. Chernikov        for iface in ifaces:
218cfc9cf9bSAlexander V. Chernikov            iface.set_vnet(vnet_name)
219cfc9cf9bSAlexander V. Chernikov            iface.set_jailed(True)
220cfc9cf9bSAlexander V. Chernikov            self.iface_alias_map[iface.alias] = iface
221cfc9cf9bSAlexander V. Chernikov            self.iface_map[iface.name] = iface
222584ad412SAlexander V. Chernikov            # Allow reference to interfce aliases as attributes
223584ad412SAlexander V. Chernikov            setattr(self, iface.alias, iface)
224cfc9cf9bSAlexander V. Chernikov        self.need_dad = False  # Disable duplicate address detection by default
225cfc9cf9bSAlexander V. Chernikov        self.attached = False
226cfc9cf9bSAlexander V. Chernikov        self.pipe = None
227cfc9cf9bSAlexander V. Chernikov        self.subprocess = None
228cfc9cf9bSAlexander V. Chernikov
229cfc9cf9bSAlexander V. Chernikov    def run_vnet_cmd(self, cmd):
230cfc9cf9bSAlexander V. Chernikov        if not self.attached:
231cfc9cf9bSAlexander V. Chernikov            cmd = "jexec {} {}".format(self.name, cmd)
232cfc9cf9bSAlexander V. Chernikov        return run_cmd(cmd)
233cfc9cf9bSAlexander V. Chernikov
234cfc9cf9bSAlexander V. Chernikov    def disable_dad(self):
235cfc9cf9bSAlexander V. Chernikov        self.run_vnet_cmd("/sbin/sysctl net.inet6.ip6.dad_count=0")
236cfc9cf9bSAlexander V. Chernikov
237cfc9cf9bSAlexander V. Chernikov    def set_pipe(self, pipe):
238cfc9cf9bSAlexander V. Chernikov        self.pipe = pipe
239cfc9cf9bSAlexander V. Chernikov
240cfc9cf9bSAlexander V. Chernikov    def set_subprocess(self, p):
241cfc9cf9bSAlexander V. Chernikov        self.subprocess = p
2428eb2bee6SAlexander V. Chernikov
2438eb2bee6SAlexander V. Chernikov    @staticmethod
2448eb2bee6SAlexander V. Chernikov    def attach_jid(jid: int):
2453873bdc2SAlexander V. Chernikov        error_code = libc.jail_attach(jid)
2463873bdc2SAlexander V. Chernikov        if error_code != 0:
2473873bdc2SAlexander V. Chernikov            raise Exception("jail_attach() failed: errno {}".format(error_code))
2488eb2bee6SAlexander V. Chernikov
2498eb2bee6SAlexander V. Chernikov    def attach(self):
2508eb2bee6SAlexander V. Chernikov        self.attach_jid(self.jid)
251cfc9cf9bSAlexander V. Chernikov        self.attached = True
2528eb2bee6SAlexander V. Chernikov
2538eb2bee6SAlexander V. Chernikov
254cfc9cf9bSAlexander V. Chernikovclass VnetFactory(object):
255cfc9cf9bSAlexander V. Chernikov    JAILS_FNAME = "created_jails.lst"
256cfc9cf9bSAlexander V. Chernikov
257f63825ffSAlexander V. Chernikov    def __init__(self, topology_id: str):
258f63825ffSAlexander V. Chernikov        self.topology_id = topology_id
259cfc9cf9bSAlexander V. Chernikov        self.file_name = self.JAILS_FNAME
260cfc9cf9bSAlexander V. Chernikov        self._vnets: List[str] = []
261cfc9cf9bSAlexander V. Chernikov
262cfc9cf9bSAlexander V. Chernikov    def _register_vnet(self, vnet_name: str):
263cfc9cf9bSAlexander V. Chernikov        self._vnets.append(vnet_name)
264cfc9cf9bSAlexander V. Chernikov        with open(self.file_name, "a") as f:
265cfc9cf9bSAlexander V. Chernikov            f.write(vnet_name + "\n")
266cfc9cf9bSAlexander V. Chernikov
267cfc9cf9bSAlexander V. Chernikov    @staticmethod
268cfc9cf9bSAlexander V. Chernikov    def _wait_interfaces(vnet_name: str, ifaces: List[str]) -> List[str]:
269cfc9cf9bSAlexander V. Chernikov        cmd = "jexec {} /sbin/ifconfig -l".format(vnet_name)
270cfc9cf9bSAlexander V. Chernikov        not_matched: List[str] = []
271cfc9cf9bSAlexander V. Chernikov        for i in range(50):
272cfc9cf9bSAlexander V. Chernikov            vnet_ifaces = run_cmd(cmd).strip().split(" ")
273cfc9cf9bSAlexander V. Chernikov            not_matched = []
274cfc9cf9bSAlexander V. Chernikov            for iface_name in ifaces:
275cfc9cf9bSAlexander V. Chernikov                if iface_name not in vnet_ifaces:
276cfc9cf9bSAlexander V. Chernikov                    not_matched.append(iface_name)
277cfc9cf9bSAlexander V. Chernikov            if len(not_matched) == 0:
278cfc9cf9bSAlexander V. Chernikov                return []
279cfc9cf9bSAlexander V. Chernikov            time.sleep(0.1)
280cfc9cf9bSAlexander V. Chernikov        return not_matched
281cfc9cf9bSAlexander V. Chernikov
282cfc9cf9bSAlexander V. Chernikov    def create_vnet(self, vnet_alias: str, ifaces: List[VnetInterface]):
283f63825ffSAlexander V. Chernikov        vnet_name = "pytest:{}".format(convert_test_name(self.topology_id))
284cfc9cf9bSAlexander V. Chernikov        if self._vnets:
285cfc9cf9bSAlexander V. Chernikov            # add number to distinguish jails
286cfc9cf9bSAlexander V. Chernikov            vnet_name = "{}_{}".format(vnet_name, len(self._vnets) + 1)
287cfc9cf9bSAlexander V. Chernikov        iface_cmds = " ".join(["vnet.interface={}".format(i.name) for i in ifaces])
288cfc9cf9bSAlexander V. Chernikov        cmd = "/usr/sbin/jail -i -c name={} persist vnet {}".format(
289cfc9cf9bSAlexander V. Chernikov            vnet_name, iface_cmds
290cfc9cf9bSAlexander V. Chernikov        )
291f63825ffSAlexander V. Chernikov        jid = 0
292f63825ffSAlexander V. Chernikov        try:
293cfc9cf9bSAlexander V. Chernikov            jid_str = run_cmd(cmd)
294cfc9cf9bSAlexander V. Chernikov            jid = int(jid_str)
29520ea7f26SAlexander V. Chernikov        except ValueError:
296f63825ffSAlexander V. Chernikov            print("Jail creation failed, output: {}".format(jid_str))
297f63825ffSAlexander V. Chernikov            raise
298cfc9cf9bSAlexander V. Chernikov        self._register_vnet(vnet_name)
299cfc9cf9bSAlexander V. Chernikov
300cfc9cf9bSAlexander V. Chernikov        # Run expedited version of routing
301cfc9cf9bSAlexander V. Chernikov        VnetInterface.setup_loopback(vnet_name)
302cfc9cf9bSAlexander V. Chernikov
303cfc9cf9bSAlexander V. Chernikov        not_found = self._wait_interfaces(vnet_name, [i.name for i in ifaces])
304cfc9cf9bSAlexander V. Chernikov        if not_found:
305cfc9cf9bSAlexander V. Chernikov            raise Exception(
306cfc9cf9bSAlexander V. Chernikov                "Interfaces {} has not appeared in vnet {}".format(not_found, vnet_name)
307cfc9cf9bSAlexander V. Chernikov            )
308cfc9cf9bSAlexander V. Chernikov        return VnetInstance(vnet_alias, vnet_name, jid, ifaces)
309cfc9cf9bSAlexander V. Chernikov
310cfc9cf9bSAlexander V. Chernikov    def cleanup(self):
31120ea7f26SAlexander V. Chernikov        iface_factory = IfaceFactory()
312cfc9cf9bSAlexander V. Chernikov        try:
313cfc9cf9bSAlexander V. Chernikov            with open(self.file_name) as f:
314cfc9cf9bSAlexander V. Chernikov                for line in f:
315f63825ffSAlexander V. Chernikov                    vnet_name = line.strip()
31620ea7f26SAlexander V. Chernikov                    iface_factory.cleanup_vnet_interfaces(vnet_name)
317f63825ffSAlexander V. Chernikov                    run_cmd("/usr/sbin/jail -r  {}".format(vnet_name))
318cfc9cf9bSAlexander V. Chernikov            os.unlink(self.JAILS_FNAME)
319cfc9cf9bSAlexander V. Chernikov        except OSError:
320cfc9cf9bSAlexander V. Chernikov            pass
321cfc9cf9bSAlexander V. Chernikov
322cfc9cf9bSAlexander V. Chernikov
323cfc9cf9bSAlexander V. Chernikovclass SingleInterfaceMap(NamedTuple):
324cfc9cf9bSAlexander V. Chernikov    ifaces: List[VnetInterface]
325cfc9cf9bSAlexander V. Chernikov    vnet_aliases: List[str]
326cfc9cf9bSAlexander V. Chernikov
327cfc9cf9bSAlexander V. Chernikov
328f63825ffSAlexander V. Chernikovclass ObjectsMap(NamedTuple):
329f63825ffSAlexander V. Chernikov    iface_map: Dict[str, SingleInterfaceMap]  # keyed by ifX
330f63825ffSAlexander V. Chernikov    vnet_map: Dict[str, VnetInstance]  # keyed by vnetX
331f63825ffSAlexander V. Chernikov    topo_map: Dict  # self.TOPOLOGY
332f63825ffSAlexander V. Chernikov
333f63825ffSAlexander V. Chernikov
3343873bdc2SAlexander V. Chernikovclass VnetTestTemplate(BaseTest):
3356332ef89SAlexander V. Chernikov    NEED_ROOT: bool = True
336cfc9cf9bSAlexander V. Chernikov    TOPOLOGY = {}
337cfc9cf9bSAlexander V. Chernikov
338*09d61b28SKristof Provost    def _require_default_modules(self):
339*09d61b28SKristof Provost        libc.kldload("if_epair.ko")
340*09d61b28SKristof Provost        self.require_module("if_epair")
341*09d61b28SKristof Provost
342cfc9cf9bSAlexander V. Chernikov    def _get_vnet_handler(self, vnet_alias: str):
343cfc9cf9bSAlexander V. Chernikov        handler_name = "{}_handler".format(vnet_alias)
344cfc9cf9bSAlexander V. Chernikov        return getattr(self, handler_name, None)
345cfc9cf9bSAlexander V. Chernikov
346cfc9cf9bSAlexander V. Chernikov    def _setup_vnet(self, vnet: VnetInstance, obj_map: Dict, pipe):
347cfc9cf9bSAlexander V. Chernikov        """Base Handler to setup given VNET.
348cfc9cf9bSAlexander V. Chernikov        Can be run in a subprocess. If so, passes control to the special
349cfc9cf9bSAlexander V. Chernikov        vnetX_handler() after setting up interface addresses
350cfc9cf9bSAlexander V. Chernikov        """
351cfc9cf9bSAlexander V. Chernikov        vnet.attach()
352cfc9cf9bSAlexander V. Chernikov        print("# setup_vnet({})".format(vnet.name))
353f63825ffSAlexander V. Chernikov        if pipe is not None:
354f63825ffSAlexander V. Chernikov            vnet.set_pipe(pipe)
355cfc9cf9bSAlexander V. Chernikov
356f63825ffSAlexander V. Chernikov        topo = obj_map.topo_map
357cfc9cf9bSAlexander V. Chernikov        ipv6_ifaces = []
358cfc9cf9bSAlexander V. Chernikov        # Disable DAD
359cfc9cf9bSAlexander V. Chernikov        if not vnet.need_dad:
360cfc9cf9bSAlexander V. Chernikov            vnet.disable_dad()
361cfc9cf9bSAlexander V. Chernikov        for iface in vnet.ifaces:
362cfc9cf9bSAlexander V. Chernikov            # check index of vnet within an interface
363cfc9cf9bSAlexander V. Chernikov            # as we have prefixes for both ends of the interface
364f63825ffSAlexander V. Chernikov            iface_map = obj_map.iface_map[iface.alias]
365cfc9cf9bSAlexander V. Chernikov            idx = iface_map.vnet_aliases.index(vnet.alias)
366cfc9cf9bSAlexander V. Chernikov            prefixes6 = topo[iface.alias].get("prefixes6", [])
367cfc9cf9bSAlexander V. Chernikov            prefixes4 = topo[iface.alias].get("prefixes4", [])
368cfc9cf9bSAlexander V. Chernikov            if prefixes6 or prefixes4:
369cfc9cf9bSAlexander V. Chernikov                ipv6_ifaces.append(iface)
370cfc9cf9bSAlexander V. Chernikov                iface.turn_up()
371cfc9cf9bSAlexander V. Chernikov                if prefixes6:
372cfc9cf9bSAlexander V. Chernikov                    iface.enable_ipv6()
373cfc9cf9bSAlexander V. Chernikov            for prefix in prefixes6 + prefixes4:
374584ad412SAlexander V. Chernikov                if prefix[idx]:
375cfc9cf9bSAlexander V. Chernikov                    iface.setup_addr(prefix[idx])
376cfc9cf9bSAlexander V. Chernikov        for iface in ipv6_ifaces:
377cfc9cf9bSAlexander V. Chernikov            while iface.has_tentative():
378cfc9cf9bSAlexander V. Chernikov                time.sleep(0.1)
379cfc9cf9bSAlexander V. Chernikov
380cfc9cf9bSAlexander V. Chernikov        # Run actual handler
381cfc9cf9bSAlexander V. Chernikov        handler = self._get_vnet_handler(vnet.alias)
382cfc9cf9bSAlexander V. Chernikov        if handler:
383cfc9cf9bSAlexander V. Chernikov            # Do unbuffered stdout for children
384cfc9cf9bSAlexander V. Chernikov            # so the logs are present if the child hangs
385cfc9cf9bSAlexander V. Chernikov            sys.stdout.reconfigure(line_buffering=True)
3866332ef89SAlexander V. Chernikov            self.drop_privileges()
387f63825ffSAlexander V. Chernikov            handler(vnet)
388cfc9cf9bSAlexander V. Chernikov
389584ad412SAlexander V. Chernikov    def _get_topo_ifmap(self, topo: Dict):
390584ad412SAlexander V. Chernikov        iface_factory = IfaceFactory()
391584ad412SAlexander V. Chernikov        iface_map: Dict[str, SingleInterfaceMap] = {}
392584ad412SAlexander V. Chernikov        iface_aliases = set()
393584ad412SAlexander V. Chernikov        for obj_name, obj_data in topo.items():
394584ad412SAlexander V. Chernikov            if obj_name.startswith("vnet"):
395584ad412SAlexander V. Chernikov                for iface_alias in obj_data["ifaces"]:
396584ad412SAlexander V. Chernikov                    iface_aliases.add(iface_alias)
397584ad412SAlexander V. Chernikov        for iface_alias in iface_aliases:
398584ad412SAlexander V. Chernikov            print("Creating {}".format(iface_alias))
399584ad412SAlexander V. Chernikov            iface_data = topo[iface_alias]
400584ad412SAlexander V. Chernikov            iface_type = iface_data.get("type", "epair")
401584ad412SAlexander V. Chernikov            ifaces = iface_factory.create_iface(iface_alias, iface_type)
402584ad412SAlexander V. Chernikov            smap = SingleInterfaceMap(ifaces, [])
403584ad412SAlexander V. Chernikov            iface_map[iface_alias] = smap
404584ad412SAlexander V. Chernikov        return iface_map
405584ad412SAlexander V. Chernikov
406f63825ffSAlexander V. Chernikov    def setup_topology(self, topo: Dict, topology_id: str):
407cfc9cf9bSAlexander V. Chernikov        """Creates jails & interfaces for the provided topology"""
408cfc9cf9bSAlexander V. Chernikov        vnet_map = {}
409f63825ffSAlexander V. Chernikov        vnet_factory = VnetFactory(topology_id)
410584ad412SAlexander V. Chernikov        iface_map = self._get_topo_ifmap(topo)
411cfc9cf9bSAlexander V. Chernikov        for obj_name, obj_data in topo.items():
412cfc9cf9bSAlexander V. Chernikov            if obj_name.startswith("vnet"):
413cfc9cf9bSAlexander V. Chernikov                vnet_ifaces = []
414cfc9cf9bSAlexander V. Chernikov                for iface_alias in obj_data["ifaces"]:
415cfc9cf9bSAlexander V. Chernikov                    # epair creates 2 interfaces, grab first _available_
416cfc9cf9bSAlexander V. Chernikov                    # and map it to the VNET being created
417cfc9cf9bSAlexander V. Chernikov                    idx = len(iface_map[iface_alias].vnet_aliases)
418cfc9cf9bSAlexander V. Chernikov                    iface_map[iface_alias].vnet_aliases.append(obj_name)
419cfc9cf9bSAlexander V. Chernikov                    vnet_ifaces.append(iface_map[iface_alias].ifaces[idx])
420cfc9cf9bSAlexander V. Chernikov                vnet = vnet_factory.create_vnet(obj_name, vnet_ifaces)
421cfc9cf9bSAlexander V. Chernikov                vnet_map[obj_name] = vnet
422584ad412SAlexander V. Chernikov                # Allow reference to VNETs as attributes
423584ad412SAlexander V. Chernikov                setattr(self, obj_name, vnet)
424cfc9cf9bSAlexander V. Chernikov        # Debug output
425cfc9cf9bSAlexander V. Chernikov        print("============= TEST TOPOLOGY =============")
426cfc9cf9bSAlexander V. Chernikov        for vnet_alias, vnet in vnet_map.items():
427cfc9cf9bSAlexander V. Chernikov            print("# vnet {} -> {}".format(vnet.alias, vnet.name), end="")
428cfc9cf9bSAlexander V. Chernikov            handler = self._get_vnet_handler(vnet.alias)
429cfc9cf9bSAlexander V. Chernikov            if handler:
430cfc9cf9bSAlexander V. Chernikov                print(" handler: {}".format(handler.__name__), end="")
431cfc9cf9bSAlexander V. Chernikov            print()
432cfc9cf9bSAlexander V. Chernikov        for iface_alias, iface_data in iface_map.items():
433cfc9cf9bSAlexander V. Chernikov            vnets = iface_data.vnet_aliases
434cfc9cf9bSAlexander V. Chernikov            ifaces: List[VnetInterface] = iface_data.ifaces
435cfc9cf9bSAlexander V. Chernikov            if len(vnets) == 1 and len(ifaces) == 2:
436cfc9cf9bSAlexander V. Chernikov                print(
437cfc9cf9bSAlexander V. Chernikov                    "# iface {}: {}::{} -> main::{}".format(
438cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets[0], ifaces[0].name, ifaces[1].name
439cfc9cf9bSAlexander V. Chernikov                    )
440cfc9cf9bSAlexander V. Chernikov                )
441cfc9cf9bSAlexander V. Chernikov            elif len(vnets) == 2 and len(ifaces) == 2:
442cfc9cf9bSAlexander V. Chernikov                print(
443cfc9cf9bSAlexander V. Chernikov                    "# iface {}: {}::{} -> {}::{}".format(
444cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets[0], ifaces[0].name, vnets[1], ifaces[1].name
445cfc9cf9bSAlexander V. Chernikov                    )
446cfc9cf9bSAlexander V. Chernikov                )
447cfc9cf9bSAlexander V. Chernikov            else:
448cfc9cf9bSAlexander V. Chernikov                print(
449cfc9cf9bSAlexander V. Chernikov                    "# iface {}: ifaces: {} vnets: {}".format(
450cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets, [i.name for i in ifaces]
451cfc9cf9bSAlexander V. Chernikov                    )
452cfc9cf9bSAlexander V. Chernikov                )
453cfc9cf9bSAlexander V. Chernikov        print()
454f63825ffSAlexander V. Chernikov        return ObjectsMap(iface_map, vnet_map, topo)
455cfc9cf9bSAlexander V. Chernikov
456f63825ffSAlexander V. Chernikov    def setup_method(self, _method):
457cfc9cf9bSAlexander V. Chernikov        """Sets up all the required topology and handlers for the given test"""
458f63825ffSAlexander V. Chernikov        super().setup_method(_method)
459*09d61b28SKristof Provost        self._require_default_modules()
460*09d61b28SKristof Provost
461f63825ffSAlexander V. Chernikov        # TestIP6Output.test_output6_pktinfo[ipandif]
462f63825ffSAlexander V. Chernikov        topology_id = get_topology_id(self.test_id)
463cfc9cf9bSAlexander V. Chernikov        topology = self.TOPOLOGY
464cfc9cf9bSAlexander V. Chernikov        # First, setup kernel objects - interfaces & vnets
465f63825ffSAlexander V. Chernikov        obj_map = self.setup_topology(topology, topology_id)
466cfc9cf9bSAlexander V. Chernikov        main_vnet = None  # one without subprocess handler
467f63825ffSAlexander V. Chernikov        for vnet_alias, vnet in obj_map.vnet_map.items():
468cfc9cf9bSAlexander V. Chernikov            if self._get_vnet_handler(vnet_alias):
469cfc9cf9bSAlexander V. Chernikov                # Need subprocess to run
470cfc9cf9bSAlexander V. Chernikov                parent_pipe, child_pipe = Pipe()
471cfc9cf9bSAlexander V. Chernikov                p = Process(
472cfc9cf9bSAlexander V. Chernikov                    target=self._setup_vnet,
473cfc9cf9bSAlexander V. Chernikov                    args=(
474cfc9cf9bSAlexander V. Chernikov                        vnet,
475cfc9cf9bSAlexander V. Chernikov                        obj_map,
476cfc9cf9bSAlexander V. Chernikov                        child_pipe,
477cfc9cf9bSAlexander V. Chernikov                    ),
478cfc9cf9bSAlexander V. Chernikov                )
479cfc9cf9bSAlexander V. Chernikov                vnet.set_pipe(parent_pipe)
480cfc9cf9bSAlexander V. Chernikov                vnet.set_subprocess(p)
481cfc9cf9bSAlexander V. Chernikov                p.start()
482cfc9cf9bSAlexander V. Chernikov            else:
483cfc9cf9bSAlexander V. Chernikov                if main_vnet is not None:
484cfc9cf9bSAlexander V. Chernikov                    raise Exception("there can be only 1 VNET w/o handler")
485cfc9cf9bSAlexander V. Chernikov                main_vnet = vnet
486cfc9cf9bSAlexander V. Chernikov        # Main vnet needs to be the last, so all the other subprocesses
487cfc9cf9bSAlexander V. Chernikov        # are started & their pipe handles collected
488cfc9cf9bSAlexander V. Chernikov        self.vnet = main_vnet
489cfc9cf9bSAlexander V. Chernikov        self._setup_vnet(main_vnet, obj_map, None)
490cfc9cf9bSAlexander V. Chernikov        # Save state for the main handler
491f63825ffSAlexander V. Chernikov        self.iface_map = obj_map.iface_map
492f63825ffSAlexander V. Chernikov        self.vnet_map = obj_map.vnet_map
4936332ef89SAlexander V. Chernikov        self.drop_privileges()
494cfc9cf9bSAlexander V. Chernikov
495cfc9cf9bSAlexander V. Chernikov    def cleanup(self, test_id: str):
496cfc9cf9bSAlexander V. Chernikov        # pytest test id: file::class::test_name
497f63825ffSAlexander V. Chernikov        topology_id = get_topology_id(self.test_id)
498cfc9cf9bSAlexander V. Chernikov
499cfc9cf9bSAlexander V. Chernikov        print("==== vnet cleanup ===")
500f63825ffSAlexander V. Chernikov        print("# topology_id: '{}'".format(topology_id))
501f63825ffSAlexander V. Chernikov        VnetFactory(topology_id).cleanup()
502f63825ffSAlexander V. Chernikov        IfaceFactory().cleanup()
503cfc9cf9bSAlexander V. Chernikov
504cfc9cf9bSAlexander V. Chernikov    def wait_object(self, pipe, timeout=5):
505cfc9cf9bSAlexander V. Chernikov        if pipe.poll(timeout):
506cfc9cf9bSAlexander V. Chernikov            return pipe.recv()
507cfc9cf9bSAlexander V. Chernikov        raise TimeoutError
508cfc9cf9bSAlexander V. Chernikov
509584ad412SAlexander V. Chernikov    def wait_objects_any(self, pipe_list, timeout=5):
510584ad412SAlexander V. Chernikov        objects = connection.wait(pipe_list, timeout)
511584ad412SAlexander V. Chernikov        if objects:
512584ad412SAlexander V. Chernikov            return objects[0].recv()
513584ad412SAlexander V. Chernikov        raise TimeoutError
514584ad412SAlexander V. Chernikov
515f63825ffSAlexander V. Chernikov    def send_object(self, pipe, obj):
516f63825ffSAlexander V. Chernikov        pipe.send(obj)
517f63825ffSAlexander V. Chernikov
518584ad412SAlexander V. Chernikov    def wait(self):
519584ad412SAlexander V. Chernikov        while True:
520584ad412SAlexander V. Chernikov            time.sleep(1)
521584ad412SAlexander V. Chernikov
522cfc9cf9bSAlexander V. Chernikov    @property
523cfc9cf9bSAlexander V. Chernikov    def curvnet(self):
524cfc9cf9bSAlexander V. Chernikov        pass
525cfc9cf9bSAlexander V. Chernikov
526cfc9cf9bSAlexander V. Chernikov
527cfc9cf9bSAlexander V. Chernikovclass SingleVnetTestTemplate(VnetTestTemplate):
5288eb2bee6SAlexander V. Chernikov    IPV6_PREFIXES: List[str] = []
5298eb2bee6SAlexander V. Chernikov    IPV4_PREFIXES: List[str] = []
530f3065e76SAlexander V. Chernikov    IFTYPE = "epair"
5318eb2bee6SAlexander V. Chernikov
532f3065e76SAlexander V. Chernikov    def _setup_default_topology(self):
533cfc9cf9bSAlexander V. Chernikov        topology = copy.deepcopy(
534cfc9cf9bSAlexander V. Chernikov            {
535cfc9cf9bSAlexander V. Chernikov                "vnet1": {"ifaces": ["if1"]},
536f3065e76SAlexander V. Chernikov                "if1": {"type": self.IFTYPE, "prefixes4": [], "prefixes6": []},
537cfc9cf9bSAlexander V. Chernikov            }
538cfc9cf9bSAlexander V. Chernikov        )
539cfc9cf9bSAlexander V. Chernikov        for prefix in self.IPV6_PREFIXES:
540cfc9cf9bSAlexander V. Chernikov            topology["if1"]["prefixes6"].append((prefix,))
541cfc9cf9bSAlexander V. Chernikov        for prefix in self.IPV4_PREFIXES:
542cfc9cf9bSAlexander V. Chernikov            topology["if1"]["prefixes4"].append((prefix,))
543f3065e76SAlexander V. Chernikov        return topology
544f3065e76SAlexander V. Chernikov
545f3065e76SAlexander V. Chernikov    def setup_method(self, method):
546f3065e76SAlexander V. Chernikov        if not getattr(self, "TOPOLOGY", None):
547f3065e76SAlexander V. Chernikov            self.TOPOLOGY = self._setup_default_topology()
548f3065e76SAlexander V. Chernikov        else:
549f3065e76SAlexander V. Chernikov            names = self.TOPOLOGY.keys()
550f3065e76SAlexander V. Chernikov            assert len([n for n in names if n.startswith("vnet")]) == 1
551cfc9cf9bSAlexander V. Chernikov        super().setup_method(method)
552