xref: /linux/tools/testing/selftests/net/netfilter/conntrack_resize.sh (revision d69eb204c255c35abd9e8cb621484e8074c75eaa)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4source lib.sh
5
6checktool "conntrack --version" "run test without conntrack"
7checktool "nft --version" "run test without nft tool"
8
9init_net_max=0
10ct_buckets=0
11tmpfile=""
12tmpfile_proc=""
13tmpfile_uniq=""
14ret=0
15have_socat=0
16
17socat -h > /dev/null && have_socat=1
18
19insert_count=2000
20[ "$KSFT_MACHINE_SLOW" = "yes" ] && insert_count=400
21
22modprobe -q nf_conntrack
23if ! sysctl -q net.netfilter.nf_conntrack_max >/dev/null;then
24	echo "SKIP: conntrack sysctls not available"
25	exit $KSFT_SKIP
26fi
27
28init_net_max=$(sysctl -n net.netfilter.nf_conntrack_max) || exit 1
29ct_buckets=$(sysctl -n net.netfilter.nf_conntrack_buckets) || exit 1
30
31cleanup() {
32	cleanup_all_ns
33
34	rm -f "$tmpfile" "$tmpfile_proc" "$tmpfile_uniq"
35
36	# restore original sysctl setting
37	sysctl -q net.netfilter.nf_conntrack_max=$init_net_max
38	sysctl -q net.netfilter.nf_conntrack_buckets=$ct_buckets
39}
40trap cleanup EXIT
41
42check_max_alias()
43{
44	local expected="$1"
45	# old name, expected to alias to the first, i.e. changing one
46	# changes the other as well.
47	local lv=$(sysctl -n net.nf_conntrack_max)
48
49	if [ $expected -ne "$lv" ];then
50		echo "nf_conntrack_max sysctls should have identical values"
51		exit 1
52	fi
53}
54
55insert_ctnetlink() {
56	local ns="$1"
57	local count="$2"
58	local i=0
59	local bulk=16
60
61	while [ $i -lt $count ] ;do
62		ip netns exec "$ns" bash -c "for i in \$(seq 1 $bulk); do \
63			if ! conntrack -I -s \$((\$RANDOM%256)).\$((\$RANDOM%256)).\$((\$RANDOM%256)).\$((\$RANDOM%255+1)) \
64					  -d \$((\$RANDOM%256)).\$((\$RANDOM%256)).\$((\$RANDOM%256)).\$((\$RANDOM%255+1)) \
65					  --protonum 17 --timeout 3600 --status ASSURED,SEEN_REPLY --sport \$RANDOM --dport 53; then \
66					  return;\
67			fi & \
68		done ; wait" 2>/dev/null
69
70		i=$((i+bulk))
71	done
72}
73
74check_ctcount() {
75	local ns="$1"
76	local count="$2"
77	local msg="$3"
78
79	local now=$(ip netns exec "$ns" conntrack -C)
80
81	if [ $now -ne "$count" ] ;then
82		echo "expected $count entries in $ns, not $now: $msg"
83		exit 1
84	fi
85
86	echo "PASS: got $count connections: $msg"
87}
88
89ctresize() {
90	local duration="$1"
91	local now=$(date +%s)
92	local end=$((now + duration))
93
94	while [ $now -lt $end ]; do
95		sysctl -q net.netfilter.nf_conntrack_buckets=$RANDOM
96		now=$(date +%s)
97	done
98}
99
100do_rsleep() {
101	local limit="$1"
102	local r=$RANDOM
103
104	r=$((r%limit))
105	sleep "$r"
106}
107
108ct_flush_once() {
109	local ns="$1"
110
111	ip netns exec "$ns" conntrack -F 2>/dev/null
112}
113
114ctflush() {
115	local ns="$1"
116	local duration="$2"
117	local now=$(date +%s)
118	local end=$((now + duration))
119
120	do_rsleep "$duration"
121
122        while [ $now -lt $end ]; do
123		ct_flush_once "$ns"
124		do_rsleep "$duration"
125		now=$(date +%s)
126        done
127}
128
129ct_pingflood()
130{
131	local ns="$1"
132	local duration="$2"
133	local msg="$3"
134	local now=$(date +%s)
135	local end=$((now + duration))
136	local j=0
137	local k=0
138
139        while [ $now -lt $end ]; do
140		j=$((j%256))
141		k=$((k%256))
142
143		ip netns exec "$ns" bash -c \
144			"j=$j k=$k; for i in \$(seq 1 254); do ping -q -c 1 127.\$k.\$j.\$i & done; wait" >/dev/null 2>&1
145
146		j=$((j+1))
147
148		if [ $j -eq 256 ];then
149			k=$((k+1))
150		fi
151
152		now=$(date +%s)
153	done
154
155	wait
156}
157
158ct_udpflood()
159{
160	local ns="$1"
161	local duration="$2"
162	local now=$(date +%s)
163	local end=$((now + duration))
164
165	[ $have_socat -ne "1" ] && return
166
167        while [ $now -lt $end ]; do
168ip netns exec "$ns" bash<<"EOF"
169	for i in $(seq 1 100);do
170		dport=$(((RANDOM%65536)+1))
171
172		echo bar | socat -u STDIN UDP:"127.0.0.1:$dport" &
173	done > /dev/null 2>&1
174	wait
175EOF
176		now=$(date +%s)
177	done
178}
179
180ct_udpclash()
181{
182	local ns="$1"
183	local duration="$2"
184	local now=$(date +%s)
185	local end=$((now + duration))
186
187	[ -x udpclash ] || return
188
189        while [ $now -lt $end ]; do
190		ip netns exec "$ns" timeout 30 ./udpclash 127.0.0.1 $((RANDOM%65536)) > /dev/null 2>&1
191
192		now=$(date +%s)
193	done
194}
195
196# dump to /dev/null.  We don't want dumps to cause infinite loops
197# or use-after-free even when conntrack table is altered while dumps
198# are in progress.
199ct_nulldump()
200{
201	local ns="$1"
202
203	ip netns exec "$ns" conntrack -L > /dev/null 2>&1 &
204
205	# Don't require /proc support in conntrack
206	if [ -r /proc/self/net/nf_conntrack ] ; then
207		ip netns exec "$ns" bash -c "wc -l < /proc/self/net/nf_conntrack" > /dev/null &
208	fi
209
210	wait
211}
212
213ct_nulldump_loop()
214{
215	local ns="$1"
216	local duration="$2"
217	local now=$(date +%s)
218	local end=$((now + duration))
219
220        while [ $now -lt $end ]; do
221		ct_nulldump "$ns"
222		sleep $((RANDOM%2))
223		now=$(date +%s)
224	done
225}
226
227change_timeouts()
228{
229	local ns="$1"
230	local r1=$((RANDOM%2))
231	local r2=$((RANDOM%2))
232
233	[ "$r1" -eq 1 ] && ip netns exec "$ns" sysctl -q net.netfilter.nf_conntrack_icmp_timeout=$((RANDOM%5))
234	[ "$r2" -eq 1 ] && ip netns exec "$ns" sysctl -q net.netfilter.nf_conntrack_udp_timeout=$((RANDOM%5))
235}
236
237ct_change_timeouts_loop()
238{
239	local ns="$1"
240	local duration="$2"
241	local now=$(date +%s)
242	local end=$((now + duration))
243
244        while [ $now -lt $end ]; do
245		change_timeouts "$ns"
246		sleep $((RANDOM%2))
247		now=$(date +%s)
248	done
249
250	# restore defaults
251	ip netns exec "$ns" sysctl -q net.netfilter.nf_conntrack_icmp_timeout=30
252	ip netns exec "$ns" sysctl -q net.netfilter.nf_conntrack_udp_timeout=30
253}
254
255check_taint()
256{
257	local tainted_then="$1"
258	local msg="$2"
259
260	local tainted_now=0
261
262	if [ "$tainted_then" -ne 0 ];then
263		return
264	fi
265
266	read tainted_now < /proc/sys/kernel/tainted
267
268	if [ "$tainted_now" -eq 0 ];then
269		echo "PASS: $msg"
270	else
271		echo "TAINT: $msg"
272		dmesg
273		exit 1
274	fi
275}
276
277insert_flood()
278{
279	local n="$1"
280	local timeout="$2"
281	local r=0
282
283	r=$((RANDOM%$insert_count))
284
285	ct_pingflood "$n" "$timeout" "floodresize" &
286	ct_udpflood "$n" "$timeout" &
287	ct_udpclash "$n" "$timeout" &
288
289	insert_ctnetlink "$n" "$r" &
290	ctflush "$n" "$timeout" &
291	ct_nulldump_loop "$n" "$timeout" &
292	ct_change_timeouts_loop "$n" "$timeout" &
293
294	wait
295}
296
297test_floodresize_all()
298{
299	local timeout=20
300	local n=""
301	local tainted_then=""
302
303	read tainted_then < /proc/sys/kernel/tainted
304
305	for n in "$nsclient1" "$nsclient2";do
306		insert_flood "$n" "$timeout" &
307	done
308
309	# resize table constantly while flood/insert/dump/flushs
310	# are happening in parallel.
311	ctresize "$timeout"
312
313	# wait for subshells to complete, everything is limited
314	# by $timeout.
315	wait
316
317	check_taint "$tainted_then" "resize+flood"
318}
319
320check_dump()
321{
322	local ns="$1"
323	local protoname="$2"
324	local c=0
325	local proto=0
326	local proc=0
327	local unique=""
328	local lret=0
329
330	# NOTE: assumes timeouts are large enough to not have
331	# expirations in all following tests.
332	l=$(ip netns exec "$ns" conntrack -L 2>/dev/null | sort | tee "$tmpfile" | wc -l)
333	c=$(ip netns exec "$ns" conntrack -C)
334
335	if [ "$c" -eq 0 ]; then
336		echo "FAIL: conntrack count for $ns is 0"
337		lret=1
338	fi
339
340	if [ "$c" -ne "$l" ]; then
341		echo "FAIL: conntrack count inconsistency for $ns -L: $c != $l"
342		lret=1
343	fi
344
345	# check the dump we retrieved is free of duplicated entries.
346	unique=$(uniq "$tmpfile" | tee "$tmpfile_uniq" | wc -l)
347	if [ "$l" -ne "$unique" ]; then
348		echo "FAIL: listing contained redundant entries for $ns: $l != $unique"
349		diff -u "$tmpfile" "$tmpfile_uniq"
350		lret=1
351	fi
352
353	# we either inserted icmp or only udp, hence, --proto should return same entry count as without filter.
354	proto=$(ip netns exec "$ns" conntrack -L --proto $protoname 2>/dev/null | sort | uniq | tee "$tmpfile_uniq" | wc -l)
355	if [ "$l" -ne "$proto" ]; then
356		echo "FAIL: dump inconsistency for $ns -L --proto $protoname: $l != $proto"
357		diff -u "$tmpfile" "$tmpfile_uniq"
358		lret=1
359	fi
360
361	if [ -r /proc/self/net/nf_conntrack ] ; then
362		proc=$(ip netns exec "$ns" bash -c "sort < /proc/self/net/nf_conntrack | tee \"$tmpfile_proc\" | wc -l")
363
364		if [ "$l" -ne "$proc" ]; then
365			echo "FAIL: proc inconsistency for $ns: $l != $proc"
366			lret=1
367		fi
368
369		proc=$(uniq "$tmpfile_proc" | tee "$tmpfile_uniq" | wc -l)
370		if [ "$l" -ne "$proc" ]; then
371			echo "FAIL: proc inconsistency after uniq filter for $ns: $l != $proc"
372			diff -u "$tmpfile_proc" "$tmpfile_uniq"
373			lret=1
374		fi
375	fi
376
377	if [ $lret -eq 0 ];then
378		echo "PASS: dump in netns $ns had same entry count (-C $c, -L $l, -p $proto, /proc $proc)"
379	else
380		echo "FAIL: dump in netns $ns had different entry count (-C $c, -L $l, -p $proto, /proc $proc)"
381		ret=1
382	fi
383}
384
385test_dump_all()
386{
387	local timeout=3
388	local tainted_then=""
389
390	read tainted_then < /proc/sys/kernel/tainted
391
392	ct_flush_once "$nsclient1"
393	ct_flush_once "$nsclient2"
394
395	ip netns exec "$nsclient1" sysctl -q net.netfilter.nf_conntrack_icmp_timeout=3600
396
397	ct_pingflood "$nsclient1" $timeout "dumpall" &
398	insert_ctnetlink "$nsclient2" $insert_count
399
400	wait
401
402	check_dump "$nsclient1" "icmp"
403	check_dump "$nsclient2" "udp"
404
405	check_taint "$tainted_then" "test parallel conntrack dumps"
406}
407
408check_sysctl_immutable()
409{
410	local ns="$1"
411	local name="$2"
412	local failhard="$3"
413	local o=0
414	local n=0
415
416	o=$(ip netns exec "$ns" sysctl -n "$name" 2>/dev/null)
417	n=$((o+1))
418
419	# return value isn't reliable, need to read it back
420	ip netns exec "$ns" sysctl -q "$name"=$n 2>/dev/null >/dev/null
421
422	n=$(ip netns exec "$ns" sysctl -n "$name" 2>/dev/null)
423
424	[ -z "$n" ] && return 1
425
426	if [ $o -ne $n ]; then
427		if [ $failhard -gt 0 ] ;then
428			echo "FAIL: net.$name should not be changeable from namespace (now $n)"
429			ret=1
430		fi
431		return 0
432	fi
433
434	return 1
435}
436
437test_conntrack_max_limit()
438{
439	sysctl -q net.netfilter.nf_conntrack_max=100
440	insert_ctnetlink "$nsclient1" 101
441
442	# check netns is clamped by init_net, i.e., either netns follows
443	# init_net value, or a higher pernet limit (compared to init_net) is ignored.
444	check_ctcount "$nsclient1" 100 "netns conntrack_max is init_net bound"
445
446	sysctl -q net.netfilter.nf_conntrack_max=$init_net_max
447}
448
449test_conntrack_disable()
450{
451	local timeout=2
452
453	# disable conntrack pickups
454	ip netns exec "$nsclient1" nft flush table ip test_ct
455
456	ct_flush_once "$nsclient1"
457	ct_flush_once "$nsclient2"
458
459	ct_pingflood "$nsclient1" "$timeout" "conntrack disable"
460	ip netns exec "$nsclient2" ping -q -c 1 127.0.0.1 >/dev/null 2>&1
461
462	# Disabled, should not have picked up any connection.
463	check_ctcount "$nsclient1" 0 "conntrack disabled"
464
465	# This one is still active, expect 1 connection.
466	check_ctcount "$nsclient2" 1 "conntrack enabled"
467}
468
469init_net_max=$(sysctl -n net.netfilter.nf_conntrack_max)
470
471check_max_alias $init_net_max
472
473sysctl -q net.netfilter.nf_conntrack_max="262000"
474check_max_alias 262000
475
476setup_ns nsclient1 nsclient2
477
478# check this only works from init_net
479for n in netfilter.nf_conntrack_buckets netfilter.nf_conntrack_expect_max net.nf_conntrack_max;do
480	check_sysctl_immutable "$nsclient1" "net.$n" 1
481done
482
483# won't work on older kernels. If it works, check that the netns obeys the limit
484if check_sysctl_immutable "$nsclient1" net.netfilter.nf_conntrack_max 0;then
485	# subtest: if pernet is changeable, check that reducing it in pernet
486	# limits the pernet entries.  Inverse, pernet clamped by a lower init_net
487	# setting, is already checked by "test_conntrack_max_limit" test.
488
489	ip netns exec "$nsclient1" sysctl -q net.netfilter.nf_conntrack_max=1
490	insert_ctnetlink "$nsclient1" 2
491	check_ctcount "$nsclient1" 1 "netns conntrack_max is pernet bound"
492	ip netns exec "$nsclient1" sysctl -q net.netfilter.nf_conntrack_max=$init_net_max
493fi
494
495for n in "$nsclient1" "$nsclient2";do
496# enable conntrack in both namespaces
497ip netns exec "$n" nft -f - <<EOF
498table ip test_ct {
499	chain input {
500		type filter hook input priority 0
501		ct state new counter
502	}
503}
504EOF
505done
506
507tmpfile=$(mktemp)
508tmpfile_proc=$(mktemp)
509tmpfile_uniq=$(mktemp)
510test_conntrack_max_limit
511test_dump_all
512test_floodresize_all
513test_conntrack_disable
514
515exit $ret
516