diff options
Diffstat (limited to 'tests/sys/netpfil/pf')
-rw-r--r-- | tests/sys/netpfil/pf/Makefile | 5 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/counters.sh | 831 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/fragmentation_pass.sh | 67 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/igmp.py | 6 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/ioctl/validation.c | 25 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/killstate.sh | 63 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/limits.sh | 35 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/mbuf.sh | 26 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/mld.py | 35 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/nat.sh | 45 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/nat44.py | 76 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/nat64.sh | 34 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/once.sh | 132 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/pflog.sh | 59 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/pfsync.sh | 466 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/route_to.sh | 729 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/sctp.sh | 3 | ||||
-rwxr-xr-x | tests/sys/netpfil/pf/src_track.sh | 36 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/table.sh | 100 | ||||
-rw-r--r-- | tests/sys/netpfil/pf/utils.subr | 7 |
20 files changed, 2698 insertions, 82 deletions
diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile index 616ffe560b3a..b363e0b17c76 100644 --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -5,6 +5,7 @@ TESTS_SUBDIRS+= ioctl ATF_TESTS_SH+= altq \ anchor \ + counters \ debug \ divert-to \ dup \ @@ -30,6 +31,7 @@ ATF_TESTS_SH+= altq \ names \ nat \ nat64 \ + once \ pass_block \ pflog \ pflow \ @@ -61,6 +63,7 @@ ATF_TESTS_PYTEST+= header.py ATF_TESTS_PYTEST+= icmp.py ATF_TESTS_PYTEST+= igmp.py ATF_TESTS_PYTEST+= mld.py +ATF_TESTS_PYTEST+= nat44.py ATF_TESTS_PYTEST+= nat64.py ATF_TESTS_PYTEST+= nat66.py ATF_TESTS_PYTEST+= return.py @@ -69,7 +72,7 @@ ATF_TESTS_PYTEST+= tcp.py # Allow tests to run in parallel in their own jails TEST_METADATA+= execenv="jail" -TEST_METADATA+= execenv_jail_params="vnet allow.raw_sockets" +TEST_METADATA+= execenv_jail_params="vnet allow.raw_sockets allow.read_msgbuf" ${PACKAGE}FILES+= \ bsnmpd.conf \ diff --git a/tests/sys/netpfil/pf/counters.sh b/tests/sys/netpfil/pf/counters.sh new file mode 100644 index 000000000000..20d7dc3c6d89 --- /dev/null +++ b/tests/sys/netpfil/pf/counters.sh @@ -0,0 +1,831 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2025 Kajetan Staszkiewicz +# +# 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 + +get_counters() +{ + echo " === rules ===" + rules=$(mktemp) || exit + (jexec router pfctl -qvvsn ; jexec router pfctl -qvvsr) | normalize_pfctl_s > $rules + cat $rules + + echo " === tables ===" + tables=$(mktemp) || exit 1 + jexec router pfctl -qvvsT > $tables + cat $tables + + echo " === states ===" + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + cat $states + + echo " === nodes ===" + nodes=$(mktemp) || exit 1 + jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes + cat $nodes +} + +atf_test_case "match_pass_state" "cleanup" +match_pass_state_head() +{ + atf_set descr 'Counters on match and pass rules' + atf_set require.user root +} + +match_pass_state_body() +{ + setup_router_server_ipv6 + + # Thest counters for a statefull firewall. Expose the behaviour of + # increasing table counters if a table is used multiple times. + # The table "tbl_in" is used both in match and pass rule. It's counters + # are incremented twice. The tables "tbl_out_match" and "tbl_out_pass" + # are used only once and have their countes increased only once. + # Test source node counters for this simple scenario too. + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_in> { ${net_tester_host_tester} }" \ + "table <tbl_out_pass> { ${net_server_host_server} }" \ + "table <tbl_out_match> { ${net_server_host_server} }" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "match in on ${epair_tester}b inet6 proto tcp from <tbl_in> scrub (random-id)" \ + "pass in on ${epair_tester}b inet6 proto tcp from <tbl_in> keep state (max-src-states 3 source-track rule)" \ + "match out on ${epair_server}a inet6 proto tcp to <tbl_out_match> scrub (random-id)" \ + "pass out on ${epair_server}a inet6 proto tcp to <tbl_out_pass> keep state" + + # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. + atf_check -s exit:0 -o match:"This is a test" -x \ + "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" + # Let FINs pass through. + sleep 1 + get_counters + + for rule_regexp in \ + "@3 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ + "@4 pass in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ + "@5 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + "@6 pass out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + table_counters_single="Evaluations: NoMatch: 0 Match: 1 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 4 Bytes: 311 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 6 Bytes: 455 Out/XPass: Packets: 0 Bytes: 0" + table_counters_double="Evaluations: NoMatch: 0 Match: 2 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 12 Bytes: 910 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 8 Bytes: 622 Out/XPass: Packets: 0 Bytes: 0" + for table_test in \ + "tbl_in___${table_counters_double}" \ + "tbl_out_match___${table_counters_single}" \ + "tbl_out_pass___${table_counters_single}" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; + + for state_regexp in \ + "${epair_tester}b tcp ${net_server_host_server}.* <- ${net_tester_host_tester}.* 6:4 pkts, 455:311 bytes, rule 4," \ + "${epair_server}a tcp ${net_server_host_tester}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 6," \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done + + for node_regexp in \ + "${net_tester_host_tester} -> :: .* 10 pkts, 766 bytes, filter rule 4, limit source-track"\ + ; do + grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" + done +} + +match_pass_state_cleanup() +{ + pft_cleanup +} + +atf_test_case "match_pass_no_state" "cleanup" +match_pass_no_state_head() +{ + atf_set descr 'Counters on match and pass rules without keep state' + atf_set require.user root +} + +match_pass_no_state_body() +{ + setup_router_server_ipv6 + + # Test counters for a stateless firewall. + # The table "tbl_in" is used both in match and pass rule in the inbound + # direction. The "In/Pass" counter is incremented twice. The table + # "tbl_inout" matches the same host on inbound and outbound direction. + # It will also be incremented twice. The tables "tbl_out_match" and + # "tbl_out_pass" will have their counters increased only once. + pft_set_rules router \ + "table <tbl_in> { ${net_tester_host_tester} }" \ + "table <tbl_inout> { ${net_tester_host_tester} }" \ + "table <tbl_out_match> { ${net_server_host_server} }" \ + "table <tbl_out_pass> { ${net_server_host_server} }" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "match in on ${epair_tester}b inet6 proto tcp from <tbl_inout>" \ + "match in on ${epair_tester}b inet6 proto tcp from <tbl_in>" \ + "pass in on ${epair_tester}b inet6 proto tcp from <tbl_in> no state" \ + "pass out on ${epair_tester}b inet6 proto tcp to <tbl_in> no state" \ + "match in on ${epair_server}a inet6 proto tcp from <tbl_out_match>" \ + "pass in on ${epair_server}a inet6 proto tcp from <tbl_out_pass> no state" \ + "match out on ${epair_server}a inet6 proto tcp from <tbl_inout> no state" \ + "pass out on ${epair_server}a inet6 proto tcp to <tbl_out_pass> no state" + + # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. + atf_check -s exit:0 -o match:"This is a test" -x \ + "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" + sleep 1 + get_counters + + for rule_regexp in \ + "@3 match in on ${epair_tester}b .* Packets: 6 Bytes: 455 " \ + "@4 match in on ${epair_tester}b .* Packets: 6 Bytes: 455 " \ + "@5 pass in on ${epair_tester}b .* Packets: 6 Bytes: 455 " \ + "@6 pass out on ${epair_tester}b .* Packets: 4 Bytes: 311 " \ + "@7 match in on ${epair_server}a .* Packets: 4 Bytes: 311 " \ + "@8 pass in on ${epair_server}a .* Packets: 4 Bytes: 311 " \ + "@10 pass out on ${epair_server}a .* Packets: 6 Bytes: 455 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + for table_test in \ + "tbl_in___Evaluations: NoMatch: 0 Match: 16 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 12 Bytes: 910 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 4 Bytes: 311 Out/XPass: Packets: 0 Bytes: 0" \ + "tbl_out_match___Evaluations: NoMatch: 0 Match: 4 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 4 Bytes: 311 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 0 Bytes: 0 Out/XPass: Packets: 0 Bytes: 0" \ + "tbl_out_pass___Evaluations: NoMatch: 0 Match: 10 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 4 Bytes: 311 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 6 Bytes: 455 Out/XPass: Packets: 0 Bytes: 0" \ + "tbl_inout___Evaluations: NoMatch: 0 Match: 12 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 6 Bytes: 455 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 6 Bytes: 455 Out/XPass: Packets: 0 Bytes: 0" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; +} + +match_pass_no_state_cleanup() +{ + pft_cleanup +} + +atf_test_case "match_block" "cleanup" +match_block_head() +{ + atf_set descr 'Counters on match and block rules' + atf_set require.user root +} + +match_block_body() +{ + setup_router_server_ipv6 + + # Stateful firewall with a blocking rule. The rule will have its + # counters increased because it matches and applies correctly. + # The "match" rule before the "pass" rule will have its counters + # increased for blocked traffic too. + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_in_match> { ${net_server_host_server} }" \ + "table <tbl_in_block> { ${net_server_host_server} }" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "match in on ${epair_tester}b inet6 proto tcp to <tbl_in_match> scrub (random-id)" \ + "block in on ${epair_tester}b inet6 proto tcp to <tbl_in_block>" \ + "pass out on ${epair_server}a inet6 proto tcp keep state" + + # Wait 3 seconds, that will cause 2 SYNs to be sent out. + echo 'This is a test' | nc -w3 ${net_server_host_server} echo + sleep 1 + get_counters + + for rule_regexp in \ + "@3 match in on ${epair_tester}b .* Packets: 2 Bytes: 160 States: 0 " \ + "@4 block drop in on ${epair_tester}b .* Packets: 2 Bytes: 160 States: 0 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + # OpenBSD has (In|Out)/Match. We don't (yet) have it in FreeBSD + # so we follow the action of the "pass" rule ("block" for this test) + # in "match" rules. + for table_test in \ + "tbl_in_match___Evaluations: NoMatch: 0 Match: 2 In/Block: Packets: 2 Bytes: 160 In/Pass: Packets: 0 Bytes: 0 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 0 Bytes: 0 Out/XPass: Packets: 0 Bytes: 0" \ + "tbl_in_block___Evaluations: NoMatch: 0 Match: 2 In/Block: Packets: 2 Bytes: 160 In/Pass: Packets: 0 Bytes: 0 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 0 Bytes: 0 Out/XPass: Packets: 0 Bytes: 0" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; +} + +match_block_cleanup() +{ + pft_cleanup +} + +atf_test_case "match_fail" "cleanup" +match_fail_head() +{ + atf_set descr 'Counters on match and failing pass rules' + atf_set require.user root +} + +match_fail_body() +{ + setup_router_server_ipv6 + + # Statefull firewall with a failing "pass" rule. + # When the rule can't apply it will not have its counters increased. + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_in_match> { ${net_server_host_server} }" \ + "table <tbl_in_fail> { ${net_server_host_server} }" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "match in on ${epair_tester}b inet6 proto tcp to <tbl_in_match> scrub (random-id)" \ + "pass in on ${epair_tester}b inet6 proto tcp to <tbl_in_fail> keep state (max 1)" \ + "pass out on ${epair_server}a inet6 proto tcp keep state" + + # The first test will pass and increase the counters for all rules. + echo 'This is a test' | nc -w3 ${net_server_host_server} echo + # The second test will go through the "match" rules but fail + # on the "pass" rule due to 'keep state (max 1)'. + # Wait 3 seconds, that will cause 2 SYNs to be sent out. + echo 'This is a test' | nc -w3 ${net_server_host_server} echo + sleep 1 + get_counters + + for rule_regexp in \ + "@3 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ + "@4 pass in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + $table_counters_single="Evaluations: NoMatch: 0 Match: 3 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 6 Bytes: 455 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 4 Bytes: 311 Out/XPass: Packets: 0 Bytes: 0" + for table_test in \ + "tbl_in_match___${table_counters_single}" \ + "tbl_in_fail___${table_counters_single}" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; +} + +match_fail_cleanup() +{ + pft_cleanup +} + +atf_test_case "nat_natonly" "cleanup" +nat_natonly_head() +{ + atf_set descr 'Counters on only a NAT rule creating state' + atf_set require.user root +} + +nat_natonly_body() +{ + setup_router_server_ipv6 + + # NAT is applied on the "nat" rule. + # The "nat" rule matches on pre-NAT addresses. There is no separate + # "pass" rule so the "nat" rule creates the state. + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_src_nat> { ${net_tester_host_tester} }" \ + "table <tbl_dst_nat> { ${net_server_host_server} }" \ + "nat on ${epair_server}a inet6 proto tcp from <tbl_src_nat> to <tbl_dst_nat> -> ${net_server_host_router}" + + # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. + atf_check -s exit:0 -o match:"This is a test" -x \ + "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" + sleep 1 + get_counters + + for rule_regexp in \ + "@0 nat on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + # All tables have counters increased for In/Pass and Out/Pass, not XPass. + table_counters="Evaluations: NoMatch: 0 Match: 1 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 4 Bytes: 311 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 6 Bytes: 455 Out/XPass: Packets: 0 Bytes: 0" + for table_test in \ + "tbl_src_nat___${table_counters}" \ + "tbl_dst_nat___${table_counters}" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; + + for state_regexp in \ + "all tcp ${net_server_host_router}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +nat_natonly_cleanup() +{ + pft_cleanup +} + +atf_test_case "nat_nat" "cleanup" +nat_nat_head() +{ + atf_set descr 'Counters on NAT, match and pass rules with keep state' + atf_set require.user root +} + +nat_nat_body() +{ + setup_router_server_ipv6 + + # NAT is applied in the NAT ruleset. + # The "nat" rule matches on pre-NAT addresses. + # The "match" rule matches on post-NAT addresses. + # The "pass" rule matches on post-NAT addresses and creates the state. + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_src_nat> { ${net_tester_host_tester} }" \ + "table <tbl_dst_nat> { ${net_server_host_server} }" \ + "table <tbl_src_match> { ${net_server_host_router} }" \ + "table <tbl_dst_match> { ${net_server_host_server} }" \ + "table <tbl_src_pass> { ${net_server_host_router} }" \ + "table <tbl_dst_pass> { ${net_server_host_server} }" \ + "nat on ${epair_server}a inet6 proto tcp from <tbl_src_nat> to <tbl_dst_nat> -> ${net_server_host_router}" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "pass in on ${epair_tester}b inet6 proto tcp keep state" \ + "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \ + "pass out on ${epair_server}a inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> keep state" + + # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. + atf_check -s exit:0 -o match:"This is a test" -x \ + "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" + sleep 1 + get_counters + + for rule_regexp in \ + "@0 nat on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + "@4 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + "@5 pass out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. + table_counters="Evaluations: NoMatch: 0 Match: 1 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 4 Bytes: 311 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 6 Bytes: 455 Out/XPass: Packets: 0 Bytes: 0" + for table_test in \ + "tbl_src_nat___${table_counters}" \ + "tbl_dst_nat___${table_counters}" \ + "tbl_src_match___${table_counters}" \ + "tbl_dst_match___${table_counters}" \ + "tbl_src_pass___${table_counters}" \ + "tbl_dst_pass___${table_counters}" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; + + for state_regexp in \ + "${epair_server}a tcp ${net_server_host_router}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 5," \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +nat_nat_cleanup() +{ + pft_cleanup +} + +atf_test_case "nat_match" "cleanup" +nat_match_head() +{ + atf_set descr 'Counters on match with NAT and pass rules' + atf_set require.user root +} + +nat_match_body() +{ + setup_router_server_ipv6 + + # NAT is applied on the "match" rule. + # The "match" rule up to and including the NAT rule match on pre-NAT addresses. + # The "match" rule after NAT matches on post-NAT addresses. + # The "pass" rule matches on post-NAT addresses and creates the state. + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_src_match1> { ${net_tester_host_tester} }" \ + "table <tbl_dst_match1> { ${net_server_host_server} }" \ + "table <tbl_src_match2> { ${net_tester_host_tester} }" \ + "table <tbl_dst_match2> { ${net_server_host_server} }" \ + "table <tbl_src_match3> { ${net_server_host_router} }" \ + "table <tbl_dst_match3> { ${net_server_host_server} }" \ + "table <tbl_src_pass> { ${net_server_host_router} }" \ + "table <tbl_dst_pass> { ${net_server_host_server} }" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "pass in on ${epair_tester}b inet6 proto tcp keep state" \ + "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match1> to <tbl_dst_match1> scrub (random-id)" \ + "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match2> to <tbl_dst_match2> nat-to ${net_server_host_router}" \ + "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match3> to <tbl_dst_match3> scrub (random-id)" \ + "pass out on ${epair_server}a inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> keep state" + + # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. + atf_check -s exit:0 -o match:"This is a test" -x \ + "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" + sleep 1 + get_counters + + for rule_regexp in \ + "@4 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + "@5 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + "@6 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + "@7 pass out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. + table_counters="Evaluations: NoMatch: 0 Match: 1 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 4 Bytes: 311 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 6 Bytes: 455 Out/XPass: Packets: 0 Bytes: 0" + for table_test in \ + "tbl_src_match1___${table_counters}" \ + "tbl_dst_match1___${table_counters}" \ + "tbl_src_match2___${table_counters}" \ + "tbl_dst_match2___${table_counters}" \ + "tbl_src_match3___${table_counters}" \ + "tbl_dst_match3___${table_counters}" \ + "tbl_src_pass___${table_counters}" \ + "tbl_dst_pass___${table_counters}" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; + + for state_regexp in \ + "${epair_server}a tcp ${net_server_host_tester}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 7, " \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +nat_match_cleanup() +{ + pft_cleanup +} + +atf_test_case "nat_pass" "cleanup" +nat_pass_head() +{ + atf_set descr 'Counters on match, and pass with NAT rules' + atf_set require.user root +} + +nat_pass_body() +{ + setup_router_server_ipv6 + + # NAT is applied on the "pass" rule which also creates the state. + # All rules match on pre-NAT addresses. + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_src_match> { ${net_tester_host_tester} }" \ + "table <tbl_dst_match> { ${net_server_host_server} }" \ + "table <tbl_src_pass> { ${net_tester_host_tester} }" \ + "table <tbl_dst_pass> { ${net_server_host_server} }" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "pass in on ${epair_tester}b inet6 proto tcp keep state" \ + "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \ + "pass out on ${epair_server}a inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> nat-to ${net_server_host_router} keep state" + + # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. + atf_check -s exit:0 -o match:"This is a test" -x \ + "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" + sleep 1 + get_counters + + for rule_regexp in \ + "@4 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + "@5 pass out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + table_counters="Evaluations: NoMatch: 0 Match: 1 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 4 Bytes: 311 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 6 Bytes: 455 Out/XPass: Packets: 0 Bytes: 0" + for table_test in \ + "tbl_src_match___${table_counters}" \ + "tbl_dst_match___${table_counters}" \ + "tbl_src_pass___${table_counters}" \ + "tbl_dst_pass___${table_counters}" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; + + for state_regexp in \ + "${epair_server}a tcp ${net_server_host_router}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 5," \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +nat_pass_cleanup() +{ + pft_cleanup +} + +atf_test_case "rdr_match" "cleanup" +rdr_match_head() +{ + atf_set descr 'Counters on match with RDR and pass rules' + atf_set require.user root +} + +rdr_match_body() +{ + setup_router_server_ipv6 + + # Similar to the nat_match test but for the RDR action. + # Hopefully we don't need all other tests duplicated for RDR. + # Send traffic to a non-existing host, RDR it to the server. + # + # The "match" rule up to and including the RDR rule match on pre-RDR dst address. + # The "match" rule after NAT matches on post-RDR dst address. + # The "pass" rule matches on post-RDR dst address. + net_server_host_notserver=${net_server_host_server%%::*}::3 + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_src_match1> { ${net_tester_host_tester} }" \ + "table <tbl_dst_match1> { ${net_server_host_notserver} }" \ + "table <tbl_src_match2> { ${net_tester_host_tester} }" \ + "table <tbl_dst_match2> { ${net_server_host_notserver} }" \ + "table <tbl_src_match3> { ${net_tester_host_tester} }" \ + "table <tbl_dst_match3> { ${net_server_host_server} }" \ + "table <tbl_src_pass> { ${net_tester_host_tester} }" \ + "table <tbl_dst_pass> { ${net_server_host_server} }" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "pass out on ${epair_server}a inet6 proto tcp keep state" \ + "match in on ${epair_tester}b inet6 proto tcp from <tbl_src_match1> to <tbl_dst_match1> scrub (random-id)" \ + "match in on ${epair_tester}b inet6 proto tcp from <tbl_src_match2> to <tbl_dst_match2> rdr-to ${net_server_host_server}" \ + "match in on ${epair_tester}b inet6 proto tcp from <tbl_src_match3> to <tbl_dst_match3> scrub (random-id)" \ + "pass in on ${epair_tester}b inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> keep state" + + # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. + atf_check -s exit:0 -o match:"This is a test" -x \ + "echo 'This is a test' | nc -w3 ${net_server_host_notserver} echo" + sleep 1 + get_counters + + for rule_regexp in \ + "@4 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ + "@5 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ + "@6 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ + "@7 pass in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. + table_counters="Evaluations: NoMatch: 0 Match: 1 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 6 Bytes: 455 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 4 Bytes: 311 Out/XPass: Packets: 0 Bytes: 0" + for table_test in \ + "tbl_src_match1___${table_counters}" \ + "tbl_dst_match1___${table_counters}" \ + "tbl_src_match2___${table_counters}" \ + "tbl_dst_match2___${table_counters}" \ + "tbl_src_match3___${table_counters}" \ + "tbl_dst_match3___${table_counters}" \ + "tbl_src_pass___${table_counters}" \ + "tbl_dst_pass___${table_counters}" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; + + for state_regexp in \ + "${epair_tester}b tcp ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 7, " \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +rdr_match_cleanup() +{ + pft_cleanup +} + +atf_test_case "nat64_in" "cleanup" +nat64_in_head() +{ + atf_set descr 'Counters on match and inbound af-to rules' + atf_set require.user root +} + +nat64_in_body() +{ + setup_router_server_nat64 + + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_src_match> { ${net_tester_6_host_tester} }" \ + "table <tbl_dst_match> { 64:ff9b::${net_server1_4_host_server} }" \ + "table <tbl_src_pass> { ${net_tester_6_host_tester} }" \ + "table <tbl_dst_pass> { 64:ff9b::${net_server1_4_host_server} }" \ + "block log" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "match in on ${epair_tester}b inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \ + "pass in on ${epair_tester}b inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> \ + af-to inet from (${epair_server1}a) \ + keep state" + + # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. + atf_check -s exit:0 -o match:"This is a test" -x \ + "echo 'This is a test' | nc -w3 64:ff9b::${net_server1_4_host_server} echo" + sleep 1 + get_counters + + # The amount of packets is counted properly but sizes are not because + # pd->tot_len is always post-nat, even when updating pre-nat counters. + for rule_regexp in \ + "@3 match in on ${epair_tester}b .* Packets: 10 Bytes: 686 States: 1 " \ + "@4 pass in on ${epair_tester}b .* Packets: 10 Bytes: 686 States: 1 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. + table_counters="Evaluations: NoMatch: 0 Match: 1 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 4 Bytes: 231 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 6 Bytes: 455 Out/XPass: Packets: 0 Bytes: 0" + for table_test in \ + "tbl_src_match___${table_counters}" \ + "tbl_dst_match___${table_counters}" \ + "tbl_src_pass___${table_counters}" \ + "tbl_dst_pass___${table_counters}" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; + + for state_regexp in \ + "${epair_server1}a tcp ${net_server_host_tester}.* 6:4 pkts, 455:231 bytes, rule 4, " \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done + + echo " === interfaces === " + echo " === tester === " + jexec router pfctl -qvvsI -i ${epair_tester}b + echo " === server === " + jexec router pfctl -qvvsI -i ${epair_server1}a + echo " === " +} + +nat64_in_cleanup() +{ + pft_cleanup +} + +atf_test_case "nat64_out" "cleanup" +nat64_out_head() +{ + atf_set descr 'Counters on match and outbound af-to rules' + atf_set require.user root +} + +nat64_out_body() +{ + setup_router_server_nat64 + + # af-to in outbound path requires routes for the pre-af-to traffic. + jexec router route add -inet6 64:ff9b::/96 -iface ${epair_server1}a + + pft_set_rules router \ + "set state-policy if-bound" \ + "table <tbl_src_match> { ${net_tester_6_host_tester} }" \ + "table <tbl_dst_match> { 64:ff9b::${net_server1_4_host_server} }" \ + "table <tbl_src_pass> { ${net_tester_6_host_tester} }" \ + "table <tbl_dst_pass> { 64:ff9b::${net_server1_4_host_server} }" \ + "block log " \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "pass in on ${epair_tester}b inet6 proto tcp keep state" \ + "match out on ${epair_server1}a inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \ + "pass out on ${epair_server1}a inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> \ + af-to inet from (${epair_server1}a) \ + keep state" + + # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. + atf_check -s exit:0 -o match:"This is a test" -x \ + "echo 'This is a test' | nc -w3 64:ff9b::${net_server1_4_host_server} echo" + sleep 1 + get_counters + + for rule_regexp in \ + "@4 match out on ${epair_server1}a .* Packets: 10 Bytes: 686 States: 1 " \ + "@5 pass out on ${epair_server1}a .* Packets: 10 Bytes: 686 States: 1 " \ + ; do + grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" + done + + # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. + table_counters="Evaluations: NoMatch: 0 Match: 1 In/Block: Packets: 0 Bytes: 0 In/Pass: Packets: 4 Bytes: 231 In/XPass: Packets: 0 Bytes: 0 Out/Block: Packets: 0 Bytes: 0 Out/Pass: Packets: 6 Bytes: 455 Out/XPass: Packets: 0 Bytes: 0" + for table_test in \ + "tbl_src_match___${table_counters}" \ + "tbl_dst_match___${table_counters}" \ + "tbl_src_pass___${table_counters}" \ + "tbl_dst_pass___${table_counters}" \ + ; do + table_name=${table_test%%___*} + table_regexp=${table_test##*___} + table=$(mktemp) || exit 1 + cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} + grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" + done; + + for state_regexp in \ + "${epair_server1}a tcp 198.51.100.17:[0-9]+ \(64:ff9b::c633:6412\[7\]\) -> 198.51.100.18:7 \(2001:db8:4200::2\[[0-9]+\]\) .* 6:4 pkts, 455:231 bytes, rule 5," \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done + + echo " === interfaces === " + echo " === tester === " + jexec router pfctl -qvvsI -i ${epair_tester}b + echo " === server === " + jexec router pfctl -qvvsI -i ${epair_server1}a + echo " === " +} + +nat64_out_cleanup() +{ + pft_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case "match_pass_state" + atf_add_test_case "match_pass_no_state" + atf_add_test_case "match_block" + atf_add_test_case "match_fail" + atf_add_test_case "nat_natonly" + atf_add_test_case "nat_nat" + atf_add_test_case "nat_match" + atf_add_test_case "nat_pass" + atf_add_test_case "rdr_match" + atf_add_test_case "nat64_in" + atf_add_test_case "nat64_out" +} diff --git a/tests/sys/netpfil/pf/fragmentation_pass.sh b/tests/sys/netpfil/pf/fragmentation_pass.sh index 5deaba18301d..c749aac793ee 100644 --- a/tests/sys/netpfil/pf/fragmentation_pass.sh +++ b/tests/sys/netpfil/pf/fragmentation_pass.sh @@ -648,6 +648,72 @@ dummynet_fragmented_cleanup() pft_cleanup } +atf_test_case "counters" "cleanup" +counters_head() +{ + atf_set descr 'Test fragment counters' + atf_set require.user root +} + +counters_body() +{ + pft_init + + epair=$(vnet_mkepair) + vnet_mkjail alcatraz ${epair}a + + ifconfig ${epair}b inet 192.0.2.1/24 up + jexec alcatraz ifconfig ${epair}a 192.0.2.2/24 up + + jexec alcatraz pfctl -e + pft_set_rules alcatraz \ + "set reassemble yes" \ + "pass keep state" + + # All fragment counters are zero + counters=$(jexec alcatraz pfctl -si -v | grep -A 4 '^Fragments') + atf_check -s exit:0 -o match:"current entries[[:space:]]+0" \ + echo $counters + atf_check -s exit:0 -o match:"searches[[:space:]]+0" \ + echo $counters + atf_check -s exit:0 -o match:"inserts[[:space:]]+0" \ + echo $counters + atf_check -s exit:0 -o match:"removals[[:space:]]+0" \ + echo $counters + + # They remain zero after we've seen non-fragmented traffic + atf_check -s exit:0 -o ignore \ + ping -c 1 192.0.2.2 + counters=$(jexec alcatraz pfctl -si -v | grep -A 4 '^Fragments') + atf_check -s exit:0 -o match:"current entries[[:space:]]+0" \ + echo $counters + atf_check -s exit:0 -o match:"searches[[:space:]]+0" \ + echo $counters + atf_check -s exit:0 -o match:"inserts[[:space:]]+0" \ + echo $counters + atf_check -s exit:0 -o match:"removals[[:space:]]+0" \ + echo $counters + + # But once we've reassembled they're no longer zero + # (Count is 2, because in + out) + atf_check -s exit:0 -o ignore \ + ping -c 1 -s 2000 192.0.2.2 + counters=$(jexec alcatraz pfctl -si -v | grep -A 4 '^Fragments') + atf_check -s exit:0 -o match:"current entries[[:space:]]+0" \ + echo $counters + atf_check -s exit:0 -o match:"searches[[:space:]]+2" \ + echo $counters + atf_check -s exit:0 -o match:"inserts[[:space:]]+2" \ + echo $counters + atf_check -s exit:0 -o match:"removals[[:space:]]+2" \ + echo $counters +} + +counters_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "too_many_fragments" @@ -665,4 +731,5 @@ atf_init_test_cases() atf_add_test_case "dummynet" atf_add_test_case "dummynet_nat" atf_add_test_case "dummynet_fragmented" + atf_add_test_case "counters" } diff --git a/tests/sys/netpfil/pf/igmp.py b/tests/sys/netpfil/pf/igmp.py index b339a2825082..5d72a1c093a7 100644 --- a/tests/sys/netpfil/pf/igmp.py +++ b/tests/sys/netpfil/pf/igmp.py @@ -93,3 +93,9 @@ class TestIGMP(VnetTestTemplate): options=[sp.IPOption_Router_Alert()]) \ / sc.igmp.IGMP(type=0x11, mrcode=1) assert not self.find_igmp_reply(pkt, ifname) + + # Or with the wrong destination address + pkt = sp.IP(dst="224.0.0.2%%%s" % ifname, ttl=2, + options=[sp.IPOption_Router_Alert()]) \ + / sc.igmp.IGMP(type=0x11, mrcode=1) + assert not self.find_igmp_reply(pkt, ifname) diff --git a/tests/sys/netpfil/pf/ioctl/validation.c b/tests/sys/netpfil/pf/ioctl/validation.c index 18fafe11c6ab..3e03163cc752 100644 --- a/tests/sys/netpfil/pf/ioctl/validation.c +++ b/tests/sys/netpfil/pf/ioctl/validation.c @@ -41,8 +41,6 @@ static int dev; #define COMMON_HEAD() \ - if (modfind("pf") == -1) \ - atf_tc_skip("pf not loaded"); \ dev = open("/dev/pf", O_RDWR); \ if (dev == -1) \ atf_tc_skip("Failed to open /dev/pf"); @@ -64,6 +62,7 @@ ATF_TC_WITH_CLEANUP(addtables); ATF_TC_HEAD(addtables, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(addtables, tc) @@ -116,6 +115,7 @@ ATF_TC_WITH_CLEANUP(deltables); ATF_TC_HEAD(deltables, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(deltables, tc) @@ -159,6 +159,7 @@ ATF_TC_WITH_CLEANUP(gettables); ATF_TC_HEAD(gettables, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(gettables, tc) @@ -197,6 +198,7 @@ ATF_TC_WITH_CLEANUP(gettstats); ATF_TC_HEAD(gettstats, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(gettstats, tc) @@ -235,6 +237,7 @@ ATF_TC_WITH_CLEANUP(clrtstats); ATF_TC_HEAD(clrtstats, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(clrtstats, tc) @@ -280,6 +283,7 @@ ATF_TC_WITH_CLEANUP(settflags); ATF_TC_HEAD(settflags, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(settflags, tc) @@ -325,6 +329,7 @@ ATF_TC_WITH_CLEANUP(addaddrs); ATF_TC_HEAD(addaddrs, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(addaddrs, tc) @@ -360,6 +365,7 @@ ATF_TC_WITH_CLEANUP(deladdrs); ATF_TC_HEAD(deladdrs, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(deladdrs, tc) @@ -395,6 +401,7 @@ ATF_TC_WITH_CLEANUP(setaddrs); ATF_TC_HEAD(setaddrs, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(setaddrs, tc) @@ -430,6 +437,7 @@ ATF_TC_WITH_CLEANUP(getaddrs); ATF_TC_HEAD(getaddrs, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(getaddrs, tc) @@ -467,6 +475,7 @@ ATF_TC_WITH_CLEANUP(getastats); ATF_TC_HEAD(getastats, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(getastats, tc) @@ -504,6 +513,7 @@ ATF_TC_WITH_CLEANUP(clrastats); ATF_TC_HEAD(clrastats, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(clrastats, tc) @@ -541,6 +551,7 @@ ATF_TC_WITH_CLEANUP(tstaddrs); ATF_TC_HEAD(tstaddrs, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(tstaddrs, tc) @@ -578,6 +589,7 @@ ATF_TC_WITH_CLEANUP(inadefine); ATF_TC_HEAD(inadefine, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(inadefine, tc) @@ -615,6 +627,7 @@ ATF_TC_WITH_CLEANUP(igetifaces); ATF_TC_HEAD(igetifaces, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(igetifaces, tc) @@ -649,6 +662,7 @@ ATF_TC_WITH_CLEANUP(cxbegin); ATF_TC_HEAD(cxbegin, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(cxbegin, tc) @@ -688,6 +702,7 @@ ATF_TC_WITH_CLEANUP(cxrollback); ATF_TC_HEAD(cxrollback, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(cxrollback, tc) @@ -727,6 +742,7 @@ ATF_TC_WITH_CLEANUP(commit); ATF_TC_HEAD(commit, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(commit, tc) @@ -766,6 +782,7 @@ ATF_TC_WITH_CLEANUP(getsrcnodes); ATF_TC_HEAD(getsrcnodes, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(getsrcnodes, tc) @@ -798,6 +815,7 @@ ATF_TC_WITH_CLEANUP(tag); ATF_TC_HEAD(tag, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(tag, tc) @@ -835,6 +853,7 @@ ATF_TC_WITH_CLEANUP(rpool_mtx); ATF_TC_HEAD(rpool_mtx, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(rpool_mtx, tc) @@ -872,6 +891,7 @@ ATF_TC_WITH_CLEANUP(rpool_mtx2); ATF_TC_HEAD(rpool_mtx2, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(rpool_mtx2, tc) @@ -898,6 +918,7 @@ ATF_TC_WITH_CLEANUP(natlook); ATF_TC_HEAD(natlook, tc) { atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.kmods", "pf"); } ATF_TC_BODY(natlook, tc) diff --git a/tests/sys/netpfil/pf/killstate.sh b/tests/sys/netpfil/pf/killstate.sh index 0d98db822535..ffb01df57908 100644 --- a/tests/sys/netpfil/pf/killstate.sh +++ b/tests/sys/netpfil/pf/killstate.sh @@ -105,6 +105,68 @@ v4_cleanup() pft_cleanup } +atf_test_case "src_dst" "cleanup" +src_dst_head() +{ + atf_set descr 'Test killing a state with source and destination specified' + atf_set require.user root +} + +src_dst_body() +{ + pft_init + + epair=$(vnet_mkepair) + ifconfig ${epair}a 192.0.2.1/24 up + + vnet_mkjail alcatraz ${epair}b + jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up + jexec alcatraz pfctl -e + + pft_set_rules alcatraz "block all" \ + "pass in proto icmp" \ + "set skip on lo" + + # Sanity check & establish state + atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ + --sendif ${epair}a \ + --to 192.0.2.2 \ + --replyif ${epair}a + + # Change rules to now deny the ICMP traffic + pft_set_rules noflush alcatraz "block all" + if ! find_state; + then + atf_fail "Setting new rules removed the state." + fi + + # Killing with the wrong source IP doesn't affect our state + jexec alcatraz pfctl -k 192.0.2.3 -k 192.0.2.2 + if ! find_state; + then + atf_fail "Killing with the wrong source IP removed our state." + fi + + # Killing with the wrong destination IP doesn't affect our state + jexec alcatraz pfctl -k 192.0.2.1 -k 192.0.2.3 + if ! find_state; + then + atf_fail "Killing with the wrong destination IP removed our state." + fi + + # But it does with the correct one + jexec alcatraz pfctl -k 192.0.2.1 -k 192.0.2.2 + if find_state; + then + atf_fail "Killing with the correct IPs did not remove our state." + fi +} + +src_dst_cleanup() +{ + pft_cleanup +} + atf_test_case "v6" "cleanup" v6_head() { @@ -698,6 +760,7 @@ nat_cleanup() atf_init_test_cases() { atf_add_test_case "v4" + atf_add_test_case "src_dst" atf_add_test_case "v6" atf_add_test_case "label" atf_add_test_case "multilabel" diff --git a/tests/sys/netpfil/pf/limits.sh b/tests/sys/netpfil/pf/limits.sh index 69f0b6af2ccf..a0d6b891ee19 100644 --- a/tests/sys/netpfil/pf/limits.sh +++ b/tests/sys/netpfil/pf/limits.sh @@ -112,8 +112,43 @@ zero_cleanup() pft_cleanup } +atf_test_case "anchors" "cleanup" +anchors_head() +{ + atf_set descr 'Test increasing maximum number of anchors' + atf_set require.user root +} + +anchors_body() +{ + pft_init + + vnet_mkjail alcatraz + + jexec alcatraz pfctl -e + + pft_set_rules alcatraz \ + "set limit anchors 1" + + pft_set_rules alcatraz \ + "set limit anchors 2" \ + "pass" \ + "anchor \"foo\" {\n + pass in\n + }" \ + "anchor \"bar\" {\n + pass out\n + }" +} + +anchors_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "basic" atf_add_test_case "zero" + atf_add_test_case "anchors" } diff --git a/tests/sys/netpfil/pf/mbuf.sh b/tests/sys/netpfil/pf/mbuf.sh index e3f138bb73b9..3abae65203cd 100644 --- a/tests/sys/netpfil/pf/mbuf.sh +++ b/tests/sys/netpfil/pf/mbuf.sh @@ -69,22 +69,22 @@ inet_in_mbuf_len_body() # Should still work for m_len=0 jexec alcatraz pfilctl link -i dummymbuf:inet inet jexec alcatraz sysctl net.dummymbuf.rules="inet in ${epair}b pull-head 0;" - atf_check_equal "0" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '0' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' # m_len=1 jexec alcatraz sysctl net.dummymbuf.rules="inet in ${epair}b pull-head 1;" jexec alcatraz sysctl net.dummymbuf.hits=0 atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' # m_len=19 # provided IPv4 basic header is 20 bytes long, it should impact the dst addr jexec alcatraz sysctl net.dummymbuf.rules="inet in ${epair}b pull-head 19;" jexec alcatraz sysctl net.dummymbuf.hits=0 atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' } inet_in_mbuf_len_cleanup() { @@ -140,22 +140,22 @@ inet6_in_mbuf_len_body() # Should still work for m_len=0 jexec alcatraz pfilctl link -i dummymbuf:inet6 inet6 jexec alcatraz sysctl net.dummymbuf.rules="inet6 in ${epair}b pull-head 0;" - atf_check_equal "0" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '0' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' atf_check -s exit:0 -o ignore ping -c1 2001:db8::2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' # m_len=1 jexec alcatraz sysctl net.dummymbuf.rules="inet6 in ${epair}b pull-head 1;" jexec alcatraz sysctl net.dummymbuf.hits=0 atf_check -s exit:0 -o ignore ping -c1 2001:db8::2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' # m_len=39 # provided IPv6 basic header is 40 bytes long, it should impact the dst addr jexec alcatraz sysctl net.dummymbuf.rules="inet6 in ${epair}b pull-head 39;" jexec alcatraz sysctl net.dummymbuf.hits=0 atf_check -s exit:0 -o ignore ping -c1 2001:db8::2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' } inet6_in_mbuf_len_cleanup() { @@ -205,29 +205,29 @@ ethernet_in_mbuf_len_body() # Should still work for m_len=0 jexec alcatraz pfilctl link -i dummymbuf:ethernet ethernet jexec alcatraz sysctl net.dummymbuf.rules="ethernet in ${epair}b pull-head 0;" - atf_check_equal "0" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '0' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' # m_len=1 jexec alcatraz sysctl net.dummymbuf.rules="ethernet in ${epair}b pull-head 1;" jexec alcatraz sysctl net.dummymbuf.hits=0 atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' # m_len=11 # for the simplest L2 Ethernet frame it should impact src field jexec alcatraz sysctl net.dummymbuf.rules="ethernet in ${epair}b pull-head 11;" jexec alcatraz sysctl net.dummymbuf.hits=0 atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' # m_len=13 # provided L2 Ethernet simplest header is 14 bytes long, it should impact ethertype field jexec alcatraz sysctl net.dummymbuf.rules="ethernet in ${epair}b pull-head 13;" jexec alcatraz sysctl net.dummymbuf.hits=0 atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 - atf_check_equal "1" "$(jexec alcatraz sysctl -n net.dummymbuf.hits)" + atf_check_equal '1' '$(jexec alcatraz sysctl -n net.dummymbuf.hits)' } ethernet_in_mbuf_len_cleanup() { diff --git a/tests/sys/netpfil/pf/mld.py b/tests/sys/netpfil/pf/mld.py index d118a34c8a7d..b3ef6c21b3de 100644 --- a/tests/sys/netpfil/pf/mld.py +++ b/tests/sys/netpfil/pf/mld.py @@ -32,23 +32,22 @@ from atf_python.sys.net.vnet import VnetTestTemplate class TestMLD(VnetTestTemplate): REQUIRED_MODULES = [ "pf" ] TOPOLOGY = { - "vnet1": {"ifaces": ["if1"]}, + "vnet1": {"ifaces": ["if1"], "opts": ["allow.read_msgbuf"]}, "vnet2": {"ifaces": ["if1"]}, "if1": {"prefixes6": [("2001:db8::2/64", "2001:db8::1/64")]}, } def vnet2_handler(self, vnet): ifname = vnet.iface_alias_map["if1"].name - #ToolsHelper.print_output("/sbin/pfctl -e") + ToolsHelper.print_output("/sbin/pfctl -e") ToolsHelper.pf_rules([ "pass", ]) ToolsHelper.print_output("/sbin/pfctl -x loud") - #ToolsHelper.print_output("echo \"j 230.0.0.1 %s\ns 3600\nq\" | /usr/sbin/mtest" % ifname) def find_mld_reply(self, pkt, ifname): pkt.show() - s = DelayedSend(pkt) + s = DelayedSend(pkt, ifname) found = False packets = self.sp.sniff(iface=ifname, timeout=5) @@ -66,7 +65,6 @@ class TestMLD(VnetTestTemplate): def test_router_alert(self): """Verify that we allow MLD packets with router alert extension header""" ifname = self.vnet.iface_alias_map["if1"].name - #ToolsHelper.print_output("/sbin/ifconfig %s inet6 -ifdisable" % ifname) ToolsHelper.print_output("/sbin/ifconfig") # Import in the correct vnet, so at to not confuse Scapy @@ -76,20 +74,17 @@ class TestMLD(VnetTestTemplate): self.sp = sp self.sc = sc - # A correct MLD query gets a reply - pkt = sp.IPv6(src="fe80::1%%%s" % ifname, dst="ff02::1", hlim=1) \ - / sp.RouterAlert(value=0) \ + # MLD packets with an incorrect hop limit get dropped. + pkt = sp.Ether() \ + / sp.IPv6(src="fe80::1%%%s" % ifname, dst="ff02::1", hlim=2) \ + / sp.IPv6ExtHdrHopByHop(options=[ \ + sp.RouterAlert(value=0) \ + ]) \ / sp.ICMPv6MLQuery2() - assert self.find_mld_reply(pkt, ifname) + # We can't reliably test this by checking for a reply, because + # the other jail may just send a spontaneous MLD reply. + self.find_mld_reply(pkt, ifname) - # The wrong extension header does not - pkt = sp.IPv6(src="fe80::1%%%s" % ifname, dst="ff02::1", hlim=1) \ - / sp.IPv6ExtHdrRouting() \ - / sp.ICMPv6MLQuery2() - assert not self.find_mld_reply(pkt, ifname) - - # Neither does an incorrect hop limit - pkt = sp.IPv6(src="fe80::1%%%s" % ifname, dst="ff02::1", hlim=2) \ - / sp.RouterAlert(value=0) \ - / sp.ICMPv6MLQuery2() - assert not self.find_mld_reply(pkt, ifname) + # Check if we logged dropping the MLD paacket + dmesg = ToolsHelper.get_output("/sbin/dmesg") + assert dmesg.find("Invalid MLD") != -1 diff --git a/tests/sys/netpfil/pf/nat.sh b/tests/sys/netpfil/pf/nat.sh index 5ea1dd6d8b2f..170d813d57fe 100644 --- a/tests/sys/netpfil/pf/nat.sh +++ b/tests/sys/netpfil/pf/nat.sh @@ -810,6 +810,50 @@ empty_pool_cleanup() pft_cleanup } +atf_test_case "dummynet_mask" "cleanup" +dummynet_mask_head() +{ + atf_set descr 'Verify that dummynet uses the pre-nat address for masking' + atf_set require.user root +} + +dummynet_mask_body() +{ + dummynet_init + + epair_srv=$(vnet_mkepair) + epair_cl=$(vnet_mkepair) + + ifconfig ${epair_cl}b 192.0.2.2/24 up + route add default 192.0.2.1 + + vnet_mkjail srv ${epair_srv}a + jexec srv ifconfig ${epair_srv}a 198.51.100.2/24 up + + vnet_mkjail gw ${epair_srv}b ${epair_cl}a + jexec gw ifconfig ${epair_srv}b 198.51.100.1/24 up + jexec gw ifconfig ${epair_cl}a 192.0.2.1/24 up + jexec gw sysctl net.inet.ip.forwarding=1 + + jexec gw dnctl pipe 1 config delay 100 mask src-ip 0xffffff00 + jexec gw pfctl -e + pft_set_rules gw \ + "nat pass on ${epair_srv}b inet from 192.0.2.0/24 to any -> (${epair_srv}b)" \ + "pass out dnpipe 1" + + atf_check -s exit:0 -o ignore \ + ping -c 3 198.51.100.2 + + # Now check that dummynet looked at the correct address + atf_check -s exit:0 -o match:"ip.*192.0.2.0/0" \ + jexec gw dnctl pipe show +} + +dummynet_mask_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "exhaust" @@ -828,4 +872,5 @@ atf_init_test_cases() atf_add_test_case "binat_compat" atf_add_test_case "binat_match" atf_add_test_case "empty_pool" + atf_add_test_case "dummynet_mask" } diff --git a/tests/sys/netpfil/pf/nat44.py b/tests/sys/netpfil/pf/nat44.py new file mode 100644 index 000000000000..d69e794a62c3 --- /dev/null +++ b/tests/sys/netpfil/pf/nat44.py @@ -0,0 +1,76 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2025 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 pytest +from atf_python.sys.net.tools import ToolsHelper +from atf_python.sys.net.vnet import VnetTestTemplate + +class TestNAT44(VnetTestTemplate): + REQUIRED_MODULES = [ "pf" ] + TOPOLOGY = { + "vnet1": {"ifaces": ["if1"]}, + "vnet2": {"ifaces": ["if1", "if2"]}, + "vnet3": {"ifaces": ["if2"]}, + "if1": {"prefixes4": [("192.0.2.2/24", "192.0.2.1/24")]}, + "if2": {"prefixes4": [("198.51.100.1/24", "198.51.100.2")]}, + } + + def vnet2_handler(self, vnet): + outifname = vnet.iface_alias_map["if2"].name + ToolsHelper.print_output("/sbin/sysctl net.inet.ip.forwarding=1") + + ToolsHelper.print_output("/sbin/pfctl -e") + ToolsHelper.print_output("/sbin/pfctl -x loud") + ToolsHelper.pf_rules([ + "set reassemble yes", + "nat on {} inet from 192.0.2.0/24 -> ({})".format(outifname, outifname), + "pass"]) + + def vnet3_handler(self, vnet): + pass + + @pytest.mark.require_user("root") + @pytest.mark.require_progs(["scapy"]) + def test_nat_igmp(self): + "Verify that NAT translation of !(TCP|UDP|SCTP|ICMP) doesn't panic" + ToolsHelper.print_output("/sbin/route add default 192.0.2.1") + ToolsHelper.print_output("ping -c 3 198.51.100.2") + + # Import in the correct vnet, so at to not confuse Scapy + import scapy.all as sp + import scapy.contrib as sc + import scapy.contrib.igmp + + pkt = sp.IP(dst="198.51.100.2", ttl=64) \ + / sc.igmp.IGMP(type=0x11, mrcode=1) + sp.send(pkt) + + # This time we'll hit an existing state + pkt = sp.IP(dst="198.51.100.2", ttl=64) \ + / sc.igmp.IGMP(type=0x11, mrcode=1) + reply = sp.sr1(pkt, timeout=3) + if reply: + reply.show() diff --git a/tests/sys/netpfil/pf/nat64.sh b/tests/sys/netpfil/pf/nat64.sh index 4438ad6abb85..6631e3eca2c7 100644 --- a/tests/sys/netpfil/pf/nat64.sh +++ b/tests/sys/netpfil/pf/nat64.sh @@ -213,12 +213,14 @@ tcp_in_if_bound_body() atf_fail "Failed to connect to TCP server" fi + sleep 1 + # Interfaces of the state are reversed when doing inbound NAT64! - # FIXME: Packets counters seem wrong! + # FIXME: Packets from both directions are counted only on the inbound direction! states=$(mktemp) || exit 1 jexec rtr pfctl -qvvss | normalize_pfctl_s > $states for state_regexp in \ - "${epair_link}a tcp 192.0.2.1:[0-9]+ \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:1234 \(64:ff9b::c000:202\[1234\]\) .* 9:9 pkts.* rule 3 .* origif: ${epair}b" \ + "${epair_link}a tcp 192.0.2.1:[0-9]+ \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:1234 \(64:ff9b::c000:202\[1234\]\) .* 5:4 pkts.* rule 3 .* origif: ${epair}b" \ ; do grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" done @@ -254,6 +256,8 @@ tcp_out_if_bound_body() atf_fail "Failed to connect to TCP server" fi + sleep 1 + # Origif is not printed when identical as if. states=$(mktemp) || exit 1 jexec rtr pfctl -qvvss | normalize_pfctl_s > $states @@ -295,12 +299,14 @@ tcp_in_floating_body() atf_fail "Failed to connect to TCP server" fi + sleep 1 + # Interfaces of the state are reversed when doing inbound NAT64! - # FIXME: Packets counters seem wrong! + # FIXME: Packets from both directions are counted only on the inbound direction! states=$(mktemp) || exit 1 jexec rtr pfctl -qvvss | normalize_pfctl_s > $states for state_regexp in \ - "all tcp 192.0.2.1:[0-9]+ \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:1234 \(64:ff9b::c000:202\[1234\]\).* 9:9 pkts.* rule 3 .* origif: ${epair}b" \ + "all tcp 192.0.2.1:[0-9]+ \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:1234 \(64:ff9b::c000:202\[1234\]\).* 5:4 pkts.* rule 3 .* origif: ${epair}b" \ ; do grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" done @@ -336,6 +342,8 @@ tcp_out_floating_body() atf_fail "Failed to connect to TCP server" fi + sleep 1 + # Origif is not printed when identical as if. states=$(mktemp) || exit 1 jexec rtr pfctl -qvvss | normalize_pfctl_s > $states @@ -1011,20 +1019,19 @@ route_to_body() pft_init epair_link=$(vnet_mkepair) - epair_null=$(vnet_mkepair) epair=$(vnet_mkepair) ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad route -6 add default 2001:db8::1 - vnet_mkjail rtr ${epair}b ${epair_link}a ${epair_null}a + vnet_mkjail rtr ${epair}b ${epair_link}a jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad - jexec rtr ifconfig ${epair_null}a 192.0.2.3/24 up jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up vnet_mkjail dst ${epair_link}b jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up - jexec dst route add default 192.0.2.1 + jexec dst ifconfig lo0 203.0.113.1/32 alias + jexec dst route add default 192.0.2.2 # Sanity checks atf_check -s exit:0 -o ignore \ @@ -1033,19 +1040,23 @@ route_to_body() jexec rtr pfctl -e pft_set_rules rtr \ "set reassemble yes" \ + "set debug loud" \ "set state-policy if-bound" \ - "block" \ + "block log (all)" \ "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ "pass in on ${epair}b route-to (${epair_link}a 192.0.2.2) inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" atf_check -s exit:0 -o ignore \ + -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ ping6 -c 3 64:ff9b::192.0.2.2 states=$(mktemp) || exit 1 jexec rtr pfctl -qvvss | normalize_pfctl_s > $states + cat $states + # Interfaces of the state are reversed when doing inbound NAT64! for state_regexp in \ - "${epair}b ipv6-icmp 192.0.2.1:.* \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:8 \(64:ff9b::c000:202\[[0-9]+\]\).*4:2 pkts.*route-to: 192.0.2.2@${epair_link}a" \ + "${epair_link}a ipv6-icmp 192.0.2.1:.* \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:8 \(64:ff9b::c000:202\[[0-9]+\]\).* 3:3 pkts.*route-to: 192.0.2.2@${epair_link}a origif: ${epair}b" \ ; do grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" done @@ -1094,6 +1105,7 @@ reply_to_body() "pass in on ${epair}b reply-to (${epair}b 2001:db8::2) inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.1" atf_check -s exit:0 -o ignore \ + -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ ping6 -c 3 64:ff9b::192.0.2.2 } @@ -1155,8 +1167,10 @@ v6_gateway_body() "pass in on ${epair_lan}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_wan_one}a)" atf_check -s exit:0 -o ignore \ + -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ ping6 -c 3 64:ff9b::192.0.2.2 atf_check -s exit:0 -o ignore \ + -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ ping6 -c 3 64:ff9b::198.51.100.1 } diff --git a/tests/sys/netpfil/pf/once.sh b/tests/sys/netpfil/pf/once.sh new file mode 100644 index 000000000000..54dbb030dbe6 --- /dev/null +++ b/tests/sys/netpfil/pf/once.sh @@ -0,0 +1,132 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2025 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. + +. $(atf_get_srcdir)/utils.subr + +atf_test_case "basic" "cleanup" +basic_head() +{ + atf_set descr 'Basic one shot rule test' + atf_set require.user root +} + +basic_body() +{ + pft_init + + epair=$(vnet_mkepair) + + vnet_mkjail alcatraz ${epair}a + jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up + + ifconfig ${epair}b 192.0.2.2/24 up + + # Sanity checks + atf_check -s exit:0 -o ignore \ + ping -c 1 192.0.2.1 + + jexec alcatraz pfctl -e + pft_set_rules alcatraz \ + "block" \ + "pass in from 192.0.2.2 once" + + # First once succeeds + atf_check -s exit:0 -o ignore \ + ping -c 3 192.0.2.1 + + # Check for '# expired' + atf_check -s exit:0 -e ignore \ + -o match:'pass in inet from 192.0.2.2 to any flags S/SA keep state once # expired' \ + jexec alcatraz pfctl -sr -vv + + # The second one does not + atf_check -s exit:2 -o ignore \ + ping -c 3 192.0.2.1 + + # Flush states, still shouldn't work + jexec alcatraz pfctl -Fs + atf_check -s exit:2 -o ignore \ + ping -c 3 192.0.2.1 +} + +basic_cleanup() +{ + pft_cleanup +} + +atf_test_case "anchor" "cleanup" +anchor_head() +{ + atf_set descr 'Test one shot rule in anchors' + atf_set require.user root +} + +anchor_body() +{ + pft_init + epair=$(vnet_mkepair) + + vnet_mkjail alcatraz ${epair}a + jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up + + ifconfig ${epair}b 192.0.2.2/24 up + + # Sanity checks + atf_check -s exit:0 -o ignore \ + ping -c 1 192.0.2.1 + + jexec alcatraz pfctl -e + pft_set_rules alcatraz \ + "block" \ + "anchor \"once\" {\n + pass in from 192.0.2.2 once\n + }" + + # First once succeeds + atf_check -s exit:0 -o ignore \ + ping -c 3 192.0.2.1 + + # Check for '# expired' + jexec alcatraz pfctl -sr -vv -a "*" + atf_check -s exit:0 -e ignore \ + -o match:'pass in inet from 192.0.2.2 to any flags S/SA keep state once # expired' \ + jexec alcatraz pfctl -sr -vv -a "*" + + # The second one does not + atf_check -s exit:2 -o ignore \ + ping -c 3 192.0.2.1 +} + +anchor_cleanup() +{ + pft_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case "basic" + atf_add_test_case "anchor" +} diff --git a/tests/sys/netpfil/pf/pflog.sh b/tests/sys/netpfil/pf/pflog.sh index a34ec893a75c..550548a59c11 100644 --- a/tests/sys/netpfil/pf/pflog.sh +++ b/tests/sys/netpfil/pf/pflog.sh @@ -394,6 +394,64 @@ rdr_action_cleanup() pft_cleanup } +atf_test_case "rule_number" "cleanup" +rule_number_head() +{ + atf_set descr 'Test rule numbers with anchors' + atf_set require.user root +} + +rule_number_body() +{ + pflog_init + + epair=$(vnet_mkepair) + + vnet_mkjail alcatraz ${epair}b + jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up + + ifconfig ${epair}a 192.0.2.2/24 up + ifconfig ${epair}a inet alias 192.0.2.3/24 up + ifconfig ${epair}a inet alias 192.0.2.4/24 up + + jexec alcatraz pfctl -e + jexec alcatraz ifconfig pflog0 up + pft_set_rules alcatraz \ + "pass log from 192.0.2.2" \ + "anchor \"foo\" {\n \ + pass log from 192.0.2.3\n \ + }" \ + "pass log from 192.0.2.4" + + jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt & + sleep 1 # Wait for tcpdump to start + + atf_check -s exit:0 -o ignore \ + ping -c 1 -S 192.0.2.2 192.0.2.1 + atf_check -s exit:0 -o ignore \ + ping -c 1 -S 192.0.2.3 192.0.2.1 + atf_check -s exit:0 -o ignore \ + ping -c 1 -S 192.0.2.4 192.0.2.1 + + jexec alcatraz pfctl -sr -a '*' -vv + + # Give tcpdump a little time to finish writing to the file + sleep 1 + cat pflog.txt + + atf_check -o match:"rule 0/0\(match\): pass in.*: 192.0.2.2.*ICMP echo request" \ + cat pflog.txt + atf_check -o match:"rule 1.foo.0/0\(match\): pass in.*: 192.0.2.3.*: ICMP echo request" \ + cat pflog.txt + atf_check -o match:"rule 2/0\(match\): pass in.*: 192.0.2.4.*: ICMP echo request" \ + cat pflog.txt +} + +rule_number_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "malformed" @@ -403,4 +461,5 @@ atf_init_test_cases() atf_add_test_case "unspecified_v4" atf_add_test_case "unspecified_v6" atf_add_test_case "rdr_action" + atf_add_test_case "rule_number" } diff --git a/tests/sys/netpfil/pf/pfsync.sh b/tests/sys/netpfil/pf/pfsync.sh index 3be4a3024393..9843c470c06f 100644 --- a/tests/sys/netpfil/pf/pfsync.sh +++ b/tests/sys/netpfil/pf/pfsync.sh @@ -921,6 +921,8 @@ rtable_cleanup() route_to_common_head() { + # TODO: Extend setup_router_server_nat64 to create a 2nd router + pfsync_version=$1 shift @@ -937,11 +939,16 @@ route_to_common_head() # 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 ${epair_one}a 198.51.100.1/28 up + jexec one ifconfig ${epair_one}a inet6 2001:db8:4211::1/64 no_dad + jexec one ifconfig ${epair_one}a name inif jexec one ifconfig ${epair_out_one}a 203.0.113.1/24 up + jexec one ifconfig ${epair_out_one}a inet6 2001:db8:4200::1/64 no_dad jexec one ifconfig ${epair_out_one}a name outif jexec one sysctl net.inet.ip.forwarding=1 - jexec one arp -s 203.0.113.254 00:01:02:03:04:05 + jexec one sysctl net.inet6.ip6.forwarding=1 + jexec one arp -s 203.0.113.254 00:01:02:00:00:04 + jexec one ndp -s 2001:db8:4200::fe 00:01:02:00:00:06 jexec one ifconfig pfsync0 \ syncdev ${epair_sync}a \ maxupd 1 \ @@ -949,20 +956,30 @@ route_to_common_head() up jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up - jexec two ifconfig ${epair_two}a 198.51.100.2/24 up - jexec two ifconfig ${epair_out_two}a 203.0.113.2/24 up + jexec two ifconfig ${epair_two}a 198.51.100.17/28 up + jexec two ifconfig ${epair_two}a inet6 2001:db8:4212::1/64 no_dad + jexec two ifconfig ${epair_two}a name inif + jexec two ifconfig ${epair_out_two}a 203.0.113.1/24 up + jexec two ifconfig ${epair_out_two}a inet6 2001:db8:4200::2/64 no_dad jexec two ifconfig ${epair_out_two}a name outif jexec two sysctl net.inet.ip.forwarding=1 - jexec two arp -s 203.0.113.254 00:01:02:03:04:05 + jexec two sysctl net.inet6.ip6.forwarding=1 + jexec two arp -s 203.0.113.254 00:01:02:00:00:04 + jexec two ndp -s 2001:db8:4200::fe 00:01:02:00:00:06 jexec two ifconfig pfsync0 \ syncdev ${epair_sync}b \ maxupd 1 \ version $pfsync_version \ up - ifconfig ${epair_one}b 198.51.100.254/24 up - ifconfig ${epair_two}b 198.51.100.253/24 up + ifconfig ${epair_one}b 198.51.100.2/28 up + ifconfig ${epair_one}b inet6 2001:db8:4211::2/64 no_dad + ifconfig ${epair_two}b 198.51.100.18/28 up + ifconfig ${epair_two}b inet6 2001:db8:4212::2/64 no_dad + # Target is behind router "one" route add -net 203.0.113.0/24 198.51.100.1 + route add -inet6 -net 64:ff9b::/96 2001:db8:4211::1 + ifconfig ${epair_two}b up ifconfig ${epair_out_one}b up ifconfig ${epair_out_two}b up @@ -1206,6 +1223,435 @@ route_to_1400_bad_ifname_cleanup() pfsynct_cleanup } +atf_test_case "af_to_in_floating" "cleanup" +af_to_in_floating_head() +{ + atf_set descr 'Test syncing of states created by inbound af-to rules with floating states' + atf_set require.user root + atf_set require.progs python3 scapy +} + +af_to_in_floating_body() +{ + route_to_common_head 1500 + + jexec one pfctl -e + pft_set_rules one \ + "set state-policy floating" \ + "set skip on ${epair_sync}a" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \ + "pass in on inif to 64:ff9b::/96 af-to inet from (outif) keep state" + + jexec two pfctl -e + pft_set_rules two \ + "set skip on ${epair_sync}b" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" + + # ptf_ping can't deal with nat64, this test will fail but generate states + atf_check -s exit:1 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --sendif ${epair_one}b \ + --fromaddr 2001:db8:4201::fe \ + --to 64:ff9b::203.0.113.254 \ + --recvif ${epair_out_one}b + + # Allow time for sync + sleep 2 + + states_one=$(mktemp) + states_two=$(mktemp) + jexec one pfctl -qvvss | normalize_pfctl_s > $states_one + jexec two pfctl -qvvss | normalize_pfctl_s > $states_two + + # Sanity check + grep -qE 'all ipv6-icmp 203.0.113.1 \(2001:db8:4201::fe\) -> 203.0.113.254:8 \(64:ff9b::cb00:71fe) .* rule 3 .* origif: inif' $states_one || + atf_fail "State missing on router one" + + grep -qE 'all ipv6-icmp 203.0.113.1 \(2001:db8:4201::fe\) -> 203.0.113.254:8 \(64:ff9b::cb00:71fe) .* origif: inif' $states_two || + atf_fail "State missing on router two" +} + +af_to_in_floating_cleanup() +{ + pfsynct_cleanup +} + +atf_test_case "af_to_in_if_bound" "cleanup" +af_to_in_if_bound_head() +{ + atf_set descr 'Test syncing of states created by inbound af-to rules with if-bound states' + atf_set require.user root + atf_set require.progs python3 scapy +} + +af_to_in_if_bound_body() +{ + route_to_common_head 1500 + + jexec one pfctl -e + pft_set_rules one \ + "set state-policy if-bound" \ + "set skip on ${epair_sync}a" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \ + "pass in on inif to 64:ff9b::/96 af-to inet from (outif) keep state" + + jexec two pfctl -e + pft_set_rules two \ + "set skip on ${epair_sync}b" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" + + # ptf_ping can't deal with nat64, this test will fail but generate states + atf_check -s exit:1 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --sendif ${epair_one}b \ + --fromaddr 2001:db8:4201::fe \ + --to 64:ff9b::203.0.113.254 \ + --recvif ${epair_out_one}b + + # Allow time for sync + sleep 2 + + states_one=$(mktemp) + states_two=$(mktemp) + jexec one pfctl -qvvss | normalize_pfctl_s > $states_one + jexec two pfctl -qvvss | normalize_pfctl_s > $states_two + + # Sanity check + grep -qE 'outif ipv6-icmp 203.0.113.1 \(2001:db8:4201::fe\) -> 203.0.113.254:8 \(64:ff9b::cb00:71fe) .* rule 3 .* origif: inif' $states_one || + atf_fail "State missing on router one" + + grep -qE 'outif ipv6-icmp 203.0.113.1 \(2001:db8:4201::fe\) -> 203.0.113.254:8 \(64:ff9b::cb00:71fe) .* origif: inif' $states_two || + atf_fail "State missing on router two" +} + +af_to_in_if_bound_cleanup() +{ + pfsynct_cleanup +} + +atf_test_case "af_to_out_if_bound" "cleanup" +af_to_out_if_bound_head() +{ + atf_set descr 'Test syncing of states created by outbound af-to rules with if-bound states' + atf_set require.user root + atf_set require.progs python3 scapy +} + +af_to_out_if_bound_body() +{ + route_to_common_head 1500 + + jexec one route add -inet6 -net 64:ff9b::/96 -iface outif + jexec one sysctl net.inet6.ip6.forwarding=1 + + jexec one pfctl -e + pft_set_rules one \ + "set state-policy if-bound" \ + "set skip on ${epair_sync}a" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \ + "pass in on inif to 64:ff9b::/96 keep state" \ + "pass out on outif to 64:ff9b::/96 af-to inet from (outif) keep state" + + jexec two pfctl -e + pft_set_rules two \ + "set skip on ${epair_sync}b" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" + + # ptf_ping can't deal with nat64, this test will fail but generate states + atf_check -s exit:1 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --sendif ${epair_one}b \ + --fromaddr 2001:db8:4201::fe \ + --to 64:ff9b::203.0.113.254 \ + --recvif ${epair_out_one}b + + # Allow time for sync + sleep 2 + + states_one=$(mktemp) + states_two=$(mktemp) + jexec one pfctl -qvvss | normalize_pfctl_s > $states_one + jexec two pfctl -qvvss | normalize_pfctl_s > $states_two + + # Sanity check + # st->orig_kif is the same as st->kif, so st->orig_kif is not printed. + for state_regexp in \ + "inif ipv6-icmp 64:ff9b::cb00:71fe\[128\] <- 2001:db8:4201::fe .* rule 3 .* creatorid: [0-9a-f]+" \ + "outif icmp 203.0.113.1 \(64:ff9b::cb00:71fe\[8\]\) -> 203.0.113.254:8 \(2001:db8:4201::fe\) .* rule 4 .* creatorid: [0-9a-f]+" \ + ; do + grep -qE "${state_regexp}" $states_one || atf_fail "State not found for '${state_regexp}'" + done + + for state_regexp in \ + "inif ipv6-icmp 64:ff9b::cb00:71fe\[128\] <- 2001:db8:4201::fe .* creatorid: [0-9a-f]+" \ + "outif icmp 203.0.113.1 \(64:ff9b::cb00:71fe\[8\]\) -> 203.0.113.254:8 \(2001:db8:4201::fe\) .* creatorid: [0-9a-f]+" \ + ; do + grep -qE "${state_regexp}" $states_two || atf_fail "State not found for '${state_regexp}'" + done +} + +af_to_out_if_bound_cleanup() +{ + pfsynct_cleanup +} + +atf_test_case "tag" "cleanup" +tag_head() +{ + atf_set descr 'Test if the pf tag is synced' + atf_set require.user root + atf_set require.progs python3 scapy +} + +tag_body() +{ + route_to_common_head 1500 + + jexec one pfctl -e + pft_set_rules one \ + "set skip on ${epair_sync}a" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \ + "pass in on inif inet proto udp tag sometag keep state" \ + "pass out on outif tagged sometag keep state (no-sync)" + + jexec two pfctl -e + pft_set_rules two \ + "set debug loud" \ + "set skip on ${epair_sync}b" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \ + "block tagged othertag" \ + "pass out on outif tagged sometag keep state (no-sync)" + + atf_check -s exit:0 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --ping-type=udp \ + --sendif ${epair_one}b \ + --fromaddr 198.51.100.254 \ + --to 203.0.113.254 \ + --recvif ${epair_out_one}b + + # Allow time for sync + sleep 2 + + # Force the next request to go through the 2nd router + route change -net 203.0.113.0/24 198.51.100.17 + + atf_check -s exit:0 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --ping-type=udp \ + --sendif ${epair_two}b \ + --fromaddr 198.51.100.254 \ + --to 203.0.113.254 \ + --recvif ${epair_out_two}b +} + +tag_cleanup() +{ + pfsynct_cleanup +} + +atf_test_case "altq_queues" "cleanup" +altq_queues_head() +{ + atf_set descr 'Test if the altq queues are synced' + atf_set require.user root + atf_set require.progs python3 scapy +} + +altq_queues_body() +{ + route_to_common_head 1500 + altq_init + is_altq_supported hfsc + + jexec one pfctl -e + pft_set_rules one \ + "set skip on ${epair_sync}a" \ + "altq on outif bandwidth 30000b hfsc queue { default other1 other2 }" \ + "queue default hfsc(linkshare 10000b default)" \ + "queue other1 hfsc(linkshare 10000b)" \ + "queue other2 hfsc(linkshare 10000b)" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \ + "pass in on inif inet proto udp queue other1 keep state" \ + "pass out on outif inet proto udp keep state" + + jexec two pfctl -e + pft_set_rules two \ + "set debug loud" \ + "set skip on ${epair_sync}b" \ + "altq on outif bandwidth 30000b hfsc queue { default other2 other1 }" \ + "queue default hfsc(linkshare 10000b default)" \ + "queue other2 hfsc(linkshare 10000b)" \ + "queue other1 hfsc(linkshare 10000b)" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \ + "pass out on outif inet proto udp keep state" + + atf_check -s exit:0 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --ping-type=udp \ + --sendif ${epair_one}b \ + --fromaddr 198.51.100.254 \ + --to 203.0.113.254 \ + --recvif ${epair_out_one}b + + queues_one=$(mktemp) + jexec one pfctl -qvsq | normalize_pfctl_s > $queues_one + echo " === queues one === " + cat $queues_one + grep -qE 'queue other1 on outif .* pkts: 1 ' $queues_one || atf_fail 'Packets not sent through queue "other1"' + + # Allow time for sync + sleep 2 + + # Force the next request to go through the 2nd router + route change -net 203.0.113.0/24 198.51.100.17 + + # Send a packet through router "two". It lacks the inbound rule + # but the inbound state should have been pfsynced from router "one" + # including altq queuing information. However the queues are created + # on router "two" in different order and we only sync queue index, + # so the packet ends up in a different queue. One must have identical + # queue set on both routers! + atf_check -s exit:0 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --ping-type=udp \ + --sendif ${epair_two}b \ + --fromaddr 198.51.100.254 \ + --to 203.0.113.254 \ + --recvif ${epair_out_two}b + + queues_two=$(mktemp) + jexec two pfctl -qvsq | normalize_pfctl_s > $queues_two + echo " === queues two === " + cat $queues_two + grep -qE 'queue other2 on outif .* pkts: 1 ' $queues_two || atf_fail 'Packets not sent through queue "other2"' +} + +altq_queues_cleanup() +{ + # Interface detaching seems badly broken in altq. If interfaces are + # destroyed when shutting down the vnet and then pf is unloaded, it will + # cause a kernel crash. Work around the issue by first flushing the + # pf rulesets + jexec one pfctl -F all + jexec two pfctl -F all + pfsynct_cleanup +} + +atf_test_case "rt_af" "cleanup" +rt_af_head() +{ + atf_set descr 'Test if the rt_af is synced' + atf_set require.user root + atf_set require.progs python3 scapy +} + +rt_af_body() +{ + route_to_common_head 1500 + + jexec one pfctl -e + pft_set_rules one \ + "set skip on ${epair_sync}a" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \ + "pass in on inif \ + route-to (outif 203.0.113.254) prefer-ipv6-nexthop \ + inet proto udp \ + to 203.0.113.241 \ + keep state" \ + "pass in on inif \ + route-to (outif 2001:db8:4200::fe) prefer-ipv6-nexthop \ + inet proto udp \ + to 203.0.113.242 \ + keep state" \ + "pass in on inif \ + route-to (outif 2001:db8:4200::fe) prefer-ipv6-nexthop \ + inet6 proto udp \ + to 2001:db8:4200::f3 \ + keep state" \ + "pass out on outif inet proto udp keep state (no-sync)" \ + "pass out on outif inet6 proto udp keep state (no-sync)" + + jexec two pfctl -e + pft_set_rules two \ + "set debug loud" \ + "set skip on ${epair_sync}b" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv } keep state (no-sync)" \ + + # IPv4 packet over IPv4 gateway + atf_check -s exit:0 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --ping-type=udp \ + --sendif ${epair_one}b \ + --fromaddr 198.51.100.254 \ + --to 203.0.113.241 \ + --recvif ${epair_out_one}b + + # FIXME: Routing IPv4 packets over IPv6 gateways with gateway added + # with `ndp -s` causes the static NDP entry to become expired. + # Pfsync tests don't use "servers" which can reply to ARP and NDP, + # but such static entry for gateway and only check if a stateless + # ICMP or UDP packet is forward through. + # + # IPv4 packert over IPv6 gateway + #atf_check -s exit:0 env PYTHONPATH=${common_dir} \ + # ${common_dir}/pft_ping.py \ + # --ping-type=udp \ + # --sendif ${epair_one}b \ + # --fromaddr 198.51.100.254 \ + # --to 203.0.113.242 \ + # --recvif ${epair_out_one}b + + # IPv6 packet over IPv6 gateway + atf_check -s exit:0 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --ping-type=udp \ + --sendif ${epair_one}b \ + --fromaddr 2001:db8:4211::fe \ + --to 2001:db8:4200::f3 \ + --recvif ${epair_out_one}b + + sleep 5 # Wait for pfsync + + states_one=$(mktemp) + states_two=$(mktemp) + jexec one pfctl -qvvss | normalize_pfctl_s > $states_one + jexec two pfctl -qvvss | normalize_pfctl_s > $states_two + + echo " === states one === " + cat $states_one + echo " === states two === " + cat $states_two + + for state_regexp in \ + "all udp 203.0.113.241:9 <- 198.51.100.254 .* route-to: 203.0.113.254@outif origif: inif" \ + "all udp 2001:db8:4200::f3\[9\] <- 2001:db8:4211::fe .* route-to: 2001:db8:4200::fe@outif origif: inif" \ + ; do + grep -qE "${state_regexp}" $states_two || atf_fail "State not found for '${state_regexp}' on router two" + done +} + +rt_af_cleanup() +{ + jexec one pfctl -qvvsr + jexec one pfctl -qvvss + jexec one arp -an + jexec one ndp -an + pfsynct_cleanup +} + atf_init_test_cases() { atf_add_test_case "basic" @@ -1224,4 +1670,10 @@ atf_init_test_cases() atf_add_test_case "route_to_1301_bad_rpool" atf_add_test_case "route_to_1400_bad_ruleset" atf_add_test_case "route_to_1400_bad_ifname" + atf_add_test_case "af_to_in_floating" + atf_add_test_case "af_to_in_if_bound" + atf_add_test_case "af_to_out_if_bound" + atf_add_test_case "tag" + atf_add_test_case "altq_queues" + atf_add_test_case "rt_af" } diff --git a/tests/sys/netpfil/pf/route_to.sh b/tests/sys/netpfil/pf/route_to.sh index 765403dcb79c..13b60c8f80bc 100644 --- a/tests/sys/netpfil/pf/route_to.sh +++ b/tests/sys/netpfil/pf/route_to.sh @@ -28,6 +28,75 @@ common_dir=$(atf_get_srcdir)/../common +# We need to somehow test if the random algorithm of pf_map_addr() is working. +# The table or prefix contains multiple IP next-hop addresses, for each one try +# to establish up to 10 connections. Fail the test if with this many attempts +# the "good" target has not been chosen. However this choice is random, +# the check might still ocasionally fail. +check_random() { + if [ "$1" = "IPv4" ]; then + ping_from="${net_clients_4}.1" + ping_to="${host_server_4}" + else + ping_from="${net_clients_6}::1" + ping_to="${host_server_6}" + fi + good_targets="$2" + bad_targets="$3" + + port=42000 + states=$(mktemp) || exit 1 + for good_target in $good_targets; do + found="no" + for attempt in $(seq 1 10); do + port=$(( port + 1 )) + jexec router pfctl -Fs + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${ping_from} --to ${ping_to} \ + --ping-type=tcp3way --send-sport=${port} + jexec router pfctl -qvvss | normalize_pfctl_s > $states + cat $states + if [ -n "${bad_targets}" ]; then + for bad_target in $bad_targets; do + if grep -qE "route-to: ${bad_target}@" $states; then + atf_fail "Bad target ${bad_target} selected!" + fi + done + fi; + if grep -qE "route-to: ${good_target}@" $states; then + found=yes + break + fi + done + if [ "${found}" = "no" ]; then + atf_fail "Target ${good_target} not selected after ${attempt} attempts!" + fi + done +} + +pf_map_addr_common() +{ + setup_router_server_nat64 + + # Clients will connect from another network behind the router. + # This allows for using multiple source addresses. + jexec router route add -6 ${net_clients_6}::/${net_clients_6_mask} ${net_tester_6_host_tester} + jexec router route add ${net_clients_4}.0/${net_clients_4_mask} ${net_tester_4_host_tester} + + # The servers are reachable over additional IP addresses for + # testing of tables and subnets. The addresses are noncontinougnus + # for pf_map_addr() counter tests. + for i in 0 1 4 5; do + a1=$((24 + i)) + jexec server1 ifconfig ${epair_server1}b inet ${net_server1_4}.${a1}/32 alias + jexec server1 ifconfig ${epair_server1}b inet6 ${net_server1_6}::42:${i}/128 alias + a2=$((40 + i)) + jexec server2 ifconfig ${epair_server2}b inet ${net_server2_4}.${a2}/32 alias + jexec server2 ifconfig ${epair_server2}b inet6 ${net_server2_6}::42:${i}/128 alias + done +} + atf_test_case "v4" "cleanup" v4_head() { @@ -893,7 +962,6 @@ empty_pool_cleanup() pft_cleanup } - atf_test_case "table_loop" "cleanup" table_loop_head() @@ -905,24 +973,7 @@ table_loop_head() table_loop_body() { - setup_router_server_nat64 - - # Clients will connect from another network behind the router. - # This allows for using multiple source addresses. - jexec router route add -6 ${net_clients_6}::/${net_clients_6_mask} ${net_tester_6_host_tester} - jexec router route add ${net_clients_4}.0/${net_clients_4_mask} ${net_tester_4_host_tester} - - # The servers are reachable over additional IP addresses for - # testing of tables and subnets. The addresses are noncontinougnus - # for pf_map_addr() counter tests. - for i in 0 1 4 5; do - a1=$((24 + i)) - jexec server1 ifconfig ${epair_server1}b inet ${net_server1_4}.${a1}/32 alias - jexec server1 ifconfig ${epair_server1}b inet6 ${net_server1_6}::42:${i}/128 alias - a2=$((40 + i)) - jexec server2 ifconfig ${epair_server2}b inet ${net_server2_4}.${a2}/32 alias - jexec server2 ifconfig ${epair_server2}b inet6 ${net_server2_6}::42:${i}/128 alias - done + pf_map_addr_common jexec router pfctl -e pft_set_rules router \ @@ -976,6 +1027,626 @@ table_loop_cleanup() } +atf_test_case "roundrobin" "cleanup" + +roundrobin_head() +{ + atf_set descr 'multiple gateways of mixed AF, including prefixes and tables, for IPv6 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +roundrobin_body() +{ + pf_map_addr_common + + # The rule is defined as "inet6 proto tcp" so directly given IPv4 hosts + # will be removed from the pool by pfctl. Tables will still be loaded + # and pf_map_addr() will only use IPv6 addresses from them. It will + # iterate over members of the pool and inside of tables and prefixes. + + jexec router pfctl -e + pft_set_rules router \ + "set debug loud" \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table <rt_targets> { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:0/127 ${net_server2_6}::42:4 }" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_4_host_server}) \ + (${epair_server2}a <rt_targets_empty>) \ + (${epair_server1}a ${net_server1_6}::42:0/127) \ + (${epair_server2}a <rt_targets_empty>) \ + (${epair_server2}a <rt_targets>) \ + } \ + inet6 proto tcp \ + keep state" + + for port in $(seq 1 6); do + port=$((4200 + port)) + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=${port} + done + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4201\] .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4202\] .* route-to: ${net_server1_6}::42:1@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4203\] .* route-to: ${net_server2_6}::42:0@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4204\] .* route-to: ${net_server2_6}::42:1@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4205\] .* route-to: ${net_server2_6}::42:4@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4206\] .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +roundrobin_cleanup() +{ + pft_cleanup +} + +atf_test_case "random_table" "cleanup" + +random_table_head() +{ + atf_set descr 'Pool with random flag and a table for IPv6' + atf_set require.user root + atf_set require.progs python3 scapy +} + +random_table_body() +{ + pf_map_addr_common + + # The "random" flag will pick random hosts from the table but will + # not dive into prefixes, always choosing the 0th address. + # Proper address family will be choosen. + + jexec router pfctl -e + pft_set_rules router \ + "set debug loud" \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table <rt_targets> { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:0/127 ${net_server2_6}::42:4 }" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a <rt_targets>) } random \ + inet6 proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:4" + bad_targets="${net_server2_6}::42:1" + check_random IPv6 "${good_targets}" "${bad_targets}" +} + +random_table_cleanup() +{ + pft_cleanup +} + +atf_test_case "random_prefix" "cleanup" + +random_prefix_head() +{ + atf_set descr 'Pool with random flag and a table for IPv4' + atf_set require.user root + atf_set require.progs python3 scapy +} + +random_prefix_body() +{ + pf_map_addr_common + + # The "random" flag will pick random hosts from given prefix. + # The choice being random makes testing it non-trivial. We do 10 + # attempts to have each target chosen. Hopefully this is enough to have + # this test pass often enough. + + jexec router pfctl -e + pft_set_rules router \ + "set debug loud" \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_6}::42:0/127) } random \ + inet6 proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:1" + check_random IPv6 "${good_targets}" +} + +random_prefix_cleanup() +{ + pft_cleanup +} + +atf_test_case "prefer_ipv6_nexthop_single_ipv4" "cleanup" + +prefer_ipv6_nexthop_single_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for a single IPv4 gateway' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_single_ipv4_body() +{ + pf_map_addr_common + + # Basic forwarding test for prefer-ipv6-nexthop pool option. + # A single IPv4 gateway will work only for IPv4 traffic. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to (${epair_server1}a ${net_server1_4_host_server}) prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4201 \ + + atf_check -s exit:1 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4202 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server1_4_host_server}@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done + + # The IPv6 packet could not have been routed over IPv4 gateway. + atf_check -o "match:map-failed +1 +" -x "jexec router pfctl -qvvsi 2> /dev/null" +} + +prefer_ipv6_nexthop_single_ipv4_cleanup() +{ + pft_cleanup +} + +atf_test_case "prefer_ipv6_nexthop_single_ipv6" "cleanup" + +prefer_ipv6_nexthop_single_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for a single IPv6 gateway' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_single_ipv6_body() +{ + pf_map_addr_common + + # Basic forwarding test for prefer-ipv6-nexthop pool option. + # A single IPve gateway will work both for IPv4 and IPv6 traffic. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to (${epair_server1}a ${net_server1_6_host_server}) prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4201 \ + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4202 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4202\] .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +prefer_ipv6_nexthop_single_ipv6_cleanup() +{ + pft_cleanup +} + +atf_test_case "prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4" "cleanup" + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for multiple gateways of mixed AF with prefixes and tables, round robin selection, for IPv4 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4_body() +{ + pf_map_addr_common + + # pf_map_addr() starts iterating over hosts of the pool from the 2nd + # host. This behaviour was here before adding prefer-ipv6-nexthop + # support, we test for it. Some targets are hosts and some are tables. + # Those are iterated too. Finally the iteration loops back + # to the beginning of the pool. Send out IPv4 probes. They will use all + # IPv4 and IPv6 addresses from the pool. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table <rt_targets> { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:4/127 }" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_6_host_server}) \ + (${epair_server1}a ${net_server1_6}::42:0/127) \ + (${epair_server2}a ${net_server2_4_host_server}) \ + (${epair_server2}a <rt_targets_empty>) \ + (${epair_server1}a ${net_server1_4}.24/31) \ + (${epair_server2}a <rt_targets>) \ + } prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + for port in $(seq 1 12); do + port=$((4200 + port)) + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=${port} + done + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4202 .* route-to: ${net_server1_6}::42:1@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4203 .* route-to: ${net_server2_4_host_server}@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4204 .* route-to: ${net_server1_4}.24@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4205 .* route-to: ${net_server1_4}.25@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4206 .* route-to: ${net_server2_6}::42:4@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4207 .* route-to: ${net_server2_6}::42:5@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4208 .* route-to: ${net_server2_4}.40@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4209 .* route-to: ${net_server2_4}.41@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4210 .* route-to: ${net_server2_4}.44@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4211 .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4212 .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for multiple gateways of mixed AF with prefixes and tables, round-robin selection, for IPv6 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv6_body() +{ + pf_map_addr_common + + # The "random" flag will pick random hosts from the table but will + # not dive into prefixes, always choosing the 0th address. + # Proper address family will be choosen. The choice being random makes + # testing it non-trivial. We do 10 attempts to have each target chosen. + # Hopefully this is enough to have this test pass often enough. + + # pf_map_addr() starts iterating over hosts of the pool from the 2nd + # host. This behaviour was here before adding prefer-ipv6-nexthop + # support, we test for it. Some targets are hosts and some are tables. + # Those are iterated too. Finally the iteration loops back + # to the beginning of the pool. Send out IPv6 probes. They will use only + # IPv6 addresses from the pool. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table <rt_targets> { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:4/127 }" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_6_host_server}) \ + (${epair_server1}a ${net_server1_6}::42:0/127) \ + (${epair_server2}a ${net_server2_4_host_server}) \ + (${epair_server2}a <rt_targets_empty>) \ + (${epair_server1}a ${net_server1_4}.24/31) \ + (${epair_server2}a <rt_targets>) \ + } prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + for port in $(seq 1 6); do + port=$((4200 + port)) + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=${port} + done + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4201\] .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4202\] .* route-to: ${net_server1_6}::42:1@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4203\] .* route-to: ${net_server2_6}::42:4@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4204\] .* route-to: ${net_server2_6}::42:5@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4205\] .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4206\] .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv6_cleanup() +{ + pft_cleanup +} + +atf_test_case "prefer_ipv6_nexthop_mixed_af_random_ipv4" "cleanup" + +prefer_ipv6_nexthop_mixed_af_random_table_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for a mixed-af table with random selection for IPv4 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_mixed_af_random_table_ipv4_body() +{ + pf_map_addr_common + + # Similarly to the test "random_table" the algorithm will choose + # IP addresses from the table not diving into prefixes. + # *prefer*-ipv6-nexthop means that IPv6 nexthops are preferred, + # so IPv4 ones will not be chosen as long as there are IPv6 ones + # available. With this tested there is no need for a test for IPv6-only + # next-hops table. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table <rt_targets> { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:0/127 ${net_server2_6}::42:4 }" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a <rt_targets>) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:4" + bad_targets="${net_server2_4}.40 ${net_server2_4}.41 ${net_server2_4}.44 ${net_server2_6}::42:1" + check_random IPv4 "${good_targets}" "${bad_targets}" +} + +prefer_ipv6_nexthop_mixed_af_random_table_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv4-only table with random selection for IPv4 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv4_body() +{ + pf_map_addr_common + + # Similarly to the test pf_map_addr:random_table the algorithm will + # choose IP addresses from the table not diving into prefixes. + # There are no IPv6 nexthops in the table, so the algorithm will + # fall back to IPv4. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table <rt_targets> { ${net_server2_4}.40/31 ${net_server2_4}.44 }" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a <rt_targets>) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_4}.40 ${net_server2_4}.44" + bad_targets="${net_server2_4}.41" + check_random IPv4 "${good_targets}" "${bad_targets}" +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv4-only table with random selection for IPv6 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv6_body() +{ + pf_map_addr_common + + # IPv6 packets can't be forwarded over IPv4 next-hops. + # The failure happens in pf_map_addr() and increases the respective + # error counter. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table <rt_targets> { ${net_server2_4}.40/31 ${net_server2_4}.44 }" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a <rt_targets>) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + atf_check -s exit:1 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4201 + + atf_check -o "match:map-failed +1 +" -x "jexec router pfctl -qvvsi 2> /dev/null" +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv6_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv6 prefix with random selection for IPv4 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv4_body() +{ + pf_map_addr_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_6}::42:0/127) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:1" + check_random IPv4 "${good_targets}" +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv6 prefix with random selection for IPv6 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv6_body() +{ + pf_map_addr_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_6}::42:0/127) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:1" + check_random IPv6 "${good_targets}" +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv6_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv4 prefix with random selection for IPv4 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv4_body() +{ + pf_map_addr_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_4}.40/31) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_4}.40 ${net_server2_4}.41" + check_random IPv4 "${good_targets}" +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv4 prefix with random selection for IPv6 packets' + atf_set require.user root + atf_set require.progs python3 scapy +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv6_body() +{ + pf_map_addr_common + + # IPv6 packets can't be forwarded over IPv4 next-hops. + # The failure happens in pf_map_addr() and increases the respective + # error counter. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_4}.40/31) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + atf_check -s exit:1 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4201 + + atf_check -o "match:map-failed +1 +" -x "jexec router pfctl -qvvsi 2> /dev/null" +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv6_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "v4" @@ -995,5 +1666,25 @@ atf_init_test_cases() atf_add_test_case "sticky" atf_add_test_case "ttl" atf_add_test_case "empty_pool" + # Tests for pf_map_addr() without prefer-ipv6-nexthop atf_add_test_case "table_loop" + atf_add_test_case "roundrobin" + atf_add_test_case "random_table" + atf_add_test_case "random_prefix" + # Tests for pf_map_addr() without prefer-ipv6-nexthop + # Next hop is a Single IP address + atf_add_test_case "prefer_ipv6_nexthop_single_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_single_ipv6" + # Next hop is tables and prefixes, accessed by the round-robin algorithm + atf_add_test_case "prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_mixed_af_roundrobin_ipv6" + # Next hop is a table, accessed by the random algorithm + atf_add_test_case "prefer_ipv6_nexthop_mixed_af_random_table_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_ipv4_random_table_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_ipv4_random_table_ipv6" + # Next hop is a prefix, accessed by the random algorithm + atf_add_test_case "prefer_ipv6_nexthop_ipv6_random_prefix_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_ipv6_random_prefix_ipv6" + atf_add_test_case "prefer_ipv6_nexthop_ipv4_random_prefix_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_ipv4_random_prefix_ipv6" } diff --git a/tests/sys/netpfil/pf/sctp.sh b/tests/sys/netpfil/pf/sctp.sh index 57dcdad1d866..78055f5a9dd2 100644 --- a/tests/sys/netpfil/pf/sctp.sh +++ b/tests/sys/netpfil/pf/sctp.sh @@ -673,6 +673,9 @@ pfsync_body() atf_fail "Initial SCTP connection failed" fi + # Give pfsync some time to do its thing + sleep 1 + # Verify that two has the connection too state=$(jexec ${j}two pfctl -ss | grep sctp) if [ -z "${state}" ]; diff --git a/tests/sys/netpfil/pf/src_track.sh b/tests/sys/netpfil/pf/src_track.sh index ae60a5df809b..d86b4ce6c466 100755 --- a/tests/sys/netpfil/pf/src_track.sh +++ b/tests/sys/netpfil/pf/src_track.sh @@ -526,10 +526,12 @@ mixed_af_body() "block" \ "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ "pass in on ${epair_tester}b \ - route-to { (${epair_server1}a ${net_server1_4_host_server}) \ - } sticky-address \ - inet6 proto tcp from any to 64:ff9b::/96 \ - af-to inet from ${net_clients_4}.0/${net_clients_4_mask} round-robin sticky-address" + route-to { \ + (${epair_server1}a ${net_server1_4_host_server}) \ + (${epair_server2}a ${net_server2_6_host_server}) \ + } prefer-ipv6-nexthop sticky-address \ + inet6 proto tcp from any to 64:ff9b::/96 \ + af-to inet from ${net_clients_4}.0/${net_clients_4_mask} round-robin sticky-address" atf_check -s exit:0 ${common_dir}/pft_ping.py \ --sendif ${epair_tester}a \ @@ -538,6 +540,20 @@ mixed_af_body() --to 64:ff9b::192.0.2.100 \ --ping-type=tcp3way \ --send-sport=4201 + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a \ + --replyif ${epair_tester}a \ + --fromaddr 2001:db8:44::1 \ + --to 64:ff9b::192.0.2.100 \ + --ping-type=tcp3way \ + --send-sport=4202 + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a \ + --replyif ${epair_tester}a \ + --fromaddr 2001:db8:44::2 \ + --to 64:ff9b::192.0.2.100 \ + --ping-type=tcp3way \ + --send-sport=4203 states=$(mktemp) || exit 1 jexec router pfctl -qvvss | normalize_pfctl_s > $states @@ -546,16 +562,22 @@ mixed_af_body() # States are checked for proper route-to information. # The route-to gateway is IPv4. + # FIXME: Sticky-address is broken for af-to pools! + # The SN is created but apparently not used, as seen in states. for state_regexp in \ - "${epair_tester}b tcp 203.0.113.0:4201 \(2001:db8:44::1\[4201\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 198.51.100.18@${epair_server1}a" \ + "${epair_server2}a tcp 203.0.113.0:4201 \(2001:db8:44::1\[4201\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a origif: ${epair_tester}b" \ + "${epair_server2}a tcp 203.0.113.1:4202 \(2001:db8:44::1\[4202\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a origif: ${epair_tester}b" \ + "${epair_server1}a tcp 203.0.113.2:4203 \(2001:db8:44::2\[4203\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 198.51.100.18@${epair_server1}a origif: ${epair_tester}b" \ ; do grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" done # Source nodes map IPv6 source address onto IPv4 gateway and IPv4 SNAT address. for node_regexp in \ - '2001:db8:44::1 -> 203.0.113.0 .* states 1, .* NAT/RDR sticky-address' \ - '2001:db8:44::1 -> 198.51.100.18 .* states 1, .* route sticky-address' \ + '2001:db8:44::2 -> 203.0.113.2 .* states 1, .* NAT/RDR sticky-address' \ + '2001:db8:44::2 -> 198.51.100.18 .* states 1, .* route sticky-address' \ + '2001:db8:44::1 -> 203.0.113.0 .* states 2, .* NAT/RDR sticky-address' \ + '2001:db8:44::1 -> 2001:db8:4202::2 .* states 2, .* route sticky-address' \ ; do grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" done diff --git a/tests/sys/netpfil/pf/table.sh b/tests/sys/netpfil/pf/table.sh index c773518e95e4..69fe12fc9804 100644 --- a/tests/sys/netpfil/pf/table.sh +++ b/tests/sys/netpfil/pf/table.sh @@ -641,9 +641,31 @@ large_body() -e match:"${expected}/${expected} addresses added." \ jexec alcatraz pfctl -t foo -T add -f ${pwd}/foo.lst actual=$(jexec alcatraz pfctl -t foo -T show | wc -l | awk '{ print $1; }') - if [[ $actual -ne $expected ]]; then + if [ $actual -ne $expected ]; then atf_fail "Unexpected number of table entries $expected $acual" fi + + # The second pass should work too, but confirm we've inserted everything + atf_check -s exit:0 \ + -e match:"0/${expected} addresses added." \ + jexec alcatraz pfctl -t foo -T add -f ${pwd}/foo.lst + + echo '42.42.42.42' >> ${pwd}/foo.lst + expected=$((${expected} + 1)) + + # And we can also insert one additional address + atf_check -s exit:0 \ + -e match:"1/${expected} addresses added." \ + jexec alcatraz pfctl -t foo -T add -f ${pwd}/foo.lst + + # Try to delete one address + atf_check -s exit:0 \ + -e match:"1/1 addresses deleted." \ + jexec alcatraz pfctl -t foo -T delete 42.42.42.42 + # And again, for the same address + atf_check -s exit:0 \ + -e match:"0/1 addresses deleted." \ + jexec alcatraz pfctl -t foo -T delete 42.42.42.42 } large_cleanup() @@ -651,6 +673,80 @@ large_cleanup() pft_cleanup } +atf_test_case "show_recursive" "cleanup" +show_recursive_head() +{ + atf_set descr 'Test displaying tables in every anchor' + atf_set require.user root +} + +show_recursive_body() +{ + pft_init + + vnet_mkjail alcatraz + + pft_set_rules alcatraz \ + + (echo "table <bar> persist" + echo "block in quick from <bar> to any" + ) | jexec alcatraz pfctl -a anchorage -f - + + pft_set_rules noflush alcatraz \ + "table <foo> counters { 192.0.2.1 }" \ + "pass in from <foo>" \ + "anchor anchorage" + + jexec alcatraz pfctl -sr -a "*" + + atf_check -s exit:0 -e ignore -o match:'-pa-r-- bar@anchorage' \ + jexec alcatraz pfctl -v -a "*" -sT + atf_check -s exit:0 -e ignore -o match:'--a-r-C foo' \ + jexec alcatraz pfctl -v -a "*" -sT +} + +show_recursive_cleanup() +{ + pft_cleanup +} + +atf_test_case "in_anchor" "cleanup" +in_anchor_head() +{ + atf_set descr 'Test declaring tables in anchors' + atf_set require.user root +} + +in_anchor_body() +{ + pft_init + + epair_send=$(vnet_mkepair) + ifconfig ${epair_send}a 192.0.2.1/24 up + + vnet_mkjail alcatraz ${epair_send}b + jexec alcatraz ifconfig ${epair_send}b 192.0.2.2/24 up + + jexec alcatraz pfctl -e + + pft_set_rules alcatraz \ + "block all" \ + "anchor \"foo\" {\n + table <bar> counters { 192.0.2.1 }\n + pass in from <bar>\n + }\n" + + atf_check -s exit:0 -o ignore ping -c 3 192.0.2.2 + + jexec alcatraz pfctl -sr -a "*" -vv + jexec alcatraz pfctl -sT -a "*" -vv +} + +in_anchor_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "v4_counters" @@ -667,4 +763,6 @@ atf_init_test_cases() atf_add_test_case "anchor" atf_add_test_case "flush" atf_add_test_case "large" + atf_add_test_case "show_recursive" + atf_add_test_case "in_anchor" } diff --git a/tests/sys/netpfil/pf/utils.subr b/tests/sys/netpfil/pf/utils.subr index 3f8d437920f9..8b3b06bf3bba 100644 --- a/tests/sys/netpfil/pf/utils.subr +++ b/tests/sys/netpfil/pf/utils.subr @@ -217,6 +217,7 @@ setup_router_server_ipv4() jexec server route add -net ${net_tester} ${net_server_host_router} inetd_conf=$(mktemp) echo "discard stream tcp nowait root internal" > $inetd_conf + echo "echo stream tcp nowait root internal" >> $inetd_conf jexec server inetd -p ${PWD}/inetd.pid $inetd_conf } @@ -271,11 +272,12 @@ setup_router_server_ipv6() jexec server route add -6 ${net_tester} ${net_server_host_router} inetd_conf=$(mktemp) echo "discard stream tcp6 nowait root internal" > $inetd_conf + echo "echo stream tcp6 nowait root internal" >> $inetd_conf jexec server inetd -p ${PWD}/inetd.pid $inetd_conf } -# Create a router and 2 server jails for nat64 and rfc5549 test cases. -# The router is connected to servers, both are dual-stack, and to the +# Create a router and 2 server jails for nat64 and prefer-ipv6-nexthop test +# cases. The router is connected to servers, both are dual-stack, and to the # tester jail. All links are dual stack. setup_router_server_nat64() { @@ -353,6 +355,7 @@ setup_router_server_nat64() inetd_conf=$(mktemp) echo "discard stream tcp46 nowait root internal" >> $inetd_conf + echo "echo stream tcp46 nowait root internal" >> $inetd_conf vnet_mkjail server1 ${epair_server1}b jexec server1 /etc/rc.d/netif start lo0 |