ip-neighbor: Replace feature for the ip-neighbor data-base
[vpp.git] / test / test_neighbor.py
index 1167b26..cc1357c 100644 (file)
@@ -1,16 +1,25 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import unittest
 
 import unittest
+import os
 from socket import AF_INET, AF_INET6, inet_pton
 
 from framework import VppTestCase, VppTestRunner
 from vpp_neighbor import VppNeighbor, find_nbr
 from socket import AF_INET, AF_INET6, inet_pton
 
 from framework import VppTestCase, VppTestRunner
 from vpp_neighbor import VppNeighbor, find_nbr
-from vpp_ip_route import VppIpRoute, VppRoutePath, find_route
+from vpp_ip_route import VppIpRoute, VppRoutePath, find_route, \
+    VppIpTable, DpoProto, FibPathType
+from vpp_papi import VppEnum
 
 
+import scapy.compat
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether, ARP, Dot1Q
 from scapy.layers.inet import IP, UDP
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether, ARP, Dot1Q
 from scapy.layers.inet import IP, UDP
+from scapy.layers.inet6 import IPv6
 from scapy.contrib.mpls import MPLS
 from scapy.contrib.mpls import MPLS
+from scapy.layers.inet6 import IPv6
+
+
+NUM_PKTS = 67
 
 # not exported by scapy, so redefined here
 arp_opts = {"who-has": 1, "is-at": 2}
 
 # not exported by scapy, so redefined here
 arp_opts = {"who-has": 1, "is-at": 2}
@@ -19,6 +28,14 @@ arp_opts = {"who-has": 1, "is-at": 2}
 class ARPTestCase(VppTestCase):
     """ ARP Test Case """
 
 class ARPTestCase(VppTestCase):
     """ ARP Test Case """
 
+    @classmethod
+    def setUpClass(cls):
+        super(ARPTestCase, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(ARPTestCase, cls).tearDownClass()
+
     def setUp(self):
         super(ARPTestCase, self).setUp()
 
     def setUp(self):
         super(ARPTestCase, self).setUp()
 
@@ -39,11 +56,13 @@ class ARPTestCase(VppTestCase):
         self.pg1.config_ip6()
 
         # pg3 in a different VRF
         self.pg1.config_ip6()
 
         # pg3 in a different VRF
+        self.tbl = VppIpTable(self, 1)
+        self.tbl.add_vpp_config()
+
         self.pg3.set_table_ip4(1)
         self.pg3.config_ip4()
 
     def tearDown(self):
         self.pg3.set_table_ip4(1)
         self.pg3.config_ip4()
 
     def tearDown(self):
-        super(ARPTestCase, self).tearDown()
         self.pg0.unconfig_ip4()
         self.pg0.unconfig_ip6()
 
         self.pg0.unconfig_ip4()
         self.pg0.unconfig_ip6()
 
@@ -51,10 +70,13 @@ class ARPTestCase(VppTestCase):
         self.pg1.unconfig_ip6()
 
         self.pg3.unconfig_ip4()
         self.pg1.unconfig_ip6()
 
         self.pg3.unconfig_ip4()
+        self.pg3.set_table_ip4(0)
 
         for i in self.pg_interfaces:
             i.admin_down()
 
 
         for i in self.pg_interfaces:
             i.admin_down()
 
+        super(ARPTestCase, self).tearDown()
+
     def verify_arp_req(self, rx, smac, sip, dip):
         ether = rx[Ether]
         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
     def verify_arp_req(self, rx, smac, sip, dip):
         ether = rx[Ether]
         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
@@ -126,23 +148,31 @@ class ARPTestCase(VppTestCase):
         self.assertEqual(ip.src, sip)
         self.assertEqual(ip.dst, dip)
 
         self.assertEqual(ip.src, sip)
         self.assertEqual(ip.dst, dip)
 
-    def send_and_assert_no_replies(self, intf, pkts, remark):
-        intf.add_stream(pkts)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-        timeout = 1
-        for i in self.pg_interfaces:
-            i.get_capture(0, timeout=timeout)
-            i.assert_nothing_captured(remark=remark)
-            timeout = 0.1
-
     def test_arp(self):
         """ ARP """
 
         #
         # Generate some hosts on the LAN
         #
     def test_arp(self):
         """ ARP """
 
         #
         # Generate some hosts on the LAN
         #
-        self.pg1.generate_remote_hosts(10)
+        self.pg1.generate_remote_hosts(11)
+
+        #
+        # watch for:
+        #  - all neighbour events
+        #  - all neighbor events on pg1
+        #  - neighbor events for host[1] on pg1
+        #
+        self.vapi.want_ip_neighbor_events(enable=1,
+                                          pid=os.getpid())
+        self.vapi.want_ip_neighbor_events(enable=1,
+                                          pid=os.getpid(),
+                                          sw_if_index=self.pg1.sw_if_index)
+        self.vapi.want_ip_neighbor_events(enable=1,
+                                          pid=os.getpid(),
+                                          sw_if_index=self.pg1.sw_if_index,
+                                          ip=self.pg1.remote_hosts[1].ip4)
+
+        self.logger.info(self.vapi.cli("sh ip neighbor-watcher"))
 
         #
         # Send IP traffic to one of these unresolved hosts.
 
         #
         # Send IP traffic to one of these unresolved hosts.
@@ -172,6 +202,14 @@ class ARPTestCase(VppTestCase):
                               self.pg1.remote_hosts[1].mac,
                               self.pg1.remote_hosts[1].ip4)
         dyn_arp.add_vpp_config()
                               self.pg1.remote_hosts[1].mac,
                               self.pg1.remote_hosts[1].ip4)
         dyn_arp.add_vpp_config()
+        self.assertTrue(dyn_arp.query_vpp_config())
+
+        # this matches all of the listnerers
+        es = [self.vapi.wait_for_event(1, "ip_neighbor_event")
+              for i in range(3)]
+        for e in es:
+            self.assertEqual(str(e.neighbor.ip_address),
+                             self.pg1.remote_hosts[1].ip4)
 
         #
         # now we expect IP traffic forwarded
 
         #
         # now we expect IP traffic forwarded
