1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2021 Rubicon Communications, LLC (Netgate) 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 29common_dir=$(atf_get_srcdir)/../common 30 31find_state() 32{ 33 jail=${1:-alcatraz} 34 ip=${2:-192.0.2.2} 35 36 jexec ${jail} pfctl -ss | grep icmp | grep ${ip} 37} 38 39find_state_v6() 40{ 41 jexec alcatraz pfctl -ss | grep icmp | grep 2001:db8::2 42} 43 44 45atf_test_case "v4" "cleanup" 46v4_head() 47{ 48 atf_set descr 'Test killing states by IPv4 address' 49 atf_set require.user root 50 atf_set require.progs scapy 51} 52 53v4_body() 54{ 55 pft_init 56 57 epair=$(vnet_mkepair) 58 ifconfig ${epair}a 192.0.2.1/24 up 59 60 vnet_mkjail alcatraz ${epair}b 61 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 62 jexec alcatraz pfctl -e 63 64 pft_set_rules alcatraz "block all" \ 65 "pass in proto icmp" \ 66 "set skip on lo" 67 68 # Sanity check & establish state 69 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 70 --sendif ${epair}a \ 71 --to 192.0.2.2 \ 72 --replyif ${epair}a 73 74 # Change rules to now deny the ICMP traffic 75 pft_set_rules noflush alcatraz "block all" 76 if ! find_state; 77 then 78 atf_fail "Setting new rules removed the state." 79 fi 80 81 # Killing with the wrong IP doesn't affect our state 82 jexec alcatraz pfctl -k 192.0.2.3 83 if ! find_state; 84 then 85 atf_fail "Killing with the wrong IP removed our state." 86 fi 87 88 # Killing with one correct address and one incorrect doesn't kill the state 89 jexec alcatraz pfctl -k 192.0.2.1 -k 192.0.2.3 90 if ! find_state; 91 then 92 atf_fail "Killing with one wrong IP removed our state." 93 fi 94 95 # Killing with correct address does remove the state 96 jexec alcatraz pfctl -k 192.0.2.1 97 if find_state; 98 then 99 atf_fail "Killing with the correct IP did not remove our state." 100 fi 101} 102 103v4_cleanup() 104{ 105 pft_cleanup 106} 107 108atf_test_case "v6" "cleanup" 109v6_head() 110{ 111 atf_set descr 'Test killing states by IPv6 address' 112 atf_set require.user root 113 atf_set require.progs scapy 114} 115 116v6_body() 117{ 118 pft_init 119 120 if [ "$(atf_config_get ci false)" = "true" ]; then 121 atf_skip "https://bugs.freebsd.org/260458" 122 fi 123 124 epair=$(vnet_mkepair) 125 ifconfig ${epair}a inet6 2001:db8::1/64 up no_dad 126 127 vnet_mkjail alcatraz ${epair}b 128 jexec alcatraz ifconfig ${epair}b inet6 2001:db8::2/64 up no_dad 129 jexec alcatraz pfctl -e 130 131 pft_set_rules alcatraz "block all" \ 132 "pass in proto icmp6" \ 133 "set skip on lo" 134 135 # Sanity check & establish state 136 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 137 --sendif ${epair}a \ 138 --to 2001:db8::2 \ 139 --replyif ${epair}a 140 141 # Change rules to now deny the ICMP traffic 142 pft_set_rules noflush alcatraz "block all" 143 if ! find_state_v6; 144 then 145 atf_fail "Setting new rules removed the state." 146 fi 147 148 # Killing with the wrong IP doesn't affect our state 149 jexec alcatraz pfctl -k 2001:db8::3 150 if ! find_state_v6; 151 then 152 atf_fail "Killing with the wrong IP removed our state." 153 fi 154 155 # Killing with one correct address and one incorrect doesn't kill the state 156 jexec alcatraz pfctl -k 2001:db8::1 -k 2001:db8::3 157 if ! find_state_v6; 158 then 159 atf_fail "Killing with one wrong IP removed our state." 160 fi 161 162 # Killing with correct address does remove the state 163 jexec alcatraz pfctl -k 2001:db8::1 164 if find_state_v6; 165 then 166 atf_fail "Killing with the correct IP did not remove our state." 167 fi 168} 169 170v6_cleanup() 171{ 172 pft_cleanup 173} 174 175atf_test_case "label" "cleanup" 176label_head() 177{ 178 atf_set descr 'Test killing states by label' 179 atf_set require.user root 180 atf_set require.progs scapy 181} 182 183label_body() 184{ 185 pft_init 186 187 epair=$(vnet_mkepair) 188 ifconfig ${epair}a 192.0.2.1/24 up 189 190 vnet_mkjail alcatraz ${epair}b 191 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 192 jexec alcatraz pfctl -e 193 194 pft_set_rules alcatraz "block all" \ 195 "pass in proto tcp label bar" \ 196 "pass in proto icmp label foo" \ 197 "set skip on lo" 198 199 # Sanity check & establish state 200 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 201 --sendif ${epair}a \ 202 --to 192.0.2.2 \ 203 --replyif ${epair}a 204 205 # Change rules to now deny the ICMP traffic 206 pft_set_rules noflush alcatraz "block all" 207 if ! find_state; 208 then 209 atf_fail "Setting new rules removed the state." 210 fi 211 212 # Killing a label on a different rules keeps the state 213 jexec alcatraz pfctl -k label -k bar 214 if ! find_state; 215 then 216 atf_fail "Killing a different label removed the state." 217 fi 218 219 # Killing a non-existing label keeps the state 220 jexec alcatraz pfctl -k label -k baz 221 if ! find_state; 222 then 223 atf_fail "Killing a non-existing label removed the state." 224 fi 225 226 # Killing the correct label kills the state 227 jexec alcatraz pfctl -k label -k foo 228 if find_state; 229 then 230 atf_fail "Killing the state did not remove it." 231 fi 232} 233 234label_cleanup() 235{ 236 pft_cleanup 237} 238 239atf_test_case "multilabel" "cleanup" 240multilabel_head() 241{ 242 atf_set descr 'Test killing states with multiple labels by label' 243 atf_set require.user root 244 atf_set require.progs scapy 245} 246 247multilabel_body() 248{ 249 pft_init 250 251 epair=$(vnet_mkepair) 252 ifconfig ${epair}a 192.0.2.1/24 up 253 254 vnet_mkjail alcatraz ${epair}b 255 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 256 jexec alcatraz pfctl -e 257 258 pft_set_rules alcatraz "block all" \ 259 "pass in proto icmp label foo label bar" \ 260 "set skip on lo" 261 262 # Sanity check & establish state 263 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 264 --sendif ${epair}a \ 265 --to 192.0.2.2 \ 266 --replyif ${epair}a 267 268 # Change rules to now deny the ICMP traffic 269 pft_set_rules noflush alcatraz "block all" 270 if ! find_state; 271 then 272 atf_fail "Setting new rules removed the state." 273 fi 274 275 # Killing a label on a different rules keeps the state 276 jexec alcatraz pfctl -k label -k baz 277 if ! find_state; 278 then 279 atf_fail "Killing a different label removed the state." 280 fi 281 282 # Killing the state with the last label works 283 jexec alcatraz pfctl -k label -k bar 284 if find_state; 285 then 286 atf_fail "Killing with the last label did not remove the state." 287 fi 288 289 pft_set_rules alcatraz "block all" \ 290 "pass in proto icmp label foo label bar" \ 291 "set skip on lo" 292 293 # Reestablish state 294 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 295 --sendif ${epair}a \ 296 --to 192.0.2.2 \ 297 --replyif ${epair}a 298 299 # Change rules to now deny the ICMP traffic 300 pft_set_rules noflush alcatraz "block all" 301 if ! find_state; 302 then 303 atf_fail "Setting new rules removed the state." 304 fi 305 306 # Killing with the first label works too 307 jexec alcatraz pfctl -k label -k foo 308 if find_state; 309 then 310 atf_fail "Killing with the first label did not remove the state." 311 fi 312} 313 314multilabel_cleanup() 315{ 316 pft_cleanup 317} 318 319atf_test_case "gateway" "cleanup" 320gateway_head() 321{ 322 atf_set descr 'Test killing states by route-to/reply-to address' 323 atf_set require.user root 324 atf_set require.progs scapy 325} 326 327gateway_body() 328{ 329 pft_init 330 331 epair=$(vnet_mkepair) 332 ifconfig ${epair}a 192.0.2.1/24 up 333 334 vnet_mkjail alcatraz ${epair}b 335 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 336 jexec alcatraz pfctl -e 337 338 pft_set_rules alcatraz "block all" \ 339 "pass in reply-to (${epair}b 192.0.2.1) proto icmp" \ 340 "set skip on lo" 341 342 # Sanity check & establish state 343 # Note: use pft_ping so we always use the same ID, so pf considers all 344 # echo requests part of the same flow. 345 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 346 --sendif ${epair}a \ 347 --to 192.0.2.2 \ 348 --replyif ${epair}a 349 350 # Change rules to now deny the ICMP traffic 351 pft_set_rules noflush alcatraz "block all" 352 if ! find_state; 353 then 354 atf_fail "Setting new rules removed the state." 355 fi 356 357 # Killing with a different gateway does not affect our state 358 jexec alcatraz pfctl -k gateway -k 192.0.2.2 359 if ! find_state; 360 then 361 atf_fail "Killing with a different gateway removed the state." 362 fi 363 364 # Killing states with the relevant gateway does terminate our state 365 jexec alcatraz pfctl -k gateway -k 192.0.2.1 366 if find_state; 367 then 368 atf_fail "Killing with the gateway did not remove the state." 369 fi 370} 371 372gateway_cleanup() 373{ 374 pft_cleanup 375} 376 377atf_test_case "match" "cleanup" 378match_head() 379{ 380 atf_set descr 'Test killing matching states' 381 atf_set require.user root 382} 383 384wait_for_state() 385{ 386 jail=$1 387 addr=$2 388 389 while ! jexec $jail pfctl -s s | grep $addr >/dev/null; 390 do 391 sleep .1 392 done 393} 394 395match_body() 396{ 397 pft_init 398 399 epair_one=$(vnet_mkepair) 400 ifconfig ${epair_one}a 192.0.2.1/24 up 401 402 epair_two=$(vnet_mkepair) 403 404 vnet_mkjail alcatraz ${epair_one}b ${epair_two}a 405 jexec alcatraz ifconfig ${epair_one}b 192.0.2.2/24 up 406 jexec alcatraz ifconfig ${epair_two}a 198.51.100.1/24 up 407 jexec alcatraz sysctl net.inet.ip.forwarding=1 408 jexec alcatraz pfctl -e 409 410 vnet_mkjail singsing ${epair_two}b 411 jexec singsing ifconfig ${epair_two}b 198.51.100.2/24 up 412 jexec singsing route add default 198.51.100.1 413 jexec singsing /usr/sbin/inetd -p inetd-echo.pid \ 414 $(atf_get_srcdir)/echo_inetd.conf 415 416 route add 198.51.100.0/24 192.0.2.2 417 418 pft_set_rules alcatraz \ 419 "nat on ${epair_two}a from 192.0.2.0/24 -> (${epair_two}a)" \ 420 "pass all" 421 422 nc 198.51.100.2 7 & 423 wait_for_state alcatraz 192.0.2.1 424 425 # Expect two states 426 states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l) 427 if [ $states -ne 2 ] ; 428 then 429 atf_fail "Expected two states, found $states" 430 fi 431 432 # If we don't kill the matching NAT state one should be left 433 jexec alcatraz pfctl -k 192.0.2.1 434 states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l) 435 if [ $states -ne 1 ] ; 436 then 437 atf_fail "Expected one states, found $states" 438 fi 439 440 # Flush 441 jexec alcatraz pfctl -F states 442 443 nc 198.51.100.2 7 & 444 wait_for_state alcatraz 192.0.2.1 445 446 # Kill matching states, expect all of them to be gone 447 jexec alcatraz pfctl -M -k 192.0.2.1 448 states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l) 449 if [ $states -ne 0 ] ; 450 then 451 atf_fail "Expected zero states, found $states" 452 fi 453} 454 455match_cleanup() 456{ 457 pft_cleanup 458} 459 460atf_test_case "interface" "cleanup" 461interface_head() 462{ 463 atf_set descr 'Test killing states based on interface' 464 atf_set require.user root 465 atf_set require.progs scapy 466} 467 468interface_body() 469{ 470 pft_init 471 472 epair=$(vnet_mkepair) 473 ifconfig ${epair}a 192.0.2.1/24 up 474 475 vnet_mkjail alcatraz ${epair}b 476 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 477 jexec alcatraz pfctl -e 478 479 pft_set_rules alcatraz "block all" \ 480 "pass in proto icmp" \ 481 "set skip on lo" 482 483 # Sanity check & establish state 484 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 485 --sendif ${epair}a \ 486 --to 192.0.2.2 \ 487 --replyif ${epair}a 488 489 # Change rules to now deny the ICMP traffic 490 pft_set_rules noflush alcatraz "block all" 491 if ! find_state; 492 then 493 atf_fail "Setting new rules removed the state." 494 fi 495 496 # Flushing states on a different interface doesn't affect our state 497 jexec alcatraz pfctl -i ${epair}a -Fs 498 if ! find_state; 499 then 500 atf_fail "Flushing on a different interface removed the state." 501 fi 502 503 # Flushing on the correct interface does (even with floating states) 504 jexec alcatraz pfctl -i ${epair}b -Fs 505 if find_state; 506 then 507 atf_fail "Flushing on a the interface did not remove the state." 508 fi 509} 510 511interface_cleanup() 512{ 513 pft_cleanup 514} 515 516atf_test_case "id" "cleanup" 517id_head() 518{ 519 atf_set descr 'Test killing states by id' 520 atf_set require.user root 521 atf_set require.progs scapy 522} 523 524id_body() 525{ 526 pft_init 527 528 epair=$(vnet_mkepair) 529 ifconfig ${epair}a 192.0.2.1/24 up 530 531 vnet_mkjail alcatraz ${epair}b 532 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 533 jexec alcatraz pfctl -e 534 535 pft_set_rules alcatraz "block all" \ 536 "pass in proto tcp" \ 537 "pass in proto icmp" \ 538 "set skip on lo" 539 540 # Sanity check & establish state 541 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 542 --sendif ${epair}a \ 543 --to 192.0.2.2 \ 544 --replyif ${epair}a 545 546 # Change rules to now deny the ICMP traffic 547 pft_set_rules noflush alcatraz "block all" 548 if ! find_state; 549 then 550 atf_fail "Setting new rules removed the state." 551 fi 552 553 # Get the state ID 554 id=$(jexec alcatraz pfctl -ss -vvv | grep -A 3 icmp | 555 grep -A 3 192.0.2.2 | awk '/id:/ { printf("%s/%s", $2, $4); }') 556 557 # Kill the wrong ID 558 jexec alcatraz pfctl -k id -k 1 559 if ! find_state; 560 then 561 atf_fail "Killing a different ID removed the state." 562 fi 563 564 # Kill the correct ID 565 jexec alcatraz pfctl -k id -k ${id} 566 if find_state; 567 then 568 atf_fail "Killing the state did not remove it." 569 fi 570} 571 572id_cleanup() 573{ 574 pft_cleanup 575} 576 577atf_test_case "nat" "cleanup" 578nat_head() 579{ 580 atf_set descr 'Test killing states by their NAT-ed IP address' 581 atf_set require.user root 582 atf_set require.progs scapy 583} 584 585nat_body() 586{ 587 pft_init 588 j="killstate:nat" 589 590 epair_c=$(vnet_mkepair) 591 epair_srv=$(vnet_mkepair) 592 593 vnet_mkjail ${j}c ${epair_c}a 594 ifconfig -j ${j}c ${epair_c}a inet 192.0.2.2/24 up 595 jexec ${j}c route add default 192.0.2.1 596 597 vnet_mkjail ${j}srv ${epair_srv}a 598 ifconfig -j ${j}srv ${epair_srv}a inet 198.51.100.2/24 up 599 600 vnet_mkjail ${j}r ${epair_c}b ${epair_srv}b 601 ifconfig -j ${j}r ${epair_c}b inet 192.0.2.1/24 up 602 ifconfig -j ${j}r ${epair_srv}b inet 198.51.100.1/24 up 603 jexec ${j}r sysctl net.inet.ip.forwarding=1 604 605 jexec ${j}r pfctl -e 606 pft_set_rules ${j}r \ 607 "nat on ${epair_srv}b inet from 192.0.2.0/24 to any -> (${epair_srv}b)" 608 609 # Sanity check 610 atf_check -s exit:0 -o ignore \ 611 jexec ${j}c ping -c 1 192.0.2.1 612 atf_check -s exit:0 -o ignore \ 613 jexec ${j}srv ping -c 1 198.51.100.1 614 atf_check -s exit:0 -o ignore \ 615 jexec ${j}c ping -c 1 198.51.100.2 616 617 # Establish state 618 # Note: use pft_ping so we always use the same ID, so pf considers all 619 # echo requests part of the same flow. 620 atf_check -s exit:0 -o ignore jexec ${j}c ${common_dir}/pft_ping.py \ 621 --sendif ${epair_c}a \ 622 --to 198.51.100.1 \ 623 --replyif ${epair_c}a 624 625 # There's NAT here, so the source IP will be 198.51.100.1 626 if ! find_state ${j}r 198.51.100.1; 627 then 628 atf_fail "Expected state not found" 629 fi 630 631 # By NAT-ed address? 632 jexec ${j}r pfctl -k nat -k 192.0.2.2 633 634 if find_state ${j}r 198.51.100.1; 635 then 636 jexec ${j}r pfctl -ss -v 637 atf_fail "Failed to remove state" 638 fi 639} 640 641nat_cleanup() 642{ 643 pft_cleanup 644} 645 646atf_init_test_cases() 647{ 648 atf_add_test_case "v4" 649 atf_add_test_case "v6" 650 atf_add_test_case "label" 651 atf_add_test_case "multilabel" 652 atf_add_test_case "gateway" 653 atf_add_test_case "match" 654 atf_add_test_case "interface" 655 atf_add_test_case "id" 656 atf_add_test_case "nat" 657} 658