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