#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Test devlink-trap L2 drops functionality over mlxsw. Each registered L2 drop
# packet trap is tested to make sure it is triggered under the right
# conditions.

lib_dir=$(dirname $0)/../../../net/forwarding

ALL_TESTS="
	source_mac_is_multicast_test
	vlan_tag_mismatch_test
	ingress_vlan_filter_test
	ingress_stp_filter_test
	port_list_is_empty_test
	port_loopback_filter_test
	locked_port_test
"
NUM_NETIFS=4
source $lib_dir/tc_common.sh
source $lib_dir/lib.sh
source $lib_dir/devlink_lib.sh

h1_create()
{
	simple_if_init $h1
}

h1_destroy()
{
	simple_if_fini $h1
}

h2_create()
{
	simple_if_init $h2
}

h2_destroy()
{
	simple_if_fini $h2
}

switch_create()
{
	ip link add dev br0 type bridge vlan_filtering 1 mcast_snooping 0

	ip link set dev $swp1 master br0
	ip link set dev $swp2 master br0

	ip link set dev br0 up
	ip link set dev $swp1 up
	ip link set dev $swp2 up

	tc qdisc add dev $swp2 clsact
}

switch_destroy()
{
	tc qdisc del dev $swp2 clsact

	ip link set dev $swp2 down
	ip link set dev $swp1 down

	ip link del dev br0
}

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
}

source_mac_is_multicast_test()
{
	local trap_name="source_mac_is_multicast"
	local smac=01:02:03:04:05:06
	local mz_pid

	tc filter add dev $swp2 egress protocol ip pref 1 handle 101 \
		flower src_mac $smac action drop

	$MZ $h1 -c 0 -p 100 -a $smac -b bcast -t ip -d 1msec -q &
	mz_pid=$!

	RET=0

	devlink_trap_drop_test $trap_name $swp2 101

	log_test "Source MAC is multicast"

	devlink_trap_drop_cleanup $mz_pid $swp2 ip 1 101
}

__vlan_tag_mismatch_test()
{
	local trap_name="vlan_tag_mismatch"
	local dmac=de:ad:be:ef:13:37
	local opt=$1; shift
	local mz_pid

	# Remove PVID flag. This should prevent untagged and prio-tagged
	# packets from entering the bridge.
	bridge vlan add vid 1 dev $swp1 untagged master

	tc filter add dev $swp2 egress protocol ip pref 1 handle 101 \
		flower dst_mac $dmac action drop

	$MZ $h1 "$opt" -c 0 -p 100 -a own -b $dmac -t ip -d 1msec -q &
	mz_pid=$!

	devlink_trap_drop_test $trap_name $swp2 101

	# Add PVID and make sure packets are no longer dropped.
	bridge vlan add vid 1 dev $swp1 pvid untagged master
	devlink_trap_action_set $trap_name "trap"

	devlink_trap_stats_idle_test $trap_name
	check_err $? "Trap stats not idle when packets should not be dropped"
	devlink_trap_group_stats_idle_test $(devlink_trap_group_get $trap_name)
	check_err $? "Trap group stats not idle with when packets should not be dropped"

	tc_check_packets "dev $swp2 egress" 101 0
	check_fail $? "Packets not forwarded when should"

	devlink_trap_action_set $trap_name "drop"

	devlink_trap_drop_cleanup $mz_pid $swp2 ip 1 101
}

vlan_tag_mismatch_untagged_test()
{
	RET=0

	__vlan_tag_mismatch_test

	log_test "VLAN tag mismatch - untagged packets"
}

vlan_tag_mismatch_vid_0_test()
{
	RET=0

	__vlan_tag_mismatch_test "-Q 0"

	log_test "VLAN tag mismatch - prio-tagged packets"
}

vlan_tag_mismatch_test()
{
	vlan_tag_mismatch_untagged_test
	vlan_tag_mismatch_vid_0_test
}

ingress_vlan_filter_test()
{
	local trap_name="ingress_vlan_filter"
	local dmac=de:ad:be:ef:13:37
	local mz_pid
	local vid=10

	bridge vlan add vid $vid dev $swp2 master

	RET=0

	tc filter add dev $swp2 egress protocol ip pref 1 handle 101 \
		flower dst_mac $dmac action drop

	$MZ $h1 -Q $vid -c 0 -p 100 -a own -b $dmac -t ip -d 1msec -q &
	mz_pid=$!

	devlink_trap_drop_test $trap_name $swp2 101

	# Add the VLAN on the bridge port and make sure packets are no longer
	# dropped.
	bridge vlan add vid $vid dev $swp1 master
	devlink_trap_action_set $trap_name "trap"

	devlink_trap_stats_idle_test $trap_name
	check_err $? "Trap stats not idle when packets should not be dropped"
	devlink_trap_group_stats_idle_test $(devlink_trap_group_get $trap_name)
	check_err $? "Trap group stats not idle with when packets should not be dropped"

	tc_check_packets "dev $swp2 egress" 101 0
	check_fail $? "Packets not forwarded when should"

	devlink_trap_action_set $trap_name "drop"

	log_test "Ingress VLAN filter"

	devlink_trap_drop_cleanup $mz_pid $swp2 ip 1 101

	bridge vlan del vid $vid dev $swp1 master
	bridge vlan del vid $vid dev $swp2 master
}

