#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# This test is for checking bridge neighbor suppression functionality. The
# topology consists of two bridges (VTEPs) connected using VXLAN. A single
# host is connected to each bridge over multiple VLANs. The test checks that
# ARP/NS messages from the first host are suppressed on the VXLAN port when
# should.
#
# +-----------------------+              +------------------------+
# | h1                    |              | h2                     |
# |                       |              |                        |
# | + eth0.10             |              | + eth0.10              |
# | | 192.0.2.1/28        |              | | 192.0.2.2/28         |
# | | 2001:db8:1::1/64    |              | | 2001:db8:1::2/64     |
# | |                     |              | |                      |
# | |  + eth0.20          |              | |  + eth0.20           |
# | \  | 192.0.2.17/28    |              | \  | 192.0.2.18/28     |
# |  \ | 2001:db8:2::1/64 |              |  \ | 2001:db8:2::2/64  |
# |   \|                  |              |   \|                   |
# |    + eth0             |              |    + eth0              |
# +----|------------------+              +----|-------------------+
#      |                                      |
#      |                                      |
# +----|-------------------------------+ +----|-------------------------------+
# |    + swp1                   + vx0  | |    + swp1                   + vx0  |
# |    |                        |      | |    |                        |      |
# |    |           br0          |      | |    |                        |      |
# |    +------------+-----------+      | |    +------------+-----------+      |
# |                 |                  | |                 |                  |
# |                 |                  | |                 |                  |
# |             +---+---+              | |             +---+---+              |
# |             |       |              | |             |       |              |
# |             |       |              | |             |       |              |
# |             +       +              | |             +       +              |
# |          br0.10  br0.20            | |          br0.10  br0.20            |
# |                                    | |                                    |
# |                 192.0.2.33         | |                 192.0.2.34         |
# |                 + lo               | |                 + lo               |
# |                                    | |                                    |
# |                                    | |                                    |
# |                   192.0.2.49/28    | |    192.0.2.50/28                   |
# |                           veth0 +-------+ veth0                           |
# |                                    | |                                    |
# | sw1                                | | sw2                                |
# +------------------------------------+ +------------------------------------+

source lib.sh
ret=0

# All tests in this script. Can be overridden with -t option.
TESTS="
	neigh_suppress_arp
	neigh_suppress_uc_arp
	neigh_suppress_ns
	neigh_suppress_uc_ns
	neigh_vlan_suppress_arp
	neigh_vlan_suppress_ns
"
VERBOSE=0
PAUSE_ON_FAIL=no
PAUSE=no

################################################################################
# Utilities

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

	if [ ${rc} -eq ${expected} ]; then
		printf "TEST: %-60s  [ OK ]\n" "${msg}"
		nsuccess=$((nsuccess+1))
	else
		ret=1
		nfail=$((nfail+1))
		printf "TEST: %-60s  [FAIL]\n" "${msg}"
		if [ "$VERBOSE" = "1" ]; then
			echo "    rc=$rc, expected $expected"
		fi

		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
		echo
			echo "hit enter to continue, 'q' to quit"
			read a
			[ "$a" = "q" ] && exit 1
		fi
	fi

	if [ "${PAUSE}" = "yes" ]; then
		echo
		echo "hit enter to continue, 'q' to quit"
		read a
		[ "$a" = "q" ] && exit 1
	fi

	[ "$VERBOSE" = "1" ] && echo
}

run_cmd()
{
	local cmd="$1"
	local out
	local stderr="2>/dev/null"

	if [ "$VERBOSE" = "1" ]; then
		printf "COMMAND: $cmd\n"
		stderr=
	fi

	out=$(eval $cmd $stderr)
	rc=$?
	if [ "$VERBOSE" = "1" -a -n "$out" ]; then
		echo "    $out"
	fi

	return $rc
}

tc_check_packets()
{
	local ns=$1; shift
	local id=$1; shift
	local handle=$1; shift
	local count=$1; shift
	local pkts

	sleep 0.1
	pkts=$(tc -n $ns -j -s filter show $id \
		| jq ".[] | select(.options.handle == $handle) | \
		.options.actions[0].stats.packets")
	[[ $pkts == $count ]]
}

