Dynamically compute ip feature subgraph order

This change-set enables plugins to add themselves to the ip4/ip6
feature subgraphs without having to modify core vpp engine code
at all. Add VNET_IP4/IP6_UNICAST/MULTICAST_FEATURE_INIT macros
which express the required ordering constraints, and off you go.

Along the way, added an implementation of Warshall's algorithm to
vppinfra; to compute the positive transitive closure of a relation. In
this case, the relation is "feature A runs before feature B."

With that in hand, ip_feature_init_cast(...) computes a partial order
across the set of configured feature subgraph nodes.

In unit-testing, we discovered VPP-145 - ip4/6 inacl wiped out
vnet_buffer(b)->ip>current_config_index, which exists in main. So, we
fixed that by moving b->trace_index, adding b->current_config_index,
and removing the ip opaque union current_config_index.

Change-Id: Iff132116f66413dc6b31ac3377198c7a32d51f48
Signed-off-by: Dave Barach <dave@barachs.net>
diff --git a/vnet/Makefile.am b/vnet/Makefile.am
index 61a1998..d0a06be 100644
--- a/vnet/Makefile.am
+++ b/vnet/Makefile.am
@@ -248,6 +248,7 @@
  vnet/ip/format.c				\
  vnet/ip/icmp4.c				\
  vnet/ip/icmp6.c				\
+ vnet/ip/ip_feature_registration.c		\
  vnet/ip/ip46_cli.c				\
  vnet/ip/ip4_format.c				\
  vnet/ip/ip4_forward.c				\
@@ -280,6 +281,7 @@
  vnet/ip/icmp6.h				\
  vnet/ip/igmp_packet.h				\
  vnet/ip/ip.h					\
+ vnet/ip/ip_feature_registration.h		\
  vnet/ip/ip4.h					\
  vnet/ip/ip4_mtrie.h				\
  vnet/ip/ip4_error.h				\
