From nobody Mon Mar 30 16:15:43 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4fkxDv65DQz6WjB6 for ; Mon, 30 Mar 2026 16:15:43 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R12" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4fkxDv4W26z49YV for ; Mon, 30 Mar 2026 16:15:43 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1774887343; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=KllWILuUYQFjLq2DFuWT3ASMKiZtP7ihwfh0rmjtEgU=; b=uiGUIUhq0Tt6MVa2NW6M6Qb5LIXjBeniE+gXWy1T3/xOHV1pOqfXwe52teqOQ55aDEMRKb Npo2uz2GYt/krxOBcfkDQu7EGGdCVlKq093/NQ8+4faVDzgIWhHDMCIHUTahH3KWj6L17f RLCTPCcGddBzIxbvTgb+S8nJAPEy5tcpCYwrNn5CU9v6Cs8q+4pfPMzfxcPOHwtR1abFJK uaEKkJj53xUsx05Ipbp5owftxfrCGrMOQR+LwSzXZlntcQhUnvYCgdmFIr2AnVZ8FGiUVH 3eIkqgFJrS6tMibbCIygUgc4J2Nef5SyuJDThMaP//VXA5ZxhPIZuDuAwLVROw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1774887343; a=rsa-sha256; cv=none; b=L/lWMQ6tFe5F41nrz7b+R4CuI8Txp6FOGImm4veN3oLs//B2gnNu3gI9dywi+pC4bcJHHq R6HIJwzcG9I+lzz0jaS531HiYh8/hTQyodIAyzag8YzstgtmqpKtCxbHyGKnSdyaOiQowF ARImpLkXPIw2CkmBIBuLovD1yDjJWm/NlEkInhuZeSLVb2CUyILajFSJUMcOV+X3QsQuke iLB7pvMIxWy15dWfh1v9OhkW4pTAvQdEZMJMJ1AbO+Yz9RDwEYry6e91Dj24FPcftlN1OO pcRSIn5aswRKW0udosZ4UnTAdstZVkcdZhnPtr5LItf+u/n2Ucf7TTVQCI5z1w== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1774887343; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=KllWILuUYQFjLq2DFuWT3ASMKiZtP7ihwfh0rmjtEgU=; b=hKrnUpE1KeCeAU200zzHVt8DB9zfNNRzC2QKLM/A8sk7owbDZmygc1/41t6itLcocWDjX7 Ldax3jj5kNJlR2cqPyKCULkq8dc6xruk0gqw/GobN/BAQ246ZVrn5UCSSnkLLURTTxGjrH rtcvDAPU/1TxDly0lHsGBXtptPuC7XLW2djBtCARIwBVLtEeBpDKXxOCqUH+79qrbqBJC3 2RNJEOjvo/Nh4oa+YyoMM7GBdRfjTLgAweDRYs1Jca8ZAWGFn0eFFsEO4HEtasYPR00MIT S0AQ/h0J92wW2T+6nM3CxvLjVKlMpQccDk3asB3DdzB/b9oKNYsuJAEcIqupBQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4fkxDv3kcQzqh1 for ; Mon, 30 Mar 2026 16:15:43 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3bdf7 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Mon, 30 Mar 2026 16:15:43 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Mark Johnston Subject: git: 09e702ad40af - main - tests: Add some simple regression tests for ip_mroute List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: markj X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 09e702ad40af0067017613070b42d72cbc2bec3a Auto-Submitted: auto-generated Date: Mon, 30 Mar 2026 16:15:43 +0000 Message-Id: <69caa1af.3bdf7.24600de@gitrepo.freebsd.org> The branch main has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=09e702ad40af0067017613070b42d72cbc2bec3a commit 09e702ad40af0067017613070b42d72cbc2bec3a Author: Mark Johnston AuthorDate: 2026-03-30 13:32:35 +0000 Commit: Mark Johnston 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 +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 ] [-m //]\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