LISP: L2 ARP handling 39/6839/5
authorFilip Tehlar <ftehlar@cisco.com>
Wed, 17 May 2017 15:21:10 +0000 (17:21 +0200)
committerFilip Tehlar <ftehlar@cisco.com>
Tue, 30 May 2017 05:18:54 +0000 (07:18 +0200)
Change-Id: I1ec328cda73f7eaf7867cd8a2a17852ee0cd23f1
Signed-off-by: Filip Tehlar <ftehlar@cisco.com>
src/vat/api_format.c
src/vnet/lisp-cp/control.c
src/vnet/lisp-cp/control.h
src/vnet/lisp-cp/gid_dictionary.c
src/vnet/lisp-cp/gid_dictionary.h
src/vnet/lisp-cp/lisp_types.c
src/vnet/lisp-cp/lisp_types.h
src/vnet/lisp-cp/one.api
src/vnet/lisp-cp/one_api.c
src/vnet/lisp-cp/one_cli.c

index f3e6f64..3eff8ef 100644 (file)
@@ -3163,6 +3163,123 @@ end:
   vam->result_ready = 1;
 }
 
+static void
+  vl_api_one_l2_arp_entries_get_reply_t_handler
+  (vl_api_one_l2_arp_entries_get_reply_t * mp)
+{
+  vat_main_t *vam = &vat_main;
+  u32 i, n;
+  int retval = clib_net_to_host_u32 (mp->retval);
+
+  if (retval)
+    goto end;
+
+  n = clib_net_to_host_u32 (mp->count);
+
+  for (i = 0; i < n; i++)
+    print (vam->ofp, "%U -> %U", format_ip4_address, &mp->entries[i].ip4,
+          format_ethernet_address, mp->entries[i].mac);
+
+end:
+  vam->retval = retval;
+  vam->result_ready = 1;
+}
+
+static void
+  vl_api_one_l2_arp_entries_get_reply_t_handler_json
+  (vl_api_one_l2_arp_entries_get_reply_t * mp)
+{
+  u8 *s = 0;
+  vat_main_t *vam = &vat_main;
+  vat_json_node_t *e = 0, root;
+  u32 i, n;
+  int retval = clib_net_to_host_u32 (mp->retval);
+  vl_api_one_l2_arp_entry_t *arp_entry;
+
+  if (retval)
+    goto end;
+
+  n = clib_net_to_host_u32 (mp->count);
+  vat_json_init_array (&root);
+
+  for (i = 0; i < n; i++)
+    {
+      e = vat_json_array_add (&root);
+      arp_entry = &mp->entries[i];
+
+      vat_json_init_object (e);
+      s = format (0, "%U", format_ethernet_address, arp_entry->mac);
+      vec_add1 (s, 0);
+
+      vat_json_object_add_string_copy (e, "mac", s);
+      vec_free (s);
+
+      s = format (0, "%U", format_ip4_address, &arp_entry->ip4);
+      vec_add1 (s, 0);
+      vat_json_object_add_string_copy (e, "ip4", s);
+      vec_free (s);
+    }
+
+  vat_json_print (vam->ofp, &root);
+  vat_json_free (&root);
+
+end:
+  vam->retval = retval;
+  vam->result_ready = 1;
+}
+
+static void
+  vl_api_one_l2_arp_bd_get_reply_t_handler
+  (vl_api_one_l2_arp_bd_get_reply_t * mp)
+{
+  vat_main_t *vam = &vat_main;
+  u32 i, n;
+  int retval = clib_net_to_host_u32 (mp->retval);
+
+  if (retval)
+    goto end;
+
+  n = clib_net_to_host_u32 (mp->count);
+
+  for (i = 0; i < n; i++)
+    {
+      print (vam->ofp, "%d", clib_net_to_host_u32 (mp->bridge_domains[i]));
+    }
+
+end:
+  vam->retval = retval;
+  vam->result_ready = 1;
+}
+
+static void
+  vl_api_one_l2_arp_bd_get_reply_t_handler_json
+  (vl_api_one_l2_arp_bd_get_reply_t * mp)
+{
+  vat_main_t *vam = &vat_main;
+  vat_json_node_t root;
+  u32 i, n;
+  int retval = clib_net_to_host_u32 (mp->retval);
+
+  if (retval)
+    goto end;
+
+  n = clib_net_to_host_u32 (mp->count);
+  vat_json_init_array (&root);
+
+  for (i = 0; i < n; i++)
+    {
+      vat_json_array_add_uint (&root,
+                              clib_net_to_host_u32 (mp->bridge_domains[i]));
+    }
+
+  vat_json_print (vam->ofp, &root);
+  vat_json_free (&root);
+
+end:
+  vam->retval = retval;
+  vam->result_ready = 1;
+}
+
 static void
   vl_api_one_adjacencies_get_reply_t_handler
   (vl_api_one_adjacencies_get_reply_t * mp)
@@ -4158,6 +4275,10 @@ static void vl_api_flow_classify_details_t_handler_json
 #define vl_api_vnet_ip6_nbr_counters_t_print vl_noop_handler
 #define vl_api_one_adjacencies_get_reply_t_endian vl_noop_handler
 #define vl_api_one_adjacencies_get_reply_t_print vl_noop_handler