diff --git a/vnet/vnet/buffer.h b/vnet/vnet/buffer.h
index ea25ad0..f74be39 100644
--- a/vnet/vnet/buffer.h
+++ b/vnet/vnet/buffer.h
@@ -113,9 +113,6 @@
 
       union {
 	struct {
-	  /* Current configuration index. */
-	  u32 current_config_index;
-
 	  /* Flow hash value for this packet computed from IP src/dst address
 	     protocol and ports. */
 	  u32 flow_hash;
diff --git a/vnet/vnet/classify/input_acl.c b/vnet/vnet/classify/input_acl.c
index 7e835a6..fb9a2a4 100644
--- a/vnet/vnet/classify/input_acl.c
+++ b/vnet/vnet/classify/input_acl.c
@@ -41,12 +41,12 @@
       if (tid == INPUT_ACL_TABLE_IP4)
         {
           lm = &ip4_main.lookup_main;
-          ftype = IP4_RX_FEATURE_CHECK_ACCESS;
+          ftype = ip4_main.ip4_unicast_rx_feature_check_access;
         }
       else
         {
           lm = &ip6_main.lookup_main;
-          ftype = IP6_RX_FEATURE_CHECK_ACCESS;
+          ftype = ip6_main.ip6_unicast_rx_feature_check_access;
         }
 
       ipcm = &lm->rx_config_mains[VNET_UNICAST];
diff --git a/vnet/vnet/ip/ip.h b/vnet/vnet/ip/ip.h
index 45062cf..c9a8293 100644
--- a/vnet/vnet/ip/ip.h
+++ b/vnet/vnet/ip/ip.h
@@ -42,6 +42,7 @@
 
 #include <vppinfra/hash.h>
 #include <vppinfra/heap.h>		/* adjacency heap */
+#include <vppinfra/ptclosure.h>
 
 #include <vnet/vnet.h>
 
diff --git a/vnet/vnet/ip/ip4.h b/vnet/vnet/ip/ip4.h
index 59ef685..c01006e 100644
--- a/vnet/vnet/ip/ip4.h
+++ b/vnet/vnet/ip/ip4.h
@@ -43,6 +43,7 @@
 #include <vnet/ip/ip4_mtrie.h>
 #include <vnet/ip/ip4_packet.h>
 #include <vnet/ip/lookup.h>
+#include <vnet/ip/ip_feature_registration.h>
 
 typedef struct ip4_fib_t {
   /* Hash table for each prefix length mapping. */
@@ -101,30 +102,6 @@
   uword function_opaque;
 } ip4_add_del_interface_address_callback_t;
 
-typedef enum {
-  /* First check access list to either permit or deny this
-     packet based on classification. */
-  IP4_RX_FEATURE_CHECK_ACCESS,
-
-  /* RPF check: verify that source address is reachable via
-     RX interface or via any interface. */
-  IP4_RX_FEATURE_SOURCE_CHECK_REACHABLE_VIA_RX,
-  IP4_RX_FEATURE_SOURCE_CHECK_REACHABLE_VIA_ANY,
-
-  /* IPSec */
-  IP4_RX_FEATURE_IPSEC,
-
-  /* vPath forwarding: won't return to call next feature
-     so any feature needed before vPath forwarding must be prior
-     to this entry */
-  IP4_RX_FEATURE_VPATH,
-
-  /* Must be last: perform forwarding lookup. */
-  IP4_RX_FEATURE_LOOKUP,
-
-  IP4_N_RX_FEATURE,
-} ip4_rx_feature_type_t;
-
 typedef struct ip4_main_t {
   ip_lookup_main_t lookup_main;
 
@@ -152,6 +129,22 @@
   /* Template used to generate IP4 ARP packets. */
   vlib_packet_template_t ip4_arp_request_packet_template;
 
+  /* feature path configuration lists */
+  vnet_ip_feature_registration_t * next_uc_feature;
+  vnet_ip_feature_registration_t * next_mc_feature;
+
+  /* Built-in unicast feature path indices, see ip_feature_init_cast(...)  */
+  u32 ip4_unicast_rx_feature_check_access;
+  u32 ip4_unicast_rx_feature_source_reachable_via_rx;
+  u32 ip4_unicast_rx_feature_source_reachable_via_any;
+  u32 ip4_unicast_rx_feature_ipsec;
+  u32 ip4_unicast_rx_feature_vpath;
+  u32 ip4_unicast_rx_feature_lookup;
+
+  /* Built-in multicast feature path indices */
+  u32 ip4_multicast_rx_feature_vpath;
+  u32 ip4_multicast_rx_feature_lookup;
+  
   /* Seed for Jenkins hash used to compute ip4 flow hash. */
   u32 flow_hash_seed;
 
@@ -169,6 +162,31 @@
 /* Global ip4 main structure. */
 extern ip4_main_t ip4_main;
 
+#define VNET_IP4_UNICAST_FEATURE_INIT(x,...)                    \
+  __VA_ARGS__ vnet_ip_feature_registration_t uc_##x;            \
+static void __vnet_add_feature_registration_uc_##x (void)       \
+  __attribute__((__constructor__)) ;                            \
+static void __vnet_add_feature_registration_uc_##x (void)       \
+{                                                               \
+  ip4_main_t * im = &ip4_main;                                  \
+  uc_##x.next = im->next_uc_feature;                            \
+  im->next_uc_feature = &uc_##x;                                \
+}                                                               \
+__VA_ARGS__ vnet_ip_feature_registration_t uc_##x 
+
+#define VNET_IP4_MULTICAST_FEATURE_INIT(x,...)                  \
+  __VA_ARGS__ vnet_ip_feature_registration_t mc_##x;            \
+static void __vnet_add_feature_registration_mc_##x (void)       \
+  __attribute__((__constructor__)) ;                            \
+static void __vnet_add_feature_registration_mc_##x (void)       \
+{                                                               \
+  ip4_main_t * im = &ip4_main;                                  \
+  mc_##x.next = im->next_mc_feature;                            \
+  im->next_mc_feature = &mc_##x;                                \
+}                                                               \
+__VA_ARGS__ vnet_ip_feature_registration_t mc_##x 
+
+
 /* Global ip4 input node.  Errors get attached to ip4 input node. */
 extern vlib_node_registration_t ip4_input_node;
 extern vlib_node_registration_t ip4_lookup_node;
diff --git a/vnet/vnet/ip/ip4_forward.c b/vnet/vnet/ip/ip4_forward.c
index 94c446b..1de0b40 100644
--- a/vnet/vnet/ip/ip4_forward.c
+++ b/vnet/vnet/ip/ip4_forward.c
@@ -1284,6 +1284,83 @@
  
 VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (ip4_sw_interface_admin_up_down);
 
+/* Built-in ip4 unicast rx feature path definition */
+VNET_IP4_UNICAST_FEATURE_INIT (ip4_inacl, static) = {
+  .node_name = "ip4-inacl", 
+  .runs_before = {"ip4-source-check-via-rx", 0}, 
+  .feature_index = &ip4_main.ip4_unicast_rx_feature_check_access,
+};
+
+VNET_IP4_UNICAST_FEATURE_INIT (ip4_source_check_1, static) = {
+  .node_name = "ip4-source-check-via-rx",
+  .runs_before = {"ip4-source-check-via-any", 0},
+  .feature_index = 
+  &ip4_main.ip4_unicast_rx_feature_source_reachable_via_rx,
+};
+
+VNET_IP4_UNICAST_FEATURE_INIT (ip4_source_check_2, static) = {
+  .node_name = "ip4-source-check-via-any",
+  .runs_before = {"ipsec-input-ip4", 0},
+  .feature_index = 
+  &ip4_main.ip4_unicast_rx_feature_source_reachable_via_any,
+};
+
+VNET_IP4_UNICAST_FEATURE_INIT (ip4_ipsec, static) = {
+  .node_name = "ipsec-input-ip4",
+  .runs_before = {"vpath-input-ip4", 0},
+  .feature_index = &ip4_main.ip4_unicast_rx_feature_ipsec,
+};
+
+VNET_IP4_UNICAST_FEATURE_INIT (ip4_vpath, static) = {
+  .node_name = "vpath-input-ip4",
+  .runs_before = {"ip4-lookup", 0},
+  .feature_index = &ip4_main.ip4_unicast_rx_feature_vpath,
+};
+
+VNET_IP4_UNICAST_FEATURE_INIT (ip4_lookup, static) = {
+  .node_name = "ip4-lookup",
+  .runs_before = {0}, /* not before any other features */
+  .feature_index = &ip4_main.ip4_unicast_rx_feature_lookup,
+};
+
+/* Built-in ip4 multicast rx feature path definition */
+VNET_IP4_MULTICAST_FEATURE_INIT (ip4_vpath_mc, static) = {
+  .node_name = "vpath-input-ip4",
+  .runs_before = {"ip4-lookup-multicast", 0},
+  .feature_index = &ip4_main.ip4_multicast_rx_feature_vpath,
+};
+
+VNET_IP4_MULTICAST_FEATURE_INIT (ip4_lookup_mc, static) = {
+  .node_name = "ip4-lookup-multicast",
+  .runs_before = {0}, /* not before any other features */
+  .feature_index = &ip4_main.ip4_multicast_rx_feature_lookup,
+};
+
+static char * feature_start_nodes[] = 
+  { "ip4-input", "ip4-input-no-checksum"};
+
+static clib_error_t *
+ip4_feature_init (vlib_main_t * vm, ip4_main_t * im)
+{
+  ip_lookup_main_t * lm = &im->lookup_main;
+  clib_error_t * error;
+  vnet_cast_t cast;
+
+  for (cast = 0; cast < VNET_N_CAST; cast++)
+    {
+      ip_config_main_t * cm = &lm->rx_config_mains[cast];
+      vnet_config_main_t * vcm = &cm->config_main;
+
+      if ((error = ip_feature_init_cast (vm, cm, vcm, 
+                                         feature_start_nodes,
+                                         ARRAY_LEN(feature_start_nodes),
+                                         cast,
+                                         1 /* is_ip4 */)))
+        return error;
+    }
+  return 0;
+}
+
 static clib_error_t *
 ip4_sw_interface_add_del (vnet_main_t * vnm,
 			  u32 sw_if_index,
@@ -1293,57 +1370,31 @@
   ip4_main_t * im = &ip4_main;
   ip_lookup_main_t * lm = &im->lookup_main;
   u32 ci, cast;
+  u32 feature_index;
 
   for (cast = 0; cast < VNET_N_CAST; cast++)
     {
       ip_config_main_t * cm = &lm->rx_config_mains[cast];
       vnet_config_main_t * vcm = &cm->config_main;
 
-      if (! vcm->node_index_by_feature_index)
-	{
-	  if (cast == VNET_UNICAST)
-	    {
-	      static char * start_nodes[] = { "ip4-input", "ip4-input-no-checksum", };
-	      static char * feature_nodes[] = {
-		[IP4_RX_FEATURE_CHECK_ACCESS] = "ip4-inacl",
-		[IP4_RX_FEATURE_SOURCE_CHECK_REACHABLE_VIA_RX] = "ip4-source-check-via-rx",
-		[IP4_RX_FEATURE_SOURCE_CHECK_REACHABLE_VIA_ANY] = "ip4-source-check-via-any",
-		[IP4_RX_FEATURE_IPSEC] = "ipsec-input-ip4",
-		[IP4_RX_FEATURE_VPATH] = "vpath-input-ip4",
-		[IP4_RX_FEATURE_LOOKUP] = "ip4-lookup",
-	      };
-
-	      vnet_config_init (vm, vcm,
-				start_nodes, ARRAY_LEN (start_nodes),
-				feature_nodes, ARRAY_LEN (feature_nodes));
-	    }
-	  else
-	    {
-	      static char * start_nodes[] = { "ip4-input", "ip4-input-no-checksum", };
-	      static char * feature_nodes[] = {
-		[IP4_RX_FEATURE_VPATH] = "vpath-input-ip4",
-		[IP4_RX_FEATURE_LOOKUP] = "ip4-lookup-multicast",
-	      };
-
-	      vnet_config_init (vm, vcm,
-				start_nodes, ARRAY_LEN (start_nodes),
-				feature_nodes, ARRAY_LEN (feature_nodes));
-	    }
-	}
-
       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];
 
+      if (cast == VNET_UNICAST)
+        feature_index = im->ip4_unicast_rx_feature_lookup;
+      else
+        feature_index = im->ip4_multicast_rx_feature_lookup;
+
       if (is_add)
 	ci = vnet_config_add_feature (vm, vcm,
 				      ci,
-				      IP4_RX_FEATURE_LOOKUP,
+                                      feature_index,
 				      /* config data */ 0,
 				      /* # bytes of config data */ 0);
       else
 	ci = vnet_config_del_feature (vm, vcm,
 				      ci,
-				      IP4_RX_FEATURE_LOOKUP,
+                                      feature_index,
 				      /* config data */ 0,
 				      /* # bytes of config data */ 0);
 
@@ -1450,6 +1501,8 @@
 			       "ip4 arp");
   }
 
+  ip4_feature_init (vm, im);
+
   return 0;
 }
 
diff --git a/vnet/vnet/ip/ip4_input.c b/vnet/vnet/ip/ip4_input.c
index 6063425..5b2b42d 100644
--- a/vnet/vnet/ip/ip4_input.c
+++ b/vnet/vnet/ip/ip4_input.c
@@ -150,18 +150,18 @@
 	  cm0 = lm->rx_config_mains + cast0;
 	  cm1 = lm->rx_config_mains + cast1;
 
-	  vnet_buffer (p0)->ip.current_config_index = vec_elt (cm0->config_index_by_sw_if_index, sw_if_index0);
-	  vnet_buffer (p1)->ip.current_config_index = vec_elt (cm1->config_index_by_sw_if_index, sw_if_index1);
+	  p0->current_config_index = vec_elt (cm0->config_index_by_sw_if_index, sw_if_index0);
+	  p1->current_config_index = vec_elt (cm1->config_index_by_sw_if_index, sw_if_index1);
 
 	  vnet_buffer (p0)->ip.adj_index[VLIB_RX] = ~0;
 	  vnet_buffer (p1)->ip.adj_index[VLIB_RX] = ~0;
 
 	  vnet_get_config_data (&cm0->config_main,
-				&vnet_buffer (p0)->ip.current_config_index,
+				&p0->current_config_index,
 				&next0,
 				/* # bytes of config data */ 0);
 	  vnet_get_config_data (&cm1->config_main,
-				&vnet_buffer (p1)->ip.current_config_index,
+				&p1->current_config_index,
 				&next1,
 				/* # bytes of config data */ 0);
 
@@ -264,10 +264,10 @@
 
 	  cast0 = ip4_address_is_multicast (&ip0->dst_address) ? VNET_MULTICAST : VNET_UNICAST;
 	  cm0 = lm->rx_config_mains + cast0;
-	  vnet_buffer (p0)->ip.current_config_index = vec_elt (cm0->config_index_by_sw_if_index, sw_if_index0);
+	  p0->current_config_index = vec_elt (cm0->config_index_by_sw_if_index, sw_if_index0);
 	  vnet_buffer (p0)->ip.adj_index[VLIB_RX] = ~0;
 	  vnet_get_config_data (&cm0->config_main,
-				&vnet_buffer (p0)->ip.current_config_index,
+				&p0->current_config_index,
 				&next0,
 				/* # bytes of config data */ 0);
 
diff --git a/vnet/vnet/ip/ip4_source_check.c b/vnet/vnet/ip/ip4_source_check.c
index 11e6678..64b1e0a 100644
--- a/vnet/vnet/ip/ip4_source_check.c
+++ b/vnet/vnet/ip/ip4_source_check.c
@@ -142,11 +142,11 @@
 	  ip1 = vlib_buffer_get_current (p1);
 
 	  c0 = vnet_get_config_data (&cm->config_main,
-				     &vnet_buffer (p0)->ip.current_config_index,
+				     &p0->current_config_index,
 				     &next0,
 				     sizeof (c0[0]));
 	  c1 = vnet_get_config_data (&cm->config_main,
-				     &vnet_buffer (p1)->ip.current_config_index,
+				     &p1->current_config_index,
 				     &next1,
 				     sizeof (c1[0]));
 
@@ -223,7 +223,7 @@
 	  ip0 = vlib_buffer_get_current (p0);
 
 	  c0 = vnet_get_config_data (&cm->config_main,
-				     &vnet_buffer (p0)->ip.current_config_index,
+				     &p0->current_config_index,
 				     &next0,
 				     sizeof (c0[0]));
 
@@ -329,7 +329,7 @@
   clib_error_t * error = 0;
   u32 sw_if_index, is_del, ci;
   ip4_source_check_config_t config;
-  ip4_rx_feature_type_t type;
+  u32 feature_index;
 
   sw_if_index = ~0;
 
@@ -343,7 +343,7 @@
   is_del = 0;
   config.no_default_route = 0;
   config.fib_index = im->fib_index_by_sw_if_index[sw_if_index];
-  type = IP4_RX_FEATURE_SOURCE_CHECK_REACHABLE_VIA_RX;
+  feature_index = im->ip4_unicast_rx_feature_source_reachable_via_rx;
   if (unformat (input, "del"))
     is_del = 1;
 
@@ -353,7 +353,7 @@
 	: vnet_config_add_feature)
     (vm, &rx_cm->config_main,
      ci,
-     type,
+     feature_index,
      &config,
      sizeof (config));
   rx_cm->config_index_by_sw_if_index[sw_if_index] = ci;
