udp: fix inner packet checksum calculation in udp-encap
[vpp.git] / test / test_ip4.py
index c89d546..3a48274 100644 (file)
@@ -7,6 +7,7 @@ import unittest
 import scapy.compat
 from scapy.contrib.mpls import MPLS
 from scapy.layers.inet import IP, UDP, TCP, ICMP, icmptypes, icmpcodes
 import scapy.compat
 from scapy.contrib.mpls import MPLS
 from scapy.layers.inet import IP, UDP, TCP, ICMP, icmptypes, icmpcodes
+from scapy.layers.inet6 import IPv6
 from scapy.layers.l2 import Ether, Dot1Q, ARP
 from scapy.packet import Raw
 from six import moves
 from scapy.layers.l2 import Ether, Dot1Q, ARP
 from scapy.packet import Raw
 from six import moves
@@ -18,9 +19,9 @@ from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpMRoute, \
     VppMRoutePath, VppMplsIpBind, \
     VppMplsTable, VppIpTable, FibPathType, find_route, \
     VppIpInterfaceAddress, find_route_in_dump, find_mroute_in_dump
     VppMRoutePath, VppMplsIpBind, \
     VppMplsTable, VppIpTable, FibPathType, find_route, \
     VppIpInterfaceAddress, find_route_in_dump, find_mroute_in_dump
-from vpp_ip import VppIpPuntPolicer, VppIpPuntRedirect
+from vpp_ip import VppIpPuntPolicer, VppIpPuntRedirect, VppIpPathMtu
 from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
 from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
-from vpp_papi import VppEnum
+from vpp_papi import vpp_papi, VppEnum
 from vpp_neighbor import VppNeighbor
 from vpp_lo_interface import VppLoInterface
 from vpp_policer import VppPolicer, PolicerAction
 from vpp_neighbor import VppNeighbor
 from vpp_lo_interface import VppLoInterface
 from vpp_policer import VppPolicer, PolicerAction
@@ -1118,24 +1119,6 @@ class TestIPLoadBalance(VppTestCase):
             i.admin_down()
         super(TestIPLoadBalance, self).tearDown()
 
             i.admin_down()
         super(TestIPLoadBalance, self).tearDown()
 
-    def send_and_expect_load_balancing(self, input, pkts, outputs):
-        self.vapi.cli("clear trace")
-        input.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        rxs = []
-        for oo in outputs:
-            rx = oo._get_capture(1)
-            self.assertNotEqual(0, len(rx))
-            rxs.append(rx)
-        return rxs
-
-    def send_and_expect_one_itf(self, input, pkts, itf):
-        input.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        rx = itf.get_capture(len(pkts))
-
     def total_len(self, rxs):
         n = 0
         for rx in rxs:
     def total_len(self, rxs):
         n = 0
         for rx in rxs:
@@ -1243,7 +1226,7 @@ class TestIPLoadBalance(VppTestCase):
         self.send_and_expect_load_balancing(self.pg0, src_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_one_itf(self.pg0, port_ip_pkts, self.pg2)
+        self.send_and_expect_only(self.pg0, port_ip_pkts, self.pg2)
 
         #
         # change the flow hash config back to defaults
 
         #
         # change the flow hash config back to defaults
@@ -1367,7 +1350,7 @@ class TestIPLoadBalance(VppTestCase):
         # inject the packet on pg0 - rx only on via routes output interface
         #
         self.vapi.cli("clear trace")
         # inject the packet on pg0 - rx only on via routes output interface
         #
         self.vapi.cli("clear trace")
-        self.send_and_expect_one_itf(self.pg0, port_pkts, self.pg3)
+        self.send_and_expect_only(self.pg0, port_pkts, self.pg3)
 
         #
         # Add a LB route in the presence of a down link - expect no
 
         #
         # Add a LB route in the presence of a down link - expect no
@@ -1390,7 +1373,7 @@ class TestIPLoadBalance(VppTestCase):
                              UDP(sport=1234, dport=1234 + ii) /
                              Raw(b'\xa5' * 100))
 
                              UDP(sport=1234, dport=1234 + ii) /
                              Raw(b'\xa5' * 100))
 
