A Protocol Independent Hierarchical FIB (VPP-352)
[vpp.git] / vnet / vnet / mpls / interface.c
similarity index 69%
rename from vnet/vnet/mpls-gre/interface.c
rename to vnet/vnet/mpls/interface.c
index dd61a80..9ef4c29 100644 (file)
 #include <vnet/vnet.h>
 #include <vnet/pg/pg.h>
 #include <vnet/gre/gre.h>
-#include <vnet/mpls-gre/mpls.h>
+#include <vnet/mpls/mpls.h>
+#include <vnet/fib/ip4_fib.h>
+#include <vnet/adj/adj_midchain.h>
+#include <vnet/dpo/classify_dpo.h>
 
 static uword mpls_gre_set_rewrite (vnet_main_t * vnm,
                               u32 sw_if_index,
@@ -525,24 +528,23 @@ VNET_HW_INTERFACE_CLASS (mpls_eth_hw_interface_class) = {
   .set_rewrite = mpls_eth_set_rewrite,
 };
 
-#define foreach_mpls_post_rewrite_next \
-  _ (IP4_LOOKUP, "ip4-lookup")
-
-typedef enum {
-#define _(s,n) MPLS_POST_REWRITE_NEXT_##s,
-  foreach_mpls_post_rewrite_next
-#undef _
-  MPLS_POST_REWRITE_N_NEXT,
-} mpls_post_rewrite_next_t;
-
+/**
+ * A conversion of DPO next object tpyes to VLIB graph next nodes from
+ * the mpls_post_rewrite node
+ */
+static const int dpo_next_2_mpls_post_rewrite[DPO_LAST] = {
+    [DPO_LOAD_BALANCE] = IP_LOOKUP_NEXT_LOAD_BALANCE,
+};
 
 static uword
 mpls_post_rewrite (vlib_main_t * vm,
                    vlib_node_runtime_t * node,
                    vlib_frame_t * from_frame)
 {
+  ip4_main_t * im = &ip4_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
   u32 n_left_from, next_index, * from, * to_next;
-  u16 old_l0 = 0, old_l1 = 0;
+  u16 old_l0 = 0; //, old_l1 = 0;
 
   from = vlib_frame_vector_args (from_frame);
   n_left_from = from_frame->n_vectors;
@@ -556,78 +558,103 @@ mpls_post_rewrite (vlib_main_t * vm,
       vlib_get_next_frame (vm, node, next_index,
                           to_next, n_left_to_next);
 
-      while (n_left_from >= 4 && n_left_to_next >= 2)
-       {
-         u32 bi0, bi1;
-         vlib_buffer_t * b0, * b1;
-          ip4_header_t * ip0, * ip1;
-         u32 next0 = MPLS_POST_REWRITE_NEXT_IP4_LOOKUP;
-         u32 next1 = MPLS_POST_REWRITE_NEXT_IP4_LOOKUP;
-          u16 new_l0, new_l1;
-          ip_csum_t sum0, sum1;
-
-         /* Prefetch next iteration. */
-         {
-           vlib_buffer_t * p2, * p3;
+      /* while (n_left_from >= 4 && n_left_to_next >= 2) */
+      /*       { */
+      /*         u32 bi0, bi1; */
+      /*         vlib_buffer_t * b0, * b1; */
+      /*     ip4_header_t * ip0, * ip1; */
+      /*         u32 next0; */
+      /*         u32 next1; */
+      /*     u16 new_l0, new_l1, adj_index0, adj_index1; */
+      /*     ip_csum_t sum0, sum1; */
+      /*         ip_adjacency_t *adj0, *adj1; */
+
+      /*         /\* Prefetch next iteration. *\/ */
+      /*         { */
+      /*           vlib_buffer_t * p2, * p3; */
+
+      /*           p2 = vlib_get_buffer (vm, from[2]); */
+      /*           p3 = vlib_get_buffer (vm, from[3]); */
+
+      /*           vlib_prefetch_buffer_header (p2, LOAD); */
+      /*           vlib_prefetch_buffer_header (p3, LOAD); */
+
+      /*           CLIB_PREFETCH (p2->data, 2*CLIB_CACHE_LINE_BYTES, LOAD); */
+      /*           CLIB_PREFETCH (p3->data, 2*CLIB_CACHE_LINE_BYTES, LOAD); */
+      /*         } */
+
+      /*         bi0 = from[0]; */
+      /*         bi1 = from[1]; */
+      /*         to_next[0] = bi0; */
+      /*         to_next[1] = bi1; */
+      /*         from += 2; */
+      /*         to_next += 2; */
+      /*         n_left_to_next -= 2; */
+      /*         n_left_from -= 2; */
+
+
+      /*         b0 = vlib_get_buffer (vm, bi0); */
+      /*         b1 = vlib_get_buffer (vm, bi1); */
+      /*     ip0 = vlib_buffer_get_current (b0); */
+      /*     ip1 = vlib_buffer_get_current (b1); */
+          
+      /*     /\* Note: the tunnel rewrite sets up sw_if_index[VLIB_TX] *\/ */
 
-           p2 = vlib_get_buffer (vm, from[2]);
-           p3 = vlib_get_buffer (vm, from[3]);
+      /*     /\* set the GRE (outer) ip packet length, fix the bloody checksum *\/ */
+      /*     sum0 = ip0->checksum; */
+      /*     sum1 = ip1->checksum; */
 
-           vlib_prefetch_buffer_header (p2, LOAD);
-           vlib_prefetch_buffer_header (p3, LOAD);
+      /*     /\* old_l0, old_l1 always 0, see the rewrite setup *\/ */
+      /*     new_l0 =  */
+      /*       clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b0)); */
+      /*     new_l1 =  */
+      /*       clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b1)); */
+          
+      /*     sum0 = ip_csum_update (sum0, old_l0, new_l0, ip4_header_t, */
+      /*                            length /\* changed member *\/); */
+      /*     sum1 = ip_csum_update (sum1, old_l1, new_l1, ip4_header_t, */
+      /*                            length /\* changed member *\/); */
+      /*     ip0->checksum = ip_csum_fold (sum0); */
+      /*     ip1->checksum = ip_csum_fold (sum1); */
+      /*     ip0->length = new_l0; */
+      /*     ip1->length = new_l1; */
 
-           CLIB_PREFETCH (p2->data, 2*CLIB_CACHE_LINE_BYTES, LOAD);
-           CLIB_PREFETCH (p3->data, 2*CLIB_CACHE_LINE_BYTES, LOAD);
-         }
+      /*         /\* replace the TX adj in the packet with the next in the chain *\/ */
+      /*         adj_index0 = vnet_buffer (b0)->ip.adj_index[VLIB_TX]; */
+      /*         adj_index1 = vnet_buffer (b1)->ip.adj_index[VLIB_TX]; */
 
-         bi0 = from[0];
-         bi1 = from[1];
-         to_next[0] = bi0;
-         to_next[1] = bi1;
-         from += 2;
-         to_next += 2;
-         n_left_to_next -= 2;
-         n_left_from -= 2;
+      /*         adj0 = ip_get_adjacency (lm, adj_index0); */
+      /*         adj1 = ip_get_adjacency (lm, adj_index1); */
 
+      /*         ASSERT(adj0->sub_type.midchain.adj_index != ADJ_INDEX_INVALID); */
+      /*         ASSERT(adj1->sub_type.midchain.adj_index != ADJ_INDEX_INVALID); */
 
-         b0 = vlib_get_buffer (vm, bi0);
-         b1 = vlib_get_buffer (vm, bi1);
-          ip0 = vlib_buffer_get_current (b0);
-          ip1 = vlib_buffer_get_current (b1);
-          
-          /* Note: the tunnel rewrite sets up sw_if_index[VLIB_TX] */
+      /*         adj_index0 = adj0->sub_type.midchain.adj_index; */
+      /*         adj_index1 = adj1->sub_type.midchain.adj_index; */
 
-          /* set the GRE (outer) ip packet length, fix the bloody checksum */
-          sum0 = ip0->checksum;
-          sum1 = ip1->checksum;
+      /*         vnet_buffer (b0)->ip.adj_index[VLIB_TX] = adj_index0; */
+      /*         vnet_buffer (b1)->ip.adj_index[VLIB_TX] = adj_index1; */
 
-          /* old_l0, old_l1 always 0, see the rewrite setup */
-          new_l0 = 
-            clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b0));
-          new_l1 = 
-            clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b1));
-          
-          sum0 = ip_csum_update (sum0, old_l0, new_l0, ip4_header_t,
-                                 length /* changed member */);
-          sum1 = ip_csum_update (sum1, old_l1, new_l1, ip4_header_t,
-                                 length /* changed member */);
-          ip0->checksum = ip_csum_fold (sum0);
-          ip1->checksum = ip_csum_fold (sum1);
-          ip0->length = new_l0;
-          ip1->length = new_l1;
+      /*         /\* get the next adj in the chain to determine the next graph node *\/ */
+      /*         adj0 = ip_get_adjacency (lm, adj_index0); */
+      /*         adj1 = ip_get_adjacency (lm, adj_index1); */
+
+      /*         next0 = adj0->lookup_next_index; */
+      /*         next1 = adj1->lookup_next_index; */
+
+      /*         vlib_validate_buffer_enqueue_x2 (vm, node, next_index, */
+      /*                                          to_next, n_left_to_next, */
+      /*                                          bi0, bi1, next0, next1); */
+      /*       } */
 
