1b27d3f71SKristof Provost# 2b27d3f71SKristof Provost# SPDX-License-Identifier: BSD-2-Clause 3b27d3f71SKristof Provost# 4b27d3f71SKristof Provost# Copyright (c) 2024 Rubicon Communications, LLC (Netgate) 5b27d3f71SKristof Provost# 6b27d3f71SKristof Provost# Redistribution and use in source and binary forms, with or without 7b27d3f71SKristof Provost# modification, are permitted provided that the following conditions 8b27d3f71SKristof Provost# are met: 9b27d3f71SKristof Provost# 1. Redistributions of source code must retain the above copyright 10b27d3f71SKristof Provost# notice, this list of conditions and the following disclaimer. 11b27d3f71SKristof Provost# 2. Redistributions in binary form must reproduce the above copyright 12b27d3f71SKristof Provost# notice, this list of conditions and the following disclaimer in the 13b27d3f71SKristof Provost# documentation and/or other materials provided with the distribution. 14b27d3f71SKristof Provost# 15b27d3f71SKristof Provost# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16b27d3f71SKristof Provost# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17b27d3f71SKristof Provost# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18b27d3f71SKristof Provost# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19b27d3f71SKristof Provost# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20b27d3f71SKristof Provost# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21b27d3f71SKristof Provost# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22b27d3f71SKristof Provost# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23b27d3f71SKristof Provost# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24b27d3f71SKristof Provost# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25b27d3f71SKristof Provost# SUCH DAMAGE. 26b27d3f71SKristof Provost 27b27d3f71SKristof Provostimport pytest 28b27d3f71SKristof Provostimport subprocess 29b27d3f71SKristof Provostimport re 30b27d3f71SKristof Provostfrom atf_python.sys.net.tools import ToolsHelper 31b27d3f71SKristof Provostfrom atf_python.sys.net.vnet import VnetTestTemplate 32b27d3f71SKristof Provost 33b27d3f71SKristof Provostdef check(cmd): 34b27d3f71SKristof Provost ps = subprocess.Popen(cmd, shell=True) 35b27d3f71SKristof Provost ret = ps.wait() 36b27d3f71SKristof Provost if ret != 0: 37b27d3f71SKristof Provost raise Exception("Command %s returned %d" % (cmd, ret)) 38b27d3f71SKristof Provost 39b27d3f71SKristof Provostclass TestICMP(VnetTestTemplate): 40b27d3f71SKristof Provost REQUIRED_MODULES = [ "pf" ] 41b27d3f71SKristof Provost TOPOLOGY = { 42b27d3f71SKristof Provost "vnet1": {"ifaces": ["if1"]}, 43b27d3f71SKristof Provost "vnet2": {"ifaces": ["if1", "if2"]}, 44b27d3f71SKristof Provost "vnet3": {"ifaces": ["if2"]}, 45b27d3f71SKristof Provost "if1": {"prefixes4": [("192.0.2.2/24", "192.0.2.1/24")]}, 46b27d3f71SKristof Provost "if2": {"prefixes4": [("198.51.100.1/24", "198.51.100.2/24")]}, 47b27d3f71SKristof Provost } 48b27d3f71SKristof Provost 49b27d3f71SKristof Provost def vnet2_handler(self, vnet): 50b27d3f71SKristof Provost ifname = vnet.iface_alias_map["if1"].name 51fffedd81SKristof Provost if2name = vnet.iface_alias_map["if2"].name 52b27d3f71SKristof Provost 53b27d3f71SKristof Provost ToolsHelper.print_output("/sbin/pfctl -e") 54b27d3f71SKristof Provost ToolsHelper.pf_rules([ 55b27d3f71SKristof Provost "set reassemble yes", 56*86f2641bSKristof Provost "set state-policy if-bound", 57b27d3f71SKristof Provost "block", 58b27d3f71SKristof Provost "pass inet proto icmp icmp-type echoreq", 59b27d3f71SKristof Provost ]) 60b27d3f71SKristof Provost 61b27d3f71SKristof Provost ToolsHelper.print_output("/sbin/sysctl net.inet.ip.forwarding=1") 62b27d3f71SKristof Provost ToolsHelper.print_output("/sbin/pfctl -x loud") 63b27d3f71SKristof Provost 64fffedd81SKristof Provost ToolsHelper.print_output("/sbin/ifconfig %s mtu 1492" % if2name) 65fffedd81SKristof Provost 66b27d3f71SKristof Provost def vnet3_handler(self, vnet): 67b27d3f71SKristof Provost # Import in the correct vnet, so at to not confuse Scapy 68b27d3f71SKristof Provost import scapy.all as sp 69b27d3f71SKristof Provost 70b27d3f71SKristof Provost ifname = vnet.iface_alias_map["if2"].name 71b27d3f71SKristof Provost ToolsHelper.print_output("/sbin/route add default 198.51.100.1") 72b27d3f71SKristof Provost ToolsHelper.print_output("/sbin/ifconfig %s inet alias 198.51.100.3/24" % ifname) 73fffedd81SKristof Provost ToolsHelper.print_output("/sbin/ifconfig %s mtu 1492" % ifname) 74b27d3f71SKristof Provost 75b27d3f71SKristof Provost def checkfn(packet): 76b27d3f71SKristof Provost icmp = packet.getlayer(sp.ICMP) 77b27d3f71SKristof Provost if not icmp: 78b27d3f71SKristof Provost return False 79b27d3f71SKristof Provost 80b27d3f71SKristof Provost if icmp.type != 3: 81b27d3f71SKristof Provost return False 82b27d3f71SKristof Provost 83b27d3f71SKristof Provost packet.show() 84b27d3f71SKristof Provost return True 85b27d3f71SKristof Provost 86b27d3f71SKristof Provost sp.sniff(iface=ifname, stop_filter=checkfn) 87b27d3f71SKristof Provost vnet.pipe.send("Got ICMP destination unreachable packet") 88b27d3f71SKristof Provost 89b27d3f71SKristof Provost @pytest.mark.require_user("root") 9065cc5af1SJose Luis Duran @pytest.mark.require_progs(["scapy"]) 91b27d3f71SKristof Provost def test_inner_match(self): 92b27d3f71SKristof Provost vnet = self.vnet_map["vnet1"] 93b27d3f71SKristof Provost dst_vnet = self.vnet_map["vnet3"] 94b27d3f71SKristof Provost sendif = vnet.iface_alias_map["if1"].name 95b27d3f71SKristof Provost 96b27d3f71SKristof Provost our_mac = ToolsHelper.get_output("/sbin/ifconfig %s ether | awk '/ether/ { print $2; }'" % sendif) 97b27d3f71SKristof Provost dst_mac = re.sub("0a$", "0b", our_mac) 98b27d3f71SKristof Provost 99b27d3f71SKristof Provost # Import in the correct vnet, so at to not confuse Scapy 100b27d3f71SKristof Provost import scapy.all as sp 101b27d3f71SKristof Provost 102b27d3f71SKristof Provost ToolsHelper.print_output("/sbin/route add default 192.0.2.1") 103b27d3f71SKristof Provost 104b27d3f71SKristof Provost # Sanity check 105b27d3f71SKristof Provost check("/sbin/ping -c 1 192.0.2.1") 106b27d3f71SKristof Provost check("/sbin/ping -c 1 198.51.100.1") 107b27d3f71SKristof Provost check("/sbin/ping -c 2 198.51.100.3") 108b27d3f71SKristof Provost 109b27d3f71SKristof Provost # Establish a state 110b27d3f71SKristof Provost pkt = sp.Ether(src=our_mac, dst=dst_mac) \ 111b27d3f71SKristof Provost / sp.IP(src="192.0.2.2", dst="198.51.100.2") \ 112b27d3f71SKristof Provost / sp.ICMP(type='echo-request') \ 113b27d3f71SKristof Provost / "PAYLOAD" 114b27d3f71SKristof Provost sp.sendp(pkt, sendif, verbose=False) 115b27d3f71SKristof Provost 116b27d3f71SKristof Provost # Now try to pass an ICMP error message piggy-backing on that state, but 117b27d3f71SKristof Provost # use a different source address 118b27d3f71SKristof Provost pkt = sp.Ether(src=our_mac, dst=dst_mac) \ 119b27d3f71SKristof Provost / sp.IP(src="192.0.2.2", dst="198.51.100.3") \ 120b27d3f71SKristof Provost / sp.ICMP(type='dest-unreach') \ 121b27d3f71SKristof Provost / sp.IP(src="198.51.100.2", dst="192.0.2.2") \ 122b27d3f71SKristof Provost / sp.ICMP(type='echo-reply') 123b27d3f71SKristof Provost sp.sendp(pkt, sendif, verbose=False) 124b27d3f71SKristof Provost 125b27d3f71SKristof Provost try: 126b27d3f71SKristof Provost rcvd = self.wait_object(dst_vnet.pipe, timeout=1) 127b27d3f71SKristof Provost if rcvd: 128b27d3f71SKristof Provost raise Exception(rcvd) 129b27d3f71SKristof Provost except TimeoutError as e: 130b27d3f71SKristof Provost # We expect the timeout here. It means we didn't get the destination 131b27d3f71SKristof Provost # unreachable packet in vnet3 132b27d3f71SKristof Provost pass 133fffedd81SKristof Provost 134fffedd81SKristof Provost def check_icmp_echo(self, sp, payload_size): 135fffedd81SKristof Provost packet = sp.IP(dst="198.51.100.2", flags="DF") \ 136fffedd81SKristof Provost / sp.ICMP(type='echo-request') \ 137fffedd81SKristof Provost / sp.raw(bytes.fromhex('f0') * payload_size) 138fffedd81SKristof Provost 139fffedd81SKristof Provost p = sp.sr1(packet, iface=self.vnet.iface_alias_map["if1"].name, 140fffedd81SKristof Provost timeout=3) 141fffedd81SKristof Provost p.show() 142fffedd81SKristof Provost 143fffedd81SKristof Provost ip = p.getlayer(sp.IP) 144fffedd81SKristof Provost icmp = p.getlayer(sp.ICMP) 145fffedd81SKristof Provost assert ip 146fffedd81SKristof Provost assert icmp 147fffedd81SKristof Provost 148fffedd81SKristof Provost if payload_size > 1464: 149fffedd81SKristof Provost # Expect ICMP destination unreachable, fragmentation needed 150fffedd81SKristof Provost assert ip.src == "192.0.2.1" 151fffedd81SKristof Provost assert ip.dst == "192.0.2.2" 152fffedd81SKristof Provost assert icmp.type == 3 # dest-unreach 153fffedd81SKristof Provost assert icmp.code == 4 154fffedd81SKristof Provost assert icmp.nexthopmtu == 1492 155fffedd81SKristof Provost else: 156fffedd81SKristof Provost # Expect echo reply 157fffedd81SKristof Provost assert ip.src == "198.51.100.2" 158fffedd81SKristof Provost assert ip.dst == "192.0.2.2" 159fffedd81SKristof Provost assert icmp.type == 0 # "echo-reply" 160fffedd81SKristof Provost assert icmp.code == 0 161fffedd81SKristof Provost 162fffedd81SKristof Provost return 163fffedd81SKristof Provost 164fffedd81SKristof Provost @pytest.mark.require_user("root") 16565cc5af1SJose Luis Duran @pytest.mark.require_progs(["scapy"]) 166fffedd81SKristof Provost def test_fragmentation_needed(self): 167fffedd81SKristof Provost ToolsHelper.print_output("/sbin/route add default 192.0.2.1") 168fffedd81SKristof Provost 169fffedd81SKristof Provost ToolsHelper.print_output("/sbin/ping -c 1 198.51.100.2") 170fffedd81SKristof Provost ToolsHelper.print_output("/sbin/ping -c 1 -D -s 1472 198.51.100.2") 171fffedd81SKristof Provost 172fffedd81SKristof Provost # Import in the correct vnet, so at to not confuse Scapy 173fffedd81SKristof Provost import scapy.all as sp 174fffedd81SKristof Provost 175fffedd81SKristof Provost self.check_icmp_echo(sp, 128) 176fffedd81SKristof Provost self.check_icmp_echo(sp, 1464) 177fffedd81SKristof Provost self.check_icmp_echo(sp, 1468) 178