unicast RPF for FIB2.0

In a heirarchical FIB performing a unicast RPF check would require the traversal of the data-plane graph to seek out all the adjacency objects and then read those to find their interface. This is not efficient. Instead, for each path-list we construct a list of unique input interfaces and link this uRPF-list against the entry in the prefix table. In the data-plane the uRPF list can be retrieved from the load-balance lookup result and the RPF check is a simple and efficient walk across the minimal interface list. The uRPF-list is maintained as the routing heirarchy changes, in a similar way to the data-plane object graph.
We also provide a knob to allow an arbitrary prefix to pass the loose check.

Change-Id: Ie7c0ae3c4483ef467cfd5b136ee0315ff98ec15b
Signed-off-by: Neale Ranns <nranns@cisco.com>
diff --git a/vlib/vlib/trace.c b/vlib/vlib/trace.c
index 5b64c55..8fe3dac 100644
--- a/vlib/vlib/trace.c
+++ b/vlib/vlib/trace.c
@@ -367,35 +367,41 @@
 cli_add_trace_buffer (vlib_main_t * vm,
 		      unformat_input_t * input, vlib_cli_command_t * cmd)
 {
+  unformat_input_t _line_input, *line_input = &_line_input;
   vlib_trace_main_t *tm;
   vlib_trace_node_t *tn;
   u32 node_index, add;
   u8 verbose = 0;
 
-  while (unformat_check_input (input) != (uword) UNFORMAT_END_OF_INPUT)
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != (uword) UNFORMAT_END_OF_INPUT)
     {
-      if (unformat
-	  (input, "%U %d", unformat_vlib_node, vm, &node_index, &add))
+      if (unformat (line_input, "%U %d",
+		    unformat_vlib_node, vm, &node_index, &add))
 	;
-      else if (unformat (input, "verbose"))
+      else if (unformat (line_input, "verbose"))
 	verbose = 1;
       else
 	return clib_error_create ("expected NODE COUNT, got `%U'",
-				  format_unformat_error, input);
+				  format_unformat_error, line_input);
     }
 
+  /* *INDENT-OFF* */
   foreach_vlib_main ((
-		       {
-		       void *oldheap;
-		       tm = &this_vlib_main->trace_main;
-		       tm->trace_active_hint = 1;
-		       tm->verbose = verbose;
-		       oldheap =
-		       clib_mem_set_heap (this_vlib_main->heap_base);
-		       vec_validate (tm->nodes, node_index);
-		       tn = tm->nodes + node_index;
-		       tn->limit += add; clib_mem_set_heap (oldheap);
-		       }));
+    {
+      void *oldheap;
+      tm = &this_vlib_main->trace_main;
+      tm->trace_active_hint = 1;
+      tm->verbose = verbose;
+      oldheap =
+	clib_mem_set_heap (this_vlib_main->heap_base);
+      vec_validate (tm->nodes, node_index);
+      tn = tm->nodes + node_index;
+      tn->limit += add; clib_mem_set_heap (oldheap);
+    }));
+  /* *INDENT-ON* */
 
   return 0;
 }
diff --git a/vnet/Makefile.am b/vnet/Makefile.am
index 88d5564..c0ae70d 100644
--- a/vnet/Makefile.am
+++ b/vnet/Makefile.am
@@ -763,6 +763,7 @@
   vnet/fib/fib_path_list.c                      \
   vnet/fib/fib_path.c				\
   vnet/fib/fib_path_ext.c			\
+  vnet/fib/fib_urpf_list.c			\
   vnet/fib/fib_attached_export.c
 
 nobase_include_HEADERS +=			\
diff --git a/vnet/etc/scripts/urpf b/vnet/etc/scripts/urpf
new file mode 100644
index 0000000..a4d8752
--- /dev/null
+++ b/vnet/etc/scripts/urpf
@@ -0,0 +1,86 @@
+
+create loop int
+
+set int state loop0 up
+set int ip addr loop0 10.10.10.10/24
+
+packet-generator new {
+  name transit-deny
+  limit 1
+  node ip4-input
+  size 64-64
+  no-recycle
+  data {
+    UDP: 1.2.3.4 -> 2.2.2.2
+    UDP: 3000 -> 3001
+    length 128 checksum 0 incrementing 1
+  }
+}
+
+packet-generator new {
+  name transit-allow
+  limit 1
+  node ip4-input
+  size 64-64
+  no-recycle
+  data {
+    UDP: 1.1.1.1 -> 2.2.2.2
+    UDP: 3000 -> 3001
+    length 128 checksum 0 incrementing 1
+  }
+}
+
+packet-generator new {
+  name transit-allow-from-excemption
+  limit 1
+  node ip4-input
+  size 64-64
+  no-recycle
+  data {
+    UDP: 11.11.12.13 -> 2.2.2.2
+    UDP: 6000 -> 6001
+    length 128 checksum 0 incrementing 1
+  }
+}
+
+packet-generator new {
+  name for-us-allow-from-excemption
+  limit 1
+  node ip4-input
+  size 64-64
+  no-recycle
+  data {
+    UDP: 11.11.12.13 -> 10.10.10.10
+    UDP: 6000 -> 6001
+    length 128 checksum 0 incrementing 1
+  }
+}
+
+packet-generator new {
+  name for-us-allow
+  limit 1
+  node ip4-input
+  size 64-64
+  no-recycle
+  data {
+    UDP: 1.1.1.1 -> 10.10.10.10
+    UDP: 3000 -> 3001
+    length 128 checksum 0 incrementing 1
+  }
+}
+
+tr add pg-input 100
+
+set int ip addr pg0 10.10.11.10/24
+
+set interface ip source-check pg0 strict
+
+ip route add 1.1.1.1/32 via 10.10.11.11 pg0
+ip route add 2.2.2.2/32 via 10.10.10.11 loop0
+
+ip urpf-accept 11.11.0.0/16
+
+#set interface ip source-check pg0 strict del
+#set interface ip source-check pg0 loose
+
+#ip urpf-accept del 11.11.0.0/16
diff --git a/vnet/vnet/dpo/load_balance.c b/vnet/vnet/dpo/load_balance.c
index 093661d..fc78850 100644
--- a/vnet/vnet/dpo/load_balance.c
+++ b/vnet/vnet/dpo/load_balance.c
@@ -20,6 +20,7 @@
 #include <vppinfra/math.h>              /* for fabs */
 #include <vnet/adj/adj.h>
 #include <vnet/adj/adj_internal.h>
+#include <vnet/fib/fib_urpf_list.h>
 
 /*
  * distribution error tolerance for load-balancing
@@ -87,6 +88,7 @@
     memset(lb, 0, sizeof(*lb));
 
     lb->lb_map = INDEX_INVALID;
+    lb->lb_urpf = INDEX_INVALID;
     vlib_validate_combined_counter(&(load_balance_main.lbm_to_counters),
                                    load_balance_get_index(lb));
     vlib_validate_combined_counter(&(load_balance_main.lbm_via_counters),
@@ -117,7 +119,7 @@
 
     s = format(s, "%U: ", format_dpo_type, DPO_LOAD_BALANCE);
     s = format(s, "[index:%d buckets:%d ", lbi, lb->lb_n_buckets);
-    s = format(s, "locks:%d ", lb->lb_locks);
+    s = format(s, "uRPF:%d ", lb->lb_urpf);
     s = format(s, "to:[%Ld:%Ld]", to.packets, to.bytes);
     if (0 != via.packets)
     {
@@ -236,6 +238,35 @@
     return (0);
 }
 
+void
+load_balance_set_urpf (index_t lbi,
+		       index_t urpf)
+{
+    load_balance_t *lb;
+    index_t old;
+
+    lb = load_balance_get(lbi);
+
+    /*
+     * packets in flight we see this change. but it's atomic, so :P
+     */
+    old = lb->lb_urpf;
+    lb->lb_urpf = urpf;
+
+    fib_urpf_list_unlock(old);
+    fib_urpf_list_lock(urpf);
+}
+
+index_t
+load_balance_get_urpf (index_t lbi)
+{
+    load_balance_t *lb;
+
+    lb = load_balance_get(lbi);
+
+    return (lb->lb_urpf);
+}
+
 const dpo_id_t *
 load_balance_get_bucket (index_t lbi,
                          u32 bucket)
@@ -652,6 +683,9 @@
         vec_free(lb->lb_buckets);
     }
 
+    fib_urpf_list_unlock(lb->lb_urpf);
+    load_balance_map_unlock(lb->lb_map);
+
     pool_put(load_balance_pool, lb);
 }
 
@@ -677,6 +711,7 @@
 			  pool_elts(load_balance_pool),
 			  pool_len(load_balance_pool),
 			  sizeof(load_balance_t));
+    load_balance_map_show_mem();
 }
 
 const static dpo_vft_t lb_vft = {
diff --git a/vnet/vnet/dpo/load_balance.h b/vnet/vnet/dpo/load_balance.h
index d630a2c..4bf3ecf 100644
--- a/vnet/vnet/dpo/load_balance.h
+++ b/vnet/vnet/dpo/load_balance.h
@@ -112,6 +112,11 @@
     index_t lb_map;
 
     /**
+     * This is the index of the uRPF list for this LB
+     */
+    index_t lb_urpf;
+
+    /**
      * the hash config to use when selecting a bucket. this is a u16
      */
     flow_hash_config_t lb_hash_config;
@@ -160,6 +165,9 @@
 extern void load_balance_set_bucket(index_t lbi,
 				    u32 bucket,
 				    const dpo_id_t *next);
+extern void load_balance_set_urpf(index_t lbi,
+				  index_t urpf);
+extern index_t load_balance_get_urpf(index_t lbi);
 
 extern u8* format_load_balance(u8 * s, va_list * args);
 
diff --git a/vnet/vnet/dpo/load_balance_map.c b/vnet/vnet/dpo/load_balance_map.c
index f08801f..70ce1bf 100644
--- a/vnet/vnet/dpo/load_balance_map.c
+++ b/vnet/vnet/dpo/load_balance_map.c
@@ -526,6 +526,15 @@
     lb_maps_by_path_index = hash_create(0, sizeof(fib_node_list_t));
 }
 
