ip-neighbor: Replace feature for the ip-neighbor data-base 25/26325/5
authorNeale Ranns <nranns@cisco.com>
Thu, 2 Apr 2020 17:08:28 +0000 (17:08 +0000)
committerNeale Ranns <nranns@cisco.com>
Thu, 23 Apr 2020 08:15:39 +0000 (08:15 +0000)
Type: feature

DB replace is implemented with a mark and sweep algorithm (just the the
FIB)

Signed-off-by: Neale Ranns <nranns@cisco.com>
Change-Id: I54ab06e11552219e2a18e1b4a87d531321cf3829

src/vnet/ip-neighbor/ip_neighbor.api
src/vnet/ip-neighbor/ip_neighbor.c
src/vnet/ip-neighbor/ip_neighbor.h
src/vnet/ip-neighbor/ip_neighbor_api.c
src/vnet/ip-neighbor/ip_neighbor_types.c
src/vnet/ip-neighbor/ip_neighbor_types.h
test/test_neighbor.py

index b820360..fc8f822 100644 (file)
@@ -126,6 +126,43 @@ autoreply define ip_neighbor_config
   bool recycle;
 };
 
+/** \brief IP neighbour replace begin
+
+    The use-case is that, for some unspecified reason, the control plane
+    has a different set of neighbours it than VPP
+    currently has. The CP would thus like to 'replace' VPP's set
+    only by specifying what the new set shall be, i.e. it is not
+    going to delete anything that already eixts, rather, is wants any
+    unspecified neighbors deleted implicitly.
+    The CP declares the start of this procedure with this replace_begin
+    API Call, and when it has populated all neighbours it wants, it calls
+    the below replace_end API. From this point on it is of course free
+    to add and delete neighbours as usual.
+    The underlying mechanism by which VPP implements this replace is
+    intentionally left unspecified.
+
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+*/
+autoreply define ip_neighbor_replace_begin
+{
+  u32 client_index;
+  u32 context;
+};
+
+/** \brief IP neighbour replace end
+
+    see ip_neighbor_replace_begin description.
+
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+*/
+autoreply define ip_neighbor_replace_end
+{
+  u32 client_index;
+  u32 context;
+};
+
 /** \brief Register for IP4 ARP resolution event on receing ARP reply or
            MAC/IP info from ARP requests in L2 BDs
     @param client_index - opaque cookie to identify the sender
index 960da12..5b18473 100644 (file)
@@ -99,6 +99,12 @@ ip_neighbor_get_index (const ip_neighbor_t * ipn)
   return (ipn - ip_neighbor_pool);
 }
 
+static void
+ip_neighbor_touch (ip_neighbor_t * ipn)
+{
+  ipn->ipn_flags &= ~IP_NEIGHBOR_FLAG_STALE;
+}
+
 static bool
 ip_neighbor_is_dynamic (const ip_neighbor_t * ipn)
 {
@@ -145,6 +151,7 @@ ip_neighbor_refresh (ip_neighbor_t * ipn)
    * list is time sorted, newest first */
   ip_neighbor_elt_t *elt, *head;
 
+  ip_neighbor_touch (ipn);
   ipn->ipn_time_last_updated = vlib_time_now (vlib_get_main ());
   ipn->ipn_n_probes = 0;
 
@@ -473,6 +480,8 @@ ip_neighbor_add (const ip46_address_t * ip,
                       format_ip_neighbor_flags, flags, format_mac_address_t,
                       mac);
 
+      ip_neighbor_touch (ipn);
+
       /* Refuse to over-write static neighbor entry. */
       if (!(flags & IP_NEIGHBOR_FLAG_STATIC) &&
          (ipn->ipn_flags & IP_NEIGHBOR_FLAG_STATIC))
@@ -1177,6 +1186,60 @@ ip_neighbor_flush (ip46_type_t type, u32 sw_if_index)
   vec_free (ipnis);
 }
 
+static walk_rc_t
+ip_neighbor_mark_one (index_t ipni, void *ctx)
+{
+  ip_neighbor_t *ipn;
+
+  ipn = ip_neighbor_get (ipni);
+
+  ipn->ipn_flags |= IP_NEIGHBOR_FLAG_STALE;
+
+  return (WALK_CONTINUE);
+}
+
+void
+ip_neighbor_mark (ip46_type_t type)
+{
+  ip_neighbor_walk (type, ~0, ip_neighbor_mark_one, NULL);
+}
+
+typedef struct ip_neighbor_sweep_ctx_t_
+{
+  index_t *ipnsc_stale;
+} ip_neighbor_sweep_ctx_t;
+
+static walk_rc_t
+ip_neighbor_sweep_one (index_t ipni, void *arg)
+{
+  ip_neighbor_sweep_ctx_t *ctx = arg;
+  ip_neighbor_t *ipn;
+
+  ipn = ip_neighbor_get (ipni);
+
+  if (ipn->ipn_flags & IP_NEIGHBOR_FLAG_STALE)
+    {
+      vec_add1 (ctx->ipnsc_stale, ipni);
+    }
+
+  return (WALK_CONTINUE);
+}
+
+void
+ip_neighbor_sweep (ip46_type_t type)
+{
+  ip_neighbor_sweep_ctx_t ctx = { };
+  index_t *ipni;
+
+  ip_neighbor_walk (type, ~0, ip_neighbor_sweep_one, &ctx);
+
+  vec_foreach (ipni, ctx.ipnsc_stale)
+  {
+    ip_neighbor_free (ip_neighbor_get (*ipni));
+  }
+  vec_free (ctx.ipnsc_stale);
+}
+
 /*
  * Remove any arp entries associated with the specified interface
  */
