+class TestIP6LoadBalance(VppTestCase):
+ """ IPv6 Load-Balancing """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestIP6LoadBalance, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestIP6LoadBalance, cls).tearDownClass()
+
+ def setUp(self):
+ super(TestIP6LoadBalance, self).setUp()
+
+ self.create_pg_interfaces(range(5))
+
+ mpls_tbl = VppMplsTable(self, 0)
+ mpls_tbl.add_vpp_config()
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip6()
+ i.resolve_ndp()
+ i.enable_mpls()
+
+ def tearDown(self):
+ for i in self.pg_interfaces:
+ i.unconfig_ip6()
+ i.admin_down()
+ i.disable_mpls()
+ super(TestIP6LoadBalance, self).tearDown()
+
+ def pg_send(self, input, pkts):
+ self.vapi.cli("clear trace")
+ input.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ def send_and_expect_load_balancing(self, input, pkts, outputs):
+ self.pg_send(input, pkts)
+ for oo in outputs:
+ rx = oo._get_capture(1)
+ self.assertNotEqual(0, len(rx))
+
+ def send_and_expect_one_itf(self, input, pkts, itf):
+ self.pg_send(input, pkts)
+ rx = itf.get_capture(len(pkts))
+
+ def test_ip6_load_balance(self):
+ """ IPv6 Load-Balancing """
+
+ #
+ # An array of packets that differ only in the destination port
+ # - IP only
+ # - MPLS EOS
+ # - MPLS non-EOS
+ # - MPLS non-EOS with an entropy label
+ #
+ port_ip_pkts = []
+ port_mpls_pkts = []
+ port_mpls_neos_pkts = []
+ port_ent_pkts = []
+
+ #
+ # An array of packets that differ only in the source address
+ #
+ src_ip_pkts = []
+ src_mpls_pkts = []
+
+ for ii in range(NUM_PKTS):
+ port_ip_hdr = (
+ IPv6(dst="3000::1", src="3000:1::1") /
+ inet6.UDP(sport=1234, dport=1234 + ii) /
+ Raw('\xa5' * 100))
+ port_ip_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ port_ip_hdr))
+ port_mpls_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=66, ttl=2) /
+ port_ip_hdr))
+ port_mpls_neos_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=67, ttl=2) /
+ MPLS(label=77, ttl=2) /
+ port_ip_hdr))
+ port_ent_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=67, ttl=2) /
+ MPLS(label=14, ttl=2) /
+ MPLS(label=999, ttl=2) /
+ port_ip_hdr))
+ src_ip_hdr = (
+ IPv6(dst="3000::1", src="3000:1::%d" % ii) /
+ inet6.UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+ src_ip_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ src_ip_hdr))
+ src_mpls_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=66, ttl=2) /
+ src_ip_hdr))
+
+ #
+ # A route for the IP packets
+ #
+ route_3000_1 = VppIpRoute(self, "3000::1", 128,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index),
+ VppRoutePath(self.pg2.remote_ip6,
+ self.pg2.sw_if_index)])
+ route_3000_1.add_vpp_config()
+
+ #
+ # a local-label for the EOS packets
+ #
+ binding = VppMplsIpBind(self, 66, "3000::1", 128, is_ip6=1)
+ binding.add_vpp_config()
+
+ #
+ # An MPLS route for the non-EOS packets
+ #
+ route_67 = VppMplsRoute(self, 67, 0,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ labels=[67]),
+ VppRoutePath(self.pg2.remote_ip6,
+ self.pg2.sw_if_index,
+ labels=[67])])
+ route_67.add_vpp_config()
+
+ #
+ # inject the packet on pg0 - expect load-balancing across the 2 paths
+ # - since the default hash config is to use IP src,dst and port
+ # src,dst
+ # We are not going to ensure equal amounts of packets across each link,
+ # since the hash algorithm is statistical and therefore this can never
+ # be guaranteed. But with 64 different packets we do expect some
+ # balancing. So instead just ensure there is traffic on each link.
+ #
+ self.send_and_expect_load_balancing(self.pg0, port_ip_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, src_ip_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, port_mpls_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, port_mpls_neos_pkts,
+ [self.pg1, self.pg2])
+
+ #
+ # The packets with Entropy label in should not load-balance,
+ # since the Entropy value is fixed.
+ #
+ self.send_and_expect_one_itf(self.pg0, port_ent_pkts, self.pg1)
+
+ #
+ # change the flow hash config so it's only IP src,dst
+ # - now only the stream with differing source address will
+ # load-balance
+ #
+ self.vapi.set_ip_flow_hash(vrf_id=0, src=1, dst=1, sport=0, dport=0,
+ is_ipv6=1)
+
+ self.send_and_expect_load_balancing(self.pg0, src_ip_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_one_itf(self.pg0, port_ip_pkts, self.pg2)
+
+ #
+ # change the flow hash config back to defaults
+ #
+ self.vapi.set_ip_flow_hash(vrf_id=0, src=1, dst=1, sport=1, dport=1,
+ is_ipv6=1)
+
+ #
+ # Recursive prefixes
+ # - testing that 2 stages of load-balancing occurs and there is no
+ # polarisation (i.e. only 2 of 4 paths are used)
+ #
+ port_pkts = []
+ src_pkts = []
+
+ for ii in range(257):
+ port_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(dst="4000::1",
+ src="4000:1::1") /
+ inet6.UDP(sport=1234,
+ dport=1234 + ii) /
+ Raw('\xa5' * 100)))
+ src_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(dst="4000::1",
+ src="4000:1::%d" % ii) /
+ inet6.UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100)))
+
+ route_3000_2 = VppIpRoute(self, "3000::2", 128,
+ [VppRoutePath(self.pg3.remote_ip6,
+ self.pg3.sw_if_index),
+ VppRoutePath(self.pg4.remote_ip6,
+ self.pg4.sw_if_index)])
+ route_3000_2.add_vpp_config()
+
+ route_4000_1 = VppIpRoute(self, "4000::1", 128,
+ [VppRoutePath("3000::1",
+ 0xffffffff),
+ VppRoutePath("3000::2",
+ 0xffffffff)])
+ route_4000_1.add_vpp_config()
+
+ #
+ # inject the packet on pg0 - expect load-balancing across all 4 paths
+ #
+ self.vapi.cli("clear trace")
+ self.send_and_expect_load_balancing(self.pg0, port_pkts,
+ [self.pg1, self.pg2,
+ self.pg3, self.pg4])
+ self.send_and_expect_load_balancing(self.pg0, src_pkts,
+ [self.pg1, self.pg2,
+ self.pg3, self.pg4])
+
+ #
+ # Recursive prefixes
+ # - testing that 2 stages of load-balancing no choices
+ #
+ port_pkts = []
+
+ for ii in range(257):
+ port_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(dst="6000::1",
+ src="6000:1::1") /
+ inet6.UDP(sport=1234,
+ dport=1234 + ii) /
+ Raw('\xa5' * 100)))
+
+ route_5000_2 = VppIpRoute(self, "5000::2", 128,
+ [VppRoutePath(self.pg3.remote_ip6,
+ self.pg3.sw_if_index)])
+ route_5000_2.add_vpp_config()
+
+ route_6000_1 = VppIpRoute(self, "6000::1", 128,
+ [VppRoutePath("5000::2",
+ 0xffffffff)])
+ route_6000_1.add_vpp_config()
+
+ #
+ # inject the packet on pg0 - expect load-balancing across all 4 paths
+ #
+ self.vapi.cli("clear trace")
+ self.send_and_expect_one_itf(self.pg0, port_pkts, self.pg3)
+
+
+class TestIP6Punt(VppTestCase):
+ """ IPv6 Punt Police/Redirect """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestIP6Punt, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestIP6Punt, cls).tearDownClass()
+
+ def setUp(self):
+ super(TestIP6Punt, self).setUp()
+
+ self.create_pg_interfaces(range(4))
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip6()
+ i.resolve_ndp()
+
+ def tearDown(self):
+ super(TestIP6Punt, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip6()
+ i.admin_down()
+
+ def test_ip_punt(self):
+ """ IP6 punt police and redirect """
+
+ p = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src=self.pg0.remote_ip6,
+ dst=self.pg0.local_ip6) /
+ inet6.TCP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ pkts = p * 1025
+
+ #
+ # Configure a punt redirect via pg1.
+ #
+ nh_addr = self.pg1.remote_ip6
+ self.vapi.ip_punt_redirect(self.pg0.sw_if_index,
+ self.pg1.sw_if_index,
+ nh_addr)
+
+ self.send_and_expect(self.pg0, pkts, self.pg1)
+
+ #
+ # add a policer
+ #
+ policer = self.vapi.policer_add_del(b"ip6-punt", 400, 0, 10, 0,
+ rate_type=1)
+ self.vapi.ip_punt_police(policer.policer_index, is_ip6=1)
+
+ self.vapi.cli("clear trace")
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ #
+ # the number of packet received should be greater than 0,
+ # but not equal to the number sent, since some were policed
+ #
+ rx = self.pg1._get_capture(1)
+ self.assertGreater(len(rx), 0)
+ self.assertLess(len(rx), len(pkts))
+
+ #
+ # remove the policer. back to full rx
+ #
+ self.vapi.ip_punt_police(policer.policer_index, is_add=0, is_ip6=1)
+ self.vapi.policer_add_del(b"ip6-punt", 400, 0, 10, 0,
+ rate_type=1, is_add=0)
+ self.send_and_expect(self.pg0, pkts, self.pg1)
+
+ #
+ # remove the redirect. expect full drop.
+ #
+ self.vapi.ip_punt_redirect(self.pg0.sw_if_index,
+ self.pg1.sw_if_index,
+ nh_addr,
+ is_add=0)
+ self.send_and_assert_no_replies(self.pg0, pkts,
+ "IP no punt config")
+
+ #
+ # Add a redirect that is not input port selective
+ #
+ self.vapi.ip_punt_redirect(0xffffffff,
+ self.pg1.sw_if_index,
+ nh_addr)
+ self.send_and_expect(self.pg0, pkts, self.pg1)
+
+ self.vapi.ip_punt_redirect(0xffffffff,
+ self.pg1.sw_if_index,
+ nh_addr,
+ is_add=0)
+
+ def test_ip_punt_dump(self):
+ """ IP6 punt redirect dump"""
+
+ #
+ # Configure a punt redirects
+ #
+ nh_addr = self.pg3.remote_ip6
+ self.vapi.ip_punt_redirect(self.pg0.sw_if_index,
+ self.pg3.sw_if_index,
+ nh_addr)
+ self.vapi.ip_punt_redirect(self.pg1.sw_if_index,
+ self.pg3.sw_if_index,
+ nh_addr)
+ self.vapi.ip_punt_redirect(self.pg2.sw_if_index,
+ self.pg3.sw_if_index,
+ '0::0')
+
+ #
+ # Dump pg0 punt redirects
+ #
+ punts = self.vapi.ip_punt_redirect_dump(self.pg0.sw_if_index,
+ is_ipv6=1)
+ for p in punts:
+ self.assertEqual(p.punt.rx_sw_if_index, self.pg0.sw_if_index)
+
+ #
+ # Dump punt redirects for all interfaces
+ #
+ punts = self.vapi.ip_punt_redirect_dump(0xffffffff, is_ipv6=1)
+ self.assertEqual(len(punts), 3)
+ for p in punts:
+ self.assertEqual(p.punt.tx_sw_if_index, self.pg3.sw_if_index)
+ self.assertNotEqual(punts[1].punt.nh, self.pg3.remote_ip6)
+ self.assertEqual(str(punts[2].punt.nh), '::')
+
+
+class TestIPDeag(VppTestCase):
+ """ IPv6 Deaggregate Routes """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestIPDeag, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestIPDeag, cls).tearDownClass()
+
+ def setUp(self):
+ super(TestIPDeag, self).setUp()
+
+ self.create_pg_interfaces(range(3))
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip6()
+ i.resolve_ndp()
+
+ def tearDown(self):
+ super(TestIPDeag, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip6()
+ i.admin_down()
+
+ def test_ip_deag(self):
+ """ IP Deag Routes """
+
+ #
+ # Create a table to be used for:
+ # 1 - another destination address lookup
+ # 2 - a source address lookup
+ #
+ table_dst = VppIpTable(self, 1, is_ip6=1)
+ table_src = VppIpTable(self, 2, is_ip6=1)
+ table_dst.add_vpp_config()
+ table_src.add_vpp_config()
+
+ #
+ # Add a route in the default table to point to a deag/
+ # second lookup in each of these tables
+ #
+ route_to_dst = VppIpRoute(self, "1::1", 128,
+ [VppRoutePath("::",
+ 0xffffffff,
+ nh_table_id=1)])
+ route_to_src = VppIpRoute(
+ self, "1::2", 128,
+ [VppRoutePath("::",
+ 0xffffffff,
+ nh_table_id=2,
+ type=FibPathType.FIB_PATH_TYPE_SOURCE_LOOKUP)])
+
+ route_to_dst.add_vpp_config()
+ route_to_src.add_vpp_config()
+
+ #
+ # packets to these destination are dropped, since they'll
+ # hit the respective default routes in the second table
+ #
+ p_dst = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src="5::5", dst="1::1") /
+ inet6.TCP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+ p_src = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src="2::2", dst="1::2") /
+ inet6.TCP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+ pkts_dst = p_dst * 257
+ pkts_src = p_src * 257
+
+ self.send_and_assert_no_replies(self.pg0, pkts_dst,
+ "IP in dst table")
+ self.send_and_assert_no_replies(self.pg0, pkts_src,
+ "IP in src table")
+
+ #
+ # add a route in the dst table to forward via pg1
+ #
+ route_in_dst = VppIpRoute(self, "1::1", 128,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index)],
+ table_id=1)
+ route_in_dst.add_vpp_config()
+
+ self.send_and_expect(self.pg0, pkts_dst, self.pg1)
+
+ #
+ # add a route in the src table to forward via pg2
+ #
+ route_in_src = VppIpRoute(self, "2::2", 128,
+ [VppRoutePath(self.pg2.remote_ip6,
+ self.pg2.sw_if_index)],
+ table_id=2)
+ route_in_src.add_vpp_config()
+ self.send_and_expect(self.pg0, pkts_src, self.pg2)
+
+ #
+ # loop in the lookup DP
+ #
+ route_loop = VppIpRoute(self, "3::3", 128,
+ [VppRoutePath("::",
+ 0xffffffff)])
+ route_loop.add_vpp_config()
+
+ p_l = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src="3::4", dst="3::3") /
+ inet6.TCP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.send_and_assert_no_replies(self.pg0, p_l * 257,
+ "IP lookup loop")
+
+
+class TestIP6Input(VppTestCase):
+ """ IPv6 Input Exception Test Cases """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestIP6Input, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestIP6Input, cls).tearDownClass()
+
+ def setUp(self):
+ super(TestIP6Input, self).setUp()
+
+ self.create_pg_interfaces(range(2))
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip6()
+ i.resolve_ndp()
+
+ def tearDown(self):
+ super(TestIP6Input, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip6()
+ i.admin_down()
+
+ def test_ip_input_icmp_reply(self):
+ """ IP6 Input Exception - Return ICMP (3,0) """
+ #
+ # hop limit - ICMP replies
+ #
+ p_version = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src=self.pg0.remote_ip6,
+ dst=self.pg1.remote_ip6,
+ hlim=1) /
+ inet6.UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ rx = self.send_and_expect(self.pg0, p_version * NUM_PKTS, self.pg0)
+ rx = rx[0]
+ icmp = rx[ICMPv6TimeExceeded]
+
+ # 0: "hop limit exceeded in transit",
+ self.assertEqual((icmp.type, icmp.code), (3, 0))
+
+ icmpv6_data = '\x0a' * 18
+ all_0s = "::"
+ all_1s = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"
+
+ @parameterized.expand([
+ # Name, src, dst, l4proto, msg, timeout
+ ("src='iface', dst='iface'", None, None,
+ inet6.UDP(sport=1234, dport=1234), "funky version", None),
+ ("src='All 0's', dst='iface'", all_0s, None,
+ ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
+ ("src='iface', dst='All 0's'", None, all_0s,
+ ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
+ ("src='All 1's', dst='iface'", all_1s, None,
+ ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
+ ("src='iface', dst='All 1's'", None, all_1s,
+ ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
+ ("src='All 1's', dst='All 1's'", all_1s, all_1s,
+ ICMPv6EchoRequest(id=0xb, seq=5, data=icmpv6_data), None, 0.1),
+
+ ])
+ def test_ip_input_no_replies(self, name, src, dst, l4, msg, timeout):
+
+ self._testMethodDoc = 'IPv6 Input Exception - %s' % name
+
+ p_version = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src=src or self.pg0.remote_ip6,
+ dst=dst or self.pg1.remote_ip6,
+ version=3) /
+ l4 /
+ Raw('\xa5' * 100))
+
+ self.send_and_assert_no_replies(self.pg0, p_version * NUM_PKTS,
+ remark=msg or "",
+ timeout=timeout)
+
+ def test_hop_by_hop(self):
+ """ Hop-by-hop header test """
+
+ p = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
+ IPv6ExtHdrHopByHop() /
+ inet6.UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+