+void
+load_balance_map_show_mem (void)
+{
+    fib_show_memory_usage("Load-Balance Map",
+			  pool_elts(load_balance_map_pool),
+			  pool_len(load_balance_map_pool),
+			  sizeof(load_balance_map_t));
+}
+
 static clib_error_t *
 load_balance_map_show (vlib_main_t * vm,
                        unformat_input_t * input,
diff --git a/vnet/vnet/dpo/load_balance_map.h b/vnet/vnet/dpo/load_balance_map.h
index f080e97..454bf4b 100644
--- a/vnet/vnet/dpo/load_balance_map.h
+++ b/vnet/vnet/dpo/load_balance_map.h
@@ -60,6 +60,7 @@
 extern void load_balance_map_path_state_change(fib_node_index_t path_index);
 
 extern u8* format_load_balance_map(u8 *s, va_list ap);
+extern void load_balance_map_show_mem(void);
 
 /**
  * The encapsulation breakages are for fast DP access
diff --git a/vnet/vnet/fib/fib_entry.c b/vnet/vnet/fib/fib_entry.c
index 1821319..0692200 100644
--- a/vnet/vnet/fib/fib_entry.c
+++ b/vnet/vnet/fib/fib_entry.c
@@ -473,6 +473,21 @@
     .fnv_mem_show = fib_entry_show_memory,
 };
 
+/**
+ * @brief Contribute the set of Adjacencies that this entry forwards with
+ * to build the uRPF list of its children
+ */
+void
+fib_entry_contribute_urpf (fib_node_index_t entry_index,
+			   index_t urpf)
+{
+    fib_entry_t *fib_entry;
+
+    fib_entry = fib_entry_get(entry_index);
+
+    return (fib_path_list_contribute_urpf(fib_entry->fe_parent, urpf));
+}
+
 /*
  * fib_entry_contribute_forwarding
  *
diff --git a/vnet/vnet/fib/fib_entry.h b/vnet/vnet/fib/fib_entry.h
index 1016bb2..bfebe5d 100644
--- a/vnet/vnet/fib/fib_entry.h
+++ b/vnet/vnet/fib/fib_entry.h
@@ -99,10 +99,15 @@
     FIB_SOURCE_AE,
     /**
      * Recursive resolution source.
-     * Used to install an entry that is thre resolution traget of another.
+     * Used to install an entry that is the resolution traget of another.
      */
     FIB_SOURCE_RR,
     /**
+     * uRPF bypass/exemption.
+     * Used to install an entry that is exempt from the loose uRPF check
+     */
+    FIB_SOURCE_URPF_EXEMPT,
+    /**
      * The default route source.
      * The default route is always added to the FIB table (like the
      * special sources) but we need to be able to over-ride it with
@@ -138,6 +143,7 @@
     [FIB_SOURCE_RR] = "recursive-resolution",	        \
     [FIB_SOURCE_AE] = "attached_export",	        \
     [FIB_SOURCE_MPLS] = "mpls",           	        \
+    [FIB_SOURCE_URPF_EXEMPT] = "urpf-exempt",	        \
     [FIB_SOURCE_DEFAULT_ROUTE] = "default-route",	\
 }
 
@@ -376,7 +382,7 @@
      *     paint the header straight on without the need to check the packet
      *     type to derive the EOS bit value.
      */
-    dpo_id_t fe_lb[FIB_FORW_CHAIN_NUM];
+    dpo_id_t fe_lb[FIB_FORW_CHAIN_MPLS_NUM];
     /**
      * Vector of source infos.
      * Most entries will only have 1 source. So we optimise for memory usage,
@@ -449,6 +455,8 @@
 extern fib_entry_src_flag_t fib_entry_delete(fib_node_index_t fib_entry_index,
 					     fib_source_t source);
 
+extern void fib_entry_contribute_urpf(fib_node_index_t path_index,
+				      index_t urpf);
 extern void fib_entry_contribute_forwarding(
     fib_node_index_t fib_entry_index,
     fib_forward_chain_type_t type,
diff --git a/vnet/vnet/fib/fib_entry_src.c b/vnet/vnet/fib/fib_entry_src.c
index 0bca17d..6107e3e 100644
--- a/vnet/vnet/fib/fib_entry_src.c
+++ b/vnet/vnet/fib/fib_entry_src.c
@@ -18,9 +18,10 @@
 #include <vnet/dpo/mpls_label_dpo.h>
 #include <vnet/dpo/drop_dpo.h>
 
-#include "fib_entry_src.h"
-#include "fib_table.h"
-#include "fib_path_ext.h"
+#include <vnet/fib/fib_entry_src.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/fib/fib_path_ext.h>
+#include <vnet/fib/fib_urpf_list.h>
 
 /*
  * per-source type vft
@@ -368,6 +369,33 @@
     load_balance_multipath_update(dpo_lb,
                                   ctx.next_hops,
                                   fib_entry_calc_lb_flags(&ctx));
+
+    /*
+     * if this entry is sourced by the uRPF-exempt source then we
+     * append the always present local0 interface (index 0) to the
+     * uRPF list so it is not empty. that way packets pass the loose check.
+     */
+    index_t ui = fib_path_list_get_urpf(esrc->fes_pl);
+
+    if (fib_entry_is_sourced(fib_entry_get_index(fib_entry),
+			     FIB_SOURCE_URPF_EXEMPT) &&
+	(0 == fib_urpf_check_size(ui)))
+    {
+	/*
+	 * The uRPF list we get from the path-list is shared by all
+	 * other users of the list, but the uRPF exemption applies
+	 * only to this prefix. So we need our own list.
+	 */
+	ui = fib_urpf_list_alloc_and_lock();
+	fib_urpf_list_append(ui, 0);
+	fib_urpf_list_bake(ui);
+	load_balance_set_urpf(dpo_lb->dpoi_index, ui);
+	fib_urpf_list_unlock(ui);
+    }
+    else
+    {
+	load_balance_set_urpf(dpo_lb->dpoi_index, ui);
+    }
 }
 
 void