################################################################################
# Setup

setup_topo_ns()
{
	local ns=$1; shift

	ip netns exec $ns sysctl -qw net.ipv6.conf.all.keep_addr_on_down=1
	ip netns exec $ns sysctl -qw net.ipv6.conf.default.ignore_routes_with_linkdown=1
	ip netns exec $ns sysctl -qw net.ipv6.conf.all.accept_dad=0
	ip netns exec $ns sysctl -qw net.ipv6.conf.default.accept_dad=0
}

setup_topo()
{
	local ns

	setup_ns h1 h2 sw1 sw2
	for ns in $h1 $h2 $sw1 $sw2; do
		setup_topo_ns $ns
	done

	ip -n $h1 link add name eth0 type veth peer name swp1 netns $sw1
	ip -n $sw1 link add name veth0 type veth peer name veth0 netns $sw2
	ip -n $h2 link add name eth0 type veth peer name swp1 netns $sw2
}

setup_host_common()
{
	local ns=$1; shift
	local v4addr1=$1; shift
	local v4addr2=$1; shift
	local v6addr1=$1; shift
	local v6addr2=$1; shift

	ip -n $ns link set dev eth0 up
	ip -n $ns link add link eth0 name eth0.10 up type vlan id 10
	ip -n $ns link add link eth0 name eth0.20 up type vlan id 20

	ip -n $ns address add $v4addr1 dev eth0.10
	ip -n $ns address add $v4addr2 dev eth0.20
	ip -n $ns address add $v6addr1 dev eth0.10
	ip -n $ns address add $v6addr2 dev eth0.20
}

setup_h1()
{
	local ns=$h1
	local v4addr1=192.0.2.1/28
	local v4addr2=192.0.2.17/28
	local v6addr1=2001:db8:1::1/64
	local v6addr2=2001:db8:2::1/64

	setup_host_common $ns $v4addr1 $v4addr2 $v6addr1 $v6addr2
}

setup_h2()
{
	local ns=$h2
	local v4addr1=192.0.2.2/28
	local v4addr2=192.0.2.18/28
	local v6addr1=2001:db8:1::2/64
	local v6addr2=2001:db8:2::2/64

	setup_host_common $ns $v4addr1 $v4addr2 $v6addr1 $v6addr2
}

setup_sw_common()
{
	local ns=$1; shift
	local local_addr=$1; shift
	local remote_addr=$1; shift
	local veth_addr=$1; shift
	local gw_addr=$1; shift

	ip -n $ns address add $local_addr/32 dev lo

	ip -n $ns link set dev veth0 up
	ip -n $ns address add $veth_addr/28 dev veth0
	ip -n $ns route add default via $gw_addr

	ip -n $ns link add name br0 up type bridge vlan_filtering 1 \
		vlan_default_pvid 0 mcast_snooping 0

	ip -n $ns link add link br0 name br0.10 up type vlan id 10
	bridge -n $ns vlan add vid 10 dev br0 self

	ip -n $ns link add link br0 name br0.20 up type vlan id 20
	bridge -n $ns vlan add vid 20 dev br0 self

	ip -n $ns link set dev swp1 up master br0
	bridge -n $ns vlan add vid 10 dev swp1
	bridge -n $ns vlan add vid 20 dev swp1

	ip -n $ns link add name vx0 up master br0 type vxlan \
		local $local_addr dstport 4789 nolearning external
	bridge -n $ns fdb add 00:00:00:00:00:00 dev vx0 self static \
		dst $remote_addr src_vni 10010
	bridge -n $ns fdb add 00:00:00:00:00:00 dev vx0 self static \
		dst $remote_addr src_vni 10020
	bridge -n $ns link set dev vx0 vlan_tunnel on learning off

	bridge -n $ns vlan add vid 10 dev vx0
	bridge -n $ns vlan add vid 10 dev vx0 tunnel_info id 10010

	bridge -n $ns vlan add vid 20 dev vx0
	bridge -n $ns vlan add vid 20 dev vx0 tunnel_info id 10020
}

