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# in - inbound 55# div - diverted 56# out - outbound 57# fwd - forwarded 58# ipfwon - with ipfw enabled, which allows all 59# 60 61. $(atf_get_srcdir)/utils.subr 62 63divert_init() 64{ 65 if ! kldstat -q -m ipdivert; then 66 atf_skip "This test requires ipdivert" 67 fi 68} 69 70ipfw_init() 71{ 72 if ! kldstat -q -m ipfw; then 73 atf_skip "This test requires ipfw" 74 fi 75} 76 77assert_ipfw_is_off() 78{ 79 if kldstat -q -m ipfw; then 80 atf_skip "This test is for the case when ipfw is not loaded" 81 fi 82} 83 84atf_test_case "ipfwoff_in_div" "cleanup" 85ipfwoff_in_div_head() 86{ 87 atf_set descr 'Test inbound > diverted | divapp terminated' 88 atf_set require.user root 89} 90ipfwoff_in_div_body() 91{ 92 local ipfwon 93 94 pft_init 95 divert_init 96 test "$1" == "ipfwon" && ipfwon="yes" 97 test $ipfwon && ipfw_init || assert_ipfw_is_off 98 99 epair=$(vnet_mkepair) 100 vnet_mkjail div ${epair}b 101 ifconfig ${epair}a 192.0.2.1/24 up 102 jexec div ifconfig ${epair}b 192.0.2.2/24 up 103 test $ipfwon && jexec div ipfw add 65534 allow all from any to any 104 105 # Sanity check 106 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 107 108 jexec div pfctl -e 109 pft_set_rules div \ 110 "pass all" \ 111 "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000" 112 113 jexec div $(atf_get_srcdir)/divapp 2000 & 114 divapp_pid=$! 115 # Wait for the divapp to be ready 116 sleep 1 117 118 # divapp is expected to "eat" the packet 119 atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2 120 121 wait $divapp_pid 122} 123ipfwoff_in_div_cleanup() 124{ 125 pft_cleanup 126} 127 128atf_test_case "ipfwon_in_div" "cleanup" 129ipfwon_in_div_head() 130{ 131 atf_set descr 'Test inbound > diverted | divapp terminated, with ipfw enabled' 132 atf_set require.user root 133} 134ipfwon_in_div_body() 135{ 136 ipfwoff_in_div_body "ipfwon" 137} 138ipfwon_in_div_cleanup() 139{ 140 pft_cleanup 141} 142 143atf_test_case "ipfwoff_in_div_in" "cleanup" 144ipfwoff_in_div_in_head() 145{ 146 atf_set descr 'Test inbound > diverted > inbound | host terminated' 147 atf_set require.user root 148} 149ipfwoff_in_div_in_body() 150{ 151 local ipfwon 152 153 pft_init 154 divert_init 155 test "$1" == "ipfwon" && ipfwon="yes" 156 test $ipfwon && ipfw_init || assert_ipfw_is_off 157 158 epair=$(vnet_mkepair) 159 vnet_mkjail div ${epair}b 160 ifconfig ${epair}a 192.0.2.1/24 up 161 jexec div ifconfig ${epair}b 192.0.2.2/24 up 162 test $ipfwon && jexec div ipfw add 65534 allow all from any to any 163 164 # Sanity check 165 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 166 167 jexec div pfctl -e 168 pft_set_rules div \ 169 "pass all" \ 170 "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000 no state" 171 172 jexec div $(atf_get_srcdir)/divapp 2000 divert-back & 173 divapp_pid=$! 174 # Wait for the divapp to be ready 175 sleep 1 176 177 # divapp is NOT expected to "eat" the packet 178 atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 179 180 wait $divapp_pid 181} 182ipfwoff_in_div_in_cleanup() 183{ 184 pft_cleanup 185} 186 187atf_test_case "ipfwon_in_div_in" "cleanup" 188ipfwon_in_div_in_head() 189{ 190 atf_set descr 'Test inbound > diverted > inbound | host terminated, with ipfw enabled' 191 atf_set require.user root 192} 193ipfwon_in_div_in_body() 194{ 195 ipfwoff_in_div_in_body "ipfwon" 196} 197ipfwon_in_div_in_cleanup() 198{ 199 pft_cleanup 200} 201 202atf_test_case "ipfwoff_out_div" "cleanup" 203ipfwoff_out_div_head() 204{ 205 atf_set descr 'Test outbound > diverted | divapp terminated' 206 atf_set require.user root 207} 208ipfwoff_out_div_body() 209{ 210 local ipfwon 211 212 pft_init 213 divert_init 214 test "$1" == "ipfwon" && ipfwon="yes" 215 test $ipfwon && ipfw_init || assert_ipfw_is_off 216 217 epair=$(vnet_mkepair) 218 vnet_mkjail div ${epair}b 219 ifconfig ${epair}a 192.0.2.1/24 up 220 jexec div ifconfig ${epair}b 192.0.2.2/24 up 221 test $ipfwon && jexec div ipfw add 65534 allow all from any to any 222 223 # Sanity check 224 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 225 226 jexec div pfctl -e 227 pft_set_rules div \ 228 "pass all" \ 229 "pass in inet proto icmp icmp-type echoreq no state" \ 230 "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state" 231 232 jexec div $(atf_get_srcdir)/divapp 2000 & 233 divapp_pid=$! 234 # Wait for the divapp to be ready 235 sleep 1 236 237 # divapp is expected to "eat" the packet 238 atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2 239 240 wait $divapp_pid 241} 242ipfwoff_out_div_cleanup() 243{ 244 pft_cleanup 245} 246 247atf_test_case "ipfwon_out_div" "cleanup" 248ipfwon_out_div_head() 249{ 250 atf_set descr 'Test outbound > diverted | divapp terminated, with ipfw enabled' 251 atf_set require.user root 252} 253ipfwon_out_div_body() 254{ 255 ipfwoff_out_div_body "ipfwon" 256} 257ipfwon_out_div_cleanup() 258{ 259 pft_cleanup 260} 261 262atf_test_case "ipfwoff_out_div_out" "cleanup" 263ipfwoff_out_div_out_head() 264{ 265 atf_set descr 'Test outbound > diverted > outbound | network terminated' 266 atf_set require.user root 267} 268ipfwoff_out_div_out_body() 269{ 270 local ipfwon 271 272 pft_init 273 divert_init 274 test "$1" == "ipfwon" && ipfwon="yes" 275 test $ipfwon && ipfw_init || assert_ipfw_is_off 276 277 epair=$(vnet_mkepair) 278 vnet_mkjail div ${epair}b 279 ifconfig ${epair}a 192.0.2.1/24 up 280 jexec div ifconfig ${epair}b 192.0.2.2/24 up 281 test $ipfwon && jexec div ipfw add 65534 allow all from any to any 282 283 # Sanity check 284 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 285 286 jexec div pfctl -e 287 pft_set_rules div \ 288 "pass all" \ 289 "pass in inet proto icmp icmp-type echoreq no state" \ 290 "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state" 291 292 jexec div $(atf_get_srcdir)/divapp 2000 divert-back & 293 divapp_pid=$! 294 # Wait for the divapp to be ready 295 sleep 1 296 297 # divapp is NOT expected to "eat" the packet 298 atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 299 300 wait $divapp_pid 301} 302ipfwoff_out_div_out_cleanup() 303{ 304 pft_cleanup 305} 306 307atf_test_case "ipfwon_out_div_out" "cleanup" 308ipfwon_out_div_out_head() 309{ 310 atf_set descr 'Test outbound > diverted > outbound | network terminated, with ipfw enabled' 311 atf_set require.user root 312} 313ipfwon_out_div_out_body() 314{ 315 ipfwoff_out_div_out_body "ipfwon" 316} 317ipfwon_out_div_out_cleanup() 318{ 319 pft_cleanup 320} 321 322atf_test_case "ipfwoff_in_div_in_fwd_out_div_out" "cleanup" 323ipfwoff_in_div_in_fwd_out_div_out_head() 324{ 325 atf_set descr 'Test inbound > diverted > inbound > forwarded > outbound > diverted > outbound | network terminated' 326 atf_set require.user root 327} 328ipfwoff_in_div_in_fwd_out_div_out_body() 329{ 330 local ipfwon 331 332 pft_init 333 divert_init 334 test "$1" == "ipfwon" && ipfwon="yes" 335 test $ipfwon && ipfw_init || assert_ipfw_is_off 336 337 # host <a--epair0--b> router <a--epair1--b> site 338 epair0=$(vnet_mkepair) 339 epair1=$(vnet_mkepair) 340 341 vnet_mkjail router ${epair0}b ${epair1}a 342 ifconfig ${epair0}a 192.0.2.1/24 up 343 jexec router sysctl net.inet.ip.forwarding=1 344 jexec router ifconfig ${epair0}b 192.0.2.2/24 up 345 jexec router ifconfig ${epair1}a 198.51.100.1/24 up 346 test $ipfwon && jexec router ipfw add 65534 allow all from any to any 347 348 vnet_mkjail site ${epair1}b 349 jexec site ifconfig ${epair1}b 198.51.100.2/24 up 350 jexec site route add default 198.51.100.1 351 test $ipfwon && jexec site ipfw add 65534 allow all from any to any 352 353 route add -net 198.51.100.0/24 192.0.2.2 354 355 # Sanity check 356 atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 357 358 # Should be routed without pf 359 atf_check -s exit:0 -o ignore ping -c3 198.51.100.2 360 361 jexec router pfctl -e 362 pft_set_rules router \ 363 "pass all" \ 364 "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2001 no state" \ 365 "pass out inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2002 no state" 366 367 jexec router $(atf_get_srcdir)/divapp 2001 divert-back & 368 indivapp_pid=$! 369 jexec router $(atf_get_srcdir)/divapp 2002 divert-back & 370 outdivapp_pid=$! 371 # Wait for the divappS to be ready 372 sleep 1 373 374 # Both divappS are NOT expected to "eat" the packet 375 atf_check -s exit:0 -o ignore ping -c1 198.51.100.2 376 377 wait $indivapp_pid && wait $outdivapp_pid 378} 379ipfwoff_in_div_in_fwd_out_div_out_cleanup() 380{ 381 pft_cleanup 382} 383 384atf_test_case "ipfwon_in_div_in_fwd_out_div_out" "cleanup" 385ipfwon_in_div_in_fwd_out_div_out_head() 386{ 387 atf_set descr 'Test inbound > diverted > inbound > forwarded > outbound > diverted > outbound | network terminated, with ipfw enabled' 388 atf_set require.user root 389} 390ipfwon_in_div_in_fwd_out_div_out_body() 391{ 392 ipfwoff_in_div_in_fwd_out_div_out_body "ipfwon" 393} 394ipfwon_in_div_in_fwd_out_div_out_cleanup() 395{ 396 pft_cleanup 397} 398 399atf_init_test_cases() 400{ 401 atf_add_test_case "ipfwoff_in_div" 402 atf_add_test_case "ipfwoff_in_div_in" 403 atf_add_test_case "ipfwon_in_div" 404 atf_add_test_case "ipfwon_in_div_in" 405 406 atf_add_test_case "ipfwoff_out_div" 407 atf_add_test_case "ipfwoff_out_div_out" 408 atf_add_test_case "ipfwon_out_div" 409 atf_add_test_case "ipfwon_out_div_out" 410 411 atf_add_test_case "ipfwoff_in_div_in_fwd_out_div_out" 412 atf_add_test_case "ipfwon_in_div_in_fwd_out_div_out" 413} 414