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

# End-to-end eBPF tunnel test suite
#   The script tests BPF network tunnel implementation.
#
# Topology:
# ---------
#     root namespace   |     at_ns0 namespace
#                      |
#      -----------     |     -----------
#      | tnl dev |     |     | tnl dev |  (overlay network)
#      -----------     |     -----------
#      metadata-mode   |     native-mode
#       with bpf       |
#                      |
#      ----------      |     ----------
#      |  veth1  | --------- |  veth0  |  (underlay network)
#      ----------    peer    ----------
#
#
# Device Configuration
# --------------------
# Root namespace with metadata-mode tunnel + BPF
# Device names and addresses:
# 	veth1 IP: 172.16.1.200, IPv6: 00::22 (underlay)
# 	tunnel dev <type>11, ex: gre11, IPv4: 10.1.1.200, IPv6: 1::22 (overlay)
#
# Namespace at_ns0 with native tunnel
# Device names and addresses:
# 	veth0 IPv4: 172.16.1.100, IPv6: 00::11 (underlay)
# 	tunnel dev <type>00, ex: gre00, IPv4: 10.1.1.100, IPv6: 1::11 (overlay)
#
#
# End-to-end ping packet flow
# ---------------------------
# Most of the tests start by namespace creation, device configuration,
# then ping the underlay and overlay network.  When doing 'ping 10.1.1.100'
# from root namespace, the following operations happen:
# 1) Route lookup shows 10.1.1.100/24 belongs to tnl dev, fwd to tnl dev.
# 2) Tnl device's egress BPF program is triggered and set the tunnel metadata,
#    with remote_ip=172.16.1.100 and others.
# 3) Outer tunnel header is prepended and route the packet to veth1's egress
# 4) veth0's ingress queue receive the tunneled packet at namespace at_ns0
# 5) Tunnel protocol handler, ex: vxlan_rcv, decap the packet
# 6) Forward the packet to the overlay tnl dev

BPF_FILE="test_tunnel_kern.bpf.o"
BPF_PIN_TUNNEL_DIR="/sys/fs/bpf/tc/tunnel"
PING_ARG="-c 3 -w 10 -q"
ret=0
GREEN='\033[0;92m'
RED='\033[0;31m'
NC='\033[0m' # No Color

config_device()
{
	ip netns add at_ns0
	ip link add veth0 type veth peer name veth1
	ip link set veth0 netns at_ns0
	ip netns exec at_ns0 ip addr add 172.16.1.100/24 dev veth0
	ip netns exec at_ns0 ip link set dev veth0 up
	ip link set dev veth1 up mtu 1500
	ip addr add dev veth1 172.16.1.200/24
}

add_gre_tunnel()
{
	tun_key=
	if [ -n "$1" ]; then
		tun_key="key $1"
	fi

	# at_ns0 namespace
	ip netns exec at_ns0 \
        ip link add dev $DEV_NS type $TYPE seq $tun_key \
		local 172.16.1.100 remote 172.16.1.200
	ip netns exec at_ns0 ip link set dev $DEV_NS up
	ip netns exec at_ns0 ip addr add dev $DEV_NS 10.1.1.100/24

	# root namespace
	ip link add dev $DEV type $TYPE $tun_key external
	ip link set dev $DEV up
	ip addr add dev $DEV 10.1.1.200/24
}

add_ip6gretap_tunnel()
{

	# assign ipv6 address
	ip netns exec at_ns0 ip addr add ::11/96 dev veth0
	ip netns exec at_ns0 ip link set dev veth0 up
	ip addr add dev veth1 ::22/96
	ip link set dev veth1 up

	# at_ns0 namespace
	ip netns exec at_ns0 \
		ip link add dev $DEV_NS type $TYPE seq flowlabel 0xbcdef key 2 \
		local ::11 remote ::22

	ip netns exec at_ns0 ip addr add dev $DEV_NS 10.1.1.100/24
	ip netns exec at_ns0 ip addr add dev $DEV_NS fc80::100/96
	ip netns exec at_ns0 ip link set dev $DEV_NS up

	# root namespace
	ip link add dev $DEV type $TYPE external
	ip addr add dev $DEV 10.1.1.200/24
	ip addr add dev $DEV fc80::200/24
	ip link set dev $DEV up
}