-         vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
-                                          to_next, n_left_to_next,
-                                          bi0, bi1, next0, next1);
-       }
-    
       while (n_left_from > 0 && n_left_to_next > 0)
        {
+         ip_adjacency_t * adj0;
          u32 bi0;
          vlib_buffer_t * b0;
           ip4_header_t * ip0;
-         u32 next0 = MPLS_POST_REWRITE_NEXT_IP4_LOOKUP;
-          u16 new_l0;
+         u32 next0;
+          u16 new_l0, adj_index0;
           ip_csum_t sum0;
 
          bi0 = from[0];
@@ -653,6 +680,20 @@ mpls_post_rewrite (vlib_main_t * vm,
           ip0->checksum = ip_csum_fold (sum0);
           ip0->length = new_l0;
 
+         /* replace the TX adj in the packet with the next in the chain */
+         adj_index0 = vnet_buffer (b0)->ip.adj_index[VLIB_TX];
+
+          ASSERT(adj_index0);
+
+         adj0 = ip_get_adjacency (lm, adj_index0);
+         ASSERT(adj0->sub_type.midchain.next_dpo.dpoi_index != ADJ_INDEX_INVALID);
+         adj_index0 = adj0->sub_type.midchain.next_dpo.dpoi_index;
+         vnet_buffer (b0)->ip.adj_index[VLIB_TX] = adj_index0;
+
+         /* get the next adj in the chain to determine the next graph node */
+         ASSERT(0);
+         next0 = 0; //adj0->sub_type.midchain.next_dpo.dpoi_next;
+
          vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
                                           to_next, n_left_to_next,
                                           bi0, next0);
@@ -673,12 +714,8 @@ VLIB_REGISTER_NODE (mpls_post_rewrite_node) = {
 
   .runtime_data_bytes = 0,
 
-  .n_next_nodes = MPLS_POST_REWRITE_N_NEXT,
-  .next_nodes = {
-#define _(s,n) [MPLS_POST_REWRITE_NEXT_##s] = n,
-    foreach_mpls_post_rewrite_next
-#undef _
-  },
+  .n_next_nodes = IP_LOOKUP_N_NEXT,
+  .next_nodes = IP4_LOOKUP_NEXT_NODES,
 };
 
 VLIB_NODE_FUNCTION_MULTIARCH (mpls_post_rewrite_node, mpls_post_rewrite)
@@ -725,237 +762,512 @@ static u8 * mpls_gre_rewrite (mpls_main_t *mm, mpls_gre_tunnel_t * t)
   return (rewrite_data);
 }
 
-int vnet_mpls_gre_add_del_tunnel (ip4_address_t *src,
-                                  ip4_address_t *dst,
-                                  ip4_address_t *intfc,
-                                  u32 mask_width,
-                                  u32 inner_fib_id, u32 outer_fib_id,
-                                  u32 * tunnel_sw_if_index,
-                                  u8 l2_only,
-                                  u8 is_add)
+u8
+mpls_sw_interface_is_enabled (u32 sw_if_index)
 {
-  ip4_main_t * im = &ip4_main;
-  ip_lookup_main_t * lm = &im->lookup_main;
-  mpls_main_t * mm = &mpls_main;
-  vnet_main_t * vnm = vnet_get_main();
-  ip4_address_t zero;
-  mpls_gre_tunnel_t *tp;
-  int need_route_add_del = 1;
-  u32 inner_fib_index = 0;
-  u32 outer_fib_index = 0;
-  ip_adjacency_t adj;
-  u32 adj_index;
-  u8 * rewrite_data;
-  int found_tunnel = 0;
-  mpls_encap_t * e = 0;
-  u32 hw_if_index = ~0;
-  vnet_hw_interface_t * hi;
-  u32 slot;
-  u32 dummy;
-  
-  zero.as_u32 = 0;
-  
-  /* No questions, no answers */
-  if (tunnel_sw_if_index == 0)
-    tunnel_sw_if_index = &dummy;
+    mpls_main_t * mm = &mpls_main;
 
-  *tunnel_sw_if_index = ~0;
+    if (vec_len(mm->mpls_enabled_by_sw_if_index) < sw_if_index)
+        return (0);
 
-  if (inner_fib_id != (u32)~0)
+    return (mm->mpls_enabled_by_sw_if_index[sw_if_index]);
+}
+
+void
+mpls_sw_interface_enable_disable (mpls_main_t * mm,
+                                  u32 sw_if_index,
+                                  u8 is_enable)
+{
+  mpls_interface_state_change_callback_t *callback;
+  vlib_main_t * vm = vlib_get_main();
+  ip_config_main_t * cm = &mm->rx_config_mains;
+  vnet_config_main_t * vcm = &cm->config_main;
+  u32 lookup_feature_index;
+  fib_node_index_t lfib_index;
+  u32 ci;
+
+  vec_validate_init_empty (mm->mpls_enabled_by_sw_if_index, sw_if_index, 0);
+
+  /*
+   * enable/disable only on the 1<->0 transition
+   */
+  if (is_enable)
     {
-      uword * p;
-      
-      p = hash_get (im->fib_index_by_table_id, inner_fib_id);
-      if (! p)
-        return VNET_API_ERROR_NO_SUCH_INNER_FIB;
-      inner_fib_index = p[0];
-    }
+      if (1 != ++mm->mpls_enabled_by_sw_if_index[sw_if_index])
+        return;
 
-  if (outer_fib_id != 0)
+      lfib_index = fib_table_find_or_create_and_lock(FIB_PROTOCOL_MPLS,
+                                                    MPLS_FIB_DEFAULT_TABLE_ID);
+      vec_validate(mm->fib_index_by_sw_if_index, 0);
+      mm->fib_index_by_sw_if_index[sw_if_index] = lfib_index;
+    }
+  else
     {
-      uword * p;
-      
-      p = hash_get (im->fib_index_by_table_id, outer_fib_id);
-      if (! p)
-        return VNET_API_ERROR_NO_SUCH_FIB;
-      outer_fib_index = p[0];
+      ASSERT(mm->mpls_enabled_by_sw_if_index[sw_if_index] > 0);
+      if (0 != --mm->mpls_enabled_by_sw_if_index[sw_if_index])
+        return;
+
+      fib_table_unlock(mm->fib_index_by_sw_if_index[sw_if_index],
+                      FIB_PROTOCOL_MPLS);
     }
 