@@ -203,6 +241,11 @@ class ARPTestCase(VppTestCase):
                                  self.pg1.remote_hosts[2].ip4,
                                  is_static=1)
         static_arp.add_vpp_config()
                                  self.pg1.remote_hosts[2].ip4,
                                  is_static=1)
         static_arp.add_vpp_config()
+        es = [self.vapi.wait_for_event(1, "ip_neighbor_event")
+              for i in range(2)]
+        for e in es:
+            self.assertEqual(str(e.neighbor.ip_address),
+                             self.pg1.remote_hosts[2].ip4)
 
         static_p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
                     IP(src=self.pg0.remote_ip4,
 
         static_p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
                     IP(src=self.pg0.remote_ip4,
@@ -222,6 +265,19 @@ class ARPTestCase(VppTestCase):
                        self.pg0.remote_ip4,
                        self.pg1._remote_hosts[2].ip4)
 
                        self.pg0.remote_ip4,
                        self.pg1._remote_hosts[2].ip4)
 
+        #
+        # remove all the listeners
+        #
+        self.vapi.want_ip_neighbor_events(enable=0,
+                                          pid=os.getpid())
+        self.vapi.want_ip_neighbor_events(enable=0,
+                                          pid=os.getpid(),
+                                          sw_if_index=self.pg1.sw_if_index)
+        self.vapi.want_ip_neighbor_events(enable=0,
+                                          pid=os.getpid(),
+                                          sw_if_index=self.pg1.sw_if_index,
+                                          ip=self.pg1.remote_hosts[1].ip4)
+
         #
         # flap the link. dynamic ARPs get flush, statics don't
         #
         #
         # flap the link. dynamic ARPs get flush, statics don't
         #
@@ -249,6 +305,8 @@ class ARPTestCase(VppTestCase):
                             self.pg1.local_ip4,
                             self.pg1._remote_hosts[1].ip4)
 
                             self.pg1.local_ip4,
                             self.pg1._remote_hosts[1].ip4)
 
+        self.assertFalse(dyn_arp.query_vpp_config())
+        self.assertTrue(static_arp.query_vpp_config())
         #
         # Send an ARP request from one of the so-far unlearned remote hosts
         #
         #
         # Send an ARP request from one of the so-far unlearned remote hosts
         #
@@ -300,6 +358,19 @@ class ARPTestCase(VppTestCase):
         #
         self.pg2.set_unnumbered(self.pg1.sw_if_index)
 
         #
         self.pg2.set_unnumbered(self.pg1.sw_if_index)
 
+        #
+        # test the unnumbered dump both by all interfaces and just the enabled
+        # one
+        #
+        unnum = self.vapi.ip_unnumbered_dump()
+        self.assertTrue(len(unnum))
+        self.assertEqual(unnum[0].ip_sw_if_index, self.pg1.sw_if_index)
+        self.assertEqual(unnum[0].sw_if_index, self.pg2.sw_if_index)
+        unnum = self.vapi.ip_unnumbered_dump(self.pg2.sw_if_index)
+        self.assertTrue(len(unnum))
+        self.assertEqual(unnum[0].ip_sw_if_index, self.pg1.sw_if_index)
+        self.assertEqual(unnum[0].sw_if_index, self.pg2.sw_if_index)
+
         #
         # We should respond to ARP requests for the unnumbered to address
         # once an attached route to the source is known
         #
         # We should respond to ARP requests for the unnumbered to address
         # once an attached route to the source is known
@@ -495,6 +566,65 @@ class ARPTestCase(VppTestCase):
                              self.pg1.local_ip4,
                              self.pg1._remote_hosts[9].ip4)
 
                              self.pg1.local_ip4,
                              self.pg1._remote_hosts[9].ip4)
 
+        #
+        # Add a hierarchy of routes for a host in the sub-net.
+        # Should still get an ARP resp since the cover is attached
+        #
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg1.remote_mac) /
+             ARP(op="who-has",
+                 hwsrc=self.pg1.remote_mac,
+                 pdst=self.pg1.local_ip4,
+                 psrc=self.pg1.remote_hosts[10].ip4))
+
+        r1 = VppIpRoute(self, self.pg1.remote_hosts[10].ip4, 30,
+                        [VppRoutePath(self.pg1.remote_hosts[10].ip4,
+                                      self.pg1.sw_if_index)])
+        r1.add_vpp_config()
+
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        rx = self.pg1.get_capture(1)
+        self.verify_arp_resp(rx[0],
+                             self.pg1.local_mac,
+                             self.pg1.remote_mac,
+                             self.pg1.local_ip4,
+                             self.pg1.remote_hosts[10].ip4)
+
+        r2 = VppIpRoute(self, self.pg1.remote_hosts[10].ip4, 32,
+                        [VppRoutePath(self.pg1.remote_hosts[10].ip4,
+                                      self.pg1.sw_if_index)])
+        r2.add_vpp_config()
+
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        rx = self.pg1.get_capture(1)
+        self.verify_arp_resp(rx[0],
+                             self.pg1.local_mac,
+                             self.pg1.remote_mac,
+                             self.pg1.local_ip4,
+                             self.pg1.remote_hosts[10].ip4)
+
+        #
+        # add an ARP entry that's not on the sub-net and so whose
+        # adj-fib fails the refinement check. then send an ARP request
+        # from that source
+        #
+        a1 = VppNeighbor(self,
+                         self.pg0.sw_if_index,
+                         self.pg0.remote_mac,
+                         "100.100.100.50")
+        a1.add_vpp_config()
+
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+             ARP(op="who-has",
+                 hwsrc=self.pg0.remote_mac,
+                 psrc="100.100.100.50",
+                 pdst=self.pg0.remote_ip4))
+        self.send_and_assert_no_replies(self.pg0, p,
+                                        "ARP req for from failed adj-fib")
+
         #
         # ERROR Cases
         #  1 - don't respond to ARP request for address not within the
         #
         # ERROR Cases
         #  1 - don't respond to ARP request for address not within the
@@ -536,7 +666,8 @@ class ARPTestCase(VppTestCase):
         #
         #  2 - don't respond to ARP request from an address not within the
         #      interface's sub-net
         #
         #  2 - don't respond to ARP request from an address not within the
         #      interface's sub-net
