1# $FreeBSD$ 2# 3# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 4# 5# Copyright © 2021. Rubicon Communications, LLC (Netgate). All Rights Reserved. 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions 9# are met: 10# 1. Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# 2. Redistributions in binary form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in the 14# documentation and/or other materials provided with the distribution. 15# 16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26# SUCH DAMAGE. 27 28. $(atf_get_srcdir)/utils.subr 29 30atf_test_case "mac" "cleanup" 31mac_head() 32{ 33 atf_set descr 'Test MAC address filtering' 34 atf_set require.user root 35} 36 37mac_body() 38{ 39 pft_init 40 41 epair=$(vnet_mkepair) 42 epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }') 43 44 ifconfig ${epair}a 192.0.2.1/24 up 45 46 vnet_mkjail alcatraz ${epair}b 47 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 48 49 pft_set_rules alcatraz \ 50 "ether block from ${epair_a_mac}" 51 52 atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2 53 54 # Now enable. Ping should fail. 55 jexec alcatraz pfctl -e 56 57 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 58 59 # Should still fail for 'to' 60 pft_set_rules alcatraz \ 61 "ether block to ${epair_a_mac}" 62 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 63 64 # Succeeds if we block a different MAC address 65 pft_set_rules alcatraz \ 66 "ether block to 00:01:02:03:04:05" 67 atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2 68 69 # Should still fail for 'to', even if it's in a list 70 pft_set_rules alcatraz \ 71 "ether block to { ${epair_a_mac}, 00:01:02:0:04:05 }" 72 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 73 74 # Now try this with an interface specified 75 pft_set_rules alcatraz \ 76 "ether block on ${epair}b from ${epair_a_mac}" 77 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 78 79 # Wrong interface should not match 80 pft_set_rules alcatraz \ 81 "ether block on ${epair}a from ${epair_a_mac}" 82 atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2 83 84 # Test negation 85 pft_set_rules alcatraz \ 86 "ether block in on ${epair}b from ! ${epair_a_mac}" 87 atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2 88 89 pft_set_rules alcatraz \ 90 "ether block out on ${epair}b to ! ${epair_a_mac}" 91 atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2 92 93 # Block everything not us 94 pft_set_rules alcatraz \ 95 "ether block out on ${epair}b to { ! ${epair_a_mac} }" 96 atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2 97 98 # Block us now 99 pft_set_rules alcatraz \ 100 "ether block out on ${epair}b to { ! 00:01:02:03:04:05 }" 101 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 102 103 # Block with a masked address 104 pft_set_rules alcatraz \ 105 "ether block out on ${epair}b to { ! 00:01:02:03:00:00/32 }" 106 jexec alcatraz pfctl -se 107 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 108 109 epair_prefix=$(echo $epair_a_mac | cut -c-8) 110 pft_set_rules alcatraz \ 111 "ether block out on ${epair}b to { ${epair_prefix}:00:00:00/24 }" 112 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 113 114 pft_set_rules alcatraz \ 115 "ether block out on ${epair}b to { ${epair_prefix}:00:00:00&ff:ff:ff:00:00:00 }" 116 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 117 118 # Check '-F ethernet' works 119 jexec alcatraz pfctl -F ethernet 120 atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2 121} 122 123mac_cleanup() 124{ 125 pft_cleanup 126} 127 128atf_test_case "proto" "cleanup" 129proto_head() 130{ 131 atf_set descr 'Test EtherType filtering' 132 atf_set require.user root 133} 134 135proto_body() 136{ 137 pft_init 138 139 epair=$(vnet_mkepair) 140 epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }') 141 142 ifconfig ${epair}a 192.0.2.1/24 up 143 144 vnet_mkjail alcatraz ${epair}b 145 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 146 147 pft_set_rules alcatraz \ 148 "ether block proto 0x0810" 149 jexec alcatraz pfctl -e 150 151 atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2 152 153 # Block IP 154 pft_set_rules alcatraz \ 155 "ether block proto 0x0800" 156 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 157 158 # Block ARP 159 pft_set_rules alcatraz \ 160 "ether block proto 0x0806" 161 arp -d 192.0.2.2 162 atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2 163} 164 165proto_cleanup() 166{ 167 pft_cleanup 168} 169 170atf_test_case "direction" "cleanup" 171direction_head() 172{ 173 atf_set descr 'Test directionality of ether rules' 174 atf_set require.user root 175 atf_set require.progs jq 176} 177 178direction_body() 179{ 180 pft_init 181 182 epair=$(vnet_mkepair) 183 epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }') 184 epair_b_mac=$(ifconfig ${epair}b ether | awk '/ether/ { print $2; }') 185 186 ifconfig ${epair}a 192.0.2.1/24 up 187 188 vnet_mkjail alcatraz ${epair}b 189 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 190 191 pft_set_rules alcatraz \ 192 "ether block in proto 0x0806" 193 jexec alcatraz pfctl -e 194 195 arp -d 192.0.2.2 196 jexec alcatraz arp -d 192.0.2.1 197 198 # We don't allow the jail to receive ARP requests, so if we try to ping 199 # from host to jail the host can't resolve the MAC address 200 ping -c 1 -t 1 192.0.2.2 201 202 mac=$(arp -an --libxo json \ 203 | jq '."arp"."arp-cache"[] | 204 select(."ip-address"=="192.0.2.2")."mac-address"') 205 atf_check_not_equal "$mac" "$epair_b_mac" 206 207 # Clear ARP table again 208 arp -d 192.0.2.2 209 jexec alcatraz arp -d 192.0.2.1 210 211 # However, we allow outbound ARP, so the host will learn our MAC if the 212 # jail tries to ping 213 jexec alcatraz ping -c 1 -t 1 192.0.2.1 214 215 mac=$(arp -an --libxo json \ 216 | jq '."arp"."arp-cache"[] | 217 select(."ip-address"=="192.0.2.2")."mac-address"') 218 atf_check_equal "$mac" "$epair_b_mac" 219 220 # Now do the same, but with outbound ARP blocking 221 pft_set_rules alcatraz \ 222 "ether block out proto 0x0806" 223 224 # Clear ARP table again 225 arp -d 192.0.2.2 226 jexec alcatraz arp -d 192.0.2.1 227 228 # The jail can't send ARP requests to us, so we'll never learn our MAC 229 # address 230 jexec alcatraz ping -c 1 -t 1 192.0.2.1 231 232 mac=$(jexec alcatraz arp -an --libxo json \ 233 | jq '."arp"."arp-cache"[] | 234 select(."ip-address"=="192.0.2.1")."mac-address"') 235 atf_check_not_equal "$mac" "$epair_a_mac" 236} 237 238direction_cleanup() 239{ 240 pft_cleanup 241} 242 243atf_test_case "captive" "cleanup" 244captive_head() 245{ 246 atf_set descr 'Test a basic captive portal-like setup' 247 atf_set require.user root 248} 249 250captive_body() 251{ 252 # Host is client, jail 'gw' is the captive portal gateway, jail 'srv' 253 # is a random (web)server. We use the echo protocol rather than http 254 # for the test, because that's easier. 255 pft_init 256 257 epair_gw=$(vnet_mkepair) 258 epair_srv=$(vnet_mkepair) 259 epair_gw_a_mac=$(ifconfig ${epair_gw}a ether | awk '/ether/ { print $2; }') 260 261 vnet_mkjail gw ${epair_gw}b ${epair_srv}a 262 vnet_mkjail srv ${epair_srv}b 263 264 ifconfig ${epair_gw}a 192.0.2.2/24 up 265 route add -net 198.51.100.0/24 192.0.2.1 266 jexec gw ifconfig ${epair_gw}b 192.0.2.1/24 up 267 jexec gw ifconfig lo0 127.0.0.1/8 up 268 jexec gw sysctl net.inet.ip.forwarding=1 269 270 jexec gw ifconfig ${epair_srv}a 198.51.100.1/24 up 271 jexec srv ifconfig ${epair_srv}b 198.51.100.2/24 up 272 jexec srv route add -net 192.0.2.0/24 198.51.100.1 273 274 # Sanity check 275 atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2 276 277 pft_set_rules gw \ 278 "ether pass quick proto 0x0806" \ 279 "ether pass tag captive" \ 280 "rdr on ${epair_gw}b proto tcp to port echo tagged captive -> 127.0.0.1 port echo" 281 jexec gw pfctl -e 282 283 # ICMP should still work, because we don't redirect it. 284 atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2 285 286 # Run the echo server only on the gw, so we know we've redirectly 287 # correctly if we get an echo message. 288 jexec gw /usr/sbin/inetd $(atf_get_srcdir)/echo_inetd.conf 289 290 # Confirm that we're getting redirected 291 atf_check -s exit:0 -o match:"^foo$" -x "echo foo | nc -N 198.51.100.2 7" 292 293 jexec gw killall inetd 294 295 # Now pretend we've authenticated, so add the client's MAC address 296 pft_set_rules gw \ 297 "ether pass quick proto 0x0806" \ 298 "ether pass quick from ${epair_gw_a_mac}" \ 299 "ether pass tag captive" \ 300 "rdr on ${epair_gw}b proto tcp to port echo tagged captive -> 127.0.0.1 port echo" 301 302 # No redirect, so failure. 303 atf_check -s exit:1 -x "echo foo | nc -N 198.51.100.2 7" 304 305 # Start a server in srv 306 jexec srv /usr/sbin/inetd $(atf_get_srcdir)/echo_inetd.conf 307 308 # And now we can talk to that one. 309 atf_check -s exit:0 -o match:"^foo$" -x "echo foo | nc -N 198.51.100.2 7" 310} 311 312captive_cleanup() 313{ 314 pft_cleanup 315} 316 317atf_test_case "captive_long" "cleanup" 318captive_long_head() 319{ 320 atf_set descr 'More complex captive portal setup' 321 atf_set require.user root 322} 323 324captive_long_body() 325{ 326 # Host is client, jail 'gw' is the captive portal gateway, jail 'srv' 327 # is a random (web)server. We use the echo protocol rather than http 328 # for the test, because that's easier. 329 pft_init 330 331 if ! kldstat -q -m dummynet; then 332 atf_skip "This test requires dummynet" 333 fi 334 335 epair_gw=$(vnet_mkepair) 336 epair_srv=$(vnet_mkepair) 337 epair_gw_a_mac=$(ifconfig ${epair_gw}a ether | awk '/ether/ { print $2; }') 338 339 vnet_mkjail gw ${epair_gw}b ${epair_srv}a 340 vnet_mkjail srv ${epair_srv}b 341 342 ifconfig ${epair_gw}a 192.0.2.2/24 up 343 route add -net 198.51.100.0/24 192.0.2.1 344 jexec gw ifconfig ${epair_gw}b 192.0.2.1/24 up 345 jexec gw ifconfig lo0 127.0.0.1/8 up 346 jexec gw sysctl net.inet.ip.forwarding=1 347 348 jexec gw ifconfig ${epair_srv}a 198.51.100.1/24 up 349 jexec srv ifconfig ${epair_srv}b 198.51.100.2/24 up 350 jexec srv route add -net 192.0.2.0/24 198.51.100.1 351 352 jexec gw dnctl pipe 1 config bw 300KByte/s 353 354 # Sanity check 355 atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2 356 357 pft_set_rules gw \ 358 "ether anchor \"captiveportal\" on { ${epair_gw}b } {" \ 359 "ether pass quick proto { 0x0806, 0x8035, 0x888e, 0x88c7, 0x8863, 0x8864 }" \ 360 "ether pass tag \"captive\"" \ 361 "}" \ 362 "rdr on ${epair_gw}b proto tcp to port daytime tagged captive -> 127.0.0.1 port echo" 363 jexec gw pfctl -e 364 365 # ICMP should still work, because we don't redirect it. 366 atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2 367 368 jexec gw /usr/sbin/inetd -p gw.pid $(atf_get_srcdir)/echo_inetd.conf 369 jexec srv /usr/sbin/inetd -p srv.pid $(atf_get_srcdir)/daytime_inetd.conf 370 371 echo foo | nc -N 198.51.100.2 13 372 373 # Confirm that we're getting redirected 374 atf_check -s exit:0 -o match:"^foo$" -x "echo foo | nc -N 198.51.100.2 13" 375 376 # Now update the rules to allow our client to pass without redirect 377 pft_set_rules gw \ 378 "ether anchor \"captiveportal\" on { ${epair_gw}b } {" \ 379 "ether pass quick proto { 0x0806, 0x8035, 0x888e, 0x88c7, 0x8863, 0x8864 }" \ 380 "ether pass quick from { ${epair_gw_a_mac} } dnpipe 1" \ 381 "ether pass tag \"captive\"" \ 382 "}" \ 383 "rdr on ${epair_gw}b proto tcp to port daytime tagged captive -> 127.0.0.1 port echo" 384 385 # We're not being redirected and get datime information now 386 atf_check -s exit:0 -o match:"^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)" -x "echo foo | nc -N 198.51.100.2 13" 387 388 jexec gw killall inetd 389 jexec srv killall inetd 390} 391 392captive_long_cleanup() 393{ 394 pft_cleanup 395} 396 397atf_test_case "dummynet" "cleanup" 398dummynet_head() 399{ 400 atf_set descr 'Test dummynet for L2 traffic' 401 atf_set require.user root 402} 403 404dummynet_body() 405{ 406 pft_init 407 408 if ! kldstat -q -m dummynet; then 409 atf_skip "This test requires dummynet" 410 fi 411 412 epair=$(vnet_mkepair) 413 vnet_mkjail alcatraz ${epair}b 414 415 ifconfig ${epair}a 192.0.2.1/24 up 416 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 417 418 # Sanity check 419 atf_check -s exit:0 -o ignore ping -i .1 -c 3 -s 1200 192.0.2.2 420 421 jexec alcatraz dnctl pipe 1 config bw 30Byte/s 422 jexec alcatraz pfctl -e 423 pft_set_rules alcatraz \ 424 "ether pass in dnpipe 1" 425 426 # single ping succeeds just fine 427 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2 428 429 # Saturate the link 430 ping -i .1 -c 5 -s 1200 192.0.2.2 431 432 # We should now be hitting the limits and get this packet dropped. 433 atf_check -s exit:2 -o ignore ping -c 1 -s 1200 192.0.2.2 434} 435 436dummynet_cleanup() 437{ 438 pft_cleanup 439} 440 441atf_test_case "anchor" "cleanup" 442anchor_head() 443{ 444 atf_set descr 'Test ether anchors' 445 atf_set require.user root 446} 447 448anchor_body() 449{ 450 pft_init 451 452 epair=$(vnet_mkepair) 453 epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }') 454 455 vnet_mkjail alcatraz ${epair}b 456 457 ifconfig ${epair}a 192.0.2.1/24 up 458 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 459 460 # Sanity check 461 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2 462 463 jexec alcatraz pfctl -e 464 pft_set_rules alcatraz \ 465 "ether anchor \"foo\" in on lo0 {" \ 466 "ether block" \ 467 "}" 468 469 # That only filters on lo0, so we should still be able to pass traffic 470 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2 471 472 pft_set_rules alcatraz \ 473 "ether block in" \ 474 "ether anchor \"foo\" in on ${epair}b {" \ 475 "ether pass" \ 476 "}" 477 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2 478 479 pft_set_rules alcatraz \ 480 "ether pass" \ 481 "ether anchor \"bar\" in on ${epair}b {" \ 482 "ether block" \ 483 "}" 484 atf_check -s exit:2 -o ignore ping -c 1 -t 2 192.0.2.2 485 486 pft_set_rules alcatraz \ 487 "ether block in" \ 488 "ether anchor \"baz\" on ${epair}b {" \ 489 "ether pass in from 01:02:03:04:05:06" \ 490 "}" \ 491 "ether pass in from ${epair_a_mac}" 492 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2 493 494 atf_check -s exit:0 -o match:'baz' jexec alcatraz pfctl -sA 495} 496 497anchor_cleanup() 498{ 499 pft_cleanup 500} 501 502atf_test_case "ip" "cleanup" 503ip_head() 504{ 505 atf_set descr 'Test filtering based on IP source/destination' 506 atf_set require.user root 507} 508 509ip_body() 510{ 511 pft_init 512 513 epair=$(vnet_mkepair) 514 515 vnet_mkjail alcatraz ${epair}b 516 517 ifconfig ${epair}a 192.0.2.1/24 up 518 jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up 519 520 # Sanity check 521 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2 522 523 jexec alcatraz pfctl -e 524 pft_set_rules alcatraz \ 525 "ether pass" \ 526 "ether block in l3 from 192.0.2.1" 527 528 atf_check -s exit:2 -o ignore ping -c 1 192.0.2.2 529 530 # Change IP address and we can ping again 531 ifconfig ${epair}a 192.0.2.3/24 up 532 atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2 533 534 # Test the 'to' keyword too 535 pft_set_rules alcatraz \ 536 "ether pass" \ 537 "ether block out l3 to 192.0.2.3" 538 atf_check -s exit:2 -o ignore ping -c 1 192.0.2.2 539 540 # Test table 541 pft_set_rules alcatraz \ 542 "table <tbl> { 192.0.2.3 }" \ 543 "ether pass" \ 544 "ether block out l3 to <tbl>" 545 atf_check -s exit:2 -o ignore ping -c 1 192.0.2.2 546} 547 548ip_cleanup() 549{ 550 pft_cleanup 551} 552 553atf_init_test_cases() 554{ 555 atf_add_test_case "mac" 556 atf_add_test_case "proto" 557 atf_add_test_case "direction" 558 atf_add_test_case "captive" 559 atf_add_test_case "captive_long" 560 atf_add_test_case "dummynet" 561 atf_add_test_case "anchor" 562 atf_add_test_case "ip" 563} 564