tests: Add some simple regression tests for ip_mroute

These use atf_python to create a number of hosts linked to a router.
The router runs pimd (for IPv4) or ip6_mrouter (simplistic IPv6
multicast router that I wrote since I couldn't find one in the ports
tree).  The vnet_host*_handler() methods are invoked in different VNET
jails connected to the router; they register the connected epair with a
multicast group and verify that they can send messages to each other.

The tests are synchronized by sending messages over a unix domain
socket.  The flow is something like:
1) test startup_method() is called, the unix socket is created,
2) the superclass creates jails and links them together using the
   declared topology,
3) we wait for all child jails to start up and send a message on the
   unix socket indicating that they are ready
4) we start the routing daemon in the main jail,
5) the test actually starts; starttest() kicks off the
   vnet_host*_handlers(), which mostly just verify that they can send
   messages to each other using multicast packets
6) once they finish running, they signal their completion, and
   waittest() returns once they're all done

There are two tests, repeated for v4 and v6.  One just exchanges packets
between two hosts, and the other has four hosts divided across two FIBs.

MFC after:	2 weeks
Sponsored by:	Stormshield
Sponsored by:	Klara, Inc.
Differential Revision:	https://reviews.freebsd.org/D55244
This commit is contained in:
Mark Johnston
2026-03-30 13:32:35 +00:00
parent d4339cebbe
commit 09e702ad40
4 changed files with 629 additions and 4 deletions
+9 -4
View File
@@ -29,9 +29,10 @@ ATF_TESTS_SH= arp \
output \
redirect
ATF_TESTS_PYTEST+= carp.py
ATF_TESTS_PYTEST+= igmp.py
ATF_TESTS_PYTEST+= tcp_hpts_test.py
ATF_TESTS_PYTEST+= carp.py \
igmp.py \
ip_mroute.py \
tcp_hpts_test.py
LIBADD.so_reuseport_lb_test= pthread
LIBADD.udp_bindings= pthread
@@ -54,7 +55,11 @@ TEST_METADATA.raw+= execenv="jail" \
execenv_jail_params="vnet allow.raw_sockets"
TEST_METADATA.redirect+= required_programs="python"
PROGS= udp_dontroute tcp_user_cookie multicast-send multicast-receive
PROGS= ip6_mrouted \
multicast-send \
multicast-receive \
tcp_user_cookie \
udp_dontroute
${PACKAGE}FILES+= redirect.py
+191
View File
@@ -0,0 +1,191 @@
/*
* Copyright (c) 2026 Stormshield
*
* SPDX-License-Identifier: BSD-2-Clause
*/
/*
* A dead-simple IPv6 multicast routing daemon. It registers itself with the
* multicast routing code and then waits for messages from the kernel. Received
* messages are handled by installing multicast routes.
*/
#include <sys/types.h>
#include <sys/event.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet6/ip6_mroute.h>
#include <arpa/inet.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct mif {
const char *name;
int mifi;
int pifi;
STAILQ_ENTRY(mif) next;
};
static STAILQ_HEAD(, mif) miflist = STAILQ_HEAD_INITIALIZER(miflist);
static void *
xmalloc(size_t size)
{
void *ptr;
ptr = malloc(size);
if (ptr == NULL)
err(1, "malloc");
return (ptr);
}
static void
usage(void)
{
fprintf(stderr,
"usage: %s [-i <iface>] [-m <srcaddr>/<groupaddr>/<iface>]\n",
getprogname());
exit(1);
}
static void
add_route(int sd, const struct in6_addr *src, const struct in6_addr *group,
mifi_t mifi)
{
struct mf6cctl mfcc;
struct mif *mif;
int error;
memset(&mfcc, 0, sizeof(mfcc));
mfcc.mf6cc_parent = mifi;
mfcc.mf6cc_origin.sin6_family = AF_INET6;
mfcc.mf6cc_origin.sin6_len = sizeof(struct sockaddr_in6);
mfcc.mf6cc_origin.sin6_addr = *src;
mfcc.mf6cc_mcastgrp.sin6_family = AF_INET6;
mfcc.mf6cc_mcastgrp.sin6_len = sizeof(struct sockaddr_in6);
mfcc.mf6cc_mcastgrp.sin6_addr = *group;
STAILQ_FOREACH(mif, &miflist, next) {
if (mif->mifi != mifi)
IF_SET(mif->mifi, &mfcc.mf6cc_ifset);
}
error = setsockopt(sd, IPPROTO_IPV6, MRT6_ADD_MFC,
&mfcc, sizeof(mfcc));
if (error != 0)
err(1, "setsockopt(MRT6_ADD_MFC)");
}
static void
handle_upcalls(int sd)
{
struct kevent ev;
int kq;
kq = kqueue();
if (kq < 0)
err(1, "kqueue");
EV_SET(&ev, sd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
if (kevent(kq, &ev, 1, NULL, 0, NULL) < 0)
err(1, "kevent");
for (;;) {
char buf1[INET6_ADDRSTRLEN], buf2[INET6_ADDRSTRLEN];
struct mrt6msg msg;
ssize_t len;
int n;
n = kevent(kq, NULL, 0, &ev, 1, NULL);
if (n < 0) {
if (errno == EINTR)
break;
err(1, "kevent");
}
if (n == 0)
continue;
assert(n == 1);
assert(ev.filter == EVFILT_READ);
len = recv(sd, &msg, sizeof(msg), 0);
if (len < 0)
err(1, "recv");
if ((size_t)len < sizeof(msg)) {
warnx("short read on upcall, %zd bytes", len);
continue;
}
printf("upcall received:\n");
printf("msgtype=%d mif=%d src=%s dst=%s\n",
msg.im6_msgtype, msg.im6_mif,
inet_ntop(AF_INET6, &msg.im6_src, buf1, sizeof(buf1)),
inet_ntop(AF_INET6, &msg.im6_dst, buf2, sizeof(buf2)));
add_route(sd, &msg.im6_src, &msg.im6_dst, msg.im6_mif);
}
close(kq);
}
int
main(int argc, char **argv)
{
struct mif *mif;
int ch, error, mifi, sd, v;
mifi = 0;
while ((ch = getopt(argc, argv, "i:m:")) != -1) {
switch (ch) {
case 'i':
mif = xmalloc(sizeof(*mif));
mif->name = strdup(optarg);
mif->mifi = mifi++;
mif->pifi = if_nametoindex(optarg);
if (mif->pifi == 0)
errx(1, "unknown interface %s", optarg);
STAILQ_INSERT_TAIL(&miflist, mif, next);
break;
default:
usage();
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;
sd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (sd < 0)
err(1, "socket");
v = 1;
error = setsockopt(sd, IPPROTO_IPV6, MRT6_INIT, &v, sizeof(v));
if (error != 0)
err(1, "setsockopt(MRT6_INIT)");
STAILQ_FOREACH(mif, &miflist, next) {
struct mif6ctl mifc;
mifc.mif6c_mifi = mif->mifi;
mifc.mif6c_pifi = mif->pifi;
mifc.mif6c_flags = 0;
error = setsockopt(sd, IPPROTO_IPV6, MRT6_ADD_MIF,
&mifc, sizeof(mifc));
if (error != 0)
err(1, "setsockopt(MRT6_ADD_MIF) on %s", mif->name);
}
handle_upcalls(sd);
error = setsockopt(sd, IPPROTO_IPV6, MRT6_DONE, NULL, 0);
if (error != 0)
err(1, "setsockopt(MRT6_DONE)");
return (0);
}
+428
View File
@@ -0,0 +1,428 @@
#
# Copyright (c) 2025 Stormshield
#
# SPDX-License-Identifier: BSD-2-Clause
#
import pytest
import socket
import struct
import subprocess
import time
from pathlib import Path
from atf_python.sys.net.vnet import VnetTestTemplate
class MRouteTestTemplate(VnetTestTemplate):
"""
Helper class for multicast routing tests. Test classes should inherit from this one.
"""
COORD_SOCK = "coord.sock"
@staticmethod
def _msgwait(sock: socket.socket, expected: bytes):
msg = sock.recv(1024)
assert msg == expected
@staticmethod
def sendmsg(msg: bytes, path: str):
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
s.sendto(msg, path)
s.close()
@staticmethod
def _makesock(path: str):
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
s.bind(path)
return s
@staticmethod
def mcast_join_INET6(addr: str, port: int):
pass
def jointest(self, vnet):
"""Let the coordinator know that we're ready, and wait for go-ahead."""
coord = self._makesock(vnet.alias + ".sock")
self.sendmsg(b"ok " + vnet.alias.encode(), self.COORD_SOCK)
self._msgwait(coord, b"join")
def donetest(self):
"""Let the coordinator that we completed successfully."""
self.sendmsg(b"done", self.COORD_SOCK)
def starttest(self, vnets: list[str]):
self.vnets = vnets
for vnet in vnets:
self.sendmsg(b"join", vnet + ".sock")
def waittest(self):
for vnet in self.vnets:
self._msgwait(self.coord, b"done")
def setup_method(self, method):
self.coord = self._makesock(self.COORD_SOCK)
super().setup_method(method)
# Loop until all other hosts have sent the ok message.
received = set()
vnet_names = set(self.vnet_map.keys()) - {self.vnet.alias}
while len(received) < len(vnet_names):
msg = self.coord.recv(1024)
received.add(msg)
assert received == {b"ok " + name.encode() for name in vnet_names}
class MRouteINETTestTemplate(MRouteTestTemplate):
@staticmethod
def run_pimd(ident: str, ifaces: list[str], rpaddr: str, group: str, fib=0):
conf = f"pimd-{ident}.conf"
with open(conf, "w") as conf_file:
conf_file.write("no phyint\n")
for iface in ifaces:
conf_file.write(f"phyint {iface} enable\n")
conf_file.write(f"rp-address {rpaddr} {group}\n")
cmd = f"setfib {fib} pimd -i {ident} -f {conf} -p pimd-{ident}.pid -n"
return subprocess.Popen(cmd.split(), stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
@staticmethod
def mcast_join(addr: str, port: int):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
mreq = struct.pack("4si", socket.inet_aton(addr), socket.INADDR_ANY)
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
s.bind((addr, port))
time.sleep(1) # Give the kernel a bit of time to join the group.
return s
@staticmethod
def mcast_sendto(addr: str, port: int, iface: str, msg: bytes):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
mreqn = struct.pack("iii", socket.INADDR_ANY, socket.INADDR_ANY,
socket.if_nametoindex(iface))
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, mreqn)
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 64)
s.sendto(msg, (addr, port))
s.close()
def setup_method(self, method):
self.require_module("ip_mroute")
super().setup_method(method)
class MRouteINET6TestTemplate(MRouteTestTemplate):
@staticmethod
def run_ip6_mrouted(ident: str, ifaces: list[str], fib=0):
ifaces_str = ' '.join(f"-i {iface}" for iface in ifaces)
exepath = Path(__file__).parent / "ip6_mrouted"
cmd = f"setfib {fib} {exepath} {ifaces_str}"
return subprocess.Popen(cmd.split(), stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
@staticmethod
def mcast_join(addr: str, port: int, iface: str):
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
mreq = struct.pack("16si", socket.inet_pton(socket.AF_INET6, addr),
socket.if_nametoindex(iface))
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
s.bind((addr, port))
time.sleep(1) # Give the kernel a bit of time to join the
return s
@staticmethod
def mcast_sendto(addr: str, port: int, iface: str, msg: bytes):
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
mreq = struct.pack("i", socket.if_nametoindex(iface))
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, mreq)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 64)
s.sendto(msg, (addr, port))
s.close()
def setup_method(self, method):
self.require_module("ip6_mroute")
super().setup_method(method)
class Test1RBasicINET(MRouteINETTestTemplate):
"""Basic multicast routing setup with 2 hosts connected via a router."""
TOPOLOGY = {
"vnet_router": {"ifaces": ["if1", "if2"]},
"vnet_host1": {"ifaces": ["if1"]},
"vnet_host2": {"ifaces": ["if2"]},
"if1": {"prefixes4": [("192.168.1.1/24", "192.168.1.2/24")]},
"if2": {"prefixes4": [("192.168.2.1/24", "192.168.2.2/24")]},
}
MULTICAST_ADDR = "239.0.0.1"
def setup_method(self, method):
# Create VNETs and start the handlers.
super().setup_method(method)
ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
self.pimd = self.run_pimd("test", ifaces, "127.0.0.1", self.MULTICAST_ADDR + "/32")
time.sleep(3) # Give pimd a bit of time to get itself together.
def vnet_host1_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
# Wait for host 2 to send a message, then send a reply.
self._msgwait(self.sock, b"Hello, Multicast!")
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Goodbye, Multicast!")
self._msgwait(self.sock, b"Goodbye, Multicast!")
self.donetest()
def vnet_host2_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
# Send a message to host 1, then wait for a reply.
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Hello, Multicast!")
self._msgwait(self.sock, b"Hello, Multicast!")
self._msgwait(self.sock, b"Goodbye, Multicast!")
self.donetest()
@pytest.mark.require_user("root")
@pytest.mark.require_progs(["pimd"])
@pytest.mark.timeout(30)
def test(self):
self.starttest(["vnet_host1", "vnet_host2"])
self.waittest()
class Test1RCrissCrossINET(MRouteINETTestTemplate):
"""
Test a router connected to four hosts, with pairs of interfaces
in different FIBs.
"""
TOPOLOGY = {
"vnet_router": {"ifaces": ["if1", "if2", "if3", "if4"]},
"vnet_host1": {"ifaces": ["if1"]},
"vnet_host2": {"ifaces": ["if2"]},
"vnet_host3": {"ifaces": ["if3"]},
"vnet_host4": {"ifaces": ["if4"]},
"if1": {
"prefixes4": [("192.168.1.1/24", "192.168.1.2/24")],
"prefixes6": [],
"fib": (0, 0),
},
"if2": {
"prefixes4": [("192.168.2.1/24", "192.168.2.2/24")],
"prefixes6": [],
"fib": (0, 0),
},
"if3": {
"prefixes4": [("192.168.3.1/24", "192.168.3.2/24")],
"prefixes6": [],
"fib": (1, 0),
},
"if4": {
"prefixes4": [("192.168.4.1/24", "192.168.4.2/24")],
"prefixes6": [],
"fib": (1, 0),
},
}
MULTICAST_ADDR = "239.0.0.1"
def setup_method(self, method):
# Create VNETs and start the handlers.
super().setup_method(method)
# Start a pimd instance per FIB.
ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
self.pimd0 = self.run_pimd("test0", ifaces, "127.0.0.1", self.MULTICAST_ADDR + "/32",
fib=0)
ifaces = [self.vnet.iface_alias_map[i].name for i in ["if3", "if4"]]
self.pimd1 = self.run_pimd("test1", ifaces, "127.0.0.1", self.MULTICAST_ADDR + "/32",
fib=1)
time.sleep(3) # Give pimd a bit of time to get itself together.
def vnet_host1_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Goodbye, Multicast on FIB 0!")
self.donetest()
def vnet_host2_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Hello, Multicast on FIB 0!")
self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
self._msgwait(self.sock, b"Goodbye, Multicast on FIB 0!")
self.donetest()
def vnet_host3_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
self._msgwait(self.sock, b"Hello, Multicast on FIB 1!")
self.mcast_sendto(self.MULTICAST_ADDR, 12345,
vnet.ifaces[0].name, b"Goodbye, Multicast on FIB 1!")
self.donetest()
def vnet_host4_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345)
time.sleep(1)
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Hello, Multicast on FIB 1!")
self._msgwait(self.sock, b"Hello, Multicast on FIB 1!")
self._msgwait(self.sock, b"Goodbye, Multicast on FIB 1!")
self.donetest()
@pytest.mark.require_user("root")
@pytest.mark.require_progs(["pimd"])
@pytest.mark.timeout(30)
def test(self):
self.starttest(["vnet_host1", "vnet_host2", "vnet_host3", "vnet_host4"])
self.waittest()
class Test1RBasicINET6(MRouteINET6TestTemplate):
"""Basic multicast routing setup with 2 hosts connected via a router."""
TOPOLOGY = {
"vnet_router": {"ifaces": ["if1", "if2"]},
"vnet_host1": {"ifaces": ["if1"]},
"vnet_host2": {"ifaces": ["if2"]},
"if1": {
"prefixes6": [("2001:db8:0:1::1/64", "2001:db8:0:1::2/64")]
},
"if2": {
"prefixes6": [("2001:db8:0:2::1/64", "2001:db8:0:2::2/64")]
},
}
MULTICAST_ADDR = "ff05::1"
def setup_method(self, method):
# Create VNETs and start the handlers.
super().setup_method(method)
ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
self.mrouted = self.run_ip6_mrouted("test", ifaces)
time.sleep(1)
def vnet_host1_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
# Wait for host 2 to send a message, then send a reply.
self._msgwait(self.sock, b"Hello, Multicast!")
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Goodbye, Multicast!")
self._msgwait(self.sock, b"Goodbye, Multicast!")
self.donetest()
def vnet_host2_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
# Send a message to host 1, then wait for a reply.
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Hello, Multicast!")
self._msgwait(self.sock, b"Hello, Multicast!")
self._msgwait(self.sock, b"Goodbye, Multicast!")
self.donetest()
@pytest.mark.require_user("root")
@pytest.mark.timeout(30)
def test(self):
self.starttest(["vnet_host1", "vnet_host2"])
self.waittest()
class Test1RCrissCrossINET6(MRouteINET6TestTemplate):
"""
Test a router connected to four hosts, with pairs of interfaces
in different FIBs.
"""
TOPOLOGY = {
"vnet_router": {"ifaces": ["if1", "if2", "if3", "if4"]},
"vnet_host1": {"ifaces": ["if1"]},
"vnet_host2": {"ifaces": ["if2"]},
"vnet_host3": {"ifaces": ["if3"]},
"vnet_host4": {"ifaces": ["if4"]},
"if1": {
"prefixes6": [("2001:db8:0:1::1/64", "2001:db8:0:1::2/64")],
"fib": (0, 0),
},
"if2": {
"prefixes6": [("2001:db8:0:2::1/64", "2001:db8:0:2::2/64")],
"fib": (0, 0),
},
"if3": {
"prefixes6": [("2001:db8:0:3::1/64", "2001:db8:0:3::2/64")],
"fib": (1, 0),
},
"if4": {
"prefixes6": [("2001:db8:0:4::1/64", "2001:db8:0:4::2/64")],
"fib": (1, 0),
},
}
MULTICAST_ADDR = "ff05::1"
def setup_method(self, method):
# Create VNETs and start the handlers.
super().setup_method(method)
# Start an ip6_mrouted instance per FIB.
ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]]
self.pimd0 = self.run_ip6_mrouted("test0", ifaces, fib=0)
ifaces = [self.vnet.iface_alias_map[i].name for i in ["if3", "if4"]]
self.pimd1 = self.run_ip6_mrouted("test1", ifaces, fib=1)
time.sleep(1) # Give ip6_mrouted a bit of time to get itself together.
def vnet_host1_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Goodbye, Multicast on FIB 0!")
self.donetest()
def vnet_host2_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Hello, Multicast on FIB 0!")
self._msgwait(self.sock, b"Hello, Multicast on FIB 0!")
self._msgwait(self.sock, b"Goodbye, Multicast on FIB 0!")
self.donetest()
def vnet_host3_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
self._msgwait(self.sock, b"Hello, Multicast on FIB 1!")
self.mcast_sendto(self.MULTICAST_ADDR, 12345,
vnet.ifaces[0].name, b"Goodbye, Multicast on FIB 1!")
self.donetest()
def vnet_host4_handler(self, vnet):
self.jointest(vnet)
self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name)
time.sleep(1)
self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name,
b"Hello, Multicast on FIB 1!")
self._msgwait(self.sock, b"Hello, Multicast on FIB 1!")
self._msgwait(self.sock, b"Goodbye, Multicast on FIB 1!")
self.donetest()
@pytest.mark.require_user("root")
@pytest.mark.timeout(30)
def test(self):
self.starttest(["vnet_host1", "vnet_host2", "vnet_host3", "vnet_host4"])
self.waittest()
+1
View File
@@ -1,6 +1,7 @@
PACKAGE= tests
TESTSDIR= ${TESTSBASE}/sys/netinet6
BINDIR= ${TESTSDIR}
FILESDIR= ${TESTSDIR}
ATF_TESTS_PYTEST= test_ip6_output.py