#!/usr/bin/env python3 import unittest import socket from framework import tag_fixme_vpp_workers from framework import VppTestCase, VppTestRunner from vpp_ip import DpoProto, INVALID_INDEX from vpp_ip_route import ( VppIpRoute, VppRoutePath, VppMplsRoute, VppMplsIpBind, VppIpMRoute, VppMRoutePath, VppIpTable, VppMplsTable, VppMplsLabel, MplsLspMode, find_mpls_route, FibPathProto, FibPathType, FibPathFlags, VppMplsLabel, MplsLspMode, ) from vpp_mpls_tunnel_interface import VppMPLSTunnelInterface from vpp_papi import VppEnum import scapy.compat from scapy.packet import Raw from scapy.layers.l2 import Ether, ARP from scapy.layers.inet import IP, UDP, ICMP, icmptypes, icmpcodes from scapy.layers.inet6 import ( IPv6, ICMPv6TimeExceeded, ICMPv6EchoRequest, ICMPv6PacketTooBig, ) from scapy.contrib.mpls import MPLS NUM_PKTS = 67 # scapy removed these attributes. # we asked that they be restored: https://github.com/secdev/scapy/pull/1878 # semantic names have more meaning than numbers. so here they are. ARP.who_has = 1 ARP.is_at = 2 def verify_filter(capture, sent): if not len(capture) == len(sent): # filter out any IPv6 RAs from the capture for p in capture: if p.haslayer(IPv6): capture.remove(p) return capture def verify_mpls_stack(tst, rx, mpls_labels): # the rx'd packet has the MPLS label popped eth = rx[Ether] tst.assertEqual(eth.type, 0x8847) rx_mpls = rx[MPLS] for ii in range(len(mpls_labels)): tst.assertEqual(rx_mpls.label, mpls_labels[ii].value) tst.assertEqual(rx_mpls.cos, mpls_labels[ii].exp) tst.assertEqual(rx_mpls.ttl, mpls_labels[ii].ttl) if ii == len(mpls_labels) - 1: tst.assertEqual(rx_mpls.s, 1) else: # not end of stack tst.assertEqual(rx_mpls.s, 0) # pop the label to expose the next rx_mpls = rx_mpls[MPLS].payload @tag_fixme_vpp_workers class TestMPLS(VppTestCase): """MPLS Test Case""" @classmethod def setUpClass(cls): super(TestMPLS, cls).setUpClass() @classmethod def tearDownClass(cls): super(TestMPLS, cls).tearDownClass() def setUp(self): super(TestMPLS, self).setUp() # create 2 pg interfaces self.create_pg_interfaces(range(4)) # setup both interfaces # assign them different tables. table_id = 0 self.tables = [] tbl = VppMplsTable(self, 0) tbl.add_vpp_config() self.tables.append(tbl) for i in self.pg_interfaces: i.admin_up() if table_id != 0: tbl = VppIpTable(self, table_id) tbl.add_vpp_config() self.tables.append(tbl) tbl = VppIpTable(self, table_id, is_ip6=1) tbl.add_vpp_config() self.tables.append(tbl) i.set_table_ip4(table_id) i.set_table_ip6(table_id) i.config_ip4() i.resolve_arp() i.config_ip6() i.resolve_ndp() i.enable_mpls() table_id += 1 def tearDown(self): for i in self.pg_interfaces: i.unconfig_ip4() i.unconfig_ip6() i.set_table_ip4(0) i.set_table_ip6(0) i.disable_mpls() i.admin_down() super(TestMPLS, self).tearDown() # the default of 64 matches the IP packet TTL default def create_stream_labelled_ip4( self, src_if, mpls_labels, ping=0, ip_itf=None, dst_ip=None, chksum=None, ip_ttl=64, n=257, ): self.reset_packet_infos() pkts = [] for i in range(0, n): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = Ether(dst=src_if.local_mac, src=src_if.remote_mac) for ii in range(len(mpls_labels)): p = p / MPLS( label=mpls_labels[ii].value, ttl=mpls_labels[ii].ttl, cos=mpls_labels[ii].exp, ) if not ping: if not dst_ip: p = ( p / IP(src=src_if.local_ip4, dst=src_if.remote_ip4, ttl=ip_ttl) / UDP(sport=1234, dport=1234) / Raw(payload) ) else: p = ( p / IP(src=src_if.local_ip4, dst=dst_ip, ttl=ip_ttl) / UDP(sport=1234, dport=1234) / Raw(payload) ) else: p = ( p / IP(src=ip_itf.remote_ip4, dst=ip_itf.local_ip4, ttl=ip_ttl) / ICMP() ) if chksum: p[IP].chksum = chksum info.data = p.copy() pkts.append(p) return pkts def create_stream_ip4( self, src_if, dst_ip, ip_ttl=64, ip_dscp=0, payload_size=None ): self.reset_packet_infos() pkts = [] for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_if.remote_ip4, dst=dst_ip, ttl=ip_ttl, tos=ip_dscp) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() if payload_size: self.extend_packet(p, payload_size) pkts.append(p) return pkts def create_stream_ip6(self, src_if, dst_ip, ip_ttl=64, ip_dscp=0): self.reset_packet_infos() pkts = [] for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IPv6(src=src_if.remote_ip6, dst=dst_ip, hlim=ip_ttl, tc=ip_dscp) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() pkts.append(p) return pkts def create_stream_labelled_ip6( self, src_if, mpls_labels, hlim=64, dst_ip=None, ping=0, ip_itf=None ): if dst_ip is None: dst_ip = src_if.remote_ip6 self.reset_packet_infos() pkts = [] for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = Ether(dst=src_if.local_mac, src=src_if.remote_mac) for l in mpls_labels: p = p / MPLS(label=l.value, ttl=l.ttl, cos=l.exp) if ping: p = p / ( IPv6(src=ip_itf.remote_ip6, dst=ip_itf.local_ip6) / ICMPv6EchoRequest() ) else: p = p / ( IPv6(src=src_if.remote_ip6, dst=dst_ip, hlim=hlim) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() pkts.append(p) return pkts def verify_capture_ip4( self, src_if, capture, sent, ping_resp=0, ip_ttl=None, ip_dscp=0 ): try: capture = verify_filter(capture, sent) self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): tx = sent[i] rx = capture[i] # the rx'd packet has the MPLS label popped eth = rx[Ether] self.assertEqual(eth.type, 0x800) tx_ip = tx[IP] rx_ip = rx[IP] if not ping_resp: self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) self.assertEqual(rx_ip.tos, ip_dscp) if not ip_ttl: # IP processing post pop has decremented the TTL self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl) else: self.assertEqual(rx_ip.ttl, ip_ttl) else: self.assertEqual(rx_ip.src, tx_ip.dst) self.assertEqual(rx_ip.dst, tx_ip.src) except: raise def verify_capture_labelled_ip4( self, src_if, capture, sent, mpls_labels, ip_ttl=None ): try: capture = verify_filter(capture, sent) self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): tx = sent[i] rx = capture[i] tx_ip = tx[IP] rx_ip = rx[IP] verify_mpls_stack(self, rx, mpls_labels) self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) if not ip_ttl: # IP processing post pop has decremented the TTL self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl) else: self.assertEqual(rx_ip.ttl, ip_ttl) except: raise def verify_capture_labelled_ip6( self, src_if, capture, sent, mpls_labels, ip_ttl=None ): try: capture = verify_filter(capture, sent) self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): tx = sent[i] rx = capture[i] tx_ip = tx[IPv6] rx_ip = rx[IPv6] verify_mpls_stack(self, rx, mpls_labels) self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) if not ip_ttl: # IP processing post pop has decremented the TTL self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim) else: self.assertEqual(rx_ip.hlim, ip_ttl) except: raise def verify_capture_tunneled_ip4(self, src_if, capture, sent, mpls_labels): try: capture = verify_filter(capture, sent) self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): tx = sent[i] rx = capture[i] tx_ip = tx[IP] rx_ip = rx[IP] verify_mpls_stack(self, rx, mpls_labels) self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) # IP processing post pop has decremented the TTL self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl) except: raise def verify_capture_labelled(self, src_if, capture, sent, mpls_labels): try: capture = verify_filter(capture, sent) self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): rx = capture[i] verify_mpls_stack(self, rx, mpls_labels) except: raise def verify_capture_ip6( self, src_if, capture, sent, ip_hlim=None, ip_dscp=0, ping_resp=0 ): try: self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): tx = sent[i] rx = capture[i] # the rx'd packet has the MPLS label popped eth = rx[Ether] self.assertEqual(eth.type, 0x86DD) tx_ip = tx[IPv6] rx_ip = rx[IPv6] if not ping_resp: self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) self.assertEqual(rx_ip.tc, ip_dscp) # IP processing post pop has decremented the TTL if not ip_hlim: self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim) else: self.assertEqual(rx_ip.hlim, ip_hlim) else: self.assertEqual(rx_ip.src, tx_ip.dst) self.assertEqual(rx_ip.dst, tx_ip.src) except: raise def verify_capture_ip6_icmp(self, src_if, capture, sent): try: # rate limited ICMP self.assertTrue(len(capture) <= len(sent)) for i in range(len(capture)): tx = sent[i] rx = capture[i] # the rx'd packet has the MPLS label popped eth = rx[Ether] self.assertEqual(eth.type, 0x86DD) tx_ip = tx[IPv6] rx_ip = rx[IPv6] self.assertEqual(rx_ip.dst, tx_ip.src) # ICMP sourced from the interface's address self.assertEqual(rx_ip.src, src_if.local_ip6) # hop-limit reset to 255 for IMCP packet self.assertEqual(rx_ip.hlim, 255) icmp = rx[ICMPv6TimeExceeded] except: raise def verify_capture_fragmented_labelled_ip4( self, src_if, capture, sent, mpls_labels, ip_ttl=None ): try: capture = verify_filter(capture, sent) for i in range(len(capture)): tx = sent[0] rx = capture[i] tx_ip = tx[IP] rx_ip = rx[IP] verify_mpls_stack(self, rx, mpls_labels) self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) if not ip_ttl: # IP processing post pop has decremented the TTL self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl) else: self.assertEqual(rx_ip.ttl, ip_ttl) except: raise def verify_capture_fragmented_labelled_ip6( self, src_if, capture, sent, mpls_labels, ip_ttl=None ): try: capture = verify_filter(capture, sent) for i in range(len(capture)): tx = sent[0] rx = capture[i] tx_ip = tx[IPv6] rx.show() rx_ip = IPv6(rx[MPLS].payload) rx_ip.show() verify_mpls_stack(self, rx, mpls_labels) self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) if not ip_ttl: # IP processing post pop has decremented the hop-limit self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim) else: self.assertEqual(rx_ip.hlim, ip_ttl) except: raise def test_swap(self): """MPLS label swap tests""" # # A simple MPLS xconnect - eos label in label out # route_32_eos = VppMplsRoute( self, 32, 1, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(33)] ) ], ) route_32_eos.add_vpp_config() self.assertTrue( find_mpls_route( self, 0, 32, 1, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(33)], ) ], ) ) # # a stream that matches the route for 10.0.0.1 # PG0 is in the default table # tx = self.create_stream_labelled_ip4( self.pg0, [VppMplsLabel(32, ttl=32, exp=1)] ) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled( self.pg0, rx, tx, [VppMplsLabel(33, ttl=31, exp=1)] ) self.assertEqual(route_32_eos.get_stats_to()["packets"], 257) # # A simple MPLS xconnect - non-eos label in label out # route_32_neos = VppMplsRoute( self, 32, 0, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(33)] ) ], ) route_32_neos.add_vpp_config() # # a stream that matches the route for 10.0.0.1 # PG0 is in the default table # tx = self.create_stream_labelled_ip4( self.pg0, [VppMplsLabel(32, ttl=21, exp=7), VppMplsLabel(99)] ) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled( self.pg0, rx, tx, [VppMplsLabel(33, ttl=20, exp=7), VppMplsLabel(99)] ) self.assertEqual(route_32_neos.get_stats_to()["packets"], 257) # # A simple MPLS xconnect - non-eos label in label out, uniform mode # route_42_neos = VppMplsRoute( self, 42, 0, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(43, MplsLspMode.UNIFORM)], ) ], ) route_42_neos.add_vpp_config() tx = self.create_stream_labelled_ip4( self.pg0, [VppMplsLabel(42, ttl=21, exp=7), VppMplsLabel(99)] ) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled( self.pg0, rx, tx, [VppMplsLabel(43, ttl=20, exp=7), VppMplsLabel(99)] ) # # An MPLS xconnect - EOS label in IP out # route_33_eos = VppMplsRoute( self, 33, 1, [VppRoutePath(self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[])], ) route_33_eos.add_vpp_config() tx = self.create_stream_labelled_ip4(self.pg0, [VppMplsLabel(33)]) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_ip4(self.pg0, rx, tx) # # disposed packets have an invalid IPv4 checksum # tx = self.create_stream_labelled_ip4( self.pg0, [VppMplsLabel(33)], dst_ip=self.pg0.remote_ip4, n=65, chksum=1 ) self.send_and_assert_no_replies(self.pg0, tx, "Invalid Checksum") # # An MPLS xconnect - EOS label in IP out, uniform mode # route_3333_eos = VppMplsRoute( self, 3333, 1, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(3, MplsLspMode.UNIFORM)], ) ], ) route_3333_eos.add_vpp_config() tx = self.create_stream_labelled_ip4( self.pg0, [VppMplsLabel(3333, ttl=55, exp=3)] ) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_ip4(self.pg0, rx, tx, ip_ttl=54, ip_dscp=0x60) tx = self.create_stream_labelled_ip4( self.pg0, [VppMplsLabel(3333, ttl=66, exp=4)] ) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_ip4(self.pg0, rx, tx, ip_ttl=65, ip_dscp=0x80) # # An MPLS xconnect - EOS label in IPv6 out # route_333_eos = VppMplsRoute( self, 333, 1, [VppRoutePath(self.pg0.remote_ip6, self.pg0.sw_if_index, labels=[])], eos_proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, ) route_333_eos.add_vpp_config() tx = self.create_stream_labelled_ip6(self.pg0, [VppMplsLabel(333)]) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_ip6(self.pg0, rx, tx) # # disposed packets have an TTL expired # tx = self.create_stream_labelled_ip6( self.pg0, [VppMplsLabel(333, ttl=64)], dst_ip=self.pg1.remote_ip6, hlim=1 ) rx = self.send_and_expect_some(self.pg0, tx, self.pg0) self.verify_capture_ip6_icmp(self.pg0, rx, tx) # # An MPLS xconnect - EOS label in IPv6 out w imp-null # route_334_eos = VppMplsRoute( self, 334, 1, [ VppRoutePath( self.pg0.remote_ip6, self.pg0.sw_if_index, labels=[VppMplsLabel(3)] ) ], eos_proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, ) route_334_eos.add_vpp_config() tx = self.create_stream_labelled_ip6(self.pg0, [VppMplsLabel(334, ttl=64)]) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_ip6(self.pg0, rx, tx) # # An MPLS xconnect - EOS label in IPv6 out w imp-null in uniform mode # route_335_eos = VppMplsRoute( self, 335, 1, [ VppRoutePath( self.pg0.remote_ip6, self.pg0.sw_if_index, labels=[VppMplsLabel(3, MplsLspMode.UNIFORM)], ) ], eos_proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, ) route_335_eos.add_vpp_config() tx = self.create_stream_labelled_ip6( self.pg0, [VppMplsLabel(335, ttl=27, exp=4)] ) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_ip6(self.pg0, rx, tx, ip_hlim=26, ip_dscp=0x80) # # disposed packets have an TTL expired # tx = self.create_stream_labelled_ip6( self.pg0, [VppMplsLabel(334)], dst_ip=self.pg1.remote_ip6, hlim=0 ) rx = self.send_and_expect_some(self.pg0, tx, self.pg0) self.verify_capture_ip6_icmp(self.pg0, rx, tx) # # An MPLS xconnect - non-EOS label in IP out - an invalid configuration # so this traffic should be dropped. # route_33_neos = VppMplsRoute( self, 33, 0, [VppRoutePath(self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[])], ) route_33_neos.add_vpp_config() tx = self.create_stream_labelled_ip4( self.pg0, [VppMplsLabel(33), VppMplsLabel(99)] ) self.send_and_assert_no_replies( self.pg0, tx, "MPLS non-EOS packets popped and forwarded" ) # # A recursive EOS x-connect, which resolves through another x-connect # in pipe mode # route_34_eos = VppMplsRoute( self, 34, 1, [ VppRoutePath( "0.0.0.0", 0xFFFFFFFF, nh_via_label=32, labels=[VppMplsLabel(44), VppMplsLabel(45)], ) ], ) route_34_eos.add_vpp_config() self.logger.info(self.vapi.cli("sh mpls fib 34")) tx = self.create_stream_labelled_ip4(self.pg0, [VppMplsLabel(34, ttl=3)]) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled( self.pg0, rx, tx, [VppMplsLabel(33), VppMplsLabel(44), VppMplsLabel(45, ttl=2)], ) self.assertEqual(route_34_eos.get_stats_to()["packets"], 257) self.assertEqual(route_32_neos.get_stats_via()["packets"], 257) # # A recursive EOS x-connect, which resolves through another x-connect # in uniform mode # route_35_eos = VppMplsRoute( self, 35, 1, [ VppRoutePath( "0.0.0.0", 0xFFFFFFFF, nh_via_label=42, labels=[VppMplsLabel(44)] ) ], ) route_35_eos.add_vpp_config() tx = self.create_stream_labelled_ip4(self.pg0, [VppMplsLabel(35, ttl=3)]) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled( self.pg0, rx, tx, [VppMplsLabel(43, ttl=2), VppMplsLabel(44, ttl=2)] ) # # A recursive non-EOS x-connect, which resolves through another # x-connect # route_34_neos = VppMplsRoute( self, 34, 0, [ VppRoutePath( "0.0.0.0", 0xFFFFFFFF, nh_via_label=32, labels=[VppMplsLabel(44), VppMplsLabel(46)], ) ], ) route_34_neos.add_vpp_config() tx = self.create_stream_labelled_ip4( self.pg0, [VppMplsLabel(34, ttl=45), VppMplsLabel(99)] ) rx = self.send_and_expect(self.pg0, tx, self.pg0) # it's the 2nd (counting from 0) label in the stack that is swapped self.verify_capture_labelled( self.pg0, rx, tx, [ VppMplsLabel(33), VppMplsLabel(44), VppMplsLabel(46, ttl=44), VppMplsLabel(99), ], ) # # an recursive IP route that resolves through the recursive non-eos # x-connect # ip_10_0_0_1 = VppIpRoute( self, "10.0.0.1", 32, [ VppRoutePath( "0.0.0.0", 0xFFFFFFFF, nh_via_label=34, labels=[VppMplsLabel(55)] ) ], ) ip_10_0_0_1.add_vpp_config() tx = self.create_stream_ip4(self.pg0, "10.0.0.1") rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled_ip4( self.pg0, rx, tx, [VppMplsLabel(33), VppMplsLabel(44), VppMplsLabel(46), VppMplsLabel(55)], ) self.assertEqual(ip_10_0_0_1.get_stats_to()["packets"], 257) ip_10_0_0_1.remove_vpp_config() route_34_neos.remove_vpp_config() route_34_eos.remove_vpp_config() route_33_neos.remove_vpp_config() route_33_eos.remove_vpp_config() route_32_neos.remove_vpp_config() route_32_eos.remove_vpp_config() def test_bind(self): """MPLS Local Label Binding test""" # # Add a non-recursive route with a single out label # route_10_0_0_1 = VppIpRoute( self, "10.0.0.1", 32, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(45)] ) ], ) route_10_0_0_1.add_vpp_config() # bind a local label to the route binding = VppMplsIpBind(self, 44, "10.0.0.1", 32) binding.add_vpp_config() # non-EOS stream tx = self.create_stream_labelled_ip4( self.pg0, [VppMplsLabel(44), VppMplsLabel(99)] ) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled( self.pg0, rx, tx, [VppMplsLabel(45, ttl=63), VppMplsLabel(99)] ) # EOS stream tx = self.create_stream_labelled_ip4(self.pg0, [VppMplsLabel(44)]) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled(self.pg0, rx, tx, [VppMplsLabel(45, ttl=63)]) # IP stream tx = self.create_stream_ip4(self.pg0, "10.0.0.1") rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled_ip4(self.pg0, rx, tx, [VppMplsLabel(45)]) # # cleanup # binding.remove_vpp_config() route_10_0_0_1.remove_vpp_config() def test_imposition(self): """MPLS label imposition test""" # # Add a non-recursive route with a single out label # route_10_0_0_1 = VppIpRoute( self, "10.0.0.1", 32, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(32)] ) ], ) route_10_0_0_1.add_vpp_config() # # a stream that matches the route for 10.0.0.1 # PG0 is in the default table # tx = self.create_stream_ip4(self.pg0, "10.0.0.1") rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled_ip4(self.pg0, rx, tx, [VppMplsLabel(32)]) # # Add a non-recursive route with a 3 out labels # route_10_0_0_2 = VppIpRoute( self, "10.0.0.2", 32, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(32), VppMplsLabel(33), VppMplsLabel(34)], ) ], ) route_10_0_0_2.add_vpp_config() tx = self.create_stream_ip4(self.pg0, "10.0.0.2", ip_ttl=44, ip_dscp=0xFF) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled_ip4( self.pg0, rx, tx, [VppMplsLabel(32), VppMplsLabel(33), VppMplsLabel(34)], ip_ttl=43, ) # # Add a non-recursive route with a single out label in uniform mode # route_10_0_0_3 = VppIpRoute( self, "10.0.0.3", 32, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(32, mode=MplsLspMode.UNIFORM)], ) ], ) route_10_0_0_3.add_vpp_config() tx = self.create_stream_ip4(self.pg0, "10.0.0.3", ip_ttl=54, ip_dscp=0xBE) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled_ip4( self.pg0, rx, tx, [VppMplsLabel(32, ttl=53, exp=5)] ) # # Add a IPv6 non-recursive route with a single out label in # uniform mode # route_2001_3 = VppIpRoute( self, "2001::3", 128, [ VppRoutePath( self.pg0.remote_ip6, self.pg0.sw_if_index, labels=[VppMplsLabel(32, mode=MplsLspMode.UNIFORM)], ) ], ) route_2001_3.add_vpp_config() tx = self.create_stream_ip6(self.pg0, "2001::3", ip_ttl=54, ip_dscp=0xBE) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled_ip6( self.pg0, rx, tx, [VppMplsLabel(32, ttl=53, exp=5)] ) # # add a recursive path, with output label, via the 1 label route # route_11_0_0_1 = VppIpRoute( self, "11.0.0.1", 32, [VppRoutePath("10.0.0.1", 0xFFFFFFFF, labels=[VppMplsLabel(44)])], ) route_11_0_0_1.add_vpp_config() # # a stream that matches the route for 11.0.0.1, should pick up # the label stack for 11.0.0.1 and 10.0.0.1 # tx = self.create_stream_ip4(self.pg0, "11.0.0.1") rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled_ip4( self.pg0, rx, tx, [VppMplsLabel(32), VppMplsLabel(44)] ) self.assertEqual(route_11_0_0_1.get_stats_to()["packets"], 257) # # add a recursive path, with 2 labels, via the 3 label route # route_11_0_0_2 = VppIpRoute( self, "11.0.0.2", 32, [ VppRoutePath( "10.0.0.2", 0xFFFFFFFF, labels=[VppMplsLabel(44), VppMplsLabel(45)] ) ], ) route_11_0_0_2.add_vpp_config() # # a stream that matches the route for 11.0.0.1, should pick up # the label stack for 11.0.0.1 and 10.0.0.1 # tx = self.create_stream_ip4(self.pg0, "11.0.0.2") rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled_ip4( self.pg0, rx, tx, [ VppMplsLabel(32), VppMplsLabel(33), VppMplsLabel(34), VppMplsLabel(44), VppMplsLabel(45), ], ) self.assertEqual(route_11_0_0_2.get_stats_to()["packets"], 257) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_labelled_ip4( self.pg0, rx, tx, [ VppMplsLabel(32), VppMplsLabel(33), VppMplsLabel(34), VppMplsLabel(44), VppMplsLabel(45), ], ) self.assertEqual(route_11_0_0_2.get_stats_to()["packets"], 514) # # cleanup # route_11_0_0_2.remove_vpp_config() route_11_0_0_1.remove_vpp_config() route_10_0_0_2.remove_vpp_config() route_10_0_0_1.remove_vpp_config() def test_imposition_fragmentation(self): """MPLS label imposition fragmentation test""" # # Add a ipv4 non-recursive route with a single out label # route_10_0_0_1 = VppIpRoute( self, "10.0.0.1", 32, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(32)] ) ], ) route_10_0_0_1.add_vpp_config() route_1000_1 = VppIpRoute( self, "1000::1", 128, [ VppRoutePath( self.pg0.remote_ip6, self.pg0.sw_if_index, labels=[VppMplsLabel(32)] ) ], ) route_1000_1.add_vpp_config() # # a stream that matches the route for 10.0.0.1 # PG0 is in the default table # tx = self.create_stream_ip4(self.pg0, "10.0.0.1") for i in range(0, 257): self.extend_packet(tx[i], 10000) # # 5 fragments per packet (257*5=1285) # rx = self.send_and_expect(self.pg0, tx, self.pg0, 1285) self.verify_capture_fragmented_labelled_ip4( self.pg0, rx, tx, [VppMplsLabel(32)] ) # packets with DF bit set generate ICMP for t in tx: t[IP].flags = "DF" rxs = self.send_and_expect_some(self.pg0, tx, self.pg0) for rx in rxs: self.assertEqual(icmptypes[rx[ICMP].type], "dest-unreach") self.assertEqual( icmpcodes[rx[ICMP].type][rx[ICMP].code], "fragmentation-needed" ) # the link MTU is 9000, the MPLS over head is 4 bytes self.assertEqual(rx[ICMP].nexthopmtu, 9000 - 4) self.assertEqual( self.statistics.get_err_counter("/err/mpls-frag/dont_fragment_set"), len(tx), ) # # a stream that matches the route for 1000::1/128 # PG0 is in the default table # tx = self.create_stream_ip6(self.pg0, "1000::1") for i in range(0, 257): self.extend_packet(tx[i], 10000) rxs = self.send_and_expect_some(self.pg0, tx, self.pg0) for rx in rxs: self.assertEqual(rx[ICMPv6PacketTooBig].mtu, 9000 - 4) # # cleanup # route_10_0_0_1.remove_vpp_config() def test_tunnel_pipe(self): """MPLS Tunnel Tests - Pipe""" # # Create a tunnel with two out labels # mpls_tun = VppMPLSTunnelInterface( self, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[VppMplsLabel(44), VppMplsLabel(46)], ) ], ) mpls_tun.add_vpp_config() mpls_tun.admin_up() # # add an unlabelled route through the new tunnel # route_10_0_0_3 = VppIpRoute( self, "10.0.0.3", 32, [VppRoutePath("0.0.0.0", mpls_tun._sw_if_index)] ) route_10_0_0_3.add_vpp_config() self.vapi.cli("clear trace") tx = self.create_stream_ip4(self.pg0, "10.0.0.3") self.pg0.add_stream(tx) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg0.get_capture() self.verify_capture_tunneled_ip4( self.pg0, rx, tx, [VppMplsLabel(44), VppMplsLabel(46)] ) # # add a labelled route through the new tunnel # route_10_0_0_4 = VppIpRoute( self, "10.0.0.4", 32, [VppRoutePath("0.0.0.0", mpls_tun._sw_if_index, labels=[33])], ) route_10_0_0_4.add_vpp_config() self.vapi.cli("clear trace") tx = self.create_stream_ip4(self.pg0, "10.0.0.4") self.pg0.add_stream(tx) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg0.get_capture() self.verify_capture_tunneled_ip4( self.pg0, rx, tx, [VppMplsLabel(44), VppMplsLabel(46), VppMplsLabel(33, ttl=255)], ) # # change tunnel's MTU to a low value # mpls_tun.set_l3_mtu(1200) # send IP into the tunnel to be fragmented tx = self.create_stream_ip4(self.pg0, "10.0.0.3", payload_size=1500) rx = self.send_and_expect(self.pg0, tx, self.pg0, len(tx) * 2) fake_tx = [] for p in tx: fake_tx.append(p) fake_tx.append(p) self.verify_capture_tunneled_ip4( self.pg0, rx, fake_tx, [VppMplsLabel(44), VppMplsLabel(46)] ) # send MPLS into the tunnel to be fragmented tx = self.create_stream_ip4(self.pg0, "10.0.0.4", payload_size=1500) rx = self.send_and_expect(self.pg0, tx, self.pg0, len(tx) * 2) fake_tx = [] for p in tx: fake_tx.append(p) fake_tx.append(p) self.verify_capture_tunneled_ip4( self.pg0, rx, fake_tx, [VppMplsLabel(44), VppMplsLabel(46), VppMplsLabel(33, ttl=255)], ) def test_tunnel_uniform(self): """MPLS Tunnel Tests - Uniform""" # # Create a tunnel with a single out label # The label stack is specified here from outer to inner # mpls_tun = VppMPLSTunnelInterface( self, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[ VppMplsLabel(44, ttl=32), VppMplsLabel(46, MplsLspMode.UNIFORM), ], ) ], ) mpls_tun.add_vpp_config() mpls_tun.admin_up() # # add an unlabelled route through the new tunnel # route_10_0_0_3 = VppIpRoute( self, "10.0.0.3", 32, [VppRoutePath("0.0.0.0", mpls_tun._sw_if_index)] ) route_10_0_0_3.add_vpp_config() self.vapi.cli("clear trace") tx = self.create_stream_ip4(self.pg0, "10.0.0.3", ip_ttl=24) self.pg0.add_stream(tx) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg0.get_capture() self.verify_capture_tunneled_ip4( self.pg0, rx, tx, [VppMplsLabel(44, ttl=32), VppMplsLabel(46, ttl=23)] ) # # add a labelled route through the new tunnel # route_10_0_0_4 = VppIpRoute( self, "10.0.0.4", 32, [ VppRoutePath( "0.0.0.0", mpls_tun._sw_if_index, labels=[VppMplsLabel(33, ttl=47)] ) ], ) route_10_0_0_4.add_vpp_config() self.vapi.cli("clear trace") tx = self.create_stream_ip4(self.pg0, "10.0.0.4") self.pg0.add_stream(tx) self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg0.get_capture() self.verify_capture_tunneled_ip4( self.pg0, rx, tx, [ VppMplsLabel(44, ttl=32), VppMplsLabel(46, ttl=47), VppMplsLabel(33, ttl=47), ], ) def test_mpls_tunnel_many(self): """MPLS Multiple Tunnels""" for ii in range(100): mpls_tun = VppMPLSTunnelInterface( self, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[ VppMplsLabel(44, ttl=32), VppMplsLabel(46, MplsLspMode.UNIFORM), ], ) ], ) mpls_tun.add_vpp_config() mpls_tun.admin_up() for ii in range(100): mpls_tun = VppMPLSTunnelInterface( self, [ VppRoutePath( self.pg0.remote_ip4, self.pg0.sw_if_index, labels=[ VppMplsLabel(44, ttl=32), VppMplsLabel(46, MplsLspMode.UNIFORM), ], ) ], is_l2=1, ) mpls_tun.add_vpp_config() mpls_tun.admin_up() def test_v4_exp_null(self): """MPLS V4 Explicit NULL test""" # # The first test case has an MPLS TTL of 0 # all packet should be dropped # tx = self.create_stream_labelled_ip4(self.pg0, [VppMplsLabel(0, ttl=0)]) self.send_and_assert_no_replies(self.pg0, tx, "MPLS TTL=0 packets forwarded") # # a stream with a non-zero MPLS TTL # PG0 is in the default table # tx = self.create_stream_labelled_ip4(self.pg0, [VppMplsLabel(0)]) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_ip4(self.pg0, rx, tx) # # a stream with a non-zero MPLS TTL # PG1 is in table 1 # we are ensuring the post-pop lookup occurs in the VRF table # tx = self.create_stream_labelled_ip4(self.pg1, [VppMplsLabel(0)]) rx = self.send_and_expect(self.pg1, tx, self.pg1) self.verify_capture_ip4(self.pg1, rx, tx) def test_v6_exp_null(self): """MPLS V6 Explicit NULL test""" # # a stream with a non-zero MPLS TTL # PG0 is in the default table # tx = self.create_stream_labelled_ip6(self.pg0, [VppMplsLabel(2)]) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_capture_ip6(self.pg0, rx, tx) # # a stream with a non-zero MPLS TTL # PG1 is in table 1 # we are ensuring the post-pop lookup occurs in the VRF table # tx = self.create_stream_labelled_ip6(self.pg1, [VppMplsLabel(2)]) rx = self.send_and_expect(self.pg1, tx, self.pg1) self.verify_capture_ip6(self.pg0, rx, tx) def test_deag(self): """MPLS Deagg""" # # A de-agg route - next-hop lookup in default table # route_34_eos = VppMplsRoute( self, 34,
/*
 * Copyright (c) 2015 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
  Copyright (c) 2001-2005 Eliot Dresselhaus

  Permission is hereby granted, free of charge, to any person obtaining
  a copy of this software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <vppinfra/hash.h>
#include <vppinfra/error.h>
#include <vppinfra/mem.h>
#include <vppinfra/byte_order.h>	/* for clib_arch_is_big_endian */