setup_sw1()
{
	local ns=$sw1
	local local_addr=192.0.2.33
	local remote_addr=192.0.2.34
	local veth_addr=192.0.2.49
	local gw_addr=192.0.2.50

	setup_sw_common $ns $local_addr $remote_addr $veth_addr $gw_addr
}

setup_sw2()
{
	local ns=$sw2
	local local_addr=192.0.2.34
	local remote_addr=192.0.2.33
	local veth_addr=192.0.2.50
	local gw_addr=192.0.2.49

	setup_sw_common $ns $local_addr $remote_addr $veth_addr $gw_addr
}

setup()
{
	set -e

	setup_topo
	setup_h1
	setup_h2
	setup_sw1
	setup_sw2

	sleep 5

	set +e
}

cleanup()
{
	cleanup_ns $h1 $h2 $sw1 $sw2
}

################################################################################
# Tests

neigh_suppress_arp_common()
{
	local vid=$1; shift
	local sip=$1; shift
	local tip=$1; shift
	local h2_mac

	echo
	echo "Per-port ARP suppression - VLAN $vid"
	echo "----------------------------------"

	run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
	run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto 0x0806 flower indev swp1 arp_tip $tip arp_sip $sip arp_op request action pass"

	# Initial state - check that ARP requests are not suppressed and that
	# ARP replies are received.
	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip -I eth0.$vid $tip"
	log_test $? 0 "arping"
	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "ARP suppression"

	# Enable neighbor suppression and check that nothing changes compared
	# to the initial state.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip -I eth0.$vid $tip"
	log_test $? 0 "arping"
	tc_check_packets $sw1 "dev vx0 egress" 101 2
	log_test $? 0 "ARP suppression"

	# Install an FDB entry for the remote host and check that nothing
	# changes compared to the initial state.
	h2_mac=$(ip -n $h2 -j -p link show eth0.$vid | jq -r '.[]["address"]')
	run_cmd "bridge -n $sw1 fdb replace $h2_mac dev vx0 master static vlan $vid"
	log_test $? 0 "FDB entry installation"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip -I eth0.$vid $tip"
	log_test $? 0 "arping"
	tc_check_packets $sw1 "dev vx0 egress" 101 3
	log_test $? 0 "ARP suppression"

	# Install a neighbor on the matching SVI interface and check that ARP
	# requests are suppressed.
	run_cmd "ip -n $sw1 neigh replace $tip lladdr $h2_mac nud permanent dev br0.$vid"
	log_test $? 0 "Neighbor entry installation"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip -I eth0.$vid $tip"
	log_test $? 0 "arping"
	tc_check_packets $sw1 "dev vx0 egress" 101 3
	log_test $? 0 "ARP suppression"

	# Take the second host down and check that ARP requests are suppressed
	# and that ARP replies are received.
	run_cmd "ip -n $h2 link set dev eth0.$vid down"
	log_test $? 0 "H2 down"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip -I eth0.$vid $tip"
	log_test $? 0 "arping"
	tc_check_packets $sw1 "dev vx0 egress" 101 3
	log_test $? 0 "ARP suppression"

	run_cmd "ip -n $h2 link set dev eth0.$vid up"
	log_test $? 0 "H2 up"

	# Disable neighbor suppression and check that ARP requests are no
	# longer suppressed.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress off"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress off\""
	log_test $? 0 "\"neigh_suppress\" is off"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip -I eth0.$vid $tip"
	log_test $? 0 "arping"
	tc_check_packets $sw1 "dev vx0 egress" 101 4
	log_test $? 0 "ARP suppression"

	# Take the second host down and check that ARP requests are not
	# suppressed and that ARP replies are not received.
	run_cmd "ip -n $h2 link set dev eth0.$vid down"
	log_test $? 0 "H2 down"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip -I eth0.$vid $tip"
	log_test $? 1 "arping"
	tc_check_packets $sw1 "dev vx0 egress" 101 5
	log_test $? 0 "ARP suppression"
}

