xref: /freebsd/tests/sys/netpfil/pf/syncookie.sh (revision 98c1bf371c5dd613646f53fdc4796c366104f844)
1#
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2021 Modirum MDPay
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26
27. $(atf_get_srcdir)/utils.subr
28
29common_dir=$(atf_get_srcdir)/../common
30
31syncookie_state()
32{
33	jail=$1
34
35	jexec $jail pfctl -si -v | grep -A 2 '^Syncookies' | grep active \
36	    | awk '{ print($2); }'
37}
38
39atf_test_case "basic" "cleanup"
40basic_head()
41{
42	atf_set descr 'Basic syncookie test'
43	atf_set require.user root
44}
45
46basic_body()
47{
48	pft_init
49
50	epair=$(vnet_mkepair)
51
52	vnet_mkjail alcatraz ${epair}b
53	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
54	jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \
55	    $(atf_get_srcdir)/echo_inetd.conf
56
57	ifconfig ${epair}a 192.0.2.2/24 up
58
59	jexec alcatraz pfctl -e
60	pft_set_rules alcatraz \
61		"set syncookies always" \
62		"pass in" \
63		"pass out"
64
65	# Sanity check
66	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
67
68	reply=$(echo foo | nc -N -w 5 192.0.2.1 7)
69	if [ "${reply}" != "foo" ];
70	then
71		atf_fail "Failed to connect to syncookie protected echo daemon"
72	fi
73
74	# Check that status shows syncookies as being active
75	active=$(syncookie_state alcatraz)
76	if [ "$active" != "active" ];
77	then
78		atf_fail "syncookies not active"
79	fi
80}
81
82basic_cleanup()
83{
84	rm -f ${PWD}/inetd-alcatraz.pid
85	pft_cleanup
86}
87
88atf_test_case "basic_v6" "cleanup"
89basic_v6_head()
90{
91	atf_set descr 'Basic syncookie IPv6 test'
92	atf_set require.user root
93}
94
95basic_v6_body()
96{
97	pft_init
98
99	epair=$(vnet_mkepair)
100
101	vnet_mkjail alcatraz ${epair}b
102	jexec alcatraz ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
103	jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \
104	    $(atf_get_srcdir)/echo_inetd.conf
105
106	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
107
108	jexec alcatraz pfctl -e
109	pft_set_rules alcatraz \
110		"set syncookies always" \
111		"pass in" \
112		"pass out"
113
114	# Sanity check
115	atf_check -s exit:0 -o ignore ping6 -c 1 2001:db8::1
116
117	reply=$(echo foo | nc -N -w 5 2001:db8::1 7)
118	if [ "${reply}" != "foo" ];
119	then
120		atf_fail "Failed to connect to syncookie protected echo daemon"
121	fi
122
123	# Check that status shows syncookies as being active
124	active=$(syncookie_state alcatraz)
125	if [ "$active" != "active" ];
126	then
127		atf_fail "syncookies not active"
128	fi
129}
130
131basic_v6_cleanup()
132{
133	pft_cleanup
134}
135
136atf_test_case "forward" "cleanup"
137forward_head()
138{
139	atf_set descr 'Syncookies for forwarded hosts'
140	atf_set require.user root
141}
142
143forward_body()
144{
145	pft_init
146
147	epair_in=$(vnet_mkepair)
148	epair_out=$(vnet_mkepair)
149
150	vnet_mkjail fwd ${epair_in}b ${epair_out}a
151	vnet_mkjail srv ${epair_out}b
152
153	jexec fwd ifconfig ${epair_in}b 192.0.2.1/24 up
154	jexec fwd ifconfig ${epair_out}a 198.51.100.1/24 up
155	jexec fwd sysctl net.inet.ip.forwarding=1
156
157	jexec srv ifconfig ${epair_out}b 198.51.100.2/24 up
158	jexec srv route add default 198.51.100.1
159	jexec srv /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \
160	    $(atf_get_srcdir)/echo_inetd.conf
161
162	ifconfig ${epair_in}a 192.0.2.2/24 up
163	route add -net 198.51.100.0/24 192.0.2.1
164
165	jexec fwd pfctl -e
166	pft_set_rules fwd \
167		"set syncookies always" \
168		"pass in" \
169		"pass out"
170
171	# Sanity check
172	atf_check -s exit:0 -o ignore ping -c 1 198.51.100.2
173
174	reply=$(echo foo | nc -N -w 5 198.51.100.2 7)
175	if [ "${reply}" != "foo" ];
176	then
177		atf_fail "Failed to connect to syncookie protected echo daemon"
178	fi
179}
180
181forward_cleanup()
182{
183	pft_cleanup
184}
185
186atf_test_case "forward_v6" "cleanup"
187forward_v6_head()
188{
189	atf_set descr 'Syncookies for forwarded hosts'
190	atf_set require.user root
191}
192
193forward_v6_body()
194{
195	pft_init
196
197	epair_in=$(vnet_mkepair)
198	epair_out=$(vnet_mkepair)
199
200	vnet_mkjail fwd ${epair_in}b ${epair_out}a
201	vnet_mkjail srv ${epair_out}b
202
203	jexec fwd ifconfig ${epair_in}b inet6 2001:db8::1/64 up no_dad
204	jexec fwd ifconfig ${epair_out}a inet6 2001:db8:1::1/64 up no_dad
205	jexec fwd sysctl net.inet6.ip6.forwarding=1
206
207	jexec srv ifconfig ${epair_out}b inet6 2001:db8:1::2/64 up no_dad
208	jexec srv route -6 add default 2001:db8:1::1
209	jexec srv /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \
210	    $(atf_get_srcdir)/echo_inetd.conf
211
212	ifconfig ${epair_in}a inet6 2001:db8::2/64 up no_dad
213	route -6 add -net 2001:db8:1::/64 2001:db8::1
214
215	jexec fwd pfctl -e
216	pft_set_rules fwd \
217		"set syncookies always" \
218		"pass in" \
219		"pass out"
220
221	# Sanity check
222	atf_check -s exit:0 -o ignore ping6 -c 1 2001:db8:1::2
223
224	reply=$(echo foo | nc -N -w 5 2001:db8:1::2 7)
225	if [ "${reply}" != "foo" ];
226	then
227		atf_fail "Failed to connect to syncookie protected echo daemon"
228	fi
229}
230
231forward_v6_cleanup()
232{
233	pft_cleanup
234}
235
236loopback_test()
237{
238	local addr port
239
240	addr=$1
241	port=$2
242
243	# syncookies don't work without state tracking enabled.
244	atf_check -e ignore pfctl -e
245	atf_check pfctl -f - <<__EOF__
246set syncookies always
247pass all keep state
248__EOF__
249
250        # Try to transmit data over a loopback connection.
251	cat <<__EOF__ >in
252Creativity, no.
253__EOF__
254	nc -l $addr $port >out &
255
256	# Give the background nc time to start
257	sleep 1
258
259	atf_check nc -N $addr $port < in
260
261	atf_check -o file:in cat out
262
263	atf_check -e ignore pfctl -d
264}
265
266atf_test_case "loopback" "cleanup"
267loopback_head()
268{
269	atf_set descr 'Make sure that loopback v4 TCP connections work with syncookies on'
270	atf_set require.user root
271}
272
273loopback_body()
274{
275	local epair
276
277	pft_init
278
279	atf_check ifconfig lo0 127.0.0.1/8
280	atf_check ifconfig lo0 up
281
282	loopback_test 127.0.0.1 8080
283
284	epair=$(vnet_mkepair)
285	atf_check ifconfig ${epair}a inet 192.0.2.1/24
286
287	loopback_test 192.0.2.1 8081
288}
289
290loopback_cleanup()
291{
292	pft_cleanup
293}
294
295atf_test_case "loopback_v6" "cleanup"
296loopback_v6_head()
297{
298	atf_set descr 'Make sure that loopback v6 TCP connections work with syncookies on'
299	atf_set require.user root
300}
301
302loopback_v6_body()
303{
304	local epair
305
306	pft_init
307
308	atf_check ifconfig lo0 up
309
310	loopback_test ::1 8080
311
312	epair=$(vnet_mkepair)
313	atf_check ifconfig ${epair}a inet6 2001:db8::1/64
314
315	loopback_test 2001:db8::1 8081
316}
317
318loopback_v6_cleanup()
319{
320	pft_cleanup
321}
322
323atf_test_case "nostate" "cleanup"
324nostate_head()
325{
326	atf_set descr 'Ensure that we do not create until SYN|ACK'
327	atf_set require.user root
328	atf_set require.progs python3 scapy
329}
330
331nostate_body()
332{
333	pft_init
334
335	epair=$(vnet_mkepair)
336	ifconfig ${epair}a 192.0.2.2/24 up
337
338	vnet_mkjail alcatraz ${epair}b
339	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
340
341	jexec alcatraz pfctl -e
342	pft_set_rules alcatraz \
343		"set syncookies always" \
344		"pass in" \
345		"pass out"
346
347	# Sanity check
348	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
349
350	# Now syn flood to create many states
351	${common_dir}/pft_synflood.py \
352		--sendif ${epair}a \
353		--to 192.0.2.2 \
354		--count 20
355
356	states=$(jexec alcatraz pfctl -ss | grep tcp)
357	if [ -n "$states" ];
358	then
359		echo "$states"
360		atf_fail "Found unexpected state"
361	fi
362}
363
364nostate_cleanup()
365{
366	pft_cleanup
367}
368
369atf_test_case "nostate_v6" "cleanup"
370nostate_v6_head()
371{
372	atf_set descr 'Ensure that we do not create until SYN|ACK'
373	atf_set require.user root
374	atf_set require.progs python3 scapy
375}
376
377nostate_v6_body()
378{
379	pft_init
380
381	epair=$(vnet_mkepair)
382	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
383
384	vnet_mkjail alcatraz ${epair}b
385	jexec alcatraz ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
386
387	jexec alcatraz pfctl -e
388	pft_set_rules alcatraz \
389		"set syncookies always" \
390		"pass in" \
391		"pass out"
392
393	# Sanity check
394	atf_check -s exit:0 -o ignore ping6 -c 1 2001:db8::1
395
396	# Now syn flood to create many states
397	${common_dir}/pft_synflood.py \
398        --ip6 \
399		--sendif ${epair}a \
400		--to 2001:db8::2 \
401		--count 20
402
403	states=$(jexec alcatraz pfctl -ss | grep tcp)
404	if [ -n "$states" ];
405	then
406		echo "$states"
407		atf_fail "Found unexpected state"
408	fi
409}
410
411nostate_v6_cleanup()
412{
413	pft_cleanup
414}
415
416atf_test_case "adaptive" "cleanup"
417adaptive_head()
418{
419	atf_set descr 'Adaptive mode test'
420	atf_set require.user root
421	atf_set require.progs python3 scapy
422}
423
424adaptive_body()
425{
426	pft_init
427
428	epair=$(vnet_mkepair)
429	ifconfig ${epair}a 192.0.2.2/24 up
430
431	vnet_mkjail alcatraz ${epair}b
432	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
433
434	jexec alcatraz pfctl -e
435	pft_set_rules alcatraz \
436		"set limit states 100" \
437		"set syncookies adaptive (start 10%%, end 5%%)" \
438		"pass in" \
439		"pass out"
440
441	# Sanity check
442	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
443
444	# Check that status shows syncookies as being inactive
445	active=$(syncookie_state alcatraz)
446	if [ "$active" != "inactive" ];
447	then
448		atf_fail "syncookies active when they should not be"
449	fi
450
451	# Now syn flood to create many states
452	${common_dir}/pft_synflood.py \
453		--sendif ${epair}a \
454		--to 192.0.2.2 \
455		--count 100
456
457	# Check that status shows syncookies as being active
458	active=$(syncookie_state alcatraz)
459	if [ "$active" != "active" ];
460	then
461		atf_fail "syncookies not active"
462	fi
463
464	# Adaptive mode should kick in and stop us from creating more than
465	# about 10 states
466	states=$(jexec alcatraz pfctl -ss | grep tcp | wc -l)
467	if [ "$states" -gt 20 ];
468	then
469		echo "$states"
470		atf_fail "Found unexpected states"
471	fi
472}
473
474adaptive_cleanup()
475{
476	pft_cleanup
477}
478
479atf_test_case "limits" "cleanup"
480limits_head()
481{
482	atf_set descr 'Ensure limit calculation works for low or high state limits'
483	atf_set require.user root
484}
485
486limits_body()
487{
488	pft_init
489
490	vnet_mkjail alcatraz
491
492	jexec alcatraz pfctl -e
493	pft_set_rules alcatraz \
494		"set limit states 1" \
495		"set syncookies adaptive (start 10%%, end 5%%)" \
496		"pass in" \
497		"pass out"
498
499	pft_set_rules alcatraz \
500		"set limit states 326000000" \
501		"set syncookies adaptive (start 10%%, end 5%%)" \
502		"pass in" \
503		"pass out"
504}
505
506limits_cleanup()
507{
508	pft_cleanup
509}
510
511atf_test_case "port_reuse" "cleanup"
512port_reuse_head()
513{
514	atf_set descr 'Test rapid port re-use'
515	atf_set require.user root
516}
517
518port_reuse_body()
519{
520	pft_init
521
522	epair=$(vnet_mkepair)
523
524	vnet_mkjail alcatraz ${epair}b
525	vnet_mkjail singsing
526	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
527	jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \
528	    $(atf_get_srcdir)/echo_inetd.conf
529
530	ifconfig ${epair}a 192.0.2.2/24 up
531
532	jexec alcatraz pfctl -e
533	jexec alcatraz pfctl -x loud
534	pft_set_rules alcatraz \
535		"set syncookies always" \
536		"pass in" \
537		"pass out"
538
539	# Sanity check
540	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
541
542	reply=$(echo foo | nc -p 1234 -N -w 5 192.0.2.1 7)
543	if [ "${reply}" != "foo" ];
544	then
545		atf_fail "Failed to connect to syncookie protected echo daemon"
546	fi
547
548	# We can't re-use the source IP/port combo quickly enough, so we're
549	# going to play a really dirty trick, and move our interface to a new
550	# jail, and do it from there.
551	ifconfig ${epair}a vnet singsing
552	jexec singsing ifconfig ${epair}a 192.0.2.2/24 up
553	atf_check -s exit:0 -o ignore jexec singsing ping -c 1 192.0.2.1
554
555	reply=$(echo bar | jexec singsing nc -p 1234 -N -w 5 192.0.2.1 7)
556	if [ "${reply}" != "bar" ];
557	then
558		atf_fail "Failed to connect to syncookie protected echo daemon (2)"
559	fi
560}
561
562port_reuse_cleanup()
563{
564	pft_cleanup
565}
566
567atf_init_test_cases()
568{
569	atf_add_test_case "basic"
570	atf_add_test_case "basic_v6"
571	atf_add_test_case "forward"
572	atf_add_test_case "forward_v6"
573	atf_add_test_case "loopback"
574	atf_add_test_case "loopback_v6"
575	atf_add_test_case "nostate"
576	atf_add_test_case "nostate_v6"
577	atf_add_test_case "adaptive"
578	atf_add_test_case "limits"
579	atf_add_test_case "port_reuse"
580}
581