1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro> 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# 28# pf divert-to action test cases 29# 30# -----------| |-- |----| ----| |----------- 31# ( ) inbound |pf_check_in| ) -> |host| -> ( ) |pf_check_out| outbound ) 32# -----------| | |-- |----| ----| | |----------- 33# | | 34# \|/ \|/ 35# |------| |------| 36# |divapp| |divapp| 37# |------| |------| 38# 39# The basic cases: 40# - inbound > diverted | divapp terminated 41# - inbound > diverted > inbound | host terminated 42# - inbound > diverted > outbound | network terminated 43# - outbound > diverted | divapp terminated 44# - outbound > diverted > outbound | network terminated 45# - outbound > diverted > inbound | e.g. host terminated 46# 47# When a packet is diverted, forwarded, and possibly diverted again: 48# - inbound > diverted > inbound > forwarded 49# > outbound | network terminated 50# - inbound > diverted > inbound > forwarded 51# > outbound > diverted > outbound | network terminated 52# 53# Test case naming legend: 54# ipfwon - with ipfw enabled 55# ipfwoff - with ipfw disabled 56# in - inbound 57# div - diverted 58# out - outbound 59# fwd - forwarded 60# dn - delayed by dummynet 61# 62 63. $(atf_get_srcdir)/utils.subr 64 65divert_init() 66{ 67 if ! kldstat -q -m ipdivert; then 68 atf_skip "This test requires ipdivert" 69 fi 70} 71 72dummynet_init() 73{ 74 if ! kldstat -q -m dummynet; then 75 atf_skip "This test requires dummynet" 76 fi 77} 78 79ipfw_init() 80{ 81 if ! kldstat -q -m ipfw; then 82 atf_skip "This test requires ipfw" 83 fi 84} 85 86assert_ipfw_is_off() 87{ 88 if kldstat -q -m ipfw; then 89 atf_skip "This test is for the case when ipfw is not loaded" 90 fi 91} 92 93atf_test_case "ipfwoff_in_div" "cleanup" 94ipfwoff_in_div_head() 95{ 96 atf_set descr 'Test inbound > diverted | divapp terminated' 97 atf_set require.user root 98} 99ipfwoff_in_div_body() 100{ 101 local ipfwon 102 103 pft_init 104 divert_init 105 test "$1" == "ipfwon" && ipfwon="yes" 106 test $ipfwon && ipfw_init || assert_ipfw_is_off 107 108 epair=$(vnet_mkepair) 109 vnet_mkjail div ${epair}b 110 ifconfig ${epair}a 192.0.2.1/24 up 111 jexec div ifconfig ${epair}b 192.0.2.2/24 up 112 test $ipfwon && jexec div ipfw add 65534 allow all from any to any 113 114 # Sanity check 115 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 116 117 jexec div pfctl -e 118 pft_set_rules div \ 119 "pass all" \ 120 "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000" 121 122 jexec div $(atf_get_srcdir)/divapp 2000 & 123 divapp_pid=$! 124 # Wait for the divapp to be ready 125 sleep 1 126 127 # divapp is expected to "eat" the packet 128 atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2 129 130 wait $divapp_pid 131} 132ipfwoff_in_div_cleanup() 133{ 134 pft_cleanup 135} 136 137atf_test_case "ipfwon_in_div" "cleanup" 138ipfwon_in_div_head() 139{ 140 atf_set descr 'Test inbound > diverted | divapp terminated, with ipfw enabled' 141 atf_set require.user root 142} 143ipfwon_in_div_body() 144{ 145 ipfwoff_in_div_body "ipfwon" 146} 147ipfwon_in_div_cleanup() 148{ 149 pft_cleanup 150} 151 152atf_test_case "ipfwoff_in_div_in" "cleanup" 153ipfwoff_in_div_in_head() 154{ 155 atf_set descr 'Test inbound > diverted > inbound | host terminated' 156 atf_set require.user root 157} 158ipfwoff_in_div_in_body() 159{ 160 local ipfwon 161 162 pft_init 163 divert_init 164 test "$1" == "ipfwon" && ipfwon="yes" 165 test $ipfwon && ipfw_init || assert_ipfw_is_off 166 167 epair=$(vnet_mkepair) 168 vnet_mkjail div ${epair}b 169 ifconfig ${epair}a 192.0.2.1/24 up 170 jexec div ifconfig ${epair}b 192.0.2.2/24 up 171 test $ipfwon && jexec div ipfw add 65534 allow all from any to any 172 173 # Sanity check 174 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 175 176 jexec div pfctl -e 177 pft_set_rules div \ 178 "pass all" \ 179 "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000 no state" 180 181 jexec div $(atf_get_srcdir)/divapp 2000 divert-back & 182 divapp_pid=$! 183 # Wait for the divapp to be ready 184 sleep 1 185 186 # divapp is NOT expected to "eat" the packet 187 atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 188 189 wait $divapp_pid 190} 191ipfwoff_in_div_in_cleanup() 192{ 193 pft_cleanup 194} 195 196atf_test_case "ipfwon_in_div_in" "cleanup" 197ipfwon_in_div_in_head() 198{ 199 atf_set descr 'Test inbound > diverted > inbound | host terminated, with ipfw enabled' 200 atf_set require.user root 201} 202ipfwon_in_div_in_body() 203{ 204 ipfwoff_in_div_in_body "ipfwon" 205} 206ipfwon_in_div_in_cleanup() 207{ 208 pft_cleanup 209} 210 211atf_test_case "ipfwoff_out_div" "cleanup" 212ipfwoff_out_div_head() 213{ 214 atf_set descr 'Test outbound > diverted | divapp terminated' 215 atf_set require.user root 216} 217ipfwoff_out_div_body() 218{ 219 local ipfwon 220 221 pft_init 222 divert_init 223 test "$1" == "ipfwon" && ipfwon="yes" 224 test $ipfwon && ipfw_init || assert_ipfw_is_off 225 226 epair=$(vnet_mkepair) 227 vnet_mkjail div ${epair}b 228 ifconfig ${epair}a 192.0.2.1/24 up 229 jexec div ifconfig ${epair}b 192.0.2.2/24 up 230 test $ipfwon && jexec div ipfw add 65534 allow all from any to any 231 232 # Sanity check 233 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 234 235 jexec div pfctl -e 236 pft_set_rules div \ 237 "pass all" \ 238 "pass in inet proto icmp icmp-type echoreq no state" \ 239 "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state" 240 241 jexec div $(atf_get_srcdir)/divapp 2000 & 242 divapp_pid=$! 243 # Wait for the divapp to be ready 244 sleep 1 245 246 # divapp is expected to "eat" the packet 247 atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2 248 249 wait $divapp_pid 250} 251ipfwoff_out_div_cleanup() 252{ 253 pft_cleanup 254} 255 256atf_test_case "ipfwon_out_div" "cleanup" 257ipfwon_out_div_head() 258{ 259 atf_set descr 'Test outbound > diverted | divapp terminated, with ipfw enabled' 260 atf_set require.user root 261} 262ipfwon_out_div_body() 263{ 264 ipfwoff_out_div_body "ipfwon" 265} 266ipfwon_out_div_cleanup() 267{ 268 pft_cleanup 269} 270 271atf_test_case "ipfwoff_out_div_out" "cleanup" 272ipfwoff_out_div_out_head() 273{ 274 atf_set descr 'Test outbound > diverted > outbound | network terminated' 275 atf_set require.user root 276} 277ipfwoff_out_div_out_body() 278{ 279 local ipfwon 280 281 pft_init 282 divert_init 283 test "$1" == "ipfwon" && ipfwon="yes" 284 test $ipfwon && ipfw_init || assert_ipfw_is_off 285 286 epair=$(vnet_mkepair) 287 vnet_mkjail div ${epair}b 288 ifconfig ${epair}a 192.0.2.1/24 up 289 jexec div ifconfig ${epair}b 192.0.2.2/24 up 290 test $ipfwon && jexec div ipfw add 65534 allow all from any to any 291 292 # Sanity check 293 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 294 295 jexec div pfctl -e 296 pft_set_rules div \ 297 "pass all" \ 298 "pass in inet proto icmp icmp-type echoreq no state" \ 299 "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state" 300 301 jexec div $(atf_get_srcdir)/divapp 2000 divert-back & 302 divapp_pid=$! 303 # Wait for the divapp to be ready 304 sleep 1 305 306 # divapp is NOT expected to "eat" the packet 307 atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 308 309 wait $divapp_pid 310} 311ipfwoff_out_div_out_cleanup() 312{ 313 pft_cleanup 314} 315 316atf_test_case "ipfwon_out_div_out" "cleanup" 317ipfwon_out_div_out_head() 318{ 319 atf_set descr 'Test outbound > diverted > outbound | network terminated, with ipfw enabled' 320 atf_set require.user root 321} 322ipfwon_out_div_out_body() 323{ 324 ipfwoff_out_div_out_body "ipfwon" 325} 326ipfwon_out_div_out_cleanup() 327{ 328 pft_cleanup 329} 330 331atf_test_case "ipfwoff_in_div_in_fwd_out_div_out" "cleanup" 332ipfwoff_in_div_in_fwd_out_div_out_head() 333{ 334 atf_set descr 'Test inbound > diverted > inbound > forwarded > outbound > diverted > outbound | network terminated' 335 atf_set require.user root 336} 337ipfwoff_in_div_in_fwd_out_div_out_body() 338{ 339 local ipfwon 340 341 pft_init 342 divert_init 343 test "$1" == "ipfwon" && ipfwon="yes" 344 test $ipfwon && ipfw_init || assert_ipfw_is_off 345 346 # host <a--epair0--b> router <a--epair1--b> site 347 epair0=$(vnet_mkepair) 348 epair1=$(vnet_mkepair) 349 350 vnet_mkjail router ${epair0}b ${epair1}a 351 ifconfig ${epair0}a 192.0.2.1/24 up 352 jexec router sysctl net.inet.ip.forwarding=1 353 jexec router ifconfig ${epair0}b 192.0.2.2/24 up 354 jexec router ifconfig ${epair1}a 198.51.100.1/24 up 355 test $ipfwon && jexec router ipfw add 65534 allow all from any to any 356 357 vnet_mkjail site ${epair1}b 358 jexec site ifconfig ${epair1}b 198.51.100.2/24 up 359 jexec site route add default 198.51.100.1 360 test $ipfwon && jexec site ipfw add 65534 allow all from any to any 361 362 route add -net 198.51.100.0/24 192.0.2.2 363 364 # Sanity check 365 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 366 367 # Should be routed without pf 368 atf_check -s exit:0 -o ignore ping -c3 198.51.100.2 369 370 jexec router pfctl -e 371 pft_set_rules router \ 372 "pass all" \ 373 "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2001 no state" \ 374 "pass out inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2002 no state" 375 376 jexec router $(atf_get_srcdir)/divapp 2001 divert-back & 377 indivapp_pid=$! 378 jexec router $(atf_get_srcdir)/divapp 2002 divert-back & 379 outdivapp_pid=$! 380 # Wait for the divappS to be ready 381 sleep 1 382 383 # Both divappS are NOT expected to "eat" the packet 384 atf_check -s exit:0 -o ignore ping -c1 198.51.100.2 385 386 wait $indivapp_pid && wait $outdivapp_pid 387} 388ipfwoff_in_div_in_fwd_out_div_out_cleanup() 389{ 390 pft_cleanup 391} 392 393atf_test_case "ipfwon_in_div_in_fwd_out_div_out" "cleanup" 394ipfwon_in_div_in_fwd_out_div_out_head() 395{ 396 atf_set descr 'Test inbound > diverted > inbound > forwarded > outbound > diverted > outbound | network terminated, with ipfw enabled' 397 atf_set require.user root 398} 399ipfwon_in_div_in_fwd_out_div_out_body() 400{ 401 ipfwoff_in_div_in_fwd_out_div_out_body "ipfwon" 402} 403ipfwon_in_div_in_fwd_out_div_out_cleanup() 404{ 405 pft_cleanup 406} 407 408atf_test_case "ipfwoff_in_dn_in_div_in_out_div_out_dn_out" "cleanup" 409ipfwoff_in_dn_in_div_in_out_div_out_dn_out_head() 410{ 411 atf_set descr 'Test inbound > delayed+diverted > outbound > diverted+delayed > outbound | network terminated' 412 atf_set require.user root 413} 414ipfwoff_in_dn_in_div_in_out_div_out_dn_out_body() 415{ 416 local ipfwon 417 418 pft_init 419 divert_init 420 dummynet_init 421 test "$1" == "ipfwon" && ipfwon="yes" 422 test $ipfwon && ipfw_init || assert_ipfw_is_off 423 424 epair=$(vnet_mkepair) 425 vnet_mkjail alcatraz ${epair}b 426 ifconfig ${epair}a 192.0.2.1/24 up 427 ifconfig ${epair}a ether 02:00:00:00:00:01 428 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 429 test $ipfwon && jexec alcatraz ipfw add 65534 allow all from any to any 430 431 # Sanity check 432 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 433 434 # a) ping should time out due to very narrow dummynet pipes { 435 436 jexec alcatraz dnctl pipe 1001 config bw 1Byte/s 437 jexec alcatraz dnctl pipe 1002 config bw 1Byte/s 438 439 jexec alcatraz pfctl -e 440 pft_set_rules alcatraz \ 441 "ether pass in from 02:00:00:00:00:01 l3 all dnpipe 1001" \ 442 "ether pass out to 02:00:00:00:00:01 l3 all dnpipe 1002 " \ 443 "pass all" \ 444 "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 1001 no state" \ 445 "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 1002 no state" 446 447 jexec alcatraz $(atf_get_srcdir)/divapp 1001 divert-back & 448 indivapp_pid=$! 449 jexec alcatraz $(atf_get_srcdir)/divapp 1002 divert-back & 450 outdivapp_pid=$! 451 # Wait for the divappS to be ready 452 sleep 1 453 454 atf_check -s not-exit:0 -o ignore ping -c1 -s56 -t1 192.0.2.2 455 456 wait $indivapp_pid 457 atf_check_not_equal 0 $? 458 wait $outdivapp_pid 459 atf_check_not_equal 0 $? 460 461 # } 462 463 # b) ping should NOT time out due to wide enough dummynet pipes { 464 465 jexec alcatraz dnctl pipe 2001 config bw 100KByte/s 466 jexec alcatraz dnctl pipe 2002 config bw 100KByte/s 467 468 jexec alcatraz pfctl -e 469 pft_set_rules alcatraz \ 470 "ether pass in from 02:00:00:00:00:01 l3 all dnpipe 2001" \ 471 "ether pass out to 02:00:00:00:00:01 l3 all dnpipe 2002 " \ 472 "pass all" \ 473 "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2001 no state" \ 474 "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2002 no state" 475 476 jexec alcatraz $(atf_get_srcdir)/divapp 2001 divert-back & 477 indivapp_pid=$! 478 jexec alcatraz $(atf_get_srcdir)/divapp 2002 divert-back & 479 outdivapp_pid=$! 480 # Wait for the divappS to be ready 481 sleep 1 482 483 atf_check -s exit:0 -o ignore ping -c1 -s56 -t1 192.0.2.2 484 485 wait $indivapp_pid 486 atf_check_equal 0 $? 487 wait $outdivapp_pid 488 atf_check_equal 0 $? 489 490 # } 491} 492ipfwoff_in_dn_in_div_in_out_div_out_dn_out_cleanup() 493{ 494 pft_cleanup 495} 496 497atf_test_case "ipfwon_in_dn_in_div_in_out_div_out_dn_out" "cleanup" 498ipfwon_in_dn_in_div_in_out_div_out_dn_out_head() 499{ 500 atf_set descr 'Test inbound > delayed+diverted > outbound > diverted+delayed > outbound | network terminated, with ipfw enabled' 501 atf_set require.user root 502} 503ipfwon_in_dn_in_div_in_out_div_out_dn_out_body() 504{ 505 ipfwoff_in_dn_in_div_in_out_div_out_dn_out_body "ipfwon" 506} 507ipfwon_in_dn_in_div_in_out_div_out_dn_out_cleanup() 508{ 509 pft_cleanup 510} 511 512atf_init_test_cases() 513{ 514 atf_add_test_case "ipfwoff_in_div" 515 atf_add_test_case "ipfwoff_in_div_in" 516 atf_add_test_case "ipfwon_in_div" 517 atf_add_test_case "ipfwon_in_div_in" 518 519 atf_add_test_case "ipfwoff_out_div" 520 atf_add_test_case "ipfwoff_out_div_out" 521 atf_add_test_case "ipfwon_out_div" 522 atf_add_test_case "ipfwon_out_div_out" 523 524 atf_add_test_case "ipfwoff_in_div_in_fwd_out_div_out" 525 atf_add_test_case "ipfwon_in_div_in_fwd_out_div_out" 526 527 atf_add_test_case "ipfwoff_in_dn_in_div_in_out_div_out_dn_out" 528 atf_add_test_case "ipfwon_in_dn_in_div_in_out_div_out_dn_out" 529} 530