| #!/bin/bash | |
| # SPDX-License-Identifier: GPL-2.0 | |
| ############################################################################## | |
| # Defines | |
| # 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 1 | |
| 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 1 | |
| 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 1 | |
| 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 1 | |
| fi | |
| } | |
| if [[ "$(id -u)" -ne 0 ]]; then | |
| echo "SKIP: need root privileges" | |
| exit 0 | |
| 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 1 | |
| fi | |
| } | |
| require_command jq | |
| require_command $MZ | |
| if [[ ! -v NUM_NETIFS ]]; then | |
| echo "SKIP: importer does not define \"NUM_NETIFS\"" | |
| exit 1 | |
| 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)) | |
| 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 1 | |
| 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 ]] | |
| } | |
| 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_offload() | |
| { | |
| "$@" | grep -q offload | |
| } | |
| 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" | |
| } | |
| 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)) | |
| } | |
| 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 | |
| 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 second 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 | |
| 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. | |
| 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 | |
| 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 | |
| } |