ethernet: check destination mac for L3 in ethernet-input node
[vpp.git] / test / test_ip_mcast.py
1 #!/usr/bin/env python3
2
3 import unittest
4
5 from framework import VppTestCase
6 from asfframework import VppTestRunner, tag_fixme_vpp_workers
7 from vpp_ip_route import (
8     VppIpMRoute,
9     VppMRoutePath,
10     VppMFibSignal,
11     VppIpTable,
12     FibPathProto,
13     FibPathType,
14 )
15 from vpp_gre_interface import VppGreInterface
16 from vpp_papi import VppEnum
17
18 from scapy.packet import Raw
19 from scapy.layers.l2 import Ether, GRE
20 from scapy.layers.inet import IP, UDP, getmacbyip, ICMP
21 from scapy.layers.inet6 import IPv6, getmacbyip6
22
23 #
24 # The number of packets sent is set to 91 so that when we replicate more than 3
25 # times, which we do for some entries, we will generate more than 256 packets
26 # to the next node in the VLIB graph. Thus we are testing the code's
27 # correctness handling this over-flow.
28 # It's also an odd number so we hit any single loops.
29 #
30 N_PKTS_IN_STREAM = 91
31
32
33 class TestMFIB(VppTestCase):
34     """MFIB Test Case"""
35
36     @classmethod
37     def setUpClass(cls):
38         super(TestMFIB, cls).setUpClass()
39
40     @classmethod
41     def tearDownClass(cls):
42         super(TestMFIB, cls).tearDownClass()
43
44     def setUp(self):
45         super(TestMFIB, self).setUp()
46
47     def test_mfib(self):
48         """MFIB Unit Tests"""
49         error = self.vapi.cli("test mfib")
50
51         if error:
52             self.logger.critical(error)
53         self.assertNotIn("Failed", error)
54
55
56 @tag_fixme_vpp_workers
57 class TestIPMcast(VppTestCase):
58     """IP Multicast Test Case"""
59
60     @classmethod
61     def setUpClass(cls):
62         super(TestIPMcast, cls).setUpClass()
63
64     @classmethod
65     def tearDownClass(cls):
66         super(TestIPMcast, cls).tearDownClass()
67
68     def setUp(self):
69         super(TestIPMcast, self).setUp()
70
71         # create 8 pg interfaces
72         self.create_pg_interfaces(range(9))
73
74         # setup interfaces
75         for i in self.pg_interfaces[:8]:
76             i.admin_up()
77             i.config_ip4()
78             i.config_ip6()
79             i.resolve_arp()
80             i.resolve_ndp()
81
82         # one more in a vrf
83         tbl4 = VppIpTable(self, 10)
84         tbl4.add_vpp_config()
85         self.pg8.set_table_ip4(10)
86         self.pg8.config_ip4()
87
88         tbl6 = VppIpTable(self, 10, is_ip6=1)
89         tbl6.add_vpp_config()
90         self.pg8.set_table_ip6(10)
91         self.pg8.config_ip6()
92
93     def tearDown(self):
94         for i in self.pg_interfaces:
95             i.unconfig_ip4()
96             i.unconfig_ip6()
97             i.admin_down()
98
99         self.pg8.set_table_ip4(0)
100         self.pg8.set_table_ip6(0)
101         super(TestIPMcast, self).tearDown()
102
103     def create_stream_ip4(self, src_if, src_ip, dst_ip, payload_size=0):
104         pkts = []
105         # default to small packet sizes
106         p = (
107             Ether(dst=getmacbyip(dst_ip), src=src_if.remote_mac)
108             / IP(src=src_ip, dst=dst_ip)
109             / UDP(sport=1234, dport=1234)
110         )
111         if not payload_size:
112             payload_size = 64 - len(p)
113             p = p / Raw(b"\xa5" * payload_size)
114
115         for i in range(0, N_PKTS_IN_STREAM):
116             pkts.append(p)
117         return pkts
118
119     def create_stream_ip6(self, src_if, src_ip, dst_ip):
120         pkts = []
121         for i in range(0, N_PKTS_IN_STREAM):
122             info = self.create_packet_info(src_if, src_if)
123             payload = self.info_to_payload(info)
124             p = (
125                 Ether(dst=getmacbyip6(dst_ip), src=src_if.remote_mac)
126                 / IPv6(src=src_ip, dst=dst_ip)
127                 / UDP(sport=1234, dport=1234)
128                 / Raw(payload)
129             )
130             info.data = p.copy()
131             pkts.append(p)
132         return pkts
133
134     def verify_filter(self, capture, sent):
135         if not len(capture) == len(sent):
136             # filter out any IPv6 RAs from the capture
137             for p in capture:
138                 if p.haslayer(IPv6):
139                     capture.remove(p)
140         return capture
141
142     def verify_capture_ip4(self, rx_if, sent, dst_mac=None):
143         rxd = rx_if.get_capture(len(sent))
144
145         try:
146             capture = self.verify_filter(rxd, sent)
147
148             self.assertEqual(len(capture), len(sent))
149
150             for i in range(len(capture)):
151                 tx = sent[i]
152                 rx = capture[i]
153
154                 eth = rx[Ether]
155                 self.assertEqual(eth.type, 0x800)
156
157                 tx_ip = tx[IP]
158                 rx_ip = rx[IP]
159
160                 if dst_mac is None:
161                     dst_mac = getmacbyip(rx_ip.dst)
162
163                 # check the MAC address on the RX'd packet is correctly formed
164                 self.assertEqual(eth.dst, dst_mac)
165
166                 self.assertEqual(rx_ip.src, tx_ip.src)
167                 self.assertEqual(rx_ip.dst, tx_ip.dst)
168                 # IP processing post pop has decremented the TTL
169                 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
170
171         except:
172             raise
173
174     def verify_capture_ip6(self, rx_if, sent):
175         capture = rx_if.get_capture(len(sent))
176
177         self.assertEqual(len(capture), len(sent))
178
179         for i in range(len(capture)):
180             tx = sent[i]
181             rx = capture[i]
182
183             eth = rx[Ether]
184             self.assertEqual(eth.type, 0x86DD)
185
186             tx_ip = tx[IPv6]
187             rx_ip = rx[IPv6]
188
189             # check the MAC address on the RX'd packet is correctly formed
190             self.assertEqual(eth.dst, getmacbyip6(rx_ip.dst))
191
192             self.assertEqual(rx_ip.src, tx_ip.src)
193             self.assertEqual(rx_ip.dst, tx_ip.dst)
194             # IP processing post pop has decremented the TTL
195             self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
196
197     def test_ip_mcast(self):
198         """IP Multicast Replication"""
199
200         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
201         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
202
203         #
204         # a stream that matches the default route. gets dropped.
205         #
206         self.vapi.cli("clear trace")
207         self.vapi.cli("packet mac-filter pg0 on")
208         self.vapi.cli("packet mac-filter pg1 on")
209         self.vapi.cli("packet mac-filter pg2 on")
210         self.vapi.cli("packet mac-filter pg4 on")
211         self.vapi.cli("packet mac-filter pg5 on")
212         self.vapi.cli("packet mac-filter pg6 on")
213         self.vapi.cli("packet mac-filter pg7 on")
214
215         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
216         self.pg0.add_stream(tx)
217
218         self.pg_enable_capture(self.pg_interfaces)
219         self.pg_start()
220
221         self.pg0.assert_nothing_captured(
222             remark="IP multicast packets forwarded on default route"
223         )
224         count = self.statistics.get_err_counter("/err/ip4-input/rpf_failure")
225         self.assertEqual(count, len(tx))
226
227         #
228         # A (*,G).
229         # one accepting interface, pg0, 7 forwarding interfaces
230         #  many forwarding interfaces test the case where the replicate DPO
231         #  needs to use extra cache lines for the buckets.
232         #
233         route_232_1_1_1 = VppIpMRoute(
234             self,
235             "0.0.0.0",
236             "232.1.1.1",
237             32,
238             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
239             [
240                 VppMRoutePath(
241                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
242                 ),
243                 VppMRoutePath(
244                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
245                 ),
246                 VppMRoutePath(
247                     self.pg2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
248                 ),
249                 VppMRoutePath(
250                     self.pg3.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
251                 ),
252                 VppMRoutePath(
253                     self.pg4.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
254                 ),
255                 VppMRoutePath(
256                     self.pg5.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
257                 ),
258                 VppMRoutePath(
259                     self.pg6.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
260                 ),
261                 VppMRoutePath(
262                     self.pg7.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
263                 ),
264             ],
265         )
266         route_232_1_1_1.add_vpp_config()
267
268         #
269         # An (S,G).
270         # one accepting interface, pg0, 2 forwarding interfaces
271         #
272         route_1_1_1_1_232_1_1_1 = VppIpMRoute(
273             self,
274             "1.1.1.1",
275             "232.1.1.1",
276             27,  # any grp-len is ok when src is set
277             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
278             [
279                 VppMRoutePath(
280                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
281                 ),
282                 VppMRoutePath(
283                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
284                 ),
285                 VppMRoutePath(
286                     self.pg2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
287                 ),
288             ],
289         )
290         route_1_1_1_1_232_1_1_1.add_vpp_config()
291
292         #
293         # An (S,G).
294         # one accepting interface, pg0, 2 forwarding interfaces
295         # that use unicast next-hops
296         #
297         route_1_1_1_1_232_1_1_2 = VppIpMRoute(
298             self,
299             "1.1.1.1",
300             "232.1.1.2",
301             64,
302             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
303             [
304                 VppMRoutePath(
305                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
306                 ),
307                 VppMRoutePath(
308                     self.pg1.sw_if_index,
309                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
310                     nh=self.pg1.remote_ip4,
311                 ),
312                 VppMRoutePath(
313                     self.pg2.sw_if_index,
314                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
315                     nh=self.pg2.remote_ip4,
316                 ),
317             ],
318         )
319         route_1_1_1_1_232_1_1_2.add_vpp_config()
320
321         #
322         # An (*,G/m).
323         # one accepting interface, pg0, 1 forwarding interfaces
324         #
325         route_232 = VppIpMRoute(
326             self,
327             "0.0.0.0",
328             "232.0.0.0",
329             8,
330             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
331             [
332                 VppMRoutePath(
333                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
334                 ),
335                 VppMRoutePath(
336                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
337                 ),
338             ],
339         )
340         route_232.add_vpp_config()
341
342         #
343         # a stream that matches the route for (1.1.1.1,232.1.1.1)
344         #  small packets
345         #
346         self.vapi.cli("clear trace")
347         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
348         self.pg0.add_stream(tx)
349
350         self.pg_enable_capture(self.pg_interfaces)
351         self.pg_start()
352
353         self.assertEqual(route_1_1_1_1_232_1_1_1.get_stats()["packets"], len(tx))
354
355         # We expect replications on Pg1->7
356         self.verify_capture_ip4(self.pg1, tx)
357         self.verify_capture_ip4(self.pg2, tx)
358
359         # no replications on Pg0
360         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
361         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
362
363         #
364         # a stream that matches the route for (1.1.1.1,232.1.1.1)
365         #  large packets
366         #
367         self.vapi.cli("clear trace")
368         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1", payload_size=1024)
369         self.pg0.add_stream(tx)
370
371         self.pg_enable_capture(self.pg_interfaces)
372         self.pg_start()
373
374         # We expect replications on Pg1->7
375         self.verify_capture_ip4(self.pg1, tx)
376         self.verify_capture_ip4(self.pg2, tx)
377
378         self.assertEqual(route_1_1_1_1_232_1_1_1.get_stats()["packets"], 2 * len(tx))
379
380         # no replications on Pg0
381         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
382         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
383
384         #
385         # a stream to the unicast next-hops
386         #
387         self.vapi.cli("clear trace")
388         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.2")
389         self.pg0.add_stream(tx)
390
391         self.pg_enable_capture(self.pg_interfaces)
392         self.pg_start()
393
394         # We expect replications on Pg1->7
395         self.verify_capture_ip4(self.pg1, tx, dst_mac=self.pg1.remote_mac)
396         self.verify_capture_ip4(self.pg2, tx, dst_mac=self.pg2.remote_mac)
397
398         # no replications on Pg0 nor pg3
399         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
400         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
401
402         #
403         # a stream that matches the route for (*,232.0.0.0/8)
404         # Send packets with the 9th bit set so we test the correct clearing
405         # of that bit in the mac rewrite
406         #
407         self.vapi.cli("clear trace")
408         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.255.255.255")
409         self.pg0.add_stream(tx)
410
411         self.pg_enable_capture(self.pg_interfaces)
412         self.pg_start()
413
414         # We expect replications on Pg1 only
415         self.verify_capture_ip4(self.pg1, tx)
416         self.assertEqual(route_232.get_stats()["packets"], len(tx))
417
418         # no replications on Pg0, Pg2 not Pg3
419         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
420         self.pg2.assert_nothing_captured(remark="IP multicast packets forwarded on PG2")
421         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
422
423         #
424         # a stream that matches the route for (*,232.1.1.1)
425         #
426         self.vapi.cli("clear trace")
427         tx = self.create_stream_ip4(self.pg0, "1.1.1.2", "232.1.1.1")
428         self.pg0.add_stream(tx)
429
430         self.pg_enable_capture(self.pg_interfaces)
431         self.pg_start()
432
433         # We expect replications on Pg1->7
434         self.verify_capture_ip4(self.pg1, tx)
435         self.verify_capture_ip4(self.pg2, tx)
436         self.verify_capture_ip4(self.pg3, tx)
437         self.verify_capture_ip4(self.pg4, tx)
438         self.verify_capture_ip4(self.pg5, tx)
439         self.verify_capture_ip4(self.pg6, tx)
440         self.verify_capture_ip4(self.pg7, tx)
441
442         # no replications on Pg0
443         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
444
445         self.vapi.cli("packet mac-filter pg0 off")
446         self.vapi.cli("packet mac-filter pg1 off")
447         self.vapi.cli("packet mac-filter pg2 off")
448         self.vapi.cli("packet mac-filter pg4 off")
449         self.vapi.cli("packet mac-filter pg5 off")
450         self.vapi.cli("packet mac-filter pg6 off")
451         self.vapi.cli("packet mac-filter pg7 off")
452
453     def test_ip6_mcast(self):
454         """IPv6 Multicast Replication"""
455
456         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
457         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
458
459         self.vapi.cli("packet mac-filter pg0 on")
460         self.vapi.cli("packet mac-filter pg1 on")
461         self.vapi.cli("packet mac-filter pg2 on")
462         self.vapi.cli("packet mac-filter pg4 on")
463         self.vapi.cli("packet mac-filter pg5 on")
464         self.vapi.cli("packet mac-filter pg6 on")
465         self.vapi.cli("packet mac-filter pg7 on")
466         #
467         # a stream that matches the default route. gets dropped.
468         #
469         self.vapi.cli("clear trace")
470         tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
471         self.pg0.add_stream(tx)
472
473         self.pg_enable_capture(self.pg_interfaces)
474         self.pg_start()
475
476         self.pg0.assert_nothing_captured(
477             remark="IPv6 multicast packets forwarded on default route"
478         )
479
480         #
481         # A (*,G).
482         # one accepting interface, pg0, 3 forwarding interfaces
483         #
484         route_ff01_1 = VppIpMRoute(
485             self,
486             "::",
487             "ff01::1",
488             128,
489             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
490             [
491                 VppMRoutePath(
492                     self.pg0.sw_if_index,
493                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT,
494                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
495                 ),
496                 VppMRoutePath(
497                     self.pg1.sw_if_index,
498                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
499                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
500                 ),
501                 VppMRoutePath(
502                     self.pg2.sw_if_index,
503                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
504                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
505                 ),
506                 VppMRoutePath(
507                     self.pg3.sw_if_index,
508                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
509                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
510                 ),
511             ],
512         )
513         route_ff01_1.add_vpp_config()
514
515         #
516         # An (S,G).
517         # one accepting interface, pg0, 2 forwarding interfaces
518         #
519         route_2001_ff01_1 = VppIpMRoute(
520             self,
521             "2001::1",
522             "ff01::1",
523             0,  # any grp-len is ok when src is set
524             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
525             [
526                 VppMRoutePath(
527                     self.pg0.sw_if_index,
528                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT,
529                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
530                 ),
531                 VppMRoutePath(
532                     self.pg1.sw_if_index,
533                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
534                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
535                 ),
536                 VppMRoutePath(
537                     self.pg2.sw_if_index,
538                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
539                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
540                 ),
541             ],
542         )
543         route_2001_ff01_1.add_vpp_config()
544
545         #
546         # An (*,G/m).
547         # one accepting interface, pg0, 1 forwarding interface
548         #
549         route_ff01 = VppIpMRoute(
550             self,
551             "::",
552             "ff01::",
553             16,
554             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
555             [
556                 VppMRoutePath(
557                     self.pg0.sw_if_index,
558                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT,
559                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
560                 ),
561                 VppMRoutePath(
562                     self.pg1.sw_if_index,
563                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
564                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
565                 ),
566             ],
567         )
568         route_ff01.add_vpp_config()
569
570         #
571         # a stream that matches the route for (*, ff01::/16)
572         # sent on the non-accepting interface
573         #
574         self.vapi.cli("clear trace")
575         tx = self.create_stream_ip6(self.pg1, "2002::1", "ff01:2::255")
576         self.send_and_assert_no_replies(self.pg1, tx, "RPF miss")
577         count = self.statistics.get_err_counter("/err/ip6-input/rpf_failure")
578         self.assertEqual(count, 2 * len(tx))
579
580         #
581         # a stream that matches the route for (*, ff01::/16)
582         # sent on the accepting interface
583         #
584         self.vapi.cli("clear trace")
585         tx = self.create_stream_ip6(self.pg0, "2002::1", "ff01:2::255")
586         self.pg0.add_stream(tx)
587
588         self.pg_enable_capture(self.pg_interfaces)
589         self.pg_start()
590
591         # We expect replications on Pg1
592         self.verify_capture_ip6(self.pg1, tx)
593
594         # no replications on Pg0, Pg3
595         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
596         self.pg2.assert_nothing_captured(remark="IP multicast packets forwarded on PG2")
597         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
598
599         #
600         # Bounce the interface and it should still work
601         #
602         self.pg1.admin_down()
603         self.pg0.add_stream(tx)
604         self.pg_enable_capture(self.pg_interfaces)
605         self.pg_start()
606         self.pg1.assert_nothing_captured(
607             remark="IP multicast packets forwarded on down PG1"
608         )
609
610         self.pg1.admin_up()
611         self.pg0.add_stream(tx)
612         self.pg_enable_capture(self.pg_interfaces)
613         self.pg_start()
614         self.verify_capture_ip6(self.pg1, tx)
615
616         #
617         # a stream that matches the route for (*,ff01::1)
618         #
619         self.vapi.cli("clear trace")
620         tx = self.create_stream_ip6(self.pg0, "2002::2", "ff01::1")
621         self.pg0.add_stream(tx)
622
623         self.pg_enable_capture(self.pg_interfaces)
624         self.pg_start()
625
626         # We expect replications on Pg1, 2, 3.
627         self.verify_capture_ip6(self.pg1, tx)
628         self.verify_capture_ip6(self.pg2, tx)
629         self.verify_capture_ip6(self.pg3, tx)
630
631         # no replications on Pg0
632         self.pg0.assert_nothing_captured(
633             remark="IPv6 multicast packets forwarded on PG0"
634         )
635
636         #
637         # a stream that matches the route for (2001::1, ff00::1)
638         #
639         self.vapi.cli("clear trace")
640         tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
641         self.pg0.add_stream(tx)
642
643         self.pg_enable_capture(self.pg_interfaces)
644         self.pg_start()
645
646         # We expect replications on Pg1, 2,
647         self.verify_capture_ip6(self.pg1, tx)
648         self.verify_capture_ip6(self.pg2, tx)
649
650         # no replications on Pg0, Pg3
651         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
652         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
653
654         self.vapi.cli("packet mac-filter pg0 off")
655         self.vapi.cli("packet mac-filter pg1 off")
656         self.vapi.cli("packet mac-filter pg2 off")
657         self.vapi.cli("packet mac-filter pg4 off")
658         self.vapi.cli("packet mac-filter pg5 off")
659         self.vapi.cli("packet mac-filter pg6 off")
660         self.vapi.cli("packet mac-filter pg7 off")
661
662     def _mcast_connected_send_stream(self, dst_ip):
663         self.vapi.cli("clear trace")
664         tx = self.create_stream_ip4(self.pg0, self.pg0.remote_ip4, dst_ip)
665         self.pg0.add_stream(tx)
666
667         self.pg_enable_capture(self.pg_interfaces)
668         self.pg_start()
669
670         # We expect replications on Pg1.
671         self.verify_capture_ip4(self.pg1, tx)
672
673         return tx
674
675     def test_ip_mcast_connected(self):
676         """IP Multicast Connected Source check"""
677
678         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
679         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
680
681         #
682         # A (*,G).
683         # one accepting interface, pg0, 1 forwarding interfaces
684         #
685         route_232_1_1_1 = VppIpMRoute(
686             self,
687             "0.0.0.0",
688             "232.1.1.1",
689             32,
690             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
691             [
692                 VppMRoutePath(
693                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
694                 ),
695                 VppMRoutePath(
696                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
697                 ),
698             ],
699         )
700
701         route_232_1_1_1.add_vpp_config()
702         route_232_1_1_1.update_entry_flags(
703             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_CONNECTED
704         )
705
706         #
707         # Now the (*,G) is present, send from connected source
708         #
709         tx = self._mcast_connected_send_stream("232.1.1.1")
710
711         #
712         # Constrct a representation of the signal we expect on pg0
713         #
714         signal_232_1_1_1_itf_0 = VppMFibSignal(
715             self, route_232_1_1_1, self.pg0.sw_if_index, tx[0]
716         )
717
718         #
719         # read the only expected signal
720         #
721         signals = self.vapi.mfib_signal_dump()
722
723         self.assertEqual(1, len(signals))
724
725         signal_232_1_1_1_itf_0.compare(signals[0])
726
727         #
728         # reading the signal allows for the generation of another
729         # so send more packets and expect the next signal
730         #
731         tx = self._mcast_connected_send_stream("232.1.1.1")
732
733         signals = self.vapi.mfib_signal_dump()
734         self.assertEqual(1, len(signals))
735         signal_232_1_1_1_itf_0.compare(signals[0])
736
737         #
738         # A Second entry with connected check
739         # one accepting interface, pg0, 1 forwarding interfaces
740         #
741         route_232_1_1_2 = VppIpMRoute(
742             self,
743             "0.0.0.0",
744             "232.1.1.2",
745             32,
746             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
747             [
748                 VppMRoutePath(
749                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
750                 ),
751                 VppMRoutePath(
752                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
753                 ),
754             ],
755         )
756
757         route_232_1_1_2.add_vpp_config()
758         route_232_1_1_2.update_entry_flags(
759             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_CONNECTED
760         )
761
762         #
763         # Send traffic to both entries. One read should net us two signals
764         #
765         signal_232_1_1_2_itf_0 = VppMFibSignal(
766             self, route_232_1_1_2, self.pg0.sw_if_index, tx[0]
767         )
768         tx = self._mcast_connected_send_stream("232.1.1.1")
769         tx2 = self._mcast_connected_send_stream("232.1.1.2")
770
771         #
772         # read the only expected signal
773         #
774         signals = self.vapi.mfib_signal_dump()
775
776         self.assertEqual(2, len(signals))
777
778         signal_232_1_1_1_itf_0.compare(signals[1])
779         signal_232_1_1_2_itf_0.compare(signals[0])
780
781         route_232_1_1_1.update_entry_flags(MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE)
782         route_232_1_1_2.update_entry_flags(MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE)
783
784     def test_ip_mcast_signal(self):
785         """IP Multicast Signal"""
786
787         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
788         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
789
790         #
791         # A (*,G).
792         # one accepting interface, pg0, 1 forwarding interfaces
793         #
794         route_232_1_1_1 = VppIpMRoute(
795             self,
796             "0.0.0.0",
797             "232.1.1.1",
798             32,
799             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
800             [
801                 VppMRoutePath(
802                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
803                 ),
804                 VppMRoutePath(
805                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
806                 ),
807             ],
808         )
809
810         route_232_1_1_1.add_vpp_config()
811
812         route_232_1_1_1.update_entry_flags(MRouteEntryFlags.MFIB_API_ENTRY_FLAG_SIGNAL)
813
814         #
815         # Now the (*,G) is present, send from connected source
816         #
817         tx = self._mcast_connected_send_stream("232.1.1.1")
818
819         #
820         # Constrct a representation of the signal we expect on pg0
821         #
822         signal_232_1_1_1_itf_0 = VppMFibSignal(
823             self, route_232_1_1_1, self.pg0.sw_if_index, tx[0]
824         )
825
826         #
827         # read the only expected signal
828         #
829         signals = self.vapi.mfib_signal_dump()
830
831         self.assertEqual(1, len(signals))
832
833         signal_232_1_1_1_itf_0.compare(signals[0])
834
835         #
836         # reading the signal allows for the generation of another
837         # so send more packets and expect the next signal
838         #
839         tx = self._mcast_connected_send_stream("232.1.1.1")
840
841         signals = self.vapi.mfib_signal_dump()
842         self.assertEqual(1, len(signals))
843         signal_232_1_1_1_itf_0.compare(signals[0])
844
845         #
846         # Set the negate-signal on the accepting interval - the signals
847         # should stop
848         #
849         route_232_1_1_1.update_path_flags(
850             self.pg0.sw_if_index,
851             (
852                 MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
853                 | MRouteItfFlags.MFIB_API_ITF_FLAG_NEGATE_SIGNAL
854             ),
855         )
856
857         self.vapi.cli("clear trace")
858         tx = self._mcast_connected_send_stream("232.1.1.1")
859
860         signals = self.vapi.mfib_signal_dump()
861         self.assertEqual(0, len(signals))
862
863         #
864         # Clear the SIGNAL flag on the entry and the signals should
865         # come back since the interface is still NEGATE-SIGNAL
866         #
867         route_232_1_1_1.update_entry_flags(MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE)
868
869         tx = self._mcast_connected_send_stream("232.1.1.1")
870
871         signals = self.vapi.mfib_signal_dump()
872         self.assertEqual(1, len(signals))
873         signal_232_1_1_1_itf_0.compare(signals[0])
874
875         #
876         # Lastly remove the NEGATE-SIGNAL from the interface and the
877         # signals should stop
878         #
879         route_232_1_1_1.update_path_flags(
880             self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
881         )
882
883         tx = self._mcast_connected_send_stream("232.1.1.1")
884         signals = self.vapi.mfib_signal_dump()
885         self.assertEqual(0, len(signals))
886
887     def test_ip_mcast_vrf(self):
888         """IP Multicast Replication in non-default table"""
889
890         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
891         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
892
893         #
894         # An (S,G).
895         # one accepting interface, pg0, 2 forwarding interfaces
896         #
897         route_1_1_1_1_232_1_1_1 = VppIpMRoute(
898             self,
899             "1.1.1.1",
900             "232.1.1.1",
901             64,
902             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
903             [
904                 VppMRoutePath(
905                     self.pg8.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
906                 ),
907                 VppMRoutePath(
908                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
909                 ),
910                 VppMRoutePath(
911                     self.pg2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
912                 ),
913             ],
914             table_id=10,
915         )
916         route_1_1_1_1_232_1_1_1.add_vpp_config()
917
918         #
919         # a stream that matches the route for (1.1.1.1,232.1.1.1)
920         #  small packets
921         #
922         self.vapi.cli("clear trace")
923         tx = self.create_stream_ip4(self.pg8, "1.1.1.1", "232.1.1.1")
924         self.pg8.add_stream(tx)
925
926         self.pg_enable_capture(self.pg_interfaces)
927         self.pg_start()
928
929         # We expect replications on Pg1 & 2
930         self.verify_capture_ip4(self.pg1, tx)
931         self.verify_capture_ip4(self.pg2, tx)
932
933         #
934         # An (S,G). for for-us
935         #
936         route_0_0_0_0_224_0_0_5 = VppIpMRoute(
937             self,
938             "0.0.0.0",
939             "224.0.0.5",
940             32,
941             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
942             [
943                 VppMRoutePath(
944                     self.pg8.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
945                 ),
946                 VppMRoutePath(
947                     0xFFFFFFFF,
948                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
949                     type=FibPathType.FIB_PATH_TYPE_LOCAL,
950                 ),
951             ],
952             table_id=10,
953         )
954         route_0_0_0_0_224_0_0_5.add_vpp_config()
955
956         #
957         # a stream that matches the route for (0.0.0.0, 224.0.0.5)
958         #  small packets
959         #
960         self.vapi.cli("clear trace")
961         self.pg8.resolve_arp()
962
963         #
964         # send a ping to mcast address from peer on pg8
965         #  expect a response
966         #
967         icmp_id = 0xB
968         icmp_seq = 5
969         icmp_load = b"\x0a" * 18
970         tx = (
971             Ether(dst=getmacbyip("224.0.0.5"), src=self.pg8.remote_mac)
972             / IP(src=self.pg8.remote_ip4, dst="224.0.0.5")
973             / ICMP(id=icmp_id, seq=icmp_seq)
974             / Raw(load=icmp_load)
975         ) * 2
976
977         self.send_and_expect(self.pg8, tx, self.pg8)
978
979     def test_ip_mcast_gre(self):
980         """IP Multicast Replication over GRE"""
981
982         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
983         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
984
985         gre_if_1 = VppGreInterface(
986             self, self.pg1.local_ip4, self.pg1.remote_ip4
987         ).add_vpp_config()
988         gre_if_2 = VppGreInterface(
989             self, self.pg2.local_ip4, self.pg2.remote_ip4
990         ).add_vpp_config()
991         gre_if_3 = VppGreInterface(
992             self, self.pg3.local_ip4, self.pg3.remote_ip4
993         ).add_vpp_config()
994
995         gre_if_1.admin_up()
996         gre_if_1.config_ip4()
997         gre_if_2.admin_up()
998         gre_if_2.config_ip4()
999         gre_if_3.admin_up()
1000         gre_if_3.config_ip4()
1001
1002         #
1003         # An (S,G).
1004         # one accepting interface, pg0, 2 forwarding interfaces
1005         #
1006         route_1_1_1_1_232_1_1_1 = VppIpMRoute(
1007             self,
1008             "1.1.1.1",
1009             "232.2.2.2",
1010             64,
1011             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
1012             [
1013                 VppMRoutePath(
1014                     gre_if_1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1015                 ),
1016                 VppMRoutePath(
1017                     gre_if_2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
1018                 ),
1019                 VppMRoutePath(
1020                     gre_if_3.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
1021                 ),
1022             ],
1023         )
1024         route_1_1_1_1_232_1_1_1.add_vpp_config()
1025
1026         #
1027         # a stream that matches the route for (1.1.1.1,232.2.2.2)
1028         #  small packets
1029         #
1030         tx = (
1031             Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac)
1032             / IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4)
1033             / GRE()
1034             / IP(src="1.1.1.1", dst="232.2.2.2")
1035             / UDP(sport=1234, dport=1234)
1036             / Raw(b"\a5" * 64)
1037         ) * 63
1038
1039         self.vapi.cli("clear trace")
1040         self.pg1.add_stream(tx)
1041
1042         self.pg_enable_capture(self.pg_interfaces)
1043         self.pg_start()
1044
1045         # We expect replications on Pg2 & 3
1046         # check the encap headers are as expected based on the egress tunnel
1047         rxs = self.pg2.get_capture(len(tx))
1048         for rx in rxs:
1049             self.assertEqual(rx[IP].src, gre_if_2.t_src)
1050             self.assertEqual(rx[IP].dst, gre_if_2.t_dst)
1051             self.assert_packet_checksums_valid(rx)
1052
1053         rxs = self.pg3.get_capture(len(tx))
1054         for rx in rxs:
1055             self.assertEqual(rx[IP].src, gre_if_3.t_src)
1056             self.assertEqual(rx[IP].dst, gre_if_3.t_dst)
1057             self.assert_packet_checksums_valid(rx)
1058
1059     def test_ip6_mcast_gre(self):
1060         """IP6 Multicast Replication over GRE"""
1061
1062         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
1063         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
1064
1065         gre_if_1 = VppGreInterface(
1066             self, self.pg1.local_ip4, self.pg1.remote_ip4
1067         ).add_vpp_config()
1068         gre_if_2 = VppGreInterface(
1069             self, self.pg2.local_ip4, self.pg2.remote_ip4
1070         ).add_vpp_config()
1071         gre_if_3 = VppGreInterface(
1072             self, self.pg3.local_ip4, self.pg3.remote_ip4
1073         ).add_vpp_config()
1074
1075         gre_if_1.admin_up()
1076         gre_if_1.config_ip6()
1077         gre_if_2.admin_up()
1078         gre_if_2.config_ip6()
1079         gre_if_3.admin_up()
1080         gre_if_3.config_ip6()
1081
1082         #
1083         # An (S,G).
1084         # one accepting interface, pg0, 2 forwarding interfaces
1085         #
1086         route_1_1_FF_1 = VppIpMRoute(
1087             self,
1088             "1::1",
1089             "FF00::1",
1090             256,
1091             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
1092             [
1093                 VppMRoutePath(
1094                     gre_if_1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1095                 ),
1096                 VppMRoutePath(
1097                     gre_if_2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
1098                 ),
1099                 VppMRoutePath(
1100                     gre_if_3.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
1101                 ),
1102             ],
1103         )
1104         route_1_1_FF_1.add_vpp_config()
1105
1106         #
1107         # a stream that matches the route for (1::1, FF::1)
1108         #  small packets
1109         #
1110         tx = (
1111             Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac)
1112             / IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4)
1113             / GRE()
1114             / IPv6(src="1::1", dst="FF00::1")
1115             / UDP(sport=1234, dport=1234)
1116             / Raw(b"\a5" * 64)
1117         ) * 63
1118
1119         self.vapi.cli("clear trace")
1120         self.pg1.add_stream(tx)
1121
1122         self.pg_enable_capture(self.pg_interfaces)
1123         self.pg_start()
1124
1125         # We expect replications on Pg2 & 3
1126         # check the encap headers are as expected based on the egress tunnel
1127         rxs = self.pg2.get_capture(len(tx))
1128         for rx in rxs:
1129             self.assertEqual(rx[IP].src, gre_if_2.t_src)
1130             self.assertEqual(rx[IP].dst, gre_if_2.t_dst)
1131             self.assert_packet_checksums_valid(rx)
1132
1133         rxs = self.pg3.get_capture(len(tx))
1134         for rx in rxs:
1135             self.assertEqual(rx[IP].src, gre_if_3.t_src)
1136             self.assertEqual(rx[IP].dst, gre_if_3.t_dst)
1137             self.assert_packet_checksums_valid(rx)
1138
1139     def test_ip6_mcast_vrf(self):
1140         """IPv6 Multicast Replication in non-default table"""
1141
1142         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
1143         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
1144
1145         #
1146         # An (S,G).
1147         # one accepting interface, pg0, 2 forwarding interfaces
1148         #
1149         route_2001_ff01_1 = VppIpMRoute(
1150             self,
1151             "2001::1",
1152             "ff01::1",
1153             256,
1154             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
1155             [
1156                 VppMRoutePath(
1157                     self.pg8.sw_if_index,
1158                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT,
1159                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
1160                 ),
1161                 VppMRoutePath(
1162                     self.pg1.sw_if_index,
1163                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1164                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
1165                 ),
1166                 VppMRoutePath(
1167                     self.pg2.sw_if_index,
1168                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1169                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
1170                 ),
1171             ],
1172             table_id=10,
1173         )
1174         route_2001_ff01_1.add_vpp_config()
1175
1176         #
1177         # a stream that matches the route for (2001::1, ff00::1)
1178         #
1179         self.vapi.cli("clear trace")
1180         tx = self.create_stream_ip6(self.pg8, "2001::1", "ff01::1")
1181         self.pg8.add_stream(tx)
1182
1183         self.pg_enable_capture(self.pg_interfaces)
1184         self.pg_start()
1185
1186         # We expect replications on Pg1, 2,
1187         self.verify_capture_ip6(self.pg1, tx)
1188         self.verify_capture_ip6(self.pg2, tx)
1189
1190     def test_bidir(self):
1191         """IP Multicast Bi-directional"""
1192
1193         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
1194         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
1195
1196         #
1197         # A (*,G). The set of accepting interfaces matching the forwarding
1198         #
1199         route_232_1_1_1 = VppIpMRoute(
1200             self,
1201             "0.0.0.0",
1202             "232.1.1.1",
1203             32,
1204             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
1205             [
1206                 VppMRoutePath(
1207                     self.pg0.sw_if_index,
1208                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1209                     | MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1210                 ),
1211                 VppMRoutePath(
1212                     self.pg1.sw_if_index,
1213                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1214                     | MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1215                 ),
1216                 VppMRoutePath(
1217                     self.pg2.sw_if_index,
1218                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1219                     | MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1220                 ),
1221                 VppMRoutePath(
1222                     self.pg3.sw_if_index,
1223                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1224                     | MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1225                 ),
1226             ],
1227         )
1228         route_232_1_1_1.add_vpp_config()
1229
1230         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
1231         self.pg0.add_stream(tx)
1232
1233         self.pg_enable_capture(self.pg_interfaces)
1234         self.pg_start()
1235
1236         # We expect replications on Pg1, 2, 3, but not on pg0
1237         self.verify_capture_ip4(self.pg1, tx)
1238         self.verify_capture_ip4(self.pg2, tx)
1239         self.verify_capture_ip4(self.pg3, tx)
1240         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
1241
1242
1243 if __name__ == "__main__":
1244     unittest.main(testRunner=VppTestRunner)