diff --git a/vnet/vnet/ip/ip6.h b/vnet/vnet/ip/ip6.h
index 69e69f2..3c27db0 100644
--- a/vnet/vnet/ip/ip6.h
+++ b/vnet/vnet/ip/ip6.h
@@ -103,33 +103,6 @@
   uword function_opaque;
 } ip6_add_del_interface_address_callback_t;
 
-typedef enum {
-  /* First check access list to either permit or deny this
-     packet based on classification. */
-  IP6_RX_FEATURE_CHECK_ACCESS,
-
-  /* RPF check: verify that source address is reachable via
-     RX interface or via any interface. */
-  IP6_RX_FEATURE_CHECK_SOURCE_REACHABLE_VIA_RX,
-  IP6_RX_FEATURE_CHECK_SOURCE_REACHABLE_VIA_ANY,
-
-  /* IPSec */
-  IP6_RX_FEATURE_IPSEC,
-
-  /* Intercept and decap L2TPv3 packets. */
-  IP6_RX_FEATURE_L2TPV3,
-
-  /* vPath forwarding: won't return to call next feature
-     so any feature needed before vPath forwarding must be prior
-     to this entry */
-  IP6_RX_FEATURE_VPATH,
-
-  /* Must be last: perform forwarding lookup. */
-  IP6_RX_FEATURE_LOOKUP,
-
-  IP6_N_RX_FEATURE,
-} ip6_rx_feature_type_t;
-
 typedef struct ip6_main_t {
   BVT(clib_bihash) ip6_lookup_table;
 
@@ -168,6 +141,21 @@
   u32 lookup_table_nbuckets;
   uword lookup_table_size;
 
+  /* feature path configuration lists */
+  vnet_ip_feature_registration_t * next_uc_feature;
+  vnet_ip_feature_registration_t * next_mc_feature;
+
+  /* Built-in unicast feature path indices, see ip_feature_init_cast(...)  */
+  u32 ip6_unicast_rx_feature_check_access;
+  u32 ip6_unicast_rx_feature_ipsec;
+  u32 ip6_unicast_rx_feature_l2tp_decap;
+  u32 ip6_unicast_rx_feature_vpath;
+  u32 ip6_unicast_rx_feature_lookup;
+
+  /* Built-in multicast feature path indices */
+  u32 ip6_multicast_rx_feature_vpath;
+  u32 ip6_multicast_rx_feature_lookup;
+
   /* Seed for Jenkins hash used to compute ip6 flow hash. */
   u32 flow_hash_seed;
 
@@ -185,6 +173,30 @@
 /* Global ip6 main structure. */
 extern ip6_main_t ip6_main;
 
+#define VNET_IP6_UNICAST_FEATURE_INIT(x,...)                    \
+  __VA_ARGS__ vnet_ip_feature_registration_t uc_##x;            \
+static void __vnet_add_feature_registration_uc_##x (void)       \
+  __attribute__((__constructor__)) ;                            \
+static void __vnet_add_feature_registration_uc_##x (void)       \
+{                                                               \
+  ip6_main_t * im = &ip6_main;                                  \
+  uc_##x.next = im->next_uc_feature;                            \
+  im->next_uc_feature = &uc_##x;                                \
+}                                                               \
+__VA_ARGS__ vnet_ip_feature_registration_t uc_##x 
+
+#define VNET_IP6_MULTICAST_FEATURE_INIT(x,...)                  \
+  __VA_ARGS__ vnet_ip_feature_registration_t mc_##x;            \
+static void __vnet_add_feature_registration_mc_##x (void)       \
+  __attribute__((__constructor__)) ;                            \
+static void __vnet_add_feature_registration_mc_##x (void)       \
+{                                                               \
+  ip6_main_t * im = &ip6_main;                                  \
+  mc_##x.next = im->next_mc_feature;                            \
+  im->next_mc_feature = &mc_##x;                                \
+}                                                               \
+__VA_ARGS__ vnet_ip_feature_registration_t mc_##x 
+
 /* Global ip6 input node.  Errors get attached to ip6 input node. */
 extern vlib_node_registration_t ip6_input_node;
 extern vlib_node_registration_t ip6_rewrite_node;
diff --git a/vnet/vnet/ip/ip6_forward.c b/vnet/vnet/ip/ip6_forward.c
index e49d242..f77b99e 100644
--- a/vnet/vnet/ip/ip6_forward.c
+++ b/vnet/vnet/ip/ip6_forward.c
@@ -1200,6 +1200,75 @@
 
 VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (ip6_sw_interface_admin_up_down);
 
+/* Built-in ip6 unicast rx feature path definition */
+VNET_IP6_UNICAST_FEATURE_INIT (ip6_inacl, static) = {
+  .node_name = "ip6-inacl", 
+  .runs_before = {"ipsec-input-ip6", 0}, 
+  .feature_index = &ip6_main.ip6_unicast_rx_feature_check_access,
+};
+
+VNET_IP6_UNICAST_FEATURE_INIT (ip6_ipsec, static) = {
+  .node_name = "ipsec-input-ip6",
+  .runs_before = {"l2tp-decap", 0},
+  .feature_index = &ip6_main.ip6_unicast_rx_feature_ipsec,
+};
+
+VNET_IP6_UNICAST_FEATURE_INIT (ip6_l2tp, static) = {
+  .node_name = "l2tp-decap",
+  .runs_before = {"vpath-input-ip6", 0},
+  .feature_index = &ip6_main.ip6_unicast_rx_feature_l2tp_decap,
+};
+
+VNET_IP6_UNICAST_FEATURE_INIT (ip6_vpath, static) = {
+  .node_name = "vpath-input-ip6",
+  .runs_before = {"ip6-lookup", 0},
+  .feature_index = &ip6_main.ip6_unicast_rx_feature_vpath,
+};
+
+VNET_IP6_UNICAST_FEATURE_INIT (ip6_lookup, static) = {
+  .node_name = "ip6-lookup",
+  .runs_before = {0}, /* not before any other features */
+  .feature_index = &ip6_main.ip6_unicast_rx_feature_lookup,
+};
+
+/* Built-in ip6 multicast rx feature path definition (none now) */
+VNET_IP6_MULTICAST_FEATURE_INIT (ip4_vpath_mc, static) = {
+  .node_name = "vpath-input-ip6",
+  .runs_before = {"ip6-lookup", 0},
+  .feature_index = &ip6_main.ip6_multicast_rx_feature_vpath,
+};
+
+VNET_IP6_MULTICAST_FEATURE_INIT (ip6_lookup, static) = {
+  .node_name = "ip6-lookup",
+  .runs_before = {0}, /* not before any other features */
+  .feature_index = &ip6_main.ip6_multicast_rx_feature_lookup,
+};
+
+static char * feature_start_nodes[] = 
+  {"ip6-input"};
+
+static clib_error_t *
+ip6_feature_init (vlib_main_t * vm, ip6_main_t * im)
+{
+  ip_lookup_main_t * lm = &im->lookup_main;
+  clib_error_t * error;
+  vnet_cast_t cast;
+  
+  for (cast = 0; cast < VNET_N_CAST; cast++)
+    {
+      ip_config_main_t * cm = &lm->rx_config_mains[cast];
+      vnet_config_main_t * vcm = &cm->config_main;
+      
+      if ((error = ip_feature_init_cast (vm, cm, vcm, 
+                                         feature_start_nodes,
+                                         ARRAY_LEN(feature_start_nodes),
+                                         cast,
+                                         0 /* is_ip4 */)))
+        return error;
+    }
+  return 0;
+}
+
 clib_error_t *
 ip6_sw_interface_add_del (vnet_main_t * vnm,
 			  u32 sw_if_index,
@@ -1209,41 +1278,31 @@
   ip6_main_t * im = &ip6_main;
   ip_lookup_main_t * lm = &im->lookup_main;
   u32 ci, cast;
+  u32 feature_index;
 
   for (cast = 0; cast < VNET_N_CAST; cast++)
     {
       ip_config_main_t * cm = &lm->rx_config_mains[cast];
       vnet_config_main_t * vcm = &cm->config_main;
 
-      /* FIXME multicast. */
-      if (! vcm->node_index_by_feature_index)
-	{
-	  char * start_nodes[] = { "ip6-input", };
-	  char * feature_nodes[] = {
-	    [IP6_RX_FEATURE_CHECK_ACCESS] = "ip6-inacl",
-            [IP6_RX_FEATURE_IPSEC] = "ipsec-input-ip6",
-	    [IP6_RX_FEATURE_L2TPV3] = "l2tp-decap",
-	    [IP6_RX_FEATURE_VPATH]  = "vpath-input-ip6",
-	    [IP6_RX_FEATURE_LOOKUP] = "ip6-lookup",
-	  };
-	  vnet_config_init (vm, vcm,
-			    start_nodes, ARRAY_LEN (start_nodes),
-			    feature_nodes, ARRAY_LEN (feature_nodes));
-	}
-
       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];
 
+      if (cast == VNET_UNICAST)
+        feature_index = im->ip6_unicast_rx_feature_lookup;
+      else
+        feature_index = im->ip6_multicast_rx_feature_lookup;
+
       if (is_add)
 	ci = vnet_config_add_feature (vm, vcm,
 				      ci,
-				      IP6_RX_FEATURE_LOOKUP,
+                                      feature_index,
 				      /* config data */ 0,
 				      /* # bytes of config data */ 0);
       else
 	ci = vnet_config_del_feature (vm, vcm,
 				      ci,
-				      IP6_RX_FEATURE_LOOKUP,
+                                      feature_index,
 				      /* config data */ 0,
 				      /* # bytes of config data */ 0);
 
@@ -2857,6 +2916,8 @@
 			       "ip6 neighbor discovery");
   }
 