+#define vl_api_one_l2_arp_bd_get_reply_t_print vl_noop_handler
+#define vl_api_one_l2_arp_entries_get_reply_t_endian vl_noop_handler
+#define vl_api_one_l2_arp_entries_get_reply_t_print vl_noop_handler
+#define vl_api_one_l2_arp_bd_get_reply_t_endian vl_noop_handler
 
 /*
  * Generate boilerplate reply handlers, which
@@ -4267,6 +4388,7 @@ _(one_add_del_map_request_itr_rlocs_reply)              \
 _(one_eid_table_add_del_map_reply)                      \
 _(one_use_petr_reply)                                   \
 _(one_stats_enable_disable_reply)                       \
+_(one_add_del_l2_arp_entry_reply)                       \
 _(one_stats_flush_reply)                                \
 _(gpe_add_del_fwd_entry_reply)                          \
 _(gpe_enable_disable_reply)                             \
@@ -4499,6 +4621,9 @@ _(ONE_STATS_FLUSH_REPLY, one_stats_flush_reply)                         \
 _(ONE_STATS_ENABLE_DISABLE_REPLY, one_stats_enable_disable_reply)       \
 _(SHOW_ONE_STATS_ENABLE_DISABLE_REPLY,                                  \
   show_one_stats_enable_disable_reply)                                  \
+_(ONE_ADD_DEL_L2_ARP_ENTRY_REPLY, one_add_del_l2_arp_entry_reply)       \
+_(ONE_L2_ARP_BD_GET_REPLY, one_l2_arp_bd_get_reply)                     \
+_(ONE_L2_ARP_ENTRIES_GET_REPLY, one_l2_arp_entries_get_reply)           \
 _(GPE_SET_ENCAP_MODE_REPLY, gpe_set_encap_mode_reply)                   \
 _(GPE_GET_ENCAP_MODE_REPLY, gpe_get_encap_mode_reply)                   \
 _(GPE_ADD_DEL_IFACE_REPLY, gpe_add_del_iface_reply)                     \
@@ -14736,6 +14861,111 @@ api_show_one_rloc_probe_state (vat_main_t * vam)
 
 #define api_show_lisp_rloc_probe_state api_show_one_rloc_probe_state
 
+static int
+api_one_add_del_l2_arp_entry (vat_main_t * vam)
+{
+  vl_api_one_add_del_l2_arp_entry_t *mp;
+  unformat_input_t *input = vam->input;
+  u8 is_add = 1;
+  u8 mac_set = 0;
+  u8 bd_set = 0;
+  u8 ip_set = 0;
+  u8 mac[6] = { 0, };
+  u32 ip4 = 0, bd = ~0;
+  int ret;
+
+  /* Parse args required to build the message */
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "del"))
+       is_add = 0;
+      else if (unformat (input, "mac %U", unformat_ethernet_address, mac))
+       mac_set = 1;
+      else if (unformat (input, "ip %U", unformat_ip4_address, &ip4))
+       ip_set = 1;
+      else if (unformat (input, "bd %d", &bd))
+       bd_set = 1;
+      else
+       {
+         errmsg ("parse error '%U'", format_unformat_error, input);
+         return -99;
+       }
+    }
+
+  if (!bd_set || !ip_set || (!mac_set && is_add))
+    {
+      errmsg ("Missing BD, IP or MAC!");
+      return -99;
+    }
+
+  M (ONE_ADD_DEL_L2_ARP_ENTRY, mp);
+  mp->is_add = is_add;
+  clib_memcpy (mp->mac, mac, 6);
+  mp->bd = clib_host_to_net_u32 (bd);
+  mp->ip4 = ip4;
+
+  /* send */
+  S (mp);
+
+  /* wait for reply */
+  W (ret);
+  return ret;
+}
+
+static int
+api_one_l2_arp_bd_get (vat_main_t * vam)
+{
+  vl_api_one_l2_arp_bd_get_t *mp;
+  int ret;
+
+  M (ONE_L2_ARP_BD_GET, mp);
+
+  /* send */
+  S (mp);
+
+  /* wait for reply */
+  W (ret);
+  return ret;
+}
+
+static int
+api_one_l2_arp_entries_get (vat_main_t * vam)
+{
+  vl_api_one_l2_arp_entries_get_t *mp;
+  unformat_input_t *input = vam->input;
+  u8 bd_set = 0;
+  u32 bd = ~0;
+  int ret;
+
+  /* Parse args required to build the message */
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "bd %d", &bd))
+       bd_set = 1;
+      else
+       {
+         errmsg ("parse error '%U'", format_unformat_error, input);
+         return -99;
+       }
+    }
+
+  if (!bd_set)
+    {
+      errmsg ("Expected bridge domain!");
+      return -99;
+    }
+
+  M (ONE_L2_ARP_ENTRIES_GET, mp);
+  mp->bd = clib_host_to_net_u32 (bd);
+
+  /* send */
+  S (mp);
+
+  /* wait for reply */
+  W (ret);
+  return ret;
+}
+
 static int
 api_one_stats_enable_disable (vat_main_t * vam)
 {
@@ -19052,6 +19282,9 @@ _(one_locator_set_dump, "[local | remote]")                             \
 _(one_locator_dump, "ls_index <index> | ls_name <name>")                \
 _(one_eid_table_dump, "[eid <ipv4|ipv6>/<prefix> | <mac>] [vni] "       \
                        "[local] | [remote]")                            \
+_(one_add_del_l2_arp_entry, "[del] mac <mac> bd <bd> ip4 <ip4>")        \
+_(one_l2_arp_bd_get, "")                                                \
+_(one_l2_arp_entries_get, "bd <bridge-domain>")                         \
 _(one_stats_enable_disable, "enable|disalbe")                           \
 _(show_one_stats_enable_disable, "")                                    \
 _(one_eid_table_vni_dump, "")                                           \
index 439802c..cea9255 100644 (file)
@@ -22,6 +22,8 @@
 #include <vnet/lisp-gpe/lisp_gpe_tunnel.h>
 #include <vnet/fib/fib_entry.h>
 #include <vnet/fib/fib_table.h>
+#include <vnet/ethernet/arp_packet.h>
+#include <vnet/ethernet/packet.h>
 
 #include <openssl/evp.h>
 #include <openssl/hmac.h>
@@ -821,6 +823,99 @@ vnet_lisp_add_del_local_mapping (vnet_lisp_add_del_mapping_args_t * a,
   return vnet_lisp_map_cache_add_del (a, map_index_result);
 }
 
+static void
+add_l2_arp_bd (BVT (clib_bihash_kv) * kvp, void *arg)
+{
+  u32 **ht = arg;
+  u32 bd = (u32) kvp->key[0];
+  hash_set (ht[0], bd, 0);
+}
+
+u32 *
+vnet_lisp_l2_arp_bds_get (void)
+{
+  lisp_cp_main_t *lcm = vnet_lisp_cp_get_main ();
+  u32 *bds = 0;
+
+  gid_dict_foreach_l2_arp_entry (&lcm->mapping_index_by_gid,
+                                add_l2_arp_bd, &bds);
+  return bds;
+}
+
+typedef struct
+{
+  void *vector;
+  u32 bd;
+} lisp_add_l2_arp_args_t;
+
+static void
+add_l2_arp_entry (BVT (clib_bihash_kv) * kvp, void *arg)
+{
+  lisp_add_l2_arp_args_t *a = arg;
+  lisp_api_l2_arp_entry_t **vector = a->vector, e;
+
+  if ((u32) kvp->key[0] == a->bd)
+    {
+      mac_copy (e.mac, (void *) &kvp->value);
+      e.ip4 = (u32) kvp->key[1];
+      vec_add1 (vector[0], e);
+    }
+}
+
+lisp_api_l2_arp_entry_t *
+vnet_lisp_l2_arp_entries_get_by_bd (u32 bd)
+{
+  lisp_api_l2_arp_entry_t *entries = 0;
+  lisp_cp_main_t *lcm = vnet_lisp_cp_get_main ();
+  lisp_add_l2_arp_args_t a;
+
+  a.vector = &entries;
+  a.bd = bd;
+
+  gid_dict_foreach_l2_arp_entry (&lcm->mapping_index_by_gid,
+                                add_l2_arp_entry, &a);
+  return entries;
+}
+
+int
+vnet_lisp_add_del_l2_arp_entry (gid_address_t * key, u8 * mac, u8 is_add)
+{
+  if (vnet_lisp_enable_disable_status () == 0)
+    {
+      clib_warning ("LISP is disabled!");
+      return VNET_API_ERROR_LISP_DISABLED;
+    }
+
+  lisp_cp_main_t *lcm = vnet_lisp_cp_get_main ();
+  int rc = 0;
+
+  u64 res = gid_dictionary_lookup (&lcm->mapping_index_by_gid, key);
+  if (is_add)
+    {
+      if (res != GID_LOOKUP_MISS_L2)
+       {
+         clib_warning ("Entry %U exists in DB!", format_gid_address, key);
+         return VNET_API_ERROR_ENTRY_ALREADY_EXISTS;
+       }
+      u64 val = mac_to_u64 (mac);
+      gid_dictionary_add_del (&lcm->mapping_index_by_gid, key, val,
+                             1 /* is_add */ );
+    }
+  else
+    {
+      if (res == GID_LOOKUP_MISS_L2)
+       {
+         clib_warning ("ONE ARP entry %U not found - cannot delete!",
+                       format_gid_address, key);
+         return -1;
+       }
+      gid_dictionary_add_del (&lcm->mapping_index_by_gid, key, 0,
+                             0 /* is_add */ );
+    }
+
+  return rc;
+}
+
 int
 vnet_lisp_eid_table_map (u32 vni, u32 dp_id, u8 is_l2, u8 is_add)
 {
@@ -830,7 +925,7 @@ vnet_lisp_eid_table_map (u32 vni, u32 dp_id, u8 is_l2, u8 is_add)
   if (vnet_lisp_enable_disable_status () == 0)
     {
       clib_warning ("LISP is disabled!");
-      return -1;
+      return VNET_API_ERROR_LISP_DISABLED;
     }
 
   dp_table_by_vni = is_l2 ? &lcm->bd_id_by_vni : &lcm->table_id_by_vni;
@@ -1931,7 +2026,8 @@ vnet_lisp_add_del_mreq_itr_rlocs (vnet_lisp_add_del_mreq_itr_rloc_args_t * a)
 /* Statistics (not really errors) */
 #define foreach_lisp_cp_lookup_error           \
 _(DROP, "drop")                                \
-_(MAP_REQUESTS_SENT, "map-request sent")
+_(MAP_REQUESTS_SENT, "map-request sent")       \
+_(ARP_REPLY_TX, "ARP replies sent")
 
 static char *lisp_cp_lookup_error_strings[] = {
 #define _(sym,string) string,
@@ -1950,6 +2046,7 @@ typedef enum
 typedef enum
 {
   LISP_CP_LOOKUP_NEXT_DROP,
+  LISP_CP_LOOKUP_NEXT_ARP_REPLY_TX,
   LISP_CP_LOOKUP_N_NEXT,
 } lisp_cp_lookup_next_t;
 
@@ -2710,10 +2807,8 @@ lisp_get_vni_from_buffer_ip (lisp_cp_main_t * lcm, vlib_buffer_t * b,
 }
 
 always_inline u32
-lisp_get_vni_from_buffer_eth (lisp_cp_main_t * lcm, vlib_buffer_t * b)
+lisp_get_bd_from_buffer_eth (vlib_buffer_t * b)
 {
-  uword *vnip;
-  u32 vni = ~0;
   u32 sw_if_index0;
 
   l2input_main_t *l2im = &l2input_main;
@@ -2724,12 +2819,21 @@ lisp_get_vni_from_buffer_eth (lisp_cp_main_t * lcm, vlib_buffer_t * b)
   config = vec_elt_at_index (l2im->configs, sw_if_index0);
   bd_config = vec_elt_at_index (l2im->bd_configs, config->bd_index);
 
-  vnip = hash_get (lcm->vni_by_bd_id, bd_config->bd_id);
+  return bd_config->bd_id;
+}
+
+always_inline u32
+lisp_get_vni_from_buffer_eth (lisp_cp_main_t * lcm, vlib_buffer_t * b)
+{
+  uword *vnip;
+  u32 vni = ~0;
+  u32 bd = lisp_get_bd_from_buffer_eth (b);
+
+  vnip = hash_get (lcm->vni_by_bd_id, bd);
   if (vnip)
     vni = vnip[0];
   else
-    clib_warning ("bridge domain %d is not mapped to any vni!",
-                 config->bd_index);
+    clib_warning ("bridge domain %d is not mapped to any vni!", bd);
 
   return vni;
 }
@@ -2744,6 +2848,9 @@ get_src_and_dst_eids_from_buffer (lisp_cp_main_t * lcm, vlib_buffer_t * b,
   memset (src, 0, sizeof (*src));
   memset (dst, 0, sizeof (*dst));
 
+  gid_address_type (dst) = GID_ADDR_NO_ADDRESS;
+  gid_address_type (src) = GID_ADDR_NO_ADDRESS;
+
   if (LISP_AFI_IP == type || LISP_AFI_IP6 == type)
     {
       ip4_header_t *ip;
@@ -2767,19 +2874,35 @@ get_src_and_dst_eids_from_buffer (lisp_cp_main_t * lcm, vlib_buffer_t * b,
   else if (LISP_AFI_MAC == type)
     {
       ethernet_header_t *eh;
+      ethernet_arp_header_t *ah;
 
       eh = vlib_buffer_get_current (b);
 
-      gid_address_type (src) = GID_ADDR_MAC;
-      gid_address_type (dst) = GID_ADDR_MAC;
-      mac_copy (&gid_address_mac (src), eh->src_address);
-      mac_copy (&gid_address_mac (dst), eh->dst_address);
+      if (clib_net_to_host_u16 (eh->type) == ETHERNET_TYPE_ARP)
+       {
+         ah = (ethernet_arp_header_t *) (((u8 *) eh) + sizeof (*eh));
+         if (clib_net_to_host_u16 (ah->opcode)
+             != ETHERNET_ARP_OPCODE_request)
+           return;
+
+         gid_address_type (dst) = GID_ADDR_ARP;
+         gid_address_arp_bd (dst) = lisp_get_bd_from_buffer_eth (b);
+         clib_memcpy (&gid_address_arp_ip4 (dst),
+                      &ah->ip4_over_ethernet[1].ip4, 4);
+       }
+      else
+       {
+         gid_address_type (src) = GID_ADDR_MAC;
+         gid_address_type (dst) = GID_ADDR_MAC;
+         mac_copy (&gid_address_mac (src), eh->src_address);
+         mac_copy (&gid_address_mac (dst), eh->dst_address);
 
-      /* get vni */
-      vni = lisp_get_vni_from_buffer_eth (lcm, b);
+         /* get vni */
+         vni = lisp_get_vni_from_buffer_eth (lcm, b);
 
-      gid_address_vni (dst) = vni;
-      gid_address_vni (src) = vni;
+         gid_address_vni (dst) = vni;
+         gid_address_vni (src) = vni;
+       }
     }
   else if (LISP_AFI_LCAF == type)
     {
@@ -2793,38 +2916,81 @@ lisp_cp_lookup_inline (vlib_main_t * vm,
                       vlib_node_runtime_t * node,
                       vlib_frame_t * from_frame, int overlay)
 {
-  u32 *from, *to_next_drop, di, si;
+  u32 *from, *to_next, di, si;
   lisp_cp_main_t *lcm = vnet_lisp_cp_get_main ();
-  u32 pkts_mapped = 0;
-  uword n_left_from, n_left_to_next_drop;
+  u32 pkts_mapped = 0, next_index;
+  uword n_left_from, n_left_to_next;
+  vnet_main_t *vnm = vnet_get_main ();
 
   from = vlib_frame_vector_args (from_frame);
   n_left_from = from_frame->n_vectors;
+  next_index = node->cached_next_index;
 
   while (n_left_from > 0)
     {
-      vlib_get_next_frame (vm, node, LISP_CP_LOOKUP_NEXT_DROP,
-                          to_next_drop, n_left_to_next_drop);
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
 
-      while (n_left_from > 0 && n_left_to_next_drop > 0)
+      while (n_left_from > 0 && n_left_to_next > 0)
        {
-         u32 pi0;
+         u32 pi0, sw_if_index0, next0;
+         u64 mac0;
          vlib_buffer_t *b0;
          gid_address_t src, dst;
+         ethernet_arp_header_t *arp0;
+         ethernet_header_t *eth0;
+         vnet_hw_interface_t *hw_if0;
 
          pi0 = from[0];
          from += 1;
          n_left_from -= 1;
-         to_next_drop[0] = pi0;
-         to_next_drop += 1;
-         n_left_to_next_drop -= 1;
+         to_next[0] = pi0;
+         to_next += 1;
+         n_left_to_next -= 1;
 
          b0 = vlib_get_buffer (vm, pi0);
-         b0->error = node->errors[LISP_CP_LOOKUP_ERROR_DROP];
 
          /* src/dst eid pair */
          get_src_and_dst_eids_from_buffer (lcm, b0, &src, &dst, overlay);
 
+         if (gid_address_type (&dst) == GID_ADDR_ARP)
+           {
+             mac0 = gid_dictionary_lookup (&lcm->mapping_index_by_gid, &dst);
+             if (GID_LOOKUP_MISS_L2 != mac0)
+               {
+                 /* send ARP reply */
+
+                 sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
+                 vnet_buffer (b0)->sw_if_index[VLIB_TX] = sw_if_index0;
+
+                 hw_if0 = vnet_get_sup_hw_interface (vnm, sw_if_index0);
+
+                 eth0 = vlib_buffer_get_current (b0);
+                 arp0 = (ethernet_arp_header_t *) (((u8 *) eth0)
+                                                   + sizeof (*eth0));
+                 arp0->opcode =
+                   clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply);
+                 arp0->ip4_over_ethernet[1] = arp0->ip4_over_ethernet[0];
+                 clib_memcpy (arp0->ip4_over_ethernet[0].ethernet,
+                              (u8 *) & mac0, 6);
+                 clib_memcpy (&arp0->ip4_over_ethernet[0].ip4,
+                              &gid_address_arp_ip4 (&dst), 4);
+
+                 /* Hardware must be ethernet-like. */
+                 ASSERT (vec_len (hw_if0->hw_address) == 6);
+
+                 clib_memcpy (eth0->dst_address, eth0->src_address, 6);
+                 clib_memcpy (eth0->src_address, hw_if0->hw_address, 6);
+
+                 b0->error = node->errors[LISP_CP_LOOKUP_ERROR_ARP_REPLY_TX];
+                 next0 = LISP_CP_LOOKUP_NEXT_ARP_REPLY_TX;
+                 vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                                  to_next,
+                                                  n_left_to_next, pi0,
+                                                  next0);
+               }
+             continue;
+           }
+
          /* if we have remote mapping for destination already in map-chache
             add forwarding tunnel directly. If not send a map-request */
          di = gid_dictionary_sd_lookup (&lcm->mapping_index_by_gid, &dst,
@@ -2859,6 +3025,7 @@ lisp_cp_lookup_inline (vlib_main_t * vm,
              pkts_mapped++;
            }
 
+         b0->error = node->errors[LISP_CP_LOOKUP_ERROR_DROP];
          if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
            {
              lisp_cp_lookup_trace_t *tr = vlib_add_trace (vm, node, b0,
@@ -2871,10 +3038,13 @@ lisp_cp_lookup_inline (vlib_main_t * vm,
            }
          gid_address_free (&dst);
          gid_address_free (&src);
+         next0 = LISP_CP_LOOKUP_NEXT_DROP;
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                          to_next,
+                                          n_left_to_next, pi0, next0);
        }
 
-      vlib_put_next_frame (vm, node, LISP_CP_LOOKUP_NEXT_DROP,
-                          n_left_to_next_drop);
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
     }
   vlib_node_increment_counter (vm, node->node_index,
                               LISP_CP_LOOKUP_ERROR_MAP_REQUESTS_SENT,
@@ -2926,6 +3096,7 @@ VLIB_REGISTER_NODE (lisp_cp_lookup_ip4_node) = {
 
   .next_nodes = {
       [LISP_CP_LOOKUP_NEXT_DROP] = "error-drop",
+      [LISP_CP_LOOKUP_NEXT_ARP_REPLY_TX] = "interface-output",
   },
 };
 /* *INDENT-ON* */
@@ -2945,6 +3116,7 @@ VLIB_REGISTER_NODE (lisp_cp_lookup_ip6_node) = {
 
   .next_nodes = {
       [LISP_CP_LOOKUP_NEXT_DROP] = "error-drop",
+      [LISP_CP_LOOKUP_NEXT_ARP_REPLY_TX] = "interface-output",
   },
 };
 /* *INDENT-ON* */
@@ -2964,6 +3136,7 @@ VLIB_REGISTER_NODE (lisp_cp_lookup_l2_node) = {
 
   .next_nodes = {
       [LISP_CP_LOOKUP_NEXT_DROP] = "error-drop",
+      [LISP_CP_LOOKUP_NEXT_ARP_REPLY_TX] = "interface-output",
   },
 };
 /* *INDENT-ON* */
@@ -2983,6 +3156,7 @@ VLIB_REGISTER_NODE (lisp_cp_lookup_nsh_node) = {
 
   .next_nodes = {
       [LISP_CP_LOOKUP_NEXT_DROP] = "error-drop",
+      [LISP_CP_LOOKUP_NEXT_ARP_REPLY_TX] = "interface-output",
   },
 };
 /* *INDENT-ON* */
index cb98eb0..feb8cfa 100644 (file)
@@ -87,6 +87,12 @@ typedef struct
   miss_packet_type_t type;
 } miss_packet_t;
 
+typedef struct
+{
+  u8 mac[6];
+  u32 ip4;
+} lisp_api_l2_arp_entry_t;
+
 typedef enum
 {
   MR_MODE_DST_ONLY = 0,
@@ -112,6 +118,12 @@ typedef enum lisp_flags
 #undef _
 } lisp_flags_e;
 
+typedef struct
+{
+  ip_address_t addr;
+  u32 bd;
+} lisp_l2_arp_key_t;
+
 typedef struct
 {
   u32 flags;
@@ -338,6 +350,9 @@ int vnet_lisp_rloc_probe_enable_disable (u8 is_enable);
 int vnet_lisp_map_register_enable_disable (u8 is_enable);
 u8 vnet_lisp_map_register_state_get (void);
 u8 vnet_lisp_rloc_probe_state_get (void);
+int vnet_lisp_add_del_l2_arp_entry (gid_address_t * key, u8 * mac, u8 is_add);
+u32 *vnet_lisp_l2_arp_bds_get (void);
+lisp_api_l2_arp_entry_t *vnet_lisp_l2_arp_entries_get_by_bd (u32 bd);
 
 map_records_arg_t *parse_map_reply (vlib_buffer_t * b);
 
index b01bb0e..80d59fa 100644 (file)
@@ -138,6 +138,15 @@ gid_dict_foreach_subprefix (gid_dictionary_t * db, gid_address_t * eid,
                                    arg);
 }
 
+void
+gid_dict_foreach_l2_arp_entry (gid_dictionary_t * db, void (*cb)
+                              (BVT (clib_bihash_kv) * kvp, void *arg),
+                              void *ht)
+{
+  gid_l2_arp_table_t *tab = &db->arp_table;
+  BV (clib_bihash_foreach_key_value_pair) (&tab->arp_lookup_table, cb, ht);
+}
+
 static void
 make_mac_sd_key (BVT (clib_bihash_kv) * kv, u32 vni, u8 src_mac[6],
                 u8 dst_mac[6])
@@ -328,7 +337,30 @@ ip_sd_lookup (gid_dictionary_t * db, u32 vni, ip_prefix_t * dst,
   return GID_LOOKUP_MISS;
 }
 
-u32
+static void
+make_arp_key (BVT (clib_bihash_kv) * kv, u32 bd, ip4_address_t * addr)
+{
+  kv->key[0] = (u64) bd;
+  kv->key[1] = (u64) addr->as_u32;
+  kv->key[2] = (u64) 0;
+}
+
+static u64
+arp_lookup (gid_l2_arp_table_t * db, u32 bd, ip4_address_t * key)
+{
+  int rv;
+  BVT (clib_bihash_kv) kv, value;
+
+  make_arp_key (&kv, bd, key);
+  rv = BV (clib_bihash_search_inline_2) (&db->arp_lookup_table, &kv, &value);
+
+  if (rv == 0)
+    return value.value;
+
+  return GID_LOOKUP_MISS_L2;
+}
+
+u64
 gid_dictionary_lookup (gid_dictionary_t * db, gid_address_t * key)
 {
   switch (gid_address_type (key))
@@ -358,6 +390,9 @@ gid_dictionary_lookup (gid_dictionary_t * db, gid_address_t * key)
          break;
        }
       break;
+    case GID_ADDR_ARP:
+      return arp_lookup (&db->arp_table, gid_address_arp_bd (key),
+                        &gid_address_arp_ip4 (key));
     default:
       clib_warning ("address type %d not supported!", gid_address_type (key));
       break;
@@ -825,21 +860,49 @@ add_del_sd (gid_dictionary_t * db, u32 vni, source_dest_t * key, u32 value,
   return ~0;
 }
 
+static u32
+add_del_arp (gid_l2_arp_table_t * db, u32 bd, ip4_address_t * key, u64 value,
+            u8 is_add)
+{
+  BVT (clib_bihash_kv) kv, result;
+  u32 old_val = ~0;
+
+  make_arp_key (&kv, bd, key);
+  if (BV (clib_bihash_search) (&db->arp_lookup_table, &kv, &result) == 0)
+    old_val = result.value;
+
+  if (is_add)
+    {
+      kv.value = value;
+      BV (clib_bihash_add_del) (&db->arp_lookup_table, &kv, 1 /* is_add */ );
+      db->count++;
+    }
+  else
+    {
+      BV (clib_bihash_add_del) (&db->arp_lookup_table, &kv, 0 /* is_add */ );
+      db->count--;
+    }
+  return old_val;
+}
+
 u32
-gid_dictionary_add_del (gid_dictionary_t * db, gid_address_t * key, u32 value,
+gid_dictionary_add_del (gid_dictionary_t * db, gid_address_t * key, u64 value,
                        u8 is_add)
 {
   switch (gid_address_type (key))
     {
     case GID_ADDR_IP_PREFIX:
       return add_del_ip (db, gid_address_vni (key), &gid_address_ippref (key),
-                        0, value, is_add);
+                        0, (u32) value, is_add);
     case GID_ADDR_MAC:
       return add_del_mac (&db->sd_mac_table, gid_address_vni (key),
-                         gid_address_mac (key), 0, value, is_add);
+                         gid_address_mac (key), 0, (u32) value, is_add);
     case GID_ADDR_SRC_DST:
       return add_del_sd (db, gid_address_vni (key), &gid_address_sd (key),
-                        value, is_add);
+                        (u32) value, is_add);
+    case GID_ADDR_ARP:
+      return add_del_arp (&db->arp_table, gid_address_arp_bd (key),
+                         &gid_address_arp_ip4 (key), value, is_add);
     default:
       clib_warning ("address type %d not supported!", gid_address_type (key));
       break;
@@ -864,12 +927,30 @@ mac_lookup_init (gid_mac_table_t * db)
                         db->mac_lookup_table_size);
 }
 
+static void
+arp_lookup_init (gid_l2_arp_table_t * db)
+{
+  if (db->arp_lookup_table_nbuckets == 0)
+    db->arp_lookup_table_nbuckets = ARP_LOOKUP_DEFAULT_HASH_NUM_BUCKETS;
+
+  db->arp_lookup_table_nbuckets =
+    1 << max_log2 (db->arp_lookup_table_nbuckets);
+
+  if (db->arp_lookup_table_size == 0)
+    db->arp_lookup_table_size = ARP_LOOKUP_DEFAULT_HASH_MEMORY_SIZE;
+
+  BV (clib_bihash_init) (&db->arp_lookup_table, "arp lookup table",
+                        db->arp_lookup_table_nbuckets,
+                        db->arp_lookup_table_size);
+}
+
 void
 gid_dictionary_init (gid_dictionary_t * db)
 {
   ip4_lookup_init (&db->dst_ip4_table);
   ip6_lookup_init (&db->dst_ip6_table);
   mac_lookup_init (&db->sd_mac_table);
+  arp_lookup_init (&db->arp_table);
 }
 
 /*
index 825e985..9612fb1 100644 (file)
@@ -22,6 +22,7 @@
 #include <vppinfra/bihash_template.h>
 
 #define GID_LOOKUP_MISS ((u32)~0)
+#define GID_LOOKUP_MISS_L2 ((u64)~0)
 
 /* Default size of the ip4 hash table */
 #define IP4_LOOKUP_DEFAULT_HASH_NUM_BUCKETS (64 * 1024)
 #define MAC_LOOKUP_DEFAULT_HASH_NUM_BUCKETS (64 * 1024)
 #define MAC_LOOKUP_DEFAULT_HASH_MEMORY_SIZE (32<<20)
 
+/* Default size of the ARP hash table */
+#define ARP_LOOKUP_DEFAULT_HASH_NUM_BUCKETS (64 * 1024)
+#define ARP_LOOKUP_DEFAULT_HASH_MEMORY_SIZE (32<<20)
+
 typedef void (*foreach_subprefix_match_cb_t) (u32, void *);
 
 typedef struct
@@ -81,6 +86,17 @@ typedef struct gid_mac_table
 
 typedef struct
 {
+  BVT (clib_bihash) arp_lookup_table;
+  u32 arp_lookup_table_nbuckets;
+  uword arp_lookup_table_size;
+  u64 count;
+} gid_l2_arp_table_t;
+
+typedef struct
+{
+  /** L2 ARP table */
+  gid_l2_arp_table_t arp_table;
+
   /** destination IP LPM ip4 lookup table */
   gid_ip4_table_t dst_ip4_table;
 
@@ -99,10 +115,10 @@ typedef struct
 } gid_dictionary_t;
 
 u32
-gid_dictionary_add_del (gid_dictionary_t * db, gid_address_t * key, u32 value,
+gid_dictionary_add_del (gid_dictionary_t * db, gid_address_t * key, u64 value,
                        u8 is_add);
 
-u32 gid_dictionary_lookup (gid_dictionary_t * db, gid_address_t * key);
+u64 gid_dictionary_lookup (gid_dictionary_t * db, gid_address_t * key);
 u32 gid_dictionary_sd_lookup (gid_dictionary_t * db, gid_address_t * dst,
                              gid_address_t * src);
 
@@ -112,6 +128,11 @@ void
 gid_dict_foreach_subprefix (gid_dictionary_t * db, gid_address_t * eid,
                            foreach_subprefix_match_cb_t cb, void *arg);
 
+void
+gid_dict_foreach_l2_arp_entry (gid_dictionary_t * db, void (*cb)
+                              (BVT (clib_bihash_kv) * kvp, void *arg),
+                              void *ht);
+
 #endif /* VNET_LISP_GPE_GID_DICTIONARY_H_ */
 
 /*
index 31a8008..85cefae 100644 (file)
@@ -256,6 +256,9 @@ format_gid_address (u8 * s, va_list * args)
                     &gid_address_mac (a));
     case GID_ADDR_NSH:
       return format (s, "%U", format_nsh_address, &gid_address_nsh (a));
+    case GID_ADDR_ARP:
+      return format (s, "[%d, %U]", gid_address_arp_bd (a),
+                    format_ip4_address, &gid_address_arp_ip4 (a));
     default:
       clib_warning ("Can't format gid type %d", type);
       return 0;
index a65a479..f5d2a67 100644 (file)
@@ -90,6 +90,7 @@ typedef enum
   GID_ADDR_MAC,
   GID_ADDR_SRC_DST,
   GID_ADDR_NSH,
+  GID_ADDR_ARP,
   GID_ADDR_NO_ADDRESS,
   GID_ADDR_TYPES
 } gid_address_type_t;
@@ -165,12 +166,22 @@ typedef struct
   u8 si;
 } nsh_t;
 
+typedef struct
+{
+  ip4_address_t addr;
+  u32 bd;
+} lcaf_arp_t;
+
+#define lcaf_arp_ip4(_a) (_a)->addr
+#define lcaf_arp_bd(_a) (_a)->bd
+
 typedef struct
 {
   /* the union needs to be at the beginning! */
   union
   {
     source_dest_t sd;
+    lcaf_arp_t arp;
     vni_t uni;
   };
   u8 type;
@@ -189,6 +200,7 @@ typedef struct _gid_address_t
     lcaf_t lcaf;
     u8 mac[6];
     source_dest_t sd;
+    lcaf_arp_t arp;
     nsh_t nsh;
   };
   u8 type;
@@ -257,6 +269,9 @@ void gid_address_ip_set (gid_address_t * dst, void *src, u8 version);
 #define gid_address_sd_dst(_a) sd_dst(&gid_address_sd(_a))
 #define gid_address_sd_src_type(_a) sd_src_type(&gid_address_sd(_a))
 #define gid_address_sd_dst_type(_a) sd_dst_type(&gid_address_sd(_a))
+#define gid_address_arp(_a) (_a)->arp
+#define gid_address_arp_ip4(_a) lcaf_arp_ip4(&gid_address_arp (_a))
+#define gid_address_arp_bd(_a) lcaf_arp_bd(&gid_address_arp (_a))
 
 /* 'sub'address functions */
 #define foreach_gid_address_type_fcns  \
index 2fa1edf..7d07cc4 100644 (file)
@@ -348,6 +348,79 @@ autoreply manual_print manual_endian define one_add_del_remote_mapping
   vl_api_one_remote_locator_t rlocs[rloc_num];
 };
 
+/** \brief Add/delete L2 ARP entries
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param is_add - add if non-zero; delete otherwise
+    @param bd - bridge domain
+    @param mac - MAC address
+    @param ip4 - IPv4 address
+*/
+autoreply define one_add_del_l2_arp_entry
+{
+  u32 client_index;
+  u32 context;
+  u8 is_add;
+  u8 mac[6];
+  u32 bd;
+  u32 ip4;
+};
+
+/** \brief Request for L2 ARP entries from specified bridge domain
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param bd - bridge domain
+*/
+define one_l2_arp_entries_get
+{
+  u32 client_index;
+  u32 context;
+  u32 bd;
+};
+
+typeonly manual_print manual_endian define one_l2_arp_entry
+{
+  u8 mac[6];
+  u32 ip4;
+};
+
+/** \brief Reply with L2 ARP entries from specified bridge domain
+    @param context - sender context, to match reply w/ request
+    @param retval - error code
+    @param count - number of elements in the list
+    @param vl_api_one_arp_entry_t - list of entries
+*/
+manual_print manual_endian define one_l2_arp_entries_get_reply
+{
+  u32 context;
+  i32 retval;
+  u32 count;
+  vl_api_one_l2_arp_entry_t entries[count];
+};
+
+/** \brief Request for list of bridge domains used by L2 ARP table
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+*/
+define one_l2_arp_bd_get
+{
+  u32 client_index;
+  u32 context;
+};
+
+/** \brief Reply with list of bridge domains used by L2 ARP table
+    @param context - sender context, to match reply w/ request
+    @param count - number of elements in the list
+    @param bridge_domains - list of BDs
+*/
+manual_print manual_endian define one_l2_arp_bd_get_reply
+{
+  u32 context;
+  i32 retval;
+  u32 count;
+  u32 bridge_domains[count];
+};
+
 /** \brief add or delete ONE adjacency adjacency
     @param client_index - opaque cookie to identify the sender
     @param context - sender context, to match reply w/ request
index 23549af..724e58d 100644 (file)
 #define vl_api_one_add_del_remote_mapping_t_endian vl_noop_handler
 #define vl_api_one_add_del_remote_mapping_t_print vl_noop_handler
 
+#define vl_api_one_l2_arp_entry_t_endian vl_noop_handler
+#define vl_api_one_l2_arp_entry_t_print vl_noop_handler
+#define vl_api_one_add_del_l2_arp_entry vl_noop_handler
+#define vl_api_one_l2_arp_bd_get vl_noop_handler
+
 #define vl_typedefs            /* define message structures */
 #include <vnet/vnet_all_api_h.h>
 #undef vl_typedefs
@@ -109,6 +114,9 @@ _(SHOW_ONE_STATS_ENABLE_DISABLE, show_one_stats_enable_disable)         \
 _(ONE_STATS_ENABLE_DISABLE, one_stats_enable_disable)                   \
 _(ONE_STATS_DUMP, one_stats_dump)                                       \
 _(ONE_STATS_FLUSH, one_stats_flush)                                     \
+_(ONE_L2_ARP_BD_GET, one_l2_arp_bd_get)                                 \
+_(ONE_L2_ARP_ENTRIES_GET, one_l2_arp_entries_get)                       \
+_(ONE_ADD_DEL_L2_ARP_ENTRY, one_add_del_l2_arp_entry)                   \
 
 
 static locator_t *
@@ -1387,6 +1395,80 @@ vl_api_one_stats_dump_t_handler (vl_api_one_stats_dump_t * mp)
   }
 }
 
+static void
+  vl_api_one_add_del_l2_arp_entry_t_handler
+  (vl_api_one_add_del_l2_arp_entry_t * mp)
+{
+  vl_api_one_add_del_l2_arp_entry_reply_t *rmp;
+  int rv = 0;
+  gid_address_t _arp, *arp = &_arp;
+  memset (arp, 0, sizeof (*arp));
+
+  gid_address_type (arp) = GID_ADDR_ARP;
+  gid_address_arp_bd (arp) = clib_net_to_host_u32 (mp->bd);
+
+  /* vpp keeps ip4 addresses in network byte order */
+  clib_memcpy (&gid_address_arp_ip4 (arp), &mp->ip4, 4);
+
+  rv = vnet_lisp_add_del_l2_arp_entry (arp, mp->mac, mp->is_add);
+
+  REPLY_MACRO (VL_API_ONE_ADD_DEL_L2_ARP_ENTRY_REPLY);
+}
+
+static void
+vl_api_one_l2_arp_bd_get_t_handler (vl_api_one_l2_arp_bd_get_t * mp)
+{
+  vl_api_one_l2_arp_bd_get_reply_t *rmp;
+  int rv = 0;
+  u32 i = 0;
+  hash_pair_t *p;
+
+  u32 *bds = vnet_lisp_l2_arp_bds_get ();
+  u32 size = hash_elts (bds) * sizeof (u32);
+
+  /* *INDENT-OFF* */
+  REPLY_MACRO4 (VL_API_ONE_L2_ARP_BD_GET_REPLY, size,
+  {
+    rmp->count = clib_host_to_net_u32 (hash_elts (bds));
+    hash_foreach_pair (p, bds,
+    ({
+      rmp->bridge_domains[i++] = clib_host_to_net_u32 (p->key);
+    }));
+  });
+  /* *INDENT-ON* */
+
+  hash_free (bds);
+}
+
+static void
+vl_api_one_l2_arp_entries_get_t_handler (vl_api_one_l2_arp_entries_get_t * mp)
+{
+  vl_api_one_l2_arp_entries_get_reply_t *rmp;
+  lisp_api_l2_arp_entry_t *entries = 0, *e;
+  u32 i = 0;
+  int rv = 0;
+
+  u32 bd = clib_net_to_host_u32 (mp->bd);
+
+  entries = vnet_lisp_l2_arp_entries_get_by_bd (bd);
+  u32 size = vec_len (entries) * sizeof (u32);
+
+  /* *INDENT-OFF* */
+  REPLY_MACRO4 (VL_API_ONE_L2_ARP_ENTRIES_GET_REPLY, size,
+  {
+    rmp->count = clib_host_to_net_u32 (vec_len (entries));
+    vec_foreach (e, entries)
+      {
+        mac_copy (rmp->entries[i].mac, e->mac);
+        rmp->entries[i].ip4 = e->ip4;
+        i++;
+      }
+  });
+  /* *INDENT-ON* */
+
+  vec_free (entries);
+}
+
 /*
  * one_api_hookup
  * Add vpe's API message handlers to the table.
index 109aca2..db6a6c8 100644 (file)
@@ -276,6 +276,106 @@ VLIB_CLI_COMMAND (one_eid_table_map_command) = {
 };
 /* *INDENT-ON* */
 
+static clib_error_t *
+lisp_add_del_l2_arp_entry_command_fn (vlib_main_t * vm,
+                                     unformat_input_t * input,
+                                     vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  clib_error_t *error = NULL;
+  int rc = 0;
+  u8 hw_addr[6], bd = 0;
+  ip4_address_t ip4;
+  u32 hw_addr_set = 0, ip_set = 0, is_add = 1;
+  gid_address_t _arp, *arp = &_arp;
+
+  memset (&ip4, 0, sizeof (ip4));
+  memset (hw_addr, 0, sizeof (hw_addr));
+  memset (arp, 0, sizeof (*arp));
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "mac %U", unformat_mac_address, hw_addr))
+       hw_addr_set = 1;
+      else if (unformat (line_input, "ip %U", unformat_ip4_address, &ip4))
+       ip_set = 1;
+      else if (unformat (line_input, "del"))
+       is_add = 0;
+      else if (unformat (line_input, "bd %d", &bd))
+       ;
+      else
+       {
+         error = clib_error_return (0, "parse error");
+         goto done;
+       }
+    }
+
+  if (!ip_set || (!hw_addr_set && is_add))
+    {
+      vlib_cli_output (vm, "expected IP and MAC addresses!");
+      return 0;
+    }
+
+  /* build GID address */
+  gid_address_arp_ip4 (arp) = ip4;
+  gid_address_arp_bd (arp) = bd;
+  gid_address_type (arp) = GID_ADDR_ARP;
+  rc = vnet_lisp_add_del_l2_arp_entry (arp, hw_addr, is_add);
+  if (rc)
+    clib_warning ("Failed to %s l2 arp entry!", is_add ? "add" : "delete");
+
+done:
+  unformat_free (line_input);
+  return error;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (one_add_del_l2_arp_entry_command) = {
+    .path = "one l2 arp",
+    .short_help = "one l2 arp [del] bd <bd> mac <mac> ip <ipv4>",
+    .function = lisp_add_del_l2_arp_entry_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+lisp_show_l2_arp_entries_command_fn (vlib_main_t * vm,
+                                    unformat_input_t * input,
+                                    vlib_cli_command_t * cmd)
+{
+  u32 *ht = vnet_lisp_l2_arp_bds_get ();
+  lisp_api_l2_arp_entry_t *entries, *e;
+  hash_pair_t *p;
+
+  /* *INDENT-OFF* */
+  hash_foreach_pair (p, ht,
+  ({
+    entries = vnet_lisp_l2_arp_entries_get_by_bd (p->key);
+    vlib_cli_output (vm, "Table: %d", p->key);
+
+    vec_foreach (e, entries)
+      {
+        vlib_cli_output (vm, "\t%U -> %U", format_ip4_address, &e->ip4,
+                         format_mac_address, e->mac);
+      }
+    vec_free (entries);
+  }));
+  /* *INDENT-ON* */
+
+  hash_free (ht);
+  return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (one_show_l2_arp_entries_command) = {
+    .path = "show one l2 arp entries",
+    .short_help = "Show ONE L2 ARP entries",
+    .function = lisp_show_l2_arp_entries_command_fn,
+};
+/* *INDENT-ON* */
+
 /**
  * Handler for add/del remote mapping CLI.
  *