Port glean neighbor entry support to IPv6
[vpp.git] / vnet / vnet / ip / ip6_neighbor.c
index 70d77a5..329cc6d 100644 (file)
@@ -36,7 +36,11 @@ typedef struct {
 typedef struct {
   ip6_neighbor_key_t key;
   u8 link_layer_address[8];
+  u16 flags;
+#define IP6_NEIGHBOR_FLAG_STATIC (1 << 0)
+#define IP6_NEIGHBOR_FLAG_GLEAN  (2 << 0)
   u64 cpu_time_last_updated;
+  u32 *adjacencies;
 } ip6_neighbor_t;
 
 /* advertised prefix option */ 
@@ -200,17 +204,26 @@ static u8 * format_ip6_neighbor_ip6_entry (u8 * s, va_list * va)
   ip6_neighbor_t * n = va_arg (*va, ip6_neighbor_t *);
   vnet_main_t * vnm = vnet_get_main();
   vnet_sw_interface_t * si;
+  u8 * flags = 0;
 
   if (! n)
-    return format (s, "%=12s%=20s%=20s%=40s", "Time", "Address", "Link layer", "Interface");
+    return format (s, "%=12s%=20s%=6s%=20s%=40s", "Time", "Address", "Flags", "Link layer", "Interface");
+
+  if (n->flags & IP6_NEIGHBOR_FLAG_GLEAN)
+    flags = format(flags, "G");
+
+  if (n->flags & IP6_NEIGHBOR_FLAG_STATIC)
+    flags = format(flags, "S");
 
   si = vnet_get_sw_interface (vnm, n->key.sw_if_index);
-  s = format (s, "%=12U%=20U%=20U%=40U",
+  s = format (s, "%=12U%=20U%=6s%=20U%=40U",
              format_vlib_cpu_time, vm, n->cpu_time_last_updated,
              format_ip6_address, &n->key.ip6_address,
+             flags ? (char *)flags : "",
              format_ethernet_address, n->link_layer_address,
              format_vnet_sw_interface_name, vnm, si);
 
+  vec_free(flags);
   return s;
 }
 
@@ -278,7 +291,7 @@ static void unset_random_neighbor_entry (void)
 
 typedef struct {
   u8 is_add;
-  u8 pad;
+  u8 is_static;
   u8 link_layer_address[6];
   u32 sw_if_index;
   ip6_address_t addr;
@@ -293,15 +306,16 @@ static void set_unset_ip6_neighbor_rpc
  u32 sw_if_index,
  ip6_address_t * a,
  u8 *link_layer_addreess,
- int is_add)
+ int is_add, int is_static)
 {
   ip6_neighbor_set_unset_rpc_args_t args;
   void vl_api_rpc_call_main_thread (void *fp, u8 * data, u32 data_length);
   
   args.sw_if_index = sw_if_index;
   args.is_add = is_add;
-  memcpy (&args.addr, a, sizeof (*a));
-  memcpy (args.link_layer_address, link_layer_addreess, 6);
+  args.is_static = is_static;
+  clib_memcpy (&args.addr, a, sizeof (*a));
+  clib_memcpy (args.link_layer_address, link_layer_addreess, 6);
   
   vl_api_rpc_call_main_thread (ip6_neighbor_set_unset_rpc_callback,
                                (u8 *) &args, sizeof (args));
@@ -313,22 +327,27 @@ vnet_set_ip6_ethernet_neighbor (vlib_main_t * vm,
                                 u32 sw_if_index,
                                 ip6_address_t * a,
                                 u8 * link_layer_address,
-                                uword n_bytes_link_layer_address)
+                                uword n_bytes_link_layer_address,
+                                int is_static)
 {
   vnet_main_t * vnm = vnet_get_main();
   ip6_neighbor_main_t * nm = &ip6_neighbor_main;
   ip6_neighbor_key_t k;
-  ip6_neighbor_t * n;
+  ip6_neighbor_t * n = 0;
   ip6_main_t * im = &ip6_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  int make_new_nd_cache_entry=1;
   uword * p;
   u32 next_index;
+  u32 adj_index;
+  ip_adjacency_t *existing_adj;
   pending_resolution_t * pr;
 
 #if DPDK > 0
   if (os_get_cpu_number())
     {
       set_unset_ip6_neighbor_rpc (vm, sw_if_index, a, link_layer_address,
-                                  1 /* set new neighbor */);
+                                  1 /* set new neighbor */, is_static);
       return 0;
     }
 #endif