@@ -425,17 +453,13 @@
 
     fct = fib_entry_get_default_chain_type(fib_entry);
     /*
-     * uninstall the forwarding chain for the given source from the
-     * forwarding tables
+     * uninstall the forwarding chain from the forwarding tables
      */
     FIB_ENTRY_DBG(fib_entry, "uninstall: %d",
 		  fib_entry->fe_adj_index);
 
     if (dpo_id_is_valid(&fib_entry->fe_lb[fct]))
     {
-	/* fib_forward_chain_type_t fct; */
-	/* fib_path_ext_t *path_ext; */
-
 	fib_table_fwding_dpo_remove(
 	    fib_entry->fe_fib_index,
 	    &fib_entry->fe_prefix,
diff --git a/vnet/vnet/fib/fib_entry_src_rr.c b/vnet/vnet/fib/fib_entry_src_rr.c
index f6b8960..9219d58 100644
--- a/vnet/vnet/fib/fib_entry_src_rr.c
+++ b/vnet/vnet/fib/fib_entry_src_rr.c
@@ -244,4 +244,5 @@
 fib_entry_src_rr_register (void)
 {
     fib_entry_src_register(FIB_SOURCE_RR, &rr_src_vft);    
+    fib_entry_src_register(FIB_SOURCE_URPF_EXEMPT, &rr_src_vft);    
 }
diff --git a/vnet/vnet/fib/fib_path.c b/vnet/vnet/fib/fib_path.c
index 03cc7be..1b82f46 100644
--- a/vnet/vnet/fib/fib_path.c
+++ b/vnet/vnet/fib/fib_path.c
@@ -24,12 +24,13 @@
 
 #include <vnet/adj/adj.h>
 
-#include "fib_path.h"
-#include "fib_node.h"
-#include "fib_table.h"
-#include "fib_entry.h"
-#include "fib_path_list.h"
-#include "fib_internal.h"
+#include <vnet/fib/fib_path.h>
+#include <vnet/fib/fib_node.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/fib/fib_entry.h>
+#include <vnet/fib/fib_path_list.h>
+#include <vnet/fib/fib_internal.h>
+#include <vnet/fib/fib_urpf_list.h>
 
 /**
  * Enurmeration of path types
@@ -1551,6 +1552,62 @@
     return (path->fp_weight);
 }
 
+/**
+ * @brief Contribute the path's adjacency to the list passed.
+ * By calling this function over all paths, recursively, a child
+ * can construct its full set of forwarding adjacencies, and hence its
+ * uRPF list.
+ */
+void
+fib_path_contribute_urpf (fib_node_index_t path_index,
+			  index_t urpf)
+{
+    fib_path_t *path;
+
+    if (!fib_path_is_resolved(path_index))
+	return;
+
+    path = fib_path_get(path_index);
+
+    switch (path->fp_type)
+    {
+    case FIB_PATH_TYPE_ATTACHED_NEXT_HOP:
+	fib_urpf_list_append(urpf, path->attached_next_hop.fp_interface);
+	break;
+
+    case FIB_PATH_TYPE_ATTACHED:
+	fib_urpf_list_append(urpf, path->attached.fp_interface);
+	break;
+
+    case FIB_PATH_TYPE_RECURSIVE:
+	fib_entry_contribute_urpf(path->fp_via_fib, urpf);
+	break;
+
+    case FIB_PATH_TYPE_EXCLUSIVE:
+    case FIB_PATH_TYPE_SPECIAL:
+	/*
+	 * these path types may link to an adj, if that's what
+	 * the clinet gave
+	 */
+	if (dpo_is_adj(&path->fp_dpo))
+	{
+	    ip_adjacency_t *adj;
+
+	    adj = adj_get(path->fp_dpo.dpoi_index);
+
+	    fib_urpf_list_append(urpf, adj->rewrite_header.sw_if_index);
+	}
+	break;
+
+    case FIB_PATH_TYPE_DEAG:
+    case FIB_PATH_TYPE_RECEIVE:
+	/*
+	 * these path types don't link to an adj
+	 */
+	break;
+    }
+}
+
 void
 fib_path_contribute_forwarding (fib_node_index_t path_index,
 				fib_forward_chain_type_t fct,
diff --git a/vnet/vnet/fib/fib_path.h b/vnet/vnet/fib/fib_path.h
index 16ca358..2c141a4 100644
--- a/vnet/vnet/fib/fib_path.h
+++ b/vnet/vnet/fib/fib_path.h
@@ -141,6 +141,8 @@
 extern void fib_path_contribute_forwarding(fib_node_index_t path_index,
 					   fib_forward_chain_type_t type,
 					   dpo_id_t *dpo);
+extern void fib_path_contribute_urpf(fib_node_index_t path_index,
+				     index_t urpf);
 extern adj_index_t fib_path_get_adj(fib_node_index_t path_index);
 extern int fib_path_recursive_loop_detect(fib_node_index_t path_index,
 					  fib_node_index_t **entry_indicies);
diff --git a/vnet/vnet/fib/fib_path_list.c b/vnet/vnet/fib/fib_path_list.c
index 611fe9f..a582749 100644
--- a/vnet/vnet/fib/fib_path_list.c
+++ b/vnet/vnet/fib/fib_path_list.c
@@ -23,6 +23,7 @@
 #include <vnet/fib/fib_internal.h>
 #include <vnet/fib/fib_node_list.h>
 #include <vnet/fib/fib_walk.h>
+#include <vnet/fib/fib_urpf_list.h>
 
 /**
  * FIB path-list
@@ -47,10 +48,15 @@
     fib_protocol_t fpl_nh_proto;
 
     /**
-     * Vector of paths indecies for all configured paths.
+     * Vector of paths indicies for all configured paths.
      * For shareable path-lists this list MUST not change.
      */
     fib_node_index_t *fpl_paths;
+
+    /**
+     * the RPF list calculated for this path list
+     */
+    fib_node_index_t fpl_urpf;
 } fib_path_list_t;
 
 /*
@@ -138,6 +144,8 @@
 	    }
 	}
     }
+    s = format (s, " %U\n", format_fib_urpf_list, path_list->fpl_urpf);
+
     vec_foreach (path_index, path_list->fpl_paths)
     {
 	s = fib_path_format(*path_index, s);
@@ -321,9 +329,10 @@
     vec_foreach (path_index, path_list->fpl_paths)
     {
 	fib_path_destroy(*path_index);
-    }    
+    }
 
     vec_free(path_list->fpl_paths);
+    fib_urpf_list_unlock(path_list->fpl_urpf);
 
     fib_node_deinit(&path_list->fpl_node);
     pool_put(fib_path_list_pool, path_list);
@@ -396,6 +405,60 @@
     vec_free(hash_key);
 }
 
+/**
+ * @brief [re]build the path list's uRPF list
+ */
+static void
+fib_path_list_mk_urpf (fib_path_list_t *path_list)
+{
+    fib_node_index_t *path_index;
+
+    /*
+     * ditch the old one. by iterating through all paths we are going
+     * to re-find all the adjs that were in the old one anyway. If we
+     * keep the old one, then the |sort|uniq requires more work.
+     * All users of the RPF list have their own lock, so we can release
+     * immediately.
+     */
+    fib_urpf_list_unlock(path_list->fpl_urpf);
+    path_list->fpl_urpf = fib_urpf_list_alloc_and_lock();
+
+    vec_foreach (path_index, path_list->fpl_paths)
+    {
+	fib_path_contribute_urpf(*path_index, path_list->fpl_urpf);
+    }
+
+    fib_urpf_list_bake(path_list->fpl_urpf);
+}
+
+/**
+ * @brief Contribute (add) this path list's uRPF list. This allows the child
+ * to construct an aggregate list.
+ */
+void
+fib_path_list_contribute_urpf (fib_node_index_t path_list_index,
+			       index_t urpf)
+{
+    fib_path_list_t *path_list;
+
+    path_list = fib_path_list_get(path_list_index);
+
+    fib_urpf_list_combine(urpf, path_list->fpl_urpf);
+}
+
+/**
+ * @brief Return the the child the RPF list pre-built for this path list
+ */
+index_t
+fib_path_list_get_urpf (fib_node_index_t path_list_index)
+{
+    fib_path_list_t *path_list;
+
+    path_list = fib_path_list_get(path_list_index);
+
+    return (path_list->fpl_urpf);
+}
+
 /*
  * fib_path_list_back_walk
  *
@@ -410,6 +473,8 @@
 
     path_list = fib_path_list_get(path_list_index);
 
+    fib_path_list_mk_urpf(path_list);
+
     /*
      * propagate the backwalk further
      */
@@ -461,6 +526,7 @@
 			  pool_elts(fib_path_list_pool),
 			  pool_len(fib_path_list_pool),
 			  sizeof(fib_path_list_t));
+    fib_urpf_list_show_mem();
 }
 
 /*
@@ -483,6 +549,7 @@
 
     fib_node_init(&path_list->fpl_node,
 		  FIB_NODE_TYPE_PATH_LIST);
+    path_list->fpl_urpf = INDEX_INVALID;
 
     if (NULL != path_list_index)
     {
@@ -519,6 +586,7 @@
     path_list = fib_path_list_get(path_list_index);
 
     FIB_PATH_LIST_DBG(path_list, "resovled");
+    fib_path_list_mk_urpf(path_list);
 
     return (path_list);
 }
diff --git a/vnet/vnet/fib/fib_path_list.h b/vnet/vnet/fib/fib_path_list.h
index 42e07ab..852f07d 100644
--- a/vnet/vnet/fib/fib_path_list.h
+++ b/vnet/vnet/fib/fib_path_list.h
@@ -104,9 +104,12 @@
     fib_node_index_t pl_index,
     fib_path_list_flags_t flags,
     const fib_route_path_t *path);
-extern void fib_path_list_contribute_forwarding (fib_node_index_t path_list_index,
-						 fib_forward_chain_type_t type,
-						 dpo_id_t *dpo);
+extern void fib_path_list_contribute_forwarding(fib_node_index_t path_list_index,
+						fib_forward_chain_type_t type,
+						dpo_id_t *dpo);
+extern void fib_path_list_contribute_urpf(fib_node_index_t path_index,
+					  index_t urpf);
+extern index_t fib_path_list_get_urpf(fib_node_index_t path_list_index);
 extern index_t fib_path_list_get_adj(fib_node_index_t path_list_index,
 				     fib_forward_chain_type_t type);
 
diff --git a/vnet/vnet/fib/fib_test.c b/vnet/vnet/fib/fib_test.c
index 898005e..3dc9c1d 100644
--- a/vnet/vnet/fib/fib_test.c
+++ b/vnet/vnet/fib/fib_test.c
@@ -29,6 +29,7 @@
 #include <vnet/fib/fib_path_list.h>
 #include <vnet/fib/fib_walk.h>
 #include <vnet/fib/fib_node_list.h>
+#include <vnet/fib/fib_urpf_list.h>
 
 #define FIB_TEST_I(_cond, _comment, _args...)			\
 ({								\
@@ -139,14 +140,15 @@
     }
 }
 
-#define FIB_TEST_REC_FORW(_rec_prefix, _via_prefix)                     \
+#define FIB_TEST_REC_FORW(_rec_prefix, _via_prefix, _bucket)		\
 {                                                                       \
     const dpo_id_t *_rec_dpo = fib_entry_contribute_ip_forwarding(      \
         fib_table_lookup_exact_match(fib_index, (_rec_prefix)));        \
     const dpo_id_t *_via_dpo = fib_entry_contribute_ip_forwarding(      \
         fib_table_lookup(fib_index, (_via_prefix)));                    \
     FIB_TEST(!dpo_cmp(_via_dpo,                                         \
-                      load_balance_get_bucket(_rec_dpo->dpoi_index, 0)), \
+                      load_balance_get_bucket(_rec_dpo->dpoi_index,	\
+					      _bucket)),		\
              "%U is recursive via %U",                                  \
              format_fib_prefix, (_rec_prefix),                          \
              format_fib_prefix, _via_prefix);                           \
@@ -167,6 +169,57 @@
              format_dpo_id, _dpo1, 0);                                  \
 }
 
+#define FIB_TEST_RPF(_cond, _comment, _args...)			\
+{								\
+    if (!FIB_TEST_I(_cond, _comment, ##_args)) {		\
+	return (0);						\
+    }								\
+}
+
+static int
+fib_test_urpf_is_equal (fib_node_index_t fei,
+		       fib_forward_chain_type_t fct,
+		       u32 num, ...)
+{
+    dpo_id_t dpo = DPO_NULL;
+    fib_urpf_list_t *urpf;
+    index_t ui;
+    va_list ap;
+    int ii;
+
+    va_start(ap, num);
+
+    fib_entry_contribute_forwarding(fei, fct, &dpo);
+    ui = load_balance_get_urpf(dpo.dpoi_index);
+
+    urpf = fib_urpf_list_get(ui);
+
+    FIB_TEST_RPF(num == vec_len(urpf->furpf_itfs),
+		 "RPF:%U len %d == %d",
+		 format_fib_urpf_list, ui,
+		 num, vec_len(urpf->furpf_itfs));
+    FIB_TEST_RPF(num == fib_urpf_check_size(ui),
+		 "RPF:%U check-size %d == %d",
+		 format_fib_urpf_list, ui,
+		 num, vec_len(urpf->furpf_itfs));
+
+    for (ii = 0; ii < num; ii++)
+    {
+	adj_index_t ai = va_arg(ap, adj_index_t);
+
+	FIB_TEST_RPF(ai == urpf->furpf_itfs[ii],
+		     "RPF:%d item:%d - %d == %d",
+		     ui, ii, ai, urpf->furpf_itfs[ii]);
+	FIB_TEST_RPF(fib_urpf_check(ui, ai),
+		     "RPF:%d %d found",
+		     ui, ai);
+    }
+
+    dpo_reset(&dpo);
+
+    return (1);
+}
+
 static void
 fib_test_v4 (void)
 {
@@ -329,6 +382,8 @@
     FIB_TEST((FIB_NODE_INDEX_INVALID != fei), "local interface route present");
 
     dpo = fib_entry_contribute_ip_forwarding(fei);
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 0),
+	     "RPF list for local length 0");
     dpo = load_balance_get_bucket(dpo->dpoi_index, 0);
     FIB_TEST((DPO_RECEIVE == dpo->dpoi_type),
 	     "local interface adj is local");
@@ -466,6 +521,10 @@
     u8 eth_addr[] = {
 	0xde, 0xde, 0xde, 0xba, 0xba, 0xba,
     };
+    ip46_address_t nh_12_12_12_12 = {
+	.ip4.as_u32 = clib_host_to_net_u32(0x0c0c0c0c),
+    };
+    adj_index_t ai_12_12_12_12;
 
     /*
      * Add a route via an incomplete ADJ. then complete the ADJ
@@ -513,6 +572,24 @@
     dpo1 = load_balance_get_bucket(dpo->dpoi_index, 0);
     FIB_TEST(DPO_ADJACENCY == dpo1->dpoi_type,
              "11.11.11.11/32 via complete adj");
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 1,
+				    tm->hw[0]->sw_if_index),
+	     "RPF list for adj-fib contains adj");
+
+    ai_12_12_12_12 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4,
+					 FIB_LINK_IP4,
+					 &nh_12_12_12_12,
+					 tm->hw[1]->sw_if_index);
+    FIB_TEST((FIB_NODE_INDEX_INVALID != ai_12_12_12_12), "adj created");
+    adj = adj_get(ai_12_12_12_12);
+    FIB_TEST((IP_LOOKUP_NEXT_ARP == adj->lookup_next_index),
+	     "adj is incomplete");
+    FIB_TEST((0 == ip46_address_cmp(&nh_12_12_12_12,
+				    &adj->sub_type.nbr.next_hop)),
+	      "adj nbr next-hop ok");
+    adj_nbr_update_rewrite(ai_12_12_12_12, eth_addr);
+    FIB_TEST((IP_LOOKUP_NEXT_REWRITE == adj->lookup_next_index),
+	     "adj is complete");
 
     /*
      * add the adj fib
@@ -591,7 +668,7 @@
 	     fib_entry_pool_size());
 
     /*
-     * Add a 2 routes via the first ADJ. ensure path-list sharing
+     * Add 2 routes via the first ADJ. ensure path-list sharing
      */
     fib_prefix_t pfx_1_1_1_1_s_32 = {
 	.fp_len = 32,
@@ -675,6 +752,9 @@
 			     FIB_ROUTE_PATH_FLAG_NONE);
     fei = fib_table_lookup(fib_index, &pfx_1_1_2_0_s_24);
     dpo = fib_entry_contribute_ip_forwarding(fei);
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+				    1, tm->hw[0]->sw_if_index),
+	     "RPF list for 1.1.2.0/24 contains both adjs");
 
     dpo1 = load_balance_get_bucket(dpo->dpoi_index, 0);
     FIB_TEST(DPO_ADJACENCY == dpo1->dpoi_type, "type is %d", dpo1->dpoi_type);
