132546d57SKristof Provost# 232546d57SKristof Provost# SPDX-License-Identifier: BSD-2-Clause 332546d57SKristof Provost# 432546d57SKristof Provost# Copyright (c) 2025 Rubicon Communications, LLC (Netgate) 532546d57SKristof Provost# 632546d57SKristof Provost# Redistribution and use in source and binary forms, with or without 732546d57SKristof Provost# modification, are permitted provided that the following conditions 832546d57SKristof Provost# are met: 932546d57SKristof Provost# 1. Redistributions of source code must retain the above copyright 1032546d57SKristof Provost# notice, this list of conditions and the following disclaimer. 1132546d57SKristof Provost# 2. Redistributions in binary form must reproduce the above copyright 1232546d57SKristof Provost# notice, this list of conditions and the following disclaimer in the 1332546d57SKristof Provost# documentation and/or other materials provided with the distribution. 1432546d57SKristof Provost# 1532546d57SKristof Provost# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 1632546d57SKristof Provost# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1732546d57SKristof Provost# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1832546d57SKristof Provost# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 1932546d57SKristof Provost# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2032546d57SKristof Provost# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2132546d57SKristof Provost# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2232546d57SKristof Provost# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2332546d57SKristof Provost# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2432546d57SKristof Provost# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2532546d57SKristof Provost# SUCH DAMAGE. 2632546d57SKristof Provost 2732546d57SKristof Provostimport pytest 2832546d57SKristof Provostimport re 2932546d57SKristof Provostfrom utils import DelayedSend 3032546d57SKristof Provostfrom atf_python.sys.net.tools import ToolsHelper 3132546d57SKristof Provostfrom atf_python.sys.net.vnet import VnetTestTemplate 3232546d57SKristof Provost 3332546d57SKristof Provostclass TestHeader(VnetTestTemplate): 3432546d57SKristof Provost REQUIRED_MODULES = [ "pf" ] 3532546d57SKristof Provost TOPOLOGY = { 3632546d57SKristof Provost "vnet1": {"ifaces": ["if1", "if2"]}, 3732546d57SKristof Provost "vnet2": {"ifaces": ["if1", "if2"]}, 3832546d57SKristof Provost "if1": {"prefixes4": [("192.0.2.2/24", "192.0.2.1/24")]}, 3932546d57SKristof Provost "if2": {"prefixes4": [("198.51.100.1/24", "198.51.100.2/24")]}, 4032546d57SKristof Provost } 4132546d57SKristof Provost 4232546d57SKristof Provost def vnet2_handler(self, vnet): 4332546d57SKristof Provost ToolsHelper.print_output("/sbin/sysctl net.inet.ip.forwarding=1") 4432546d57SKristof Provost ToolsHelper.print_output("/usr/sbin/arp -s 198.51.100.3 00:01:02:03:04:05") 4532546d57SKristof Provost ToolsHelper.print_output("/sbin/pfctl -e") 4632546d57SKristof Provost ToolsHelper.print_output("/sbin/pfctl -x loud") 4732546d57SKristof Provost ToolsHelper.pf_rules([ 4832546d57SKristof Provost "pass", 4932546d57SKristof Provost ]) 5032546d57SKristof Provost 5132546d57SKristof Provost @pytest.mark.require_user("root") 5232546d57SKristof Provost @pytest.mark.require_progs(["scapy"]) 5332546d57SKristof Provost def test_too_many(self): 5432546d57SKristof Provost "Verify that we drop packets with silly numbers of headers." 5532546d57SKristof Provost 5632546d57SKristof Provost sendif = self.vnet.iface_alias_map["if1"].name 5732546d57SKristof Provost recvif = self.vnet.iface_alias_map["if2"].name 5832546d57SKristof Provost gw_mac = ToolsHelper.get_output("/sbin/ifconfig %s ether | awk '/ether/ { print $2; }'" % sendif) 5932546d57SKristof Provost gw_mac = re.sub("0a$", "0b", gw_mac) 6032546d57SKristof Provost 6132546d57SKristof Provost ToolsHelper.print_output("/sbin/route add default 192.0.2.1") 6232546d57SKristof Provost 6332546d57SKristof Provost # Import in the correct vnet, so at to not confuse Scapy 6432546d57SKristof Provost import scapy.all as sp 6532546d57SKristof Provost 6632546d57SKristof Provost # Sanity check, ensure we get replies to normal ping 6732546d57SKristof Provost pkt = sp.Ether(dst=gw_mac) \ 6832546d57SKristof Provost / sp.IP(dst="198.51.100.3") \ 6932546d57SKristof Provost / sp.ICMP(type='echo-request') 7032546d57SKristof Provost s = DelayedSend(pkt, sendif) 7132546d57SKristof Provost reply = sp.sniff(iface=recvif, timeout=3) 7232546d57SKristof Provost print(reply) 7332546d57SKristof Provost 7432546d57SKristof Provost found = False 7532546d57SKristof Provost for r in reply: 7632546d57SKristof Provost r.show() 7732546d57SKristof Provost 7832546d57SKristof Provost icmp = r.getlayer(sp.ICMP) 7932546d57SKristof Provost if not icmp: 8032546d57SKristof Provost continue 8132546d57SKristof Provost assert icmp.type == 8 # 'echo-request' 8232546d57SKristof Provost found = True 8332546d57SKristof Provost assert found 8432546d57SKristof Provost 8532546d57SKristof Provost # Up to 19 AH headers will pass 8632546d57SKristof Provost pkt = sp.Ether(dst=gw_mac) \ 8732546d57SKristof Provost / sp.IP(dst="198.51.100.3") 8832546d57SKristof Provost for i in range(0, 18): 8932546d57SKristof Provost pkt = pkt / sp.AH(nh=51, payloadlen=1) 9032546d57SKristof Provost pkt = pkt / sp.AH(nh=1, payloadlen=1) / sp.ICMP(type='echo-request') 9132546d57SKristof Provost 9232546d57SKristof Provost s = DelayedSend(pkt, sendif) 9332546d57SKristof Provost reply = sp.sniff(iface=recvif, timeout=3) 9432546d57SKristof Provost print(reply) 9532546d57SKristof Provost found = False 9632546d57SKristof Provost for r in reply: 9732546d57SKristof Provost r.show() 9832546d57SKristof Provost 9932546d57SKristof Provost ah = r.getlayer(sp.AH) 10032546d57SKristof Provost if not ah: 10132546d57SKristof Provost continue 10232546d57SKristof Provost found = True 10332546d57SKristof Provost assert found 10432546d57SKristof Provost 10532546d57SKristof Provost # But more will get dropped 10632546d57SKristof Provost pkt = sp.Ether(dst=gw_mac) \ 10732546d57SKristof Provost / sp.IP(dst="198.51.100.3") 10832546d57SKristof Provost for i in range(0, 19): 10932546d57SKristof Provost pkt = pkt / sp.AH(nh=51, payloadlen=1) 11032546d57SKristof Provost pkt = pkt / sp.AH(nh=1, payloadlen=1) / sp.ICMP(type='echo-request') 11132546d57SKristof Provost 11232546d57SKristof Provost s = DelayedSend(pkt, sendif) 11332546d57SKristof Provost reply = sp.sniff(iface=recvif, timeout=3) 11432546d57SKristof Provost print(reply) 11532546d57SKristof Provost 11632546d57SKristof Provost found = False 11732546d57SKristof Provost for r in reply: 11832546d57SKristof Provost r.show() 11932546d57SKristof Provost 12032546d57SKristof Provost ah = r.getlayer(sp.AH) 12132546d57SKristof Provost if not ah: 12232546d57SKristof Provost continue 12332546d57SKristof Provost found = True 12432546d57SKristof Provost assert not found 12532546d57SKristof Provost 12632546d57SKristof Provostclass TestHeader6(VnetTestTemplate): 12732546d57SKristof Provost REQUIRED_MODULES = [ "pf" ] 128*7659d0faSKristof Provost SKIP_MODULES = [ "ipfilter" ] 12932546d57SKristof Provost TOPOLOGY = { 13032546d57SKristof Provost "vnet1": {"ifaces": ["if1", "if2"]}, 13132546d57SKristof Provost "vnet2": {"ifaces": ["if1", "if2"]}, 13232546d57SKristof Provost "if1": {"prefixes6": [("2001:db8::2/64", "2001:db8::1/64")]}, 13332546d57SKristof Provost "if2": {"prefixes6": [("2001:db8:1::2/64", "2001:db8:1::1/64")]}, 13432546d57SKristof Provost } 13532546d57SKristof Provost 13632546d57SKristof Provost def vnet2_handler(self, vnet): 13732546d57SKristof Provost ToolsHelper.print_output("/sbin/sysctl net.inet6.ip6.forwarding=1") 13832546d57SKristof Provost ToolsHelper.print_output("/usr/sbin/ndp -s 2001:db8:1::3 00:01:02:03:04:05") 13932546d57SKristof Provost ToolsHelper.print_output("/sbin/pfctl -e") 14032546d57SKristof Provost ToolsHelper.print_output("/sbin/pfctl -x loud") 14132546d57SKristof Provost ToolsHelper.pf_rules([ 14232546d57SKristof Provost "pass", 14332546d57SKristof Provost ]) 14432546d57SKristof Provost 14532546d57SKristof Provost @pytest.mark.require_user("root") 14632546d57SKristof Provost @pytest.mark.require_progs(["scapy"]) 14732546d57SKristof Provost def test_too_many(self): 14832546d57SKristof Provost "Verify that we drop packets with silly numbers of headers." 14932546d57SKristof Provost ToolsHelper.print_output("/sbin/ifconfig") 15032546d57SKristof Provost 15132546d57SKristof Provost sendif = self.vnet.iface_alias_map["if1"].name 15232546d57SKristof Provost recvif = self.vnet.iface_alias_map["if2"].name 15332546d57SKristof Provost our_mac = ToolsHelper.get_output("/sbin/ifconfig %s ether | awk '/ether/ { print $2; }'" % sendif) 15432546d57SKristof Provost gw_mac = re.sub("0a$", "0b", our_mac) 15532546d57SKristof Provost 15632546d57SKristof Provost ToolsHelper.print_output("/sbin/route -6 add default 2001:db8::1") 15732546d57SKristof Provost 15832546d57SKristof Provost # Import in the correct vnet, so at to not confuse Scapy 15932546d57SKristof Provost import scapy.all as sp 16032546d57SKristof Provost 16132546d57SKristof Provost # Sanity check, ensure we get replies to normal ping 16232546d57SKristof Provost pkt = sp.Ether(src=our_mac, dst=gw_mac) \ 16332546d57SKristof Provost / sp.IPv6(src="2001:db8::2", dst="2001:db8:1::3") \ 16432546d57SKristof Provost / sp.ICMPv6EchoRequest() 16532546d57SKristof Provost s = DelayedSend(pkt, sendif) 16632546d57SKristof Provost reply = sp.sniff(iface=recvif, timeout=3) 16732546d57SKristof Provost print(reply) 16832546d57SKristof Provost 16932546d57SKristof Provost found = False 17032546d57SKristof Provost for r in reply: 17132546d57SKristof Provost r.show() 17232546d57SKristof Provost 17332546d57SKristof Provost icmp = r.getlayer(sp.ICMPv6EchoRequest) 17432546d57SKristof Provost if not icmp: 17532546d57SKristof Provost continue 17632546d57SKristof Provost found = True 17732546d57SKristof Provost assert found 17832546d57SKristof Provost 17932546d57SKristof Provost # Up to 19 AH headers will pass 18032546d57SKristof Provost pkt = sp.Ether(src=our_mac, dst=gw_mac) \ 18132546d57SKristof Provost / sp.IPv6(src="2001:db8::2", dst="2001:db8:1::3") 18232546d57SKristof Provost for i in range(0, 18): 18332546d57SKristof Provost pkt = pkt / sp.AH(nh=51, payloadlen=1) 18432546d57SKristof Provost pkt = pkt / sp.AH(nh=58, payloadlen=1) / sp.ICMPv6EchoRequest() 18532546d57SKristof Provost s = DelayedSend(pkt, sendif) 18632546d57SKristof Provost reply = sp.sniff(iface=recvif, timeout=3) 18732546d57SKristof Provost print(reply) 18832546d57SKristof Provost 18932546d57SKristof Provost found = False 19032546d57SKristof Provost for r in reply: 19132546d57SKristof Provost r.show() 19232546d57SKristof Provost 19332546d57SKristof Provost ah = r.getlayer(sp.AH) 19432546d57SKristof Provost if not ah: 19532546d57SKristof Provost continue 19632546d57SKristof Provost found = True 19732546d57SKristof Provost assert found 19832546d57SKristof Provost 19932546d57SKristof Provost # But more will get dropped 20032546d57SKristof Provost pkt = sp.Ether(src=our_mac, dst=gw_mac) \ 20132546d57SKristof Provost / sp.IPv6(src="2001:db8::2", dst="2001:db8:1::3") 20232546d57SKristof Provost for i in range(0, 19): 20332546d57SKristof Provost pkt = pkt / sp.AH(nh=51, payloadlen=1) 20432546d57SKristof Provost pkt = pkt / sp.AH(nh=58, payloadlen=1) / sp.ICMPv6EchoRequest() 20532546d57SKristof Provost s = DelayedSend(pkt, sendif) 20632546d57SKristof Provost reply = sp.sniff(iface=recvif, timeout=3) 20732546d57SKristof Provost print(reply) 20832546d57SKristof Provost 20932546d57SKristof Provost found = False 21032546d57SKristof Provost for r in reply: 21132546d57SKristof Provost r.show() 21232546d57SKristof Provost 21332546d57SKristof Provost ah = r.getlayer(sp.AH) 21432546d57SKristof Provost if not ah: 21532546d57SKristof Provost continue 21632546d57SKristof Provost found = True 21732546d57SKristof Provost assert not found 218