neigh_suppress_arp()
{
	local vid=10
	local sip=192.0.2.1
	local tip=192.0.2.2

	neigh_suppress_arp_common $vid $sip $tip

	vid=20
	sip=192.0.2.17
	tip=192.0.2.18
	neigh_suppress_arp_common $vid $sip $tip
}

neigh_suppress_uc_arp_common()
{
	local vid=$1; shift
	local sip=$1; shift
	local tip=$1; shift
	local tmac

	echo
	echo "Unicast ARP, per-port ARP suppression - VLAN $vid"
	echo "-----------------------------------------------"

	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on"

	tmac=$(ip -n $h2 -j -p link show eth0.$vid | jq -r '.[]["address"]')
	run_cmd "bridge -n $sw1 fdb replace $tmac dev vx0 master static vlan $vid"
	run_cmd "ip -n $sw1 neigh replace $tip lladdr $tmac nud permanent dev br0.$vid"

	run_cmd "tc -n $h1 qdisc replace dev eth0.$vid clsact"
	run_cmd "tc -n $h1 filter replace dev eth0.$vid ingress pref 1 handle 101 proto arp flower arp_sip $tip arp_op reply action pass"

	run_cmd "tc -n $h2 qdisc replace dev eth0.$vid clsact"
	run_cmd "tc -n $h2 filter replace dev eth0.$vid egress pref 1 handle 101 proto arp flower arp_tip $sip arp_op reply action pass"

	run_cmd "ip netns exec $h1 mausezahn eth0.$vid -c 1 -a own -b $tmac -t arp 'request sip=$sip, tip=$tip, tmac=$tmac' -q"
	tc_check_packets $h1 "dev eth0.$vid ingress" 101 1
	log_test $? 0 "Unicast ARP, suppression on, h1 filter"
	tc_check_packets $h2 "dev eth0.$vid egress" 101 1
	log_test $? 0 "Unicast ARP, suppression on, h2 filter"
}

neigh_suppress_uc_arp()
{
	local vid=10
	local sip=192.0.2.1
	local tip=192.0.2.2

	neigh_suppress_uc_arp_common $vid $sip $tip

	vid=20
	sip=192.0.2.17
	tip=192.0.2.18
	neigh_suppress_uc_arp_common $vid $sip $tip
}

neigh_suppress_ns_common()
{
	local vid=$1; shift
	local saddr=$1; shift
	local daddr=$1; shift
	local maddr=$1; shift
	local h2_mac

	echo
	echo "Per-port NS suppression - VLAN $vid"
	echo "---------------------------------"

	run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
	run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $maddr src_ip $saddr type 135 code 0 action pass"

	# Initial state - check that NS messages are not suppressed and that ND
	# messages are received.
	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr -w 5000 $daddr eth0.$vid"
	log_test $? 0 "ndisc6"
	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "NS suppression"

	# Enable neighbor suppression and check that nothing changes compared
	# to the initial state.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr -w 5000 $daddr eth0.$vid"
	log_test $? 0 "ndisc6"
	tc_check_packets $sw1 "dev vx0 egress" 101 2
	log_test $? 0 "NS suppression"

	# Install an FDB entry for the remote host and check that nothing
	# changes compared to the initial state.
	h2_mac=$(ip -n $h2 -j -p link show eth0.$vid | jq -r '.[]["address"]')
	run_cmd "bridge -n $sw1 fdb replace $h2_mac dev vx0 master static vlan $vid"
	log_test $? 0 "FDB entry installation"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr -w 5000 $daddr eth0.$vid"
	log_test $? 0 "ndisc6"
	tc_check_packets $sw1 "dev vx0 egress" 101 3
	log_test $? 0 "NS suppression"

	# Install a neighbor on the matching SVI interface and check that NS
	# messages are suppressed.
	run_cmd "ip -n $sw1 neigh replace $daddr lladdr $h2_mac nud permanent dev br0.$vid"
	log_test $? 0 "Neighbor entry installation"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr -w 5000 $daddr eth0.$vid"
	log_test $? 0 "ndisc6"
	tc_check_packets $sw1 "dev vx0 egress" 101 3
	log_test $? 0 "NS suppression"

	# Take the second host down and check that NS messages are suppressed
	# and that ND messages are received.
	run_cmd "ip -n $h2 link set dev eth0.$vid down"
	log_test $? 0 "H2 down"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr -w 5000 $daddr eth0.$vid"
	log_test $? 0 "ndisc6"
	tc_check_packets $sw1 "dev vx0 egress" 101 3
	log_test $? 0 "NS suppression"

	run_cmd "ip -n $h2 link set dev eth0.$vid up"
	log_test $? 0 "H2 up"

	# Disable neighbor suppression and check that NS messages are no longer
	# suppressed.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress off"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress off\""
	log_test $? 0 "\"neigh_suppress\" is off"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr -w 5000 $daddr eth0.$vid"
	log_test $? 0 "ndisc6"
	tc_check_packets $sw1 "dev vx0 egress" 101 4
	log_test $? 0 "NS suppression"

	# Take the second host down and check that NS messages are not
	# suppressed and that ND messages are not received.
	run_cmd "ip -n $h2 link set dev eth0.$vid down"
	log_test $? 0 "H2 down"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr -w 5000 $daddr eth0.$vid"
	log_test $? 2 "ndisc6"
	tc_check_packets $sw1 "dev vx0 egress" 101 5
	log_test $? 0 "NS suppression"
}

