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