@@ -340,44 +359,80 @@ vnet_set_ip6_ethernet_neighbor (vlib_main_t * vm,
   vlib_worker_thread_barrier_sync (vm);
 
   p = mhash_get (&nm->neighbor_index_by_key, &k);
-  if (p)
+  if (p) {
     n = pool_elt_at_index (nm->neighbor_pool, p[0]);
-  else
+    /* Refuse to over-write static neighbor entry. */
+    if (!is_static &&
+        (n->flags & IP6_NEIGHBOR_FLAG_STATIC))
+      return -2;
+    make_new_nd_cache_entry = 0;
+  }
+
+  /* Note: always install the route. It might have been deleted */
+  ip6_add_del_route_args_t args;
+  ip_adjacency_t adj;
+
+  memset (&adj, 0, sizeof(adj));
+  adj.lookup_next_index = IP_LOOKUP_NEXT_REWRITE;
+  adj.explicit_fib_index = ~0;
+
+  vnet_rewrite_for_sw_interface
+  (vnm,
+   VNET_L3_PACKET_TYPE_IP6,
+   sw_if_index,
+   ip6_rewrite_node.index,
+   link_layer_address,
+   &adj.rewrite_header,
+   sizeof (adj.rewrite_data));
+
+  /* result of this lookup should be next-hop adjacency */
+  adj_index = ip6_fib_lookup_with_table (im, im->fib_index_by_sw_if_index[sw_if_index], a);
+  existing_adj = ip_get_adjacency(lm, adj_index);
+
+  if (existing_adj->lookup_next_index == IP_LOOKUP_NEXT_ARP &&
+      existing_adj->arp.next_hop.ip6.as_u64[0] == a->as_u64[0] &&
+      existing_adj->arp.next_hop.ip6.as_u64[1] == a->as_u64[1])
+  {
+    u32 * ai;
+    u32 * adjs = vec_dup(n->adjacencies);
+    /* Update all adj assigned to this arp entry */
+    vec_foreach(ai, adjs)
     {
-      ip6_add_del_route_args_t args;
-      ip_adjacency_t adj;
-
-      memset (&adj, 0, sizeof(adj));
-      adj.lookup_next_index = IP_LOOKUP_NEXT_REWRITE;
-      adj.explicit_fib_index = ~0;
-
-      vnet_rewrite_for_sw_interface
-       (vnm,
-        VNET_L3_PACKET_TYPE_IP6,
-        sw_if_index,
-        ip6_rewrite_node.index,
-        link_layer_address,
-        &adj.rewrite_header,
-        sizeof (adj.rewrite_data));
-
-      args.table_index_or_table_id = im->fib_index_by_sw_if_index[sw_if_index];
-      args.flags = IP6_ROUTE_FLAG_FIB_INDEX | IP6_ROUTE_FLAG_ADD | IP6_ROUTE_FLAG_NEIGHBOR;
-      args.dst_address = a[0];
-      args.dst_address_length = 128;
-      args.adj_index = ~0;
-      args.add_adj = &adj;
-      args.n_add_adj = 1;
-
-      ip6_add_del_route (im, &args);
-      pool_get (nm->neighbor_pool, n);
-      mhash_set (&nm->neighbor_index_by_key, &k, n - nm->neighbor_pool,
-                /* old value */ 0);
-      n->key = k;
+      int i;
+      ip_adjacency_t * uadj = ip_get_adjacency(lm, *ai);
+      for (i = 0; i < uadj->n_adj; i++)
+        if (uadj[i].lookup_next_index == IP_LOOKUP_NEXT_ARP &&
+            uadj[i].arp.next_hop.ip6.as_u64[0] == a->as_u64[0] &&
+            uadj[i].arp.next_hop.ip6.as_u64[1] == a->as_u64[1])
+          ip_update_adjacency (lm, *ai + i, &adj);
     }
+    vec_free(adjs);
+  }
+  else
+  {
+    /* create new adj */
+    args.table_index_or_table_id = im->fib_index_by_sw_if_index[sw_if_index];
+    args.flags = IP6_ROUTE_FLAG_FIB_INDEX | IP6_ROUTE_FLAG_ADD | IP6_ROUTE_FLAG_NEIGHBOR;
+    args.dst_address = a[0];
+    args.dst_address_length = 128;
+    args.adj_index = ~0;
+    args.add_adj = &adj;
+    args.n_add_adj = 1;
+    ip6_add_del_route (im, &args);
+  }
+
+  if (make_new_nd_cache_entry) {
+    pool_get (nm->neighbor_pool, n);
+    mhash_set (&nm->neighbor_index_by_key, &k, n - nm->neighbor_pool,
+               /* old value */ 0);
+    n->key = k;
+  }
 
   /* Update time stamp and ethernet address. */
-  memcpy (n->link_layer_address, link_layer_address, n_bytes_link_layer_address);
+  clib_memcpy (n->link_layer_address, link_layer_address, n_bytes_link_layer_address);
   n->cpu_time_last_updated = clib_cpu_time_now ();
+  if (is_static)
+    n->flags |= IP6_NEIGHBOR_FLAG_STATIC;
 
   /* Customer(s) waiting for this address to be resolved? */
   p = mhash_get (&nm->pending_resolutions_by_address, a);
@@ -422,7 +477,7 @@ vnet_unset_ip6_ethernet_neighbor (vlib_main_t * vm,
   if (os_get_cpu_number())
     {
       set_unset_ip6_neighbor_rpc (vm, sw_if_index, a, link_layer_address,
-                                  0 /* unset */);
+                                  0 /* unset */, 0);
       return 0;
     }
 #endif
@@ -458,6 +513,56 @@ vnet_unset_ip6_ethernet_neighbor (vlib_main_t * vm,
   return rv;
 }
 
+
+u32
+vnet_ip6_neighbor_glean_add(u32 fib_index, void * next_hop_arg)
+{
+  ip6_neighbor_main_t * nm = &ip6_neighbor_main;
+  ip6_main_t * im = &ip6_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  ip6_address_t * next_hop = next_hop_arg;
+  ip_adjacency_t add_adj, *adj;
+  ip6_add_del_route_args_t args;
+  ip6_neighbor_t * n;
+  ip6_neighbor_key_t k;
+  u32 adj_index;
+
+  adj_index = ip6_fib_lookup_with_table(im, fib_index, next_hop);
+  adj = ip_get_adjacency(lm, adj_index);
+
+  if (!adj || adj->lookup_next_index != IP_LOOKUP_NEXT_ARP)
+    return ~0;
+
+  if (adj->arp.next_hop.ip6.as_u64[0] ||
+      adj->arp.next_hop.ip6.as_u64[1])
+    return adj_index;
+
+  k.sw_if_index = adj->rewrite_header.sw_if_index;
+  k.ip6_address = *next_hop;
+  k.pad = 0;
+  if (mhash_get (&nm->neighbor_index_by_key, &k))
+    return adj_index;
+
+  pool_get (nm->neighbor_pool, n);
+  mhash_set (&nm->neighbor_index_by_key, &k, n - nm->neighbor_pool, /* old value */ 0);
+  n->key = k;
+  n->cpu_time_last_updated = clib_cpu_time_now ();
+  n->flags = IP6_NEIGHBOR_FLAG_GLEAN;
+
+  memset(&args, 0, sizeof(args));
+  memcpy(&add_adj, adj, sizeof(add_adj));
+  add_adj.arp.next_hop.ip6 = *next_hop; /* install neighbor /128 route */
+  args.table_index_or_table_id = fib_index;
+  args.flags = IP6_ROUTE_FLAG_FIB_INDEX | IP6_ROUTE_FLAG_ADD | IP6_ROUTE_FLAG_NEIGHBOR;
+  args.dst_address = *next_hop;
+  args.dst_address_length = 128;
+  args.adj_index = ~0;
+  args.add_adj = &add_adj;
+  args.n_add_adj = 1;
+  ip6_add_del_route (im, &args);
+  return ip6_fib_lookup_with_table (im, fib_index, next_hop);
+}
+
 #if DPDK > 0
 static void ip6_neighbor_set_unset_rpc_callback 
 ( ip6_neighbor_set_unset_rpc_args_t * a)
@@ -465,7 +570,7 @@ static void ip6_neighbor_set_unset_rpc_callback
   vlib_main_t * vm = vlib_get_main();
   if (a->is_add) 
       vnet_set_ip6_ethernet_neighbor (vm, a->sw_if_index, &a->addr, 
-                                      a->link_layer_address, 6);
+                                      a->link_layer_address, 6, a->is_static);
   else
     vnet_unset_ip6_ethernet_neighbor (vm, a->sw_if_index, &a->addr, 
                                       a->link_layer_address, 6);