neigh_suppress_ns()
{
	local vid=10
	local saddr=2001:db8:1::1
	local daddr=2001:db8:1::2
	local maddr=ff02::1:ff00:2

	neigh_suppress_ns_common $vid $saddr $daddr $maddr

	vid=20
	saddr=2001:db8:2::1
	daddr=2001:db8:2::2
	maddr=ff02::1:ff00:2

	neigh_suppress_ns_common $vid $saddr $daddr $maddr
}

icmpv6_header_get()
{
	local csum=$1; shift
	local tip=$1; shift
	local type
	local p

	# Type 135 (Neighbor Solicitation), hex format
	type="87"
	p=$(:
		)"$type:"$(                     : ICMPv6.type
		)"00:"$(                        : ICMPv6.code
		)"$csum:"$(                     : ICMPv6.checksum
		)"00:00:00:00:"$(               : Reserved
	        )"$tip:"$(	                : Target Address
		)
	echo $p
}

neigh_suppress_uc_ns_common()
{
	local vid=$1; shift
	local sip=$1; shift
	local dip=$1; shift
	local full_dip=$1; shift
	local csum=$1; shift
	local tmac

	echo
	echo "Unicast NS, per-port NS suppression - VLAN $vid"
	echo "---------------------------------------------"

	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on"

	tmac=$(ip -n $h2 -j -p link show eth0.$vid | jq -r '.[]["address"]')
	run_cmd "bridge -n $sw1 fdb replace $tmac dev vx0 master static vlan $vid"
	run_cmd "ip -n $sw1 -6 neigh replace $dip lladdr $tmac nud permanent dev br0.$vid"

	run_cmd "tc -n $h1 qdisc replace dev eth0.$vid clsact"
	run_cmd "tc -n $h1 filter replace dev eth0.$vid ingress pref 1 handle 101 proto ipv6 flower ip_proto icmpv6 src_ip $dip type 136 code 0 action pass"

	run_cmd "tc -n $h2 qdisc replace dev eth0.$vid clsact"
	run_cmd "tc -n $h2 filter replace dev eth0.$vid egress pref 1 handle 101 proto ipv6 flower ip_proto icmpv6 dst_ip $sip type 136 code 0 action pass"

	run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a own -b $tmac -A $sip -B $dip -t ip hop=255,next=58,payload=$(icmpv6_header_get $csum $full_dip) -q"
	tc_check_packets $h1 "dev eth0.$vid ingress" 101 1
	log_test $? 0 "Unicast NS, suppression on, h1 filter"
	tc_check_packets $h2 "dev eth0.$vid egress" 101 1
	log_test $? 0 "Unicast NS, suppression on, h2 filter"
}