__ingress_stp_filter_test()
{
	local trap_name="ingress_spanning_tree_filter"
	local dmac=de:ad:be:ef:13:37
	local state=$1; shift
	local mz_pid
	local vid=20

	bridge vlan add vid $vid dev $swp2 master
	bridge vlan add vid $vid dev $swp1 master
	ip link set dev $swp1 type bridge_slave state $state

	tc filter add dev $swp2 egress protocol ip pref 1 handle 101 \
		flower dst_mac $dmac action drop

	$MZ $h1 -Q $vid -c 0 -p 100 -a own -b $dmac -t ip -d 1msec -q &
	mz_pid=$!

	devlink_trap_drop_test $trap_name $swp2 101

	# Change STP state to forwarding and make sure packets are no longer
	# dropped.
	ip link set dev $swp1 type bridge_slave state 3
	devlink_trap_action_set $trap_name "trap"

	devlink_trap_stats_idle_test $trap_name
	check_err $? "Trap stats not idle when packets should not be dropped"
	devlink_trap_group_stats_idle_test $(devlink_trap_group_get $trap_name)
	check_err $? "Trap group stats not idle with when packets should not be dropped"

	tc_check_packets "dev $swp2 egress" 101 0
	check_fail $? "Packets not forwarded when should"

	devlink_trap_action_set $trap_name "drop"

	devlink_trap_drop_cleanup $mz_pid $swp2 ip 1 101

	bridge vlan del vid $vid dev $swp1 master
	bridge vlan del vid $vid dev $swp2 master
}

ingress_stp_filter_listening_test()
{
	local state=$1; shift

	RET=0

	__ingress_stp_filter_test $state

	log_test "Ingress STP filter - listening state"
}

ingress_stp_filter_learning_test()
{
	local state=$1; shift

	RET=0

	__ingress_stp_filter_test $state

	log_test "Ingress STP filter - learning state"
}

ingress_stp_filter_test()
{
	ingress_stp_filter_listening_test 1
	ingress_stp_filter_learning_test 2
}

port_list_is_empty_uc_test()
{
	local trap_name="port_list_is_empty"
	local dmac=de:ad:be:ef:13:37
	local mz_pid

	# Disable unicast flooding on both ports, so that packets cannot egress
	# any port.
	ip link set dev $swp1 type bridge_slave flood off
	ip link set dev $swp2 type bridge_slave flood off

	RET=0

	tc filter add dev $swp2 egress protocol ip pref 1 handle 101 \
		flower dst_mac $dmac action drop

	$MZ $h1 -c 0 -p 100 -a own -b $dmac -t ip -d 1msec -q &
	mz_pid=$!

	devlink_trap_drop_test $trap_name $swp2 101

	# Allow packets to be flooded to one port.
	ip link set dev $swp2 type bridge_slave flood on
	devlink_trap_action_set $trap_name "trap"

	devlink_trap_stats_idle_test $trap_name
	check_err $? "Trap stats not idle when packets should not be dropped"
	devlink_trap_group_stats_idle_test $(devlink_trap_group_get $trap_name)
	check_err $? "Trap group stats not idle with when packets should not be dropped"

	tc_check_packets "dev $swp2 egress" 101 0
	check_fail $? "Packets not forwarded when should"

	devlink_trap_action_set $trap_name "drop"

	log_test "Port list is empty - unicast"

	devlink_trap_drop_cleanup $mz_pid $swp2 ip 1 101

	ip link set dev $swp1 type bridge_slave flood on
}

port_list_is_empty_mc_test()
{
	local trap_name="port_list_is_empty"
	local dmac=01:00:5e:00:00:01
	local dip=239.0.0.1
	local mz_pid

	# Disable multicast flooding on both ports, so that packets cannot
	# egress any port. We also need to flush IP addresses from the bridge
	# in order to prevent packets from being flooded to the router port.
	ip link set dev $swp1 type bridge_slave mcast_flood off
	ip link set dev $swp2 type bridge_slave mcast_flood off
	ip address flush dev br0

	RET=0

	tc filter add dev $swp2 egress protocol ip pref 1 handle 101 \
		flower dst_mac $dmac action drop

	$MZ $h1 -c 0 -p 100 -a own -b $dmac -t ip -B $dip -d 1msec -q &
	mz_pid=$!

	devlink_trap_drop_test $trap_name $swp2 101

	# Allow packets to be flooded to one port.
	ip link set dev $swp2 type bridge_slave mcast_flood on
	devlink_trap_action_set $trap_name "trap"

	devlink_trap_stats_idle_test $trap_name
	check_err $? "Trap stats not idle when packets should not be dropped"
	devlink_trap_group_stats_idle_test $(devlink_trap_group_get $trap_name)
	check_err $? "Trap group stats not idle with when packets should not be dropped"

	tc_check_packets "dev $swp2 egress" 101 0
	check_fail $? "Packets not forwarded when should"

	devlink_trap_action_set $trap_name "drop"

	log_test "Port list is empty - multicast"

	devlink_trap_drop_cleanup $mz_pid $swp2 ip 1 101

	ip link set dev $swp1 type bridge_slave mcast_flood on
}