add_erspan_tunnel()
{
	# at_ns0 namespace
	if [ "$1" == "v1" ]; then
		ip netns exec at_ns0 \
		ip link add dev $DEV_NS type $TYPE seq key 2 \
		local 172.16.1.100 remote 172.16.1.200 \
		erspan_ver 1 erspan 123
	else
		ip netns exec at_ns0 \
		ip link add dev $DEV_NS type $TYPE seq key 2 \
		local 172.16.1.100 remote 172.16.1.200 \
		erspan_ver 2 erspan_dir egress erspan_hwid 3
	fi
	ip netns exec at_ns0 ip link set dev $DEV_NS up
	ip netns exec at_ns0 ip addr add dev $DEV_NS 10.1.1.100/24

	# root namespace
	ip link add dev $DEV type $TYPE external
	ip link set dev $DEV up
	ip addr add dev $DEV 10.1.1.200/24
}

add_ip6erspan_tunnel()
{

	# assign ipv6 address
	ip netns exec at_ns0 ip addr add ::11/96 dev veth0
	ip netns exec at_ns0 ip link set dev veth0 up
	ip addr add dev veth1 ::22/96
	ip link set dev veth1 up

	# at_ns0 namespace
	if [ "$1" == "v1" ]; then
		ip netns exec at_ns0 \
		ip link add dev $DEV_NS type $TYPE seq key 2 \
		local ::11 remote ::22 \
		erspan_ver 1 erspan 123
	else
		ip netns exec at_ns0 \
		ip link add dev $DEV_NS type $TYPE seq key 2 \
		local ::11 remote ::22 \
		erspan_ver 2 erspan_dir egress erspan_hwid 7
	fi
	ip netns exec at_ns0 ip addr add dev $DEV_NS 10.1.1.100/24
	ip netns exec at_ns0 ip link set dev $DEV_NS up

	# root namespace
	ip link add dev $DEV type $TYPE external
	ip addr add dev $DEV 10.1.1.200/24
	ip link set dev $DEV up
}

add_geneve_tunnel()
{
	# at_ns0 namespace
	ip netns exec at_ns0 \
		ip link add dev $DEV_NS type $TYPE \
		id 2 dstport 6081 remote 172.16.1.200
	ip netns exec at_ns0 ip link set dev $DEV_NS up
	ip netns exec at_ns0 ip addr add dev $DEV_NS 10.1.1.100/24

	# root namespace
	ip link add dev $DEV type $TYPE dstport 6081 external
	ip link set dev $DEV up
	ip addr add dev $DEV 10.1.1.200/24
}

add_ip6geneve_tunnel()
{
	ip netns exec at_ns0 ip addr add ::11/96 dev veth0
	ip netns exec at_ns0 ip link set dev veth0 up
	ip addr add dev veth1 ::22/96
	ip link set dev veth1 up

	# at_ns0 namespace
	ip netns exec at_ns0 \
		ip link add dev $DEV_NS type $TYPE id 22 \
		remote ::22     # geneve has no local option
	ip netns exec at_ns0 ip addr add dev $DEV_NS 10.1.1.100/24
	ip netns exec at_ns0 ip link set dev $DEV_NS up

	# root namespace
	ip link add dev $DEV type $TYPE external
	ip addr add dev $DEV 10.1.1.200/24
	ip link set dev $DEV up
}

add_ipip_tunnel()
{
	# at_ns0 namespace
	ip netns exec at_ns0 \
		ip link add dev $DEV_NS type $TYPE \
		local 172.16.1.100 remote 172.16.1.200
	ip netns exec at_ns0 ip link set dev $DEV_NS up
	ip netns exec at_ns0 ip addr add dev $DEV_NS 10.1.1.100/24

	# root namespace
	ip link add dev $DEV type $TYPE external
	ip link set dev $DEV up
	ip addr add dev $DEV 10.1.1.200/24
}

add_ip6tnl_tunnel()
{
	ip netns exec at_ns0 ip addr add ::11/96 dev veth0
	ip netns exec at_ns0 ip link set dev veth0 up
	ip addr add dev veth1 ::22/96
	ip link set dev veth1 up

	# at_ns0 namespace
	ip netns exec at_ns0 \
		ip link add dev $DEV_NS type $TYPE \
		local ::11 remote ::22
	ip netns exec at_ns0 ip addr add dev $DEV_NS 10.1.1.100/24
	ip netns exec at_ns0 ip addr add dev $DEV_NS 1::11/96
	ip netns exec at_ns0 ip link set dev $DEV_NS up

	# root namespace
	ip link add dev $DEV type $TYPE external
	ip addr add dev $DEV 10.1.1.200/24
	ip addr add dev $DEV 1::22/96
	ip link set dev $DEV up
}

test_gre()
{
	TYPE=gretap
	DEV_NS=gretap00
	DEV=gretap11
	ret=0

	check $TYPE
	config_device
	add_gre_tunnel 2
	attach_bpf $DEV gre_set_tunnel gre_get_tunnel
	ping $PING_ARG 10.1.1.100
	check_err $?
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	check_err $?
	cleanup

        if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: $TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: $TYPE"${NC}
}

