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/0\(match\): 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 251state_max_cleanup() 252{ 253 pft_cleanup 254} 255 256atf_test_case "unspecified_v4" "cleanup" 257unspecified_v4_head() 258{ 259 atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks' 260 atf_set require.user root 261} 262 263unspecified_v4_body() 264{ 265 pflog_init 266 267 vnet_mkjail alcatraz 268 jexec alcatraz ifconfig lo0 inet 127.0.0.1 269 jexec alcatraz route add default 127.0.0.1 270 271 jexec alcatraz pfctl -e 272 jexec alcatraz ifconfig pflog0 up 273 pft_set_rules alcatraz "block log on lo0 to 0.0.0.0" 274 275 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt & 276 sleep 1 # Wait for tcpdump to start 277 278 atf_check -s not-exit:0 -o ignore -e ignore \ 279 jexec alcatraz ping -S 127.0.0.1 -c 1 0.0.0.0 280 281 atf_check -o match:".*: block out on lo0: 127.0.0.1 > 0.0.0.0: ICMP echo request,.*" \ 282 cat pflog.txt 283} 284 285unspecified_v4_cleanup() 286{ 287 pft_cleanup 288} 289 290atf_test_case "unspecified_v6" "cleanup" 291unspecified_v6_head() 292{ 293 atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks' 294 atf_set require.user root 295} 296 297unspecified_v6_body() 298{ 299 pflog_init 300 301 vnet_mkjail alcatraz 302 jexec alcatraz ifconfig lo0 up 303 jexec alcatraz route -6 add ::0 ::1 304 305 jexec alcatraz pfctl -e 306 jexec alcatraz ifconfig pflog0 up 307 pft_set_rules alcatraz "block log on lo0 to ::0" 308 309 jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt & 310 sleep 1 # Wait for tcpdump to start 311 312 atf_check -s not-exit:0 -o ignore -e ignore \ 313 jexec alcatraz ping -6 -S ::1 -c 1 ::0 314 315 cat pflog.txt 316 atf_check -o match:".*: block out on lo0: ::1 > ::: ICMP6, echo request,.*" \ 317 cat pflog.txt 318} 319 320unspecified_v6_cleanup() 321{ 322 pft_cleanup 323} 324 325atf_test_case "rdr_action" "cleanup" 326rdr_head() 327{ 328 atf_set descr 'Ensure that NAT rule actions are logged correctly' 329 atf_set require.user root 330} 331 332rdr_action_body() 333{ 334 pflog_init 335 336 j="pflog:rdr_action" 337 epair_c=$(vnet_mkepair) 338 epair_srv=$(vnet_mkepair) 339 340 vnet_mkjail ${j}srv ${epair_srv}a 341 vnet_mkjail ${j}gw ${epair_srv}b ${epair_c}a 342 vnet_mkjail ${j}c ${epair_c}b 343 344 jexec ${j}srv ifconfig ${epair_srv}a 198.51.100.1/24 up 345 # No default route in srv jail, to ensure we're NAT-ing 346 jexec ${j}gw ifconfig ${epair_srv}b 198.51.100.2/24 up 347 jexec ${j}gw ifconfig ${epair_c}a 192.0.2.1/24 up 348 jexec ${j}gw sysctl net.inet.ip.forwarding=1 349 jexec ${j}c ifconfig ${epair_c}b 192.0.2.2/24 up 350 jexec ${j}c route add default 192.0.2.1 351 352 jexec ${j}gw pfctl -e 353 jexec ${j}gw ifconfig pflog0 up 354 pft_set_rules ${j}gw \ 355 "rdr log on ${epair_srv}b proto tcp from 198.51.100.0/24 to any port 1234 -> 192.0.2.2 port 1234" \ 356 "block quick inet6" \ 357 "pass in log" 358 359 jexec ${j}gw tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt & 360 sleep 1 # Wait for tcpdump to start 361 362 # send a SYN to catch in the log 363 jexec ${j}srv nc -N -w 0 198.51.100.2 1234 364 365 echo "Log" 366 cat ${PWD}/pflog.txt 367 368 # log line generated for rdr hit (pre-NAT) 369 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\].*" \ 370 cat pflog.txt 371 372 # log line generated for pass hit (post-NAT) 373 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\].*" \ 374 cat pflog.txt 375 376 # only two log lines shall be written 377 atf_check -o match:2 grep -c . pflog.txt 378} 379 380rdr_action_cleanup() 381{ 382 pft_cleanup 383} 384 385atf_init_test_cases() 386{ 387 atf_add_test_case "malformed" 388 atf_add_test_case "matches" 389 atf_add_test_case "matches_logif" 390 atf_add_test_case "state_max" 391 atf_add_test_case "unspecified_v4" 392 atf_add_test_case "unspecified_v6" 393 atf_add_test_case "rdr_action" 394} 395