| #!/bin/bash | |
| # SPDX-License-Identifier: GPL-2.0 | |
| # ShellCheck incorrectly believes that most of the code here is unreachable | |
| # because it's invoked by variable name following ALL_TESTS. | |
| # | |
| # shellcheck disable=SC2317 | |
| ALL_TESTS="check_accounting check_limit" | |
| NUM_NETIFS=6 | |
| source lib.sh | |
| TEST_MAC_BASE=de:ad:be:ef:42: | |
| NUM_PKTS=16 | |
| FDB_LIMIT=8 | |
| FDB_TYPES=( | |
| # name is counted? overrides learned? | |
| 'learned 1 0' | |
| 'static 0 1' | |
| 'user 0 1' | |
| 'extern_learn 0 1' | |
| 'local 0 1' | |
| ) | |
| mac() | |
| { | |
| printf "${TEST_MAC_BASE}%02x" "$1" | |
| } | |
| H1_DEFAULT_MAC=$(mac 42) | |
| switch_create() | |
| { | |
| ip link add dev br0 type bridge | |
| ip link set dev "$swp1" master br0 | |
| ip link set dev "$swp2" master br0 | |
| # swp3 is used to add local MACs, so do not add it to the bridge yet. | |
| # swp2 is only used for replying when learning on swp1, its MAC should not be learned. | |
| ip link set dev "$swp2" type bridge_slave learning off | |
| ip link set dev br0 up | |
| ip link set dev "$swp1" up | |
| ip link set dev "$swp2" up | |
| ip link set dev "$swp3" up | |
| } | |
| switch_destroy() | |
| { | |
| ip link set dev "$swp3" down | |
| ip link set dev "$swp2" down | |
| ip link set dev "$swp1" down | |
| ip link del dev br0 | |
| } | |
| h_create() | |
| { | |
| ip link set "$h1" addr "$H1_DEFAULT_MAC" | |
| simple_if_init "$h1" 192.0.2.1/24 | |
| simple_if_init "$h2" 192.0.2.2/24 | |
| } | |
| h_destroy() | |
| { | |
| simple_if_fini "$h1" 192.0.2.1/24 | |
| simple_if_fini "$h2" 192.0.2.2/24 | |
| } | |
| setup_prepare() | |
| { | |
| h1=${NETIFS[p1]} | |
| swp1=${NETIFS[p2]} | |
| h2=${NETIFS[p3]} | |
| swp2=${NETIFS[p4]} | |
| swp3=${NETIFS[p6]} | |
| vrf_prepare | |
| h_create | |
| switch_create | |
| } | |
| cleanup() | |
| { | |
| pre_cleanup | |
| switch_destroy | |
| h_destroy | |
| vrf_cleanup | |
| } | |
| fdb_get_n_learned() | |
| { | |
| ip -d -j link show dev br0 type bridge | \ | |
| jq '.[]["linkinfo"]["info_data"]["fdb_n_learned"]' | |
| } | |
| fdb_get_n_mac() | |
| { | |
| local mac=${1} | |
| bridge -j fdb show br br0 | \ | |
| jq "map(select(.mac == \"${mac}\" and (has(\"vlan\") | not))) | length" | |
| } | |
| fdb_fill_learned() | |
| { | |
| local i | |
| for i in $(seq 1 "$NUM_PKTS"); do | |
| fdb_add learned "$(mac "$i")" | |
| done | |
| } | |
| fdb_reset() | |
| { | |
| bridge fdb flush dev br0 | |
| # Keep the default MAC address of h1 in the table. We set it to a different one when | |
| # testing dynamic learning. | |
| bridge fdb add "$H1_DEFAULT_MAC" dev "$swp1" master static use | |
| } | |
| fdb_add() | |
| { | |
| local type=$1 mac=$2 | |
| case "$type" in | |
| learned) | |
| ip link set "$h1" addr "$mac" | |
| # Wait for a reply so we implicitly wait until after the forwarding | |
| # code finished and the FDB entry was created. | |
| PING_COUNT=1 ping_do "$h1" 192.0.2.2 | |
| check_err $? "Failed to ping another bridge port" | |
| ip link set "$h1" addr "$H1_DEFAULT_MAC" | |
| ;; | |
| local) | |
| ip link set dev "$swp3" addr "$mac" && ip link set "$swp3" master br0 | |
| ;; | |
| static) | |
| bridge fdb replace "$mac" dev "$swp1" master static | |
| ;; | |
| user) | |
| bridge fdb replace "$mac" dev "$swp1" master static use | |
| ;; | |
| extern_learn) | |
| bridge fdb replace "$mac" dev "$swp1" master extern_learn | |
| ;; | |
| esac | |
| check_err $? "Failed to add a FDB entry of type ${type}" | |
| } | |
| fdb_del() | |
| { | |
| local type=$1 mac=$2 | |
| case "$type" in | |
| local) | |
| ip link set "$swp3" nomaster | |
| ;; | |
| *) | |
| bridge fdb del "$mac" dev "$swp1" master | |
| ;; | |
| esac | |
| check_err $? "Failed to remove a FDB entry of type ${type}" | |
| } | |
| check_fdb_n_learned_support() | |
| { | |
| if ! ip link help bridge 2>&1 | grep -q "fdb_max_learned"; then | |
| echo "SKIP: iproute2 too old, missing bridge max learned support" | |
| exit $ksft_skip | |
| fi | |
| ip link add dev br0 type bridge | |
| local learned=$(fdb_get_n_learned) | |
| ip link del dev br0 | |
| if [ "$learned" == "null" ]; then | |
| echo "SKIP: kernel too old; bridge fdb_n_learned feature not supported." | |
| exit $ksft_skip | |
| fi | |
| } | |
| check_accounting_one_type() | |
| { | |
| local type=$1 is_counted=$2 overrides_learned=$3 | |
| shift 3 | |
| RET=0 | |
| fdb_reset | |
| fdb_add "$type" "$(mac 0)" | |
| learned=$(fdb_get_n_learned) | |
| [ "$learned" -ne "$is_counted" ] | |
| check_fail $? "Inserted FDB type ${type}: Expected the count ${is_counted}, but got ${learned}" | |
| fdb_del "$type" "$(mac 0)" | |
| learned=$(fdb_get_n_learned) | |
| [ "$learned" -ne 0 ] | |
| check_fail $? "Removed FDB type ${type}: Expected the count 0, but got ${learned}" | |
| if [ "$overrides_learned" -eq 1 ]; then | |
| fdb_reset | |
| fdb_add learned "$(mac 0)" | |
| fdb_add "$type" "$(mac 0)" | |
| learned=$(fdb_get_n_learned) | |
| [ "$learned" -ne "$is_counted" ] | |
| check_fail $? "Set a learned entry to FDB type ${type}: Expected the count ${is_counted}, but got ${learned}" | |
| fdb_del "$type" "$(mac 0)" | |
| fi | |
| log_test "FDB accounting interacting with FDB type ${type}" | |
| } | |
| check_accounting() | |
| { | |
| local type_args learned | |
| RET=0 | |
| fdb_reset | |
| learned=$(fdb_get_n_learned) | |
| [ "$learned" -ne 0 ] | |
| check_fail $? "Flushed the FDB table: Expected the count 0, but got ${learned}" | |
| fdb_fill_learned | |
| sleep 1 | |
| learned=$(fdb_get_n_learned) | |
| [ "$learned" -ne "$NUM_PKTS" ] | |
| check_fail $? "Filled the FDB table: Expected the count ${NUM_PKTS}, but got ${learned}" | |
| log_test "FDB accounting" | |
| for type_args in "${FDB_TYPES[@]}"; do | |
| # This is intentional use of word splitting. | |
| # shellcheck disable=SC2086 | |
| check_accounting_one_type $type_args | |
| done | |
| } | |
| check_limit_one_type() | |
| { | |
| local type=$1 is_counted=$2 | |
| local n_mac expected=$((1 - is_counted)) | |
| RET=0 | |
| fdb_reset | |
| fdb_fill_learned | |
| fdb_add "$type" "$(mac 0)" | |
| n_mac=$(fdb_get_n_mac "$(mac 0)") | |
| [ "$n_mac" -ne "$expected" ] | |
| check_fail $? "Inserted FDB type ${type} at limit: Expected the count ${expected}, but got ${n_mac}" | |
| log_test "FDB limits interacting with FDB type ${type}" | |
| } | |
| check_limit() | |
| { | |
| local learned | |
| RET=0 | |
| ip link set br0 type bridge fdb_max_learned "$FDB_LIMIT" | |
| fdb_reset | |
| fdb_fill_learned | |
| learned=$(fdb_get_n_learned) | |
| [ "$learned" -ne "$FDB_LIMIT" ] | |
| check_fail $? "Filled the limited FDB table: Expected the count ${FDB_LIMIT}, but got ${learned}" | |
| log_test "FDB limits" | |
| for type_args in "${FDB_TYPES[@]}"; do | |
| # This is intentional use of word splitting. | |
| # shellcheck disable=SC2086 | |
| check_limit_one_type $type_args | |
| done | |
| } | |
| check_fdb_n_learned_support | |
| trap cleanup EXIT | |
| setup_prepare | |
| tests_run | |
| exit $EXIT_STATUS |