xref: /freebsd/tests/sys/netpfil/pf/syncookie.sh (revision 4a7c6d6206cf0851e11ecd38ab5c618f67892dab)
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	atf_check nc -N $addr $port < in
257
258	atf_check -o file:in cat out
259
260	atf_check -e ignore pfctl -d
261}
262
263atf_test_case "loopback" "cleanup"
264loopback_head()
265{
266	atf_set descr 'Make sure that loopback v4 TCP connections work with syncookies on'
267	atf_set require.user root
268}
269
270loopback_body()
271{
272	local epair
273
274	pft_init
275
276	atf_check ifconfig lo0 127.0.0.1/8
277	atf_check ifconfig lo0 up
278
279	loopback_test 127.0.0.1 8080
280
281	epair=$(vnet_mkepair)
282	atf_check ifconfig ${epair}a inet 192.0.2.1/24
283
284	loopback_test 192.0.2.1 8081
285}
286
287loopback_cleanup()
288{
289	pft_cleanup
290}
291
292atf_test_case "loopback_v6" "cleanup"
293loopback_v6_head()
294{
295	atf_set descr 'Make sure that loopback v6 TCP connections work with syncookies on'
296	atf_set require.user root
297}
298
299loopback_v6_body()
300{
301	local epair
302
303	pft_init
304
305	atf_check ifconfig lo0 up
306
307	loopback_test ::1 8080
308
309	epair=$(vnet_mkepair)
310	atf_check ifconfig ${epair}a inet6 2001:db8::1/64
311
312	loopback_test 2001:db8::1 8081
313}
314
315loopback_v6_cleanup()
316{
317	pft_cleanup
318}
319
320atf_test_case "nostate" "cleanup"
321nostate_head()
322{
323	atf_set descr 'Ensure that we do not create until SYN|ACK'
324	atf_set require.user root
325	atf_set require.progs scapy
326}
327
328nostate_body()
329{
330	pft_init
331
332	epair=$(vnet_mkepair)
333	ifconfig ${epair}a 192.0.2.2/24 up
334
335	vnet_mkjail alcatraz ${epair}b
336	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
337
338	jexec alcatraz pfctl -e
339	pft_set_rules alcatraz \
340		"set syncookies always" \
341		"pass in" \
342		"pass out"
343
344	# Sanity check
345	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
346
347	# Now syn flood to create many states
348	${common_dir}/pft_synflood.py \
349		--sendif ${epair}a \
350		--to 192.0.2.2 \
351		--count 20
352
353	states=$(jexec alcatraz pfctl -ss | grep tcp)
354	if [ -n "$states" ];
355	then
356		echo "$states"
357		atf_fail "Found unexpected state"
358	fi
359}
360
361nostate_cleanup()
362{
363	pft_cleanup
364}
365
366atf_test_case "nostate_v6" "cleanup"
367nostate_v6_head()
368{
369	atf_set descr 'Ensure that we do not create until SYN|ACK'
370	atf_set require.user root
371	atf_set require.progs scapy
372}
373
374nostate_v6_body()
375{
376	pft_init
377
378	epair=$(vnet_mkepair)
379	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
380
381	vnet_mkjail alcatraz ${epair}b
382	jexec alcatraz ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
383
384	jexec alcatraz pfctl -e
385	pft_set_rules alcatraz \
386		"set syncookies always" \
387		"pass in" \
388		"pass out"
389
390	# Sanity check
391	atf_check -s exit:0 -o ignore ping6 -c 1 2001:db8::1
392
393	# Now syn flood to create many states
394	${common_dir}/pft_synflood.py \
395        --ip6 \
396		--sendif ${epair}a \
397		--to 2001:db8::2 \
398		--count 20
399
400	states=$(jexec alcatraz pfctl -ss | grep tcp)
401	if [ -n "$states" ];
402	then
403		echo "$states"
404		atf_fail "Found unexpected state"
405	fi
406}
407
408nostate_v6_cleanup()
409{
410	pft_cleanup
411}
412
413atf_test_case "adaptive" "cleanup"
414adaptive_head()
415{
416	atf_set descr 'Adaptive mode test'
417	atf_set require.user root
418	atf_set require.progs scapy
419}
420
421adaptive_body()
422{
423	pft_init
424
425	epair=$(vnet_mkepair)
426	ifconfig ${epair}a 192.0.2.2/24 up
427
428	vnet_mkjail alcatraz ${epair}b
429	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
430
431	jexec alcatraz pfctl -e
432	pft_set_rules alcatraz \
433		"set limit states 100" \
434		"set syncookies adaptive (start 10%%, end 5%%)" \
435		"pass in" \
436		"pass out"
437
438	# Sanity check
439	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
440
441	# Check that status shows syncookies as being inactive
442	active=$(syncookie_state alcatraz)
443	if [ "$active" != "inactive" ];
444	then
445		atf_fail "syncookies active when they should not be"
446	fi
447
448	# Now syn flood to create many states
449	${common_dir}/pft_synflood.py \
450		--sendif ${epair}a \
451		--to 192.0.2.2 \
452		--count 100
453
454	# Check that status shows syncookies as being active
455	active=$(syncookie_state alcatraz)
456	if [ "$active" != "active" ];
457	then
458		atf_fail "syncookies not active"
459	fi
460
461	# Adaptive mode should kick in and stop us from creating more than
462	# about 10 states
463	states=$(jexec alcatraz pfctl -ss | grep tcp | wc -l)
464	if [ "$states" -gt 20 ];
465	then
466		echo "$states"
467		atf_fail "Found unexpected states"
468	fi
469}
470
471adaptive_cleanup()
472{
473	pft_cleanup
474}
475
476atf_test_case "limits" "cleanup"
477limits_head()
478{
479	atf_set descr 'Ensure limit calculation works for low or high state limits'
480	atf_set require.user root
481}
482
483limits_body()
484{
485	pft_init
486
487	vnet_mkjail alcatraz
488
489	jexec alcatraz pfctl -e
490	pft_set_rules alcatraz \
491		"set limit states 1" \
492		"set syncookies adaptive (start 10%%, end 5%%)" \
493		"pass in" \
494		"pass out"
495
496	pft_set_rules alcatraz \
497		"set limit states 326000000" \
498		"set syncookies adaptive (start 10%%, end 5%%)" \
499		"pass in" \
500		"pass out"
501}
502
503limits_cleanup()
504{
505	pft_cleanup
506}
507
508atf_test_case "port_reuse" "cleanup"
509port_reuse_head()
510{
511	atf_set descr 'Test rapid port re-use'
512	atf_set require.user root
513}
514
515port_reuse_body()
516{
517	pft_init
518
519	epair=$(vnet_mkepair)
520
521	vnet_mkjail alcatraz ${epair}b
522	vnet_mkjail singsing
523	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
524	jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \
525	    $(atf_get_srcdir)/echo_inetd.conf
526
527	ifconfig ${epair}a 192.0.2.2/24 up
528
529	jexec alcatraz pfctl -e
530	jexec alcatraz pfctl -x loud
531	pft_set_rules alcatraz \
532		"set syncookies always" \
533		"pass in" \
534		"pass out"
535
536	# Sanity check
537	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
538
539	reply=$(echo foo | nc -p 1234 -N -w 5 192.0.2.1 7)
540	if [ "${reply}" != "foo" ];
541	then
542		atf_fail "Failed to connect to syncookie protected echo daemon"
543	fi
544
545	# We can't re-use the source IP/port combo quickly enough, so we're
546	# going to play a really dirty trick, and move our interface to a new
547	# jail, and do it from there.
548	ifconfig ${epair}a vnet singsing
549	jexec singsing ifconfig ${epair}a 192.0.2.2/24 up
550	atf_check -s exit:0 -o ignore jexec singsing ping -c 1 192.0.2.1
551
552	reply=$(echo bar | jexec singsing nc -p 1234 -N -w 5 192.0.2.1 7)
553	if [ "${reply}" != "bar" ];
554	then
555		atf_fail "Failed to connect to syncookie protected echo daemon (2)"
556	fi
557}
558
559port_reuse_cleanup()
560{
561	pft_cleanup
562}
563
564atf_init_test_cases()
565{
566	atf_add_test_case "basic"
567	atf_add_test_case "basic_v6"
568	atf_add_test_case "forward"
569	atf_add_test_case "forward_v6"
570	atf_add_test_case "loopback"
571	atf_add_test_case "loopback_v6"
572	atf_add_test_case "nostate"
573	atf_add_test_case "nostate_v6"
574	atf_add_test_case "adaptive"
575	atf_add_test_case "limits"
576	atf_add_test_case "port_reuse"
577}
578