5 from framework import VppTestCase, VppTestRunner
6 from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
7 from vpp_ip_route import IpMRoute, MRoutePath, MFibSignal
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
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
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
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
41 class TestIPMcast(VppTestCase):
42 """ IP Multicast Test Case """
45 super(TestIPMcast, self).setUp()
47 # create 4 pg interfaces
48 self.create_pg_interfaces(range(4))
51 for i in self.pg_interfaces:
58 def create_stream_ip4(self, src_if, src_ip, dst_ip):
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) /
71 def create_stream_ip6(self, src_if, src_ip, dst_ip):
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) /
84 def verify_filter(self, capture, sent):
85 if not len(capture) == len(sent):
86 # filter out any IPv6 RAs from the captur
88 if (p.haslayer(IPv6)):
92 def verify_capture_ip4(self, src_if, sent):
93 rxd = self.pg1.get_capture(N_PKTS_IN_STREAM)
96 capture = self.verify_filter(rxd, sent)
98 self.assertEqual(len(capture), len(sent))
100 for i in range(len(capture)):
104 # the rx'd packet has the MPLS label popped
106 self.assertEqual(eth.type, 0x800)
111 # check the MAC address on the RX'd packet is correctly formed
112 self.assertEqual(eth.dst, getmacbyip(rx_ip.dst))
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)
122 def verify_capture_ip6(self, src_if, sent):
123 capture = self.pg1.get_capture(N_PKTS_IN_STREAM)
125 self.assertEqual(len(capture), len(sent))
127 for i in range(len(capture)):
131 # the rx'd packet has the MPLS label popped
133 self.assertEqual(eth.type, 0x86DD)
138 # check the MAC address on the RX'd packet is correctly formed
139 self.assertEqual(eth.dst, getmacbyip6(rx_ip.dst))
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)
146 def test_ip_mcast(self):
147 """ IP Multicast Replication """
150 # a stream that matches the default route. gets dropped.
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)
156 self.pg_enable_capture(self.pg_interfaces)
159 self.pg0.assert_nothing_captured(
160 remark="IP multicast packets forwarded on default route")
164 # one accepting interface, pg0, 3 forwarding interfaces
166 route_232_1_1_1 = IpMRoute(
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()
183 # one accepting interface, pg0, 2 forwarding interfaces
185 route_1_1_1_1_232_1_1_1 = IpMRoute(
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()
200 # one accepting interface, pg0, 1 forwarding interfaces
202 route_232 = IpMRoute(
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()
214 # a stream that matches the route for (1.1.1.1,232.1.1.1)
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)
220 self.pg_enable_capture(self.pg_interfaces)
223 # We expect replications on Pg1, 2,
224 self.verify_capture_ip4(self.pg1, tx)
225 self.verify_capture_ip4(self.pg2, tx)
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")
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
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)
242 self.pg_enable_capture(self.pg_interfaces)
245 # We expect replications on Pg1 only
246 self.verify_capture_ip4(self.pg1, tx)
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")
257 # a stream that matches the route for (*,232.1.1.1)
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)
263 self.pg_enable_capture(self.pg_interfaces)
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)
271 # no replications on Pg0
272 self.pg0.assert_nothing_captured(
273 remark="IP multicast packets forwarded on PG0")
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()
279 def test_ip6_mcast(self):
280 """ IPv6 Multicast Replication """
283 # a stream that matches the default route. gets dropped.
285 self.vapi.cli("clear trace")
286 tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
287 self.pg0.add_stream(tx)
289 self.pg_enable_capture(self.pg_interfaces)
292 self.pg0.assert_nothing_captured(
293 remark="IPv6 multicast packets forwarded on default route")
297 # one accepting interface, pg0, 3 forwarding interfaces
299 route_ff01_1 = IpMRoute(
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)],
313 route_ff01_1.add_vpp_config()
317 # one accepting interface, pg0, 2 forwarding interfaces
319 route_2001_ff01_1 = IpMRoute(
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)],
331 route_2001_ff01_1.add_vpp_config()
335 # one accepting interface, pg0, 1 forwarding interface
337 route_ff01 = IpMRoute(
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)],
347 route_ff01.add_vpp_config()
350 # a stream that matches the route for (*, ff01::/16)
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)
356 self.pg_enable_capture(self.pg_interfaces)
359 # We expect replications on Pg1
360 self.verify_capture_ip6(self.pg1, tx)
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")
371 # a stream that matches the route for (*,ff01::1)
373 self.vapi.cli("clear trace")
374 tx = self.create_stream_ip6(self.pg0, "2002::2", "ff01::1")
375 self.pg0.add_stream(tx)
377 self.pg_enable_capture(self.pg_interfaces)
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)
385 # no replications on Pg0
386 self.pg0.assert_nothing_captured(
387 remark="IPv6 multicast packets forwarded on PG0")
390 # a stream that matches the route for (2001::1, ff00::1)
392 self.vapi.cli("clear trace")
393 tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
394 self.pg0.add_stream(tx)
396 self.pg_enable_capture(self.pg_interfaces)
399 # We expect replications on Pg1, 2,
400 self.verify_capture_ip6(self.pg1, tx)
401 self.verify_capture_ip6(self.pg2, tx)
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")
409 route_ff01.remove_vpp_config()
410 route_ff01_1.remove_vpp_config()
411 route_2001_ff01_1.remove_vpp_config()
413 def _mcast_connected_send_stream(self, dst_ip):
414 self.vapi.cli("clear trace")
415 tx = self.create_stream_ip4(self.pg0,
418 self.pg0.add_stream(tx)
420 self.pg_enable_capture(self.pg_interfaces)
423 # We expect replications on Pg1.
424 self.verify_capture_ip4(self.pg1, tx)
428 def test_ip_mcast_connected(self):
429 """ IP Multicast Connected Source check """
433 # one accepting interface, pg0, 1 forwarding interfaces
435 route_232_1_1_1 = IpMRoute(
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)])
445 route_232_1_1_1.add_vpp_config()
446 route_232_1_1_1.update_entry_flags(
447 MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
450 # Now the (*,G) is present, send from connected source
452 tx = self._mcast_connected_send_stream("232.1.1.1")
455 # Constrct a representation of the signal we expect on pg0
457 signal_232_1_1_1_itf_0 = MFibSignal(self,
459 self.pg0.sw_if_index,
463 # read the only expected signal
465 signals = self.vapi.mfib_signal_dump()
467 self.assertEqual(1, len(signals))
469 signal_232_1_1_1_itf_0.compare(signals[0])
472 # reading the signal allows for the generation of another
473 # so send more packets and expect the next signal
475 tx = self._mcast_connected_send_stream("232.1.1.1")
477 signals = self.vapi.mfib_signal_dump()
478 self.assertEqual(1, len(signals))
479 signal_232_1_1_1_itf_0.compare(signals[0])
482 # A Second entry with connected check
483 # one accepting interface, pg0, 1 forwarding interfaces
485 route_232_1_1_2 = IpMRoute(
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)])
495 route_232_1_1_2.add_vpp_config()
496 route_232_1_1_2.update_entry_flags(
497 MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
500 # Send traffic to both entries. One read should net us two signals
502 signal_232_1_1_2_itf_0 = MFibSignal(self,
504 self.pg0.sw_if_index,
506 tx = self._mcast_connected_send_stream("232.1.1.1")
507 tx2 = self._mcast_connected_send_stream("232.1.1.2")
510 # read the only expected signal
512 signals = self.vapi.mfib_signal_dump()
514 self.assertEqual(2, len(signals))
516 signal_232_1_1_1_itf_0.compare(signals[1])
517 signal_232_1_1_2_itf_0.compare(signals[0])
519 route_232_1_1_1.remove_vpp_config()
520 route_232_1_1_2.remove_vpp_config()
522 def test_ip_mcast_signal(self):
523 """ IP Multicast Signal """
527 # one accepting interface, pg0, 1 forwarding interfaces
529 route_232_1_1_1 = IpMRoute(
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)])
539 route_232_1_1_1.add_vpp_config()
540 route_232_1_1_1.update_entry_flags(
541 MRouteEntryFlags.MFIB_ENTRY_FLAG_SIGNAL)
544 # Now the (*,G) is present, send from connected source
546 tx = self._mcast_connected_send_stream("232.1.1.1")
549 # Constrct a representation of the signal we expect on pg0
551 signal_232_1_1_1_itf_0 = MFibSignal(self,
553 self.pg0.sw_if_index,
557 # read the only expected signal
559 signals = self.vapi.mfib_signal_dump()
561 self.assertEqual(1, len(signals))
563 signal_232_1_1_1_itf_0.compare(signals[0])
566 # reading the signal allows for the generation of another
567 # so send more packets and expect the next signal
569 tx = self._mcast_connected_send_stream("232.1.1.1")
571 signals = self.vapi.mfib_signal_dump()
572 self.assertEqual(1, len(signals))
573 signal_232_1_1_1_itf_0.compare(signals[0])
576 # Set the negate-signal on the accepting interval - the signals
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))
584 tx = self._mcast_connected_send_stream("232.1.1.1")
586 signals = self.vapi.mfib_signal_dump()
587 self.assertEqual(0, len(signals))
590 # Clear the SIGNAL flag on the entry and the signals should
591 # come back since the interface is still NEGATE-SIGNAL
593 route_232_1_1_1.update_entry_flags(
594 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE)
596 tx = self._mcast_connected_send_stream("232.1.1.1")
598 signals = self.vapi.mfib_signal_dump()
599 self.assertEqual(1, len(signals))
600 signal_232_1_1_1_itf_0.compare(signals[0])
603 # Lastly remove the NEGATE-SIGNAL from the interface and the
604 # signals should stop
606 route_232_1_1_1.update_path_flags(self.pg0.sw_if_index,
607 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT)
609 tx = self._mcast_connected_send_stream("232.1.1.1")
610 signals = self.vapi.mfib_signal_dump()
611 self.assertEqual(0, len(signals))
616 route_232_1_1_1.remove_vpp_config()
619 if __name__ == '__main__':
620 unittest.main(testRunner=VppTestRunner)