xref: /src/tests/atf_python/sys/net/vnet.py (revision a8b8feced998c8c74f9a572f069bcb689cabd09d)
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]] = []
60*a8b8feceSMark Johnston        self.fib: int
618eb2bee6SAlexander V. Chernikov        if iface_name.startswith("lo"):
628eb2bee6SAlexander V. Chernikov            self.iftype = self.IFT_LOOP
638eb2bee6SAlexander V. Chernikov        else:
648eb2bee6SAlexander V. Chernikov            self.iftype = self.IFT_ETHER
659c95fcb7SRonald Klop            self.ether = ToolsHelper.get_output("/sbin/ifconfig %s ether | awk '/ether/ { print $2; }'" % iface_name).rstrip()
668eb2bee6SAlexander V. Chernikov
678eb2bee6SAlexander V. Chernikov    @property
688eb2bee6SAlexander V. Chernikov    def ifindex(self):
698eb2bee6SAlexander V. Chernikov        return socket.if_nametoindex(self.name)
708eb2bee6SAlexander V. Chernikov
71cfc9cf9bSAlexander V. Chernikov    @property
72cfc9cf9bSAlexander V. Chernikov    def first_ipv6(self):
73cfc9cf9bSAlexander V. Chernikov        d = self.addr_map["inet6"]
74cfc9cf9bSAlexander V. Chernikov        return d[next(iter(d))]
75cfc9cf9bSAlexander V. Chernikov
76cfc9cf9bSAlexander V. Chernikov    @property
77cfc9cf9bSAlexander V. Chernikov    def first_ipv4(self):
78cfc9cf9bSAlexander V. Chernikov        d = self.addr_map["inet"]
79cfc9cf9bSAlexander V. Chernikov        return d[next(iter(d))]
80cfc9cf9bSAlexander V. Chernikov
818eb2bee6SAlexander V. Chernikov    def set_vnet(self, vnet_name: str):
828eb2bee6SAlexander V. Chernikov        self.vnet_name = vnet_name
838eb2bee6SAlexander V. Chernikov
848eb2bee6SAlexander V. Chernikov    def set_jailed(self, jailed: bool):
858eb2bee6SAlexander V. Chernikov        self.jailed = jailed
868eb2bee6SAlexander V. Chernikov
87a1eb150cSJose Luis Duran    def run_cmd(self, cmd, verbose=False):
888eb2bee6SAlexander V. Chernikov        if self.vnet_name and not self.jailed:
89a1eb150cSJose Luis Duran            cmd = "/usr/sbin/jexec {} {}".format(self.vnet_name, cmd)
90cfc9cf9bSAlexander V. Chernikov        return run_cmd(cmd, verbose)
918eb2bee6SAlexander V. Chernikov
928eb2bee6SAlexander V. Chernikov    @classmethod
93cfc9cf9bSAlexander V. Chernikov    def setup_loopback(cls, vnet_name: str):
94cfc9cf9bSAlexander V. Chernikov        lo = VnetInterface("", "lo0")
95cfc9cf9bSAlexander V. Chernikov        lo.set_vnet(vnet_name)
964856aeaaSJose Luis Duran        lo.setup_addr("127.0.0.1/8")
97cfc9cf9bSAlexander V. Chernikov        lo.turn_up()
98cfc9cf9bSAlexander V. Chernikov
99cfc9cf9bSAlexander V. Chernikov    @classmethod
100cfc9cf9bSAlexander V. Chernikov    def create_iface(cls, alias_name: str, iface_name: str) -> List["VnetInterface"]:
1018eb2bee6SAlexander V. Chernikov        name = run_cmd("/sbin/ifconfig {} create".format(iface_name)).rstrip()
1028eb2bee6SAlexander V. Chernikov        if not name:
1038eb2bee6SAlexander V. Chernikov            raise Exception("Unable to create iface {}".format(iface_name))
1049c95fcb7SRonald Klop        if1 = cls(alias_name, name)
1059c95fcb7SRonald Klop        ret = [if1]
1068eb2bee6SAlexander V. Chernikov        if name.startswith("epair"):
107bd8296e7SMichael Tuexen            run_cmd("/sbin/ifconfig {} -txcsum -txcsum6".format(name))
1089c95fcb7SRonald Klop            if2 = cls(alias_name, name[:-1] + "b")
1099c95fcb7SRonald Klop            if1.epairb = if2
110*a8b8feceSMark Johnston            ret.append(if2)
111cfc9cf9bSAlexander V. Chernikov        return ret
1128eb2bee6SAlexander V. Chernikov
1134f35a84bSKristof Provost    def set_mtu(self, mtu):
1144f35a84bSKristof Provost        run_cmd("/sbin/ifconfig {} mtu {}".format(self.name, mtu))
1154f35a84bSKristof Provost
116cfc9cf9bSAlexander V. Chernikov    def setup_addr(self, _addr: str):
117cfc9cf9bSAlexander V. Chernikov        addr = ipaddress.ip_interface(_addr)
118cfc9cf9bSAlexander V. Chernikov        if addr.version == 6:
1198eb2bee6SAlexander V. Chernikov            family = "inet6"
1207064c94aSAlexander V. Chernikov            cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
1218eb2bee6SAlexander V. Chernikov        else:
1228eb2bee6SAlexander V. Chernikov            family = "inet"
1237064c94aSAlexander V. Chernikov            if self.addr_map[family]:
1247064c94aSAlexander V. Chernikov                cmd = "/sbin/ifconfig {} alias {}".format(self.name, addr)
1257064c94aSAlexander V. Chernikov            else:
1268eb2bee6SAlexander V. Chernikov                cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
1278eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1287064c94aSAlexander V. Chernikov        self.addr_map[family][str(addr.ip)] = addr
1298eb2bee6SAlexander V. Chernikov
130cfc9cf9bSAlexander V. Chernikov    def delete_addr(self, _addr: str):
131cfc9cf9bSAlexander V. Chernikov        addr = ipaddress.ip_address(_addr)
132cfc9cf9bSAlexander V. Chernikov        if addr.version == 6:
133cfc9cf9bSAlexander V. Chernikov            family = "inet6"
1348eb2bee6SAlexander V. Chernikov            cmd = "/sbin/ifconfig {} inet6 {} delete".format(self.name, addr)
1358eb2bee6SAlexander V. Chernikov        else:
136cfc9cf9bSAlexander V. Chernikov            family = "inet"
1378eb2bee6SAlexander V. Chernikov            cmd = "/sbin/ifconfig {} -alias {}".format(self.name, addr)
1388eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
139cfc9cf9bSAlexander V. Chernikov        del self.addr_map[family][str(addr)]
1408eb2bee6SAlexander V. Chernikov
1418eb2bee6SAlexander V. Chernikov    def turn_up(self):
1428eb2bee6SAlexander V. Chernikov        cmd = "/sbin/ifconfig {} up".format(self.name)
1438eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1448eb2bee6SAlexander V. Chernikov
145*a8b8feceSMark Johnston    def setfib(self, fib: int):
146*a8b8feceSMark Johnston        cmd = "/sbin/ifconfig {} fib {}".format(self.name, fib)
147*a8b8feceSMark Johnston        self.run_cmd(cmd)
148*a8b8feceSMark Johnston
1498eb2bee6SAlexander V. Chernikov    def enable_ipv6(self):
1506ae89b2fSKristof Provost        cmd = "/usr/sbin/ndp -i {} -- -disabled".format(self.name)
1518eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1528eb2bee6SAlexander V. Chernikov
153cfc9cf9bSAlexander V. Chernikov    def has_tentative(self) -> bool:
154cfc9cf9bSAlexander V. Chernikov        """True if an interface has some addresses in tenative state"""
155cfc9cf9bSAlexander V. Chernikov        cmd = "/sbin/ifconfig {} inet6".format(self.name)
156cfc9cf9bSAlexander V. Chernikov        out = self.run_cmd(cmd, verbose=False)
157cfc9cf9bSAlexander V. Chernikov        for line in out.splitlines():
158cfc9cf9bSAlexander V. Chernikov            if "tentative" in line:
1598eb2bee6SAlexander V. Chernikov                return True
1608eb2bee6SAlexander V. Chernikov        return False
1618eb2bee6SAlexander V. Chernikov
1628eb2bee6SAlexander V. Chernikov
163cfc9cf9bSAlexander V. Chernikovclass IfaceFactory(object):
164cfc9cf9bSAlexander V. Chernikov    INTERFACES_FNAME = "created_ifaces.lst"
165f3065e76SAlexander V. Chernikov    AUTODELETE_TYPES = ("epair", "gif", "gre", "lo", "tap", "tun")
166cfc9cf9bSAlexander V. Chernikov
167f63825ffSAlexander V. Chernikov    def __init__(self):
168cfc9cf9bSAlexander V. Chernikov        self.file_name = self.INTERFACES_FNAME
169cfc9cf9bSAlexander V. Chernikov
170cfc9cf9bSAlexander V. Chernikov    def _register_iface(self, iface_name: str):
171cfc9cf9bSAlexander V. Chernikov        with open(self.file_name, "a") as f:
172cfc9cf9bSAlexander V. Chernikov            f.write(iface_name + "\n")
173cfc9cf9bSAlexander V. Chernikov
17420ea7f26SAlexander V. Chernikov    def _list_ifaces(self) -> List[str]:
17520ea7f26SAlexander V. Chernikov        ret: List[str] = []
1768eb2bee6SAlexander V. Chernikov        try:
177cfc9cf9bSAlexander V. Chernikov            with open(self.file_name, "r") as f:
1788eb2bee6SAlexander V. Chernikov                for line in f:
17920ea7f26SAlexander V. Chernikov                    ret.append(line.strip())
18020ea7f26SAlexander V. Chernikov        except OSError:
18120ea7f26SAlexander V. Chernikov            pass
18220ea7f26SAlexander V. Chernikov        return ret
18320ea7f26SAlexander V. Chernikov
18420ea7f26SAlexander V. Chernikov    def create_iface(self, alias_name: str, iface_name: str) -> List[VnetInterface]:
18520ea7f26SAlexander V. Chernikov        ifaces = VnetInterface.create_iface(alias_name, iface_name)
18620ea7f26SAlexander V. Chernikov        for iface in ifaces:
18720ea7f26SAlexander V. Chernikov            if not self.is_autodeleted(iface.name):
18820ea7f26SAlexander V. Chernikov                self._register_iface(iface.name)
18920ea7f26SAlexander V. Chernikov        return ifaces
19020ea7f26SAlexander V. Chernikov
19120ea7f26SAlexander V. Chernikov    @staticmethod
19220ea7f26SAlexander V. Chernikov    def is_autodeleted(iface_name: str) -> bool:
1932e620256SJose Luis Duran        if iface_name == "lo0":
1942e620256SJose Luis Duran            return False
19520ea7f26SAlexander V. Chernikov        iface_type = re.split(r"\d+", iface_name)[0]
19620ea7f26SAlexander V. Chernikov        return iface_type in IfaceFactory.AUTODELETE_TYPES
19720ea7f26SAlexander V. Chernikov
19820ea7f26SAlexander V. Chernikov    def cleanup_vnet_interfaces(self, vnet_name: str) -> List[str]:
19920ea7f26SAlexander V. Chernikov        """Destroys"""
20020ea7f26SAlexander V. Chernikov        ifaces_lst = ToolsHelper.get_output(
201a1eb150cSJose Luis Duran            "/usr/sbin/jexec {} /sbin/ifconfig -l".format(vnet_name)
20220ea7f26SAlexander V. Chernikov        )
20320ea7f26SAlexander V. Chernikov        for iface_name in ifaces_lst.split():
20420ea7f26SAlexander V. Chernikov            if not self.is_autodeleted(iface_name):
20520ea7f26SAlexander V. Chernikov                if iface_name not in self._list_ifaces():
20620ea7f26SAlexander V. Chernikov                    print("Skipping interface {}:{}".format(vnet_name, iface_name))
20720ea7f26SAlexander V. Chernikov                    continue
20820ea7f26SAlexander V. Chernikov            run_cmd(
209a1eb150cSJose Luis Duran                "/usr/sbin/jexec {} /sbin/ifconfig {} destroy".format(vnet_name, iface_name)
21020ea7f26SAlexander V. Chernikov            )
21120ea7f26SAlexander V. Chernikov
21220ea7f26SAlexander V. Chernikov    def cleanup(self):
21320ea7f26SAlexander V. Chernikov        try:
214cfc9cf9bSAlexander V. Chernikov            os.unlink(self.INTERFACES_FNAME)
21520ea7f26SAlexander V. Chernikov        except OSError:
2168eb2bee6SAlexander V. Chernikov            pass
2178eb2bee6SAlexander V. Chernikov
2188eb2bee6SAlexander V. Chernikov
219cfc9cf9bSAlexander V. Chernikovclass VnetInstance(object):
220cfc9cf9bSAlexander V. Chernikov    def __init__(
221cfc9cf9bSAlexander V. Chernikov        self, vnet_alias: str, vnet_name: str, jid: int, ifaces: List[VnetInterface]
222cfc9cf9bSAlexander V. Chernikov    ):
223cfc9cf9bSAlexander V. Chernikov        self.name = vnet_name
224cfc9cf9bSAlexander V. Chernikov        self.alias = vnet_alias  # reference in the test topology
225cfc9cf9bSAlexander V. Chernikov        self.jid = jid
226cfc9cf9bSAlexander V. Chernikov        self.ifaces = ifaces
227cfc9cf9bSAlexander V. Chernikov        self.iface_alias_map = {}  # iface.alias: iface
228cfc9cf9bSAlexander V. Chernikov        self.iface_map = {}  # iface.name: iface
2298eb2bee6SAlexander V. Chernikov        for iface in ifaces:
230cfc9cf9bSAlexander V. Chernikov            iface.set_vnet(vnet_name)
231cfc9cf9bSAlexander V. Chernikov            iface.set_jailed(True)
232cfc9cf9bSAlexander V. Chernikov            self.iface_alias_map[iface.alias] = iface
233cfc9cf9bSAlexander V. Chernikov            self.iface_map[iface.name] = iface
234584ad412SAlexander V. Chernikov            # Allow reference to interfce aliases as attributes
235584ad412SAlexander V. Chernikov            setattr(self, iface.alias, iface)
236cfc9cf9bSAlexander V. Chernikov        self.need_dad = False  # Disable duplicate address detection by default
237cfc9cf9bSAlexander V. Chernikov        self.attached = False
238cfc9cf9bSAlexander V. Chernikov        self.pipe = None
239cfc9cf9bSAlexander V. Chernikov        self.subprocess = None
240cfc9cf9bSAlexander V. Chernikov
2418a30ab53SJose Luis Duran    def run_vnet_cmd(self, cmd, verbose=True):
242cfc9cf9bSAlexander V. Chernikov        if not self.attached:
243a1eb150cSJose Luis Duran            cmd = "/usr/sbin/jexec {} {}".format(self.name, cmd)
2448a30ab53SJose Luis Duran        return run_cmd(cmd, verbose)
245cfc9cf9bSAlexander V. Chernikov
246cfc9cf9bSAlexander V. Chernikov    def disable_dad(self):
247cfc9cf9bSAlexander V. Chernikov        self.run_vnet_cmd("/sbin/sysctl net.inet6.ip6.dad_count=0")
248cfc9cf9bSAlexander V. Chernikov
249cfc9cf9bSAlexander V. Chernikov    def set_pipe(self, pipe):
250cfc9cf9bSAlexander V. Chernikov        self.pipe = pipe
251cfc9cf9bSAlexander V. Chernikov
252cfc9cf9bSAlexander V. Chernikov    def set_subprocess(self, p):
253cfc9cf9bSAlexander V. Chernikov        self.subprocess = p
2548eb2bee6SAlexander V. Chernikov
2558eb2bee6SAlexander V. Chernikov    @staticmethod
2568eb2bee6SAlexander V. Chernikov    def attach_jid(jid: int):
2573873bdc2SAlexander V. Chernikov        error_code = libc.jail_attach(jid)
2583873bdc2SAlexander V. Chernikov        if error_code != 0:
2593873bdc2SAlexander V. Chernikov            raise Exception("jail_attach() failed: errno {}".format(error_code))
2608eb2bee6SAlexander V. Chernikov
2618eb2bee6SAlexander V. Chernikov    def attach(self):
2628eb2bee6SAlexander V. Chernikov        self.attach_jid(self.jid)
263cfc9cf9bSAlexander V. Chernikov        self.attached = True
2648eb2bee6SAlexander V. Chernikov
2658eb2bee6SAlexander V. Chernikov
266cfc9cf9bSAlexander V. Chernikovclass VnetFactory(object):
267cfc9cf9bSAlexander V. Chernikov    JAILS_FNAME = "created_jails.lst"
268cfc9cf9bSAlexander V. Chernikov
269f63825ffSAlexander V. Chernikov    def __init__(self, topology_id: str):
270f63825ffSAlexander V. Chernikov        self.topology_id = topology_id
271cfc9cf9bSAlexander V. Chernikov        self.file_name = self.JAILS_FNAME
272cfc9cf9bSAlexander V. Chernikov        self._vnets: List[str] = []
273cfc9cf9bSAlexander V. Chernikov
274cfc9cf9bSAlexander V. Chernikov    def _register_vnet(self, vnet_name: str):
275cfc9cf9bSAlexander V. Chernikov        self._vnets.append(vnet_name)
276cfc9cf9bSAlexander V. Chernikov        with open(self.file_name, "a") as f:
277cfc9cf9bSAlexander V. Chernikov            f.write(vnet_name + "\n")
278cfc9cf9bSAlexander V. Chernikov
279cfc9cf9bSAlexander V. Chernikov    @staticmethod
280cfc9cf9bSAlexander V. Chernikov    def _wait_interfaces(vnet_name: str, ifaces: List[str]) -> List[str]:
281a1eb150cSJose Luis Duran        cmd = "/usr/sbin/jexec {} /sbin/ifconfig -l".format(vnet_name)
282cfc9cf9bSAlexander V. Chernikov        not_matched: List[str] = []
283cfc9cf9bSAlexander V. Chernikov        for i in range(50):
284cfc9cf9bSAlexander V. Chernikov            vnet_ifaces = run_cmd(cmd).strip().split(" ")
285cfc9cf9bSAlexander V. Chernikov            not_matched = []
286cfc9cf9bSAlexander V. Chernikov            for iface_name in ifaces:
287cfc9cf9bSAlexander V. Chernikov                if iface_name not in vnet_ifaces:
288cfc9cf9bSAlexander V. Chernikov                    not_matched.append(iface_name)
289cfc9cf9bSAlexander V. Chernikov            if len(not_matched) == 0:
290cfc9cf9bSAlexander V. Chernikov                return []
291cfc9cf9bSAlexander V. Chernikov            time.sleep(0.1)
292cfc9cf9bSAlexander V. Chernikov        return not_matched
293cfc9cf9bSAlexander V. Chernikov
2942213e158SKristof Provost    def create_vnet(self, vnet_alias: str, ifaces: List[VnetInterface], opts: List[str]):
295f63825ffSAlexander V. Chernikov        vnet_name = "pytest:{}".format(convert_test_name(self.topology_id))
296cfc9cf9bSAlexander V. Chernikov        if self._vnets:
297cfc9cf9bSAlexander V. Chernikov            # add number to distinguish jails
298cfc9cf9bSAlexander V. Chernikov            vnet_name = "{}_{}".format(vnet_name, len(self._vnets) + 1)
299cfc9cf9bSAlexander V. Chernikov        iface_cmds = " ".join(["vnet.interface={}".format(i.name) for i in ifaces])
3002213e158SKristof Provost        opt_cmds = " ".join(["{}".format(i) for i in opts])
3012213e158SKristof Provost        cmd = "/usr/sbin/jail -i -c name={} persist vnet {} {}".format(
3022213e158SKristof Provost            vnet_name, iface_cmds, opt_cmds
303cfc9cf9bSAlexander V. Chernikov        )
304f63825ffSAlexander V. Chernikov        jid = 0
305f63825ffSAlexander V. Chernikov        try:
306cfc9cf9bSAlexander V. Chernikov            jid_str = run_cmd(cmd)
307cfc9cf9bSAlexander V. Chernikov            jid = int(jid_str)
30820ea7f26SAlexander V. Chernikov        except ValueError:
309f63825ffSAlexander V. Chernikov            print("Jail creation failed, output: {}".format(jid_str))
310f63825ffSAlexander V. Chernikov            raise
311cfc9cf9bSAlexander V. Chernikov        self._register_vnet(vnet_name)
312cfc9cf9bSAlexander V. Chernikov
313cfc9cf9bSAlexander V. Chernikov        # Run expedited version of routing
314cfc9cf9bSAlexander V. Chernikov        VnetInterface.setup_loopback(vnet_name)
315cfc9cf9bSAlexander V. Chernikov
316cfc9cf9bSAlexander V. Chernikov        not_found = self._wait_interfaces(vnet_name, [i.name for i in ifaces])
317cfc9cf9bSAlexander V. Chernikov        if not_found:
318cfc9cf9bSAlexander V. Chernikov            raise Exception(
319cfc9cf9bSAlexander V. Chernikov                "Interfaces {} has not appeared in vnet {}".format(not_found, vnet_name)
320cfc9cf9bSAlexander V. Chernikov            )
321cfc9cf9bSAlexander V. Chernikov        return VnetInstance(vnet_alias, vnet_name, jid, ifaces)
322cfc9cf9bSAlexander V. Chernikov
323cfc9cf9bSAlexander V. Chernikov    def cleanup(self):
32420ea7f26SAlexander V. Chernikov        iface_factory = IfaceFactory()
325cfc9cf9bSAlexander V. Chernikov        try:
326cfc9cf9bSAlexander V. Chernikov            with open(self.file_name) as f:
327cfc9cf9bSAlexander V. Chernikov                for line in f:
328f63825ffSAlexander V. Chernikov                    vnet_name = line.strip()
32920ea7f26SAlexander V. Chernikov                    iface_factory.cleanup_vnet_interfaces(vnet_name)
330f63825ffSAlexander V. Chernikov                    run_cmd("/usr/sbin/jail -r  {}".format(vnet_name))
331cfc9cf9bSAlexander V. Chernikov            os.unlink(self.JAILS_FNAME)
332cfc9cf9bSAlexander V. Chernikov        except OSError:
333cfc9cf9bSAlexander V. Chernikov            pass
334cfc9cf9bSAlexander V. Chernikov
335cfc9cf9bSAlexander V. Chernikov
336cfc9cf9bSAlexander V. Chernikovclass SingleInterfaceMap(NamedTuple):
337cfc9cf9bSAlexander V. Chernikov    ifaces: List[VnetInterface]
338cfc9cf9bSAlexander V. Chernikov    vnet_aliases: List[str]
339cfc9cf9bSAlexander V. Chernikov
340cfc9cf9bSAlexander V. Chernikov
341f63825ffSAlexander V. Chernikovclass ObjectsMap(NamedTuple):
342f63825ffSAlexander V. Chernikov    iface_map: Dict[str, SingleInterfaceMap]  # keyed by ifX
343f63825ffSAlexander V. Chernikov    vnet_map: Dict[str, VnetInstance]  # keyed by vnetX
344f63825ffSAlexander V. Chernikov    topo_map: Dict  # self.TOPOLOGY
345f63825ffSAlexander V. Chernikov
346f63825ffSAlexander V. Chernikov
3473873bdc2SAlexander V. Chernikovclass VnetTestTemplate(BaseTest):
3486332ef89SAlexander V. Chernikov    NEED_ROOT: bool = True
349cfc9cf9bSAlexander V. Chernikov    TOPOLOGY = {}
350cfc9cf9bSAlexander V. Chernikov
351ae8d5881SKristof Provost    def _require_default_modules(self):
352ae8d5881SKristof Provost        libc.kldload("if_epair.ko")
353ae8d5881SKristof Provost        self.require_module("if_epair")
354ae8d5881SKristof Provost
355cfc9cf9bSAlexander V. Chernikov    def _get_vnet_handler(self, vnet_alias: str):
356cfc9cf9bSAlexander V. Chernikov        handler_name = "{}_handler".format(vnet_alias)
357cfc9cf9bSAlexander V. Chernikov        return getattr(self, handler_name, None)
358cfc9cf9bSAlexander V. Chernikov
359cfc9cf9bSAlexander V. Chernikov    def _setup_vnet(self, vnet: VnetInstance, obj_map: Dict, pipe):
360cfc9cf9bSAlexander V. Chernikov        """Base Handler to setup given VNET.
361cfc9cf9bSAlexander V. Chernikov        Can be run in a subprocess. If so, passes control to the special
362cfc9cf9bSAlexander V. Chernikov        vnetX_handler() after setting up interface addresses
363cfc9cf9bSAlexander V. Chernikov        """
364cfc9cf9bSAlexander V. Chernikov        vnet.attach()
36507940d1dSMark Johnston        os.chdir(os.getenv("HOME"))
366cfc9cf9bSAlexander V. Chernikov        print("# setup_vnet({})".format(vnet.name))
367f63825ffSAlexander V. Chernikov        if pipe is not None:
368f63825ffSAlexander V. Chernikov            vnet.set_pipe(pipe)
369cfc9cf9bSAlexander V. Chernikov
370f63825ffSAlexander V. Chernikov        topo = obj_map.topo_map
371cfc9cf9bSAlexander V. Chernikov        ipv6_ifaces = []
372cfc9cf9bSAlexander V. Chernikov        # Disable DAD
373cfc9cf9bSAlexander V. Chernikov        if not vnet.need_dad:
374cfc9cf9bSAlexander V. Chernikov            vnet.disable_dad()
375cfc9cf9bSAlexander V. Chernikov        for iface in vnet.ifaces:
376cfc9cf9bSAlexander V. Chernikov            # check index of vnet within an interface
377cfc9cf9bSAlexander V. Chernikov            # as we have prefixes for both ends of the interface
378f63825ffSAlexander V. Chernikov            iface_map = obj_map.iface_map[iface.alias]
379cfc9cf9bSAlexander V. Chernikov            idx = iface_map.vnet_aliases.index(vnet.alias)
380cfc9cf9bSAlexander V. Chernikov            prefixes6 = topo[iface.alias].get("prefixes6", [])
381cfc9cf9bSAlexander V. Chernikov            prefixes4 = topo[iface.alias].get("prefixes4", [])
3824f35a84bSKristof Provost            mtu = topo[iface.alias].get("mtu", 0)
383*a8b8feceSMark Johnston            if "fib" in topo[iface.alias]:
384*a8b8feceSMark Johnston                fib = topo[iface.alias]["fib"]
385*a8b8feceSMark Johnston                iface.setfib(fib[idx])
386cfc9cf9bSAlexander V. Chernikov            if prefixes6 or prefixes4:
387cfc9cf9bSAlexander V. Chernikov                ipv6_ifaces.append(iface)
388cfc9cf9bSAlexander V. Chernikov                iface.turn_up()
389cfc9cf9bSAlexander V. Chernikov                if prefixes6:
390cfc9cf9bSAlexander V. Chernikov                    iface.enable_ipv6()
391cfc9cf9bSAlexander V. Chernikov            for prefix in prefixes6 + prefixes4:
392584ad412SAlexander V. Chernikov                if prefix[idx]:
393cfc9cf9bSAlexander V. Chernikov                    iface.setup_addr(prefix[idx])
3944f35a84bSKristof Provost            if mtu != 0:
3954f35a84bSKristof Provost                iface.set_mtu(mtu)
396cfc9cf9bSAlexander V. Chernikov        for iface in ipv6_ifaces:
397cfc9cf9bSAlexander V. Chernikov            while iface.has_tentative():
398cfc9cf9bSAlexander V. Chernikov                time.sleep(0.1)
399cfc9cf9bSAlexander V. Chernikov
400cfc9cf9bSAlexander V. Chernikov        # Run actual handler
401cfc9cf9bSAlexander V. Chernikov        handler = self._get_vnet_handler(vnet.alias)
402cfc9cf9bSAlexander V. Chernikov        if handler:
403cfc9cf9bSAlexander V. Chernikov            # Do unbuffered stdout for children
404cfc9cf9bSAlexander V. Chernikov            # so the logs are present if the child hangs
405cfc9cf9bSAlexander V. Chernikov            sys.stdout.reconfigure(line_buffering=True)
4066332ef89SAlexander V. Chernikov            self.drop_privileges()
407f63825ffSAlexander V. Chernikov            handler(vnet)
408cfc9cf9bSAlexander V. Chernikov
409584ad412SAlexander V. Chernikov    def _get_topo_ifmap(self, topo: Dict):
410584ad412SAlexander V. Chernikov        iface_factory = IfaceFactory()
411584ad412SAlexander V. Chernikov        iface_map: Dict[str, SingleInterfaceMap] = {}
412584ad412SAlexander V. Chernikov        iface_aliases = set()
413584ad412SAlexander V. Chernikov        for obj_name, obj_data in topo.items():
414584ad412SAlexander V. Chernikov            if obj_name.startswith("vnet"):
415584ad412SAlexander V. Chernikov                for iface_alias in obj_data["ifaces"]:
416584ad412SAlexander V. Chernikov                    iface_aliases.add(iface_alias)
417584ad412SAlexander V. Chernikov        for iface_alias in iface_aliases:
418584ad412SAlexander V. Chernikov            print("Creating {}".format(iface_alias))
419584ad412SAlexander V. Chernikov            iface_data = topo[iface_alias]
420584ad412SAlexander V. Chernikov            iface_type = iface_data.get("type", "epair")
421584ad412SAlexander V. Chernikov            ifaces = iface_factory.create_iface(iface_alias, iface_type)
422584ad412SAlexander V. Chernikov            smap = SingleInterfaceMap(ifaces, [])
423584ad412SAlexander V. Chernikov            iface_map[iface_alias] = smap
424584ad412SAlexander V. Chernikov        return iface_map
425584ad412SAlexander V. Chernikov
426f63825ffSAlexander V. Chernikov    def setup_topology(self, topo: Dict, topology_id: str):
427cfc9cf9bSAlexander V. Chernikov        """Creates jails & interfaces for the provided topology"""
428cfc9cf9bSAlexander V. Chernikov        vnet_map = {}
429f63825ffSAlexander V. Chernikov        vnet_factory = VnetFactory(topology_id)
430584ad412SAlexander V. Chernikov        iface_map = self._get_topo_ifmap(topo)
431cfc9cf9bSAlexander V. Chernikov        for obj_name, obj_data in topo.items():
432cfc9cf9bSAlexander V. Chernikov            if obj_name.startswith("vnet"):
433cfc9cf9bSAlexander V. Chernikov                vnet_ifaces = []
434*a8b8feceSMark Johnston                maxfib = 0
435cfc9cf9bSAlexander V. Chernikov                for iface_alias in obj_data["ifaces"]:
436cfc9cf9bSAlexander V. Chernikov                    # epair creates 2 interfaces, grab first _available_
437cfc9cf9bSAlexander V. Chernikov                    # and map it to the VNET being created
438cfc9cf9bSAlexander V. Chernikov                    idx = len(iface_map[iface_alias].vnet_aliases)
439cfc9cf9bSAlexander V. Chernikov                    iface_map[iface_alias].vnet_aliases.append(obj_name)
440cfc9cf9bSAlexander V. Chernikov                    vnet_ifaces.append(iface_map[iface_alias].ifaces[idx])
441*a8b8feceSMark Johnston                    fib = topo[iface_alias].get("fib", (0, 0))
442*a8b8feceSMark Johnston                    maxfib = max(maxfib, fib[idx])
4432213e158SKristof Provost                opts = []
4442213e158SKristof Provost                if "opts" in obj_data:
4452213e158SKristof Provost                    opts = obj_data["opts"]
4462213e158SKristof Provost                vnet = vnet_factory.create_vnet(obj_name, vnet_ifaces, opts)
447*a8b8feceSMark Johnston                if maxfib != 0:
448*a8b8feceSMark Johnston                    # Make sure the VNET has enough FIBs.
449*a8b8feceSMark Johnston                    vnet.run_vnet_cmd("/sbin/sysctl net.fibs={}".format(maxfib + 1))
450cfc9cf9bSAlexander V. Chernikov                vnet_map[obj_name] = vnet
451584ad412SAlexander V. Chernikov                # Allow reference to VNETs as attributes
452584ad412SAlexander V. Chernikov                setattr(self, obj_name, vnet)
453cfc9cf9bSAlexander V. Chernikov        # Debug output
454cfc9cf9bSAlexander V. Chernikov        print("============= TEST TOPOLOGY =============")
455cfc9cf9bSAlexander V. Chernikov        for vnet_alias, vnet in vnet_map.items():
456cfc9cf9bSAlexander V. Chernikov            print("# vnet {} -> {}".format(vnet.alias, vnet.name), end="")
457cfc9cf9bSAlexander V. Chernikov            handler = self._get_vnet_handler(vnet.alias)
458cfc9cf9bSAlexander V. Chernikov            if handler:
459cfc9cf9bSAlexander V. Chernikov                print(" handler: {}".format(handler.__name__), end="")
460cfc9cf9bSAlexander V. Chernikov            print()
461cfc9cf9bSAlexander V. Chernikov        for iface_alias, iface_data in iface_map.items():
462cfc9cf9bSAlexander V. Chernikov            vnets = iface_data.vnet_aliases
463cfc9cf9bSAlexander V. Chernikov            ifaces: List[VnetInterface] = iface_data.ifaces
464cfc9cf9bSAlexander V. Chernikov            if len(vnets) == 1 and len(ifaces) == 2:
465cfc9cf9bSAlexander V. Chernikov                print(
466cfc9cf9bSAlexander V. Chernikov                    "# iface {}: {}::{} -> main::{}".format(
467cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets[0], ifaces[0].name, ifaces[1].name
468cfc9cf9bSAlexander V. Chernikov                    )
469cfc9cf9bSAlexander V. Chernikov                )
470cfc9cf9bSAlexander V. Chernikov            elif len(vnets) == 2 and len(ifaces) == 2:
471cfc9cf9bSAlexander V. Chernikov                print(
472cfc9cf9bSAlexander V. Chernikov                    "# iface {}: {}::{} -> {}::{}".format(
473cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets[0], ifaces[0].name, vnets[1], ifaces[1].name
474cfc9cf9bSAlexander V. Chernikov                    )
475cfc9cf9bSAlexander V. Chernikov                )
476cfc9cf9bSAlexander V. Chernikov            else:
477cfc9cf9bSAlexander V. Chernikov                print(
478cfc9cf9bSAlexander V. Chernikov                    "# iface {}: ifaces: {} vnets: {}".format(
479cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets, [i.name for i in ifaces]
480cfc9cf9bSAlexander V. Chernikov                    )
481cfc9cf9bSAlexander V. Chernikov                )
482cfc9cf9bSAlexander V. Chernikov        print()
483f63825ffSAlexander V. Chernikov        return ObjectsMap(iface_map, vnet_map, topo)
484cfc9cf9bSAlexander V. Chernikov
485f63825ffSAlexander V. Chernikov    def setup_method(self, _method):
486cfc9cf9bSAlexander V. Chernikov        """Sets up all the required topology and handlers for the given test"""
487f63825ffSAlexander V. Chernikov        super().setup_method(_method)
488ae8d5881SKristof Provost        self._require_default_modules()
489ae8d5881SKristof Provost
490f63825ffSAlexander V. Chernikov        # TestIP6Output.test_output6_pktinfo[ipandif]
491f63825ffSAlexander V. Chernikov        topology_id = get_topology_id(self.test_id)
492cfc9cf9bSAlexander V. Chernikov        topology = self.TOPOLOGY
493cfc9cf9bSAlexander V. Chernikov        # First, setup kernel objects - interfaces & vnets
494f63825ffSAlexander V. Chernikov        obj_map = self.setup_topology(topology, topology_id)
495cfc9cf9bSAlexander V. Chernikov        main_vnet = None  # one without subprocess handler
496f63825ffSAlexander V. Chernikov        for vnet_alias, vnet in obj_map.vnet_map.items():
497cfc9cf9bSAlexander V. Chernikov            if self._get_vnet_handler(vnet_alias):
498cfc9cf9bSAlexander V. Chernikov                # Need subprocess to run
499cfc9cf9bSAlexander V. Chernikov                parent_pipe, child_pipe = Pipe()
500cfc9cf9bSAlexander V. Chernikov                p = Process(
501cfc9cf9bSAlexander V. Chernikov                    target=self._setup_vnet,
502cfc9cf9bSAlexander V. Chernikov                    args=(
503cfc9cf9bSAlexander V. Chernikov                        vnet,
504cfc9cf9bSAlexander V. Chernikov                        obj_map,
505cfc9cf9bSAlexander V. Chernikov                        child_pipe,
506cfc9cf9bSAlexander V. Chernikov                    ),
507cfc9cf9bSAlexander V. Chernikov                )
508cfc9cf9bSAlexander V. Chernikov                vnet.set_pipe(parent_pipe)
509cfc9cf9bSAlexander V. Chernikov                vnet.set_subprocess(p)
510cfc9cf9bSAlexander V. Chernikov                p.start()
511cfc9cf9bSAlexander V. Chernikov            else:
512cfc9cf9bSAlexander V. Chernikov                if main_vnet is not None:
513cfc9cf9bSAlexander V. Chernikov                    raise Exception("there can be only 1 VNET w/o handler")
514cfc9cf9bSAlexander V. Chernikov                main_vnet = vnet
515cfc9cf9bSAlexander V. Chernikov        # Main vnet needs to be the last, so all the other subprocesses
516cfc9cf9bSAlexander V. Chernikov        # are started & their pipe handles collected
517cfc9cf9bSAlexander V. Chernikov        self.vnet = main_vnet
518cfc9cf9bSAlexander V. Chernikov        self._setup_vnet(main_vnet, obj_map, None)
519cfc9cf9bSAlexander V. Chernikov        # Save state for the main handler
520f63825ffSAlexander V. Chernikov        self.iface_map = obj_map.iface_map
521f63825ffSAlexander V. Chernikov        self.vnet_map = obj_map.vnet_map
5226332ef89SAlexander V. Chernikov        self.drop_privileges()
523cfc9cf9bSAlexander V. Chernikov
524cfc9cf9bSAlexander V. Chernikov    def cleanup(self, test_id: str):
525cfc9cf9bSAlexander V. Chernikov        # pytest test id: file::class::test_name
526f63825ffSAlexander V. Chernikov        topology_id = get_topology_id(self.test_id)
527cfc9cf9bSAlexander V. Chernikov
528d4a5d495SJose Luis Duran        print("============= vnet cleanup =============")
529f63825ffSAlexander V. Chernikov        print("# topology_id: '{}'".format(topology_id))
530f63825ffSAlexander V. Chernikov        VnetFactory(topology_id).cleanup()
531f63825ffSAlexander V. Chernikov        IfaceFactory().cleanup()
532cfc9cf9bSAlexander V. Chernikov
533cfc9cf9bSAlexander V. Chernikov    def wait_object(self, pipe, timeout=5):
534cfc9cf9bSAlexander V. Chernikov        if pipe.poll(timeout):
535cfc9cf9bSAlexander V. Chernikov            return pipe.recv()
536cfc9cf9bSAlexander V. Chernikov        raise TimeoutError
537cfc9cf9bSAlexander V. Chernikov
538584ad412SAlexander V. Chernikov    def wait_objects_any(self, pipe_list, timeout=5):
539584ad412SAlexander V. Chernikov        objects = connection.wait(pipe_list, timeout)
540584ad412SAlexander V. Chernikov        if objects:
541584ad412SAlexander V. Chernikov            return objects[0].recv()
542584ad412SAlexander V. Chernikov        raise TimeoutError
543584ad412SAlexander V. Chernikov
544f63825ffSAlexander V. Chernikov    def send_object(self, pipe, obj):
545f63825ffSAlexander V. Chernikov        pipe.send(obj)
546f63825ffSAlexander V. Chernikov
547584ad412SAlexander V. Chernikov    def wait(self):
548584ad412SAlexander V. Chernikov        while True:
549584ad412SAlexander V. Chernikov            time.sleep(1)
550584ad412SAlexander V. Chernikov
551cfc9cf9bSAlexander V. Chernikov    @property
552cfc9cf9bSAlexander V. Chernikov    def curvnet(self):
553cfc9cf9bSAlexander V. Chernikov        pass
554cfc9cf9bSAlexander V. Chernikov
555cfc9cf9bSAlexander V. Chernikov
556cfc9cf9bSAlexander V. Chernikovclass SingleVnetTestTemplate(VnetTestTemplate):
5578eb2bee6SAlexander V. Chernikov    IPV6_PREFIXES: List[str] = []
5588eb2bee6SAlexander V. Chernikov    IPV4_PREFIXES: List[str] = []
559f3065e76SAlexander V. Chernikov    IFTYPE = "epair"
5608eb2bee6SAlexander V. Chernikov
561f3065e76SAlexander V. Chernikov    def _setup_default_topology(self):
562cfc9cf9bSAlexander V. Chernikov        topology = copy.deepcopy(
563cfc9cf9bSAlexander V. Chernikov            {
564cfc9cf9bSAlexander V. Chernikov                "vnet1": {"ifaces": ["if1"]},
565f3065e76SAlexander V. Chernikov                "if1": {"type": self.IFTYPE, "prefixes4": [], "prefixes6": []},
566cfc9cf9bSAlexander V. Chernikov            }
567cfc9cf9bSAlexander V. Chernikov        )
568cfc9cf9bSAlexander V. Chernikov        for prefix in self.IPV6_PREFIXES:
569cfc9cf9bSAlexander V. Chernikov            topology["if1"]["prefixes6"].append((prefix,))
570cfc9cf9bSAlexander V. Chernikov        for prefix in self.IPV4_PREFIXES:
571cfc9cf9bSAlexander V. Chernikov            topology["if1"]["prefixes4"].append((prefix,))
572f3065e76SAlexander V. Chernikov        return topology
573f3065e76SAlexander V. Chernikov
574f3065e76SAlexander V. Chernikov    def setup_method(self, method):
575f3065e76SAlexander V. Chernikov        if not getattr(self, "TOPOLOGY", None):
576f3065e76SAlexander V. Chernikov            self.TOPOLOGY = self._setup_default_topology()
577f3065e76SAlexander V. Chernikov        else:
578f3065e76SAlexander V. Chernikov            names = self.TOPOLOGY.keys()
579f3065e76SAlexander V. Chernikov            assert len([n for n in names if n.startswith("vnet")]) == 1
580cfc9cf9bSAlexander V. Chernikov        super().setup_method(method)
581