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