1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3 4############################################################################## 5# Defines 6 7# Can be overridden by the configuration file. 8PING=${PING:=ping} 9PING6=${PING6:=ping6} 10MZ=${MZ:=mausezahn} 11WAIT_TIME=${WAIT_TIME:=5} 12PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no} 13PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no} 14NETIF_TYPE=${NETIF_TYPE:=veth} 15NETIF_CREATE=${NETIF_CREATE:=yes} 16 17if [[ -f forwarding.config ]]; then 18 source forwarding.config 19fi 20 21############################################################################## 22# Sanity checks 23 24check_tc_version() 25{ 26 tc -j &> /dev/null 27 if [[ $? -ne 0 ]]; then 28 echo "SKIP: iproute2 too old; tc is missing JSON support" 29 exit 1 30 fi 31 32 tc filter help 2>&1 | grep block &> /dev/null 33 if [[ $? -ne 0 ]]; then 34 echo "SKIP: iproute2 too old; tc is missing shared block support" 35 exit 1 36 fi 37} 38 39if [[ "$(id -u)" -ne 0 ]]; then 40 echo "SKIP: need root privileges" 41 exit 0 42fi 43 44if [[ "$CHECK_TC" = "yes" ]]; then 45 check_tc_version 46fi 47 48if [[ ! -x "$(command -v jq)" ]]; then 49 echo "SKIP: jq not installed" 50 exit 1 51fi 52 53if [[ ! -x "$(command -v $MZ)" ]]; then 54 echo "SKIP: $MZ not installed" 55 exit 1 56fi 57 58if [[ ! -v NUM_NETIFS ]]; then 59 echo "SKIP: importer does not define \"NUM_NETIFS\"" 60 exit 1 61fi 62 63############################################################################## 64# Command line options handling 65 66count=0 67 68while [[ $# -gt 0 ]]; do 69 if [[ "$count" -eq "0" ]]; then 70 unset NETIFS 71 declare -A NETIFS 72 fi 73 count=$((count + 1)) 74 NETIFS[p$count]="$1" 75 shift 76done 77 78############################################################################## 79# Network interfaces configuration 80 81create_netif_veth() 82{ 83 local i 84 85 for i in $(eval echo {1..$NUM_NETIFS}); do 86 local j=$((i+1)) 87 88 ip link show dev ${NETIFS[p$i]} &> /dev/null 89 if [[ $? -ne 0 ]]; then 90 ip link add ${NETIFS[p$i]} type veth \ 91 peer name ${NETIFS[p$j]} 92 if [[ $? -ne 0 ]]; then 93 echo "Failed to create netif" 94 exit 1 95 fi 96 fi 97 i=$j 98 done 99} 100 101create_netif() 102{ 103 case "$NETIF_TYPE" in 104 veth) create_netif_veth 105 ;; 106 *) echo "Can not create interfaces of type \'$NETIF_TYPE\'" 107 exit 1 108 ;; 109 esac 110} 111 112if [[ "$NETIF_CREATE" = "yes" ]]; then 113 create_netif 114fi 115 116for i in $(eval echo {1..$NUM_NETIFS}); do 117 ip link show dev ${NETIFS[p$i]} &> /dev/null 118 if [[ $? -ne 0 ]]; then 119 echo "SKIP: could not find all required interfaces" 120 exit 1 121 fi 122done 123 124############################################################################## 125# Helpers 126 127# Exit status to return at the end. Set in case one of the tests fails. 128EXIT_STATUS=0 129# Per-test return value. Clear at the beginning of each test. 130RET=0 131 132check_err() 133{ 134 local err=$1 135 local msg=$2 136 137 if [[ $RET -eq 0 && $err -ne 0 ]]; then 138 RET=$err 139 retmsg=$msg 140 fi 141} 142 143check_fail() 144{ 145 local err=$1 146 local msg=$2 147 148 if [[ $RET -eq 0 && $err -eq 0 ]]; then 149 RET=1 150 retmsg=$msg 151 fi 152} 153 154log_test() 155{ 156 local test_name=$1 157 local opt_str=$2 158 159 if [[ $# -eq 2 ]]; then 160 opt_str="($opt_str)" 161 fi 162 163 if [[ $RET -ne 0 ]]; then 164 EXIT_STATUS=1 165 printf "TEST: %-60s [FAIL]\n" "$test_name $opt_str" 166 if [[ ! -z "$retmsg" ]]; then 167 printf "\t%s\n" "$retmsg" 168 fi 169 if [ "${PAUSE_ON_FAIL}" = "yes" ]; then 170 echo "Hit enter to continue, 'q' to quit" 171 read a 172 [ "$a" = "q" ] && exit 1 173 fi 174 return 1 175 fi 176 177 printf "TEST: %-60s [PASS]\n" "$test_name $opt_str" 178 return 0 179} 180 181log_info() 182{ 183 local msg=$1 184 185 echo "INFO: $msg" 186} 187 188setup_wait() 189{ 190 for i in $(eval echo {1..$NUM_NETIFS}); do 191 while true; do 192 ip link show dev ${NETIFS[p$i]} up \ 193 | grep 'state UP' &> /dev/null 194 if [[ $? -ne 0 ]]; then 195 sleep 1 196 else 197 break 198 fi 199 done 200 done 201 202 # Make sure links are ready. 203 sleep $WAIT_TIME 204} 205 206pre_cleanup() 207{ 208 if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then 209 echo "Pausing before cleanup, hit any key to continue" 210 read 211 fi 212} 213 214vrf_prepare() 215{ 216 ip -4 rule add pref 32765 table local 217 ip -4 rule del pref 0 218 ip -6 rule add pref 32765 table local 219 ip -6 rule del pref 0 220} 221 222vrf_cleanup() 223{ 224 ip -6 rule add pref 0 table local 225 ip -6 rule del pref 32765 226 ip -4 rule add pref 0 table local 227 ip -4 rule del pref 32765 228} 229 230__last_tb_id=0 231declare -A __TB_IDS 232 233__vrf_td_id_assign() 234{ 235 local vrf_name=$1 236 237 __last_tb_id=$((__last_tb_id + 1)) 238 __TB_IDS[$vrf_name]=$__last_tb_id 239 return $__last_tb_id 240} 241 242__vrf_td_id_lookup() 243{ 244 local vrf_name=$1 245 246 return ${__TB_IDS[$vrf_name]} 247} 248 249vrf_create() 250{ 251 local vrf_name=$1 252 local tb_id 253 254 __vrf_td_id_assign $vrf_name 255 tb_id=$? 256 257 ip link add dev $vrf_name type vrf table $tb_id 258 ip -4 route add table $tb_id unreachable default metric 4278198272 259 ip -6 route add table $tb_id unreachable default metric 4278198272 260} 261 262vrf_destroy() 263{ 264 local vrf_name=$1 265 local tb_id 266 267 __vrf_td_id_lookup $vrf_name 268 tb_id=$? 269 270 ip -6 route del table $tb_id unreachable default metric 4278198272 271 ip -4 route del table $tb_id unreachable default metric 4278198272 272 ip link del dev $vrf_name 273} 274 275__addr_add_del() 276{ 277 local if_name=$1 278 local add_del=$2 279 local array 280 281 shift 282 shift 283 array=("${@}") 284 285 for addrstr in "${array[@]}"; do 286 ip address $add_del $addrstr dev $if_name 287 done 288} 289 290simple_if_init() 291{ 292 local if_name=$1 293 local vrf_name 294 local array 295 296 shift 297 vrf_name=v$if_name 298 array=("${@}") 299 300 vrf_create $vrf_name 301 ip link set dev $if_name master $vrf_name 302 ip link set dev $vrf_name up 303 ip link set dev $if_name up 304 305 __addr_add_del $if_name add "${array[@]}" 306} 307 308simple_if_fini() 309{ 310 local if_name=$1 311 local vrf_name 312 local array 313 314 shift 315 vrf_name=v$if_name 316 array=("${@}") 317 318 __addr_add_del $if_name del "${array[@]}" 319 320 ip link set dev $if_name down 321 vrf_destroy $vrf_name 322} 323 324tunnel_create() 325{ 326 local name=$1; shift 327 local type=$1; shift 328 local local=$1; shift 329 local remote=$1; shift 330 331 ip link add name $name type $type \ 332 local $local remote $remote "$@" 333 ip link set dev $name up 334} 335 336tunnel_destroy() 337{ 338 local name=$1; shift 339 340 ip link del dev $name 341} 342 343vlan_create() 344{ 345 local if_name=$1; shift 346 local vid=$1; shift 347 local vrf=$1; shift 348 local ips=("${@}") 349 local name=$if_name.$vid 350 351 ip link add name $name link $if_name type vlan id $vid 352 if [ "$vrf" != "" ]; then 353 ip link set dev $name master $vrf 354 fi 355 ip link set dev $name up 356 __addr_add_del $name add "${ips[@]}" 357} 358 359vlan_destroy() 360{ 361 local if_name=$1; shift 362 local vid=$1; shift 363 local name=$if_name.$vid 364 365 ip link del dev $name 366} 367 368master_name_get() 369{ 370 local if_name=$1 371 372 ip -j link show dev $if_name | jq -r '.[]["master"]' 373} 374 375link_stats_tx_packets_get() 376{ 377 local if_name=$1 378 379 ip -j -s link show dev $if_name | jq '.[]["stats64"]["tx"]["packets"]' 380} 381 382tc_rule_stats_get() 383{ 384 local dev=$1; shift 385 local pref=$1; shift 386 387 tc -j -s filter show dev $dev ingress pref $pref | 388 jq '.[1].options.actions[].stats.packets' 389} 390 391mac_get() 392{ 393 local if_name=$1 394 395 ip -j link show dev $if_name | jq -r '.[]["address"]' 396} 397 398bridge_ageing_time_get() 399{ 400 local bridge=$1 401 local ageing_time 402 403 # Need to divide by 100 to convert to seconds. 404 ageing_time=$(ip -j -d link show dev $bridge \ 405 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]') 406 echo $((ageing_time / 100)) 407} 408 409declare -A SYSCTL_ORIG 410sysctl_set() 411{ 412 local key=$1; shift 413 local value=$1; shift 414 415 SYSCTL_ORIG[$key]=$(sysctl -n $key) 416 sysctl -qw $key=$value 417} 418 419sysctl_restore() 420{ 421 local key=$1; shift 422 423 sysctl -qw $key=${SYSCTL_ORIG["$key"]} 424} 425 426forwarding_enable() 427{ 428 sysctl_set net.ipv4.conf.all.forwarding 1 429 sysctl_set net.ipv6.conf.all.forwarding 1 430} 431 432forwarding_restore() 433{ 434 sysctl_restore net.ipv6.conf.all.forwarding 435 sysctl_restore net.ipv4.conf.all.forwarding 436} 437 438tc_offload_check() 439{ 440 for i in $(eval echo {1..$NUM_NETIFS}); do 441 ethtool -k ${NETIFS[p$i]} \ 442 | grep "hw-tc-offload: on" &> /dev/null 443 if [[ $? -ne 0 ]]; then 444 return 1 445 fi 446 done 447 448 return 0 449} 450 451trap_install() 452{ 453 local dev=$1; shift 454 local direction=$1; shift 455 456 # For slow-path testing, we need to install a trap to get to 457 # slow path the packets that would otherwise be switched in HW. 458 tc filter add dev $dev $direction pref 1 flower skip_sw action trap 459} 460 461trap_uninstall() 462{ 463 local dev=$1; shift 464 local direction=$1; shift 465 466 tc filter del dev $dev $direction pref 1 flower skip_sw 467} 468 469slow_path_trap_install() 470{ 471 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 472 trap_install "$@" 473 fi 474} 475 476slow_path_trap_uninstall() 477{ 478 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 479 trap_uninstall "$@" 480 fi 481} 482 483__icmp_capture_add_del() 484{ 485 local add_del=$1; shift 486 local pref=$1; shift 487 local vsuf=$1; shift 488 local tundev=$1; shift 489 local filter=$1; shift 490 491 tc filter $add_del dev "$tundev" ingress \ 492 proto ip$vsuf pref $pref \ 493 flower ip_proto icmp$vsuf $filter \ 494 action pass 495} 496 497icmp_capture_install() 498{ 499 __icmp_capture_add_del add 100 "" "$@" 500} 501 502icmp_capture_uninstall() 503{ 504 __icmp_capture_add_del del 100 "" "$@" 505} 506 507icmp6_capture_install() 508{ 509 __icmp_capture_add_del add 100 v6 "$@" 510} 511 512icmp6_capture_uninstall() 513{ 514 __icmp_capture_add_del del 100 v6 "$@" 515} 516 517__vlan_capture_add_del() 518{ 519 local add_del=$1; shift 520 local pref=$1; shift 521 local dev=$1; shift 522 local filter=$1; shift 523 524 tc filter $add_del dev "$dev" ingress \ 525 proto 802.1q pref $pref \ 526 flower $filter \ 527 action pass 528} 529 530vlan_capture_install() 531{ 532 __vlan_capture_add_del add 100 "$@" 533} 534 535vlan_capture_uninstall() 536{ 537 __vlan_capture_add_del del 100 "$@" 538} 539 540matchall_sink_create() 541{ 542 local dev=$1; shift 543 544 tc qdisc add dev $dev clsact 545 tc filter add dev $dev ingress \ 546 pref 10000 \ 547 matchall \ 548 action drop 549} 550 551tests_run() 552{ 553 local current_test 554 555 for current_test in ${TESTS:-$ALL_TESTS}; do 556 $current_test 557 done 558} 559 560############################################################################## 561# Tests 562 563ping_test() 564{ 565 local if_name=$1 566 local dip=$2 567 local vrf_name 568 569 RET=0 570 571 vrf_name=$(master_name_get $if_name) 572 ip vrf exec $vrf_name $PING $dip -c 10 -i 0.1 -w 2 &> /dev/null 573 check_err $? 574 log_test "ping" 575} 576 577ping6_test() 578{ 579 local if_name=$1 580 local dip=$2 581 local vrf_name 582 583 RET=0 584 585 vrf_name=$(master_name_get $if_name) 586 ip vrf exec $vrf_name $PING6 $dip -c 10 -i 0.1 -w 2 &> /dev/null 587 check_err $? 588 log_test "ping6" 589} 590 591learning_test() 592{ 593 local bridge=$1 594 local br_port1=$2 # Connected to `host1_if`. 595 local host1_if=$3 596 local host2_if=$4 597 local mac=de:ad:be:ef:13:37 598 local ageing_time 599 600 RET=0 601 602 bridge -j fdb show br $bridge brport $br_port1 \ 603 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 604 check_fail $? "Found FDB record when should not" 605 606 # Disable unknown unicast flooding on `br_port1` to make sure 607 # packets are only forwarded through the port after a matching 608 # FDB entry was installed. 609 bridge link set dev $br_port1 flood off 610 611 tc qdisc add dev $host1_if ingress 612 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \ 613 flower dst_mac $mac action drop 614 615 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 616 sleep 1 617 618 tc -j -s filter show dev $host1_if ingress \ 619 | jq -e ".[] | select(.options.handle == 101) \ 620 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 621 check_fail $? "Packet reached second host when should not" 622 623 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 624 sleep 1 625 626 bridge -j fdb show br $bridge brport $br_port1 \ 627 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 628 check_err $? "Did not find FDB record when should" 629 630 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 631 sleep 1 632 633 tc -j -s filter show dev $host1_if ingress \ 634 | jq -e ".[] | select(.options.handle == 101) \ 635 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 636 check_err $? "Packet did not reach second host when should" 637 638 # Wait for 10 seconds after the ageing time to make sure FDB 639 # record was aged-out. 640 ageing_time=$(bridge_ageing_time_get $bridge) 641 sleep $((ageing_time + 10)) 642 643 bridge -j fdb show br $bridge brport $br_port1 \ 644 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 645 check_fail $? "Found FDB record when should not" 646 647 bridge link set dev $br_port1 learning off 648 649 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 650 sleep 1 651 652 bridge -j fdb show br $bridge brport $br_port1 \ 653 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 654 check_fail $? "Found FDB record when should not" 655 656 bridge link set dev $br_port1 learning on 657 658 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower 659 tc qdisc del dev $host1_if ingress 660 661 bridge link set dev $br_port1 flood on 662 663 log_test "FDB learning" 664} 665 666flood_test_do() 667{ 668 local should_flood=$1 669 local mac=$2 670 local ip=$3 671 local host1_if=$4 672 local host2_if=$5 673 local err=0 674 675 # Add an ACL on `host2_if` which will tell us whether the packet 676 # was flooded to it or not. 677 tc qdisc add dev $host2_if ingress 678 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \ 679 flower dst_mac $mac action drop 680 681 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q 682 sleep 1 683 684 tc -j -s filter show dev $host2_if ingress \ 685 | jq -e ".[] | select(.options.handle == 101) \ 686 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 687 if [[ $? -ne 0 && $should_flood == "true" || \ 688 $? -eq 0 && $should_flood == "false" ]]; then 689 err=1 690 fi 691 692 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower 693 tc qdisc del dev $host2_if ingress 694 695 return $err 696} 697 698flood_unicast_test() 699{ 700 local br_port=$1 701 local host1_if=$2 702 local host2_if=$3 703 local mac=de:ad:be:ef:13:37 704 local ip=192.0.2.100 705 706 RET=0 707 708 bridge link set dev $br_port flood off 709 710 flood_test_do false $mac $ip $host1_if $host2_if 711 check_err $? "Packet flooded when should not" 712 713 bridge link set dev $br_port flood on 714 715 flood_test_do true $mac $ip $host1_if $host2_if 716 check_err $? "Packet was not flooded when should" 717 718 log_test "Unknown unicast flood" 719} 720 721flood_multicast_test() 722{ 723 local br_port=$1 724 local host1_if=$2 725 local host2_if=$3 726 local mac=01:00:5e:00:00:01 727 local ip=239.0.0.1 728 729 RET=0 730 731 bridge link set dev $br_port mcast_flood off 732 733 flood_test_do false $mac $ip $host1_if $host2_if 734 check_err $? "Packet flooded when should not" 735 736 bridge link set dev $br_port mcast_flood on 737 738 flood_test_do true $mac $ip $host1_if $host2_if 739 check_err $? "Packet was not flooded when should" 740 741 log_test "Unregistered multicast flood" 742} 743 744flood_test() 745{ 746 # `br_port` is connected to `host2_if` 747 local br_port=$1 748 local host1_if=$2 749 local host2_if=$3 750 751 flood_unicast_test $br_port $host1_if $host2_if 752 flood_multicast_test $br_port $host1_if $host2_if 753} 754