6 from framework import VppTestCase, VppTestRunner
7 from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
8 from vpp_ip_route import IpRoute, RoutePath, MplsRoute, MplsIpBind
10 from scapy.packet import Raw
11 from scapy.layers.l2 import Ether
12 from scapy.layers.inet import IP, UDP
13 from scapy.layers.inet6 import IPv6
14 from scapy.contrib.mpls import MPLS
19 class TestMPLS(VppTestCase):
20 """ MPLS Test Case """
24 super(TestMPLS, cls).setUpClass()
27 super(TestMPLS, self).setUp()
29 # create 2 pg interfaces
30 self.create_pg_interfaces(range(2))
32 # setup both interfaces
33 # assign them different tables.
36 for i in self.pg_interfaces:
38 i.set_table_ip4(table_id)
39 i.set_table_ip6(table_id)
48 super(TestMPLS, self).tearDown()
50 # the default of 64 matches the IP packet TTL default
51 def create_stream_labelled_ip4(self, src_if, mpls_labels, mpls_ttl=255):
53 for i in range(0, 257):
54 info = self.create_packet_info(src_if.sw_if_index,
56 payload = self.info_to_payload(info)
57 p = Ether(dst=src_if.local_mac, src=src_if.remote_mac)
59 for ii in range(len(mpls_labels)):
60 if ii == len(mpls_labels) - 1:
61 p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=1)
63 p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=0)
64 p = (p / IP(src=src_if.remote_ip4, dst=src_if.remote_ip4) /
65 UDP(sport=1234, dport=1234) /
71 def create_stream_ip4(self, src_if, dst_ip):
73 for i in range(0, 257):
74 info = self.create_packet_info(src_if.sw_if_index,
76 payload = self.info_to_payload(info)
77 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
78 IP(src=src_if.remote_ip4, dst=dst_ip) /
79 UDP(sport=1234, dport=1234) /
85 def create_stream_labelled_ip6(self, src_if, mpls_label, mpls_ttl):
87 for i in range(0, 257):
88 info = self.create_packet_info(src_if.sw_if_index,
90 payload = self.info_to_payload(info)
91 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
92 MPLS(label=mpls_label, ttl=mpls_ttl) /
93 IPv6(src=src_if.remote_ip6, dst=src_if.remote_ip6) /
94 UDP(sport=1234, dport=1234) /
100 def verify_filter(self, capture, sent):
101 if not len(capture) == len(sent):
102 # filter out any IPv6 RAs from the captur
104 if (p.haslayer(IPv6)):
108 def verify_capture_ip4(self, src_if, capture, sent):
110 capture = self.verify_filter(capture, sent)
112 self.assertEqual(len(capture), len(sent))
114 for i in range(len(capture)):
118 # the rx'd packet has the MPLS label popped
120 self.assertEqual(eth.type, 0x800)
125 self.assertEqual(rx_ip.src, tx_ip.src)
126 self.assertEqual(rx_ip.dst, tx_ip.dst)
127 # IP processing post pop has decremented the TTL
128 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
133 def verify_mpls_stack(self, rx, mpls_labels, ttl=255, num=0):
134 # the rx'd packet has the MPLS label popped
136 self.assertEqual(eth.type, 0x8847)
140 for ii in range(len(mpls_labels)):
141 self.assertEqual(rx_mpls.label, mpls_labels[ii])
142 self.assertEqual(rx_mpls.cos, 0)
144 self.assertEqual(rx_mpls.ttl, ttl)
146 self.assertEqual(rx_mpls.ttl, 255)
148 if ii == len(mpls_labels) - 1:
149 self.assertEqual(rx_mpls.s, 1)
152 self.assertEqual(rx_mpls.s, 0)
153 # pop the label to expose the next
154 rx_mpls = rx_mpls[MPLS].payload
156 def verify_capture_labelled_ip4(self, src_if, capture, sent,
159 capture = self.verify_filter(capture, sent)
161 self.assertEqual(len(capture), len(sent))
163 for i in range(len(capture)):
169 # the MPLS TTL is copied from the IP
170 self.verify_mpls_stack(
171 rx, mpls_labels, rx_ip.ttl, len(mpls_labels) - 1)
173 self.assertEqual(rx_ip.src, tx_ip.src)
174 self.assertEqual(rx_ip.dst, tx_ip.dst)
175 # IP processing post pop has decremented the TTL
176 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
181 def verify_capture_tunneled_ip4(self, src_if, capture, sent, mpls_labels):
183 capture = self.verify_filter(capture, sent)
185 self.assertEqual(len(capture), len(sent))
187 for i in range(len(capture)):
193 # the MPLS TTL is 255 since it enters a new tunnel
194 self.verify_mpls_stack(
195 rx, mpls_labels, 255, len(mpls_labels) - 1)
197 self.assertEqual(rx_ip.src, tx_ip.src)
198 self.assertEqual(rx_ip.dst, tx_ip.dst)
199 # IP processing post pop has decremented the TTL
200 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
205 def verify_capture_labelled(self, src_if, capture, sent,
206 mpls_labels, ttl=254, num=0):
208 capture = self.verify_filter(capture, sent)
210 self.assertEqual(len(capture), len(sent))
212 for i in range(len(capture)):
214 self.verify_mpls_stack(rx, mpls_labels, ttl, num)
218 def verify_capture_ip6(self, src_if, capture, sent):
220 self.assertEqual(len(capture), len(sent))
222 for i in range(len(capture)):
226 # the rx'd packet has the MPLS label popped
228 self.assertEqual(eth.type, 0x86DD)
233 self.assertEqual(rx_ip.src, tx_ip.src)
234 self.assertEqual(rx_ip.dst, tx_ip.dst)
235 # IP processing post pop has decremented the TTL
236 self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
242 """ MPLS label swap tests """
245 # A simple MPLS xconnect - eos label in label out
247 route_32_eos = MplsRoute(self, 32, 1,
248 [RoutePath(self.pg0.remote_ip4,
249 self.pg0.sw_if_index,
251 route_32_eos.add_vpp_config()
254 # a stream that matches the route for 10.0.0.1
255 # PG0 is in the default table
257 self.vapi.cli("clear trace")
258 tx = self.create_stream_labelled_ip4(self.pg0, [32])
259 self.pg0.add_stream(tx)
261 self.pg_enable_capture(self.pg_interfaces)
264 rx = self.pg0.get_capture()
265 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33])
268 # A simple MPLS xconnect - non-eos label in label out
270 route_32_neos = MplsRoute(self, 32, 0,
271 [RoutePath(self.pg0.remote_ip4,
272 self.pg0.sw_if_index,
274 route_32_neos.add_vpp_config()
277 # a stream that matches the route for 10.0.0.1
278 # PG0 is in the default table
280 self.vapi.cli("clear trace")
281 tx = self.create_stream_labelled_ip4(self.pg0, [32, 99])
282 self.pg0.add_stream(tx)
284 self.pg_enable_capture(self.pg_interfaces)
287 rx = self.pg0.get_capture()
288 self.verify_capture_labelled(self.pg0, rx, tx, [33, 99])
291 # An MPLS xconnect - EOS label in IP out
293 route_33_eos = MplsRoute(self, 33, 1,
294 [RoutePath(self.pg0.remote_ip4,
295 self.pg0.sw_if_index,
297 route_33_eos.add_vpp_config()
299 self.vapi.cli("clear trace")
300 tx = self.create_stream_labelled_ip4(self.pg0, [33])
301 self.pg0.add_stream(tx)
303 self.pg_enable_capture(self.pg_interfaces)
306 rx = self.pg0.get_capture()
307 self.verify_capture_ip4(self.pg0, rx, tx)
310 # An MPLS xconnect - non-EOS label in IP out - an invalid configuration
311 # so this traffic should be dropped.
313 route_33_neos = MplsRoute(self, 33, 0,
314 [RoutePath(self.pg0.remote_ip4,
315 self.pg0.sw_if_index,
317 route_33_neos.add_vpp_config()
319 self.vapi.cli("clear trace")
320 tx = self.create_stream_labelled_ip4(self.pg0, [33, 99])
321 self.pg0.add_stream(tx)
323 self.pg_enable_capture(self.pg_interfaces)
326 rx = self.pg0.get_capture()
328 self.assertEqual(0, len(rx))
330 error("MPLS non-EOS packets popped and forwarded")
335 # A recursive EOS x-connect, which resolves through another x-connect
337 route_34_eos = MplsRoute(self, 34, 1,
338 [RoutePath("0.0.0.0",
342 route_34_eos.add_vpp_config()
344 self.vapi.cli("clear trace")
345 tx = self.create_stream_labelled_ip4(self.pg0, [34])
346 self.pg0.add_stream(tx)
348 self.pg_enable_capture(self.pg_interfaces)
351 rx = self.pg0.get_capture()
352 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 45])
355 # A recursive non-EOS x-connect, which resolves through another x-connect
357 route_34_neos = MplsRoute(self, 34, 0,
358 [RoutePath("0.0.0.0",
362 route_34_neos.add_vpp_config()
364 self.vapi.cli("clear trace")
365 tx = self.create_stream_labelled_ip4(self.pg0, [34, 99])
366 self.pg0.add_stream(tx)
368 self.pg_enable_capture(self.pg_interfaces)
371 rx = self.pg0.get_capture()
372 # it's the 2nd (counting from 0) lael in the stack that is swapped
373 self.verify_capture_labelled(self.pg0, rx, tx, [33, 44, 46, 99], num=2)
376 # an recursive IP route that resolves through the recursive non-eos x-connect
378 ip_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
379 [RoutePath("0.0.0.0",
383 ip_10_0_0_1.add_vpp_config()
385 self.vapi.cli("clear trace")
386 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
387 self.pg0.add_stream(tx)
389 self.pg_enable_capture(self.pg_interfaces)
392 rx = self.pg0.get_capture()
393 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 46, 55])
395 ip_10_0_0_1.remove_vpp_config()
396 route_34_neos.remove_vpp_config()
397 route_34_eos.remove_vpp_config()
398 route_33_neos.remove_vpp_config()
399 route_33_eos.remove_vpp_config()
400 route_32_neos.remove_vpp_config()
401 route_32_eos.remove_vpp_config()
404 """ MPLS Local Label Binding test """
407 # Add a non-recursive route with a single out label
409 route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
410 [RoutePath(self.pg0.remote_ip4,
411 self.pg0.sw_if_index,
413 route_10_0_0_1.add_vpp_config()
415 # bind a local label to the route
416 binding = MplsIpBind(self, 44, "10.0.0.1", 32)
417 binding.add_vpp_config()
420 self.vapi.cli("clear trace")
421 tx = self.create_stream_labelled_ip4(self.pg0, [44, 99])
422 self.pg0.add_stream(tx)
424 self.pg_enable_capture(self.pg_interfaces)
427 rx = self.pg0.get_capture()
428 self.verify_capture_labelled(self.pg0, rx, tx, [45, 99])
431 self.vapi.cli("clear trace")
432 tx = self.create_stream_labelled_ip4(self.pg0, [44])
433 self.pg0.add_stream(tx)
435 self.pg_enable_capture(self.pg_interfaces)
438 rx = self.pg0.get_capture()
439 self.verify_capture_labelled(self.pg0, rx, tx, [45])
442 self.vapi.cli("clear trace")
443 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
444 self.pg0.add_stream(tx)
446 self.pg_enable_capture(self.pg_interfaces)
449 rx = self.pg0.get_capture()
450 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [45])
455 binding.remove_vpp_config()
456 route_10_0_0_1.remove_vpp_config()
458 def test_imposition(self):
459 """ MPLS label imposition test """
462 # Add a non-recursive route with a single out label
464 route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
465 [RoutePath(self.pg0.remote_ip4,
466 self.pg0.sw_if_index,
468 route_10_0_0_1.add_vpp_config()
471 # a stream that matches the route for 10.0.0.1
472 # PG0 is in the default table
474 self.vapi.cli("clear trace")
475 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
476 self.pg0.add_stream(tx)
478 self.pg_enable_capture(self.pg_interfaces)
481 rx = self.pg0.get_capture()
482 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32])
485 # Add a non-recursive route with a 3 out labels
487 route_10_0_0_2 = IpRoute(self, "10.0.0.2", 32,
488 [RoutePath(self.pg0.remote_ip4,
489 self.pg0.sw_if_index,
490 labels=[32, 33, 34])])
491 route_10_0_0_2.add_vpp_config()
494 # a stream that matches the route for 10.0.0.1
495 # PG0 is in the default table
497 self.vapi.cli("clear trace")
498 tx = self.create_stream_ip4(self.pg0, "10.0.0.2")
499 self.pg0.add_stream(tx)
501 self.pg_enable_capture(self.pg_interfaces)
504 rx = self.pg0.get_capture()
505 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 33, 34])
508 # add a recursive path, with ouput label, via the 1 label route
510 route_11_0_0_1 = IpRoute(self, "11.0.0.1", 32,
511 [RoutePath("10.0.0.1",
514 route_11_0_0_1.add_vpp_config()
517 # a stream that matches the route for 11.0.0.1, should pick up
518 # the label stack for 11.0.0.1 and 10.0.0.1
520 self.vapi.cli("clear trace")
521 tx = self.create_stream_ip4(self.pg0, "11.0.0.1")
522 self.pg0.add_stream(tx)
524 self.pg_enable_capture(self.pg_interfaces)
527 rx = self.pg0.get_capture()
528 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 44])
531 # add a recursive path, with 2 labels, via the 3 label route
533 route_11_0_0_2 = IpRoute(self, "11.0.0.2", 32,
534 [RoutePath("10.0.0.2",
537 route_11_0_0_2.add_vpp_config()
540 # a stream that matches the route for 11.0.0.1, should pick up
541 # the label stack for 11.0.0.1 and 10.0.0.1
543 self.vapi.cli("clear trace")
544 tx = self.create_stream_ip4(self.pg0, "11.0.0.2")
545 self.pg0.add_stream(tx)
547 self.pg_enable_capture(self.pg_interfaces)
550 rx = self.pg0.get_capture()
551 self.verify_capture_labelled_ip4(
552 self.pg0, rx, tx, [32, 33, 34, 44, 45])
557 route_11_0_0_2.remove_vpp_config()
558 route_11_0_0_1.remove_vpp_config()
559 route_10_0_0_2.remove_vpp_config()
560 route_10_0_0_1.remove_vpp_config()
562 def test_tunnel(self):
563 """ MPLS Tunnel Tests """
566 # Create a tunnel with a single out label
568 nh_addr = socket.inet_pton(socket.AF_INET, self.pg0.remote_ip4)
570 reply = self.vapi.mpls_tunnel_add_del(0xffffffff, # don't know the if index yet
573 self.pg0.sw_if_index,
574 0, # next-hop-table-id
578 self.vapi.sw_interface_set_flags(reply.sw_if_index, admin_up_down=1)
581 # add an unlabelled route through the new tunnel
583 dest_addr = socket.inet_pton(socket.AF_INET, "10.0.0.3")
584 nh_addr = socket.inet_pton(socket.AF_INET, "0.0.0.0")
587 self.vapi.ip_add_del_route(dest_addr,
589 nh_addr, # all zeros next-hop - tunnel is p2p
590 reply.sw_if_index, # sw_if_index of the new tunnel
592 0, # next-hop-table-id
597 self.vapi.cli("clear trace")
598 tx = self.create_stream_ip4(self.pg0, "10.0.0.3")
599 self.pg0.add_stream(tx)
601 self.pg_enable_capture(self.pg_interfaces)
604 rx = self.pg0.get_capture()
605 self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [44, 46])
607 def test_v4_exp_null(self):
608 """ MPLS V4 Explicit NULL test """
611 # The first test case has an MPLS TTL of 0
612 # all packet should be dropped
614 tx = self.create_stream_labelled_ip4(self.pg0, [0], 0)
615 self.pg0.add_stream(tx)
617 self.pg_enable_capture(self.pg_interfaces)
620 rx = self.pg0.get_capture()
623 self.assertEqual(0, len(rx))
625 self.logger.error("MPLS TTL=0 packets forwarded")
626 self.logger.error(ppp("", rx))
630 # a stream with a non-zero MPLS TTL
631 # PG0 is in the default table
633 self.vapi.cli("clear trace")
634 tx = self.create_stream_labelled_ip4(self.pg0, [0])
635 self.pg0.add_stream(tx)
637 self.pg_enable_capture(self.pg_interfaces)
640 rx = self.pg0.get_capture()
641 self.verify_capture_ip4(self.pg0, rx, tx)
644 # a stream with a non-zero MPLS TTL
646 # we are ensuring the post-pop lookup occurs in the VRF table
648 self.vapi.cli("clear trace")
649 tx = self.create_stream_labelled_ip4(self.pg1, [0])
650 self.pg1.add_stream(tx)
652 self.pg_enable_capture(self.pg_interfaces)
655 rx = self.pg1.get_capture()
656 self.verify_capture_ip4(self.pg0, rx, tx)
658 def test_v6_exp_null(self):
659 """ MPLS V6 Explicit NULL test """
662 # a stream with a non-zero MPLS TTL
663 # PG0 is in the default table
665 self.vapi.cli("clear trace")
666 tx = self.create_stream_labelled_ip6(self.pg0, 2, 2)
667 self.pg0.add_stream(tx)
669 self.pg_enable_capture(self.pg_interfaces)
672 rx = self.pg0.get_capture()
673 self.verify_capture_ip6(self.pg0, rx, tx)
676 # a stream with a non-zero MPLS TTL
678 # we are ensuring the post-pop lookup occurs in the VRF table
680 self.vapi.cli("clear trace")
681 tx = self.create_stream_labelled_ip6(self.pg1, 2, 2)
682 self.pg1.add_stream(tx)
684 self.pg_enable_capture(self.pg_interfaces)
687 rx = self.pg1.get_capture()
688 self.verify_capture_ip6(self.pg0, rx, tx)
691 if __name__ == '__main__':
692 unittest.main(testRunner=VppTestRunner)