xref: /freebsd/tests/atf_python/sys/net/vnet.py (revision 9c95fcb7cd20bdb0e9445ec98a06d24ce0d5d2ac)
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
64*9c95fcb7SRonald Klop            self.ether = ToolsHelper.get_output("/sbin/ifconfig %s ether | awk '/ether/ { print $2; }'" % iface_name).rstrip()
658eb2bee6SAlexander V. Chernikov
668eb2bee6SAlexander V. Chernikov    @property
678eb2bee6SAlexander V. Chernikov    def ifindex(self):
688eb2bee6SAlexander V. Chernikov        return socket.if_nametoindex(self.name)
698eb2bee6SAlexander V. Chernikov
70cfc9cf9bSAlexander V. Chernikov    @property
71cfc9cf9bSAlexander V. Chernikov    def first_ipv6(self):
72cfc9cf9bSAlexander V. Chernikov        d = self.addr_map["inet6"]
73cfc9cf9bSAlexander V. Chernikov        return d[next(iter(d))]
74cfc9cf9bSAlexander V. Chernikov
75cfc9cf9bSAlexander V. Chernikov    @property
76cfc9cf9bSAlexander V. Chernikov    def first_ipv4(self):
77cfc9cf9bSAlexander V. Chernikov        d = self.addr_map["inet"]
78cfc9cf9bSAlexander V. Chernikov        return d[next(iter(d))]
79cfc9cf9bSAlexander V. Chernikov
808eb2bee6SAlexander V. Chernikov    def set_vnet(self, vnet_name: str):
818eb2bee6SAlexander V. Chernikov        self.vnet_name = vnet_name
828eb2bee6SAlexander V. Chernikov
838eb2bee6SAlexander V. Chernikov    def set_jailed(self, jailed: bool):
848eb2bee6SAlexander V. Chernikov        self.jailed = jailed
858eb2bee6SAlexander V. Chernikov
86a1eb150cSJose Luis Duran    def run_cmd(self, cmd, verbose=False):
878eb2bee6SAlexander V. Chernikov        if self.vnet_name and not self.jailed:
88a1eb150cSJose Luis Duran            cmd = "/usr/sbin/jexec {} {}".format(self.vnet_name, cmd)
89cfc9cf9bSAlexander V. Chernikov        return run_cmd(cmd, verbose)
908eb2bee6SAlexander V. Chernikov
918eb2bee6SAlexander V. Chernikov    @classmethod
92cfc9cf9bSAlexander V. Chernikov    def setup_loopback(cls, vnet_name: str):
93cfc9cf9bSAlexander V. Chernikov        lo = VnetInterface("", "lo0")
94cfc9cf9bSAlexander V. Chernikov        lo.set_vnet(vnet_name)
954856aeaaSJose Luis Duran        lo.setup_addr("127.0.0.1/8")
96cfc9cf9bSAlexander V. Chernikov        lo.turn_up()
97cfc9cf9bSAlexander V. Chernikov
98cfc9cf9bSAlexander V. Chernikov    @classmethod
99cfc9cf9bSAlexander V. Chernikov    def create_iface(cls, alias_name: str, iface_name: str) -> List["VnetInterface"]:
1008eb2bee6SAlexander V. Chernikov        name = run_cmd("/sbin/ifconfig {} create".format(iface_name)).rstrip()
1018eb2bee6SAlexander V. Chernikov        if not name:
1028eb2bee6SAlexander V. Chernikov            raise Exception("Unable to create iface {}".format(iface_name))
103*9c95fcb7SRonald Klop        if1 = cls(alias_name, name)
104*9c95fcb7SRonald Klop        ret = [if1]
1058eb2bee6SAlexander V. Chernikov        if name.startswith("epair"):
106*9c95fcb7SRonald Klop            if2 = cls(alias_name, name[:-1] + "b")
107*9c95fcb7SRonald Klop            if1.epairb = if2
108*9c95fcb7SRonald Klop            ret.append(if2);
109cfc9cf9bSAlexander V. Chernikov        return ret
1108eb2bee6SAlexander V. Chernikov
111cfc9cf9bSAlexander V. Chernikov    def setup_addr(self, _addr: str):
112cfc9cf9bSAlexander V. Chernikov        addr = ipaddress.ip_interface(_addr)
113cfc9cf9bSAlexander V. Chernikov        if addr.version == 6:
1148eb2bee6SAlexander V. Chernikov            family = "inet6"
1157064c94aSAlexander V. Chernikov            cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
1168eb2bee6SAlexander V. Chernikov        else:
1178eb2bee6SAlexander V. Chernikov            family = "inet"
1187064c94aSAlexander V. Chernikov            if self.addr_map[family]:
1197064c94aSAlexander V. Chernikov                cmd = "/sbin/ifconfig {} alias {}".format(self.name, addr)
1207064c94aSAlexander V. Chernikov            else:
1218eb2bee6SAlexander V. Chernikov                cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr)
1228eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1237064c94aSAlexander V. Chernikov        self.addr_map[family][str(addr.ip)] = addr
1248eb2bee6SAlexander V. Chernikov
125cfc9cf9bSAlexander V. Chernikov    def delete_addr(self, _addr: str):
126cfc9cf9bSAlexander V. Chernikov        addr = ipaddress.ip_address(_addr)
127cfc9cf9bSAlexander V. Chernikov        if addr.version == 6:
128cfc9cf9bSAlexander V. Chernikov            family = "inet6"
1298eb2bee6SAlexander V. Chernikov            cmd = "/sbin/ifconfig {} inet6 {} delete".format(self.name, addr)
1308eb2bee6SAlexander V. Chernikov        else:
131cfc9cf9bSAlexander V. Chernikov            family = "inet"
1328eb2bee6SAlexander V. Chernikov            cmd = "/sbin/ifconfig {} -alias {}".format(self.name, addr)
1338eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
134cfc9cf9bSAlexander V. Chernikov        del self.addr_map[family][str(addr)]
1358eb2bee6SAlexander V. Chernikov
1368eb2bee6SAlexander V. Chernikov    def turn_up(self):
1378eb2bee6SAlexander V. Chernikov        cmd = "/sbin/ifconfig {} up".format(self.name)
1388eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1398eb2bee6SAlexander V. Chernikov
1408eb2bee6SAlexander V. Chernikov    def enable_ipv6(self):
1416ae89b2fSKristof Provost        cmd = "/usr/sbin/ndp -i {} -- -disabled".format(self.name)
1428eb2bee6SAlexander V. Chernikov        self.run_cmd(cmd)
1438eb2bee6SAlexander V. Chernikov
144cfc9cf9bSAlexander V. Chernikov    def has_tentative(self) -> bool:
145cfc9cf9bSAlexander V. Chernikov        """True if an interface has some addresses in tenative state"""
146cfc9cf9bSAlexander V. Chernikov        cmd = "/sbin/ifconfig {} inet6".format(self.name)
147cfc9cf9bSAlexander V. Chernikov        out = self.run_cmd(cmd, verbose=False)
148cfc9cf9bSAlexander V. Chernikov        for line in out.splitlines():
149cfc9cf9bSAlexander V. Chernikov            if "tentative" in line:
1508eb2bee6SAlexander V. Chernikov                return True
1518eb2bee6SAlexander V. Chernikov        return False
1528eb2bee6SAlexander V. Chernikov
1538eb2bee6SAlexander V. Chernikov
154cfc9cf9bSAlexander V. Chernikovclass IfaceFactory(object):
155cfc9cf9bSAlexander V. Chernikov    INTERFACES_FNAME = "created_ifaces.lst"
156f3065e76SAlexander V. Chernikov    AUTODELETE_TYPES = ("epair", "gif", "gre", "lo", "tap", "tun")
157cfc9cf9bSAlexander V. Chernikov
158f63825ffSAlexander V. Chernikov    def __init__(self):
159cfc9cf9bSAlexander V. Chernikov        self.file_name = self.INTERFACES_FNAME
160cfc9cf9bSAlexander V. Chernikov
161cfc9cf9bSAlexander V. Chernikov    def _register_iface(self, iface_name: str):
162cfc9cf9bSAlexander V. Chernikov        with open(self.file_name, "a") as f:
163cfc9cf9bSAlexander V. Chernikov            f.write(iface_name + "\n")
164cfc9cf9bSAlexander V. Chernikov
16520ea7f26SAlexander V. Chernikov    def _list_ifaces(self) -> List[str]:
16620ea7f26SAlexander V. Chernikov        ret: List[str] = []
1678eb2bee6SAlexander V. Chernikov        try:
168cfc9cf9bSAlexander V. Chernikov            with open(self.file_name, "r") as f:
1698eb2bee6SAlexander V. Chernikov                for line in f:
17020ea7f26SAlexander V. Chernikov                    ret.append(line.strip())
17120ea7f26SAlexander V. Chernikov        except OSError:
17220ea7f26SAlexander V. Chernikov            pass
17320ea7f26SAlexander V. Chernikov        return ret
17420ea7f26SAlexander V. Chernikov
17520ea7f26SAlexander V. Chernikov    def create_iface(self, alias_name: str, iface_name: str) -> List[VnetInterface]:
17620ea7f26SAlexander V. Chernikov        ifaces = VnetInterface.create_iface(alias_name, iface_name)
17720ea7f26SAlexander V. Chernikov        for iface in ifaces:
17820ea7f26SAlexander V. Chernikov            if not self.is_autodeleted(iface.name):
17920ea7f26SAlexander V. Chernikov                self._register_iface(iface.name)
18020ea7f26SAlexander V. Chernikov        return ifaces
18120ea7f26SAlexander V. Chernikov
18220ea7f26SAlexander V. Chernikov    @staticmethod
18320ea7f26SAlexander V. Chernikov    def is_autodeleted(iface_name: str) -> bool:
1842e620256SJose Luis Duran        if iface_name == "lo0":
1852e620256SJose Luis Duran            return False
18620ea7f26SAlexander V. Chernikov        iface_type = re.split(r"\d+", iface_name)[0]
18720ea7f26SAlexander V. Chernikov        return iface_type in IfaceFactory.AUTODELETE_TYPES
18820ea7f26SAlexander V. Chernikov
18920ea7f26SAlexander V. Chernikov    def cleanup_vnet_interfaces(self, vnet_name: str) -> List[str]:
19020ea7f26SAlexander V. Chernikov        """Destroys"""
19120ea7f26SAlexander V. Chernikov        ifaces_lst = ToolsHelper.get_output(
192a1eb150cSJose Luis Duran            "/usr/sbin/jexec {} /sbin/ifconfig -l".format(vnet_name)
19320ea7f26SAlexander V. Chernikov        )
19420ea7f26SAlexander V. Chernikov        for iface_name in ifaces_lst.split():
19520ea7f26SAlexander V. Chernikov            if not self.is_autodeleted(iface_name):
19620ea7f26SAlexander V. Chernikov                if iface_name not in self._list_ifaces():
19720ea7f26SAlexander V. Chernikov                    print("Skipping interface {}:{}".format(vnet_name, iface_name))
19820ea7f26SAlexander V. Chernikov                    continue
19920ea7f26SAlexander V. Chernikov            run_cmd(
200a1eb150cSJose Luis Duran                "/usr/sbin/jexec {} /sbin/ifconfig {} destroy".format(vnet_name, iface_name)
20120ea7f26SAlexander V. Chernikov            )
20220ea7f26SAlexander V. Chernikov
20320ea7f26SAlexander V. Chernikov    def cleanup(self):
20420ea7f26SAlexander V. Chernikov        try:
205cfc9cf9bSAlexander V. Chernikov            os.unlink(self.INTERFACES_FNAME)
20620ea7f26SAlexander V. Chernikov        except OSError:
2078eb2bee6SAlexander V. Chernikov            pass
2088eb2bee6SAlexander V. Chernikov
2098eb2bee6SAlexander V. Chernikov
210cfc9cf9bSAlexander V. Chernikovclass VnetInstance(object):
211cfc9cf9bSAlexander V. Chernikov    def __init__(
212cfc9cf9bSAlexander V. Chernikov        self, vnet_alias: str, vnet_name: str, jid: int, ifaces: List[VnetInterface]
213cfc9cf9bSAlexander V. Chernikov    ):
214cfc9cf9bSAlexander V. Chernikov        self.name = vnet_name
215cfc9cf9bSAlexander V. Chernikov        self.alias = vnet_alias  # reference in the test topology
216cfc9cf9bSAlexander V. Chernikov        self.jid = jid
217cfc9cf9bSAlexander V. Chernikov        self.ifaces = ifaces
218cfc9cf9bSAlexander V. Chernikov        self.iface_alias_map = {}  # iface.alias: iface
219cfc9cf9bSAlexander V. Chernikov        self.iface_map = {}  # iface.name: iface
2208eb2bee6SAlexander V. Chernikov        for iface in ifaces:
221cfc9cf9bSAlexander V. Chernikov            iface.set_vnet(vnet_name)
222cfc9cf9bSAlexander V. Chernikov            iface.set_jailed(True)
223cfc9cf9bSAlexander V. Chernikov            self.iface_alias_map[iface.alias] = iface
224cfc9cf9bSAlexander V. Chernikov            self.iface_map[iface.name] = iface
225584ad412SAlexander V. Chernikov            # Allow reference to interfce aliases as attributes
226584ad412SAlexander V. Chernikov            setattr(self, iface.alias, iface)
227cfc9cf9bSAlexander V. Chernikov        self.need_dad = False  # Disable duplicate address detection by default
228cfc9cf9bSAlexander V. Chernikov        self.attached = False
229cfc9cf9bSAlexander V. Chernikov        self.pipe = None
230cfc9cf9bSAlexander V. Chernikov        self.subprocess = None
231cfc9cf9bSAlexander V. Chernikov
2328a30ab53SJose Luis Duran    def run_vnet_cmd(self, cmd, verbose=True):
233cfc9cf9bSAlexander V. Chernikov        if not self.attached:
234a1eb150cSJose Luis Duran            cmd = "/usr/sbin/jexec {} {}".format(self.name, cmd)
2358a30ab53SJose Luis Duran        return run_cmd(cmd, verbose)
236cfc9cf9bSAlexander V. Chernikov
237cfc9cf9bSAlexander V. Chernikov    def disable_dad(self):
238cfc9cf9bSAlexander V. Chernikov        self.run_vnet_cmd("/sbin/sysctl net.inet6.ip6.dad_count=0")
239cfc9cf9bSAlexander V. Chernikov
240cfc9cf9bSAlexander V. Chernikov    def set_pipe(self, pipe):
241cfc9cf9bSAlexander V. Chernikov        self.pipe = pipe
242cfc9cf9bSAlexander V. Chernikov
243cfc9cf9bSAlexander V. Chernikov    def set_subprocess(self, p):
244cfc9cf9bSAlexander V. Chernikov        self.subprocess = p
2458eb2bee6SAlexander V. Chernikov
2468eb2bee6SAlexander V. Chernikov    @staticmethod
2478eb2bee6SAlexander V. Chernikov    def attach_jid(jid: int):
2483873bdc2SAlexander V. Chernikov        error_code = libc.jail_attach(jid)
2493873bdc2SAlexander V. Chernikov        if error_code != 0:
2503873bdc2SAlexander V. Chernikov            raise Exception("jail_attach() failed: errno {}".format(error_code))
2518eb2bee6SAlexander V. Chernikov
2528eb2bee6SAlexander V. Chernikov    def attach(self):
2538eb2bee6SAlexander V. Chernikov        self.attach_jid(self.jid)
254cfc9cf9bSAlexander V. Chernikov        self.attached = True
2558eb2bee6SAlexander V. Chernikov
2568eb2bee6SAlexander V. Chernikov
257cfc9cf9bSAlexander V. Chernikovclass VnetFactory(object):
258cfc9cf9bSAlexander V. Chernikov    JAILS_FNAME = "created_jails.lst"
259cfc9cf9bSAlexander V. Chernikov
260f63825ffSAlexander V. Chernikov    def __init__(self, topology_id: str):
261f63825ffSAlexander V. Chernikov        self.topology_id = topology_id
262cfc9cf9bSAlexander V. Chernikov        self.file_name = self.JAILS_FNAME
263cfc9cf9bSAlexander V. Chernikov        self._vnets: List[str] = []
264cfc9cf9bSAlexander V. Chernikov
265cfc9cf9bSAlexander V. Chernikov    def _register_vnet(self, vnet_name: str):
266cfc9cf9bSAlexander V. Chernikov        self._vnets.append(vnet_name)
267cfc9cf9bSAlexander V. Chernikov        with open(self.file_name, "a") as f:
268cfc9cf9bSAlexander V. Chernikov            f.write(vnet_name + "\n")
269cfc9cf9bSAlexander V. Chernikov
270cfc9cf9bSAlexander V. Chernikov    @staticmethod
271cfc9cf9bSAlexander V. Chernikov    def _wait_interfaces(vnet_name: str, ifaces: List[str]) -> List[str]:
272a1eb150cSJose Luis Duran        cmd = "/usr/sbin/jexec {} /sbin/ifconfig -l".format(vnet_name)
273cfc9cf9bSAlexander V. Chernikov        not_matched: List[str] = []
274cfc9cf9bSAlexander V. Chernikov        for i in range(50):
275cfc9cf9bSAlexander V. Chernikov            vnet_ifaces = run_cmd(cmd).strip().split(" ")
276cfc9cf9bSAlexander V. Chernikov            not_matched = []
277cfc9cf9bSAlexander V. Chernikov            for iface_name in ifaces:
278cfc9cf9bSAlexander V. Chernikov                if iface_name not in vnet_ifaces:
279cfc9cf9bSAlexander V. Chernikov                    not_matched.append(iface_name)
280cfc9cf9bSAlexander V. Chernikov            if len(not_matched) == 0:
281cfc9cf9bSAlexander V. Chernikov                return []
282cfc9cf9bSAlexander V. Chernikov            time.sleep(0.1)
283cfc9cf9bSAlexander V. Chernikov        return not_matched
284cfc9cf9bSAlexander V. Chernikov
285cfc9cf9bSAlexander V. Chernikov    def create_vnet(self, vnet_alias: str, ifaces: List[VnetInterface]):
286f63825ffSAlexander V. Chernikov        vnet_name = "pytest:{}".format(convert_test_name(self.topology_id))
287cfc9cf9bSAlexander V. Chernikov        if self._vnets:
288cfc9cf9bSAlexander V. Chernikov            # add number to distinguish jails
289cfc9cf9bSAlexander V. Chernikov            vnet_name = "{}_{}".format(vnet_name, len(self._vnets) + 1)
290cfc9cf9bSAlexander V. Chernikov        iface_cmds = " ".join(["vnet.interface={}".format(i.name) for i in ifaces])
291cfc9cf9bSAlexander V. Chernikov        cmd = "/usr/sbin/jail -i -c name={} persist vnet {}".format(
292cfc9cf9bSAlexander V. Chernikov            vnet_name, iface_cmds
293cfc9cf9bSAlexander V. Chernikov        )
294f63825ffSAlexander V. Chernikov        jid = 0
295f63825ffSAlexander V. Chernikov        try:
296cfc9cf9bSAlexander V. Chernikov            jid_str = run_cmd(cmd)
297cfc9cf9bSAlexander V. Chernikov            jid = int(jid_str)
29820ea7f26SAlexander V. Chernikov        except ValueError:
299f63825ffSAlexander V. Chernikov            print("Jail creation failed, output: {}".format(jid_str))
300f63825ffSAlexander V. Chernikov            raise
301cfc9cf9bSAlexander V. Chernikov        self._register_vnet(vnet_name)
302cfc9cf9bSAlexander V. Chernikov
303cfc9cf9bSAlexander V. Chernikov        # Run expedited version of routing
304cfc9cf9bSAlexander V. Chernikov        VnetInterface.setup_loopback(vnet_name)
305cfc9cf9bSAlexander V. Chernikov
306cfc9cf9bSAlexander V. Chernikov        not_found = self._wait_interfaces(vnet_name, [i.name for i in ifaces])
307cfc9cf9bSAlexander V. Chernikov        if not_found:
308cfc9cf9bSAlexander V. Chernikov            raise Exception(
309cfc9cf9bSAlexander V. Chernikov                "Interfaces {} has not appeared in vnet {}".format(not_found, vnet_name)
310cfc9cf9bSAlexander V. Chernikov            )
311cfc9cf9bSAlexander V. Chernikov        return VnetInstance(vnet_alias, vnet_name, jid, ifaces)
312cfc9cf9bSAlexander V. Chernikov
313cfc9cf9bSAlexander V. Chernikov    def cleanup(self):
31420ea7f26SAlexander V. Chernikov        iface_factory = IfaceFactory()
315cfc9cf9bSAlexander V. Chernikov        try:
316cfc9cf9bSAlexander V. Chernikov            with open(self.file_name) as f:
317cfc9cf9bSAlexander V. Chernikov                for line in f:
318f63825ffSAlexander V. Chernikov                    vnet_name = line.strip()
31920ea7f26SAlexander V. Chernikov                    iface_factory.cleanup_vnet_interfaces(vnet_name)
320f63825ffSAlexander V. Chernikov                    run_cmd("/usr/sbin/jail -r  {}".format(vnet_name))
321cfc9cf9bSAlexander V. Chernikov            os.unlink(self.JAILS_FNAME)
322cfc9cf9bSAlexander V. Chernikov        except OSError:
323cfc9cf9bSAlexander V. Chernikov            pass
324cfc9cf9bSAlexander V. Chernikov
325cfc9cf9bSAlexander V. Chernikov
326cfc9cf9bSAlexander V. Chernikovclass SingleInterfaceMap(NamedTuple):
327cfc9cf9bSAlexander V. Chernikov    ifaces: List[VnetInterface]
328cfc9cf9bSAlexander V. Chernikov    vnet_aliases: List[str]
329cfc9cf9bSAlexander V. Chernikov
330cfc9cf9bSAlexander V. Chernikov
331f63825ffSAlexander V. Chernikovclass ObjectsMap(NamedTuple):
332f63825ffSAlexander V. Chernikov    iface_map: Dict[str, SingleInterfaceMap]  # keyed by ifX
333f63825ffSAlexander V. Chernikov    vnet_map: Dict[str, VnetInstance]  # keyed by vnetX
334f63825ffSAlexander V. Chernikov    topo_map: Dict  # self.TOPOLOGY
335f63825ffSAlexander V. Chernikov
336f63825ffSAlexander V. Chernikov
3373873bdc2SAlexander V. Chernikovclass VnetTestTemplate(BaseTest):
3386332ef89SAlexander V. Chernikov    NEED_ROOT: bool = True
339cfc9cf9bSAlexander V. Chernikov    TOPOLOGY = {}
340cfc9cf9bSAlexander V. Chernikov
341ae8d5881SKristof Provost    def _require_default_modules(self):
342ae8d5881SKristof Provost        libc.kldload("if_epair.ko")
343ae8d5881SKristof Provost        self.require_module("if_epair")
344ae8d5881SKristof Provost
345cfc9cf9bSAlexander V. Chernikov    def _get_vnet_handler(self, vnet_alias: str):
346cfc9cf9bSAlexander V. Chernikov        handler_name = "{}_handler".format(vnet_alias)
347cfc9cf9bSAlexander V. Chernikov        return getattr(self, handler_name, None)
348cfc9cf9bSAlexander V. Chernikov
349cfc9cf9bSAlexander V. Chernikov    def _setup_vnet(self, vnet: VnetInstance, obj_map: Dict, pipe):
350cfc9cf9bSAlexander V. Chernikov        """Base Handler to setup given VNET.
351cfc9cf9bSAlexander V. Chernikov        Can be run in a subprocess. If so, passes control to the special
352cfc9cf9bSAlexander V. Chernikov        vnetX_handler() after setting up interface addresses
353cfc9cf9bSAlexander V. Chernikov        """
354cfc9cf9bSAlexander V. Chernikov        vnet.attach()
355cfc9cf9bSAlexander V. Chernikov        print("# setup_vnet({})".format(vnet.name))
356f63825ffSAlexander V. Chernikov        if pipe is not None:
357f63825ffSAlexander V. Chernikov            vnet.set_pipe(pipe)
358cfc9cf9bSAlexander V. Chernikov
359f63825ffSAlexander V. Chernikov        topo = obj_map.topo_map
360cfc9cf9bSAlexander V. Chernikov        ipv6_ifaces = []
361cfc9cf9bSAlexander V. Chernikov        # Disable DAD
362cfc9cf9bSAlexander V. Chernikov        if not vnet.need_dad:
363cfc9cf9bSAlexander V. Chernikov            vnet.disable_dad()
364cfc9cf9bSAlexander V. Chernikov        for iface in vnet.ifaces:
365cfc9cf9bSAlexander V. Chernikov            # check index of vnet within an interface
366cfc9cf9bSAlexander V. Chernikov            # as we have prefixes for both ends of the interface
367f63825ffSAlexander V. Chernikov            iface_map = obj_map.iface_map[iface.alias]
368cfc9cf9bSAlexander V. Chernikov            idx = iface_map.vnet_aliases.index(vnet.alias)
369cfc9cf9bSAlexander V. Chernikov            prefixes6 = topo[iface.alias].get("prefixes6", [])
370cfc9cf9bSAlexander V. Chernikov            prefixes4 = topo[iface.alias].get("prefixes4", [])
371cfc9cf9bSAlexander V. Chernikov            if prefixes6 or prefixes4:
372cfc9cf9bSAlexander V. Chernikov                ipv6_ifaces.append(iface)
373cfc9cf9bSAlexander V. Chernikov                iface.turn_up()
374cfc9cf9bSAlexander V. Chernikov                if prefixes6:
375cfc9cf9bSAlexander V. Chernikov                    iface.enable_ipv6()
376cfc9cf9bSAlexander V. Chernikov            for prefix in prefixes6 + prefixes4:
377584ad412SAlexander V. Chernikov                if prefix[idx]:
378cfc9cf9bSAlexander V. Chernikov                    iface.setup_addr(prefix[idx])
379cfc9cf9bSAlexander V. Chernikov        for iface in ipv6_ifaces:
380cfc9cf9bSAlexander V. Chernikov            while iface.has_tentative():
381cfc9cf9bSAlexander V. Chernikov                time.sleep(0.1)
382cfc9cf9bSAlexander V. Chernikov
383cfc9cf9bSAlexander V. Chernikov        # Run actual handler
384cfc9cf9bSAlexander V. Chernikov        handler = self._get_vnet_handler(vnet.alias)
385cfc9cf9bSAlexander V. Chernikov        if handler:
386cfc9cf9bSAlexander V. Chernikov            # Do unbuffered stdout for children
387cfc9cf9bSAlexander V. Chernikov            # so the logs are present if the child hangs
388cfc9cf9bSAlexander V. Chernikov            sys.stdout.reconfigure(line_buffering=True)
3896332ef89SAlexander V. Chernikov            self.drop_privileges()
390f63825ffSAlexander V. Chernikov            handler(vnet)
391cfc9cf9bSAlexander V. Chernikov
392584ad412SAlexander V. Chernikov    def _get_topo_ifmap(self, topo: Dict):
393584ad412SAlexander V. Chernikov        iface_factory = IfaceFactory()
394584ad412SAlexander V. Chernikov        iface_map: Dict[str, SingleInterfaceMap] = {}
395584ad412SAlexander V. Chernikov        iface_aliases = set()
396584ad412SAlexander V. Chernikov        for obj_name, obj_data in topo.items():
397584ad412SAlexander V. Chernikov            if obj_name.startswith("vnet"):
398584ad412SAlexander V. Chernikov                for iface_alias in obj_data["ifaces"]:
399584ad412SAlexander V. Chernikov                    iface_aliases.add(iface_alias)
400584ad412SAlexander V. Chernikov        for iface_alias in iface_aliases:
401584ad412SAlexander V. Chernikov            print("Creating {}".format(iface_alias))
402584ad412SAlexander V. Chernikov            iface_data = topo[iface_alias]
403584ad412SAlexander V. Chernikov            iface_type = iface_data.get("type", "epair")
404584ad412SAlexander V. Chernikov            ifaces = iface_factory.create_iface(iface_alias, iface_type)
405584ad412SAlexander V. Chernikov            smap = SingleInterfaceMap(ifaces, [])
406584ad412SAlexander V. Chernikov            iface_map[iface_alias] = smap
407584ad412SAlexander V. Chernikov        return iface_map
408584ad412SAlexander V. Chernikov
409f63825ffSAlexander V. Chernikov    def setup_topology(self, topo: Dict, topology_id: str):
410cfc9cf9bSAlexander V. Chernikov        """Creates jails & interfaces for the provided topology"""
411cfc9cf9bSAlexander V. Chernikov        vnet_map = {}
412f63825ffSAlexander V. Chernikov        vnet_factory = VnetFactory(topology_id)
413584ad412SAlexander V. Chernikov        iface_map = self._get_topo_ifmap(topo)
414cfc9cf9bSAlexander V. Chernikov        for obj_name, obj_data in topo.items():
415cfc9cf9bSAlexander V. Chernikov            if obj_name.startswith("vnet"):
416cfc9cf9bSAlexander V. Chernikov                vnet_ifaces = []
417cfc9cf9bSAlexander V. Chernikov                for iface_alias in obj_data["ifaces"]:
418cfc9cf9bSAlexander V. Chernikov                    # epair creates 2 interfaces, grab first _available_
419cfc9cf9bSAlexander V. Chernikov                    # and map it to the VNET being created
420cfc9cf9bSAlexander V. Chernikov                    idx = len(iface_map[iface_alias].vnet_aliases)
421cfc9cf9bSAlexander V. Chernikov                    iface_map[iface_alias].vnet_aliases.append(obj_name)
422cfc9cf9bSAlexander V. Chernikov                    vnet_ifaces.append(iface_map[iface_alias].ifaces[idx])
423cfc9cf9bSAlexander V. Chernikov                vnet = vnet_factory.create_vnet(obj_name, vnet_ifaces)
424cfc9cf9bSAlexander V. Chernikov                vnet_map[obj_name] = vnet
425584ad412SAlexander V. Chernikov                # Allow reference to VNETs as attributes
426584ad412SAlexander V. Chernikov                setattr(self, obj_name, vnet)
427cfc9cf9bSAlexander V. Chernikov        # Debug output
428cfc9cf9bSAlexander V. Chernikov        print("============= TEST TOPOLOGY =============")
429cfc9cf9bSAlexander V. Chernikov        for vnet_alias, vnet in vnet_map.items():
430cfc9cf9bSAlexander V. Chernikov            print("# vnet {} -> {}".format(vnet.alias, vnet.name), end="")
431cfc9cf9bSAlexander V. Chernikov            handler = self._get_vnet_handler(vnet.alias)
432cfc9cf9bSAlexander V. Chernikov            if handler:
433cfc9cf9bSAlexander V. Chernikov                print(" handler: {}".format(handler.__name__), end="")
434cfc9cf9bSAlexander V. Chernikov            print()
435cfc9cf9bSAlexander V. Chernikov        for iface_alias, iface_data in iface_map.items():
436cfc9cf9bSAlexander V. Chernikov            vnets = iface_data.vnet_aliases
437cfc9cf9bSAlexander V. Chernikov            ifaces: List[VnetInterface] = iface_data.ifaces
438cfc9cf9bSAlexander V. Chernikov            if len(vnets) == 1 and len(ifaces) == 2:
439cfc9cf9bSAlexander V. Chernikov                print(
440cfc9cf9bSAlexander V. Chernikov                    "# iface {}: {}::{} -> main::{}".format(
441cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets[0], ifaces[0].name, ifaces[1].name
442cfc9cf9bSAlexander V. Chernikov                    )
443cfc9cf9bSAlexander V. Chernikov                )
444cfc9cf9bSAlexander V. Chernikov            elif len(vnets) == 2 and len(ifaces) == 2:
445cfc9cf9bSAlexander V. Chernikov                print(
446cfc9cf9bSAlexander V. Chernikov                    "# iface {}: {}::{} -> {}::{}".format(
447cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets[0], ifaces[0].name, vnets[1], ifaces[1].name
448cfc9cf9bSAlexander V. Chernikov                    )
449cfc9cf9bSAlexander V. Chernikov                )
450cfc9cf9bSAlexander V. Chernikov            else:
451cfc9cf9bSAlexander V. Chernikov                print(
452cfc9cf9bSAlexander V. Chernikov                    "# iface {}: ifaces: {} vnets: {}".format(
453cfc9cf9bSAlexander V. Chernikov                        iface_alias, vnets, [i.name for i in ifaces]
454cfc9cf9bSAlexander V. Chernikov                    )
455cfc9cf9bSAlexander V. Chernikov                )
456cfc9cf9bSAlexander V. Chernikov        print()
457f63825ffSAlexander V. Chernikov        return ObjectsMap(iface_map, vnet_map, topo)
458cfc9cf9bSAlexander V. Chernikov
459f63825ffSAlexander V. Chernikov    def setup_method(self, _method):
460cfc9cf9bSAlexander V. Chernikov        """Sets up all the required topology and handlers for the given test"""
461f63825ffSAlexander V. Chernikov        super().setup_method(_method)
462ae8d5881SKristof Provost        self._require_default_modules()
463ae8d5881SKristof Provost
464f63825ffSAlexander V. Chernikov        # TestIP6Output.test_output6_pktinfo[ipandif]
465f63825ffSAlexander V. Chernikov        topology_id = get_topology_id(self.test_id)
466cfc9cf9bSAlexander V. Chernikov        topology = self.TOPOLOGY
467cfc9cf9bSAlexander V. Chernikov        # First, setup kernel objects - interfaces & vnets
468f63825ffSAlexander V. Chernikov        obj_map = self.setup_topology(topology, topology_id)
469cfc9cf9bSAlexander V. Chernikov        main_vnet = None  # one without subprocess handler
470f63825ffSAlexander V. Chernikov        for vnet_alias, vnet in obj_map.vnet_map.items():
471cfc9cf9bSAlexander V. Chernikov            if self._get_vnet_handler(vnet_alias):
472cfc9cf9bSAlexander V. Chernikov                # Need subprocess to run
473cfc9cf9bSAlexander V. Chernikov                parent_pipe, child_pipe = Pipe()
474cfc9cf9bSAlexander V. Chernikov                p = Process(
475cfc9cf9bSAlexander V. Chernikov                    target=self._setup_vnet,
476cfc9cf9bSAlexander V. Chernikov                    args=(
477cfc9cf9bSAlexander V. Chernikov                        vnet,
478cfc9cf9bSAlexander V. Chernikov                        obj_map,
479cfc9cf9bSAlexander V. Chernikov                        child_pipe,
480cfc9cf9bSAlexander V. Chernikov                    ),
481cfc9cf9bSAlexander V. Chernikov                )
482cfc9cf9bSAlexander V. Chernikov                vnet.set_pipe(parent_pipe)
483cfc9cf9bSAlexander V. Chernikov                vnet.set_subprocess(p)
484cfc9cf9bSAlexander V. Chernikov                p.start()
485cfc9cf9bSAlexander V. Chernikov            else:
486cfc9cf9bSAlexander V. Chernikov                if main_vnet is not None:
487cfc9cf9bSAlexander V. Chernikov                    raise Exception("there can be only 1 VNET w/o handler")
488cfc9cf9bSAlexander V. Chernikov                main_vnet = vnet
489cfc9cf9bSAlexander V. Chernikov        # Main vnet needs to be the last, so all the other subprocesses
490cfc9cf9bSAlexander V. Chernikov        # are started & their pipe handles collected
491cfc9cf9bSAlexander V. Chernikov        self.vnet = main_vnet
492cfc9cf9bSAlexander V. Chernikov        self._setup_vnet(main_vnet, obj_map, None)
493cfc9cf9bSAlexander V. Chernikov        # Save state for the main handler
494f63825ffSAlexander V. Chernikov        self.iface_map = obj_map.iface_map
495f63825ffSAlexander V. Chernikov        self.vnet_map = obj_map.vnet_map
4966332ef89SAlexander V. Chernikov        self.drop_privileges()
497cfc9cf9bSAlexander V. Chernikov
498cfc9cf9bSAlexander V. Chernikov    def cleanup(self, test_id: str):
499cfc9cf9bSAlexander V. Chernikov        # pytest test id: file::class::test_name
500f63825ffSAlexander V. Chernikov        topology_id = get_topology_id(self.test_id)
501cfc9cf9bSAlexander V. Chernikov
502d4a5d495SJose Luis Duran        print("============= vnet cleanup =============")
503f63825ffSAlexander V. Chernikov        print("# topology_id: '{}'".format(topology_id))
504f63825ffSAlexander V. Chernikov        VnetFactory(topology_id).cleanup()
505f63825ffSAlexander V. Chernikov        IfaceFactory().cleanup()
506cfc9cf9bSAlexander V. Chernikov
507cfc9cf9bSAlexander V. Chernikov    def wait_object(self, pipe, timeout=5):
508cfc9cf9bSAlexander V. Chernikov        if pipe.poll(timeout):
509cfc9cf9bSAlexander V. Chernikov            return pipe.recv()
510cfc9cf9bSAlexander V. Chernikov        raise TimeoutError
511cfc9cf9bSAlexander V. Chernikov
512584ad412SAlexander V. Chernikov    def wait_objects_any(self, pipe_list, timeout=5):
513584ad412SAlexander V. Chernikov        objects = connection.wait(pipe_list, timeout)
514584ad412SAlexander V. Chernikov        if objects:
515584ad412SAlexander V. Chernikov            return objects[0].recv()
516584ad412SAlexander V. Chernikov        raise TimeoutError
517584ad412SAlexander V. Chernikov
518f63825ffSAlexander V. Chernikov    def send_object(self, pipe, obj):
519f63825ffSAlexander V. Chernikov        pipe.send(obj)
520f63825ffSAlexander V. Chernikov
521584ad412SAlexander V. Chernikov    def wait(self):
522584ad412SAlexander V. Chernikov        while True:
523584ad412SAlexander V. Chernikov            time.sleep(1)
524584ad412SAlexander V. Chernikov
525cfc9cf9bSAlexander V. Chernikov    @property
526cfc9cf9bSAlexander V. Chernikov    def curvnet(self):
527cfc9cf9bSAlexander V. Chernikov        pass
528cfc9cf9bSAlexander V. Chernikov
529cfc9cf9bSAlexander V. Chernikov
530cfc9cf9bSAlexander V. Chernikovclass SingleVnetTestTemplate(VnetTestTemplate):
5318eb2bee6SAlexander V. Chernikov    IPV6_PREFIXES: List[str] = []
5328eb2bee6SAlexander V. Chernikov    IPV4_PREFIXES: List[str] = []
533f3065e76SAlexander V. Chernikov    IFTYPE = "epair"
5348eb2bee6SAlexander V. Chernikov
535f3065e76SAlexander V. Chernikov    def _setup_default_topology(self):
536cfc9cf9bSAlexander V. Chernikov        topology = copy.deepcopy(
537cfc9cf9bSAlexander V. Chernikov            {
538cfc9cf9bSAlexander V. Chernikov                "vnet1": {"ifaces": ["if1"]},
539f3065e76SAlexander V. Chernikov                "if1": {"type": self.IFTYPE, "prefixes4": [], "prefixes6": []},
540cfc9cf9bSAlexander V. Chernikov            }
541cfc9cf9bSAlexander V. Chernikov        )
542cfc9cf9bSAlexander V. Chernikov        for prefix in self.IPV6_PREFIXES:
543cfc9cf9bSAlexander V. Chernikov            topology["if1"]["prefixes6"].append((prefix,))
544cfc9cf9bSAlexander V. Chernikov        for prefix in self.IPV4_PREFIXES:
545cfc9cf9bSAlexander V. Chernikov            topology["if1"]["prefixes4"].append((prefix,))
546f3065e76SAlexander V. Chernikov        return topology
547f3065e76SAlexander V. Chernikov
548f3065e76SAlexander V. Chernikov    def setup_method(self, method):
549f3065e76SAlexander V. Chernikov        if not getattr(self, "TOPOLOGY", None):
550f3065e76SAlexander V. Chernikov            self.TOPOLOGY = self._setup_default_topology()
551f3065e76SAlexander V. Chernikov        else:
552f3065e76SAlexander V. Chernikov            names = self.TOPOLOGY.keys()
553f3065e76SAlexander V. Chernikov            assert len([n for n in names if n.startswith("vnet")]) == 1
554cfc9cf9bSAlexander V. Chernikov        super().setup_method(method)
555