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