1494 lines
28 KiB
Bash
1494 lines
28 KiB
Bash
#!/bin/bash
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
##############################################################################
|
|
# Defines
|
|
|
|
# Kselftest framework requirement - SKIP code is 4.
|
|
ksft_skip=4
|
|
|
|
# Can be overridden by the configuration file.
|
|
PING=${PING:=ping}
|
|
PING6=${PING6:=ping6}
|
|
MZ=${MZ:=mausezahn}
|
|
ARPING=${ARPING:=arping}
|
|
TEAMD=${TEAMD:=teamd}
|
|
WAIT_TIME=${WAIT_TIME:=5}
|
|
PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
|
|
PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
|
|
NETIF_TYPE=${NETIF_TYPE:=veth}
|
|
NETIF_CREATE=${NETIF_CREATE:=yes}
|
|
MCD=${MCD:=smcrouted}
|
|
MC_CLI=${MC_CLI:=smcroutectl}
|
|
PING_TIMEOUT=${PING_TIMEOUT:=5}
|
|
WAIT_TIMEOUT=${WAIT_TIMEOUT:=20}
|
|
INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600}
|
|
|
|
relative_path="${BASH_SOURCE%/*}"
|
|
if [[ "$relative_path" == "${BASH_SOURCE}" ]]; then
|
|
relative_path="."
|
|
fi
|
|
|
|
if [[ -f $relative_path/forwarding.config ]]; then
|
|
source "$relative_path/forwarding.config"
|
|
fi
|
|
|
|
##############################################################################
|
|
# Sanity checks
|
|
|
|
check_tc_version()
|
|
{
|
|
tc -j &> /dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "SKIP: iproute2 too old; tc is missing JSON support"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
# Old versions of tc don't understand "mpls_uc"
|
|
check_tc_mpls_support()
|
|
{
|
|
local dev=$1; shift
|
|
|
|
tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
|
|
matchall action pipe &> /dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "SKIP: iproute2 too old; tc is missing MPLS support"
|
|
return $ksft_skip
|
|
fi
|
|
tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
|
|
matchall
|
|
}
|
|
|
|
# Old versions of tc produce invalid json output for mpls lse statistics
|
|
check_tc_mpls_lse_stats()
|
|
{
|
|
local dev=$1; shift
|
|
local ret;
|
|
|
|
tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
|
|
flower mpls lse depth 2 \
|
|
action continue &> /dev/null
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "SKIP: iproute2 too old; tc-flower is missing extended MPLS support"
|
|
return $ksft_skip
|
|
fi
|
|
|
|
tc -j filter show dev $dev ingress protocol mpls_uc | jq . &> /dev/null
|
|
ret=$?
|
|
tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
|
|
flower
|
|
|
|
if [[ $ret -ne 0 ]]; then
|
|
echo "SKIP: iproute2 too old; tc-flower produces invalid json output for extended MPLS filters"
|
|
return $ksft_skip
|
|
fi
|
|
}
|
|
|
|
check_tc_shblock_support()
|
|
{
|
|
tc filter help 2>&1 | grep block &> /dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "SKIP: iproute2 too old; tc is missing shared block support"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
check_tc_chain_support()
|
|
{
|
|
tc help 2>&1|grep chain &> /dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "SKIP: iproute2 too old; tc is missing chain support"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
check_tc_action_hw_stats_support()
|
|
{
|
|
tc actions help 2>&1 | grep -q hw_stats
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "SKIP: iproute2 too old; tc is missing action hw_stats support"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
check_ethtool_lanes_support()
|
|
{
|
|
ethtool --help 2>&1| grep lanes &> /dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "SKIP: ethtool too old; it is missing lanes support"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
skip_on_veth()
|
|
{
|
|
local kind=$(ip -j -d link show dev ${NETIFS[p1]} |
|
|
jq -r '.[].linkinfo.info_kind')
|
|
|
|
if [[ $kind == veth ]]; then
|
|
echo "SKIP: Test cannot be run with veth pairs"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
if [[ "$(id -u)" -ne 0 ]]; then
|
|
echo "SKIP: need root privileges"
|
|
exit $ksft_skip
|
|
fi
|
|
|
|
if [[ "$CHECK_TC" = "yes" ]]; then
|
|
check_tc_version
|
|
fi
|
|
|
|
require_command()
|
|
{
|
|
local cmd=$1; shift
|
|
|
|
if [[ ! -x "$(command -v "$cmd")" ]]; then
|
|
echo "SKIP: $cmd not installed"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
require_command jq
|
|
require_command $MZ
|
|
|
|
if [[ ! -v NUM_NETIFS ]]; then
|
|
echo "SKIP: importer does not define \"NUM_NETIFS\""
|
|
exit $ksft_skip
|
|
fi
|
|
|
|
##############################################################################
|
|
# Command line options handling
|
|
|
|
count=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
if [[ "$count" -eq "0" ]]; then
|
|
unset NETIFS
|
|
declare -A NETIFS
|
|
fi
|
|
count=$((count + 1))
|
|
NETIFS[p$count]="$1"
|
|
shift
|
|
done
|
|
|
|
##############################################################################
|
|
# Network interfaces configuration
|
|
|
|
create_netif_veth()
|
|
{
|
|
local i
|
|
|
|
for ((i = 1; i <= NUM_NETIFS; ++i)); do
|
|
local j=$((i+1))
|
|
|
|
if [ -z ${NETIFS[p$i]} ]; then
|
|
echo "SKIP: Cannot create interface. Name not specified"
|
|
exit $ksft_skip
|
|
fi
|
|
|
|
ip link show dev ${NETIFS[p$i]} &> /dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
ip link add ${NETIFS[p$i]} type veth \
|
|
peer name ${NETIFS[p$j]}
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "Failed to create netif"
|
|
exit 1
|
|
fi
|
|
fi
|
|
i=$j
|
|
done
|
|
}
|
|
|
|
create_netif()
|
|
{
|
|
case "$NETIF_TYPE" in
|
|
veth) create_netif_veth
|
|
;;
|
|
*) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
if [[ "$NETIF_CREATE" = "yes" ]]; then
|
|
create_netif
|
|
fi
|
|
|
|
for ((i = 1; i <= NUM_NETIFS; ++i)); do
|
|
ip link show dev ${NETIFS[p$i]} &> /dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "SKIP: could not find all required interfaces"
|
|
exit $ksft_skip
|
|
fi
|
|
done
|
|
|
|
##############################################################################
|
|
# Helpers
|
|
|
|
# Exit status to return at the end. Set in case one of the tests fails.
|
|
EXIT_STATUS=0
|
|
# Per-test return value. Clear at the beginning of each test.
|
|
RET=0
|
|
|
|
check_err()
|
|
{
|
|
local err=$1
|
|
local msg=$2
|
|
|
|
if [[ $RET -eq 0 && $err -ne 0 ]]; then
|
|
RET=$err
|
|
retmsg=$msg
|
|
fi
|
|
}
|
|
|
|
check_fail()
|
|
{
|
|
local err=$1
|
|
local msg=$2
|
|
|
|
if [[ $RET -eq 0 && $err -eq 0 ]]; then
|
|
RET=1
|
|
retmsg=$msg
|
|
fi
|
|
}
|
|
|
|
check_err_fail()
|
|
{
|
|
local should_fail=$1; shift
|
|
local err=$1; shift
|
|
local what=$1; shift
|
|
|
|
if ((should_fail)); then
|
|
check_fail $err "$what succeeded, but should have failed"
|
|
else
|
|
check_err $err "$what failed"
|
|
fi
|
|
}
|
|
|
|
log_test()
|
|
{
|
|
local test_name=$1
|
|
local opt_str=$2
|
|
|
|
if [[ $# -eq 2 ]]; then
|
|
opt_str="($opt_str)"
|
|
fi
|
|
|
|
if [[ $RET -ne 0 ]]; then
|
|
EXIT_STATUS=1
|
|
printf "TEST: %-60s [FAIL]\n" "$test_name $opt_str"
|
|
if [[ ! -z "$retmsg" ]]; then
|
|
printf "\t%s\n" "$retmsg"
|
|
fi
|
|
if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
|
|
echo "Hit enter to continue, 'q' to quit"
|
|
read a
|
|
[ "$a" = "q" ] && exit 1
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
printf "TEST: %-60s [ OK ]\n" "$test_name $opt_str"
|
|
return 0
|
|
}
|
|
|
|
log_info()
|
|
{
|
|
local msg=$1
|
|
|
|
echo "INFO: $msg"
|
|
}
|
|
|
|
busywait()
|
|
{
|
|
local timeout=$1; shift
|
|
|
|
local start_time="$(date -u +%s%3N)"
|
|
while true
|
|
do
|
|
local out
|
|
out=$("$@")
|
|
local ret=$?
|
|
if ((!ret)); then
|
|
echo -n "$out"
|
|
return 0
|
|
fi
|
|
|
|
local current_time="$(date -u +%s%3N)"
|
|
if ((current_time - start_time > timeout)); then
|
|
echo -n "$out"
|
|
return 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
not()
|
|
{
|
|
"$@"
|
|
[[ $? != 0 ]]
|
|
}
|
|
|
|
get_max()
|
|
{
|
|
local arr=("$@")
|
|
|
|
max=${arr[0]}
|
|
for cur in ${arr[@]}; do
|
|
if [[ $cur -gt $max ]]; then
|
|
max=$cur
|
|
fi
|
|
done
|
|
|
|
echo $max
|
|
}
|
|
|
|
grep_bridge_fdb()
|
|
{
|
|
local addr=$1; shift
|
|
local word
|
|
local flag
|
|
|
|
if [ "$1" == "self" ] || [ "$1" == "master" ]; then
|
|
word=$1; shift
|
|
if [ "$1" == "-v" ]; then
|
|
flag=$1; shift
|
|
fi
|
|
fi
|
|
|
|
$@ | grep $addr | grep $flag "$word"
|
|
}
|
|
|
|
wait_for_port_up()
|
|
{
|
|
"$@" | grep -q "Link detected: yes"
|
|
}
|
|
|
|
wait_for_offload()
|
|
{
|
|
"$@" | grep -q offload
|
|
}
|
|
|
|
wait_for_trap()
|
|
{
|
|
"$@" | grep -q trap
|
|
}
|
|
|
|
until_counter_is()
|
|
{
|
|
local expr=$1; shift
|
|
local current=$("$@")
|
|
|
|
echo $((current))
|
|
((current $expr))
|
|
}
|
|
|
|
busywait_for_counter()
|
|
{
|
|
local timeout=$1; shift
|
|
local delta=$1; shift
|
|
|
|
local base=$("$@")
|
|
busywait "$timeout" until_counter_is ">= $((base + delta))" "$@"
|
|
}
|
|
|
|
setup_wait_dev()
|
|
{
|
|
local dev=$1; shift
|
|
local wait_time=${1:-$WAIT_TIME}; shift
|
|
|
|
setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time
|
|
|
|
if (($?)); then
|
|
check_err 1
|
|
log_test setup_wait_dev ": Interface $dev does not come up."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
setup_wait_dev_with_timeout()
|
|
{
|
|
local dev=$1; shift
|
|
local max_iterations=${1:-$WAIT_TIMEOUT}; shift
|
|
local wait_time=${1:-$WAIT_TIME}; shift
|
|
local i
|
|
|
|
for ((i = 1; i <= $max_iterations; ++i)); do
|
|
ip link show dev $dev up \
|
|
| grep 'state UP' &> /dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
sleep 1
|
|
else
|
|
sleep $wait_time
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
setup_wait()
|
|
{
|
|
local num_netifs=${1:-$NUM_NETIFS}
|
|
local i
|
|
|
|
for ((i = 1; i <= num_netifs; ++i)); do
|
|
setup_wait_dev ${NETIFS[p$i]} 0
|
|
done
|
|
|
|
# Make sure links are ready.
|
|
sleep $WAIT_TIME
|
|
}
|
|
|
|
cmd_jq()
|
|
{
|
|
local cmd=$1
|
|
local jq_exp=$2
|
|
local jq_opts=$3
|
|
local ret
|
|
local output
|
|
|
|
output="$($cmd)"
|
|
# it the command fails, return error right away
|
|
ret=$?
|
|
if [[ $ret -ne 0 ]]; then
|
|
return $ret
|
|
fi
|
|
output=$(echo $output | jq -r $jq_opts "$jq_exp")
|
|
ret=$?
|
|
if [[ $ret -ne 0 ]]; then
|
|
return $ret
|
|
fi
|
|
echo $output
|
|
# return success only in case of non-empty output
|
|
[ ! -z "$output" ]
|
|
}
|
|
|
|
lldpad_app_wait_set()
|
|
{
|
|
local dev=$1; shift
|
|
|
|
while lldptool -t -i $dev -V APP -c app | grep -Eq "pending|unknown"; do
|
|
echo "$dev: waiting for lldpad to push pending APP updates"
|
|
sleep 5
|
|
done
|
|
}
|
|
|
|
lldpad_app_wait_del()
|
|
{
|
|
# Give lldpad a chance to push down the changes. If the device is downed
|
|
# too soon, the updates will be left pending. However, they will have
|
|
# been struck off the lldpad's DB already, so we won't be able to tell
|
|
# they are pending. Then on next test iteration this would cause
|
|
# weirdness as newly-added APP rules conflict with the old ones,
|
|
# sometimes getting stuck in an "unknown" state.
|
|
sleep 5
|
|
}
|
|
|
|
pre_cleanup()
|
|
{
|
|
if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
|
|
echo "Pausing before cleanup, hit any key to continue"
|
|
read
|
|
fi
|
|
}
|
|
|
|
vrf_prepare()
|
|
{
|
|
ip -4 rule add pref 32765 table local
|
|
ip -4 rule del pref 0
|
|
ip -6 rule add pref 32765 table local
|
|
ip -6 rule del pref 0
|
|
}
|
|
|
|
vrf_cleanup()
|
|
{
|
|
ip -6 rule add pref 0 table local
|
|
ip -6 rule del pref 32765
|
|
ip -4 rule add pref 0 table local
|
|
ip -4 rule del pref 32765
|
|
}
|
|
|
|
__last_tb_id=0
|
|
declare -A __TB_IDS
|
|
|
|
__vrf_td_id_assign()
|
|
{
|
|
local vrf_name=$1
|
|
|
|
__last_tb_id=$((__last_tb_id + 1))
|
|
__TB_IDS[$vrf_name]=$__last_tb_id
|
|
return $__last_tb_id
|
|
}
|
|
|
|
__vrf_td_id_lookup()
|
|
{
|
|
local vrf_name=$1
|
|
|
|
return ${__TB_IDS[$vrf_name]}
|
|
}
|
|
|
|
vrf_create()
|
|
{
|
|
local vrf_name=$1
|
|
local tb_id
|
|
|
|
__vrf_td_id_assign $vrf_name
|
|
tb_id=$?
|
|
|
|
ip link add dev $vrf_name type vrf table $tb_id
|
|
ip -4 route add table $tb_id unreachable default metric 4278198272
|
|
ip -6 route add table $tb_id unreachable default metric 4278198272
|
|
}
|
|
|
|
vrf_destroy()
|
|
{
|
|
local vrf_name=$1
|
|
local tb_id
|
|
|
|
__vrf_td_id_lookup $vrf_name
|
|
tb_id=$?
|
|
|
|
ip -6 route del table $tb_id unreachable default metric 4278198272
|
|
ip -4 route del table $tb_id unreachable default metric 4278198272
|
|
ip link del dev $vrf_name
|
|
}
|
|
|
|
__addr_add_del()
|
|
{
|
|
local if_name=$1
|
|
local add_del=$2
|
|
local array
|
|
|
|
shift
|
|
shift
|
|
array=("${@}")
|
|
|
|
for addrstr in "${array[@]}"; do
|
|
ip address $add_del $addrstr dev $if_name
|
|
done
|
|
}
|
|
|
|
__simple_if_init()
|
|
{
|
|
local if_name=$1; shift
|
|
local vrf_name=$1; shift
|
|
local addrs=("${@}")
|
|
|
|
ip link set dev $if_name master $vrf_name
|
|
ip link set dev $if_name up
|
|
|
|
__addr_add_del $if_name add "${addrs[@]}"
|
|
}
|
|
|
|
__simple_if_fini()
|
|
{
|
|
local if_name=$1; shift
|
|
local addrs=("${@}")
|
|
|
|
__addr_add_del $if_name del "${addrs[@]}"
|
|
|
|
ip link set dev $if_name down
|
|
ip link set dev $if_name nomaster
|
|
}
|
|
|
|
simple_if_init()
|
|
{
|
|
local if_name=$1
|
|
local vrf_name
|
|
local array
|
|
|
|
shift
|
|
vrf_name=v$if_name
|
|
array=("${@}")
|
|
|
|
vrf_create $vrf_name
|
|
ip link set dev $vrf_name up
|
|
__simple_if_init $if_name $vrf_name "${array[@]}"
|
|
}
|
|
|
|
simple_if_fini()
|
|
{
|
|
local if_name=$1
|
|
local vrf_name
|
|
local array
|
|
|
|
shift
|
|
vrf_name=v$if_name
|
|
array=("${@}")
|
|
|
|
__simple_if_fini $if_name "${array[@]}"
|
|
vrf_destroy $vrf_name
|
|
}
|
|
|
|
tunnel_create()
|
|
{
|
|
local name=$1; shift
|
|
local type=$1; shift
|
|
local local=$1; shift
|
|
local remote=$1; shift
|
|
|
|
ip link add name $name type $type \
|
|
local $local remote $remote "$@"
|
|
ip link set dev $name up
|
|
}
|
|
|
|
tunnel_destroy()
|
|
{
|
|
local name=$1; shift
|
|
|
|
ip link del dev $name
|
|
}
|
|
|
|
vlan_create()
|
|
{
|
|
local if_name=$1; shift
|
|
local vid=$1; shift
|
|
local vrf=$1; shift
|
|
local ips=("${@}")
|
|
local name=$if_name.$vid
|
|
|
|
ip link add name $name link $if_name type vlan id $vid
|
|
if [ "$vrf" != "" ]; then
|
|
ip link set dev $name master $vrf
|
|
fi
|
|
ip link set dev $name up
|
|
__addr_add_del $name add "${ips[@]}"
|
|
}
|
|
|
|
vlan_destroy()
|
|
{
|
|
local if_name=$1; shift
|
|
local vid=$1; shift
|
|
local name=$if_name.$vid
|
|
|
|
ip link del dev $name
|
|
}
|
|
|
|
team_create()
|
|
{
|
|
local if_name=$1; shift
|
|
local mode=$1; shift
|
|
|
|
require_command $TEAMD
|
|
$TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
|
|
for slave in "$@"; do
|
|
ip link set dev $slave down
|
|
ip link set dev $slave master $if_name
|
|
ip link set dev $slave up
|
|
done
|
|
ip link set dev $if_name up
|
|
}
|
|
|
|
team_destroy()
|
|
{
|
|
local if_name=$1; shift
|
|
|
|
$TEAMD -t $if_name -k
|
|
}
|
|
|
|
master_name_get()
|
|
{
|
|
local if_name=$1
|
|
|
|
ip -j link show dev $if_name | jq -r '.[]["master"]'
|
|
}
|
|
|
|
link_stats_get()
|
|
{
|
|
local if_name=$1; shift
|
|
local dir=$1; shift
|
|
local stat=$1; shift
|
|
|
|
ip -j -s link show dev $if_name \
|
|
| jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
|
|
}
|
|
|
|
link_stats_tx_packets_get()
|
|
{
|
|
link_stats_get $1 tx packets
|
|
}
|
|
|
|
link_stats_rx_errors_get()
|
|
{
|
|
link_stats_get $1 rx errors
|
|
}
|
|
|
|
tc_rule_stats_get()
|
|
{
|
|
local dev=$1; shift
|
|
local pref=$1; shift
|
|
local dir=$1; shift
|
|
local selector=${1:-.packets}; shift
|
|
|
|
tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
|
|
| jq ".[1].options.actions[].stats$selector"
|
|
}
|
|
|
|
tc_rule_handle_stats_get()
|
|
{
|
|
local id=$1; shift
|
|
local handle=$1; shift
|
|
local selector=${1:-.packets}; shift
|
|
|
|
tc -j -s filter show $id \
|
|
| jq ".[] | select(.options.handle == $handle) | \
|
|
.options.actions[0].stats$selector"
|
|
}
|
|
|
|
ethtool_stats_get()
|
|
{
|
|
local dev=$1; shift
|
|
local stat=$1; shift
|
|
|
|
ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
|
|
}
|
|
|
|
qdisc_stats_get()
|
|
{
|
|
local dev=$1; shift
|
|
local handle=$1; shift
|
|
local selector=$1; shift
|
|
|
|
tc -j -s qdisc show dev "$dev" \
|
|
| jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
|
|
}
|
|
|
|
qdisc_parent_stats_get()
|
|
{
|
|
local dev=$1; shift
|
|
local parent=$1; shift
|
|
local selector=$1; shift
|
|
|
|
tc -j -s qdisc show dev "$dev" invisible \
|
|
| jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
|
|
}
|
|
|
|
ipv6_stats_get()
|
|
{
|
|
local dev=$1; shift
|
|
local stat=$1; shift
|
|
|
|
cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
|
|
}
|
|
|
|
humanize()
|
|
{
|
|
local speed=$1; shift
|
|
|
|
for unit in bps Kbps Mbps Gbps; do
|
|
if (($(echo "$speed < 1024" | bc))); then
|
|
break
|
|
fi
|
|
|
|
speed=$(echo "scale=1; $speed / 1024" | bc)
|
|
done
|
|
|
|
echo "$speed${unit}"
|
|
}
|
|
|
|
rate()
|
|
{
|
|
local t0=$1; shift
|
|
local t1=$1; shift
|
|
local interval=$1; shift
|
|
|
|
echo $((8 * (t1 - t0) / interval))
|
|
}
|
|
|
|
packets_rate()
|
|
{
|
|
local t0=$1; shift
|
|
local t1=$1; shift
|
|
local interval=$1; shift
|
|
|
|
echo $(((t1 - t0) / interval))
|
|
}
|
|
|
|
mac_get()
|
|
{
|
|
local if_name=$1
|
|
|
|
ip -j link show dev $if_name | jq -r '.[]["address"]'
|
|
}
|
|
|
|
bridge_ageing_time_get()
|
|
{
|
|
local bridge=$1
|
|
local ageing_time
|
|
|
|
# Need to divide by 100 to convert to seconds.
|
|
ageing_time=$(ip -j -d link show dev $bridge \
|
|
| jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
|
|
echo $((ageing_time / 100))
|
|
}
|
|
|
|
declare -A SYSCTL_ORIG
|
|
sysctl_set()
|
|
{
|
|
local key=$1; shift
|
|
local value=$1; shift
|
|
|
|
SYSCTL_ORIG[$key]=$(sysctl -n $key)
|
|
sysctl -qw $key="$value"
|
|
}
|
|
|
|
sysctl_restore()
|
|
{
|
|
local key=$1; shift
|
|
|
|
sysctl -qw $key="${SYSCTL_ORIG[$key]}"
|
|
}
|
|
|
|
forwarding_enable()
|
|
{
|
|
sysctl_set net.ipv4.conf.all.forwarding 1
|
|
sysctl_set net.ipv6.conf.all.forwarding 1
|
|
}
|
|
|
|
forwarding_restore()
|
|
{
|
|
sysctl_restore net.ipv6.conf.all.forwarding
|
|
sysctl_restore net.ipv4.conf.all.forwarding
|
|
}
|
|
|
|
declare -A MTU_ORIG
|
|
mtu_set()
|
|
{
|
|
local dev=$1; shift
|
|
local mtu=$1; shift
|
|
|
|
MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
|
|
ip link set dev $dev mtu $mtu
|
|
}
|
|
|
|
mtu_restore()
|
|
{
|
|
local dev=$1; shift
|
|
|
|
ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
|
|
}
|
|
|
|
tc_offload_check()
|
|
{
|
|
local num_netifs=${1:-$NUM_NETIFS}
|
|
|
|
for ((i = 1; i <= num_netifs; ++i)); do
|
|
ethtool -k ${NETIFS[p$i]} \
|
|
| grep "hw-tc-offload: on" &> /dev/null
|
|
if [[ $? -ne 0 ]]; then
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
trap_install()
|
|
{
|
|
local dev=$1; shift
|
|
local direction=$1; shift
|
|
|
|
# Some devices may not support or need in-hardware trapping of traffic
|
|
# (e.g. the veth pairs that this library creates for non-existent
|
|
# loopbacks). Use continue instead, so that there is a filter in there
|
|
# (some tests check counters), and so that other filters are still
|
|
# processed.
|
|
tc filter add dev $dev $direction pref 1 \
|
|
flower skip_sw action trap 2>/dev/null \
|
|
|| tc filter add dev $dev $direction pref 1 \
|
|
flower action continue
|
|
}
|
|
|
|
trap_uninstall()
|
|
{
|
|
local dev=$1; shift
|
|
local direction=$1; shift
|
|
|
|
tc filter del dev $dev $direction pref 1 flower
|
|
}
|
|
|
|
slow_path_trap_install()
|
|
{
|
|
# For slow-path testing, we need to install a trap to get to
|
|
# slow path the packets that would otherwise be switched in HW.
|
|
if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
|
|
trap_install "$@"
|
|
fi
|
|
}
|
|
|
|
slow_path_trap_uninstall()
|
|
{
|
|
if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
|
|
trap_uninstall "$@"
|
|
fi
|
|
}
|
|
|
|
__icmp_capture_add_del()
|
|
{
|
|
local add_del=$1; shift
|
|
local pref=$1; shift
|
|
local vsuf=$1; shift
|
|
local tundev=$1; shift
|
|
local filter=$1; shift
|
|
|
|
tc filter $add_del dev "$tundev" ingress \
|
|
proto ip$vsuf pref $pref \
|
|
flower ip_proto icmp$vsuf $filter \
|
|
action pass
|
|
}
|
|
|
|
icmp_capture_install()
|
|
{
|
|
__icmp_capture_add_del add 100 "" "$@"
|
|
}
|
|
|
|
icmp_capture_uninstall()
|
|
{
|
|
__icmp_capture_add_del del 100 "" "$@"
|
|
}
|
|
|
|
icmp6_capture_install()
|
|
{
|
|
__icmp_capture_add_del add 100 v6 "$@"
|
|
}
|
|
|
|
icmp6_capture_uninstall()
|
|
{
|
|
__icmp_capture_add_del del 100 v6 "$@"
|
|
}
|
|
|
|
__vlan_capture_add_del()
|
|
{
|
|
local add_del=$1; shift
|
|
local pref=$1; shift
|
|
local dev=$1; shift
|
|
local filter=$1; shift
|
|
|
|
tc filter $add_del dev "$dev" ingress \
|
|
proto 802.1q pref $pref \
|
|
flower $filter \
|
|
action pass
|
|
}
|
|
|
|
vlan_capture_install()
|
|
{
|
|
__vlan_capture_add_del add 100 "$@"
|
|
}
|
|
|
|
vlan_capture_uninstall()
|
|
{
|
|
__vlan_capture_add_del del 100 "$@"
|
|
}
|
|
|
|
__dscp_capture_add_del()
|
|
{
|
|
local add_del=$1; shift
|
|
local dev=$1; shift
|
|
local base=$1; shift
|
|
local dscp;
|
|
|
|
for prio in {0..7}; do
|
|
dscp=$((base + prio))
|
|
__icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
|
|
"skip_hw ip_tos $((dscp << 2))"
|
|
done
|
|
}
|
|
|
|
dscp_capture_install()
|
|
{
|
|
local dev=$1; shift
|
|
local base=$1; shift
|
|
|
|
__dscp_capture_add_del add $dev $base
|
|
}
|
|
|
|
dscp_capture_uninstall()
|
|
{
|
|
local dev=$1; shift
|
|
local base=$1; shift
|
|
|
|
__dscp_capture_add_del del $dev $base
|
|
}
|
|
|
|
dscp_fetch_stats()
|
|
{
|
|
local dev=$1; shift
|
|
local base=$1; shift
|
|
|
|
for prio in {0..7}; do
|
|
local dscp=$((base + prio))
|
|
local t=$(tc_rule_stats_get $dev $((dscp + 100)))
|
|
echo "[$dscp]=$t "
|
|
done
|
|
}
|
|
|
|
matchall_sink_create()
|
|
{
|
|
local dev=$1; shift
|
|
|
|
tc qdisc add dev $dev clsact
|
|
tc filter add dev $dev ingress \
|
|
pref 10000 \
|
|
matchall \
|
|
action drop
|
|
}
|
|
|
|
tests_run()
|
|
{
|
|
local current_test
|
|
|
|
for current_test in ${TESTS:-$ALL_TESTS}; do
|
|
$current_test
|
|
done
|
|
}
|
|
|
|
multipath_eval()
|
|
{
|
|
local desc="$1"
|
|
local weight_rp12=$2
|
|
local weight_rp13=$3
|
|
local packets_rp12=$4
|
|
local packets_rp13=$5
|
|
local weights_ratio packets_ratio diff
|
|
|
|
RET=0
|
|
|
|
if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
|
|
weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
|
|
| bc -l)
|
|
else
|
|
weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
|
|
| bc -l)
|
|
fi
|
|
|
|
if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
|
|
check_err 1 "Packet difference is 0"
|
|
log_test "Multipath"
|
|
log_info "Expected ratio $weights_ratio"
|
|
return
|
|
fi
|
|
|
|
if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
|
|
packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
|
|
| bc -l)
|
|
else
|
|
packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
|
|
| bc -l)
|
|
fi
|
|
|
|
diff=$(echo $weights_ratio - $packets_ratio | bc -l)
|
|
diff=${diff#-}
|
|
|
|
test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
|
|
check_err $? "Too large discrepancy between expected and measured ratios"
|
|
log_test "$desc"
|
|
log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
|
|
}
|
|
|
|
in_ns()
|
|
{
|
|
local name=$1; shift
|
|
|
|
ip netns exec $name bash <<-EOF
|
|
NUM_NETIFS=0
|
|
source lib.sh
|
|
$(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
|
|
EOF
|
|
}
|
|
|
|
##############################################################################
|
|
# Tests
|
|
|
|
ping_do()
|
|
{
|
|
local if_name=$1
|
|
local dip=$2
|
|
local args=$3
|
|
local vrf_name
|
|
|
|
vrf_name=$(master_name_get $if_name)
|
|
ip vrf exec $vrf_name \
|
|
$PING $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null
|
|
}
|
|
|
|
ping_test()
|
|
{
|
|
RET=0
|
|
|
|
ping_do $1 $2
|
|
check_err $?
|
|
log_test "ping$3"
|
|
}
|
|
|
|
ping6_do()
|
|
{
|
|
local if_name=$1
|
|
local dip=$2
|
|
local args=$3
|
|
local vrf_name
|
|
|
|
vrf_name=$(master_name_get $if_name)
|
|
ip vrf exec $vrf_name \
|
|
$PING6 $args $dip -c 10 -i 0.1 -w $PING_TIMEOUT &> /dev/null
|
|
}
|
|
|
|
ping6_test()
|
|
{
|
|
RET=0
|
|
|
|
ping6_do $1 $2
|
|
check_err $?
|
|
log_test "ping6$3"
|
|
}
|
|
|
|
learning_test()
|
|
{
|
|
local bridge=$1
|
|
local br_port1=$2 # Connected to `host1_if`.
|
|
local host1_if=$3
|
|
local host2_if=$4
|
|
local mac=de:ad:be:ef:13:37
|
|
local ageing_time
|
|
|
|
RET=0
|
|
|
|
bridge -j fdb show br $bridge brport $br_port1 \
|
|
| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
|
|
check_fail $? "Found FDB record when should not"
|
|
|
|
# Disable unknown unicast flooding on `br_port1` to make sure
|
|
# packets are only forwarded through the port after a matching
|
|
# FDB entry was installed.
|
|
bridge link set dev $br_port1 flood off
|
|
|
|
ip link set $host1_if promisc on
|
|
tc qdisc add dev $host1_if ingress
|
|
tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
|
|
flower dst_mac $mac action drop
|
|
|
|
$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
|
|
sleep 1
|
|
|
|
tc -j -s filter show dev $host1_if ingress \
|
|
| jq -e ".[] | select(.options.handle == 101) \
|
|
| select(.options.actions[0].stats.packets == 1)" &> /dev/null
|
|
check_fail $? "Packet reached first host when should not"
|
|
|
|
$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
|
|
sleep 1
|
|
|
|
bridge -j fdb show br $bridge brport $br_port1 \
|
|
| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
|
|
check_err $? "Did not find FDB record when should"
|
|
|
|
$MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
|
|
sleep 1
|
|
|
|
tc -j -s filter show dev $host1_if ingress \
|
|
| jq -e ".[] | select(.options.handle == 101) \
|
|
| select(.options.actions[0].stats.packets == 1)" &> /dev/null
|
|
check_err $? "Packet did not reach second host when should"
|
|
|
|
# Wait for 10 seconds after the ageing time to make sure FDB
|
|
# record was aged-out.
|
|
ageing_time=$(bridge_ageing_time_get $bridge)
|
|
sleep $((ageing_time + 10))
|
|
|
|
bridge -j fdb show br $bridge brport $br_port1 \
|
|
| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
|
|
check_fail $? "Found FDB record when should not"
|
|
|
|
bridge link set dev $br_port1 learning off
|
|
|
|
$MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
|
|
sleep 1
|
|
|
|
bridge -j fdb show br $bridge brport $br_port1 \
|
|
| jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
|
|
check_fail $? "Found FDB record when should not"
|
|
|
|
bridge link set dev $br_port1 learning on
|
|
|
|
tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
|
|
tc qdisc del dev $host1_if ingress
|
|
ip link set $host1_if promisc off
|
|
|
|
bridge link set dev $br_port1 flood on
|
|
|
|
log_test "FDB learning"
|
|
}
|
|
|
|
flood_test_do()
|
|
{
|
|
local should_flood=$1
|
|
local mac=$2
|
|
local ip=$3
|
|
local host1_if=$4
|
|
local host2_if=$5
|
|
local err=0
|
|
|
|
# Add an ACL on `host2_if` which will tell us whether the packet
|
|
# was flooded to it or not.
|
|
ip link set $host2_if promisc on
|
|
tc qdisc add dev $host2_if ingress
|
|
tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
|
|
flower dst_mac $mac action drop
|
|
|
|
$MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
|
|
sleep 1
|
|
|
|
tc -j -s filter show dev $host2_if ingress \
|
|
| jq -e ".[] | select(.options.handle == 101) \
|
|
| select(.options.actions[0].stats.packets == 1)" &> /dev/null
|
|
if [[ $? -ne 0 && $should_flood == "true" || \
|
|
$? -eq 0 && $should_flood == "false" ]]; then
|
|
err=1
|
|
fi
|
|
|
|
tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
|
|
tc qdisc del dev $host2_if ingress
|
|
ip link set $host2_if promisc off
|
|
|
|
return $err
|
|
}
|
|
|
|
flood_unicast_test()
|
|
{
|
|
local br_port=$1
|
|
local host1_if=$2
|
|
local host2_if=$3
|
|
local mac=de:ad:be:ef:13:37
|
|
local ip=192.0.2.100
|
|
|
|
RET=0
|
|
|
|
bridge link set dev $br_port flood off
|
|
|
|
flood_test_do false $mac $ip $host1_if $host2_if
|
|
check_err $? "Packet flooded when should not"
|
|
|
|
bridge link set dev $br_port flood on
|
|
|
|
flood_test_do true $mac $ip $host1_if $host2_if
|
|
check_err $? "Packet was not flooded when should"
|
|
|
|
log_test "Unknown unicast flood"
|
|
}
|
|
|
|
flood_multicast_test()
|
|
{
|
|
local br_port=$1
|
|
local host1_if=$2
|
|
local host2_if=$3
|
|
local mac=01:00:5e:00:00:01
|
|
local ip=239.0.0.1
|
|
|
|
RET=0
|
|
|
|
bridge link set dev $br_port mcast_flood off
|
|
|
|
flood_test_do false $mac $ip $host1_if $host2_if
|
|
check_err $? "Packet flooded when should not"
|
|
|
|
bridge link set dev $br_port mcast_flood on
|
|
|
|
flood_test_do true $mac $ip $host1_if $host2_if
|
|
check_err $? "Packet was not flooded when should"
|
|
|
|
log_test "Unregistered multicast flood"
|
|
}
|
|
|
|
flood_test()
|
|
{
|
|
# `br_port` is connected to `host2_if`
|
|
local br_port=$1
|
|
local host1_if=$2
|
|
local host2_if=$3
|
|
|
|
flood_unicast_test $br_port $host1_if $host2_if
|
|
flood_multicast_test $br_port $host1_if $host2_if
|
|
}
|
|
|
|
__start_traffic()
|
|
{
|
|
local proto=$1; shift
|
|
local h_in=$1; shift # Where the traffic egresses the host
|
|
local sip=$1; shift
|
|
local dip=$1; shift
|
|
local dmac=$1; shift
|
|
|
|
$MZ $h_in -p 8000 -A $sip -B $dip -c 0 \
|
|
-a own -b $dmac -t "$proto" -q "$@" &
|
|
sleep 1
|
|
}
|
|
|
|
start_traffic()
|
|
{
|
|
__start_traffic udp "$@"
|
|
}
|
|
|
|
start_tcp_traffic()
|
|
{
|
|
__start_traffic tcp "$@"
|
|
}
|
|
|
|
stop_traffic()
|
|
{
|
|
# Suppress noise from killing mausezahn.
|
|
{ kill %% && wait %%; } 2>/dev/null
|
|
}
|
|
|
|
tcpdump_start()
|
|
{
|
|
local if_name=$1; shift
|
|
local ns=$1; shift
|
|
|
|
capfile=$(mktemp)
|
|
capout=$(mktemp)
|
|
|
|
if [ -z $ns ]; then
|
|
ns_cmd=""
|
|
else
|
|
ns_cmd="ip netns exec ${ns}"
|
|
fi
|
|
|
|
if [ -z $SUDO_USER ] ; then
|
|
capuser=""
|
|
else
|
|
capuser="-Z $SUDO_USER"
|
|
fi
|
|
|
|
$ns_cmd tcpdump -e -n -Q in -i $if_name \
|
|
-s 65535 -B 32768 $capuser -w $capfile > "$capout" 2>&1 &
|
|
cappid=$!
|
|
|
|
sleep 1
|
|
}
|
|
|
|
tcpdump_stop()
|
|
{
|
|
$ns_cmd kill $cappid
|
|
sleep 1
|
|
}
|
|
|
|
tcpdump_cleanup()
|
|
{
|
|
rm $capfile $capout
|
|
}
|
|
|
|
tcpdump_show()
|
|
{
|
|
tcpdump -e -n -r $capfile 2>&1
|
|
}
|
|
|
|
# return 0 if the packet wasn't seen on host2_if or 1 if it was
|
|
mcast_packet_test()
|
|
{
|
|
local mac=$1
|
|
local src_ip=$2
|
|
local ip=$3
|
|
local host1_if=$4
|
|
local host2_if=$5
|
|
local seen=0
|
|
local tc_proto="ip"
|
|
local mz_v6arg=""
|
|
|
|
# basic check to see if we were passed an IPv4 address, if not assume IPv6
|
|
if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
|
tc_proto="ipv6"
|
|
mz_v6arg="-6"
|
|
fi
|
|
|
|
# Add an ACL on `host2_if` which will tell us whether the packet
|
|
# was received by it or not.
|
|
tc qdisc add dev $host2_if ingress
|
|
tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
|
|
flower ip_proto udp dst_mac $mac action drop
|
|
|
|
$MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
|
|
sleep 1
|
|
|
|
tc -j -s filter show dev $host2_if ingress \
|
|
| jq -e ".[] | select(.options.handle == 101) \
|
|
| select(.options.actions[0].stats.packets == 1)" &> /dev/null
|
|
if [[ $? -eq 0 ]]; then
|
|
seen=1
|
|
fi
|
|
|
|
tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
|
|
tc qdisc del dev $host2_if ingress
|
|
|
|
return $seen
|
|
}
|
|
|
|
brmcast_check_sg_entries()
|
|
{
|
|
local report=$1; shift
|
|
local slist=("$@")
|
|
local sarg=""
|
|
|
|
for src in "${slist[@]}"; do
|
|
sarg="${sarg} and .source_list[].address == \"$src\""
|
|
done
|
|
bridge -j -d -s mdb show dev br0 \
|
|
| jq -e ".[].mdb[] | \
|
|
select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
|
|
check_err $? "Wrong *,G entry source list after $report report"
|
|
|
|
for sgent in "${slist[@]}"; do
|
|
bridge -j -d -s mdb show dev br0 \
|
|
| jq -e ".[].mdb[] | \
|
|
select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
|
|
check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
|
|
done
|
|
}
|
|
|
|
brmcast_check_sg_fwding()
|
|
{
|
|
local should_fwd=$1; shift
|
|
local sources=("$@")
|
|
|
|
for src in "${sources[@]}"; do
|
|
local retval=0
|
|
|
|
mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
|
|
retval=$?
|
|
if [ $should_fwd -eq 1 ]; then
|
|
check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
|
|
else
|
|
check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
|
|
fi
|
|
done
|
|
}
|
|
|
|
brmcast_check_sg_state()
|
|
{
|
|
local is_blocked=$1; shift
|
|
local sources=("$@")
|
|
local should_fail=1
|
|
|
|
if [ $is_blocked -eq 1 ]; then
|
|
should_fail=0
|
|
fi
|
|
|
|
for src in "${sources[@]}"; do
|
|
bridge -j -d -s mdb show dev br0 \
|
|
| jq -e ".[].mdb[] | \
|
|
select(.grp == \"$TEST_GROUP\" and .source_list != null) |
|
|
.source_list[] |
|
|
select(.address == \"$src\") |
|
|
select(.timer == \"0.00\")" &>/dev/null
|
|
check_err_fail $should_fail $? "Entry $src has zero timer"
|
|
|
|
bridge -j -d -s mdb show dev br0 \
|
|
| jq -e ".[].mdb[] | \
|
|
select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
|
|
.flags[] == \"blocked\")" &>/dev/null
|
|
check_err_fail $should_fail $? "Entry $src has blocked flag"
|
|
done
|
|
}
|