Test that ICMP error messages generated by an IPsec gateway use the correct source address (the gateway's address, not the unreachable destination).
Signed-off-by: Antony Antony <[email protected]> --- v1->v2 : add kernel selftest script v2->v3 : fix test script. The IFS, space, got mangled. v3->v4 : added MTU tests - fix test script based on reviews --- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/xfrm_state.sh | 578 ++++++++++++++++++++++ 2 files changed, 579 insertions(+) create mode 100755 tools/testing/selftests/net/xfrm_state.sh diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index afdea6d95bde..c8f4d5e19d19 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -119,6 +119,7 @@ TEST_PROGS := \ vrf_route_leaking.sh \ vrf_strict_mode_test.sh \ xfrm_policy.sh \ + xfrm_state.sh \ # end of TEST_PROGS TEST_PROGS_EXTENDED := \ diff --git a/tools/testing/selftests/net/xfrm_state.sh b/tools/testing/selftests/net/xfrm_state.sh new file mode 100755 index 000000000000..c820c68c85fb --- /dev/null +++ b/tools/testing/selftests/net/xfrm_state.sh @@ -0,0 +1,578 @@ +#!/bin/bash -e +# SPDX-License-Identifier: GPL-2.0 +# +# xfrm/IPsec tests. +# Currently implemented: +# - ICMP error source address verification (IETF RFC 4301 section 6) +# - ICMP MTU exceeded handling over IPsec tunnels. +# +# Addresses and topology: +# IPv4 prefix 10.1.c.d IPv6 prefix fc00:c::d/64 where c is the segment number +# and d is the interface identifier. +# IPv6 uses the same c:d as IPv4, and start with IPv6 prefix instead ipv4 prefix +# +# Network topology default: ns_set_v4 or ns_set_v6 +# 1.1 1.2 2.1 2.2 3.1 3.2 4.1 4.2 5.1 5.2 6.1 6.2 +# eth0 eth1 eth0 eth1 eth0 eth1 eth0 eth1 eth0 eth1 eth0 eth1 +# a -------- r1 -------- s1 -------- r2 -------- s2 -------- r3 -------- b +# a, b = Alice and Bob hosts without IPsec. +# r1, r2, r3 routers, without IPsec +# s1, s2, IPsec gateways/routers that setup tunnel(s). + +# Network topology x: IPsec gateway that generates ICMP response - ns_set_v4x or ns_set_v6x +# 1.1 1.2 2.1 2.2 3.1 3.2 4.1 4.2 5.1 5.2 +# eth0 eth1 eth0 eth1 eth0 eth1 eth0 eth1 eth0 eth1 +# a -------- r1 -------- s1 -------- r2 -------- s2 -------- b + +source lib.sh + +EXIT_ON_TEST_FAIL=no +PAUSE=no +VERBOSE=${VERBOSE:-0} + +# Name Description +tests=" + unreachable_ipv4 IPv4 unreachable from router r3 + unreachable_ipv6 IPv6 unreachable from router r3 + unreachable_gw_ipv4 IPv4 unreachable from IPsec gateway s2 + unreachable_gw_ipv6 IPv6 unreachable from IPsec gateway s2 + mtu_ipv4_s2 IPv4 MTU exceeded from IPsec gateway s2 + mtu_ipv6_s2 IPv6 MTU exceeded from IPsec gateway s2 + mtu_ipv4_r2 IPv4 MTU exceeded from ESP router r2 + mtu_ipv6_r2 IPv6 MTU exceeded from ESP router r2 + mtu_ipv4_r3 IPv4 MTU exceeded from router r3 + mtu_ipv6_r3 IPv6 MTU exceeded from router r3" + +prefix4="10.1" +prefix6="fc00" + +run_cmd_err() { + cmd="$*" + + if [ "$VERBOSE" -gt 0 ]; then + printf " COMMAND: $cmd\n" + fi + + out="$($cmd 2>&1)" && rc=0 || rc=$? + if [ "$VERBOSE" -gt 1 ] && [ -n "$out" ]; then + echo " $out" + echo + fi + return 0 +} + +run_cmd() { + run_cmd_err "$@" || exit 1 +} + +run_test() { + # If errexit is set, unset it for sub-shell and restore after test + errexit=0 + if [[ $- =~ "e" ]]; then + errexit=1 + set +e + fi + + ( + tname="$1" + tdesc="$2" + + unset IFS + + fail="yes" + + # Since cleanup() relies on variables modified by this sub shell, + # it has to run in this context. + trap 'log_test_error $?; cleanup' EXIT INT TERM + + if [ "$VERBOSE" -gt 0 ]; then + printf "\n#############################################################\n\n" + fi + + ret=0 + eval test_${tname} + ret=$? + + if [ $ret -eq 0 ]; then + fail="no" + printf "TEST: %-60s [ PASS ]\n" "${tdesc}" + elif [ $ret -eq $ksft_skip ]; then + fail="no" + printf "TEST: %-60s [SKIP]\n" "${tdesc}" + fi + + return $ret + ) + ret=$? + + [ $errexit -eq 1 ] && set -e + + case $ret in + 0) + all_skipped=false + [ $exitcode -eq $ksft_skip ] && exitcode=0 + ;; + $ksft_skip) + [ $all_skipped = true ] && exitcode=$ksft_skip + ;; + *) + all_skipped=false + exitcode=1 + ;; + esac + + return 0 # don't trigger errexit (-e); actual status in exitcode +} + +# Find the auto-generated name for this namespace +nsname() { + eval echo ns_$1 +} + +nscmd() { + eval echo "ip netns exec $1" +} + +setup_namespaces() { + local namespaces="" + + NS_A="" + NS_B="" + NS_R1="" + NS_R2="" + NS_R3="" + NS_S1="" + NS_S2="" + + local namespaces="" + for ns in ${ns_set}; do + namespaces="$namespaces NS_${ns^^}" + done + + setup_ns $namespaces + + ns_active= #ordered list of namespaces for this test. + + [ -n "${NS_A}" ] && ns_a="ip netns exec ${NS_A}" && ns_active="${ns_active} $NS_A" + [ -n "${NS_R1}" ] && ns_r1="ip netns exec ${NS_R1}" && ns_active="${ns_active} $NS_R1" + [ -n "${NS_S1}" ] && ns_s1="ip netns exec ${NS_S1}" && ns_active="${ns_active} $NS_S1" + [ -n "${NS_R2}" ] && ns_r2="ip netns exec ${NS_R2}" && ns_active="${ns_active} $NS_R2" + [ -n "${NS_S2}" ] && ns_s2="ip netns exec ${NS_S2}" && ns_active="${ns_active} $NS_S2" + [ -n "${NS_R3}" ] && ns_r3="ip netns exec ${NS_R3}" && ns_active="${ns_active} $NS_R3" + [ -n "${NS_B}" ] && ns_b="ip netns exec ${NS_B}" && ns_active="${ns_active} $NS_B" +} + +addr_add() { + local ns_cmd=$(nscmd $1) + local ip="$2" + local dev="$3" + + run_cmd ${ns_cmd} ip addr add ${ip} dev ${dev} + run_cmd ${ns_cmd} ip link set up ${dev} +} + +veth_add() { + local ns=$2 + local pns=$1 + local ns_cmd=$(nscmd ${pns}) + local ln="eth0" + local rn="eth1" + run_cmd ${ns_cmd} ip link add ${ln} type veth peer name ${rn} netns $ns +} + +setup_icmp_filter() { + local ns_cmd="${1:-${ns_r2}}" + + run_cmd ${ns_cmd} nft add table inet filter + run_cmd ${ns_cmd} nft add chain inet filter FORWARD \ + { type filter hook forward priority filter\; policy drop \; } + run_cmd ${ns_cmd} nft add rule inet filter FORWARD counter ip protocol esp \ + counter log accept + run_cmd ${ns_cmd} nft add rule inet filter FORWARD counter ip protocol \ + icmp counter log drop + + if [ "$VERBOSE" -gt 0 ]; then + run_cmd ${ns_cmd} nft list ruleset + echo "$out" + fi +} + +setup_icmpv6_filter() { + local ns_cmd=${ns_r2} + + run_cmd ${ns_cmd} nft add table inet filter + run_cmd ${ns_cmd} nft add chain inet filter FORWARD { type filter \ + hook forward priority filter\; policy drop \; } + run_cmd ${ns_cmd} nft add rule inet filter FORWARD ip6 nexthdr \ + ipv6-icmp icmpv6 type echo-request counter log drop + run_cmd ${ns_cmd} nft add rule inet filter FORWARD ip6 nexthdr esp \ + counter log accept + run_cmd ${ns_cmd} nft add rule inet filter FORWARD ip6 nexthdr \ + ipv6-icmp icmpv6 type {nd-neighbor-solicit,nd-neighbor-advert, \ + nd-router-solicit,nd-router-advert} counter log drop + if [ "$VERBOSE" -gt 0 ]; then + run_cmd ${ns_cmd} nft list ruleset + echo "$out" + fi +} + +ns_set() { + s1_src=${src} + s1_dst=${dst} + s1_src_net=${src_net} + s1_dst_net=${dst_net} +} + +setup_ns_set_v4() { + ns_set="a r1 s1 r2 s2 r3 b" # Network topology default + imax=$(echo "$ns_set" | wc -w) # number of namespaces in this topology + + src="10.1.3.1" + dst="10.1.4.2" + src_net="10.1.1.0/24" + dst_net="10.1.6.0/24" + + prefix=${prefix4} + prefix_len=24 + s="." + S="." + + ns_set +} + +setup_ns_set_v4x() { + ns_set="a r1 s1 r2 s2 b" # Network topology: x + imax=$(echo "$ns_set" | wc -w) # number of namespaces in this topology + prefix=${prefix4} + s="." + S="." + src="10.1.3.1" + dst="10.1.4.2" + src_net="10.1.1.0/24" + dst_net="10.1.5.0/24" + prefix_len=24 + + ns_set +} + +setup_ns_set_v6() { + ns_set="a r1 s1 r2 s2 r3 b" # Network topology default + imax=$(echo "$ns_set" | wc -w) # number of namespaces in this topology + prefix=${prefix6} + s=":" + S="::" + src="fc00:3::1" + dst="fc00:4::2" + src_net="fc00:1::0/64" + dst_net="fc00:6::0/64" + prefix_len=64 + + ns_set +} + +setup_ns_set_v6x() { + ns_set="a r1 s1 r2 s2 b" # Network topology: x + imax=6 + prefix=${prefix6} + s=":" + S="::" + src="fc00:3::1" + dst="fc00:4::2" + src_net="fc00:1::0/64" + dst_net="fc00:5::0/64" + prefix_len=64 + + ns_set +} + +setup_network() { + # Create veths and add addresses + i=1 + p="" + for ns in ${ns_active}; do + ns_cmd=$(nscmd ${ns}) + + if [ ${i} -ne 1 ]; then + # Create veth between previous and current namespace + veth_add ${p} ${ns} + # Add addresses: previous gets .1 on eth0, current gets .2 on eth1 + addr_add ${p} "${prefix}${s}$((i-1))${S}1/${prefix_len}" eth0 + addr_add ${ns} "${prefix}${s}$((i-1))${S}2/${prefix_len}" eth1 + fi + + # Enable forwarding + run_cmd ${ns_cmd} sysctl -q net/ipv4/ip_forward=1 + run_cmd ${ns_cmd} sysctl -q net/ipv6/conf/all/forwarding=1 + run_cmd ${ns_cmd} sysctl -q net/ipv6/conf/default/accept_dad=0 + + p=${ns} + i=$((i + 1)) + done + + # Add routes (needs all addresses to exist first) + i=1 + for ns in ${ns_active}; do + ns_cmd=$(nscmd ${ns}) + + # Forward routes to networks beyond this node + if [ ${i} -ne ${imax} ]; then + nhf="${prefix}${s}${i}${S}2" # nexthop forward + for j in $(seq $((i + 1)) ${imax}); do + run_cmd ${ns_cmd} ip route replace \ + "${prefix}${s}${j}${S}0/${prefix_len}" via ${nhf} + done + fi + + # Reverse routes to networks before this node + if [ ${i} -gt 1 ]; then + nhr="${prefix}${s}$((i-1))${S}1" # nexthop reverse + for j in $(seq 1 $((i - 2))); do + run_cmd ${ns_cmd} ip route replace \ + "${prefix}${s}${j}${S}0/${prefix_len}" via ${nhr} + done + fi + + i=$((i + 1)) + done +} + +setup_xfrm_mode() { + local MODE=${1:-tunnel} + if [ "${MODE}" != "tunnel" ] && [ "${MODE}" != "beet" ]; then + echo "xfrm mode ${MODE} not supported" + log_test_error + return 1 + fi + + run_cmd ${ns_s1} ip xfrm policy add src ${s1_src_net} dst ${s1_dst_net} dir out \ + tmpl src ${s1_src} dst ${s1_dst} proto esp reqid 1 mode ${MODE} + + # no "input" policies. we are only doing forwarding so far + + run_cmd ${ns_s1} ip xfrm policy add src ${s1_dst_net} dst ${s1_src_net} dir fwd \ + flag icmp tmpl src ${s1_dst} dst ${s1_src} proto esp reqid 2 mode ${MODE} + + run_cmd ${ns_s1} ip xfrm state add src ${s1_src} dst ${s1_dst} proto esp spi 1 \ + reqid 1 mode ${MODE} aead 'rfc4106(gcm(aes))' \ + 0x1111111111111111111111111111111111111111 96 \ + sel src ${s1_src_net} dst ${s1_dst_net} dir out + + run_cmd ${ns_s1} ip xfrm state add src ${s1_dst} dst ${s1_src} proto esp spi 2 \ + reqid 2 flag icmp replay-window 8 mode ${MODE} aead 'rfc4106(gcm(aes))' \ + 0x2222222222222222222222222222222222222222 96 \ + sel src ${s1_dst_net} dst ${s1_src_net} dir in + + run_cmd ${ns_s2} ip xfrm policy add src ${s1_dst_net} dst ${s1_src_net} dir out \ + flag icmp tmpl src ${s1_dst} dst ${s1_src} proto esp reqid 2 mode ${MODE} + + run_cmd ${ns_s2} ip xfrm policy add src ${s1_src_net} dst ${s1_dst_net} dir fwd \ + tmpl src ${s1_src} dst ${s1_dst} proto esp reqid 1 mode ${MODE} + + run_cmd ${ns_s2} ip xfrm state add src ${s1_dst} dst ${s1_src} proto esp spi 2 \ + reqid 2 mode ${MODE} aead 'rfc4106(gcm(aes))' \ + 0x2222222222222222222222222222222222222222 96 \ + sel src ${s1_dst_net} dst ${s1_src_net} dir out + + run_cmd ${ns_s2} ip xfrm state add src ${s1_src} dst ${s1_dst} proto esp spi 1 \ + reqid 1 flag icmp replay-window 8 mode ${MODE} aead 'rfc4106(gcm(aes))' \ + 0x1111111111111111111111111111111111111111 96 \ + sel src ${s1_src_net} dst ${s1_dst_net} dir in +} + +setup_xfrm() { + setup_xfrm_mode tunnel +} + +setup() { + [ "$(id -u)" -ne 0 ] && echo " need to run as root" && return $ksft_skip + + for arg; do + eval setup_${arg} || { + echo " ${arg} not supported" + return 1 + } + done +} + +pause() { + echo + echo "Pausing. Hit enter to continue" + read a +} + +log_test_error() { + if [ "${fail}" = "yes" -a -n "${tdesc}" ]; then + printf "TEST: %-60s [ FAIL ] ${name}\n" "${tdesc}" + [ -n "${cmd}" ] && echo -e "${cmd}\n" + [ -n "${out}" ] && echo -e "${out}\n" + fi +} + +cleanup() { + [[ "$PAUSE" = "always" || ( "$PAUSE" = "fail" && "$fail" = "yes" ) ]] && pause + cleanup_all_ns + [ "${EXIT_ON_TEST_FAIL}" = "yes" -a "${fail}" = "yes" ] && exit 1 +} + +test_unreachable_ipv6() { + setup ns_set_v6 namespaces network xfrm icmpv6_filter || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 fc00:6::2 + run_cmd_err ${ns_a} ping -W 5 -w 4 -c 1 fc00:6::3 + rc=0 + echo -e "$out" | grep -q -E 'From fc00:5::2 icmp_seq.* Destination' || rc=1 + return ${rc} +} + +test_unreachable_gw_ipv6() { + setup ns_set_v6x namespaces network xfrm icmpv6_filter || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 fc00:5::2 + run_cmd_err ${ns_a} ping -W 5 -w 4 -c 1 fc00:5::3 + rc=0 + echo -e "$out" | grep -q -E 'From fc00:4::2 icmp_seq.* Destination' || rc=1 + return ${rc} +} + +test_unreachable_ipv4() { + setup ns_set_v4 namespaces network icmp_filter xfrm || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 10.1.6.2 + run_cmd_err ${ns_a} ping -W 5 -w 4 -c 1 10.1.6.3 + rc=0 + echo -e "$out" | grep -q -E 'From 10.1.5.2 icmp_seq.* Destination' || rc=1 + return ${rc} +} + +test_unreachable_gw_ipv4() { + setup ns_set_v4x namespaces network icmp_filter xfrm || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 10.1.5.2 + run_cmd_err ${ns_a} ping -W 5 -w 4 -c 1 10.1.5.3 + rc=0 + echo -e "$out" | grep -q -E 'From 10.1.4.2 icmp_seq.* Destination' || rc=1 + return ${rc} +} + +test_mtu_ipv4_r2() { + setup ns_set_v4 namespaces network icmp_filter xfrm || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 10.1.6.2 + run_cmd ${ns_r2} ip route replace 10.1.3.0/24 dev eth1 src 10.1.3.2 mtu 1300 + run_cmd ${ns_r2} ip route replace 10.1.4.0/24 dev eth0 src 10.1.4.1 mtu 1300 + run_cmd ${ns_a} ping -M do -s 1300 -W 5 -w 4 -c 1 10.1.6.2 || true + rc=0 + echo -e "$out" | grep -q -E "From 10.1.2.2 icmp_seq=.* Frag needed and DF set" || rc=1 + return ${rc} +} + +test_mtu_ipv6_r2() { + setup ns_set_v6 namespaces network xfrm icmpv6_filter || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 fc00:6::2 + run_cmd ${ns_r2} ip -6 route replace fc00:3::/64 dev eth1 metric 256 src fc00:3::2 mtu 1300 + run_cmd ${ns_r2} ip -6 route replace fc00:4::/64 dev eth0 metric 256 src fc00:4::1 mtu 1300 + run_cmd ${ns_a} ping -M do -s 1300 -W 5 -w 4 -c 1 fc00:6::2 || true + rc=0 + echo -e "$out" | grep -q -E "From fc00:2::2 icmp_seq=.* Packet too big: mtu=1230" || rc=1 + return ${rc} +} + +test_mtu_ipv4_r3() { + setup ns_set_v4 namespaces network icmp_filter xfrm || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 10.1.6.2 + run_cmd ${ns_r3} ip route replace 10.1.6.0/24 dev eth0 mtu 1300 + run_cmd ${ns_a} ping -M do -s 1350 -W 5 -w 4 -c 1 10.1.6.2 || true + rc=0 + echo -e "$out" | grep -q -E "From 10.1.5.2 .* Frag needed and DF set \(mtu = 1300\)" || rc=1 + return ${rc} +} + +test_mtu_ipv4_s2() { + setup ns_set_v4x namespaces network icmp_filter xfrm || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 10.1.5.2 + run_cmd ${ns_s2} ip route replace 10.1.5.0/24 dev eth0 src 10.1.5.1 mtu 1300 + run_cmd ${ns_a} ping -M do -s 1350 -W 5 -w 4 -c 1 10.1.5.2 || true + rc=0 + echo -e "$out" | grep -q -E "From 10.1.4.2.*Frag needed and DF set \(mtu = 1300\)" || rc=1 + return ${rc} +} + +test_mtu_ipv6_s2() { + setup ns_set_v6x namespaces network xfrm icmpv6_filter || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 fc00:5::2 + run_cmd ${ns_s2} ip -6 route replace fc00:5::/64 dev eth0 metric 256 mtu 1300 + run_cmd ${ns_a} ping -M do -s 1350 -W 5 -w 4 -c 1 fc00:5::2 || true + rc=0 + echo -e "$out" | grep -q -E "From fc00:4::2.*Packet too big: mtu=1300" || rc=1 + return ${rc} +} + +test_mtu_ipv6_r3() { + setup ns_set_v6 namespaces network xfrm icmpv6_filter || return $ksft_skip + run_cmd ${ns_a} ping -W 5 -w 4 -c 1 fc00:6::2 + run_cmd ${ns_r3} ip -6 route replace fc00:6::/64 dev eth1 metric 256 mtu 1300 + run_cmd ${ns_a} ping -M do -s 1300 -W 5 -w 4 -c 1 fc00:6::2 || true + rc=0 + echo -e "$out" | grep -q -E "From fc00:5::2 icmp_seq=.* Packet too big: mtu=1300" || rc=1 + return ${rc} +} + +################################################################################ +# +usage() { + echo + echo "$0 [OPTIONS] [TEST]..." + echo "If no TEST argument is given, all tests will be run." + echo + echo -e "\t-p Pause on fail. Namespaces are kept for diagnostics" + echo -e "\t-P Pause after the test. Namespaces are kept for diagnostics" + echo -e "\t-v Verbose output. Show commands; -vv Show output also" + echo "Available tests${tests}" + exit 1 +} + +################################################################################ +# +exitcode=0 +all_skipped=true +out= +cmd= + +while getopts :epPv o; do + case $o in + e) EXIT_ON_TEST_FAIL=yes ;; + P) PAUSE=always ;; + p) PAUSE=fail ;; + v) VERBOSE=$((VERBOSE + 1)) ;; + *) usage ;; + esac +done +shift $(($OPTIND - 1)) + +IFS=$'\t\n' + +for arg; do + # Check first that all requested tests are available before running any + command -v "test_${arg}" >/dev/null || { + echo "=== Test ${arg} not found" + usage + } +done + +name="" +desc="" +fail="no" + +for t in ${tests}; do + [ "${name}" = "" ] && name="${t}" && continue + [ "${desc}" = "" ] && desc="${t}" + + run_this=1 + for arg; do + [ "${arg}" != "${arg#--*}" ] && continue + [ "${arg}" = "${name}" ] && run_this=1 && break + run_this=0 + done + if [ $run_this -eq 1 ]; then + run_test "${name}" "${desc}" + fi + name="" + desc="" +done + +exit ${exitcode} -- 2.39.5