@@ -709,6 +789,11 @@
 				1,
 				FIB_ROUTE_PATH_FLAG_NONE);
     fei = fib_table_lookup(fib_index, &pfx_1_1_2_0_s_24);
+    dpo = fib_entry_contribute_ip_forwarding(fei);
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
+				   1, tm->hw[0]->sw_if_index),
+	     "RPF list for 1.1.2.0/24 contains one adj");
+
     ai = fib_entry_get_adj(fei);
     FIB_TEST((ai_01 == ai), "1.1.2.0/24 resolves via 10.10.10.1");
 
@@ -740,19 +825,22 @@
 	.ip4.as_u32 = clib_host_to_net_u32(0x01010101),
     };
 
-    fib_table_entry_path_add(fib_index,
-			     &bgp_100_pfx,
-			     FIB_SOURCE_API,
-			     FIB_ENTRY_FLAG_NONE,
-			     FIB_PROTOCOL_IP4,
-			     &nh_1_1_1_1,
-			     ~0, // no index provided.
-			     fib_index, // nexthop in same fib as route
-			     1,
-			     MPLS_LABEL_INVALID,
-			     FIB_ROUTE_PATH_FLAG_NONE);
+    fei = fib_table_entry_path_add(fib_index,
+				   &bgp_100_pfx,
+				   FIB_SOURCE_API,
+				   FIB_ENTRY_FLAG_NONE,
+				   FIB_PROTOCOL_IP4,
+				   &nh_1_1_1_1,
+				   ~0, // no index provided.
+				   fib_index, // nexthop in same fib as route
+				   1,
+				   MPLS_LABEL_INVALID,
+				   FIB_ROUTE_PATH_FLAG_NONE);
 
-    FIB_TEST_REC_FORW(&bgp_100_pfx, &pfx_1_1_1_1_s_32);
+    FIB_TEST_REC_FORW(&bgp_100_pfx, &pfx_1_1_1_1_s_32, 0);
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 1,
+				    tm->hw[0]->sw_if_index),
+	     "RPF list for adj-fib contains adj");
 
     /*
      * +1 entry and +1 shared-path-list
@@ -785,7 +873,10 @@
 			     MPLS_LABEL_INVALID,
 			     FIB_ROUTE_PATH_FLAG_NONE);
 
-    FIB_TEST_REC_FORW(&bgp_101_pfx, &pfx_1_1_1_1_s_32);
+    FIB_TEST_REC_FORW(&bgp_101_pfx, &pfx_1_1_1_1_s_32, 0);
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 1,
+				    tm->hw[0]->sw_if_index),
+	     "RPF list for adj-fib contains adj");
 
     /*
      * +1 entry, but the recursive path-list is shared.
@@ -889,7 +980,7 @@
 			     MPLS_LABEL_INVALID,
 			     FIB_ROUTE_PATH_FLAG_NONE);
 
-    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32);
+    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32, 0);
 
     /*
      * the adj should be recursive via drop, since the route resolves via
@@ -898,6 +989,8 @@
     fei = fib_table_lookup(fib_index, &pfx_1_1_1_2_s_32);
     dpo1 = fib_entry_contribute_ip_forwarding(fei);
     FIB_TEST(load_balance_is_drop(dpo1), "1.1.1.2/32 is drop");
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 0),
+	     "RPF list for 1.1.1.2/32 contains 0 adjs");
 
     /*
      * +2 entry and +1 shared-path-list
@@ -911,6 +1004,8 @@
 
     /*
      * Unequal Cost load-balance. 3:1 ratio. fits in a 4 bucket LB
+     * The paths are sort by NH first. in this case the the path with greater
+     * weight is first in the set. This ordering is to test the RPF sort|uniq logic
      */
     fib_prefix_t pfx_1_2_3_4_s_32 = {
 	.fp_len = 32,
@@ -924,7 +1019,7 @@
 			     FIB_SOURCE_API,
 			     FIB_ENTRY_FLAG_NONE,
 			     FIB_PROTOCOL_IP4,
-                             &nh_10_10_10_2,
+                             &nh_10_10_10_1,
                              tm->hw[0]->sw_if_index,
                              ~0,
                              1,
@@ -935,8 +1030,8 @@
                                    FIB_SOURCE_API,
                                    FIB_ENTRY_FLAG_NONE,
 				   FIB_PROTOCOL_IP4,
-                                   &nh_10_10_10_1,
-                                   tm->hw[0]->sw_if_index,
+                                   &nh_12_12_12_12,
+                                   tm->hw[1]->sw_if_index,
                                    ~0,
                                    3,
                                    MPLS_LABEL_INVALID,
@@ -949,14 +1044,16 @@
              "1.2.3.4/32 LB has %d bucket",
              lb->lb_n_buckets);
 
-    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_4_s_32, 0, ai_01);
-    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_4_s_32, 1, ai_01);
-    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_4_s_32, 2, ai_01);
-    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_4_s_32, 3, ai_02);
+    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_4_s_32, 0, ai_12_12_12_12);
+    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_4_s_32, 1, ai_12_12_12_12);
+    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_4_s_32, 2, ai_12_12_12_12);
+    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_4_s_32, 3, ai_01);
 
-    fib_table_entry_delete(fib_index,
-                           &pfx_1_2_3_4_s_32,
-                           FIB_SOURCE_API);
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 2,
+				    tm->hw[0]->sw_if_index,
+				    tm->hw[1]->sw_if_index),
+	     "RPF list for 1.2.3.4/32 contains both adjs");
+
 
     /*
      * Unequal Cost load-balance. 4:1 ratio.
@@ -974,8 +1071,8 @@
 			     FIB_SOURCE_API,
 			     FIB_ENTRY_FLAG_NONE,
 			     FIB_PROTOCOL_IP4,
-                             &nh_10_10_10_2,
-                             tm->hw[0]->sw_if_index,
+                             &nh_12_12_12_12,
+                             tm->hw[1]->sw_if_index,
                              ~0,
                              1,
                              MPLS_LABEL_INVALID,
@@ -1012,13 +1109,85 @@
     FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_5_s_32, 10, ai_01);
     FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_5_s_32, 11, ai_01);
     FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_5_s_32, 12, ai_01);
-    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_5_s_32, 13, ai_02);
-    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_5_s_32, 14, ai_02);
-    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_5_s_32, 15, ai_02);
+    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_5_s_32, 13, ai_12_12_12_12);
+    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_5_s_32, 14, ai_12_12_12_12);
+    FIB_TEST_LB_BUCKET_VIA_ADJ(&pfx_1_2_3_5_s_32, 15, ai_12_12_12_12);
+
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 2,
+				    tm->hw[0]->sw_if_index,
+				    tm->hw[1]->sw_if_index),
+	     "RPF list for 1.2.3.4/32 contains both adjs");
+
+    /*
+     * A recursive via the two unequal cost entries
+     */
+    fib_prefix_t bgp_44_s_32 = {
+	.fp_len = 32,
+	.fp_proto = FIB_PROTOCOL_IP4,
+	.fp_addr = {
+	    /* 200.200.200.201/32 */
+	    .ip4.as_u32 = clib_host_to_net_u32(0x44444444),
+	},
+    };
+    fei = fib_table_entry_path_add(fib_index,
+                                   &bgp_44_s_32,
+                                   FIB_SOURCE_API,
+                                   FIB_ENTRY_FLAG_NONE,
+				   FIB_PROTOCOL_IP4,
+				   &pfx_1_2_3_4_s_32.fp_addr,
+                                   ~0,
+                                   fib_index,
+                                   1,
+                                   MPLS_LABEL_INVALID,
+                                   FIB_ROUTE_PATH_FLAG_NONE);
+    fei = fib_table_entry_path_add(fib_index,
+                                   &bgp_44_s_32,
+                                   FIB_SOURCE_API,
+                                   FIB_ENTRY_FLAG_NONE,
+				   FIB_PROTOCOL_IP4,
+				   &pfx_1_2_3_5_s_32.fp_addr,
+                                   ~0,
+                                   fib_index,
+                                   1,
+                                   MPLS_LABEL_INVALID,
+                                   FIB_ROUTE_PATH_FLAG_NONE);
+
+    FIB_TEST_REC_FORW(&bgp_44_s_32, &pfx_1_2_3_4_s_32, 0);
+    FIB_TEST_REC_FORW(&bgp_44_s_32, &pfx_1_2_3_5_s_32, 1);
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 2,
+				    tm->hw[0]->sw_if_index,
+				    tm->hw[1]->sw_if_index),
+	     "RPF list for 1.2.3.4/32 contains both adjs");
+
+    /*
+     * test the uRPF check functions
+     */
+    dpo_id_t dpo_44 = DPO_NULL;
+    index_t urpfi;
+
+    fib_entry_contribute_forwarding(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, &dpo_44);
+    urpfi = load_balance_get_urpf(dpo_44.dpoi_index);
+
+    FIB_TEST(fib_urpf_check(urpfi, tm->hw[0]->sw_if_index),
+	     "uRPF check for 68.68.68.68/32 on %d OK",
+	     tm->hw[0]->sw_if_index);
+    FIB_TEST(fib_urpf_check(urpfi, tm->hw[1]->sw_if_index),
+	     "uRPF check for 68.68.68.68/32 on %d OK",
+	     tm->hw[1]->sw_if_index);
+    FIB_TEST(!fib_urpf_check(urpfi, 99),
+	     "uRPF check for 68.68.68.68/32 on 99 not-OK",
+	     99);
+    dpo_reset(&dpo_44);
 
     fib_table_entry_delete(fib_index,
+                           &bgp_44_s_32,
+                           FIB_SOURCE_API);
+    fib_table_entry_delete(fib_index,
                            &pfx_1_2_3_5_s_32,
                            FIB_SOURCE_API);
