6 from framework import VppTestCase, VppTestRunner
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, ICMP
12 from scapy.layers.inet6 import IPv6
13 from scapy.contrib.mpls import MPLS
16 class TestMPLS(VppTestCase):
17 """ MPLS Test Case """
21 super(TestMPLS, cls).setUpClass()
24 super(TestMPLS, self).setUp()
26 # create 2 pg interfaces
27 self.create_pg_interfaces(range(2))
29 # setup both interfaces
30 # assign them different tables.
33 for i in self.pg_interfaces:
35 i.set_table_ip4(table_id)
36 i.set_table_ip6(table_id)
45 super(TestMPLS, self).tearDown()
47 # the default of 64 matches the IP packet TTL default
48 def create_stream_labelled_ip4(
55 self.reset_packet_infos()
57 for i in range(0, 257):
58 info = self.create_packet_info(src_if, src_if)
59 payload = self.info_to_payload(info)
60 p = Ether(dst=src_if.local_mac, src=src_if.remote_mac)
62 for ii in range(len(mpls_labels)):
63 if ii == len(mpls_labels) - 1:
64 p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=1)
66 p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=0)
68 p = (p / IP(src=src_if.local_ip4, dst=src_if.remote_ip4) /
69 UDP(sport=1234, dport=1234) /
72 p = (p / IP(src=ip_itf.remote_ip4,
73 dst=ip_itf.local_ip4) /
80 def create_stream_ip4(self, src_if, dst_ip):
81 self.reset_packet_infos()
83 for i in range(0, 257):
84 info = self.create_packet_info(src_if, src_if)
85 payload = self.info_to_payload(info)
86 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
87 IP(src=src_if.remote_ip4, dst=dst_ip) /
88 UDP(sport=1234, dport=1234) /
94 def create_stream_labelled_ip6(self, src_if, mpls_label, mpls_ttl):
95 self.reset_packet_infos()
97 for i in range(0, 257):
98 info = self.create_packet_info(src_if, src_if)
99 payload = self.info_to_payload(info)
100 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
101 MPLS(label=mpls_label, ttl=mpls_ttl) /
102 IPv6(src=src_if.remote_ip6, dst=src_if.remote_ip6) /
103 UDP(sport=1234, dport=1234) /
110 def verify_filter(capture, sent):
111 if not len(capture) == len(sent):
112 # filter out any IPv6 RAs from the capture
118 def verify_capture_ip4(self, src_if, capture, sent, ping_resp=0):
120 capture = self.verify_filter(capture, sent)
122 self.assertEqual(len(capture), len(sent))
124 for i in range(len(capture)):
128 # the rx'd packet has the MPLS label popped
130 self.assertEqual(eth.type, 0x800)
136 self.assertEqual(rx_ip.src, tx_ip.src)
137 self.assertEqual(rx_ip.dst, tx_ip.dst)
138 # IP processing post pop has decremented the TTL
139 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
141 self.assertEqual(rx_ip.src, tx_ip.dst)
142 self.assertEqual(rx_ip.dst, tx_ip.src)
147 def verify_mpls_stack(self, rx, mpls_labels, ttl=255, num=0):
148 # the rx'd packet has the MPLS label popped
150 self.assertEqual(eth.type, 0x8847)
154 for ii in range(len(mpls_labels)):
155 self.assertEqual(rx_mpls.label, mpls_labels[ii])
156 self.assertEqual(rx_mpls.cos, 0)
158 self.assertEqual(rx_mpls.ttl, ttl)
160 self.assertEqual(rx_mpls.ttl, 255)
162 if ii == len(mpls_labels) - 1:
163 self.assertEqual(rx_mpls.s, 1)
166 self.assertEqual(rx_mpls.s, 0)
167 # pop the label to expose the next
168 rx_mpls = rx_mpls[MPLS].payload
170 def verify_capture_labelled_ip4(self, src_if, capture, sent,
173 capture = self.verify_filter(capture, sent)
175 self.assertEqual(len(capture), len(sent))
177 for i in range(len(capture)):
183 # the MPLS TTL is copied from the IP
184 self.verify_mpls_stack(
185 rx, mpls_labels, rx_ip.ttl, len(mpls_labels) - 1)
187 self.assertEqual(rx_ip.src, tx_ip.src)
188 self.assertEqual(rx_ip.dst, tx_ip.dst)
189 # IP processing post pop has decremented the TTL
190 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
195 def verify_capture_tunneled_ip4(self, src_if, capture, sent, mpls_labels):
197 capture = self.verify_filter(capture, sent)
199 self.assertEqual(len(capture), len(sent))
201 for i in range(len(capture)):
207 # the MPLS TTL is 255 since it enters a new tunnel
208 self.verify_mpls_stack(
209 rx, mpls_labels, 255, len(mpls_labels) - 1)
211 self.assertEqual(rx_ip.src, tx_ip.src)
212 self.assertEqual(rx_ip.dst, tx_ip.dst)
213 # IP processing post pop has decremented the TTL
214 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
219 def verify_capture_labelled(self, src_if, capture, sent,
220 mpls_labels, ttl=254, num=0):
222 capture = self.verify_filter(capture, sent)
224 self.assertEqual(len(capture), len(sent))
226 for i in range(len(capture)):
228 self.verify_mpls_stack(rx, mpls_labels, ttl, num)
232 def verify_capture_ip6(self, src_if, capture, sent):
234 self.assertEqual(len(capture), len(sent))
236 for i in range(len(capture)):
240 # the rx'd packet has the MPLS label popped
242 self.assertEqual(eth.type, 0x86DD)
247 self.assertEqual(rx_ip.src, tx_ip.src)
248 self.assertEqual(rx_ip.dst, tx_ip.dst)
249 # IP processing post pop has decremented the TTL
250 self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
256 """ MPLS label swap tests """
259 # A simple MPLS xconnect - eos label in label out
261 route_32_eos = MplsRoute(self, 32, 1,
262 [RoutePath(self.pg0.remote_ip4,
263 self.pg0.sw_if_index,
265 route_32_eos.add_vpp_config()
268 # a stream that matches the route for 10.0.0.1
269 # PG0 is in the default table
271 self.vapi.cli("clear trace")
272 tx = self.create_stream_labelled_ip4(self.pg0, [32])
273 self.pg0.add_stream(tx)
275 self.pg_enable_capture(self.pg_interfaces)
278 rx = self.pg0.get_capture()
279 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33])
282 # A simple MPLS xconnect - non-eos label in label out
284 route_32_neos = MplsRoute(self, 32, 0,
285 [RoutePath(self.pg0.remote_ip4,
286 self.pg0.sw_if_index,
288 route_32_neos.add_vpp_config()
291 # a stream that matches the route for 10.0.0.1
292 # PG0 is in the default table
294 self.vapi.cli("clear trace")
295 tx = self.create_stream_labelled_ip4(self.pg0, [32, 99])
296 self.pg0.add_stream(tx)
298 self.pg_enable_capture(self.pg_interfaces)
301 rx = self.pg0.get_capture()
302 self.verify_capture_labelled(self.pg0, rx, tx, [33, 99])
305 # An MPLS xconnect - EOS label in IP out
307 route_33_eos = MplsRoute(self, 33, 1,
308 [RoutePath(self.pg0.remote_ip4,
309 self.pg0.sw_if_index,
311 route_33_eos.add_vpp_config()
313 self.vapi.cli("clear trace")
314 tx = self.create_stream_labelled_ip4(self.pg0, [33])
315 self.pg0.add_stream(tx)
317 self.pg_enable_capture(self.pg_interfaces)
320 rx = self.pg0.get_capture()
321 self.verify_capture_ip4(self.pg0, rx, tx)
324 # An MPLS xconnect - non-EOS label in IP out - an invalid configuration
325 # so this traffic should be dropped.
327 route_33_neos = MplsRoute(self, 33, 0,
328 [RoutePath(self.pg0.remote_ip4,
329 self.pg0.sw_if_index,
331 route_33_neos.add_vpp_config()
333 self.vapi.cli("clear trace")
334 tx = self.create_stream_labelled_ip4(self.pg0, [33, 99])
335 self.pg0.add_stream(tx)
337 self.pg_enable_capture(self.pg_interfaces)
339 self.pg0.assert_nothing_captured(
340 remark="MPLS non-EOS packets popped and forwarded")
343 # A recursive EOS x-connect, which resolves through another x-connect
345 route_34_eos = MplsRoute(self, 34, 1,
346 [RoutePath("0.0.0.0",
350 route_34_eos.add_vpp_config()
352 tx = self.create_stream_labelled_ip4(self.pg0, [34])
353 self.pg0.add_stream(tx)
355 self.pg_enable_capture(self.pg_interfaces)
358 rx = self.pg0.get_capture()
359 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 45])
362 # A recursive non-EOS x-connect, which resolves through another
365 route_34_neos = MplsRoute(self, 34, 0,
366 [RoutePath("0.0.0.0",
370 route_34_neos.add_vpp_config()
372 self.vapi.cli("clear trace")
373 tx = self.create_stream_labelled_ip4(self.pg0, [34, 99])
374 self.pg0.add_stream(tx)
376 self.pg_enable_capture(self.pg_interfaces)
379 rx = self.pg0.get_capture()
380 # it's the 2nd (counting from 0) label in the stack that is swapped
381 self.verify_capture_labelled(self.pg0, rx, tx, [33, 44, 46, 99], num=2)
384 # an recursive IP route that resolves through the recursive non-eos
387 ip_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
388 [RoutePath("0.0.0.0",
392 ip_10_0_0_1.add_vpp_config()
394 self.vapi.cli("clear trace")
395 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
396 self.pg0.add_stream(tx)
398 self.pg_enable_capture(self.pg_interfaces)
401 rx = self.pg0.get_capture()
402 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 46, 55])
404 ip_10_0_0_1.remove_vpp_config()
405 route_34_neos.remove_vpp_config()
406 route_34_eos.remove_vpp_config()
407 route_33_neos.remove_vpp_config()
408 route_33_eos.remove_vpp_config()
409 route_32_neos.remove_vpp_config()
410 route_32_eos.remove_vpp_config()
413 """ MPLS Local Label Binding test """
416 # Add a non-recursive route with a single out label
418 route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
419 [RoutePath(self.pg0.remote_ip4,
420 self.pg0.sw_if_index,
422 route_10_0_0_1.add_vpp_config()
424 # bind a local label to the route
425 binding = MplsIpBind(self, 44, "10.0.0.1", 32)
426 binding.add_vpp_config()
429 self.vapi.cli("clear trace")
430 tx = self.create_stream_labelled_ip4(self.pg0, [44, 99])
431 self.pg0.add_stream(tx)
433 self.pg_enable_capture(self.pg_interfaces)
436 rx = self.pg0.get_capture()
437 self.verify_capture_labelled(self.pg0, rx, tx, [45, 99])
440 self.vapi.cli("clear trace")
441 tx = self.create_stream_labelled_ip4(self.pg0, [44])
442 self.pg0.add_stream(tx)
444 self.pg_enable_capture(self.pg_interfaces)
447 rx = self.pg0.get_capture()
448 self.verify_capture_labelled(self.pg0, rx, tx, [45])
451 self.vapi.cli("clear trace")
452 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
453 self.pg0.add_stream(tx)
455 self.pg_enable_capture(self.pg_interfaces)
458 rx = self.pg0.get_capture()
459 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [45])
464 binding.remove_vpp_config()
465 route_10_0_0_1.remove_vpp_config()
467 def test_imposition(self):
468 """ MPLS label imposition test """
471 # Add a non-recursive route with a single out label
473 route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
474 [RoutePath(self.pg0.remote_ip4,
475 self.pg0.sw_if_index,
477 route_10_0_0_1.add_vpp_config()
480 # a stream that matches the route for 10.0.0.1
481 # PG0 is in the default table
483 self.vapi.cli("clear trace")
484 tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
485 self.pg0.add_stream(tx)
487 self.pg_enable_capture(self.pg_interfaces)
490 rx = self.pg0.get_capture()
491 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32])
494 # Add a non-recursive route with a 3 out labels
496 route_10_0_0_2 = IpRoute(self, "10.0.0.2", 32,
497 [RoutePath(self.pg0.remote_ip4,
498 self.pg0.sw_if_index,
499 labels=[32, 33, 34])])
500 route_10_0_0_2.add_vpp_config()
503 # a stream that matches the route for 10.0.0.1
504 # PG0 is in the default table
506 self.vapi.cli("clear trace")
507 tx = self.create_stream_ip4(self.pg0, "10.0.0.2")
508 self.pg0.add_stream(tx)
510 self.pg_enable_capture(self.pg_interfaces)
513 rx = self.pg0.get_capture()
514 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 33, 34])
517 # add a recursive path, with output label, via the 1 label route
519 route_11_0_0_1 = IpRoute(self, "11.0.0.1", 32,
520 [RoutePath("10.0.0.1",
523 route_11_0_0_1.add_vpp_config()
526 # a stream that matches the route for 11.0.0.1, should pick up
527 # the label stack for 11.0.0.1 and 10.0.0.1
529 self.vapi.cli("clear trace")
530 tx = self.create_stream_ip4(self.pg0, "11.0.0.1")
531 self.pg0.add_stream(tx)
533 self.pg_enable_capture(self.pg_interfaces)
536 rx = self.pg0.get_capture()
537 self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 44])
540 # add a recursive path, with 2 labels, via the 3 label route
542 route_11_0_0_2 = IpRoute(self, "11.0.0.2", 32,
543 [RoutePath("10.0.0.2",
546 route_11_0_0_2.add_vpp_config()
549 # a stream that matches the route for 11.0.0.1, should pick up
550 # the label stack for 11.0.0.1 and 10.0.0.1
552 self.vapi.cli("clear trace")
553 tx = self.create_stream_ip4(self.pg0, "11.0.0.2")
554 self.pg0.add_stream(tx)
556 self.pg_enable_capture(self.pg_interfaces)
559 rx = self.pg0.get_capture()
560 self.verify_capture_labelled_ip4(
561 self.pg0, rx, tx, [32, 33, 34, 44, 45])
566 route_11_0_0_2.remove_vpp_config()
567 route_11_0_0_1.remove_vpp_config()
568 route_10_0_0_2.remove_vpp_config()
569 route_10_0_0_1.remove_vpp_config()
571 def test_tunnel(self):
572 """ MPLS Tunnel Tests """
575 # Create a tunnel with a single out label
577 nh_addr = socket.inet_pton(socket.AF_INET, self.pg0.remote_ip4)
579 reply = self.vapi.mpls_tunnel_add_del(
580 0xffffffff, # don't know the if index yet
583 self.pg0.sw_if_index,
584 0, # next-hop-table-id
588 self.vapi.sw_interface_set_flags(reply.sw_if_index, admin_up_down=1)
591 # add an unlabelled route through the new tunnel
593 dest_addr = socket.inet_pton(socket.AF_INET, "10.0.0.3")
594 nh_addr = socket.inet_pton(socket.AF_INET, "0.0.0.0")
597 self.vapi.ip_add_del_route(
600 nh_addr, # all zeros next-hop - tunnel is p2p
601 reply.sw_if_index, # sw_if_index of the new tunnel
603 0, # next-hop-table-id
608 self.vapi.cli("clear trace")
609 tx = self.create_stream_ip4(self.pg0, "10.0.0.3")
610 self.pg0.add_stream(tx)
612 self.pg_enable_capture(self.pg_interfaces)
615 rx = self.pg0.get_capture()
616 self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [44, 46])
618 def test_v4_exp_null(self):
619 """ MPLS V4 Explicit NULL test """
622 # The first test case has an MPLS TTL of 0
623 # all packet should be dropped
625 tx = self.create_stream_labelled_ip4(self.pg0, [0], 0)
626 self.pg0.add_stream(tx)
628 self.pg_enable_capture(self.pg_interfaces)
631 self.pg0.assert_nothing_captured(remark="MPLS TTL=0 packets forwarded")
634 # a stream with a non-zero MPLS TTL
635 # PG0 is in the default table
637 tx = self.create_stream_labelled_ip4(self.pg0, [0])
638 self.pg0.add_stream(tx)
640 self.pg_enable_capture(self.pg_interfaces)
643 rx = self.pg0.get_capture()
644 self.verify_capture_ip4(self.pg0, rx, tx)
647 # a stream with a non-zero MPLS TTL
649 # we are ensuring the post-pop lookup occurs in the VRF table
651 self.vapi.cli("clear trace")
652 tx = self.create_stream_labelled_ip4(self.pg1, [0])
653 self.pg1.add_stream(tx)
655 self.pg_enable_capture(self.pg_interfaces)
658 rx = self.pg1.get_capture()
659 self.verify_capture_ip4(self.pg0, rx, tx)
661 def test_v6_exp_null(self):
662 """ MPLS V6 Explicit NULL test """
665 # a stream with a non-zero MPLS TTL
666 # PG0 is in the default table
668 self.vapi.cli("clear trace")
669 tx = self.create_stream_labelled_ip6(self.pg0, 2, 2)
670 self.pg0.add_stream(tx)
672 self.pg_enable_capture(self.pg_interfaces)
675 rx = self.pg0.get_capture()
676 self.verify_capture_ip6(self.pg0, rx, tx)
679 # a stream with a non-zero MPLS TTL
681 # we are ensuring the post-pop lookup occurs in the VRF table
683 self.vapi.cli("clear trace")
684 tx = self.create_stream_labelled_ip6(self.pg1, 2, 2)
685 self.pg1.add_stream(tx)
687 self.pg_enable_capture(self.pg_interfaces)
690 rx = self.pg1.get_capture()
691 self.verify_capture_ip6(self.pg0, rx, tx)
697 # A de-agg route - next-hop lookup in default table
699 route_34_eos = MplsRoute(self, 34, 1,
700 [RoutePath("0.0.0.0",
703 route_34_eos.add_vpp_config()
706 # ping an interface in the default table
707 # PG0 is in the default table
709 self.vapi.cli("clear trace")
710 tx = self.create_stream_labelled_ip4(self.pg0, [34], ping=1,
712 self.pg0.add_stream(tx)
714 self.pg_enable_capture(self.pg_interfaces)
717 rx = self.pg0.get_capture()
718 self.verify_capture_ip4(self.pg0, rx, tx, ping_resp=1)
721 # A de-agg route - next-hop lookup in non-default table
723 route_35_eos = MplsRoute(self, 35, 1,
724 [RoutePath("0.0.0.0",
727 route_35_eos.add_vpp_config()
730 # ping an interface in the non-default table
731 # PG0 is in the default table. packet arrive labelled in the
732 # default table and egress unlabelled in the non-default
734 self.vapi.cli("clear trace")
735 tx = self.create_stream_labelled_ip4(
736 self.pg0, [35], ping=1, ip_itf=self.pg1)
737 self.pg0.add_stream(tx)
739 self.pg_enable_capture(self.pg_interfaces)
742 packet_count = self.get_packet_count_for_if_idx(self.pg0.sw_if_index)
743 rx = self.pg1.get_capture(packet_count)
744 self.verify_capture_ip4(self.pg1, rx, tx, ping_resp=1)
746 route_35_eos.remove_vpp_config()
747 route_34_eos.remove_vpp_config()
749 if __name__ == '__main__':
750 unittest.main(testRunner=VppTestRunner)