index cb384c5..8769fd5 100644 (file)
@@ -62,6 +62,9 @@ extern void ip_neighbor_probe (const ip_adjacency_t * adj);
 extern void ip_neighbor_probe_dst (const ip_adjacency_t * adj,
                                   const ip46_address_t * ip);
 
+extern void ip_neighbor_mark (ip46_type_t type);
+extern void ip_neighbor_sweep (ip46_type_t type);
+
 /**
  * From the watcher to the API to publish a new neighbor
  */
index ec1e493..86587fa 100644 (file)
@@ -275,6 +275,32 @@ vl_api_ip_neighbor_config_t_handler (vl_api_ip_neighbor_config_t * mp)
   REPLY_MACRO (VL_API_IP_NEIGHBOR_CONFIG_REPLY);
 }
 
+static void
+vl_api_ip_neighbor_replace_begin_t_handler (vl_api_ip_neighbor_replace_begin_t
+                                           * mp)
+{
+  vl_api_ip_neighbor_replace_begin_reply_t *rmp;
+  int rv = 0;
+
+  ip_neighbor_mark (IP46_TYPE_IP4);
+  ip_neighbor_mark (IP46_TYPE_IP6);
+
+  REPLY_MACRO (VL_API_IP_NEIGHBOR_REPLACE_BEGIN_REPLY);
+}
+
+static void
+vl_api_ip_neighbor_replace_end_t_handler (vl_api_ip_neighbor_replace_end_t *
+                                         mp)
+{
+  vl_api_ip_neighbor_replace_end_reply_t *rmp;
+  int rv = 0;
+
+  ip_neighbor_sweep (IP46_TYPE_IP4);
+  ip_neighbor_sweep (IP46_TYPE_IP6);
+
+  REPLY_MACRO (VL_API_IP_NEIGHBOR_REPLACE_END_REPLY);
+}
+
 #define vl_msg_name_crc_list
 #include <vnet/ip-neighbor/ip_neighbor.api.h>
 #undef vl_msg_name_crc_list
index 27262a5..32c674d 100644 (file)
@@ -22,19 +22,14 @@ format_ip_neighbor_flags (u8 * s, va_list * args)
 {
   ip_neighbor_flags_t flags = va_arg (*args, int);
 
-  if (flags & IP_NEIGHBOR_FLAG_STATIC)
-    s = format (s, "S");
-
-  if (flags & IP_NEIGHBOR_FLAG_DYNAMIC)
-    s = format (s, "D");
-
-  if (flags & IP_NEIGHBOR_FLAG_NO_FIB_ENTRY)
-    s = format (s, "N");
-
-  return s;
+#define _(a,b,c,d)                              \
+  if (flags & IP_NEIGHBOR_FLAG_##a)             \
+    s = format (s, "%s", d);
+  foreach_ip_neighbor_flag
+#undef _
+    return s;
 }
 
-
 u8 *
 format_ip_neighbor_key (u8 * s, va_list * va)
 {
index c6d4e10..82c5417 100644 (file)
@@ -37,13 +37,19 @@ typedef struct
   u8 stale_threshold;          /* Threshold in minutes to delete nei entry */
 } ip_neighbor_scan_arg_t;
 
+#define foreach_ip_neighbor_flag                 \
+  _(STATIC, 1 << 0, "static", "S")               \
+  _(DYNAMIC, 1 << 1, "dynamic", "D")             \
+  _(NO_FIB_ENTRY, 1 << 2, "no-fib-entry", "N")   \
+  _(PENDING, 1 << 3, "pending", "P")             \
+  _(STALE, 1 << 4, "stale", "A")                 \
+
 typedef enum ip_neighbor_flags_t_
 {
   IP_NEIGHBOR_FLAG_NONE = 0,
-  IP_NEIGHBOR_FLAG_STATIC = (1 << 0),
-  IP_NEIGHBOR_FLAG_DYNAMIC = (1 << 1),
-  IP_NEIGHBOR_FLAG_NO_FIB_ENTRY = (1 << 2),
-  IP_NEIGHBOR_FLAG_PENDING = (1 << 3),
+#define _(a,b,c,d) IP_NEIGHBOR_FLAG_##a = b,
+  foreach_ip_neighbor_flag
+#undef _
 } __attribute__ ((packed)) ip_neighbor_flags_t;
 
 typedef struct ip_neighbor_watcher_t_
index 7157839..cc1357c 100644 (file)
@@ -1849,5 +1849,122 @@ class NeighborAgeTestCase(VppTestCase):
                                  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)