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} 11ARPING=${ARPING:=arping} 12TEAMD=${TEAMD:=teamd} 13WAIT_TIME=${WAIT_TIME:=5} 14PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no} 15PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no} 16NETIF_TYPE=${NETIF_TYPE:=veth} 17NETIF_CREATE=${NETIF_CREATE:=yes} 18MCD=${MCD:=smcrouted} 19MC_CLI=${MC_CLI:=smcroutectl} 20PING_TIMEOUT=${PING_TIMEOUT:=5} 21WAIT_TIMEOUT=${WAIT_TIMEOUT:=20} 22INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600} 23 24relative_path="${BASH_SOURCE%/*}" 25if [[ "$relative_path" == "${BASH_SOURCE}" ]]; then 26 relative_path="." 27fi 28 29if [[ -f $relative_path/forwarding.config ]]; then 30 source "$relative_path/forwarding.config" 31fi 32 33############################################################################## 34# Sanity checks 35 36check_tc_version() 37{ 38 tc -j &> /dev/null 39 if [[ $? -ne 0 ]]; then 40 echo "SKIP: iproute2 too old; tc is missing JSON support" 41 exit 1 42 fi 43} 44 45# Old versions of tc don't understand "mpls_uc" 46check_tc_mpls_support() 47{ 48 local dev=$1; shift 49 50 tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \ 51 matchall action pipe &> /dev/null 52 if [[ $? -ne 0 ]]; then 53 echo "SKIP: iproute2 too old; tc is missing MPLS support" 54 return 1 55 fi 56 tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \ 57 matchall 58} 59 60# Old versions of tc produce invalid json output for mpls lse statistics 61check_tc_mpls_lse_stats() 62{ 63 local dev=$1; shift 64 local ret; 65 66 tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \ 67 flower mpls lse depth 2 \ 68 action continue &> /dev/null 69 70 if [[ $? -ne 0 ]]; then 71 echo "SKIP: iproute2 too old; tc-flower is missing extended MPLS support" 72 return 1 73 fi 74 75 tc -j filter show dev $dev ingress protocol mpls_uc | jq . &> /dev/null 76 ret=$? 77 tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \ 78 flower 79 80 if [[ $ret -ne 0 ]]; then 81 echo "SKIP: iproute2 too old; tc-flower produces invalid json output for extended MPLS filters" 82 return 1 83 fi 84} 85 86check_tc_shblock_support() 87{ 88 tc filter help 2>&1 | grep block &> /dev/null 89 if [[ $? -ne 0 ]]; then 90 echo "SKIP: iproute2 too old; tc is missing shared block support" 91 exit 1 92 fi 93} 94 95check_tc_chain_support() 96{ 97 tc help 2>&1|grep chain &> /dev/null 98 if [[ $? -ne 0 ]]; then 99 echo "SKIP: iproute2 too old; tc is missing chain support" 100 exit 1 101 fi 102} 103 104check_tc_action_hw_stats_support() 105{ 106 tc actions help 2>&1 | grep -q hw_stats 107 if [[ $? -ne 0 ]]; then 108 echo "SKIP: iproute2 too old; tc is missing action hw_stats support" 109 exit 1 110 fi 111} 112 113check_ethtool_lanes_support() 114{ 115 ethtool --help 2>&1| grep lanes &> /dev/null 116 if [[ $? -ne 0 ]]; then 117 echo "SKIP: ethtool too old; it is missing lanes support" 118 exit 1 119 fi 120} 121 122if [[ "$(id -u)" -ne 0 ]]; then 123 echo "SKIP: need root privileges" 124 exit 0 125fi 126 127if [[ "$CHECK_TC" = "yes" ]]; then 128 check_tc_version 129fi 130 131require_command() 132{ 133 local cmd=$1; shift 134 135 if [[ ! -x "$(command -v "$cmd")" ]]; then 136 echo "SKIP: $cmd not installed" 137 exit 1 138 fi 139} 140 141require_command jq 142require_command $MZ 143 144if [[ ! -v NUM_NETIFS ]]; then 145 echo "SKIP: importer does not define \"NUM_NETIFS\"" 146 exit 1 147fi 148 149############################################################################## 150# Command line options handling 151 152count=0 153 154while [[ $# -gt 0 ]]; do 155 if [[ "$count" -eq "0" ]]; then 156 unset NETIFS 157 declare -A NETIFS 158 fi 159 count=$((count + 1)) 160 NETIFS[p$count]="$1" 161 shift 162done 163 164############################################################################## 165# Network interfaces configuration 166 167create_netif_veth() 168{ 169 local i 170 171 for ((i = 1; i <= NUM_NETIFS; ++i)); do 172 local j=$((i+1)) 173 174 ip link show dev ${NETIFS[p$i]} &> /dev/null 175 if [[ $? -ne 0 ]]; then 176 ip link add ${NETIFS[p$i]} type veth \ 177 peer name ${NETIFS[p$j]} 178 if [[ $? -ne 0 ]]; then 179 echo "Failed to create netif" 180 exit 1 181 fi 182 fi 183 i=$j 184 done 185} 186 187create_netif() 188{ 189 case "$NETIF_TYPE" in 190 veth) create_netif_veth 191 ;; 192 *) echo "Can not create interfaces of type \'$NETIF_TYPE\'" 193 exit 1 194 ;; 195 esac 196} 197 198if [[ "$NETIF_CREATE" = "yes" ]]; then 199 create_netif 200fi 201 202for ((i = 1; i <= NUM_NETIFS; ++i)); do 203 ip link show dev ${NETIFS[p$i]} &> /dev/null 204 if [[ $? -ne 0 ]]; then 205 echo "SKIP: could not find all required interfaces" 206 exit 1 207 fi 208done 209 210############################################################################## 211# Helpers 212 213# Exit status to return at the end. Set in case one of the tests fails. 214EXIT_STATUS=0 215# Per-test return value. Clear at the beginning of each test. 216RET=0 217 218check_err() 219{ 220 local err=$1 221 local msg=$2 222 223 if [[ $RET -eq 0 && $err -ne 0 ]]; then 224 RET=$err 225 retmsg=$msg 226 fi 227} 228 229check_fail() 230{ 231 local err=$1 232 local msg=$2 233 234 if [[ $RET -eq 0 && $err -eq 0 ]]; then 235 RET=1 236 retmsg=$msg 237 fi 238} 239 240check_err_fail() 241{ 242 local should_fail=$1; shift 243 local err=$1; shift 244 local what=$1; shift 245 246 if ((should_fail)); then 247 check_fail $err "$what succeeded, but should have failed" 248 else 249 check_err $err "$what failed" 250 fi 251} 252 253log_test() 254{ 255 local test_name=$1 256 local opt_str=$2 257 258 if [[ $# -eq 2 ]]; then 259 opt_str="($opt_str)" 260 fi 261 262 if [[ $RET -ne 0 ]]; then 263 EXIT_STATUS=1 264 printf "TEST: %-60s [FAIL]\n" "$test_name $opt_str" 265 if [[ ! -z "$retmsg" ]]; then 266 printf "\t%s\n" "$retmsg" 267 fi 268 if [ "${PAUSE_ON_FAIL}" = "yes" ]; then 269 echo "Hit enter to continue, 'q' to quit" 270 read a 271 [ "$a" = "q" ] && exit 1 272 fi 273 return 1 274 fi 275 276 printf "TEST: %-60s [ OK ]\n" "$test_name $opt_str" 277 return 0 278} 279 280log_info() 281{ 282 local msg=$1 283 284 echo "INFO: $msg" 285} 286 287busywait() 288{ 289 local timeout=$1; shift 290 291 local start_time="$(date -u +%s%3N)" 292 while true 293 do 294 local out 295 out=$("$@") 296 local ret=$? 297 if ((!ret)); then 298 echo -n "$out" 299 return 0 300 fi 301 302 local current_time="$(date -u +%s%3N)" 303 if ((current_time - start_time > timeout)); then 304 echo -n "$out" 305 return 1 306 fi 307 done 308} 309 310not() 311{ 312 "$@" 313 [[ $? != 0 ]] 314} 315 316get_max() 317{ 318 local arr=("$@") 319 320 max=${arr[0]} 321 for cur in ${arr[@]}; do 322 if [[ $cur -gt $max ]]; then 323 max=$cur 324 fi 325 done 326 327 echo $max 328} 329 330grep_bridge_fdb() 331{ 332 local addr=$1; shift 333 local word 334 local flag 335 336 if [ "$1" == "self" ] || [ "$1" == "master" ]; then 337 word=$1; shift 338 if [ "$1" == "-v" ]; then 339 flag=$1; shift 340 fi 341 fi 342 343 $@ | grep $addr | grep $flag "$word" 344} 345 346wait_for_port_up() 347{ 348 "$@" | grep -q "Link detected: yes" 349} 350 351wait_for_offload() 352{ 353 "$@" | grep -q offload 354} 355 356until_counter_is() 357{ 358 local expr=$1; shift 359 local current=$("$@") 360 361 echo $((current)) 362 ((current $expr)) 363} 364 365busywait_for_counter() 366{ 367 local timeout=$1; shift 368 local delta=$1; shift 369 370 local base=$("$@") 371 busywait "$timeout" until_counter_is ">= $((base + delta))" "$@" 372} 373 374setup_wait_dev() 375{ 376 local dev=$1; shift 377 local wait_time=${1:-$WAIT_TIME}; shift 378 379 setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time 380 381 if (($?)); then 382 check_err 1 383 log_test setup_wait_dev ": Interface $dev does not come up." 384 exit 1 385 fi 386} 387 388setup_wait_dev_with_timeout() 389{ 390 local dev=$1; shift 391 local max_iterations=${1:-$WAIT_TIMEOUT}; shift 392 local wait_time=${1:-$WAIT_TIME}; shift 393 local i 394 395 for ((i = 1; i <= $max_iterations; ++i)); do 396 ip link show dev $dev up \ 397 | grep 'state UP' &> /dev/null 398 if [[ $? -ne 0 ]]; then 399 sleep 1 400 else 401 sleep $wait_time 402 return 0 403 fi 404 done 405 406 return 1 407} 408 409setup_wait() 410{ 411 local num_netifs=${1:-$NUM_NETIFS} 412 local i 413 414 for ((i = 1; i <= num_netifs; ++i)); do 415 setup_wait_dev ${NETIFS[p$i]} 0 416 done 417 418 # Make sure links are ready. 419 sleep $WAIT_TIME 420} 421 422cmd_jq() 423{ 424 local cmd=$1 425 local jq_exp=$2 426 local jq_opts=$3 427 local ret 428 local output 429 430 output="$($cmd)" 431 # it the command fails, return error right away 432 ret=$? 433 if [[ $ret -ne 0 ]]; then 434 return $ret 435 fi 436 output=$(echo $output | jq -r $jq_opts "$jq_exp") 437 ret=$? 438 if [[ $ret -ne 0 ]]; then 439 return $ret 440 fi 441 echo $output 442 # return success only in case of non-empty output 443 [ ! -z "$output" ] 444} 445 446lldpad_app_wait_set() 447{ 448 local dev=$1; shift 449 450 while lldptool -t -i $dev -V APP -c app | grep -Eq "pending|unknown"; do 451 echo "$dev: waiting for lldpad to push pending APP updates" 452 sleep 5 453 done 454} 455 456lldpad_app_wait_del() 457{ 458 # Give lldpad a chance to push down the changes. If the device is downed 459 # too soon, the updates will be left pending. However, they will have 460 # been struck off the lldpad's DB already, so we won't be able to tell 461 # they are pending. Then on next test iteration this would cause 462 # weirdness as newly-added APP rules conflict with the old ones, 463 # sometimes getting stuck in an "unknown" state. 464 sleep 5 465} 466 467pre_cleanup() 468{ 469 if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then 470 echo "Pausing before cleanup, hit any key to continue" 471 read 472 fi 473} 474 475vrf_prepare() 476{ 477 ip -4 rule add pref 32765 table local 478 ip -4 rule del pref 0 479 ip -6 rule add pref 32765 table local 480 ip -6 rule del pref 0 481} 482 483vrf_cleanup() 484{ 485 ip -6 rule add pref 0 table local 486 ip -6 rule del pref 32765 487 ip -4 rule add pref 0 table local 488 ip -4 rule del pref 32765 489} 490 491__last_tb_id=0 492declare -A __TB_IDS 493 494__vrf_td_id_assign() 495{ 496 local vrf_name=$1 497 498 __last_tb_id=$((__last_tb_id + 1)) 499 __TB_IDS[$vrf_name]=$__last_tb_id 500 return $__last_tb_id 501} 502 503__vrf_td_id_lookup() 504{ 505 local vrf_name=$1 506 507 return ${__TB_IDS[$vrf_name]} 508} 509 510vrf_create() 511{ 512 local vrf_name=$1 513 local tb_id 514 515 __vrf_td_id_assign $vrf_name 516 tb_id=$? 517 518 ip link add dev $vrf_name type vrf table $tb_id 519 ip -4 route add table $tb_id unreachable default metric 4278198272 520 ip -6 route add table $tb_id unreachable default metric 4278198272 521} 522 523vrf_destroy() 524{ 525 local vrf_name=$1 526 local tb_id 527 528 __vrf_td_id_lookup $vrf_name 529 tb_id=$? 530 531 ip -6 route del table $tb_id unreachable default metric 4278198272 532 ip -4 route del table $tb_id unreachable default metric 4278198272 533 ip link del dev $vrf_name 534} 535 536__addr_add_del() 537{ 538 local if_name=$1 539 local add_del=$2 540 local array 541 542 shift 543 shift 544 array=("${@}") 545 546 for addrstr in "${array[@]}"; do 547 ip address $add_del $addrstr dev $if_name 548 done 549} 550 551__simple_if_init() 552{ 553 local if_name=$1; shift 554 local vrf_name=$1; shift 555 local addrs=("${@}") 556 557 ip link set dev $if_name master $vrf_name 558 ip link set dev $if_name up 559 560 __addr_add_del $if_name add "${addrs[@]}" 561} 562 563__simple_if_fini() 564{ 565 local if_name=$1; shift 566 local addrs=("${@}") 567 568 __addr_add_del $if_name del "${addrs[@]}" 569 570 ip link set dev $if_name down 571 ip link set dev $if_name nomaster 572} 573 574simple_if_init() 575{ 576 local if_name=$1 577 local vrf_name 578 local array 579 580 shift 581 vrf_name=v$if_name 582 array=("${@}") 583 584 vrf_create $vrf_name 585 ip link set dev $vrf_name up 586 __simple_if_init $if_name $vrf_name "${array[@]}" 587} 588 589simple_if_fini() 590{ 591 local if_name=$1 592 local vrf_name 593 local array 594 595 shift 596 vrf_name=v$if_name 597 array=("${@}") 598 599 __simple_if_fini $if_name "${array[@]}" 600 vrf_destroy $vrf_name 601} 602 603tunnel_create() 604{ 605 local name=$1; shift 606 local type=$1; shift 607 local local=$1; shift 608 local remote=$1; shift 609 610 ip link add name $name type $type \ 611 local $local remote $remote "$@" 612 ip link set dev $name up 613} 614 615tunnel_destroy() 616{ 617 local name=$1; shift 618 619 ip link del dev $name 620} 621 622vlan_create() 623{ 624 local if_name=$1; shift 625 local vid=$1; shift 626 local vrf=$1; shift 627 local ips=("${@}") 628 local name=$if_name.$vid 629 630 ip link add name $name link $if_name type vlan id $vid 631 if [ "$vrf" != "" ]; then 632 ip link set dev $name master $vrf 633 fi 634 ip link set dev $name up 635 __addr_add_del $name add "${ips[@]}" 636} 637 638vlan_destroy() 639{ 640 local if_name=$1; shift 641 local vid=$1; shift 642 local name=$if_name.$vid 643 644 ip link del dev $name 645} 646 647team_create() 648{ 649 local if_name=$1; shift 650 local mode=$1; shift 651 652 require_command $TEAMD 653 $TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}' 654 for slave in "$@"; do 655 ip link set dev $slave down 656 ip link set dev $slave master $if_name 657 ip link set dev $slave up 658 done 659 ip link set dev $if_name up 660} 661 662team_destroy() 663{ 664 local if_name=$1; shift 665 666 $TEAMD -t $if_name -k 667} 668 669master_name_get() 670{ 671 local if_name=$1 672 673 ip -j link show dev $if_name | jq -r '.[]["master"]' 674} 675 676link_stats_get() 677{ 678 local if_name=$1; shift 679 local dir=$1; shift 680 local stat=$1; shift 681 682 ip -j -s link show dev $if_name \ 683 | jq '.[]["stats64"]["'$dir'"]["'$stat'"]' 684} 685 686link_stats_tx_packets_get() 687{ 688 link_stats_get $1 tx packets 689} 690 691link_stats_rx_errors_get() 692{ 693 link_stats_get $1 rx errors 694} 695 696tc_rule_stats_get() 697{ 698 local dev=$1; shift 699 local pref=$1; shift 700 local dir=$1; shift 701 local selector=${1:-.packets}; shift 702 703 tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \ 704 | jq ".[1].options.actions[].stats$selector" 705} 706 707tc_rule_handle_stats_get() 708{ 709 local id=$1; shift 710 local handle=$1; shift 711 local selector=${1:-.packets}; shift 712 713 tc -j -s filter show $id \ 714 | jq ".[] | select(.options.handle == $handle) | \ 715 .options.actions[0].stats$selector" 716} 717 718ethtool_stats_get() 719{ 720 local dev=$1; shift 721 local stat=$1; shift 722 723 ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2 724} 725 726qdisc_stats_get() 727{ 728 local dev=$1; shift 729 local handle=$1; shift 730 local selector=$1; shift 731 732 tc -j -s qdisc show dev "$dev" \ 733 | jq '.[] | select(.handle == "'"$handle"'") | '"$selector" 734} 735 736qdisc_parent_stats_get() 737{ 738 local dev=$1; shift 739 local parent=$1; shift 740 local selector=$1; shift 741 742 tc -j -s qdisc show dev "$dev" invisible \ 743 | jq '.[] | select(.parent == "'"$parent"'") | '"$selector" 744} 745 746humanize() 747{ 748 local speed=$1; shift 749 750 for unit in bps Kbps Mbps Gbps; do 751 if (($(echo "$speed < 1024" | bc))); then 752 break 753 fi 754 755 speed=$(echo "scale=1; $speed / 1024" | bc) 756 done 757 758 echo "$speed${unit}" 759} 760 761rate() 762{ 763 local t0=$1; shift 764 local t1=$1; shift 765 local interval=$1; shift 766 767 echo $((8 * (t1 - t0) / interval)) 768} 769 770mac_get() 771{ 772 local if_name=$1 773 774 ip -j link show dev $if_name | jq -r '.[]["address"]' 775} 776 777bridge_ageing_time_get() 778{ 779 local bridge=$1 780 local ageing_time 781 782 # Need to divide by 100 to convert to seconds. 783 ageing_time=$(ip -j -d link show dev $bridge \ 784 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]') 785 echo $((ageing_time / 100)) 786} 787 788declare -A SYSCTL_ORIG 789sysctl_set() 790{ 791 local key=$1; shift 792 local value=$1; shift 793 794 SYSCTL_ORIG[$key]=$(sysctl -n $key) 795 sysctl -qw $key=$value 796} 797 798sysctl_restore() 799{ 800 local key=$1; shift 801 802 sysctl -qw $key=${SYSCTL_ORIG["$key"]} 803} 804 805forwarding_enable() 806{ 807 sysctl_set net.ipv4.conf.all.forwarding 1 808 sysctl_set net.ipv6.conf.all.forwarding 1 809} 810 811forwarding_restore() 812{ 813 sysctl_restore net.ipv6.conf.all.forwarding 814 sysctl_restore net.ipv4.conf.all.forwarding 815} 816 817declare -A MTU_ORIG 818mtu_set() 819{ 820 local dev=$1; shift 821 local mtu=$1; shift 822 823 MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu') 824 ip link set dev $dev mtu $mtu 825} 826 827mtu_restore() 828{ 829 local dev=$1; shift 830 831 ip link set dev $dev mtu ${MTU_ORIG["$dev"]} 832} 833 834tc_offload_check() 835{ 836 local num_netifs=${1:-$NUM_NETIFS} 837 838 for ((i = 1; i <= num_netifs; ++i)); do 839 ethtool -k ${NETIFS[p$i]} \ 840 | grep "hw-tc-offload: on" &> /dev/null 841 if [[ $? -ne 0 ]]; then 842 return 1 843 fi 844 done 845 846 return 0 847} 848 849trap_install() 850{ 851 local dev=$1; shift 852 local direction=$1; shift 853 854 # Some devices may not support or need in-hardware trapping of traffic 855 # (e.g. the veth pairs that this library creates for non-existent 856 # loopbacks). Use continue instead, so that there is a filter in there 857 # (some tests check counters), and so that other filters are still 858 # processed. 859 tc filter add dev $dev $direction pref 1 \ 860 flower skip_sw action trap 2>/dev/null \ 861 || tc filter add dev $dev $direction pref 1 \ 862 flower action continue 863} 864 865trap_uninstall() 866{ 867 local dev=$1; shift 868 local direction=$1; shift 869 870 tc filter del dev $dev $direction pref 1 flower 871} 872 873slow_path_trap_install() 874{ 875 # For slow-path testing, we need to install a trap to get to 876 # slow path the packets that would otherwise be switched in HW. 877 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 878 trap_install "$@" 879 fi 880} 881 882slow_path_trap_uninstall() 883{ 884 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 885 trap_uninstall "$@" 886 fi 887} 888 889__icmp_capture_add_del() 890{ 891 local add_del=$1; shift 892 local pref=$1; shift 893 local vsuf=$1; shift 894 local tundev=$1; shift 895 local filter=$1; shift 896 897 tc filter $add_del dev "$tundev" ingress \ 898 proto ip$vsuf pref $pref \ 899 flower ip_proto icmp$vsuf $filter \ 900 action pass 901} 902 903icmp_capture_install() 904{ 905 __icmp_capture_add_del add 100 "" "$@" 906} 907 908icmp_capture_uninstall() 909{ 910 __icmp_capture_add_del del 100 "" "$@" 911} 912 913icmp6_capture_install() 914{ 915 __icmp_capture_add_del add 100 v6 "$@" 916} 917 918icmp6_capture_uninstall() 919{ 920 __icmp_capture_add_del del 100 v6 "$@" 921} 922 923__vlan_capture_add_del() 924{ 925 local add_del=$1; shift 926 local pref=$1; shift 927 local dev=$1; shift 928 local filter=$1; shift 929 930 tc filter $add_del dev "$dev" ingress \ 931 proto 802.1q pref $pref \ 932 flower $filter \ 933 action pass 934} 935 936vlan_capture_install() 937{ 938 __vlan_capture_add_del add 100 "$@" 939} 940 941vlan_capture_uninstall() 942{ 943 __vlan_capture_add_del del 100 "$@" 944} 945 946__dscp_capture_add_del() 947{ 948 local add_del=$1; shift 949 local dev=$1; shift 950 local base=$1; shift 951 local dscp; 952 953 for prio in {0..7}; do 954 dscp=$((base + prio)) 955 __icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \ 956 "skip_hw ip_tos $((dscp << 2))" 957 done 958} 959 960dscp_capture_install() 961{ 962 local dev=$1; shift 963 local base=$1; shift 964 965 __dscp_capture_add_del add $dev $base 966} 967 968dscp_capture_uninstall() 969{ 970 local dev=$1; shift 971 local base=$1; shift 972 973 __dscp_capture_add_del del $dev $base 974} 975 976dscp_fetch_stats() 977{ 978 local dev=$1; shift 979 local base=$1; shift 980 981 for prio in {0..7}; do 982 local dscp=$((base + prio)) 983 local t=$(tc_rule_stats_get $dev $((dscp + 100))) 984 echo "[$dscp]=$t " 985 done 986} 987 988matchall_sink_create() 989{ 990 local dev=$1; shift 991 992 tc qdisc add dev $dev clsact 993 tc filter add dev $dev ingress \ 994 pref 10000 \ 995 matchall \ 996 action drop 997} 998 999tests_run() 1000{ 1001 local current_test 1002 1003 for current_test in ${TESTS:-$ALL_TESTS}; do 1004 $current_test 1005 done 1006} 1007 1008multipath_eval() 1009{ 1010 local desc="$1" 1011 local weight_rp12=$2 1012 local weight_rp13=$3 1013 local packets_rp12=$4 1014 local packets_rp13=$5 1015 local weights_ratio packets_ratio diff 1016 1017 RET=0 1018 1019 if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then 1020 weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \ 1021 | bc -l) 1022 else 1023 weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \ 1024 | bc -l) 1025 fi 1026 1027 if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then 1028 check_err 1 "Packet difference is 0" 1029 log_test "Multipath" 1030 log_info "Expected ratio $weights_ratio" 1031 return 1032 fi 1033 1034 if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then 1035 packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \ 1036 | bc -l) 1037 else 1038 packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \ 1039 | bc -l) 1040 fi 1041 1042 diff=$(echo $weights_ratio - $packets_ratio | bc -l) 1043 diff=${diff#-} 1044 1045 test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0 1046 check_err $? "Too large discrepancy between expected and measured ratios" 1047 log_test "$desc" 1048 log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio" 1049} 1050 1051in_ns() 1052{ 1053 local name=$1; shift 1054 1055 ip netns exec $name bash <<-EOF 1056 NUM_NETIFS=0 1057 source lib.sh 1058 $(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done) 1059 EOF 1060} 1061 1062############################################################################## 1063# Tests 1064 1065ping_do() 1066{ 1067 local if_name=$1 1068 local dip=$2 1069 local args=$3 1070 local vrf_name 1071 1072 vrf_name=$(master_name_get $if_name) 1073 ip vrf exec $vrf_name \ 1074 $PING $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null 1075} 1076 1077ping_test() 1078{ 1079 RET=0 1080 1081 ping_do $1 $2 1082 check_err $? 1083 log_test "ping$3" 1084} 1085 1086ping6_do() 1087{ 1088 local if_name=$1 1089 local dip=$2 1090 local args=$3 1091 local vrf_name 1092 1093 vrf_name=$(master_name_get $if_name) 1094 ip vrf exec $vrf_name \ 1095 $PING6 $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null 1096} 1097 1098ping6_test() 1099{ 1100 RET=0 1101 1102 ping6_do $1 $2 1103 check_err $? 1104 log_test "ping6$3" 1105} 1106 1107learning_test() 1108{ 1109 local bridge=$1 1110 local br_port1=$2 # Connected to `host1_if`. 1111 local host1_if=$3 1112 local host2_if=$4 1113 local mac=de:ad:be:ef:13:37 1114 local ageing_time 1115 1116 RET=0 1117 1118 bridge -j fdb show br $bridge brport $br_port1 \ 1119 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1120 check_fail $? "Found FDB record when should not" 1121 1122 # Disable unknown unicast flooding on `br_port1` to make sure 1123 # packets are only forwarded through the port after a matching 1124 # FDB entry was installed. 1125 bridge link set dev $br_port1 flood off 1126 1127 tc qdisc add dev $host1_if ingress 1128 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \ 1129 flower dst_mac $mac action drop 1130 1131 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 1132 sleep 1 1133 1134 tc -j -s filter show dev $host1_if ingress \ 1135 | jq -e ".[] | select(.options.handle == 101) \ 1136 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1137 check_fail $? "Packet reached second host when should not" 1138 1139 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 1140 sleep 1 1141 1142 bridge -j fdb show br $bridge brport $br_port1 \ 1143 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1144 check_err $? "Did not find FDB record when should" 1145 1146 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 1147 sleep 1 1148 1149 tc -j -s filter show dev $host1_if ingress \ 1150 | jq -e ".[] | select(.options.handle == 101) \ 1151 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1152 check_err $? "Packet did not reach second host when should" 1153 1154 # Wait for 10 seconds after the ageing time to make sure FDB 1155 # record was aged-out. 1156 ageing_time=$(bridge_ageing_time_get $bridge) 1157 sleep $((ageing_time + 10)) 1158 1159 bridge -j fdb show br $bridge brport $br_port1 \ 1160 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1161 check_fail $? "Found FDB record when should not" 1162 1163 bridge link set dev $br_port1 learning off 1164 1165 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 1166 sleep 1 1167 1168 bridge -j fdb show br $bridge brport $br_port1 \ 1169 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1170 check_fail $? "Found FDB record when should not" 1171 1172 bridge link set dev $br_port1 learning on 1173 1174 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower 1175 tc qdisc del dev $host1_if ingress 1176 1177 bridge link set dev $br_port1 flood on 1178 1179 log_test "FDB learning" 1180} 1181 1182flood_test_do() 1183{ 1184 local should_flood=$1 1185 local mac=$2 1186 local ip=$3 1187 local host1_if=$4 1188 local host2_if=$5 1189 local err=0 1190 1191 # Add an ACL on `host2_if` which will tell us whether the packet 1192 # was flooded to it or not. 1193 tc qdisc add dev $host2_if ingress 1194 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \ 1195 flower dst_mac $mac action drop 1196 1197 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q 1198 sleep 1 1199 1200 tc -j -s filter show dev $host2_if ingress \ 1201 | jq -e ".[] | select(.options.handle == 101) \ 1202 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1203 if [[ $? -ne 0 && $should_flood == "true" || \ 1204 $? -eq 0 && $should_flood == "false" ]]; then 1205 err=1 1206 fi 1207 1208 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower 1209 tc qdisc del dev $host2_if ingress 1210 1211 return $err 1212} 1213 1214flood_unicast_test() 1215{ 1216 local br_port=$1 1217 local host1_if=$2 1218 local host2_if=$3 1219 local mac=de:ad:be:ef:13:37 1220 local ip=192.0.2.100 1221 1222 RET=0 1223 1224 bridge link set dev $br_port flood off 1225 1226 flood_test_do false $mac $ip $host1_if $host2_if 1227 check_err $? "Packet flooded when should not" 1228 1229 bridge link set dev $br_port flood on 1230 1231 flood_test_do true $mac $ip $host1_if $host2_if 1232 check_err $? "Packet was not flooded when should" 1233 1234 log_test "Unknown unicast flood" 1235} 1236 1237flood_multicast_test() 1238{ 1239 local br_port=$1 1240 local host1_if=$2 1241 local host2_if=$3 1242 local mac=01:00:5e:00:00:01 1243 local ip=239.0.0.1 1244 1245 RET=0 1246 1247 bridge link set dev $br_port mcast_flood off 1248 1249 flood_test_do false $mac $ip $host1_if $host2_if 1250 check_err $? "Packet flooded when should not" 1251 1252 bridge link set dev $br_port mcast_flood on 1253 1254 flood_test_do true $mac $ip $host1_if $host2_if 1255 check_err $? "Packet was not flooded when should" 1256 1257 log_test "Unregistered multicast flood" 1258} 1259 1260flood_test() 1261{ 1262 # `br_port` is connected to `host2_if` 1263 local br_port=$1 1264 local host1_if=$2 1265 local host2_if=$3 1266 1267 flood_unicast_test $br_port $host1_if $host2_if 1268 flood_multicast_test $br_port $host1_if $host2_if 1269} 1270 1271__start_traffic() 1272{ 1273 local proto=$1; shift 1274 local h_in=$1; shift # Where the traffic egresses the host 1275 local sip=$1; shift 1276 local dip=$1; shift 1277 local dmac=$1; shift 1278 1279 $MZ $h_in -p 8000 -A $sip -B $dip -c 0 \ 1280 -a own -b $dmac -t "$proto" -q "$@" & 1281 sleep 1 1282} 1283 1284start_traffic() 1285{ 1286 __start_traffic udp "$@" 1287} 1288 1289start_tcp_traffic() 1290{ 1291 __start_traffic tcp "$@" 1292} 1293 1294stop_traffic() 1295{ 1296 # Suppress noise from killing mausezahn. 1297 { kill %% && wait %%; } 2>/dev/null 1298} 1299 1300tcpdump_start() 1301{ 1302 local if_name=$1; shift 1303 local ns=$1; shift 1304 1305 capfile=$(mktemp) 1306 capout=$(mktemp) 1307 1308 if [ -z $ns ]; then 1309 ns_cmd="" 1310 else 1311 ns_cmd="ip netns exec ${ns}" 1312 fi 1313 1314 if [ -z $SUDO_USER ] ; then 1315 capuser="" 1316 else 1317 capuser="-Z $SUDO_USER" 1318 fi 1319 1320 $ns_cmd tcpdump -e -n -Q in -i $if_name \ 1321 -s 65535 -B 32768 $capuser -w $capfile > "$capout" 2>&1 & 1322 cappid=$! 1323 1324 sleep 1 1325} 1326 1327tcpdump_stop() 1328{ 1329 $ns_cmd kill $cappid 1330 sleep 1 1331} 1332 1333tcpdump_cleanup() 1334{ 1335 rm $capfile $capout 1336} 1337 1338tcpdump_show() 1339{ 1340 tcpdump -e -n -r $capfile 2>&1 1341} 1342 1343# return 0 if the packet wasn't seen on host2_if or 1 if it was 1344mcast_packet_test() 1345{ 1346 local mac=$1 1347 local src_ip=$2 1348 local ip=$3 1349 local host1_if=$4 1350 local host2_if=$5 1351 local seen=0 1352 local tc_proto="ip" 1353 local mz_v6arg="" 1354 1355 # basic check to see if we were passed an IPv4 address, if not assume IPv6 1356 if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then 1357 tc_proto="ipv6" 1358 mz_v6arg="-6" 1359 fi 1360 1361 # Add an ACL on `host2_if` which will tell us whether the packet 1362 # was received by it or not. 1363 tc qdisc add dev $host2_if ingress 1364 tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \ 1365 flower ip_proto udp dst_mac $mac action drop 1366 1367 $MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q 1368 sleep 1 1369 1370 tc -j -s filter show dev $host2_if ingress \ 1371 | jq -e ".[] | select(.options.handle == 101) \ 1372 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1373 if [[ $? -eq 0 ]]; then 1374 seen=1 1375 fi 1376 1377 tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower 1378 tc qdisc del dev $host2_if ingress 1379 1380 return $seen 1381} 1382 1383brmcast_check_sg_entries() 1384{ 1385 local report=$1; shift 1386 local slist=("$@") 1387 local sarg="" 1388 1389 for src in "${slist[@]}"; do 1390 sarg="${sarg} and .source_list[].address == \"$src\"" 1391 done 1392 bridge -j -d -s mdb show dev br0 \ 1393 | jq -e ".[].mdb[] | \ 1394 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null 1395 check_err $? "Wrong *,G entry source list after $report report" 1396 1397 for sgent in "${slist[@]}"; do 1398 bridge -j -d -s mdb show dev br0 \ 1399 | jq -e ".[].mdb[] | \ 1400 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null 1401 check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)" 1402 done 1403} 1404 1405brmcast_check_sg_fwding() 1406{ 1407 local should_fwd=$1; shift 1408 local sources=("$@") 1409 1410 for src in "${sources[@]}"; do 1411 local retval=0 1412 1413 mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1 1414 retval=$? 1415 if [ $should_fwd -eq 1 ]; then 1416 check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)" 1417 else 1418 check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)" 1419 fi 1420 done 1421} 1422 1423brmcast_check_sg_state() 1424{ 1425 local is_blocked=$1; shift 1426 local sources=("$@") 1427 local should_fail=1 1428 1429 if [ $is_blocked -eq 1 ]; then 1430 should_fail=0 1431 fi 1432 1433 for src in "${sources[@]}"; do 1434 bridge -j -d -s mdb show dev br0 \ 1435 | jq -e ".[].mdb[] | \ 1436 select(.grp == \"$TEST_GROUP\" and .source_list != null) | 1437 .source_list[] | 1438 select(.address == \"$src\") | 1439 select(.timer == \"0.00\")" &>/dev/null 1440 check_err_fail $should_fail $? "Entry $src has zero timer" 1441 1442 bridge -j -d -s mdb show dev br0 \ 1443 | jq -e ".[].mdb[] | \ 1444 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \ 1445 .flags[] == \"blocked\")" &>/dev/null 1446 check_err_fail $should_fail $? "Entry $src has blocked flag" 1447 done 1448} 1449