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

# +-----------------------+                             +----------------------+
# | H1 (vrf)              |                             | H2 (vrf)             |
# |    + $h1              |                             |              $h2 +   |
# |    | 192.0.2.1/28     |                             |     192.0.2.2/28 |   |
# |    | 2001:db8:1::1/64 |                             | 2001:db8:1::2/64 |   |
# +----|------------------+                             +------------------|---+
#      |                                                                   |
# +----|-------------------------------------------------------------------|---+
# | SW |                                                                   |   |
# |  +-|-------------------------------------------------------------------|-+ |
# |  | + $swp1                       BR                              $swp2 + | |
# |  +-----------------------------------------------------------------------+ |
# +----------------------------------------------------------------------------+

ALL_TESTS="
	test_port_range_ipv4_udp
	test_port_range_ipv4_tcp
	test_port_range_ipv6_udp
	test_port_range_ipv6_tcp
"

NUM_NETIFS=4
source lib.sh
source tc_common.sh

h1_create()
{
	simple_if_init $h1 192.0.2.1/28 2001:db8:1::1/64
}

h1_destroy()
{
	simple_if_fini $h1 192.0.2.1/28 2001:db8:1::1/64
}

h2_create()
{
	simple_if_init $h2 192.0.2.2/28 2001:db8:1::2/64
}

h2_destroy()
{
	simple_if_fini $h2 192.0.2.2/28 2001:db8:1::2/64
}

switch_create()
{
	ip link add name br1 type bridge
	ip link set dev $swp1 master br1
	ip link set dev $swp1 up
	ip link set dev $swp2 master br1
	ip link set dev $swp2 up
	ip link set dev br1 up

	tc qdisc add dev $swp1 clsact
	tc qdisc add dev $swp2 clsact
}

switch_destroy()
{
	tc qdisc del dev $swp2 clsact
	tc qdisc del dev $swp1 clsact

	ip link set dev br1 down
	ip link set dev $swp2 down
	ip link set dev $swp2 nomaster
	ip link set dev $swp1 down
	ip link set dev $swp1 nomaster
	ip link del dev br1
}

