xref: /freebsd-src/tests/atf_python/sys/net/vnet.py (revision ae8d58814089308028046ac80aeeb9cbb784bd0a)
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:
227964a28cSJose Luis Duran    if verbose:
238eb2bee6SAlexander V. Chernikov        print("run: '{}'".format(cmd))
248eb2bee6SAlexander V. Chernikov    return os.popen(cmd).read()
258eb2bee6SAlexander V. Chernikov
268eb2bee6SAlexander V. Chernikov
27f63825ffSAlexander V. Chernikovdef get_topology_id(test_id: str) -> str:
28f63825ffSAlexander V. Chernikov    """
29f63825ffSAlexander V. Chernikov    Gets a unique topology id based on the pytest test_id.
30f63825ffSAlexander V. Chernikov      "test_ip6_output.py::TestIP6Output::test_output6_pktinfo[ipandif]" ->
31f63825ffSAlexander V. Chernikov      "TestIP6Output:test_output6_pktinfo[ipandif]"
32f63825ffSAlexander V. Chernikov    """
33f63825ffSAlexander V. Chernikov    return ":".join(test_id.split("::")[-2:])
34f63825ffSAlexander V. Chernikov
35f63825ffSAlexander V. Chernikov
36cfc9cf9bSAlexander V. Chernikovdef convert_test_name(test_name: str) -> str:
37cfc9cf9bSAlexander V. Chernikov    """Convert test name to a string that can be used in the file/jail names"""
38cfc9cf9bSAlexander V. Chernikov    ret = ""
39cfc9cf9bSAlexander V. Chernikov    for char in test_name:
40f63825ffSAlexander V. Chernikov        if char.isalnum() or char in ("_", "-", ":"):
41cfc9cf9bSAlexander V. Chernikov            ret += char
42cfc9cf9bSAlexander V. Chernikov        elif char in ("["):
43cfc9cf9bSAlexander V. Chernikov            ret += "_"
44cfc9cf9bSAlexander V. Chernikov    return ret
458eb2bee6SAlexander V. Chernikov
46cfc9cf9bSAlexander V. Chernikov
47cfc9cf9bSAlexander V. Chernikovclass VnetInterface(object):
488eb2bee6SAlexander V. Chernikov    # defines from net/if_types.h
498eb2bee6SAlexander V. Chernikov    IFT_LOOP = 0x18
508eb2bee6SAlexander V. Chernikov    IFT_ETHER = 0x06
518eb2bee6SAlexander V. Chernikov
52cfc9cf9bSAlexander V. Chernikov    def __init__(self, iface_alias: str, iface_name: str):
538eb2bee6SAlexander V. Chernikov        self.name = iface_name
54cfc9cf9bSAlexander V. Chernikov        self.alias = iface_alias
558eb2bee6SAlexander V. Chernikov        self.vnet_name = ""
568eb2bee6SAlexander V. Chernikov        self.jailed = False
57cfc9cf9bSAlexander V. Chernikov        self.addr_map: Dict[str, Dict] = {"inet6": {}, "inet": {}}
58cfc9cf9bSAlexander V. Chernikov        self.prefixes4: List[List[str]] = []
59cfc9cf9bSAlexander V. Chernikov        self.prefixes6: List[List[str]] = []
608eb2bee6SAlexander V. Chernikov        if iface_name.startswith("lo"):
618eb2bee6SAlexander V. Chernikov            self.iftype = self.IFT_LOOP
628eb2bee6SAlexander V. Chernikov        else:
638eb2bee6SAlexander V. Chernikov            self.iftype = self.IFT_ETHER
648eb2bee6SAlexander V. Chernikov
658eb2bee6SAlexander V. Chernikov    @property
668eb2bee6SAlexander V. Chernikov    def ifindex(self):
678eb2bee6SAlexander V. Chernikov        return socket.if_nametoindex(self.name)
688eb2bee6SAlexander V. Chernikov
69cfc9cf9bSAlexander V. Chernikov    @property
70cfc9cf9bSAlexander V. Chernikov    def first_ipv6(self):
71cfc9cf9bSAlexander V. Chernikov        d = self.addr_map["inet6"]
72cfc9cf9bSAlexander V. Chernikov        return d[next(iter(d))]
73cfc9cf9bSAlexander V. Chernikov
74cfc9cf9bSAlexander V. Chernikov    @property
75cfc9cf9bSAlexander V. Chernikov    def first_ipv4(self):
76cfc9cf9bSAlexander V. Chernikov        d = self.addr_map["inet"]
77cfc9cf9bSAlexander V. Chernikov        return d[next(iter(d))]
78cfc9cf9bSAlexander V. Chernikov
798eb2bee6SAlexander V. Chernikov    def set_vnet(self, vnet_name: str):
808eb2bee6SAlexander V. Chernikov        self.vnet_name = vnet_name
818eb2bee6SAlexander V. Chernikov
828eb2bee6SAlexander V. Chernikov    def set_jailed(self, jailed: bool):
838eb2bee6SAlexander V. Chernikov        self.jailed = jailed
848eb2bee6SAlexander V. Chernikov
85a1eb150cSJose Luis Duran    def run_cmd(self, cmd, verbose=False):
868eb2bee6SAlexander V. Chernikov        if self.vnet_name and not self.jailed:
87a1eb150cSJose Luis Duran            cmd = "/usr/sbin/jexec {} {}".format(self.vnet_name, cmd)
88cfc9cf9bSAlexander V. Chernikov        return run_cmd(cmd, verbose)
898eb2bee6SAlexander V. Chernikov
908eb2bee6SAlexander V. Chernikov    @classmethod
91cfc9cf9bSAlexander V. Chernikov    def setup_loopback(cls, vnet_name: str):
92cfc9cf9bSAlexander V. Chernikov        lo = VnetInterface("", "lo0")
93cfc9cf9bSAlexander V. Chernikov        lo.set_vnet(vnet_name)
944856aeaaSJose Luis Duran        lo.setup_addr("127.0.0.1/8")
95cfc9cf9bSAlexander V. Chernikov        lo.turn_up()
96cfc9cf9bSAlexander V. Chernikov
97cfc9cf9bSAlexander V. Chernikov    @classmethod
98cfc9cf9bSAlexander V. Chernikov    def create_iface(cls, alias_name: str, iface_name: str) -> List["VnetInterface"]:
998eb2bee6SAlexander V. Chernikov        name = run_cmd("/sbin/ifconfig {} create".format(iface_name)).rstrip()
1008eb2bee6SAlexander V. Chernikov        if not name:
1018eb2bee6SAlexander V. Chernikov            raise Exception("Unable to create iface {}".format(iface_name))
102cfc9cf9bSAlexander V. Chernikov        ret = [cls(alias_name, name)]
1038eb2bee6SAlexander V. Chernikov        if name.startswith("epair"):
104cfc9cf9bSAlexander V. Chernikov            ret.append(cls(alias_name, name[:-1] + "b"))
105cfc9cf9bSAlexander V. Chernikov        return ret
1068eb2bee6SAlexander V. Chernikov
107cfc9cf9bSAlexander V. Chernikov    def setup_addr(self, _addr: str):
108cfc9cf9bSAlexander V. Chernikov        addr = ipaddress.ip_interface(_addr)
109cfc9cf9bSAlexander V. Chernikov        if addr.version == 6:
1108eb2bee6SAlexander V. Chernikov            family = "inet6"
1117064c94aSAlexander V. Chernikov            cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
1128eb2bee6SAlexander V. Chernikov        else:
1138eb2bee6SAlexander V. Chernikov            family = "inet"
1147064c94aSAlexander V. Chernikov            if self.addr_map[family]:
1157064c94aSAlexander V. Chernikov                cmd = "/sbin/ifconfig {} alias {}".format(self.name, addr)
1167064c94aSAlexander V. Chernikov            else:
1178eb2bee6SAlexander V. Chernikov                cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
1188eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1197064c94aSAlexander V. Chernikov        self.addr_map[family][str(addr.ip)] = addr
1208eb2bee6SAlexander V. Chernikov
121cfc9cf9bSAlexander V. Chernikov    def delete_addr(self, _addr: str):
122cfc9cf9bSAlexander V. Chernikov        addr = ipaddress.ip_address(_addr)
123cfc9cf9bSAlexander V. Chernikov        if addr.version == 6:
124cfc9cf9bSAlexander V. Chernikov            family = "inet6"
1258eb2bee6SAlexander V. Chernikov            cmd = "/sbin/ifconfig {} inet6 {} delete".format(self.name, addr)
1268eb2bee6SAlexander V. Chernikov        else:
127cfc9cf9bSAlexander V. Chernikov            family = "inet"
1288eb2bee6SAlexander V. Chernikov            cmd = "/sbin/ifconfig {} -alias {}".format(self.name, addr)
1298eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
130cfc9cf9bSAlexander V. Chernikov        del self.addr_map[family][str(addr)]
1318eb2bee6SAlexander V. Chernikov
1328eb2bee6SAlexander V. Chernikov    def turn_up(self):
1338eb2bee6SAlexander V. Chernikov        cmd = "/sbin/ifconfig {} up".format(self.name)
1348eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1358eb2bee6SAlexander V. Chernikov
1368eb2bee6SAlexander V. Chernikov    def enable_ipv6(self):
1378eb2bee6SAlexander V. Chernikov        cmd = "/usr/sbin/ndp -i {} -disabled".format(self.name)
1388eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1398eb2bee6SAlexander V. Chernikov
140cfc9cf9bSAlexander V. Chernikov    def has_tentative(self) -> bool:
141cfc9cf9bSAlexander V. Chernikov        """True if an interface has some addresses in tenative state"""
142cfc9cf9bSAlexander V. Chernikov        cmd = "/sbin/ifconfig {} inet6".format(self.name)
143cfc9cf9bSAlexander V. Chernikov        out = self.run_cmd(cmd, verbose=False)
144cfc9cf9bSAlexander V. Chernikov        for line in out.splitlines():
145cfc9cf9bSAlexander V. Chernikov            if "tentative" in line:
1468eb2bee6SAlexander V. Chernikov                return True
1478eb2bee6SAlexander V. Chernikov        return False
1488eb2bee6SAlexander V. Chernikov
1498eb2bee6SAlexander V. Chernikov
150cfc9cf9bSAlexander V. Chernikovclass IfaceFactory(object):
151cfc9cf9bSAlexander V. Chernikov    INTERFACES_FNAME = "created_ifaces.lst"
152f3065e76SAlexander V. Chernikov    AUTODELETE_TYPES = ("epair", "gif", "gre", "lo", "tap", "tun")
153cfc9cf9bSAlexander V. Chernikov
154f63825ffSAlexander V. Chernikov    def __init__(self):
155cfc9cf9bSAlexander V. Chernikov        self.file_name = self.INTERFACES_FNAME
156cfc9cf9bSAlexander V. Chernikov
157cfc9cf9bSAlexander V. Chernikov    def _register_iface(self, iface_name: str):
158cfc9cf9bSAlexander V. Chernikov        with open(self.file_name, "a") as f:
159cfc9cf9bSAlexander V. Chernikov            f.write(iface_name + "\n")
160cfc9cf9bSAlexander V. Chernikov
16120ea7f26SAlexander V. Chernikov    def _list_ifaces(self) -> List[str]:
16220ea7f26SAlexander V. Chernikov        ret: List[str] = []
1638eb2bee6SAlexander V. Chernikov        try:
164cfc9cf9bSAlexander V. Chernikov            with open(self.file_name, "r") as f:
1658eb2bee6SAlexander V. Chernikov                for line in f:
16620ea7f26SAlexander V. Chernikov                    ret.append(line.strip())
16720ea7f26SAlexander V. Chernikov        except OSError:
16820ea7f26SAlexander V. Chernikov            pass
16920ea7f26SAlexander V. Chernikov        return ret
17020ea7f26SAlexander V. Chernikov
17120ea7f26SAlexander V. Chernikov    def create_iface(self, alias_name: str, iface_name: str) -> List[VnetInterface]:
17220ea7f26SAlexander V. Chernikov        ifaces = VnetInterface.create_iface(alias_name, iface_name)
17320ea7f26SAlexander V. Chernikov        for iface in ifaces:
17420ea7f26SAlexander V. Chernikov            if not self.is_autodeleted(iface.name):
17520ea7f26SAlexander V. Chernikov                self._register_iface(iface.name)
17620ea7f26SAlexander V. Chernikov        return ifaces
17720ea7f26SAlexander V. Chernikov
17820ea7f26SAlexander V. Chernikov    @staticmethod
17920ea7f26SAlexander V. Chernikov    def is_autodeleted(iface_name: str) -> bool:
1802e620256SJose Luis Duran        if iface_name == "lo0":
1812e620256SJose Luis Duran            return False
18220ea7f26SAlexander V. Chernikov        iface_type = re.split(r"\d+", iface_name)[0]
18320ea7f26SAlexander V. Chernikov        return iface_type in IfaceFactory.AUTODELETE_TYPES
18420ea7f26SAlexander V. Chernikov
18520ea7f26SAlexander V. Chernikov    def cleanup_vnet_interfaces(self, vnet_name: str) -> List[str]:
18620ea7f26SAlexander V. Chernikov        """Destroys"""
18720ea7f26SAlexander V. Chernikov        ifaces_lst = ToolsHelper.get_output(
188a1eb150cSJose Luis Duran            "/usr/sbin/jexec {} /sbin/ifconfig -l".format(vnet_name)
18920ea7f26SAlexander V. Chernikov        )
19020ea7f26SAlexander V. Chernikov        for iface_name in ifaces_lst.split():
19120ea7f26SAlexander V. Chernikov            if not self.is_autodeleted(iface_name):
19220ea7f26SAlexander V. Chernikov                if iface_name not in self._list_ifaces():
19320ea7f26SAlexander V. Chernikov                    print("Skipping interface {}:{}".format(vnet_name, iface_name))
19420ea7f26SAlexander V. Chernikov                    continue
19520ea7f26SAlexander V. Chernikov            run_cmd(
196a1eb150cSJose Luis Duran                "/usr/sbin/jexec {} /sbin/ifconfig {} destroy".format(vnet_name, iface_name)
19720ea7f26SAlexander V. Chernikov            )
19820ea7f26SAlexander V. Chernikov
19920ea7f26SAlexander V. Chernikov    def cleanup(self):
20020ea7f26SAlexander V. Chernikov        try:
201cfc9cf9bSAlexander V. Chernikov            os.unlink(self.INTERFACES_FNAME)
20220ea7f26SAlexander V. Chernikov        except OSError:
2038eb2bee6SAlexander V. Chernikov            pass
2048eb2bee6SAlexander V. Chernikov
2058eb2bee6SAlexander V. Chernikov
206cfc9cf9bSAlexander V. Chernikovclass VnetInstance(object):
207cfc9cf9bSAlexander V. Chernikov    def __init__(
208cfc9cf9bSAlexander V. Chernikov        self, vnet_alias: str, vnet_name: str, jid: int, ifaces: List[VnetInterface]
209cfc9cf9bSAlexander V. Chernikov    ):
210cfc9cf9bSAlexander V. Chernikov        self.name = vnet_name
211cfc9cf9bSAlexander V. Chernikov        self.alias = vnet_alias  # reference in the test topology
212cfc9cf9bSAlexander V. Chernikov        self.jid = jid
213cfc9cf9bSAlexander V. Chernikov        self.ifaces = ifaces
214cfc9cf9bSAlexander V. Chernikov        self.iface_alias_map = {}  # iface.alias: iface
215cfc9cf9bSAlexander V. Chernikov        self.iface_map = {}  # iface.name: iface
2168eb2bee6SAlexander V. Chernikov        for iface in ifaces:
217cfc9cf9bSAlexander V. Chernikov            iface.set_vnet(vnet_name)
218cfc9cf9bSAlexander V. Chernikov            iface.set_jailed(True)
219cfc9cf9bSAlexander V. Chernikov            self.iface_alias_map[iface.alias] = iface
220cfc9cf9bSAlexander V. Chernikov            self.iface_map[iface.name] = iface
221584ad412SAlexander V. Chernikov            # Allow reference to interfce aliases as attributes
222584ad412SAlexander V. Chernikov            setattr(self, iface.alias, iface)
223cfc9cf9bSAlexander V. Chernikov        self.need_dad = False  # Disable duplicate address detection by default
224cfc9cf9bSAlexander V. Chernikov        self.attached = False
225cfc9cf9bSAlexander V. Chernikov        self.pipe = None
226cfc9cf9bSAlexander V. Chernikov        self.subprocess = None
227cfc9cf9bSAlexander V. Chernikov
2288a30ab53SJose Luis Duran    def run_vnet_cmd(self, cmd, verbose=True):
229cfc9cf9bSAlexander V. Chernikov        if not self.attached:
230a1eb150cSJose Luis Duran            cmd = "/usr/sbin/jexec {} {}".format(self.name, cmd)
2318a30ab53SJose Luis Duran        return run_cmd(cmd, verbose)
232cfc9cf9bSAlexander V. Chernikov
233cfc9cf9bSAlexander V. Chernikov    def disable_dad(self):
234cfc9cf9bSAlexander V. Chernikov        self.run_vnet_cmd("/sbin/sysctl net.inet6.ip6.dad_count=0")
235cfc9cf9bSAlexander V. Chernikov
236cfc9cf9bSAlexander V. Chernikov    def set_pipe(self, pipe):
237cfc9cf9bSAlexander V. Chernikov        self.pipe = pipe
238cfc9cf9bSAlexander V. Chernikov
239cfc9cf9bSAlexander V. Chernikov    def set_subprocess(self, p):
240cfc9cf9bSAlexander V. Chernikov        self.subprocess = p
2418eb2bee6SAlexander V. Chernikov
2428eb2bee6SAlexander V. Chernikov    @staticmethod
2438eb2bee6SAlexander V. Chernikov    def attach_jid(jid: int):
2443873bdc2SAlexander V. Chernikov        error_code = libc.jail_attach(jid)
2453873bdc2SAlexander V. Chernikov        if error_code != 0:
2463873bdc2SAlexander V. Chernikov            raise Exception("jail_attach() failed: errno {}".format(error_code))
2478eb2bee6SAlexander V. Chernikov
2488eb2bee6SAlexander V. Chernikov    def attach(self):
2498eb2bee6SAlexander V. Chernikov        self.attach_jid(self.jid)
250cfc9cf9bSAlexander V. Chernikov        self.attached = True
2518eb2bee6SAlexander V. Chernikov
2528eb2bee6SAlexander V. Chernikov
253cfc9cf9bSAlexander V. Chernikovclass VnetFactory(object):
254cfc9cf9bSAlexander V. Chernikov    JAILS_FNAME = "created_jails.lst"
255cfc9cf9bSAlexander V. Chernikov
256f63825ffSAlexander V. Chernikov    def __init__(self, topology_id: str):
257f63825ffSAlexander V. Chernikov        self.topology_id = topology_id
258cfc9cf9bSAlexander V. Chernikov        self.file_name = self.JAILS_FNAME
259cfc9cf9bSAlexander V. Chernikov        self._vnets: List[str] = []
260cfc9cf9bSAlexander V. Chernikov
261cfc9cf9bSAlexander V. Chernikov    def _register_vnet(self, vnet_name: str):
262cfc9cf9bSAlexander V. Chernikov        self._vnets.append(vnet_name)
263cfc9cf9bSAlexander V. Chernikov        with open(self.file_name, "a") as f:
264cfc9cf9bSAlexander V. Chernikov            f.write(vnet_name + "\n")
265cfc9cf9bSAlexander V. Chernikov
266cfc9cf9bSAlexander V. Chernikov    @staticmethod
267cfc9cf9bSAlexander V. Chernikov    def _wait_interfaces(vnet_name: str, ifaces: List[str]) -> List[str]:
268a1eb150cSJose Luis Duran        cmd = "/usr/sbin/jexec {} /sbin/ifconfig -l".format(vnet_name)
269cfc9cf9bSAlexander V. Chernikov        not_matched: List[str] = []
270cfc9cf9bSAlexander V. Chernikov        for i in range(50):
271cfc9cf9bSAlexander V. Chernikov            vnet_ifaces = run_cmd(cmd).strip().split(" ")
272cfc9cf9bSAlexander V. Chernikov            not_matched = []
273cfc9cf9bSAlexander V. Chernikov            for iface_name in ifaces:
274cfc9cf9bSAlexander V. Chernikov                if iface_name not in vnet_ifaces:
275cfc9cf9bSAlexander V. Chernikov                    not_matched.append(iface_name)
276cfc9cf9bSAlexander V. Chernikov            if len(not_matched) == 0:
277cfc9cf9bSAlexander V. Chernikov                return []
278cfc9cf9bSAlexander V. Chernikov            time.sleep(0.1)
279cfc9cf9bSAlexander V. Chernikov        return not_matched
280cfc9cf9bSAlexander V. Chernikov
281cfc9cf9bSAlexander V. Chernikov    def create_vnet(self, vnet_alias: str, ifaces: List[VnetInterface]):
282f63825ffSAlexander V. Chernikov        vnet_name = "pytest:{}".format(convert_test_name(self.topology_id))
283cfc9cf9bSAlexander V. Chernikov        if self._vnets:
284cfc9cf9bSAlexander V. Chernikov            # add number to distinguish jails
285cfc9cf9bSAlexander V. Chernikov            vnet_name = "{}_{}".format(vnet_name, len(self._vnets) + 1)
286cfc9cf9bSAlexander V. Chernikov        iface_cmds = " ".join(["vnet.interface={}".format(i.name) for i in ifaces])
287cfc9cf9bSAlexander V. Chernikov        cmd = "/usr/sbin/jail -i -c name={} persist vnet {}".format(
288cfc9cf9bSAlexander V. Chernikov            vnet_name, iface_cmds
289cfc9cf9bSAlexander V. Chernikov        )
290f63825ffSAlexander V. Chernikov        jid = 0
291f63825ffSAlexander V. Chernikov        try:
292cfc9cf9bSAlexander V. Chernikov            jid_str = run_cmd(cmd)
293cfc9cf9bSAlexander V. Chernikov            jid = int(jid_str)
29420ea7f26SAlexander V. Chernikov        except ValueError:
295f63825ffSAlexander V. Chernikov            print("Jail creation failed, output: {}".format(jid_str))
296f63825ffSAlexander V. Chernikov            raise
297cfc9cf9bSAlexander V. Chernikov        self._register_vnet(vnet_name)
298cfc9cf9bSAlexander V. Chernikov
299cfc9cf9bSAlexander V. Chernikov        # Run expedited version of routing
300cfc9cf9bSAlexander V. Chernikov        VnetInterface.setup_loopback(vnet_name)
301cfc9cf9bSAlexander V. Chernikov
302cfc9cf9bSAlexander V. Chernikov        not_found = self._wait_interfaces(vnet_name, [i.name for i in ifaces])
303cfc9cf9bSAlexander V. Chernikov        if not_found:
304cfc9cf9bSAlexander V. Chernikov            raise Exception(
305cfc9cf9bSAlexander V. Chernikov                "Interfaces {} has not appeared in vnet {}".format(not_found, vnet_name)
306cfc9cf9bSAlexander V. Chernikov            )
307cfc9cf9bSAlexander V. Chernikov        return VnetInstance(vnet_alias, vnet_name, jid, ifaces)
308cfc9cf9bSAlexander V. Chernikov
309cfc9cf9bSAlexander V. Chernikov    def cleanup(self):
31020ea7f26SAlexander V. Chernikov        iface_factory = IfaceFactory()
311cfc9cf9bSAlexander V. Chernikov        try:
312cfc9cf9bSAlexander V. Chernikov            with open(self.file_name) as f:
313cfc9cf9bSAlexander V. Chernikov                for line in f:
314f63825ffSAlexander V. Chernikov                    vnet_name = line.strip()
31520ea7f26SAlexander V. Chernikov                    iface_factory.cleanup_vnet_interfaces(vnet_name)
316f63825ffSAlexander V. Chernikov                    run_cmd("/usr/sbin/jail -r  {}".format(vnet_name))
317cfc9cf9bSAlexander V. Chernikov            os.unlink(self.JAILS_FNAME)
318cfc9cf9bSAlexander V. Chernikov        except OSError:
319cfc9cf9bSAlexander V. Chernikov            pass
320cfc9cf9bSAlexander V. Chernikov
321cfc9cf9bSAlexander V. Chernikov
322cfc9cf9bSAlexander V. Chernikovclass SingleInterfaceMap(NamedTuple):
323cfc9cf9bSAlexander V. Chernikov    ifaces: List[VnetInterface]
324cfc9cf9bSAlexander V. Chernikov    vnet_aliases: List[str]
325cfc9cf9bSAlexander V. Chernikov
326cfc9cf9bSAlexander V. Chernikov
327f63825ffSAlexander V. Chernikovclass ObjectsMap(NamedTuple):
328f63825ffSAlexander V. Chernikov    iface_map: Dict[str, SingleInterfaceMap]  # keyed by ifX
329f63825ffSAlexander V. Chernikov    vnet_map: Dict[str, VnetInstance]  # keyed by vnetX
330f63825ffSAlexander V. Chernikov    topo_map: Dict  # self.TOPOLOGY
331f63825ffSAlexander V. Chernikov
332f63825ffSAlexander V. Chernikov
3333873bdc2SAlexander V. Chernikovclass VnetTestTemplate(BaseTest):
3346332ef89SAlexander V. Chernikov    NEED_ROOT: bool = True
335cfc9cf9bSAlexander V. Chernikov    TOPOLOGY = {}
336cfc9cf9bSAlexander V. Chernikov
337*ae8d5881SKristof Provost    def _require_default_modules(self):
338*ae8d5881SKristof Provost        libc.kldload("if_epair.ko")
339*ae8d5881SKristof Provost        self.require_module("if_epair")
340*ae8d5881SKristof Provost
341cfc9cf9bSAlexander V. Chernikov    def _get_vnet_handler(self, vnet_alias: str):
342cfc9cf9bSAlexander V. Chernikov        handler_name = "{}_handler".format(vnet_alias)
343cfc9cf9bSAlexander V. Chernikov        return getattr(self, handler_name, None)
344cfc9cf9bSAlexander V. Chernikov
345cfc9cf9bSAlexander V. Chernikov    def _setup_vnet(self, vnet: VnetInstance, obj_map: Dict, pipe):
346cfc9cf9bSAlexander V. Chernikov        """Base Handler to setup given VNET.
347cfc9cf9bSAlexander V. Chernikov        Can be run in a subprocess. If so, passes control to the special
348cfc9cf9bSAlexander V. Chernikov        vnetX_handler() after setting up interface addresses
349cfc9cf9bSAlexander V. Chernikov        """
350cfc9cf9bSAlexander V. Chernikov        vnet.attach()
351cfc9cf9bSAlexander V. Chernikov        print("# setup_vnet({})".format(vnet.name))
352f63825ffSAlexander V. Chernikov        if pipe is not None:
353f63825ffSAlexander V. Chernikov            vnet.set_pipe(pipe)
354cfc9cf9bSAlexander V. Chernikov
355f63825ffSAlexander V. Chernikov        topo = obj_map.topo_map
356cfc9cf9bSAlexander V. Chernikov        ipv6_ifaces = []
357cfc9cf9bSAlexander V. Chernikov        # Disable DAD
358cfc9cf9bSAlexander V. Chernikov        if not vnet.need_dad:
359cfc9cf9bSAlexander V. Chernikov            vnet.disable_dad()
360cfc9cf9bSAlexander V. Chernikov        for iface in vnet.ifaces:
361cfc9cf9bSAlexander V. Chernikov            # check index of vnet within an interface
362cfc9cf9bSAlexander V. Chernikov            # as we have prefixes for both ends of the interface
363f63825ffSAlexander V. Chernikov            iface_map = obj_map.iface_map[iface.alias]
364cfc9cf9bSAlexander V. Chernikov            idx = iface_map.vnet_aliases.index(vnet.alias)
365cfc9cf9bSAlexander V. Chernikov            prefixes6 = topo[iface.alias].get("prefixes6", [])
366cfc9cf9bSAlexander V. Chernikov            prefixes4 = topo[iface.alias].get("prefixes4", [])
367cfc9cf9bSAlexander V. Chernikov            if prefixes6 or prefixes4:
368cfc9cf9bSAlexander V. Chernikov                ipv6_ifaces.append(iface)
369cfc9cf9bSAlexander V. Chernikov                iface.turn_up()
370cfc9cf9bSAlexander V. Chernikov                if prefixes6:
371cfc9cf9bSAlexander V. Chernikov                    iface.enable_ipv6()
372cfc9cf9bSAlexander V. Chernikov            for prefix in prefixes6 + prefixes4:
373584ad412SAlexander V. Chernikov                if prefix[idx]:
374cfc9cf9bSAlexander V. Chernikov                    iface.setup_addr(prefix[idx])
375cfc9cf9bSAlexander V. Chernikov        for iface in ipv6_ifaces:
376cfc9cf9bSAlexander V. Chernikov            while iface.has_tentative():
377cfc9cf9bSAlexander V. Chernikov                time.sleep(0.1)
378cfc9cf9bSAlexander V. Chernikov
379cfc9cf9bSAlexander V. Chernikov        # Run actual handler
380cfc9cf9bSAlexander V. Chernikov        handler = self._get_vnet_handler(vnet.alias)
381cfc9cf9bSAlexander V. Chernikov        if handler:
382cfc9cf9bSAlexander V. Chernikov            # Do unbuffered stdout for children
383cfc9cf9bSAlexander V. Chernikov            # so the logs are present if the child hangs
384cfc9cf9bSAlexander V. Chernikov            sys.stdout.reconfigure(line_buffering=True)
3856332ef89SAlexander V. Chernikov            self.drop_privileges()
386f63825ffSAlexander V. Chernikov            handler(vnet)
387cfc9cf9bSAlexander V. Chernikov
388584ad412SAlexander V. Chernikov    def _get_topo_ifmap(self, topo: Dict):
389584ad412SAlexander V. Chernikov        iface_factory = IfaceFactory()
390584ad412SAlexander V. Chernikov        iface_map: Dict[str, SingleInterfaceMap] = {}
391584ad412SAlexander V. Chernikov        iface_aliases = set()
392584ad412SAlexander V. Chernikov        for obj_name, obj_data in topo.items():
393584ad412SAlexander V. Chernikov            if obj_name.startswith("vnet"):
394584ad412SAlexander V. Chernikov                for iface_alias in obj_data["ifaces"]:
395584ad412SAlexander V. Chernikov                    iface_aliases.add(iface_alias)
396584ad412SAlexander V. Chernikov        for iface_alias in iface_aliases:
397584ad412SAlexander V. Chernikov            print("Creating {}".format(iface_alias))
398584ad412SAlexander V. Chernikov            iface_data = topo[iface_alias]
399584ad412SAlexander V. Chernikov            iface_type = iface_data.get("type", "epair")
400584ad412SAlexander V. Chernikov            ifaces = iface_factory.create_iface(iface_alias, iface_type)
401584ad412SAlexander V. Chernikov            smap = SingleInterfaceMap(ifaces, [])
402584ad412SAlexander V. Chernikov            iface_map[iface_alias] = smap
403584ad412SAlexander V. Chernikov        return iface_map
404584ad412SAlexander V. Chernikov
405f63825ffSAlexander V. Chernikov    def setup_topology(self, topo: Dict, topology_id: str):
406cfc9cf9bSAlexander V. Chernikov        """Creates jails & interfaces for the provided topology"""
407cfc9cf9bSAlexander V. Chernikov        vnet_map = {}
408f63825ffSAlexander V. Chernikov        vnet_factory = VnetFactory(topology_id)
409584ad412SAlexander V. Chernikov        iface_map = self._get_topo_ifmap(topo)
410cfc9cf9bSAlexander V. Chernikov        for obj_name, obj_data in topo.items():
411cfc9cf9bSAlexander V. Chernikov            if obj_name.startswith("vnet"):
412cfc9cf9bSAlexander V. Chernikov                vnet_ifaces = []
413cfc9cf9bSAlexander V. Chernikov                for iface_alias in obj_data["ifaces"]:
414cfc9cf9bSAlexander V. Chernikov                    # epair creates 2 interfaces, grab first _available_
415cfc9cf9bSAlexander V. Chernikov                    # and map it to the VNET being created
416cfc9cf9bSAlexander V. Chernikov                    idx = len(iface_map[iface_alias].vnet_aliases)
417cfc9cf9bSAlexander V. Chernikov                    iface_map[iface_alias].vnet_aliases.append(obj_name)
418cfc9cf9bSAlexander V. Chernikov                    vnet_ifaces.append(iface_map[iface_alias].ifaces[idx])
419cfc9cf9bSAlexander V. Chernikov                vnet = vnet_factory.create_vnet(obj_name, vnet_ifaces)
420cfc9cf9bSAlexander V. Chernikov                vnet_map[obj_name] = vnet
421584ad412SAlexander V. Chernikov                # Allow reference to VNETs as attributes
422584ad412SAlexander V. Chernikov                setattr(self, obj_name, vnet)
423cfc9cf9bSAlexander V. Chernikov        # Debug output
424cfc9cf9bSAlexander V. Chernikov        print("============= TEST TOPOLOGY =============")
425cfc9cf9bSAlexander V. Chernikov        for vnet_alias, vnet in vnet_map.items():
426cfc9cf9bSAlexander V. Chernikov            print("# vnet {} -> {}".format(vnet.alias, vnet.name), end="")
427cfc9cf9bSAlexander V. Chernikov            handler = self._get_vnet_handler(vnet.alias)
428cfc9cf9bSAlexander V. Chernikov            if handler:
429cfc9cf9bSAlexander V. Chernikov                print(" handler: {}".format(handler.__name__), end="")
430cfc9cf9bSAlexander V. Chernikov            print()
431cfc9cf9bSAlexander V. Chernikov        for iface_alias, iface_data in iface_map.items():
432cfc9cf9bSAlexander V. Chernikov            vnets = iface_data.vnet_aliases
433cfc9cf9bSAlexander V. Chernikov            ifaces: List[VnetInterface] = iface_data.ifaces
434cfc9cf9bSAlexander V. Chernikov            if len(vnets) == 1 and len(ifaces) == 2:
435cfc9cf9bSAlexander V. Chernikov                print(
436cfc9cf9bSAlexander V. Chernikov                    "# iface {}: {}::{} -> main::{}".format(
437cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets[0], ifaces[0].name, ifaces[1].name
438cfc9cf9bSAlexander V. Chernikov                    )
439cfc9cf9bSAlexander V. Chernikov                )
440cfc9cf9bSAlexander V. Chernikov            elif len(vnets) == 2 and len(ifaces) == 2:
441cfc9cf9bSAlexander V. Chernikov                print(
442cfc9cf9bSAlexander V. Chernikov                    "# iface {}: {}::{} -> {}::{}".format(
443cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets[0], ifaces[0].name, vnets[1], ifaces[1].name
444cfc9cf9bSAlexander V. Chernikov                    )
445cfc9cf9bSAlexander V. Chernikov                )
446cfc9cf9bSAlexander V. Chernikov            else:
447cfc9cf9bSAlexander V. Chernikov                print(
448cfc9cf9bSAlexander V. Chernikov                    "# iface {}: ifaces: {} vnets: {}".format(
449cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets, [i.name for i in ifaces]
450cfc9cf9bSAlexander V. Chernikov                    )
451cfc9cf9bSAlexander V. Chernikov                )
452cfc9cf9bSAlexander V. Chernikov        print()
453f63825ffSAlexander V. Chernikov        return ObjectsMap(iface_map, vnet_map, topo)
454cfc9cf9bSAlexander V. Chernikov
455f63825ffSAlexander V. Chernikov    def setup_method(self, _method):
456cfc9cf9bSAlexander V. Chernikov        """Sets up all the required topology and handlers for the given test"""
457f63825ffSAlexander V. Chernikov        super().setup_method(_method)
458*ae8d5881SKristof Provost        self._require_default_modules()
459*ae8d5881SKristof Provost
460f63825ffSAlexander V. Chernikov        # TestIP6Output.test_output6_pktinfo[ipandif]
461f63825ffSAlexander V. Chernikov        topology_id = get_topology_id(self.test_id)
462cfc9cf9bSAlexander V. Chernikov        topology = self.TOPOLOGY
463cfc9cf9bSAlexander V. Chernikov        # First, setup kernel objects - interfaces & vnets
464f63825ffSAlexander V. Chernikov        obj_map = self.setup_topology(topology, topology_id)
465cfc9cf9bSAlexander V. Chernikov        main_vnet = None  # one without subprocess handler
466f63825ffSAlexander V. Chernikov        for vnet_alias, vnet in obj_map.vnet_map.items():
467cfc9cf9bSAlexander V. Chernikov            if self._get_vnet_handler(vnet_alias):
468cfc9cf9bSAlexander V. Chernikov                # Need subprocess to run
469cfc9cf9bSAlexander V. Chernikov                parent_pipe, child_pipe = Pipe()
470cfc9cf9bSAlexander V. Chernikov                p = Process(
471cfc9cf9bSAlexander V. Chernikov                    target=self._setup_vnet,
472cfc9cf9bSAlexander V. Chernikov                    args=(
473cfc9cf9bSAlexander V. Chernikov                        vnet,
474cfc9cf9bSAlexander V. Chernikov                        obj_map,
475cfc9cf9bSAlexander V. Chernikov                        child_pipe,
476cfc9cf9bSAlexander V. Chernikov                    ),
477cfc9cf9bSAlexander V. Chernikov                )
478cfc9cf9bSAlexander V. Chernikov                vnet.set_pipe(parent_pipe)
479cfc9cf9bSAlexander V. Chernikov                vnet.set_subprocess(p)
480cfc9cf9bSAlexander V. Chernikov                p.start()
481cfc9cf9bSAlexander V. Chernikov            else:
482cfc9cf9bSAlexander V. Chernikov                if main_vnet is not None:
483cfc9cf9bSAlexander V. Chernikov                    raise Exception("there can be only 1 VNET w/o handler")
484cfc9cf9bSAlexander V. Chernikov                main_vnet = vnet
485cfc9cf9bSAlexander V. Chernikov        # Main vnet needs to be the last, so all the other subprocesses
486cfc9cf9bSAlexander V. Chernikov        # are started & their pipe handles collected
487cfc9cf9bSAlexander V. Chernikov        self.vnet = main_vnet
488cfc9cf9bSAlexander V. Chernikov        self._setup_vnet(main_vnet, obj_map, None)
489cfc9cf9bSAlexander V. Chernikov        # Save state for the main handler
490f63825ffSAlexander V. Chernikov        self.iface_map = obj_map.iface_map
491f63825ffSAlexander V. Chernikov        self.vnet_map = obj_map.vnet_map
4926332ef89SAlexander V. Chernikov        self.drop_privileges()
493cfc9cf9bSAlexander V. Chernikov
494cfc9cf9bSAlexander V. Chernikov    def cleanup(self, test_id: str):
495cfc9cf9bSAlexander V. Chernikov        # pytest test id: file::class::test_name
496f63825ffSAlexander V. Chernikov        topology_id = get_topology_id(self.test_id)
497cfc9cf9bSAlexander V. Chernikov
498d4a5d495SJose Luis Duran        print("============= vnet cleanup =============")
499f63825ffSAlexander V. Chernikov        print("# topology_id: '{}'".format(topology_id))
500f63825ffSAlexander V. Chernikov        VnetFactory(topology_id).cleanup()
501f63825ffSAlexander V. Chernikov        IfaceFactory().cleanup()
502cfc9cf9bSAlexander V. Chernikov
503cfc9cf9bSAlexander V. Chernikov    def wait_object(self, pipe, timeout=5):
504cfc9cf9bSAlexander V. Chernikov        if pipe.poll(timeout):
505cfc9cf9bSAlexander V. Chernikov            return pipe.recv()
506cfc9cf9bSAlexander V. Chernikov        raise TimeoutError
507cfc9cf9bSAlexander V. Chernikov
508584ad412SAlexander V. Chernikov    def wait_objects_any(self, pipe_list, timeout=5):
509584ad412SAlexander V. Chernikov        objects = connection.wait(pipe_list, timeout)
510584ad412SAlexander V. Chernikov        if objects:
511584ad412SAlexander V. Chernikov            return objects[0].recv()
512584ad412SAlexander V. Chernikov        raise TimeoutError
513584ad412SAlexander V. Chernikov
514f63825ffSAlexander V. Chernikov    def send_object(self, pipe, obj):
515f63825ffSAlexander V. Chernikov        pipe.send(obj)
516f63825ffSAlexander V. Chernikov
517584ad412SAlexander V. Chernikov    def wait(self):
518584ad412SAlexander V. Chernikov        while True:
519584ad412SAlexander V. Chernikov            time.sleep(1)
520584ad412SAlexander V. Chernikov
521cfc9cf9bSAlexander V. Chernikov    @property
522cfc9cf9bSAlexander V. Chernikov    def curvnet(self):
523cfc9cf9bSAlexander V. Chernikov        pass
524cfc9cf9bSAlexander V. Chernikov
525cfc9cf9bSAlexander V. Chernikov
526cfc9cf9bSAlexander V. Chernikovclass SingleVnetTestTemplate(VnetTestTemplate):
5278eb2bee6SAlexander V. Chernikov    IPV6_PREFIXES: List[str] = []
5288eb2bee6SAlexander V. Chernikov    IPV4_PREFIXES: List[str] = []
529f3065e76SAlexander V. Chernikov    IFTYPE = "epair"
5308eb2bee6SAlexander V. Chernikov
531f3065e76SAlexander V. Chernikov    def _setup_default_topology(self):
532cfc9cf9bSAlexander V. Chernikov        topology = copy.deepcopy(
533cfc9cf9bSAlexander V. Chernikov            {
534cfc9cf9bSAlexander V. Chernikov                "vnet1": {"ifaces": ["if1"]},
535f3065e76SAlexander V. Chernikov                "if1": {"type": self.IFTYPE, "prefixes4": [], "prefixes6": []},
536cfc9cf9bSAlexander V. Chernikov            }
537cfc9cf9bSAlexander V. Chernikov        )
538cfc9cf9bSAlexander V. Chernikov        for prefix in self.IPV6_PREFIXES:
539cfc9cf9bSAlexander V. Chernikov            topology["if1"]["prefixes6"].append((prefix,))
540cfc9cf9bSAlexander V. Chernikov        for prefix in self.IPV4_PREFIXES:
541cfc9cf9bSAlexander V. Chernikov            topology["if1"]["prefixes4"].append((prefix,))
542f3065e76SAlexander V. Chernikov        return topology
543f3065e76SAlexander V. Chernikov
544f3065e76SAlexander V. Chernikov    def setup_method(self, method):
545f3065e76SAlexander V. Chernikov        if not getattr(self, "TOPOLOGY", None):
546f3065e76SAlexander V. Chernikov            self.TOPOLOGY = self._setup_default_topology()
547f3065e76SAlexander V. Chernikov        else:
548f3065e76SAlexander V. Chernikov            names = self.TOPOLOGY.keys()
549f3065e76SAlexander V. Chernikov            assert len([n for n in names if n.startswith("vnet")]) == 1
550cfc9cf9bSAlexander V. Chernikov        super().setup_method(method)
551