xref: /freebsd/tests/sys/netpfil/pf/ether.sh (revision fdadb006828680427e13436d3d219a73464953ed)
1# $FreeBSD$
2#
3# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4#
5# Copyright © 2021. Rubicon Communications, LLC (Netgate). All Rights Reserved.
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
30atf_test_case "mac" "cleanup"
31mac_head()
32{
33	atf_set descr 'Test MAC address filtering'
34	atf_set require.user root
35}
36
37mac_body()
38{
39	pft_init
40
41	epair=$(vnet_mkepair)
42	epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }')
43
44	ifconfig ${epair}a 192.0.2.1/24 up
45
46	vnet_mkjail alcatraz ${epair}b
47	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
48
49	pft_set_rules alcatraz \
50		"ether block from ${epair_a_mac}"
51
52	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
53
54	# Now enable. Ping should fail.
55	jexec alcatraz pfctl -e
56
57	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
58
59	# Should still fail for 'to'
60	pft_set_rules alcatraz \
61		"ether block to ${epair_a_mac}"
62	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
63
64	# Succeeds if we block a different MAC address
65	pft_set_rules alcatraz \
66		"ether block to 00:01:02:03:04:05"
67	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
68
69	# Should still fail for 'to', even if it's in a list
70	pft_set_rules alcatraz \
71		"ether block to { ${epair_a_mac}, 00:01:02:0:04:05 }"
72	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
73
74	# Now try this with an interface specified
75	pft_set_rules alcatraz \
76		"ether block on ${epair}b from ${epair_a_mac}"
77	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
78
79	# Wrong interface should not match
80	pft_set_rules alcatraz \
81		"ether block on ${epair}a from ${epair_a_mac}"
82	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
83
84	# Test negation
85	pft_set_rules alcatraz \
86		"ether block in on ${epair}b from ! ${epair_a_mac}"
87	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
88
89	pft_set_rules alcatraz \
90		"ether block out on ${epair}b to ! ${epair_a_mac}"
91	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
92
93	# Block everything not us
94	pft_set_rules alcatraz \
95		"ether block out on ${epair}b to { ! ${epair_a_mac} }"
96	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
97
98	# Block us now
99	pft_set_rules alcatraz \
100		"ether block out on ${epair}b to { ! 00:01:02:03:04:05 }"
101	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
102
103	# Check '-F ethernet' works
104	jexec alcatraz pfctl -F ethernet
105	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
106}
107
108mac_cleanup()
109{
110	pft_cleanup
111}
112
113atf_test_case "proto" "cleanup"
114proto_head()
115{
116	atf_set descr 'Test EtherType filtering'
117	atf_set require.user root
118}
119
120proto_body()
121{
122	pft_init
123
124	epair=$(vnet_mkepair)
125	epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }')
126
127	ifconfig ${epair}a 192.0.2.1/24 up
128
129	vnet_mkjail alcatraz ${epair}b
130	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
131
132	pft_set_rules alcatraz \
133		"ether block proto 0x0810"
134	jexec alcatraz pfctl -e
135
136	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
137
138	# Block IP
139	pft_set_rules alcatraz \
140		"ether block proto 0x0800"
141	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
142
143	# Block ARP
144	pft_set_rules alcatraz \
145		"ether block proto 0x0806"
146	arp -d 192.0.2.2
147	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
148}
149
150proto_cleanup()
151{
152	pft_cleanup
153}
154
155atf_test_case "direction" "cleanup"
156direction_head()
157{
158	atf_set descr 'Test directionality of ether rules'
159	atf_set require.user root
160	atf_set require.progs jq
161}
162
163direction_body()
164{
165	pft_init
166
167	epair=$(vnet_mkepair)
168	epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }')
169	epair_b_mac=$(ifconfig ${epair}b ether | awk '/ether/ { print $2; }')
170
171	ifconfig ${epair}a 192.0.2.1/24 up
172
173	vnet_mkjail alcatraz ${epair}b
174	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
175
176	pft_set_rules alcatraz \
177		"ether block in proto 0x0806"
178	jexec alcatraz pfctl -e
179
180	arp -d 192.0.2.2
181	jexec alcatraz arp -d 192.0.2.1
182
183	# We don't allow the jail to receive ARP requests, so if we try to ping
184	# from host to jail the host can't resolve the MAC address
185	ping -c 1 -t 1 192.0.2.2
186
187	mac=$(arp -an --libxo json \
188	    | jq '."arp"."arp-cache"[] |
189	    select(."ip-address"=="192.0.2.2")."mac-address"')
190	atf_check_not_equal "$mac" "$epair_b_mac"
191
192	# Clear ARP table again
193	arp -d 192.0.2.2
194	jexec alcatraz arp -d 192.0.2.1
195
196	# However, we allow outbound ARP, so the host will learn our MAC if the
197	# jail tries to ping
198	jexec alcatraz ping -c 1 -t 1 192.0.2.1
199
200	mac=$(arp -an --libxo json \
201	    | jq '."arp"."arp-cache"[] |
202	    select(."ip-address"=="192.0.2.2")."mac-address"')
203	atf_check_equal "$mac" "$epair_b_mac"
204
205	# Now do the same, but with outbound ARP blocking
206	pft_set_rules alcatraz \
207		"ether block out proto 0x0806"
208
209	# Clear ARP table again
210	arp -d 192.0.2.2
211	jexec alcatraz arp -d 192.0.2.1
212
213	# The jail can't send ARP requests to us, so we'll never learn our MAC
214	# address
215	jexec alcatraz ping -c 1 -t 1 192.0.2.1
216
217	mac=$(jexec alcatraz arp -an --libxo json \
218	    | jq '."arp"."arp-cache"[] |
219	    select(."ip-address"=="192.0.2.1")."mac-address"')
220	atf_check_not_equal "$mac" "$epair_a_mac"
221}
222
223direction_cleanup()
224{
225	pft_cleanup
226}
227
228atf_test_case "captive" "cleanup"
229captive_head()
230{
231	atf_set descr 'Test a basic captive portal-like setup'
232	atf_set require.user root
233}
234
235captive_body()
236{
237	# Host is client, jail 'gw' is the captive portal gateway, jail 'srv'
238	# is a random (web)server. We use the echo protocol rather than http
239	# for the test, because that's easier.
240	pft_init
241
242	epair_gw=$(vnet_mkepair)
243	epair_srv=$(vnet_mkepair)
244	epair_gw_a_mac=$(ifconfig ${epair_gw}a ether | awk '/ether/ { print $2; }')
245
246	vnet_mkjail gw ${epair_gw}b ${epair_srv}a
247	vnet_mkjail srv ${epair_srv}b
248
249	ifconfig ${epair_gw}a 192.0.2.2/24 up
250	route add -net 198.51.100.0/24 192.0.2.1
251	jexec gw ifconfig ${epair_gw}b 192.0.2.1/24 up
252	jexec gw ifconfig lo0 127.0.0.1/8 up
253	jexec gw sysctl net.inet.ip.forwarding=1
254
255	jexec gw ifconfig ${epair_srv}a 198.51.100.1/24 up
256	jexec srv ifconfig ${epair_srv}b 198.51.100.2/24 up
257	jexec srv route add -net 192.0.2.0/24 198.51.100.1
258
259	# Sanity check
260	atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2
261
262	pft_set_rules gw \
263		"ether pass quick proto 0x0806" \
264		"ether pass tag captive" \
265		"rdr on ${epair_gw}b proto tcp to port echo tagged captive -> 127.0.0.1 port echo"
266	jexec gw pfctl -e
267
268	# ICMP should still work, because we don't redirect it.
269	atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2
270
271	# Run the echo server only on the gw, so we know we've redirectly
272	# correctly if we get an echo message.
273	jexec gw /usr/sbin/inetd $(atf_get_srcdir)/echo_inetd.conf
274
275	# Confirm that we're getting redirected
276	atf_check -s exit:0 -o match:"^foo$" -x "echo foo | nc -N 198.51.100.2 7"
277
278	jexec gw killall inetd
279
280	# Now pretend we've authenticated, so add the client's MAC address
281	pft_set_rules gw \
282		"ether pass quick proto 0x0806" \
283		"ether pass quick from ${epair_gw_a_mac}" \
284		"ether pass tag captive" \
285		"rdr on ${epair_gw}b proto tcp to port echo tagged captive -> 127.0.0.1 port echo"
286
287	# No redirect, so failure.
288	atf_check -s exit:1 -x "echo foo | nc -N 198.51.100.2 7"
289
290	# Start a server in srv
291	jexec srv /usr/sbin/inetd $(atf_get_srcdir)/echo_inetd.conf
292
293	# And now we can talk to that one.
294	atf_check -s exit:0 -o match:"^foo$" -x "echo foo | nc -N 198.51.100.2 7"
295}
296
297captive_cleanup()
298{
299	pft_cleanup
300}
301
302atf_test_case "captive_long" "cleanup"
303captive_long_head()
304{
305	atf_set descr 'More complex captive portal setup'
306	atf_set require.user root
307}
308
309captive_long_body()
310{
311	# Host is client, jail 'gw' is the captive portal gateway, jail 'srv'
312	# is a random (web)server. We use the echo protocol rather than http
313	# for the test, because that's easier.
314	pft_init
315
316	if ! kldstat -q -m dummynet; then
317		atf_skip "This test requires dummynet"
318	fi
319
320	epair_gw=$(vnet_mkepair)
321	epair_srv=$(vnet_mkepair)
322	epair_gw_a_mac=$(ifconfig ${epair_gw}a ether | awk '/ether/ { print $2; }')
323
324	vnet_mkjail gw ${epair_gw}b ${epair_srv}a
325	vnet_mkjail srv ${epair_srv}b
326
327	ifconfig ${epair_gw}a 192.0.2.2/24 up
328	route add -net 198.51.100.0/24 192.0.2.1
329	jexec gw ifconfig ${epair_gw}b 192.0.2.1/24 up
330	jexec gw ifconfig lo0 127.0.0.1/8 up
331	jexec gw sysctl net.inet.ip.forwarding=1
332
333	jexec gw ifconfig ${epair_srv}a 198.51.100.1/24 up
334	jexec srv ifconfig ${epair_srv}b 198.51.100.2/24 up
335	jexec srv route add -net 192.0.2.0/24 198.51.100.1
336
337	jexec gw dnctl pipe 1 config bw 300KByte/s
338
339	# Sanity check
340	atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2
341
342	pft_set_rules gw \
343		"ether anchor \"captiveportal\" on { ${epair_gw}b } {" \
344			"ether pass quick proto { 0x0806, 0x8035, 0x888e, 0x88c7, 0x8863, 0x8864 }" \
345			"ether pass tag \"captive\"" \
346		"}" \
347		"rdr on ${epair_gw}b proto tcp to port daytime tagged captive -> 127.0.0.1 port echo"
348	jexec gw pfctl -e
349
350	# ICMP should still work, because we don't redirect it.
351	atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2
352
353	jexec gw /usr/sbin/inetd -p gw.pid $(atf_get_srcdir)/echo_inetd.conf
354	jexec srv /usr/sbin/inetd -p srv.pid $(atf_get_srcdir)/daytime_inetd.conf
355
356	echo foo | nc -N 198.51.100.2 13
357
358	# Confirm that we're getting redirected
359	atf_check -s exit:0 -o match:"^foo$" -x "echo foo | nc -N 198.51.100.2 13"
360
361	# Now update the rules to allow our client to pass without redirect
362	pft_set_rules gw \
363		"ether anchor \"captiveportal\" on { ${epair_gw}b } {" \
364			"ether pass quick proto { 0x0806, 0x8035, 0x888e, 0x88c7, 0x8863, 0x8864 }" \
365			"ether pass quick from { ${epair_gw_a_mac} } dnpipe 1" \
366			"ether pass tag \"captive\"" \
367		"}" \
368		"rdr on ${epair_gw}b proto tcp to port daytime tagged captive -> 127.0.0.1 port echo"
369
370	# We're not being redirected and get datime information now
371	atf_check -s exit:0 -o match:"^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)" -x "echo foo | nc -N 198.51.100.2 13"
372
373	jexec gw killall inetd
374	jexec srv killall inetd
375}
376
377captive_long_cleanup()
378{
379	pft_cleanup
380}
381
382atf_test_case "dummynet" "cleanup"
383dummynet_head()
384{
385	atf_set descr 'Test dummynet for L2 traffic'
386	atf_set require.user root
387}
388
389dummynet_body()
390{
391	pft_init
392
393	if ! kldstat -q -m dummynet; then
394		atf_skip "This test requires dummynet"
395	fi
396
397	epair=$(vnet_mkepair)
398	vnet_mkjail alcatraz ${epair}b
399
400	ifconfig ${epair}a 192.0.2.1/24 up
401	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
402
403	# Sanity check
404	atf_check -s exit:0 -o ignore ping -i .1 -c 3 -s 1200 192.0.2.2
405
406	jexec alcatraz dnctl pipe 1 config bw 30Byte/s
407	jexec alcatraz pfctl -e
408	pft_set_rules alcatraz \
409		"ether pass in dnpipe 1"
410
411	# single ping succeeds just fine
412	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
413
414	# Saturate the link
415	ping -i .1 -c 5 -s 1200 192.0.2.2
416
417	# We should now be hitting the limits and get this packet dropped.
418	atf_check -s exit:2 -o ignore ping -c 1 -s 1200 192.0.2.2
419}
420
421dummynet_cleanup()
422{
423	pft_cleanup
424}
425
426atf_test_case "anchor" "cleanup"
427anchor_head()
428{
429	atf_set descr 'Test ether anchors'
430	atf_set require.user root
431}
432
433anchor_body()
434{
435	pft_init
436
437	epair=$(vnet_mkepair)
438	epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }')
439
440	vnet_mkjail alcatraz ${epair}b
441
442	ifconfig ${epair}a 192.0.2.1/24 up
443	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
444
445	# Sanity check
446	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
447
448	jexec alcatraz pfctl -e
449	pft_set_rules alcatraz \
450		"ether anchor \"foo\" in on lo0 {" \
451			"ether block" \
452		"}"
453
454	# That only filters on lo0, so we should still be able to pass traffic
455	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
456
457	pft_set_rules alcatraz \
458		"ether block in" \
459		"ether anchor \"foo\" in on ${epair}b {" \
460			"ether pass" \
461		"}"
462	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
463
464	pft_set_rules alcatraz \
465		"ether pass" \
466		"ether anchor \"bar\" in on ${epair}b {" \
467			"ether block" \
468		"}"
469	atf_check -s exit:2 -o ignore ping -c 1 -t 2 192.0.2.2
470
471	pft_set_rules alcatraz \
472		"ether block in" \
473		"ether anchor \"baz\" on ${epair}b {" \
474			"ether pass in from 01:02:03:04:05:06" \
475		"}" \
476		"ether pass in from ${epair_a_mac}"
477	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
478}
479
480anchor_cleanup()
481{
482	pft_cleanup
483}
484
485atf_init_test_cases()
486{
487	atf_add_test_case "mac"
488	atf_add_test_case "proto"
489	atf_add_test_case "direction"
490	atf_add_test_case "captive"
491	atf_add_test_case "captive_long"
492	atf_add_test_case "dummynet"
493	atf_add_test_case "anchor"
494}
495