#!/bin/bash
# SPDX-License-Identifier: GPL-2.0

# This test is designed for testing the new VRF strict_mode functionality.

# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4

ret=0

# identifies the "init" network namespace which is often called root network
# namespace.
INIT_NETNS_NAME="init"

PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}

TESTS="init testns mix"

log_test()
{
	local rc=$1
	local expected=$2
	local msg="$3"

	if [ ${rc} -eq ${expected} ]; then
		nsuccess=$((nsuccess+1))
		printf "\n    TEST: %-60s  [ OK ]\n" "${msg}"
	else
		ret=1
		nfail=$((nfail+1))
		printf "\n    TEST: %-60s  [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
}

print_log_test_results()
{
	if [ "$TESTS" != "none" ]; then
		printf "\nTests passed: %3d\n" ${nsuccess}
		printf "Tests failed: %3d\n"   ${nfail}
	fi
}

log_section()
{
	echo
	echo "################################################################################"
	echo "TEST SECTION: $*"
	echo "################################################################################"
}

ip_expand_args()
{
	local nsname=$1
	local nsarg=""

	if [ "${nsname}" != "${INIT_NETNS_NAME}" ]; then
		nsarg="-netns ${nsname}"
	fi

	echo "${nsarg}"
}

vrf_count()
{
	local nsname=$1
	local nsarg="$(ip_expand_args ${nsname})"

	ip ${nsarg} -o link show type vrf | wc -l
}

count_vrf_by_table_id()
{
	local nsname=$1
	local tableid=$2
	local nsarg="$(ip_expand_args ${nsname})"

	ip ${nsarg} -d -o link show type vrf | grep "table ${tableid}" | wc -l
}

add_vrf()
{
	local nsname=$1
	local vrfname=$2
	local vrftable=$3
	local nsarg="$(ip_expand_args ${nsname})"

	ip ${nsarg} link add ${vrfname} type vrf table ${vrftable} &>/dev/null
}

add_vrf_and_check()
{
	local nsname=$1
	local vrfname=$2
	local vrftable=$3
	local cnt
	local rc

	add_vrf ${nsname} ${vrfname} ${vrftable}; rc=$?

	cnt=$(count_vrf_by_table_id ${nsname} ${vrftable})

	log_test ${rc} 0 "${nsname}: add vrf ${vrfname}, ${cnt} vrfs for table ${vrftable}"
}

add_vrf_and_check_fail()
{
	local nsname=$1
	local vrfname=$2
	local vrftable=$3
	local cnt
	local rc

	add_vrf ${nsname} ${vrfname} ${vrftable}; rc=$?

	cnt=$(count_vrf_by_table_id ${nsname} ${vrftable})

	log_test ${rc} 2 "${nsname}: CANNOT add vrf ${vrfname}, ${cnt} vrfs for table ${vrftable}"
}

del_vrf_and_check()
{
	local nsname=$1
	local vrfname=$2
	local nsarg="$(ip_expand_args ${nsname})"

	ip ${nsarg} link del ${vrfname}
	log_test $? 0 "${nsname}: remove vrf ${vrfname}"
}

config_vrf_and_check()
{
	local nsname=$1
	local addr=$2
	local vrfname=$3
	local nsarg="$(ip_expand_args ${nsname})"

	ip ${nsarg} link set dev ${vrfname} up && \
		ip ${nsarg} addr add ${addr} dev ${vrfname}
	log_test $? 0 "${nsname}: vrf ${vrfname} up, addr ${addr}"
}

read_strict_mode()
{
	local nsname=$1
	local rval
	local rc=0
	local nsexec=""

	if [ "${nsname}" != "${INIT_NETNS_NAME}" ]; then
		# a custom network namespace is provided
		nsexec="ip netns exec ${nsname}"
	fi

	rval="$(${nsexec} bash -c "cat /proc/sys/net/vrf/strict_mode" | \
		grep -E "^[0-1]$")" &> /dev/null
	if [ $? -ne 0 ]; then
		# set errors
		rval=255
		rc=1
	fi

	# on success, rval can be only 0 or 1; on error, rval is equal to 255
	echo ${rval}
	return ${rc}
}

read_strict_mode_compare_and_check()
{
	local nsname=$1
	local expected=$2
	local res

	res="$(read_strict_mode ${nsname})"
	log_test ${res} ${expected} "${nsname}: check strict_mode=${res}"
}

set_strict_mode()
{
	local nsname=$1
	local val=$2
	local nsexec=""

	if [ "${nsname}" != "${INIT_NETNS_NAME}" ]; then
		# a custom network namespace is provided
		nsexec="ip netns exec ${nsname}"
	fi

	${nsexec} bash -c "echo ${val} >/proc/sys/net/vrf/strict_mode" &>/dev/null
}

enable_strict_mode()
{
	local nsname=$1

	set_strict_mode ${nsname} 1
}

disable_strict_mode()
{
	local nsname=$1

	set_strict_mode ${nsname} 0
}

disable_strict_mode_and_check()
{
	local nsname=$1

	disable_strict_mode ${nsname}
	log_test $? 0 "${nsname}: disable strict_mode (=0)"
}

