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