1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4# +------------------+
5# | H1 (v$h1)        |
6# | 2001:db8:1::2/64 |
7# | 198.51.100.2/28  |
8# |         $h1 +    |
9# +-------------|----+
10#               |
11# +-------------|-------------------------------+
12# | SW1         |                               |
13# |        $rp1 +                               |
14# | 198.51.100.1/28                             |
15# | 2001:db8:1::1/64                            |
16# |                                             |
17# | 2001:db8:2::1/64           2001:db8:3::1/64 |
18# | 198.51.100.17/28           198.51.100.33/28 |
19# |         $rp2 +                     $rp3 +   |
20# +--------------|--------------------------|---+
21#                |                          |
22#                |                          |
23# +--------------|---+       +--------------|---+
24# | H2 (v$h2)    |   |       | H3 (v$h3)    |   |
25# |          $h2 +   |       |          $h3 +   |
26# | 198.51.100.18/28 |       | 198.51.100.34/28 |
27# | 2001:db8:2::2/64 |       | 2001:db8:3::2/64 |
28# +------------------+       +------------------+
29#
30
31ALL_TESTS="mcast_v4 mcast_v6 rpf_v4 rpf_v6 unres_v4 unres_v6"
32NUM_NETIFS=6
33source lib.sh
34source tc_common.sh
35
36require_command $MCD
37require_command $MC_CLI
38table_name=selftests
39
40h1_create()
41{
42	simple_if_init $h1 198.51.100.2/28 2001:db8:1::2/64
43
44	ip route add 198.51.100.16/28 vrf v$h1 nexthop via 198.51.100.1
45	ip route add 198.51.100.32/28 vrf v$h1 nexthop via 198.51.100.1
46
47	ip route add 2001:db8:2::/64 vrf v$h1 nexthop via 2001:db8:1::1
48	ip route add 2001:db8:3::/64 vrf v$h1 nexthop via 2001:db8:1::1
49
50	tc qdisc add dev $h1 ingress
51}
52
53h1_destroy()
54{
55	tc qdisc del dev $h1 ingress
56
57	ip route del 2001:db8:3::/64 vrf v$h1
58	ip route del 2001:db8:2::/64 vrf v$h1
59
60	ip route del 198.51.100.32/28 vrf v$h1
61	ip route del 198.51.100.16/28 vrf v$h1
62
63	simple_if_fini $h1 198.51.100.2/28 2001:db8:1::2/64
64}
65
66h2_create()
67{
68	simple_if_init $h2 198.51.100.18/28 2001:db8:2::2/64
69
70	ip route add 198.51.100.0/28 vrf v$h2 nexthop via 198.51.100.17
71	ip route add 198.51.100.32/28 vrf v$h2 nexthop via 198.51.100.17
72
73	ip route add 2001:db8:1::/64 vrf v$h2 nexthop via 2001:db8:2::1
74	ip route add 2001:db8:3::/64 vrf v$h2 nexthop via 2001:db8:2::1
75
76	tc qdisc add dev $h2 ingress
77}
78
79h2_destroy()
80{
81	tc qdisc del dev $h2 ingress
82
83	ip route del 2001:db8:3::/64 vrf v$h2
84	ip route del 2001:db8:1::/64 vrf v$h2
85
86	ip route del 198.51.100.32/28 vrf v$h2
87	ip route del 198.51.100.0/28 vrf v$h2
88
89	simple_if_fini $h2 198.51.100.18/28 2001:db8:2::2/64
90}
91
92h3_create()
93{
94	simple_if_init $h3 198.51.100.34/28 2001:db8:3::2/64
95
96	ip route add 198.51.100.0/28 vrf v$h3 nexthop via 198.51.100.33
97	ip route add 198.51.100.16/28 vrf v$h3 nexthop via 198.51.100.33
98
99	ip route add 2001:db8:1::/64 vrf v$h3 nexthop via 2001:db8:3::1
100	ip route add 2001:db8:2::/64 vrf v$h3 nexthop via 2001:db8:3::1
101
102	tc qdisc add dev $h3 ingress
103}
104
105h3_destroy()
106{
107	tc qdisc del dev $h3 ingress
108
109	ip route del 2001:db8:2::/64 vrf v$h3
110	ip route del 2001:db8:1::/64 vrf v$h3
111
112	ip route del 198.51.100.16/28 vrf v$h3
113	ip route del 198.51.100.0/28 vrf v$h3
114
115	simple_if_fini $h3 198.51.100.34/28 2001:db8:3::2/64
116}
117
118router_create()
119{
120	ip link set dev $rp1 up
121	ip link set dev $rp2 up
122	ip link set dev $rp3 up
123
124	ip address add 198.51.100.1/28 dev $rp1
125	ip address add 198.51.100.17/28 dev $rp2
126	ip address add 198.51.100.33/28 dev $rp3
127
128	ip address add 2001:db8:1::1/64 dev $rp1
129	ip address add 2001:db8:2::1/64 dev $rp2
130	ip address add 2001:db8:3::1/64 dev $rp3
131
132	tc qdisc add dev $rp3 ingress
133}
134
135router_destroy()
136{
137	tc qdisc del dev $rp3 ingress
138
139	ip address del 2001:db8:3::1/64 dev $rp3
140	ip address del 2001:db8:2::1/64 dev $rp2
141	ip address del 2001:db8:1::1/64 dev $rp1
142
143	ip address del 198.51.100.33/28 dev $rp3
144	ip address del 198.51.100.17/28 dev $rp2
145	ip address del 198.51.100.1/28 dev $rp1
146
147	ip link set dev $rp3 down
148	ip link set dev $rp2 down
149	ip link set dev $rp1 down
150}
151
152start_mcd()
153{
154	SMCROUTEDIR="$(mktemp -d)"
155
156	for ((i = 1; i <= $NUM_NETIFS; ++i)); do
157		echo "phyint ${NETIFS[p$i]} enable" >> \
158			$SMCROUTEDIR/$table_name.conf
159	done
160
161	$MCD -N -I $table_name -f $SMCROUTEDIR/$table_name.conf \
162		-P $SMCROUTEDIR/$table_name.pid
163}
164
165kill_mcd()
166{
167	pkill $MCD
168	rm -rf $SMCROUTEDIR
169}
170
171setup_prepare()
172{
173	h1=${NETIFS[p1]}
174	rp1=${NETIFS[p2]}
175
176	rp2=${NETIFS[p3]}
177	h2=${NETIFS[p4]}
178
179	rp3=${NETIFS[p5]}
180	h3=${NETIFS[p6]}
181
182	start_mcd
183
184	vrf_prepare
185
186	h1_create
187	h2_create
188	h3_create
189
190	router_create
191
192	forwarding_enable
193}
194
195cleanup()
196{
197	pre_cleanup
198
199	forwarding_restore
200
201	router_destroy
202
203	h3_destroy
204	h2_destroy
205	h1_destroy
206
207	vrf_cleanup
208
209	kill_mcd
210}
211
212create_mcast_sg()
213{
214	local if_name=$1; shift
215	local s_addr=$1; shift
216	local mcast=$1; shift
217	local dest_ifs=${@}
218
219	$MC_CLI -I $table_name add $if_name $s_addr $mcast $dest_ifs
220}
221
222delete_mcast_sg()
223{
224	local if_name=$1; shift
225	local s_addr=$1; shift
226	local mcast=$1; shift
227	local dest_ifs=${@}
228
229        $MC_CLI -I $table_name remove $if_name $s_addr $mcast $dest_ifs
230}
231
232mcast_v4()
233{
234	# Add two interfaces to an MC group, send a packet to the MC group and
235	# verify packets are received on both. Then delete the route and verify
236	# packets are no longer received.
237
238	RET=0
239
240	tc filter add dev $h2 ingress protocol ip pref 1 handle 122 flower \
241		dst_ip 225.1.2.3 action drop
242	tc filter add dev $h3 ingress protocol ip pref 1 handle 133 flower \
243		dst_ip 225.1.2.3 action drop
244
245	create_mcast_sg $rp1 198.51.100.2 225.1.2.3 $rp2 $rp3
246
247	# Send frames with the corresponding L2 destination address.
248	$MZ $h1 -c 5 -p 128 -t udp -a 00:11:22:33:44:55 -b 01:00:5e:01:02:03 \
249		-A 198.51.100.2 -B 225.1.2.3 -q
250
251	tc_check_packets "dev $h2 ingress" 122 5
252	check_err $? "Multicast not received on first host"
253	tc_check_packets "dev $h3 ingress" 133 5
254	check_err $? "Multicast not received on second host"
255
256	delete_mcast_sg $rp1 198.51.100.2 225.1.2.3 $rp2 $rp3
257
258	$MZ $h1 -c 5 -p 128 -t udp -a 00:11:22:33:44:55 -b 01:00:5e:01:02:03 \
259		-A 198.51.100.2 -B 225.1.2.3 -q
260
261	tc_check_packets "dev $h2 ingress" 122 5
262	check_err $? "Multicast received on host although deleted"
263	tc_check_packets "dev $h3 ingress" 133 5
264	check_err $? "Multicast received on second host although deleted"
265
266	tc filter del dev $h3 ingress protocol ip pref 1 handle 133 flower
267	tc filter del dev $h2 ingress protocol ip pref 1 handle 122 flower
268
269	log_test "mcast IPv4"
270}
271
272mcast_v6()
273{
274	# Add two interfaces to an MC group, send a packet to the MC group and
275	# verify packets are received on both. Then delete the route and verify
276	# packets are no longer received.
277
278	RET=0
279
280	tc filter add dev $h2 ingress protocol ipv6 pref 1 handle 122 flower \
281		dst_ip ff0e::3 action drop
282	tc filter add dev $h3 ingress protocol ipv6 pref 1 handle 133 flower \
283		dst_ip ff0e::3 action drop
284
285	create_mcast_sg $rp1 2001:db8:1::2 ff0e::3 $rp2 $rp3
286
287	# Send frames with the corresponding L2 destination address.
288	$MZ $h1 -6 -c 5 -p 128 -t udp -a 00:11:22:33:44:55 \
289		-b 33:33:00:00:00:03 -A 2001:db8:1::2 -B ff0e::3 -q
290
291	tc_check_packets "dev $h2 ingress" 122 5
292	check_err $? "Multicast not received on first host"
293	tc_check_packets "dev $h3 ingress" 133 5
294	check_err $? "Multicast not received on second host"
295
296	delete_mcast_sg $rp1 2001:db8:1::2 ff0e::3 $rp2 $rp3
297
298	$MZ $h1 -6 -c 5 -p 128 -t udp -a 00:11:22:33:44:55 \
299		-b 33:33:00:00:00:03 -A 2001:db8:1::2 -B ff0e::3 -q
300
301	tc_check_packets "dev $h2 ingress" 122 5
302	check_err $? "Multicast received on first host although deleted"
303	tc_check_packets "dev $h3 ingress" 133 5
304	check_err $? "Multicast received on second host although deleted"
305
306	tc filter del dev $h3 ingress protocol ipv6 pref 1 handle 133 flower
307	tc filter del dev $h2 ingress protocol ipv6 pref 1 handle 122 flower
308
309	log_test "mcast IPv6"
310}
311
312rpf_v4()
313{
314	# Add a multicast route from first router port to the other two. Send
315	# matching packets and test that both hosts receive them. Then, send
316	# the same packets via the third router port and test that they do not
317	# reach any host due to RPF check. A filter with 'skip_hw' is added to
318	# test that devices capable of multicast routing offload trap those
319	# packets. The filter is essentialy a NOP in other scenarios.
320
321	RET=0
322
323	tc filter add dev $h1 ingress protocol ip pref 1 handle 1 flower \
324		dst_ip 225.1.2.3 ip_proto udp dst_port 12345 action drop
325	tc filter add dev $h2 ingress protocol ip pref 1 handle 1 flower \
326		dst_ip 225.1.2.3 ip_proto udp dst_port 12345 action drop
327	tc filter add dev $h3 ingress protocol ip pref 1 handle 1 flower \
328		dst_ip 225.1.2.3 ip_proto udp dst_port 12345 action drop
329	tc filter add dev $rp3 ingress protocol ip pref 1 handle 1 flower \
330		skip_hw dst_ip 225.1.2.3 ip_proto udp dst_port 12345 action pass
331
332	create_mcast_sg $rp1 198.51.100.2 225.1.2.3 $rp2 $rp3
333
334	$MZ $h1 -c 5 -p 128 -t udp "ttl=10,sp=54321,dp=12345" \
335		-a 00:11:22:33:44:55 -b 01:00:5e:01:02:03 \
336		-A 198.51.100.2 -B 225.1.2.3 -q
337
338	tc_check_packets "dev $h2 ingress" 1 5
339	check_err $? "Multicast not received on first host"
340	tc_check_packets "dev $h3 ingress" 1 5
341	check_err $? "Multicast not received on second host"
342
343	$MZ $h3 -c 5 -p 128 -t udp "ttl=10,sp=54321,dp=12345" \
344		-a 00:11:22:33:44:55 -b 01:00:5e:01:02:03 \
345		-A 198.51.100.2 -B 225.1.2.3 -q
346
347	tc_check_packets "dev $h1 ingress" 1 0
348	check_err $? "Multicast received on first host when should not"
349	tc_check_packets "dev $h2 ingress" 1 5
350	check_err $? "Multicast received on second host when should not"
351	tc_check_packets "dev $rp3 ingress" 1 5
352	check_err $? "Packets not trapped due to RPF check"
353
354	delete_mcast_sg $rp1 198.51.100.2 225.1.2.3 $rp2 $rp3
355
356	tc filter del dev $rp3 ingress protocol ip pref 1 handle 1 flower
357	tc filter del dev $h3 ingress protocol ip pref 1 handle 1 flower
358	tc filter del dev $h2 ingress protocol ip pref 1 handle 1 flower
359	tc filter del dev $h1 ingress protocol ip pref 1 handle 1 flower
360
361	log_test "RPF IPv4"
362}
363
364rpf_v6()
365{
366	RET=0
367
368	tc filter add dev $h1 ingress protocol ipv6 pref 1 handle 1 flower \
369		dst_ip ff0e::3 ip_proto udp dst_port 12345 action drop
370	tc filter add dev $h2 ingress protocol ipv6 pref 1 handle 1 flower \
371		dst_ip ff0e::3 ip_proto udp dst_port 12345 action drop
372	tc filter add dev $h3 ingress protocol ipv6 pref 1 handle 1 flower \
373		dst_ip ff0e::3 ip_proto udp dst_port 12345 action drop
374	tc filter add dev $rp3 ingress protocol ipv6 pref 1 handle 1 flower \
375		skip_hw dst_ip ff0e::3 ip_proto udp dst_port 12345 action pass
376
377	create_mcast_sg $rp1 2001:db8:1::2 ff0e::3 $rp2 $rp3
378
379	$MZ $h1 -6 -c 5 -p 128 -t udp "ttl=10,sp=54321,dp=12345" \
380		-a 00:11:22:33:44:55 -b 33:33:00:00:00:03 \
381		-A 2001:db8:1::2 -B ff0e::3 -q
382
383	tc_check_packets "dev $h2 ingress" 1 5
384	check_err $? "Multicast not received on first host"
385	tc_check_packets "dev $h3 ingress" 1 5
386	check_err $? "Multicast not received on second host"
387
388	$MZ $h3 -6 -c 5 -p 128 -t udp "ttl=10,sp=54321,dp=12345" \
389		-a 00:11:22:33:44:55 -b 33:33:00:00:00:03 \
390		-A 2001:db8:1::2 -B ff0e::3 -q
391
392	tc_check_packets "dev $h1 ingress" 1 0
393	check_err $? "Multicast received on first host when should not"
394	tc_check_packets "dev $h2 ingress" 1 5
395	check_err $? "Multicast received on second host when should not"
396	tc_check_packets "dev $rp3 ingress" 1 5
397	check_err $? "Packets not trapped due to RPF check"
398
399	delete_mcast_sg $rp1 2001:db8:1::2 ff0e::3 $rp2 $rp3
400
401	tc filter del dev $rp3 ingress protocol ipv6 pref 1 handle 1 flower
402	tc filter del dev $h3 ingress protocol ipv6 pref 1 handle 1 flower
403	tc filter del dev $h2 ingress protocol ipv6 pref 1 handle 1 flower
404	tc filter del dev $h1 ingress protocol ipv6 pref 1 handle 1 flower
405
406	log_test "RPF IPv6"
407}
408
409unres_v4()
410{
411	# Send a multicast packet not corresponding to an installed route,
412	# causing the kernel to queue the packet for resolution and emit an
413	# IGMPMSG_NOCACHE notification. smcrouted will react to this
414	# notification by consulting its (*, G) list and installing an (S, G)
415	# route, which will be used to forward the queued packet.
416
417	RET=0
418
419	tc filter add dev $h2 ingress protocol ip pref 1 handle 1 flower \
420		dst_ip 225.1.2.3 ip_proto udp dst_port 12345 action drop
421	tc filter add dev $h3 ingress protocol ip pref 1 handle 1 flower \
422		dst_ip 225.1.2.3 ip_proto udp dst_port 12345 action drop
423
424	# Forwarding should fail before installing a matching (*, G).
425	$MZ $h1 -c 1 -p 128 -t udp "ttl=10,sp=54321,dp=12345" \
426		-a 00:11:22:33:44:55 -b 01:00:5e:01:02:03 \
427		-A 198.51.100.2 -B 225.1.2.3 -q
428
429	tc_check_packets "dev $h2 ingress" 1 0
430	check_err $? "Multicast received on first host when should not"
431	tc_check_packets "dev $h3 ingress" 1 0
432	check_err $? "Multicast received on second host when should not"
433
434	# Create (*, G). Will not be installed in the kernel.
435	create_mcast_sg $rp1 0.0.0.0 225.1.2.3 $rp2 $rp3
436
437	$MZ $h1 -c 1 -p 128 -t udp "ttl=10,sp=54321,dp=12345" \
438		-a 00:11:22:33:44:55 -b 01:00:5e:01:02:03 \
439		-A 198.51.100.2 -B 225.1.2.3 -q
440
441	tc_check_packets "dev $h2 ingress" 1 1
442	check_err $? "Multicast not received on first host"
443	tc_check_packets "dev $h3 ingress" 1 1
444	check_err $? "Multicast not received on second host"
445
446	delete_mcast_sg $rp1 0.0.0.0 225.1.2.3 $rp2 $rp3
447
448	tc filter del dev $h3 ingress protocol ip pref 1 handle 1 flower
449	tc filter del dev $h2 ingress protocol ip pref 1 handle 1 flower
450
451	log_test "Unresolved queue IPv4"
452}
453
454unres_v6()
455{
456	# Send a multicast packet not corresponding to an installed route,
457	# causing the kernel to queue the packet for resolution and emit an
458	# MRT6MSG_NOCACHE notification. smcrouted will react to this
459	# notification by consulting its (*, G) list and installing an (S, G)
460	# route, which will be used to forward the queued packet.
461
462	RET=0
463
464	tc filter add dev $h2 ingress protocol ipv6 pref 1 handle 1 flower \
465		dst_ip ff0e::3 ip_proto udp dst_port 12345 action drop
466	tc filter add dev $h3 ingress protocol ipv6 pref 1 handle 1 flower \
467		dst_ip ff0e::3 ip_proto udp dst_port 12345 action drop
468
469	# Forwarding should fail before installing a matching (*, G).
470	$MZ $h1 -6 -c 1 -p 128 -t udp "ttl=10,sp=54321,dp=12345" \
471		-a 00:11:22:33:44:55 -b 33:33:00:00:00:03 \
472		-A 2001:db8:1::2 -B ff0e::3 -q
473
474	tc_check_packets "dev $h2 ingress" 1 0
475	check_err $? "Multicast received on first host when should not"
476	tc_check_packets "dev $h3 ingress" 1 0
477	check_err $? "Multicast received on second host when should not"
478
479	# Create (*, G). Will not be installed in the kernel.
480	create_mcast_sg $rp1 :: ff0e::3 $rp2 $rp3
481
482	$MZ $h1 -6 -c 1 -p 128 -t udp "ttl=10,sp=54321,dp=12345" \
483		-a 00:11:22:33:44:55 -b 33:33:00:00:00:03 \
484		-A 2001:db8:1::2 -B ff0e::3 -q
485
486	tc_check_packets "dev $h2 ingress" 1 1
487	check_err $? "Multicast not received on first host"
488	tc_check_packets "dev $h3 ingress" 1 1
489	check_err $? "Multicast not received on second host"
490
491	delete_mcast_sg $rp1 :: ff0e::3 $rp2 $rp3
492
493	tc filter del dev $h3 ingress protocol ipv6 pref 1 handle 1 flower
494	tc filter del dev $h2 ingress protocol ipv6 pref 1 handle 1 flower
495
496	log_test "Unresolved queue IPv6"
497}
498
499trap cleanup EXIT
500
501setup_prepare
502setup_wait
503
504tests_run
505
506exit $EXIT_STATUS
507