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 192.0.2.1/24 up 209 210 ifconfig ${epair}b 192.0.2.2/24 up 211 212 # Sanity check 213 atf_check -s exit:0 -o ignore \ 214 ping -c 1 192.0.2.1 215 216 jexec alcatraz pfctl -e 217 jexec alcatraz ifconfig pflog0 up 218 pft_set_rules alcatraz "pass log inet keep state (max 1)" 219 220 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt & 221 sleep 1 # Wait for tcpdump to start 222 223 atf_check -s exit:0 -o ignore \ 224 ping -c 1 192.0.2.1 225 226 atf_check -s exit:2 -o ignore \ 227 ping -c 1 192.0.2.1 228 229 echo "Rules" 230 jexec alcatraz pfctl -sr -vv 231 echo "States" 232 jexec alcatraz pfctl -ss -vv 233 echo "Log" 234 cat ${PWD}/pflog.txt 235 236 # First ping passes. 237 atf_check -o match:".*rule 0/0\(match\): pass in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \ 238 cat pflog.txt 239 240 # Second ping is blocked due to the state limit. 241 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.*" \ 242 cat pflog.txt 243 244 # At most three lines should be written: one for the first ping, and 245 # two for the second: one for the initial pass through the ruleset, and 246 # then a drop because of the state limit. Ideally only the drop would 247 # be logged; if this is fixed, the count will be 2 instead of 3. 248 atf_check -o match:3 grep -c . pflog.txt 249 250 # If the rule doesn't specify logging, we shouldn't log drops 251 # due to state limits. 252 pft_set_rules alcatraz "pass inet keep state (max 1)" 253 254 atf_check -s exit:0 -o ignore \ 255 ping -c 1 192.0.2.1 256 257 atf_check -s exit:2 -o ignore \ 258 ping -c 1 192.0.2.1 259 260 atf_check -o match:3 grep -c . pflog.txt 261} 262 263state_max_cleanup() 264{ 265 pft_cleanup 266} 267 268atf_test_case "unspecified_v4" "cleanup" 269unspecified_v4_head() 270{ 271 atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks' 272 atf_set require.user root 273} 274 275unspecified_v4_body() 276{ 277 pflog_init 278 279 vnet_mkjail alcatraz 280 jexec alcatraz ifconfig lo0 inet 127.0.0.1 281 jexec alcatraz route add default 127.0.0.1 282 283 jexec alcatraz pfctl -e 284 jexec alcatraz ifconfig pflog0 up 285 pft_set_rules alcatraz "block log on lo0 to 0.0.0.0" 286 287 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt & 288 sleep 1 # Wait for tcpdump to start 289 290 atf_check -s not-exit:0 -o ignore -e ignore \ 291 jexec alcatraz ping -S 127.0.0.1 -c 1 0.0.0.0 292 293 atf_check -o match:".*: block out on lo0: 127.0.0.1 > 0.0.0.0: ICMP echo request,.*" \ 294 cat pflog.txt 295} 296 297unspecified_v4_cleanup() 298{ 299 pft_cleanup 300} 301 302atf_test_case "unspecified_v6" "cleanup" 303unspecified_v6_head() 304{ 305 atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks' 306 atf_set require.user root 307} 308 309unspecified_v6_body() 310{ 311 pflog_init 312 313 vnet_mkjail alcatraz 314 jexec alcatraz ifconfig lo0 up 315 jexec alcatraz route -6 add ::0 ::1 316 317 jexec alcatraz pfctl -e 318 jexec alcatraz ifconfig pflog0 up 319 pft_set_rules alcatraz "block log on lo0 to ::0" 320 321 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt & 322 sleep 1 # Wait for tcpdump to start 323 324 atf_check -s not-exit:0 -o ignore -e ignore \ 325 jexec alcatraz ping -6 -S ::1 -c 1 ::0 326 327 cat pflog.txt 328 atf_check -o match:".*: block out on lo0: ::1 > ::: ICMP6, echo request,.*" \ 329 cat pflog.txt 330} 331 332unspecified_v6_cleanup() 333{ 334 pft_cleanup 335} 336 337atf_test_case "rdr_action" "cleanup" 338rdr_head() 339{ 340 atf_set descr 'Ensure that NAT rule actions are logged correctly' 341 atf_set require.user root 342} 343 344rdr_action_body() 345{ 346 pflog_init 347 348 j="pflog:rdr_action" 349 epair_c=$(vnet_mkepair) 350 epair_srv=$(vnet_mkepair) 351 352 vnet_mkjail ${j}srv ${epair_srv}a 353 vnet_mkjail ${j}gw ${epair_srv}b ${epair_c}a 354 vnet_mkjail ${j}c ${epair_c}b 355 356 jexec ${j}srv ifconfig ${epair_srv}a 198.51.100.1/24 up 357 # No default route in srv jail, to ensure we're NAT-ing 358 jexec ${j}gw ifconfig ${epair_srv}b 198.51.100.2/24 up 359 jexec ${j}gw ifconfig ${epair_c}a 192.0.2.1/24 up 360 jexec ${j}gw sysctl net.inet.ip.forwarding=1 361 jexec ${j}c ifconfig ${epair_c}b 192.0.2.2/24 up 362 jexec ${j}c route add default 192.0.2.1 363 364 jexec ${j}gw pfctl -e 365 jexec ${j}gw ifconfig pflog0 up 366 pft_set_rules ${j}gw \ 367 "rdr log on ${epair_srv}b proto tcp from 198.51.100.0/24 to any port 1234 -> 192.0.2.2 port 1234" \ 368 "block quick inet6" \ 369 "pass in log" 370 371 jexec ${j}gw tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt & 372 sleep 1 # Wait for tcpdump to start 373 374 # send a SYN to catch in the log 375 jexec ${j}srv nc -N -w 0 198.51.100.2 1234 376 377 echo "Log" 378 cat ${PWD}/pflog.txt 379 380 # log line generated for rdr hit (pre-NAT) 381 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\].*" \ 382 cat pflog.txt 383 384 # log line generated for pass hit (post-NAT) 385 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\].*" \ 386 cat pflog.txt 387 388 # only two log lines shall be written 389 atf_check -o match:2 grep -c . pflog.txt 390} 391 392rdr_action_cleanup() 393{ 394 pft_cleanup 395} 396 397atf_init_test_cases() 398{ 399 atf_add_test_case "malformed" 400 atf_add_test_case "matches" 401 atf_add_test_case "matches_logif" 402 atf_add_test_case "state_max" 403 atf_add_test_case "unspecified_v4" 404 atf_add_test_case "unspecified_v6" 405 atf_add_test_case "rdr_action" 406} 407