-        self.send_and_expect_one_itf(self.pg0, port_pkts, self.pg4)
+        self.send_and_expect_only(self.pg0, port_pkts, self.pg4)
 
         # bring the link back up
         self.pg3.link_up()
 
         # bring the link back up
         self.pg3.link_up()
@@ -1478,6 +1461,20 @@ class IPPuntSetup(object):
 
         self.vapi.set_punt(is_add=1, punt=punt_udp)
 
 
         self.vapi.set_punt(is_add=1, punt=punt_udp)
 
+        af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
+        punt_udp = {
+            'type': pt_l4,
+            'punt': {
+                'l4': {
+                    'af': af_ip6,
+                    'protocol': udp_proto,
+                    'port': 1236,
+                }
+            }
+        }
+
+        self.vapi.set_punt(is_add=1, punt=punt_udp)
+
         self.pkt = (Ether(src=self.pg0.remote_mac,
                           dst=self.pg0.local_mac) /
                     IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
         self.pkt = (Ether(src=self.pg0.remote_mac,
                           dst=self.pg0.local_mac) /
                     IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
@@ -1494,12 +1491,31 @@ class TestIPPunt(IPPuntSetup, VppTestCase):
     """ IPv4 Punt Police/Redirect """
 
     def setUp(self):
     """ IPv4 Punt Police/Redirect """
 
     def setUp(self):
-        super(TestIPPunt, self).setUp()
-        super(TestIPPunt, self).punt_setup()
+        super().setUp()
+        super().punt_setup()
 
     def tearDown(self):
 
     def tearDown(self):
-        super(TestIPPunt, self).punt_teardown()
-        super(TestIPPunt, self).tearDown()
+        super().punt_teardown()
+        super().tearDown()
+
+    def test_ip_punt_api_validation(self):
+        """ IP punt API parameter validation """
+
+        nh_addr = self.pg1.remote_ip4
+        punt = {"rx_sw_if_index": self.pg0.sw_if_index,
+                "af": VppEnum.vl_api_address_family_t.ADDRESS_IP4,
+                "n_paths": 1000000,
+                "paths": []}
+
+        with self.assertRaises(vpp_papi.VPPIOError):
+            self.vapi.add_del_ip_punt_redirect_v2(punt=punt, is_add=True)
+
+        punt = {"rx_sw_if_index": self.pg0.sw_if_index,
+                "af": VppEnum.vl_api_address_family_t.ADDRESS_IP4,
+                "n_paths": 0,
+                "paths": []}
+
+        self.vapi.add_del_ip_punt_redirect_v2(punt=punt, is_add=True)
 
     def test_ip_punt(self):
         """ IP punt police and redirect """
 
     def test_ip_punt(self):
         """ IP punt police and redirect """
@@ -1568,6 +1584,172 @@ class TestIPPunt(IPPuntSetup, VppTestCase):
         self.send_and_expect(self.pg0, pkts, self.pg1)
         ip_punt_redirect.remove_vpp_config()
 
         self.send_and_expect(self.pg0, pkts, self.pg1)
         ip_punt_redirect.remove_vpp_config()
 
+    def test_ip_punt_vrf(self):
+        """ IP punt/local with VRFs """
+
+        # use a punt redirect to test if for-us  packets are accepted
+        pkts = self.pkt * 1025
+
+        vlans_pg0 = [VppDot1QSubint(self, self.pg0, v)
+                     for v in range(100, 104)]
+        vlans_pg1 = [VppDot1QSubint(self, self.pg1, v)
+                     for v in range(100, 104)]
+        tbl4 = [VppIpTable(self, v).add_vpp_config()
+                for v in range(100, 104)]
+        tbl6 = [VppIpTable(self, v, True).add_vpp_config()
+                for v in range(100, 104)]
+
+        for v in vlans_pg0 + vlans_pg1:
+            v.admin_up()
+            v.set_table_ip4(v.vlan)
+            v.set_table_ip6(v.vlan)
+            v.config_ip4()
+            v.config_ip6()
+            v.resolve_arp()
+            v.resolve_ndp()
+
+        [VppIpPuntRedirect
+         (self,
+          vlans_pg0[i].sw_if_index,
+          vlans_pg1[i].sw_if_index,
+          vlans_pg1[i].remote_ip4).add_vpp_config()
+         for i in range(4)]
+        [VppIpPuntRedirect
+         (self,
+          vlans_pg0[i].sw_if_index,
+          vlans_pg1[i].sw_if_index,
+          vlans_pg1[i].remote_ip6).add_vpp_config()
+         for i in range(4)]
+
+        pkts = [(Ether(src=self.pg0.remote_mac,
+                       dst=self.pg0.local_mac) /
+                 Dot1Q(vlan=i.vlan) /
+                 IP(src=i.remote_ip4,
+                    dst=i.local_ip4) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(b'\xa5' * 100))
+                for i in vlans_pg0]
+
+        self.send_and_expect(self.pg0, pkts, self.pg1)
+
+        #
+        # IPv4
+        #
+
+        # we reject packets for source addresses in the wrong vlan/VRF
+        pkts = [(Ether(src=self.pg0.remote_mac,
+                       dst=self.pg0.local_mac) /
+                 Dot1Q(vlan=i.vlan) /
+                 IP(src="1.1.1.1",
+                    dst=i.local_ip4) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(b'\xa5' * 100))
+                for i in vlans_pg0]
+        # single and dual loop
+        self.send_and_assert_no_replies(self.pg0, [pkts[0]])
+        self.send_and_assert_no_replies(self.pg0, pkts)
+
+        self.assert_error_counter_equal(
+            "/err/ip4-local/ip4 source lookup miss",
+            len(pkts) + 1)
+
+        # using the same source in different tables, should reject
+        # for the table that the source is not present in
+        # the first packet in the stream is drop
+        pkts = [(Ether(src=self.pg0.remote_mac,
+                       dst=self.pg0.local_mac) /
+                 Dot1Q(vlan=i.vlan) /
+                 IP(src=vlans_pg0[0].remote_ip4,
+                    dst=i.local_ip4) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(b'\xa5' * 100))
+                for i in vlans_pg0]
+        # single loop accept and drop
+        # followed by both in the same frame/loop
+        self.send_and_expect(self.pg0, [pkts[0]], self.pg1)
+        self.send_and_assert_no_replies(self.pg0, [pkts[1]])
+        self.send_and_expect(self.pg0, pkts * 4, self.pg1, n_rx=4)
+
+        # using the same source in different tables, should reject
+        # for the table that the source is not present in
+        # the first packet in the stream is accept
+        pkts = [(Ether(src=self.pg0.remote_mac,
+                       dst=self.pg0.local_mac) /
+                 Dot1Q(vlan=i.vlan) /
+                 IP(src=vlans_pg0[3].remote_ip4,
+                    dst=i.local_ip4) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(b'\xa5' * 100))
+                for i in vlans_pg0]
+
+        # single loop accept and drop
+        # followed by both in the same frame/loop
+        self.send_and_expect(self.pg0, [pkts[3]], self.pg1)
+        self.send_and_assert_no_replies(self.pg0, [pkts[1]])
+        self.send_and_expect(self.pg0, pkts * 4, self.pg1, n_rx=4)
+
+        #
+        # IPv6
+        #
+
+        # we reject packets for source addresses in the wrong vlan/VRF
+        pkts = [(Ether(src=self.pg0.remote_mac,
+                       dst=self.pg0.local_mac) /
+                 Dot1Q(vlan=i.vlan) /
+                 IPv6(src="1::1",
+                      dst=i.local_ip6) /
+                 UDP(sport=1236, dport=1236) /
+                 Raw(b'\xa5' * 100))
+                for i in vlans_pg0]
+        # single and dual loop
+        self.send_and_assert_no_replies(self.pg0, [pkts[0]])
+        self.send_and_assert_no_replies(self.pg0, pkts)
+
+        self.assert_error_counter_equal(
+            "/err/ip6-input/ip6 source lookup miss",
+            len(pkts) + 1)
+
+        # using the same source in different tables, should reject
+        # for the table that the source is not present in
+        # the first packet in the stream is drop
+        pkts = [(Ether(src=self.pg0.remote_mac,
+                       dst=self.pg0.local_mac) /
+                 Dot1Q(vlan=i.vlan) /
+                 IPv6(src=vlans_pg0[0].remote_ip6,
+                      dst=i.local_ip6) /
+                 UDP(sport=1236, dport=1236) /
+                 Raw(b'\xa5' * 100))
+                for i in vlans_pg0]
+        # single loop accept and drop
+        # followed by both in the same frame/loop
+        self.send_and_expect(self.pg0, [pkts[0]], self.pg1)
+        self.send_and_assert_no_replies(self.pg0, [pkts[1]])
+        self.send_and_expect(self.pg0, pkts * 4, self.pg1, n_rx=4)
+
+        # using the same source in different tables, should reject
+        # for the table that the source is not present in
+        # the first packet in the stream is accept
+        pkts = [(Ether(src=self.pg0.remote_mac,
+                       dst=self.pg0.local_mac) /
+                 Dot1Q(vlan=i.vlan) /
+                 IPv6(src=vlans_pg0[3].remote_ip6,
+                      dst=i.local_ip6) /
+                 UDP(sport=1236, dport=1236) /
+                 Raw(b'\xa5' * 100))
+                for i in vlans_pg0]
+
+        # single loop accept and drop
+        # followed by both in the same frame/loop
+        self.send_and_expect(self.pg0, [pkts[3]], self.pg1)
+        self.send_and_assert_no_replies(self.pg0, [pkts[1]])
+        self.send_and_expect(self.pg0, pkts * 4, self.pg1, n_rx=4)
+
+        for v in vlans_pg0 + vlans_pg1:
+            v.unconfig_ip4()
+            v.unconfig_ip6()
+            v.set_table_ip4(0)
+            v.set_table_ip6(0)
+
     def test_ip_punt_dump(self):
         """ IP4 punt redirect dump"""
 
     def test_ip_punt_dump(self):
         """ IP4 punt redirect dump"""
 
@@ -1605,7 +1787,7 @@ class TestIPPunt(IPPuntSetup, VppTestCase):
 
 class TestIPPuntHandoff(IPPuntSetup, VppTestCase):
     """ IPv4 Punt Policer thread handoff """
 
 class TestIPPuntHandoff(IPPuntSetup, VppTestCase):
     """ IPv4 Punt Policer thread handoff """
-    worker_config = "workers 2"
+    vpp_worker_count = 2
 
     def setUp(self):
         super(TestIPPuntHandoff, self).setUp()
 
     def setUp(self):
         super(TestIPPuntHandoff, self).setUp()
@@ -1946,16 +2128,15 @@ class TestIPInput(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw(b'\xa5' * 100))
 
                  UDP(sport=1234, dport=1234) /
                  Raw(b'\xa5' * 100))
 
-        rx = self.send_and_expect(self.pg0, p_ttl * NUM_PKTS, self.pg0)
+        rxs = self.send_and_expect_some(self.pg0, p_ttl * NUM_PKTS, self.pg0)
 
 
-        rx = rx[0]
-        icmp = rx[ICMP]
-
-        self.assertEqual(icmptypes[icmp.type], "time-exceeded")
-        self.assertEqual(icmpcodes[icmp.type][icmp.code],
-                         "ttl-zero-during-transit")
-        self.assertEqual(icmp.src, self.pg0.remote_ip4)
-        self.assertEqual(icmp.dst, self.pg1.remote_ip4)
+        for rx in rxs:
+            icmp = rx[ICMP]
+            self.assertEqual(icmptypes[icmp.type], "time-exceeded")
+            self.assertEqual(icmpcodes[icmp.type][icmp.code],
+                             "ttl-zero-during-transit")
+            self.assertEqual(icmp.src, self.pg0.remote_ip4)
+            self.assertEqual(icmp.dst, self.pg1.remote_ip4)
 
         #
         # MTU exceeded
 
         #
         # MTU exceeded
@@ -1970,15 +2151,16 @@ class TestIPInput(VppTestCase):
 
         self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1500, 0, 0, 0])
 
 
         self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1500, 0, 0, 0])
 