test_gre_no_tunnel_key()
{
	TYPE=gre
	DEV_NS=gre00
	DEV=gre11
	ret=0

	check $TYPE
	config_device
	add_gre_tunnel
	attach_bpf $DEV gre_set_tunnel_no_key gre_get_tunnel
	ping $PING_ARG 10.1.1.100
	check_err $?
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	check_err $?
	cleanup

        if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: $TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: $TYPE"${NC}
}

test_ip6gre()
{
	TYPE=ip6gre
	DEV_NS=ip6gre00
	DEV=ip6gre11
	ret=0

	check $TYPE
	config_device
	# reuse the ip6gretap function
	add_ip6gretap_tunnel
	attach_bpf $DEV ip6gretap_set_tunnel ip6gretap_get_tunnel
	# underlay
	ping6 $PING_ARG ::11
	# overlay: ipv4 over ipv6
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	ping $PING_ARG 10.1.1.100
	check_err $?
	# overlay: ipv6 over ipv6
	ip netns exec at_ns0 ping6 $PING_ARG fc80::200
	check_err $?
	cleanup

        if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: $TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: $TYPE"${NC}
}

test_ip6gretap()
{
	TYPE=ip6gretap
	DEV_NS=ip6gretap00
	DEV=ip6gretap11
	ret=0

	check $TYPE
	config_device
	add_ip6gretap_tunnel
	attach_bpf $DEV ip6gretap_set_tunnel ip6gretap_get_tunnel
	# underlay
	ping6 $PING_ARG ::11
	# overlay: ipv4 over ipv6
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	ping $PING_ARG 10.1.1.100
	check_err $?
	# overlay: ipv6 over ipv6
	ip netns exec at_ns0 ping6 $PING_ARG fc80::200
	check_err $?
	cleanup

	if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: $TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: $TYPE"${NC}
}

test_erspan()
{
	TYPE=erspan
	DEV_NS=erspan00
	DEV=erspan11
	ret=0

	check $TYPE
	config_device
	add_erspan_tunnel $1
	attach_bpf $DEV erspan_set_tunnel erspan_get_tunnel
	ping $PING_ARG 10.1.1.100
	check_err $?
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	check_err $?
	cleanup

	if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: $TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: $TYPE"${NC}
}

test_ip6erspan()
{
	TYPE=ip6erspan
	DEV_NS=ip6erspan00
	DEV=ip6erspan11
	ret=0

	check $TYPE
	config_device
	add_ip6erspan_tunnel $1
	attach_bpf $DEV ip4ip6erspan_set_tunnel ip4ip6erspan_get_tunnel
	ping6 $PING_ARG ::11
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	check_err $?
	cleanup

	if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: $TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: $TYPE"${NC}
}

test_geneve()
{
	TYPE=geneve
	DEV_NS=geneve00
	DEV=geneve11
	ret=0

	check $TYPE
	config_device
	add_geneve_tunnel
	attach_bpf $DEV geneve_set_tunnel geneve_get_tunnel
	ping $PING_ARG 10.1.1.100
	check_err $?
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	check_err $?
	cleanup

	if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: $TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: $TYPE"${NC}
}

test_ip6geneve()
{
	TYPE=geneve
	DEV_NS=ip6geneve00
	DEV=ip6geneve11
	ret=0

	check $TYPE
	config_device
	add_ip6geneve_tunnel
	attach_bpf $DEV ip6geneve_set_tunnel ip6geneve_get_tunnel
	ping $PING_ARG 10.1.1.100
	check_err $?
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	check_err $?
	cleanup

	if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: ip6$TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: ip6$TYPE"${NC}
}

test_ipip()
{
	TYPE=ipip
	DEV_NS=ipip00
	DEV=ipip11
	ret=0

	check $TYPE
	config_device
	add_ipip_tunnel
	ip link set dev veth1 mtu 1500
	attach_bpf $DEV ipip_set_tunnel ipip_get_tunnel
	ping $PING_ARG 10.1.1.100
	check_err $?
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	check_err $?
	cleanup

	if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: $TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: $TYPE"${NC}
}

test_ipip6()
{
	TYPE=ip6tnl
	DEV_NS=ipip6tnl00
	DEV=ipip6tnl11
	ret=0

	check $TYPE
	config_device
	add_ip6tnl_tunnel
	ip link set dev veth1 mtu 1500
	attach_bpf $DEV ipip6_set_tunnel ipip6_get_tunnel
	# underlay
	ping6 $PING_ARG ::11
	# ip4 over ip6
	ping $PING_ARG 10.1.1.100
	check_err $?
	ip netns exec at_ns0 ping $PING_ARG 10.1.1.200
	check_err $?
	cleanup

	if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: $TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: $TYPE"${NC}
}