neigh_suppress_uc_ns()
{
	local vid=10
	local saddr=2001:db8:1::1
	local daddr=2001:db8:1::2
	local full_daddr=20:01:0d:b8:00:01:00:00:00:00:00:00:00:00:00:02
	local csum="ef:79"

	neigh_suppress_uc_ns_common $vid $saddr $daddr $full_daddr $csum

	vid=20
	saddr=2001:db8:2::1
	daddr=2001:db8:2::2
	full_daddr=20:01:0d:b8:00:02:00:00:00:00:00:00:00:00:00:02
	csum="ef:76"

	neigh_suppress_uc_ns_common $vid $saddr $daddr $full_daddr $csum
}

neigh_vlan_suppress_arp()
{
	local vid1=10
	local vid2=20
	local sip1=192.0.2.1
	local sip2=192.0.2.17
	local tip1=192.0.2.2
	local tip2=192.0.2.18
	local h2_mac1
	local h2_mac2

	echo
	echo "Per-{Port, VLAN} ARP suppression"
	echo "--------------------------------"

	run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
	run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto 0x0806 flower indev swp1 arp_tip $tip1 arp_sip $sip1 arp_op request action pass"
	run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 102 proto 0x0806 flower indev swp1 arp_tip $tip2 arp_sip $sip2 arp_op request action pass"

	h2_mac1=$(ip -n $h2 -j -p link show eth0.$vid1 | jq -r '.[]["address"]')
	h2_mac2=$(ip -n $h2 -j -p link show eth0.$vid2 | jq -r '.[]["address"]')
	run_cmd "bridge -n $sw1 fdb replace $h2_mac1 dev vx0 master static vlan $vid1"
	run_cmd "bridge -n $sw1 fdb replace $h2_mac2 dev vx0 master static vlan $vid2"
	run_cmd "ip -n $sw1 neigh replace $tip1 lladdr $h2_mac1 nud permanent dev br0.$vid1"
	run_cmd "ip -n $sw1 neigh replace $tip2 lladdr $h2_mac2 nud permanent dev br0.$vid2"

	# Enable per-{Port, VLAN} neighbor suppression and check that ARP
	# requests are not suppressed and that ARP replies are received.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_vlan_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_vlan_suppress on\""
	log_test $? 0 "\"neigh_vlan_suppress\" is on"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip1 -I eth0.$vid1 $tip1"
	log_test $? 0 "arping (VLAN $vid1)"
	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip2 -I eth0.$vid2 $tip2"
	log_test $? 0 "arping (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "ARP suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 1
	log_test $? 0 "ARP suppression (VLAN $vid2)"

	# Enable neighbor suppression on VLAN 10 and check that only on this
	# VLAN ARP requests are suppressed.
	run_cmd "bridge -n $sw1 vlan set vid $vid1 dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid1 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on (VLAN $vid1)"
	run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid2 | grep \"neigh_suppress off\""
	log_test $? 0 "\"neigh_suppress\" is off (VLAN $vid2)"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip1 -I eth0.$vid1 $tip1"
	log_test $? 0 "arping (VLAN $vid1)"
	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip2 -I eth0.$vid2 $tip2"
	log_test $? 0 "arping (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "ARP suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 2
	log_test $? 0 "ARP suppression (VLAN $vid2)"

	# Enable neighbor suppression on the port and check that it has no
	# effect compared to previous state.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip1 -I eth0.$vid1 $tip1"
	log_test $? 0 "arping (VLAN $vid1)"
	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip2 -I eth0.$vid2 $tip2"
	log_test $? 0 "arping (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "ARP suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 3
	log_test $? 0 "ARP suppression (VLAN $vid2)"

	# Disable neighbor suppression on the port and check that it has no
	# effect compared to previous state.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress off"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress off\""
	log_test $? 0 "\"neigh_suppress\" is off"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip1 -I eth0.$vid1 $tip1"
	log_test $? 0 "arping (VLAN $vid1)"
	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip2 -I eth0.$vid2 $tip2"
	log_test $? 0 "arping (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "ARP suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 4
	log_test $? 0 "ARP suppression (VLAN $vid2)"

	# Disable neighbor suppression on VLAN 10 and check that ARP requests
	# are no longer suppressed on this VLAN.
	run_cmd "bridge -n $sw1 vlan set vid $vid1 dev vx0 neigh_suppress off"
	run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid1 | grep \"neigh_suppress off\""
	log_test $? 0 "\"neigh_suppress\" is off (VLAN $vid1)"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip1 -I eth0.$vid1 $tip1"
	log_test $? 0 "arping (VLAN $vid1)"
	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip2 -I eth0.$vid2 $tip2"
	log_test $? 0 "arping (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 2
	log_test $? 0 "ARP suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 5
	log_test $? 0 "ARP suppression (VLAN $vid2)"

	# Disable per-{Port, VLAN} neighbor suppression, enable neighbor
	# suppression on the port and check that on both VLANs ARP requests are
	# suppressed.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_vlan_suppress off"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_vlan_suppress off\""
	log_test $? 0 "\"neigh_vlan_suppress\" is off"

	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on"

	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip1 -I eth0.$vid1 $tip1"
	log_test $? 0 "arping (VLAN $vid1)"
	run_cmd "ip netns exec $h1 arping -q -b -c 1 -w 5 -s $sip2 -I eth0.$vid2 $tip2"
	log_test $? 0 "arping (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 2
	log_test $? 0 "ARP suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 5
	log_test $? 0 "ARP suppression (VLAN $vid2)"
}