port_list_is_empty_test()
{
	port_list_is_empty_uc_test
	port_list_is_empty_mc_test
}

port_loopback_filter_uc_test()
{
	local trap_name="port_loopback_filter"
	local dmac=de:ad:be:ef:13:37
	local mz_pid

	# Make sure packets can only egress the input port.
	ip link set dev $swp2 type bridge_slave flood off

	RET=0

	tc filter add dev $swp2 egress protocol ip pref 1 handle 101 \
		flower dst_mac $dmac action drop

	$MZ $h1 -c 0 -p 100 -a own -b $dmac -t ip -d 1msec -q &
	mz_pid=$!

	devlink_trap_drop_test $trap_name $swp2 101

	# Allow packets to be flooded.
	ip link set dev $swp2 type bridge_slave flood on
	devlink_trap_action_set $trap_name "trap"

	devlink_trap_stats_idle_test $trap_name
	check_err $? "Trap stats not idle when packets should not be dropped"
	devlink_trap_group_stats_idle_test $(devlink_trap_group_get $trap_name)
	check_err $? "Trap group stats not idle with when packets should not be dropped"

	tc_check_packets "dev $swp2 egress" 101 0
	check_fail $? "Packets not forwarded when should"

	devlink_trap_action_set $trap_name "drop"

	log_test "Port loopback filter - unicast"

	devlink_trap_drop_cleanup $mz_pid $swp2 ip 1 101
}

port_loopback_filter_test()
{
	port_loopback_filter_uc_test
}

locked_port_miss_test()
{
	local trap_name="locked_port"
	local smac=00:11:22:33:44:55

	bridge link set dev $swp1 learning off
	bridge link set dev $swp1 locked on

	RET=0

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_fail $? "Trap stats increased before setting action to \"trap\""

	devlink_trap_action_set $trap_name "trap"

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_err $? "Trap stats did not increase when should"

	devlink_trap_action_set $trap_name "drop"

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_fail $? "Trap stats increased after setting action to \"drop\""

	devlink_trap_action_set $trap_name "trap"

	bridge fdb replace $smac dev $swp1 master static vlan 1

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_fail $? "Trap stats increased after adding an FDB entry"

	bridge fdb del $smac dev $swp1 master static vlan 1
	bridge link set dev $swp1 locked off

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_fail $? "Trap stats increased after unlocking port"

	log_test "Locked port - FDB miss"

	devlink_trap_action_set $trap_name "drop"
	bridge link set dev $swp1 learning on
}

locked_port_mismatch_test()
{
	local trap_name="locked_port"
	local smac=00:11:22:33:44:55

	bridge link set dev $swp1 learning off
	bridge link set dev $swp1 locked on

	RET=0

	bridge fdb replace $smac dev $swp2 master static vlan 1

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_fail $? "Trap stats increased before setting action to \"trap\""

	devlink_trap_action_set $trap_name "trap"

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_err $? "Trap stats did not increase when should"

	devlink_trap_action_set $trap_name "drop"

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_fail $? "Trap stats increased after setting action to \"drop\""

	devlink_trap_action_set $trap_name "trap"
	bridge link set dev $swp1 locked off

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_fail $? "Trap stats increased after unlocking port"

	bridge link set dev $swp1 locked on
	bridge fdb replace $smac dev $swp1 master static vlan 1

	devlink_trap_stats_check $trap_name $MZ $h1 -c 1 \
		-a $smac -b $(mac_get $h2) -A 192.0.2.1 -B 192.0.2.2 -p 100 -q
	check_fail $? "Trap stats increased after replacing an FDB entry"

	bridge fdb del $smac dev $swp1 master static vlan 1
	devlink_trap_action_set $trap_name "drop"

	log_test "Locked port - FDB mismatch"

	bridge link set dev $swp1 locked off
	bridge link set dev $swp1 learning on
}

locked_port_test()
{
	locked_port_miss_test
	locked_port_mismatch_test
}

trap cleanup EXIT

setup_prepare
setup_wait

tests_run

exit $EXIT_STATUS