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