IP Multicast FIB (mfib)
[vpp.git] / test / test_ip_mcast.py
1 #!/usr/bin/env python
2
3 import unittest
4
5 from framework import VppTestCase, VppTestRunner
6 from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
7 from vpp_ip_route import IpMRoute, MRoutePath, MFibSignal
8
9 from scapy.packet import Raw
10 from scapy.layers.l2 import Ether
11 from scapy.layers.inet import IP, UDP, getmacbyip
12 from scapy.layers.inet6 import IPv6, getmacbyip6
13 from util import ppp
14
15
16 class MRouteItfFlags:
17     MFIB_ITF_FLAG_NONE = 0
18     MFIB_ITF_FLAG_NEGATE_SIGNAL = 1
19     MFIB_ITF_FLAG_ACCEPT = 2
20     MFIB_ITF_FLAG_FORWARD = 4
21     MFIB_ITF_FLAG_SIGNAL_PRESENT = 8
22     MFIB_ITF_FLAG_INTERNAL_COPY = 16
23
24
25 class MRouteEntryFlags:
26     MFIB_ENTRY_FLAG_NONE = 0
27     MFIB_ENTRY_FLAG_SIGNAL = 1
28     MFIB_ENTRY_FLAG_DROP = 2
29     MFIB_ENTRY_FLAG_CONNECTED = 4
30     MFIB_ENTRY_FLAG_INHERIT_ACCEPT = 8
31
32
33 class TestIPMcast(VppTestCase):
34     """ IP Multicast Test Case """
35
36     def setUp(self):
37         super(TestIPMcast, self).setUp()
38
39         # create 4 pg interfaces
40         self.create_pg_interfaces(range(4))
41
42         # setup interfaces
43         for i in self.pg_interfaces:
44             i.admin_up()
45             i.config_ip4()
46             i.config_ip6()
47             i.resolve_arp()
48             i.resolve_ndp()
49
50     def create_stream_ip4(self, src_if, src_ip, dst_ip):
51         pkts = []
52         for i in range(0, 65):
53             info = self.create_packet_info(src_if, src_if)
54             payload = self.info_to_payload(info)
55             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
56                  IP(src=src_ip, dst=dst_ip) /
57                  UDP(sport=1234, dport=1234) /
58                  Raw(payload))
59             info.data = p.copy()
60             pkts.append(p)
61         return pkts
62
63     def create_stream_ip6(self, src_if, src_ip, dst_ip):
64         pkts = []
65         for i in range(0, 65):
66             info = self.create_packet_info(src_if, src_if)
67             payload = self.info_to_payload(info)
68             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
69                  IPv6(src=src_ip, dst=dst_ip) /
70                  UDP(sport=1234, dport=1234) /
71                  Raw(payload))
72             info.data = p.copy()
73             pkts.append(p)
74         return pkts
75
76     def verify_filter(self, capture, sent):
77         if not len(capture) == len(sent):
78             # filter out any IPv6 RAs from the captur
79             for p in capture:
80                 if (p.haslayer(IPv6)):
81                     capture.remove(p)
82         return capture
83
84     def verify_capture_ip4(self, src_if, sent):
85         rxd = self.pg1.get_capture(65)
86
87         try:
88             capture = self.verify_filter(rxd, sent)
89
90             self.assertEqual(len(capture), len(sent))
91
92             for i in range(len(capture)):
93                 tx = sent[i]
94                 rx = capture[i]
95
96                 # the rx'd packet has the MPLS label popped
97                 eth = rx[Ether]
98                 self.assertEqual(eth.type, 0x800)
99
100                 tx_ip = tx[IP]
101                 rx_ip = rx[IP]
102
103                 # check the MAC address on the RX'd packet is correctly formed
104                 self.assertEqual(eth.dst, getmacbyip(rx_ip.dst))
105
106                 self.assertEqual(rx_ip.src, tx_ip.src)
107                 self.assertEqual(rx_ip.dst, tx_ip.dst)
108                 # IP processing post pop has decremented the TTL
109                 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
110
111         except:
112             raise
113
114     def verify_capture_ip6(self, src_if, sent):
115         capture = self.pg1.get_capture(65)
116
117         self.assertEqual(len(capture), len(sent))
118
119         for i in range(len(capture)):
120             tx = sent[i]
121             rx = capture[i]
122
123             # the rx'd packet has the MPLS label popped
124             eth = rx[Ether]
125             self.assertEqual(eth.type, 0x86DD)
126
127             tx_ip = tx[IPv6]
128             rx_ip = rx[IPv6]
129
130             # check the MAC address on the RX'd packet is correctly formed
131             self.assertEqual(eth.dst, getmacbyip6(rx_ip.dst))
132
133             self.assertEqual(rx_ip.src, tx_ip.src)
134             self.assertEqual(rx_ip.dst, tx_ip.dst)
135             # IP processing post pop has decremented the TTL
136             self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
137
138     def test_ip_mcast(self):
139         """ IP Multicast Replication """
140
141         #
142         # a stream that matches the default route. gets dropped.
143         #
144         self.vapi.cli("clear trace")
145         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
146         self.pg0.add_stream(tx)
147
148         self.pg_enable_capture(self.pg_interfaces)
149         self.pg_start()
150
151         self.pg0.assert_nothing_captured(
152             remark="IP multicast packets forwarded on default route")
153
154         #
155         # A (*,G).
156         # one accepting interface, pg0, 3 forwarding interfaces
157         #
158         route_232_1_1_1 = IpMRoute(
159             self,
160             "0.0.0.0",
161             "232.1.1.1", 32,
162             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
163             [MRoutePath(self.pg0.sw_if_index,
164                         MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
165              MRoutePath(self.pg1.sw_if_index,
166                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
167              MRoutePath(self.pg2.sw_if_index,
168                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
169              MRoutePath(self.pg3.sw_if_index,
170                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
171         route_232_1_1_1.add_vpp_config()
172
173         #
174         # An (S,G).
175         # one accepting interface, pg0, 2 forwarding interfaces
176         #
177         route_1_1_1_1_232_1_1_1 = IpMRoute(
178             self,
179             "1.1.1.1",
180             "232.1.1.1", 64,
181             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
182             [MRoutePath(self.pg0.sw_if_index,
183                         MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
184              MRoutePath(self.pg1.sw_if_index,
185                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
186              MRoutePath(self.pg2.sw_if_index,
187                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
188         route_1_1_1_1_232_1_1_1.add_vpp_config()
189
190         #
191         # An (*,G/m).
192         # one accepting interface, pg0, 1 forwarding interfaces
193         #
194         route_232 = IpMRoute(
195             self,
196             "0.0.0.0",
197             "232.0.0.0", 8,
198             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
199             [MRoutePath(self.pg0.sw_if_index,
200                         MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
201              MRoutePath(self.pg1.sw_if_index,
202                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
203         route_232.add_vpp_config()
204
205         #
206         # a stream that matches the route for (1.1.1.1,232.1.1.1)
207         #
208         self.vapi.cli("clear trace")
209         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
210         self.pg0.add_stream(tx)
211
212         self.pg_enable_capture(self.pg_interfaces)
213         self.pg_start()
214
215         # We expect replications on Pg1, 2,
216         self.verify_capture_ip4(self.pg1, tx)
217         self.verify_capture_ip4(self.pg2, tx)
218
219         # no replications on Pg0
220         self.pg0.assert_nothing_captured(
221             remark="IP multicast packets forwarded on PG0")
222         self.pg3.assert_nothing_captured(
223             remark="IP multicast packets forwarded on PG3")
224
225         #
226         # a stream that matches the route for (*,232.0.0.0/8)
227         # Send packets with the 9th bit set so we test the correct clearing
228         # of that bit in the mac rewrite
229         #
230         self.vapi.cli("clear trace")
231         tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.255.255.255")
232         self.pg0.add_stream(tx)
233
234         self.pg_enable_capture(self.pg_interfaces)
235         self.pg_start()
236
237         # We expect replications on Pg1 only
238         self.verify_capture_ip4(self.pg1, tx)
239
240         # no replications on Pg0, Pg2 not Pg3
241         self.pg0.assert_nothing_captured(
242             remark="IP multicast packets forwarded on PG0")
243         self.pg2.assert_nothing_captured(
244             remark="IP multicast packets forwarded on PG2")
245         self.pg3.assert_nothing_captured(
246             remark="IP multicast packets forwarded on PG3")
247
248         #
249         # a stream that matches the route for (*,232.1.1.1)
250         #
251         self.vapi.cli("clear trace")
252         tx = self.create_stream_ip4(self.pg0, "1.1.1.2", "232.1.1.1")
253         self.pg0.add_stream(tx)
254
255         self.pg_enable_capture(self.pg_interfaces)
256         self.pg_start()
257
258         # We expect replications on Pg1, 2, 3.
259         self.verify_capture_ip4(self.pg1, tx)
260         self.verify_capture_ip4(self.pg2, tx)
261         self.verify_capture_ip4(self.pg3, tx)
262
263         # no replications on Pg0
264         self.pg0.assert_nothing_captured(
265             remark="IP multicast packets forwarded on PG0")
266
267         route_232_1_1_1.remove_vpp_config()
268         route_1_1_1_1_232_1_1_1.remove_vpp_config()
269         route_232.remove_vpp_config()
270
271     def test_ip6_mcast(self):
272         """ IPv6 Multicast Replication """
273
274         #
275         # a stream that matches the default route. gets dropped.
276         #
277         self.vapi.cli("clear trace")
278         tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
279         self.pg0.add_stream(tx)
280
281         self.pg_enable_capture(self.pg_interfaces)
282         self.pg_start()
283
284         self.pg0.assert_nothing_captured(
285             remark="IPv6 multicast packets forwarded on default route")
286
287         #
288         # A (*,G).
289         # one accepting interface, pg0, 3 forwarding interfaces
290         #
291         route_ff01_1 = IpMRoute(
292             self,
293             "::",
294             "ff01::1", 128,
295             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
296             [MRoutePath(self.pg0.sw_if_index,
297                         MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
298              MRoutePath(self.pg1.sw_if_index,
299                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
300              MRoutePath(self.pg2.sw_if_index,
301                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
302              MRoutePath(self.pg3.sw_if_index,
303                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
304             is_ip6=1)
305         route_ff01_1.add_vpp_config()
306
307         #
308         # An (S,G).
309         # one accepting interface, pg0, 2 forwarding interfaces
310         #
311         route_2001_ff01_1 = IpMRoute(
312             self,
313             "2001::1",
314             "ff01::1", 256,
315             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
316             [MRoutePath(self.pg0.sw_if_index,
317                         MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
318              MRoutePath(self.pg1.sw_if_index,
319                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
320              MRoutePath(self.pg2.sw_if_index,
321                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
322             is_ip6=1)
323         route_2001_ff01_1.add_vpp_config()
324
325         #
326         # An (*,G/m).
327         # one accepting interface, pg0, 1 forwarding interface
328         #
329         route_ff01 = IpMRoute(
330             self,
331             "::",
332             "ff01::", 16,
333             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
334             [MRoutePath(self.pg0.sw_if_index,
335                         MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
336              MRoutePath(self.pg1.sw_if_index,
337                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
338             is_ip6=1)
339         route_ff01.add_vpp_config()
340
341         #
342         # a stream that matches the route for (*, ff01::/16)
343         #
344         self.vapi.cli("clear trace")
345         tx = self.create_stream_ip6(self.pg0, "2002::1", "ff01:2::255")
346         self.pg0.add_stream(tx)
347
348         self.pg_enable_capture(self.pg_interfaces)
349         self.pg_start()
350
351         # We expect replications on Pg1
352         self.verify_capture_ip6(self.pg1, tx)
353
354         # no replications on Pg0, Pg3
355         self.pg0.assert_nothing_captured(
356             remark="IP multicast packets forwarded on PG0")
357         self.pg2.assert_nothing_captured(
358             remark="IP multicast packets forwarded on PG2")
359         self.pg3.assert_nothing_captured(
360             remark="IP multicast packets forwarded on PG3")
361
362         #
363         # a stream that matches the route for (*,ff01::1)
364         #
365         self.vapi.cli("clear trace")
366         tx = self.create_stream_ip6(self.pg0, "2002::2", "ff01::1")
367         self.pg0.add_stream(tx)
368
369         self.pg_enable_capture(self.pg_interfaces)
370         self.pg_start()
371
372         # We expect replications on Pg1, 2, 3.
373         self.verify_capture_ip6(self.pg1, tx)
374         self.verify_capture_ip6(self.pg2, tx)
375         self.verify_capture_ip6(self.pg3, tx)
376
377         # no replications on Pg0
378         self.pg0.assert_nothing_captured(
379             remark="IPv6 multicast packets forwarded on PG0")
380
381         #
382         # a stream that matches the route for (2001::1, ff00::1)
383         #
384         self.vapi.cli("clear trace")
385         tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
386         self.pg0.add_stream(tx)
387
388         self.pg_enable_capture(self.pg_interfaces)
389         self.pg_start()
390
391         # We expect replications on Pg1, 2,
392         self.verify_capture_ip6(self.pg1, tx)
393         self.verify_capture_ip6(self.pg2, tx)
394
395         # no replications on Pg0, Pg3
396         self.pg0.assert_nothing_captured(
397             remark="IP multicast packets forwarded on PG0")
398         self.pg3.assert_nothing_captured(
399             remark="IP multicast packets forwarded on PG3")
400
401         route_ff01.remove_vpp_config()
402         route_ff01_1.remove_vpp_config()
403         route_2001_ff01_1.remove_vpp_config()
404
405     def _mcast_connected_send_stream(self, dst_ip):
406         self.vapi.cli("clear trace")
407         tx = self.create_stream_ip4(self.pg0,
408                                     self.pg0.remote_ip4,
409                                     dst_ip)
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.
416         self.verify_capture_ip4(self.pg1, tx)
417
418         return tx
419
420     def test_ip_mcast_connected(self):
421         """ IP Multicast Connected Source check """
422
423         #
424         # A (*,G).
425         # one accepting interface, pg0, 1 forwarding interfaces
426         #
427         route_232_1_1_1 = IpMRoute(
428             self,
429             "0.0.0.0",
430             "232.1.1.1", 32,
431             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
432             [MRoutePath(self.pg0.sw_if_index,
433                         MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
434              MRoutePath(self.pg1.sw_if_index,
435                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
436
437         route_232_1_1_1.add_vpp_config()
438         route_232_1_1_1.update_entry_flags(
439             MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
440
441         #
442         # Now the (*,G) is present, send from connected source
443         #
444         tx = self._mcast_connected_send_stream("232.1.1.1")
445
446         #
447         # Constrct a representation of the signal we expect on pg0
448         #
449         signal_232_1_1_1_itf_0 = MFibSignal(self,
450                                             route_232_1_1_1,
451                                             self.pg0.sw_if_index,
452                                             tx[0])
453
454         #
455         # read the only expected signal
456         #
457         signals = self.vapi.mfib_signal_dump()
458
459         self.assertEqual(1, len(signals))
460
461         signal_232_1_1_1_itf_0.compare(signals[0])
462
463         #
464         # reading the signal allows for the generation of another
465         # so send more packets and expect the next signal
466         #
467         tx = self._mcast_connected_send_stream("232.1.1.1")
468
469         signals = self.vapi.mfib_signal_dump()
470         self.assertEqual(1, len(signals))
471         signal_232_1_1_1_itf_0.compare(signals[0])
472
473         #
474         # A Second entry with connected check
475         # one accepting interface, pg0, 1 forwarding interfaces
476         #
477         route_232_1_1_2 = IpMRoute(
478             self,
479             "0.0.0.0",
480             "232.1.1.2", 32,
481             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
482             [MRoutePath(self.pg0.sw_if_index,
483                         MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
484              MRoutePath(self.pg1.sw_if_index,
485                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
486
487         route_232_1_1_2.add_vpp_config()
488         route_232_1_1_2.update_entry_flags(
489             MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
490
491         #
492         # Send traffic to both entries. One read should net us two signals
493         #
494         signal_232_1_1_2_itf_0 = MFibSignal(self,
495                                             route_232_1_1_2,
496                                             self.pg0.sw_if_index,
497                                             tx[0])
498         tx = self._mcast_connected_send_stream("232.1.1.1")
499         tx2 = self._mcast_connected_send_stream("232.1.1.2")
500
501         #
502         # read the only expected signal
503         #
504         signals = self.vapi.mfib_signal_dump()
505
506         self.assertEqual(2, len(signals))
507
508         signal_232_1_1_1_itf_0.compare(signals[1])
509         signal_232_1_1_2_itf_0.compare(signals[0])
510
511         route_232_1_1_1.remove_vpp_config()
512         route_232_1_1_2.remove_vpp_config()
513
514     def test_ip_mcast_signal(self):
515         """ IP Multicast Signal """
516
517         #
518         # A (*,G).
519         # one accepting interface, pg0, 1 forwarding interfaces
520         #
521         route_232_1_1_1 = IpMRoute(
522             self,
523             "0.0.0.0",
524             "232.1.1.1", 32,
525             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
526             [MRoutePath(self.pg0.sw_if_index,
527                         MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
528              MRoutePath(self.pg1.sw_if_index,
529                         MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
530
531         route_232_1_1_1.add_vpp_config()
532         route_232_1_1_1.update_entry_flags(
533             MRouteEntryFlags.MFIB_ENTRY_FLAG_SIGNAL)
534
535         #
536         # Now the (*,G) is present, send from connected source
537         #
538         tx = self._mcast_connected_send_stream("232.1.1.1")
539
540         #
541         # Constrct a representation of the signal we expect on pg0
542         #
543         signal_232_1_1_1_itf_0 = MFibSignal(self,
544                                             route_232_1_1_1,
545                                             self.pg0.sw_if_index,
546                                             tx[0])
547
548         #
549         # read the only expected signal
550         #
551         signals = self.vapi.mfib_signal_dump()
552
553         self.assertEqual(1, len(signals))
554
555         signal_232_1_1_1_itf_0.compare(signals[0])
556
557         #
558         # reading the signal allows for the generation of another
559         # so send more packets and expect the next signal
560         #
561         tx = self._mcast_connected_send_stream("232.1.1.1")
562
563         signals = self.vapi.mfib_signal_dump()
564         self.assertEqual(1, len(signals))
565         signal_232_1_1_1_itf_0.compare(signals[0])
566
567         #
568         # Set the negate-signal on the accepting interval - the signals
569         # should stop
570         #
571         route_232_1_1_1.update_path_flags(
572             self.pg0.sw_if_index,
573             (MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT |
574              MRouteItfFlags.MFIB_ITF_FLAG_NEGATE_SIGNAL))
575
576         tx = self._mcast_connected_send_stream("232.1.1.1")
577
578         signals = self.vapi.mfib_signal_dump()
579         self.assertEqual(0, len(signals))
580
581         #
582         # Clear the SIGNAL flag on the entry and the signals should
583         # come back since the interface is still NEGATE-SIGNAL
584         #
585         route_232_1_1_1.update_entry_flags(
586             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE)
587
588         tx = self._mcast_connected_send_stream("232.1.1.1")
589
590         signals = self.vapi.mfib_signal_dump()
591         self.assertEqual(1, len(signals))
592         signal_232_1_1_1_itf_0.compare(signals[0])
593
594         #
595         # Lastly remove the NEGATE-SIGNAL from the interface and the
596         # signals should stop
597         #
598         route_232_1_1_1.update_path_flags(self.pg0.sw_if_index,
599                                           MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT)
600
601         tx = self._mcast_connected_send_stream("232.1.1.1")
602         signals = self.vapi.mfib_signal_dump()
603         self.assertEqual(0, len(signals))
604
605         #
606         # Cleanup
607         #
608         route_232_1_1_1.remove_vpp_config()
609
610
611 if __name__ == '__main__':
612     unittest.main(testRunner=VppTestRunner)