diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile index ebeec7f85650..599ff7c75378 100644 --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -1,51 +1,53 @@ # $FreeBSD$ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/netpfil/pf TESTS_SUBDIRS+= ioctl ATF_TESTS_SH+= altq \ anchor \ pass_block \ checksum \ forward \ fragmentation \ get_state \ icmp \ killstate \ macro \ map_e \ names \ nat \ rules_counter \ set_tos \ src_track \ rdr \ ridentifier \ route_to \ syncookie \ synproxy \ set_skip \ pfsync \ proxy \ table \ tos ${PACKAGE}FILES+= utils.subr \ echo_inetd.conf \ CVE-2019-5597.py \ CVE-2019-5598.py \ fragcommon.py \ frag-overindex.py \ frag-overlimit.py \ - frag-overreplace.py + frag-overreplace.py \ + pfsync_defer.py ${PACKAGE}FILESMODE_CVE-2019-5597.py= 0555 ${PACKAGE}FILESMODE_CVE-2019-5598.py= 0555 ${PACKAGE}FILESMODE_fragcommon.py= 0555 ${PACKAGE}FILESMODE_frag-overindex.py= 0555 ${PACKAGE}FILESMODE_frag-overlimit.py= 0555 ${PACKAGE}FILESMODE_frag-overreplace.py= 0555 +${PACKAGE}FILESMODE_pfsync_defer.py= 0555 .include diff --git a/tests/sys/netpfil/pf/pfsync.sh b/tests/sys/netpfil/pf/pfsync.sh index a6fc7ec9f7e9..1422c0b47c49 100755 --- a/tests/sys/netpfil/pf/pfsync.sh +++ b/tests/sys/netpfil/pf/pfsync.sh @@ -1,187 +1,256 @@ # $FreeBSD$ # # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # # Copyright (c) 2018 Orange Business Services # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. . $(atf_get_srcdir)/utils.subr +common_dir=$(atf_get_srcdir)/../common + atf_test_case "basic" "cleanup" basic_head() { atf_set descr 'Basic pfsync test' atf_set require.user root } basic_body() { common_body } common_body() { defer=$1 pfsynct_init epair_sync=$(vnet_mkepair) epair_one=$(vnet_mkepair) epair_two=$(vnet_mkepair) vnet_mkjail one ${epair_one}a ${epair_sync}a vnet_mkjail two ${epair_two}a ${epair_sync}b # pfsync interface jexec one ifconfig ${epair_sync}a 192.0.2.1/24 up jexec one ifconfig ${epair_one}a 198.51.100.1/24 up jexec one ifconfig pfsync0 \ syncdev ${epair_sync}a \ maxupd 1 \ $defer \ up jexec two ifconfig ${epair_two}a 198.51.100.2/24 up jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up jexec two ifconfig pfsync0 \ syncdev ${epair_sync}b \ maxupd 1 \ $defer \ up # Enable pf! jexec one pfctl -e pft_set_rules one \ "set skip on ${epair_sync}a" \ "pass keep state" jexec two pfctl -e pft_set_rules two \ "set skip on ${epair_sync}b" \ "pass keep state" ifconfig ${epair_one}b 198.51.100.254/24 up ping -c 1 -S 198.51.100.254 198.51.100.1 # Give pfsync time to do its thing sleep 2 if ! jexec two pfctl -s states | grep icmp | grep 198.51.100.1 | \ grep 198.51.100.2 ; then atf_fail "state not found on synced host" fi } basic_cleanup() { pfsynct_cleanup } +atf_test_case "basic_defer" "cleanup" +basic_defer_head() +{ + atf_set descr 'Basic defer mode pfsync test' + atf_set require.user root +} + +basic_defer_body() +{ + common_body defer +} + +basic_defer_cleanup() +{ + pfsynct_cleanup +} + atf_test_case "defer" "cleanup" defer_head() { atf_set descr 'Defer mode pfsync test' atf_set require.user root } defer_body() { - common_body defer + pfsynct_init + + epair_sync=$(vnet_mkepair) + epair_in=$(vnet_mkepair) + epair_out=$(vnet_mkepair) + + vnet_mkjail alcatraz ${epair_sync}a ${epair_in}a ${epair_out}a + + jexec alcatraz ifconfig ${epair_sync}a 192.0.2.1/24 up + jexec alcatraz ifconfig ${epair_out}a 198.51.100.1/24 up + jexec alcatraz ifconfig ${epair_in}a 203.0.113.1/24 up + jexec alcatraz arp -s 203.0.113.2 00:01:02:03:04:05 + jexec alcatraz sysctl net.inet.ip.forwarding=1 + + jexec alcatraz ifconfig pfsync0 \ + syncdev ${epair_sync}a \ + maxupd 1 \ + defer \ + up + + ifconfig ${epair_sync}b 192.0.2.2/24 up + ifconfig ${epair_out}b 198.51.100.2/24 up + ifconfig ${epair_in}b up + route add -net 203.0.113.0/24 198.51.100.1 + + # Enable pf + jexec alcatraz pfctl -e + pft_set_rules alcatraz \ + "set skip on ${epair_sync}a" \ + "pass keep state" + + atf_check -s exit:0 env PYTHONPATH=${common_dir} \ + $(atf_get_srcdir)/pfsync_defer.py \ + --syncdev ${epair_sync}b \ + --indev ${epair_in}b \ + --outdev ${epair_out}b + + # Now disable defer mode and expect failure. + jexec alcatraz ifconfig pfsync0 -defer + + # Flush state + pft_set_rules alcatraz \ + "set skip on ${epair_sync}a" \ + "pass keep state" + + atf_check -s exit:1 env PYTHONPATH=${common_dir} \ + $(atf_get_srcdir)/pfsync_defer.py \ + --syncdev ${epair_sync}b \ + --indev ${epair_in}b \ + --outdev ${epair_out}b } defer_cleanup() { pfsynct_cleanup } atf_test_case "bulk" "cleanup" bulk_head() { atf_set descr 'Test bulk updates' atf_set require.user root } bulk_body() { pfsynct_init epair_sync=$(vnet_mkepair) epair_one=$(vnet_mkepair) epair_two=$(vnet_mkepair) vnet_mkjail one ${epair_one}a ${epair_sync}a vnet_mkjail two ${epair_two}a ${epair_sync}b # pfsync interface jexec one ifconfig ${epair_sync}a 192.0.2.1/24 up jexec one ifconfig ${epair_one}a 198.51.100.1/24 up jexec one ifconfig pfsync0 \ syncdev ${epair_sync}a \ maxupd 1\ up jexec two ifconfig ${epair_two}a 198.51.100.2/24 up jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up # Enable pf jexec one pfctl -e pft_set_rules one \ "set skip on ${epair_sync}a" \ "pass keep state" jexec two pfctl -e pft_set_rules two \ "set skip on ${epair_sync}b" \ "pass keep state" ifconfig ${epair_one}b 198.51.100.254/24 up # Create state prior to setting up pfsync ping -c 1 -S 198.51.100.254 198.51.100.1 # Wait before setting up pfsync on two, so we don't accidentally catch # the update anyway. sleep 1 # Now set up pfsync in jail two jexec two ifconfig pfsync0 \ syncdev ${epair_sync}b \ up # Give pfsync time to do its thing sleep 2 jexec two pfctl -s states if ! jexec two pfctl -s states | grep icmp | grep 198.51.100.1 | \ grep 198.51.100.2 ; then atf_fail "state not found on synced host" fi } bulk_cleanup() { pfsynct_cleanup } atf_init_test_cases() { atf_add_test_case "basic" + atf_add_test_case "basic_defer" atf_add_test_case "defer" atf_add_test_case "bulk" } diff --git a/tests/sys/netpfil/pf/pfsync_defer.py b/tests/sys/netpfil/pf/pfsync_defer.py new file mode 100644 index 000000000000..4a691240b466 --- /dev/null +++ b/tests/sys/netpfil/pf/pfsync_defer.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2021 Rubicon Communications, LLC (Netgate) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# + +import argparse +import logging +logging.getLogger("scapy").setLevel(logging.CRITICAL) +import scapy.all as sp +import socket +import sys +import time +from sniffer import Sniffer + +got_pfsync = False +got_ping = False +sent_ping = False + +def check_pfsync(args, packet): + global got_pfsync + global got_ping + + ip = packet.getlayer(sp.IP) + if not ip: + return False + + if ip.proto != 240: + return False + + # Only look at the first packet + if got_pfsync: + return False + + got_pfsync = time.time() + + return False + +def check_reply(args, packet): + global got_pfsync + global got_ping + + if not packet.getlayer(sp.ICMP): + return False + + # Only look at the first packet + if got_ping: + return False + + got_ping = time.time() + + return False + +def ping(intf): + global sent_ping + + ether = sp.Ether() + ip = sp.IP(dst="203.0.113.2", src="198.51.100.2") + icmp = sp.ICMP(type='echo-request') + raw = sp.raw(bytes.fromhex('00010203')) + + req = ether / ip / icmp / raw + sp.sendp(req, iface=intf, verbose=False) + sent_ping = time.time() + +def main(): + global got_pfsync + global got_ping + global sent_ping + + parser = argparse.ArgumentParser("pfsync_defer.py", + description="pfsync defer mode test") + parser.add_argument('--syncdev', nargs=1, + required=True, + help='The pfsync interface') + parser.add_argument('--outdev', nargs=1, + required=True, + help='The interface we will send packets on') + parser.add_argument('--indev', nargs=1, + required=True, + help='The interface we will receive packets on') + + args = parser.parse_args() + + syncmon = Sniffer(args, check_pfsync, args.syncdev[0]) + datamon = Sniffer(args, check_reply, args.indev[0]) + + # Send traffic on datadev, which should create state and produce a pfsync message + ping(args.outdev[0]) + + syncmon.join() + datamon.join() + + if not got_pfsync: + sys.exit(1) + + if not got_ping: + sys.exit(1) + + if got_pfsync > got_ping: + sys.exit(1) + + # Deferred packets are delayed up to 20ms (unless the pfsync peer, which we + # don't have here, acks their state update earlier) + if (sent_ping + 0.020) > got_ping: + sys.exit(1) + +if __name__ == '__main__': + main()