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 343master_name_get() 344{ 345 local if_name=$1 346 347 ip -j link show dev $if_name | jq -r '.[]["master"]' 348} 349 350link_stats_tx_packets_get() 351{ 352 local if_name=$1 353 354 ip -j -s link show dev $if_name | jq '.[]["stats64"]["tx"]["packets"]' 355} 356 357tc_rule_stats_get() 358{ 359 local dev=$1; shift 360 local pref=$1; shift 361 362 tc -j -s filter show dev $dev ingress pref $pref | 363 jq '.[1].options.actions[].stats.packets' 364} 365 366mac_get() 367{ 368 local if_name=$1 369 370 ip -j link show dev $if_name | jq -r '.[]["address"]' 371} 372 373bridge_ageing_time_get() 374{ 375 local bridge=$1 376 local ageing_time 377 378 # Need to divide by 100 to convert to seconds. 379 ageing_time=$(ip -j -d link show dev $bridge \ 380 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]') 381 echo $((ageing_time / 100)) 382} 383 384declare -A SYSCTL_ORIG 385sysctl_set() 386{ 387 local key=$1; shift 388 local value=$1; shift 389 390 SYSCTL_ORIG[$key]=$(sysctl -n $key) 391 sysctl -qw $key=$value 392} 393 394sysctl_restore() 395{ 396 local key=$1; shift 397 398 sysctl -qw $key=${SYSCTL_ORIG["$key"]} 399} 400 401forwarding_enable() 402{ 403 sysctl_set net.ipv4.conf.all.forwarding 1 404 sysctl_set net.ipv6.conf.all.forwarding 1 405} 406 407forwarding_restore() 408{ 409 sysctl_restore net.ipv6.conf.all.forwarding 410 sysctl_restore net.ipv4.conf.all.forwarding 411} 412 413tc_offload_check() 414{ 415 for i in $(eval echo {1..$NUM_NETIFS}); do 416 ethtool -k ${NETIFS[p$i]} \ 417 | grep "hw-tc-offload: on" &> /dev/null 418 if [[ $? -ne 0 ]]; then 419 return 1 420 fi 421 done 422 423 return 0 424} 425 426slow_path_trap_install() 427{ 428 local dev=$1; shift 429 local direction=$1; shift 430 431 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 432 # For slow-path testing, we need to install a trap to get to 433 # slow path the packets that would otherwise be switched in HW. 434 tc filter add dev $dev $direction pref 1 \ 435 flower skip_sw action trap 436 fi 437} 438 439slow_path_trap_uninstall() 440{ 441 local dev=$1; shift 442 local direction=$1; shift 443 444 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 445 tc filter del dev $dev $direction pref 1 flower skip_sw 446 fi 447} 448 449__icmp_capture_add_del() 450{ 451 local add_del=$1; shift 452 local pref=$1; shift 453 local vsuf=$1; shift 454 local tundev=$1; shift 455 local filter=$1; shift 456 457 tc filter $add_del dev "$tundev" ingress \ 458 proto ip$vsuf pref $pref \ 459 flower ip_proto icmp$vsuf $filter \ 460 action pass 461} 462 463icmp_capture_install() 464{ 465 __icmp_capture_add_del add 100 "" "$@" 466} 467 468icmp_capture_uninstall() 469{ 470 __icmp_capture_add_del del 100 "" "$@" 471} 472 473icmp6_capture_install() 474{ 475 __icmp_capture_add_del add 100 v6 "$@" 476} 477 478icmp6_capture_uninstall() 479{ 480 __icmp_capture_add_del del 100 v6 "$@" 481} 482 483matchall_sink_create() 484{ 485 local dev=$1; shift 486 487 tc qdisc add dev $dev clsact 488 tc filter add dev $dev ingress \ 489 pref 10000 \ 490 matchall \ 491 action drop 492} 493 494tests_run() 495{ 496 local current_test 497 498 for current_test in ${TESTS:-$ALL_TESTS}; do 499 $current_test 500 done 501} 502 503############################################################################## 504# Tests 505 506ping_test() 507{ 508 local if_name=$1 509 local dip=$2 510 local vrf_name 511 512 RET=0 513 514 vrf_name=$(master_name_get $if_name) 515 ip vrf exec $vrf_name $PING $dip -c 10 -i 0.1 -w 2 &> /dev/null 516 check_err $? 517 log_test "ping" 518} 519 520ping6_test() 521{ 522 local if_name=$1 523 local dip=$2 524 local vrf_name 525 526 RET=0 527 528 vrf_name=$(master_name_get $if_name) 529 ip vrf exec $vrf_name $PING6 $dip -c 10 -i 0.1 -w 2 &> /dev/null 530 check_err $? 531 log_test "ping6" 532} 533 534learning_test() 535{ 536 local bridge=$1 537 local br_port1=$2 # Connected to `host1_if`. 538 local host1_if=$3 539 local host2_if=$4 540 local mac=de:ad:be:ef:13:37 541 local ageing_time 542 543 RET=0 544 545 bridge -j fdb show br $bridge brport $br_port1 \ 546 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 547 check_fail $? "Found FDB record when should not" 548 549 # Disable unknown unicast flooding on `br_port1` to make sure 550 # packets are only forwarded through the port after a matching 551 # FDB entry was installed. 552 bridge link set dev $br_port1 flood off 553 554 tc qdisc add dev $host1_if ingress 555 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \ 556 flower dst_mac $mac action drop 557 558 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 559 sleep 1 560 561 tc -j -s filter show dev $host1_if ingress \ 562 | jq -e ".[] | select(.options.handle == 101) \ 563 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 564 check_fail $? "Packet reached second host when should not" 565 566 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 567 sleep 1 568 569 bridge -j fdb show br $bridge brport $br_port1 \ 570 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 571 check_err $? "Did not find FDB record when should" 572 573 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 574 sleep 1 575 576 tc -j -s filter show dev $host1_if ingress \ 577 | jq -e ".[] | select(.options.handle == 101) \ 578 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 579 check_err $? "Packet did not reach second host when should" 580 581 # Wait for 10 seconds after the ageing time to make sure FDB 582 # record was aged-out. 583 ageing_time=$(bridge_ageing_time_get $bridge) 584 sleep $((ageing_time + 10)) 585 586 bridge -j fdb show br $bridge brport $br_port1 \ 587 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 588 check_fail $? "Found FDB record when should not" 589 590 bridge link set dev $br_port1 learning off 591 592 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 593 sleep 1 594 595 bridge -j fdb show br $bridge brport $br_port1 \ 596 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 597 check_fail $? "Found FDB record when should not" 598 599 bridge link set dev $br_port1 learning on 600 601 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower 602 tc qdisc del dev $host1_if ingress 603 604 bridge link set dev $br_port1 flood on 605 606 log_test "FDB learning" 607} 608 609flood_test_do() 610{ 611 local should_flood=$1 612 local mac=$2 613 local ip=$3 614 local host1_if=$4 615 local host2_if=$5 616 local err=0 617 618 # Add an ACL on `host2_if` which will tell us whether the packet 619 # was flooded to it or not. 620 tc qdisc add dev $host2_if ingress 621 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \ 622 flower dst_mac $mac action drop 623 624 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q 625 sleep 1 626 627 tc -j -s filter show dev $host2_if ingress \ 628 | jq -e ".[] | select(.options.handle == 101) \ 629 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 630 if [[ $? -ne 0 && $should_flood == "true" || \ 631 $? -eq 0 && $should_flood == "false" ]]; then 632 err=1 633 fi 634 635 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower 636 tc qdisc del dev $host2_if ingress 637 638 return $err 639} 640 641flood_unicast_test() 642{ 643 local br_port=$1 644 local host1_if=$2 645 local host2_if=$3 646 local mac=de:ad:be:ef:13:37 647 local ip=192.0.2.100 648 649 RET=0 650 651 bridge link set dev $br_port flood off 652 653 flood_test_do false $mac $ip $host1_if $host2_if 654 check_err $? "Packet flooded when should not" 655 656 bridge link set dev $br_port flood on 657 658 flood_test_do true $mac $ip $host1_if $host2_if 659 check_err $? "Packet was not flooded when should" 660 661 log_test "Unknown unicast flood" 662} 663 664flood_multicast_test() 665{ 666 local br_port=$1 667 local host1_if=$2 668 local host2_if=$3 669 local mac=01:00:5e:00:00:01 670 local ip=239.0.0.1 671 672 RET=0 673 674 bridge link set dev $br_port mcast_flood off 675 676 flood_test_do false $mac $ip $host1_if $host2_if 677 check_err $? "Packet flooded when should not" 678 679 bridge link set dev $br_port mcast_flood on 680 681 flood_test_do true $mac $ip $host1_if $host2_if 682 check_err $? "Packet was not flooded when should" 683 684 log_test "Unregistered multicast flood" 685} 686 687flood_test() 688{ 689 # `br_port` is connected to `host2_if` 690 local br_port=$1 691 local host1_if=$2 692 local host2_if=$3 693 694 flood_unicast_test $br_port $host1_if $host2_if 695 flood_multicast_test $br_port $host1_if $host2_if 696} 697