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 290__simple_if_init() 291{ 292 local if_name=$1; shift 293 local vrf_name=$1; shift 294 local addrs=("${@}") 295 296 ip link set dev $if_name master $vrf_name 297 ip link set dev $if_name up 298 299 __addr_add_del $if_name add "${addrs[@]}" 300} 301 302__simple_if_fini() 303{ 304 local if_name=$1; shift 305 local addrs=("${@}") 306 307 __addr_add_del $if_name del "${addrs[@]}" 308 309 ip link set dev $if_name down 310 ip link set dev $if_name nomaster 311} 312 313simple_if_init() 314{ 315 local if_name=$1 316 local vrf_name 317 local array 318 319 shift 320 vrf_name=v$if_name 321 array=("${@}") 322 323 vrf_create $vrf_name 324 ip link set dev $vrf_name up 325 __simple_if_init $if_name $vrf_name "${array[@]}" 326} 327 328simple_if_fini() 329{ 330 local if_name=$1 331 local vrf_name 332 local array 333 334 shift 335 vrf_name=v$if_name 336 array=("${@}") 337 338 __simple_if_fini $if_name "${array[@]}" 339 vrf_destroy $vrf_name 340} 341 342tunnel_create() 343{ 344 local name=$1; shift 345 local type=$1; shift 346 local local=$1; shift 347 local remote=$1; shift 348 349 ip link add name $name type $type \ 350 local $local remote $remote "$@" 351 ip link set dev $name up 352} 353 354tunnel_destroy() 355{ 356 local name=$1; shift 357 358 ip link del dev $name 359} 360 361vlan_create() 362{ 363 local if_name=$1; shift 364 local vid=$1; shift 365 local vrf=$1; shift 366 local ips=("${@}") 367 local name=$if_name.$vid 368 369 ip link add name $name link $if_name type vlan id $vid 370 if [ "$vrf" != "" ]; then 371 ip link set dev $name master $vrf 372 fi 373 ip link set dev $name up 374 __addr_add_del $name add "${ips[@]}" 375} 376 377vlan_destroy() 378{ 379 local if_name=$1; shift 380 local vid=$1; shift 381 local name=$if_name.$vid 382 383 ip link del dev $name 384} 385 386master_name_get() 387{ 388 local if_name=$1 389 390 ip -j link show dev $if_name | jq -r '.[]["master"]' 391} 392 393link_stats_tx_packets_get() 394{ 395 local if_name=$1 396 397 ip -j -s link show dev $if_name | jq '.[]["stats64"]["tx"]["packets"]' 398} 399 400tc_rule_stats_get() 401{ 402 local dev=$1; shift 403 local pref=$1; shift 404 local dir=$1; shift 405 406 tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \ 407 | jq '.[1].options.actions[].stats.packets' 408} 409 410mac_get() 411{ 412 local if_name=$1 413 414 ip -j link show dev $if_name | jq -r '.[]["address"]' 415} 416 417bridge_ageing_time_get() 418{ 419 local bridge=$1 420 local ageing_time 421 422 # Need to divide by 100 to convert to seconds. 423 ageing_time=$(ip -j -d link show dev $bridge \ 424 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]') 425 echo $((ageing_time / 100)) 426} 427 428declare -A SYSCTL_ORIG 429sysctl_set() 430{ 431 local key=$1; shift 432 local value=$1; shift 433 434 SYSCTL_ORIG[$key]=$(sysctl -n $key) 435 sysctl -qw $key=$value 436} 437 438sysctl_restore() 439{ 440 local key=$1; shift 441 442 sysctl -qw $key=${SYSCTL_ORIG["$key"]} 443} 444 445forwarding_enable() 446{ 447 sysctl_set net.ipv4.conf.all.forwarding 1 448 sysctl_set net.ipv6.conf.all.forwarding 1 449} 450 451forwarding_restore() 452{ 453 sysctl_restore net.ipv6.conf.all.forwarding 454 sysctl_restore net.ipv4.conf.all.forwarding 455} 456 457tc_offload_check() 458{ 459 for i in $(eval echo {1..$NUM_NETIFS}); do 460 ethtool -k ${NETIFS[p$i]} \ 461 | grep "hw-tc-offload: on" &> /dev/null 462 if [[ $? -ne 0 ]]; then 463 return 1 464 fi 465 done 466 467 return 0 468} 469 470trap_install() 471{ 472 local dev=$1; shift 473 local direction=$1; shift 474 475 # For slow-path testing, we need to install a trap to get to 476 # slow path the packets that would otherwise be switched in HW. 477 tc filter add dev $dev $direction pref 1 flower skip_sw action trap 478} 479 480trap_uninstall() 481{ 482 local dev=$1; shift 483 local direction=$1; shift 484 485 tc filter del dev $dev $direction pref 1 flower skip_sw 486} 487 488slow_path_trap_install() 489{ 490 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 491 trap_install "$@" 492 fi 493} 494 495slow_path_trap_uninstall() 496{ 497 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 498 trap_uninstall "$@" 499 fi 500} 501 502__icmp_capture_add_del() 503{ 504 local add_del=$1; shift 505 local pref=$1; shift 506 local vsuf=$1; shift 507 local tundev=$1; shift 508 local filter=$1; shift 509 510 tc filter $add_del dev "$tundev" ingress \ 511 proto ip$vsuf pref $pref \ 512 flower ip_proto icmp$vsuf $filter \ 513 action pass 514} 515 516icmp_capture_install() 517{ 518 __icmp_capture_add_del add 100 "" "$@" 519} 520 521icmp_capture_uninstall() 522{ 523 __icmp_capture_add_del del 100 "" "$@" 524} 525 526icmp6_capture_install() 527{ 528 __icmp_capture_add_del add 100 v6 "$@" 529} 530 531icmp6_capture_uninstall() 532{ 533 __icmp_capture_add_del del 100 v6 "$@" 534} 535 536__vlan_capture_add_del() 537{ 538 local add_del=$1; shift 539 local pref=$1; shift 540 local dev=$1; shift 541 local filter=$1; shift 542 543 tc filter $add_del dev "$dev" ingress \ 544 proto 802.1q pref $pref \ 545 flower $filter \ 546 action pass 547} 548 549vlan_capture_install() 550{ 551 __vlan_capture_add_del add 100 "$@" 552} 553 554vlan_capture_uninstall() 555{ 556 __vlan_capture_add_del del 100 "$@" 557} 558 559matchall_sink_create() 560{ 561 local dev=$1; shift 562 563 tc qdisc add dev $dev clsact 564 tc filter add dev $dev ingress \ 565 pref 10000 \ 566 matchall \ 567 action drop 568} 569 570tests_run() 571{ 572 local current_test 573 574 for current_test in ${TESTS:-$ALL_TESTS}; do 575 $current_test 576 done 577} 578 579multipath_eval() 580{ 581 local desc="$1" 582 local weight_rp12=$2 583 local weight_rp13=$3 584 local packets_rp12=$4 585 local packets_rp13=$5 586 local weights_ratio packets_ratio diff 587 588 RET=0 589 590 if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then 591 weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \ 592 | bc -l) 593 else 594 weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \ 595 | bc -l) 596 fi 597 598 if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then 599 check_err 1 "Packet difference is 0" 600 log_test "Multipath" 601 log_info "Expected ratio $weights_ratio" 602 return 603 fi 604 605 if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then 606 packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \ 607 | bc -l) 608 else 609 packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \ 610 | bc -l) 611 fi 612 613 diff=$(echo $weights_ratio - $packets_ratio | bc -l) 614 diff=${diff#-} 615 616 test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0 617 check_err $? "Too large discrepancy between expected and measured ratios" 618 log_test "$desc" 619 log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio" 620} 621 622############################################################################## 623# Tests 624 625ping_test() 626{ 627 local if_name=$1 628 local dip=$2 629 local vrf_name 630 631 RET=0 632 633 vrf_name=$(master_name_get $if_name) 634 ip vrf exec $vrf_name $PING $dip -c 10 -i 0.1 -w 2 &> /dev/null 635 check_err $? 636 log_test "ping" 637} 638 639ping6_test() 640{ 641 local if_name=$1 642 local dip=$2 643 local vrf_name 644 645 RET=0 646 647 vrf_name=$(master_name_get $if_name) 648 ip vrf exec $vrf_name $PING6 $dip -c 10 -i 0.1 -w 2 &> /dev/null 649 check_err $? 650 log_test "ping6" 651} 652 653learning_test() 654{ 655 local bridge=$1 656 local br_port1=$2 # Connected to `host1_if`. 657 local host1_if=$3 658 local host2_if=$4 659 local mac=de:ad:be:ef:13:37 660 local ageing_time 661 662 RET=0 663 664 bridge -j fdb show br $bridge brport $br_port1 \ 665 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 666 check_fail $? "Found FDB record when should not" 667 668 # Disable unknown unicast flooding on `br_port1` to make sure 669 # packets are only forwarded through the port after a matching 670 # FDB entry was installed. 671 bridge link set dev $br_port1 flood off 672 673 tc qdisc add dev $host1_if ingress 674 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \ 675 flower dst_mac $mac action drop 676 677 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 678 sleep 1 679 680 tc -j -s filter show dev $host1_if ingress \ 681 | jq -e ".[] | select(.options.handle == 101) \ 682 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 683 check_fail $? "Packet reached second host when should not" 684 685 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 686 sleep 1 687 688 bridge -j fdb show br $bridge brport $br_port1 \ 689 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 690 check_err $? "Did not find FDB record when should" 691 692 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 693 sleep 1 694 695 tc -j -s filter show dev $host1_if ingress \ 696 | jq -e ".[] | select(.options.handle == 101) \ 697 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 698 check_err $? "Packet did not reach second host when should" 699 700 # Wait for 10 seconds after the ageing time to make sure FDB 701 # record was aged-out. 702 ageing_time=$(bridge_ageing_time_get $bridge) 703 sleep $((ageing_time + 10)) 704 705 bridge -j fdb show br $bridge brport $br_port1 \ 706 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 707 check_fail $? "Found FDB record when should not" 708 709 bridge link set dev $br_port1 learning off 710 711 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 712 sleep 1 713 714 bridge -j fdb show br $bridge brport $br_port1 \ 715 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 716 check_fail $? "Found FDB record when should not" 717 718 bridge link set dev $br_port1 learning on 719 720 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower 721 tc qdisc del dev $host1_if ingress 722 723 bridge link set dev $br_port1 flood on 724 725 log_test "FDB learning" 726} 727 728flood_test_do() 729{ 730 local should_flood=$1 731 local mac=$2 732 local ip=$3 733 local host1_if=$4 734 local host2_if=$5 735 local err=0 736 737 # Add an ACL on `host2_if` which will tell us whether the packet 738 # was flooded to it or not. 739 tc qdisc add dev $host2_if ingress 740 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \ 741 flower dst_mac $mac action drop 742 743 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q 744 sleep 1 745 746 tc -j -s filter show dev $host2_if ingress \ 747 | jq -e ".[] | select(.options.handle == 101) \ 748 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 749 if [[ $? -ne 0 && $should_flood == "true" || \ 750 $? -eq 0 && $should_flood == "false" ]]; then 751 err=1 752 fi 753 754 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower 755 tc qdisc del dev $host2_if ingress 756 757 return $err 758} 759 760flood_unicast_test() 761{ 762 local br_port=$1 763 local host1_if=$2 764 local host2_if=$3 765 local mac=de:ad:be:ef:13:37 766 local ip=192.0.2.100 767 768 RET=0 769 770 bridge link set dev $br_port flood off 771 772 flood_test_do false $mac $ip $host1_if $host2_if 773 check_err $? "Packet flooded when should not" 774 775 bridge link set dev $br_port flood on 776 777 flood_test_do true $mac $ip $host1_if $host2_if 778 check_err $? "Packet was not flooded when should" 779 780 log_test "Unknown unicast flood" 781} 782 783flood_multicast_test() 784{ 785 local br_port=$1 786 local host1_if=$2 787 local host2_if=$3 788 local mac=01:00:5e:00:00:01 789 local ip=239.0.0.1 790 791 RET=0 792 793 bridge link set dev $br_port mcast_flood off 794 795 flood_test_do false $mac $ip $host1_if $host2_if 796 check_err $? "Packet flooded when should not" 797 798 bridge link set dev $br_port mcast_flood on 799 800 flood_test_do true $mac $ip $host1_if $host2_if 801 check_err $? "Packet was not flooded when should" 802 803 log_test "Unregistered multicast flood" 804} 805 806flood_test() 807{ 808 # `br_port` is connected to `host2_if` 809 local br_port=$1 810 local host1_if=$2 811 local host2_if=$3 812 813 flood_unicast_test $br_port $host1_if $host2_if 814 flood_multicast_test $br_port $host1_if $host2_if 815} 816