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