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