@@ -530,6 +635,7 @@ set_ip6_neighbor (vlib_main_t * vm,
   u8 mac_address[6];
   int addr_valid = 0;
   int is_del = 0;
+  int is_static = 0;
   u32 sw_if_index;
 
   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) 
@@ -543,6 +649,8 @@ set_ip6_neighbor (vlib_main_t * vm,
 
       else if (unformat (input, "delete") || unformat (input, "del"))
         is_del = 1;
+      else if (unformat (input, "static"))
+        is_static = 1;
       else
         break;
     }
@@ -552,7 +660,7 @@ set_ip6_neighbor (vlib_main_t * vm,
   
   if (!is_del)
     vnet_set_ip6_ethernet_neighbor (vm, sw_if_index, &addr,
-                                    mac_address, sizeof(mac_address));
+                                    mac_address, sizeof(mac_address), is_static);
   else
     vnet_unset_ip6_ethernet_neighbor (vm, sw_if_index, &addr,
                                       mac_address, sizeof(mac_address));
@@ -562,7 +670,7 @@ set_ip6_neighbor (vlib_main_t * vm,
 VLIB_CLI_COMMAND (set_ip6_neighbor_command, static) = {
   .path = "set ip6 neighbor",
   .function = set_ip6_neighbor,
-  .short_help = "set ip6 neighbor [del] <intfc> <ip6-address> <mac-address>",
+  .short_help = "set ip6 neighbor [del] <intfc> <ip6-address> <mac-address> [static]",
 };
 
 typedef enum {
@@ -666,7 +774,7 @@ icmp6_neighbor_solicitation_or_advertisement (vlib_main_t * vm,
               vnet_set_ip6_ethernet_neighbor (
                   vm, sw_if_index0,
                   is_solicitation ? &ip0->src_address : &h0->target_address,
-                  o0->ethernet_address, sizeof (o0->ethernet_address));
+                  o0->ethernet_address, sizeof (o0->ethernet_address), 0);
             }
 
          if (is_solicitation && error0 == ICMP6_ERROR_NONE)
@@ -722,7 +830,7 @@ icmp6_neighbor_solicitation_or_advertisement (vlib_main_t * vm,
              eth_if0 = ethernet_get_interface (&ethernet_main, sw_if0->hw_if_index);
              if (eth_if0 && o0)
                {
-                  memcpy (o0->ethernet_address, eth_if0->address, 6);
+                  clib_memcpy (o0->ethernet_address, eth_if0->address, 6);
                  o0->header.type = 
                       ICMP6_NEIGHBOR_DISCOVERY_OPTION_target_link_layer_address;
                }
@@ -741,8 +849,8 @@ icmp6_neighbor_solicitation_or_advertisement (vlib_main_t * vm,
                * interface MAC to SMAC */
               vlib_buffer_reset (p0);
               eth0 = vlib_buffer_get_current(p0);
-              memcpy(eth0->dst_address, eth0->src_address, 6);
-              memcpy(eth0->src_address, eth_if0->address, 6);
+              clib_memcpy(eth0->dst_address, eth0->src_address, 6);
+              clib_memcpy(eth0->src_address, eth_if0->address, 6);
 
               /* Setup input and output sw_if_index for packet */
               ASSERT(vnet_buffer(p0)->sw_if_index[VLIB_RX] == sw_if_index0);
@@ -931,7 +1039,7 @@ icmp6_router_solicitation(vlib_main_t * vm,
               vnet_set_ip6_ethernet_neighbor (vm, sw_if_index0,
                                               &ip0->src_address,
                                               o0->ethernet_address,
-                                              sizeof (o0->ethernet_address));
+                                              sizeof (o0->ethernet_address), 0);
           }
              
          /* default is to drop */
@@ -1013,7 +1121,7 @@ icmp6_router_solicitation(vlib_main_t * vm,
                          h.header.n_data_u64s = 1;
 
                          /* copy ll address */
-                         memcpy(&h.ethernet_address[0], eth_if0->address,  6);
+                         clib_memcpy(&h.ethernet_address[0], eth_if0->address,  6);
 
                          vlib_buffer_add_data (vm,
                                                p0->free_list_index,
@@ -1081,7 +1189,7 @@ icmp6_router_solicitation(vlib_main_t * vm,
                                  }
                                h.unused  = 0;
                                
-                               memcpy(&h.dst_address, &pr_info->prefix,  sizeof(ip6_address_t));
+                               clib_memcpy(&h.dst_address, &pr_info->prefix,  sizeof(ip6_address_t));
 
                                payload_length += sizeof( icmp6_neighbor_discovery_prefix_information_option_t); 
 
@@ -1131,8 +1239,8 @@ icmp6_router_solicitation(vlib_main_t * vm,
                            * interface MAC to SMAC */
                           vlib_buffer_reset (p0);
                           eth0 = vlib_buffer_get_current(p0);
-                          memcpy(eth0->dst_address, eth0->src_address, 6);
-                          memcpy(eth0->src_address, eth_if0->address, 6);
+                          clib_memcpy(eth0->dst_address, eth0->src_address, 6);
+                          clib_memcpy(eth0->src_address, eth_if0->address, 6);
                          next0 = is_dropped ? 
                               next0 : ICMP6_ROUTER_SOLICITATION_NEXT_REPLY_TX;
                           vnet_buffer(p0)->sw_if_index[VLIB_TX] = sw_if_index0;
@@ -1568,7 +1676,7 @@ ip6_neighbor_sw_interface_add_del (vnet_main_t * vnm,
         /* fill in radv_info for this interface that will be needed later */
         a->adv_link_mtu = hw_if0->max_l3_packet_bytes[VLIB_RX];
         
-        memcpy (a->link_layer_address, eth_if0->address, 6);
+        clib_memcpy (a->link_layer_address, eth_if0->address, 6);
         
         /* fill in default link-local address  (this may be overridden) */
         ip6_link_local_address_from_ethernet_address (&a->link_local_address, eth_if0->address);
@@ -1666,7 +1774,7 @@ ip6_neighbor_sw_interface_add_del (vnet_main_t * vnm,
             mcast_group_info->type = 4;
             mcast_group_info->mcast_source_address_pool = 0;
             mcast_group_info->num_sources = 0;
-            memcpy(&mcast_group_info->mcast_address, &addr, sizeof(ip6_address_t));
+            clib_memcpy(&mcast_group_info->mcast_address, &addr, sizeof(ip6_address_t));
           } 
         
         ip6_set_reserved_multicast_address (&addr,
@@ -1688,7 +1796,7 @@ ip6_neighbor_sw_interface_add_del (vnet_main_t * vnm,
             mcast_group_info->type = 4;
             mcast_group_info->mcast_source_address_pool = 0;
             mcast_group_info->num_sources = 0;
-            memcpy(&mcast_group_info->mcast_address, &addr, sizeof(ip6_address_t));
+            clib_memcpy(&mcast_group_info->mcast_address, &addr, sizeof(ip6_address_t));
           } 
         
         ip6_set_reserved_multicast_address (&addr,
@@ -1710,7 +1818,7 @@ ip6_neighbor_sw_interface_add_del (vnet_main_t * vnm,
             mcast_group_info->type = 4;
             mcast_group_info->mcast_source_address_pool = 0;
             mcast_group_info->num_sources = 0;
-            memcpy(&mcast_group_info->mcast_address, &addr, sizeof(ip6_address_t));
+            clib_memcpy(&mcast_group_info->mcast_address, &addr, sizeof(ip6_address_t));
           } 
        }
    } 
@@ -1824,7 +1932,7 @@ ip6_neighbor_send_mldpv2_report(u32 sw_if_index)
        rr.type = m->type;
        rr.aux_data_len_u32s = 0;
        rr.num_sources = clib_host_to_net_u16 (m->num_sources);
-       memcpy(&rr.mcast_addr, &m->mcast_address, sizeof(ip6_address_t));
+       clib_memcpy(&rr.mcast_addr, &m->mcast_address, sizeof(ip6_address_t));
 
        num_addr_records++;
 
@@ -1894,8 +2002,8 @@ ip6_neighbor_process_timer_event (vlib_main_t * vm,
   ip6_radv_t *radv_info; 
   vlib_frame_t * f = 0; 
   u32 n_this_frame = 0;
-  u32 n_left_to_next;
-  u32 * to_next;
+  u32 n_left_to_next = 0;
+  u32 * to_next = 0;
   u32 bo0; 
   icmp6_router_solicitation_header_t * h0;
   vlib_buffer_t * b0;
@@ -2270,7 +2378,7 @@ ip6_neighbor_ra_prefix(vlib_main_t * vm, u32 sw_if_index,
          memset(prefix, 0x0, sizeof(ip6_radv_prefix_t));
          
          prefix->prefix_len = prefix_len;
-         memcpy(&prefix->prefix,  prefix_addr, sizeof(ip6_address_t));
+         clib_memcpy(&prefix->prefix,  prefix_addr, sizeof(ip6_address_t));
          
          /* initialize default values */
          prefix->adv_on_link_flag = 1;      /* L bit set */
@@ -2775,7 +2883,7 @@ enable_ip6_interface(vlib_main_t * vm,
                      md5_add (&m, &link_local_address, 16);
                      md5_finish (&m,  digest);
                      
-                     memcpy(&link_local_address, digest, 16);
+                     clib_memcpy(&link_local_address, digest, 16);
                      
                      radv_info->randomizer = link_local_address.as_u64[0];
                      
@@ -3028,7 +3136,7 @@ ip6_neighbor_add_del_interface_address (ip6_main_t * im,
              mcast_group_info->type = 4;
              mcast_group_info->mcast_source_address_pool = 0;
              mcast_group_info->num_sources = 0;
-             memcpy(&mcast_group_info->mcast_address, &a, sizeof(ip6_address_t));
+             clib_memcpy(&mcast_group_info->mcast_address, &a, sizeof(ip6_address_t));
            } 
        }
     }
@@ -3072,10 +3180,87 @@ clib_error_t *ip6_set_neighbor_limit (u32 neighbor_limit)
   return 0;
 }
 
+
+static void
+ip6_neighbor_entry_del_adj(ip6_neighbor_t *n, u32 adj_index)
+{
+  int done = 0;
+  int i;
+  while (!done)
+    {
+      vec_foreach_index(i, n->adjacencies)
+        if (vec_elt(n->adjacencies, i) == adj_index)
+          {
+            vec_del1(n->adjacencies, i);
+            continue;
+          }
+      done = 1;
+    }
+}
+
+static void
+ip6_neighbor_entry_add_adj(ip6_neighbor_t *n, u32 adj_index)
+{
+  int i;
+  vec_foreach_index(i, n->adjacencies)
+    if (vec_elt(n->adjacencies, i) == adj_index)
+      return;
+  vec_add1(n->adjacencies, adj_index);
+}
+
+static void
+ip6_neighbor_add_del_adj_cb (struct ip_lookup_main_t * lm,
+                    u32 adj_index,
+                    ip_adjacency_t * adj,
+                    u32 is_del)
+{
+  ip6_neighbor_main_t * nm = &ip6_neighbor_main;
+  ip6_neighbor_key_t k;
+  ip6_neighbor_t *n = 0;
+  uword * p;
+  u32 ai;
+
+  for(ai = adj->heap_handle; ai < adj->heap_handle + adj->n_adj ; ai++)
+    {
+      adj = ip_get_adjacency (lm, ai);
+      if (adj->lookup_next_index == IP_LOOKUP_NEXT_ARP &&
+          (adj->arp.next_hop.ip6.as_u64[0] || adj->arp.next_hop.ip6.as_u64[1]))
+        {
+          k.sw_if_index = adj->rewrite_header.sw_if_index;
+          k.ip6_address.as_u64[0] = adj->arp.next_hop.ip6.as_u64[0];
+          k.ip6_address.as_u64[1] = adj->arp.next_hop.ip6.as_u64[1];
+          k.pad = 0;
+          p = mhash_get (&nm->neighbor_index_by_key, &k);
+          if (p)
+            n = pool_elt_at_index (nm->neighbor_pool, p[0]);
+        }
+      else
+        continue;
+
+      if (is_del)
+        {
+          if (!n)
+            clib_warning("Adjacency contains unknown ND next hop %U (del)",
+                         format_ip46_address, &adj->arp.next_hop);
+          else
+            ip6_neighbor_entry_del_adj(n, adj->heap_handle);
+        }
+      else /* add */
+        {
+          if (!n)
+            clib_warning("Adjacency contains unknown ND next hop %U (add)",
+                         format_ip46_address, &adj->arp.next_hop);
+          else
+            ip6_neighbor_entry_add_adj(n, adj->heap_handle);
+        }
+    }
+}
+
 static clib_error_t * ip6_neighbor_init (vlib_main_t * vm)
 {
   ip6_neighbor_main_t * nm = &ip6_neighbor_main;
   ip6_main_t * im = &ip6_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
  
   mhash_init (&nm->neighbor_index_by_key,
              /* value size */ sizeof (uword),
@@ -3111,6 +3296,8 @@ static clib_error_t * ip6_neighbor_init (vlib_main_t * vm)
       (im->discover_neighbor_next_index_by_hw_if_index, 32, 0 /* drop */);
 #endif
 
+  ip_register_add_del_adjacency_callback(lm, ip6_neighbor_add_del_adj_cb);
+
   return 0;
 }