1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2024 Rubicon Communications, LLC (Netgate) 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 29nat64_setup() 30{ 31 pft_init 32 33 epair_link=$(vnet_mkepair) 34 epair=$(vnet_mkepair) 35 36 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 37 route -6 add default 2001:db8::1 38 39 vnet_mkjail rtr ${epair}b ${epair_link}a 40 jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 41 jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up 42 43 vnet_mkjail dst ${epair_link}b 44 jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up 45 jexec dst route add default 192.0.2.1 46 47 # Sanity checks 48 atf_check -s exit:0 -o ignore \ 49 ping6 -c 1 2001:db8::1 50 atf_check -s exit:0 -o ignore \ 51 jexec dst ping -c 1 192.0.2.1 52 53 jexec rtr pfctl -e 54 pft_set_rules rtr \ 55 "set reassemble yes" \ 56 "set state-policy if-bound" \ 57 "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" 58} 59 60atf_test_case "icmp_echo" "cleanup" 61icmp_echo_head() 62{ 63 atf_set descr 'Basic NAT64 ICMP echo test' 64 atf_set require.user root 65} 66 67icmp_echo_body() 68{ 69 nat64_setup 70 71 # One ping 72 atf_check -s exit:0 -o ignore \ 73 ping6 -c 1 64:ff9b::192.0.2.2 74 75 # Make sure packets make it even when state is established 76 atf_check -s exit:0 \ 77 -o match:'5 packets transmitted, 5 packets received, 0.0% packet loss' \ 78 ping6 -c 5 64:ff9b::192.0.2.2 79} 80 81icmp_echo_cleanup() 82{ 83 pft_cleanup 84} 85 86atf_test_case "fragmentation" "cleanup" 87fragmentation_head() 88{ 89 atf_set descr 'Test fragmented packets' 90 atf_set require.user root 91} 92 93fragmentation_body() 94{ 95 nat64_setup 96 97 atf_check -s exit:0 -o ignore \ 98 ping6 -c 1 -s 1280 64:ff9b::192.0.2.2 99 100 atf_check -s exit:0 \ 101 -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ 102 ping6 -c 3 -s 2000 64:ff9b::192.0.2.2 103 atf_check -s exit:0 \ 104 -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \ 105 ping6 -c 3 -s 10000 -b 20000 64:ff9b::192.0.2.2 106} 107 108fragmentation_cleanup() 109{ 110 pft_cleanup 111} 112 113atf_test_case "tcp" "cleanup" 114tcp_head() 115{ 116 atf_set descr 'TCP NAT64 test' 117 atf_set require.user root 118} 119 120tcp_body() 121{ 122 nat64_setup 123 124 echo "foo" | jexec dst nc -l 1234 & 125 126 # Sanity check & delay for nc startup 127 atf_check -s exit:0 -o ignore \ 128 ping6 -c 1 64:ff9b::192.0.2.2 129 130 rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234) 131 if [ "${rcv}" != "foo" ]; 132 then 133 echo "rcv=${rcv}" 134 atf_fail "Failed to connect to TCP server" 135 fi 136} 137 138tcp_cleanup() 139{ 140 pft_cleanup 141} 142 143atf_test_case "udp" "cleanup" 144udp_head() 145{ 146 atf_set descr 'UDP NAT64 test' 147 atf_set require.user root 148} 149 150udp_body() 151{ 152 nat64_setup 153 154 echo "foo" | jexec dst nc -u -l 1234 & 155 156 # Sanity check & delay for nc startup 157 atf_check -s exit:0 -o ignore \ 158 ping6 -c 1 64:ff9b::192.0.2.2 159 160 rcv=$(echo bar | nc -w 3 -6 -u 64:ff9b::c000:202 1234) 161 if [ "${rcv}" != "foo" ]; 162 then 163 echo "rcv=${rcv}" 164 atf_fail "Failed to connect to UDP server" 165 fi 166} 167 168udp_cleanup() 169{ 170 pft_cleanup 171} 172 173atf_test_case "sctp" "cleanup" 174sctp_head() 175{ 176 atf_set descr 'SCTP NAT64 test' 177 atf_set require.user root 178} 179 180sctp_body() 181{ 182 nat64_setup 183 if ! kldstat -q -m sctp; then 184 atf_skip "This test requires SCTP" 185 fi 186 187 echo "foo" | jexec dst nc --sctp -N -l 1234 & 188 189 # Sanity check & delay for nc startup 190 atf_check -s exit:0 -o ignore \ 191 ping6 -c 1 64:ff9b::192.0.2.2 192 193 rcv=$(echo bar | nc --sctp -w 3 -6 64:ff9b::c000:202 1234) 194 if [ "${rcv}" != "foo" ]; 195 then 196 echo "rcv=${rcv}" 197 atf_fail "Failed to connect to SCTP server" 198 fi 199} 200 201sctp_cleanup() 202{ 203 pft_cleanup 204} 205 206atf_test_case "tos" "cleanup" 207tos_head() 208{ 209 atf_set descr 'ToS translation test' 210 atf_set require.user root 211} 212 213tos_body() 214{ 215 nat64_setup 216 217 # Ensure we can distinguish ToS on the destination 218 jexec dst pfctl -e 219 pft_set_rules dst \ 220 "pass" \ 221 "block in inet tos 8" 222 223 atf_check -s exit:0 -o ignore \ 224 ping6 -c 1 -z 4 64:ff9b::192.0.2.2 225 atf_check -s exit:2 -o ignore \ 226 ping6 -c 1 -z 8 64:ff9b::192.0.2.2 227 atf_check -s exit:0 -o ignore \ 228 ping6 -c 1 -z 16 64:ff9b::192.0.2.2 229 230 jexec dst pfctl -sr -vv 231} 232 233tos_cleanup() 234{ 235 pft_cleanup 236} 237 238atf_test_case "no_v4" "cleanup" 239no_v4_head() 240{ 241 atf_set descr 'Test error handling when there is no IPv4 address to translate to' 242 atf_set require.user root 243} 244 245no_v4_body() 246{ 247 pft_init 248 249 epair_link=$(vnet_mkepair) 250 epair=$(vnet_mkepair) 251 252 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 253 route -6 add default 2001:db8::1 254 255 vnet_mkjail rtr ${epair}b ${epair_link}a 256 jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 257 258 vnet_mkjail dst ${epair_link}b 259 jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up 260 jexec dst route add default 192.0.2.1 261 262 # Sanity check 263 atf_check -s exit:0 -o ignore \ 264 ping6 -c 1 2001:db8::1 265 266 jexec rtr pfctl -e 267 pft_set_rules rtr \ 268 "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" 269 270 atf_check -s exit:2 -o ignore \ 271 ping6 -c 3 64:ff9b::192.0.2.2 272} 273 274no_v4_cleanup() 275{ 276 pft_cleanup 277} 278 279atf_test_case "range" "cleanup" 280range_head() 281{ 282 atf_set descr 'Test using an address range for the IPv4 side' 283 atf_set require.user root 284} 285 286range_body() 287{ 288 pft_init 289 290 epair_link=$(vnet_mkepair) 291 epair=$(vnet_mkepair) 292 293 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 294 route -6 add default 2001:db8::1 295 296 vnet_mkjail rtr ${epair}b ${epair_link}a 297 jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 298 jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up 299 jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up 300 301 vnet_mkjail dst ${epair_link}b 302 jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up 303 jexec dst route add default 192.0.2.2 304 305 # Sanity checks 306 atf_check -s exit:0 -o ignore \ 307 jexec rtr ping -c 1 192.0.2.254 308 atf_check -s exit:0 -o ignore \ 309 ping6 -c 1 2001:db8::1 310 atf_check -s exit:0 -o ignore \ 311 jexec dst ping -c 1 192.0.2.2 312 atf_check -s exit:0 -o ignore \ 313 jexec dst ping -c 1 192.0.2.3 314 315 jexec rtr pfctl -e 316 pft_set_rules rtr \ 317 "set reassemble yes" \ 318 "set state-policy if-bound" \ 319 "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.2/31 round-robin" 320 321 # Use pf to count sources 322 jexec dst pfctl -e 323 pft_set_rules dst \ 324 "pass" 325 326 atf_check -s exit:0 -o ignore \ 327 ping6 -c 1 64:ff9b::192.0.2.254 328 atf_check -s exit:0 -o ignore \ 329 ping6 -c 1 64:ff9b::192.0.2.254 330 331 # Verify on dst that we saw different source addresses 332 atf_check -s exit:0 -o match:".*192.0.2.2.*" \ 333 jexec dst pfctl -ss 334 atf_check -s exit:0 -o match:".*192.0.2.3.*" \ 335 jexec dst pfctl -ss 336} 337 338range_cleanup() 339{ 340 pft_cleanup 341} 342 343atf_test_case "pool" "cleanup" 344pool_head() 345{ 346 atf_set descr 'Use a pool of IPv4 addresses' 347 atf_set require.user root 348} 349 350pool_body() 351{ 352 pft_init 353 354 epair_link=$(vnet_mkepair) 355 epair=$(vnet_mkepair) 356 357 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 358 route -6 add default 2001:db8::1 359 360 vnet_mkjail rtr ${epair}b ${epair_link}a 361 jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 362 jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up 363 jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up 364 jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up 365 366 vnet_mkjail dst ${epair_link}b 367 jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up 368 jexec dst route add default 192.0.2.1 369 370 # Sanity checks 371 atf_check -s exit:0 -o ignore \ 372 ping6 -c 1 2001:db8::1 373 atf_check -s exit:0 -o ignore \ 374 jexec dst ping -c 1 192.0.2.1 375 376 jexec rtr pfctl -e 377 pft_set_rules rtr \ 378 "set reassemble yes" \ 379 "set state-policy if-bound" \ 380 "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from { 192.0.2.1, 192.0.2.3, 192.0.2.4 } round-robin" 381 382 # Use pf to count sources 383 jexec dst pfctl -e 384 pft_set_rules dst \ 385 "pass" 386 387 atf_check -s exit:0 -o ignore \ 388 ping6 -c 1 64:ff9b::192.0.2.2 389 atf_check -s exit:0 -o ignore \ 390 ping6 -c 1 64:ff9b::192.0.2.2 391 atf_check -s exit:0 -o ignore \ 392 ping6 -c 1 64:ff9b::192.0.2.2 393 394 # Verify on dst that we saw different source addresses 395 atf_check -s exit:0 -o match:".*192.0.2.1.*" \ 396 jexec dst pfctl -ss 397 atf_check -s exit:0 -o match:".*192.0.2.3.*" \ 398 jexec dst pfctl -ss 399 atf_check -s exit:0 -o match:".*192.0.2.4.*" \ 400 jexec dst pfctl -ss 401} 402 403pool_cleanup() 404{ 405 pft_cleanup 406} 407 408 409atf_test_case "table" 410table_head() 411{ 412 atf_set descr 'Tables require round-robin' 413 atf_set require.user root 414} 415 416table_body() 417{ 418 pft_init 419 420 echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from <wanaddr>" | \ 421 atf_check -s exit:1 \ 422 -e match:"tables are only supported in round-robin pools" \ 423 pfctl -f - 424} 425 426table_cleanup() 427{ 428 pft_cleanup 429} 430 431atf_test_case "table_range" "cleanup" 432table_range_head() 433{ 434 atf_set descr 'Test using an address range within a table for the IPv4 side' 435 atf_set require.user root 436} 437 438table_range_body() 439{ 440 pft_init 441 442 epair_link=$(vnet_mkepair) 443 epair=$(vnet_mkepair) 444 445 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 446 route -6 add default 2001:db8::1 447 448 vnet_mkjail rtr ${epair}b ${epair_link}a 449 jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 450 jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up 451 jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up 452 453 vnet_mkjail dst ${epair_link}b 454 jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up 455 jexec dst route add default 192.0.2.2 456 457 # Sanity checks 458 atf_check -s exit:0 -o ignore \ 459 ping6 -c 1 2001:db8::1 460 atf_check -s exit:0 -o ignore \ 461 jexec dst ping -c 1 192.0.2.2 462 463 jexec rtr pfctl -e 464 pft_set_rules rtr \ 465 "set reassemble yes" \ 466 "set state-policy if-bound" \ 467 "table <wanaddrs> { 192.0.2.2/31 }" \ 468 "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from <wanaddrs> round-robin" 469 470 # Use pf to count sources 471 jexec dst pfctl -e 472 pft_set_rules dst \ 473 "pass" 474 475 atf_check -s exit:0 -o ignore \ 476 ping6 -c 1 64:ff9b::192.0.2.254 477 atf_check -s exit:0 -o ignore \ 478 ping6 -c 1 64:ff9b::192.0.2.254 479 480 # Verify on dst that we saw different source addresses 481 atf_check -s exit:0 -o match:".*192.0.2.2.*" \ 482 jexec dst pfctl -ss 483 atf_check -s exit:0 -o match:".*192.0.2.3.*" \ 484 jexec dst pfctl -ss 485} 486 487table_range_cleanup() 488{ 489 pft_cleanup 490} 491 492atf_test_case "table_round_robin" "cleanup" 493table_round_robin_head() 494{ 495 atf_set descr 'Use a table of IPv4 addresses in round-robin mode' 496 atf_set require.user root 497} 498 499table_round_robin_body() 500{ 501 pft_init 502 503 epair_link=$(vnet_mkepair) 504 epair=$(vnet_mkepair) 505 506 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 507 route -6 add default 2001:db8::1 508 509 vnet_mkjail rtr ${epair}b ${epair_link}a 510 jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 511 jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up 512 jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up 513 jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up 514 515 vnet_mkjail dst ${epair_link}b 516 jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up 517 jexec dst route add default 192.0.2.1 518 519 # Sanity checks 520 atf_check -s exit:0 -o ignore \ 521 ping6 -c 1 2001:db8::1 522 atf_check -s exit:0 -o ignore \ 523 jexec dst ping -c 1 192.0.2.1 524 525 jexec rtr pfctl -e 526 pft_set_rules rtr \ 527 "set reassemble yes" \ 528 "set state-policy if-bound" \ 529 "table <wanaddrs> { 192.0.2.1, 192.0.2.3, 192.0.2.4 }" \ 530 "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from <wanaddrs> round-robin" 531 532 # Use pf to count sources 533 jexec dst pfctl -e 534 pft_set_rules dst \ 535 "pass" 536 537 atf_check -s exit:0 -o ignore \ 538 ping6 -c 1 64:ff9b::192.0.2.2 539 atf_check -s exit:0 -o ignore \ 540 ping6 -c 1 64:ff9b::192.0.2.2 541 atf_check -s exit:0 -o ignore \ 542 ping6 -c 1 64:ff9b::192.0.2.2 543 544 # Verify on dst that we saw different source addresses 545 atf_check -s exit:0 -o match:".*192.0.2.1.*" \ 546 jexec dst pfctl -ss 547 atf_check -s exit:0 -o match:".*192.0.2.3.*" \ 548 jexec dst pfctl -ss 549 atf_check -s exit:0 -o match:".*192.0.2.4.*" \ 550 jexec dst pfctl -ss 551} 552 553table_round_robin_cleanup() 554{ 555 pft_cleanup 556} 557 558atf_test_case "dummynet" "cleanup" 559dummynet_head() 560{ 561 atf_set descr 'Test dummynet on af-to rules' 562 atf_set require.user root 563} 564 565dummynet_body() 566{ 567 pft_init 568 dummynet_init 569 570 epair_link=$(vnet_mkepair) 571 epair=$(vnet_mkepair) 572 573 ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad 574 route -6 add default 2001:db8::1 575 576 vnet_mkjail rtr ${epair}b ${epair_link}a 577 jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad 578 jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up 579 580 vnet_mkjail dst ${epair_link}b 581 jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up 582 jexec dst route add default 192.0.2.1 583 584 # Sanity checks 585 atf_check -s exit:0 -o ignore \ 586 ping6 -c 1 2001:db8::1 587 atf_check -s exit:0 -o ignore \ 588 jexec dst ping -c 1 192.0.2.1 589 590 jexec rtr pfctl -e 591 jexec rtr dnctl pipe 1 config delay 600 592 pft_set_rules rtr \ 593 "set reassemble yes" \ 594 "set state-policy if-bound" \ 595 "pass in on ${epair}b inet6 from any to 64:ff9b::/96 dnpipe 1 af-to inet from (${epair_link}a)" 596 597 # The ping request will pass, but take 1.2 seconds (.6 in, .6 out) 598 # So this works: 599 atf_check -s exit:0 -o ignore \ 600 ping6 -c 1 -t 2 64:ff9b::192.0.2.2 601 602 # But this times out: 603 atf_check -s exit:2 -o ignore \ 604 ping6 -c 1 -t 1 64:ff9b::192.0.2.2 605} 606 607dummynet_cleanup() 608{ 609 pft_cleanup 610} 611 612atf_init_test_cases() 613{ 614 atf_add_test_case "icmp_echo" 615 atf_add_test_case "fragmentation" 616 atf_add_test_case "tcp" 617 atf_add_test_case "udp" 618 atf_add_test_case "sctp" 619 atf_add_test_case "tos" 620 atf_add_test_case "no_v4" 621 atf_add_test_case "range" 622 atf_add_test_case "pool" 623 atf_add_test_case "table" 624 atf_add_test_case "table_range" 625 atf_add_test_case "table_round_robin" 626 atf_add_test_case "dummynet" 627} 628