-  /* suppress duplicate mpls interface generation. */
-  pool_foreach (tp, mm->gre_tunnels, 
-  ({
-    /* 
-     * If we have a tunnel which matches (src, dst, intfc/mask)
-     * AND the expected route is in the FIB, it's a dup 
+  vec_validate_init_empty (cm->config_index_by_sw_if_index, sw_if_index, ~0);
+  ci = cm->config_index_by_sw_if_index[sw_if_index];
+
+  lookup_feature_index = mm->mpls_rx_feature_lookup;
+
+  if (is_enable)
+    ci = vnet_config_add_feature (vm, vcm,
+                                  ci,
+                                  lookup_feature_index,
+                                  /* config data */ 0,
+                                  /* # bytes of config data */ 0);
+  else
+    ci = vnet_config_del_feature (vm, vcm, ci,
+                                  lookup_feature_index,
+                                  /* config data */ 0,
+                                  /* # bytes of config data */ 0);
+
+  cm->config_index_by_sw_if_index[sw_if_index] = ci;
+
+  /*
+   * notify all interested clients of the change of state.
+   */
+  vec_foreach(callback, mm->mpls_interface_state_change_callbacks)
+  {
+      (*callback)(sw_if_index, is_enable);
+  }
+}
+
+static mpls_gre_tunnel_t *
+mpls_gre_tunnel_from_fib_node (fib_node_t *node)
+{
+#if (CLIB_DEBUG > 0)
+    ASSERT(FIB_NODE_TYPE_MPLS_GRE_TUNNEL == node->fn_type);
+#endif
+    return ((mpls_gre_tunnel_t*)node);
+}
+
+/*
+ * mpls_gre_tunnel_stack
+ *
+ * 'stack' (resolve the recursion for) the tunnel's midchain adjacency
+ */
+static void
+mpls_gre_tunnel_stack (mpls_gre_tunnel_t *mgt)
+{
+    /*
+     * find the adjacency that is contributed by the FIB entry
+     * that this tunnel resovles via, and use it as the next adj
+     * in the midchain
      */
-    if (!memcmp (&tp->tunnel_src, src, sizeof (*src))
-        && !memcmp (&tp->tunnel_dst, dst, sizeof (*dst))
-        && !memcmp (&tp->intfc_address, intfc, sizeof (*intfc))
-        && tp->inner_fib_index == inner_fib_index) 
-      {
-        ip4_fib_t * fib = vec_elt_at_index (im->fibs, inner_fib_index);
-        uword * hash = fib->adj_index_by_dst_address[mask_width];
-        uword key = intfc->as_u32 & im->fib_masks[mask_width];
-        uword *p = hash_get (hash, key);
+    adj_nbr_midchain_stack(mgt->adj_index,
+                          fib_entry_contribute_ip_forwarding(mgt->fei));
+}
 
-        found_tunnel = 1;
+/**
+ * Function definition to backwalk a FIB node
+ */
+static fib_node_back_walk_rc_t
+mpls_gre_tunnel_back_walk (fib_node_t *node,
+                          fib_node_back_walk_ctx_t *ctx)
+{
+    mpls_gre_tunnel_stack(mpls_gre_tunnel_from_fib_node(node));
 
-        if (is_add)
-          {
-            /* A dup, and the route is in the fib. Done */
-            if (p || l2_only)
-              return 1;
-            else
-              {
-                /* Reinstall the route (and other stuff) */
-                e = mpls_encap_by_fib_and_dest (mm, inner_fib_index, 
-                                                dst->as_u32);
-                if (e == 0)
-                  return VNET_API_ERROR_NO_SUCH_LABEL;
-                goto reinstall_it;
-              }
-          }
-        else
-          {
-            /* Delete, the route is already gone? */
-            if (!p)
-              need_route_add_del = 0;
-            goto add_del_route;
-          }
+    return (FIB_NODE_BACK_WALK_CONTINUE);
+}
 
-      }
-  }));
-    
-  /* Delete, and we can't find the tunnel */
-  if (is_add == 0 && found_tunnel == 0)
-    return VNET_API_ERROR_NO_SUCH_ENTRY;
+/**
+ * Function definition to get a FIB node from its index
+ */
+static fib_node_t*
+mpls_gre_tunnel_fib_node_get (fib_node_index_t index)
+{
+    mpls_gre_tunnel_t * mgt;
+    mpls_main_t * mm;
 
-  e = mpls_encap_by_fib_and_dest (mm, inner_fib_index, dst->as_u32);
-  if (e == 0)
-    return VNET_API_ERROR_NO_SUCH_LABEL;
+    mm  = &mpls_main;
+    mgt = pool_elt_at_index(mm->gre_tunnels, index);
 
-  pool_get(mm->gre_tunnels, tp);
-  memset (tp, 0, sizeof (*tp));
+    return (&mgt->mgt_node);
+}
 
