xref: /freebsd/tests/sys/netpfil/pf/fragmentation_pass.sh (revision e736f6df1ec1d2f5a846a20cec8744cf843daf78)
1#
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2017 Kristof Provost <kp@FreeBSD.org>
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
31atf_test_case "too_many_fragments" "cleanup"
32
33too_many_fragments_head()
34{
35	atf_set descr 'IPv4 fragment limitation test'
36	atf_set require.user root
37}
38
39too_many_fragments_body()
40{
41	pft_init
42
43	epair=$(vnet_mkepair)
44	vnet_mkjail alcatraz ${epair}a
45
46	ifconfig ${epair}b inet 192.0.2.1/24 up
47	jexec alcatraz ifconfig ${epair}a 192.0.2.2/24 up
48
49	ifconfig ${epair}b mtu 200
50	jexec alcatraz ifconfig ${epair}a mtu 200
51
52	jexec alcatraz pfctl -e
53	pft_set_rules alcatraz \
54		"set reassemble yes" \
55		"pass keep state"
56
57	# So we know pf is limiting things
58	jexec alcatraz sysctl net.inet.ip.maxfragsperpacket=1024
59
60	# Sanity check
61	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
62
63	# We can ping with < 64 fragments
64	atf_check -s exit:0 -o ignore ping -c 1 -s 800 192.0.2.2
65
66	# Too many fragments should fail
67	atf_check -s exit:2 -o ignore ping -c 1 -s 20000 192.0.2.2
68}
69
70too_many_fragments_cleanup()
71{
72	pft_cleanup
73}
74
75atf_test_case "v6" "cleanup"
76v6_head()
77{
78	atf_set descr 'IPv6 fragmentation test'
79	atf_set require.user root
80	atf_set require.progs scapy
81}
82
83v6_body()
84{
85	pft_init
86
87	epair_send=$(vnet_mkepair)
88	epair_link=$(vnet_mkepair)
89
90	vnet_mkjail alcatraz ${epair_send}b ${epair_link}a
91	vnet_mkjail singsing ${epair_link}b
92
93	ifconfig ${epair_send}a inet6 2001:db8:42::1/64 no_dad up
94
95	jexec alcatraz ifconfig ${epair_send}b inet6 2001:db8:42::2/64 no_dad up
96	jexec alcatraz ifconfig ${epair_link}a inet6 2001:db8:43::2/64 no_dad up
97	jexec alcatraz sysctl net.inet6.ip6.forwarding=1
98
99	jexec singsing ifconfig ${epair_link}b inet6 2001:db8:43::3/64 no_dad up
100	jexec singsing route add -6 2001:db8:42::/64 2001:db8:43::2
101	route add -6 2001:db8:43::/64 2001:db8:42::2
102
103	jexec alcatraz ifconfig ${epair_send}b inet6 -ifdisabled
104	jexec alcatraz ifconfig ${epair_link}a inet6 -ifdisabled
105	jexec singsing ifconfig ${epair_link}b inet6 -ifdisabled
106	ifconfig ${epair_send}a inet6 -ifdisabled
107
108	ifconfig ${epair_send}a
109	jexec alcatraz ifconfig ${epair_send}b
110	lladdr=$(jexec alcatraz ifconfig ${epair_send}b | awk '/ scopeid / { print($2); }' | cut -f 1 -d %)
111
112	jexec alcatraz pfctl -e
113	pft_set_rules alcatraz \
114		"set reassemble yes" \
115		"pass keep state" \
116		"block in" \
117		"pass in inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
118		"pass in inet6 proto icmp6 icmp6-type { echoreq, echorep }" \
119		"set skip on lo"
120
121	# Host test
122	atf_check -s exit:0 -o ignore \
123		ping -6 -c 1 2001:db8:42::2
124
125	atf_check -s exit:0 -o ignore \
126		ping -6 -c 1 -s 4500 2001:db8:42::2
127
128	atf_check -s exit:0 -o ignore\
129		ping -6 -c 1 -b 70000 -s 65000 2001:db8:42::2
130
131	# Force an NDP lookup
132	ping -6 -c 1 ${lladdr}%${epair_send}a
133
134	atf_check -s exit:0 -o ignore\
135		ping -6 -c 1 -b 70000 -s 65000 ${lladdr}%${epair_send}a
136
137	# Forwarding test
138	atf_check -s exit:0 -o ignore \
139		ping -6 -c 1 2001:db8:43::3
140
141	atf_check -s exit:0 -o ignore \
142		ping -6 -c 1 -s 4500 2001:db8:43::3
143
144	atf_check -s exit:0 -o ignore\
145		ping -6 -c 1 -b 70000 -s 65000 2001:db8:43::3
146
147	$(atf_get_srcdir)/CVE-2019-5597.py \
148		${epair_send}a \
149		2001:db8:42::1 \
150		2001:db8:43::3
151}
152
153v6_cleanup()
154{
155	pft_cleanup
156}
157
158atf_test_case "v6_route_to" "cleanup"
159v6_route_to_head()
160{
161	atf_set descr 'Test IPv6 reassembly combined with route-to'
162	atf_set require.user root
163}
164
165v6_route_to_body()
166{
167	pft_init
168
169	epair_send=$(vnet_mkepair)
170	epair_link=$(vnet_mkepair)
171
172	vnet_mkjail alcatraz ${epair_send}b ${epair_link}a
173	vnet_mkjail singsing ${epair_link}b
174
175	ifconfig ${epair_send}a inet6 2001:db8:42::1/64 no_dad up
176
177	jexec alcatraz ifconfig ${epair_send}b inet6 2001:db8:42::2/64 no_dad up
178	jexec alcatraz ifconfig ${epair_link}a inet6 2001:db8:43::2/64 no_dad up
179	jexec alcatraz sysctl net.inet6.ip6.forwarding=1
180
181	jexec singsing ifconfig ${epair_link}b inet6 2001:db8:43::3/64 no_dad up
182	jexec singsing route add -6 2001:db8:42::/64 2001:db8:43::2
183	route add -6 2001:db8:43::/64 2001:db8:42::2
184
185	jexec alcatraz ifconfig ${epair_send}b inet6 -ifdisabled
186	jexec alcatraz ifconfig ${epair_link}a inet6 -ifdisabled
187	jexec singsing ifconfig ${epair_link}b inet6 -ifdisabled
188	ifconfig ${epair_send}a inet6 -ifdisabled
189
190	jexec alcatraz pfctl -e
191	pft_set_rules alcatraz \
192		"set reassemble yes" \
193		"pass" \
194		"pass in route-to (${epair_link}a 2001:db8:43::3) inet6 proto icmp6 from any to 2001:db8:43::3 keep state"
195
196	# Forwarding test
197	atf_check -s exit:0 -o ignore \
198		ping -6 -c 1 2001:db8:43::3
199
200	atf_check -s exit:0 -o ignore \
201		ping -6 -c 1 -s 4500 2001:db8:43::3
202
203	atf_check -s exit:0 -o ignore\
204		ping -6 -c 1 -b 70000 -s 65000 2001:db8:43::3
205
206	# Now test this without fragmentation
207	pft_set_rules alcatraz \
208		"set reassemble no" \
209		"pass" \
210		"pass in route-to (${epair_link}a 2001:db8:43::3) inet6 proto icmp6 from any to 2001:db8:43::3 keep state"
211
212	atf_check -s exit:0 -o ignore \
213		ping -6 -c 1 2001:db8:43::3
214
215	atf_check -s exit:0 -o ignore \
216		ping -6 -c 1 -s 4500 2001:db8:43::3
217
218	atf_check -s exit:0 -o ignore\
219		ping -6 -c 1 -b 70000 -s 65000 2001:db8:43::3
220}
221
222v6_route_to_cleanup()
223{
224	pft_cleanup
225}
226
227atf_test_case "mtu_diff" "cleanup"
228mtu_diff_head()
229{
230	atf_set descr 'Test reassembly across different MTUs, PR #255432'
231	atf_set require.user root
232}
233
234mtu_diff_body()
235{
236	pft_init
237
238	epair_small=$(vnet_mkepair)
239	epair_large=$(vnet_mkepair)
240
241	vnet_mkjail first ${epair_small}b ${epair_large}a
242	vnet_mkjail second ${epair_large}b
243
244	ifconfig ${epair_small}a 192.0.2.1/25 up
245	jexec first ifconfig ${epair_small}b 192.0.2.2/25 up
246
247	jexec first sysctl net.inet.ip.forwarding=1
248	jexec first ifconfig ${epair_large}a 192.0.2.130/25 up
249	jexec first ifconfig ${epair_large}a mtu 9000
250	jexec second ifconfig ${epair_large}b 192.0.2.131/25 up
251	jexec second ifconfig ${epair_large}b mtu 9000
252	jexec second route add default 192.0.2.130
253
254	route add 192.0.2.128/25 192.0.2.2
255
256	jexec first pfctl -e
257	pft_set_rules first \
258		"set reassemble yes" \
259		"pass keep state"
260
261	# Sanity checks
262	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
263	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.130
264	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.131
265
266	# Large packet that'll get reassembled and sent out in one on the large
267	# epair
268	atf_check -s exit:0 -o ignore ping -c 1 -s 8000 192.0.2.131
269}
270
271mtu_diff_cleanup()
272{
273	pft_cleanup
274}
275
276frag_common()
277{
278	name=$1
279
280	pft_init
281
282	epair=$(vnet_mkepair)
283	vnet_mkjail alcatraz ${epair}a
284
285	ifconfig ${epair}b inet 192.0.2.1/24 up
286	jexec alcatraz ifconfig ${epair}a 192.0.2.2/24 up
287
288	jexec alcatraz pfctl -e
289	pft_set_rules alcatraz \
290		"set reassemble yes" \
291		"pass keep state"
292
293	# Sanity check
294	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
295
296	atf_check -s exit:0 -o ignore $(atf_get_srcdir)/frag-${1}.py \
297		--to 192.0.2.2 \
298		--fromaddr 192.0.2.1 \
299		--sendif ${epair}b \
300		--recvif ${epair}b
301}
302
303atf_test_case "overreplace" "cleanup"
304overreplace_head()
305{
306	atf_set descr 'ping fragment that overlaps fragment at index boundary and replace it'
307	atf_set require.user root
308	atf_set require.progs scapy
309}
310
311overreplace_body()
312{
313	frag_common overreplace
314}
315
316overreplace_cleanup()
317{
318	pft_cleanup
319}
320
321atf_test_case "overindex" "cleanup"
322overindex_head()
323{
324	atf_set descr 'ping fragment that overlaps the first fragment at index boundary'
325	atf_set require.user root
326	atf_set require.progs scapy
327}
328
329overindex_body()
330{
331	frag_common overindex
332}
333
334overindex_cleanup()
335{
336	pft_cleanup
337}
338
339atf_test_case "overlimit" "cleanup"
340overlimit_head()
341{
342	atf_set descr 'ping fragment at index boundary that cannot be requeued'
343	atf_set require.user root
344	atf_set require.progs scapy
345}
346
347overlimit_body()
348{
349	frag_common overlimit
350}
351
352overlimit_cleanup()
353{
354	pft_cleanup
355}
356
357atf_test_case "overhole" "cleanup"
358overhole_head()
359{
360	atf_set descr 'ping fragment at index boundary which modifies pf hole counter'
361	atf_set require.user root
362	atf_set require.progs scapy
363}
364
365overhole_body()
366{
367	frag_common overhole
368}
369
370overhole_cleanup()
371{
372	pft_cleanup
373}
374
375atf_test_case "adjhole" "cleanup"
376adjhole_head()
377{
378	atf_set descr 'overlapping ping fragments which modifies pf hole counter'
379	atf_set require.user root
380	atf_set require.progs scapy
381}
382
383adjhole_body()
384{
385	frag_common adjhole
386}
387
388adjhole_cleanup()
389{
390	pft_cleanup
391}
392
393atf_test_case "reassemble" "cleanup"
394reassemble_head()
395{
396	atf_set descr 'Test reassembly'
397	atf_set require.user root
398}
399
400reassemble_body()
401{
402	pft_init
403
404	epair=$(vnet_mkepair)
405	vnet_mkjail alcatraz ${epair}a
406
407	ifconfig ${epair}b inet 192.0.2.1/24 up
408	jexec alcatraz ifconfig ${epair}a 192.0.2.2/24 up
409
410	# Sanity check
411	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
412
413	jexec alcatraz pfctl -e
414	pft_set_rules alcatraz \
415		"pass out" \
416		"block in" \
417		"pass in inet proto icmp all icmp-type echoreq"
418
419	# Single fragment passes
420	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
421
422	# But a fragmented ping does not
423	atf_check -s exit:2 -o ignore ping -c 1 -s 2000 192.0.2.2
424
425	pft_set_rules alcatraz \
426		"set reassemble yes" \
427		"pass out" \
428		"block in" \
429		"pass in inet proto icmp all icmp-type echoreq"
430
431	# Both single packet & fragmented pass when we scrub
432	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
433	atf_check -s exit:0 -o ignore ping -c 1 -s 2000 192.0.2.2
434}
435
436reassemble_cleanup()
437{
438	pft_cleanup
439}
440
441atf_test_case "no_df" "cleanup"
442no_df_head()
443{
444	atf_set descr 'Test removing of DF flag'
445	atf_set require.user root
446	atf_set require.progs scapy
447}
448
449no_df_body()
450{
451	setup_router_server_ipv4
452
453	# Tester can send long packets which will get fragmented by the router.
454	# Replies from server will come in fragments which might get
455	# reassembled resulting in a long reply packet sent back to tester.
456	ifconfig ${epair_tester}a mtu 9000
457	jexec router ifconfig ${epair_tester}b mtu 9000
458	jexec router ifconfig ${epair_server}a mtu 1500
459	jexec server ifconfig ${epair_server}b mtu 1500
460
461	# Sanity check.
462	ping_server_check_reply exit:0 --ping-type=icmp
463
464	# Enable packet reassembly with clearing of the no-df flag.
465	pft_set_rules router \
466		"scrub all fragment reassemble no-df" \
467		"block" \
468		"pass inet proto icmp all icmp-type echoreq"
469	# Ping with non-fragmentable packets.
470	# pf will strip the DF flag resulting in fragmentation and packets
471	# getting properly forwarded.
472	ping_server_check_reply exit:0 --ping-type=icmp --send-length=2000 --send-flags DF
473}
474
475no_df_cleanup()
476{
477	pft_cleanup
478}
479
480atf_test_case "reassemble_slowpath" "cleanup"
481reassemble_slowpath_head()
482{
483	atf_set descr 'Test reassembly on the slow path'
484	atf_set require.user root
485	atf_set require.progs scapy
486}
487
488reassemble_slowpath_body()
489{
490	if ! sysctl -q kern.features.ipsec >/dev/null ; then
491		atf_skip "This test requires ipsec"
492	fi
493
494	setup_router_server_ipv4
495
496	# Now define an ipsec policy so we end up taking the slow path.
497	# We don't actually need the traffic to go through ipsec, we just don't
498	# want to go through ip_tryforward().
499	echo "flush;
500	spdflush;
501	spdadd 203.0.113.1/32 203.0.113.2/32 any -P out ipsec esp/transport//require;
502	add 203.0.113.1 203.0.113.2 esp 0x1001 -E aes-gcm-16 \"12345678901234567890\";" \
503	    | jexec router setkey -c
504
505	# Sanity check.
506	ping_server_check_reply exit:0 --ping-type=icmp
507
508	# Enable packet reassembly with clearing of the no-df flag.
509	pft_set_rules router \
510		"scrub in on ${epair_tester}b fragment no reassemble" \
511		"scrub on ${epair_server}a fragment reassemble" \
512		"pass"
513
514	# Ensure that the packet makes it through the slow path
515	atf_check -s exit:0 -o ignore \
516	    ping -c 1 -s 2000 198.51.100.2
517}
518
519reassemble_slowpath_cleanup()
520{
521	pft_cleanup
522}
523
524atf_test_case "dummynet" "cleanup"
525dummynet_head()
526{
527	atf_set descr 'dummynet + reassembly test'
528	atf_set require.user root
529}
530
531dummynet_body()
532{
533	pft_init
534	dummynet_init
535
536	epair=$(vnet_mkepair)
537	vnet_mkjail alcatraz ${epair}a
538
539	ifconfig ${epair}b inet 192.0.2.1/24 up
540	jexec alcatraz ifconfig ${epair}a 192.0.2.2/24 up
541
542	# Sanity check
543	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
544
545	jexec alcatraz dnctl pipe 1 config bw 600Byte/s
546	jexec alcatraz dnctl pipe 2 config bw 700Byte/s
547
548	jexec alcatraz pfctl -e
549	pft_set_rules alcatraz \
550		"set reassemble yes" \
551		"block" \
552		"pass inet proto icmp all icmp-type echoreq dnpipe (1, 2)"
553
554	atf_check -s exit:0 -o ignore ping -s 2000 -c 1 192.0.2.2
555}
556
557dummynet_cleanup()
558{
559	pft_cleanup
560}
561
562atf_test_case "dummynet_nat" "cleanup"
563dummynet_nat_head()
564{
565	atf_set descr 'Test dummynet on NATed fragmented traffic'
566	atf_set require.user root
567}
568
569dummynet_nat_body()
570{
571	pft_init
572	dummynet_init
573
574	epair_one=$(vnet_mkepair)
575	ifconfig ${epair_one}a 192.0.2.1/24 up
576
577	epair_two=$(vnet_mkepair)
578
579	vnet_mkjail alcatraz ${epair_one}b ${epair_two}a
580	jexec alcatraz ifconfig ${epair_one}b 192.0.2.2/24 up
581	jexec alcatraz ifconfig ${epair_two}a 198.51.100.1/24 up
582	jexec alcatraz sysctl net.inet.ip.forwarding=1
583
584	vnet_mkjail singsing ${epair_two}b
585	jexec singsing ifconfig ${epair_two}b 198.51.100.2/24 up
586	jexec singsing route add default 198.51.100.1
587
588	route add 198.51.100.0/24 192.0.2.2
589
590	jexec alcatraz dnctl pipe 1 config bw 1600Byte/s
591	jexec alcatraz dnctl pipe 2 config bw 1700Byte/s
592
593	jexec alcatraz pfctl -e
594	pft_set_rules alcatraz \
595		"set reassemble yes" \
596		"nat on ${epair_two}a from 192.0.2.0/24 -> (${epair_two}a)" \
597		"block in" \
598		"pass in inet proto icmp all icmp-type echoreq dnpipe (1, 2)"
599
600	atf_check -s exit:0 -o ignore ping -c 1 198.51.100.2
601	atf_check -s exit:0 -o ignore ping -c 1 -s 2000 198.51.100.2
602}
603
604dummynet_nat_cleanup()
605{
606	pft_cleanup
607}
608
609atf_test_case "dummynet_fragmented" "cleanup"
610dummynet_fragmented_head()
611{
612	atf_set descr 'Test dummynet on NATed fragmented traffic'
613	atf_set require.user root
614	atf_set require.progs scapy
615}
616
617dummynet_fragmented_body()
618{
619	pft_init
620	dummynet_init
621
622	# No test for IPv6. IPv6 fragment reassembly can't be disabled.
623	setup_router_dummy_ipv4
624
625	jexec router dnctl pipe 1 config delay 1
626
627	pft_set_rules router \
628		"set reassemble no" \
629		"block" \
630		"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
631		"pass in  on ${epair_tester}b inet  proto udp dnpipe (1, 1)" \
632		"pass out on ${epair_server}a inet  proto udp" \
633
634	ping_dummy_check_request exit:0 --ping-type=udp --send-length=10000 --send-frag-length=1280
635
636	rules=$(mktemp) || exit 1
637	jexec router pfctl -qvsr | normalize_pfctl_s > $rules
638
639	# Count that fragmented packets have hit the rule only once and that
640	# they have not created states. There is no stateful firewall support
641	# for fragmented packets.
642	grep -qE 'pass in on epair0b inet proto udp all keep state dnpipe\(1, 1\) .* Packets: 8 Bytes: 10168 States: 0 ' $rules ||
643		atf_fail "Fragmented packets not counted correctly"
644}
645
646dummynet_fragmented_cleanup()
647{
648	pft_cleanup
649}
650
651atf_init_test_cases()
652{
653	atf_add_test_case "too_many_fragments"
654	atf_add_test_case "v6"
655	atf_add_test_case "v6_route_to"
656	atf_add_test_case "mtu_diff"
657	atf_add_test_case "overreplace"
658	atf_add_test_case "overindex"
659	atf_add_test_case "overlimit"
660	atf_add_test_case "overhole"
661	atf_add_test_case "adjhole"
662	atf_add_test_case "reassemble"
663	atf_add_test_case "no_df"
664	atf_add_test_case "reassemble_slowpath"
665	atf_add_test_case "dummynet"
666	atf_add_test_case "dummynet_nat"
667	atf_add_test_case "dummynet_fragmented"
668}
669