ip: Use .api declared error counters
[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("/err/ip4-input/rpf_failure")
226         self.assertEqual(count, len(tx))
227
228         #
229         # A (*,G).
230         # one accepting interface, pg0, 7 forwarding interfaces
231         #  many forwarding interfaces test the case where the replicate DPO
232         #  needs to use extra cache lines for the buckets.
233         #
234         route_232_1_1_1 = VppIpMRoute(
235             self,
236             "0.0.0.0",
237             "232.1.1.1",
238             32,
239             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
240             [
241                 VppMRoutePath(
242                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
243                 ),
244                 VppMRoutePath(
245                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
246                 ),
247                 VppMRoutePath(
248                     self.pg2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
249                 ),
250                 VppMRoutePath(
251                     self.pg3.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
252                 ),
253                 VppMRoutePath(
254                     self.pg4.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
255                 ),
256                 VppMRoutePath(
257                     self.pg5.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
258                 ),
259                 VppMRoutePath(
260                     self.pg6.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
261                 ),
262                 VppMRoutePath(
263                     self.pg7.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
264                 ),
265             ],
266         )
267         route_232_1_1_1.add_vpp_config()
268
269         #
270         # An (S,G).
271         # one accepting interface, pg0, 2 forwarding interfaces
272         #
273         route_1_1_1_1_232_1_1_1 = VppIpMRoute(
274             self,
275             "1.1.1.1",
276             "232.1.1.1",
277             27,  # any grp-len is ok when src is set
278             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
279             [
280                 VppMRoutePath(
281                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
282                 ),
283                 VppMRoutePath(
284                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
285                 ),
286                 VppMRoutePath(
287                     self.pg2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
288                 ),
289             ],
290         )
291         route_1_1_1_1_232_1_1_1.add_vpp_config()
292
293         #
294         # An (S,G).
295         # one accepting interface, pg0, 2 forwarding interfaces
296         # that use unicast next-hops
297         #
298         route_1_1_1_1_232_1_1_2 = VppIpMRoute(
299             self,
300             "1.1.1.1",
301             "232.1.1.2",
302             64,
303             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
304             [
305                 VppMRoutePath(
306                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
307                 ),
308                 VppMRoutePath(
309                     self.pg1.sw_if_index,
310                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
311                     nh=self.pg1.remote_ip4,
312                 ),
313                 VppMRoutePath(
314                     self.pg2.sw_if_index,
315                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
316                     nh=self.pg2.remote_ip4,
317                 ),
318             ],
319         )
320         route_1_1_1_1_232_1_1_2.add_vpp_config()
321
322         #
323         # An (*,G/m).
324         # one accepting interface, pg0, 1 forwarding interfaces
325         #
326         route_232 = VppIpMRoute(
327             self,
328             "0.0.0.0",
329             "232.0.0.0",
330             8,
331             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
332             [
333                 VppMRoutePath(
334                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
335                 ),
336                 VppMRoutePath(
337                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
338                 ),
339             ],
340         )
341         route_232.add_vpp_config()
342
343         #
344         # a stream that matches the route for (1.1.1.1,232.1.1.1)
345         #  small packets
346         #
347         self.vapi.cli("clear trace")
348         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
349         self.pg0.add_stream(tx)
350
351         self.pg_enable_capture(self.pg_interfaces)
352         self.pg_start()
353
354         self.assertEqual(route_1_1_1_1_232_1_1_1.get_stats()["packets"], len(tx))
355
356         # We expect replications on Pg1->7
357         self.verify_capture_ip4(self.pg1, tx)
358         self.verify_capture_ip4(self.pg2, tx)
359
360         # no replications on Pg0
361         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
362         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
363
364         #
365         # a stream that matches the route for (1.1.1.1,232.1.1.1)
366         #  large packets
367         #
368         self.vapi.cli("clear trace")
369         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1", payload_size=1024)
370         self.pg0.add_stream(tx)
371
372         self.pg_enable_capture(self.pg_interfaces)
373         self.pg_start()
374
375         # We expect replications on Pg1->7
376         self.verify_capture_ip4(self.pg1, tx)
377         self.verify_capture_ip4(self.pg2, tx)
378
379         self.assertEqual(route_1_1_1_1_232_1_1_1.get_stats()["packets"], 2 * len(tx))
380
381         # no replications on Pg0
382         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
383         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
384
385         #
386         # a stream to the unicast next-hops
387         #
388         self.vapi.cli("clear trace")
389         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.2")
390         self.pg0.add_stream(tx)
391
392         self.pg_enable_capture(self.pg_interfaces)
393         self.pg_start()
394
395         # We expect replications on Pg1->7
396         self.verify_capture_ip4(self.pg1, tx, dst_mac=self.pg1.remote_mac)
397         self.verify_capture_ip4(self.pg2, tx, dst_mac=self.pg2.remote_mac)
398
399         # no replications on Pg0 nor pg3
400         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
401         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
402
403         #
404         # a stream that matches the route for (*,232.0.0.0/8)
405         # Send packets with the 9th bit set so we test the correct clearing
406         # of that bit in the mac rewrite
407         #
408         self.vapi.cli("clear trace")
409         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.255.255.255")
410         self.pg0.add_stream(tx)
411
412         self.pg_enable_capture(self.pg_interfaces)
413         self.pg_start()
414
415         # We expect replications on Pg1 only
416         self.verify_capture_ip4(self.pg1, tx)
417         self.assertEqual(route_232.get_stats()["packets"], len(tx))
418
419         # no replications on Pg0, Pg2 not Pg3
420         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
421         self.pg2.assert_nothing_captured(remark="IP multicast packets forwarded on PG2")
422         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
423
424         #
425         # a stream that matches the route for (*,232.1.1.1)
426         #
427         self.vapi.cli("clear trace")
428         tx = self.create_stream_ip4(self.pg0, "1.1.1.2", "232.1.1.1")
429         self.pg0.add_stream(tx)
430
431         self.pg_enable_capture(self.pg_interfaces)
432         self.pg_start()
433
434         # We expect replications on Pg1->7
435         self.verify_capture_ip4(self.pg1, tx)
436         self.verify_capture_ip4(self.pg2, tx)
437         self.verify_capture_ip4(self.pg3, tx)
438         self.verify_capture_ip4(self.pg4, tx)
439         self.verify_capture_ip4(self.pg5, tx)
440         self.verify_capture_ip4(self.pg6, tx)
441         self.verify_capture_ip4(self.pg7, tx)
442
443         # no replications on Pg0
444         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
445
446         self.vapi.cli("packet mac-filter pg0 off")
447         self.vapi.cli("packet mac-filter pg1 off")
448         self.vapi.cli("packet mac-filter pg2 off")
449         self.vapi.cli("packet mac-filter pg4 off")
450         self.vapi.cli("packet mac-filter pg5 off")
451         self.vapi.cli("packet mac-filter pg6 off")
452         self.vapi.cli("packet mac-filter pg7 off")
453
454     def test_ip6_mcast(self):
455         """IPv6 Multicast Replication"""
456
457         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
458         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
459
460         self.vapi.cli("packet mac-filter pg0 on")
461         self.vapi.cli("packet mac-filter pg1 on")
462         self.vapi.cli("packet mac-filter pg2 on")
463         self.vapi.cli("packet mac-filter pg4 on")
464         self.vapi.cli("packet mac-filter pg5 on")
465         self.vapi.cli("packet mac-filter pg6 on")
466         self.vapi.cli("packet mac-filter pg7 on")
467         #
468         # a stream that matches the default route. gets dropped.
469         #
470         self.vapi.cli("clear trace")
471         tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
472         self.pg0.add_stream(tx)
473
474         self.pg_enable_capture(self.pg_interfaces)
475         self.pg_start()
476
477         self.pg0.assert_nothing_captured(
478             remark="IPv6 multicast packets forwarded on default route"
479         )
480
481         #
482         # A (*,G).
483         # one accepting interface, pg0, 3 forwarding interfaces
484         #
485         route_ff01_1 = VppIpMRoute(
486             self,
487             "::",
488             "ff01::1",
489             128,
490             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
491             [
492                 VppMRoutePath(
493                     self.pg0.sw_if_index,
494                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT,
495                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
496                 ),
497                 VppMRoutePath(
498                     self.pg1.sw_if_index,
499                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
500                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
501                 ),
502                 VppMRoutePath(
503                     self.pg2.sw_if_index,
504                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
505                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
506                 ),
507                 VppMRoutePath(
508                     self.pg3.sw_if_index,
509                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
510                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
511                 ),
512             ],
513         )
514         route_ff01_1.add_vpp_config()
515
516         #
517         # An (S,G).
518         # one accepting interface, pg0, 2 forwarding interfaces
519         #
520         route_2001_ff01_1 = VppIpMRoute(
521             self,
522             "2001::1",
523             "ff01::1",
524             0,  # any grp-len is ok when src is set
525             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
526             [
527                 VppMRoutePath(
528                     self.pg0.sw_if_index,
529                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT,
530                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
531                 ),
532                 VppMRoutePath(
533                     self.pg1.sw_if_index,
534                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
535                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
536                 ),
537                 VppMRoutePath(
538                     self.pg2.sw_if_index,
539                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
540                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
541                 ),
542             ],
543         )
544         route_2001_ff01_1.add_vpp_config()
545
546         #
547         # An (*,G/m).
548         # one accepting interface, pg0, 1 forwarding interface
549         #
550         route_ff01 = VppIpMRoute(
551             self,
552             "::",
553             "ff01::",
554             16,
555             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
556             [
557                 VppMRoutePath(
558                     self.pg0.sw_if_index,
559                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT,
560                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
561                 ),
562                 VppMRoutePath(
563                     self.pg1.sw_if_index,
564                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
565                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
566                 ),
567             ],
568         )
569         route_ff01.add_vpp_config()
570
571         #
572         # a stream that matches the route for (*, ff01::/16)
573         # sent on the non-accepting interface
574         #
575         self.vapi.cli("clear trace")
576         tx = self.create_stream_ip6(self.pg1, "2002::1", "ff01:2::255")
577         self.send_and_assert_no_replies(self.pg1, tx, "RPF miss")
578         count = self.statistics.get_err_counter("/err/ip6-input/rpf_failure")
579         self.assertEqual(count, 2 * len(tx))
580
581         #
582         # a stream that matches the route for (*, ff01::/16)
583         # sent on the accepting interface
584         #
585         self.vapi.cli("clear trace")
586         tx = self.create_stream_ip6(self.pg0, "2002::1", "ff01:2::255")
587         self.pg0.add_stream(tx)
588
589         self.pg_enable_capture(self.pg_interfaces)
590         self.pg_start()
591
592         # We expect replications on Pg1
593         self.verify_capture_ip6(self.pg1, tx)
594
595         # no replications on Pg0, Pg3
596         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
597         self.pg2.assert_nothing_captured(remark="IP multicast packets forwarded on PG2")
598         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
599
600         #
601         # Bounce the interface and it should still work
602         #
603         self.pg1.admin_down()
604         self.pg0.add_stream(tx)
605         self.pg_enable_capture(self.pg_interfaces)
606         self.pg_start()
607         self.pg1.assert_nothing_captured(
608             remark="IP multicast packets forwarded on down PG1"
609         )
610
611         self.pg1.admin_up()
612         self.pg0.add_stream(tx)
613         self.pg_enable_capture(self.pg_interfaces)
614         self.pg_start()
615         self.verify_capture_ip6(self.pg1, tx)
616
617         #
618         # a stream that matches the route for (*,ff01::1)
619         #
620         self.vapi.cli("clear trace")
621         tx = self.create_stream_ip6(self.pg0, "2002::2", "ff01::1")
622         self.pg0.add_stream(tx)
623
624         self.pg_enable_capture(self.pg_interfaces)
625         self.pg_start()
626
627         # We expect replications on Pg1, 2, 3.
628         self.verify_capture_ip6(self.pg1, tx)
629         self.verify_capture_ip6(self.pg2, tx)
630         self.verify_capture_ip6(self.pg3, tx)
631
632         # no replications on Pg0
633         self.pg0.assert_nothing_captured(
634             remark="IPv6 multicast packets forwarded on PG0"
635         )
636
637         #
638         # a stream that matches the route for (2001::1, ff00::1)
639         #
640         self.vapi.cli("clear trace")
641         tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
642         self.pg0.add_stream(tx)
643
644         self.pg_enable_capture(self.pg_interfaces)
645         self.pg_start()
646
647         # We expect replications on Pg1, 2,
648         self.verify_capture_ip6(self.pg1, tx)
649         self.verify_capture_ip6(self.pg2, tx)
650
651         # no replications on Pg0, Pg3
652         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
653         self.pg3.assert_nothing_captured(remark="IP multicast packets forwarded on PG3")
654
655         self.vapi.cli("packet mac-filter pg0 off")
656         self.vapi.cli("packet mac-filter pg1 off")
657         self.vapi.cli("packet mac-filter pg2 off")
658         self.vapi.cli("packet mac-filter pg4 off")
659         self.vapi.cli("packet mac-filter pg5 off")
660         self.vapi.cli("packet mac-filter pg6 off")
661         self.vapi.cli("packet mac-filter pg7 off")
662
663     def _mcast_connected_send_stream(self, dst_ip):
664         self.vapi.cli("clear trace")
665         tx = self.create_stream_ip4(self.pg0, self.pg0.remote_ip4, dst_ip)
666         self.pg0.add_stream(tx)
667
668         self.pg_enable_capture(self.pg_interfaces)
669         self.pg_start()
670
671         # We expect replications on Pg1.
672         self.verify_capture_ip4(self.pg1, tx)
673
674         return tx
675
676     def test_ip_mcast_connected(self):
677         """IP Multicast Connected Source check"""
678
679         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
680         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
681
682         #
683         # A (*,G).
684         # one accepting interface, pg0, 1 forwarding interfaces
685         #
686         route_232_1_1_1 = VppIpMRoute(
687             self,
688             "0.0.0.0",
689             "232.1.1.1",
690             32,
691             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
692             [
693                 VppMRoutePath(
694                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
695                 ),
696                 VppMRoutePath(
697                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
698                 ),
699             ],
700         )
701
702         route_232_1_1_1.add_vpp_config()
703         route_232_1_1_1.update_entry_flags(
704             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_CONNECTED
705         )
706
707         #
708         # Now the (*,G) is present, send from connected source
709         #
710         tx = self._mcast_connected_send_stream("232.1.1.1")
711
712         #
713         # Constrct a representation of the signal we expect on pg0
714         #
715         signal_232_1_1_1_itf_0 = VppMFibSignal(
716             self, route_232_1_1_1, self.pg0.sw_if_index, tx[0]
717         )
718
719         #
720         # read the only expected signal
721         #
722         signals = self.vapi.mfib_signal_dump()
723
724         self.assertEqual(1, len(signals))
725
726         signal_232_1_1_1_itf_0.compare(signals[0])
727
728         #
729         # reading the signal allows for the generation of another
730         # so send more packets and expect the next signal
731         #
732         tx = self._mcast_connected_send_stream("232.1.1.1")
733
734         signals = self.vapi.mfib_signal_dump()
735         self.assertEqual(1, len(signals))
736         signal_232_1_1_1_itf_0.compare(signals[0])
737
738         #
739         # A Second entry with connected check
740         # one accepting interface, pg0, 1 forwarding interfaces
741         #
742         route_232_1_1_2 = VppIpMRoute(
743             self,
744             "0.0.0.0",
745             "232.1.1.2",
746             32,
747             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
748             [
749                 VppMRoutePath(
750                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
751                 ),
752                 VppMRoutePath(
753                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
754                 ),
755             ],
756         )
757
758         route_232_1_1_2.add_vpp_config()
759         route_232_1_1_2.update_entry_flags(
760             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_CONNECTED
761         )
762
763         #
764         # Send traffic to both entries. One read should net us two signals
765         #
766         signal_232_1_1_2_itf_0 = VppMFibSignal(
767             self, route_232_1_1_2, self.pg0.sw_if_index, tx[0]
768         )
769         tx = self._mcast_connected_send_stream("232.1.1.1")
770         tx2 = self._mcast_connected_send_stream("232.1.1.2")
771
772         #
773         # read the only expected signal
774         #
775         signals = self.vapi.mfib_signal_dump()
776
777         self.assertEqual(2, len(signals))
778
779         signal_232_1_1_1_itf_0.compare(signals[1])
780         signal_232_1_1_2_itf_0.compare(signals[0])
781
782         route_232_1_1_1.update_entry_flags(MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE)
783         route_232_1_1_2.update_entry_flags(MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE)
784
785     def test_ip_mcast_signal(self):
786         """IP Multicast Signal"""
787
788         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
789         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
790
791         #
792         # A (*,G).
793         # one accepting interface, pg0, 1 forwarding interfaces
794         #
795         route_232_1_1_1 = VppIpMRoute(
796             self,
797             "0.0.0.0",
798             "232.1.1.1",
799             32,
800             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
801             [
802                 VppMRoutePath(
803                     self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
804                 ),
805                 VppMRoutePath(
806                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
807                 ),
808             ],
809         )
810
811         route_232_1_1_1.add_vpp_config()
812
813         route_232_1_1_1.update_entry_flags(MRouteEntryFlags.MFIB_API_ENTRY_FLAG_SIGNAL)
814
815         #
816         # Now the (*,G) is present, send from connected source
817         #
818         tx = self._mcast_connected_send_stream("232.1.1.1")
819
820         #
821         # Constrct a representation of the signal we expect on pg0
822         #
823         signal_232_1_1_1_itf_0 = VppMFibSignal(
824             self, route_232_1_1_1, self.pg0.sw_if_index, tx[0]
825         )
826
827         #
828         # read the only expected signal
829         #
830         signals = self.vapi.mfib_signal_dump()
831
832         self.assertEqual(1, len(signals))
833
834         signal_232_1_1_1_itf_0.compare(signals[0])
835
836         #
837         # reading the signal allows for the generation of another
838         # so send more packets and expect the next signal
839         #
840         tx = self._mcast_connected_send_stream("232.1.1.1")
841
842         signals = self.vapi.mfib_signal_dump()
843         self.assertEqual(1, len(signals))
844         signal_232_1_1_1_itf_0.compare(signals[0])
845
846         #
847         # Set the negate-signal on the accepting interval - the signals
848         # should stop
849         #
850         route_232_1_1_1.update_path_flags(
851             self.pg0.sw_if_index,
852             (
853                 MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
854                 | MRouteItfFlags.MFIB_API_ITF_FLAG_NEGATE_SIGNAL
855             ),
856         )
857
858         self.vapi.cli("clear trace")
859         tx = self._mcast_connected_send_stream("232.1.1.1")
860
861         signals = self.vapi.mfib_signal_dump()
862         self.assertEqual(0, len(signals))
863
864         #
865         # Clear the SIGNAL flag on the entry and the signals should
866         # come back since the interface is still NEGATE-SIGNAL
867         #
868         route_232_1_1_1.update_entry_flags(MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE)
869
870         tx = self._mcast_connected_send_stream("232.1.1.1")
871
872         signals = self.vapi.mfib_signal_dump()
873         self.assertEqual(1, len(signals))
874         signal_232_1_1_1_itf_0.compare(signals[0])
875
876         #
877         # Lastly remove the NEGATE-SIGNAL from the interface and the
878         # signals should stop
879         #
880         route_232_1_1_1.update_path_flags(
881             self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
882         )
883
884         tx = self._mcast_connected_send_stream("232.1.1.1")
885         signals = self.vapi.mfib_signal_dump()
886         self.assertEqual(0, len(signals))
887
888     def test_ip_mcast_vrf(self):
889         """IP Multicast Replication in non-default table"""
890
891         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
892         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
893
894         #
895         # An (S,G).
896         # one accepting interface, pg0, 2 forwarding interfaces
897         #
898         route_1_1_1_1_232_1_1_1 = VppIpMRoute(
899             self,
900             "1.1.1.1",
901             "232.1.1.1",
902             64,
903             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
904             [
905                 VppMRoutePath(
906                     self.pg8.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
907                 ),
908                 VppMRoutePath(
909                     self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
910                 ),
911                 VppMRoutePath(
912                     self.pg2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
913                 ),
914             ],
915             table_id=10,
916         )
917         route_1_1_1_1_232_1_1_1.add_vpp_config()
918
919         #
920         # a stream that matches the route for (1.1.1.1,232.1.1.1)
921         #  small packets
922         #
923         self.vapi.cli("clear trace")
924         tx = self.create_stream_ip4(self.pg8, "1.1.1.1", "232.1.1.1")
925         self.pg8.add_stream(tx)
926
927         self.pg_enable_capture(self.pg_interfaces)
928         self.pg_start()
929
930         # We expect replications on Pg1 & 2
931         self.verify_capture_ip4(self.pg1, tx)
932         self.verify_capture_ip4(self.pg2, tx)
933
934         #
935         # An (S,G). for for-us
936         #
937         route_0_0_0_0_224_0_0_5 = VppIpMRoute(
938             self,
939             "0.0.0.0",
940             "224.0.0.5",
941             32,
942             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
943             [
944                 VppMRoutePath(
945                     self.pg8.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
946                 ),
947                 VppMRoutePath(
948                     0xFFFFFFFF,
949                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
950                     type=FibPathType.FIB_PATH_TYPE_LOCAL,
951                 ),
952             ],
953             table_id=10,
954         )
955         route_0_0_0_0_224_0_0_5.add_vpp_config()
956
957         #
958         # a stream that matches the route for (0.0.0.0, 224.0.0.5)
959         #  small packets
960         #
961         self.vapi.cli("clear trace")
962         self.pg8.resolve_arp()
963
964         #
965         # send a ping to mcast address from peer on pg8
966         #  expect a response
967         #
968         icmp_id = 0xB
969         icmp_seq = 5
970         icmp_load = b"\x0a" * 18
971         tx = (
972             Ether(dst=getmacbyip("224.0.0.5"), src=self.pg8.remote_mac)
973             / IP(src=self.pg8.remote_ip4, dst="224.0.0.5")
974             / ICMP(id=icmp_id, seq=icmp_seq)
975             / Raw(load=icmp_load)
976         ) * 2
977
978         self.send_and_expect(self.pg8, tx, self.pg8)
979
980     def test_ip_mcast_gre(self):
981         """IP Multicast Replication over GRE"""
982
983         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
984         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
985
986         gre_if_1 = VppGreInterface(
987             self, self.pg1.local_ip4, self.pg1.remote_ip4
988         ).add_vpp_config()
989         gre_if_2 = VppGreInterface(
990             self, self.pg2.local_ip4, self.pg2.remote_ip4
991         ).add_vpp_config()
992         gre_if_3 = VppGreInterface(
993             self, self.pg3.local_ip4, self.pg3.remote_ip4
994         ).add_vpp_config()
995
996         gre_if_1.admin_up()
997         gre_if_1.config_ip4()
998         gre_if_2.admin_up()
999         gre_if_2.config_ip4()
1000         gre_if_3.admin_up()
1001         gre_if_3.config_ip4()
1002
1003         #
1004         # An (S,G).
1005         # one accepting interface, pg0, 2 forwarding interfaces
1006         #
1007         route_1_1_1_1_232_1_1_1 = VppIpMRoute(
1008             self,
1009             "1.1.1.1",
1010             "232.2.2.2",
1011             64,
1012             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
1013             [
1014                 VppMRoutePath(
1015                     gre_if_1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1016                 ),
1017                 VppMRoutePath(
1018                     gre_if_2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
1019                 ),
1020                 VppMRoutePath(
1021                     gre_if_3.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
1022                 ),
1023             ],
1024         )
1025         route_1_1_1_1_232_1_1_1.add_vpp_config()
1026
1027         #
1028         # a stream that matches the route for (1.1.1.1,232.2.2.2)
1029         #  small packets
1030         #
1031         tx = (
1032             Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac)
1033             / IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4)
1034             / GRE()
1035             / IP(src="1.1.1.1", dst="232.2.2.2")
1036             / UDP(sport=1234, dport=1234)
1037             / Raw(b"\a5" * 64)
1038         ) * 63
1039
1040         self.vapi.cli("clear trace")
1041         self.pg1.add_stream(tx)
1042
1043         self.pg_enable_capture(self.pg_interfaces)
1044         self.pg_start()
1045
1046         # We expect replications on Pg2 & 3
1047         # check the encap headers are as expected based on the egress tunnel
1048         rxs = self.pg2.get_capture(len(tx))
1049         for rx in rxs:
1050             self.assertEqual(rx[IP].src, gre_if_2.t_src)
1051             self.assertEqual(rx[IP].dst, gre_if_2.t_dst)
1052             self.assert_packet_checksums_valid(rx)
1053
1054         rxs = self.pg3.get_capture(len(tx))
1055         for rx in rxs:
1056             self.assertEqual(rx[IP].src, gre_if_3.t_src)
1057             self.assertEqual(rx[IP].dst, gre_if_3.t_dst)
1058             self.assert_packet_checksums_valid(rx)
1059
1060     def test_ip6_mcast_gre(self):
1061         """IP6 Multicast Replication over GRE"""
1062
1063         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
1064         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
1065
1066         gre_if_1 = VppGreInterface(
1067             self, self.pg1.local_ip4, self.pg1.remote_ip4
1068         ).add_vpp_config()
1069         gre_if_2 = VppGreInterface(
1070             self, self.pg2.local_ip4, self.pg2.remote_ip4
1071         ).add_vpp_config()
1072         gre_if_3 = VppGreInterface(
1073             self, self.pg3.local_ip4, self.pg3.remote_ip4
1074         ).add_vpp_config()
1075
1076         gre_if_1.admin_up()
1077         gre_if_1.config_ip6()
1078         gre_if_2.admin_up()
1079         gre_if_2.config_ip6()
1080         gre_if_3.admin_up()
1081         gre_if_3.config_ip6()
1082
1083         #
1084         # An (S,G).
1085         # one accepting interface, pg0, 2 forwarding interfaces
1086         #
1087         route_1_1_FF_1 = VppIpMRoute(
1088             self,
1089             "1::1",
1090             "FF00::1",
1091             256,
1092             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
1093             [
1094                 VppMRoutePath(
1095                     gre_if_1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1096                 ),
1097                 VppMRoutePath(
1098                     gre_if_2.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
1099                 ),
1100                 VppMRoutePath(
1101                     gre_if_3.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD
1102                 ),
1103             ],
1104         )
1105         route_1_1_FF_1.add_vpp_config()
1106
1107         #
1108         # a stream that matches the route for (1::1, FF::1)
1109         #  small packets
1110         #
1111         tx = (
1112             Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac)
1113             / IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4)
1114             / GRE()
1115             / IPv6(src="1::1", dst="FF00::1")
1116             / UDP(sport=1234, dport=1234)
1117             / Raw(b"\a5" * 64)
1118         ) * 63
1119
1120         self.vapi.cli("clear trace")
1121         self.pg1.add_stream(tx)
1122
1123         self.pg_enable_capture(self.pg_interfaces)
1124         self.pg_start()
1125
1126         # We expect replications on Pg2 & 3
1127         # check the encap headers are as expected based on the egress tunnel
1128         rxs = self.pg2.get_capture(len(tx))
1129         for rx in rxs:
1130             self.assertEqual(rx[IP].src, gre_if_2.t_src)
1131             self.assertEqual(rx[IP].dst, gre_if_2.t_dst)
1132             self.assert_packet_checksums_valid(rx)
1133
1134         rxs = self.pg3.get_capture(len(tx))
1135         for rx in rxs:
1136             self.assertEqual(rx[IP].src, gre_if_3.t_src)
1137             self.assertEqual(rx[IP].dst, gre_if_3.t_dst)
1138             self.assert_packet_checksums_valid(rx)
1139
1140     def test_ip6_mcast_vrf(self):
1141         """IPv6 Multicast Replication in non-default table"""
1142
1143         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
1144         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
1145
1146         #
1147         # An (S,G).
1148         # one accepting interface, pg0, 2 forwarding interfaces
1149         #
1150         route_2001_ff01_1 = VppIpMRoute(
1151             self,
1152             "2001::1",
1153             "ff01::1",
1154             256,
1155             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
1156             [
1157                 VppMRoutePath(
1158                     self.pg8.sw_if_index,
1159                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT,
1160                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
1161                 ),
1162                 VppMRoutePath(
1163                     self.pg1.sw_if_index,
1164                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1165                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
1166                 ),
1167                 VppMRoutePath(
1168                     self.pg2.sw_if_index,
1169                     MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1170                     proto=FibPathProto.FIB_PATH_NH_PROTO_IP6,
1171                 ),
1172             ],
1173             table_id=10,
1174         )
1175         route_2001_ff01_1.add_vpp_config()
1176
1177         #
1178         # a stream that matches the route for (2001::1, ff00::1)
1179         #
1180         self.vapi.cli("clear trace")
1181         tx = self.create_stream_ip6(self.pg8, "2001::1", "ff01::1")
1182         self.pg8.add_stream(tx)
1183
1184         self.pg_enable_capture(self.pg_interfaces)
1185         self.pg_start()
1186
1187         # We expect replications on Pg1, 2,
1188         self.verify_capture_ip6(self.pg1, tx)
1189         self.verify_capture_ip6(self.pg2, tx)
1190
1191     def test_bidir(self):
1192         """IP Multicast Bi-directional"""
1193
1194         MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t
1195         MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t
1196
1197         #
1198         # A (*,G). The set of accepting interfaces matching the forwarding
1199         #
1200         route_232_1_1_1 = VppIpMRoute(
1201             self,
1202             "0.0.0.0",
1203             "232.1.1.1",
1204             32,
1205             MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE,
1206             [
1207                 VppMRoutePath(
1208                     self.pg0.sw_if_index,
1209                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1210                     | MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1211                 ),
1212                 VppMRoutePath(
1213                     self.pg1.sw_if_index,
1214                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1215                     | MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1216                 ),
1217                 VppMRoutePath(
1218                     self.pg2.sw_if_index,
1219                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1220                     | MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1221                 ),
1222                 VppMRoutePath(
1223                     self.pg3.sw_if_index,
1224                     MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT
1225                     | MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD,
1226                 ),
1227             ],
1228         )
1229         route_232_1_1_1.add_vpp_config()
1230
1231         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
1232         self.pg0.add_stream(tx)
1233
1234         self.pg_enable_capture(self.pg_interfaces)
1235         self.pg_start()
1236
1237         # We expect replications on Pg1, 2, 3, but not on pg0
1238         self.verify_capture_ip4(self.pg1, tx)
1239         self.verify_capture_ip4(self.pg2, tx)
1240         self.verify_capture_ip4(self.pg3, tx)
1241         self.pg0.assert_nothing_captured(remark="IP multicast packets forwarded on PG0")
1242
1243
1244 if __name__ == "__main__":
1245     unittest.main(testRunner=VppTestRunner)