+  ip6_feature_init (vm, im);
+
   return 0;
 }
 
diff --git a/vnet/vnet/ip/ip6_input.c b/vnet/vnet/ip/ip6_input.c
index 2042cbd..7b5470d 100644
--- a/vnet/vnet/ip/ip6_input.c
+++ b/vnet/vnet/ip/ip6_input.c
@@ -149,18 +149,18 @@
 	  cm0 = lm->rx_config_mains + cast0;
 	  cm1 = lm->rx_config_mains + cast1;
 
-	  vnet_buffer (p0)->ip.current_config_index = vec_elt (cm0->config_index_by_sw_if_index, sw_if_index0);
-	  vnet_buffer (p1)->ip.current_config_index = vec_elt (cm1->config_index_by_sw_if_index, sw_if_index1);
+	  p0->current_config_index = vec_elt (cm0->config_index_by_sw_if_index, sw_if_index0);
+	  p1->current_config_index = vec_elt (cm1->config_index_by_sw_if_index, sw_if_index1);
 
 	  vnet_buffer (p0)->ip.adj_index[VLIB_RX] = ~0;
 	  vnet_buffer (p1)->ip.adj_index[VLIB_RX] = ~0;
 
 	  vnet_get_config_data (&cm0->config_main,
-				&vnet_buffer (p0)->ip.current_config_index,
+				&p0->current_config_index,
 				&next0,
 				/* # bytes of config data */ 0);
 	  vnet_get_config_data (&cm1->config_main,
-				&vnet_buffer (p1)->ip.current_config_index,
+				&p1->current_config_index,
 				&next1,
 				/* # bytes of config data */ 0);
 