neigh_vlan_suppress_ns()
{
	local vid1=10
	local vid2=20
	local saddr1=2001:db8:1::1
	local saddr2=2001:db8:2::1
	local daddr1=2001:db8:1::2
	local daddr2=2001:db8:2::2
	local maddr=ff02::1:ff00:2
	local h2_mac1
	local h2_mac2

	echo
	echo "Per-{Port, VLAN} NS suppression"
	echo "-------------------------------"

	run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact"
	run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $maddr src_ip $saddr1 type 135 code 0 action pass"
	run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 102 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $maddr src_ip $saddr2 type 135 code 0 action pass"

	h2_mac1=$(ip -n $h2 -j -p link show eth0.$vid1 | jq -r '.[]["address"]')
	h2_mac2=$(ip -n $h2 -j -p link show eth0.$vid2 | jq -r '.[]["address"]')
	run_cmd "bridge -n $sw1 fdb replace $h2_mac1 dev vx0 master static vlan $vid1"
	run_cmd "bridge -n $sw1 fdb replace $h2_mac2 dev vx0 master static vlan $vid2"
	run_cmd "ip -n $sw1 neigh replace $daddr1 lladdr $h2_mac1 nud permanent dev br0.$vid1"
	run_cmd "ip -n $sw1 neigh replace $daddr2 lladdr $h2_mac2 nud permanent dev br0.$vid2"

	# Enable per-{Port, VLAN} neighbor suppression and check that NS
	# messages are not suppressed and that ND messages are received.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_vlan_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_vlan_suppress on\""
	log_test $? 0 "\"neigh_vlan_suppress\" is on"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr1 -w 5000 $daddr1 eth0.$vid1"
	log_test $? 0 "ndisc6 (VLAN $vid1)"
	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr2 -w 5000 $daddr2 eth0.$vid2"
	log_test $? 0 "ndisc6 (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "NS suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 1
	log_test $? 0 "NS suppression (VLAN $vid2)"

	# Enable neighbor suppression on VLAN 10 and check that only on this
	# VLAN NS messages are suppressed.
	run_cmd "bridge -n $sw1 vlan set vid $vid1 dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid1 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on (VLAN $vid1)"
	run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid2 | grep \"neigh_suppress off\""
	log_test $? 0 "\"neigh_suppress\" is off (VLAN $vid2)"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr1 -w 5000 $daddr1 eth0.$vid1"
	log_test $? 0 "ndisc6 (VLAN $vid1)"
	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr2 -w 5000 $daddr2 eth0.$vid2"
	log_test $? 0 "ndisc6 (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "NS suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 2
	log_test $? 0 "NS suppression (VLAN $vid2)"

	# Enable neighbor suppression on the port and check that it has no
	# effect compared to previous state.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr1 -w 5000 $daddr1 eth0.$vid1"
	log_test $? 0 "ndisc6 (VLAN $vid1)"
	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr2 -w 5000 $daddr2 eth0.$vid2"
	log_test $? 0 "ndisc6 (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "NS suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 3
	log_test $? 0 "NS suppression (VLAN $vid2)"

	# Disable neighbor suppression on the port and check that it has no
	# effect compared to previous state.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress off"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress off\""
	log_test $? 0 "\"neigh_suppress\" is off"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr1 -w 5000 $daddr1 eth0.$vid1"
	log_test $? 0 "ndisc6 (VLAN $vid1)"
	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr2 -w 5000 $daddr2 eth0.$vid2"
	log_test $? 0 "ndisc6 (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 1
	log_test $? 0 "NS suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 4
	log_test $? 0 "NS suppression (VLAN $vid2)"

	# Disable neighbor suppression on VLAN 10 and check that NS messages
	# are no longer suppressed on this VLAN.
	run_cmd "bridge -n $sw1 vlan set vid $vid1 dev vx0 neigh_suppress off"
	run_cmd "bridge -n $sw1 -d vlan show dev vx0 vid $vid1 | grep \"neigh_suppress off\""
	log_test $? 0 "\"neigh_suppress\" is off (VLAN $vid1)"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr1 -w 5000 $daddr1 eth0.$vid1"
	log_test $? 0 "ndisc6 (VLAN $vid1)"
	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr2 -w 5000 $daddr2 eth0.$vid2"
	log_test $? 0 "ndisc6 (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 2
	log_test $? 0 "NS suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 5
	log_test $? 0 "NS suppression (VLAN $vid2)"

	# Disable per-{Port, VLAN} neighbor suppression, enable neighbor
	# suppression on the port and check that on both VLANs NS messages are
	# suppressed.
	run_cmd "bridge -n $sw1 link set dev vx0 neigh_vlan_suppress off"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_vlan_suppress off\""
	log_test $? 0 "\"neigh_vlan_suppress\" is off"

	run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on"
	run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\""
	log_test $? 0 "\"neigh_suppress\" is on"

	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr1 -w 5000 $daddr1 eth0.$vid1"
	log_test $? 0 "ndisc6 (VLAN $vid1)"
	run_cmd "ip netns exec $h1 ndisc6 -q -r 1 -s $saddr2 -w 5000 $daddr2 eth0.$vid2"
	log_test $? 0 "ndisc6 (VLAN $vid2)"

	tc_check_packets $sw1 "dev vx0 egress" 101 2
	log_test $? 0 "NS suppression (VLAN $vid1)"
	tc_check_packets $sw1 "dev vx0 egress" 102 5
	log_test $? 0 "NS suppression (VLAN $vid2)"
}

################################################################################
# Usage

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

        -t <test>   Test(s) to run (default: all)
                    (options: $TESTS)
        -p          Pause on fail
        -P          Pause after each test before cleanup
        -v          Verbose mode (show commands and output)
EOF
}

################################################################################
# Main

trap cleanup EXIT

while getopts ":t:pPvh" opt; do
	case $opt in
		t) TESTS=$OPTARG;;
		p) PAUSE_ON_FAIL=yes;;
		P) PAUSE=yes;;
		v) VERBOSE=$(($VERBOSE + 1));;
		h) usage; exit 0;;
		*) usage; exit 1;;
	esac
done

# Make sure we don't pause twice.
[ "${PAUSE}" = "yes" ] && PAUSE_ON_FAIL=no

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

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

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

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

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

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

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

bridge link help 2>&1 | grep -q "neigh_vlan_suppress"
if [ $? -ne 0 ]; then
   echo "SKIP: iproute2 bridge too old, missing per-VLAN neighbor suppression support"
   exit $ksft_skip
fi

# Start clean.
cleanup

for t in $TESTS
do
	setup; $t; cleanup;
done

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

exit $ret
