xref: /freebsd/tests/sys/netpfil/pf/killstate.sh (revision 681ce946)
1# $FreeBSD$
2#
3# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4#
5# Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27
28. $(atf_get_srcdir)/utils.subr
29
30common_dir=$(atf_get_srcdir)/../common
31
32atf_test_case "v4" "cleanup"
33v4_head()
34{
35	atf_set descr 'Test killing states by IPv4 address'
36	atf_set require.user root
37	atf_set require.progs scapy
38}
39
40v4_body()
41{
42	pft_init
43
44	epair=$(vnet_mkepair)
45	ifconfig ${epair}a 192.0.2.1/24 up
46
47	vnet_mkjail alcatraz ${epair}b
48	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
49	jexec alcatraz pfctl -e
50
51	pft_set_rules alcatraz "block all" \
52		"pass in proto icmp"
53
54	# Sanity check & establish state
55	# Note: use pft_ping so we always use the same ID, so pf considers all
56	# echo requests part of the same flow.
57	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
58		--sendif ${epair}a \
59		--to 192.0.2.2 \
60		--replyif ${epair}a
61
62	# Change rules to now deny the ICMP traffic
63	pft_set_rules noflush alcatraz "block all"
64
65	# Established state means we can still ping alcatraz
66	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
67		--sendif ${epair}a \
68		--to 192.0.2.2 \
69		--replyif ${epair}a
70
71	# Killing with the wrong IP doesn't affect our state
72	jexec alcatraz pfctl -k 192.0.2.3
73
74	# So we can still ping
75	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
76		--sendif ${epair}a \
77		--to 192.0.2.2 \
78		--replyif ${epair}a
79
80	# Killing with one correct address and one incorrect doesn't kill the state
81	jexec alcatraz pfctl -k 192.0.2.1 -k 192.0.2.3
82
83	# So we can still ping
84	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
85		--sendif ${epair}a \
86		--to 192.0.2.2 \
87		--replyif ${epair}a
88
89	# Killing with correct address does remove the state
90	jexec alcatraz pfctl -k 192.0.2.1
91
92	# Now the ping fails
93	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
94		--sendif ${epair}a \
95		--to 192.0.2.2 \
96		--replyif ${epair}a
97}
98
99v4_cleanup()
100{
101	pft_cleanup
102}
103
104atf_test_case "v6" "cleanup"
105v6_head()
106{
107	atf_set descr 'Test killing states by IPv6 address'
108	atf_set require.user root
109	atf_set require.progs scapy
110}
111
112v6_body()
113{
114	pft_init
115
116	if [ "$(atf_config_get ci false)" = "true" ]; then
117		atf_skip "https://bugs.freebsd.org/260458"
118	fi
119
120	epair=$(vnet_mkepair)
121	ifconfig ${epair}a inet6 2001:db8::1/64 up no_dad
122
123	vnet_mkjail alcatraz ${epair}b
124	jexec alcatraz ifconfig ${epair}b inet6 2001:db8::2/64 up no_dad
125	jexec alcatraz pfctl -e
126
127	pft_set_rules alcatraz "block all" \
128		"pass in proto icmp6"
129
130	# Sanity check & establish state
131	# Note: use pft_ping so we always use the same ID, so pf considers all
132	# echo requests part of the same flow.
133	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
134		--ip6 \
135		--sendif ${epair}a \
136		--to 2001:db8::2 \
137		--replyif ${epair}a
138
139	# Change rules to now deny the ICMP traffic
140	pft_set_rules noflush alcatraz "block all"
141
142	# Established state means we can still ping alcatraz
143	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
144		--ip6 \
145		--sendif ${epair}a \
146		--to 2001:db8::2 \
147		--replyif ${epair}a
148
149	# Killing with the wrong IP doesn't affect our state
150	jexec alcatraz pfctl -k 2001:db8::3
151	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
152		--ip6 \
153		--sendif ${epair}a \
154		--to 2001:db8::2 \
155		--replyif ${epair}a
156
157	# Killing with one correct address and one incorrect doesn't kill the state
158	jexec alcatraz pfctl -k 2001:db8::1 -k 2001:db8::3
159	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
160		--ip6 \
161		--sendif ${epair}a \
162		--to 2001:db8::2 \
163		--replyif ${epair}a
164
165	# Killing with correct address does remove the state
166	jexec alcatraz pfctl -k 2001:db8::1
167	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
168		--ip6 \
169		--sendif ${epair}a \
170		--to 2001:db8::2 \
171		--replyif ${epair}a
172
173}
174
175v6_cleanup()
176{
177	pft_cleanup
178}
179
180atf_test_case "label" "cleanup"
181label_head()
182{
183	atf_set descr 'Test killing states by label'
184	atf_set require.user root
185	atf_set require.progs scapy
186}
187
188label_body()
189{
190	pft_init
191
192	epair=$(vnet_mkepair)
193	ifconfig ${epair}a 192.0.2.1/24 up
194
195	vnet_mkjail alcatraz ${epair}b
196	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
197	jexec alcatraz pfctl -e
198
199	pft_set_rules alcatraz "block all" \
200		"pass in proto tcp label bar" \
201		"pass in proto icmp label foo"
202
203	# Sanity check & establish state
204	# Note: use pft_ping so we always use the same ID, so pf considers all
205	# echo requests part of the same flow.
206	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
207		--sendif ${epair}a \
208		--to 192.0.2.2 \
209		--replyif ${epair}a
210
211	# Change rules to now deny the ICMP traffic
212	pft_set_rules noflush alcatraz "block all"
213
214	# Established state means we can still ping alcatraz
215	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
216		--sendif ${epair}a \
217		--to 192.0.2.2 \
218		--replyif ${epair}a
219
220	# Killing a label on a different rules keeps the state
221	jexec alcatraz pfctl -k label -k bar
222	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
223		--sendif ${epair}a \
224		--to 192.0.2.2 \
225		--replyif ${epair}a
226
227	# Killing a non-existing label keeps the state
228	jexec alcatraz pfctl -k label -k baz
229	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
230		--sendif ${epair}a \
231		--to 192.0.2.2 \
232		--replyif ${epair}a
233
234	# Killing the correct label kills the state
235	jexec alcatraz pfctl -k label -k foo
236	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
237		--sendif ${epair}a \
238		--to 192.0.2.2 \
239		--replyif ${epair}a
240}
241
242label_cleanup()
243{
244	pft_cleanup
245}
246
247atf_test_case "multilabel" "cleanup"
248multilabel_head()
249{
250	atf_set descr 'Test killing states with multiple labels by label'
251	atf_set require.user root
252	atf_set require.progs scapy
253}
254
255multilabel_body()
256{
257	pft_init
258
259	epair=$(vnet_mkepair)
260	ifconfig ${epair}a 192.0.2.1/24 up
261
262	vnet_mkjail alcatraz ${epair}b
263	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
264	jexec alcatraz pfctl -e
265
266	pft_set_rules alcatraz "block all" \
267		"pass in proto icmp label foo label bar"
268
269	# Sanity check & establish state
270	# Note: use pft_ping so we always use the same ID, so pf considers all
271	# echo requests part of the same flow.
272	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
273		--sendif ${epair}a \
274		--to 192.0.2.2 \
275		--replyif ${epair}a
276
277	# Change rules to now deny the ICMP traffic
278	pft_set_rules noflush alcatraz "block all"
279
280	# Established state means we can still ping alcatraz
281	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
282		--sendif ${epair}a \
283		--to 192.0.2.2 \
284		--replyif ${epair}a
285
286	# Killing a label on a different rules keeps the state
287	jexec alcatraz pfctl -k label -k baz
288	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
289		--sendif ${epair}a \
290		--to 192.0.2.2 \
291		--replyif ${epair}a
292
293	# Killing the state with the last label works
294	jexec alcatraz pfctl -k label -k bar
295	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
296		--sendif ${epair}a \
297		--to 192.0.2.2 \
298		--replyif ${epair}a
299
300	pft_set_rules alcatraz "block all" \
301		"pass in proto icmp label foo label bar"
302
303	# Reestablish state
304	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
305		--sendif ${epair}a \
306		--to 192.0.2.2 \
307		--replyif ${epair}a
308
309	# Change rules to now deny the ICMP traffic
310	pft_set_rules noflush alcatraz "block all"
311
312	# Killing with the first label works too
313	jexec alcatraz pfctl -k label -k foo
314	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
315		--sendif ${epair}a \
316		--to 192.0.2.2 \
317		--replyif ${epair}a
318}
319
320multilabel_cleanup()
321{
322	pft_cleanup
323}
324
325atf_test_case "gateway" "cleanup"
326gateway_head()
327{
328	atf_set descr 'Test killing states by route-to/reply-to address'
329	atf_set require.user root
330	atf_set require.progs scapy
331}
332
333gateway_body()
334{
335	pft_init
336
337	epair=$(vnet_mkepair)
338	ifconfig ${epair}a 192.0.2.1/24 up
339
340	vnet_mkjail alcatraz ${epair}b
341	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
342	jexec alcatraz pfctl -e
343
344	pft_set_rules alcatraz "block all" \
345		"pass in reply-to (${epair}b 192.0.2.1) proto icmp"
346
347	# Sanity check & establish state
348	# Note: use pft_ping so we always use the same ID, so pf considers all
349	# echo requests part of the same flow.
350	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
351		--sendif ${epair}a \
352		--to 192.0.2.2 \
353		--replyif ${epair}a
354
355	# Change rules to now deny the ICMP traffic
356	pft_set_rules noflush alcatraz "block all"
357
358	# Established state means we can still ping alcatraz
359	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
360		--sendif ${epair}a \
361		--to 192.0.2.2 \
362		--replyif ${epair}a
363
364	# Killing with a different gateway does not affect our state
365	jexec alcatraz pfctl -k gateway -k 192.0.2.2
366	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
367		--sendif ${epair}a \
368		--to 192.0.2.2 \
369		--replyif ${epair}a
370
371	# Killing states with the relevant gateway does terminate our state
372	jexec alcatraz pfctl -k gateway -k 192.0.2.1
373	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
374		--sendif ${epair}a \
375		--to 192.0.2.2 \
376		--replyif ${epair}a
377}
378
379gateway_cleanup()
380{
381	pft_cleanup
382}
383
384atf_test_case "match" "cleanup"
385match_head()
386{
387	atf_set descr 'Test killing matching states'
388	atf_set require.user root
389}
390
391wait_for_state()
392{
393	jail=$1
394	addr=$2
395
396	while ! jexec $jail pfctl -s s | grep $addr >/dev/null;
397	do
398		sleep .1
399	done
400}
401
402match_body()
403{
404	pft_init
405
406	epair_one=$(vnet_mkepair)
407	ifconfig ${epair_one}a 192.0.2.1/24 up
408
409	epair_two=$(vnet_mkepair)
410
411	vnet_mkjail alcatraz ${epair_one}b ${epair_two}a
412	jexec alcatraz ifconfig ${epair_one}b 192.0.2.2/24 up
413	jexec alcatraz ifconfig ${epair_two}a 198.51.100.1/24 up
414	jexec alcatraz sysctl net.inet.ip.forwarding=1
415	jexec alcatraz pfctl -e
416
417	vnet_mkjail singsing ${epair_two}b
418	jexec singsing ifconfig ${epair_two}b 198.51.100.2/24 up
419	jexec singsing route add default 198.51.100.1
420	jexec singsing /usr/sbin/inetd -p inetd-echo.pid \
421	    $(atf_get_srcdir)/echo_inetd.conf
422
423	route add 198.51.100.0/24 192.0.2.2
424
425	pft_set_rules alcatraz \
426		"nat on ${epair_two}a from 192.0.2.0/24 -> (${epair_two}a)" \
427		"pass all"
428
429	nc 198.51.100.2 7 &
430	wait_for_state alcatraz 192.0.2.1
431
432	# Expect two states
433	states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l)
434	if [ $states -ne 2 ] ;
435	then
436		atf_fail "Expected two states, found $states"
437	fi
438
439	# If we don't kill the matching NAT state one should be left
440	jexec alcatraz pfctl -k 192.0.2.1
441	states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l)
442	if [ $states -ne 1 ] ;
443	then
444		atf_fail "Expected one states, found $states"
445	fi
446
447	# Flush
448	jexec alcatraz pfctl -F states
449
450	nc 198.51.100.2 7 &
451	wait_for_state alcatraz 192.0.2.1
452
453	# Kill matching states, expect all of them to be gone
454	jexec alcatraz pfctl -M -k 192.0.2.1
455	states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l)
456	if [ $states -ne 0 ] ;
457	then
458		atf_fail "Expected zero states, found $states"
459	fi
460}
461
462match_cleanup()
463{
464	pft_cleanup
465}
466
467atf_test_case "interface" "cleanup"
468interface_head()
469{
470	atf_set descr 'Test killing states based on interface'
471	atf_set require.user root
472	atf_set require.progs scapy
473}
474
475interface_body()
476{
477	pft_init
478
479	epair=$(vnet_mkepair)
480	ifconfig ${epair}a 192.0.2.1/24 up
481
482	vnet_mkjail alcatraz ${epair}b
483	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
484	jexec alcatraz pfctl -e
485
486	pft_set_rules alcatraz "block all" \
487		"pass in proto icmp"
488
489	# Sanity check & establish state
490	# Note: use pft_ping so we always use the same ID, so pf considers all
491	# echo requests part of the same flow.
492	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
493		--sendif ${epair}a \
494		--to 192.0.2.2 \
495		--replyif ${epair}a
496
497	# Change rules to now deny the ICMP traffic
498	pft_set_rules noflush alcatraz "block all"
499
500	# Established state means we can still ping alcatraz
501	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
502		--sendif ${epair}a \
503		--to 192.0.2.2 \
504		--replyif ${epair}a
505
506	# Flushing states on a different interface doesn't affect our state
507	jexec alcatraz pfctl -i ${epair}a -Fs
508	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
509		--sendif ${epair}a \
510		--to 192.0.2.2 \
511		--replyif ${epair}a
512
513	# Flushing on the correct interface does (even with floating states)
514	jexec alcatraz pfctl -i ${epair}b -Fs
515	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
516		--sendif ${epair}a \
517		--to 192.0.2.2 \
518		--replyif ${epair}a
519}
520
521interface_cleanup()
522{
523	pft_cleanup
524}
525
526atf_test_case "id" "cleanup"
527id_head()
528{
529	atf_set descr 'Test killing states by id'
530	atf_set require.user root
531	atf_set require.progs scapy
532}
533
534id_body()
535{
536	pft_init
537
538	epair=$(vnet_mkepair)
539	ifconfig ${epair}a 192.0.2.1/24 up
540
541	vnet_mkjail alcatraz ${epair}b
542	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
543	jexec alcatraz pfctl -e
544
545	pft_set_rules alcatraz "block all" \
546		"pass in proto tcp" \
547		"pass in proto icmp"
548
549	# Sanity check & establish state
550	# Note: use pft_ping so we always use the same ID, so pf considers all
551	# echo requests part of the same flow.
552	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
553		--sendif ${epair}a \
554		--to 192.0.2.2 \
555		--replyif ${epair}a
556
557	# Change rules to now deny the ICMP traffic
558	pft_set_rules noflush alcatraz "block all"
559
560	# Established state means we can still ping alcatraz
561	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
562		--sendif ${epair}a \
563		--to 192.0.2.2 \
564		--replyif ${epair}a
565
566	# Get the state ID
567	id=$(jexec alcatraz pfctl -ss -vvv | grep -A 3 icmp |
568	    grep -A 3 192.0.2.2 | awk '/id:/ { printf("%s/%s", $2, $4); }')
569
570	# Kill the wrong ID
571	jexec alcatraz pfctl -k id -k 1
572	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
573		--sendif ${epair}a \
574		--to 192.0.2.2 \
575		--replyif ${epair}a
576
577	# Kill the correct ID
578	jexec alcatraz pfctl -k id -k ${id}
579	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
580		--sendif ${epair}a \
581		--to 192.0.2.2 \
582		--replyif ${epair}a
583}
584
585id_cleanup()
586{
587	pft_cleanup
588}
589
590atf_init_test_cases()
591{
592	atf_add_test_case "v4"
593	atf_add_test_case "v6"
594	atf_add_test_case "label"
595	atf_add_test_case "multilabel"
596	atf_add_test_case "gateway"
597	atf_add_test_case "match"
598	atf_add_test_case "interface"
599	atf_add_test_case "id"
600}
601