@@ -234,11 +234,11 @@
 	  sw_if_index0 = vnet_buffer (p0)->sw_if_index[VLIB_RX];
 	  cast0 = ip6_address_is_multicast (&ip0->dst_address) ? VNET_MULTICAST : VNET_UNICAST;
 	  cm0 = lm->rx_config_mains + cast0;
-	  vnet_buffer (p0)->ip.current_config_index = vec_elt (cm0->config_index_by_sw_if_index, sw_if_index0);
+	  p0->current_config_index = vec_elt (cm0->config_index_by_sw_if_index, sw_if_index0);
 	  vnet_buffer (p0)->ip.adj_index[VLIB_RX] = ~0;
 
 	  vnet_get_config_data (&cm0->config_main,
-				&vnet_buffer (p0)->ip.current_config_index,
+				&p0->current_config_index,
 				&next0,
 				/* # bytes of config data */ 0);
 
diff --git a/vnet/vnet/ip/ip_feature_registration.c b/vnet/vnet/ip/ip_feature_registration.c
new file mode 100644
index 0000000..02699c4
--- /dev/null
+++ b/vnet/vnet/ip/ip_feature_registration.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2015 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vnet/vnet.h>
+#include <vnet/ip/ip.h>
+
+static int comma_split (u8 *s, u8 **a, u8 **b)
+{
+  *a = s;
+
+  while (*s && *s != ',')
+    s++;
+
+  if (*s == ',')
+    *s = 0;
+  else
+    return 1;
+
+  *b = (u8 *) (s+1);
+  return 0;
+}
+
+clib_error_t *
+ip_feature_init_cast (vlib_main_t * vm,
+                      ip_config_main_t * cm,
+                      vnet_config_main_t * vcm,
+                      char **feature_start_nodes,
+                      int num_feature_start_nodes,
+                      vnet_cast_t cast,
+                      int is_ip4)
+{
+  uword * index_by_name;
+  uword * reg_by_index;
+  u8 ** node_names = 0;
+  u8 * node_name;
+  char ** these_constraints;
+  char * this_constraint_c;
+  u8 ** constraints = 0;
+  u8 * constraint_tuple;
+  u8 * this_constraint;
+  u8 ** orig, ** closure;
+  uword * p;
+  int i, j, k;
+  u8 * a_name, * b_name;
+  int a_index, b_index;
+  int n_features;
+  u32 * result = 0;
+  vnet_ip_feature_registration_t * this_reg, * first_reg;
+  char ** feature_nodes = 0;
+  hash_pair_t * hp;
+  u8 ** keys_to_delete = 0;
+  ip4_main_t * im4 = &ip4_main;
+  ip6_main_t * im6 = &ip6_main;
+
+  index_by_name = hash_create_string (0, sizeof (uword));
+  reg_by_index = hash_create (0, sizeof (uword));
+
+  if (cast == VNET_UNICAST)
+    {
+      if (is_ip4)
+        first_reg = im4->next_uc_feature;
+      else
+        first_reg = im6->next_uc_feature;
+    }
+  else
+    {
+      if (is_ip4)
+        first_reg = im4->next_mc_feature;
+      else
+        first_reg = im6->next_mc_feature;
+    }
+  
+  this_reg = first_reg;
+
+  /* pass 1, collect feature node names, construct a before b pairs */
+  while (this_reg)
+    {
+      node_name = format (0, "%s%c", this_reg->node_name, 0);
+      hash_set (reg_by_index, vec_len(node_names), (uword) this_reg);
+
+      hash_set_mem (index_by_name, node_name, vec_len(node_names));
+
+      vec_add1 (node_names, node_name);
+
+      these_constraints = this_reg->runs_before;
+
+      while (these_constraints [0])
+        {
+          this_constraint_c = these_constraints[0];
+
+          constraint_tuple = format (0, "%s,%s%c", node_name,
+                                     this_constraint_c, 0);
+          vec_add1 (constraints, constraint_tuple);
+          these_constraints++;
+        }
+      this_reg = this_reg->next;
+    }
+
+  n_features = vec_len (node_names);
+  orig = clib_ptclosure_alloc (n_features);
+
+  for (i = 0; i < vec_len (constraints); i++)
+    {
+      this_constraint = constraints[i];
+
+      if (comma_split (this_constraint, &a_name, &b_name))
+        return clib_error_return (0, "comma_split failed!");
+      
+      p = hash_get_mem (index_by_name, a_name);
+      if (p == 0)
+        return clib_error_return (0, "feature node '%s' not found", a_name);
+      a_index = p[0];
+
+      p = hash_get_mem (index_by_name, b_name);
+      if (p == 0)
+        return clib_error_return (0, "feature node '%s' not found", b_name);
+      b_index = p[0];
+
+      /* add a before b to the original set of constraints */
+      orig[a_index][b_index] = 1;
+      vec_free (this_constraint);
+    }
+  
+  /* Compute the positive transitive closure of the original constraints */
+  closure = clib_ptclosure (orig);
+
+  /* Compute a partial order across feature nodes, if one exists. */
+ again:
+  for (i = 0; i < n_features; i++)
+    {
+      for (j = 0; j < n_features; j++)
+        {
+          if (closure[i][j])
+            goto item_constrained;
+        }
+      /* Item i can be output */
+      vec_add1 (result, i);
+      {
+        for (k = 0; k < n_features; k++)
+          closure [k][i] = 0;
+        /* 
+         * Add a "Magic" a before a constraint. 
+         * This means we'll never output it again
+         */
+        closure [i][i] = 1;
+        goto again;
+      }
+    item_constrained:
+      ;
+    }
+
+  /* see if we got a partial order... */
+  if (vec_len (result) != n_features)
+    return clib_error_return (0, "ip4_feature_init_cast (cast=%d), no PO!");
+
+  /* 
+   * We win.
+   * Bind the index variables, and output the feature node name vector
+   * using the partial order we just computed. Result is in stack
+   * order, because the entry with the fewest constraints (e.g. none)
+   * is output first, etc.
+   */
+
+  for (i = n_features-1; i >= 0; i--)
+    {
+      p = hash_get (reg_by_index, result[i]);
+      ASSERT (p != 0);
+      this_reg = (vnet_ip_feature_registration_t *)p[0];
+      *this_reg->feature_index = n_features - (i+1);
+      vec_add1 (feature_nodes, this_reg->node_name);
+    }
+  
+  /* Set up the config infrastructure */
+  vnet_config_init (vm, vcm,
+                    feature_start_nodes, 
+                    num_feature_start_nodes,
+                    feature_nodes, 
+                    vec_len(feature_nodes));
+
+  /* Finally, clean up all the shit we allocated */
+  hash_foreach_pair (hp, index_by_name,
+  ({
+    vec_add1 (keys_to_delete, (u8 *)hp->key);
+  }));
+  hash_free (index_by_name);
+  for (i = 0; i < vec_len(keys_to_delete); i++)
+    vec_free (keys_to_delete[i]);
+  vec_free (keys_to_delete);
+  hash_free (reg_by_index);
+  vec_free (result);
+  clib_ptclosure_free (orig);
+  clib_ptclosure_free (closure);
+  return 0;
+}
+
diff --git a/vnet/vnet/ip/ip_feature_registration.h b/vnet/vnet/ip/ip_feature_registration.h
new file mode 100644
index 0000000..da2a005
--- /dev/null
+++ b/vnet/vnet/ip/ip_feature_registration.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef included_ip_feature_registration_h
+#define included_ip_feature_registration_h
+
+typedef struct _vnet_ip_feature_registration {
+  struct _vnet_ip_feature_registration * next;
+  char * node_name;
+  u32 * feature_index;
+  char * runs_before[];
+} vnet_ip_feature_registration_t;
+
+clib_error_t *
+ip_feature_init_cast (vlib_main_t * vm,
+                      ip_config_main_t * cm,
+                      vnet_config_main_t * vcm,
+                      char **feature_start_nodes,
+                      int num_feature_start_nodes,
+                      vnet_cast_t cast,
+                      int is_ip4);
+
+#endif /* included_ip_feature_registration_h */
diff --git a/vnet/vnet/ip/ip_input_acl.c b/vnet/vnet/ip/ip_input_acl.c
index fcf8eea..eaf7f34 100644
--- a/vnet/vnet/ip/ip_input_acl.c
+++ b/vnet/vnet/ip/ip_input_acl.c
@@ -232,7 +232,7 @@
           e0 = 0;
           t0 = 0;
           vnet_get_config_data (am->vnet_config_main[tid],
-                                &vnet_buffer(b0)->ip.current_config_index,
+                                &b0->current_config_index,
                                 &next0,
                                 /* # bytes of config data */ 0);
 
