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