xref: /freebsd/tests/sys/netinet/ip_mroute.py (revision 63a40b65c9be74193bb07a76fd66c249bd562eae)
1#
2# Copyright (c) 2025 Stormshield
3#
4# SPDX-License-Identifier: BSD-2-Clause
5#
6
7import pytest
8import socket
9import struct
10import subprocess
11import time
12from pathlib import Path
13
14from atf_python.sys.net.vnet import VnetTestTemplate
15
16
17class MRouteTestTemplate(VnetTestTemplate):
18    """
19    Helper class for multicast routing tests.  Test classes should inherit from this one.
20    """
21    COORD_SOCK = "coord.sock"
22
23    @staticmethod
24    def _msgwait(sock: socket.socket, expected: bytes, timeout=None):
25        if timeout is not None:
26            sock.settimeout(timeout)
27        msg = sock.recv(1024)
28        assert msg == expected
29
30    @staticmethod
31    def sendmsg(msg: bytes, path: str):
32        s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
33        s.sendto(msg, path)
34        s.close()
35
36    @staticmethod
37    def _makesock(path: str):
38        s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
39        s.bind(path)
40        return s
41
42    @staticmethod
43    def mcast_join_INET6(addr: str, port: int):
44        pass
45
46    def jointest(self, vnet):
47        """Let the coordinator know that we're ready, and wait for go-ahead."""
48        coord = self._makesock(vnet.alias + ".sock")
49        self.sendmsg(b"ok " + vnet.alias.encode(), self.COORD_SOCK)
50        self._msgwait(coord, b"join")
51
52    def donetest(self):
53        """Let the coordinator that we completed successfully."""
54        self.sendmsg(b"done", self.COORD_SOCK)
55
56    def starttest(self, vnets: list[str]):
57        self.vnets = vnets
58        for vnet in vnets:
59            self.sendmsg(b"join", vnet + ".sock")
60
61    def waittest(self):
62        for vnet in self.vnets:
63            self._msgwait(self.coord, b"done")
64
65    def setup_method(self, method):
66        self.coord = self._makesock(self.COORD_SOCK)
67        super().setup_method(method)
68
69        # Loop until all other hosts have sent the ok message.
70        received = set()
71        vnet_names = set(self.vnet_map.keys()) - {self.vnet.alias}
72        while len(received) < len(vnet_names):
73            msg = self.coord.recv(1024)
74            received.add(msg)
75        assert received == {b"ok " + name.encode() for name in vnet_names}
76
77
78class MRouteINETTestTemplate(MRouteTestTemplate):
79    @staticmethod
80    def run_pimd(ident: str, ifaces: list[str], rpaddr: str, group: str, fib=0):
81        conf = f"pimd-{ident}.conf"
82        with open(conf, "w") as conf_file:
83            conf_file.write("no phyint\n")
84            for iface in ifaces:
85                conf_file.write(f"phyint {iface} enable\n")
86            conf_file.write(f"rp-address {rpaddr} {group}\n")
87
88        cmd = f"setfib {fib} pimd -i {ident} -f {conf} -p pimd-{ident}.pid -n"
89        return subprocess.Popen(cmd.split(), stdout=subprocess.DEVNULL,
90                                stderr=subprocess.DEVNULL)
91
92    @staticmethod
93    def mcast_join(addr: str, port: int):
94        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
95        mreq = struct.pack("4si", socket.inet_aton(addr), socket.INADDR_ANY)
96        s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
97        s.bind((addr, port))
98        time.sleep(1)  # Give the kernel a bit of time to join the group.
99        return s
100
101    @staticmethod
102    def mcast_sendto(addr: str, port: int, iface: str, msg: bytes):
103        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
104        mreqn = struct.pack("iii", socket.INADDR_ANY, socket.INADDR_ANY,
105                            socket.if_nametoindex(iface))
106        s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, mreqn)
107        s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 64)
108        s.sendto(msg, (addr, port))
109        s.close()
110
111    def setup_method(self, method):
112        self.require_module("ip_mroute")
113        super().setup_method(method)
114
115
116class MRouteINET6TestTemplate(MRouteTestTemplate):
117    @staticmethod
118    def run_ip6_mrouted(ident: str, ifaces: list[str], fib=0):
119        ifaces_str = ' '.join(f"-i {iface}" for iface in ifaces)
120        exepath = Path(__file__).parent / "ip6_mrouted"
121        cmd = f"setfib {fib} {exepath} {ifaces_str}"
122        return subprocess.Popen(cmd.split(), stdout=subprocess.DEVNULL,
123                                stderr=subprocess.DEVNULL)
124
125    @staticmethod
126    def mcast_join(addr: str, port: int, iface: str):
127        s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
128        mreq = struct.pack("16si", socket.inet_pton(socket.AF_INET6, addr),
129                            socket.if_nametoindex(iface))
130        s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
131        s.bind((addr, port))
132        time.sleep(1)  # Give the kernel a bit of time to join the
133        return s
134
135    @staticmethod
136    def mcast_sendto(addr: str, port: int, iface: str, msg: bytes):
137        s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
138        mreq = struct.pack("i", socket.if_nametoindex(iface))
139        s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, mreq)
140        s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 64)
141        s.sendto(msg, (addr, port))
142        s.close()
143
144    def setup_method(self, method):
145        self.require_module("ip6_mroute")
146        super().setup_method(method)
147
148
149class Test1RBasicINET(MRouteINETTestTemplate):
150    """Basic multicast routing setup with 2 hosts connected via a router."""
151
152    TOPOLOGY = {
153        "vnet_router": {"ifaces": ["if1", "if2"]},
154        "vnet_host1": {"ifaces": ["if1"]},
155        "vnet_host2": {"ifaces": ["if2"]},
156        "if1": {"prefixes4": [("192.168.1.1/24", "192.168.1.2/24")]},
157        "if2": {"prefixes4": [("192.168.2.1/24", "192.168.2.2/24")]},
158    }
159    MULTICAST_ADDR = "239.0.0.1"
160
161    def setup_method(self, method):
162        # Create VNETs and start the handlers.
163        super().setup_method(method)
164
165        ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
166        self.pimd = self.run_pimd("test", ifaces, "127.0.0.1", self.MULTICAST_ADDR + "/32")
167        time.sleep(3)  # Give pimd a bit of time to get itself together.
168
169    def vnet_host1_handler(self, vnet):
170        self.jointest(vnet)
171
172        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
173
174        # Wait for host 2 to send a message, then send a reply.
175        self._msgwait(self.sock, b"Hello, Multicast!")
176        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
177                          b"Goodbye, Multicast!")
178        self._msgwait(self.sock, b"Goodbye, Multicast!")
179        self.donetest()
180
181    def vnet_host2_handler(self, vnet):
182        self.jointest(vnet)
183
184        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
185
186        # Send a message to host 1, then wait for a reply.
187        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
188                          b"Hello, Multicast!")
189        self._msgwait(self.sock, b"Hello, Multicast!")
190        self._msgwait(self.sock, b"Goodbye, Multicast!")
191        self.donetest()
192
193    @pytest.mark.require_user("root")
194    @pytest.mark.require_progs(["pimd"])
195    @pytest.mark.timeout(30)
196    def test(self):
197        self.starttest(["vnet_host1", "vnet_host2"])
198        self.waittest()
199
200
201class MRouteINETCrissCrossTestTemplate(MRouteINETTestTemplate):
202    TOPOLOGY = {
203        "vnet_router": {"ifaces": ["if1", "if2", "if3", "if4"]},
204        "vnet_host1": {"ifaces": ["if1"]},
205        "vnet_host2": {"ifaces": ["if2"]},
206        "vnet_host3": {"ifaces": ["if3"]},
207        "vnet_host4": {"ifaces": ["if4"]},
208        "if1": {
209            "prefixes4": [("192.168.1.1/24", "192.168.1.2/24")],
210            "prefixes6": [],
211            "fib": (0, 0),
212        },
213        "if2": {
214            "prefixes4": [("192.168.2.1/24", "192.168.2.2/24")],
215            "prefixes6": [],
216            "fib": (0, 0),
217        },
218        "if3": {
219            "prefixes4": [("192.168.3.1/24", "192.168.3.2/24")],
220            "prefixes6": [],
221            "fib": (1, 0),
222        },
223        "if4": {
224            "prefixes4": [("192.168.4.1/24", "192.168.4.2/24")],
225            "prefixes6": [],
226            "fib": (1, 0),
227        },
228    }
229    MULTICAST_ADDR = "239.0.0.1"
230
231
232
233class Test1RCrissCrossINET(MRouteINETCrissCrossTestTemplate):
234    """
235    Test a router connected to four hosts, with pairs of interfaces
236    in different FIBs.
237    """
238
239    def setup_method(self, method):
240        # Create VNETs and start the handlers.
241        super().setup_method(method)
242
243        # Start a pimd instance per FIB.
244        ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
245        self.pimd0 = self.run_pimd("test0", ifaces, "127.0.0.1", self.MULTICAST_ADDR + "/32",
246                                   fib=0)
247        ifaces = [self.vnet.iface_alias_map[i].name for i in ["if3", "if4"]]
248        self.pimd1 = self.run_pimd("test1", ifaces, "127.0.0.1", self.MULTICAST_ADDR + "/32",
249                                   fib=1)
250        time.sleep(3)  # Give pimd a bit of time to get itself together.
251
252    def vnet_host1_handler(self, vnet):
253        self.jointest(vnet)
254
255        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
256        self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
257        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
258                          b"Goodbye, Multicast on FIB 0!")
259        self.donetest()
260
261    def vnet_host2_handler(self, vnet):
262        self.jointest(vnet)
263        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
264        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
265                          b"Hello, Multicast on FIB 0!")
266        self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
267        self._msgwait(self.sock, b"Goodbye, Multicast on FIB 0!")
268        self.donetest()
269
270    def vnet_host3_handler(self, vnet):
271        self.jointest(vnet)
272        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
273        self._msgwait(self.sock, b"Hello, Multicast on FIB 1!")
274        self.mcast_sendto(self.MULTICAST_ADDR, 12345,
275                          vnet.ifaces[0].name, b"Goodbye, Multicast on FIB 1!")
276        self.donetest()
277
278    def vnet_host4_handler(self, vnet):
279        self.jointest(vnet)
280        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
281        time.sleep(1)
282        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
283                          b"Hello, Multicast on FIB 1!")
284        self._msgwait(self.sock, b"Hello, Multicast on FIB 1!")
285        self._msgwait(self.sock, b"Goodbye, Multicast on FIB 1!")
286        self.donetest()
287
288    @pytest.mark.require_user("root")
289    @pytest.mark.require_progs(["pimd"])
290    @pytest.mark.timeout(30)
291    def test(self):
292        self.starttest(["vnet_host1", "vnet_host2", "vnet_host3", "vnet_host4"])
293        self.waittest()
294
295
296class Test1RCrissCrossINETMissingRouter(MRouteINETCrissCrossTestTemplate):
297    """
298    Test what happens when a router is configured for some FIBs but not others.
299    """
300
301    def setup_method(self, method):
302        # Create VNETs and start the handlers.
303        super().setup_method(method)
304
305        # Only start a pimd instance in FIB 0.
306        ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
307        self.pimd0 = self.run_pimd("test0", ifaces, "127.0.0.1",
308                                   self.MULTICAST_ADDR + "/32", fib=0)
309
310        time.sleep(3)  # Give pimd a bit of time to get itself together.
311
312    def vnet_host1_handler(self, vnet):
313        self.jointest(vnet)
314
315        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
316        self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
317        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
318                          b"Goodbye, Multicast on FIB 0!")
319        self.donetest()
320
321    def vnet_host2_handler(self, vnet):
322        self.jointest(vnet)
323        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
324        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
325                          b"Hello, Multicast on FIB 0!")
326        self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
327        self._msgwait(self.sock, b"Goodbye, Multicast on FIB 0!")
328        self.donetest()
329
330    def vnet_host3_handler(self, vnet):
331        self.jointest(vnet)
332        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
333        timedout = False
334        try:
335            self._msgwait(self.sock, b"Hello, Multicast on FIB 1!", timeout=5)
336        except socket.timeout:
337            timedout = True
338        assert timedout, "Received a message when we shouldn't have"
339        self.donetest()
340
341    def vnet_host4_handler(self, vnet):
342        self.jointest(vnet)
343        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
344        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
345                          b"Hello, Multicast on FIB 1!")
346        self.donetest()
347
348    @pytest.mark.require_user("root")
349    @pytest.mark.require_progs(["pimd"])
350    @pytest.mark.timeout(30)
351    def test(self):
352        self.starttest(["vnet_host1", "vnet_host2", "vnet_host3", "vnet_host4"])
353        self.waittest()
354
355class Test1RBasicINET6(MRouteINET6TestTemplate):
356    """Basic multicast routing setup with 2 hosts connected via a router."""
357
358    TOPOLOGY = {
359        "vnet_router": {"ifaces": ["if1", "if2"]},
360        "vnet_host1": {"ifaces": ["if1"]},
361        "vnet_host2": {"ifaces": ["if2"]},
362        "if1": {
363            "prefixes6": [("2001:db8:0:1::1/64", "2001:db8:0:1::2/64")]
364        },
365        "if2": {
366            "prefixes6": [("2001:db8:0:2::1/64", "2001:db8:0:2::2/64")]
367        },
368    }
369    MULTICAST_ADDR = "ff05::1"
370
371    def setup_method(self, method):
372        # Create VNETs and start the handlers.
373        super().setup_method(method)
374
375        ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
376        self.mrouted = self.run_ip6_mrouted("test", ifaces)
377        time.sleep(1)
378
379    def vnet_host1_handler(self, vnet):
380        self.jointest(vnet)
381
382        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
383
384        # Wait for host 2 to send a message, then send a reply.
385        self._msgwait(self.sock, b"Hello, Multicast!")
386        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
387                          b"Goodbye, Multicast!")
388        self._msgwait(self.sock, b"Goodbye, Multicast!")
389        self.donetest()
390
391    def vnet_host2_handler(self, vnet):
392        self.jointest(vnet)
393
394        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
395
396        # Send a message to host 1, then wait for a reply.
397        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
398                          b"Hello, Multicast!")
399        self._msgwait(self.sock, b"Hello, Multicast!")
400        self._msgwait(self.sock, b"Goodbye, Multicast!")
401        self.donetest()
402
403    @pytest.mark.require_user("root")
404    @pytest.mark.timeout(30)
405    def test(self):
406        self.starttest(["vnet_host1", "vnet_host2"])
407        self.waittest()
408
409
410class MRouteINET6CrissCrossTestTemplate(MRouteINET6TestTemplate):
411    TOPOLOGY = {
412        "vnet_router": {"ifaces": ["if1", "if2", "if3", "if4"]},
413        "vnet_host1": {"ifaces": ["if1"]},
414        "vnet_host2": {"ifaces": ["if2"]},
415        "vnet_host3": {"ifaces": ["if3"]},
416        "vnet_host4": {"ifaces": ["if4"]},
417        "if1": {
418            "prefixes6": [("2001:db8:0:1::1/64", "2001:db8:0:1::2/64")],
419            "fib": (0, 0),
420        },
421        "if2": {
422            "prefixes6": [("2001:db8:0:2::1/64", "2001:db8:0:2::2/64")],
423            "fib": (0, 0),
424        },
425        "if3": {
426            "prefixes6": [("2001:db8:0:3::1/64", "2001:db8:0:3::2/64")],
427            "fib": (1, 0),
428        },
429        "if4": {
430            "prefixes6": [("2001:db8:0:4::1/64", "2001:db8:0:4::2/64")],
431            "fib": (1, 0),
432        },
433    }
434    MULTICAST_ADDR = "ff05::1"
435
436
437class Test1RCrissCrossINET6MissingRouter(MRouteINET6CrissCrossTestTemplate):
438    """
439    Test what happens when a router is configured for some FIBs but not others.
440    """
441
442    def setup_method(self, method):
443        # Create VNETs and start the handlers.
444        super().setup_method(method)
445
446        # Only start an ip6_mrouted instance in FIB 0.
447        ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
448        self.mrouted0 = self.run_ip6_mrouted("test0", ifaces, fib=0)
449        time.sleep(1)  # Give ip6_mrouted a bit of time to get itself together.
450
451    def vnet_host1_handler(self, vnet):
452        self.jointest(vnet)
453
454        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
455        self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
456        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
457                          b"Goodbye, Multicast on FIB 0!")
458        self.donetest()
459
460    def vnet_host2_handler(self, vnet):
461        self.jointest(vnet)
462        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
463        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
464                          b"Hello, Multicast on FIB 0!")
465        self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
466        self._msgwait(self.sock, b"Goodbye, Multicast on FIB 0!")
467        self.donetest()
468
469    def vnet_host3_handler(self, vnet):
470        self.jointest(vnet)
471
472        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345,
473                                    vnet.ifaces[0].name)
474        timedout = False
475        try:
476            self._msgwait(self.sock, b"Hello, Multicast on FIB 1!", timeout=5)
477        except socket.timeout:
478            timedout = True
479        assert timedout, "Received a message when we shouldn't have"
480        self.donetest()
481
482    def vnet_host4_handler(self, vnet):
483        self.jointest(vnet)
484
485        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345,
486                                    vnet.ifaces[0].name)
487        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
488                          b"Hello, Multicast on FIB 1!")
489        self.donetest()
490
491    @pytest.mark.require_user("root")
492    @pytest.mark.timeout(30)
493    def test(self):
494        self.starttest(["vnet_host1", "vnet_host2", "vnet_host3", "vnet_host4"])
495        self.waittest()
496
497
498class Test1RCrissCrossINET6(MRouteINET6CrissCrossTestTemplate):
499    """
500    Test a router connected to four hosts, with pairs of interfaces
501    in different FIBs.
502    """
503
504    def setup_method(self, method):
505        # Create VNETs and start the handlers.
506        super().setup_method(method)
507
508        # Start an ip6_mrouted instance per FIB.
509        ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
510        self.pimd0 = self.run_ip6_mrouted("test0", ifaces, fib=0)
511        ifaces = [self.vnet.iface_alias_map[i].name for i in ["if3", "if4"]]
512        self.pimd1 = self.run_ip6_mrouted("test1", ifaces, fib=1)
513        time.sleep(1)  # Give ip6_mrouted a bit of time to get itself together.
514
515    def vnet_host1_handler(self, vnet):
516        self.jointest(vnet)
517
518        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
519        self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
520        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
521                          b"Goodbye, Multicast on FIB 0!")
522        self.donetest()
523
524    def vnet_host2_handler(self, vnet):
525        self.jointest(vnet)
526        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
527        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
528                          b"Hello, Multicast on FIB 0!")
529        self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
530        self._msgwait(self.sock, b"Goodbye, Multicast on FIB 0!")
531        self.donetest()
532
533    def vnet_host3_handler(self, vnet):
534        self.jointest(vnet)
535        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
536        self._msgwait(self.sock, b"Hello, Multicast on FIB 1!")
537        self.mcast_sendto(self.MULTICAST_ADDR, 12345,
538                          vnet.ifaces[0].name, b"Goodbye, Multicast on FIB 1!")
539        self.donetest()
540
541    def vnet_host4_handler(self, vnet):
542        self.jointest(vnet)
543        self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
544        time.sleep(1)
545        self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
546                          b"Hello, Multicast on FIB 1!")
547        self._msgwait(self.sock, b"Hello, Multicast on FIB 1!")
548        self._msgwait(self.sock, b"Goodbye, Multicast on FIB 1!")
549        self.donetest()
550
551    @pytest.mark.require_user("root")
552    @pytest.mark.timeout(30)
553    def test(self):
554        self.starttest(["vnet_host1", "vnet_host2", "vnet_host3", "vnet_host4"])
555        self.waittest()
556