diff --git a/vnet/vnet/ipsec/ipsec.c b/vnet/vnet/ipsec/ipsec.c
index ea077d0..1c9d57b 100644
--- a/vnet/vnet/ipsec/ipsec.c
+++ b/vnet/vnet/ipsec/ipsec.c
@@ -73,7 +73,7 @@
   ci = (is_add ? vnet_config_add_feature : vnet_config_del_feature)
     (vm, &rx_cm->config_main,
      ci,
-     IP4_RX_FEATURE_IPSEC,
+     ip4_main.ip4_unicast_rx_feature_ipsec,
      &config,
      sizeof (config));
   rx_cm->config_index_by_sw_if_index[sw_if_index] = ci;
@@ -87,7 +87,7 @@
   ci = (is_add ? vnet_config_add_feature : vnet_config_del_feature)
     (vm, &rx_cm->config_main,
      ci,
-     IP6_RX_FEATURE_IPSEC,
+     ip6_main.ip6_unicast_rx_feature_ipsec,
      &config,
      sizeof (config));
   rx_cm->config_index_by_sw_if_index[sw_if_index] = ci;
diff --git a/vnet/vnet/ipsec/ipsec_input.c b/vnet/vnet/ipsec/ipsec_input.c
index 09acd10..e701117 100644
--- a/vnet/vnet/ipsec/ipsec_input.c
+++ b/vnet/vnet/ipsec/ipsec_input.c
@@ -212,7 +212,7 @@
 
           b0 = vlib_get_buffer (vm, bi0);
           c0 = vnet_get_config_data (&cm->config_main,
-                                     &vnet_buffer (b0)->ip.current_config_index,
+                                     &b0->current_config_index,
                                      &next0, sizeof (c0[0]));
 
           spd0 = pool_elt_at_index(im->spds, c0->spd_index);