always_inline void
zero_pair (hash_t * h, hash_pair_t * p)
{
  clib_memset (p, 0, hash_pair_bytes (h));
}

always_inline void
init_pair (hash_t * h, hash_pair_t * p)
{
  clib_memset (p->value, ~0, hash_value_bytes (h));
}

always_inline hash_pair_union_t *
get_pair (void *v, uword i)
{
  hash_t *h = hash_header (v);
  hash_pair_t *p;
  ASSERT (i < vec_len (v));
  p = v;
  p += i << h->log2_pair_size;
  return (hash_pair_union_t *) p;
}

always_inline void
set_is_user (void *v, uword i, uword is_user)
{
  hash_t *h = hash_header (v);
  uword i0 = i / BITS (h->is_user[0]);
  uword i1 = (uword) 1 << (i % BITS (h->is_user[0]));
  if (is_user)
    h->is_user[i0] |= i1;
  else
    h->is_user[i0] &= ~i1;
}

static u8 *hash_format_pair_default (u8 * s, va_list * args);

#if uword_bits == 64

static inline u64
zap64 (u64 x, word n)
{
#define _(n) (((u64) 1 << (u64) (8*(n))) - (u64) 1)
  static u64 masks_little_endian[] = {
    0, _(1), _(2), _(3), _(4), _(5), _(6), _(7),
  };
  static u64 masks_big_endian[] = {
    0, ~_(7), ~_(6), ~_(5), ~_(4), ~_(3), ~_(2), ~_(1),
  };
#undef _
  if (clib_arch_is_big_endian)
    return x & masks_big_endian[n];
  else
    return x & masks_little_endian[n];
}

