# # 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 { ${net_tester_host_tester} }" \ "table { ${net_server_host_server} }" \ "table { ${net_server_host_server} }" \ "block" \ "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ "match in on ${epair_tester}b inet6 proto tcp from scrub (random-id)" \ "pass in on ${epair_tester}b inet6 proto tcp from keep state (max-src-states 3 source-track rule)" \ "match out on ${epair_server}a inet6 proto tcp to scrub (random-id)" \ "pass out on ${epair_server}a inet6 proto tcp to 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 { ${net_tester_host_tester} }" \ "table { ${net_tester_host_tester} }" \ "table { ${net_server_host_server} }" \ "table { ${net_server_host_server} }" \ "block" \ "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ "match in on ${epair_tester}b inet6 proto tcp from " \ "match in on ${epair_tester}b inet6 proto tcp from " \ "pass in on ${epair_tester}b inet6 proto tcp from no state" \ "pass out on ${epair_tester}b inet6 proto tcp to no state" \ "match in on ${epair_server}a inet6 proto tcp from " \ "pass in on ${epair_server}a inet6 proto tcp from no state" \ "match out on ${epair_server}a inet6 proto tcp from no state" \ "pass out on ${epair_server}a inet6 proto tcp to 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 { ${net_server_host_server} }" \ "table { ${net_server_host_server} }" \ "block" \ "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ "match in on ${epair_tester}b inet6 proto tcp to scrub (random-id)" \ "block in on ${epair_tester}b inet6 proto tcp to " \ "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 { ${net_server_host_server} }" \ "table { ${net_server_host_server} }" \ "block" \ "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ "match in on ${epair_tester}b inet6 proto tcp to scrub (random-id)" \ "pass in on ${epair_tester}b inet6 proto tcp to 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 { ${net_tester_host_tester} }" \ "table { ${net_server_host_server} }" \ "nat on ${epair_server}a inet6 proto tcp from to -> ${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 { ${net_tester_host_tester} }" \ "table { ${net_server_host_server} }" \ "table { ${net_server_host_router} }" \ "table { ${net_server_host_server} }" \ "table { ${net_server_host_router} }" \ "table { ${net_server_host_server} }" \ "nat on ${epair_server}a inet6 proto tcp from to -> ${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 to scrub (random-id)" \ "pass out on ${epair_server}a inet6 proto tcp from to 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 { ${net_tester_host_tester} }" \ "table { ${net_server_host_server} }" \ "table { ${net_tester_host_tester} }" \ "table { ${net_server_host_server} }" \ "table { ${net_server_host_router} }" \ "table { ${net_server_host_server} }" \ "table { ${net_server_host_router} }" \ "table { ${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 to scrub (random-id)" \ "match out on ${epair_server}a inet6 proto tcp from to nat-to ${net_server_host_router}" \ "match out on ${epair_server}a inet6 proto tcp from to scrub (random-id)" \ "pass out on ${epair_server}a inet6 proto tcp from to 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 { ${net_tester_host_tester} }" \ "table { ${net_server_host_server} }" \ "table { ${net_tester_host_tester} }" \ "table { ${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 to scrub (random-id)" \ "pass out on ${epair_server}a inet6 proto tcp from to 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 { ${net_tester_host_tester} }" \ "table { ${net_server_host_notserver} }" \ "table { ${net_tester_host_tester} }" \ "table { ${net_server_host_notserver} }" \ "table { ${net_tester_host_tester} }" \ "table { ${net_server_host_server} }" \ "table { ${net_tester_host_tester} }" \ "table { ${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 to scrub (random-id)" \ "match in on ${epair_tester}b inet6 proto tcp from to rdr-to ${net_server_host_server}" \ "match in on ${epair_tester}b inet6 proto tcp from to scrub (random-id)" \ "pass in on ${epair_tester}b inet6 proto tcp from to 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 { ${net_tester_6_host_tester} }" \ "table { 64:ff9b::${net_server1_4_host_server} }" \ "table { ${net_tester_6_host_tester} }" \ "table { 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 to scrub (random-id)" \ "pass in on ${epair_tester}b inet6 proto tcp from to \ 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 { ${net_tester_6_host_tester} }" \ "table { 64:ff9b::${net_server1_4_host_server} }" \ "table { ${net_tester_6_host_tester} }" \ "table { 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 to scrub (random-id)" \ "pass out on ${epair_server1}a inet6 proto tcp from to \ 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" }