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