1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2025 Kajetan Staszkiewicz 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26 27. $(atf_get_srcdir)/utils.subr 28 29get_counters() 30{ 31 echo " === rules ===" 32 rules=$(mktemp) || exit 33 (jexec router pfctl -qvvsn ; jexec router pfctl -qvvsr) | normalize_pfctl_s > $rules 34 cat $rules 35 36 echo " === tables ===" 37 tables=$(mktemp) || exit 1 38 jexec router pfctl -qvvsT > $tables 39 cat $tables 40 41 echo " === states ===" 42 states=$(mktemp) || exit 1 43 jexec router pfctl -qvvss | normalize_pfctl_s > $states 44 cat $states 45 46 echo " === nodes ===" 47 nodes=$(mktemp) || exit 1 48 jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes 49 cat $nodes 50} 51 52atf_test_case "match_pass_state" "cleanup" 53match_pass_state_head() 54{ 55 atf_set descr 'Counters on match and pass rules' 56 atf_set require.user root 57} 58 59match_pass_state_body() 60{ 61 setup_router_server_ipv6 62 63 # Thest counters for a statefull firewall. Expose the behaviour of 64 # increasing table counters if a table is used multiple times. 65 # The table "tbl_in" is used both in match and pass rule. It's counters 66 # are incremented twice. The tables "tbl_out_match" and "tbl_out_pass" 67 # are used only once and have their countes increased only once. 68 # Test source node counters for this simple scenario too. 69 pft_set_rules router \ 70 "set state-policy if-bound" \ 71 "table <tbl_in> { ${net_tester_host_tester} }" \ 72 "table <tbl_out_pass> { ${net_server_host_server} }" \ 73 "table <tbl_out_match> { ${net_server_host_server} }" \ 74 "block" \ 75 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 76 "match in on ${epair_tester}b inet6 proto tcp from <tbl_in> scrub (random-id)" \ 77 "pass in on ${epair_tester}b inet6 proto tcp from <tbl_in> keep state (max-src-states 3 source-track rule)" \ 78 "match out on ${epair_server}a inet6 proto tcp to <tbl_out_match> scrub (random-id)" \ 79 "pass out on ${epair_server}a inet6 proto tcp to <tbl_out_pass> keep state" 80 81 # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. 82 atf_check -s exit:0 -o match:"This is a test" -x \ 83 "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" 84 # Let FINs pass through. 85 sleep 1 86 get_counters 87 88 for rule_regexp in \ 89 "@3 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ 90 "@4 pass in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ 91 "@5 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 92 "@6 pass out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 93 ; do 94 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 95 done 96 97 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" 98 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" 99 for table_test in \ 100 "tbl_in___${table_counters_double}" \ 101 "tbl_out_match___${table_counters_single}" \ 102 "tbl_out_pass___${table_counters_single}" \ 103 ; do 104 table_name=${table_test%%___*} 105 table_regexp=${table_test##*___} 106 table=$(mktemp) || exit 1 107 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 108 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 109 done; 110 111 for state_regexp in \ 112 "${epair_tester}b tcp ${net_server_host_server}.* <- ${net_tester_host_tester}.* 6:4 pkts, 455:311 bytes, rule 4," \ 113 "${epair_server}a tcp ${net_server_host_tester}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 6," \ 114 ; do 115 grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" 116 done 117 118 for node_regexp in \ 119 "${net_tester_host_tester} -> :: .* 10 pkts, 766 bytes, filter rule 4, limit source-track"\ 120 ; do 121 grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" 122 done 123} 124 125match_pass_state_cleanup() 126{ 127 pft_cleanup 128} 129 130atf_test_case "match_pass_no_state" "cleanup" 131match_pass_no_state_head() 132{ 133 atf_set descr 'Counters on match and pass rules without keep state' 134 atf_set require.user root 135} 136 137match_pass_no_state_body() 138{ 139 setup_router_server_ipv6 140 141 # Test counters for a stateless firewall. 142 # The table "tbl_in" is used both in match and pass rule in the inbound 143 # direction. The "In/Pass" counter is incremented twice. The table 144 # "tbl_inout" matches the same host on inbound and outbound direction. 145 # It will also be incremented twice. The tables "tbl_out_match" and 146 # "tbl_out_pass" will have their counters increased only once. 147 pft_set_rules router \ 148 "table <tbl_in> { ${net_tester_host_tester} }" \ 149 "table <tbl_inout> { ${net_tester_host_tester} }" \ 150 "table <tbl_out_match> { ${net_server_host_server} }" \ 151 "table <tbl_out_pass> { ${net_server_host_server} }" \ 152 "block" \ 153 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 154 "match in on ${epair_tester}b inet6 proto tcp from <tbl_inout>" \ 155 "match in on ${epair_tester}b inet6 proto tcp from <tbl_in>" \ 156 "pass in on ${epair_tester}b inet6 proto tcp from <tbl_in> no state" \ 157 "pass out on ${epair_tester}b inet6 proto tcp to <tbl_in> no state" \ 158 "match in on ${epair_server}a inet6 proto tcp from <tbl_out_match>" \ 159 "pass in on ${epair_server}a inet6 proto tcp from <tbl_out_pass> no state" \ 160 "match out on ${epair_server}a inet6 proto tcp from <tbl_inout> no state" \ 161 "pass out on ${epair_server}a inet6 proto tcp to <tbl_out_pass> no state" 162 163 # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. 164 atf_check -s exit:0 -o match:"This is a test" -x \ 165 "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" 166 sleep 1 167 get_counters 168 169 for rule_regexp in \ 170 "@3 match in on ${epair_tester}b .* Packets: 6 Bytes: 455 " \ 171 "@4 match in on ${epair_tester}b .* Packets: 6 Bytes: 455 " \ 172 "@5 pass in on ${epair_tester}b .* Packets: 6 Bytes: 455 " \ 173 "@6 pass out on ${epair_tester}b .* Packets: 4 Bytes: 311 " \ 174 "@7 match in on ${epair_server}a .* Packets: 4 Bytes: 311 " \ 175 "@8 pass in on ${epair_server}a .* Packets: 4 Bytes: 311 " \ 176 "@10 pass out on ${epair_server}a .* Packets: 6 Bytes: 455 " \ 177 ; do 178 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 179 done 180 181 for table_test in \ 182 "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" \ 183 "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" \ 184 "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" \ 185 "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" \ 186 ; do 187 table_name=${table_test%%___*} 188 table_regexp=${table_test##*___} 189 table=$(mktemp) || exit 1 190 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 191 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 192 done; 193} 194 195match_pass_no_state_cleanup() 196{ 197 pft_cleanup 198} 199 200atf_test_case "match_block" "cleanup" 201match_block_head() 202{ 203 atf_set descr 'Counters on match and block rules' 204 atf_set require.user root 205} 206 207match_block_body() 208{ 209 setup_router_server_ipv6 210 211 # Stateful firewall with a blocking rule. The rule will have its 212 # counters increased because it matches and applies correctly. 213 # The "match" rule before the "pass" rule will have its counters 214 # increased for blocked traffic too. 215 pft_set_rules router \ 216 "set state-policy if-bound" \ 217 "table <tbl_in_match> { ${net_server_host_server} }" \ 218 "table <tbl_in_block> { ${net_server_host_server} }" \ 219 "block" \ 220 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 221 "match in on ${epair_tester}b inet6 proto tcp to <tbl_in_match> scrub (random-id)" \ 222 "block in on ${epair_tester}b inet6 proto tcp to <tbl_in_block>" \ 223 "pass out on ${epair_server}a inet6 proto tcp keep state" 224 225 # Wait 3 seconds, that will cause 2 SYNs to be sent out. 226 echo 'This is a test' | nc -w3 ${net_server_host_server} echo 227 sleep 1 228 get_counters 229 230 for rule_regexp in \ 231 "@3 match in on ${epair_tester}b .* Packets: 2 Bytes: 160 States: 0 " \ 232 "@4 block drop in on ${epair_tester}b .* Packets: 2 Bytes: 160 States: 0 " \ 233 ; do 234 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 235 done 236 237 # OpenBSD has (In|Out)/Match. We don't (yet) have it in FreeBSD 238 # so we follow the action of the "pass" rule ("block" for this test) 239 # in "match" rules. 240 for table_test in \ 241 "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" \ 242 "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" \ 243 ; do 244 table_name=${table_test%%___*} 245 table_regexp=${table_test##*___} 246 table=$(mktemp) || exit 1 247 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 248 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 249 done; 250} 251 252match_block_cleanup() 253{ 254 pft_cleanup 255} 256 257atf_test_case "match_fail" "cleanup" 258match_fail_head() 259{ 260 atf_set descr 'Counters on match and failing pass rules' 261 atf_set require.user root 262} 263 264match_fail_body() 265{ 266 setup_router_server_ipv6 267 268 # Statefull firewall with a failing "pass" rule. 269 # When the rule can't apply it will not have its counters increased. 270 pft_set_rules router \ 271 "set state-policy if-bound" \ 272 "table <tbl_in_match> { ${net_server_host_server} }" \ 273 "table <tbl_in_fail> { ${net_server_host_server} }" \ 274 "block" \ 275 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 276 "match in on ${epair_tester}b inet6 proto tcp to <tbl_in_match> scrub (random-id)" \ 277 "pass in on ${epair_tester}b inet6 proto tcp to <tbl_in_fail> keep state (max 1)" \ 278 "pass out on ${epair_server}a inet6 proto tcp keep state" 279 280 # The first test will pass and increase the counters for all rules. 281 echo 'This is a test' | nc -w3 ${net_server_host_server} echo 282 # The second test will go through the "match" rules but fail 283 # on the "pass" rule due to 'keep state (max 1)'. 284 # Wait 3 seconds, that will cause 2 SYNs to be sent out. 285 echo 'This is a test' | nc -w3 ${net_server_host_server} echo 286 sleep 1 287 get_counters 288 289 for rule_regexp in \ 290 "@3 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ 291 "@4 pass in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ 292 ; do 293 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 294 done 295 296 $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" 297 for table_test in \ 298 "tbl_in_match___${table_counters_single}" \ 299 "tbl_in_fail___${table_counters_single}" \ 300 ; do 301 table_name=${table_test%%___*} 302 table_regexp=${table_test##*___} 303 table=$(mktemp) || exit 1 304 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 305 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 306 done; 307} 308 309match_fail_cleanup() 310{ 311 pft_cleanup 312} 313 314atf_test_case "nat_natonly" "cleanup" 315nat_natonly_head() 316{ 317 atf_set descr 'Counters on only a NAT rule creating state' 318 atf_set require.user root 319} 320 321nat_natonly_body() 322{ 323 setup_router_server_ipv6 324 325 # NAT is applied on the "nat" rule. 326 # The "nat" rule matches on pre-NAT addresses. There is no separate 327 # "pass" rule so the "nat" rule creates the state. 328 pft_set_rules router \ 329 "set state-policy if-bound" \ 330 "table <tbl_src_nat> { ${net_tester_host_tester} }" \ 331 "table <tbl_dst_nat> { ${net_server_host_server} }" \ 332 "nat on ${epair_server}a inet6 proto tcp from <tbl_src_nat> to <tbl_dst_nat> -> ${net_server_host_router}" 333 334 # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. 335 atf_check -s exit:0 -o match:"This is a test" -x \ 336 "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" 337 sleep 1 338 get_counters 339 340 for rule_regexp in \ 341 "@0 nat on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 342 ; do 343 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 344 done 345 346 # All tables have counters increased for In/Pass and Out/Pass, not XPass. 347 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" 348 for table_test in \ 349 "tbl_src_nat___${table_counters}" \ 350 "tbl_dst_nat___${table_counters}" \ 351 ; do 352 table_name=${table_test%%___*} 353 table_regexp=${table_test##*___} 354 table=$(mktemp) || exit 1 355 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 356 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 357 done; 358 359 for state_regexp in \ 360 "all tcp ${net_server_host_router}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes" \ 361 ; do 362 grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" 363 done 364} 365 366nat_natonly_cleanup() 367{ 368 pft_cleanup 369} 370 371atf_test_case "nat_nat" "cleanup" 372nat_nat_head() 373{ 374 atf_set descr 'Counters on NAT, match and pass rules with keep state' 375 atf_set require.user root 376} 377 378nat_nat_body() 379{ 380 setup_router_server_ipv6 381 382 # NAT is applied in the NAT ruleset. 383 # The "nat" rule matches on pre-NAT addresses. 384 # The "match" rule matches on post-NAT addresses. 385 # The "pass" rule matches on post-NAT addresses and creates the state. 386 pft_set_rules router \ 387 "set state-policy if-bound" \ 388 "table <tbl_src_nat> { ${net_tester_host_tester} }" \ 389 "table <tbl_dst_nat> { ${net_server_host_server} }" \ 390 "table <tbl_src_match> { ${net_server_host_router} }" \ 391 "table <tbl_dst_match> { ${net_server_host_server} }" \ 392 "table <tbl_src_pass> { ${net_server_host_router} }" \ 393 "table <tbl_dst_pass> { ${net_server_host_server} }" \ 394 "nat on ${epair_server}a inet6 proto tcp from <tbl_src_nat> to <tbl_dst_nat> -> ${net_server_host_router}" \ 395 "block" \ 396 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 397 "pass in on ${epair_tester}b inet6 proto tcp keep state" \ 398 "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \ 399 "pass out on ${epair_server}a inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> keep state" 400 401 # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. 402 atf_check -s exit:0 -o match:"This is a test" -x \ 403 "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" 404 sleep 1 405 get_counters 406 407 for rule_regexp in \ 408 "@0 nat on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 409 "@4 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 410 "@5 pass out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 411 ; do 412 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 413 done 414 415 # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. 416 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" 417 for table_test in \ 418 "tbl_src_nat___${table_counters}" \ 419 "tbl_dst_nat___${table_counters}" \ 420 "tbl_src_match___${table_counters}" \ 421 "tbl_dst_match___${table_counters}" \ 422 "tbl_src_pass___${table_counters}" \ 423 "tbl_dst_pass___${table_counters}" \ 424 ; do 425 table_name=${table_test%%___*} 426 table_regexp=${table_test##*___} 427 table=$(mktemp) || exit 1 428 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 429 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 430 done; 431 432 for state_regexp in \ 433 "${epair_server}a tcp ${net_server_host_router}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 5," \ 434 ; do 435 grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" 436 done 437} 438 439nat_nat_cleanup() 440{ 441 pft_cleanup 442} 443 444atf_test_case "nat_match" "cleanup" 445nat_match_head() 446{ 447 atf_set descr 'Counters on match with NAT and pass rules' 448 atf_set require.user root 449} 450 451nat_match_body() 452{ 453 setup_router_server_ipv6 454 455 # NAT is applied on the "match" rule. 456 # The "match" rule up to and including the NAT rule match on pre-NAT addresses. 457 # The "match" rule after NAT matches on post-NAT addresses. 458 # The "pass" rule matches on post-NAT addresses and creates the state. 459 pft_set_rules router \ 460 "set state-policy if-bound" \ 461 "table <tbl_src_match1> { ${net_tester_host_tester} }" \ 462 "table <tbl_dst_match1> { ${net_server_host_server} }" \ 463 "table <tbl_src_match2> { ${net_tester_host_tester} }" \ 464 "table <tbl_dst_match2> { ${net_server_host_server} }" \ 465 "table <tbl_src_match3> { ${net_server_host_router} }" \ 466 "table <tbl_dst_match3> { ${net_server_host_server} }" \ 467 "table <tbl_src_pass> { ${net_server_host_router} }" \ 468 "table <tbl_dst_pass> { ${net_server_host_server} }" \ 469 "block" \ 470 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 471 "pass in on ${epair_tester}b inet6 proto tcp keep state" \ 472 "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match1> to <tbl_dst_match1> scrub (random-id)" \ 473 "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match2> to <tbl_dst_match2> nat-to ${net_server_host_router}" \ 474 "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match3> to <tbl_dst_match3> scrub (random-id)" \ 475 "pass out on ${epair_server}a inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> keep state" 476 477 # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. 478 atf_check -s exit:0 -o match:"This is a test" -x \ 479 "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" 480 sleep 1 481 get_counters 482 483 for rule_regexp in \ 484 "@4 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 485 "@5 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 486 "@6 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 487 "@7 pass out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 488 ; do 489 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 490 done 491 492 # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. 493 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" 494 for table_test in \ 495 "tbl_src_match1___${table_counters}" \ 496 "tbl_dst_match1___${table_counters}" \ 497 "tbl_src_match2___${table_counters}" \ 498 "tbl_dst_match2___${table_counters}" \ 499 "tbl_src_match3___${table_counters}" \ 500 "tbl_dst_match3___${table_counters}" \ 501 "tbl_src_pass___${table_counters}" \ 502 "tbl_dst_pass___${table_counters}" \ 503 ; do 504 table_name=${table_test%%___*} 505 table_regexp=${table_test##*___} 506 table=$(mktemp) || exit 1 507 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 508 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 509 done; 510 511 for state_regexp in \ 512 "${epair_server}a tcp ${net_server_host_tester}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 7, " \ 513 ; do 514 grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" 515 done 516} 517 518nat_match_cleanup() 519{ 520 pft_cleanup 521} 522 523atf_test_case "nat_pass" "cleanup" 524nat_pass_head() 525{ 526 atf_set descr 'Counters on match, and pass with NAT rules' 527 atf_set require.user root 528} 529 530nat_pass_body() 531{ 532 setup_router_server_ipv6 533 534 # NAT is applied on the "pass" rule which also creates the state. 535 # All rules match on pre-NAT addresses. 536 pft_set_rules router \ 537 "set state-policy if-bound" \ 538 "table <tbl_src_match> { ${net_tester_host_tester} }" \ 539 "table <tbl_dst_match> { ${net_server_host_server} }" \ 540 "table <tbl_src_pass> { ${net_tester_host_tester} }" \ 541 "table <tbl_dst_pass> { ${net_server_host_server} }" \ 542 "block" \ 543 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 544 "pass in on ${epair_tester}b inet6 proto tcp keep state" \ 545 "match out on ${epair_server}a inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \ 546 "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" 547 548 # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. 549 atf_check -s exit:0 -o match:"This is a test" -x \ 550 "echo 'This is a test' | nc -w3 ${net_server_host_server} echo" 551 sleep 1 552 get_counters 553 554 for rule_regexp in \ 555 "@4 match out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 556 "@5 pass out on ${epair_server}a .* Packets: 10 Bytes: 766 States: 1 " \ 557 ; do 558 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 559 done 560 561 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" 562 for table_test in \ 563 "tbl_src_match___${table_counters}" \ 564 "tbl_dst_match___${table_counters}" \ 565 "tbl_src_pass___${table_counters}" \ 566 "tbl_dst_pass___${table_counters}" \ 567 ; do 568 table_name=${table_test%%___*} 569 table_regexp=${table_test##*___} 570 table=$(mktemp) || exit 1 571 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 572 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 573 done; 574 575 for state_regexp in \ 576 "${epair_server}a tcp ${net_server_host_router}.* -> ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 5," \ 577 ; do 578 grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" 579 done 580} 581 582nat_pass_cleanup() 583{ 584 pft_cleanup 585} 586 587atf_test_case "rdr_match" "cleanup" 588rdr_match_head() 589{ 590 atf_set descr 'Counters on match with RDR and pass rules' 591 atf_set require.user root 592} 593 594rdr_match_body() 595{ 596 setup_router_server_ipv6 597 598 # Similar to the nat_match test but for the RDR action. 599 # Hopefully we don't need all other tests duplicated for RDR. 600 # Send traffic to a non-existing host, RDR it to the server. 601 # 602 # The "match" rule up to and including the RDR rule match on pre-RDR dst address. 603 # The "match" rule after NAT matches on post-RDR dst address. 604 # The "pass" rule matches on post-RDR dst address. 605 net_server_host_notserver=${net_server_host_server%%::*}::3 606 pft_set_rules router \ 607 "set state-policy if-bound" \ 608 "table <tbl_src_match1> { ${net_tester_host_tester} }" \ 609 "table <tbl_dst_match1> { ${net_server_host_notserver} }" \ 610 "table <tbl_src_match2> { ${net_tester_host_tester} }" \ 611 "table <tbl_dst_match2> { ${net_server_host_notserver} }" \ 612 "table <tbl_src_match3> { ${net_tester_host_tester} }" \ 613 "table <tbl_dst_match3> { ${net_server_host_server} }" \ 614 "table <tbl_src_pass> { ${net_tester_host_tester} }" \ 615 "table <tbl_dst_pass> { ${net_server_host_server} }" \ 616 "block" \ 617 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 618 "pass out on ${epair_server}a inet6 proto tcp keep state" \ 619 "match in on ${epair_tester}b inet6 proto tcp from <tbl_src_match1> to <tbl_dst_match1> scrub (random-id)" \ 620 "match in on ${epair_tester}b inet6 proto tcp from <tbl_src_match2> to <tbl_dst_match2> rdr-to ${net_server_host_server}" \ 621 "match in on ${epair_tester}b inet6 proto tcp from <tbl_src_match3> to <tbl_dst_match3> scrub (random-id)" \ 622 "pass in on ${epair_tester}b inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> keep state" 623 624 # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. 625 atf_check -s exit:0 -o match:"This is a test" -x \ 626 "echo 'This is a test' | nc -w3 ${net_server_host_notserver} echo" 627 sleep 1 628 get_counters 629 630 for rule_regexp in \ 631 "@4 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ 632 "@5 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ 633 "@6 match in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ 634 "@7 pass in on ${epair_tester}b .* Packets: 10 Bytes: 766 States: 1 " \ 635 ; do 636 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 637 done 638 639 # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. 640 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" 641 for table_test in \ 642 "tbl_src_match1___${table_counters}" \ 643 "tbl_dst_match1___${table_counters}" \ 644 "tbl_src_match2___${table_counters}" \ 645 "tbl_dst_match2___${table_counters}" \ 646 "tbl_src_match3___${table_counters}" \ 647 "tbl_dst_match3___${table_counters}" \ 648 "tbl_src_pass___${table_counters}" \ 649 "tbl_dst_pass___${table_counters}" \ 650 ; do 651 table_name=${table_test%%___*} 652 table_regexp=${table_test##*___} 653 table=$(mktemp) || exit 1 654 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 655 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 656 done; 657 658 for state_regexp in \ 659 "${epair_tester}b tcp ${net_server_host_server}.* 6:4 pkts, 455:311 bytes, rule 7, " \ 660 ; do 661 grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" 662 done 663} 664 665rdr_match_cleanup() 666{ 667 pft_cleanup 668} 669 670atf_test_case "nat64_in" "cleanup" 671nat64_in_head() 672{ 673 atf_set descr 'Counters on match and inbound af-to rules' 674 atf_set require.user root 675} 676 677nat64_in_body() 678{ 679 setup_router_server_nat64 680 681 pft_set_rules router \ 682 "set state-policy if-bound" \ 683 "table <tbl_src_match> { ${net_tester_6_host_tester} }" \ 684 "table <tbl_dst_match> { 64:ff9b::${net_server1_4_host_server} }" \ 685 "table <tbl_src_pass> { ${net_tester_6_host_tester} }" \ 686 "table <tbl_dst_pass> { 64:ff9b::${net_server1_4_host_server} }" \ 687 "block log" \ 688 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 689 "match in on ${epair_tester}b inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \ 690 "pass in on ${epair_tester}b inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> \ 691 af-to inet from (${epair_server1}a) \ 692 keep state" 693 694 # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. 695 atf_check -s exit:0 -o match:"This is a test" -x \ 696 "echo 'This is a test' | nc -w3 64:ff9b::${net_server1_4_host_server} echo" 697 sleep 1 698 get_counters 699 700 # The amount of packets is counted properly but sizes are not because 701 # pd->tot_len is always post-nat, even when updating pre-nat counters. 702 for rule_regexp in \ 703 "@3 match in on ${epair_tester}b .* Packets: 10 Bytes: 686 States: 1 " \ 704 "@4 pass in on ${epair_tester}b .* Packets: 10 Bytes: 686 States: 1 " \ 705 ; do 706 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 707 done 708 709 # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. 710 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" 711 for table_test in \ 712 "tbl_src_match___${table_counters}" \ 713 "tbl_dst_match___${table_counters}" \ 714 "tbl_src_pass___${table_counters}" \ 715 "tbl_dst_pass___${table_counters}" \ 716 ; do 717 table_name=${table_test%%___*} 718 table_regexp=${table_test##*___} 719 table=$(mktemp) || exit 1 720 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 721 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 722 done; 723 724 for state_regexp in \ 725 "${epair_server1}a tcp ${net_server_host_tester}.* 6:4 pkts, 455:231 bytes, rule 4, " \ 726 ; do 727 grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" 728 done 729 730 echo " === interfaces === " 731 echo " === tester === " 732 jexec router pfctl -qvvsI -i ${epair_tester}b 733 echo " === server === " 734 jexec router pfctl -qvvsI -i ${epair_server1}a 735 echo " === " 736} 737 738nat64_in_cleanup() 739{ 740 pft_cleanup 741} 742 743atf_test_case "nat64_out" "cleanup" 744nat64_out_head() 745{ 746 atf_set descr 'Counters on match and outbound af-to rules' 747 atf_set require.user root 748} 749 750nat64_out_body() 751{ 752 setup_router_server_nat64 753 754 # af-to in outbound path requires routes for the pre-af-to traffic. 755 jexec router route add -inet6 64:ff9b::/96 -iface ${epair_server1}a 756 757 pft_set_rules router \ 758 "set state-policy if-bound" \ 759 "table <tbl_src_match> { ${net_tester_6_host_tester} }" \ 760 "table <tbl_dst_match> { 64:ff9b::${net_server1_4_host_server} }" \ 761 "table <tbl_src_pass> { ${net_tester_6_host_tester} }" \ 762 "table <tbl_dst_pass> { 64:ff9b::${net_server1_4_host_server} }" \ 763 "block log " \ 764 "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ 765 "pass in on ${epair_tester}b inet6 proto tcp keep state" \ 766 "match out on ${epair_server1}a inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \ 767 "pass out on ${epair_server1}a inet6 proto tcp from <tbl_src_pass> to <tbl_dst_pass> \ 768 af-to inet from (${epair_server1}a) \ 769 keep state" 770 771 # Use a real TCP connection so that it will be properly closed, guaranteeing the amount of packets. 772 atf_check -s exit:0 -o match:"This is a test" -x \ 773 "echo 'This is a test' | nc -w3 64:ff9b::${net_server1_4_host_server} echo" 774 sleep 1 775 get_counters 776 777 for rule_regexp in \ 778 "@4 match out on ${epair_server1}a .* Packets: 10 Bytes: 686 States: 1 " \ 779 "@5 pass out on ${epair_server1}a .* Packets: 10 Bytes: 686 States: 1 " \ 780 ; do 781 grep -qE "${rule_regexp}" $rules || atf_fail "Rule regexp not found for '${rule_regexp}'" 782 done 783 784 # All tables have counters increased for In/Pass and Out/Pass, not XPass nor Block. 785 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" 786 for table_test in \ 787 "tbl_src_match___${table_counters}" \ 788 "tbl_dst_match___${table_counters}" \ 789 "tbl_src_pass___${table_counters}" \ 790 "tbl_dst_pass___${table_counters}" \ 791 ; do 792 table_name=${table_test%%___*} 793 table_regexp=${table_test##*___} 794 table=$(mktemp) || exit 1 795 cat $tables | grep -A10 $table_name | tr '\n' ' ' | awk '{gsub("[\\[\\]]", " ", $0); gsub("[[:blank:]]+"," ",$0); print $0}' > ${table} 796 grep -qE "${table_regexp}" ${table} || atf_fail "Bad counters for table ${table_name}" 797 done; 798 799 for state_regexp in \ 800 "${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," \ 801 ; do 802 grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" 803 done 804 805 echo " === interfaces === " 806 echo " === tester === " 807 jexec router pfctl -qvvsI -i ${epair_tester}b 808 echo " === server === " 809 jexec router pfctl -qvvsI -i ${epair_server1}a 810 echo " === " 811} 812 813nat64_out_cleanup() 814{ 815 pft_cleanup 816} 817 818atf_init_test_cases() 819{ 820 atf_add_test_case "match_pass_state" 821 atf_add_test_case "match_pass_no_state" 822 atf_add_test_case "match_block" 823 atf_add_test_case "match_fail" 824 atf_add_test_case "nat_natonly" 825 atf_add_test_case "nat_nat" 826 atf_add_test_case "nat_match" 827 atf_add_test_case "nat_pass" 828 atf_add_test_case "rdr_match" 829 atf_add_test_case "nat64_in" 830 atf_add_test_case "nat64_out" 831} 832