6 from framework import VppTestCase, VppTestRunner
7 from vpp_ip_route import VppIpRoute, VppRoutePath, VppMplsRoute, \
10 from scapy.packet import Raw
11 from scapy.layers.l2 import Ether
12 from scapy.layers.inet import IP, UDP, ICMP
13 from scapy.layers.inet6 import IPv6
14 from scapy.contrib.mpls import MPLS
17 class TestMPLS(VppTestCase):
18 """ MPLS Test Case """
22 super(TestMPLS, cls).setUpClass()
25 super(TestMPLS, self).setUp()
27 # create 2 pg interfaces
28 self.create_pg_interfaces(range(2))
30 # setup both interfaces
31 # assign them different tables.
34 for i in self.pg_interfaces:
36 i.set_table_ip4(table_id)
37 i.set_table_ip6(table_id)
46 super(TestMPLS, self).tearDown()
48 # the default of 64 matches the IP packet TTL default
49 def create_stream_labelled_ip4(
56 self.reset_packet_infos()
58 for i in range(0, 257):
59 info = self.create_packet_info(src_if, src_if)
60 payload = self.info_to_payload(info)
61 p = Ether(dst=src_if.local_mac, src=src_if.remote_mac)
63 for ii in range(len(mpls_labels)):
64 if ii == len(mpls_labels) - 1:
65 p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=1)
67 p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=0)
69 p = (p / IP(src=src_if.local_ip4, dst=src_if.remote_ip4) /
70 UDP(sport=1234, dport=1234) /
73 p = (p / IP(src=ip_itf.remote_ip4,
74 dst=ip_itf.local_ip4) /
81 def create_stream_ip4(self, src_if, dst_ip):
82 self.reset_packet_infos()
84 for i in range(0, 257):
85 info = self.create_packet_info(src_if, src_if)
86 payload = self.info_to_payload(info)
87 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
88 IP(src=src_if.remote_ip4, dst=dst_ip) /
89 UDP(sport=1234, dport=1234) /
95 def create_stream_labelled_ip6(self, src_if, mpls_label, mpls_ttl):
96 self.reset_packet_infos()
98 for i in range(0, 257):
99 info = self.create_packet_info(src_if, src_if)
100 payload = self.info_to_payload(info)
101 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
102 MPLS(label=mpls_label, ttl=mpls_ttl) /
103 IPv6(src=src_if.remote_ip6, dst=src_if.remote_ip6) /
104 UDP(sport=1234, dport=1234) /
111 def verify_filter(capture, sent):
112 if not len(capture) == len(sent):
113 # filter out any IPv6 RAs from the capture
119 def verify_capture_ip4(self, src_if, capture, sent, ping_resp=0):
121 capture = self.verify_filter(capture, sent)
123 self.assertEqual(len(capture), len(sent))
125 for i in range(len(capture)):
129 # the rx'd packet has the MPLS label popped
131 self.assertEqual(eth.type, 0x800)
137 self.assertEqual(rx_ip.src, tx_ip.src)
138 self.assertEqual(rx_ip.dst, tx_ip.dst)
139 # IP processing post pop has decremented the TTL
140 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
142 self.assertEqual(rx_ip.src, tx_ip.dst)
143 self.assertEqual(rx_ip.dst, tx_ip.src)
148 def verify_mpls_stack(self, rx, mpls_labels, ttl=255, num=0):
149 # the rx'd packet has the MPLS label popped
151 self.assertEqual(eth.type, 0x8847)
155 for ii in range(len(mpls_labels)):
156 self.assertEqual(rx_mpls.label, mpls_labels[ii])
157 self.assertEqual(rx_mpls.cos, 0)
159 self.assertEqual(rx_mpls.ttl, ttl)
161 self.assertEqual(rx_mpls.ttl, 255)
163 if ii == len(mpls_labels) - 1:
164 self.assertEqual(rx_mpls.s, 1)
167 self.assertEqual(rx_mpls.s, 0)
168 # pop the label to expose the next
169 rx_mpls = rx_mpls[MPLS].payload
171 def verify_capture_labelled_ip4(self, src_if, capture, sent,
174 capture = self.verify_filter(capture, sent)
176 self.assertEqual(len(capture), len(sent))
178 for i in range(len(capture)):
184 # the MPLS TTL is copied from the IP
185 self.verify_mpls_stack(
186 rx, mpls_labels, rx_ip.ttl, len(mpls_labels) - 1)
188 self.assertEqual(rx_ip.src, tx_ip.src)
189 self.assertEqual(rx_ip.dst, tx_ip.dst)
190 # IP processing post pop has decremented the TTL
191 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
196 def verify_capture_tunneled_ip4(self, src_if, capture, sent, mpls_labels):
198 capture = self.verify_filter(capture, sent)
200 self.assertEqual(len(capture), len(sent))
202 for i in range(len(capture)):
208 # the MPLS TTL is 255 since it enters a new tunnel
209 self.verify_mpls_stack(
210 rx, mpls_labels, 255, len(mpls_labels) - 1)
212 self.assertEqual(rx_ip.src, tx_ip.src)
213 self.assertEqual(rx_ip.dst, tx_ip.dst)
214 # IP processing post pop has decremented the TTL
215 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
220 def verify_capture_labelled(self, src_if, capture, sent,
221 mpls_labels, ttl=254, num=0):
223 capture = self.verify_filter(capture, sent)
225 self.assertEqual(len(capture), len(sent))
227 for i in range(len(capture)):
229 self.verify_mpls_stack(rx, mpls_labels, ttl, num)
233 def verify_capture_ip6(self, src_if, capture, sent):
235 self.assertEqual(len(capture), len(sent))
237 for i in range(len(capture)):
241 # the rx'd packet has the MPLS label popped
243 self.assertEqual(eth.type, 0x86DD)
248 self.assertEqual(rx_ip.src, tx_ip.src)
249 self.assertEqual(rx_ip.dst, tx_ip.dst)
250 # IP processing post pop has decremented the TTL
251 self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
257 """ MPLS label swap tests """
260 # A simple MPLS xconnect - eos label in label out
262 route_32_eos = VppMplsRoute(self, 32, 1,
263 [VppRoutePath(self.pg0.remote_ip4,
264 self.pg0.sw_if_index,
266 route_32_eos.add_vpp_config()
269 # a stream that matches the route for 10.0.0.1
270 # PG0 is in the default table
272 self.vapi.cli("clear trace")
273 tx = self.create_stream_labelled_ip4(self.pg0, [32])
274 self.pg0.add_stream(tx)
276 self.pg_enable_capture(self.pg_interfaces)
279 rx = self.pg0.get_capture()
280 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33])
283 # A simple MPLS xconnect - non-eos label in label out
285 route_32_neos = VppMplsRoute(self, 32, 0,
286 [VppRoutePath(self.pg0.remote_ip4,
287 self.pg0.sw_if_index,
289 route_32_neos.add_vpp_config()
292 # a stream that matches the route for 10.0.0.1
293 # PG0 is in the default table
295 self.vapi.cli("clear trace")
296 tx = self.create_stream_labelled_ip4(self.pg0, [32, 99])
297 self.pg0.add_stream(tx)
299 self.pg_enable_capture(self.pg_interfaces)
302 rx = self.pg0.get_capture()
303 self.verify_capture_labelled(self.pg0, rx, tx, [33, 99])
306 # An MPLS xconnect - EOS label in IP out
308 route_33_eos = VppMplsRoute(self, 33, 1,
309 [VppRoutePath(self.pg0.remote_ip4,
310 self.pg0.sw_if_index,
312 route_33_eos.add_vpp_config()
314 self.vapi.cli("clear trace")
315 tx = self.create_stream_labelled_ip4(self.pg0, [33])
316 self.pg0.add_stream(tx)
318 self.pg_enable_capture(self.pg_interfaces)
321 rx = self.pg0.get_capture()
322 self.verify_capture_ip4(self.pg0, rx, tx)
325 # An MPLS xconnect - non-EOS label in IP out - an invalid configuration
326 # so this traffic should be dropped.
328 route_33_neos = VppMplsRoute(self, 33, 0,
329 [VppRoutePath(self.pg0.remote_ip4,
330 self.pg0.sw_if_index,
332 route_33_neos.add_vpp_config()
334 self.vapi.cli("clear trace")
335 tx = self.create_stream_labelled_ip4(self.pg0, [33, 99])
336 self.pg0.add_stream(tx)
338 self.pg_enable_capture(self.pg_interfaces)
340 self.pg0.assert_nothing_captured(
341 remark="MPLS non-EOS packets popped and forwarded")
344 # A recursive EOS x-connect, which resolves through another x-connect
346 route_34_eos = VppMplsRoute(self, 34, 1,
347 [VppRoutePath("0.0.0.0",
351 route_34_eos.add_vpp_config()
353 tx = self.create_stream_labelled_ip4(self.pg0, [34])
354 self.pg0.add_stream(tx)
356 self.pg_enable_capture(self.pg_interfaces)
359 rx = self.pg0.get_capture()
360 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 45])
363 # A recursive non-EOS x-connect, which resolves through another
366 route_34_neos = VppMplsRoute(self, 34, 0,
367 [VppRoutePath("0.0.0.0",
371 route_34_neos.add_vpp_config()
373 self.vapi.cli("clear trace")
374 tx = self.create_stream_labelled_ip4(self.pg0, [34, 99])
375 self.pg0.add_stream(tx)
377 self.pg_enable_capture(self.pg_interfaces)
380 rx = self.pg0.get_capture()
381 # it's the 2nd (counting from 0) label in the stack that is swapped
382 self.verify_capture_labelled(self.pg0, rx, tx, [33, 44, 46, 99], num=2)
385 # an recursive IP route that resolves through the recursive non-eos
388 ip_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
389 [VppRoutePath("0.0.0.0",
393 ip_10_0_0_1.add_vpp_config()
395 self.vapi.cli("clear trace")
396 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
397 self.pg0.add_stream(tx)
399 self.pg_enable_capture(self.pg_interfaces)
402 rx = self.pg0.get_capture()
403 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 46, 55])
405 ip_10_0_0_1.remove_vpp_config()
406 route_34_neos.remove_vpp_config()
407 route_34_eos.remove_vpp_config()
408 route_33_neos.remove_vpp_config()
409 route_33_eos.remove_vpp_config()
410 route_32_neos.remove_vpp_config()
411 route_32_eos.remove_vpp_config()
414 """ MPLS Local Label Binding test """
417 # Add a non-recursive route with a single out label
419 route_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
420 [VppRoutePath(self.pg0.remote_ip4,
421 self.pg0.sw_if_index,
423 route_10_0_0_1.add_vpp_config()
425 # bind a local label to the route
426 binding = VppMplsIpBind(self, 44, "10.0.0.1", 32)
427 binding.add_vpp_config()
430 self.vapi.cli("clear trace")
431 tx = self.create_stream_labelled_ip4(self.pg0, [44, 99])
432 self.pg0.add_stream(tx)
434 self.pg_enable_capture(self.pg_interfaces)
437 rx = self.pg0.get_capture()
438 self.verify_capture_labelled(self.pg0, rx, tx, [45, 99])
441 self.vapi.cli("clear trace")
442 tx = self.create_stream_labelled_ip4(self.pg0, [44])
443 self.pg0.add_stream(tx)
445 self.pg_enable_capture(self.pg_interfaces)
448 rx = self.pg0.get_capture()
449 self.verify_capture_labelled(self.pg0, rx, tx, [45])
452 self.vapi.cli("clear trace")
453 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
454 self.pg0.add_stream(tx)
456 self.pg_enable_capture(self.pg_interfaces)
459 rx = self.pg0.get_capture()
460 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [45])
465 binding.remove_vpp_config()
466 route_10_0_0_1.remove_vpp_config()
468 def test_imposition(self):
469 """ MPLS label imposition test """
472 # Add a non-recursive route with a single out label
474 route_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
475 [VppRoutePath(self.pg0.remote_ip4,
476 self.pg0.sw_if_index,
478 route_10_0_0_1.add_vpp_config()
481 # a stream that matches the route for 10.0.0.1
482 # PG0 is in the default table
484 self.vapi.cli("clear trace")
485 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
486 self.pg0.add_stream(tx)
488 self.pg_enable_capture(self.pg_interfaces)
491 rx = self.pg0.get_capture()
492 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32])
495 # Add a non-recursive route with a 3 out labels
497 route_10_0_0_2 = VppIpRoute(self, "10.0.0.2", 32,
498 [VppRoutePath(self.pg0.remote_ip4,
499 self.pg0.sw_if_index,
500 labels=[32, 33, 34])])
501 route_10_0_0_2.add_vpp_config()
504 # a stream that matches the route for 10.0.0.1
505 # PG0 is in the default table
507 self.vapi.cli("clear trace")
508 tx = self.create_stream_ip4(self.pg0, "10.0.0.2")
509 self.pg0.add_stream(tx)
511 self.pg_enable_capture(self.pg_interfaces)
514 rx = self.pg0.get_capture()
515 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 33, 34])
518 # add a recursive path, with output label, via the 1 label route
520 route_11_0_0_1 = VppIpRoute(self, "11.0.0.1", 32,
521 [VppRoutePath("10.0.0.1",
524 route_11_0_0_1.add_vpp_config()
527 # a stream that matches the route for 11.0.0.1, should pick up
528 # the label stack for 11.0.0.1 and 10.0.0.1
530 self.vapi.cli("clear trace")
531 tx = self.create_stream_ip4(self.pg0, "11.0.0.1")
532 self.pg0.add_stream(tx)
534 self.pg_enable_capture(self.pg_interfaces)
537 rx = self.pg0.get_capture()
538 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 44])
541 # add a recursive path, with 2 labels, via the 3 label route
543 route_11_0_0_2 = VppIpRoute(self, "11.0.0.2", 32,
544 [VppRoutePath("10.0.0.2",
547 route_11_0_0_2.add_vpp_config()
550 # a stream that matches the route for 11.0.0.1, should pick up
551 # the label stack for 11.0.0.1 and 10.0.0.1
553 self.vapi.cli("clear trace")
554 tx = self.create_stream_ip4(self.pg0, "11.0.0.2")
555 self.pg0.add_stream(tx)
557 self.pg_enable_capture(self.pg_interfaces)
560 rx = self.pg0.get_capture()
561 self.verify_capture_labelled_ip4(
562 self.pg0, rx, tx, [32, 33, 34, 44, 45])
567 route_11_0_0_2.remove_vpp_config()
568 route_11_0_0_1.remove_vpp_config()
569 route_10_0_0_2.remove_vpp_config()
570 route_10_0_0_1.remove_vpp_config()
572 def test_tunnel(self):
573 """ MPLS Tunnel Tests """
576 # Create a tunnel with a single out label
578 nh_addr = socket.inet_pton(socket.AF_INET, self.pg0.remote_ip4)
580 reply = self.vapi.mpls_tunnel_add_del(
581 0xffffffff, # don't know the if index yet
584 self.pg0.sw_if_index,
585 0, # next-hop-table-id
589 self.vapi.sw_interface_set_flags(reply.sw_if_index, admin_up_down=1)
592 # add an unlabelled route through the new tunnel
594 route_10_0_0_3 = VppIpRoute(self, "10.0.0.3", 32,
595 [VppRoutePath("0.0.0.0",
597 route_10_0_0_3.add_vpp_config()
599 self.vapi.cli("clear trace")
600 tx = self.create_stream_ip4(self.pg0, "10.0.0.3")
601 self.pg0.add_stream(tx)
603 self.pg_enable_capture(self.pg_interfaces)
606 rx = self.pg0.get_capture()
607 self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [44, 46])
609 def test_v4_exp_null(self):
610 """ MPLS V4 Explicit NULL test """
613 # The first test case has an MPLS TTL of 0
614 # all packet should be dropped
616 tx = self.create_stream_labelled_ip4(self.pg0, [0], 0)
617 self.pg0.add_stream(tx)
619 self.pg_enable_capture(self.pg_interfaces)
622 self.pg0.assert_nothing_captured(remark="MPLS TTL=0 packets forwarded")
625 # a stream with a non-zero MPLS TTL
626 # PG0 is in the default table
628 tx = self.create_stream_labelled_ip4(self.pg0, [0])
629 self.pg0.add_stream(tx)
631 self.pg_enable_capture(self.pg_interfaces)
634 rx = self.pg0.get_capture()
635 self.verify_capture_ip4(self.pg0, rx, tx)
638 # a stream with a non-zero MPLS TTL
640 # we are ensuring the post-pop lookup occurs in the VRF table
642 self.vapi.cli("clear trace")
643 tx = self.create_stream_labelled_ip4(self.pg1, [0])
644 self.pg1.add_stream(tx)
646 self.pg_enable_capture(self.pg_interfaces)
649 rx = self.pg1.get_capture()
650 self.verify_capture_ip4(self.pg0, rx, tx)
652 def test_v6_exp_null(self):
653 """ MPLS V6 Explicit NULL test """
656 # a stream with a non-zero MPLS TTL
657 # PG0 is in the default table
659 self.vapi.cli("clear trace")
660 tx = self.create_stream_labelled_ip6(self.pg0, 2, 2)
661 self.pg0.add_stream(tx)
663 self.pg_enable_capture(self.pg_interfaces)
666 rx = self.pg0.get_capture()
667 self.verify_capture_ip6(self.pg0, rx, tx)
670 # a stream with a non-zero MPLS TTL
672 # we are ensuring the post-pop lookup occurs in the VRF table
674 self.vapi.cli("clear trace")
675 tx = self.create_stream_labelled_ip6(self.pg1, 2, 2)
676 self.pg1.add_stream(tx)
678 self.pg_enable_capture(self.pg_interfaces)
681 rx = self.pg1.get_capture()
682 self.verify_capture_ip6(self.pg0, rx, tx)
688 # A de-agg route - next-hop lookup in default table
690 route_34_eos = VppMplsRoute(self, 34, 1,
691 [VppRoutePath("0.0.0.0",
694 route_34_eos.add_vpp_config()
697 # ping an interface in the default table
698 # PG0 is in the default table
700 self.vapi.cli("clear trace")
701 tx = self.create_stream_labelled_ip4(self.pg0, [34], ping=1,
703 self.pg0.add_stream(tx)
705 self.pg_enable_capture(self.pg_interfaces)
708 rx = self.pg0.get_capture()
709 self.verify_capture_ip4(self.pg0, rx, tx, ping_resp=1)
712 # A de-agg route - next-hop lookup in non-default table
714 route_35_eos = VppMplsRoute(self, 35, 1,
715 [VppRoutePath("0.0.0.0",
718 route_35_eos.add_vpp_config()
721 # ping an interface in the non-default table
722 # PG0 is in the default table. packet arrive labelled in the
723 # default table and egress unlabelled in the non-default
725 self.vapi.cli("clear trace")
726 tx = self.create_stream_labelled_ip4(
727 self.pg0, [35], ping=1, ip_itf=self.pg1)
728 self.pg0.add_stream(tx)
730 self.pg_enable_capture(self.pg_interfaces)
733 packet_count = self.get_packet_count_for_if_idx(self.pg0.sw_if_index)
734 rx = self.pg1.get_capture(packet_count)
735 self.verify_capture_ip4(self.pg1, rx, tx, ping_resp=1)
737 route_35_eos.remove_vpp_config()
738 route_34_eos.remove_vpp_config()
740 if __name__ == '__main__':
741 unittest.main(testRunner=VppTestRunner)