-        #
+        #   2b - to a proxied address
+        #   2c - not within a different interface's sub-net
         p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
              ARP(op="who-has",
                  hwsrc=self.pg0.remote_mac,
         p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
              ARP(op="who-has",
                  hwsrc=self.pg0.remote_mac,
@@ -552,6 +683,13 @@ class ARPTestCase(VppTestCase):
         self.send_and_assert_no_replies(
             self.pg0, p,
             "ARP req for non-local source - unnum")
         self.send_and_assert_no_replies(
             self.pg0, p,
             "ARP req for non-local source - unnum")
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+             ARP(op="who-has",
+                 hwsrc=self.pg0.remote_mac,
+                 psrc=self.pg1.remote_ip4,
+                 pdst=self.pg0.local_ip4))
+        self.send_and_assert_no_replies(self.pg0, p,
+                                        "ARP req for non-local source 2c")
 
         #
         #  3 - don't respond to ARP request from an address that belongs to
 
         #
         #  3 - don't respond to ARP request from an address that belongs to
@@ -568,7 +706,6 @@ class ARPTestCase(VppTestCase):
         #
         #  4 - don't respond to ARP requests that has mac source different
         #      from ARP request HW source
         #
         #  4 - don't respond to ARP requests that has mac source different
         #      from ARP request HW source
-        #      the router
         #
         p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
              ARP(op="who-has",
         #
         p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
              ARP(op="who-has",
@@ -578,16 +715,93 @@ class ARPTestCase(VppTestCase):
         self.send_and_assert_no_replies(self.pg0, p,
                                         "ARP req for non-local source")
 
         self.send_and_assert_no_replies(self.pg0, p,
                                         "ARP req for non-local source")
 
+        #
+        #  5 - don't respond to ARP requests for address within the
+        #      interface's sub-net but not the interface's address
+        #
+        self.pg0.generate_remote_hosts(2)
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+             ARP(op="who-has",
+                 hwsrc=self.pg0.remote_mac,
+                 psrc=self.pg0.remote_hosts[0].ip4,
+                 pdst=self.pg0.remote_hosts[1].ip4))
+        self.send_and_assert_no_replies(self.pg0, p,
+                                        "ARP req for non-local destination")
+
         #
         # cleanup
         #
         #
         # cleanup
         #
-        dyn_arp.remove_vpp_config()
         static_arp.remove_vpp_config()
         self.pg2.unset_unnumbered(self.pg1.sw_if_index)
 
         # need this to flush the adj-fibs
         self.pg2.unset_unnumbered(self.pg1.sw_if_index)
         self.pg2.admin_down()
         static_arp.remove_vpp_config()
         self.pg2.unset_unnumbered(self.pg1.sw_if_index)
 
         # need this to flush the adj-fibs
         self.pg2.unset_unnumbered(self.pg1.sw_if_index)
         self.pg2.admin_down()
+        self.pg1.admin_down()
+
+    def test_proxy_mirror_arp(self):
+        """ Interface Mirror Proxy ARP """
+
+        #
+        # When VPP has an interface whose address is also applied to a TAP
+        # interface on the host, then VPP's TAP interface will be unnumbered
+        # to the 'real' interface and do proxy ARP from the host.
+        # the curious aspect of this setup is that ARP requests from the host
+        # will come from the VPP's own address.
+        #
+        self.pg0.generate_remote_hosts(2)
+
+        arp_req_from_me = (Ether(src=self.pg2.remote_mac,
+                                 dst="ff:ff:ff:ff:ff:ff") /
+                           ARP(op="who-has",
+                               hwsrc=self.pg2.remote_mac,
+                               pdst=self.pg0.remote_hosts[1].ip4,
+                               psrc=self.pg0.local_ip4))
+
+        #
+        # Configure Proxy ARP for the subnet on PG0addresses on pg0
+        #
+        self.vapi.proxy_arp_add_del(self.pg0._local_ip4_subnet,
+                                    self.pg0._local_ip4_bcast)
+
+        # Make pg2 un-numbered to pg0
+        #
+        self.pg2.set_unnumbered(self.pg0.sw_if_index)
+
+        #
+        # Enable pg2 for proxy ARP
+        #
+        self.pg2.set_proxy_arp()
+
+        #
+        # Send the ARP request with an originating address that
+        # is VPP's own address
+        #
+        self.pg2.add_stream(arp_req_from_me)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx = self.pg2.get_capture(1)
+        self.verify_arp_resp(rx[0],
+                             self.pg2.local_mac,
+                             self.pg2.remote_mac,
+                             self.pg0.remote_hosts[1].ip4,
+                             self.pg0.local_ip4)
+
+        #
+        # validate we have not learned an ARP entry as a result of this
+        #
+        self.assertFalse(find_nbr(self,
+                                  self.pg2.sw_if_index,
+                                  self.pg0.local_ip4))
+
+        #
+        # cleanup
+        #
+        self.pg2.set_proxy_arp(0)
+        self.vapi.proxy_arp_add_del(self.pg0._local_ip4_subnet,
+                                    self.pg0._local_ip4_bcast,
+                                    is_add=0)
 
     def test_proxy_arp(self):
         """ Proxy ARP """
 
     def test_proxy_arp(self):
         """ Proxy ARP """
@@ -595,7 +809,7 @@ class ARPTestCase(VppTestCase):
         self.pg1.generate_remote_hosts(2)
 
         #
         self.pg1.generate_remote_hosts(2)
 
         #
-        # Proxy ARP rewquest packets for each interface
+        # Proxy ARP request packets for each interface
         #
         arp_req_pg0 = (Ether(src=self.pg0.remote_mac,
                              dst="ff:ff:ff:ff:ff:ff") /
         #
         arp_req_pg0 = (Ether(src=self.pg0.remote_mac,
                              dst="ff:ff:ff:ff:ff:ff") /
@@ -763,7 +977,7 @@ class ARPTestCase(VppTestCase):
         self.pg2.generate_remote_hosts(2)
 
         #
         self.pg2.generate_remote_hosts(2)
 
         #
-        # Add a reoute with out going label via an ARP unresolved next-hop
+        # Add a route with out going label via an ARP unresolved next-hop
         #
         ip_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
                                  [VppRoutePath(self.pg2.remote_hosts[1].ip4,
         #
         ip_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
                                  [VppRoutePath(self.pg2.remote_hosts[1].ip4,
@@ -778,7 +992,7 @@ class ARPTestCase(VppTestCase):
                    dst=self.pg0.local_mac) /
              IP(src=self.pg0.remote_ip4, dst="10.0.0.1") /
              UDP(sport=1234, dport=1234) /
                    dst=self.pg0.local_mac) /
              IP(src=self.pg0.remote_ip4, dst="10.0.0.1") /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         self.pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
 
         self.pg0.add_stream(p)
         self.pg_enable_capture(self.pg_interfaces)
@@ -825,11 +1039,7 @@ class ARPTestCase(VppTestCase):
               UDP(sport=1234, dport=1234) /
               Raw())
 
               UDP(sport=1234, dport=1234) /
               Raw())
 
-        self.pg0.add_stream(p0)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-
-        rx1 = self.pg1.get_capture(1)
+        rx1 = self.send_and_expect(self.pg0, [p0], self.pg1)
 
         self.verify_arp_req(rx1[0],
                             self.pg1.local_mac,
 
         self.verify_arp_req(rx1[0],
                             self.pg1.local_mac,
@@ -846,20 +1056,14 @@ class ARPTestCase(VppTestCase):
                   hwsrc="00:00:5e:00:01:09", pdst=self.pg1.local_ip4,
                   psrc=self.pg1.remote_ip4))
 
                   hwsrc="00:00:5e:00:01:09", pdst=self.pg1.local_ip4,
                   psrc=self.pg1.remote_ip4))
 
-        self.pg1.add_stream(p1)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
+        self.send_and_assert_no_replies(self.pg1, p1, "ARP reply")
 
         #
         # IP packet destined for pg1 remote host arrives on pg0 again.
         # VPP should have an ARP entry for that address now and the packet
         # should be sent out pg1.
         #
 
         #
         # IP packet destined for pg1 remote host arrives on pg0 again.
         # VPP should have an ARP entry for that address now and the packet
         # should be sent out pg1.
         #
-        self.pg0.add_stream(p0)
-        self.pg_enable_capture(self.pg_interfaces)
-        self.pg_start()
-
-        rx1 = self.pg1.get_capture(1)
+        rx1 = self.send_and_expect(self.pg0, [p0], self.pg1)
 
         self.verify_ip(rx1[0],
                        self.pg1.local_mac,
 
         self.verify_ip(rx1[0],
                        self.pg1.local_mac,
@@ -916,7 +1120,7 @@ class ARPTestCase(VppTestCase):
 
         #
         # remove the duplicate on pg1
 
         #
         # remove the duplicate on pg1
-        # packet stream shoud generate ARPs out of pg1
+        # packet stream should generate ARPs out of pg1
         #
         arp_pg1.remove_vpp_config()
 
         #
         arp_pg1.remove_vpp_config()
 
@@ -948,6 +1152,819 @@ class ARPTestCase(VppTestCase):
                        self.pg0.remote_ip4,
                        self.pg1.remote_hosts[1].ip4)
 
                        self.pg0.remote_ip4,
                        self.pg1.remote_hosts[1].ip4)
 
+    def test_arp_static(self):
+        """ ARP Static"""
+        self.pg2.generate_remote_hosts(3)
+
+        #
+        # Add a static ARP entry
+        #
+        static_arp = VppNeighbor(self,
+                                 self.pg2.sw_if_index,
+                                 self.pg2.remote_hosts[1].mac,
+                                 self.pg2.remote_hosts[1].ip4,
+                                 is_static=1)
+        static_arp.add_vpp_config()
+
+        #
+        # Add the connected prefix to the interface
+        #
+        self.pg2.config_ip4()
+
+        #
+        # We should now find the adj-fib
+        #
+        self.assertTrue(find_nbr(self,
+                                 self.pg2.sw_if_index,
+                                 self.pg2.remote_hosts[1].ip4,
+                                 is_static=1))
+        self.assertTrue(find_route(self,
+                                   self.pg2.remote_hosts[1].ip4,
+                                   32))
+
+        #
+        # remove the connected
+        #
+        self.pg2.unconfig_ip4()
+
+        #
+        # put the interface into table 1
+        #
+        self.pg2.set_table_ip4(1)
+
+        #
+        # configure the same connected and expect to find the
+        # adj fib in the new table
+        #
+        self.pg2.config_ip4()
+        self.assertTrue(find_route(self,
+                                   self.pg2.remote_hosts[1].ip4,
+                                   32,
+                                   table_id=1))
+
+        #
+        # clean-up
+        #
+        self.pg2.unconfig_ip4()
+        static_arp.remove_vpp_config()
+        self.pg2.set_table_ip4(0)
+
+    def test_arp_incomplete(self):
+        """ ARP Incomplete"""
+        self.pg1.generate_remote_hosts(3)
+
+        p0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+              IP(src=self.pg0.remote_ip4,
+                 dst=self.pg1.remote_hosts[1].ip4) /
+              UDP(sport=1234, dport=1234) /
+              Raw())
+        p1 = (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=1234) /
+              Raw())
+
+        #
+        # a packet to an unresolved destination generates an ARP request
+        #
+        rx = self.send_and_expect(self.pg0, [p0], self.pg1)
+        self.verify_arp_req(rx[0],
+                            self.pg1.local_mac,
+                            self.pg1.local_ip4,
+                            self.pg1._remote_hosts[1].ip4)
+
+        #
+        # add a neighbour for remote host 1
+        #
+        static_arp = VppNeighbor(self,
+                                 self.pg1.sw_if_index,
+                                 self.pg1.remote_hosts[1].mac,
+                                 self.pg1.remote_hosts[1].ip4,
+                                 is_static=1)
+        static_arp.add_vpp_config()
+
+        #
+        # change the interface's MAC
+        #
+        mac = [scapy.compat.chb(0x00), scapy.compat.chb(0x00),
+               scapy.compat.chb(0x00), scapy.compat.chb(0x33),
+               scapy.compat.chb(0x33), scapy.compat.chb(0x33)]
+        mac_string = ''.join(mac)
+
+        self.vapi.sw_interface_set_mac_address(self.pg1.sw_if_index,
+                                               mac_string)
+
+        #
+        # now ARP requests come from the new source mac
+        #
+        rx = self.send_and_expect(self.pg0, [p1], self.pg1)
+        self.verify_arp_req(rx[0],
+                            "00:00:00:33:33:33",
+                            self.pg1.local_ip4,
+                            self.pg1._remote_hosts[2].ip4)
+
+        #
+        # packets to the resolved host also have the new source mac
+        #
+        rx = self.send_and_expect(self.pg0, [p0], self.pg1)
+        self.verify_ip(rx[0],
+                       "00:00:00:33:33:33",
+                       self.pg1.remote_hosts[1].mac,
+                       self.pg0.remote_ip4,
+                       self.pg1.remote_hosts[1].ip4)
+
+        #
+        # set the mac address on the interface that does not have a
+        # configured subnet and thus no glean
+        #
+        self.vapi.sw_interface_set_mac_address(self.pg2.sw_if_index,
+                                               mac_string)
+
+    def test_garp(self):
+        """ GARP """
+
+        #
+        # Generate some hosts on the LAN
+        #
+        self.pg1.generate_remote_hosts(4)
+
+        #
+        # And an ARP entry
+        #
+        arp = VppNeighbor(self,
+                          self.pg1.sw_if_index,
+                          self.pg1.remote_hosts[1].mac,
+                          self.pg1.remote_hosts[1].ip4)
+        arp.add_vpp_config()
+
+        self.assertTrue(find_nbr(self,
+                                 self.pg1.sw_if_index,
+                                 self.pg1.remote_hosts[1].ip4,
+                                 mac=self.pg1.remote_hosts[1].mac))
+
+        #
+        # Send a GARP (request) to swap the host 1's address to that of host 2
+        #
+        p1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                    src=self.pg1.remote_hosts[2].mac) /
+              ARP(op="who-has",
+                  hwdst=self.pg1.local_mac,
+                  hwsrc=self.pg1.remote_hosts[2].mac,
+                  pdst=self.pg1.remote_hosts[1].ip4,
+                  psrc=self.pg1.remote_hosts[1].ip4))
+
+        self.pg1.add_stream(p1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.assertTrue(find_nbr(self,
+                                 self.pg1.sw_if_index,
+                                 self.pg1.remote_hosts[1].ip4,
+                                 mac=self.pg1.remote_hosts[2].mac))
+
+        #
+        # Send a GARP (reply) to swap the host 1's address to that of host 3
+        #
+        p1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                    src=self.pg1.remote_hosts[3].mac) /
+              ARP(op="is-at",
+                  hwdst=self.pg1.local_mac,
+                  hwsrc=self.pg1.remote_hosts[3].mac,
+                  pdst=self.pg1.remote_hosts[1].ip4,
+                  psrc=self.pg1.remote_hosts[1].ip4))
+
+        self.pg1.add_stream(p1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.assertTrue(find_nbr(self,
+                                 self.pg1.sw_if_index,
+                                 self.pg1.remote_hosts[1].ip4,
+                                 mac=self.pg1.remote_hosts[3].mac))
+
+        #
+        # GARPs (request nor replies) for host we don't know yet
+        # don't result in new neighbour entries
+        #
+        p1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                    src=self.pg1.remote_hosts[3].mac) /
+              ARP(op="who-has",
+                  hwdst=self.pg1.local_mac,
+                  hwsrc=self.pg1.remote_hosts[3].mac,
+                  pdst=self.pg1.remote_hosts[2].ip4,
+                  psrc=self.pg1.remote_hosts[2].ip4))
+
+        self.pg1.add_stream(p1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.assertFalse(find_nbr(self,
+                                  self.pg1.sw_if_index,
+                                  self.pg1.remote_hosts[2].ip4))
+
+        p1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                    src=self.pg1.remote_hosts[3].mac) /
+              ARP(op="is-at",
+                  hwdst=self.pg1.local_mac,
+                  hwsrc=self.pg1.remote_hosts[3].mac,
+                  pdst=self.pg1.remote_hosts[2].ip4,
+                  psrc=self.pg1.remote_hosts[2].ip4))
+
+        self.pg1.add_stream(p1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.assertFalse(find_nbr(self,
+                                  self.pg1.sw_if_index,
+                                  self.pg1.remote_hosts[2].ip4))
+
+    def test_arp_incomplete(self):
+        """ Incomplete Entries """
+
+        #
+        # ensure that we throttle the ARP and ND requests
+        #
+        self.pg0.generate_remote_hosts(2)
+
+        #
+        # IPv4/ARP
+        #
+        ip_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
+                                 [VppRoutePath(self.pg0.remote_hosts[1].ip4,
+                                               self.pg0.sw_if_index)])
+        ip_10_0_0_1.add_vpp_config()
+
+        p1 = (Ether(dst=self.pg1.local_mac,
+                    src=self.pg1.remote_mac) /
+              IP(src=self.pg1.remote_ip4,
+                 dst="10.0.0.1") /
+              UDP(sport=1234, dport=1234) /
+              Raw())
+
+        self.pg1.add_stream(p1 * 257)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        rx = self.pg0._get_capture(1)
+
+        #
+        # how many we get is going to be dependent on the time for packet
+        # processing but it should be small
+        #
+        self.assertLess(len(rx), 64)
+
+        #
+        # IPv6/ND
+        #
+        ip_10_1 = VppIpRoute(self, "10::1", 128,
+                             [VppRoutePath(self.pg0.remote_hosts[1].ip6,
+                                           self.pg0.sw_if_index,
+                                           proto=DpoProto.DPO_PROTO_IP6)])
+        ip_10_1.add_vpp_config()
+
+        p1 = (Ether(dst=self.pg1.local_mac,
+                    src=self.pg1.remote_mac) /
+              IPv6(src=self.pg1.remote_ip6,
+                   dst="10::1") /
+              UDP(sport=1234, dport=1234) /
+              Raw())
+
+        self.pg1.add_stream(p1 * 257)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        rx = self.pg0._get_capture(1)
+
+        #
+        # how many we get is going to be dependent on the time for packet
+        # processing but it should be small
+        #
+        self.assertLess(len(rx), 64)
+
+    def test_arp_forus(self):
+        """ ARP for for-us """
+
+        #
+        # Test that VPP responds with ARP requests to addresses that
+        # are connected and local routes.
+        # Use one of the 'remote' addresses in the subnet as a local address
+        # The intention of this route is that it then acts like a secondary
+        # address added to an interface
+        #
+        self.pg0.generate_remote_hosts(2)
+
+        forus = VppIpRoute(
+            self, self.pg0.remote_hosts[1].ip4, 32,
+            [VppRoutePath("0.0.0.0",
+                          self.pg0.sw_if_index,
+                          type=FibPathType.FIB_PATH_TYPE_LOCAL)])
+        forus.add_vpp_config()
+
+        p = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                   src=self.pg0.remote_mac) /
+             ARP(op="who-has",
+                 hwdst=self.pg0.local_mac,
+                 hwsrc=self.pg0.remote_mac,
+                 pdst=self.pg0.remote_hosts[1].ip4,
+                 psrc=self.pg0.remote_ip4))
+
+        rx = self.send_and_expect(self.pg0, [p], self.pg0)
+
+        self.verify_arp_resp(rx[0],
+                             self.pg0.local_mac,
+                             self.pg0.remote_mac,
+                             self.pg0.remote_hosts[1].ip4,
+                             self.pg0.remote_ip4)
+
+    def test_arp_table_swap(self):
+        #
+        # Generate some hosts on the LAN
+        #
+        N_NBRS = 4
+        self.pg1.generate_remote_hosts(N_NBRS)
+
+        for n in range(N_NBRS):
+            # a route thru each neighbour
+            VppIpRoute(self, "10.0.0.%d" % n, 32,
+                       [VppRoutePath(self.pg1.remote_hosts[n].ip4,
+                                     self.pg1.sw_if_index)]).add_vpp_config()
+
+            # resolve each neighbour
+            p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+                  ARP(op="is-at", hwdst=self.pg1.local_mac,
+                      hwsrc="00:00:5e:00:01:09", pdst=self.pg1.local_ip4,
+                      psrc=self.pg1.remote_hosts[n].ip4))
+
+            self.send_and_assert_no_replies(self.pg1, p1, "ARP reply")
+
+        self.logger.info(self.vapi.cli("sh ip neighbors"))
+
+        #
+        # swap the table pg1 is in
+        #
+        table = VppIpTable(self, 100).add_vpp_config()
+
+        self.pg1.unconfig_ip4()
+        self.pg1.set_table_ip4(100)
+        self.pg1.config_ip4()
+
+        #
+        # all neighbours are cleared
+        #
+        for n in range(N_NBRS):
+            self.assertFalse(find_nbr(self,
+                                      self.pg1.sw_if_index,
+                                      self.pg1.remote_hosts[n].ip4))
+
+        #
+        # packets to all neighbours generate ARP requests
+        #
+        for n in range(N_NBRS):
+            # a route thru each neighbour
+            VppIpRoute(self, "10.0.0.%d" % n, 32,
+                       [VppRoutePath(self.pg1.remote_hosts[n].ip4,
+                                     self.pg1.sw_if_index)],
+                       table_id=100).add_vpp_config()
+
+            p = (Ether(src=self.pg1.remote_hosts[n].mac,
+                       dst=self.pg1.local_mac) /
+                 IP(src=self.pg1.remote_hosts[n].ip4,
+                    dst="10.0.0.%d" % n) /
+                 Raw(b'0x5' * 100))
+            rxs = self.send_and_expect(self.pg1, [p], self.pg1)
+            for rx in rxs:
+                self.verify_arp_req(rx,
+                                    self.pg1.local_mac,
+                                    self.pg1.local_ip4,
+                                    self.pg1.remote_hosts[n].ip4)
+
+        self.pg1.unconfig_ip4()
+        self.pg1.set_table_ip4(0)
+
+
+class NeighborStatsTestCase(VppTestCase):
+    """ ARP/ND Counters """
+
+    @classmethod
+    def setUpClass(cls):
+        super(NeighborStatsTestCase, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(NeighborStatsTestCase, cls).tearDownClass()
+
+    def setUp(self):
+        super(NeighborStatsTestCase, self).setUp()
+
+        self.create_pg_interfaces(range(2))
+
+        # pg0 configured with ip4 and 6 addresses used for input
+        # pg1 configured with ip4 and 6 addresses used for output
+        # pg2 is unnumbered to pg0
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.config_ip6()
+            i.resolve_arp()
+            i.resolve_ndp()
+
+    def tearDown(self):
+        super(NeighborStatsTestCase, self).tearDown()
+
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+            i.admin_down()
+
+    def test_arp_stats(self):
+        """ ARP Counters """
+
+        self.vapi.cli("adj counters enable")
+        self.pg1.generate_remote_hosts(2)
+
+        arp1 = VppNeighbor(self,
+                           self.pg1.sw_if_index,
+                           self.pg1.remote_hosts[0].mac,
+                           self.pg1.remote_hosts[0].ip4)
+        arp1.add_vpp_config()
+        arp2 = VppNeighbor(self,
+                           self.pg1.sw_if_index,
+                           self.pg1.remote_hosts[1].mac,
+                           self.pg1.remote_hosts[1].ip4)
+        arp2.add_vpp_config()
+
+        p1 = (Ether(dst=self.pg0.local_mac,
+                    src=self.pg0.remote_mac) /
+              IP(src=self.pg0.remote_ip4,
+                 dst=self.pg1.remote_hosts[0].ip4) /
+              UDP(sport=1234, dport=1234) /
+              Raw())
+        p2 = (Ether(dst=self.pg0.local_mac,
+                    src=self.pg0.remote_mac) /
+              IP(src=self.pg0.remote_ip4,
+                 dst=self.pg1.remote_hosts[1].ip4) /
+              UDP(sport=1234, dport=1234) /
+              Raw())
+
+        rx = self.send_and_expect(self.pg0, p1 * NUM_PKTS, self.pg1)
+        rx = self.send_and_expect(self.pg0, p2 * NUM_PKTS, self.pg1)
+
+        self.assertEqual(NUM_PKTS, arp1.get_stats()['packets'])
+        self.assertEqual(NUM_PKTS, arp2.get_stats()['packets'])
+
+        rx = self.send_and_expect(self.pg0, p1 * NUM_PKTS, self.pg1)
+        self.assertEqual(NUM_PKTS*2, arp1.get_stats()['packets'])
+
+    def test_nd_stats(self):
+        """ ND Counters """
+
+        self.vapi.cli("adj counters enable")
+        self.pg0.generate_remote_hosts(3)
+
+        nd1 = VppNeighbor(self,
+                          self.pg0.sw_if_index,
+                          self.pg0.remote_hosts[1].mac,
+                          self.pg0.remote_hosts[1].ip6)
+        nd1.add_vpp_config()
+        nd2 = VppNeighbor(self,
+                          self.pg0.sw_if_index,
+                          self.pg0.remote_hosts[2].mac,
+                          self.pg0.remote_hosts[2].ip6)
+        nd2.add_vpp_config()
+
+        p1 = (Ether(dst=self.pg1.local_mac,
+                    src=self.pg1.remote_mac) /
+              IPv6(src=self.pg1.remote_ip6,
+                   dst=self.pg0.remote_hosts[1].ip6) /
+              UDP(sport=1234, dport=1234) /
+              Raw())
+        p2 = (Ether(dst=self.pg1.local_mac,
+                    src=self.pg1.remote_mac) /
+              IPv6(src=self.pg1.remote_ip6,
+                   dst=self.pg0.remote_hosts[2].ip6) /
+              UDP(sport=1234, dport=1234) /
+              Raw())
+
+        rx = self.send_and_expect(self.pg1, p1 * 16, self.pg0)
+        rx = self.send_and_expect(self.pg1, p2 * 16, self.pg0)
+
+        self.assertEqual(16, nd1.get_stats()['packets'])
+        self.assertEqual(16, nd2.get_stats()['packets'])
+
+        rx = self.send_and_expect(self.pg1, p1 * NUM_PKTS, self.pg0)
+        self.assertEqual(NUM_PKTS+16, nd1.get_stats()['packets'])
+
+
+class NeighborAgeTestCase(VppTestCase):
+    """ ARP/ND Aging """
+
+    @classmethod
+    def setUpClass(cls):
+        super(NeighborAgeTestCase, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(NeighborAgeTestCase, cls).tearDownClass()
+
+    def setUp(self):
+        super(NeighborAgeTestCase, self).setUp()
+
+        self.create_pg_interfaces(range(1))
+
+        # pg0 configured with ip4 and 6 addresses used for input
+        # pg1 configured with ip4 and 6 addresses used for output
+        # pg2 is unnumbered to pg0
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.config_ip6()
+            i.resolve_arp()
+            i.resolve_ndp()
+
+    def tearDown(self):
+        super(NeighborAgeTestCase, self).tearDown()
+
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+            i.admin_down()
+
+    def wait_for_no_nbr(self, intf, address,
+                        n_tries=50, s_time=1):
+        while (n_tries):
+            if not find_nbr(self, intf, address):
+                return True
+            n_tries = n_tries - 1
+            self.sleep(s_time)
+
+        return False
+
+    def verify_arp_req(self, rx, smac, sip, dip):
+        ether = rx[Ether]
+        self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
+        self.assertEqual(ether.src, smac)
+
+        arp = rx[ARP]
+        self.assertEqual(arp.hwtype, 1)
+        self.assertEqual(arp.ptype, 0x800)
+        self.assertEqual(arp.hwlen, 6)
+        self.assertEqual(arp.plen, 4)
+        self.assertEqual(arp.op, arp_opts["who-has"])
+        self.assertEqual(arp.hwsrc, smac)
+        self.assertEqual(arp.hwdst, "00:00:00:00:00:00")
+        self.assertEqual(arp.psrc, sip)
+        self.assertEqual(arp.pdst, dip)
+
+    def test_age(self):
+        """ Aging/Recycle """
+
+        self.vapi.cli("set logging unthrottle 0")
+        self.vapi.cli("set logging size %d" % 0xffff)
+
+        self.pg0.generate_remote_hosts(201)
+
+        vaf = VppEnum.vl_api_address_family_t
+
+        #
+        # start listening on all interfaces
+        #
+        self.pg_enable_capture(self.pg_interfaces)
+
+        #
+        # Set the neighbor configuration:
+        #   limi = 200
+        #   age  = 0 seconds
+        #   recycle = false
+        #
+        self.vapi.ip_neighbor_config(af=vaf.ADDRESS_IP4,
+                                     max_number=200,
+                                     max_age=0,
+                                     recycle=False)
+
+        self.vapi.cli("sh ip neighbor-config")
+
+        # add the 198 neighbours that should pass (-1 for one created in setup)
+        for ii in range(200):
+            VppNeighbor(self,
+                        self.pg0.sw_if_index,
+                        self.pg0.remote_hosts[ii].mac,
+                        self.pg0.remote_hosts[ii].ip4).add_vpp_config()
+
+        # one more neighbor over the limit should fail
+        with self.vapi.assert_negative_api_retval():
+            VppNeighbor(self,
+                        self.pg0.sw_if_index,
+                        self.pg0.remote_hosts[200].mac,
+                        self.pg0.remote_hosts[200].ip4).add_vpp_config()
+
+        #
+        # change the config to allow recycling the old neighbors
+        #
+        self.vapi.ip_neighbor_config(af=vaf.ADDRESS_IP4,
+                                     max_number=200,
+                                     max_age=0,
+                                     recycle=True)
+
+        # now new additions are allowed
+        VppNeighbor(self,
+                    self.pg0.sw_if_index,
+                    self.pg0.remote_hosts[200].mac,
+                    self.pg0.remote_hosts[200].ip4).add_vpp_config()
+
+        # add the first neighbor we configured has been re-used
+        self.assertFalse(find_nbr(self,
+                                  self.pg0.sw_if_index,
+                                  self.pg0.remote_hosts[0].ip4))
+        self.assertTrue(find_nbr(self,
+                                 self.pg0.sw_if_index,
+                                 self.pg0.remote_hosts[200].ip4))
+
+        #
+        # change the config to age old neighbors
+        #
+        self.vapi.ip_neighbor_config(af=vaf.ADDRESS_IP4,
+                                     max_number=200,
+                                     max_age=2,
+                                     recycle=True)
+
+        self.vapi.cli("sh ip4 neighbor-sorted")
+
+        #
+        # expect probes from all these ARP entries as they age
+        # 3 probes for each neighbor 3*200 = 600
+        rxs = self.pg0.get_capture(600, timeout=8)
+
+        for ii in range(3):
+            for jj in range(200):
+                rx = rxs[ii*200 + jj]
+                # rx.show()
+
+        #
+        # 3 probes sent then 1 more second to see if a reply comes, before
+        # they age out
+        #
+        for jj in range(1, 201):
+            self.wait_for_no_nbr(self.pg0.sw_if_index,
+                                 self.pg0.remote_hosts[jj].ip4)
+
+        self.assertFalse(self.vapi.ip_neighbor_dump(sw_if_index=0xffffffff,
+                                                    af=vaf.ADDRESS_IP4))
+
+        #
+        # load up some neighbours again with 2s aging enabled
+        # they should be removed after 10s (2s age + 4s for probes + gap)
+        #
+        for ii in range(10):
+            VppNeighbor(self,
+                        self.pg0.sw_if_index,
+                        self.pg0.remote_hosts[ii].mac,
+                        self.pg0.remote_hosts[ii].ip4).add_vpp_config()
+        self.sleep(10)
+        self.assertFalse(self.vapi.ip_neighbor_dump(sw_if_index=0xffffffff,
+                                                    af=vaf.ADDRESS_IP4))
+
+        #
+        # check if we can set age and recycle with empty neighbor list
+        #
+        self.vapi.ip_neighbor_config(af=vaf.ADDRESS_IP4,
+                                     max_number=200,
+                                     max_age=1000,
+                                     recycle=True)
+
+        #
+        # load up some neighbours again, then disable the aging
+        # they should still be there in 10 seconds time
+        #
+        for ii in range(10):
+            VppNeighbor(self,
+                        self.pg0.sw_if_index,
+                        self.pg0.remote_hosts[ii].mac,
+                        self.pg0.remote_hosts[ii].ip4).add_vpp_config()
+        self.vapi.ip_neighbor_config(af=vaf.ADDRESS_IP4,
+                                     max_number=200,
+                                     max_age=0,
+                                     recycle=False)
+
+        self.sleep(10)
+        self.assertTrue(find_nbr(self,
+                                 self.pg0.sw_if_index,
+                                 self.pg0.remote_hosts[0].ip4))
+
+
+class NeighborReplaceTestCase(VppTestCase):
+    """ ARP/ND Replacement """
+
+    @classmethod
+    def setUpClass(cls):
+        super(NeighborReplaceTestCase, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(NeighborReplaceTestCase, cls).tearDownClass()
+
+    def setUp(self):
+        super(NeighborReplaceTestCase, self).setUp()
+
+        self.create_pg_interfaces(range(4))
+
+        # pg0 configured with ip4 and 6 addresses used for input
+        # pg1 configured with ip4 and 6 addresses used for output
+        # pg2 is unnumbered to pg0
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.config_ip6()
+            i.resolve_arp()
+            i.resolve_ndp()
+
+    def tearDown(self):
+        super(NeighborReplaceTestCase, self).tearDown()
+
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+            i.admin_down()
+
+    def test_replace(self):
+        """ replace """
+
+        N_HOSTS = 16
+
+        for i in self.pg_interfaces:
+            i.generate_remote_hosts(N_HOSTS)
+            i.configure_ipv4_neighbors()
+            i.configure_ipv6_neighbors()
+
+        # replace them all
+        self.vapi.ip_neighbor_replace_begin()
+        self.vapi.ip_neighbor_replace_end()
+
+        for i in self.pg_interfaces:
+            for h in range(N_HOSTS):
+                self.assertFalse(find_nbr(self,
+                                          self.pg0.sw_if_index,
+                                          self.pg0.remote_hosts[h].ip4))
+                self.assertFalse(find_nbr(self,
+                                          self.pg0.sw_if_index,
+                                          self.pg0.remote_hosts[h].ip6))
+
+        #
+        # and them all back via the API
+        #
+        for i in self.pg_interfaces:
+            for h in range(N_HOSTS):
+                VppNeighbor(self,
+                            i.sw_if_index,
+                            i.remote_hosts[h].mac,
+                            i.remote_hosts[h].ip4).add_vpp_config()
+                VppNeighbor(self,
+                            i.sw_if_index,
+                            i.remote_hosts[h].mac,
+                            i.remote_hosts[h].ip6).add_vpp_config()
+
+        #
+        # begin the replacement again, this time touch some
+        # the neighbours on pg1 so they are not deleted
+        #
+        self.vapi.ip_neighbor_replace_begin()
+
+        # update from the API all neighbours on pg1
+        for h in range(N_HOSTS):
+            VppNeighbor(self,
+                        self.pg1.sw_if_index,
+                        self.pg1.remote_hosts[h].mac,
+                        self.pg1.remote_hosts[h].ip4).add_vpp_config()
+            VppNeighbor(self,
+                        self.pg1.sw_if_index,
+                        self.pg1.remote_hosts[h].mac,
+                        self.pg1.remote_hosts[h].ip6).add_vpp_config()
+
+        # update from the data-plane all neighbours on pg3
+        self.pg3.configure_ipv4_neighbors()
+        self.pg3.configure_ipv6_neighbors()
+
+        # complete the replacement
+        self.logger.info(self.vapi.cli("sh ip neighbors"))
+        self.vapi.ip_neighbor_replace_end()
+
+        for i in self.pg_interfaces:
+            if i == self.pg1 or i == self.pg3:
+                # neighbours on pg1 and pg3 are still present
+                for h in range(N_HOSTS):
+                    self.assertTrue(find_nbr(self,
+                                             i.sw_if_index,
+                                             i.remote_hosts[h].ip4))
+                    self.assertTrue(find_nbr(self,
+                                             i.sw_if_index,
+                                             i.remote_hosts[h].ip6))
+            else:
+                # all other neighbours are toast
+                for h in range(N_HOSTS):
+                    self.assertFalse(find_nbr(self,
+                                              i.sw_if_index,
+                                              i.remote_hosts[h].ip4))
+                    self.assertFalse(find_nbr(self,
+                                              i.sw_if_index,
+                                              i.remote_hosts[h].ip6))
+
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)