1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2023 Rubicon Communications, LLC (Netgate) 5# Copyright (c) 2024 Deciso B.V. 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions 9# are met: 10# 1. Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# 2. Redistributions in binary form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in the 14# documentation and/or other materials provided with the distribution. 15# 16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26# SUCH DAMAGE. 27 28. $(atf_get_srcdir)/utils.subr 29 30common_dir=$(atf_get_srcdir)/../common 31 32atf_test_case "malformed" "cleanup" 33malformed_head() 34{ 35 atf_set descr 'Test that we do not log malformed packets as passing' 36 atf_set require.user root 37 atf_set require.progs python3 scapy 38} 39 40malformed_body() 41{ 42 pflog_init 43 44 epair=$(vnet_mkepair) 45 46 vnet_mkjail srv ${epair}b 47 jexec srv ifconfig ${epair}b 192.0.2.1/24 up 48 49 vnet_mkjail cl ${epair}a 50 jexec cl ifconfig ${epair}a 192.0.2.2/24 up 51 52 jexec cl pfctl -e 53 jexec cl ifconfig pflog0 up 54 pft_set_rules cl \ 55 "pass log keep state" 56 57 # Not required, but the 'pf: dropping packet with ip options' kernel log can 58 # help when debugging the test. 59 jexec cl pfctl -x loud 60 61 jexec cl tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt & 62 sleep 1 # Wait for tcpdump to start 63 64 # Sanity check 65 atf_check -s exit:0 -o ignore \ 66 jexec srv ping -c 1 192.0.2.2 67 68 jexec srv ${common_dir}/pft_ping.py \ 69 --sendif ${epair}b \ 70 --to 192.0.2.2 \ 71 --send-nop \ 72 --recvif ${epair}b 73 74 atf_check -o match:".*rule 0/8\(ip-option\): block in on ${epair}a: 192.0.2.1 > 192.0.2.2: ICMP echo request.*" \ 75 cat pflog.txt 76} 77 78malformed_cleanup() 79{ 80 pft_cleanup 81} 82 83atf_test_case "matches" "cleanup" 84matches_head() 85{ 86 atf_set descr 'Test the pflog matches keyword' 87 atf_set require.user root 88} 89 90matches_body() 91{ 92 pflog_init 93 94 epair=$(vnet_mkepair) 95 96 vnet_mkjail alcatraz ${epair}a 97 jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up 98 99 ifconfig ${epair}b 192.0.2.2/24 up 100 101 # Sanity check 102 atf_check -s exit:0 -o ignore \ 103 ping -c 1 192.0.2.1 104 105 jexec alcatraz pfctl -e 106 jexec alcatraz ifconfig pflog0 up 107 pft_set_rules alcatraz \ 108 "match log(matches) inet proto icmp" \ 109 "match log(matches) inet from 192.0.2.2" \ 110 "pass" 111 112 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt & 113 sleep 1 # Wait for tcpdump to start 114 115 atf_check -s exit:0 -o ignore \ 116 ping -c 1 192.0.2.1 117 118 echo "Rules" 119 jexec alcatraz pfctl -sr -vv 120 echo "States" 121 jexec alcatraz pfctl -ss -vv 122 echo "Log" 123 cat ${PWD}/pflog.txt 124 125 atf_check -o match:".*rule 0/0\(match\): match in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \ 126 cat pflog.txt 127 atf_check -o match:".*rule 1/0\(match\): match in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \ 128 cat pflog.txt 129} 130 131matches_cleanup() 132{ 133 pft_cleanup 134} 135 136atf_test_case "matches_logif" "cleanup" 137matches_logif_head() 138{ 139 atf_set descr 'Test log(matches, to pflogX)' 140 atf_set require.user root 141} 142 143matches_logif_body() 144{ 145 pflog_init 146 147 epair=$(vnet_mkepair) 148 149 vnet_mkjail alcatraz ${epair}a 150 jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up 151 152 ifconfig ${epair}b 192.0.2.2/24 up 153 154 # Sanity check 155 atf_check -s exit:0 -o ignore \ 156 ping -c 1 192.0.2.1 157 158 jexec alcatraz pfctl -e 159 jexec alcatraz ifconfig pflog0 up 160 jexec alcatraz ifconfig pflog1 create 161 jexec alcatraz ifconfig pflog1 up 162 pft_set_rules alcatraz \ 163 "match log(matches, to pflog1) inet proto icmp" \ 164 "match log inet from 192.0.2.2" \ 165 "pass log(to pflog0)" 166 167 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog1 >> ${PWD}/pflog1.txt & 168 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog0.txt & 169 sleep 1 # Wait for tcpdump to start 170 171 atf_check -s exit:0 -o ignore \ 172 ping -c 1 192.0.2.1 173 174 echo "Rules" 175 jexec alcatraz pfctl -sr -vv 176 echo "States" 177 jexec alcatraz pfctl -ss -vv 178 echo "Log 0" 179 cat ${PWD}/pflog0.txt 180 echo "Log 1" 181 cat ${PWD}/pflog1.txt 182 183 atf_check -o match:".*rule 0/0\(match\): match in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \ 184 cat pflog1.txt 185 atf_check -o match:".*rule 1/0\(match\): match in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \ 186 cat pflog1.txt 187} 188 189matches_logif_cleanup() 190{ 191 pft_cleanup 192} 193 194atf_test_case "state_max" "cleanup" 195state_max_head() 196{ 197 atf_set descr 'Ensure that drops due to state limits are logged' 198 atf_set require.user root 199} 200 201state_max_body() 202{ 203 pflog_init 204 205 epair=$(vnet_mkepair) 206 207 vnet_mkjail alcatraz ${epair}a 208 jexec alcatraz ifconfig ${epair}a inet6 ifdisabled 209 jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up 210 211 ifconfig ${epair}b 192.0.2.2/24 up 212 ifconfig ${epair}b inet6 ifdisabled 213 214 # Sanity check 215 atf_check -s exit:0 -o ignore \ 216 ping -c 1 192.0.2.1 217 218 jexec alcatraz pfctl -e 219 jexec alcatraz ifconfig pflog0 up 220 pft_set_rules alcatraz "pass log inet keep state (max 1)" 221 222 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt & 223 sleep 1 # Wait for tcpdump to start 224 225 atf_check -s exit:0 -o ignore \ 226 ping -c 1 192.0.2.1 227 228 atf_check -s exit:2 -o ignore \ 229 ping -c 1 192.0.2.1 230 231 echo "Rules" 232 jexec alcatraz pfctl -sr -vv 233 echo "States" 234 jexec alcatraz pfctl -ss -vv 235 echo "Log" 236 cat ${PWD}/pflog.txt 237 238 # First ping passes. 239 atf_check -o match:".*rule 0/0\(match\): pass in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \ 240 cat pflog.txt 241 242 # Second ping is blocked due to the state limit. 243 atf_check -o match:".*rule 0/12\(state-limit\): block in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \ 244 cat pflog.txt 245 246 # At most three lines should be written: one for the first ping, and 247 # two for the second: one for the initial pass through the ruleset, and 248 # then a drop because of the state limit. Ideally only the drop would 249 # be logged; if this is fixed, the count will be 2 instead of 3. 250 atf_check -o match:3 grep -c . pflog.txt 251 252 # If the rule doesn't specify logging, we shouldn't log drops 253 # due to state limits. 254 pft_set_rules alcatraz "pass inet keep state (max 1)" 255 256 atf_check -s exit:0 -o ignore \ 257 ping -c 1 192.0.2.1 258 259 atf_check -s exit:2 -o ignore \ 260 ping -c 1 192.0.2.1 261 262 atf_check -o match:3 grep -c . pflog.txt 263} 264 265state_max_cleanup() 266{ 267 pft_cleanup 268} 269 270atf_test_case "unspecified_v4" "cleanup" 271unspecified_v4_head() 272{ 273 atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks' 274 atf_set require.user root 275} 276 277unspecified_v4_body() 278{ 279 pflog_init 280 281 vnet_mkjail alcatraz 282 jexec alcatraz ifconfig lo0 inet 127.0.0.1 283 jexec alcatraz route add default 127.0.0.1 284 285 jexec alcatraz pfctl -e 286 jexec alcatraz ifconfig pflog0 up 287 pft_set_rules alcatraz "block log on lo0 to 0.0.0.0" 288 289 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt & 290 sleep 1 # Wait for tcpdump to start 291 292 atf_check -s not-exit:0 -o ignore -e ignore \ 293 jexec alcatraz ping -S 127.0.0.1 -c 1 0.0.0.0 294 295 atf_check -o match:".*: block out on lo0: 127.0.0.1 > 0.0.0.0: ICMP echo request,.*" \ 296 cat pflog.txt 297} 298 299unspecified_v4_cleanup() 300{ 301 pft_cleanup 302} 303 304atf_test_case "unspecified_v6" "cleanup" 305unspecified_v6_head() 306{ 307 atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks' 308 atf_set require.user root 309} 310 311unspecified_v6_body() 312{ 313 pflog_init 314 315 vnet_mkjail alcatraz 316 jexec alcatraz ifconfig lo0 up 317 jexec alcatraz route -6 add ::0 ::1 318 319 jexec alcatraz pfctl -e 320 jexec alcatraz ifconfig pflog0 up 321 pft_set_rules alcatraz "block log on lo0 to ::0" 322 323 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt & 324 sleep 1 # Wait for tcpdump to start 325 326 atf_check -s not-exit:0 -o ignore -e ignore \ 327 jexec alcatraz ping -6 -S ::1 -c 1 ::0 328 329 cat pflog.txt 330 atf_check -o match:".*: block out on lo0: ::1 > ::: ICMP6, echo request,.*" \ 331 cat pflog.txt 332} 333 334unspecified_v6_cleanup() 335{ 336 pft_cleanup 337} 338 339atf_test_case "rdr_action" "cleanup" 340rdr_head() 341{ 342 atf_set descr 'Ensure that NAT rule actions are logged correctly' 343 atf_set require.user root 344} 345 346rdr_action_body() 347{ 348 pflog_init 349 350 j="pflog:rdr_action" 351 epair_c=$(vnet_mkepair) 352 epair_srv=$(vnet_mkepair) 353 354 vnet_mkjail ${j}srv ${epair_srv}a 355 vnet_mkjail ${j}gw ${epair_srv}b ${epair_c}a 356 vnet_mkjail ${j}c ${epair_c}b 357 358 jexec ${j}srv ifconfig ${epair_srv}a inet6 ifdisabled 359 jexec ${j}gw ifconfig ${epair_srv}b inet6 ifdisabled 360 jexec ${j}gw ifconfig ${epair_c}a inet6 ifdisabled 361 jexec ${j}c ifconfig ${epair_c}b inet6 ifdisabled 362 363 jexec ${j}srv ifconfig ${epair_srv}a 198.51.100.1/24 up 364 # No default route in srv jail, to ensure we're NAT-ing 365 jexec ${j}gw ifconfig ${epair_srv}b 198.51.100.2/24 up 366 jexec ${j}gw ifconfig ${epair_c}a 192.0.2.1/24 up 367 jexec ${j}gw sysctl net.inet.ip.forwarding=1 368 jexec ${j}c ifconfig ${epair_c}b 192.0.2.2/24 up 369 jexec ${j}c route add default 192.0.2.1 370 371 jexec ${j}gw pfctl -e 372 jexec ${j}gw ifconfig pflog0 up 373 pft_set_rules ${j}gw \ 374 "rdr log on ${epair_srv}b proto tcp from 198.51.100.0/24 to any port 1234 -> 192.0.2.2 port 1234" \ 375 "block quick inet6" \ 376 "pass in log" 377 378 jexec ${j}gw tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt & 379 sleep 1 # Wait for tcpdump to start 380 381 # send a SYN to catch in the log 382 jexec ${j}srv nc -N -w 0 198.51.100.2 1234 383 384 echo "Log" 385 cat ${PWD}/pflog.txt 386 387 # log line generated for rdr hit (pre-NAT) 388 atf_check -o match:".*.*rule 0/0\(match\): rdr in on ${epair_srv}b: 198.51.100.1.[0-9]* > 198.51.100.2.1234: Flags \[S\].*" \ 389 cat pflog.txt 390 391 # log line generated for pass hit (post-NAT) 392 atf_check -o match:".*.*rule 1/0\(match\): pass in on ${epair_srv}b: 198.51.100.1.[0-9]* > 192.0.2.2.1234: Flags \[S\].*" \ 393 cat pflog.txt 394 395 # only two log lines shall be written 396 atf_check -o match:2 grep -c . pflog.txt 397} 398 399rdr_action_cleanup() 400{ 401 pft_cleanup 402} 403 404atf_test_case "rule_number" "cleanup" 405rule_number_head() 406{ 407 atf_set descr 'Test rule numbers with anchors' 408 atf_set require.user root 409} 410 411rule_number_body() 412{ 413 pflog_init 414 415 epair=$(vnet_mkepair) 416 417 vnet_mkjail alcatraz ${epair}b 418 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 419 420 ifconfig ${epair}a 192.0.2.2/24 up 421 ifconfig ${epair}a inet alias 192.0.2.3/24 up 422 ifconfig ${epair}a inet alias 192.0.2.4/24 up 423 424 jexec alcatraz pfctl -e 425 jexec alcatraz ifconfig pflog0 up 426 pft_set_rules alcatraz \ 427 "pass log from 192.0.2.2" \ 428 "anchor \"foo\" {\n \ 429 pass log from 192.0.2.3\n \ 430 }" \ 431 "pass log from 192.0.2.4" 432 433 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt & 434 sleep 1 # Wait for tcpdump to start 435 436 atf_check -s exit:0 -o ignore \ 437 ping -c 1 -S 192.0.2.2 192.0.2.1 438 atf_check -s exit:0 -o ignore \ 439 ping -c 1 -S 192.0.2.3 192.0.2.1 440 atf_check -s exit:0 -o ignore \ 441 ping -c 1 -S 192.0.2.4 192.0.2.1 442 443 jexec alcatraz pfctl -sr -a '*' -vv 444 445 # Give tcpdump a little time to finish writing to the file 446 sleep 1 447 cat pflog.txt 448 449 atf_check -o match:"rule 0/0\(match\): pass in.*: 192.0.2.2.*ICMP echo request" \ 450 cat pflog.txt 451 atf_check -o match:"rule 1.foo.0/0\(match\): pass in.*: 192.0.2.3.*: ICMP echo request" \ 452 cat pflog.txt 453 atf_check -o match:"rule 2/0\(match\): pass in.*: 192.0.2.4.*: ICMP echo request" \ 454 cat pflog.txt 455} 456 457rule_number_cleanup() 458{ 459 pft_cleanup 460} 461 462atf_init_test_cases() 463{ 464 atf_add_test_case "malformed" 465 atf_add_test_case "matches" 466 atf_add_test_case "matches_logif" 467 atf_add_test_case "state_max" 468 atf_add_test_case "unspecified_v4" 469 atf_add_test_case "unspecified_v6" 470 atf_add_test_case "rdr_action" 471 atf_add_test_case "rule_number" 472} 473