| #!/bin/bash |
| # SPDX-License-Identifier: GPL-2.0 |
| |
| # IPv4 and IPv6 onlink tests |
| |
| PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no} |
| VERBOSE=0 |
| |
| # Network interfaces |
| # - odd in current namespace; even in peer ns |
| declare -A NETIFS |
| # default VRF |
| NETIFS[p1]=veth1 |
| NETIFS[p2]=veth2 |
| NETIFS[p3]=veth3 |
| NETIFS[p4]=veth4 |
| # VRF |
| NETIFS[p5]=veth5 |
| NETIFS[p6]=veth6 |
| NETIFS[p7]=veth7 |
| NETIFS[p8]=veth8 |
| |
| # /24 network |
| declare -A V4ADDRS |
| V4ADDRS[p1]=169.254.1.1 |
| V4ADDRS[p2]=169.254.1.2 |
| V4ADDRS[p3]=169.254.3.1 |
| V4ADDRS[p4]=169.254.3.2 |
| V4ADDRS[p5]=169.254.5.1 |
| V4ADDRS[p6]=169.254.5.2 |
| V4ADDRS[p7]=169.254.7.1 |
| V4ADDRS[p8]=169.254.7.2 |
| |
| # /64 network |
| declare -A V6ADDRS |
| V6ADDRS[p1]=2001:db8:101::1 |
| V6ADDRS[p2]=2001:db8:101::2 |
| V6ADDRS[p3]=2001:db8:301::1 |
| V6ADDRS[p4]=2001:db8:301::2 |
| V6ADDRS[p5]=2001:db8:501::1 |
| V6ADDRS[p6]=2001:db8:501::2 |
| V6ADDRS[p7]=2001:db8:701::1 |
| V6ADDRS[p8]=2001:db8:701::2 |
| |
| # Test networks: |
| # [1] = default table |
| # [2] = VRF |
| # |
| # /32 host routes |
| declare -A TEST_NET4 |
| TEST_NET4[1]=169.254.101 |
| TEST_NET4[2]=169.254.102 |
| # /128 host routes |
| declare -A TEST_NET6 |
| TEST_NET6[1]=2001:db8:101 |
| TEST_NET6[2]=2001:db8:102 |
| |
| # connected gateway |
| CONGW[1]=169.254.1.254 |
| CONGW[2]=169.254.3.254 |
| CONGW[3]=169.254.5.254 |
| |
| # recursive gateway |
| RECGW4[1]=169.254.11.254 |
| RECGW4[2]=169.254.12.254 |
| RECGW6[1]=2001:db8:11::64 |
| RECGW6[2]=2001:db8:12::64 |
| |
| # for v4 mapped to v6 |
| declare -A TEST_NET4IN6IN6 |
| TEST_NET4IN6[1]=10.1.1.254 |
| TEST_NET4IN6[2]=10.2.1.254 |
| |
| # mcast address |
| MCAST6=ff02::1 |
| |
| |
| PEER_NS=bart |
| PEER_CMD="ip netns exec ${PEER_NS}" |
| VRF=lisa |
| VRF_TABLE=1101 |
| PBR_TABLE=101 |
| |
| ################################################################################ |
| # utilities |
| |
| log_test() |
| { |
| local rc=$1 |
| local expected=$2 |
| local msg="$3" |
| |
| if [ ${rc} -eq ${expected} ]; then |
| nsuccess=$((nsuccess+1)) |
| printf " TEST: %-50s [ OK ]\n" "${msg}" |
| else |
| nfail=$((nfail+1)) |
| printf " TEST: %-50s [FAIL]\n" "${msg}" |
| if [ "${PAUSE_ON_FAIL}" = "yes" ]; then |
| echo |
| echo "hit enter to continue, 'q' to quit" |
| read a |
| [ "$a" = "q" ] && exit 1 |
| fi |
| fi |
| } |
| |
| log_section() |
| { |
| echo |
| echo "######################################################################" |
| echo "TEST SECTION: $*" |
| echo "######################################################################" |
| } |
| |
| log_subsection() |
| { |
| echo |
| echo "#########################################" |
| echo "TEST SUBSECTION: $*" |
| } |
| |
| run_cmd() |
| { |
| local cmd="$*" |
| local out |
| local rc |
| |
| if [ "$VERBOSE" = "1" ]; then |
| printf " COMMAND: $cmd\n" |
| fi |
| |
| out=$(eval $cmd 2>&1) |
| rc=$? |
| if [ "$VERBOSE" = "1" -a -n "$out" ]; then |
| echo " $out" |
| fi |
| |
| [ "$VERBOSE" = "1" ] && echo |
| |
| return $rc |
| } |
| |
| get_linklocal() |
| { |
| local dev=$1 |
| local pfx |
| local addr |
| |
| addr=$(${pfx} ip -6 -br addr show dev ${dev} | \ |
| awk '{ |
| for (i = 3; i <= NF; ++i) { |
| if ($i ~ /^fe80/) |
| print $i |
| } |
| }' |
| ) |
| addr=${addr/\/*} |
| |
| [ -z "$addr" ] && return 1 |
| |
| echo $addr |
| |
| return 0 |
| } |
| |
| ################################################################################ |
| # |
| |
| setup() |
| { |
| echo |
| echo "########################################" |
| echo "Configuring interfaces" |
| |
| set -e |
| |
| # create namespace |
| ip netns add ${PEER_NS} |
| ip -netns ${PEER_NS} li set lo up |
| |
| # add vrf table |
| ip li add ${VRF} type vrf table ${VRF_TABLE} |
| ip li set ${VRF} up |
| ip ro add table ${VRF_TABLE} unreachable default metric 8192 |
| ip -6 ro add table ${VRF_TABLE} unreachable default metric 8192 |
| |
| # create test interfaces |
| ip li add ${NETIFS[p1]} type veth peer name ${NETIFS[p2]} |
| ip li add ${NETIFS[p3]} type veth peer name ${NETIFS[p4]} |
| ip li add ${NETIFS[p5]} type veth peer name ${NETIFS[p6]} |
| ip li add ${NETIFS[p7]} type veth peer name ${NETIFS[p8]} |
| |
| # enslave vrf interfaces |
| for n in 5 7; do |
| ip li set ${NETIFS[p${n}]} vrf ${VRF} |
| done |
| |
| # add addresses |
| for n in 1 3 5 7; do |
| ip li set ${NETIFS[p${n}]} up |
| ip addr add ${V4ADDRS[p${n}]}/24 dev ${NETIFS[p${n}]} |
| ip addr add ${V6ADDRS[p${n}]}/64 dev ${NETIFS[p${n}]} nodad |
| done |
| |
| # move peer interfaces to namespace and add addresses |
| for n in 2 4 6 8; do |
| ip li set ${NETIFS[p${n}]} netns ${PEER_NS} up |
| ip -netns ${PEER_NS} addr add ${V4ADDRS[p${n}]}/24 dev ${NETIFS[p${n}]} |
| ip -netns ${PEER_NS} addr add ${V6ADDRS[p${n}]}/64 dev ${NETIFS[p${n}]} nodad |
| done |
| |
| ip -6 ro add default via ${V6ADDRS[p3]/::[0-9]/::64} |
| ip -6 ro add table ${VRF_TABLE} default via ${V6ADDRS[p7]/::[0-9]/::64} |
| |
| set +e |
| } |
| |
| cleanup() |
| { |
| # make sure we start from a clean slate |
| ip netns del ${PEER_NS} 2>/dev/null |
| for n in 1 3 5 7; do |
| ip link del ${NETIFS[p${n}]} 2>/dev/null |
| done |
| ip link del ${VRF} 2>/dev/null |
| ip ro flush table ${VRF_TABLE} |
| ip -6 ro flush table ${VRF_TABLE} |
| } |
| |
| ################################################################################ |
| # IPv4 tests |
| # |
| |
| run_ip() |
| { |
| local table="$1" |
| local prefix="$2" |
| local gw="$3" |
| local dev="$4" |
| local exp_rc="$5" |
| local desc="$6" |
| |
| # dev arg may be empty |
| [ -n "${dev}" ] && dev="dev ${dev}" |
| |
| run_cmd ip ro add table "${table}" "${prefix}"/32 via "${gw}" "${dev}" onlink |
| log_test $? ${exp_rc} "${desc}" |
| } |
| |
| run_ip_mpath() |
| { |
| local table="$1" |
| local prefix="$2" |
| local nh1="$3" |
| local nh2="$4" |
| local exp_rc="$5" |
| local desc="$6" |
| |
| # dev arg may be empty |
| [ -n "${dev}" ] && dev="dev ${dev}" |
| |
| run_cmd ip ro add table "${table}" "${prefix}"/32 \ |
| nexthop via ${nh1} nexthop via ${nh2} |
| log_test $? ${exp_rc} "${desc}" |
| } |
| |
| valid_onlink_ipv4() |
| { |
| # - unicast connected, unicast recursive |
| # |
| log_subsection "default VRF - main table" |
| |
| run_ip 254 ${TEST_NET4[1]}.1 ${CONGW[1]} ${NETIFS[p1]} 0 "unicast connected" |
| run_ip 254 ${TEST_NET4[1]}.2 ${RECGW4[1]} ${NETIFS[p1]} 0 "unicast recursive" |
| |
| log_subsection "VRF ${VRF}" |
| |
| run_ip ${VRF_TABLE} ${TEST_NET4[2]}.1 ${CONGW[3]} ${NETIFS[p5]} 0 "unicast connected" |
| run_ip ${VRF_TABLE} ${TEST_NET4[2]}.2 ${RECGW4[2]} ${NETIFS[p5]} 0 "unicast recursive" |
| |
| log_subsection "VRF device, PBR table" |
| |
| run_ip ${PBR_TABLE} ${TEST_NET4[2]}.3 ${CONGW[3]} ${NETIFS[p5]} 0 "unicast connected" |
| run_ip ${PBR_TABLE} ${TEST_NET4[2]}.4 ${RECGW4[2]} ${NETIFS[p5]} 0 "unicast recursive" |
| |
| # multipath version |
| # |
| log_subsection "default VRF - main table - multipath" |
| |
| run_ip_mpath 254 ${TEST_NET4[1]}.5 \ |
| "${CONGW[1]} dev ${NETIFS[p1]} onlink" \ |
| "${CONGW[2]} dev ${NETIFS[p3]} onlink" \ |
| 0 "unicast connected - multipath" |
| |
| run_ip_mpath 254 ${TEST_NET4[1]}.6 \ |
| "${RECGW4[1]} dev ${NETIFS[p1]} onlink" \ |
| "${RECGW4[2]} dev ${NETIFS[p3]} onlink" \ |
| 0 "unicast recursive - multipath" |
| |
| run_ip_mpath 254 ${TEST_NET4[1]}.7 \ |
| "${CONGW[1]} dev ${NETIFS[p1]}" \ |
| "${CONGW[2]} dev ${NETIFS[p3]} onlink" \ |
| 0 "unicast connected - multipath onlink first only" |
| |
| run_ip_mpath 254 ${TEST_NET4[1]}.8 \ |
| "${CONGW[1]} dev ${NETIFS[p1]} onlink" \ |
| "${CONGW[2]} dev ${NETIFS[p3]}" \ |
| 0 "unicast connected - multipath onlink second only" |
| } |
| |
| invalid_onlink_ipv4() |
| { |
| run_ip 254 ${TEST_NET4[1]}.11 ${V4ADDRS[p1]} ${NETIFS[p1]} 2 \ |
| "Invalid gw - local unicast address" |
| |
| run_ip ${VRF_TABLE} ${TEST_NET4[2]}.11 ${V4ADDRS[p5]} ${NETIFS[p5]} 2 \ |
| "Invalid gw - local unicast address, VRF" |
| |
| run_ip 254 ${TEST_NET4[1]}.101 ${V4ADDRS[p1]} "" 2 "No nexthop device given" |
| |
| run_ip 254 ${TEST_NET4[1]}.102 ${V4ADDRS[p3]} ${NETIFS[p1]} 2 \ |
| "Gateway resolves to wrong nexthop device" |
| |
| run_ip ${VRF_TABLE} ${TEST_NET4[2]}.103 ${V4ADDRS[p7]} ${NETIFS[p5]} 2 \ |
| "Gateway resolves to wrong nexthop device - VRF" |
| } |
| |
| ################################################################################ |
| # IPv6 tests |
| # |
| |
| run_ip6() |
| { |
| local table="$1" |
| local prefix="$2" |
| local gw="$3" |
| local dev="$4" |
| local exp_rc="$5" |
| local desc="$6" |
| |
| # dev arg may be empty |
| [ -n "${dev}" ] && dev="dev ${dev}" |
| |
| run_cmd ip -6 ro add table "${table}" "${prefix}"/128 via "${gw}" "${dev}" onlink |
| log_test $? ${exp_rc} "${desc}" |
| } |
| |
| run_ip6_mpath() |
| { |
| local table="$1" |
| local prefix="$2" |
| local opts="$3" |
| local nh1="$4" |
| local nh2="$5" |
| local exp_rc="$6" |
| local desc="$7" |
| |
| run_cmd ip -6 ro add table "${table}" "${prefix}"/128 "${opts}" \ |
| nexthop via ${nh1} nexthop via ${nh2} |
| log_test $? ${exp_rc} "${desc}" |
| } |
| |
| valid_onlink_ipv6() |
| { |
| # - unicast connected, unicast recursive, v4-mapped |
| # |
| log_subsection "default VRF - main table" |
| |
| run_ip6 254 ${TEST_NET6[1]}::1 ${V6ADDRS[p1]/::*}::64 ${NETIFS[p1]} 0 "unicast connected" |
| run_ip6 254 ${TEST_NET6[1]}::2 ${RECGW6[1]} ${NETIFS[p1]} 0 "unicast recursive" |
| run_ip6 254 ${TEST_NET6[1]}::3 ::ffff:${TEST_NET4IN6[1]} ${NETIFS[p1]} 0 "v4-mapped" |
| |
| log_subsection "VRF ${VRF}" |
| |
| run_ip6 ${VRF_TABLE} ${TEST_NET6[2]}::1 ${V6ADDRS[p5]/::*}::64 ${NETIFS[p5]} 0 "unicast connected" |
| run_ip6 ${VRF_TABLE} ${TEST_NET6[2]}::2 ${RECGW6[2]} ${NETIFS[p5]} 0 "unicast recursive" |
| run_ip6 ${VRF_TABLE} ${TEST_NET6[2]}::3 ::ffff:${TEST_NET4IN6[2]} ${NETIFS[p5]} 0 "v4-mapped" |
| |
| log_subsection "VRF device, PBR table" |
| |
| run_ip6 ${PBR_TABLE} ${TEST_NET6[2]}::4 ${V6ADDRS[p5]/::*}::64 ${NETIFS[p5]} 0 "unicast connected" |
| run_ip6 ${PBR_TABLE} ${TEST_NET6[2]}::5 ${RECGW6[2]} ${NETIFS[p5]} 0 "unicast recursive" |
| run_ip6 ${PBR_TABLE} ${TEST_NET6[2]}::6 ::ffff:${TEST_NET4IN6[2]} ${NETIFS[p5]} 0 "v4-mapped" |
| |
| # multipath version |
| # |
| log_subsection "default VRF - main table - multipath" |
| |
| run_ip6_mpath 254 ${TEST_NET6[1]}::4 "onlink" \ |
| "${V6ADDRS[p1]/::*}::64 dev ${NETIFS[p1]}" \ |
| "${V6ADDRS[p3]/::*}::64 dev ${NETIFS[p3]}" \ |
| 0 "unicast connected - multipath onlink" |
| |
| run_ip6_mpath 254 ${TEST_NET6[1]}::5 "onlink" \ |
| "${RECGW6[1]} dev ${NETIFS[p1]}" \ |
| "${RECGW6[2]} dev ${NETIFS[p3]}" \ |
| 0 "unicast recursive - multipath onlink" |
| |
| run_ip6_mpath 254 ${TEST_NET6[1]}::6 "onlink" \ |
| "::ffff:${TEST_NET4IN6[1]} dev ${NETIFS[p1]}" \ |
| "::ffff:${TEST_NET4IN6[2]} dev ${NETIFS[p3]}" \ |
| 0 "v4-mapped - multipath onlink" |
| |
| run_ip6_mpath 254 ${TEST_NET6[1]}::7 "" \ |
| "${V6ADDRS[p1]/::*}::64 dev ${NETIFS[p1]} onlink" \ |
| "${V6ADDRS[p3]/::*}::64 dev ${NETIFS[p3]} onlink" \ |
| 0 "unicast connected - multipath onlink both nexthops" |
| |
| run_ip6_mpath 254 ${TEST_NET6[1]}::8 "" \ |
| "${V6ADDRS[p1]/::*}::64 dev ${NETIFS[p1]} onlink" \ |
| "${V6ADDRS[p3]/::*}::64 dev ${NETIFS[p3]}" \ |
| 0 "unicast connected - multipath onlink first only" |
| |
| run_ip6_mpath 254 ${TEST_NET6[1]}::9 "" \ |
| "${V6ADDRS[p1]/::*}::64 dev ${NETIFS[p1]}" \ |
| "${V6ADDRS[p3]/::*}::64 dev ${NETIFS[p3]} onlink" \ |
| 0 "unicast connected - multipath onlink second only" |
| } |
| |
| invalid_onlink_ipv6() |
| { |
| local lladdr |
| |
| lladdr=$(get_linklocal ${NETIFS[p1]}) || return 1 |
| |
| run_ip6 254 ${TEST_NET6[1]}::11 ${V6ADDRS[p1]} ${NETIFS[p1]} 2 \ |
| "Invalid gw - local unicast address" |
| run_ip6 254 ${TEST_NET6[1]}::12 ${lladdr} ${NETIFS[p1]} 2 \ |
| "Invalid gw - local linklocal address" |
| run_ip6 254 ${TEST_NET6[1]}::12 ${MCAST6} ${NETIFS[p1]} 2 \ |
| "Invalid gw - multicast address" |
| |
| lladdr=$(get_linklocal ${NETIFS[p5]}) || return 1 |
| run_ip6 ${VRF_TABLE} ${TEST_NET6[2]}::11 ${V6ADDRS[p5]} ${NETIFS[p5]} 2 \ |
| "Invalid gw - local unicast address, VRF" |
| run_ip6 ${VRF_TABLE} ${TEST_NET6[2]}::12 ${lladdr} ${NETIFS[p5]} 2 \ |
| "Invalid gw - local linklocal address, VRF" |
| run_ip6 ${VRF_TABLE} ${TEST_NET6[2]}::12 ${MCAST6} ${NETIFS[p5]} 2 \ |
| "Invalid gw - multicast address, VRF" |
| |
| run_ip6 254 ${TEST_NET6[1]}::101 ${V6ADDRS[p1]} "" 2 \ |
| "No nexthop device given" |
| |
| # default VRF validation is done against LOCAL table |
| # run_ip6 254 ${TEST_NET6[1]}::102 ${V6ADDRS[p3]/::[0-9]/::64} ${NETIFS[p1]} 2 \ |
| # "Gateway resolves to wrong nexthop device" |
| |
| run_ip6 ${VRF_TABLE} ${TEST_NET6[2]}::103 ${V6ADDRS[p7]/::[0-9]/::64} ${NETIFS[p5]} 2 \ |
| "Gateway resolves to wrong nexthop device - VRF" |
| } |
| |
| run_onlink_tests() |
| { |
| log_section "IPv4 onlink" |
| log_subsection "Valid onlink commands" |
| valid_onlink_ipv4 |
| log_subsection "Invalid onlink commands" |
| invalid_onlink_ipv4 |
| |
| log_section "IPv6 onlink" |
| log_subsection "Valid onlink commands" |
| valid_onlink_ipv6 |
| log_subsection "Invalid onlink commands" |
| invalid_onlink_ipv6 |
| } |
| |
| ################################################################################ |
| # usage |
| |
| usage() |
| { |
| cat <<EOF |
| usage: ${0##*/} OPTS |
| |
| -p Pause on fail |
| -v verbose mode (show commands and output) |
| EOF |
| } |
| |
| ################################################################################ |
| # main |
| |
| nsuccess=0 |
| nfail=0 |
| |
| while getopts :t:pPhv o |
| do |
| case $o in |
| p) PAUSE_ON_FAIL=yes;; |
| v) VERBOSE=$(($VERBOSE + 1));; |
| h) usage; exit 0;; |
| *) usage; exit 1;; |
| esac |
| done |
| |
| cleanup |
| setup |
| run_onlink_tests |
| cleanup |
| |
| if [ "$TESTS" != "none" ]; then |
| printf "\nTests passed: %3d\n" ${nsuccess} |
| printf "Tests failed: %3d\n" ${nfail} |
| fi |