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 "label" "cleanup"
105label_head()
106{
107	atf_set descr 'Test killing states by label'
108	atf_set require.user root
109	atf_set require.progs scapy
110}
111
112label_body()
113{
114	pft_init
115
116	epair=$(vnet_mkepair)
117	ifconfig ${epair}a 192.0.2.1/24 up
118
119	vnet_mkjail alcatraz ${epair}b
120	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
121	jexec alcatraz pfctl -e
122
123	pft_set_rules alcatraz "block all" \
124		"pass in proto tcp label bar" \
125		"pass in proto icmp label foo"
126
127	# Sanity check & establish state
128	# Note: use pft_ping so we always use the same ID, so pf considers all
129	# echo requests part of the same flow.
130	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
131		--sendif ${epair}a \
132		--to 192.0.2.2 \
133		--replyif ${epair}a
134
135	# Change rules to now deny the ICMP traffic
136	pft_set_rules noflush alcatraz "block all"
137
138	# Established state means we can still ping alcatraz
139	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
140		--sendif ${epair}a \
141		--to 192.0.2.2 \
142		--replyif ${epair}a
143
144	# Killing a label on a different rules keeps the state
145	jexec alcatraz pfctl -k label -k bar
146	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
147		--sendif ${epair}a \
148		--to 192.0.2.2 \
149		--replyif ${epair}a
150
151	# Killing a non-existing label keeps the state
152	jexec alcatraz pfctl -k label -k baz
153	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
154		--sendif ${epair}a \
155		--to 192.0.2.2 \
156		--replyif ${epair}a
157
158	# Killing the correct label kills the state
159	jexec alcatraz pfctl -k label -k foo
160	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
161		--sendif ${epair}a \
162		--to 192.0.2.2 \
163		--replyif ${epair}a
164}
165
166label_cleanup()
167{
168	pft_cleanup
169}
170
171atf_test_case "multilabel" "cleanup"
172multilabel_head()
173{
174	atf_set descr 'Test killing states with multiple labels by label'
175	atf_set require.user root
176	atf_set require.progs scapy
177}
178
179multilabel_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 icmp label foo label bar"
192
193	# Sanity check & establish state
194	# Note: use pft_ping so we always use the same ID, so pf considers all
195	# echo requests part of the same flow.
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
204	# Established state means we can still ping alcatraz
205	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
206		--sendif ${epair}a \
207		--to 192.0.2.2 \
208		--replyif ${epair}a
209
210	# Killing a label on a different rules keeps the state
211	jexec alcatraz pfctl -k label -k baz
212	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
213		--sendif ${epair}a \
214		--to 192.0.2.2 \
215		--replyif ${epair}a
216
217	# Killing the state with the last label works
218	jexec alcatraz pfctl -k label -k bar
219	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
220		--sendif ${epair}a \
221		--to 192.0.2.2 \
222		--replyif ${epair}a
223
224	pft_set_rules alcatraz "block all" \
225		"pass in proto icmp label foo label bar"
226
227	# Reestablish state
228	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
229		--sendif ${epair}a \
230		--to 192.0.2.2 \
231		--replyif ${epair}a
232
233	# Change rules to now deny the ICMP traffic
234	pft_set_rules noflush alcatraz "block all"
235
236	# Killing with the first label works too
237	jexec alcatraz pfctl -k label -k foo
238	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
239		--sendif ${epair}a \
240		--to 192.0.2.2 \
241		--replyif ${epair}a
242}
243
244multilabel_cleanup()
245{
246	pft_cleanup
247}
248
249atf_test_case "gateway" "cleanup"
250gateway_head()
251{
252	atf_set descr 'Test killing states by route-to/reply-to address'
253	atf_set require.user root
254	atf_set require.progs scapy
255}
256
257gateway_body()
258{
259	pft_init
260
261	epair=$(vnet_mkepair)
262	ifconfig ${epair}a 192.0.2.1/24 up
263
264	vnet_mkjail alcatraz ${epair}b
265	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
266	jexec alcatraz pfctl -e
267
268	pft_set_rules alcatraz "block all" \
269		"pass in reply-to (${epair}b 192.0.2.1) proto icmp"
270
271	# Sanity check & establish state
272	# Note: use pft_ping so we always use the same ID, so pf considers all
273	# echo requests part of the same flow.
274	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
275		--sendif ${epair}a \
276		--to 192.0.2.2 \
277		--replyif ${epair}a
278
279	# Change rules to now deny the ICMP traffic
280	pft_set_rules noflush alcatraz "block all"
281
282	# Established state means we can still ping alcatraz
283	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
284		--sendif ${epair}a \
285		--to 192.0.2.2 \
286		--replyif ${epair}a
287
288	# Killing with a different gateway does not affect our state
289	jexec alcatraz pfctl -k gateway -k 192.0.2.2
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	# Killing states with the relevant gateway does terminate our state
296	jexec alcatraz pfctl -k gateway -k 192.0.2.1
297	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
298		--sendif ${epair}a \
299		--to 192.0.2.2 \
300		--replyif ${epair}a
301}
302
303gateway_cleanup()
304{
305	pft_cleanup
306}
307
308atf_test_case "match" "cleanup"
309match_head()
310{
311	atf_set descr 'Test killing matching states'
312	atf_set require.user root
313}
314
315wait_for_state()
316{
317	jail=$1
318	addr=$2
319
320	while ! jexec $jail pfctl -s s | grep $addr >/dev/null;
321	do
322		sleep .1
323	done
324}
325
326match_body()
327{
328	pft_init
329
330	epair_one=$(vnet_mkepair)
331	ifconfig ${epair_one}a 192.0.2.1/24 up
332
333	epair_two=$(vnet_mkepair)
334
335	vnet_mkjail alcatraz ${epair_one}b ${epair_two}a
336	jexec alcatraz ifconfig ${epair_one}b 192.0.2.2/24 up
337	jexec alcatraz ifconfig ${epair_two}a 198.51.100.1/24 up
338	jexec alcatraz sysctl net.inet.ip.forwarding=1
339	jexec alcatraz pfctl -e
340
341	vnet_mkjail singsing ${epair_two}b
342	jexec singsing ifconfig ${epair_two}b 198.51.100.2/24 up
343	jexec singsing route add default 198.51.100.1
344	jexec singsing /usr/sbin/inetd -p inetd-echo.pid \
345	    $(atf_get_srcdir)/echo_inetd.conf
346
347	route add 198.51.100.0/24 192.0.2.2
348
349	pft_set_rules alcatraz \
350		"nat on ${epair_two}a from 192.0.2.0/24 -> (${epair_two}a)" \
351		"pass all"
352
353	nc 198.51.100.2 7 &
354	wait_for_state alcatraz 192.0.2.1
355
356	# Expect two states
357	states=$(jexec alcatraz pfctl -s s | wc -l)
358	if [ $states -ne 2 ] ;
359	then
360		atf_fail "Expected two states, found $states"
361	fi
362
363	# If we don't kill the matching NAT state one should be left
364	jexec alcatraz pfctl -k 192.0.2.1
365	states=$(jexec alcatraz pfctl -s s | wc -l)
366	if [ $states -ne 1 ] ;
367	then
368		atf_fail "Expected one states, found $states"
369	fi
370
371	# Flush
372	jexec alcatraz pfctl -F states
373
374	nc 198.51.100.2 7 &
375	wait_for_state alcatraz 192.0.2.1
376
377	# Kill matching states, expect all of them to be gone
378	jexec alcatraz pfctl -M -k 192.0.2.1
379	states=$(jexec alcatraz pfctl -s s | wc -l)
380	if [ $states -ne 0 ] ;
381	then
382		atf_fail "Expected zero states, found $states"
383	fi
384}
385
386match_cleanup()
387{
388	pft_cleanup
389}
390
391atf_test_case "interface" "cleanup"
392interface_head()
393{
394	atf_set descr 'Test killing states based on interface'
395	atf_set require.user root
396	atf_set require.progs scapy
397}
398
399interface_body()
400{
401	pft_init
402
403	epair=$(vnet_mkepair)
404	ifconfig ${epair}a 192.0.2.1/24 up
405
406	vnet_mkjail alcatraz ${epair}b
407	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
408	jexec alcatraz pfctl -e
409
410	pft_set_rules alcatraz "block all" \
411		"pass in proto icmp"
412
413	# Sanity check & establish state
414	# Note: use pft_ping so we always use the same ID, so pf considers all
415	# echo requests part of the same flow.
416	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
417		--sendif ${epair}a \
418		--to 192.0.2.2 \
419		--replyif ${epair}a
420
421	# Change rules to now deny the ICMP traffic
422	pft_set_rules noflush alcatraz "block all"
423
424	# Established state means we can still ping alcatraz
425	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
426		--sendif ${epair}a \
427		--to 192.0.2.2 \
428		--replyif ${epair}a
429
430	# Flushing states on a different interface doesn't affect our state
431	jexec alcatraz pfctl -i ${epair}a -Fs
432	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
433		--sendif ${epair}a \
434		--to 192.0.2.2 \
435		--replyif ${epair}a
436
437	# Flushing on the correct interface does (even with floating states)
438	jexec alcatraz pfctl -i ${epair}b -Fs
439	atf_check -s exit:1 -o ignore ${common_dir}/pft_ping.py \
440		--sendif ${epair}a \
441		--to 192.0.2.2 \
442		--replyif ${epair}a
443}
444
445interface_cleanup()
446{
447	pft_cleanup
448}
449
450atf_init_test_cases()
451{
452	atf_add_test_case "v4"
453	atf_add_test_case "label"
454	atf_add_test_case "multilabel"
455	atf_add_test_case "gateway"
456	atf_add_test_case "match"
457	atf_add_test_case "interface"
458}
459