xref: /freebsd/tests/sys/netpfil/pf/killstate.sh (revision 6ff78a63d8cd0dd64ae79cbda5cb03572c1e17f5)
1#
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
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
31find_state()
32{
33	jail=${1:-alcatraz}
34	ip=${2:-192.0.2.2}
35
36	jexec ${jail} pfctl -ss | grep icmp | grep ${ip}
37}
38
39find_state_v6()
40{
41	jexec alcatraz pfctl -ss | grep icmp | grep 2001:db8::2
42}
43
44
45atf_test_case "v4" "cleanup"
46v4_head()
47{
48	atf_set descr 'Test killing states by IPv4 address'
49	atf_set require.user root
50	atf_set require.progs python3 scapy
51}
52
53v4_body()
54{
55	pft_init
56
57	epair=$(vnet_mkepair)
58	ifconfig ${epair}a 192.0.2.1/24 up
59
60	vnet_mkjail alcatraz ${epair}b
61	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
62	jexec alcatraz pfctl -e
63
64	pft_set_rules alcatraz "block all" \
65		"pass in proto icmp" \
66		"set skip on lo"
67
68	# Sanity check & establish state
69	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
70		--sendif ${epair}a \
71		--to 192.0.2.2 \
72		--replyif ${epair}a
73
74	# Change rules to now deny the ICMP traffic
75	pft_set_rules noflush alcatraz "block all"
76	if ! find_state;
77	then
78		atf_fail "Setting new rules removed the state."
79	fi
80
81	# Killing with the wrong IP doesn't affect our state
82	jexec alcatraz pfctl -k 192.0.2.3
83	if ! find_state;
84	then
85		atf_fail "Killing with the wrong IP removed our state."
86	fi
87
88	# Killing with one correct address and one incorrect doesn't kill the state
89	jexec alcatraz pfctl -k 192.0.2.1 -k 192.0.2.3
90	if ! find_state;
91	then
92		atf_fail "Killing with one wrong IP removed our state."
93	fi
94
95	# Killing with correct address does remove the state
96	jexec alcatraz pfctl -k 192.0.2.1
97	if find_state;
98	then
99		atf_fail "Killing with the correct IP did not remove our state."
100	fi
101}
102
103v4_cleanup()
104{
105	pft_cleanup
106}
107
108atf_test_case "v6" "cleanup"
109v6_head()
110{
111	atf_set descr 'Test killing states by IPv6 address'
112	atf_set require.user root
113	atf_set require.progs python3 scapy
114}
115
116v6_body()
117{
118	pft_init
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		"set skip on lo"
130
131	# Sanity check & establish state
132	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
133		--sendif ${epair}a \
134		--to 2001:db8::2 \
135		--replyif ${epair}a
136
137	# Change rules to now deny the ICMP traffic
138	pft_set_rules noflush alcatraz "block all"
139	if ! find_state_v6;
140	then
141		atf_fail "Setting new rules removed the state."
142	fi
143
144	# Killing with the wrong IP doesn't affect our state
145	jexec alcatraz pfctl -k 2001:db8::3
146	if ! find_state_v6;
147	then
148		atf_fail "Killing with the wrong IP removed our state."
149	fi
150
151	# Killing with one correct address and one incorrect doesn't kill the state
152	jexec alcatraz pfctl -k 2001:db8::1 -k 2001:db8::3
153	if ! find_state_v6;
154	then
155		atf_fail "Killing with one wrong IP removed our state."
156	fi
157
158	# Killing with correct address does remove the state
159	jexec alcatraz pfctl -k 2001:db8::1
160	if find_state_v6;
161	then
162		atf_fail "Killing with the correct IP did not remove our state."
163	fi
164}
165
166v6_cleanup()
167{
168	pft_cleanup
169}
170
171atf_test_case "label" "cleanup"
172label_head()
173{
174	atf_set descr 'Test killing states by label'
175	atf_set require.user root
176	atf_set require.progs python3 scapy
177}
178
179label_body()
180{
181	pft_init
182
183	epair=$(vnet_mkepair)
184	ifconfig ${epair}a 192.0.2.1/24 up
185
186	vnet_mkjail alcatraz ${epair}b
187	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
188	jexec alcatraz pfctl -e
189
190	pft_set_rules alcatraz "block all" \
191		"pass in proto tcp label bar" \
192		"pass in proto icmp label foo" \
193		"set skip on lo"
194
195	# Sanity check & establish state
196	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
197		--sendif ${epair}a \
198		--to 192.0.2.2 \
199		--replyif ${epair}a
200
201	# Change rules to now deny the ICMP traffic
202	pft_set_rules noflush alcatraz "block all"
203	if ! find_state;
204	then
205		atf_fail "Setting new rules removed the state."
206	fi
207
208	# Killing a label on a different rules keeps the state
209	jexec alcatraz pfctl -k label -k bar
210	if ! find_state;
211	then
212		atf_fail "Killing a different label removed the state."
213	fi
214
215	# Killing a non-existing label keeps the state
216	jexec alcatraz pfctl -k label -k baz
217	if ! find_state;
218	then
219		atf_fail "Killing a non-existing label removed the state."
220	fi
221
222	# Killing the correct label kills the state
223	jexec alcatraz pfctl -k label -k foo
224	if find_state;
225	then
226		atf_fail "Killing the state did not remove it."
227	fi
228}
229
230label_cleanup()
231{
232	pft_cleanup
233}
234
235atf_test_case "multilabel" "cleanup"
236multilabel_head()
237{
238	atf_set descr 'Test killing states with multiple labels by label'
239	atf_set require.user root
240	atf_set require.progs python3 scapy
241}
242
243multilabel_body()
244{
245	pft_init
246
247	epair=$(vnet_mkepair)
248	ifconfig ${epair}a 192.0.2.1/24 up
249
250	vnet_mkjail alcatraz ${epair}b
251	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
252	jexec alcatraz pfctl -e
253
254	pft_set_rules alcatraz "block all" \
255		"pass in proto icmp label foo label bar" \
256		"set skip on lo"
257
258	# Sanity check & establish state
259	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
260		--sendif ${epair}a \
261		--to 192.0.2.2 \
262		--replyif ${epair}a
263
264	# Change rules to now deny the ICMP traffic
265	pft_set_rules noflush alcatraz "block all"
266	if ! find_state;
267	then
268		atf_fail "Setting new rules removed the state."
269	fi
270
271	# Killing a label on a different rules keeps the state
272	jexec alcatraz pfctl -k label -k baz
273	if ! find_state;
274	then
275		atf_fail "Killing a different label removed the state."
276	fi
277
278	# Killing the state with the last label works
279	jexec alcatraz pfctl -k label -k bar
280	if find_state;
281	then
282		atf_fail "Killing with the last label did not remove the state."
283	fi
284
285	pft_set_rules alcatraz "block all" \
286		"pass in proto icmp label foo label bar" \
287		"set skip on lo"
288
289	# Reestablish state
290	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
291		--sendif ${epair}a \
292		--to 192.0.2.2 \
293		--replyif ${epair}a
294
295	# Change rules to now deny the ICMP traffic
296	pft_set_rules noflush alcatraz "block all"
297	if ! find_state;
298	then
299		atf_fail "Setting new rules removed the state."
300	fi
301
302	# Killing with the first label works too
303	jexec alcatraz pfctl -k label -k foo
304	if find_state;
305	then
306		atf_fail "Killing with the first label did not remove the state."
307	fi
308}
309
310multilabel_cleanup()
311{
312	pft_cleanup
313}
314
315atf_test_case "gateway" "cleanup"
316gateway_head()
317{
318	atf_set descr 'Test killing states by route-to/reply-to address'
319	atf_set require.user root
320	atf_set require.progs python3 scapy
321}
322
323gateway_body()
324{
325	pft_init
326
327	epair=$(vnet_mkepair)
328	ifconfig ${epair}a 192.0.2.1/24 up
329
330	vnet_mkjail alcatraz ${epair}b
331	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
332	jexec alcatraz pfctl -e
333
334	pft_set_rules alcatraz "block all" \
335		"pass in reply-to (${epair}b 192.0.2.1) proto icmp" \
336		"set skip on lo"
337
338	# Sanity check & establish state
339	# Note: use pft_ping so we always use the same ID, so pf considers all
340	# echo requests part of the same flow.
341	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
342		--sendif ${epair}a \
343		--to 192.0.2.2 \
344		--replyif ${epair}a
345
346	# Change rules to now deny the ICMP traffic
347	pft_set_rules noflush alcatraz "block all"
348	if ! find_state;
349	then
350		atf_fail "Setting new rules removed the state."
351	fi
352
353	# Killing with a different gateway does not affect our state
354	jexec alcatraz pfctl -k gateway -k 192.0.2.2
355	if ! find_state;
356	then
357		atf_fail "Killing with a different gateway removed the state."
358	fi
359
360	# Killing states with the relevant gateway does terminate our state
361	jexec alcatraz pfctl -k gateway -k 192.0.2.1
362	if find_state;
363	then
364		atf_fail "Killing with the gateway did not remove the state."
365	fi
366}
367
368gateway_cleanup()
369{
370	pft_cleanup
371}
372
373atf_test_case "match" "cleanup"
374match_head()
375{
376	atf_set descr 'Test killing matching states'
377	atf_set require.user root
378}
379
380wait_for_state()
381{
382	jail=$1
383	addr=$2
384
385	while ! jexec $jail pfctl -s s | grep $addr >/dev/null;
386	do
387		sleep .1
388	done
389}
390
391match_body()
392{
393	pft_init
394
395	epair_one=$(vnet_mkepair)
396	ifconfig ${epair_one}a 192.0.2.1/24 up
397
398	epair_two=$(vnet_mkepair)
399
400	vnet_mkjail alcatraz ${epair_one}b ${epair_two}a
401	jexec alcatraz ifconfig ${epair_one}b 192.0.2.2/24 up
402	jexec alcatraz ifconfig ${epair_two}a 198.51.100.1/24 up
403	jexec alcatraz sysctl net.inet.ip.forwarding=1
404	jexec alcatraz pfctl -e
405
406	vnet_mkjail singsing ${epair_two}b
407	jexec singsing ifconfig ${epair_two}b 198.51.100.2/24 up
408	jexec singsing route add default 198.51.100.1
409	jexec singsing /usr/sbin/inetd -p ${PWD}/inetd-echo.pid \
410	    $(atf_get_srcdir)/echo_inetd.conf
411
412	route add 198.51.100.0/24 192.0.2.2
413
414	pft_set_rules alcatraz \
415		"nat on ${epair_two}a from 192.0.2.0/24 -> (${epair_two}a)" \
416		"pass all"
417
418	nc 198.51.100.2 7 &
419	wait_for_state alcatraz 192.0.2.1
420
421	# Expect two states
422	states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l)
423	if [ $states -ne 2 ] ;
424	then
425		atf_fail "Expected two states, found $states"
426	fi
427
428	# If we don't kill the matching NAT state one should be left
429	jexec alcatraz pfctl -k 192.0.2.1
430	states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l)
431	if [ $states -ne 1 ] ;
432	then
433		atf_fail "Expected one states, found $states"
434	fi
435
436	# Flush
437	jexec alcatraz pfctl -F states
438
439	nc 198.51.100.2 7 &
440	wait_for_state alcatraz 192.0.2.1
441
442	# Kill matching states, expect all of them to be gone
443	jexec alcatraz pfctl -M -k 192.0.2.1
444	states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l)
445	if [ $states -ne 0 ] ;
446	then
447		atf_fail "Expected zero states, found $states"
448	fi
449}
450
451match_cleanup()
452{
453	pft_cleanup
454}
455
456atf_test_case "interface" "cleanup"
457interface_head()
458{
459	atf_set descr 'Test killing states based on interface'
460	atf_set require.user root
461	atf_set require.progs python3 scapy
462}
463
464interface_body()
465{
466	pft_init
467
468	epair=$(vnet_mkepair)
469	ifconfig ${epair}a 192.0.2.1/24 up
470
471	vnet_mkjail alcatraz ${epair}b
472	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
473	jexec alcatraz pfctl -e
474
475	pft_set_rules alcatraz "block all" \
476		"pass in proto icmp" \
477		"set skip on lo"
478
479	# Sanity check & establish state
480	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
481		--sendif ${epair}a \
482		--to 192.0.2.2 \
483		--replyif ${epair}a
484
485	# Change rules to now deny the ICMP traffic
486	pft_set_rules noflush alcatraz "block all"
487	if ! find_state;
488	then
489		atf_fail "Setting new rules removed the state."
490	fi
491
492	# Flushing states on a different interface doesn't affect our state
493	jexec alcatraz pfctl -i ${epair}a -Fs
494	if ! find_state;
495	then
496		atf_fail "Flushing on a different interface removed the state."
497	fi
498
499	# Flushing on the correct interface does (even with floating states)
500	jexec alcatraz pfctl -i ${epair}b -Fs
501	if find_state;
502	then
503		atf_fail "Flushing on a the interface did not remove the state."
504	fi
505}
506
507interface_cleanup()
508{
509	pft_cleanup
510}
511
512atf_test_case "id" "cleanup"
513id_head()
514{
515	atf_set descr 'Test killing states by id'
516	atf_set require.user root
517	atf_set require.progs python3 scapy
518}
519
520id_body()
521{
522	pft_init
523
524	epair=$(vnet_mkepair)
525	ifconfig ${epair}a 192.0.2.1/24 up
526
527	vnet_mkjail alcatraz ${epair}b
528	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
529	jexec alcatraz pfctl -e
530
531	pft_set_rules alcatraz "block all" \
532		"pass in proto tcp" \
533		"pass in proto icmp" \
534		"set skip on lo"
535
536	# Sanity check & establish state
537	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
538		--sendif ${epair}a \
539		--to 192.0.2.2 \
540		--replyif ${epair}a
541
542	# Change rules to now deny the ICMP traffic
543	pft_set_rules noflush alcatraz "block all"
544	if ! find_state;
545	then
546		atf_fail "Setting new rules removed the state."
547	fi
548
549	# Get the state ID
550	id=$(jexec alcatraz pfctl -ss -vvv | grep -A 3 icmp |
551	    grep -A 3 192.0.2.2 | awk '/id:/ { printf("%s/%s", $2, $4); }')
552
553	# Kill the wrong ID
554	jexec alcatraz pfctl -k id -k 1
555	if ! find_state;
556	then
557		atf_fail "Killing a different ID removed the state."
558	fi
559
560	# Kill the correct ID
561	jexec alcatraz pfctl -k id -k ${id}
562	if find_state;
563	then
564		atf_fail "Killing the state did not remove it."
565	fi
566}
567
568id_cleanup()
569{
570	pft_cleanup
571}
572
573atf_test_case "key" "cleanup"
574key_head()
575{
576	atf_set descr 'Test killing states by their key'
577	atf_set require.user root
578	atf_set require.progs python3 scapy
579}
580
581key_body()
582{
583	pft_init
584
585	epair=$(vnet_mkepair)
586	ifconfig ${epair}a 192.0.2.1/24 up
587
588	vnet_mkjail alcatraz ${epair}b
589	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
590	jexec alcatraz pfctl -e
591
592	pft_set_rules alcatraz \
593		"block all" \
594		"pass in proto tcp" \
595		"pass in proto icmp"
596
597	# Sanity check & establish state
598	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
599		--sendif ${epair}a \
600		--to 192.0.2.2 \
601		--replyif ${epair}a
602
603	# Get the state key
604	key=$(jexec alcatraz pfctl -ss -vvv | awk '/icmp/ { print($2 " " $3 " " $4 " " $5); }')
605	bad_key=$(echo ${key} | sed 's/icmp/tcp/')
606
607	# Kill the wrong key
608	atf_check -s exit:0 -e "match:killed 0 states" \
609	    jexec alcatraz pfctl -k key -k "${bad_key}"
610	if ! find_state;
611	then
612		atf_fail "Killing a different ID removed the state."
613	fi
614
615	# Kill the correct key
616	atf_check -s exit:0 -e "match:killed 1 states" \
617	    jexec alcatraz pfctl -k key -k "${key}"
618	if find_state;
619	then
620		atf_fail "Killing the state did not remove it."
621	fi
622}
623
624key_cleanup()
625{
626	pft_cleanup
627}
628
629atf_test_case "nat" "cleanup"
630nat_head()
631{
632	atf_set descr 'Test killing states by their NAT-ed IP address'
633	atf_set require.user root
634	atf_set require.progs python3 scapy
635}
636
637nat_body()
638{
639	pft_init
640	j="killstate:nat"
641
642	epair_c=$(vnet_mkepair)
643	epair_srv=$(vnet_mkepair)
644
645	vnet_mkjail ${j}c ${epair_c}a
646	ifconfig -j ${j}c ${epair_c}a inet 192.0.2.2/24 up
647	jexec ${j}c route add default 192.0.2.1
648
649	vnet_mkjail ${j}srv ${epair_srv}a
650	ifconfig -j ${j}srv ${epair_srv}a inet 198.51.100.2/24 up
651
652	vnet_mkjail ${j}r ${epair_c}b ${epair_srv}b
653	ifconfig -j ${j}r ${epair_c}b inet 192.0.2.1/24 up
654	ifconfig -j ${j}r ${epair_srv}b inet 198.51.100.1/24 up
655	jexec ${j}r sysctl net.inet.ip.forwarding=1
656
657	jexec ${j}r pfctl -e
658	pft_set_rules ${j}r \
659		"nat on ${epair_srv}b inet from 192.0.2.0/24 to any -> (${epair_srv}b)"
660
661	# Sanity check
662	atf_check -s exit:0 -o ignore \
663	    jexec ${j}c ping -c 1 192.0.2.1
664	atf_check -s exit:0 -o ignore \
665	    jexec ${j}srv ping -c 1 198.51.100.1
666	atf_check -s exit:0 -o ignore \
667	    jexec ${j}c ping -c 1 198.51.100.2
668
669	# Establish state
670	# Note: use pft_ping so we always use the same ID, so pf considers all
671	# echo requests part of the same flow.
672	atf_check -s exit:0 -o ignore jexec ${j}c ${common_dir}/pft_ping.py \
673		--sendif ${epair_c}a \
674		--to 198.51.100.1 \
675		--replyif ${epair_c}a
676
677	# There's NAT here, so the source IP will be 198.51.100.1
678	if ! find_state ${j}r 198.51.100.1;
679	then
680		atf_fail "Expected state not found"
681	fi
682
683	# By NAT-ed address?
684	jexec ${j}r pfctl -k nat -k 192.0.2.2
685
686	if find_state ${j}r 198.51.100.1;
687	then
688		jexec ${j}r pfctl -ss -v
689		atf_fail "Failed to remove state"
690	fi
691}
692
693nat_cleanup()
694{
695	pft_cleanup
696}
697
698atf_init_test_cases()
699{
700	atf_add_test_case "v4"
701	atf_add_test_case "v6"
702	atf_add_test_case "label"
703	atf_add_test_case "multilabel"
704	atf_add_test_case "gateway"
705	atf_add_test_case "match"
706	atf_add_test_case "interface"
707	atf_add_test_case "id"
708	atf_add_test_case "key"
709	atf_add_test_case "nat"
710}
711