#1# SPDX-License-Identifier: BSD-2-Clause2#3# Copyright (c) 2025 Rubicon Communications, LLC (Netgate)4#5# Redistribution and use in source and binary forms, with or without6# modification, are permitted provided that the following conditions7# are met:8# 1. Redistributions of source code must retain the above copyright9# notice, this list of conditions and the following disclaimer.10# 2. Redistributions in binary form must reproduce the above copyright11# notice, this list of conditions and the following disclaimer in the12# documentation and/or other materials provided with the distribution.13#14# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND15# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE18# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24# SUCH DAMAGE.2526import pytest27from utils import DelayedSend28from atf_python.sys.net.tools import ToolsHelper29from atf_python.sys.net.vnet import VnetTestTemplate3031class TestMLD(VnetTestTemplate):32REQUIRED_MODULES = [ "pf" ]33TOPOLOGY = {34"vnet1": {"ifaces": ["if1"], "opts": ["allow.read_msgbuf"]},35"vnet2": {"ifaces": ["if1"]},36"if1": {"prefixes6": [("2001:db8::2/64", "2001:db8::1/64")]},37}3839def vnet2_handler(self, vnet):40ifname = vnet.iface_alias_map["if1"].name41ToolsHelper.print_output("/sbin/pfctl -e")42ToolsHelper.pf_rules([43"pass",44])45ToolsHelper.print_output("/sbin/pfctl -x loud")4647def find_mld_reply(self, pkt, ifname):48pkt.show()49s = DelayedSend(pkt, ifname)5051found = False52packets = self.sp.sniff(iface=ifname, timeout=5)53for r in packets:54r.show()55mld = r.getlayer(self.sp.ICMPv6MLReport2)56if not mld:57continue58mld.show()59found = True60return found6162@pytest.mark.require_user("root")63@pytest.mark.require_progs(["scapy"])64def test_router_alert(self):65"""Verify that we allow MLD packets with router alert extension header"""66ifname = self.vnet.iface_alias_map["if1"].name67ToolsHelper.print_output("/sbin/ifconfig")6869# Import in the correct vnet, so at to not confuse Scapy70import scapy.all as sp71import scapy.contrib as sc72import scapy.contrib.igmp73self.sp = sp74self.sc = sc7576# MLD packets with an incorrect hop limit get dropped.77pkt = sp.Ether() \78/ sp.IPv6(src="fe80::1%%%s" % ifname, dst="ff02::1", hlim=2) \79/ sp.IPv6ExtHdrHopByHop(options=[ \80sp.RouterAlert(value=0) \81]) \82/ sp.ICMPv6MLQuery2()83# We can't reliably test this by checking for a reply, because84# the other jail may just send a spontaneous MLD reply.85self.find_mld_reply(pkt, ifname)8687# Check if we logged dropping the MLD paacket88dmesg = ToolsHelper.get_output("/sbin/dmesg")89assert dmesg.find("Invalid MLD") != -1909192