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 -6 -netns ${ROUTER_NS_V6} link add name ${ROUTER_INTF} \
77		type veth peer name ${HOST_INTF}
78
79	ip -6 -netns ${ROUTER_NS_V6} link set dev ${ROUTER_INTF} up
80	ip -6 -netns ${ROUTER_NS_V6} link set dev ${HOST_INTF} netns \
81		${HOST_NS_V6}
82
83	ip -6 -netns ${HOST_NS_V6} link set dev ${HOST_INTF} up
84	ip -6 -netns ${ROUTER_NS_V6} addr add \
85		${ROUTER_ADDR_V6}/${PREFIX_WIDTH_V6} dev ${ROUTER_INTF} nodad
86
87	HOST_CONF=net.ipv6.conf.${HOST_INTF}
88	ip netns exec ${HOST_NS_V6} sysctl -qw ${HOST_CONF}.ndisc_notify=1
89	ip netns exec ${HOST_NS_V6} sysctl -qw ${HOST_CONF}.disable_ipv6=0
90	ip -6 -netns ${HOST_NS_V6} addr add ${HOST_ADDR_V6}/${PREFIX_WIDTH_V6} \
91		dev ${HOST_INTF}
92
93	ROUTER_CONF=net.ipv6.conf.${ROUTER_INTF}
94
95	ip netns exec ${ROUTER_NS_V6} sysctl -w \
96		${ROUTER_CONF}.forwarding=1 >/dev/null 2>&1
97	ip netns exec ${ROUTER_NS_V6} sysctl -w \
98		${ROUTER_CONF}.drop_unsolicited_na=0 >/dev/null 2>&1
99	ip netns exec ${ROUTER_NS_V6} sysctl -w \
100		${ROUTER_CONF}.accept_untracked_na=${accept_untracked_na} \
101		>/dev/null 2>&1
102	set +e
103}
104
105verify_arp() {
106	local arp_accept=$1
107	local same_subnet=$2
108
109	neigh_show_output=$(ip netns exec ${ROUTER_NS} ip neigh get \
110		${HOST_ADDR} dev ${ROUTER_INTF} 2>/dev/null)
111
112	if [ ${arp_accept} -eq 1 ]; then
113		# Neighbor entries expected
114		[[ ${neigh_show_output} ]]
115	elif [ ${arp_accept} -eq 2 ]; then
116		if [ ${same_subnet} -eq 1 ]; then
117			# Neighbor entries expected
118			[[ ${neigh_show_output} ]]
119		else
120			[[ -z "${neigh_show_output}" ]]
121		fi
122	else
123		[[ -z "${neigh_show_output}" ]]
124	fi
125 }
126
127arp_test_gratuitous() {
128	set -e
129	local arp_accept=$1
130	local same_subnet=$2
131
132	if [ ${arp_accept} -eq 2 ]; then
133		test_msg=("test_arp: "
134			  "accept_arp=$1 "
135			  "same_subnet=$2")
136		if [ ${same_subnet} -eq 0 ]; then
137			HOST_ADDR=10.0.11.3
138		else
139			HOST_ADDR=10.0.10.3
140		fi
141	else
142		test_msg=("test_arp: "
143			  "accept_arp=$1")
144	fi
145	# Supply arp_accept option to set up which sets it in sysctl
146	setup ${arp_accept}
147	ip netns exec ${HOST_NS} arping -A -I ${HOST_INTF} -U ${HOST_ADDR} -c1 2>&1 >/dev/null
148
149	if verify_arp $1 $2; then
150		printf "    TEST: %-60s  [ OK ]\n" "${test_msg[*]}"
151	else
152		printf "    TEST: %-60s  [FAIL]\n" "${test_msg[*]}"
153	fi
154	cleanup
155	set +e
156}
157
158arp_test_gratuitous_combinations() {
159	arp_test_gratuitous 0
160	arp_test_gratuitous 1
161	arp_test_gratuitous 2 0 # Second entry indicates subnet or not
162	arp_test_gratuitous 2 1
163}
164
165cleanup_tcpdump() {
166	set -e
167	[[ ! -z  ${tcpdump_stdout} ]] && rm -f ${tcpdump_stdout}
168	[[ ! -z  ${tcpdump_stderr} ]] && rm -f ${tcpdump_stderr}
169	tcpdump_stdout=
170	tcpdump_stderr=
171	set +e
172}
173
174start_tcpdump() {
175	set -e
176	tcpdump_stdout=`mktemp`
177	tcpdump_stderr=`mktemp`
178	ip netns exec ${ROUTER_NS_V6} timeout 15s \
179		tcpdump --immediate-mode -tpni ${ROUTER_INTF} -c 1 \
180		"icmp6 && icmp6[0] == 136 && src ${HOST_ADDR_V6}" \
181		> ${tcpdump_stdout} 2> /dev/null
182	set +e
183}
184
185verify_ndisc() {
186	local accept_untracked_na=$1
187	local same_subnet=$2
188
189	neigh_show_output=$(ip -6 -netns ${ROUTER_NS_V6} neigh show \
190		to ${HOST_ADDR_V6} dev ${ROUTER_INTF} nud stale)
191
192	if [ ${accept_untracked_na} -eq 1 ]; then
193		# Neighbour entry expected to be present
194		[[ ${neigh_show_output} ]]
195	elif [ ${accept_untracked_na} -eq 2 ]; then
196		if [ ${same_subnet} -eq 1 ]; then
197			[[ ${neigh_show_output} ]]
198		else
199			[[ -z "${neigh_show_output}" ]]
200		fi
201	else
202		# Neighbour entry expected to be absent for all other cases
203		[[ -z "${neigh_show_output}" ]]
204	fi
205}
206
207ndisc_test_untracked_advertisements() {
208	set -e
209	test_msg=("test_ndisc: "
210		  "accept_untracked_na=$1")
211
212	local accept_untracked_na=$1
213	local same_subnet=$2
214	if [ ${accept_untracked_na} -eq 2 ]; then
215		test_msg=("test_ndisc: "
216			  "accept_untracked_na=$1 "
217			  "same_subnet=$2")
218		if [ ${same_subnet} -eq 0 ]; then
219			# Not same subnet
220			HOST_ADDR_V6=2000:db8:abcd:0013::4
221		else
222			HOST_ADDR_V6=2001:db8:abcd:0012::3
223		fi
224	fi
225	setup_v6 $1 $2
226	start_tcpdump
227
228	if verify_ndisc $1 $2; then
229		printf "    TEST: %-60s  [ OK ]\n" "${test_msg[*]}"
230	else
231		printf "    TEST: %-60s  [FAIL]\n" "${test_msg[*]}"
232	fi
233
234	cleanup_tcpdump
235	cleanup_v6
236	set +e
237}
238
239ndisc_test_untracked_combinations() {
240	ndisc_test_untracked_advertisements 0
241	ndisc_test_untracked_advertisements 1
242	ndisc_test_untracked_advertisements 2 0
243	ndisc_test_untracked_advertisements 2 1
244}
245
246################################################################################
247# usage
248
249usage()
250{
251	cat <<EOF
252usage: ${0##*/} OPTS
253
254	-t <test>       Test(s) to run (default: all)
255			(options: $TESTS)
256EOF
257}
258
259################################################################################
260# main
261
262while getopts ":t:h" opt; do
263	case $opt in
264		t) TESTS=$OPTARG;;
265		h) usage; exit 0;;
266		*) usage; exit 1;;
267	esac
268done
269
270if [ "$(id -u)" -ne 0 ];then
271	echo "SKIP: Need root privileges"
272	exit $ksft_skip;
273fi
274
275if [ ! -x "$(command -v ip)" ]; then
276	echo "SKIP: Could not run test without ip tool"
277	exit $ksft_skip
278fi
279
280if [ ! -x "$(command -v tcpdump)" ]; then
281	echo "SKIP: Could not run test without tcpdump tool"
282	exit $ksft_skip
283fi
284
285if [ ! -x "$(command -v arping)" ]; then
286	echo "SKIP: Could not run test without arping tool"
287	exit $ksft_skip
288fi
289
290# start clean
291cleanup &> /dev/null
292cleanup_v6 &> /dev/null
293
294for t in $TESTS
295do
296	case $t in
297	arp_test_gratuitous_combinations|arp) arp_test_gratuitous_combinations;;
298	ndisc_test_untracked_combinations|ndisc) \
299		ndisc_test_untracked_combinations;;
300	help) echo "Test names: $TESTS"; exit 0;;
301esac
302done
303