+    fib_table_entry_delete(fib_index,
+                           &pfx_1_2_3_4_s_32,
+                           FIB_SOURCE_API);
 
     /*
      * Add a recursive route:
@@ -1053,11 +1222,13 @@
 			     MPLS_LABEL_INVALID,
 			     FIB_ROUTE_PATH_FLAG_NONE);
 
-    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32);
+    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32, 0);
 
     fei = fib_table_lookup_exact_match(fib_index, &pfx_1_1_1_200_s_32);
     FIB_TEST((FIB_ENTRY_FLAG_NONE == fib_entry_get_flags(fei)),
 	     "Flags set on RR via non-attached");
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 0),
+	     "RPF list for BGP route empty");
 
     /*
      * +2 entry (BGP & RR) and +1 shared-path-list
@@ -1119,8 +1290,12 @@
     /*
      * the recursive adj for 200.200.200.200 should be updated.
      */
-    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32);
-    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32);
+    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32, 0);
+    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32, 0);
+    fei = fib_table_lookup(fib_index, &bgp_200_pfx);
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 1,
+				    tm->hw[0]->sw_if_index),
+	     "RPF list for BGP route has itf index 0");
 
     /*
      * insert a more specific route than 1.1.1.0/24 that also covers the
@@ -1166,8 +1341,8 @@
      * the recursive adj for 200.200.200.200 should be updated.
      * 200.200.200.201 remains unchanged.
      */
-    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32);
-    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32);
+    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32, 0);
+    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32, 0);
 
     /*
      * remove this /28. 200.200.200.200/32 should revert back to via 1.1.1.0/24
@@ -1187,8 +1362,8 @@
     FIB_TEST((fib_table_lookup(fib_index, &pfx_1_1_1_0_s_28) == 
 	      fib_table_lookup(fib_index, &pfx_1_1_1_0_s_24)),
 	     "1.1.1.0/28 lookup via /24");
-    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32);
-    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32);
+    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32, 0);
+    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32, 0);
 
     /*
      * -1 entry. -1 shared path-list
@@ -1223,8 +1398,8 @@
     FIB_TEST(load_balance_is_drop(fib_entry_contribute_ip_forwarding(fei)),
 	     "1.1.1.200/32 route is DROP");
 
-    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32);
-    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32);
+    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32, 0);
+    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32, 0);
 
     /*
      * -1 entry
@@ -1254,8 +1429,8 @@
     ai = fib_entry_get_adj(fei);
     FIB_TEST((ai = ai_01), "1.1.1.2/32 resolves via 10.10.10.1");
 
-    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32);
-    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32);
+    FIB_TEST_REC_FORW(&bgp_201_pfx, &pfx_1_1_1_200_s_32, 0);
+    FIB_TEST_REC_FORW(&bgp_200_pfx, &pfx_1_1_1_2_s_32, 0);
 
     /*
      * no change. 1.1.1.2/32 was already there RR sourced.
@@ -2530,6 +2705,28 @@
 	     "2001::/64 ADJ-adj is NH proto v4");
     fib_table_entry_delete(0, &pfx_2001_s_64, FIB_SOURCE_API);
 
+    /*
+     * add a uRPF exempt prefix:
+     *  test:
+     *   - it's forwarding is drop
+     *   - it's uRPF list is not empty
+     *   - the uRPF list for the default route (it's cover) is empty
+     */
+    fei = fib_table_entry_special_add(fib_index,
+				      &pfx_4_1_1_1_s_32,
+				      FIB_SOURCE_URPF_EXEMPT,
+				      FIB_ENTRY_FLAG_DROP,
+				      ADJ_INDEX_INVALID);
+    dpo = fib_entry_contribute_ip_forwarding(fei);
+    FIB_TEST(load_balance_is_drop(dpo),
+	     "uRPF exempt 4.1.1.1/32 DROP");
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 1, 0),
+	     "uRPF list for exempt prefix has itf index 0");
+    fei = fib_table_lookup_exact_match(fib_index, &pfx_0_0_0_0_s_0);
+    FIB_TEST(fib_test_urpf_is_equal(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, 0),
+	     "uRPF list for 0.0.0.0/0 empty");
+
+    fib_table_entry_delete(fib_index, &pfx_4_1_1_1_s_32, FIB_SOURCE_URPF_EXEMPT);
 
     /*
      * CLEANUP
@@ -2559,11 +2756,12 @@
     	     fib_entry_pool_size());
 
     /*
-     * unlock the 2 adjacencies for which this test provided a rewrite.
+     * unlock the adjacencies for which this test provided a rewrite.
      * These are the last locks on these adjs. they should thus go away.
      */
     adj_unlock(ai_02);
     adj_unlock(ai_01);
+    adj_unlock(ai_12_12_12_12);
 
     FIB_TEST((0 == adj_nbr_db_size()), "ADJ DB size is %d",
 	     adj_nbr_db_size());
@@ -2623,6 +2821,8 @@
     	     fib_path_list_pool_size());
     FIB_TEST((NBR-5 == fib_entry_pool_size()), "entry pool size is %d",
     	     fib_entry_pool_size());
+    FIB_TEST((NBR-5 == pool_elts(fib_urpf_list_pool)), "uRPF pool size is %d",
+    	     pool_elts(fib_urpf_list_pool));
 
     return;
 }
@@ -5014,6 +5214,28 @@
 	     "2.2.2.2.2/32 LB 1 buckets via: "
 	     "label 1600 over 1.1.1.1");
 
+    dpo_id_t dpo_44 = DPO_NULL;
+    index_t urpfi;
+
+    fib_entry_contribute_forwarding(fei, FIB_FORW_CHAIN_TYPE_UNICAST_IP4, &dpo_44);
+    urpfi = load_balance_get_urpf(dpo_44.dpoi_index);
+
+    FIB_TEST(fib_urpf_check(urpfi, tm->hw[0]->sw_if_index),
+	     "uRPF check for 2.2.2.2/32 on %d OK",
+	     tm->hw[0]->sw_if_index);
+    FIB_TEST(fib_urpf_check(urpfi, tm->hw[1]->sw_if_index),
+	     "uRPF check for 2.2.2.2/32 on %d OK",
+	     tm->hw[1]->sw_if_index);
+    FIB_TEST(!fib_urpf_check(urpfi, 99),
+	     "uRPF check for 2.2.2.2/32 on 99 not-OK",
+	     99);
+
+    fib_entry_contribute_forwarding(fei, FIB_FORW_CHAIN_TYPE_MPLS_NON_EOS, &dpo_44);
+    FIB_TEST(urpfi == load_balance_get_urpf(dpo_44.dpoi_index),
+	     "Shared uRPF on IP and non-EOS chain");
+
+    dpo_reset(&dpo_44);
+
     /*
      * we are holding a lock on the non-eos LB of the via-entry.
      * do a PIC-core failover by shutting the link of the via-entry.
diff --git a/vnet/vnet/fib/fib_types.h b/vnet/vnet/fib/fib_types.h
index 232a230..250aad7 100644
--- a/vnet/vnet/fib/fib_types.h
+++ b/vnet/vnet/fib/fib_types.h
@@ -118,10 +118,6 @@
  */
 typedef enum fib_forward_chain_type_t_ {
     /**
-     * Contribute an object that is to be used to forward Ethernet packets
-     */
-    FIB_FORW_CHAIN_TYPE_ETHERNET,
-    /**
      * Contribute an object that is to be used to forward IP4 packets
      */
     FIB_FORW_CHAIN_TYPE_UNICAST_IP4,
@@ -141,17 +137,24 @@
      * option is converted into one of the other three internally.
      */
     FIB_FORW_CHAIN_TYPE_MPLS_EOS,
+    /**
+     * Contribute an object that is to be used to forward Ethernet packets.
+     * This is last in the list since it is not valid for many FIB objects,
+     * and thus their array of per-chain-type DPOs can be sized smaller.
+     */
+    FIB_FORW_CHAIN_TYPE_ETHERNET,
 }  __attribute__ ((packed)) fib_forward_chain_type_t;
 
 #define FIB_FORW_CHAINS {					\
-    [FIB_FORW_CHAIN_TYPE_ETHERNET]      = "ehternet",     	\
+    [FIB_FORW_CHAIN_TYPE_ETHERNET]      = "ethernet",     	\
     [FIB_FORW_CHAIN_TYPE_UNICAST_IP4]   = "unicast-ip4",	\
     [FIB_FORW_CHAIN_TYPE_UNICAST_IP6]   = "unicast-ip6",	\
     [FIB_FORW_CHAIN_TYPE_MPLS_NON_EOS]  = "mpls-neos",	        \
     [FIB_FORW_CHAIN_TYPE_MPLS_EOS]      = "mpls-eos",	        \
 }
 