-  if (vec_len (mm->free_gre_sw_if_indices) > 0)
+/**
+ * Function definition to inform the FIB node that its last lock has gone.
+ */
+static void
+mpls_gre_tunnel_last_lock_gone (fib_node_t *node)
+{
+    /*
+     * The MPLS GRE tunnel is a root of the graph. As such
+     * it never has children and thus is never locked.
+     */
+    ASSERT(0);
+}
+
+/*
+ * Virtual function table registered by MPLS GRE tunnels
+ * for participation in the FIB object graph.
+ */
+const static fib_node_vft_t mpls_gre_vft = {
+    .fnv_get = mpls_gre_tunnel_fib_node_get,
+    .fnv_last_lock = mpls_gre_tunnel_last_lock_gone,
+    .fnv_back_walk = mpls_gre_tunnel_back_walk,
+};
+static mpls_gre_tunnel_t *
+mpls_gre_tunnel_find (ip4_address_t *src,
+                     ip4_address_t *dst,
+                     ip4_address_t *intfc,
+                     u32 inner_fib_index)
+{
+    mpls_main_t * mm = &mpls_main;
+    mpls_gre_tunnel_t *tp;
+    int found_tunnel = 0;
+
+    /* suppress duplicate mpls interface generation. */
+    pool_foreach (tp, mm->gre_tunnels, 
+    ({
+       /* 
+        * If we have a tunnel which matches (src, dst, intfc/mask)
+        * AND the expected route is in the FIB, it's a dup 
+        */
+       if (!memcmp (&tp->tunnel_src, src, sizeof (*src))
+           && !memcmp (&tp->tunnel_dst, dst, sizeof (*dst))
+           && !memcmp (&tp->intfc_address, intfc, sizeof (*intfc))
+           && tp->inner_fib_index == inner_fib_index) 
+       {
+           found_tunnel = 1;
+           goto found;
+       }
+    }));
+
+found:
+    if (found_tunnel)
     {
-      hw_if_index = 
-        mm->free_gre_sw_if_indices[vec_len(mm->free_gre_sw_if_indices)-1];
-      _vec_len (mm->free_gre_sw_if_indices) -= 1;
-      hi = vnet_get_hw_interface (vnm, hw_if_index);
-      hi->dev_instance = tp - mm->gre_tunnels;
-      hi->hw_instance = tp - mm->gre_tunnels;
+       return (tp);
     }
-  else 
+    return (NULL);
+}
+
+int mpls_gre_tunnel_add (ip4_address_t *src,
+                        ip4_address_t *dst,
+                        ip4_address_t *intfc,
+                        u32 mask_width,
+                        u32 inner_fib_index,
+                        u32 outer_fib_index,
+                        u32 * tunnel_sw_if_index,
+                        u8 l2_only)
+{
+    mpls_main_t * mm = &mpls_main;
+    gre_main_t * gm = &gre_main;
+    vnet_main_t * vnm = vnet_get_main();
+    mpls_gre_tunnel_t *tp;
+    ip_adjacency_t adj;
+    u8 * rewrite_data;
+    mpls_encap_t * e = 0;
+    u32 hw_if_index = ~0;
+    vnet_hw_interface_t * hi;
+    u32 slot;
+    const ip46_address_t zero_nh = {
+       .ip4.as_u32 = 0,
+    };
+
+    tp = mpls_gre_tunnel_find(src,dst,intfc,inner_fib_index);
+
+    /* Add, duplicate */
+    if (NULL != tp)
+       return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+    e = mpls_encap_by_fib_and_dest (mm, inner_fib_index, dst->as_u32);
+    if (e == 0)
+       return VNET_API_ERROR_NO_SUCH_LABEL;
+
+    pool_get(mm->gre_tunnels, tp);
+    memset (tp, 0, sizeof (*tp));
+    fib_node_init(&tp->mgt_node,
+                 FIB_NODE_TYPE_MPLS_GRE_TUNNEL);
+
+    if (vec_len (mm->free_gre_sw_if_indices) > 0)
     {
-      hw_if_index = vnet_register_interface
-        (vnm, mpls_gre_device_class.index, tp - mm->gre_tunnels,
-         mpls_gre_hw_interface_class.index,
-         tp - mm->gre_tunnels);
-      hi = vnet_get_hw_interface (vnm, hw_if_index);
+       hw_if_index = 
+           mm->free_gre_sw_if_indices[vec_len(mm->free_gre_sw_if_indices)-1];
+       _vec_len (mm->free_gre_sw_if_indices) -= 1;
+       hi = vnet_get_hw_interface (vnm, hw_if_index);
+       hi->dev_instance = tp - mm->gre_tunnels;
+       hi->hw_instance = tp - mm->gre_tunnels;
+    }
+    else 
+    {
+       hw_if_index = vnet_register_interface
+           (vnm, mpls_gre_device_class.index, tp - mm->gre_tunnels,
+            mpls_gre_hw_interface_class.index,
+            tp - mm->gre_tunnels);
+       hi = vnet_get_hw_interface (vnm, hw_if_index);
+
+       /* ... to make the IP and L2 x-connect cases identical */
+       slot = vlib_node_add_named_next_with_slot
+           (vnm->vlib_main, hi->tx_node_index, 
+            "mpls-post-rewrite", MPLS_GRE_OUTPUT_NEXT_POST_REWRITE);
+
+       ASSERT (slot == MPLS_GRE_OUTPUT_NEXT_POST_REWRITE);
+    }
+  
+    *tunnel_sw_if_index = hi->sw_if_index;
+    vnet_sw_interface_set_flags (vnm, hi->sw_if_index, 
+                                VNET_SW_INTERFACE_FLAG_ADMIN_UP);      
+    vec_validate(ip4_main.fib_index_by_sw_if_index, *tunnel_sw_if_index);
+    ip4_main.fib_index_by_sw_if_index[*tunnel_sw_if_index] = outer_fib_index;
+
+    tp->hw_if_index = hw_if_index;
+
+    /* bind the MPLS and IPv4 FIBs to the interface and enable */
+    vec_validate(mm->fib_index_by_sw_if_index, hi->sw_if_index);
+    mm->fib_index_by_sw_if_index[hi->sw_if_index] = inner_fib_index;
+    mpls_sw_interface_enable_disable(mm, hi->sw_if_index, 1);
+    ip4_main.fib_index_by_sw_if_index[hi->sw_if_index] = inner_fib_index;
+    ip4_sw_interface_enable_disable(hi->sw_if_index, 1);
+
+    tp->tunnel_src.as_u32 = src->as_u32;
+    tp->tunnel_dst.as_u32 = dst->as_u32;
+    tp->intfc_address.as_u32 = intfc->as_u32;
+    tp->mask_width = mask_width;
+    tp->inner_fib_index = inner_fib_index;
+    tp->outer_fib_index = outer_fib_index;
+    tp->encap_index = e - mm->encaps;
+    tp->l2_only = l2_only;
+
+    /* Add the tunnel to the hash table of all GRE tunnels */
+    u64 key = (u64)src->as_u32 << 32 | (u64)dst->as_u32;
+
+    ASSERT(NULL == hash_get (gm->tunnel_by_key, key));
+    hash_set (gm->tunnel_by_key, key, tp - mm->gre_tunnels);
+
+    /* Create the adjacency and add to v4 fib */
+    memset(&adj, 0, sizeof (adj));
+    adj.lookup_next_index = IP_LOOKUP_NEXT_REWRITE;
+    
+    rewrite_data = mpls_gre_rewrite (mm, tp);
+    if (rewrite_data == 0)
+    {
+       if (*tunnel_sw_if_index != ~0)
+       {
+           hi = vnet_get_hw_interface (vnm, tp->hw_if_index);
+           vnet_sw_interface_set_flags (vnm, hi->sw_if_index, 
+                                        0 /* admin down */);
+           vec_add1 (mm->free_gre_sw_if_indices, tp->hw_if_index);
+       }
+       pool_put (mm->gre_tunnels, tp);
+       return VNET_API_ERROR_NO_SUCH_LABEL;
+    }
 
-      /* ... to make the IP and L2 x-connect cases identical */
-      slot = vlib_node_add_named_next_with_slot
-        (vnm->vlib_main, hi->tx_node_index, 
-         "mpls-post-rewrite", MPLS_GRE_OUTPUT_NEXT_POST_REWRITE);
+    /* Save a copy of the rewrite data for L2 x-connect */
+    vec_free (tp->rewrite_data);
 
-      ASSERT (slot == MPLS_GRE_OUTPUT_NEXT_POST_REWRITE);
+    tp->rewrite_data = rewrite_data;
+  
+    if (!l2_only)
+    {
+       /*
+        * source the FIB entry for the tunnel's destination
+        * and become a child thereof. The tunnel will then get poked
+        * when the forwarding for the entry updates, and the tunnel can
+        * re-stack accordingly
+        */
+       const fib_prefix_t tun_dst_pfx = {
+           .fp_len = 32,
+           .fp_proto = FIB_PROTOCOL_IP4,
+           .fp_addr = {
+               .ip4 = *dst,
+           }
+       };
+
+       tp->fei = fib_table_entry_special_add(outer_fib_index,
+                                             &tun_dst_pfx,
+                                             FIB_SOURCE_RR,
+                                             FIB_ENTRY_FLAG_NONE,
+                                             ADJ_INDEX_INVALID);
+       tp->sibling_index = fib_entry_child_add(tp->fei,
+                                               FIB_NODE_TYPE_MPLS_GRE_TUNNEL,
+                                               tp - mm->gre_tunnels);
+
+       /*
+        * create and update the midchain adj this tunnel sources.
+        * This is the adj the route we add below will resolve to.
+        */
+       tp->adj_index = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4,
+                                           FIB_LINK_IP4,
+                                           &zero_nh,
+                                           hi->sw_if_index);
+
+       adj_nbr_midchain_update_rewrite(tp->adj_index,
+                                       mpls_post_rewrite_node.index,
+                                       rewrite_data);
+       mpls_gre_tunnel_stack(tp);
+
+       /*
+        * Update the route for the tunnel's subnet to point through the tunnel
+        */
+       const fib_prefix_t tun_sub_net_pfx = {
+           .fp_len = tp->mask_width,
+           .fp_proto = FIB_PROTOCOL_IP4,
+           .fp_addr = {
+               .ip4 = tp->intfc_address,
+           },
+       };
+
+       fib_table_entry_update_one_path(inner_fib_index,
+                                       &tun_sub_net_pfx,
+                                       FIB_SOURCE_INTERFACE,
+                                       (FIB_ENTRY_FLAG_CONNECTED |
+                                        FIB_ENTRY_FLAG_ATTACHED),
+                                       FIB_PROTOCOL_IP4,
+                                       &zero_nh,
+                                       hi->sw_if_index,
+                                       ~0, // invalid fib index
+                                       1,
+                                       MPLS_LABEL_INVALID,
+                                       FIB_ROUTE_PATH_FLAG_NONE);
     }
