Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 30 Mar 2026 16:15:43 +0000
From:      Mark Johnston <markj@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 09e702ad40af - main - tests: Add some simple regression tests for ip_mroute
Message-ID:  <69caa1af.3bdf7.24600de@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=09e702ad40af0067017613070b42d72cbc2bec3a

commit 09e702ad40af0067017613070b42d72cbc2bec3a
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-03-30 13:32:35 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-03-30 16:08:21 +0000

    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
---
 tests/sys/netinet/Makefile      |  13 +-
 tests/sys/netinet/ip6_mrouted.c | 191 ++++++++++++++++++
 tests/sys/netinet/ip_mroute.py  | 428 ++++++++++++++++++++++++++++++++++++++++
 tests/sys/netinet6/Makefile     |   1 +
 4 files changed, 629 insertions(+), 4 deletions(-)

diff --git a/tests/sys/netinet/Makefile b/tests/sys/netinet/Makefile
index 42906aa2dd93..a13b0b42e2bc 100644
--- a/tests/sys/netinet/Makefile
+++ b/tests/sys/netinet/Makefile
@@ -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
 
diff --git a/tests/sys/netinet/ip6_mrouted.c b/tests/sys/netinet/ip6_mrouted.c
new file mode 100644
index 000000000000..f3df8330df10
--- /dev/null
+++ b/tests/sys/netinet/ip6_mrouted.c
@@ -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);
+}
diff --git a/tests/sys/netinet/ip_mroute.py b/tests/sys/netinet/ip_mroute.py
new file mode 100644
index 000000000000..5416d824d3c2
--- /dev/null
+++ b/tests/sys/netinet/ip_mroute.py
@@ -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()
diff --git a/tests/sys/netinet6/Makefile b/tests/sys/netinet6/Makefile
index 26f1a18a8d32..f5a04a299dc6 100644
--- a/tests/sys/netinet6/Makefile
+++ b/tests/sys/netinet6/Makefile
@@ -1,6 +1,7 @@
 PACKAGE=	tests
 
 TESTSDIR=	${TESTSBASE}/sys/netinet6
+BINDIR=		${TESTSDIR}
 FILESDIR=	${TESTSDIR}
 
 ATF_TESTS_PYTEST=	test_ip6_output.py


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69caa1af.3bdf7.24600de>