-#define FIB_FORW_CHAIN_NUM (FIB_FORW_CHAIN_TYPE_MPLS_EOS+1)
+#define FIB_FORW_CHAIN_NUM (FIB_FORW_CHAIN_TYPE_MPLS_ETHERNET+1)
+#define FIB_FORW_CHAIN_MPLS_NUM (FIB_FORW_CHAIN_TYPE_MPLS_EOS+1)
 
 #define FOR_EACH_FIB_FORW_CHAIN(_item)			  \
     for (_item = FIB_FORW_CHAIN_TYPE_ETHERNET;   	  \
diff --git a/vnet/vnet/fib/fib_urpf_list.c b/vnet/vnet/fib/fib_urpf_list.c
new file mode 100644
index 0000000..263812a
--- /dev/null
+++ b/vnet/vnet/fib/fib_urpf_list.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2016 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/fib/fib_urpf_list.h>
+#include <vnet/adj/adj.h>
+
+/**
+ * @brief pool of all fib_urpf_list
+ */
+fib_urpf_list_t *fib_urpf_list_pool;
+
+u8 *
+format_fib_urpf_list (u8 *s, va_list args)
+{
+    fib_urpf_list_t *urpf;
+    index_t ui;
+    u32 *swi;
+
+    ui = va_arg(args, index_t);
+    urpf = fib_urpf_list_get(ui);
+
+    s = format(s, "uPRF-list:%d len:%d itfs:[",
+	       ui, vec_len(urpf->furpf_itfs));
+
+    vec_foreach(swi, urpf->furpf_itfs)
+    {
+	s = format(s, "%d, ", *swi);
+    }
+    s = format(s, "]");
+
+    return (s);
+}
+
+index_t
+fib_urpf_list_alloc_and_lock (void)
+{
+    fib_urpf_list_t *urpf;
+
+    pool_get(fib_urpf_list_pool, urpf);
+    memset(urpf, 0, sizeof(*urpf));
+
+    urpf->furpf_locks++;
+
+    return (urpf - fib_urpf_list_pool);
+}
+
+void
+fib_urpf_list_unlock (index_t ui)
+{
+    fib_urpf_list_t *urpf;
+
+    if (INDEX_INVALID == ui)
+	return;
+
+    urpf = fib_urpf_list_get(ui);
+
+    urpf->furpf_locks--;
+
+    if (0 == urpf->furpf_locks)
+    {
+	vec_free(urpf->furpf_itfs);
+	pool_put(fib_urpf_list_pool, urpf);
+    }
+}
+
+void
+fib_urpf_list_lock (index_t ui)
+{
+    fib_urpf_list_t *urpf;
+
+    urpf = fib_urpf_list_get(ui);
+
+    urpf->furpf_locks++;
+}
+
+/**
+ * @brief Append another interface to the list.
+ */
+void
+fib_urpf_list_append (index_t ui,
+		      u32 sw_if_index)
+{
+    fib_urpf_list_t *urpf;
+
+    urpf = fib_urpf_list_get(ui);
+
+    vec_add1(urpf->furpf_itfs, sw_if_index);
+}
+
+/**
+ * @brief Combine to interface lists
+ */
+void
+fib_urpf_list_combine (index_t ui1,
+		       index_t ui2)
+{
+    fib_urpf_list_t *urpf1, *urpf2;
+
+    urpf1 = fib_urpf_list_get(ui1);
+    urpf2 = fib_urpf_list_get(ui2);
+
+    vec_append(urpf1->furpf_itfs, urpf2->furpf_itfs);
+}
+
+/**
+ * @brief Sort the interface indicies.
+ * The sort is the first step in obtaining a unique list, so the order,
+ * w.r.t. next-hop, interface,etc is not important. So a sort based on the
+ * index is all we need.
+ */
+static int
+fib_urpf_itf_cmp_for_sort (void * v1,
+			   void * v2)
+{
+    fib_node_index_t *i1 = v1, *i2 = v2;
+
+    return (*i2 < *i1);
+}
+
+/**
+ * @brief Convert the uRPF list from the itf set obtained during the walk
+ * to a unique list.
+ */
+void
+fib_urpf_list_bake (index_t ui)
+{
+    fib_urpf_list_t *urpf;
+
+    urpf = fib_urpf_list_get(ui);
+
+    ASSERT(!(urpf->furpf_flags & FIB_URPF_LIST_BAKED));
+
+    if (vec_len(urpf->furpf_itfs) > 1)
+    {
+	u32 i,j;
+
+	/*
+	 * cat list | sort | uniq > rpf_list
+	 */
+	vec_sort_with_function(urpf->furpf_itfs, fib_urpf_itf_cmp_for_sort);
+
+	i = 0, j = 1;
+	while (j < vec_len(urpf->furpf_itfs))
+	{
+	    if (urpf->furpf_itfs[i] == urpf->furpf_itfs[j])
+	    {
+		/*
+		 * the itfacenct entries are the same.
+		 * search forward for a unique one
+		 */
+		while (urpf->furpf_itfs[i] == urpf->furpf_itfs[j] &&
+		       j < vec_len(urpf->furpf_itfs))
+		{
+		    j++;
+		}
+		if (j == vec_len(urpf->furpf_itfs))
+		{
+		    /*
+		     * ran off the end without finding a unique index.
+		     * we are done.
+		     */
+		    break;
+		}
+		else
+		{
+		    urpf->furpf_itfs[i+1] = urpf->furpf_itfs[j];
+		}
+	    }
+	    i++, j++;
+	}
+
+	/*
+	 * set the length of the vector to the number of unique itfs
+	 */
+	_vec_len(urpf->furpf_itfs) = i+1;
+    }
+
+    urpf->furpf_flags |= FIB_URPF_LIST_BAKED;
+}
+
+void
+fib_urpf_list_show_mem (void)
+{
+    fib_show_memory_usage("uRPF-list",
+			  pool_elts(fib_urpf_list_pool),
+			  pool_len(fib_urpf_list_pool),
+			  sizeof(fib_urpf_list_t));
+}
+
+static clib_error_t *
+show_fib_urpf_list_command (vlib_main_t * vm,
+			    unformat_input_t * input,
+			    vlib_cli_command_t * cmd)
+{
+    index_t ui;
+
+    if (unformat (input, "%d", &ui))
+    {
+	/*
+	 * show one in detail
+	 */
+	if (!pool_is_free_index(fib_urpf_list_pool, ui))
+	{
+	    vlib_cli_output (vm, "%d@%U",
+			     ui,
+			     format_fib_urpf_list, ui);
+	}
+	else
+	{
+	    vlib_cli_output (vm, "uRPF %d invalid", ui);
+	}
+    }
+    else
+    {
+	/*
+	 * show all
+	 */
+	vlib_cli_output (vm, "FIB uRPF Entries:");
+	pool_foreach_index(ui, fib_urpf_list_pool,
+        ({
+	    vlib_cli_output (vm, "%d@%U",
+			     ui,
+			     format_fib_urpf_list, ui);
+	}));
+    }
+
+    return (NULL);
+}
+
+/* *INDENT-OFF* */
+/*?
+ * The '<em>sh fib uRPF [index] </em>' command displays the uRPF lists
+ *
+ * @cliexpar
+ * @cliexstart{show fib uRPF}
+ * FIB uRPF Entries:
+ *  0@uPRF-list:0 len:0 itfs:[]
+ *  1@uPRF-list:1 len:2 itfs:[1, 2, ]
+ *  2@uPRF-list:2 len:1 itfs:[3, ]
+ *  3@uPRF-list:3 len:1 itfs:[9, ]
+ * @cliexend
+?*/
+VLIB_CLI_COMMAND (show_fib_urpf_list, static) = {
+  .path = "show fib uRPF",
+  .function = show_fib_urpf_list_command,
+  .short_help = "show fib uRPF",
+};
+/* *INDENT-OFF* */
diff --git a/vnet/vnet/fib/fib_urpf_list.h b/vnet/vnet/fib/fib_urpf_list.h
new file mode 100644
index 0000000..09f4757
--- /dev/null
+++ b/vnet/vnet/fib/fib_urpf_list.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2016 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.
+ */
+/**
+ * @brief A unicast RPF list.
+ * The uRPF list is the set of interfaces that a prefix can be reached through.
+ * There are 3 levels of RPF check:
+ *   - do we have any route to the source (i.e. it's not drop)
+ *   - did the packet arrive on an interface that the source is reachable through
+ *   - did the packet arrive from a peer that the source is reachable through
+ * we don't support the last. But it could be done by storing adjs in the uPRF
+ * list rather than interface indices.
+ *
+ * these conditions are checked against the list by:
+ *   - the list is not empty
+ *   - there is an interface in the list that is on the input interface.
+ *   - there is an adj in the list whose MAC address matches the packet's
+ *     source MAC and input interface.
+ *
+ * To speed the last two checks the interface list only needs to have the unique
+ * interfaces present. If the uRPF check was instead implemented by forward
+ * walking the DPO chain, then that walk would encounter a great deal of
+ * non-adjacency objects (i.e. load-balances, mpls-labels, etc) and potentially
+ * the same adjacency many times (esp. when UCMP is used).
+ * To that end the uRPF list is a collapsed, unique interface only list.
+ */
+
+#ifndef __FIB_URPF_LIST_H__
+#define __FIB_URPF_LIST_H__
+
+#include <vnet/fib/fib_types.h>
+#include <vnet/adj/adj.h>
+
+/**
+ * @brief flags
+ */
+typedef enum fib_urpf_list_flag_t_
+{
+    /**
+     * @brief Set to indicated that the uRPF list has already been baked.
+     * This is protection against it being baked more than once. These
+     * are not chunky fries - once is enough.
+     */
+    FIB_URPF_LIST_BAKED = (1 << 0),
+} fib_urpf_list_flag_t;
+
+typedef struct fib_urpf_list_t_
+{
+    /**
+     * The list of interfaces that comprise the allowed accepting interfaces
+     */
+    adj_index_t *furpf_itfs;
+
+    /**
+     * flags
+     */
+    fib_urpf_list_flag_t furpf_flags;
+
+    /**
+     * uRPF lists are shared amongst many entries so we require a locking
+     * mechanism.
+     */
+    u32 furpf_locks;
+} fib_urpf_list_t;
+
+extern index_t fib_urpf_list_alloc_and_lock(void);
+extern void fib_urpf_list_unlock(index_t urpf);
+extern void fib_urpf_list_lock(index_t urpf);
+
+extern void fib_urpf_list_append(index_t urpf, adj_index_t adj);
+extern void fib_urpf_list_combine(index_t urpf1, index_t urpf2);
+
+extern void fib_urpf_list_bake(index_t urpf);
+
+extern u8 *format_fib_urpf_list(u8 *s, va_list ap);
+
+extern void fib_urpf_list_show_mem(void);
+
+/**
+ * @brief pool of all fib_urpf_list
+ */
+extern fib_urpf_list_t *fib_urpf_list_pool;
+
+static inline fib_urpf_list_t *
+fib_urpf_list_get (index_t index)
+{
+    return (pool_elt_at_index(fib_urpf_list_pool, index));
+}
+
+/**
+ * @brief Data-Plane function to check an input interface against an uRPF list
+ *
+ * @param ui The uRPF list index to check against. Get this from the load-balance
+ *             object that is the result of the FIB lookup
+ * @param sw_if_index The SW interface index to validate
+ *
+ * @return 1 if the interface is found, 0 otherwise
+ */
+always_inline int
+fib_urpf_check (index_t ui, u32 sw_if_index)
+{
+    fib_urpf_list_t *urpf;
+    u32 *swi;
+
+    urpf = fib_urpf_list_get(ui);
+
+    vec_foreach(swi, urpf->furpf_itfs)
+    {
+	if (*swi == sw_if_index)
+	    return (1);
+    }
+
+    return (0);
+}
+
+/**
+ * @brief Data-Plane function to check the size of an uRPF list, (i.e. the number
+ *        of interfaces in the list).
+ *
+ * @param ui The uRPF list index to check against. Get this from the load-balance
+ *             object that is the result of the FIB lookup
+ *
+ * @return the number of interfaces in the list
+ */
+always_inline int
+fib_urpf_check_size (index_t ui)
+{
+    fib_urpf_list_t *urpf;
+
+    urpf = fib_urpf_list_get(ui);
+
+    return (vec_len(urpf->furpf_itfs));
+}
+
+#endif
diff --git a/vnet/vnet/gre/gre.c b/vnet/vnet/gre/gre.c
index 0028118..aa6fca0 100644
--- a/vnet/vnet/gre/gre.c
+++ b/vnet/vnet/gre/gre.c
@@ -365,6 +365,11 @@
   u32 ti;
 
   hi = vnet_get_hw_interface (vnm, hw_if_index);
+
+  if (NULL == gm->tunnel_index_by_sw_if_index ||
+      hi->sw_if_index >= vec_len(gm->tunnel_index_by_sw_if_index))
+      return (NULL);
+
   ti = gm->tunnel_index_by_sw_if_index[hi->sw_if_index];
 
   if (~0 == ti)
diff --git a/vnet/vnet/ip/ip4_forward.c b/vnet/vnet/ip/ip4_forward.c
index b8d326f..72c9fb0 100644
--- a/vnet/vnet/ip/ip4_forward.c
+++ b/vnet/vnet/ip/ip4_forward.c
@@ -46,6 +46,7 @@
 #include <vnet/api_errno.h>     /* for API error numbers */
 #include <vnet/fib/fib_table.h> /* for FIB table and entry creation */
 #include <vnet/fib/fib_entry.h> /* for FIB table and entry creation */
+#include <vnet/fib/fib_urpf_list.h> /* for FIB uRPF check */
 #include <vnet/fib/ip4_fib.h>
 #include <vnet/dpo/load_balance.h>
 #include <vnet/dpo/classify_dpo.h>
@@ -1501,31 +1502,31 @@
 	  /* 
            * Must have a route to source otherwise we drop the packet.
            * ip4 broadcasts are accepted, e.g. to make dhcp client work
+	   *
+	   * The checks are:
+	   *  - the source is a recieve => it's from us => bogus, do this
+	   *    first since it sets a different error code.
+	   *  - uRPF check for any route to source - accept if passes.
+	   *  - allow packets destined to the broadcast address from unknown sources
            */
-	  error0 = (error0 == IP4_ERROR_UNKNOWN_PROTOCOL
-		    && dpo0->dpoi_type != DPO_ADJACENCY
-		    && dpo0->dpoi_type != DPO_ADJACENCY_INCOMPLETE
-		    && dpo0->dpoi_type != DPO_RECEIVE
-		    && dpo0->dpoi_type != DPO_DROP
-		    && dpo0->dpoi_type != DPO_ADJACENCY_GLEAN
-		    && ip0->dst_address.as_u32 != 0xFFFFFFFF
-		    ? IP4_ERROR_SRC_LOOKUP_MISS
-		    : error0);
-          error0 = (dpo0->dpoi_type == DPO_RECEIVE ?
+          error0 = ((error0 == IP4_ERROR_UNKNOWN_PROTOCOL &&
+		     dpo0->dpoi_type == DPO_RECEIVE) ?
                     IP4_ERROR_SPOOFED_LOCAL_PACKETS : 
                     error0);
-	  error1 = (error1 == IP4_ERROR_UNKNOWN_PROTOCOL
-		    && dpo1->dpoi_type != DPO_ADJACENCY
-		    && dpo1->dpoi_type != DPO_ADJACENCY_INCOMPLETE
-		    && dpo1->dpoi_type != DPO_RECEIVE
-		    && dpo1->dpoi_type != DPO_DROP
-		    && dpo1->dpoi_type != DPO_ADJACENCY_GLEAN
-		    && ip1->dst_address.as_u32 != 0xFFFFFFFF
+	  error0 = ((error0 == IP4_ERROR_UNKNOWN_PROTOCOL &&
+		     !fib_urpf_check_size(lb0->lb_urpf) &&
+		     ip0->dst_address.as_u32 != 0xFFFFFFFF)
 		    ? IP4_ERROR_SRC_LOOKUP_MISS
-		    : error1);
-          error1 = (dpo0->dpoi_type == DPO_RECEIVE ?
+		    : error0);
+          error1 = ((error1 == IP4_ERROR_UNKNOWN_PROTOCOL &&
+		     dpo1->dpoi_type == DPO_RECEIVE) ?
                     IP4_ERROR_SPOOFED_LOCAL_PACKETS : 
                     error1);
+	  error1 = ((error1 == IP4_ERROR_UNKNOWN_PROTOCOL &&
+		     !fib_urpf_check_size(lb1->lb_urpf) &&
+		     ip1->dst_address.as_u32 != 0xFFFFFFFF)
+		    ? IP4_ERROR_SRC_LOOKUP_MISS
+		    : error1);
 
 	  next0 = lm->local_next_by_ip_protocol[proto0];
 	  next1 = lm->local_next_by_ip_protocol[proto1];
@@ -1670,20 +1671,15 @@
 	      vnet_buffer (p0)->ip.adj_index[VLIB_RX] =
 	          dpo0->dpoi_index;
 
-	  /* Must have a route to source otherwise we drop the packet. */
-	  error0 = (error0 == IP4_ERROR_UNKNOWN_PROTOCOL
-		    && dpo0->dpoi_type != DPO_ADJACENCY
-		    && dpo0->dpoi_type != DPO_ADJACENCY_INCOMPLETE
-		    && dpo0->dpoi_type != DPO_RECEIVE
-		    && dpo0->dpoi_type != DPO_DROP
-		    && dpo0->dpoi_type != DPO_ADJACENCY_GLEAN
-		    && ip0->dst_address.as_u32 != 0xFFFFFFFF
-		    ? IP4_ERROR_SRC_LOOKUP_MISS
-		    : error0);
-          /* Packet originated from a local address => spoofing */
-          error0 = (dpo0->dpoi_type == DPO_RECEIVE ?
+          error0 = ((error0 == IP4_ERROR_UNKNOWN_PROTOCOL &&
+		     dpo0->dpoi_type == DPO_RECEIVE) ?
                     IP4_ERROR_SPOOFED_LOCAL_PACKETS : 
                     error0);
+	  error0 = ((error0 == IP4_ERROR_UNKNOWN_PROTOCOL &&
+		     !fib_urpf_check_size(lb0->lb_urpf) &&
+		     ip0->dst_address.as_u32 != 0xFFFFFFFF)
+		    ? IP4_ERROR_SRC_LOOKUP_MISS
+		    : error0);
 
 	  next0 = lm->local_next_by_ip_protocol[proto0];
 
diff --git a/vnet/vnet/ip/ip4_source_check.c b/vnet/vnet/ip/ip4_source_check.c
index 2323ac2..97d4703 100644
--- a/vnet/vnet/ip/ip4_source_check.c
+++ b/vnet/vnet/ip/ip4_source_check.c
@@ -39,10 +39,12 @@
 
 #include <vnet/ip/ip.h>
 #include <vnet/fib/ip4_fib.h>
+#include <vnet/fib/fib_urpf_list.h>
 #include <vnet/dpo/load_balance.h>
 
 typedef struct {
   u8 packet_data[64];
+    index_t urpf;
 } ip4_source_check_trace_t;
 
 static u8 * format_ip4_source_check_trace (u8 * s, va_list * va)
@@ -69,11 +71,7 @@
 } ip4_source_check_type_t;
 
 typedef union {
-  struct {
-    u32 no_default_route : 1;
-    u32 fib_index : 31;
-  };
-  u32 as_u32[1];
+  u32 fib_index;
 } ip4_source_check_config_t;
 
 always_inline uword
@@ -115,9 +113,6 @@
 	  const load_balance_t * lb0, * lb1;
 	  u32 pi0, next0, pass0, lb_index0;
 	  u32 pi1, next1, pass1, lb_index1;
-          const ip_adjacency_t *adj0, *adj1;
-          const dpo_id_t *dpo0, *dpo1;
-          u32 ii0, ii1;
 
 	  /* Prefetch next iteration. */
 	  {
@@ -182,61 +177,18 @@
 	  pass0 = ip4_address_is_multicast (&ip0->src_address) || ip0->src_address.as_u32 == clib_host_to_net_u32(0xFFFFFFFF);
 	  pass1 = ip4_address_is_multicast (&ip1->src_address) || ip1->src_address.as_u32 == clib_host_to_net_u32(0xFFFFFFFF);
 
-          if (PREDICT_TRUE(1 == lb0->lb_n_buckets))
-          {
-              dpo0 = load_balance_get_bucket_i(lb0, 0);
-              if (PREDICT_TRUE(dpo0->dpoi_type == DPO_ADJACENCY))
-              {
-                  pass0 |= (source_check_type ==
-                            IP4_SOURCE_CHECK_REACHABLE_VIA_ANY);
-                  adj0 = adj_get(dpo0->dpoi_index);
-                  pass0 |= (vnet_buffer (p0)->sw_if_index[VLIB_RX] ==
-                            adj0->rewrite_header.sw_if_index);
-              }
-          }
-          else
-          {
-              for (ii0 = 0; ii0 < lb0->lb_n_buckets && !pass0; ii0++)
-              {
-                  dpo0 = load_balance_get_bucket_i(lb0, ii0);
-                  if (PREDICT_TRUE(dpo0->dpoi_type == DPO_ADJACENCY))
-                  {
-                      pass0 |= (source_check_type ==
-                                IP4_SOURCE_CHECK_REACHABLE_VIA_ANY);
-                      adj0 = adj_get(dpo0->dpoi_index);
-                      pass0 |= (vnet_buffer (p0)->sw_if_index[VLIB_RX] ==
-                                adj0->rewrite_header.sw_if_index);
-                  }
-              }
-          }
-          if (PREDICT_TRUE(1 == lb1->lb_n_buckets))
-          {
-              dpo1 = load_balance_get_bucket_i(lb1, 0);
-              if (PREDICT_TRUE(dpo1->dpoi_type == DPO_ADJACENCY))
-              {
-                  pass1 |= (source_check_type ==
-                            IP4_SOURCE_CHECK_REACHABLE_VIA_ANY);
-                  adj1 = adj_get(dpo1->dpoi_index);
-                  pass1 |= (vnet_buffer (p1)->sw_if_index[VLIB_RX] ==
-                            adj1->rewrite_header.sw_if_index);
-              }
-          }
-          else
-          {
-              for (ii1 = 0; ii1 < lb1->lb_n_buckets && !pass1; ii1++)
-              {
-                  dpo1 = load_balance_get_bucket_i(lb1, ii1);
-                 if (PREDICT_TRUE(dpo1->dpoi_type == DPO_ADJACENCY))
-                  {
-                      pass1 |= (source_check_type ==
-                                IP4_SOURCE_CHECK_REACHABLE_VIA_ANY);
-                      adj1 = adj_get(dpo1->dpoi_index);
-                      pass1 |= (vnet_buffer (p1)->sw_if_index[VLIB_RX] ==
-                                adj1->rewrite_header.sw_if_index);
-                  }
-              }
-          }
-
+	  if (IP4_SOURCE_CHECK_REACHABLE_VIA_RX == source_check_type)
+	  {
+	      pass0 |= fib_urpf_check(lb0->lb_urpf,
+				      vnet_buffer (p0)->sw_if_index[VLIB_RX]);
+	      pass1 |= fib_urpf_check(lb1->lb_urpf,
+				      vnet_buffer (p1)->sw_if_index[VLIB_RX]);
+	  }
+	  else
+	  {
+	      pass0 |= fib_urpf_check_size(lb0->lb_urpf);
+	      pass1 |= fib_urpf_check_size(lb1->lb_urpf);
+	  }
 	  next0 = (pass0 ? next0 : IP4_SOURCE_CHECK_NEXT_DROP);
 	  next1 = (pass1 ? next1 : IP4_SOURCE_CHECK_NEXT_DROP);
 
@@ -255,11 +207,8 @@
 	  ip4_fib_mtrie_t * mtrie0;
 	  ip4_fib_mtrie_leaf_t leaf0;
 	  ip4_source_check_config_t * c0;
-	  ip_adjacency_t * adj0;
 	  u32 pi0, next0, pass0, lb_index0;
  	  const load_balance_t * lb0;
-          const dpo_id_t *dpo0;
-          u32 ii0;
 
 	  pi0 = from[0];
 	  to_next[0] = pi0;
@@ -295,33 +244,15 @@
 	  /* Pass multicast. */
 	  pass0 = ip4_address_is_multicast (&ip0->src_address) || ip0->src_address.as_u32 == clib_host_to_net_u32(0xFFFFFFFF);
 
-          if (PREDICT_TRUE(1 == lb0->lb_n_buckets))
-          {
-              dpo0 = load_balance_get_bucket_i(lb0, 0);
-              if (PREDICT_TRUE(dpo0->dpoi_type == DPO_ADJACENCY))
-              {
-                  pass0 |= (source_check_type ==
-                            IP4_SOURCE_CHECK_REACHABLE_VIA_ANY);
-                  adj0 = adj_get(dpo0->dpoi_index);
-                  pass0 |= (vnet_buffer (p0)->sw_if_index[VLIB_RX] ==
-                            adj0->rewrite_header.sw_if_index);
-              }
-          }
-          else
-          {
-              for (ii0 = 0; ii0 < lb0->lb_n_buckets && !pass0; ii0++)
-              {
-                  dpo0 = load_balance_get_bucket_i(lb0, ii0);
-                  if (PREDICT_TRUE(dpo0->dpoi_type == DPO_ADJACENCY))
-                  {
-                      pass0 |= (source_check_type ==
-                                IP4_SOURCE_CHECK_REACHABLE_VIA_ANY);
-                      adj0 = adj_get(dpo0->dpoi_index);
-                      pass0 |= (vnet_buffer (p0)->sw_if_index[VLIB_RX] ==
-                                adj0->rewrite_header.sw_if_index);
-                  }
-              }
-          }
+	  if (IP4_SOURCE_CHECK_REACHABLE_VIA_RX == source_check_type)
+	  {
+	      pass0 |= fib_urpf_check(lb0->lb_urpf,
+				      vnet_buffer (p0)->sw_if_index[VLIB_RX]);
+	  }
+	  else
+	  {
+	      pass0 |= fib_urpf_check_size(lb0->lb_urpf);
+	  }
 
 	  next0 = (pass0 ? next0 : IP4_SOURCE_CHECK_NEXT_DROP);
 	  p0->error = error_node->errors[IP4_ERROR_UNICAST_SOURCE_CHECK_FAILS];
@@ -392,6 +323,7 @@
 		     unformat_input_t * input,
 		     vlib_cli_command_t * cmd)
 {
+  unformat_input_t _line_input, * line_input = &_line_input;
   vnet_main_t * vnm = vnet_get_main();
   ip4_main_t * im = &ip4_main;
   ip_lookup_main_t * lm = &im->lookup_main;
@@ -402,21 +334,37 @@
   u32 feature_index;
 
   sw_if_index = ~0;
+  is_del = 0;
+  feature_index = im->ip4_unicast_rx_feature_source_reachable_via_rx;
 
-  if (! unformat_user (input, unformat_vnet_sw_interface, vnm, &sw_if_index))
+  if (! unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat_user (line_input, unformat_vnet_sw_interface, vnm, &sw_if_index))
+	  ;
+      else if (unformat (line_input, "del"))
+        is_del = 1;
+      else if (unformat (line_input, "strict"))
+	feature_index = im->ip4_unicast_rx_feature_source_reachable_via_rx;
+      else if (unformat (line_input, "loose"))
+        feature_index = im->ip4_unicast_rx_feature_source_reachable_via_any;
+      else
+        {
+	  error = unformat_parse_error (line_input);
+	  goto done;
+	}
+    }
+
+  if (~0 == sw_if_index)
     {
       error = clib_error_return (0, "unknown interface `%U'",
-				 format_unformat_error, input);
+				 format_unformat_error, line_input);
       goto done;
     }
 
-  is_del = 0;
-  config.no_default_route = 0;
   config.fib_index = im->fib_index_by_sw_if_index[sw_if_index];
-  feature_index = im->ip4_unicast_rx_feature_source_reachable_via_rx;
-  if (unformat (input, "del"))
-    is_del = 1;
-
   ci = rx_cm->config_index_by_sw_if_index[sw_if_index];
   ci = (is_del
 	? vnet_config_del_feature
@@ -432,11 +380,121 @@
   return error;
 }
 
+/* *INDENT-OFF* */
+/*?
+ * Add the unicast RPF check feature to an input interface
+ *
+ * @cliexpar
+ * @cliexstart{set interface ip source-check}
+ * Two flavours are supported;
+ *  loose: accept ingress packet if there is a route to reach the source
+ *  strict: accept ingress packet if it arrived on an interface which
+ *          the route to the source uses. i.e. an interface that the source
+ *          is reachable via.
+ * the deafult is strict.
+ *
+ * @cliexend
+?*/
 VLIB_CLI_COMMAND (set_interface_ip_source_check_command, static) = {
   .path = "set interface ip source-check",
   .function = set_ip_source_check,
   .short_help = "Set IP4/IP6 interface unicast source check",
 };
+/* *INDENT-ON* */
+
+static clib_error_t *
+ip_source_check_accept (vlib_main_t * vm,
+			unformat_input_t * input,
+			vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, * line_input = &_line_input;
+  fib_prefix_t pfx = {
+      .fp_proto = FIB_PROTOCOL_IP4,
+  };
+  clib_error_t * error = NULL;
+  u32 table_id, is_add, fib_index;
+
+  is_add = 1;
+  table_id = ~0;
+
+  /* Get a line of input. */
+  if (! unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "table %d", &table_id))
+	;
+      else if (unformat (line_input, "del"))
+	is_add = 0;
+      else if (unformat (line_input, "add"))
+	is_add = 1;
+      else if (unformat (line_input, "%U/%d",
+			 unformat_ip4_address,
+			 &pfx.fp_addr.ip4,
+			 &pfx.fp_len))
+	  pfx.fp_proto = FIB_PROTOCOL_IP4;
+      else
+      {
+	  error = unformat_parse_error (line_input);
+	  goto done;
+      }
+    }
+
+  if (~0 != table_id)
+    {
+      fib_index = fib_table_id_find_fib_index(pfx.fp_proto, table_id);
+      if (~0 == fib_index)
+        {
+	  error = clib_error_return (0,
+				     "Nonexistent table id %d",
+				     table_id);
+	  goto done;
+	}
+    }
+  else
+    {
+      fib_index = 0;
+    }
+
+  if (is_add)
+    {
+      fib_table_entry_special_add(fib_index,
+				  &pfx,
+				  FIB_SOURCE_URPF_EXEMPT,
+				  FIB_ENTRY_FLAG_DROP,
+				  ADJ_INDEX_INVALID);
+    }
+  else
+  {
+      fib_table_entry_special_remove(fib_index,
+				     &pfx,
+				     FIB_SOURCE_URPF_EXEMPT);
+  }
+
+done:
+  return (error);
+}
+
+/* *INDENT-OFF* */
+/*?
+ * Add an exemption for a prefix to pass the uRPF loose check. Testing purposes only.
+ *
+ * @cliexpar
+ * @cliexstart{ip rpf-accept}
+ *
+ * Add an exception for a prefix to pass the loose RPF tests. This is usefull
+ * for testing purposes.
+ * VPP always performs a loose uRPF check for for-us traffic.
+ * @cliexend
+?*/
+VLIB_CLI_COMMAND (ip_source_check_accept_command, static) = {
+  .path = "ip urpf-accept",
+  .function = ip_source_check_accept,
+  .short_help = "Add a loose uRPF check exemption",
+};
+/* *INDENT-ON* */
+
 
 /* Dummy init function to get us linked in. */
 clib_error_t * ip4_source_check_init (vlib_main_t * vm)
diff --git a/vnet/vnet/ip/lookup.c b/vnet/vnet/ip/lookup.c
index 5cfdc23..384e389 100644
--- a/vnet/vnet/ip/lookup.c
+++ b/vnet/vnet/ip/lookup.c
@@ -598,12 +598,20 @@
 	  {
 	      for (j = 0; j < vec_len (rpaths); j++)
 	      {
+		  u32 fi;
 		  /*
 		   * the CLI parsing stored table Ids, swap to FIB indicies
 		   */
-		  rpaths[i].frp_fib_index =
-		      fib_table_id_find_fib_index(prefixs[i].fp_proto,
-						  rpaths[i].frp_fib_index);
+		  fi = fib_table_id_find_fib_index(prefixs[i].fp_proto,
+						   rpaths[i].frp_fib_index);
+
+		  if (~0 == fi)
+		  {
+		      error = clib_error_return(0 , "Via table %d does not exist",
+						rpaths[i].frp_fib_index);
+		      goto done;
+		  }
+		  rpaths[i].frp_fib_index = fi;
 
 		  fib_prefix_t rpfx = {
 		      .fp_len = prefixs[i].fp_len,