5 from framework import VppTestCase, VppTestRunner
6 from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
7 from vpp_ip_route import IpRoute, RoutePath, MplsRoute, MplsIpBind
9 from scapy.packet import Raw
10 from scapy.layers.l2 import Ether
11 from scapy.layers.inet import IP, UDP
12 from scapy.layers.inet6 import IPv6
13 from scapy.contrib.mpls import MPLS
18 class TestMPLS(VppTestCase):
19 """ MPLS Test Case """
23 super(TestMPLS, cls).setUpClass()
26 super(TestMPLS, self).setUp()
28 # create 2 pg interfaces
29 self.create_pg_interfaces(range(2))
31 # setup both interfaces
32 # assign them different tables.
35 for i in self.pg_interfaces:
37 i.set_table_ip4(table_id)
38 i.set_table_ip6(table_id)
47 super(TestMPLS, self).tearDown()
49 # the default of 64 matches the IP packet TTL default
50 def create_stream_labelled_ip4(self, src_if, mpls_labels, mpls_ttl=255):
52 for i in range(0, 257):
53 info = self.create_packet_info(src_if.sw_if_index,
55 payload = self.info_to_payload(info)
56 p = Ether(dst=src_if.local_mac, src=src_if.remote_mac)
58 for ii in range(len(mpls_labels)):
59 if ii == len(mpls_labels) - 1:
60 p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=1)
62 p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=0)
63 p = (p / IP(src=src_if.remote_ip4, dst=src_if.remote_ip4) /
64 UDP(sport=1234, dport=1234) /
70 def create_stream_ip4(self, src_if, dst_ip):
72 for i in range(0, 257):
73 info = self.create_packet_info(src_if.sw_if_index,
75 payload = self.info_to_payload(info)
76 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
77 IP(src=src_if.remote_ip4, dst=dst_ip) /
78 UDP(sport=1234, dport=1234) /
84 def create_stream_labelled_ip6(self, src_if, mpls_label, mpls_ttl):
86 for i in range(0, 257):
87 info = self.create_packet_info(src_if.sw_if_index,
89 payload = self.info_to_payload(info)
90 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
91 MPLS(label=mpls_label, ttl=mpls_ttl) /
92 IPv6(src=src_if.remote_ip6, dst=src_if.remote_ip6) /
93 UDP(sport=1234, dport=1234) /
99 def verify_filter(self, capture, sent):
100 if not len(capture) == len(sent):
101 # filter out any IPv6 RAs from the captur
103 if (p.haslayer(IPv6)):
107 def verify_capture_ip4(self, src_if, capture, sent):
109 capture = self.verify_filter(capture, sent)
111 self.assertEqual(len(capture), len(sent))
113 for i in range(len(capture)):
117 # the rx'd packet has the MPLS label popped
119 self.assertEqual(eth.type, 0x800)
124 self.assertEqual(rx_ip.src, tx_ip.src)
125 self.assertEqual(rx_ip.dst, tx_ip.dst)
126 # IP processing post pop has decremented the TTL
127 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
132 def verify_mpls_stack(self, rx, mpls_labels, ttl=255, num=0):
133 # the rx'd packet has the MPLS label popped
135 self.assertEqual(eth.type, 0x8847)
139 for ii in range(len(mpls_labels)):
140 self.assertEqual(rx_mpls.label, mpls_labels[ii])
141 self.assertEqual(rx_mpls.cos, 0)
143 self.assertEqual(rx_mpls.ttl, ttl)
145 self.assertEqual(rx_mpls.ttl, 255)
147 if ii == len(mpls_labels) - 1:
148 self.assertEqual(rx_mpls.s, 1)
151 self.assertEqual(rx_mpls.s, 0)
152 # pop the label to expose the next
153 rx_mpls = rx_mpls[MPLS].payload
155 def verify_capture_labelled_ip4(self, src_if, capture, sent,
158 capture = self.verify_filter(capture, sent)
160 self.assertEqual(len(capture), len(sent))
162 for i in range(len(capture)):
168 # the MPLS TTL is copied from the IP
169 self.verify_mpls_stack(
170 rx, mpls_labels, rx_ip.ttl, len(mpls_labels) - 1)
172 self.assertEqual(rx_ip.src, tx_ip.src)
173 self.assertEqual(rx_ip.dst, tx_ip.dst)
174 # IP processing post pop has decremented the TTL
175 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
180 def verify_capture_tunneled_ip4(self, src_if, capture, sent, mpls_labels):
182 capture = self.verify_filter(capture, sent)
184 self.assertEqual(len(capture), len(sent))
186 for i in range(len(capture)):
192 # the MPLS TTL is 255 since it enters a new tunnel
193 self.verify_mpls_stack(
194 rx, mpls_labels, 255, len(mpls_labels) - 1)
196 self.assertEqual(rx_ip.src, tx_ip.src)
197 self.assertEqual(rx_ip.dst, tx_ip.dst)
198 # IP processing post pop has decremented the TTL
199 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
204 def verify_capture_labelled(self, src_if, capture, sent,
205 mpls_labels, ttl=254, num=0):
207 capture = self.verify_filter(capture, sent)
209 self.assertEqual(len(capture), len(sent))
211 for i in range(len(capture)):
213 self.verify_mpls_stack(rx, mpls_labels, ttl, num)
217 def verify_capture_ip6(self, src_if, capture, sent):
219 self.assertEqual(len(capture), len(sent))
221 for i in range(len(capture)):
225 # the rx'd packet has the MPLS label popped
227 self.assertEqual(eth.type, 0x86DD)
232 self.assertEqual(rx_ip.src, tx_ip.src)
233 self.assertEqual(rx_ip.dst, tx_ip.dst)
234 # IP processing post pop has decremented the TTL
235 self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
241 """ MPLS label swap tests """
244 # A simple MPLS xconnect - eos label in label out
246 route_32_eos = MplsRoute(self, 32, 1,
247 [RoutePath(self.pg0.remote_ip4,
248 self.pg0.sw_if_index,
250 route_32_eos.add_vpp_config()
253 # a stream that matches the route for 10.0.0.1
254 # PG0 is in the default table
256 self.vapi.cli("clear trace")
257 tx = self.create_stream_labelled_ip4(self.pg0, [32])
258 self.pg0.add_stream(tx)
260 self.pg_enable_capture(self.pg_interfaces)
263 rx = self.pg0.get_capture()
264 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33])
267 # A simple MPLS xconnect - non-eos label in label out
269 route_32_neos = MplsRoute(self, 32, 0,
270 [RoutePath(self.pg0.remote_ip4,
271 self.pg0.sw_if_index,
273 route_32_neos.add_vpp_config()
276 # a stream that matches the route for 10.0.0.1
277 # PG0 is in the default table
279 self.vapi.cli("clear trace")
280 tx = self.create_stream_labelled_ip4(self.pg0, [32, 99])
281 self.pg0.add_stream(tx)
283 self.pg_enable_capture(self.pg_interfaces)
286 rx = self.pg0.get_capture()
287 self.verify_capture_labelled(self.pg0, rx, tx, [33, 99])
290 # An MPLS xconnect - EOS label in IP out
292 route_33_eos = MplsRoute(self, 33, 1,
293 [RoutePath(self.pg0.remote_ip4,
294 self.pg0.sw_if_index,
296 route_33_eos.add_vpp_config()
298 self.vapi.cli("clear trace")
299 tx = self.create_stream_labelled_ip4(self.pg0, [33])
300 self.pg0.add_stream(tx)
302 self.pg_enable_capture(self.pg_interfaces)
305 rx = self.pg0.get_capture()
306 self.verify_capture_ip4(self.pg0, rx, tx)
309 # An MPLS xconnect - non-EOS label in IP out - an invalid configuration
310 # so this traffic should be dropped.
312 route_33_neos = MplsRoute(self, 33, 0,
313 [RoutePath(self.pg0.remote_ip4,
314 self.pg0.sw_if_index,
316 route_33_neos.add_vpp_config()
318 self.vapi.cli("clear trace")
319 tx = self.create_stream_labelled_ip4(self.pg0, [33, 99])
320 self.pg0.add_stream(tx)
322 self.pg_enable_capture(self.pg_interfaces)
325 rx = self.pg0.get_capture()
327 self.assertEqual(0, len(rx))
329 error("MPLS non-EOS packets popped and forwarded")
334 # A recursive EOS x-connect, which resolves through another x-connect
336 route_34_eos = MplsRoute(self, 34, 1,
337 [RoutePath("0.0.0.0",
341 route_34_eos.add_vpp_config()
343 self.vapi.cli("clear trace")
344 tx = self.create_stream_labelled_ip4(self.pg0, [34])
345 self.pg0.add_stream(tx)
347 self.pg_enable_capture(self.pg_interfaces)
350 rx = self.pg0.get_capture()
351 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 45])
354 # A recursive non-EOS x-connect, which resolves through another x-connect
356 route_34_neos = MplsRoute(self, 34, 0,
357 [RoutePath("0.0.0.0",
361 route_34_neos.add_vpp_config()
363 self.vapi.cli("clear trace")
364 tx = self.create_stream_labelled_ip4(self.pg0, [34, 99])
365 self.pg0.add_stream(tx)
367 self.pg_enable_capture(self.pg_interfaces)
370 rx = self.pg0.get_capture()
371 # it's the 2nd (counting from 0) lael in the stack that is swapped
372 self.verify_capture_labelled(self.pg0, rx, tx, [33, 44, 46, 99], num=2)
375 # an recursive IP route that resolves through the recursive non-eos x-connect
377 ip_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
378 [RoutePath("0.0.0.0",
382 ip_10_0_0_1.add_vpp_config()
384 self.vapi.cli("clear trace")
385 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
386 self.pg0.add_stream(tx)
388 self.pg_enable_capture(self.pg_interfaces)
391 rx = self.pg0.get_capture()
392 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 46, 55])
394 ip_10_0_0_1.remove_vpp_config()
395 route_34_neos.remove_vpp_config()
396 route_34_eos.remove_vpp_config()
397 route_33_neos.remove_vpp_config()
398 route_33_eos.remove_vpp_config()
399 route_32_neos.remove_vpp_config()
400 route_32_eos.remove_vpp_config()
403 """ MPLS Local Label Binding test """
406 # Add a non-recursive route with a single out label
408 route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
409 [RoutePath(self.pg0.remote_ip4,
410 self.pg0.sw_if_index,
412 route_10_0_0_1.add_vpp_config()
414 # bind a local label to the route
415 binding = MplsIpBind(self, 44, "10.0.0.1", 32)
416 binding.add_vpp_config()
419 self.vapi.cli("clear trace")
420 tx = self.create_stream_labelled_ip4(self.pg0, [44, 99])
421 self.pg0.add_stream(tx)
423 self.pg_enable_capture(self.pg_interfaces)
426 rx = self.pg0.get_capture()
427 self.verify_capture_labelled(self.pg0, rx, tx, [45, 99])
430 self.vapi.cli("clear trace")
431 tx = self.create_stream_labelled_ip4(self.pg0, [44])
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])
441 self.vapi.cli("clear trace")
442 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
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_ip4(self.pg0, rx, tx, [45])
454 binding.remove_vpp_config()
455 route_10_0_0_1.remove_vpp_config()
457 def test_imposition(self):
458 """ MPLS label imposition test """
461 # Add a non-recursive route with a single out label
463 route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
464 [RoutePath(self.pg0.remote_ip4,
465 self.pg0.sw_if_index,
467 route_10_0_0_1.add_vpp_config()
470 # a stream that matches the route for 10.0.0.1
471 # PG0 is in the default table
473 self.vapi.cli("clear trace")
474 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
475 self.pg0.add_stream(tx)
477 self.pg_enable_capture(self.pg_interfaces)
480 rx = self.pg0.get_capture()
481 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32])
484 # Add a non-recursive route with a 3 out labels
486 route_10_0_0_2 = IpRoute(self, "10.0.0.2", 32,
487 [RoutePath(self.pg0.remote_ip4,
488 self.pg0.sw_if_index,
489 labels=[32, 33, 34])])
490 route_10_0_0_2.add_vpp_config()
493 # a stream that matches the route for 10.0.0.1
494 # PG0 is in the default table
496 self.vapi.cli("clear trace")
497 tx = self.create_stream_ip4(self.pg0, "10.0.0.2")
498 self.pg0.add_stream(tx)
500 self.pg_enable_capture(self.pg_interfaces)
503 rx = self.pg0.get_capture()
504 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 33, 34])
507 # add a recursive path, with ouput label, via the 1 label route
509 route_11_0_0_1 = IpRoute(self, "11.0.0.1", 32,
510 [RoutePath("10.0.0.1",
513 route_11_0_0_1.add_vpp_config()
516 # a stream that matches the route for 11.0.0.1, should pick up
517 # the label stack for 11.0.0.1 and 10.0.0.1
519 self.vapi.cli("clear trace")
520 tx = self.create_stream_ip4(self.pg0, "11.0.0.1")
521 self.pg0.add_stream(tx)
523 self.pg_enable_capture(self.pg_interfaces)
526 rx = self.pg0.get_capture()
527 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 44])
530 # add a recursive path, with 2 labels, via the 3 label route
532 route_11_0_0_2 = IpRoute(self, "11.0.0.2", 32,
533 [RoutePath("10.0.0.2",
536 route_11_0_0_2.add_vpp_config()
539 # a stream that matches the route for 11.0.0.1, should pick up
540 # the label stack for 11.0.0.1 and 10.0.0.1
542 self.vapi.cli("clear trace")
543 tx = self.create_stream_ip4(self.pg0, "11.0.0.2")
544 self.pg0.add_stream(tx)
546 self.pg_enable_capture(self.pg_interfaces)
549 rx = self.pg0.get_capture()
550 self.verify_capture_labelled_ip4(
551 self.pg0, rx, tx, [32, 33, 34, 44, 45])
556 route_11_0_0_2.remove_vpp_config()
557 route_11_0_0_1.remove_vpp_config()
558 route_10_0_0_2.remove_vpp_config()
559 route_10_0_0_1.remove_vpp_config()
561 def test_tunnel(self):
562 """ MPLS Tunnel Tests """
565 # Create a tunnel with a single out label
567 nh_addr = socket.inet_pton(socket.AF_INET, self.pg0.remote_ip4)
569 reply = self.vapi.mpls_tunnel_add_del(0xffffffff, # don't know the if index yet
572 self.pg0.sw_if_index,
573 0, # next-hop-table-id
577 self.vapi.sw_interface_set_flags(reply.sw_if_index, admin_up_down=1)
580 # add an unlabelled route through the new tunnel
582 dest_addr = socket.inet_pton(socket.AF_INET, "10.0.0.3")
583 nh_addr = socket.inet_pton(socket.AF_INET, "0.0.0.0")
586 self.vapi.ip_add_del_route(dest_addr,
588 nh_addr, # all zeros next-hop - tunnel is p2p
589 reply.sw_if_index, # sw_if_index of the new tunnel
591 0, # next-hop-table-id
596 self.vapi.cli("clear trace")
597 tx = self.create_stream_ip4(self.pg0, "10.0.0.3")
598 self.pg0.add_stream(tx)
600 self.pg_enable_capture(self.pg_interfaces)
603 rx = self.pg0.get_capture()
604 self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [44, 46])
606 def test_v4_exp_null(self):
607 """ MPLS V4 Explicit NULL test """
610 # The first test case has an MPLS TTL of 0
611 # all packet should be dropped
613 tx = self.create_stream_labelled_ip4(self.pg0, [0], 0)
614 self.pg0.add_stream(tx)
616 self.pg_enable_capture(self.pg_interfaces)
619 rx = self.pg0.get_capture()
622 self.assertEqual(0, len(rx))
624 self.logger.error("MPLS TTL=0 packets forwarded")
625 self.logger.error(ppp("", rx))
629 # a stream with a non-zero MPLS TTL
630 # PG0 is in the default table
632 self.vapi.cli("clear trace")
633 tx = self.create_stream_labelled_ip4(self.pg0, [0])
634 self.pg0.add_stream(tx)
636 self.pg_enable_capture(self.pg_interfaces)
639 rx = self.pg0.get_capture()
640 self.verify_capture_ip4(self.pg0, rx, tx)
643 # a stream with a non-zero MPLS TTL
645 # we are ensuring the post-pop lookup occurs in the VRF table
647 self.vapi.cli("clear trace")
648 tx = self.create_stream_labelled_ip4(self.pg1, [0])
649 self.pg1.add_stream(tx)
651 self.pg_enable_capture(self.pg_interfaces)
654 rx = self.pg1.get_capture()
655 self.verify_capture_ip4(self.pg0, rx, tx)
657 def test_v6_exp_null(self):
658 """ MPLS V6 Explicit NULL test """
661 # a stream with a non-zero MPLS TTL
662 # PG0 is in the default table
664 self.vapi.cli("clear trace")
665 tx = self.create_stream_labelled_ip6(self.pg0, 2, 2)
666 self.pg0.add_stream(tx)
668 self.pg_enable_capture(self.pg_interfaces)
671 rx = self.pg0.get_capture()
672 self.verify_capture_ip6(self.pg0, rx, tx)
675 # a stream with a non-zero MPLS TTL
677 # we are ensuring the post-pop lookup occurs in the VRF table
679 self.vapi.cli("clear trace")
680 tx = self.create_stream_labelled_ip6(self.pg1, 2, 2)
681 self.pg1.add_stream(tx)
683 self.pg_enable_capture(self.pg_interfaces)
686 rx = self.pg1.get_capture()
687 self.verify_capture_ip6(self.pg0, rx, tx)
690 if __name__ == '__main__':
691 unittest.main(testRunner=VppTestRunner)