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 atf_check nc -N $addr $port < in 257 258 atf_check -o file:in cat out 259 260 atf_check -e ignore pfctl -d 261} 262 263atf_test_case "loopback" "cleanup" 264loopback_head() 265{ 266 atf_set descr 'Make sure that loopback v4 TCP connections work with syncookies on' 267 atf_set require.user root 268} 269 270loopback_body() 271{ 272 local epair 273 274 pft_init 275 276 atf_check ifconfig lo0 127.0.0.1/8 277 atf_check ifconfig lo0 up 278 279 loopback_test 127.0.0.1 8080 280 281 epair=$(vnet_mkepair) 282 atf_check ifconfig ${epair}a inet 192.0.2.1/24 283 284 loopback_test 192.0.2.1 8081 285} 286 287loopback_cleanup() 288{ 289 pft_cleanup 290} 291 292atf_test_case "loopback_v6" "cleanup" 293loopback_v6_head() 294{ 295 atf_set descr 'Make sure that loopback v6 TCP connections work with syncookies on' 296 atf_set require.user root 297} 298 299loopback_v6_body() 300{ 301 local epair 302 303 pft_init 304 305 atf_check ifconfig lo0 up 306 307 loopback_test ::1 8080 308 309 epair=$(vnet_mkepair) 310 atf_check ifconfig ${epair}a inet6 2001:db8::1/64 311 312 loopback_test 2001:db8::1 8081 313} 314 315loopback_v6_cleanup() 316{ 317 pft_cleanup 318} 319 320atf_test_case "nostate" "cleanup" 321nostate_head() 322{ 323 atf_set descr 'Ensure that we do not create until SYN|ACK' 324 atf_set require.user root 325 atf_set require.progs scapy 326} 327 328nostate_body() 329{ 330 pft_init 331 332 epair=$(vnet_mkepair) 333 ifconfig ${epair}a 192.0.2.2/24 up 334 335 vnet_mkjail alcatraz ${epair}b 336 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 337 338 jexec alcatraz pfctl -e 339 pft_set_rules alcatraz \ 340 "set syncookies always" \ 341 "pass in" \ 342 "pass out" 343 344 # Sanity check 345 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 346 347 # Now syn flood to create many states 348 ${common_dir}/pft_synflood.py \ 349 --sendif ${epair}a \ 350 --to 192.0.2.2 \ 351 --count 20 352 353 states=$(jexec alcatraz pfctl -ss | grep tcp) 354 if [ -n "$states" ]; 355 then 356 echo "$states" 357 atf_fail "Found unexpected state" 358 fi 359} 360 361nostate_cleanup() 362{ 363 pft_cleanup 364} 365 366atf_test_case "nostate_v6" "cleanup" 367nostate_v6_head() 368{ 369 atf_set descr 'Ensure that we do not create until SYN|ACK' 370 atf_set require.user root 371 atf_set require.progs scapy 372} 373 374nostate_v6_body() 375{ 376 pft_init 377 378 epair=$(vnet_mkepair) 379 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 380 381 vnet_mkjail alcatraz ${epair}b 382 jexec alcatraz ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 383 384 jexec alcatraz pfctl -e 385 pft_set_rules alcatraz \ 386 "set syncookies always" \ 387 "pass in" \ 388 "pass out" 389 390 # Sanity check 391 atf_check -s exit:0 -o ignore ping6 -c 1 2001:db8::1 392 393 # Now syn flood to create many states 394 ${common_dir}/pft_synflood.py \ 395 --ip6 \ 396 --sendif ${epair}a \ 397 --to 2001:db8::2 \ 398 --count 20 399 400 states=$(jexec alcatraz pfctl -ss | grep tcp) 401 if [ -n "$states" ]; 402 then 403 echo "$states" 404 atf_fail "Found unexpected state" 405 fi 406} 407 408nostate_v6_cleanup() 409{ 410 pft_cleanup 411} 412 413atf_test_case "adaptive" "cleanup" 414adaptive_head() 415{ 416 atf_set descr 'Adaptive mode test' 417 atf_set require.user root 418 atf_set require.progs scapy 419} 420 421adaptive_body() 422{ 423 pft_init 424 425 epair=$(vnet_mkepair) 426 ifconfig ${epair}a 192.0.2.2/24 up 427 428 vnet_mkjail alcatraz ${epair}b 429 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 430 431 jexec alcatraz pfctl -e 432 pft_set_rules alcatraz \ 433 "set limit states 100" \ 434 "set syncookies adaptive (start 10%%, end 5%%)" \ 435 "pass in" \ 436 "pass out" 437 438 # Sanity check 439 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 440 441 # Check that status shows syncookies as being inactive 442 active=$(syncookie_state alcatraz) 443 if [ "$active" != "inactive" ]; 444 then 445 atf_fail "syncookies active when they should not be" 446 fi 447 448 # Now syn flood to create many states 449 ${common_dir}/pft_synflood.py \ 450 --sendif ${epair}a \ 451 --to 192.0.2.2 \ 452 --count 100 453 454 # Check that status shows syncookies as being active 455 active=$(syncookie_state alcatraz) 456 if [ "$active" != "active" ]; 457 then 458 atf_fail "syncookies not active" 459 fi 460 461 # Adaptive mode should kick in and stop us from creating more than 462 # about 10 states 463 states=$(jexec alcatraz pfctl -ss | grep tcp | wc -l) 464 if [ "$states" -gt 20 ]; 465 then 466 echo "$states" 467 atf_fail "Found unexpected states" 468 fi 469} 470 471adaptive_cleanup() 472{ 473 pft_cleanup 474} 475 476atf_test_case "limits" "cleanup" 477limits_head() 478{ 479 atf_set descr 'Ensure limit calculation works for low or high state limits' 480 atf_set require.user root 481} 482 483limits_body() 484{ 485 pft_init 486 487 vnet_mkjail alcatraz 488 489 jexec alcatraz pfctl -e 490 pft_set_rules alcatraz \ 491 "set limit states 1" \ 492 "set syncookies adaptive (start 10%%, end 5%%)" \ 493 "pass in" \ 494 "pass out" 495 496 pft_set_rules alcatraz \ 497 "set limit states 326000000" \ 498 "set syncookies adaptive (start 10%%, end 5%%)" \ 499 "pass in" \ 500 "pass out" 501} 502 503limits_cleanup() 504{ 505 pft_cleanup 506} 507 508atf_test_case "port_reuse" "cleanup" 509port_reuse_head() 510{ 511 atf_set descr 'Test rapid port re-use' 512 atf_set require.user root 513} 514 515port_reuse_body() 516{ 517 pft_init 518 519 epair=$(vnet_mkepair) 520 521 vnet_mkjail alcatraz ${epair}b 522 vnet_mkjail singsing 523 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 524 jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \ 525 $(atf_get_srcdir)/echo_inetd.conf 526 527 ifconfig ${epair}a 192.0.2.2/24 up 528 529 jexec alcatraz pfctl -e 530 jexec alcatraz pfctl -x loud 531 pft_set_rules alcatraz \ 532 "set syncookies always" \ 533 "pass in" \ 534 "pass out" 535 536 # Sanity check 537 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 538 539 reply=$(echo foo | nc -p 1234 -N -w 5 192.0.2.1 7) 540 if [ "${reply}" != "foo" ]; 541 then 542 atf_fail "Failed to connect to syncookie protected echo daemon" 543 fi 544 545 # We can't re-use the source IP/port combo quickly enough, so we're 546 # going to play a really dirty trick, and move our interface to a new 547 # jail, and do it from there. 548 ifconfig ${epair}a vnet singsing 549 jexec singsing ifconfig ${epair}a 192.0.2.2/24 up 550 atf_check -s exit:0 -o ignore jexec singsing ping -c 1 192.0.2.1 551 552 reply=$(echo bar | jexec singsing nc -p 1234 -N -w 5 192.0.2.1 7) 553 if [ "${reply}" != "bar" ]; 554 then 555 atf_fail "Failed to connect to syncookie protected echo daemon (2)" 556 fi 557} 558 559port_reuse_cleanup() 560{ 561 pft_cleanup 562} 563 564atf_init_test_cases() 565{ 566 atf_add_test_case "basic" 567 atf_add_test_case "basic_v6" 568 atf_add_test_case "forward" 569 atf_add_test_case "forward_v6" 570 atf_add_test_case "loopback" 571 atf_add_test_case "loopback_v6" 572 atf_add_test_case "nostate" 573 atf_add_test_case "nostate_v6" 574 atf_add_test_case "adaptive" 575 atf_add_test_case "limits" 576 atf_add_test_case "port_reuse" 577} 578