xref: /freebsd/tests/sys/netpfil/pf/syncookie.sh (revision a64729f5077d77e13b9497cb33ecb3c82e606ee8)
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
236atf_test_case "nostate" "cleanup"
237nostate_head()
238{
239	atf_set descr 'Ensure that we do not create until SYN|ACK'
240	atf_set require.user root
241	atf_set require.progs scapy
242}
243
244nostate_body()
245{
246	pft_init
247
248	epair=$(vnet_mkepair)
249	ifconfig ${epair}a 192.0.2.2/24 up
250
251	vnet_mkjail alcatraz ${epair}b
252	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
253
254	jexec alcatraz pfctl -e
255	pft_set_rules alcatraz \
256		"set syncookies always" \
257		"pass in" \
258		"pass out"
259
260	# Sanity check
261	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
262
263	# Now syn flood to create many states
264	${common_dir}/pft_synflood.py \
265		--sendif ${epair}a \
266		--to 192.0.2.2 \
267		--count 20
268
269	states=$(jexec alcatraz pfctl -ss | grep tcp)
270	if [ -n "$states" ];
271	then
272		echo "$states"
273		atf_fail "Found unexpected state"
274	fi
275}
276
277nostate_cleanup()
278{
279	pft_cleanup
280}
281
282atf_test_case "nostate_v6" "cleanup"
283nostate_v6_head()
284{
285	atf_set descr 'Ensure that we do not create until SYN|ACK'
286	atf_set require.user root
287	atf_set require.progs scapy
288}
289
290nostate_v6_body()
291{
292	pft_init
293
294	epair=$(vnet_mkepair)
295	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
296
297	vnet_mkjail alcatraz ${epair}b
298	jexec alcatraz ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
299
300	jexec alcatraz pfctl -e
301	pft_set_rules alcatraz \
302		"set syncookies always" \
303		"pass in" \
304		"pass out"
305
306	# Sanity check
307	atf_check -s exit:0 -o ignore ping6 -c 1 2001:db8::1
308
309	# Now syn flood to create many states
310	${common_dir}/pft_synflood.py \
311        --ip6 \
312		--sendif ${epair}a \
313		--to 2001:db8::2 \
314		--count 20
315
316	states=$(jexec alcatraz pfctl -ss | grep tcp)
317	if [ -n "$states" ];
318	then
319		echo "$states"
320		atf_fail "Found unexpected state"
321	fi
322}
323
324nostate_v6_cleanup()
325{
326	pft_cleanup
327}
328
329atf_test_case "adaptive" "cleanup"
330adaptive_head()
331{
332	atf_set descr 'Adaptive mode test'
333	atf_set require.user root
334	atf_set require.progs scapy
335}
336
337adaptive_body()
338{
339	pft_init
340
341	epair=$(vnet_mkepair)
342	ifconfig ${epair}a 192.0.2.2/24 up
343
344	vnet_mkjail alcatraz ${epair}b
345	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
346
347	jexec alcatraz pfctl -e
348	pft_set_rules alcatraz \
349		"set limit states 100" \
350		"set syncookies adaptive (start 10%%, end 5%%)" \
351		"pass in" \
352		"pass out"
353
354	# Sanity check
355	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
356
357	# Check that status shows syncookies as being inactive
358	active=$(syncookie_state alcatraz)
359	if [ "$active" != "inactive" ];
360	then
361		atf_fail "syncookies active when they should not be"
362	fi
363
364	# Now syn flood to create many states
365	${common_dir}/pft_synflood.py \
366		--sendif ${epair}a \
367		--to 192.0.2.2 \
368		--count 100
369
370	# Check that status shows syncookies as being active
371	active=$(syncookie_state alcatraz)
372	if [ "$active" != "active" ];
373	then
374		atf_fail "syncookies not active"
375	fi
376
377	# Adaptive mode should kick in and stop us from creating more than
378	# about 10 states
379	states=$(jexec alcatraz pfctl -ss | grep tcp | wc -l)
380	if [ "$states" -gt 20 ];
381	then
382		echo "$states"
383		atf_fail "Found unexpected states"
384	fi
385}
386
387adaptive_cleanup()
388{
389	pft_cleanup
390}
391
392atf_test_case "limits" "cleanup"
393limits_head()
394{
395	atf_set descr 'Ensure limit calculation works for low or high state limits'
396	atf_set require.user root
397}
398
399limits_body()
400{
401	pft_init
402
403	vnet_mkjail alcatraz
404
405	jexec alcatraz pfctl -e
406	pft_set_rules alcatraz \
407		"set limit states 1" \
408		"set syncookies adaptive (start 10%%, end 5%%)" \
409		"pass in" \
410		"pass out"
411
412	pft_set_rules alcatraz \
413		"set limit states 326000000" \
414		"set syncookies adaptive (start 10%%, end 5%%)" \
415		"pass in" \
416		"pass out"
417}
418
419limits_cleanup()
420{
421	pft_cleanup
422}
423
424atf_test_case "port_reuse" "cleanup"
425port_reuse_head()
426{
427	atf_set descr 'Test rapid port re-use'
428	atf_set require.user root
429}
430
431port_reuse_body()
432{
433	pft_init
434
435	epair=$(vnet_mkepair)
436
437	vnet_mkjail alcatraz ${epair}b
438	vnet_mkjail singsing
439	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
440	jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-alcatraz.pid \
441	    $(atf_get_srcdir)/echo_inetd.conf
442
443	ifconfig ${epair}a 192.0.2.2/24 up
444
445	jexec alcatraz pfctl -e
446	jexec alcatraz pfctl -x loud
447	pft_set_rules alcatraz \
448		"set syncookies always" \
449		"pass in" \
450		"pass out"
451
452	# Sanity check
453	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
454
455	reply=$(echo foo | nc -p 1234 -N -w 5 192.0.2.1 7)
456	if [ "${reply}" != "foo" ];
457	then
458		atf_fail "Failed to connect to syncookie protected echo daemon"
459	fi
460
461	# We can't re-use the source IP/port combo quickly enough, so we're
462	# going to play a really dirty trick, and move our interface to a new
463	# jail, and do it from there.
464	ifconfig ${epair}a vnet singsing
465	jexec singsing ifconfig ${epair}a 192.0.2.2/24 up
466	atf_check -s exit:0 -o ignore jexec singsing ping -c 1 192.0.2.1
467
468	reply=$(echo bar | jexec singsing nc -p 1234 -N -w 5 192.0.2.1 7)
469	if [ "${reply}" != "bar" ];
470	then
471		atf_fail "Failed to connect to syncookie protected echo daemon (2)"
472	fi
473}
474
475port_reuse_cleanup()
476{
477	pft_cleanup
478}
479
480atf_init_test_cases()
481{
482	atf_add_test_case "basic"
483	atf_add_test_case "basic_v6"
484	atf_add_test_case "forward"
485	atf_add_test_case "forward_v6"
486	atf_add_test_case "nostate"
487	atf_add_test_case "nostate_v6"
488	atf_add_test_case "adaptive"
489	atf_add_test_case "limits"
490	atf_add_test_case "port_reuse"
491}
492