1#!/bin/bash
2#
3# This tests nf_queue:
4# 1. can process packets from all hooks
5# 2. support running nfqueue from more than one base chain
6#
7# Kselftest framework requirement - SKIP code is 4.
8ksft_skip=4
9ret=0
10
11sfx=$(mktemp -u "XXXXXXXX")
12ns1="ns1-$sfx"
13ns2="ns2-$sfx"
14nsrouter="nsrouter-$sfx"
15timeout=4
16
17cleanup()
18{
19	ip netns del ${ns1}
20	ip netns del ${ns2}
21	ip netns del ${nsrouter}
22	rm -f "$TMPFILE0"
23	rm -f "$TMPFILE1"
24	rm -f "$TMPFILE2" "$TMPFILE3"
25}
26
27nft --version > /dev/null 2>&1
28if [ $? -ne 0 ];then
29	echo "SKIP: Could not run test without nft tool"
30	exit $ksft_skip
31fi
32
33ip -Version > /dev/null 2>&1
34if [ $? -ne 0 ];then
35	echo "SKIP: Could not run test without ip tool"
36	exit $ksft_skip
37fi
38
39ip netns add ${nsrouter}
40if [ $? -ne 0 ];then
41	echo "SKIP: Could not create net namespace"
42	exit $ksft_skip
43fi
44
45TMPFILE0=$(mktemp)
46TMPFILE1=$(mktemp)
47TMPFILE2=$(mktemp)
48TMPFILE3=$(mktemp)
49trap cleanup EXIT
50
51ip netns add ${ns1}
52ip netns add ${ns2}
53
54ip link add veth0 netns ${nsrouter} type veth peer name eth0 netns ${ns1} > /dev/null 2>&1
55if [ $? -ne 0 ];then
56    echo "SKIP: No virtual ethernet pair device support in kernel"
57    exit $ksft_skip
58fi
59ip link add veth1 netns ${nsrouter} type veth peer name eth0 netns ${ns2}
60
61ip -net ${nsrouter} link set lo up
62ip -net ${nsrouter} link set veth0 up
63ip -net ${nsrouter} addr add 10.0.1.1/24 dev veth0
64ip -net ${nsrouter} addr add dead:1::1/64 dev veth0
65
66ip -net ${nsrouter} link set veth1 up
67ip -net ${nsrouter} addr add 10.0.2.1/24 dev veth1
68ip -net ${nsrouter} addr add dead:2::1/64 dev veth1
69
70ip -net ${ns1} link set lo up
71ip -net ${ns1} link set eth0 up
72
73ip -net ${ns2} link set lo up
74ip -net ${ns2} link set eth0 up
75
76ip -net ${ns1} addr add 10.0.1.99/24 dev eth0
77ip -net ${ns1} addr add dead:1::99/64 dev eth0
78ip -net ${ns1} route add default via 10.0.1.1
79ip -net ${ns1} route add default via dead:1::1
80
81ip -net ${ns2} addr add 10.0.2.99/24 dev eth0
82ip -net ${ns2} addr add dead:2::99/64 dev eth0
83ip -net ${ns2} route add default via 10.0.2.1
84ip -net ${ns2} route add default via dead:2::1
85
86load_ruleset() {
87	local name=$1
88	local prio=$2
89
90ip netns exec ${nsrouter} nft -f /dev/stdin <<EOF
91table inet $name {
92	chain nfq {
93		ip protocol icmp queue bypass
94		icmpv6 type { "echo-request", "echo-reply" } queue num 1 bypass
95	}
96	chain pre {
97		type filter hook prerouting priority $prio; policy accept;
98		jump nfq
99	}
100	chain input {
101		type filter hook input priority $prio; policy accept;
102		jump nfq
103	}
104	chain forward {
105		type filter hook forward priority $prio; policy accept;
106		tcp dport 12345 queue num 2
107		jump nfq
108	}
109	chain output {
110		type filter hook output priority $prio; policy accept;
111		tcp dport 12345 queue num 3
112		jump nfq
113	}
114	chain post {
115		type filter hook postrouting priority $prio; policy accept;
116		jump nfq
117	}
118}
119EOF
120}
121
122load_counter_ruleset() {
123	local prio=$1
124
125ip netns exec ${nsrouter} nft -f /dev/stdin <<EOF
126table inet countrules {
127	chain pre {
128		type filter hook prerouting priority $prio; policy accept;
129		counter
130	}
131	chain input {
132		type filter hook input priority $prio; policy accept;
133		counter
134	}
135	chain forward {
136		type filter hook forward priority $prio; policy accept;
137		counter
138	}
139	chain output {
140		type filter hook output priority $prio; policy accept;
141		counter
142	}
143	chain post {
144		type filter hook postrouting priority $prio; policy accept;
145		counter
146	}
147}
148EOF
149}
150
151test_ping() {
152  ip netns exec ${ns1} ping -c 1 -q 10.0.2.99 > /dev/null
153  if [ $? -ne 0 ];then
154	return 1
155  fi
156
157  ip netns exec ${ns1} ping -c 1 -q dead:2::99 > /dev/null
158  if [ $? -ne 0 ];then
159	return 1
160  fi
161
162  return 0
163}
164
165test_ping_router() {
166  ip netns exec ${ns1} ping -c 1 -q 10.0.2.1 > /dev/null
167  if [ $? -ne 0 ];then
168	return 1
169  fi
170
171  ip netns exec ${ns1} ping -c 1 -q dead:2::1 > /dev/null
172  if [ $? -ne 0 ];then
173	return 1
174  fi
175
176  return 0
177}
178
179test_queue_blackhole() {
180	local proto=$1
181
182ip netns exec ${nsrouter} nft -f /dev/stdin <<EOF
183table $proto blackh {
184	chain forward {
185	type filter hook forward priority 0; policy accept;
186		queue num 600
187	}
188}
189EOF
190	if [ $proto = "ip" ] ;then
191		ip netns exec ${ns1} ping -W 2 -c 1 -q 10.0.2.99 > /dev/null
192		lret=$?
193	elif [ $proto = "ip6" ]; then
194		ip netns exec ${ns1} ping -W 2 -c 1 -q dead:2::99 > /dev/null
195		lret=$?
196	else
197		lret=111
198	fi
199
200	# queue without bypass keyword should drop traffic if no listener exists.
201	if [ $lret -eq 0 ];then
202		echo "FAIL: $proto expected failure, got $lret" 1>&2
203		exit 1
204	fi
205
206	ip netns exec ${nsrouter} nft delete table $proto blackh
207	if [ $? -ne 0 ] ;then
208	        echo "FAIL: $proto: Could not delete blackh table"
209	        exit 1
210	fi
211
212        echo "PASS: $proto: statement with no listener results in packet drop"
213}
214
215test_queue()
216{
217	local expected=$1
218	local last=""
219
220	# spawn nf-queue listeners
221	ip netns exec ${nsrouter} ./nf-queue -c -q 0 -t $timeout > "$TMPFILE0" &
222	ip netns exec ${nsrouter} ./nf-queue -c -q 1 -t $timeout > "$TMPFILE1" &
223	sleep 1
224	test_ping
225	ret=$?
226	if [ $ret -ne 0 ];then
227		echo "FAIL: netns routing/connectivity with active listener on queue $queue: $ret" 1>&2
228		exit $ret
229	fi
230
231	test_ping_router
232	ret=$?
233	if [ $ret -ne 0 ];then
234		echo "FAIL: netns router unreachable listener on queue $queue: $ret" 1>&2
235		exit $ret
236	fi
237
238	wait
239	ret=$?
240
241	for file in $TMPFILE0 $TMPFILE1; do
242		last=$(tail -n1 "$file")
243		if [ x"$last" != x"$expected packets total" ]; then
244			echo "FAIL: Expected $expected packets total, but got $last" 1>&2
245			cat "$file" 1>&2
246
247			ip netns exec ${nsrouter} nft list ruleset
248			exit 1
249		fi
250	done
251
252	echo "PASS: Expected and received $last"
253}
254
255test_tcp_forward()
256{
257	ip netns exec ${nsrouter} ./nf-queue -q 2 -t $timeout &
258	local nfqpid=$!
259
260	tmpfile=$(mktemp) || exit 1
261	dd conv=sparse status=none if=/dev/zero bs=1M count=200 of=$tmpfile
262	ip netns exec ${ns2} nc -w 5 -l -p 12345 <"$tmpfile" >/dev/null &
263	local rpid=$!
264
265	sleep 1
266	ip netns exec ${ns1} nc -w 5 10.0.2.99 12345 <"$tmpfile" >/dev/null &
267
268	rm -f "$tmpfile"
269
270	wait $rpid
271	wait $lpid
272	[ $? -eq 0 ] && echo "PASS: tcp and nfqueue in forward chain"
273}
274
275test_tcp_localhost()
276{
277	tmpfile=$(mktemp) || exit 1
278
279	dd conv=sparse status=none if=/dev/zero bs=1M count=200 of=$tmpfile
280	ip netns exec ${nsrouter} nc -w 5 -l -p 12345 <"$tmpfile" >/dev/null &
281	local rpid=$!
282
283	ip netns exec ${nsrouter} ./nf-queue -q 3 -t $timeout &
284	local nfqpid=$!
285
286	sleep 1
287	ip netns exec ${nsrouter} nc -w 5 127.0.0.1 12345 <"$tmpfile" > /dev/null
288	rm -f "$tmpfile"
289
290	wait $rpid
291	[ $? -eq 0 ] && echo "PASS: tcp via loopback"
292	wait 2>/dev/null
293}
294
295test_tcp_localhost_requeue()
296{
297ip netns exec ${nsrouter} nft -f /dev/stdin <<EOF
298flush ruleset
299table inet filter {
300	chain output {
301		type filter hook output priority 0; policy accept;
302		tcp dport 12345 limit rate 1/second burst 1 packets counter queue num 0
303	}
304	chain post {
305		type filter hook postrouting priority 0; policy accept;
306		tcp dport 12345 limit rate 1/second burst 1 packets counter queue num 0
307	}
308}
309EOF
310	tmpfile=$(mktemp) || exit 1
311	dd conv=sparse status=none if=/dev/zero bs=1M count=200 of=$tmpfile
312	ip netns exec ${nsrouter} nc -w 5 -l -p 12345 <"$tmpfile" >/dev/null &
313	local rpid=$!
314
315	ip netns exec ${nsrouter} ./nf-queue -c -q 1 -t $timeout > "$TMPFILE2" &
316
317	# nfqueue 1 will be called via output hook.  But this time,
318        # re-queue the packet to nfqueue program on queue 2.
319	ip netns exec ${nsrouter} ./nf-queue -G -d 150 -c -q 0 -Q 1 -t $timeout > "$TMPFILE3" &
320
321	sleep 1
322	ip netns exec ${nsrouter} nc -w 5 127.0.0.1 12345 <"$tmpfile" > /dev/null
323	rm -f "$tmpfile"
324
325	wait
326
327	if ! diff -u "$TMPFILE2" "$TMPFILE3" ; then
328		echo "FAIL: lost packets during requeue?!" 1>&2
329		return
330	fi
331
332	echo "PASS: tcp via loopback and re-queueing"
333}
334
335ip netns exec ${nsrouter} sysctl net.ipv6.conf.all.forwarding=1 > /dev/null
336ip netns exec ${nsrouter} sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null
337ip netns exec ${nsrouter} sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null
338
339load_ruleset "filter" 0
340
341sleep 3
342
343test_ping
344ret=$?
345if [ $ret -eq 0 ];then
346	# queue bypass works (rules were skipped, no listener)
347	echo "PASS: ${ns1} can reach ${ns2}"
348else
349	echo "FAIL: ${ns1} cannot reach ${ns2}: $ret" 1>&2
350	exit $ret
351fi
352
353test_queue_blackhole ip
354test_queue_blackhole ip6
355
356# dummy ruleset to add base chains between the
357# queueing rules.  We don't want the second reinject
358# to re-execute the old hooks.
359load_counter_ruleset 10
360
361# we are hooking all: prerouting/input/forward/output/postrouting.
362# we ping ${ns2} from ${ns1} via ${nsrouter} using ipv4 and ipv6, so:
363# 1x icmp prerouting,forward,postrouting -> 3 queue events (6 incl. reply).
364# 1x icmp prerouting,input,output postrouting -> 4 queue events incl. reply.
365# so we expect that userspace program receives 10 packets.
366test_queue 10
367
368# same.  We queue to a second program as well.
369load_ruleset "filter2" 20
370test_queue 20
371
372test_tcp_forward
373test_tcp_localhost
374test_tcp_localhost_requeue
375
376exit $ret
377