-        rx = self.send_and_expect(self.pg0, p_mtu * NUM_PKTS, self.pg0)
-        rx = rx[0]
-        icmp = rx[ICMP]
+        rxs = self.send_and_expect_some(self.pg0, p_mtu * NUM_PKTS, self.pg0)
 
 
-        self.assertEqual(icmptypes[icmp.type], "dest-unreach")
-        self.assertEqual(icmpcodes[icmp.type][icmp.code],
-                         "fragmentation-needed")
-        self.assertEqual(icmp.src, self.pg0.remote_ip4)
-        self.assertEqual(icmp.dst, self.pg1.remote_ip4)
+        for rx in rxs:
+            icmp = rx[ICMP]
+            self.assertEqual(icmptypes[icmp.type], "dest-unreach")
+            self.assertEqual(icmpcodes[icmp.type][icmp.code],
+                             "fragmentation-needed")
+            self.assertEqual(icmp.nexthopmtu, 1500)
+            self.assertEqual(icmp.src, self.pg0.remote_ip4)
+            self.assertEqual(icmp.dst, self.pg1.remote_ip4)
 
         self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [2500, 0, 0, 0])
         rx = self.send_and_expect(self.pg0, p_mtu * NUM_PKTS, self.pg1)
 
         self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [2500, 0, 0, 0])
         rx = self.send_and_expect(self.pg0, p_mtu * NUM_PKTS, self.pg1)