/**
 * make address-sanitizer skip this:
 * clib_mem_unaligned + zap64 casts its input as u64, computes a mask
 * according to the input length, and returns the casted maked value.
 * Therefore all the 8 Bytes of the u64 are systematically read, which
 * rightfully causes address-sanitizer to raise an error on smaller inputs.
 *
 * However the invalid Bytes are discarded within zap64(), which is why
 * this can be silenced safely.
 *
 * The above is true *unless* the extra bytes cross a page boundary
 * into unmapped or no-access space, hence the boundary crossing check.
 */
static inline u64
hash_memory64 (void *p, word n_bytes, u64 state)
{
  u64 *q = p;
  u64 a, b, c, n;
  int page_boundary_crossing;
  u64 start_addr, end_addr;
  union
  {
    u8 as_u8[8];
    u64 as_u64;
  } tmp;

  /*
   * If the request crosses a 4k boundary, it's not OK to assume
   * that the zap64 game is safe. 4k is the minimum known page size.
   */
  start_addr = (u64) p;
  end_addr = start_addr + n_bytes + 7;
  page_boundary_crossing = (start_addr >> 12) != (end_addr >> 12);

  a = b = 0x9e3779b97f4a7c13LL;
  c = state;
  n = n_bytes;

  while (n >= 3 * sizeof (u64))
    {
      a += clib_mem_unaligned (q + 0, u64);
      b += clib_mem_unaligned (q + 1, u64);
      c += clib_mem_unaligned (q + 2, u64);
      hash_mix64 (a, b, c);
      n -= 3 * sizeof (u64);
      q += 3;
    }

  c += n_bytes;
  switch (n / sizeof (u64))
    {
    case 2:
      a += clib_mem_unaligned (q + 0, u64);
      b += clib_mem_unaligned (q + 1, u64);
      if (n % sizeof (u64))
	{
	  if (PREDICT_TRUE (page_boundary_crossing == 0))
	    c +=
	      zap64 (CLIB_MEM_OVERFLOW
		     (clib_mem_unaligned (q + 2, u64), q + 2, sizeof (u64)),
		     n % sizeof (u64)) << 8;
	  else
	    {
	      clib_memcpy_fast (tmp.as_u8, q + 2, n % sizeof (u64));
	      c += zap64 (tmp.as_u64, n % sizeof (u64)) << 8;
	    }
	}
      break;

    case 1:
      a += clib_mem_unaligned (q + 0, u64);
      if (n % sizeof (u64))
	{
	  if (PREDICT_TRUE (page_boundary_crossing == 0))
	    b +=
	      zap64 (CLIB_MEM_OVERFLOW
		     (clib_mem_unaligned (q + 1, u64), q + 1, sizeof (u64)),
		     n % sizeof (u64));
	  else
	    {
	      clib_memcpy_fast (tmp.as_u8, q + 1, n % sizeof (u64));
	      b += zap64 (tmp.as_u64, n % sizeof (u64));
	    }
	}
      break;

    case 0:
      if (n % sizeof (u64))
	{
	  if (PREDICT_TRUE (page_boundary_crossing == 0))
	    a +=
	      zap64 (CLIB_MEM_OVERFLOW
		     (clib_mem_unaligned (q + 0, u64), q + 0, sizeof (u64)),
		     n % sizeof (u64));
	  else
	    {
	      clib_memcpy_fast (tmp.as_u8, q, n % sizeof (u64));
	      a += zap64 (tmp.as_u64, n % sizeof (u64));
	    }
	}
      break;
    }

  hash_mix64 (a, b, c);

  return c;
}

