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