160a3a371SKristof Provost#!/usr/bin/env python3 260a3a371SKristof Provost# 360a3a371SKristof Provost# SPDX-License-Identifier: BSD-2-Clause 460a3a371SKristof Provost# 560a3a371SKristof Provost# Copyright (c) 2021 Rubicon Communications, LLC (Netgate) 660a3a371SKristof Provost# 760a3a371SKristof Provost# Redistribution and use in source and binary forms, with or without 860a3a371SKristof Provost# modification, are permitted provided that the following conditions 960a3a371SKristof Provost# are met: 1060a3a371SKristof Provost# 1. Redistributions of source code must retain the above copyright 1160a3a371SKristof Provost# notice, this list of conditions and the following disclaimer. 1260a3a371SKristof Provost# 2. Redistributions in binary form must reproduce the above copyright 1360a3a371SKristof Provost# notice, this list of conditions and the following disclaimer in the 1460a3a371SKristof Provost# documentation and/or other materials provided with the distribution. 1560a3a371SKristof Provost# 1660a3a371SKristof Provost# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 1760a3a371SKristof Provost# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1860a3a371SKristof Provost# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1960a3a371SKristof Provost# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 2060a3a371SKristof Provost# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2160a3a371SKristof Provost# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2260a3a371SKristof Provost# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2360a3a371SKristof Provost# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2460a3a371SKristof Provost# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2560a3a371SKristof Provost# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2660a3a371SKristof Provost# SUCH DAMAGE. 2760a3a371SKristof Provost# 2860a3a371SKristof Provost 2960a3a371SKristof Provostimport argparse 3060a3a371SKristof Provostimport logging 3160a3a371SKristof Provostlogging.getLogger("scapy").setLevel(logging.CRITICAL) 3260a3a371SKristof Provostimport scapy.all as sp 3360a3a371SKristof Provostimport socket 3460a3a371SKristof Provostimport sys 3560a3a371SKristof Provostimport time 3660a3a371SKristof Provostfrom sniffer import Sniffer 3760a3a371SKristof Provost 3860a3a371SKristof Provostgot_pfsync = False 3960a3a371SKristof Provostgot_ping = False 4060a3a371SKristof Provostsent_ping = False 4160a3a371SKristof Provost 4260a3a371SKristof Provostdef check_pfsync(args, packet): 4360a3a371SKristof Provost global got_pfsync 4460a3a371SKristof Provost global got_ping 4560a3a371SKristof Provost 4660a3a371SKristof Provost ip = packet.getlayer(sp.IP) 4760a3a371SKristof Provost if not ip: 4860a3a371SKristof Provost return False 4960a3a371SKristof Provost 5060a3a371SKristof Provost if ip.proto != 240: 5160a3a371SKristof Provost return False 5260a3a371SKristof Provost 5360a3a371SKristof Provost # Only look at the first packet 5460a3a371SKristof Provost if got_pfsync: 5560a3a371SKristof Provost return False 5660a3a371SKristof Provost 5760a3a371SKristof Provost got_pfsync = time.time() 5860a3a371SKristof Provost 5960a3a371SKristof Provost return False 6060a3a371SKristof Provost 6160a3a371SKristof Provostdef check_reply(args, packet): 6260a3a371SKristof Provost global got_pfsync 6360a3a371SKristof Provost global got_ping 6460a3a371SKristof Provost 6560a3a371SKristof Provost if not packet.getlayer(sp.ICMP): 6660a3a371SKristof Provost return False 6760a3a371SKristof Provost 6860a3a371SKristof Provost # Only look at the first packet 6960a3a371SKristof Provost if got_ping: 7060a3a371SKristof Provost return False 7160a3a371SKristof Provost 7260a3a371SKristof Provost got_ping = time.time() 7360a3a371SKristof Provost 7460a3a371SKristof Provost return False 7560a3a371SKristof Provost 7660a3a371SKristof Provostdef ping(intf): 7760a3a371SKristof Provost global sent_ping 7860a3a371SKristof Provost 7960a3a371SKristof Provost ether = sp.Ether() 8060a3a371SKristof Provost ip = sp.IP(dst="203.0.113.2", src="198.51.100.2") 8160a3a371SKristof Provost icmp = sp.ICMP(type='echo-request') 8260a3a371SKristof Provost raw = sp.raw(bytes.fromhex('00010203')) 8360a3a371SKristof Provost 8460a3a371SKristof Provost req = ether / ip / icmp / raw 8560a3a371SKristof Provost sp.sendp(req, iface=intf, verbose=False) 8660a3a371SKristof Provost sent_ping = time.time() 8760a3a371SKristof Provost 8860a3a371SKristof Provostdef main(): 8960a3a371SKristof Provost global got_pfsync 9060a3a371SKristof Provost global got_ping 9160a3a371SKristof Provost global sent_ping 9260a3a371SKristof Provost 9360a3a371SKristof Provost parser = argparse.ArgumentParser("pfsync_defer.py", 9460a3a371SKristof Provost description="pfsync defer mode test") 9560a3a371SKristof Provost parser.add_argument('--syncdev', nargs=1, 9660a3a371SKristof Provost required=True, 9760a3a371SKristof Provost help='The pfsync interface') 9860a3a371SKristof Provost parser.add_argument('--outdev', nargs=1, 9960a3a371SKristof Provost required=True, 10060a3a371SKristof Provost help='The interface we will send packets on') 10160a3a371SKristof Provost parser.add_argument('--indev', nargs=1, 10260a3a371SKristof Provost required=True, 10360a3a371SKristof Provost help='The interface we will receive packets on') 10460a3a371SKristof Provost 10560a3a371SKristof Provost args = parser.parse_args() 10660a3a371SKristof Provost 10760a3a371SKristof Provost syncmon = Sniffer(args, check_pfsync, args.syncdev[0]) 10860a3a371SKristof Provost datamon = Sniffer(args, check_reply, args.indev[0]) 10960a3a371SKristof Provost 11060a3a371SKristof Provost # Send traffic on datadev, which should create state and produce a pfsync message 11160a3a371SKristof Provost ping(args.outdev[0]) 11260a3a371SKristof Provost 11360a3a371SKristof Provost syncmon.join() 11460a3a371SKristof Provost datamon.join() 11560a3a371SKristof Provost 11660a3a371SKristof Provost if not got_pfsync: 11760a3a371SKristof Provost sys.exit(1) 11860a3a371SKristof Provost 11960a3a371SKristof Provost if not got_ping: 12006012728SKajetan Staszkiewicz sys.exit(2) 12160a3a371SKristof Provost 122*f25ceb05SKristof Provost # Deferred packets are delayed around 2.5s (unless the pfsync peer, which 123*f25ceb05SKristof Provost # we don't have here, acks their state update earlier) 124*f25ceb05SKristof Provost # Expect at least a second of delay, to be somewhat robust against 125*f25ceb05SKristof Provost # scheduling-induced jitter. 126*f25ceb05SKristof Provost if (sent_ping + 1) > got_ping: 12706012728SKajetan Staszkiewicz sys.exit(3) 12860a3a371SKristof Provost 12960a3a371SKristof Provostif __name__ == '__main__': 13060a3a371SKristof Provost main() 131