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