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 356wait_for_trap() 357{ 358 "$@" | grep -q trap 359} 360 361until_counter_is() 362{ 363 local expr=$1; shift 364 local current=$("$@") 365 366 echo $((current)) 367 ((current $expr)) 368} 369 370busywait_for_counter() 371{ 372 local timeout=$1; shift 373 local delta=$1; shift 374 375 local base=$("$@") 376 busywait "$timeout" until_counter_is ">= $((base + delta))" "$@" 377} 378 379setup_wait_dev() 380{ 381 local dev=$1; shift 382 local wait_time=${1:-$WAIT_TIME}; shift 383 384 setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time 385 386 if (($?)); then 387 check_err 1 388 log_test setup_wait_dev ": Interface $dev does not come up." 389 exit 1 390 fi 391} 392 393setup_wait_dev_with_timeout() 394{ 395 local dev=$1; shift 396 local max_iterations=${1:-$WAIT_TIMEOUT}; shift 397 local wait_time=${1:-$WAIT_TIME}; shift 398 local i 399 400 for ((i = 1; i <= $max_iterations; ++i)); do 401 ip link show dev $dev up \ 402 | grep 'state UP' &> /dev/null 403 if [[ $? -ne 0 ]]; then 404 sleep 1 405 else 406 sleep $wait_time 407 return 0 408 fi 409 done 410 411 return 1 412} 413 414setup_wait() 415{ 416 local num_netifs=${1:-$NUM_NETIFS} 417 local i 418 419 for ((i = 1; i <= num_netifs; ++i)); do 420 setup_wait_dev ${NETIFS[p$i]} 0 421 done 422 423 # Make sure links are ready. 424 sleep $WAIT_TIME 425} 426 427cmd_jq() 428{ 429 local cmd=$1 430 local jq_exp=$2 431 local jq_opts=$3 432 local ret 433 local output 434 435 output="$($cmd)" 436 # it the command fails, return error right away 437 ret=$? 438 if [[ $ret -ne 0 ]]; then 439 return $ret 440 fi 441 output=$(echo $output | jq -r $jq_opts "$jq_exp") 442 ret=$? 443 if [[ $ret -ne 0 ]]; then 444 return $ret 445 fi 446 echo $output 447 # return success only in case of non-empty output 448 [ ! -z "$output" ] 449} 450 451lldpad_app_wait_set() 452{ 453 local dev=$1; shift 454 455 while lldptool -t -i $dev -V APP -c app | grep -Eq "pending|unknown"; do 456 echo "$dev: waiting for lldpad to push pending APP updates" 457 sleep 5 458 done 459} 460 461lldpad_app_wait_del() 462{ 463 # Give lldpad a chance to push down the changes. If the device is downed 464 # too soon, the updates will be left pending. However, they will have 465 # been struck off the lldpad's DB already, so we won't be able to tell 466 # they are pending. Then on next test iteration this would cause 467 # weirdness as newly-added APP rules conflict with the old ones, 468 # sometimes getting stuck in an "unknown" state. 469 sleep 5 470} 471 472pre_cleanup() 473{ 474 if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then 475 echo "Pausing before cleanup, hit any key to continue" 476 read 477 fi 478} 479 480vrf_prepare() 481{ 482 ip -4 rule add pref 32765 table local 483 ip -4 rule del pref 0 484 ip -6 rule add pref 32765 table local 485 ip -6 rule del pref 0 486} 487 488vrf_cleanup() 489{ 490 ip -6 rule add pref 0 table local 491 ip -6 rule del pref 32765 492 ip -4 rule add pref 0 table local 493 ip -4 rule del pref 32765 494} 495 496__last_tb_id=0 497declare -A __TB_IDS 498 499__vrf_td_id_assign() 500{ 501 local vrf_name=$1 502 503 __last_tb_id=$((__last_tb_id + 1)) 504 __TB_IDS[$vrf_name]=$__last_tb_id 505 return $__last_tb_id 506} 507 508__vrf_td_id_lookup() 509{ 510 local vrf_name=$1 511 512 return ${__TB_IDS[$vrf_name]} 513} 514 515vrf_create() 516{ 517 local vrf_name=$1 518 local tb_id 519 520 __vrf_td_id_assign $vrf_name 521 tb_id=$? 522 523 ip link add dev $vrf_name type vrf table $tb_id 524 ip -4 route add table $tb_id unreachable default metric 4278198272 525 ip -6 route add table $tb_id unreachable default metric 4278198272 526} 527 528vrf_destroy() 529{ 530 local vrf_name=$1 531 local tb_id 532 533 __vrf_td_id_lookup $vrf_name 534 tb_id=$? 535 536 ip -6 route del table $tb_id unreachable default metric 4278198272 537 ip -4 route del table $tb_id unreachable default metric 4278198272 538 ip link del dev $vrf_name 539} 540 541__addr_add_del() 542{ 543 local if_name=$1 544 local add_del=$2 545 local array 546 547 shift 548 shift 549 array=("${@}") 550 551 for addrstr in "${array[@]}"; do 552 ip address $add_del $addrstr dev $if_name 553 done 554} 555 556__simple_if_init() 557{ 558 local if_name=$1; shift 559 local vrf_name=$1; shift 560 local addrs=("${@}") 561 562 ip link set dev $if_name master $vrf_name 563 ip link set dev $if_name up 564 565 __addr_add_del $if_name add "${addrs[@]}" 566} 567 568__simple_if_fini() 569{ 570 local if_name=$1; shift 571 local addrs=("${@}") 572 573 __addr_add_del $if_name del "${addrs[@]}" 574 575 ip link set dev $if_name down 576 ip link set dev $if_name nomaster 577} 578 579simple_if_init() 580{ 581 local if_name=$1 582 local vrf_name 583 local array 584 585 shift 586 vrf_name=v$if_name 587 array=("${@}") 588 589 vrf_create $vrf_name 590 ip link set dev $vrf_name up 591 __simple_if_init $if_name $vrf_name "${array[@]}" 592} 593 594simple_if_fini() 595{ 596 local if_name=$1 597 local vrf_name 598 local array 599 600 shift 601 vrf_name=v$if_name 602 array=("${@}") 603 604 __simple_if_fini $if_name "${array[@]}" 605 vrf_destroy $vrf_name 606} 607 608tunnel_create() 609{ 610 local name=$1; shift 611 local type=$1; shift 612 local local=$1; shift 613 local remote=$1; shift 614 615 ip link add name $name type $type \ 616 local $local remote $remote "$@" 617 ip link set dev $name up 618} 619 620tunnel_destroy() 621{ 622 local name=$1; shift 623 624 ip link del dev $name 625} 626 627vlan_create() 628{ 629 local if_name=$1; shift 630 local vid=$1; shift 631 local vrf=$1; shift 632 local ips=("${@}") 633 local name=$if_name.$vid 634 635 ip link add name $name link $if_name type vlan id $vid 636 if [ "$vrf" != "" ]; then 637 ip link set dev $name master $vrf 638 fi 639 ip link set dev $name up 640 __addr_add_del $name add "${ips[@]}" 641} 642 643vlan_destroy() 644{ 645 local if_name=$1; shift 646 local vid=$1; shift 647 local name=$if_name.$vid 648 649 ip link del dev $name 650} 651 652team_create() 653{ 654 local if_name=$1; shift 655 local mode=$1; shift 656 657 require_command $TEAMD 658 $TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}' 659 for slave in "$@"; do 660 ip link set dev $slave down 661 ip link set dev $slave master $if_name 662 ip link set dev $slave up 663 done 664 ip link set dev $if_name up 665} 666 667team_destroy() 668{ 669 local if_name=$1; shift 670 671 $TEAMD -t $if_name -k 672} 673 674master_name_get() 675{ 676 local if_name=$1 677 678 ip -j link show dev $if_name | jq -r '.[]["master"]' 679} 680 681link_stats_get() 682{ 683 local if_name=$1; shift 684 local dir=$1; shift 685 local stat=$1; shift 686 687 ip -j -s link show dev $if_name \ 688 | jq '.[]["stats64"]["'$dir'"]["'$stat'"]' 689} 690 691link_stats_tx_packets_get() 692{ 693 link_stats_get $1 tx packets 694} 695 696link_stats_rx_errors_get() 697{ 698 link_stats_get $1 rx errors 699} 700 701tc_rule_stats_get() 702{ 703 local dev=$1; shift 704 local pref=$1; shift 705 local dir=$1; shift 706 local selector=${1:-.packets}; shift 707 708 tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \ 709 | jq ".[1].options.actions[].stats$selector" 710} 711 712tc_rule_handle_stats_get() 713{ 714 local id=$1; shift 715 local handle=$1; shift 716 local selector=${1:-.packets}; shift 717 718 tc -j -s filter show $id \ 719 | jq ".[] | select(.options.handle == $handle) | \ 720 .options.actions[0].stats$selector" 721} 722 723ethtool_stats_get() 724{ 725 local dev=$1; shift 726 local stat=$1; shift 727 728 ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2 729} 730 731qdisc_stats_get() 732{ 733 local dev=$1; shift 734 local handle=$1; shift 735 local selector=$1; shift 736 737 tc -j -s qdisc show dev "$dev" \ 738 | jq '.[] | select(.handle == "'"$handle"'") | '"$selector" 739} 740 741qdisc_parent_stats_get() 742{ 743 local dev=$1; shift 744 local parent=$1; shift 745 local selector=$1; shift 746 747 tc -j -s qdisc show dev "$dev" invisible \ 748 | jq '.[] | select(.parent == "'"$parent"'") | '"$selector" 749} 750 751humanize() 752{ 753 local speed=$1; shift 754 755 for unit in bps Kbps Mbps Gbps; do 756 if (($(echo "$speed < 1024" | bc))); then 757 break 758 fi 759 760 speed=$(echo "scale=1; $speed / 1024" | bc) 761 done 762 763 echo "$speed${unit}" 764} 765 766rate() 767{ 768 local t0=$1; shift 769 local t1=$1; shift 770 local interval=$1; shift 771 772 echo $((8 * (t1 - t0) / interval)) 773} 774 775packets_rate() 776{ 777 local t0=$1; shift 778 local t1=$1; shift 779 local interval=$1; shift 780 781 echo $(((t1 - t0) / interval)) 782} 783 784mac_get() 785{ 786 local if_name=$1 787 788 ip -j link show dev $if_name | jq -r '.[]["address"]' 789} 790 791bridge_ageing_time_get() 792{ 793 local bridge=$1 794 local ageing_time 795 796 # Need to divide by 100 to convert to seconds. 797 ageing_time=$(ip -j -d link show dev $bridge \ 798 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]') 799 echo $((ageing_time / 100)) 800} 801 802declare -A SYSCTL_ORIG 803sysctl_set() 804{ 805 local key=$1; shift 806 local value=$1; shift 807 808 SYSCTL_ORIG[$key]=$(sysctl -n $key) 809 sysctl -qw $key=$value 810} 811 812sysctl_restore() 813{ 814 local key=$1; shift 815 816 sysctl -qw $key=${SYSCTL_ORIG["$key"]} 817} 818 819forwarding_enable() 820{ 821 sysctl_set net.ipv4.conf.all.forwarding 1 822 sysctl_set net.ipv6.conf.all.forwarding 1 823} 824 825forwarding_restore() 826{ 827 sysctl_restore net.ipv6.conf.all.forwarding 828 sysctl_restore net.ipv4.conf.all.forwarding 829} 830 831declare -A MTU_ORIG 832mtu_set() 833{ 834 local dev=$1; shift 835 local mtu=$1; shift 836 837 MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu') 838 ip link set dev $dev mtu $mtu 839} 840 841mtu_restore() 842{ 843 local dev=$1; shift 844 845 ip link set dev $dev mtu ${MTU_ORIG["$dev"]} 846} 847 848tc_offload_check() 849{ 850 local num_netifs=${1:-$NUM_NETIFS} 851 852 for ((i = 1; i <= num_netifs; ++i)); do 853 ethtool -k ${NETIFS[p$i]} \ 854 | grep "hw-tc-offload: on" &> /dev/null 855 if [[ $? -ne 0 ]]; then 856 return 1 857 fi 858 done 859 860 return 0 861} 862 863trap_install() 864{ 865 local dev=$1; shift 866 local direction=$1; shift 867 868 # Some devices may not support or need in-hardware trapping of traffic 869 # (e.g. the veth pairs that this library creates for non-existent 870 # loopbacks). Use continue instead, so that there is a filter in there 871 # (some tests check counters), and so that other filters are still 872 # processed. 873 tc filter add dev $dev $direction pref 1 \ 874 flower skip_sw action trap 2>/dev/null \ 875 || tc filter add dev $dev $direction pref 1 \ 876 flower action continue 877} 878 879trap_uninstall() 880{ 881 local dev=$1; shift 882 local direction=$1; shift 883 884 tc filter del dev $dev $direction pref 1 flower 885} 886 887slow_path_trap_install() 888{ 889 # For slow-path testing, we need to install a trap to get to 890 # slow path the packets that would otherwise be switched in HW. 891 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 892 trap_install "$@" 893 fi 894} 895 896slow_path_trap_uninstall() 897{ 898 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 899 trap_uninstall "$@" 900 fi 901} 902 903__icmp_capture_add_del() 904{ 905 local add_del=$1; shift 906 local pref=$1; shift 907 local vsuf=$1; shift 908 local tundev=$1; shift 909 local filter=$1; shift 910 911 tc filter $add_del dev "$tundev" ingress \ 912 proto ip$vsuf pref $pref \ 913 flower ip_proto icmp$vsuf $filter \ 914 action pass 915} 916 917icmp_capture_install() 918{ 919 __icmp_capture_add_del add 100 "" "$@" 920} 921 922icmp_capture_uninstall() 923{ 924 __icmp_capture_add_del del 100 "" "$@" 925} 926 927icmp6_capture_install() 928{ 929 __icmp_capture_add_del add 100 v6 "$@" 930} 931 932icmp6_capture_uninstall() 933{ 934 __icmp_capture_add_del del 100 v6 "$@" 935} 936 937__vlan_capture_add_del() 938{ 939 local add_del=$1; shift 940 local pref=$1; shift 941 local dev=$1; shift 942 local filter=$1; shift 943 944 tc filter $add_del dev "$dev" ingress \ 945 proto 802.1q pref $pref \ 946 flower $filter \ 947 action pass 948} 949 950vlan_capture_install() 951{ 952 __vlan_capture_add_del add 100 "$@" 953} 954 955vlan_capture_uninstall() 956{ 957 __vlan_capture_add_del del 100 "$@" 958} 959 960__dscp_capture_add_del() 961{ 962 local add_del=$1; shift 963 local dev=$1; shift 964 local base=$1; shift 965 local dscp; 966 967 for prio in {0..7}; do 968 dscp=$((base + prio)) 969 __icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \ 970 "skip_hw ip_tos $((dscp << 2))" 971 done 972} 973 974dscp_capture_install() 975{ 976 local dev=$1; shift 977 local base=$1; shift 978 979 __dscp_capture_add_del add $dev $base 980} 981 982dscp_capture_uninstall() 983{ 984 local dev=$1; shift 985 local base=$1; shift 986 987 __dscp_capture_add_del del $dev $base 988} 989 990dscp_fetch_stats() 991{ 992 local dev=$1; shift 993 local base=$1; shift 994 995 for prio in {0..7}; do 996 local dscp=$((base + prio)) 997 local t=$(tc_rule_stats_get $dev $((dscp + 100))) 998 echo "[$dscp]=$t " 999 done 1000} 1001 1002matchall_sink_create() 1003{ 1004 local dev=$1; shift 1005 1006 tc qdisc add dev $dev clsact 1007 tc filter add dev $dev ingress \ 1008 pref 10000 \ 1009 matchall \ 1010 action drop 1011} 1012 1013tests_run() 1014{ 1015 local current_test 1016 1017 for current_test in ${TESTS:-$ALL_TESTS}; do 1018 $current_test 1019 done 1020} 1021 1022multipath_eval() 1023{ 1024 local desc="$1" 1025 local weight_rp12=$2 1026 local weight_rp13=$3 1027 local packets_rp12=$4 1028 local packets_rp13=$5 1029 local weights_ratio packets_ratio diff 1030 1031 RET=0 1032 1033 if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then 1034 weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \ 1035 | bc -l) 1036 else 1037 weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \ 1038 | bc -l) 1039 fi 1040 1041 if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then 1042 check_err 1 "Packet difference is 0" 1043 log_test "Multipath" 1044 log_info "Expected ratio $weights_ratio" 1045 return 1046 fi 1047 1048 if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then 1049 packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \ 1050 | bc -l) 1051 else 1052 packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \ 1053 | bc -l) 1054 fi 1055 1056 diff=$(echo $weights_ratio - $packets_ratio | bc -l) 1057 diff=${diff#-} 1058 1059 test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0 1060 check_err $? "Too large discrepancy between expected and measured ratios" 1061 log_test "$desc" 1062 log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio" 1063} 1064 1065in_ns() 1066{ 1067 local name=$1; shift 1068 1069 ip netns exec $name bash <<-EOF 1070 NUM_NETIFS=0 1071 source lib.sh 1072 $(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done) 1073 EOF 1074} 1075 1076############################################################################## 1077# Tests 1078 1079ping_do() 1080{ 1081 local if_name=$1 1082 local dip=$2 1083 local args=$3 1084 local vrf_name 1085 1086 vrf_name=$(master_name_get $if_name) 1087 ip vrf exec $vrf_name \ 1088 $PING $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null 1089} 1090 1091ping_test() 1092{ 1093 RET=0 1094 1095 ping_do $1 $2 1096 check_err $? 1097 log_test "ping$3" 1098} 1099 1100ping6_do() 1101{ 1102 local if_name=$1 1103 local dip=$2 1104 local args=$3 1105 local vrf_name 1106 1107 vrf_name=$(master_name_get $if_name) 1108 ip vrf exec $vrf_name \ 1109 $PING6 $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null 1110} 1111 1112ping6_test() 1113{ 1114 RET=0 1115 1116 ping6_do $1 $2 1117 check_err $? 1118 log_test "ping6$3" 1119} 1120 1121learning_test() 1122{ 1123 local bridge=$1 1124 local br_port1=$2 # Connected to `host1_if`. 1125 local host1_if=$3 1126 local host2_if=$4 1127 local mac=de:ad:be:ef:13:37 1128 local ageing_time 1129 1130 RET=0 1131 1132 bridge -j fdb show br $bridge brport $br_port1 \ 1133 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1134 check_fail $? "Found FDB record when should not" 1135 1136 # Disable unknown unicast flooding on `br_port1` to make sure 1137 # packets are only forwarded through the port after a matching 1138 # FDB entry was installed. 1139 bridge link set dev $br_port1 flood off 1140 1141 tc qdisc add dev $host1_if ingress 1142 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \ 1143 flower dst_mac $mac action drop 1144 1145 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 1146 sleep 1 1147 1148 tc -j -s filter show dev $host1_if ingress \ 1149 | jq -e ".[] | select(.options.handle == 101) \ 1150 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1151 check_fail $? "Packet reached second host when should not" 1152 1153 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 1154 sleep 1 1155 1156 bridge -j fdb show br $bridge brport $br_port1 \ 1157 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1158 check_err $? "Did not find FDB record when should" 1159 1160 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 1161 sleep 1 1162 1163 tc -j -s filter show dev $host1_if ingress \ 1164 | jq -e ".[] | select(.options.handle == 101) \ 1165 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1166 check_err $? "Packet did not reach second host when should" 1167 1168 # Wait for 10 seconds after the ageing time to make sure FDB 1169 # record was aged-out. 1170 ageing_time=$(bridge_ageing_time_get $bridge) 1171 sleep $((ageing_time + 10)) 1172 1173 bridge -j fdb show br $bridge brport $br_port1 \ 1174 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1175 check_fail $? "Found FDB record when should not" 1176 1177 bridge link set dev $br_port1 learning off 1178 1179 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 1180 sleep 1 1181 1182 bridge -j fdb show br $bridge brport $br_port1 \ 1183 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1184 check_fail $? "Found FDB record when should not" 1185 1186 bridge link set dev $br_port1 learning on 1187 1188 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower 1189 tc qdisc del dev $host1_if ingress 1190 1191 bridge link set dev $br_port1 flood on 1192 1193 log_test "FDB learning" 1194} 1195 1196flood_test_do() 1197{ 1198 local should_flood=$1 1199 local mac=$2 1200 local ip=$3 1201 local host1_if=$4 1202 local host2_if=$5 1203 local err=0 1204 1205 # Add an ACL on `host2_if` which will tell us whether the packet 1206 # was flooded to it or not. 1207 tc qdisc add dev $host2_if ingress 1208 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \ 1209 flower dst_mac $mac action drop 1210 1211 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q 1212 sleep 1 1213 1214 tc -j -s filter show dev $host2_if ingress \ 1215 | jq -e ".[] | select(.options.handle == 101) \ 1216 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1217 if [[ $? -ne 0 && $should_flood == "true" || \ 1218 $? -eq 0 && $should_flood == "false" ]]; then 1219 err=1 1220 fi 1221 1222 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower 1223 tc qdisc del dev $host2_if ingress 1224 1225 return $err 1226} 1227 1228flood_unicast_test() 1229{ 1230 local br_port=$1 1231 local host1_if=$2 1232 local host2_if=$3 1233 local mac=de:ad:be:ef:13:37 1234 local ip=192.0.2.100 1235 1236 RET=0 1237 1238 bridge link set dev $br_port flood off 1239 1240 flood_test_do false $mac $ip $host1_if $host2_if 1241 check_err $? "Packet flooded when should not" 1242 1243 bridge link set dev $br_port flood on 1244 1245 flood_test_do true $mac $ip $host1_if $host2_if 1246 check_err $? "Packet was not flooded when should" 1247 1248 log_test "Unknown unicast flood" 1249} 1250 1251flood_multicast_test() 1252{ 1253 local br_port=$1 1254 local host1_if=$2 1255 local host2_if=$3 1256 local mac=01:00:5e:00:00:01 1257 local ip=239.0.0.1 1258 1259 RET=0 1260 1261 bridge link set dev $br_port mcast_flood off 1262 1263 flood_test_do false $mac $ip $host1_if $host2_if 1264 check_err $? "Packet flooded when should not" 1265 1266 bridge link set dev $br_port mcast_flood on 1267 1268 flood_test_do true $mac $ip $host1_if $host2_if 1269 check_err $? "Packet was not flooded when should" 1270 1271 log_test "Unregistered multicast flood" 1272} 1273 1274flood_test() 1275{ 1276 # `br_port` is connected to `host2_if` 1277 local br_port=$1 1278 local host1_if=$2 1279 local host2_if=$3 1280 1281 flood_unicast_test $br_port $host1_if $host2_if 1282 flood_multicast_test $br_port $host1_if $host2_if 1283} 1284 1285__start_traffic() 1286{ 1287 local proto=$1; shift 1288 local h_in=$1; shift # Where the traffic egresses the host 1289 local sip=$1; shift 1290 local dip=$1; shift 1291 local dmac=$1; shift 1292 1293 $MZ $h_in -p 8000 -A $sip -B $dip -c 0 \ 1294 -a own -b $dmac -t "$proto" -q "$@" & 1295 sleep 1 1296} 1297 1298start_traffic() 1299{ 1300 __start_traffic udp "$@" 1301} 1302 1303start_tcp_traffic() 1304{ 1305 __start_traffic tcp "$@" 1306} 1307 1308stop_traffic() 1309{ 1310 # Suppress noise from killing mausezahn. 1311 { kill %% && wait %%; } 2>/dev/null 1312} 1313 1314tcpdump_start() 1315{ 1316 local if_name=$1; shift 1317 local ns=$1; shift 1318 1319 capfile=$(mktemp) 1320 capout=$(mktemp) 1321 1322 if [ -z $ns ]; then 1323 ns_cmd="" 1324 else 1325 ns_cmd="ip netns exec ${ns}" 1326 fi 1327 1328 if [ -z $SUDO_USER ] ; then 1329 capuser="" 1330 else 1331 capuser="-Z $SUDO_USER" 1332 fi 1333 1334 $ns_cmd tcpdump -e -n -Q in -i $if_name \ 1335 -s 65535 -B 32768 $capuser -w $capfile > "$capout" 2>&1 & 1336 cappid=$! 1337 1338 sleep 1 1339} 1340 1341tcpdump_stop() 1342{ 1343 $ns_cmd kill $cappid 1344 sleep 1 1345} 1346 1347tcpdump_cleanup() 1348{ 1349 rm $capfile $capout 1350} 1351 1352tcpdump_show() 1353{ 1354 tcpdump -e -n -r $capfile 2>&1 1355} 1356 1357# return 0 if the packet wasn't seen on host2_if or 1 if it was 1358mcast_packet_test() 1359{ 1360 local mac=$1 1361 local src_ip=$2 1362 local ip=$3 1363 local host1_if=$4 1364 local host2_if=$5 1365 local seen=0 1366 local tc_proto="ip" 1367 local mz_v6arg="" 1368 1369 # basic check to see if we were passed an IPv4 address, if not assume IPv6 1370 if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then 1371 tc_proto="ipv6" 1372 mz_v6arg="-6" 1373 fi 1374 1375 # Add an ACL on `host2_if` which will tell us whether the packet 1376 # was received by it or not. 1377 tc qdisc add dev $host2_if ingress 1378 tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \ 1379 flower ip_proto udp dst_mac $mac action drop 1380 1381 $MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q 1382 sleep 1 1383 1384 tc -j -s filter show dev $host2_if ingress \ 1385 | jq -e ".[] | select(.options.handle == 101) \ 1386 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1387 if [[ $? -eq 0 ]]; then 1388 seen=1 1389 fi 1390 1391 tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower 1392 tc qdisc del dev $host2_if ingress 1393 1394 return $seen 1395} 1396 1397brmcast_check_sg_entries() 1398{ 1399 local report=$1; shift 1400 local slist=("$@") 1401 local sarg="" 1402 1403 for src in "${slist[@]}"; do 1404 sarg="${sarg} and .source_list[].address == \"$src\"" 1405 done 1406 bridge -j -d -s mdb show dev br0 \ 1407 | jq -e ".[].mdb[] | \ 1408 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null 1409 check_err $? "Wrong *,G entry source list after $report report" 1410 1411 for sgent in "${slist[@]}"; do 1412 bridge -j -d -s mdb show dev br0 \ 1413 | jq -e ".[].mdb[] | \ 1414 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null 1415 check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)" 1416 done 1417} 1418 1419brmcast_check_sg_fwding() 1420{ 1421 local should_fwd=$1; shift 1422 local sources=("$@") 1423 1424 for src in "${sources[@]}"; do 1425 local retval=0 1426 1427 mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1 1428 retval=$? 1429 if [ $should_fwd -eq 1 ]; then 1430 check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)" 1431 else 1432 check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)" 1433 fi 1434 done 1435} 1436 1437brmcast_check_sg_state() 1438{ 1439 local is_blocked=$1; shift 1440 local sources=("$@") 1441 local should_fail=1 1442 1443 if [ $is_blocked -eq 1 ]; then 1444 should_fail=0 1445 fi 1446 1447 for src in "${sources[@]}"; do 1448 bridge -j -d -s mdb show dev br0 \ 1449 | jq -e ".[].mdb[] | \ 1450 select(.grp == \"$TEST_GROUP\" and .source_list != null) | 1451 .source_list[] | 1452 select(.address == \"$src\") | 1453 select(.timer == \"0.00\")" &>/dev/null 1454 check_err_fail $should_fail $? "Entry $src has zero timer" 1455 1456 bridge -j -d -s mdb show dev br0 \ 1457 | jq -e ".[].mdb[] | \ 1458 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \ 1459 .flags[] == \"blocked\")" &>/dev/null 1460 check_err_fail $should_fail $? "Entry $src has blocked flag" 1461 done 1462} 1463