#else /* if uword_bits == 64 */

static inline u32
zap32 (u32 x, word n)
{
#define _(n) (((u32) 1 << (u32) (8*(n))) - (u32) 1)
  static u32 masks_little_endian[] = {
    0, _(1), _(2), _(3),
  };
  static u32 masks_big_endian[] = {
    0, ~_(3), ~_(2), ~_(1),
  };
#undef _
  if (clib_arch_is_big_endian)
    return x & masks_big_endian[n];
  else
    return x & masks_little_endian[n];
}

static inline u32
hash_memory32 (void *p, word n_bytes, u32 state)
{
  u32 *q = p;
  u32 a, b, c, n;

  a = b = 0x9e3779b9;
  c = state;
  n = n_bytes;

  while (n >= 3 * sizeof (u32))
    {
      a += clib_mem_unaligned (q + 0, u32);
      b += clib_mem_unaligned (q + 1, u32);
      c += clib_mem_unaligned (q + 2, u32);
      hash_mix32 (a, b, c);
      n -= 3 * sizeof (u32);
      q += 3;
    }

  c += n_bytes;
  switch (n / sizeof (u32))
    {
    case 2:
      a += clib_mem_unaligned (q + 0, u32);
      b += clib_mem_unaligned (q + 1, u32);
      if (n % sizeof (u32))
	c += zap32 (clib_mem_unaligned (q + 2, u32), n % sizeof (u32)) << 8;
      break;

    case 1:
      a += clib_mem_unaligned (q + 0, u32);
      if (n % sizeof (u32))
	b += zap32 (clib_mem_unaligned (q + 1, u32), n % sizeof (u32));
      break;

    case 0:
      if (n % sizeof (u32))
	a += zap32 (clib_mem_unaligned (q + 0, u32), n % sizeof (u32));
      break;
    }

  hash_mix32 (a, b, c);

  return c;
}
#endif

__clib_export uword
hash_memory (void *p, word n_bytes, uword state)
{
  uword *q = p;

#if uword_bits == 64
  return hash_memory64 (q, n_bytes, state);
#else
  return hash_memory32 (q, n_bytes, state);
#endif
}

#if uword_bits == 64
always_inline uword
hash_uword (uword x)
{
  u64 a, b, c;

  a = b = 0x9e3779b97f4a7c13LL;
  c = 0;
  a += x;
  hash_mix64 (a,