test_ip_mcast.py revision ead1e536
1#!/usr/bin/env python3
2
3import unittest
4
5from framework import VppTestCase, VppTestRunner
6from vpp_ip import DpoProto
7from vpp_ip_route import VppIpMRoute, VppMRoutePath, VppMFibSignal, \
8    MRouteItfFlags, MRouteEntryFlags, VppIpTable, FibPathProto
9from vpp_gre_interface import VppGreInterface
10
11from scapy.packet import Raw
12from scapy.layers.l2 import Ether, GRE
13from scapy.layers.inet import IP, UDP, getmacbyip
14from scapy.layers.inet6 import IPv6, getmacbyip6
15
16#
17# The number of packets sent is set to 91 so that when we replicate more than 3
18# times, which we do for some entries, we will generate more than 256 packets
19# to the next node in the VLIB graph. Thus we are testing the code's
20# correctness handling this over-flow.
21# It's also an odd number so we hit any single loops.
22#
23N_PKTS_IN_STREAM = 91
24
25
26class TestMFIB(VppTestCase):
27    """ MFIB Test Case """
28
29    @classmethod
30    def setUpClass(cls):
31        super(TestMFIB, cls).setUpClass()
32
33    @classmethod
34    def tearDownClass(cls):
35        super(TestMFIB, cls).tearDownClass()
36
37    def setUp(self):
38        super(TestMFIB, self).setUp()
39
40    def test_mfib(self):
41        """ MFIB Unit Tests """
42        error = self.vapi.cli("test mfib")
43
44        if error:
45            self.logger.critical(error)
46        self.assertNotIn("Failed", error)
47
48
49class TestIPMcast(VppTestCase):
50    """ IP Multicast Test Case """
51
52    @classmethod
53    def setUpClass(cls):
54        super(TestIPMcast, cls).setUpClass()
55
56    @classmethod
57    def tearDownClass(cls):
58        super(TestIPMcast, cls).tearDownClass()
59
60    def setUp(self):
61        super(TestIPMcast, self).setUp()
62
63        # create 8 pg interfaces
64        self.create_pg_interfaces(range(9))
65
66        # setup interfaces
67        for i in self.pg_interfaces[:8]:
68            i.admin_up()
69            i.config_ip4()
70            i.config_ip6()
71            i.resolve_arp()
72            i.resolve_ndp()
73
74        # one more in a vrf
75        tbl4 = VppIpTable(self, 10)
76        tbl4.add_vpp_config()
77        self.pg8.set_table_ip4(10)
78        self.pg8.config_ip4()
79
80        tbl6 = VppIpTable(self, 10, is_ip6=1)
81        tbl6.add_vpp_config()
82        self.pg8.set_table_ip6(10)
83        self.pg8.config_ip6()
84
85    def tearDown(self):
86        for i in self.pg_interfaces:
87            i.unconfig_ip4()
88            i.unconfig_ip6()
89            i.admin_down()
90
91        self.pg8.set_table_ip4(0)
92        self.pg8.set_table_ip6(0)
93        super(TestIPMcast, self).tearDown()
94
95    def create_stream_ip4(self, src_if, src_ip, dst_ip, payload_size=0):
96        pkts = []
97        # default to small packet sizes
98        p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
99             IP(src=src_ip, dst=dst_ip) /
100             UDP(sport=1234, dport=1234))
101        if not payload_size:
102            payload_size = 64 - len(p)
103            p = p / Raw('\xa5' * payload_size)
104
105        for i in range(0, N_PKTS_IN_STREAM):
106            pkts.append(p)
107        return pkts
108
109    def create_stream_ip6(self, src_if, src_ip, dst_ip):
110        pkts = []
111        for i in range(0, N_PKTS_IN_STREAM):
112            info = self.create_packet_info(src_if, src_if)
113            payload = self.info_to_payload(info)
114            p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
115                 IPv6(src=src_ip, dst=dst_ip) /
116                 UDP(sport=1234, dport=1234) /
117                 Raw(payload))
118            info.data = p.copy()
119            pkts.append(p)
120        return pkts
121
122    def verify_filter(self, capture, sent):
123        if not len(capture) == len(sent):
124            # filter out any IPv6 RAs from the capture
125            for p in capture:
126                if (p.haslayer(IPv6)):
127                    capture.remove(p)
128        return capture
129
130    def verify_capture_ip4(self, rx_if, sent, dst_mac=None):
131        rxd = rx_if.get_capture(len(sent))
132
133        try:
134            capture = self.verify_filter(rxd, sent)
135
136            self.assertEqual(len(capture), len(sent))
137
138            for i in range(len(capture)):
139                tx = sent[i]
140                rx = capture[i]
141
142                eth = rx[Ether]
143                self.assertEqual(eth.type, 0x800)
144
145                tx_ip = tx[IP]
146                rx_ip = rx[IP]
147
148                if dst_mac is None:
149                    dst_mac = getmacbyip(rx_ip.dst)
150
151                # check the MAC address on the RX'd packet is correctly formed
152                self.assertEqual(eth.dst, dst_mac)
153
154                self.assertEqual(rx_ip.src, tx_ip.src)
155                self.assertEqual(rx_ip.dst, tx_ip.dst)
156                # IP processing post pop has decremented the TTL
157                self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
158
159        except:
160            raise
161
162    def verify_capture_ip6(self, rx_if, sent):
163        capture = rx_if.get_capture(len(sent))
164
165        self.assertEqual(len(capture), len(sent))
166
167        for i in range(len(capture)):
168            tx = sent[i]
169            rx = capture[i]
170
171            eth = rx[Ether]
172            self.assertEqual(eth.type, 0x86DD)
173
174            tx_ip = tx[IPv6]
175            rx_ip = rx[IPv6]
176
177            # check the MAC address on the RX'd packet is correctly formed
178            self.assertEqual(eth.dst, getmacbyip6(rx_ip.dst))
179
180            self.assertEqual(rx_ip.src, tx_ip.src)
181            self.assertEqual(rx_ip.dst, tx_ip.dst)
182            # IP processing post pop has decremented the TTL
183            self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
184
185    def test_ip_mcast(self):
186        """ IP Multicast Replication """
187
188        #
189        # a stream that matches the default route. gets dropped.
190        #
191        self.vapi.cli("clear trace")
192        tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
193        self.pg0.add_stream(tx)
194
195        self.pg_enable_capture(self.pg_interfaces)
196        self.pg_start()
197
198        self.pg0.assert_nothing_captured(
199            remark="IP multicast packets forwarded on default route")
200
201        #
202        # A (*,G).
203        # one accepting interface, pg0, 7 forwarding interfaces
204        #  many forwarding interfaces test the case where the replicate DPO
205        #  needs to use extra cache lines for the buckets.
206        #
207        route_232_1_1_1 = VppIpMRoute(
208            self,
209            "0.0.0.0",
210            "232.1.1.1", 32,
211            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
212            [VppMRoutePath(self.pg0.sw_if_index,
213                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
214             VppMRoutePath(self.pg1.sw_if_index,
215                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
216             VppMRoutePath(self.pg2.sw_if_index,
217                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
218             VppMRoutePath(self.pg3.sw_if_index,
219                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
220             VppMRoutePath(self.pg4.sw_if_index,
221                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
222             VppMRoutePath(self.pg5.sw_if_index,
223                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
224             VppMRoutePath(self.pg6.sw_if_index,
225                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
226             VppMRoutePath(self.pg7.sw_if_index,
227                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
228        route_232_1_1_1.add_vpp_config()
229
230        #
231        # An (S,G).
232        # one accepting interface, pg0, 2 forwarding interfaces
233        #
234        route_1_1_1_1_232_1_1_1 = VppIpMRoute(
235            self,
236            "1.1.1.1",
237            "232.1.1.1", 27,  # any grp-len is ok when src is set
238            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
239            [VppMRoutePath(self.pg0.sw_if_index,
240                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
241             VppMRoutePath(self.pg1.sw_if_index,
242                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
243             VppMRoutePath(self.pg2.sw_if_index,
244                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
245        route_1_1_1_1_232_1_1_1.add_vpp_config()
246
247        #
248        # An (S,G).
249        # one accepting interface, pg0, 2 forwarding interfaces
250        # that use unicast next-hops
251        #
252        route_1_1_1_1_232_1_1_2 = VppIpMRoute(
253            self,
254            "1.1.1.1",
255            "232.1.1.2", 64,
256            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
257            [VppMRoutePath(self.pg0.sw_if_index,
258                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
259             VppMRoutePath(self.pg1.sw_if_index,
260                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
261                           nh=self.pg1.remote_ip4),
262             VppMRoutePath(self.pg2.sw_if_index,
263                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
264                           nh=self.pg2.remote_ip4)])
265        route_1_1_1_1_232_1_1_2.add_vpp_config()
266
267        #
268        # An (*,G/m).
269        # one accepting interface, pg0, 1 forwarding interfaces
270        #
271        route_232 = VppIpMRoute(
272            self,
273            "0.0.0.0",
274            "232.0.0.0", 8,
275            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
276            [VppMRoutePath(self.pg0.sw_if_index,
277                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
278             VppMRoutePath(self.pg1.sw_if_index,
279                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
280        route_232.add_vpp_config()
281
282        #
283        # a stream that matches the route for (1.1.1.1,232.1.1.1)
284        #  small packets
285        #
286        self.vapi.cli("clear trace")
287        tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
288        self.pg0.add_stream(tx)
289
290        self.pg_enable_capture(self.pg_interfaces)
291        self.pg_start()
292
293        self.assertEqual(route_1_1_1_1_232_1_1_1.get_stats()['packets'],
294                         len(tx))
295
296        # We expect replications on Pg1->7
297        self.verify_capture_ip4(self.pg1, tx)
298        self.verify_capture_ip4(self.pg2, tx)
299
300        # no replications on Pg0
301        self.pg0.assert_nothing_captured(
302            remark="IP multicast packets forwarded on PG0")
303        self.pg3.assert_nothing_captured(
304            remark="IP multicast packets forwarded on PG3")
305
306        #
307        # a stream that matches the route for (1.1.1.1,232.1.1.1)
308        #  large packets
309        #
310        self.vapi.cli("clear trace")
311        tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1",
312                                    payload_size=1024)
313        self.pg0.add_stream(tx)
314
315        self.pg_enable_capture(self.pg_interfaces)
316        self.pg_start()
317
318        # We expect replications on Pg1->7
319        self.verify_capture_ip4(self.pg1, tx)
320        self.verify_capture_ip4(self.pg2, tx)
321
322        self.assertEqual(route_1_1_1_1_232_1_1_1.get_stats()['packets'],
323                         2*len(tx))
324
325        # no replications on Pg0
326        self.pg0.assert_nothing_captured(
327            remark="IP multicast packets forwarded on PG0")
328        self.pg3.assert_nothing_captured(
329            remark="IP multicast packets forwarded on PG3")
330
331        #
332        # a stream to the unicast next-hops
333        #
334        self.vapi.cli("clear trace")
335        tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.2")
336        self.pg0.add_stream(tx)
337
338        self.pg_enable_capture(self.pg_interfaces)
339        self.pg_start()
340
341        # We expect replications on Pg1->7
342        self.verify_capture_ip4(self.pg1, tx, dst_mac=self.pg1.remote_mac)
343        self.verify_capture_ip4(self.pg2, tx, dst_mac=self.pg2.remote_mac)
344
345        # no replications on Pg0 nor pg3
346        self.pg0.assert_nothing_captured(
347            remark="IP multicast packets forwarded on PG0")
348        self.pg3.assert_nothing_captured(
349            remark="IP multicast packets forwarded on PG3")
350
351        #
352        # a stream that matches the route for (*,232.0.0.0/8)
353        # Send packets with the 9th bit set so we test the correct clearing
354        # of that bit in the mac rewrite
355        #
356        self.vapi.cli("clear trace")
357        tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.255.255.255")
358        self.pg0.add_stream(tx)
359
360        self.pg_enable_capture(self.pg_interfaces)
361        self.pg_start()
362
363        # We expect replications on Pg1 only
364        self.verify_capture_ip4(self.pg1, tx)
365        self.assertEqual(route_232.get_stats()['packets'], len(tx))
366
367        # no replications on Pg0, Pg2 not Pg3
368        self.pg0.assert_nothing_captured(
369            remark="IP multicast packets forwarded on PG0")
370        self.pg2.assert_nothing_captured(
371            remark="IP multicast packets forwarded on PG2")
372        self.pg3.assert_nothing_captured(
373            remark="IP multicast packets forwarded on PG3")
374
375        #
376        # a stream that matches the route for (*,232.1.1.1)
377        #
378        self.vapi.cli("clear trace")
379        tx = self.create_stream_ip4(self.pg0, "1.1.1.2", "232.1.1.1")
380        self.pg0.add_stream(tx)
381
382        self.pg_enable_capture(self.pg_interfaces)
383        self.pg_start()
384
385        # We expect replications on Pg1->7
386        self.verify_capture_ip4(self.pg1, tx)
387        self.verify_capture_ip4(self.pg2, tx)
388        self.verify_capture_ip4(self.pg3, tx)
389        self.verify_capture_ip4(self.pg4, tx)
390        self.verify_capture_ip4(self.pg5, tx)
391        self.verify_capture_ip4(self.pg6, tx)
392        self.verify_capture_ip4(self.pg7, tx)
393
394        # no replications on Pg0
395        self.pg0.assert_nothing_captured(
396            remark="IP multicast packets forwarded on PG0")
397
398    def test_ip6_mcast(self):
399        """ IPv6 Multicast Replication """
400
401        #
402        # a stream that matches the default route. gets dropped.
403        #
404        self.vapi.cli("clear trace")
405        tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
406        self.pg0.add_stream(tx)
407
408        self.pg_enable_capture(self.pg_interfaces)
409        self.pg_start()
410
411        self.pg0.assert_nothing_captured(
412            remark="IPv6 multicast packets forwarded on default route")
413
414        #
415        # A (*,G).
416        # one accepting interface, pg0, 3 forwarding interfaces
417        #
418        route_ff01_1 = VppIpMRoute(
419            self,
420            "::",
421            "ff01::1", 128,
422            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
423            [VppMRoutePath(self.pg0.sw_if_index,
424                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT,
425                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6),
426             VppMRoutePath(self.pg1.sw_if_index,
427                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
428                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6),
429             VppMRoutePath(self.pg2.sw_if_index,
430                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
431                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6),
432             VppMRoutePath(self.pg3.sw_if_index,
433                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
434                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6)])
435        route_ff01_1.add_vpp_config()
436
437        #
438        # An (S,G).
439        # one accepting interface, pg0, 2 forwarding interfaces
440        #
441        route_2001_ff01_1 = VppIpMRoute(
442            self,
443            "2001::1",
444            "ff01::1", 0,  # any grp-len is ok when src is set
445            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
446            [VppMRoutePath(self.pg0.sw_if_index,
447                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT,
448                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6),
449             VppMRoutePath(self.pg1.sw_if_index,
450                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
451                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6),
452             VppMRoutePath(self.pg2.sw_if_index,
453                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
454                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6)])
455        route_2001_ff01_1.add_vpp_config()
456
457        #
458        # An (*,G/m).
459        # one accepting interface, pg0, 1 forwarding interface
460        #
461        route_ff01 = VppIpMRoute(
462            self,
463            "::",
464            "ff01::", 16,
465            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
466            [VppMRoutePath(self.pg0.sw_if_index,
467                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT,
468                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6),
469             VppMRoutePath(self.pg1.sw_if_index,
470                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
471                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6)])
472        route_ff01.add_vpp_config()
473
474        #
475        # a stream that matches the route for (*, ff01::/16)
476        # sent on the non-accepting interface
477        #
478        self.vapi.cli("clear trace")
479        tx = self.create_stream_ip6(self.pg1, "2002::1", "ff01:2::255")
480        self.send_and_assert_no_replies(self.pg1, tx, "RPF miss")
481
482        #
483        # a stream that matches the route for (*, ff01::/16)
484        # sent on the accepting interface
485        #
486        self.vapi.cli("clear trace")
487        tx = self.create_stream_ip6(self.pg0, "2002::1", "ff01:2::255")
488        self.pg0.add_stream(tx)
489
490        self.pg_enable_capture(self.pg_interfaces)
491        self.pg_start()
492
493        # We expect replications on Pg1
494        self.verify_capture_ip6(self.pg1, tx)
495
496        # no replications on Pg0, Pg3
497        self.pg0.assert_nothing_captured(
498            remark="IP multicast packets forwarded on PG0")
499        self.pg2.assert_nothing_captured(
500            remark="IP multicast packets forwarded on PG2")
501        self.pg3.assert_nothing_captured(
502            remark="IP multicast packets forwarded on PG3")
503
504        #
505        # Bounce the interface and it should still work
506        #
507        self.pg1.admin_down()
508        self.pg0.add_stream(tx)
509        self.pg_enable_capture(self.pg_interfaces)
510        self.pg_start()
511        self.pg1.assert_nothing_captured(
512            remark="IP multicast packets forwarded on down PG1")
513
514        self.pg1.admin_up()
515        self.pg0.add_stream(tx)
516        self.pg_enable_capture(self.pg_interfaces)
517        self.pg_start()
518        self.verify_capture_ip6(self.pg1, tx)
519
520        #
521        # a stream that matches the route for (*,ff01::1)
522        #
523        self.vapi.cli("clear trace")
524        tx = self.create_stream_ip6(self.pg0, "2002::2", "ff01::1")
525        self.pg0.add_stream(tx)
526
527        self.pg_enable_capture(self.pg_interfaces)
528        self.pg_start()
529
530        # We expect replications on Pg1, 2, 3.
531        self.verify_capture_ip6(self.pg1, tx)
532        self.verify_capture_ip6(self.pg2, tx)
533        self.verify_capture_ip6(self.pg3, tx)
534
535        # no replications on Pg0
536        self.pg0.assert_nothing_captured(
537            remark="IPv6 multicast packets forwarded on PG0")
538
539        #
540        # a stream that matches the route for (2001::1, ff00::1)
541        #
542        self.vapi.cli("clear trace")
543        tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
544        self.pg0.add_stream(tx)
545
546        self.pg_enable_capture(self.pg_interfaces)
547        self.pg_start()
548
549        # We expect replications on Pg1, 2,
550        self.verify_capture_ip6(self.pg1, tx)
551        self.verify_capture_ip6(self.pg2, tx)
552
553        # no replications on Pg0, Pg3
554        self.pg0.assert_nothing_captured(
555            remark="IP multicast packets forwarded on PG0")
556        self.pg3.assert_nothing_captured(
557            remark="IP multicast packets forwarded on PG3")
558
559    def _mcast_connected_send_stream(self, dst_ip):
560        self.vapi.cli("clear trace")
561        tx = self.create_stream_ip4(self.pg0,
562                                    self.pg0.remote_ip4,
563                                    dst_ip)
564        self.pg0.add_stream(tx)
565
566        self.pg_enable_capture(self.pg_interfaces)
567        self.pg_start()
568
569        # We expect replications on Pg1.
570        self.verify_capture_ip4(self.pg1, tx)
571
572        return tx
573
574    def test_ip_mcast_connected(self):
575        """ IP Multicast Connected Source check """
576
577        #
578        # A (*,G).
579        # one accepting interface, pg0, 1 forwarding interfaces
580        #
581        route_232_1_1_1 = VppIpMRoute(
582            self,
583            "0.0.0.0",
584            "232.1.1.1", 32,
585            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
586            [VppMRoutePath(self.pg0.sw_if_index,
587                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
588             VppMRoutePath(self.pg1.sw_if_index,
589                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
590
591        route_232_1_1_1.add_vpp_config()
592        route_232_1_1_1.update_entry_flags(
593            MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
594
595        #
596        # Now the (*,G) is present, send from connected source
597        #
598        tx = self._mcast_connected_send_stream("232.1.1.1")
599
600        #
601        # Constrct a representation of the signal we expect on pg0
602        #
603        signal_232_1_1_1_itf_0 = VppMFibSignal(self,
604                                               route_232_1_1_1,
605                                               self.pg0.sw_if_index,
606                                               tx[0])
607
608        #
609        # read the only expected signal
610        #
611        signals = self.vapi.mfib_signal_dump()
612
613        self.assertEqual(1, len(signals))
614
615        signal_232_1_1_1_itf_0.compare(signals[0])
616
617        #
618        # reading the signal allows for the generation of another
619        # so send more packets and expect the next signal
620        #
621        tx = self._mcast_connected_send_stream("232.1.1.1")
622
623        signals = self.vapi.mfib_signal_dump()
624        self.assertEqual(1, len(signals))
625        signal_232_1_1_1_itf_0.compare(signals[0])
626
627        #
628        # A Second entry with connected check
629        # one accepting interface, pg0, 1 forwarding interfaces
630        #
631        route_232_1_1_2 = VppIpMRoute(
632            self,
633            "0.0.0.0",
634            "232.1.1.2", 32,
635            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
636            [VppMRoutePath(self.pg0.sw_if_index,
637                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
638             VppMRoutePath(self.pg1.sw_if_index,
639                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
640
641        route_232_1_1_2.add_vpp_config()
642        route_232_1_1_2.update_entry_flags(
643            MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
644
645        #
646        # Send traffic to both entries. One read should net us two signals
647        #
648        signal_232_1_1_2_itf_0 = VppMFibSignal(self,
649                                               route_232_1_1_2,
650                                               self.pg0.sw_if_index,
651                                               tx[0])
652        tx = self._mcast_connected_send_stream("232.1.1.1")
653        tx2 = self._mcast_connected_send_stream("232.1.1.2")
654
655        #
656        # read the only expected signal
657        #
658        signals = self.vapi.mfib_signal_dump()
659
660        self.assertEqual(2, len(signals))
661
662        signal_232_1_1_1_itf_0.compare(signals[1])
663        signal_232_1_1_2_itf_0.compare(signals[0])
664
665        route_232_1_1_1.update_entry_flags(
666            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE)
667        route_232_1_1_2.update_entry_flags(
668            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE)
669
670    def test_ip_mcast_signal(self):
671        """ IP Multicast Signal """
672
673        #
674        # A (*,G).
675        # one accepting interface, pg0, 1 forwarding interfaces
676        #
677        route_232_1_1_1 = VppIpMRoute(
678            self,
679            "0.0.0.0",
680            "232.1.1.1", 32,
681            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
682            [VppMRoutePath(self.pg0.sw_if_index,
683                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
684             VppMRoutePath(self.pg1.sw_if_index,
685                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
686
687        route_232_1_1_1.add_vpp_config()
688
689        route_232_1_1_1.update_entry_flags(
690            MRouteEntryFlags.MFIB_ENTRY_FLAG_SIGNAL)
691
692        #
693        # Now the (*,G) is present, send from connected source
694        #
695        tx = self._mcast_connected_send_stream("232.1.1.1")
696
697        #
698        # Constrct a representation of the signal we expect on pg0
699        #
700        signal_232_1_1_1_itf_0 = VppMFibSignal(self,
701                                               route_232_1_1_1,
702                                               self.pg0.sw_if_index,
703                                               tx[0])
704
705        #
706        # read the only expected signal
707        #
708        signals = self.vapi.mfib_signal_dump()
709
710        self.assertEqual(1, len(signals))
711
712        signal_232_1_1_1_itf_0.compare(signals[0])
713
714        #
715        # reading the signal allows for the generation of another
716        # so send more packets and expect the next signal
717        #
718        tx = self._mcast_connected_send_stream("232.1.1.1")
719
720        signals = self.vapi.mfib_signal_dump()
721        self.assertEqual(1, len(signals))
722        signal_232_1_1_1_itf_0.compare(signals[0])
723
724        #
725        # Set the negate-signal on the accepting interval - the signals
726        # should stop
727        #
728        route_232_1_1_1.update_path_flags(
729            self.pg0.sw_if_index,
730            (MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT |
731             MRouteItfFlags.MFIB_ITF_FLAG_NEGATE_SIGNAL))
732
733        self.vapi.cli("clear trace")
734        tx = self._mcast_connected_send_stream("232.1.1.1")
735
736        signals = self.vapi.mfib_signal_dump()
737        self.assertEqual(0, len(signals))
738
739        #
740        # Clear the SIGNAL flag on the entry and the signals should
741        # come back since the interface is still NEGATE-SIGNAL
742        #
743        route_232_1_1_1.update_entry_flags(
744            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE)
745
746        tx = self._mcast_connected_send_stream("232.1.1.1")
747
748        signals = self.vapi.mfib_signal_dump()
749        self.assertEqual(1, len(signals))
750        signal_232_1_1_1_itf_0.compare(signals[0])
751
752        #
753        # Lastly remove the NEGATE-SIGNAL from the interface and the
754        # signals should stop
755        #
756        route_232_1_1_1.update_path_flags(self.pg0.sw_if_index,
757                                          MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT)
758
759        tx = self._mcast_connected_send_stream("232.1.1.1")
760        signals = self.vapi.mfib_signal_dump()
761        self.assertEqual(0, len(signals))
762
763    def test_ip_mcast_vrf(self):
764        """ IP Multicast Replication in non-default table"""
765
766        #
767        # An (S,G).
768        # one accepting interface, pg0, 2 forwarding interfaces
769        #
770        route_1_1_1_1_232_1_1_1 = VppIpMRoute(
771            self,
772            "1.1.1.1",
773            "232.1.1.1", 64,
774            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
775            [VppMRoutePath(self.pg8.sw_if_index,
776                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
777             VppMRoutePath(self.pg1.sw_if_index,
778                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
779             VppMRoutePath(self.pg2.sw_if_index,
780                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
781            table_id=10)
782        route_1_1_1_1_232_1_1_1.add_vpp_config()
783
784        #
785        # a stream that matches the route for (1.1.1.1,232.1.1.1)
786        #  small packets
787        #
788        self.vapi.cli("clear trace")
789        tx = self.create_stream_ip4(self.pg8, "1.1.1.1", "232.1.1.1")
790        self.pg8.add_stream(tx)
791
792        self.pg_enable_capture(self.pg_interfaces)
793        self.pg_start()
794
795        # We expect replications on Pg1 & 2
796        self.verify_capture_ip4(self.pg1, tx)
797        self.verify_capture_ip4(self.pg2, tx)
798
799    def test_ip_mcast_gre(self):
800        """ IP Multicast Replication over GRE"""
801
802        gre_if_1 = VppGreInterface(
803            self,
804            self.pg1.local_ip4,
805            self.pg1.remote_ip4).add_vpp_config()
806        gre_if_2 = VppGreInterface(
807            self,
808            self.pg2.local_ip4,
809            self.pg2.remote_ip4).add_vpp_config()
810        gre_if_3 = VppGreInterface(
811            self,
812            self.pg3.local_ip4,
813            self.pg3.remote_ip4).add_vpp_config()
814
815        gre_if_1.admin_up()
816        gre_if_1.config_ip4()
817        gre_if_2.admin_up()
818        gre_if_2.config_ip4()
819        gre_if_3.admin_up()
820        gre_if_3.config_ip4()
821
822        #
823        # An (S,G).
824        # one accepting interface, pg0, 2 forwarding interfaces
825        #
826        route_1_1_1_1_232_1_1_1 = VppIpMRoute(
827            self,
828            "1.1.1.1",
829            "232.2.2.2", 64,
830            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
831            [VppMRoutePath(gre_if_1.sw_if_index,
832                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
833             VppMRoutePath(gre_if_2.sw_if_index,
834                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
835             VppMRoutePath(gre_if_3.sw_if_index,
836                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
837        route_1_1_1_1_232_1_1_1.add_vpp_config()
838
839        #
840        # a stream that matches the route for (1.1.1.1,232.2.2.2)
841        #  small packets
842        #
843        tx = (Ether(dst=self.pg1.local_mac,
844                    src=self.pg1.remote_mac) /
845              IP(src=self.pg1.remote_ip4,
846                 dst=self.pg1.local_ip4) /
847              GRE() /
848              IP(src="1.1.1.1", dst="232.2.2.2") /
849              UDP(sport=1234, dport=1234) /
850              Raw('\a5' * 64)) * 63
851
852        self.vapi.cli("clear trace")
853        self.pg1.add_stream(tx)
854
855        self.pg_enable_capture(self.pg_interfaces)
856        self.pg_start()
857
858        # We expect replications on Pg2 & 3
859        # check the encap headers are as expected based on the egress tunnel
860        rxs = self.pg2.get_capture(len(tx))
861        for rx in rxs:
862            self.assertEqual(rx[IP].src, gre_if_2.t_src)
863            self.assertEqual(rx[IP].dst, gre_if_2.t_dst)
864            self.assert_packet_checksums_valid(rx)
865
866        rxs = self.pg3.get_capture(len(tx))
867        for rx in rxs:
868            self.assertEqual(rx[IP].src, gre_if_3.t_src)
869            self.assertEqual(rx[IP].dst, gre_if_3.t_dst)
870            self.assert_packet_checksums_valid(rx)
871
872    def test_ip6_mcast_vrf(self):
873        """ IPv6 Multicast Replication in non-default table"""
874
875        #
876        # An (S,G).
877        # one accepting interface, pg0, 2 forwarding interfaces
878        #
879        route_2001_ff01_1 = VppIpMRoute(
880            self,
881            "2001::1",
882            "ff01::1", 256,
883            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
884            [VppMRoutePath(self.pg8.sw_if_index,
885                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT,
886                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6),
887             VppMRoutePath(self.pg1.sw_if_index,
888                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
889                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6),
890             VppMRoutePath(self.pg2.sw_if_index,
891                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
892                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6)],
893            table_id=10)
894        route_2001_ff01_1.add_vpp_config()
895
896        #
897        # a stream that matches the route for (2001::1, ff00::1)
898        #
899        self.vapi.cli("clear trace")
900        tx = self.create_stream_ip6(self.pg8, "2001::1", "ff01::1")
901        self.pg8.add_stream(tx)
902
903        self.pg_enable_capture(self.pg_interfaces)
904        self.pg_start()
905
906        # We expect replications on Pg1, 2,
907        self.verify_capture_ip6(self.pg1, tx)
908        self.verify_capture_ip6(self.pg2, tx)
909
910    def test_bidir(self):
911        """ IP Multicast Bi-directional """
912
913        #
914        # A (*,G). The set of accepting interfaces matching the forwarding
915        #
916        route_232_1_1_1 = VppIpMRoute(
917            self,
918            "0.0.0.0",
919            "232.1.1.1", 32,
920            MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
921            [VppMRoutePath(self.pg0.sw_if_index,
922                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT |
923                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
924             VppMRoutePath(self.pg1.sw_if_index,
925                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT |
926                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
927             VppMRoutePath(self.pg2.sw_if_index,
928                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT |
929                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
930             VppMRoutePath(self.pg3.sw_if_index,
931                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT |
932                           MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
933        route_232_1_1_1.add_vpp_config()
934
935        tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
936        self.pg0.add_stream(tx)
937
938        self.pg_enable_capture(self.pg_interfaces)
939        self.pg_start()
940
941        # We expect replications on Pg1, 2, 3, but not on pg0
942        self.verify_capture_ip4(self.pg1, tx)
943        self.verify_capture_ip4(self.pg2, tx)
944        self.verify_capture_ip4(self.pg3, tx)
945        self.pg0.assert_nothing_captured(
946            remark="IP multicast packets forwarded on PG0")
947
948
949if __name__ == '__main__':
950    unittest.main(testRunner=VppTestRunner)
951