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 python3 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 "src_dst" "cleanup" 109src_dst_head() 110{ 111 atf_set descr 'Test killing a state with source and destination specified' 112 atf_set require.user root 113} 114 115src_dst_body() 116{ 117 pft_init 118 119 epair=$(vnet_mkepair) 120 ifconfig ${epair}a 192.0.2.1/24 up 121 122 vnet_mkjail alcatraz ${epair}b 123 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 124 jexec alcatraz pfctl -e 125 126 pft_set_rules alcatraz "block all" \ 127 "pass in proto icmp" \ 128 "set skip on lo" 129 130 # Sanity check & establish state 131 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 132 --sendif ${epair}a \ 133 --to 192.0.2.2 \ 134 --replyif ${epair}a 135 136 # Change rules to now deny the ICMP traffic 137 pft_set_rules noflush alcatraz "block all" 138 if ! find_state; 139 then 140 atf_fail "Setting new rules removed the state." 141 fi 142 143 # Killing with the wrong source IP doesn't affect our state 144 jexec alcatraz pfctl -k 192.0.2.3 -k 192.0.2.2 145 if ! find_state; 146 then 147 atf_fail "Killing with the wrong source IP removed our state." 148 fi 149 150 # Killing with the wrong destination IP doesn't affect our state 151 jexec alcatraz pfctl -k 192.0.2.1 -k 192.0.2.3 152 if ! find_state; 153 then 154 atf_fail "Killing with the wrong destination IP removed our state." 155 fi 156 157 # But it does with the correct one 158 jexec alcatraz pfctl -k 192.0.2.1 -k 192.0.2.2 159 if find_state; 160 then 161 atf_fail "Killing with the correct IPs did not remove our state." 162 fi 163} 164 165src_dst_cleanup() 166{ 167 pft_cleanup 168} 169 170atf_test_case "v6" "cleanup" 171v6_head() 172{ 173 atf_set descr 'Test killing states by IPv6 address' 174 atf_set require.user root 175 atf_set require.progs python3 scapy 176} 177 178v6_body() 179{ 180 pft_init 181 182 epair=$(vnet_mkepair) 183 ifconfig ${epair}a inet6 2001:db8::1/64 up no_dad 184 185 vnet_mkjail alcatraz ${epair}b 186 jexec alcatraz ifconfig ${epair}b inet6 2001:db8::2/64 up no_dad 187 jexec alcatraz pfctl -e 188 189 pft_set_rules alcatraz "block all" \ 190 "pass in proto icmp6" \ 191 "set skip on lo" 192 193 # Sanity check & establish state 194 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 195 --sendif ${epair}a \ 196 --to 2001:db8::2 \ 197 --replyif ${epair}a 198 199 # Change rules to now deny the ICMP traffic 200 pft_set_rules noflush alcatraz "block all" 201 if ! find_state_v6; 202 then 203 atf_fail "Setting new rules removed the state." 204 fi 205 206 # Killing with the wrong IP doesn't affect our state 207 jexec alcatraz pfctl -k 2001:db8::3 208 if ! find_state_v6; 209 then 210 atf_fail "Killing with the wrong IP removed our state." 211 fi 212 213 # Killing with one correct address and one incorrect doesn't kill the state 214 jexec alcatraz pfctl -k 2001:db8::1 -k 2001:db8::3 215 if ! find_state_v6; 216 then 217 atf_fail "Killing with one wrong IP removed our state." 218 fi 219 220 # Killing with correct address does remove the state 221 jexec alcatraz pfctl -k 2001:db8::1 222 if find_state_v6; 223 then 224 atf_fail "Killing with the correct IP did not remove our state." 225 fi 226} 227 228v6_cleanup() 229{ 230 pft_cleanup 231} 232 233atf_test_case "label" "cleanup" 234label_head() 235{ 236 atf_set descr 'Test killing states by label' 237 atf_set require.user root 238 atf_set require.progs python3 scapy 239} 240 241label_body() 242{ 243 pft_init 244 245 epair=$(vnet_mkepair) 246 ifconfig ${epair}a 192.0.2.1/24 up 247 248 vnet_mkjail alcatraz ${epair}b 249 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 250 jexec alcatraz pfctl -e 251 252 pft_set_rules alcatraz "block all" \ 253 "pass in proto tcp label bar" \ 254 "pass in proto icmp label foo" \ 255 "set skip on lo" 256 257 # Sanity check & establish state 258 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 259 --sendif ${epair}a \ 260 --to 192.0.2.2 \ 261 --replyif ${epair}a 262 263 # Change rules to now deny the ICMP traffic 264 pft_set_rules noflush alcatraz "block all" 265 if ! find_state; 266 then 267 atf_fail "Setting new rules removed the state." 268 fi 269 270 # Killing a label on a different rules keeps the state 271 jexec alcatraz pfctl -k label -k bar 272 if ! find_state; 273 then 274 atf_fail "Killing a different label removed the state." 275 fi 276 277 # Killing a non-existing label keeps the state 278 jexec alcatraz pfctl -k label -k baz 279 if ! find_state; 280 then 281 atf_fail "Killing a non-existing label removed the state." 282 fi 283 284 # Killing the correct label kills the state 285 jexec alcatraz pfctl -k label -k foo 286 if find_state; 287 then 288 atf_fail "Killing the state did not remove it." 289 fi 290} 291 292label_cleanup() 293{ 294 pft_cleanup 295} 296 297atf_test_case "multilabel" "cleanup" 298multilabel_head() 299{ 300 atf_set descr 'Test killing states with multiple labels by label' 301 atf_set require.user root 302 atf_set require.progs python3 scapy 303} 304 305multilabel_body() 306{ 307 pft_init 308 309 epair=$(vnet_mkepair) 310 ifconfig ${epair}a 192.0.2.1/24 up 311 312 vnet_mkjail alcatraz ${epair}b 313 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 314 jexec alcatraz pfctl -e 315 316 pft_set_rules alcatraz "block all" \ 317 "pass in proto icmp label foo label bar" \ 318 "set skip on lo" 319 320 # Sanity check & establish state 321 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 322 --sendif ${epair}a \ 323 --to 192.0.2.2 \ 324 --replyif ${epair}a 325 326 # Change rules to now deny the ICMP traffic 327 pft_set_rules noflush alcatraz "block all" 328 if ! find_state; 329 then 330 atf_fail "Setting new rules removed the state." 331 fi 332 333 # Killing a label on a different rules keeps the state 334 jexec alcatraz pfctl -k label -k baz 335 if ! find_state; 336 then 337 atf_fail "Killing a different label removed the state." 338 fi 339 340 # Killing the state with the last label works 341 jexec alcatraz pfctl -k label -k bar 342 if find_state; 343 then 344 atf_fail "Killing with the last label did not remove the state." 345 fi 346 347 pft_set_rules alcatraz "block all" \ 348 "pass in proto icmp label foo label bar" \ 349 "set skip on lo" 350 351 # Reestablish state 352 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 353 --sendif ${epair}a \ 354 --to 192.0.2.2 \ 355 --replyif ${epair}a 356 357 # Change rules to now deny the ICMP traffic 358 pft_set_rules noflush alcatraz "block all" 359 if ! find_state; 360 then 361 atf_fail "Setting new rules removed the state." 362 fi 363 364 # Killing with the first label works too 365 jexec alcatraz pfctl -k label -k foo 366 if find_state; 367 then 368 atf_fail "Killing with the first label did not remove the state." 369 fi 370} 371 372multilabel_cleanup() 373{ 374 pft_cleanup 375} 376 377atf_test_case "gateway" "cleanup" 378gateway_head() 379{ 380 atf_set descr 'Test killing states by route-to/reply-to address' 381 atf_set require.user root 382 atf_set require.progs python3 scapy 383} 384 385gateway_body() 386{ 387 pft_init 388 389 epair=$(vnet_mkepair) 390 ifconfig ${epair}a 192.0.2.1/24 up 391 392 vnet_mkjail alcatraz ${epair}b 393 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 394 jexec alcatraz pfctl -e 395 396 pft_set_rules alcatraz "block all" \ 397 "pass in reply-to (${epair}b 192.0.2.1) proto icmp" \ 398 "set skip on lo" 399 400 # Sanity check & establish state 401 # Note: use pft_ping so we always use the same ID, so pf considers all 402 # echo requests part of the same flow. 403 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 404 --sendif ${epair}a \ 405 --to 192.0.2.2 \ 406 --replyif ${epair}a 407 408 # Change rules to now deny the ICMP traffic 409 pft_set_rules noflush alcatraz "block all" 410 if ! find_state; 411 then 412 atf_fail "Setting new rules removed the state." 413 fi 414 415 # Killing with a different gateway does not affect our state 416 jexec alcatraz pfctl -k gateway -k 192.0.2.2 417 if ! find_state; 418 then 419 atf_fail "Killing with a different gateway removed the state." 420 fi 421 422 # Killing states with the relevant gateway does terminate our state 423 jexec alcatraz pfctl -k gateway -k 192.0.2.1 424 if find_state; 425 then 426 atf_fail "Killing with the gateway did not remove the state." 427 fi 428} 429 430gateway_cleanup() 431{ 432 pft_cleanup 433} 434 435atf_test_case "match" "cleanup" 436match_head() 437{ 438 atf_set descr 'Test killing matching states' 439 atf_set require.user root 440} 441 442wait_for_state() 443{ 444 jail=$1 445 addr=$2 446 447 while ! jexec $jail pfctl -s s | grep $addr >/dev/null; 448 do 449 sleep .1 450 done 451} 452 453match_body() 454{ 455 pft_init 456 457 epair_one=$(vnet_mkepair) 458 ifconfig ${epair_one}a 192.0.2.1/24 up 459 460 epair_two=$(vnet_mkepair) 461 462 vnet_mkjail alcatraz ${epair_one}b ${epair_two}a 463 jexec alcatraz ifconfig ${epair_one}b 192.0.2.2/24 up 464 jexec alcatraz ifconfig ${epair_two}a 198.51.100.1/24 up 465 jexec alcatraz sysctl net.inet.ip.forwarding=1 466 jexec alcatraz pfctl -e 467 468 vnet_mkjail singsing ${epair_two}b 469 jexec singsing ifconfig ${epair_two}b 198.51.100.2/24 up 470 jexec singsing route add default 198.51.100.1 471 jexec singsing /usr/sbin/inetd -p ${PWD}/inetd-echo.pid \ 472 $(atf_get_srcdir)/echo_inetd.conf 473 474 route add 198.51.100.0/24 192.0.2.2 475 476 pft_set_rules alcatraz \ 477 "nat on ${epair_two}a from 192.0.2.0/24 -> (${epair_two}a)" \ 478 "pass all" 479 480 nc 198.51.100.2 7 & 481 wait_for_state alcatraz 192.0.2.1 482 483 # Expect two states 484 states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l) 485 if [ $states -ne 2 ] ; 486 then 487 atf_fail "Expected two states, found $states" 488 fi 489 490 # If we don't kill the matching NAT state one should be left 491 jexec alcatraz pfctl -k 192.0.2.1 492 states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l) 493 if [ $states -ne 1 ] ; 494 then 495 atf_fail "Expected one states, found $states" 496 fi 497 498 # Flush 499 jexec alcatraz pfctl -F states 500 501 nc 198.51.100.2 7 & 502 wait_for_state alcatraz 192.0.2.1 503 504 # Kill matching states, expect all of them to be gone 505 jexec alcatraz pfctl -M -k 192.0.2.1 506 states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l) 507 if [ $states -ne 0 ] ; 508 then 509 atf_fail "Expected zero states, found $states" 510 fi 511} 512 513match_cleanup() 514{ 515 pft_cleanup 516} 517 518atf_test_case "interface" "cleanup" 519interface_head() 520{ 521 atf_set descr 'Test killing states based on interface' 522 atf_set require.user root 523 atf_set require.progs python3 scapy 524} 525 526interface_body() 527{ 528 pft_init 529 530 epair=$(vnet_mkepair) 531 ifconfig ${epair}a 192.0.2.1/24 up 532 533 vnet_mkjail alcatraz ${epair}b 534 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 535 jexec alcatraz pfctl -e 536 537 pft_set_rules alcatraz "block all" \ 538 "pass in proto icmp" \ 539 "set skip on lo" 540 541 # Sanity check & establish state 542 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 543 --sendif ${epair}a \ 544 --to 192.0.2.2 \ 545 --replyif ${epair}a 546 547 # Change rules to now deny the ICMP traffic 548 pft_set_rules noflush alcatraz "block all" 549 if ! find_state; 550 then 551 atf_fail "Setting new rules removed the state." 552 fi 553 554 # Flushing states on a different interface doesn't affect our state 555 jexec alcatraz pfctl -i ${epair}a -Fs 556 if ! find_state; 557 then 558 atf_fail "Flushing on a different interface removed the state." 559 fi 560 561 # Flushing on the correct interface does (even with floating states) 562 jexec alcatraz pfctl -i ${epair}b -Fs 563 if find_state; 564 then 565 atf_fail "Flushing on a the interface did not remove the state." 566 fi 567} 568 569interface_cleanup() 570{ 571 pft_cleanup 572} 573 574atf_test_case "id" "cleanup" 575id_head() 576{ 577 atf_set descr 'Test killing states by id' 578 atf_set require.user root 579 atf_set require.progs python3 scapy 580} 581 582id_body() 583{ 584 pft_init 585 586 epair=$(vnet_mkepair) 587 ifconfig ${epair}a 192.0.2.1/24 up 588 589 vnet_mkjail alcatraz ${epair}b 590 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 591 jexec alcatraz pfctl -e 592 593 pft_set_rules alcatraz "block all" \ 594 "pass in proto tcp" \ 595 "pass in proto icmp" \ 596 "set skip on lo" 597 598 # Sanity check & establish state 599 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 600 --sendif ${epair}a \ 601 --to 192.0.2.2 \ 602 --replyif ${epair}a 603 604 # Change rules to now deny the ICMP traffic 605 pft_set_rules noflush alcatraz "block all" 606 if ! find_state; 607 then 608 atf_fail "Setting new rules removed the state." 609 fi 610 611 # Get the state ID 612 id=$(jexec alcatraz pfctl -ss -vvv | grep -A 3 icmp | 613 grep -A 3 192.0.2.2 | awk '/id:/ { printf("%s/%s", $2, $4); }') 614 615 # Kill the wrong ID 616 jexec alcatraz pfctl -k id -k 1 617 if ! find_state; 618 then 619 atf_fail "Killing a different ID removed the state." 620 fi 621 622 # Kill the correct ID 623 jexec alcatraz pfctl -k id -k ${id} 624 if find_state; 625 then 626 atf_fail "Killing the state did not remove it." 627 fi 628} 629 630id_cleanup() 631{ 632 pft_cleanup 633} 634 635atf_test_case "key" "cleanup" 636key_head() 637{ 638 atf_set descr 'Test killing states by their key' 639 atf_set require.user root 640 atf_set require.progs python3 scapy 641} 642 643key_body() 644{ 645 pft_init 646 647 epair=$(vnet_mkepair) 648 ifconfig ${epair}a 192.0.2.1/24 up 649 650 vnet_mkjail alcatraz ${epair}b 651 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 652 jexec alcatraz pfctl -e 653 654 pft_set_rules alcatraz \ 655 "block all" \ 656 "pass in proto tcp" \ 657 "pass in proto icmp" 658 659 # Sanity check & establish state 660 atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \ 661 --sendif ${epair}a \ 662 --to 192.0.2.2 \ 663 --replyif ${epair}a 664 665 # Get the state key 666 key=$(jexec alcatraz pfctl -ss -vvv | awk '/icmp/ { print($2 " " $3 " " $4 " " $5); }') 667 bad_key=$(echo ${key} | sed 's/icmp/tcp/') 668 669 # Kill the wrong key 670 atf_check -s exit:0 -e "match:killed 0 states" \ 671 jexec alcatraz pfctl -k key -k "${bad_key}" 672 if ! find_state; 673 then 674 atf_fail "Killing a different ID removed the state." 675 fi 676 677 # Kill the correct key 678 atf_check -s exit:0 -e "match:killed 1 states" \ 679 jexec alcatraz pfctl -k key -k "${key}" 680 if find_state; 681 then 682 atf_fail "Killing the state did not remove it." 683 fi 684} 685 686key_cleanup() 687{ 688 pft_cleanup 689} 690 691atf_test_case "nat" "cleanup" 692nat_head() 693{ 694 atf_set descr 'Test killing states by their NAT-ed IP address' 695 atf_set require.user root 696 atf_set require.progs python3 scapy 697} 698 699nat_body() 700{ 701 pft_init 702 j="killstate:nat" 703 704 epair_c=$(vnet_mkepair) 705 epair_srv=$(vnet_mkepair) 706 707 vnet_mkjail ${j}c ${epair_c}a 708 ifconfig -j ${j}c ${epair_c}a inet 192.0.2.2/24 up 709 jexec ${j}c route add default 192.0.2.1 710 711 vnet_mkjail ${j}srv ${epair_srv}a 712 ifconfig -j ${j}srv ${epair_srv}a inet 198.51.100.2/24 up 713 714 vnet_mkjail ${j}r ${epair_c}b ${epair_srv}b 715 ifconfig -j ${j}r ${epair_c}b inet 192.0.2.1/24 up 716 ifconfig -j ${j}r ${epair_srv}b inet 198.51.100.1/24 up 717 jexec ${j}r sysctl net.inet.ip.forwarding=1 718 719 jexec ${j}r pfctl -e 720 pft_set_rules ${j}r \ 721 "nat on ${epair_srv}b inet from 192.0.2.0/24 to any -> (${epair_srv}b)" 722 723 # Sanity check 724 atf_check -s exit:0 -o ignore \ 725 jexec ${j}c ping -c 1 192.0.2.1 726 atf_check -s exit:0 -o ignore \ 727 jexec ${j}srv ping -c 1 198.51.100.1 728 atf_check -s exit:0 -o ignore \ 729 jexec ${j}c ping -c 1 198.51.100.2 730 731 # Establish state 732 # Note: use pft_ping so we always use the same ID, so pf considers all 733 # echo requests part of the same flow. 734 atf_check -s exit:0 -o ignore jexec ${j}c ${common_dir}/pft_ping.py \ 735 --sendif ${epair_c}a \ 736 --to 198.51.100.1 \ 737 --replyif ${epair_c}a 738 739 # There's NAT here, so the source IP will be 198.51.100.1 740 if ! find_state ${j}r 198.51.100.1; 741 then 742 atf_fail "Expected state not found" 743 fi 744 745 # By NAT-ed address? 746 jexec ${j}r pfctl -k nat -k 192.0.2.2 747 748 if find_state ${j}r 198.51.100.1; 749 then 750 jexec ${j}r pfctl -ss -v 751 atf_fail "Failed to remove state" 752 fi 753} 754 755nat_cleanup() 756{ 757 pft_cleanup 758} 759 760atf_init_test_cases() 761{ 762 atf_add_test_case "v4" 763 atf_add_test_case "src_dst" 764 atf_add_test_case "v6" 765 atf_add_test_case "label" 766 atf_add_test_case "multilabel" 767 atf_add_test_case "gateway" 768 atf_add_test_case "match" 769 atf_add_test_case "interface" 770 atf_add_test_case "id" 771 atf_add_test_case "key" 772 atf_add_test_case "nat" 773} 774