@@ -2565,5 +2747,308 @@ class TestIP4Replace(VppTestCase):
             self.assertTrue(pfx.query_vpp_config())
 
 
             self.assertTrue(pfx.query_vpp_config())
 
 
+class TestIPv4PathMTU(VppTestCase):
+    """ IPv4 Path MTU """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestIPv4PathMTU, cls).setUpClass()
+
+        cls.create_pg_interfaces(range(2))
+
+        # setup all interfaces
+        for i in cls.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.resolve_arp()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestIPv4PathMTU, cls).tearDownClass()
+
+    def test_path_mtu(self):
+        """ Path MTU """
+
+        #
+        # The goal here is not to test that fragmentation works correctly,
+        # that's done elsewhere, the intent is to ensure that the Path MTU
+        # settings are honoured.
+        #
+        self.vapi.cli("adjacency counters enable")
+
+        # set the interface MTU to a reasonable value
+        self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index,
+                                       [1800, 0, 0, 0])
+
+        self.pg1.generate_remote_hosts(4)
+
+        p_2k = (Ether(dst=self.pg0.local_mac,
+                      src=self.pg0.remote_mac) /
+                IP(src=self.pg0.remote_ip4,
+                   dst=self.pg1.remote_ip4) /
+                UDP(sport=1234, dport=5678) /
+                Raw(b'0xa' * 640))
+        p_1k = (Ether(dst=self.pg0.local_mac,
+                      src=self.pg0.remote_mac) /
+                IP(src=self.pg0.remote_ip4,
+                   dst=self.pg1.remote_ip4) /
+                UDP(sport=1234, dport=5678) /
+                Raw(b'0xa' * 320))
+
+        nbr = VppNeighbor(self,
+                          self.pg1.sw_if_index,
+                          self.pg1.remote_mac,
+                          self.pg1.remote_ip4).add_vpp_config()
+
+        # this is now the interface MTU frags
+        self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=2)
+        self.send_and_expect(self.pg0, [p_1k], self.pg1)
+
+        # drop the path MTU for this neighbour to below the interface MTU
+        # expect more frags
+        pmtu = VppIpPathMtu(self, self.pg1.remote_ip4, 900).add_vpp_config()
+
+        self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3)
+        self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2)
+
+        # print/format the adj delegate
+        self.logger.info(self.vapi.cli("sh adj 5"))
+
+        # increase the path MTU to more than the interface
+        # expect to use the interface MTU
+        pmtu.modify(8192)
+
+        self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=2)
+        self.send_and_expect(self.pg0, [p_1k], self.pg1)
+
+        # go back to an MTU from the path
+        # wrap the call around mark-n-sweep to enusre updates clear stale
+        self.vapi.ip_path_mtu_replace_begin()
+        pmtu.modify(900)
+        self.vapi.ip_path_mtu_replace_end()
+
+        self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3)
+        self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2)
+
+        # raise the interface's MTU
+        # should still use that of the path
+        self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index,
+                                       [2000, 0, 0, 0])
+        self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3)
+        self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2)
+
+        # set path high and interface low
+        pmtu.modify(2000)
+        self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index,
+                                       [900, 0, 0, 0])
+        self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3)
+        self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2)
+
+        # remove the path MTU using the mark-n-sweep semantics
+        self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index,
+                                       [1800, 0, 0, 0])
+        self.vapi.ip_path_mtu_replace_begin()
+        self.vapi.ip_path_mtu_replace_end()
+
+        self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=2)
+        self.send_and_expect(self.pg0, [p_1k], self.pg1)
+
+        #
+        # set path MTU for a neighbour that doesn't exist, yet
+        #
+        pmtu2 = VppIpPathMtu(self,
+                             self.pg1.remote_hosts[2].ip4,
+                             900).add_vpp_config()
+
+        p_2k = (Ether(dst=self.pg0.local_mac,
+                      src=self.pg0.remote_mac) /
+                IP(src=self.pg0.remote_ip4,
+                   dst=self.pg1.remote_hosts[2].ip4) /
+                UDP(sport=1234, dport=5678) /
+                Raw(b'0xa' * 640))
+        p_1k = (Ether(dst=self.pg0.local_mac,
+                      src=self.pg0.remote_mac) /
+                IP(src=self.pg0.remote_ip4,
+                   dst=self.pg1.remote_hosts[2].ip4) /
+                UDP(sport=1234, dport=5678) /
+                Raw(b'0xa' * 320))
+
+        nbr2 = VppNeighbor(self,
+                           self.pg1.sw_if_index,
+                           self.pg1.remote_hosts[2].mac,
+                           self.pg1.remote_hosts[2].ip4).add_vpp_config()
+
+        # should frag to the path MTU
+        self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3)
+        self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2)
+
+        # remove and re-add the neighbour
+        nbr2.remove_vpp_config()
+        nbr2.add_vpp_config()
+
+        # should frag to the path MTU
+        self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3)
+        self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2)
+
+        #
+        # set PMTUs for many peers
+        #
+        N_HOSTS = 16
+        self.pg1.generate_remote_hosts(16)
+        self.pg1.configure_ipv4_neighbors()
+
+        for h in range(N_HOSTS):
+            pmtu = VppIpPathMtu(self, self.pg1.remote_hosts[h].ip4, 900)
+            pmtu.add_vpp_config()
+            self.assertTrue(pmtu.query_vpp_config())
+
+        self.logger.info(self.vapi.cli("sh ip pmtu"))
+        dump = list(self.vapi.vpp.details_iter(self.vapi.ip_path_mtu_get))
+        self.assertEqual(N_HOSTS, len(dump))
+
+        for h in range(N_HOSTS):
+            p_2k[IP].dst = self.pg1.remote_hosts[h].ip4
+            p_1k[IP].dst = self.pg1.remote_hosts[h].ip4
+
+            # should frag to the path MTU
+            self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3)
+            self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2)
+
+
+class TestIPv4ItfRebind(VppTestCase):
+    """ IPv4 Interface Bind w/ attached routes """
+
+    def setUp(self):
+        super(TestIPv4ItfRebind, self).setUp()
+
+        self.create_pg_interfaces(range(3))
+
+    def tearDown(self):
+        super(TestIPv4ItfRebind, self).tearDown()
+
+    def test_rebind(self):
+        """ Import to no import """
+
+        TABLE_ID = 1
+        tbl = VppIpTable(self, TABLE_ID).add_vpp_config()
+        self.pg1.set_table_ip4(TABLE_ID)
+
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.resolve_arp()
+
+        # add an attached route via an pg0
+        # in a different table. this prefix should import
+        rt = VppIpRoute(self, self.pg0.local_ip4, 24,
+                        [VppRoutePath("0.0.0.0",
+                                      self.pg0.sw_if_index)],
+                        table_id=TABLE_ID).add_vpp_config()
+
+        p = (Ether(dst=self.pg1.local_mac,
+                   src=self.pg1.remote_mac) /
+             IP(src=self.pg1.remote_ip4,
+                dst=self.pg0.remote_ip4) /
+             UDP(sport=1234, dport=5678) /
+             Raw(b'0xa' * 640))
+
+        rx = self.send_and_expect(self.pg1, [p], self.pg0)
+        self.assertFalse(rx[0].haslayer(ARP))
+
+        # then bind pg0 to a new table
+        # so the prefix no longer imports
+        self.pg0.unconfig_ip4()
+        self.pg0.set_table_ip4(TABLE_ID)
+        self.pg0.config_ip4()
+        self.pg0.resolve_arp()
+
+        rx = self.send_and_expect(self.pg1, [p], self.pg0)
+        self.assertFalse(rx[0].haslayer(ARP))
+
+        # revert back to imported
+        self.pg0.unconfig_ip4()
+        self.pg0.set_table_ip4(0)
+        self.pg0.config_ip4()
+        self.pg0.resolve_arp()
+
+        rx = self.send_and_expect(self.pg1, [p], self.pg0)
+        self.assertFalse(rx[0].haslayer(ARP))
+
+        # cleanup
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.set_table_ip4(0)
+            i.admin_down()
+
+        rt.remove_vpp_config()
+        tbl.remove_vpp_config()
+
+    def test_delete(self):
+        """ Swap import tables """
+
+        TABLE_ID1 = 1
+        tbl1_4 = VppIpTable(self, TABLE_ID1).add_vpp_config()
+        tbl1_6 = VppIpTable(self, TABLE_ID1, True).add_vpp_config()
+        TABLE_ID2 = 2
+        tbl2_4 = VppIpTable(self, TABLE_ID2).add_vpp_config()
+        tbl2_6 = VppIpTable(self, TABLE_ID2, True).add_vpp_config()
+
+        # table mappings
+        self.pg1.set_table_ip4(TABLE_ID1)
+        self.pg1.set_table_ip6(TABLE_ID1)
+        self.pg2.set_table_ip4(TABLE_ID2)
+        self.pg2.set_table_ip6(TABLE_ID2)
+
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.resolve_arp()
+
+        # add an attached route in the default table via pg0
+        # this should import to table 1
+        rt4 = VppIpRoute(self, self.pg1.local_ip4, 24,
+                         [VppRoutePath("0.0.0.0",
+                                       self.pg1.sw_if_index)]).add_vpp_config()
+        rt6 = VppIpRoute(self, self.pg1.local_ip6, 64,
+                         [VppRoutePath("0.0.0.0",
+                                       self.pg1.sw_if_index)]).add_vpp_config()
+
+        p1 = (Ether(dst=self.pg0.local_mac,
+                    src=self.pg0.remote_mac) /
+              IP(src=self.pg1.remote_ip4,
+                 dst=self.pg1.remote_ip4) /
+              UDP(sport=1234, dport=5678) /
+              Raw(b'0xa' * 640))
+
+        # inject into table 0
+        rx = self.send_and_expect(self.pg0, [p1], self.pg1)
+        self.assertFalse(rx[0].haslayer(ARP))
+
+        # swap the attached interface to table 2
+        self.pg1.unconfig_ip4()
+        self.pg1.unconfig_ip6()
+        self.pg1.set_table_ip4(TABLE_ID2)
+        self.pg1.set_table_ip6(TABLE_ID2)
+        self.pg1.config_ip4()
+        self.pg1.config_ip6()
+        self.pg1.resolve_arp()
+
+        # delete table 1
+        tbl1_4.flush()
+        tbl1_6.flush()
+        tbl1_4.remove_vpp_config()
+        tbl1_6.remove_vpp_config()
+
+        rx = self.send_and_expect(self.pg0, [p1], self.pg1)
+        self.assertFalse(rx[0].haslayer(ARP))
+
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+            i.set_table_ip4(0)
+            i.set_table_ip6(0)
+            i.admin_down()
+
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)