@@ -335,7 +335,7 @@
 
           b0 = vlib_get_buffer (vm, bi0);
           c0 = vnet_get_config_data (&cm->config_main,
-                                     &vnet_buffer (b0)->ip.current_config_index,
+                                     &b0->current_config_index,
                                      &next0, sizeof (c0[0]));
 
           spd0 = pool_elt_at_index(im->spds, c0->spd_index);
diff --git a/vnet/vnet/l2tp/decap.c b/vnet/vnet/l2tp/decap.c
index 5f0d05c..8b9761a 100644
--- a/vnet/vnet/l2tp/decap.c
+++ b/vnet/vnet/l2tp/decap.c
@@ -198,7 +198,7 @@
       ip6_l2tpv3_config_t * c0;
 
       vnet_get_config_data (&cm->config_main,
-                            &vnet_buffer (b)->ip.current_config_index,
+                            &b->current_config_index,
                             &next_index,
                             sizeof (c0[0]));
     }
diff --git a/vnet/vnet/l2tp/l2tp.c b/vnet/vnet/l2tp/l2tp.c
index 7dfbe15..db4b4f5 100644
--- a/vnet/vnet/l2tp/l2tp.c
+++ b/vnet/vnet/l2tp/l2tp.c
@@ -577,12 +577,12 @@
   ip_config_main_t * rx_cm = &lm->rx_config_mains[VNET_UNICAST];
   u32 ci;
   ip6_l2tpv3_config_t config;
