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