test_ip6ip6()
{
	TYPE=ip6tnl
	DEV_NS=ip6ip6tnl00
	DEV=ip6ip6tnl11
	ret=0

	check $TYPE
	config_device
	add_ip6tnl_tunnel
	ip link set dev veth1 mtu 1500
	attach_bpf $DEV ip6ip6_set_tunnel ip6ip6_get_tunnel
	# underlay
	ping6 $PING_ARG ::11
	# ip6 over ip6
	ping6 $PING_ARG 1::11
	check_err $?
	ip netns exec at_ns0 ping6 $PING_ARG 1::22
	check_err $?
	cleanup

	if [ $ret -ne 0 ]; then
                echo -e ${RED}"FAIL: ip6$TYPE"${NC}
                return 1
        fi
        echo -e ${GREEN}"PASS: ip6$TYPE"${NC}
}

attach_bpf()
{
	DEV=$1
	SET=$2
	GET=$3
	mkdir -p ${BPF_PIN_TUNNEL_DIR}
	bpftool prog loadall ${BPF_FILE} ${BPF_PIN_TUNNEL_DIR}/
	tc qdisc add dev $DEV clsact
	tc filter add dev $DEV egress bpf da object-pinned ${BPF_PIN_TUNNEL_DIR}/$SET
	tc filter add dev $DEV ingress bpf da object-pinned ${BPF_PIN_TUNNEL_DIR}/$GET
}

cleanup()
{
        rm -rf ${BPF_PIN_TUNNEL_DIR}

	ip netns delete at_ns0 2> /dev/null
	ip link del veth1 2> /dev/null
	ip link del ipip11 2> /dev/null
	ip link del ipip6tnl11 2> /dev/null
	ip link del ip6ip6tnl11 2> /dev/null
	ip link del gretap11 2> /dev/null
	ip link del gre11 2> /dev/null
	ip link del ip6gre11 2> /dev/null
	ip link del ip6gretap11 2> /dev/null
	ip link del geneve11 2> /dev/null
	ip link del ip6geneve11 2> /dev/null
	ip link del erspan11 2> /dev/null
	ip link del ip6erspan11 2> /dev/null
}

cleanup_exit()
{
	echo "CATCH SIGKILL or SIGINT, cleanup and exit"
	cleanup
	exit 0
}

check()
{
	ip link help 2>&1 | grep -q "\s$1\s"
	if [ $? -ne 0 ];then
		echo "SKIP $1: iproute2 not support"
	cleanup
	return 1
	fi
}

enable_debug()
{
	echo 'file ip_gre.c +p' > /sys/kernel/debug/dynamic_debug/control
	echo 'file ip6_gre.c +p' > /sys/kernel/debug/dynamic_debug/control
	echo 'file geneve.c +p' > /sys/kernel/debug/dynamic_debug/control
	echo 'file ipip.c +p' > /sys/kernel/debug/dynamic_debug/control
}

check_err()
{
	if [ $ret -eq 0 ]; then
		ret=$1
	fi
}

bpf_tunnel_test()
{
	local errors=0

	echo "Testing GRE tunnel..."
	test_gre
	errors=$(( $errors + $? ))

	echo "Testing GRE tunnel (without tunnel keys)..."
	test_gre_no_tunnel_key
	errors=$(( $errors + $? ))

	echo "Testing IP6GRE tunnel..."
	test_ip6gre
	errors=$(( $errors + $? ))

	echo "Testing IP6GRETAP tunnel..."
	test_ip6gretap
	errors=$(( $errors + $? ))

	echo "Testing ERSPAN tunnel..."
	test_erspan v2
	errors=$(( $errors + $? ))

	echo "Testing IP6ERSPAN tunnel..."
	test_ip6erspan v2
	errors=$(( $errors + $? ))

	echo "Testing GENEVE tunnel..."
	test_geneve
	errors=$(( $errors + $? ))

	echo "Testing IP6GENEVE tunnel..."
	test_ip6geneve
	errors=$(( $errors + $? ))

	echo "Testing IPIP tunnel..."
	test_ipip
	errors=$(( $errors + $? ))

	echo "Testing IPIP6 tunnel..."
	test_ipip6
	errors=$(( $errors + $? ))

	echo "Testing IP6IP6 tunnel..."
	test_ip6ip6
	errors=$(( $errors + $? ))

	return $errors
}

trap cleanup 0 3 6
trap cleanup_exit 2 9

cleanup
bpf_tunnel_test

if [ $? -ne 0 ]; then
	echo -e "$(basename $0): ${RED}FAIL${NC}"
	exit 1
fi
echo -e "$(basename $0): ${GREEN}PASS${NC}"
exit 0
