1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2025 Rubicon Communications, LLC (Netgate) 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26 27import sys 28import pytest 29import random 30import socket 31import selectors 32import threading 33import time 34from atf_python.sys.net.tools import ToolsHelper 35from atf_python.sys.net.vnet import VnetTestTemplate 36 37class DelayedSend(threading.Thread): 38 def __init__(self, packet): 39 threading.Thread.__init__(self) 40 self._packet = packet 41 42 self.start() 43 44 def run(self): 45 import scapy.all as sp 46 time.sleep(1) 47 sp.send(self._packet) 48 49class TCPClient: 50 def __init__(self, src, dst, sport, dport, sp): 51 self.src = src 52 self.dst = dst 53 self.sport = sport 54 self.dport = dport 55 self.sp = sp 56 self.seq = random.randrange(1, (2**32)-1) 57 self.ack = 0 58 59 def syn(self): 60 syn = self.sp.IP(src=self.src, dst=self.dst) \ 61 / self.sp.TCP(sport=self.sport, dport=self.dport, flags="S", seq=self.seq) 62 return syn 63 64 def connect(self): 65 syn = self.syn() 66 r = self.sp.sr1(syn, timeout=5) 67 68 assert r 69 t = r.getlayer(self.sp.TCP) 70 assert t 71 assert t.sport == self.dport 72 assert t.dport == self.sport 73 assert t.flags == "SA" 74 75 self.seq += 1 76 self.ack = t.seq + 1 77 ack = self.sp.IP(src=self.src, dst=self.dst) \ 78 / self.sp.TCP(sport=self.sport, dport=self.dport, flags="A", ack=self.ack, seq=self.seq) 79 self.sp.send(ack) 80 81 def send(self, data): 82 length = len(data) 83 pkt = self.sp.IP(src=self.src, dst=self.dst) \ 84 / self.sp.TCP(sport=self.sport, dport=self.dport, ack=self.ack, seq=self.seq, flags="") \ 85 / self.sp.Raw(data) 86 self.seq += length 87 pkt.show() 88 self.sp.send(pkt) 89 90class TestTcp(VnetTestTemplate): 91 REQUIRED_MODULES = [ "pf" ] 92 TOPOLOGY = { 93 "vnet1": {"ifaces": ["if1"]}, 94 "vnet2": {"ifaces": ["if1"]}, 95 "if1": {"prefixes4": [("192.0.2.1/24", "192.0.2.2/24")]}, 96 } 97 98 def vnet2_handler(self, vnet): 99 ToolsHelper.print_output("/usr/sbin/arp -s 192.0.2.3 00:01:02:03:04:05") 100 ToolsHelper.print_output("/sbin/pfctl -e") 101 ToolsHelper.pf_rules([ 102 "pass" 103 ]) 104 ToolsHelper.print_output("/sbin/pfctl -x loud") 105 106 # Start TCP listener 107 sel = selectors.DefaultSelector() 108 t = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 109 t.bind(("0.0.0.0", 1234)) 110 t.listen(100) 111 t.setblocking(False) 112 sel.register(t, selectors.EVENT_READ, data=None) 113 114 while True: 115 events = sel.select(timeout=2) 116 for key, mask in events: 117 sock = key.fileobj 118 if key.data is None: 119 conn, addr = sock.accept() 120 print(f"Accepted connection from {addr}") 121 events = selectors.EVENT_READ | selectors.EVENT_WRITE 122 sel.register(conn, events, data="TCP") 123 else: 124 if mask & selectors.EVENT_READ: 125 recv_data = sock.recv(1024) 126 print(f"Received TCP {recv_data}") 127 ToolsHelper.print_output("/sbin/pfctl -ss -vv") 128 sock.send(recv_data) 129 130 @pytest.mark.require_user("root") 131 @pytest.mark.require_progs(["scapy"]) 132 def test_challenge_ack(self): 133 vnet = self.vnet_map["vnet1"] 134 ifname = vnet.iface_alias_map["if1"].name 135 136 # Import in the correct vnet, so at to not confuse Scapy 137 import scapy.all as sp 138 139 #time.sleep(30) 140 141 a = TCPClient("192.0.2.3", "192.0.2.2", 1234, 1234, sp) 142 a.connect() 143 a.send(b"foo") 144 145 b = TCPClient("192.0.2.3", "192.0.2.2", 1234, 1234, sp) 146 syn = b.syn() 147 syn.show() 148 s = DelayedSend(syn) 149 packets = sp.sniff(iface=ifname, timeout=3) 150 found = False 151 for p in packets: 152 ip = p.getlayer(sp.IP) 153 if not ip: 154 continue 155 tcp = p.getlayer(sp.TCP) 156 if not tcp: 157 continue 158 159 if ip.src != "192.0.2.2": 160 continue 161 162 p.show() 163 164 assert ip.dst == "192.0.2.3" 165 assert tcp.sport == 1234 166 assert tcp.dport == 1234 167 assert tcp.flags == "A" 168 169 # We only expect one 170 assert not found 171 found = True 172 173 assert found 174