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 324master_name_get() 325{ 326 local if_name=$1 327 328 ip -j link show dev $if_name | jq -r '.[]["master"]' 329} 330 331link_stats_tx_packets_get() 332{ 333 local if_name=$1 334 335 ip -j -s link show dev $if_name | jq '.[]["stats64"]["tx"]["packets"]' 336} 337 338mac_get() 339{ 340 local if_name=$1 341 342 ip -j link show dev $if_name | jq -r '.[]["address"]' 343} 344 345bridge_ageing_time_get() 346{ 347 local bridge=$1 348 local ageing_time 349 350 # Need to divide by 100 to convert to seconds. 351 ageing_time=$(ip -j -d link show dev $bridge \ 352 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]') 353 echo $((ageing_time / 100)) 354} 355 356forwarding_enable() 357{ 358 ipv4_fwd=$(sysctl -n net.ipv4.conf.all.forwarding) 359 ipv6_fwd=$(sysctl -n net.ipv6.conf.all.forwarding) 360 361 sysctl -q -w net.ipv4.conf.all.forwarding=1 362 sysctl -q -w net.ipv6.conf.all.forwarding=1 363} 364 365forwarding_restore() 366{ 367 sysctl -q -w net.ipv6.conf.all.forwarding=$ipv6_fwd 368 sysctl -q -w net.ipv4.conf.all.forwarding=$ipv4_fwd 369} 370 371tc_offload_check() 372{ 373 for i in $(eval echo {1..$NUM_NETIFS}); do 374 ethtool -k ${NETIFS[p$i]} \ 375 | grep "hw-tc-offload: on" &> /dev/null 376 if [[ $? -ne 0 ]]; then 377 return 1 378 fi 379 done 380 381 return 0 382} 383 384############################################################################## 385# Tests 386 387ping_test() 388{ 389 local if_name=$1 390 local dip=$2 391 local vrf_name 392 393 RET=0 394 395 vrf_name=$(master_name_get $if_name) 396 ip vrf exec $vrf_name $PING $dip -c 10 -i 0.1 -w 2 &> /dev/null 397 check_err $? 398 log_test "ping" 399} 400 401ping6_test() 402{ 403 local if_name=$1 404 local dip=$2 405 local vrf_name 406 407 RET=0 408 409 vrf_name=$(master_name_get $if_name) 410 ip vrf exec $vrf_name $PING6 $dip -c 10 -i 0.1 -w 2 &> /dev/null 411 check_err $? 412 log_test "ping6" 413} 414 415learning_test() 416{ 417 local bridge=$1 418 local br_port1=$2 # Connected to `host1_if`. 419 local host1_if=$3 420 local host2_if=$4 421 local mac=de:ad:be:ef:13:37 422 local ageing_time 423 424 RET=0 425 426 bridge -j fdb show br $bridge brport $br_port1 \ 427 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 428 check_fail $? "Found FDB record when should not" 429 430 # Disable unknown unicast flooding on `br_port1` to make sure 431 # packets are only forwarded through the port after a matching 432 # FDB entry was installed. 433 bridge link set dev $br_port1 flood off 434 435 tc qdisc add dev $host1_if ingress 436 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \ 437 flower dst_mac $mac action drop 438 439 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 440 sleep 1 441 442 tc -j -s filter show dev $host1_if ingress \ 443 | jq -e ".[] | select(.options.handle == 101) \ 444 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 445 check_fail $? "Packet reached second host when should not" 446 447 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 448 sleep 1 449 450 bridge -j fdb show br $bridge brport $br_port1 \ 451 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 452 check_err $? "Did not find FDB record when should" 453 454 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 455 sleep 1 456 457 tc -j -s filter show dev $host1_if ingress \ 458 | jq -e ".[] | select(.options.handle == 101) \ 459 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 460 check_err $? "Packet did not reach second host when should" 461 462 # Wait for 10 seconds after the ageing time to make sure FDB 463 # record was aged-out. 464 ageing_time=$(bridge_ageing_time_get $bridge) 465 sleep $((ageing_time + 10)) 466 467 bridge -j fdb show br $bridge brport $br_port1 \ 468 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 469 check_fail $? "Found FDB record when should not" 470 471 bridge link set dev $br_port1 learning off 472 473 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 474 sleep 1 475 476 bridge -j fdb show br $bridge brport $br_port1 \ 477 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 478 check_fail $? "Found FDB record when should not" 479 480 bridge link set dev $br_port1 learning on 481 482 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower 483 tc qdisc del dev $host1_if ingress 484 485 bridge link set dev $br_port1 flood on 486 487 log_test "FDB learning" 488} 489 490flood_test_do() 491{ 492 local should_flood=$1 493 local mac=$2 494 local ip=$3 495 local host1_if=$4 496 local host2_if=$5 497 local err=0 498 499 # Add an ACL on `host2_if` which will tell us whether the packet 500 # was flooded to it or not. 501 tc qdisc add dev $host2_if ingress 502 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \ 503 flower dst_mac $mac action drop 504 505 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q 506 sleep 1 507 508 tc -j -s filter show dev $host2_if ingress \ 509 | jq -e ".[] | select(.options.handle == 101) \ 510 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 511 if [[ $? -ne 0 && $should_flood == "true" || \ 512 $? -eq 0 && $should_flood == "false" ]]; then 513 err=1 514 fi 515 516 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower 517 tc qdisc del dev $host2_if ingress 518 519 return $err 520} 521 522flood_unicast_test() 523{ 524 local br_port=$1 525 local host1_if=$2 526 local host2_if=$3 527 local mac=de:ad:be:ef:13:37 528 local ip=192.0.2.100 529 530 RET=0 531 532 bridge link set dev $br_port flood off 533 534 flood_test_do false $mac $ip $host1_if $host2_if 535 check_err $? "Packet flooded when should not" 536 537 bridge link set dev $br_port flood on 538 539 flood_test_do true $mac $ip $host1_if $host2_if 540 check_err $? "Packet was not flooded when should" 541 542 log_test "Unknown unicast flood" 543} 544 545flood_multicast_test() 546{ 547 local br_port=$1 548 local host1_if=$2 549 local host2_if=$3 550 local mac=01:00:5e:00:00:01 551 local ip=239.0.0.1 552 553 RET=0 554 555 bridge link set dev $br_port mcast_flood off 556 557 flood_test_do false $mac $ip $host1_if $host2_if 558 check_err $? "Packet flooded when should not" 559 560 bridge link set dev $br_port mcast_flood on 561 562 flood_test_do true $mac $ip $host1_if $host2_if 563 check_err $? "Packet was not flooded when should" 564 565 log_test "Unregistered multicast flood" 566} 567 568flood_test() 569{ 570 # `br_port` is connected to `host2_if` 571 local br_port=$1 572 local host1_if=$2 573 local host2_if=$3 574 575 flood_unicast_test $br_port $host1_if $host2_if 576 flood_multicast_test $br_port $host1_if $host2_if 577} 578