1#!/usr/bin/env python3 2# 3# SPDX-License-Identifier: BSD-2-Clause 4# 5# Copyright (c) 2021 Rubicon Communications, LLC (Netgate) 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions 9# are met: 10# 1. Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# 2. Redistributions in binary form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in the 14# documentation and/or other materials provided with the distribution. 15# 16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26# SUCH DAMAGE. 27# 28 29import argparse 30import logging 31logging.getLogger("scapy").setLevel(logging.CRITICAL) 32import scapy.all as sp 33import socket 34import sys 35import time 36from sniffer import Sniffer 37 38got_pfsync = False 39got_ping = False 40sent_ping = False 41 42def check_pfsync(args, packet): 43 global got_pfsync 44 global got_ping 45 46 ip = packet.getlayer(sp.IP) 47 if not ip: 48 return False 49 50 if ip.proto != 240: 51 return False 52 53 # Only look at the first packet 54 if got_pfsync: 55 return False 56 57 got_pfsync = time.time() 58 59 return False 60 61def check_reply(args, packet): 62 global got_pfsync 63 global got_ping 64 65 if not packet.getlayer(sp.ICMP): 66 return False 67 68 # Only look at the first packet 69 if got_ping: 70 return False 71 72 got_ping = time.time() 73 74 return False 75 76def ping(intf): 77 global sent_ping 78 79 ether = sp.Ether() 80 ip = sp.IP(dst="203.0.113.2", src="198.51.100.2") 81 icmp = sp.ICMP(type='echo-request') 82 raw = sp.raw(bytes.fromhex('00010203')) 83 84 req = ether / ip / icmp / raw 85 sp.sendp(req, iface=intf, verbose=False) 86 sent_ping = time.time() 87 88def main(): 89 global got_pfsync 90 global got_ping 91 global sent_ping 92 93 parser = argparse.ArgumentParser("pfsync_defer.py", 94 description="pfsync defer mode test") 95 parser.add_argument('--syncdev', nargs=1, 96 required=True, 97 help='The pfsync interface') 98 parser.add_argument('--outdev', nargs=1, 99 required=True, 100 help='The interface we will send packets on') 101 parser.add_argument('--indev', nargs=1, 102 required=True, 103 help='The interface we will receive packets on') 104 105 args = parser.parse_args() 106 107 syncmon = Sniffer(args, check_pfsync, args.syncdev[0]) 108 datamon = Sniffer(args, check_reply, args.indev[0]) 109 110 # Send traffic on datadev, which should create state and produce a pfsync message 111 ping(args.outdev[0]) 112 113 syncmon.join() 114 datamon.join() 115 116 if not got_pfsync: 117 sys.exit(1) 118 119 if not got_ping: 120 sys.exit(2) 121 122 # Deferred packets are delayed around 2.5s (unless the pfsync peer, which 123 # we don't have here, acks their state update earlier) 124 # Expect at least a second of delay, to be somewhat robust against 125 # scheduling-induced jitter. 126 if (sent_ping + 1) > got_ping: 127 sys.exit(3) 128 129if __name__ == '__main__': 130 main() 131