+
+    return 0;
+}
+
+static int
+mpls_gre_tunnel_del (ip4_address_t *src,
+                    ip4_address_t *dst,
+                    ip4_address_t *intfc,
+                    u32 mask_width,
+                    u32 inner_fib_index,
+                    u32 outer_fib_index,
+                    u32 * tunnel_sw_if_index,
+                    u8 l2_only)
+{
+    mpls_main_t * mm = &mpls_main;
+    vnet_main_t * vnm = vnet_get_main();
+    gre_main_t * gm = &gre_main;
+    mpls_gre_tunnel_t *tp;
+    vnet_hw_interface_t * hi;
   
-  *tunnel_sw_if_index = hi->sw_if_index;
-  vnet_sw_interface_set_flags (vnm, hi->sw_if_index, 
-                               VNET_SW_INTERFACE_FLAG_ADMIN_UP);      
+    tp = mpls_gre_tunnel_find(src,dst,intfc,inner_fib_index);
 
-  tp->hw_if_index = hw_if_index;
-    
- reinstall_it:
-  tp->tunnel_src.as_u32 = src->as_u32;
-  tp->tunnel_dst.as_u32 = dst->as_u32;
-  tp->intfc_address.as_u32 = intfc->as_u32;
-  tp->mask_width = mask_width;
-  tp->inner_fib_index = inner_fib_index;
-  tp->outer_fib_index = outer_fib_index;
-  tp->encap_index = e - mm->encaps;
-  tp->l2_only = l2_only;
+    /* Delete, and we can't find the tunnel */
+    if (NULL == tp)
+       return VNET_API_ERROR_NO_SUCH_ENTRY;
 
-  /* Create the adjacency and add to v4 fib */
-  memset(&adj, 0, sizeof (adj));
-  adj.explicit_fib_index = ~0;
-  adj.lookup_next_index = IP_LOOKUP_NEXT_REWRITE;
-    
-  rewrite_data = mpls_gre_rewrite (mm, tp);
-  if (rewrite_data == 0)
+    hi = vnet_get_hw_interface (vnm, tp->hw_if_index);
+
+    if (!l2_only)
     {
-      if (*tunnel_sw_if_index != ~0)
-        {
-          hi = vnet_get_hw_interface (vnm, tp->hw_if_index);
-          vnet_sw_interface_set_flags (vnm, hi->sw_if_index, 
-                                       0 /* admin down */);
-          vec_add1 (mm->free_gre_sw_if_indices, tp->hw_if_index);
-      }
-      pool_put (mm->gre_tunnels, tp);
-      return VNET_API_ERROR_NO_SUCH_LABEL;
+       /*
+        * unsource the FIB entry for the tunnel's destination
+        */
+       const fib_prefix_t tun_dst_pfx = {
+           .fp_len = 32,
+           .fp_proto = FIB_PROTOCOL_IP4,
+           .fp_addr = {
+               .ip4 = *dst,
+           }
+       };
+
+       fib_entry_child_remove(tp->fei,
+                              tp->sibling_index);
+       fib_table_entry_special_remove(outer_fib_index,
+                                      &tun_dst_pfx,
+                                      FIB_SOURCE_RR);
+       tp->fei = FIB_NODE_INDEX_INVALID;
+       adj_unlock(tp->adj_index);
+       /*
+        * unsource the route for the tunnel's subnet
+        */
+       const fib_prefix_t tun_sub_net_pfx = {
+           .fp_len = tp->mask_width,
+           .fp_proto = FIB_PROTOCOL_IP4,
+           .fp_addr = {
+               .ip4 = tp->intfc_address,
+           },
+       };
+
+       fib_table_entry_delete(inner_fib_index,
+                              &tun_sub_net_pfx,
+                              FIB_SOURCE_INTERFACE);
     }
-  
-  /* Save a copy of the rewrite data for L2 x-connect */
-  vec_free (tp->rewrite_data);
 
-  tp->rewrite_data = rewrite_data;
+    u64 key = ((u64)tp->tunnel_src.as_u32 << 32 |
+               (u64)tp->tunnel_src.as_u32);
 
-  vnet_rewrite_for_tunnel
-    (vnm,
-     outer_fib_index /* tx_sw_if_index, aka outer fib ID */,
-     ip4_rewrite_node.index,
-     mpls_post_rewrite_node.index,
-     &adj.rewrite_header,
-     rewrite_data, vec_len(rewrite_data));
-  
-  if (!l2_only)
-      ip_add_adjacency (lm, &adj, 1 /* one adj */,
-                        &adj_index);
+    hash_unset (gm->tunnel_by_key, key);
+    mpls_sw_interface_enable_disable(mm, hi->sw_if_index, 0);
+    ip4_sw_interface_enable_disable(hi->sw_if_index, 0);
+
+    vnet_sw_interface_set_flags (vnm, hi->sw_if_index, 
+                                0 /* admin down */);
+    vec_add1 (mm->free_gre_sw_if_indices, tp->hw_if_index);
+    vec_free (tp->rewrite_data);
+    fib_node_deinit(&tp->mgt_node);
+    pool_put (mm->gre_tunnels, tp);
+
+    return 0;
+}
+
+int
+vnet_mpls_gre_add_del_tunnel (ip4_address_t *src,
+                             ip4_address_t *dst,
+                             ip4_address_t *intfc,
+                             u32 mask_width,
+                             u32 inner_fib_id, u32 outer_fib_id,
+                             u32 * tunnel_sw_if_index,
+                             u8 l2_only,
+                             u8 is_add)
+{
+    u32 inner_fib_index = 0;
+    u32 outer_fib_index = 0;
+    u32 dummy;
+    ip4_main_t * im = &ip4_main;
   
- add_del_route:
+    /* No questions, no answers */
+    if (NULL == tunnel_sw_if_index)
+       tunnel_sw_if_index = &dummy;
 
-  if (need_route_add_del && !l2_only)
+    *tunnel_sw_if_index = ~0;
+
+    if (inner_fib_id != (u32)~0)
     {
-      if (is_add)
-        ip4_add_del_route_next_hop (im,
-                                    IP4_ROUTE_FLAG_ADD,
-                                    &tp->intfc_address,
-                                    tp->mask_width,
-                                    &zero /* no next hop */,
-                                    (u32)~0 /* next_hop_sw_if_index */,
-                                    1 /* weight */, 
-                                    adj_index,
-                                    tp->inner_fib_index);
-      else
-        {
-          ip4_add_del_route_args_t a;
-          memset (&a, 0, sizeof (a));
-
-          a.flags = IP4_ROUTE_FLAG_FIB_INDEX | IP4_ROUTE_FLAG_DEL;
-          a.table_index_or_table_id = tp->inner_fib_index;
-          a.dst_address = tp->intfc_address;
-          a.dst_address_length = tp->mask_width;
-          a.adj_index = ~0;
-
-          ip4_add_del_route (im, &a);
-          ip4_maybe_remap_adjacencies (im, tp->inner_fib_index, 
-                                       IP4_ROUTE_FLAG_FIB_INDEX);
-        }
+       uword * p;
+      
+       p = hash_get (im->fib_index_by_table_id, inner_fib_id);
+       if (! p)
+           return VNET_API_ERROR_NO_SUCH_INNER_FIB;
+       inner_fib_index = p[0];
     }
 
-  if (is_add == 0 && found_tunnel)
+    if (outer_fib_id != 0)
     {
-      hi = vnet_get_hw_interface (vnm, tp->hw_if_index);
-      vnet_sw_interface_set_flags (vnm, hi->sw_if_index, 
-                                   0 /* admin down */);
-      vec_add1 (mm->free_gre_sw_if_indices, tp->hw_if_index);
-      vec_free (tp->rewrite_data);
-      pool_put (mm->gre_tunnels, tp);
+       uword * p;
+      
+       p = hash_get (im->fib_index_by_table_id, outer_fib_id);
+       if (! p)
+           return VNET_API_ERROR_NO_SUCH_FIB;
+       outer_fib_index = p[0];
     }
 
-  return 0;
+    if (is_add)
+    {
+       return (mpls_gre_tunnel_add(src,dst,intfc, mask_width,
+                                   inner_fib_index,
+                                   outer_fib_index,
+                                   tunnel_sw_if_index,
+                                   l2_only));
+    }
+    else
+    {
+       return (mpls_gre_tunnel_del(src,dst,intfc, mask_width,
+                                   inner_fib_index,
+                                   outer_fib_index,
+                                   tunnel_sw_if_index,
+                                   l2_only));
+    }
 }
 
 /*
@@ -963,21 +1275,17 @@ int vnet_mpls_gre_add_del_tunnel (ip4_address_t *src,
  */
 int vnet_mpls_gre_delete_fib_tunnels (u32 fib_id)
 {
-  ip4_main_t * im = &ip4_main;
   mpls_main_t * mm = &mpls_main;
   vnet_main_t * vnm = mm->vnet_main;
   mpls_gre_tunnel_t *tp;
   u32 fib_index = 0;
-  uword * p;
   u32 * tunnels_to_delete = 0;
   vnet_hw_interface_t * hi;
-  ip4_fib_t * fib;
   int i;
 
-  p = hash_get (im->fib_index_by_table_id, fib_id);
-  if (! p)
+  fib_index = ip4_fib_index_from_table_id(fib_id);
+  if (~0 == fib_index)
       return VNET_API_ERROR_NO_SUCH_INNER_FIB;
-  fib_index = p[0];
 
   pool_foreach (tp, mm->gre_tunnels, 
     ({
@@ -985,28 +1293,40 @@ int vnet_mpls_gre_delete_fib_tunnels (u32 fib_id)
         vec_add1 (tunnels_to_delete, tp - mm->gre_tunnels);
     }));
   
-  fib = vec_elt_at_index (im->fibs, fib_index);
-  
   for (i = 0; i < vec_len(tunnels_to_delete); i++) {
       tp = pool_elt_at_index (mm->gre_tunnels, tunnels_to_delete[i]);
-      uword * hash = fib->adj_index_by_dst_address[tp->mask_width];
-      uword key = tp->intfc_address.as_u32 & im->fib_masks[tp->mask_width];
-      uword *p = hash_get (hash, key);
-      ip4_add_del_route_args_t a;
 
       /* Delete, the route if not already gone */
-      if (p && !tp->l2_only) 
-        {
-          memset (&a, 0, sizeof (a));
-          a.flags = IP4_ROUTE_FLAG_FIB_INDEX | IP4_ROUTE_FLAG_DEL;
-          a.table_index_or_table_id = tp->inner_fib_index;
-          a.dst_address = tp->intfc_address;
-          a.dst_address_length = tp->mask_width;
-          a.adj_index = ~0;
-          ip4_add_del_route (im, &a);
-          ip4_maybe_remap_adjacencies (im, tp->inner_fib_index, 
-                                       IP4_ROUTE_FLAG_FIB_INDEX);
-        }
+      if (FIB_NODE_INDEX_INVALID != tp->fei && !tp->l2_only) 
+      {
+         const fib_prefix_t tun_dst_pfx = {
+             .fp_len = 32,
+             .fp_proto = FIB_PROTOCOL_IP4,
+             .fp_addr = {
+                 .ip4 = tp->tunnel_dst,
+             }
+         };
+
+         fib_entry_child_remove(tp->fei,
+                                tp->sibling_index);
+         fib_table_entry_special_remove(tp->outer_fib_index,
+                                        &tun_dst_pfx,
+                                        FIB_SOURCE_RR);
+         tp->fei = FIB_NODE_INDEX_INVALID;
+         adj_unlock(tp->adj_index);
+         const fib_prefix_t tun_sub_net_pfx = {
+             .fp_len = tp->mask_width,
+             .fp_proto = FIB_PROTOCOL_IP4,
+             .fp_addr = {
+                 .ip4 = tp->intfc_address,
+             },
+         };
+
+         fib_table_entry_delete(tp->inner_fib_index,
+                                &tun_sub_net_pfx,
+                                FIB_SOURCE_INTERFACE);
+      }
       
       hi = vnet_get_hw_interface (vnm, tp->hw_if_index);
       vnet_sw_interface_set_flags (vnm, hi->sw_if_index, 
@@ -1229,11 +1549,15 @@ VLIB_CLI_COMMAND (show_mpls_tunnel_command, static) = {
     .function = show_mpls_tunnel_command_fn,
 };
 
+
 /* force inclusion from application's main.c */
 clib_error_t *mpls_interface_init (vlib_main_t *vm)
 {
   clib_error_t * error;
 
+  fib_node_register_type(FIB_NODE_TYPE_MPLS_GRE_TUNNEL,
+                        &mpls_gre_vft);
+
   if ((error = vlib_call_init_function (vm, mpls_policy_encap_init)))
       return error;
 
@@ -1286,9 +1610,7 @@ int vnet_mpls_ethernet_add_del_tunnel (u8 *dst,
   ip_lookup_main_t * lm = &im->lookup_main;
   mpls_main_t * mm = &mpls_main;
   vnet_main_t * vnm = vnet_get_main();
-  ip4_address_t zero;
   mpls_eth_tunnel_t *tp;
-  int need_route_add_del = 1;
   u32 inner_fib_index = 0;
   ip_adjacency_t adj;
   u32 adj_index;
@@ -1300,8 +1622,6 @@ int vnet_mpls_ethernet_add_del_tunnel (u8 *dst,
   u32 slot;
   u32 dummy;
   
-  zero.as_u32 = 0;
-  
   if (tunnel_sw_if_index == 0)
     tunnel_sw_if_index = &dummy;
 
@@ -1326,18 +1646,14 @@ int vnet_mpls_ethernet_add_del_tunnel (u8 *dst,
      */
     if (!memcmp (&tp->tunnel_dst, dst, sizeof (*dst))
         && !memcmp (&tp->intfc_address, intfc, sizeof (*intfc))
-        && tp->inner_fib_index == inner_fib_index) 
+        && tp->inner_fib_index == inner_fib_index
+       && FIB_NODE_INDEX_INVALID != tp->fei)
       {
-        ip4_fib_t * fib = vec_elt_at_index (im->fibs, inner_fib_index);
-        uword * hash = fib->adj_index_by_dst_address[mask_width];
-        uword key = intfc->as_u32 & im->fib_masks[mask_width];
-        uword *p = hash_get (hash, key);
-
         found_tunnel = 1;
 
         if (is_add)
           {
-            if (p || l2_only)
+            if (l2_only)
               return 1;
             else
               {
@@ -1351,9 +1667,7 @@ int vnet_mpls_ethernet_add_del_tunnel (u8 *dst,
           }
         else
           {
-            /* Delete, the route is already gone? */
-            if (!p)
-              need_route_add_del = 0;
+            /* Delete */
             goto add_del_route;
           }
 
@@ -1413,7 +1727,6 @@ int vnet_mpls_ethernet_add_del_tunnel (u8 *dst,
 
   /* Create the adjacency and add to v4 fib */
   memset(&adj, 0, sizeof (adj));
-  adj.explicit_fib_index = ~0;
   adj.lookup_next_index = IP_LOOKUP_NEXT_REWRITE;
     
   rewrite_data = mpls_ethernet_rewrite (mm, tp);
@@ -1465,33 +1778,26 @@ int vnet_mpls_ethernet_add_del_tunnel (u8 *dst,
   
  add_del_route:
 
-  if (need_route_add_del && !l2_only)
+  if (!l2_only)
     {
+      const fib_prefix_t pfx = {
+         .fp_addr = {
+             .ip4 = tp->intfc_address,
+         },
+         .fp_len = tp->mask_width,
+         .fp_proto = FIB_PROTOCOL_IP4,
+      };
       if (is_add)
-        ip4_add_del_route_next_hop (im,
-                                    IP4_ROUTE_FLAG_ADD,
-                                    &tp->intfc_address,
-                                    tp->mask_width,
-                                    &zero /* no next hop */,
-                                    (u32)~0 /* next_hop_sw_if_index */,
-                                    1 /* weight */, 
-                                    adj_index,
-                                    tp->inner_fib_index);
+         tp->fei = fib_table_entry_special_add(tp->inner_fib_index,
+                                               &pfx,
+                                               FIB_SOURCE_API,
+                                               FIB_ENTRY_FLAG_NONE,
+                                               adj_index);
       else
         {
-          ip4_add_del_route_args_t a;
-          memset (&a, 0, sizeof (a));
-
-          a.flags = IP4_ROUTE_FLAG_FIB_INDEX | IP4_ROUTE_FLAG_DEL;
-          a.table_index_or_table_id = tp->inner_fib_index;
-          a.dst_address = tp->intfc_address;
-          a.dst_address_length = tp->mask_width;
-          a.adj_index = ~0;
-
-          ip4_add_del_route (im, &a);
-          ip4_maybe_remap_adjacencies (im, tp->inner_fib_index, 
-                                       IP4_ROUTE_FLAG_FIB_INDEX);
-        }
+         fib_table_entry_delete(tp->inner_fib_index, &pfx, FIB_SOURCE_API);
+         tp->fei = FIB_NODE_INDEX_INVALID;
+       }
     }
   if (is_add == 0 && found_tunnel)
     {
@@ -1667,15 +1973,10 @@ int vnet_mpls_ethernet_add_del_policy_tunnel (u8 *dst,
                                               u8 is_add)
 {
   ip4_main_t * im = &ip4_main;
-  ip_lookup_main_t * lm = &im->lookup_main;
   mpls_main_t * mm = &mpls_main;
   vnet_main_t * vnm = vnet_get_main();
-  ip4_address_t zero;
   mpls_eth_tunnel_t *tp;
-  int need_route_add_del = 1;
   u32 inner_fib_index = 0;
-  ip_adjacency_t adj;
-  u32 adj_index;
   int found_tunnel = 0;
   mpls_encap_t * e = 0;
   u32 hw_if_index = ~0;
@@ -1683,8 +1984,6 @@ int vnet_mpls_ethernet_add_del_policy_tunnel (u8 *dst,
   u32 slot;
   u32 dummy;
   
-  zero.as_u32 = 0;
-  
   if (tunnel_sw_if_index == 0)
     tunnel_sw_if_index = &dummy;
 
@@ -1709,18 +2008,14 @@ int vnet_mpls_ethernet_add_del_policy_tunnel (u8 *dst,
      */
     if (!memcmp (&tp->tunnel_dst, dst, sizeof (*dst))
         && !memcmp (&tp->intfc_address, intfc, sizeof (*intfc))
-        && tp->inner_fib_index == inner_fib_index) 
+        && tp->inner_fib_index == inner_fib_index
+       && FIB_NODE_INDEX_INVALID != tp->fei)
       {
-        ip4_fib_t * fib = vec_elt_at_index (im->fibs, inner_fib_index);
-        uword * hash = fib->adj_index_by_dst_address[mask_width];
-        uword key = intfc->as_u32 & im->fib_masks[mask_width];
-        uword *p = hash_get (hash, key);
-
         found_tunnel = 1;
 
         if (is_add)
           {
-            if (p || l2_only)
+            if (l2_only)
               return 1;
             else
               {
@@ -1729,9 +2024,7 @@ int vnet_mpls_ethernet_add_del_policy_tunnel (u8 *dst,
           }
         else
           {
-            /* Delete, the route is already gone? */
-            if (!p)
-              need_route_add_del = 0;
+            /* Delete */
             goto add_del_route;
           }
 
@@ -1784,49 +2077,44 @@ int vnet_mpls_ethernet_add_del_policy_tunnel (u8 *dst,
   tp->encap_index = e - mm->encaps;
   tp->tx_sw_if_index = tx_sw_if_index;
   tp->l2_only = l2_only;
+  tp->fei = FIB_NODE_INDEX_INVALID;
 
   if (new_tunnel_index)
     *new_tunnel_index = tp - mm->eth_tunnels;
 
-  /* Create the classify adjacency and add to v4 fib */
-  memset(&adj, 0, sizeof (adj));
-  adj.explicit_fib_index = ~0;
-  adj.lookup_next_index = IP_LOOKUP_NEXT_CLASSIFY;
-  adj.classify.table_index = classify_table_index;
-    
-  if (!l2_only)
-    ip_add_adjacency (lm, &adj, 1 /* one adj */,
-                      &adj_index);
-  
  add_del_route:
 
-  if (need_route_add_del && !l2_only)
+  if (!l2_only)
     {
+      const fib_prefix_t pfx = {
+         .fp_addr = {
+             .ip4 = tp->intfc_address,
+         },
+         .fp_len = tp->mask_width,
+         .fp_proto = FIB_PROTOCOL_IP4,
+      };
+      dpo_id_t dpo = DPO_NULL;
+
       if (is_add)
-        ip4_add_del_route_next_hop (im,
-                                    IP4_ROUTE_FLAG_ADD,
-                                    &tp->intfc_address,
-                                    tp->mask_width,
-                                    &zero /* no next hop */,
-                                    (u32)~0 /* next_hop_sw_if_index */,
-                                    1 /* weight */, 
-                                    adj_index,
-                                    tp->inner_fib_index);
-      else
         {
-          ip4_add_del_route_args_t a;
-          memset (&a, 0, sizeof (a));
-
-          a.flags = IP4_ROUTE_FLAG_FIB_INDEX | IP4_ROUTE_FLAG_DEL;
-          a.table_index_or_table_id = tp->inner_fib_index;
-          a.dst_address = tp->intfc_address;
-          a.dst_address_length = tp->mask_width;
-          a.adj_index = ~0;
-
-          ip4_add_del_route (im, &a);
-          ip4_maybe_remap_adjacencies (im, tp->inner_fib_index, 
-                                       IP4_ROUTE_FLAG_FIB_INDEX);
+          dpo_set(&dpo,
+                  DPO_CLASSIFY,
+                  DPO_PROTO_IP4,
+                  classify_dpo_create(FIB_PROTOCOL_IP4,
+                                      classify_table_index));
+
+          tp->fei = fib_table_entry_special_dpo_add(tp->inner_fib_index,
+                                                    &pfx,
+                                                    FIB_SOURCE_API,
+                                                    FIB_ENTRY_FLAG_EXCLUSIVE,
+                                                    &dpo);
+          dpo_reset(&dpo);
         }
+      else
+        {
+         fib_table_entry_delete(tp->inner_fib_index, &pfx, FIB_SOURCE_API);
+         tp->fei = FIB_NODE_INDEX_INVALID;
+       }
     }
   if (is_add == 0 && found_tunnel)
     {
@@ -1945,3 +2233,44 @@ VLIB_CLI_COMMAND (create_mpls_ethernet_policy_tunnel_command, static) = {
   " classify-table-index <nn>",
   .function = create_mpls_ethernet_policy_tunnel_command_fn,
 };
+
+static clib_error_t *
+mpls_interface_enable_disable (vlib_main_t * vm,
+                               unformat_input_t * input,
+                               vlib_cli_command_t * cmd)
+{
+  vnet_main_t * vnm = vnet_get_main();
+  clib_error_t * error = 0;
+  u32 sw_if_index, enable;
+
+  sw_if_index = ~0;
+
+  if (! unformat_user (input, unformat_vnet_sw_interface, vnm, &sw_if_index))
+    {
+      error = clib_error_return (0, "unknown interface `%U'",
+                                format_unformat_error, input);
+      goto done;
+    }
+
+  if (unformat (input, "enable"))
+      enable = 1;
+  else if (unformat (input, "disable"))
+      enable = 0;
+  else
+    {
+      error = clib_error_return (0, "expected 'enable' or 'disable'",
+                                format_unformat_error, input);
+      goto done;
+    }
+
+  mpls_sw_interface_enable_disable(&mpls_main, sw_if_index, enable);
+
+ done:
+  return error;
+}
+
+VLIB_CLI_COMMAND (set_interface_ip_table_command, static) = {
+  .path = "set interface mpls",
+  .function = mpls_interface_enable_disable,
+  .short_help = "Enable/Disable an interface for MPLS forwarding",
+};