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