enable_strict_mode_and_check()
{
	local nsname=$1

	enable_strict_mode ${nsname}
	log_test $? 0 "${nsname}: enable strict_mode (=1)"
}

enable_strict_mode_and_check_fail()
{
	local nsname=$1

	enable_strict_mode ${nsname}
	log_test $? 1 "${nsname}: CANNOT enable strict_mode"
}

strict_mode_check_default()
{
	local nsname=$1
	local strictmode
	local vrfcnt

	vrfcnt=$(vrf_count ${nsname})
	strictmode=$(read_strict_mode ${nsname})
	log_test ${strictmode} 0 "${nsname}: strict_mode=0 by default, ${vrfcnt} vrfs"
}

setup()
{
	modprobe vrf

	ip netns add testns
	ip netns exec testns ip link set lo up
}

cleanup()
{
	ip netns del testns 2>/dev/null

	ip link del vrf100 2>/dev/null
	ip link del vrf101 2>/dev/null
	ip link del vrf102 2>/dev/null

	echo 0 >/proc/sys/net/vrf/strict_mode 2>/dev/null
}

vrf_strict_mode_tests_init()
{
	log_section "VRF strict_mode test on init network namespace"

	vrf_strict_mode_check_support init

	strict_mode_check_default init

	add_vrf_and_check init vrf100 100
	config_vrf_and_check init 172.16.100.1/24 vrf100

	enable_strict_mode_and_check init

	add_vrf_and_check_fail init vrf101 100

	disable_strict_mode_and_check init

	add_vrf_and_check init vrf101 100
	config_vrf_and_check init 172.16.101.1/24 vrf101

	enable_strict_mode_and_check_fail init

	del_vrf_and_check init vrf101

	enable_strict_mode_and_check init

	add_vrf_and_check init vrf102 102
	config_vrf_and_check init 172.16.102.1/24 vrf102

	# the strict_modle is enabled in the init
}

vrf_strict_mode_tests_testns()
{
	log_section "VRF strict_mode test on testns network namespace"

	vrf_strict_mode_check_support testns

	strict_mode_check_default testns

	enable_strict_mode_and_check testns

	add_vrf_and_check testns vrf100 100
	config_vrf_and_check testns 10.0.100.1/24 vrf100

	add_vrf_and_check_fail testns vrf101 100

	add_vrf_and_check_fail testns vrf102 100

	add_vrf_and_check testns vrf200 200

	disable_strict_mode_and_check testns

	add_vrf_and_check testns vrf101 100

	add_vrf_and_check testns vrf102 100

	#the strict_mode is disabled in the testns
}

vrf_strict_mode_tests_mix()
{
	log_section "VRF strict_mode test mixing init and testns network namespaces"

	read_strict_mode_compare_and_check init 1

	read_strict_mode_compare_and_check testns 0

	del_vrf_and_check testns vrf101

	del_vrf_and_check testns vrf102

	disable_strict_mode_and_check init

	enable_strict_mode_and_check testns

	enable_strict_mode_and_check init
	enable_strict_mode_and_check init

	disable_strict_mode_and_check testns
	disable_strict_mode_and_check testns

	read_strict_mode_compare_and_check init 1

	read_strict_mode_compare_and_check testns 0
}

################################################################################
# usage

usage()
{
	cat <<EOF
usage: ${0##*/} OPTS

	-t <test>	Test(s) to run (default: all)
			(options: $TESTS)
EOF
}

################################################################################
# main

while getopts ":t:h" opt; do
	case $opt in
		t) TESTS=$OPTARG;;
		h) usage; exit 0;;
		*) usage; exit 1;;
	esac
done

vrf_strict_mode_check_support()
{
	local nsname=$1
	local output
	local rc

	output="$(lsmod | grep '^vrf' | awk '{print $1}')"
	if [ -z "${output}" ]; then
		modinfo vrf || return $?
	fi

	# we do not care about the value of the strict_mode; we only check if
	# the strict_mode parameter is available or not.
	read_strict_mode ${nsname} &>/dev/null; rc=$?
	log_test ${rc} 0 "${nsname}: net.vrf.strict_mode is available"

	return ${rc}
}

if [ "$(id -u)" -ne 0 ];then
	echo "SKIP: Need root privileges"
	exit $ksft_skip
fi

if [ ! -x "$(command -v ip)" ]; then
	echo "SKIP: Could not run test without ip tool"
	exit $ksft_skip
fi

modprobe vrf &>/dev/null
if [ ! -e /proc/sys/net/vrf/strict_mode ]; then
	echo "SKIP: vrf sysctl does not exist"
	exit $ksft_skip
fi

cleanup &> /dev/null

setup
for t in $TESTS
do
	case $t in
	vrf_strict_mode_tests_init|init) vrf_strict_mode_tests_init;;
	vrf_strict_mode_tests_testns|testns) vrf_strict_mode_tests_testns;;
	vrf_strict_mode_tests_mix|mix) vrf_strict_mode_tests_mix;;

	help) echo "Test names: $TESTS"; exit 0;;

	esac
done
cleanup

print_log_test_results

exit $ret