__test_port_range()
{
	local proto=$1; shift
	local ip_proto=$1; shift
	local sip=$1; shift
	local dip=$1; shift
	local mode=$1; shift
	local name=$1; shift
	local dmac=$(mac_get $h2)
	local smac=$(mac_get $h1)
	local sport_min=100
	local sport_max=200
	local sport_mid=$((sport_min + (sport_max - sport_min) / 2))
	local dport_min=300
	local dport_max=400
	local dport_mid=$((dport_min + (dport_max - dport_min) / 2))

	RET=0

	tc filter add dev $swp1 ingress protocol $proto handle 101 pref 1 \
		flower src_ip $sip dst_ip $dip ip_proto $ip_proto \
		src_port $sport_min-$sport_max \
		dst_port $dport_min-$dport_max \
		action pass
	tc filter add dev $swp2 egress protocol $proto handle 101 pref 1 \
		flower src_ip $sip dst_ip $dip ip_proto $ip_proto \
		src_port $sport_min-$sport_max \
		dst_port $dport_min-$dport_max \
		action drop

	$MZ $mode $h1 -c 1 -q -p 100 -a $smac -b $dmac -A $sip -B $dip \
		-t $ip_proto "sp=$sport_min,dp=$dport_min"
	tc_check_packets "dev $swp1 ingress" 101 1
	check_err $? "Ingress filter not hit with minimum ports"
	tc_check_packets "dev $swp2 egress" 101 1
	check_err $? "Egress filter not hit with minimum ports"

	$MZ $mode $h1 -c 1 -q -p 100 -a $smac -b $dmac -A $sip -B $dip \
		-t $ip_proto "sp=$sport_mid,dp=$dport_mid"
	tc_check_packets "dev $swp1 ingress" 101 2
	check_err $? "Ingress filter not hit with middle ports"
	tc_check_packets "dev $swp2 egress" 101 2
	check_err $? "Egress filter not hit with middle ports"

	$MZ $mode $h1 -c 1 -q -p 100 -a $smac -b $dmac -A $sip -B $dip \
		-t $ip_proto "sp=$sport_max,dp=$dport_max"
	tc_check_packets "dev $swp1 ingress" 101 3
	check_err $? "Ingress filter not hit with maximum ports"
	tc_check_packets "dev $swp2 egress" 101 3
	check_err $? "Egress filter not hit with maximum ports"

	# Send traffic when both ports are out of range and when only one port
	# is out of range.
	$MZ $mode $h1 -c 1 -q -p 100 -a $smac -b $dmac -A $sip -B $dip \
		-t $ip_proto "sp=$((sport_min - 1)),dp=$dport_min"
	$MZ $mode $h1 -c 1 -q -p 100 -a $smac -b $dmac -A $sip -B $dip \
		-t $ip_proto "sp=$((sport_max + 1)),dp=$dport_min"
	$MZ $mode $h1 -c 1 -q -p 100 -a $smac -b $dmac -A $sip -B $dip \
		-t $ip_proto "sp=$sport_min,dp=$((dport_min - 1))"
	$MZ $mode $h1 -c 1 -q -p 100 -a $smac -b $dmac -A $sip -B $dip \
		-t $ip_proto "sp=$sport_min,dp=$((dport_max + 1))"
	$MZ $mode $h1 -c 1 -q -p 100 -a $smac -b $dmac -A $sip -B $dip \
		-t $ip_proto "sp=$((sport_max + 1)),dp=$((dport_max + 1))"
	tc_check_packets "dev $swp1 ingress" 101 3
	check_err $? "Ingress filter was hit when should not"
	tc_check_packets "dev $swp2 egress" 101 3
	check_err $? "Egress filter was hit when should not"

	tc filter del dev $swp2 egress protocol $proto pref 1 handle 101 flower
	tc filter del dev $swp1 ingress protocol $proto pref 1 handle 101 flower

	log_test "Port range matching - $name"
}

test_port_range_ipv4_udp()
{
	local proto=ipv4
	local ip_proto=udp
	local sip=192.0.2.1
	local dip=192.0.2.2
	local mode="-4"
	local name="IPv4 UDP"

	__test_port_range $proto $ip_proto $sip $dip $mode "$name"
}

test_port_range_ipv4_tcp()
{
	local proto=ipv4
	local ip_proto=tcp
	local sip=192.0.2.1
	local dip=192.0.2.2
	local mode="-4"
	local name="IPv4 TCP"

	__test_port_range $proto $ip_proto $sip $dip $mode "$name"
}

test_port_range_ipv6_udp()
{
	local proto=ipv6
	local ip_proto=udp
	local sip=2001:db8:1::1
	local dip=2001:db8:1::2
	local mode="-6"
	local name="IPv6 UDP"

	__test_port_range $proto $ip_proto $sip $dip $mode "$name"
}

test_port_range_ipv6_tcp()
{
	local proto=ipv6
	local ip_proto=tcp
	local sip=2001:db8:1::1
	local dip=2001:db8:1::2
	local mode="-6"
	local name="IPv6 TCP"

	__test_port_range $proto $ip_proto $sip $dip $mode "$name"
}

setup_prepare()
{
	h1=${NETIFS[p1]}
	swp1=${NETIFS[p2]}

	swp2=${NETIFS[p3]}
	h2=${NETIFS[p4]}

	vrf_prepare
	h1_create
	h2_create
	switch_create
}

cleanup()
{
	pre_cleanup

	switch_destroy
	h2_destroy
	h1_destroy
	vrf_cleanup
}

trap cleanup EXIT

setup_prepare
setup_wait

tests_run

exit $EXIT_STATUS