1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2021 Modirum MDPay 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 31syncookie_state() 32{ 33 jail=$1 34 35 jexec $jail pfctl -si -v | grep -A 2 '^Syncookies' | grep active \ 36 | awk '{ print($2); }' 37} 38 39atf_test_case "basic" "cleanup" 40basic_head() 41{ 42 atf_set descr 'Basic syncookie test' 43 atf_set require.user root 44} 45 46basic_body() 47{ 48 pft_init 49 50 epair=$(vnet_mkepair) 51 52 vnet_mkjail alcatraz ${epair}b 53 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 54 jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \ 55 $(atf_get_srcdir)/echo_inetd.conf 56 57 ifconfig ${epair}a 192.0.2.2/24 up 58 59 jexec alcatraz pfctl -e 60 pft_set_rules alcatraz \ 61 "set syncookies always" \ 62 "pass in" \ 63 "pass out" 64 65 # Sanity check 66 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 67 68 reply=$(echo foo | nc -N -w 5 192.0.2.1 7) 69 if [ "${reply}" != "foo" ]; 70 then 71 atf_fail "Failed to connect to syncookie protected echo daemon" 72 fi 73 74 # Check that status shows syncookies as being active 75 active=$(syncookie_state alcatraz) 76 if [ "$active" != "active" ]; 77 then 78 atf_fail "syncookies not active" 79 fi 80} 81 82basic_cleanup() 83{ 84 rm -f ${PWD}/inetd-alcatraz.pid 85 pft_cleanup 86} 87 88atf_test_case "basic_v6" "cleanup" 89basic_v6_head() 90{ 91 atf_set descr 'Basic syncookie IPv6 test' 92 atf_set require.user root 93} 94 95basic_v6_body() 96{ 97 pft_init 98 99 epair=$(vnet_mkepair) 100 101 vnet_mkjail alcatraz ${epair}b 102 jexec alcatraz ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 103 jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \ 104 $(atf_get_srcdir)/echo_inetd.conf 105 106 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 107 108 jexec alcatraz pfctl -e 109 pft_set_rules alcatraz \ 110 "set syncookies always" \ 111 "pass in" \ 112 "pass out" 113 114 # Sanity check 115 atf_check -s exit:0 -o ignore ping6 -c 1 2001:db8::1 116 117 reply=$(echo foo | nc -N -w 5 2001:db8::1 7) 118 if [ "${reply}" != "foo" ]; 119 then 120 atf_fail "Failed to connect to syncookie protected echo daemon" 121 fi 122 123 # Check that status shows syncookies as being active 124 active=$(syncookie_state alcatraz) 125 if [ "$active" != "active" ]; 126 then 127 atf_fail "syncookies not active" 128 fi 129} 130 131basic_v6_cleanup() 132{ 133 pft_cleanup 134} 135 136atf_test_case "forward" "cleanup" 137forward_head() 138{ 139 atf_set descr 'Syncookies for forwarded hosts' 140 atf_set require.user root 141} 142 143forward_body() 144{ 145 pft_init 146 147 epair_in=$(vnet_mkepair) 148 epair_out=$(vnet_mkepair) 149 150 vnet_mkjail fwd ${epair_in}b ${epair_out}a 151 vnet_mkjail srv ${epair_out}b 152 153 jexec fwd ifconfig ${epair_in}b 192.0.2.1/24 up 154 jexec fwd ifconfig ${epair_out}a 198.51.100.1/24 up 155 jexec fwd sysctl net.inet.ip.forwarding=1 156 157 jexec srv ifconfig ${epair_out}b 198.51.100.2/24 up 158 jexec srv route add default 198.51.100.1 159 jexec srv /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \ 160 $(atf_get_srcdir)/echo_inetd.conf 161 162 ifconfig ${epair_in}a 192.0.2.2/24 up 163 route add -net 198.51.100.0/24 192.0.2.1 164 165 jexec fwd pfctl -e 166 pft_set_rules fwd \ 167 "set syncookies always" \ 168 "pass in" \ 169 "pass out" 170 171 # Sanity check 172 atf_check -s exit:0 -o ignore ping -c 1 198.51.100.2 173 174 reply=$(echo foo | nc -N -w 5 198.51.100.2 7) 175 if [ "${reply}" != "foo" ]; 176 then 177 atf_fail "Failed to connect to syncookie protected echo daemon" 178 fi 179} 180 181forward_cleanup() 182{ 183 pft_cleanup 184} 185 186atf_test_case "forward_v6" "cleanup" 187forward_v6_head() 188{ 189 atf_set descr 'Syncookies for forwarded hosts' 190 atf_set require.user root 191} 192 193forward_v6_body() 194{ 195 pft_init 196 197 epair_in=$(vnet_mkepair) 198 epair_out=$(vnet_mkepair) 199 200 vnet_mkjail fwd ${epair_in}b ${epair_out}a 201 vnet_mkjail srv ${epair_out}b 202 203 jexec fwd ifconfig ${epair_in}b inet6 2001:db8::1/64 up no_dad 204 jexec fwd ifconfig ${epair_out}a inet6 2001:db8:1::1/64 up no_dad 205 jexec fwd sysctl net.inet6.ip6.forwarding=1 206 207 jexec srv ifconfig ${epair_out}b inet6 2001:db8:1::2/64 up no_dad 208 jexec srv route -6 add default 2001:db8:1::1 209 jexec srv /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \ 210 $(atf_get_srcdir)/echo_inetd.conf 211 212 ifconfig ${epair_in}a inet6 2001:db8::2/64 up no_dad 213 route -6 add -net 2001:db8:1::/64 2001:db8::1 214 215 jexec fwd pfctl -e 216 pft_set_rules fwd \ 217 "set syncookies always" \ 218 "pass in" \ 219 "pass out" 220 221 # Sanity check 222 atf_check -s exit:0 -o ignore ping6 -c 1 2001:db8:1::2 223 224 reply=$(echo foo | nc -N -w 5 2001:db8:1::2 7) 225 if [ "${reply}" != "foo" ]; 226 then 227 atf_fail "Failed to connect to syncookie protected echo daemon" 228 fi 229} 230 231forward_v6_cleanup() 232{ 233 pft_cleanup 234} 235 236loopback_test() 237{ 238 local addr port 239 240 addr=$1 241 port=$2 242 243 # syncookies don't work without state tracking enabled. 244 atf_check -e ignore pfctl -e 245 atf_check pfctl -f - <<__EOF__ 246set syncookies always 247pass all keep state 248__EOF__ 249 250 # Try to transmit data over a loopback connection. 251 cat <<__EOF__ >in 252Creativity, no. 253__EOF__ 254 nc -l $addr $port >out & 255 256 # Give the background nc time to start 257 sleep 1 258 259 atf_check nc -N $addr $port < in 260 261 atf_check -o file:in cat out 262 263 atf_check -e ignore pfctl -d 264} 265 266atf_test_case "loopback" "cleanup" 267loopback_head() 268{ 269 atf_set descr 'Make sure that loopback v4 TCP connections work with syncookies on' 270 atf_set require.user root 271} 272 273loopback_body() 274{ 275 local epair 276 277 pft_init 278 279 atf_check ifconfig lo0 127.0.0.1/8 280 atf_check ifconfig lo0 up 281 282 loopback_test 127.0.0.1 8080 283 284 epair=$(vnet_mkepair) 285 atf_check ifconfig ${epair}a inet 192.0.2.1/24 286 287 loopback_test 192.0.2.1 8081 288} 289 290loopback_cleanup() 291{ 292 pft_cleanup 293} 294 295atf_test_case "loopback_v6" "cleanup" 296loopback_v6_head() 297{ 298 atf_set descr 'Make sure that loopback v6 TCP connections work with syncookies on' 299 atf_set require.user root 300} 301 302loopback_v6_body() 303{ 304 local epair 305 306 pft_init 307 308 atf_check ifconfig lo0 up 309 310 loopback_test ::1 8080 311 312 epair=$(vnet_mkepair) 313 atf_check ifconfig ${epair}a inet6 2001:db8::1/64 314 315 loopback_test 2001:db8::1 8081 316} 317 318loopback_v6_cleanup() 319{ 320 pft_cleanup 321} 322 323atf_test_case "nostate" "cleanup" 324nostate_head() 325{ 326 atf_set descr 'Ensure that we do not create until SYN|ACK' 327 atf_set require.user root 328 atf_set require.progs python3 scapy 329} 330 331nostate_body() 332{ 333 pft_init 334 335 epair=$(vnet_mkepair) 336 ifconfig ${epair}a 192.0.2.2/24 up 337 338 vnet_mkjail alcatraz ${epair}b 339 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 340 341 jexec alcatraz pfctl -e 342 pft_set_rules alcatraz \ 343 "set syncookies always" \ 344 "pass in" \ 345 "pass out" 346 347 # Sanity check 348 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 349 350 # Now syn flood to create many states 351 ${common_dir}/pft_synflood.py \ 352 --sendif ${epair}a \ 353 --to 192.0.2.2 \ 354 --count 20 355 356 states=$(jexec alcatraz pfctl -ss | grep tcp) 357 if [ -n "$states" ]; 358 then 359 echo "$states" 360 atf_fail "Found unexpected state" 361 fi 362} 363 364nostate_cleanup() 365{ 366 pft_cleanup 367} 368 369atf_test_case "nostate_v6" "cleanup" 370nostate_v6_head() 371{ 372 atf_set descr 'Ensure that we do not create until SYN|ACK' 373 atf_set require.user root 374 atf_set require.progs python3 scapy 375} 376 377nostate_v6_body() 378{ 379 pft_init 380 381 epair=$(vnet_mkepair) 382 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 383 384 vnet_mkjail alcatraz ${epair}b 385 jexec alcatraz ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 386 387 jexec alcatraz pfctl -e 388 pft_set_rules alcatraz \ 389 "set syncookies always" \ 390 "pass in" \ 391 "pass out" 392 393 # Sanity check 394 atf_check -s exit:0 -o ignore ping6 -c 1 2001:db8::1 395 396 # Now syn flood to create many states 397 ${common_dir}/pft_synflood.py \ 398 --ip6 \ 399 --sendif ${epair}a \ 400 --to 2001:db8::2 \ 401 --count 20 402 403 states=$(jexec alcatraz pfctl -ss | grep tcp) 404 if [ -n "$states" ]; 405 then 406 echo "$states" 407 atf_fail "Found unexpected state" 408 fi 409} 410 411nostate_v6_cleanup() 412{ 413 pft_cleanup 414} 415 416atf_test_case "adaptive" "cleanup" 417adaptive_head() 418{ 419 atf_set descr 'Adaptive mode test' 420 atf_set require.user root 421 atf_set require.progs python3 scapy 422} 423 424adaptive_body() 425{ 426 pft_init 427 428 epair=$(vnet_mkepair) 429 ifconfig ${epair}a 192.0.2.2/24 up 430 431 vnet_mkjail alcatraz ${epair}b 432 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 433 434 jexec alcatraz pfctl -e 435 pft_set_rules alcatraz \ 436 "set limit states 100" \ 437 "set syncookies adaptive (start 10%%, end 5%%)" \ 438 "pass in" \ 439 "pass out" 440 441 # Sanity check 442 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 443 444 # Check that status shows syncookies as being inactive 445 active=$(syncookie_state alcatraz) 446 if [ "$active" != "inactive" ]; 447 then 448 atf_fail "syncookies active when they should not be" 449 fi 450 451 # Now syn flood to create many states 452 ${common_dir}/pft_synflood.py \ 453 --sendif ${epair}a \ 454 --to 192.0.2.2 \ 455 --count 100 456 457 # Check that status shows syncookies as being active 458 active=$(syncookie_state alcatraz) 459 if [ "$active" != "active" ]; 460 then 461 atf_fail "syncookies not active" 462 fi 463 464 # Adaptive mode should kick in and stop us from creating more than 465 # about 10 states 466 states=$(jexec alcatraz pfctl -ss | grep tcp | wc -l) 467 if [ "$states" -gt 20 ]; 468 then 469 echo "$states" 470 atf_fail "Found unexpected states" 471 fi 472} 473 474adaptive_cleanup() 475{ 476 pft_cleanup 477} 478 479atf_test_case "limits" "cleanup" 480limits_head() 481{ 482 atf_set descr 'Ensure limit calculation works for low or high state limits' 483 atf_set require.user root 484} 485 486limits_body() 487{ 488 pft_init 489 490 vnet_mkjail alcatraz 491 492 jexec alcatraz pfctl -e 493 pft_set_rules alcatraz \ 494 "set limit states 1" \ 495 "set syncookies adaptive (start 10%%, end 5%%)" \ 496 "pass in" \ 497 "pass out" 498 499 pft_set_rules alcatraz \ 500 "set limit states 326000000" \ 501 "set syncookies adaptive (start 10%%, end 5%%)" \ 502 "pass in" \ 503 "pass out" 504} 505 506limits_cleanup() 507{ 508 pft_cleanup 509} 510 511atf_test_case "port_reuse" "cleanup" 512port_reuse_head() 513{ 514 atf_set descr 'Test rapid port re-use' 515 atf_set require.user root 516} 517 518port_reuse_body() 519{ 520 pft_init 521 522 epair=$(vnet_mkepair) 523 524 vnet_mkjail alcatraz ${epair}b 525 vnet_mkjail singsing 526 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 527 jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \ 528 $(atf_get_srcdir)/echo_inetd.conf 529 530 ifconfig ${epair}a 192.0.2.2/24 up 531 532 jexec alcatraz pfctl -e 533 jexec alcatraz pfctl -x loud 534 pft_set_rules alcatraz \ 535 "set syncookies always" \ 536 "pass in" \ 537 "pass out" 538 539 # Sanity check 540 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 541 542 reply=$(echo foo | nc -p 1234 -N -w 5 192.0.2.1 7) 543 if [ "${reply}" != "foo" ]; 544 then 545 atf_fail "Failed to connect to syncookie protected echo daemon" 546 fi 547 548 # We can't re-use the source IP/port combo quickly enough, so we're 549 # going to play a really dirty trick, and move our interface to a new 550 # jail, and do it from there. 551 ifconfig ${epair}a vnet singsing 552 jexec singsing ifconfig ${epair}a 192.0.2.2/24 up 553 atf_check -s exit:0 -o ignore jexec singsing ping -c 1 192.0.2.1 554 555 reply=$(echo bar | jexec singsing nc -p 1234 -N -w 5 192.0.2.1 7) 556 if [ "${reply}" != "bar" ]; 557 then 558 atf_fail "Failed to connect to syncookie protected echo daemon (2)" 559 fi 560} 561 562port_reuse_cleanup() 563{ 564 pft_cleanup 565} 566 567atf_init_test_cases() 568{ 569 atf_add_test_case "basic" 570 atf_add_test_case "basic_v6" 571 atf_add_test_case "forward" 572 atf_add_test_case "forward_v6" 573 atf_add_test_case "loopback" 574 atf_add_test_case "loopback_v6" 575 atf_add_test_case "nostate" 576 atf_add_test_case "nostate_v6" 577 atf_add_test_case "adaptive" 578 atf_add_test_case "limits" 579 atf_add_test_case "port_reuse" 580} 581