137b6e0d8SKristof Provost# 237b6e0d8SKristof Provost# SPDX-License-Identifier: BSD-2-Clause 337b6e0d8SKristof Provost# 437b6e0d8SKristof Provost# Copyright (c) 2025 Rubicon Communications, LLC (Netgate) 537b6e0d8SKristof Provost# 637b6e0d8SKristof Provost# Redistribution and use in source and binary forms, with or without 737b6e0d8SKristof Provost# modification, are permitted provided that the following conditions 837b6e0d8SKristof Provost# are met: 937b6e0d8SKristof Provost# 1. Redistributions of source code must retain the above copyright 1037b6e0d8SKristof Provost# notice, this list of conditions and the following disclaimer. 1137b6e0d8SKristof Provost# 2. Redistributions in binary form must reproduce the above copyright 1237b6e0d8SKristof Provost# notice, this list of conditions and the following disclaimer in the 1337b6e0d8SKristof Provost# documentation and/or other materials provided with the distribution. 1437b6e0d8SKristof Provost# 1537b6e0d8SKristof Provost# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 1637b6e0d8SKristof Provost# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1737b6e0d8SKristof Provost# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1837b6e0d8SKristof Provost# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 1937b6e0d8SKristof Provost# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2037b6e0d8SKristof Provost# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2137b6e0d8SKristof Provost# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2237b6e0d8SKristof Provost# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2337b6e0d8SKristof Provost# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2437b6e0d8SKristof Provost# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2537b6e0d8SKristof Provost# SUCH DAMAGE. 2637b6e0d8SKristof Provost 2737b6e0d8SKristof Provostimport sys 2837b6e0d8SKristof Provostimport pytest 2937b6e0d8SKristof Provostimport random 3037b6e0d8SKristof Provostimport socket 3137b6e0d8SKristof Provostimport selectors 32*63cc817aSKristof Provostfrom utils import DelayedSend 3337b6e0d8SKristof Provostfrom atf_python.sys.net.tools import ToolsHelper 3437b6e0d8SKristof Provostfrom atf_python.sys.net.vnet import VnetTestTemplate 3537b6e0d8SKristof Provost 3637b6e0d8SKristof Provostclass TCPClient: 3737b6e0d8SKristof Provost def __init__(self, src, dst, sport, dport, sp): 3837b6e0d8SKristof Provost self.src = src 3937b6e0d8SKristof Provost self.dst = dst 4037b6e0d8SKristof Provost self.sport = sport 4137b6e0d8SKristof Provost self.dport = dport 4237b6e0d8SKristof Provost self.sp = sp 4337b6e0d8SKristof Provost self.seq = random.randrange(1, (2**32)-1) 4437b6e0d8SKristof Provost self.ack = 0 4537b6e0d8SKristof Provost 4637b6e0d8SKristof Provost def syn(self): 4737b6e0d8SKristof Provost syn = self.sp.IP(src=self.src, dst=self.dst) \ 4837b6e0d8SKristof Provost / self.sp.TCP(sport=self.sport, dport=self.dport, flags="S", seq=self.seq) 4937b6e0d8SKristof Provost return syn 5037b6e0d8SKristof Provost 5137b6e0d8SKristof Provost def connect(self): 5237b6e0d8SKristof Provost syn = self.syn() 5337b6e0d8SKristof Provost r = self.sp.sr1(syn, timeout=5) 5437b6e0d8SKristof Provost 5537b6e0d8SKristof Provost assert r 5637b6e0d8SKristof Provost t = r.getlayer(self.sp.TCP) 5737b6e0d8SKristof Provost assert t 5837b6e0d8SKristof Provost assert t.sport == self.dport 5937b6e0d8SKristof Provost assert t.dport == self.sport 6037b6e0d8SKristof Provost assert t.flags == "SA" 6137b6e0d8SKristof Provost 6237b6e0d8SKristof Provost self.seq += 1 6337b6e0d8SKristof Provost self.ack = t.seq + 1 6437b6e0d8SKristof Provost ack = self.sp.IP(src=self.src, dst=self.dst) \ 6537b6e0d8SKristof Provost / self.sp.TCP(sport=self.sport, dport=self.dport, flags="A", ack=self.ack, seq=self.seq) 6637b6e0d8SKristof Provost self.sp.send(ack) 6737b6e0d8SKristof Provost 6837b6e0d8SKristof Provost def send(self, data): 6937b6e0d8SKristof Provost length = len(data) 7037b6e0d8SKristof Provost pkt = self.sp.IP(src=self.src, dst=self.dst) \ 7137b6e0d8SKristof Provost / self.sp.TCP(sport=self.sport, dport=self.dport, ack=self.ack, seq=self.seq, flags="") \ 7237b6e0d8SKristof Provost / self.sp.Raw(data) 7337b6e0d8SKristof Provost self.seq += length 7437b6e0d8SKristof Provost pkt.show() 7537b6e0d8SKristof Provost self.sp.send(pkt) 7637b6e0d8SKristof Provost 7737b6e0d8SKristof Provostclass TestTcp(VnetTestTemplate): 7837b6e0d8SKristof Provost REQUIRED_MODULES = [ "pf" ] 7937b6e0d8SKristof Provost TOPOLOGY = { 8037b6e0d8SKristof Provost "vnet1": {"ifaces": ["if1"]}, 8137b6e0d8SKristof Provost "vnet2": {"ifaces": ["if1"]}, 8237b6e0d8SKristof Provost "if1": {"prefixes4": [("192.0.2.1/24", "192.0.2.2/24")]}, 8337b6e0d8SKristof Provost } 8437b6e0d8SKristof Provost 8537b6e0d8SKristof Provost def vnet2_handler(self, vnet): 8637b6e0d8SKristof Provost ToolsHelper.print_output("/usr/sbin/arp -s 192.0.2.3 00:01:02:03:04:05") 8737b6e0d8SKristof Provost ToolsHelper.print_output("/sbin/pfctl -e") 8837b6e0d8SKristof Provost ToolsHelper.pf_rules([ 8937b6e0d8SKristof Provost "pass" 9037b6e0d8SKristof Provost ]) 9137b6e0d8SKristof Provost ToolsHelper.print_output("/sbin/pfctl -x loud") 9237b6e0d8SKristof Provost 9337b6e0d8SKristof Provost # Start TCP listener 9437b6e0d8SKristof Provost sel = selectors.DefaultSelector() 9537b6e0d8SKristof Provost t = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 9637b6e0d8SKristof Provost t.bind(("0.0.0.0", 1234)) 9737b6e0d8SKristof Provost t.listen(100) 9837b6e0d8SKristof Provost t.setblocking(False) 9937b6e0d8SKristof Provost sel.register(t, selectors.EVENT_READ, data=None) 10037b6e0d8SKristof Provost 10137b6e0d8SKristof Provost while True: 10237b6e0d8SKristof Provost events = sel.select(timeout=2) 10337b6e0d8SKristof Provost for key, mask in events: 10437b6e0d8SKristof Provost sock = key.fileobj 10537b6e0d8SKristof Provost if key.data is None: 10637b6e0d8SKristof Provost conn, addr = sock.accept() 10737b6e0d8SKristof Provost print(f"Accepted connection from {addr}") 10837b6e0d8SKristof Provost events = selectors.EVENT_READ | selectors.EVENT_WRITE 10937b6e0d8SKristof Provost sel.register(conn, events, data="TCP") 11037b6e0d8SKristof Provost else: 11137b6e0d8SKristof Provost if mask & selectors.EVENT_READ: 11237b6e0d8SKristof Provost recv_data = sock.recv(1024) 11337b6e0d8SKristof Provost print(f"Received TCP {recv_data}") 11437b6e0d8SKristof Provost ToolsHelper.print_output("/sbin/pfctl -ss -vv") 11537b6e0d8SKristof Provost sock.send(recv_data) 11637b6e0d8SKristof Provost 11737b6e0d8SKristof Provost @pytest.mark.require_user("root") 11837b6e0d8SKristof Provost @pytest.mark.require_progs(["scapy"]) 11937b6e0d8SKristof Provost def test_challenge_ack(self): 12037b6e0d8SKristof Provost vnet = self.vnet_map["vnet1"] 12137b6e0d8SKristof Provost ifname = vnet.iface_alias_map["if1"].name 12237b6e0d8SKristof Provost 12337b6e0d8SKristof Provost # Import in the correct vnet, so at to not confuse Scapy 12437b6e0d8SKristof Provost import scapy.all as sp 12537b6e0d8SKristof Provost 12637b6e0d8SKristof Provost a = TCPClient("192.0.2.3", "192.0.2.2", 1234, 1234, sp) 12737b6e0d8SKristof Provost a.connect() 12837b6e0d8SKristof Provost a.send(b"foo") 12937b6e0d8SKristof Provost 13037b6e0d8SKristof Provost b = TCPClient("192.0.2.3", "192.0.2.2", 1234, 1234, sp) 13137b6e0d8SKristof Provost syn = b.syn() 13237b6e0d8SKristof Provost syn.show() 13337b6e0d8SKristof Provost s = DelayedSend(syn) 13437b6e0d8SKristof Provost packets = sp.sniff(iface=ifname, timeout=3) 13537b6e0d8SKristof Provost found = False 13637b6e0d8SKristof Provost for p in packets: 13737b6e0d8SKristof Provost ip = p.getlayer(sp.IP) 13837b6e0d8SKristof Provost if not ip: 13937b6e0d8SKristof Provost continue 14037b6e0d8SKristof Provost tcp = p.getlayer(sp.TCP) 14137b6e0d8SKristof Provost if not tcp: 14237b6e0d8SKristof Provost continue 14337b6e0d8SKristof Provost 14437b6e0d8SKristof Provost if ip.src != "192.0.2.2": 14537b6e0d8SKristof Provost continue 14637b6e0d8SKristof Provost 14737b6e0d8SKristof Provost p.show() 14837b6e0d8SKristof Provost 14937b6e0d8SKristof Provost assert ip.dst == "192.0.2.3" 15037b6e0d8SKristof Provost assert tcp.sport == 1234 15137b6e0d8SKristof Provost assert tcp.dport == 1234 15237b6e0d8SKristof Provost assert tcp.flags == "A" 15337b6e0d8SKristof Provost 15437b6e0d8SKristof Provost # We only expect one 15537b6e0d8SKristof Provost assert not found 15637b6e0d8SKristof Provost found = True 15737b6e0d8SKristof Provost 15837b6e0d8SKristof Provost assert found 159