-  ip6_rx_feature_type_t type;
+  u32 feature_index;
 
   if (pool_is_free_index (vnm->interface_main.sw_interfaces, sw_if_index))
     return VNET_API_ERROR_INVALID_SW_IF_INDEX;
 
-  type = IP6_RX_FEATURE_L2TPV3;
+  feature_index = im->ip6_unicast_rx_feature_ipsec;
 
   ci = rx_cm->config_index_by_sw_if_index[sw_if_index];
   ci = (enable_disable
@@ -590,7 +590,7 @@
         : vnet_config_del_feature)
     (vlib_get_main(), &rx_cm->config_main,
      ci,
-     type,
+     feature_index,
      &config,
      sizeof (config));
   rx_cm->config_index_by_sw_if_index[sw_if_index] = ci;
diff --git a/vnet/vnet/misc.c b/vnet/vnet/misc.c
index 6effe6e..9dbed8d 100644
--- a/vnet/vnet/misc.c
+++ b/vnet/vnet/misc.c
@@ -76,6 +76,15 @@
   if ((error = vlib_call_init_function (vm, vnet_interface_init)))
     return error;
 
+  if ((error = vlib_call_init_function (vm, ip_main_init)))
+    return error;
+
+  if ((error = vlib_call_init_function (vm, ip4_lookup_init)))
+    return error;
+
+  if ((error = vlib_call_init_function (vm, ip6_lookup_init)))
+    return error;
+
   vnm->vlib_main = vm;
 
   hw_if_index = vnet_register_interface