ip-neighbor: Add flush API

Type: feature

Signed-off-by: Neale Ranns <nranns@cisco.com>
Change-Id: I96509c274895b917c3e220204d7959d9270de30d
diff --git a/src/vnet/ip-neighbor/ip_neighbor.api b/src/vnet/ip-neighbor/ip_neighbor.api
index 473024a..fe344af 100644
--- a/src/vnet/ip-neighbor/ip_neighbor.api
+++ b/src/vnet/ip-neighbor/ip_neighbor.api
@@ -163,6 +163,22 @@
   u32 context;
 };
 
+/** \brief IP neighbor flush request - removes *all* neighbours.
+     dynamic and static from API/CLI and dynamic from data-plane.
+
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param af - Flush neighbours of this address family
+    @param sw_if_index - Flush on this interface (~0 => all interfaces)
+*/
+autoreply define ip_neighbor_flush
+{
+  u32 client_index;
+  u32 context;
+  vl_api_address_family_t af;
+  vl_api_interface_index_t sw_if_index [default=0xffffffff];
+};
+
 /** \brief Register for IP4 ARP resolution event on receiving ARP reply or
            MAC/IP info from ARP requests in L2 BDs
     @param client_index - opaque cookie to identify the sender
diff --git a/src/vnet/ip-neighbor/ip_neighbor.c b/src/vnet/ip-neighbor/ip_neighbor.c
index 5b18473..afb97ac 100644
--- a/src/vnet/ip-neighbor/ip_neighbor.c
+++ b/src/vnet/ip-neighbor/ip_neighbor.c
@@ -576,6 +576,40 @@
   return (0);
 }
 
+typedef struct ip_neighbor_del_all_ctx_t_
+{
+  index_t *ipn_del;
+} ip_neighbor_del_all_ctx_t;
+
+static walk_rc_t
+ip_neighbor_del_all_walk_cb (index_t ipni, void *arg)
+{
+  ip_neighbor_del_all_ctx_t *ctx = arg;
+
+  vec_add1 (ctx->ipn_del, ipni);
+
+  return (WALK_CONTINUE);
+}
+
+void
+ip_neighbor_del_all (ip46_type_t type, u32 sw_if_index)
+{
+  IP_NEIGHBOR_INFO ("delete-all: %U, %U",
+		    format_ip46_type, type,
+		    format_vnet_sw_if_index_name, vnet_get_main (),
+		    sw_if_index);
+
+  ip_neighbor_del_all_ctx_t ctx = {
+    .ipn_del = NULL,
+  };
+  index_t *ipni;
+
+  ip_neighbor_walk (type, sw_if_index, ip_neighbor_del_all_walk_cb, &ctx);
+
+  vec_foreach (ipni, ctx.ipn_del) ip_neighbor_free (ip_neighbor_get (*ipni));
+  vec_free (ctx.ipn_del);
+}
+
 void
 ip_neighbor_update (vnet_main_t * vnm, adj_index_t ai)
 {
diff --git a/src/vnet/ip-neighbor/ip_neighbor.h b/src/vnet/ip-neighbor/ip_neighbor.h
index 8769fd5..cdceadb 100644
--- a/src/vnet/ip-neighbor/ip_neighbor.h
+++ b/src/vnet/ip-neighbor/ip_neighbor.h
@@ -41,6 +41,8 @@
 extern int ip_neighbor_config (ip46_type_t type, u32 limit, u32 age,
 			       bool recycle);
 
+extern void ip_neighbor_del_all (ip46_type_t type, u32 sw_if_index);
+
 typedef walk_rc_t (*ip_neighbor_walk_cb_t) (index_t ipni, void *ctx);
 extern void ip_neighbor_walk (ip46_type_t type,
 			      u32 sw_if_index,
diff --git a/src/vnet/ip-neighbor/ip_neighbor_api.c b/src/vnet/ip-neighbor/ip_neighbor_api.c
index 86587fa..02952c7 100644
--- a/src/vnet/ip-neighbor/ip_neighbor_api.c
+++ b/src/vnet/ip-neighbor/ip_neighbor_api.c
@@ -301,6 +301,25 @@
   REPLY_MACRO (VL_API_IP_NEIGHBOR_REPLACE_END_REPLY);
 }
 
+static void
+vl_api_ip_neighbor_flush_t_handler (vl_api_ip_neighbor_flush_t * mp)
+{
+  vl_api_ip_neighbor_flush_reply_t *rmp;
+  ip_address_family_t af;
+  int rv;
+
+  if (mp->sw_if_index != ~0)
+    VALIDATE_SW_IF_INDEX (mp);
+
+  rv = ip_address_family_decode (mp->af, &af);
+
+  if (!rv)
+    ip_neighbor_del_all (ip46_type_from_af (af), ntohl (mp->sw_if_index));
+
+  BAD_SW_IF_INDEX_LABEL;
+  REPLY_MACRO (VL_API_IP_NEIGHBOR_FLUSH_REPLY);
+}
+
 #define vl_msg_name_crc_list
 #include <vnet/ip-neighbor/ip_neighbor.api.h>
 #undef vl_msg_name_crc_list
diff --git a/test/test_neighbor.py b/test/test_neighbor.py
index cc1357c..416241e 100644
--- a/test/test_neighbor.py
+++ b/test/test_neighbor.py
@@ -13,7 +13,7 @@
 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.layers.inet import IP, UDP, TCP
 from scapy.layers.inet6 import IPv6
 from scapy.contrib.mpls import MPLS
 from scapy.layers.inet6 import IPv6
@@ -777,11 +777,7 @@
         # 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)
+        rx = self.send_and_expect(self.pg2, [arp_req_from_me], self.pg2)
         self.verify_arp_resp(rx[0],
                              self.pg2.local_mac,
                              self.pg2.remote_mac,
@@ -796,6 +792,48 @@
                                   self.pg0.local_ip4))
 
         #
+        # setup a punt redirect so packets from the uplink go to the tap
+        #
+        self.vapi.ip_punt_redirect(self.pg0.sw_if_index,
+                                   self.pg2.sw_if_index,
+                                   self.pg0.local_ip4)
+
+        p_tcp = (Ether(src=self.pg0.remote_mac,
+                       dst=self.pg0.local_mac,) /
+                 IP(src=self.pg0.remote_ip4,
+                    dst=self.pg0.local_ip4) /
+                 TCP(sport=80, dport=80) /
+                 Raw())
+        rx = self.send_and_expect(self.pg0, [p_tcp], self.pg2)
+
+        # there's no ARP entry so this is an ARP req
+        self.assertTrue(rx[0].haslayer(ARP))
+
+        # and ARP entry for VPP's pg0 address on the host interface
+        n1 = VppNeighbor(self,
+                         self.pg2.sw_if_index,
+                         self.pg2.remote_mac,
+                         self.pg0.local_ip4,
+                         is_no_fib_entry=True).add_vpp_config()
+        # now the packets shold forward
+        rx = self.send_and_expect(self.pg0, [p_tcp], self.pg2)
+        self.assertFalse(rx[0].haslayer(ARP))
+        self.assertEqual(rx[0][Ether].dst, self.pg2.remote_mac)
+
+        #
+        # flush the neighbor cache on the uplink
+        #
+        af = VppEnum.vl_api_address_family_t
+        self.vapi.ip_neighbor_flush(af.ADDRESS_IP4, self.pg0.sw_if_index)
+
+        # ensure we can still resolve the ARPs on the uplink
+        self.pg0.resolve_arp()
+
+        self.assertTrue(find_nbr(self,
+                                 self.pg0.sw_if_index,
+                                 self.pg0.remote_ip4))
+
+        #
         # cleanup
         #
         self.pg2.set_proxy_arp(0)
@@ -1966,5 +2004,117 @@
                                               i.remote_hosts[h].ip6))
 
 
+class NeighborFlush(VppTestCase):
+    """ Neighbor Flush """
+
+    @classmethod
+    def setUpClass(cls):
+        super(NeighborFlush, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(NeighborFlush, cls).tearDownClass()
+
+    def setUp(self):
+        super(NeighborFlush, self).setUp()
+
+        self.create_pg_interfaces(range(2))
+
+        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(NeighborFlush, self).tearDown()
+
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+            i.admin_down()
+
+    def test_flush(self):
+        """ Neighbour Flush """
+
+        e = VppEnum
+        nf = e.vl_api_ip_neighbor_flags_t
+        af = e.vl_api_address_family_t
+        N_HOSTS = 16
+        static = [False, True]
+        self.pg0.generate_remote_hosts(N_HOSTS)
+        self.pg1.generate_remote_hosts(N_HOSTS)
+
+        for s in static:
+            # a few v4 and v6 dynamic neoghbors
+            for n in range(N_HOSTS):
+                VppNeighbor(self,
+                            self.pg0.sw_if_index,
+                            self.pg0.remote_hosts[n].mac,
+                            self.pg0.remote_hosts[n].ip4,
+                            is_static=s).add_vpp_config()
+                VppNeighbor(self,
+                            self.pg1.sw_if_index,
+                            self.pg1.remote_hosts[n].mac,
+                            self.pg1.remote_hosts[n].ip6,
+                            is_static=s).add_vpp_config()
+
+            # flush the interfaces individually
+            self.vapi.ip_neighbor_flush(af.ADDRESS_IP4, self.pg0.sw_if_index)
+
+            # check we haven't flushed that which we shouldn't
+            for n in range(N_HOSTS):
+                self.assertTrue(find_nbr(self,
+                                         self.pg1.sw_if_index,
+                                         self.pg1.remote_hosts[n].ip6,
+                                         is_static=s))
+
+            self.vapi.ip_neighbor_flush(af.ADDRESS_IP6, self.pg1.sw_if_index)
+
+            for n in range(N_HOSTS):
+                self.assertFalse(find_nbr(self,
+                                          self.pg0.sw_if_index,
+                                          self.pg0.remote_hosts[n].ip4))
+                self.assertFalse(find_nbr(self,
+                                          self.pg1.sw_if_index,
+                                          self.pg1.remote_hosts[n].ip6))
+
+            # add the nieghbours back
+            for n in range(N_HOSTS):
+                VppNeighbor(self,
+                            self.pg0.sw_if_index,
+                            self.pg0.remote_hosts[n].mac,
+                            self.pg0.remote_hosts[n].ip4,
+                            is_static=s).add_vpp_config()
+                VppNeighbor(self,
+                            self.pg1.sw_if_index,
+                            self.pg1.remote_hosts[n].mac,
+                            self.pg1.remote_hosts[n].ip6,
+                            is_static=s).add_vpp_config()
+
+            self.logger.info(self.vapi.cli("sh ip neighbor"))
+
+            # flush both interfaces at the same time
+            self.vapi.ip_neighbor_flush(af.ADDRESS_IP6, 0xffffffff)
+
+            # check we haven't flushed that which we shouldn't
+            for n in range(N_HOSTS):
+                self.assertTrue(find_nbr(self,
+                                         self.pg0.sw_if_index,
+                                         self.pg0.remote_hosts[n].ip4,
+                                         is_static=s))
+
+            self.vapi.ip_neighbor_flush(af.ADDRESS_IP4, 0xffffffff)
+
+            for n in range(N_HOSTS):
+                self.assertFalse(find_nbr(self,
+                                          self.pg0.sw_if_index,
+                                          self.pg0.remote_hosts[n].ip4))
+                self.assertFalse(find_nbr(self,
+                                          self.pg1.sw_if_index,
+                                          self.pg1.remote_hosts[n].ip6))
+
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)