xref: /freebsd/tests/sys/netpfil/pf/pfsync_defer.py (revision f25ceb05af666f22ac3bf14332a6a3ec679a3332)
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