1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3#
4# 2 namespaces: one host and one router. Use arping from the host to send a
5# garp to the router. Router accepts or ignores based on its arp_accept
6# or accept_untracked_na configuration.
7
8source lib.sh
9
10TESTS="arp ndisc"
11
12ROUTER_INTF="veth-router"
13ROUTER_ADDR="10.0.10.1"
14ROUTER_ADDR_V6="2001:db8:abcd:0012::1"
15
16HOST_INTF="veth-host"
17HOST_ADDR="10.0.10.2"
18HOST_ADDR_V6="2001:db8:abcd:0012::2"
19
20SUBNET_WIDTH=24
21PREFIX_WIDTH_V6=64
22
23cleanup() {
24	cleanup_ns ${HOST_NS} ${ROUTER_NS}
25}
26
27cleanup_v6() {
28	cleanup_ns ${HOST_NS_V6} ${ROUTER_NS_V6}
29}
30
31setup() {
32	set -e
33	local arp_accept=$1
34
35	# Set up two namespaces
36	setup_ns HOST_NS ROUTER_NS
37
38	# Set up interfaces veth0 and veth1, which are pairs in separate
39	# namespaces. veth0 is veth-router, veth1 is veth-host.
40	# first, set up the inteface's link to the namespace
41	# then, set the interface "up"
42	ip netns exec ${ROUTER_NS} ip link add name ${ROUTER_INTF} \
43		type veth peer name ${HOST_INTF}
44
45	ip netns exec ${ROUTER_NS} ip link set dev ${ROUTER_INTF} up
46	ip netns exec ${ROUTER_NS} ip link set dev ${HOST_INTF} netns ${HOST_NS}
47
48	ip netns exec ${HOST_NS} ip link set dev ${HOST_INTF} up
49	ip netns exec ${ROUTER_NS} ip addr add ${ROUTER_ADDR}/${SUBNET_WIDTH} \
50		dev ${ROUTER_INTF}
51
52	ip netns exec ${HOST_NS} ip addr add ${HOST_ADDR}/${SUBNET_WIDTH} \
53		dev ${HOST_INTF}
54	ip netns exec ${HOST_NS} ip route add default via ${HOST_ADDR} \
55		dev ${HOST_INTF}
56	ip netns exec ${ROUTER_NS} ip route add default via ${ROUTER_ADDR} \
57		dev ${ROUTER_INTF}
58
59	ROUTER_CONF=net.ipv4.conf.${ROUTER_INTF}
60	ip netns exec ${ROUTER_NS} sysctl -w \
61		${ROUTER_CONF}.arp_accept=${arp_accept} >/dev/null 2>&1
62	set +e
63}
64
65setup_v6() {
66	set -e
67	local accept_untracked_na=$1
68
69	# Set up two namespaces
70	setup_ns HOST_NS_V6 ROUTER_NS_V6
71
72	# Set up interfaces veth0 and veth1, which are pairs in separate
73	# namespaces. veth0 is veth-router, veth1 is veth-host.
74	# first, set up the inteface's link to the namespace
75	# then, set the interface "up"
76	ip -n ${ROUTER_NS_V6} link add name ${ROUTER_INTF} \
77		type veth peer name ${HOST_INTF} netns ${HOST_NS_V6}
78
79	# Add tc rule to filter out host na message
80	tc -n ${ROUTER_NS_V6} qdisc add dev ${ROUTER_INTF} clsact
81	tc -n ${ROUTER_NS_V6} filter add dev ${ROUTER_INTF} \
82		ingress protocol ipv6 pref 1 handle 101 \
83		flower src_ip ${HOST_ADDR_V6} ip_proto icmpv6 type 136 skip_hw action pass
84
85	HOST_CONF=net.ipv6.conf.${HOST_INTF}
86	ip netns exec ${HOST_NS_V6} sysctl -qw ${HOST_CONF}.ndisc_notify=1
87	ip netns exec ${HOST_NS_V6} sysctl -qw ${HOST_CONF}.disable_ipv6=0
88	ROUTER_CONF=net.ipv6.conf.${ROUTER_INTF}
89	ip netns exec ${ROUTER_NS_V6} sysctl -w \
90		${ROUTER_CONF}.forwarding=1 >/dev/null 2>&1
91	ip netns exec ${ROUTER_NS_V6} sysctl -w \
92		${ROUTER_CONF}.drop_unsolicited_na=0 >/dev/null 2>&1
93	ip netns exec ${ROUTER_NS_V6} sysctl -w \
94		${ROUTER_CONF}.accept_untracked_na=${accept_untracked_na} \
95		>/dev/null 2>&1
96
97	ip -n ${ROUTER_NS_V6} link set dev ${ROUTER_INTF} up
98	ip -n ${HOST_NS_V6} link set dev ${HOST_INTF} up
99	ip -n ${ROUTER_NS_V6} addr add ${ROUTER_ADDR_V6}/${PREFIX_WIDTH_V6} \
100		dev ${ROUTER_INTF} nodad
101	ip -n ${HOST_NS_V6} addr add ${HOST_ADDR_V6}/${PREFIX_WIDTH_V6} \
102		dev ${HOST_INTF}
103	set +e
104}
105
106verify_arp() {
107	local arp_accept=$1
108	local same_subnet=$2
109
110	neigh_show_output=$(ip netns exec ${ROUTER_NS} ip neigh get \
111		${HOST_ADDR} dev ${ROUTER_INTF} 2>/dev/null)
112
113	if [ ${arp_accept} -eq 1 ]; then
114		# Neighbor entries expected
115		[[ ${neigh_show_output} ]]
116	elif [ ${arp_accept} -eq 2 ]; then
117		if [ ${same_subnet} -eq 1 ]; then
118			# Neighbor entries expected
119			[[ ${neigh_show_output} ]]
120		else
121			[[ -z "${neigh_show_output}" ]]
122		fi
123	else
124		[[ -z "${neigh_show_output}" ]]
125	fi
126 }
127
128arp_test_gratuitous() {
129	set -e
130	local arp_accept=$1
131	local same_subnet=$2
132
133	if [ ${arp_accept} -eq 2 ]; then
134		test_msg=("test_arp: "
135			  "accept_arp=$1 "
136			  "same_subnet=$2")
137		if [ ${same_subnet} -eq 0 ]; then
138			HOST_ADDR=10.0.11.3
139		else
140			HOST_ADDR=10.0.10.3
141		fi
142	else
143		test_msg=("test_arp: "
144			  "accept_arp=$1")
145	fi
146	# Supply arp_accept option to set up which sets it in sysctl
147	setup ${arp_accept}
148	ip netns exec ${HOST_NS} arping -A -I ${HOST_INTF} -U ${HOST_ADDR} -c1 2>&1 >/dev/null
149
150	if verify_arp $1 $2; then
151		printf "    TEST: %-60s  [ OK ]\n" "${test_msg[*]}"
152	else
153		printf "    TEST: %-60s  [FAIL]\n" "${test_msg[*]}"
154	fi
155	cleanup
156	set +e
157}
158
159arp_test_gratuitous_combinations() {
160	arp_test_gratuitous 0
161	arp_test_gratuitous 1
162	arp_test_gratuitous 2 0 # Second entry indicates subnet or not
163	arp_test_gratuitous 2 1
164}
165
166verify_ndisc() {
167	local accept_untracked_na=$1
168	local same_subnet=$2
169
170	neigh_show_output=$(ip -6 -netns ${ROUTER_NS_V6} neigh show \
171		to ${HOST_ADDR_V6} dev ${ROUTER_INTF} nud stale)
172
173	if [ ${accept_untracked_na} -eq 1 ]; then
174		# Neighbour entry expected to be present
175		[[ ${neigh_show_output} ]]
176	elif [ ${accept_untracked_na} -eq 2 ]; then
177		if [ ${same_subnet} -eq 1 ]; then
178			[[ ${neigh_show_output} ]]
179		else
180			[[ -z "${neigh_show_output}" ]]
181		fi
182	else
183		# Neighbour entry expected to be absent for all other cases
184		[[ -z "${neigh_show_output}" ]]
185	fi
186}
187
188ndisc_test_untracked_advertisements() {
189	set -e
190	test_msg=("test_ndisc: "
191		  "accept_untracked_na=$1")
192
193	local accept_untracked_na=$1
194	local same_subnet=$2
195	if [ ${accept_untracked_na} -eq 2 ]; then
196		test_msg=("test_ndisc: "
197			  "accept_untracked_na=$1 "
198			  "same_subnet=$2")
199		if [ ${same_subnet} -eq 0 ]; then
200			# Not same subnet
201			HOST_ADDR_V6=2000:db8:abcd:0013::4
202		else
203			HOST_ADDR_V6=2001:db8:abcd:0012::3
204		fi
205	fi
206	setup_v6 $1
207	slowwait_for_counter 15 1 \
208		tc_rule_handle_stats_get "dev ${ROUTER_INTF} ingress" 101 ".packets" "-n ${ROUTER_NS_V6}"
209
210	if verify_ndisc $1 $2; then
211		printf "    TEST: %-60s  [ OK ]\n" "${test_msg[*]}"
212	else
213		printf "    TEST: %-60s  [FAIL]\n" "${test_msg[*]}"
214	fi
215
216	cleanup_v6
217	set +e
218}
219
220ndisc_test_untracked_combinations() {
221	ndisc_test_untracked_advertisements 0
222	ndisc_test_untracked_advertisements 1
223	ndisc_test_untracked_advertisements 2 0
224	ndisc_test_untracked_advertisements 2 1
225}
226
227################################################################################
228# usage
229
230usage()
231{
232	cat <<EOF
233usage: ${0##*/} OPTS
234
235	-t <test>       Test(s) to run (default: all)
236			(options: $TESTS)
237EOF
238}
239
240################################################################################
241# main
242
243while getopts ":t:h" opt; do
244	case $opt in
245		t) TESTS=$OPTARG;;
246		h) usage; exit 0;;
247		*) usage; exit 1;;
248	esac
249done
250
251if [ "$(id -u)" -ne 0 ];then
252	echo "SKIP: Need root privileges"
253	exit $ksft_skip;
254fi
255
256if [ ! -x "$(command -v ip)" ]; then
257	echo "SKIP: Could not run test without ip tool"
258	exit $ksft_skip
259fi
260
261if [ ! -x "$(command -v tcpdump)" ]; then
262	echo "SKIP: Could not run test without tcpdump tool"
263	exit $ksft_skip
264fi
265
266if [ ! -x "$(command -v arping)" ]; then
267	echo "SKIP: Could not run test without arping tool"
268	exit $ksft_skip
269fi
270
271# start clean
272cleanup &> /dev/null
273cleanup_v6 &> /dev/null
274
275for t in $TESTS
276do
277	case $t in
278	arp_test_gratuitous_combinations|arp) arp_test_gratuitous_combinations;;
279	ndisc_test_untracked_combinations|ndisc) \
280		ndisc_test_untracked_combinations;;
281	help) echo "Test names: $TESTS"; exit 0;;
282esac
283done
284