VPP-226 IPv4 src-address + port range checker

Change-Id: Ia251e9d7d53e894a5666109f69e9626d27ea74cb
Signed-off-by: Dave Barach <dave@barachs.net>
Signed-off-by: Keith Burns (alagalah) <alagalah@gmail.com>
diff --git a/vnet/Makefile.am b/vnet/Makefile.am
index e9a7347..738f33f 100644
--- a/vnet/Makefile.am
+++ b/vnet/Makefile.am
@@ -258,6 +258,7 @@
  vnet/ip/ip4_mtrie.c				\
  vnet/ip/ip4_pg.c				\
  vnet/ip/ip4_source_check.c			\
+ vnet/ip/ip4_source_and_port_range_check.c	\
  vnet/ip/ip6_format.c				\
  vnet/ip/ip6_forward.c				\
  vnet/ip/ip6_hop_by_hop.c			\
@@ -284,6 +285,7 @@
  vnet/ip/igmp_packet.h				\
  vnet/ip/ip.h					\
  vnet/ip/ip_feature_registration.h		\
+ vnet/ip/ip_source_and_port_range_check.h \
  vnet/ip/ip4.h					\
  vnet/ip/ip4_mtrie.h				\
  vnet/ip/ip4_error.h				\
diff --git a/vnet/vnet/api_errno.h b/vnet/vnet/api_errno.h
index 2c247d4..2b18ef5 100644
--- a/vnet/vnet/api_errno.h
+++ b/vnet/vnet/api_errno.h
@@ -83,7 +83,10 @@
 _(LISP_DISABLED, -90, "LISP is disabled")                               \
 _(CLASSIFY_TABLE_NOT_FOUND, -91, "Classify table not found")            \
 _(INVALID_EID_TYPE, -92, "Unsupported LSIP EID type")                   \
-_(CANNOT_CREATE_PCAP_FILE, -93, "Cannot create pcap file")
+_(CANNOT_CREATE_PCAP_FILE, -93, "Cannot create pcap file")              \
+_(INCORRECT_ADJACENCY_TYPE, -94, "Invalid adjacency type for this operation") \
+_(EXCEEDED_NUMBER_OF_RANGES_CAPACITY, -95, "Operation would exceed configured capacity of ranges") \
+_(EXCEEDED_NUMBER_OF_PORTS_CAPACITY, -96, "Operation would exceed capacity of number of ports")
 
 typedef enum {
 #define _(a,b,c) VNET_API_ERROR_##a = (b),
diff --git a/vnet/vnet/ip/ip4.h b/vnet/vnet/ip/ip4.h
index fb04893..92dff07 100644
--- a/vnet/vnet/ip/ip4.h
+++ b/vnet/vnet/ip/ip4.h
@@ -102,68 +102,87 @@
   uword function_opaque;
 } ip4_add_del_interface_address_callback_t;
 
+/**
+ * @brief IPv4 main type.
+ *
+ * State of IPv4 VPP processing including:
+ * - FIBs
+ * - Feature indices used in feature topological sort
+ * - Feature node run time references
+ */
+
 typedef struct ip4_main_t {
   ip_lookup_main_t lookup_main;
 
-  /* Vector of FIBs. */
+  /** Vector of FIBs. */
   ip4_fib_t * fibs;
 
   u32 fib_masks[33];
 
-  /* Table index indexed by software interface. */
+  /** Table index indexed by software interface. */
   u32 * fib_index_by_sw_if_index;
 
-  /* Hash table mapping table id to fib index.
+  /** Hash table mapping table id to fib index.
      ID space is not necessarily dense; index space is dense. */
   uword * fib_index_by_table_id;
 
-  /* Vector of functions to call when routes are added/deleted. */
+  /** Vector of functions to call when routes are added/deleted. */
   ip4_add_del_route_callback_t * add_del_route_callbacks;
 
-  /* Hash table mapping interface route rewrite adjacency index by sw if index. */
+  /** Hash table mapping interface route rewrite adjacency index by sw if index. */
   uword * interface_route_adj_index_by_sw_if_index;
 
-  /* Functions to call when interface address changes. */
+  /** Functions to call when interface address changes. */
   ip4_add_del_interface_address_callback_t * add_del_interface_address_callbacks;
 
-  /* Template used to generate IP4 ARP packets. */
+  /** Template used to generate IP4 ARP packets. */
   vlib_packet_template_t ip4_arp_request_packet_template;
 
-  /* feature path configuration lists */
+  /** 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(...)  */
+  /** Built-in unicast feature path indice, see @ref ip_feature_init_cast()  */
   u32 ip4_unicast_rx_feature_check_access;
+  /** Built-in unicast feature path indice, see @ref ip_feature_init_cast()  */
   u32 ip4_unicast_rx_feature_source_reachable_via_rx;
+  /** Built-in unicast feature path indice, see @ref ip_feature_init_cast()  */
   u32 ip4_unicast_rx_feature_source_reachable_via_any;
+  /** Built-in unicast feature path indice, see @ref ip_feature_init_cast()  */
   u32 ip4_unicast_rx_feature_policer_classify;
+  /** Built-in unicast feature path indice, see @ref ip_feature_init_cast()  */
   u32 ip4_unicast_rx_feature_ipsec;
+  /** Built-in unicast feature path indice, see @ref ip_feature_init_cast()  */
   u32 ip4_unicast_rx_feature_vpath;
+  /** Built-in unicast feature path indice, see @ref ip_feature_init_cast()  */
   u32 ip4_unicast_rx_feature_lookup;
+  /** Built-in unicast feature path indice, see @ref ip_feature_init_cast()  */
+  u32 ip4_unicast_rx_feature_source_and_port_range_check;
 
-  /* Built-in multicast feature path indices */
+  /** Built-in multicast feature path indices */
   u32 ip4_multicast_rx_feature_vpath;
+  /** Built-in multicast feature path indices */
   u32 ip4_multicast_rx_feature_lookup;
 
-  /* Save results for show command */
+  /** Save results for show command */
   char ** feature_nodes[VNET_N_CAST];
-  
-  /* Seed for Jenkins hash used to compute ip4 flow hash. */
+
+  /** Seed for Jenkins hash used to compute ip4 flow hash. */
   u32 flow_hash_seed;
 
+  /** @brief Template information for VPP generated packets */
   struct {
-    /* TTL to use for host generated packets. */
+    /** TTL to use for host generated packets. */
     u8 ttl;
 
-    /* TOS byte to use for host generated packets. */
+    /** TOS byte to use for host generated packets. */
     u8 tos;
 
     u8 pad[2];
   } host_config;
 } ip4_main_t;
 
-/* Global ip4 main structure. */
+/** Global ip4 main structure. */
 extern ip4_main_t ip4_main;
 
 #define VNET_IP4_UNICAST_FEATURE_INIT(x,...)                    \
diff --git a/vnet/vnet/ip/ip4_error.h b/vnet/vnet/ip/ip4_error.h
index b84b082..8d7fc53 100644
--- a/vnet/vnet/ip/ip4_error.h
+++ b/vnet/vnet/ip/ip4_error.h
@@ -72,10 +72,10 @@
   /* Spoofed packets in ip4-rewrite-local */                            \
   _(SPOOFED_LOCAL_PACKETS, "ip4 spoofed local-address packet drops")    \
                                                                         \
- /* Erros singalled by ip4-inacl */                                     \
+  /* Errors singalled by ip4-inacl */                                   \
   _ (INACL_TABLE_MISS, "input ACL table-miss drops")                    \
   _ (INACL_SESSION_DENY, "input ACL session deny drops")
-  
+
 typedef enum {
 #define _(sym,str) IP4_ERROR_##sym,
   foreach_ip4_error
diff --git a/vnet/vnet/ip/ip4_forward.c b/vnet/vnet/ip/ip4_forward.c
index 8b71d2d..1862d89 100644
--- a/vnet/vnet/ip/ip4_forward.c
+++ b/vnet/vnet/ip/ip4_forward.c
@@ -39,14 +39,18 @@
 
 #include <vnet/vnet.h>
 #include <vnet/ip/ip.h>
-#include <vnet/ethernet/ethernet.h>	/* for ethernet_header_t */
-#include <vnet/ethernet/arp_packet.h>	/* for ethernet_arp_header_t */
+/** for ethernet_header_t */
+#include <vnet/ethernet/ethernet.h>
+/** for ethernet_arp_header_t */
+#include <vnet/ethernet/arp_packet.h>	
 #include <vnet/ppp/ppp.h>
-#include <vnet/srp/srp.h>	/* for srp_hw_interface_class */
-#include <vnet/api_errno.h>     /* for API error numbers */
+/** for srp_hw_interface_class */
+#include <vnet/srp/srp.h>
+/** for API error numbers */
+#include <vnet/api_errno.h>     
 
-/** \file
-    vnet ip4 forwarding 
+/** @file
+    vnet ip4 forwarding
 */
 
 /* This is really, really simple but stupid fib. */
@@ -1382,6 +1386,13 @@
   &ip4_main.ip4_unicast_rx_feature_source_reachable_via_any,
 };
 
+VNET_IP4_UNICAST_FEATURE_INIT (ip4_source_and_port_range_check, static) = {
+  .node_name = "ip4-source-and-port-range-check",
+  .runs_before = {"ip4-policer-classify", 0},
+  .feature_index =
+  &ip4_main.ip4_unicast_rx_feature_source_and_port_range_check,
+};
+
 VNET_IP4_UNICAST_FEATURE_INIT (ip4_policer_classify, static) = {
   .node_name = "ip4-policer-classify",
   .runs_before = {"ipsec-input-ip4", 0},
diff --git a/vnet/vnet/ip/ip4_input.c b/vnet/vnet/ip/ip4_input.c
index 1c7d327..96a6866 100644
--- a/vnet/vnet/ip/ip4_input.c
+++ b/vnet/vnet/ip/ip4_input.c
@@ -453,6 +453,10 @@
   if ((error = vlib_call_init_function (vm, ip4_source_check_init)))
     return error;
 
+  if ((error = vlib_call_init_function 
+       (vm, ip4_source_and_port_range_check_init)))
+    return error;
+
   /* Set flow hash to something non-zero. */
   ip4_main.flow_hash_seed = 0xdeadbeef;
 
diff --git a/vnet/vnet/ip/ip4_source_and_port_range_check.c b/vnet/vnet/ip/ip4_source_and_port_range_check.c
new file mode 100644
index 0000000..9dfb522
--- /dev/null
+++ b/vnet/vnet/ip/ip4_source_and_port_range_check.c
@@ -0,0 +1,956 @@
+/*
+ * 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/ip/ip.h>
+
+typedef struct {
+  u32 ranges_per_adjacency;
+  u32 special_adjacency_format_function_index;
+
+  /* convenience */
+  vlib_main_t *vlib_main;
+  vnet_main_t *vnet_main;
+} source_range_check_main_t;
+
+source_range_check_main_t source_range_check_main;
+
+vlib_node_registration_t ip4_source_port_and_range_check;
+
+typedef struct {
+  union {
+    u16x8 as_u16x8;
+    u16 as_u16[8];
+  };
+} u16x8vec_t;
+
+typedef struct {
+  u16x8vec_t low;
+  u16x8vec_t hi;
+} port_range_t;
+
+#define foreach_ip4_source_and_port_range_check_error		\
+_(CHECK_FAIL, "ip4 source and port range check bad packets")	\
+_(CHECK_OK, "ip4 source and port range check good packets")
+
+typedef enum {
+#define _(sym,str) IP4_SOURCE_AND_PORT_RANGE_CHECK_ERROR_##sym,
+  foreach_ip4_source_and_port_range_check_error
+#undef _
+  IP4_SOURCE_AND_PORT_RANGE_CHECK_N_ERROR,
+} ip4_source_and_port_range_check_error_t;
+
+static char * ip4_source_and_port_range_check_error_strings[] = {
+#define _(sym,string) string,
+  foreach_ip4_source_and_port_range_check_error
+#undef _
+};
+
+typedef struct {
+  u32 pass;
+  u32 bypass;
+  u32 is_tcp;
+  ip4_address_t src_addr;
+  u16 dst_port;
+} ip4_source_and_port_range_check_trace_t;
+
+static u8 * format_ip4_source_and_port_range_check_trace (u8 * s, va_list * va)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *);
+  ip4_source_and_port_range_check_trace_t * t =
+    va_arg (*va, ip4_source_and_port_range_check_trace_t *);
+
+  if (t->bypass)
+    s = format (s, "PASS (bypass case)");
+  else
+    s = format (s, "src ip %U %s dst port %d: %s",
+        format_ip4_address, &t->src_addr, t->is_tcp ? "TCP" : "UDP",
+        (u32) t->dst_port,
+        (t->pass == 1) ? "PASS" : "FAIL");
+  return s;
+}
+
+typedef enum {
+  IP4_SOURCE_AND_PORT_RANGE_CHECK_NEXT_DROP,
+  IP4_SOURCE_AND_PORT_RANGE_CHECK_N_NEXT,
+} ip4_source_and_port_range_check_next_t;
+
+typedef union {
+  u32 fib_index;
+} ip4_source_and_port_range_check_config_t;
+
+static inline u32 check_adj_port_range_x1 (ip_adjacency_t * adj,
+                                           u16 dst_port,
+                                           u32 next)
+{
+  port_range_t *range;
+  u16x8vec_t key;
+  u16x8vec_t diff1;
+  u16x8vec_t diff2;
+  u16x8vec_t sum, sum_equal_diff2;
+  u16 sum_nonzero, sum_equal, winner_mask;
+  int i;
+  u8 * rwh;
+
+  if (adj->lookup_next_index != IP_LOOKUP_NEXT_ICMP_ERROR || dst_port == 0)
+    return IP4_SOURCE_AND_PORT_RANGE_CHECK_NEXT_DROP;
+
+  rwh = (u8 *)(&adj->rewrite_header);
+  range = (port_range_t *)rwh;
+
+  /* Make the obvious screw-case work. A variant also works w/ no MMX */
+  if (PREDICT_FALSE(dst_port == 65535))
+    {
+      int j;
+
+      for (i = 0; i < VLIB_BUFFER_PRE_DATA_SIZE / sizeof(port_range_t); i++)
+        {
+          for (j = 0; j < 8; j++)
+            if (range->low.as_u16x8[j] == 65535)
+              return next;
+          range++;
+        }
+      return IP4_SOURCE_AND_PORT_RANGE_CHECK_NEXT_DROP;
+    }
+
+  key.as_u16x8 = u16x8_splat (dst_port);
+
+  for (i = 0; i < VLIB_BUFFER_PRE_DATA_SIZE / sizeof(port_range_t); i++)
+    {
+      diff1.as_u16x8 = u16x8_sub_saturate (range->low.as_u16x8, key.as_u16x8);
+      diff2.as_u16x8 = u16x8_sub_saturate (range->hi.as_u16x8, key.as_u16x8);
+      sum.as_u16x8 = u16x8_add (diff1.as_u16x8, diff2.as_u16x8);
+      sum_equal_diff2.as_u16x8 = u16x8_is_equal (sum.as_u16x8, diff2.as_u16x8);
+      sum_nonzero = ~u16x8_zero_byte_mask (sum.as_u16x8);
+      sum_equal = ~u16x8_zero_byte_mask (sum_equal_diff2.as_u16x8);
+      winner_mask = sum_nonzero & sum_equal;
+      if (winner_mask)
+        return next;
+      range++;
+    }
+  return IP4_SOURCE_AND_PORT_RANGE_CHECK_NEXT_DROP;
+}
+
+always_inline uword
+ip4_source_and_port_range_check_inline
+(vlib_main_t * vm, vlib_node_runtime_t * node,
+ vlib_frame_t * frame)
+{
+  ip4_main_t * im = &ip4_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  ip_config_main_t * cm = &lm->rx_config_mains[VNET_UNICAST];
+  u32 n_left_from, * from, * to_next;
+  u32 next_index;
+  vlib_node_runtime_t * error_node = node;
+  u32 good_packets = 0;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index,
+               to_next, n_left_to_next);
+
+      while (n_left_from >= 4 && n_left_to_next >= 2)
+    {
+          vlib_buffer_t * b0, * b1;
+      ip4_header_t * ip0, * ip1;
+      ip4_fib_mtrie_t * mtrie0, * mtrie1;
+      ip4_fib_mtrie_leaf_t leaf0, leaf1;
+      ip4_source_and_port_range_check_config_t * c0, * c1;
+      ip_adjacency_t * adj0, * adj1;
+      u32 bi0, next0, adj_index0, pass0, save_next0;
+      u32 bi1, next1, adj_index1, pass1, save_next1;
+          udp_header_t * udp0, * udp1;
+
+      /* Prefetch next iteration. */
+      {
+        vlib_buffer_t * p2, * p3;
+
+        p2 = vlib_get_buffer (vm, from[2]);
+        p3 = vlib_get_buffer (vm, from[3]);
+
+        vlib_prefetch_buffer_header (p2, LOAD);
+        vlib_prefetch_buffer_header (p3, LOAD);
+
+        CLIB_PREFETCH (p2->data, sizeof (ip0[0]), LOAD);
+        CLIB_PREFETCH (p3->data, sizeof (ip1[0]), LOAD);
+      }
+
+      bi0 = to_next[0] = from[0];
+      bi1 = to_next[1] = from[1];
+      from += 2;
+      to_next += 2;
+      n_left_from -= 2;
+      n_left_to_next -= 2;
+
+      b0 = vlib_get_buffer (vm, bi0);
+      b1 = vlib_get_buffer (vm, bi1);
+
+      ip0 = vlib_buffer_get_current (b0);
+      ip1 = vlib_buffer_get_current (b1);
+
+      c0 = vnet_get_config_data (&cm->config_main,
+                     &b0->current_config_index,
+                     &next0,
+                     sizeof (c0[0]));
+      c1 = vnet_get_config_data (&cm->config_main,
+                     &b1->current_config_index,
+                     &next1,
+                     sizeof (c1[0]));
+
+          /* we can't use the default VRF here... */
+          ASSERT (c0->fib_index && c1->fib_index);
+
+      mtrie0 = &vec_elt_at_index (im->fibs, c0->fib_index)->mtrie;
+      mtrie1 = &vec_elt_at_index (im->fibs, c1->fib_index)->mtrie;
+
+      leaf0 = leaf1 = IP4_FIB_MTRIE_LEAF_ROOT;
+
+      leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
+                                             &ip0->src_address, 0);
+      leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1,
+                                             &ip1->src_address, 0);
+
+      leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
+                                             &ip0->src_address, 1);
+      leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1,
+                                             &ip1->src_address, 1);
+
+      leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
+                                             &ip0->src_address, 2);
+      leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1,
+                                             &ip1->src_address, 2);
+
+      leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
+                                             &ip0->src_address, 3);
+      leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1,
+                                             &ip1->src_address, 3);
+
+      adj_index0 = ip4_fib_mtrie_leaf_get_adj_index (leaf0);
+      adj_index1 = ip4_fib_mtrie_leaf_get_adj_index (leaf1);
+
+      ASSERT (adj_index0 == ip4_fib_lookup_with_table (im, c0->fib_index,
+                               &ip0->src_address,
+                                                           0 /* use dflt rt */));
+
+      ASSERT (adj_index1 == ip4_fib_lookup_with_table (im, c1->fib_index,
+                               &ip1->src_address,
+                               0));
+      adj0 = ip_get_adjacency (lm, adj_index0);
+      adj1 = ip_get_adjacency (lm, adj_index1);
+
+          pass0 = 0;
+          pass0 |= ip4_address_is_multicast (&ip0->src_address);
+          pass0 |= ip0->src_address.as_u32 == clib_host_to_net_u32(0xFFFFFFFF);
+          pass0 |= (ip0->protocol != IP_PROTOCOL_UDP) &&
+        (ip0->protocol != IP_PROTOCOL_TCP);
+
+          pass1 = 0;
+          pass1 |= ip4_address_is_multicast (&ip1->src_address);
+          pass1 |= ip1->src_address.as_u32 == clib_host_to_net_u32(0xFFFFFFFF);
+          pass1 |= (ip1->protocol != IP_PROTOCOL_UDP) &&
+        (ip1->protocol != IP_PROTOCOL_TCP);
+
+      save_next0 = next0;
+      udp0 = ip4_next_header (ip0);
+      save_next1 = next1;
+      udp1 = ip4_next_header (ip1);
+
+          if (PREDICT_TRUE(pass0 == 0))
+            {
+          good_packets ++;
+              next0 = check_adj_port_range_x1
+                (adj0, clib_net_to_host_u16(udp0->dst_port), next0);
+          good_packets -= (save_next0 != next0);
+              b0->error = error_node->errors
+                [IP4_SOURCE_AND_PORT_RANGE_CHECK_ERROR_CHECK_FAIL];
+            }
+
+          if (PREDICT_TRUE(pass1 == 0))
+            {
+          good_packets ++;
+              next1 = check_adj_port_range_x1
+                (adj1, clib_net_to_host_u16(udp1->dst_port), next1);
+          good_packets -= (save_next1 != next1);
+              b1->error = error_node->errors
+                [IP4_SOURCE_AND_PORT_RANGE_CHECK_ERROR_CHECK_FAIL];
+            }
+
+          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
+                            && (b0->flags & VLIB_BUFFER_IS_TRACED))) {
+            ip4_source_and_port_range_check_trace_t * t =
+          vlib_add_trace (vm, node, b0, sizeof (*t));
+            t->pass = next0 == save_next0;
+        t->bypass = pass0;
+        t->src_addr.as_u32 = ip0->src_address.as_u32;
+        t->dst_port = (pass0 == 0) ?
+          clib_net_to_host_u16(udp0->dst_port) : 0;
+        t->is_tcp = ip0->protocol == IP_PROTOCOL_TCP;
+            }
+
+          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
+                            && (b1->flags & VLIB_BUFFER_IS_TRACED))) {
+            ip4_source_and_port_range_check_trace_t * t =
+          vlib_add_trace (vm, node, b1, sizeof (*t));
+            t->pass = next1 == save_next1;
+        t->bypass = pass1;
+        t->src_addr.as_u32 = ip1->src_address.as_u32;
+        t->dst_port = (pass1 == 0) ?
+          clib_net_to_host_u16(udp1->dst_port) : 0;
+        t->is_tcp = ip1->protocol == IP_PROTOCOL_TCP;
+            }
+
+      vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
+                       to_next, n_left_to_next,
+                       bi0, bi1, next0, next1);
+    }
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+    {
+      vlib_buffer_t * b0;
+      ip4_header_t * ip0;
+      ip4_fib_mtrie_t * mtrie0;
+      ip4_fib_mtrie_leaf_t leaf0;
+      ip4_source_and_port_range_check_config_t * c0;
+      ip_adjacency_t * adj0;
+      u32 bi0, next0, adj_index0, pass0, save_next0;
+          udp_header_t * udp0;
+
+      bi0 = from[0];
+      to_next[0] = bi0;
+      from += 1;
+      to_next += 1;
+      n_left_from -= 1;
+      n_left_to_next -= 1;
+
+      b0 = vlib_get_buffer (vm, bi0);
+      ip0 = vlib_buffer_get_current (b0);
+
+      c0 = vnet_get_config_data
+            (&cm->config_main, &b0->current_config_index,
+             &next0,
+             sizeof (c0[0]));
+
+          /* we can't use the default VRF here... */
+          ASSERT(c0->fib_index);
+
+      mtrie0 = &vec_elt_at_index (im->fibs, c0->fib_index)->mtrie;
+
+      leaf0 = IP4_FIB_MTRIE_LEAF_ROOT;
+
+      leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
+                                             &ip0->src_address, 0);
+
+      leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
+                                             &ip0->src_address, 1);
+
+      leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
+                                             &ip0->src_address, 2);
+
+      leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
+                                             &ip0->src_address, 3);
+
+      adj_index0 = ip4_fib_mtrie_leaf_get_adj_index (leaf0);
+
+      ASSERT (adj_index0 == ip4_fib_lookup_with_table
+                  (im, c0->fib_index,
+                   &ip0->src_address,
+                   0 /* use default route */));
+          adj0 = ip_get_adjacency (lm, adj_index0);
+
+      /*
+       * $$$ which (src,dst) categories should we always pass?
+       */
+          pass0 = 0;
+          pass0 |= ip4_address_is_multicast (&ip0->src_address);
+          pass0 |= ip0->src_address.as_u32 == clib_host_to_net_u32(0xFFFFFFFF);
+          pass0 |= (ip0->protocol != IP_PROTOCOL_UDP) &&
+        (ip0->protocol != IP_PROTOCOL_TCP);
+
+      save_next0 = next0;
+      udp0 = ip4_next_header (ip0);
+
+          if (PREDICT_TRUE(pass0 == 0))
+            {
+          good_packets ++;
+              next0 = check_adj_port_range_x1
+                (adj0, clib_net_to_host_u16(udp0->dst_port), next0);
+          good_packets -= (save_next0 != next0);
+              b0->error = error_node->errors
+                [IP4_SOURCE_AND_PORT_RANGE_CHECK_ERROR_CHECK_FAIL];
+            }
+
+          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)
+                            && (b0->flags & VLIB_BUFFER_IS_TRACED))) {
+            ip4_source_and_port_range_check_trace_t * t =
+          vlib_add_trace (vm, node, b0, sizeof (*t));
+            t->pass = next0 == save_next0;
+        t->bypass = pass0;
+        t->src_addr.as_u32 = ip0->src_address.as_u32;
+        t->dst_port = (pass0 == 0) ?
+          clib_net_to_host_u16(udp0->dst_port) : 0;
+        t->is_tcp = ip0->protocol == IP_PROTOCOL_TCP;
+            }
+
+      vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                       to_next, n_left_to_next,
+                       bi0, next0);
+    }
+
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  vlib_node_increment_counter (vm, ip4_source_port_and_range_check.index,
+                   IP4_SOURCE_AND_PORT_RANGE_CHECK_ERROR_CHECK_OK,
+                   good_packets);
+  return frame->n_vectors;
+}
+
+static uword
+ip4_source_and_port_range_check (vlib_main_t * vm,
+                                 vlib_node_runtime_t * node,
+                                 vlib_frame_t * frame)
+{
+  return ip4_source_and_port_range_check_inline (vm, node, frame);
+}
+
+VLIB_REGISTER_NODE (ip4_source_port_and_range_check) = {
+  .function = ip4_source_and_port_range_check,
+  .name = "ip4-source-and-port-range-check",
+  .vector_size = sizeof (u32),
+
+  .n_errors = ARRAY_LEN(ip4_source_and_port_range_check_error_strings),
+  .error_strings = ip4_source_and_port_range_check_error_strings,
+
+  .n_next_nodes = IP4_SOURCE_AND_PORT_RANGE_CHECK_N_NEXT,
+  .next_nodes = {
+    [IP4_SOURCE_AND_PORT_RANGE_CHECK_NEXT_DROP] = "error-drop",
+  },
+
+  .format_buffer = format_ip4_header,
+  .format_trace = format_ip4_source_and_port_range_check_trace,
+};
+
+int set_ip_source_and_port_range_check (vlib_main_t * vm,
+                                        u32 fib_index,
+                                        u32 sw_if_index,
+                                        u32 is_add)
+{
+  ip4_main_t * im = &ip4_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  ip_config_main_t * rx_cm = &lm->rx_config_mains[VNET_UNICAST];
+  u32 ci;
+  ip4_source_and_port_range_check_config_t config;
+  u32 feature_index;
+  int rv = 0;
+  u8 is_del = !is_add;
+
+  config.fib_index = fib_index;
+  feature_index = im->ip4_unicast_rx_feature_source_and_port_range_check;
+
+  vec_validate (rx_cm->config_index_by_sw_if_index, sw_if_index);
+
+  ci = rx_cm->config_index_by_sw_if_index[sw_if_index];
+  ci = (is_del
+    ? vnet_config_del_feature
+    : vnet_config_add_feature)
+    (vm, &rx_cm->config_main,
+     ci,
+     feature_index,
+     &config,
+     sizeof (config));
+  rx_cm->config_index_by_sw_if_index[sw_if_index] = ci;
+
+  return rv;
+}
+
+static clib_error_t *
+set_ip_source_and_port_range_check_fn (vlib_main_t * vm,
+             unformat_input_t * input,
+             vlib_cli_command_t * cmd)
+{
+  vnet_main_t * vnm = vnet_get_main();
+  ip4_main_t * im = &ip4_main;
+  clib_error_t * error = 0;
+  u32 is_add = 1;
+  u32 sw_if_index = ~0;
+  u32 vrf_id = ~0;
+  u32 fib_index;
+  uword * p;
+  int rv = 0;
+
+  sw_if_index = ~0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "%U", unformat_vnet_sw_interface, vnm,
+            &sw_if_index))
+	;
+      else if (unformat (input, "vrf %d", &vrf_id))
+	;
+      else if (unformat (input, "del"))
+	is_add = 0;
+      else
+	break;
+    }
+
+  if (sw_if_index == ~0)
+    return clib_error_return (0, "Interface required but not specified");
+
+  if (vrf_id == ~0)
+    return clib_error_return (0, "VRF ID required but not specified");
+
+  if (vrf_id == 0)
+    return clib_error_return (0, "VRF ID should not be default. Should be distinct VRF for this purpose. ");
+
+  p = hash_get (im->fib_index_by_table_id, vrf_id);
+
+  if (p == 0)
+    return clib_error_return (0, "Invalid VRF ID %d", vrf_id);
+
+  fib_index = p[0];
+  rv = set_ip_source_and_port_range_check (vm, fib_index, sw_if_index, is_add);
+
+  switch(rv)
+    {
+    case 0:
+      break;
+
+    default:
+      return clib_error_return
+        (0, "set source and port-range on interface returned an unexpected value: %d", rv);
+    }
+  return error;
+}
+
+VLIB_CLI_COMMAND (set_interface_ip_source_and_port_range_check_command,
+                  static) = {
+  .path = "set interface ip source-and-port-range-check",
+  .function = set_ip_source_and_port_range_check_fn,
+  .short_help = "set int ip source-and-port-range-check <intfc> vrf <n> [del]",
+};
+
+static u8 * format_source_and_port_rc_adjacency (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vnet_main_t * vnm) = va_arg (*args, vnet_main_t *);
+  ip_lookup_main_t * lm = va_arg (*args, ip_lookup_main_t *);
+  u32 adj_index = va_arg (*args, u32);
+  ip_adjacency_t * adj = ip_get_adjacency (lm, adj_index);
+  source_range_check_main_t * srm = &source_range_check_main;
+  u8 * rwh = (u8 *) (&adj->rewrite_header);
+  port_range_t * range;
+  int i, j;
+  int printed = 0;
+
+  range = (port_range_t *) rwh;
+
+  s = format (s, "allow ");
+
+  for (i = 0; i < srm->ranges_per_adjacency; i++)
+    {
+      for (j = 0; j < 8; j++)
+        {
+          if (range->low.as_u16[j])
+            {
+              if (printed)
+                s = format (s, ", ");
+              if (range->hi.as_u16[j] > (range->low.as_u16[j] + 1))
+                s = format (s, "%d-%d", (u32) range->low.as_u16[j],
+                            (u32) range->hi.as_u16[j] - 1);
+              else
+                s = format (s, "%d", range->low.as_u16[j]);
+              printed = 1;
+            }
+        }
+      range++;
+    }
+  return s;
+}
+
+clib_error_t * ip4_source_and_port_range_check_init (vlib_main_t * vm)
+{
+  source_range_check_main_t * srm = &source_range_check_main;
+  ip4_main_t * im = &ip4_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+
+  srm->vlib_main = vm;
+  srm->vnet_main = vnet_get_main();
+
+  srm->ranges_per_adjacency = VLIB_BUFFER_PRE_DATA_SIZE / (2*sizeof(u16x8));
+  srm->special_adjacency_format_function_index =
+      vnet_register_special_adjacency_format_function
+      (lm, format_source_and_port_rc_adjacency);
+  ASSERT (srm->special_adjacency_format_function_index);
+
+  return 0;
+}
+
+VLIB_INIT_FUNCTION (ip4_source_and_port_range_check_init);
+
+
+int ip4_source_and_port_range_check_add_del
+(ip4_address_t * address, u32 length, u32 vrf_id, u16 * low_ports,
+ u16 * hi_ports, int is_add)
+{
+  source_range_check_main_t * srm = &source_range_check_main;
+  ip4_main_t * im = &ip4_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  uword * p;
+  u32 fib_index;
+  u32 adj_index;
+  ip_adjacency_t * adj;
+  int i, j, k;
+  port_range_t * range;
+  u8 *rwh;
+
+  p = hash_get (im->fib_index_by_table_id, vrf_id);
+  if (!p)
+    {
+      ip4_fib_t * f;
+      f = find_ip4_fib_by_table_index_or_id (im, vrf_id, 0 /* flags */);
+      fib_index = f->index;
+    }
+  else
+    fib_index = p[0];
+
+  adj_index = ip4_fib_lookup_with_table
+    (im, fib_index, address, 0 /* disable_default_route */);
+
+  if (is_add == 0)
+    {
+      adj = ip_get_adjacency (lm, adj_index);
+      if (adj->lookup_next_index != IP_LOOKUP_NEXT_ICMP_ERROR)
+        return VNET_API_ERROR_INCORRECT_ADJACENCY_TYPE;
+
+      rwh = (u8 *)(&adj->rewrite_header);
+
+      for (i = 0; i < vec_len (low_ports); i++)
+        {
+          range = (port_range_t *) rwh;
+          for (j = 0; j < srm->ranges_per_adjacency; j++)
+            {
+              for (k = 0; k < 8; k++)
+                {
+                  if (low_ports[i] == range->low.as_u16[k] &&
+                      hi_ports[i] == range->hi.as_u16[k])
+                    {
+                      range->low.as_u16[k] = range->hi.as_u16[k] = 0;
+                      goto doublebreak;
+                    }
+                }
+              range++;
+            }
+        doublebreak: ;
+        }
+
+      range = (port_range_t *) rwh;
+      /* Have we deleted all ranges yet? */
+      for (i = 0; i < srm->ranges_per_adjacency; i++)
+        {
+          for (j = 0; j < 8; j++)
+            {
+              if (range->low.as_u16[i] != 0)
+                goto still_occupied;
+            }
+          range++;
+        }
+      /* Yes, lose the adjacency... */
+      {
+    ip4_add_del_route_args_t a;
+
+        memset (&a, 0, sizeof(a));
+        a.flags = IP4_ROUTE_FLAG_FIB_INDEX | IP4_ROUTE_FLAG_DEL;
+        a.table_index_or_table_id = fib_index;
+        a.dst_address = address[0];
+        a.dst_address_length = length;
+        a.adj_index = adj_index;
+        ip4_add_del_route (im, &a);
+      }
+
+    still_occupied:
+      ;
+    }
+  else
+    {
+      adj = ip_get_adjacency (lm, adj_index);
+      /* $$$$ fixme: add ports if address + mask match */
+      if (adj->lookup_next_index == IP_LOOKUP_NEXT_ICMP_ERROR)
+        return VNET_API_ERROR_INCORRECT_ADJACENCY_TYPE;
+
+      {
+        ip_adjacency_t template_adj;
+        ip4_add_del_route_args_t a;
+
+        memset (&template_adj, 0, sizeof (template_adj));
+
+        template_adj.lookup_next_index = IP_LOOKUP_NEXT_ICMP_ERROR;
+        template_adj.if_address_index = ~0;
+        template_adj.special_adjacency_format_function_index =
+          srm->special_adjacency_format_function_index;
+
+        rwh = (u8 *) (&template_adj.rewrite_header);
+
+        range = (port_range_t *) rwh;
+
+        if (vec_len (low_ports) > 8 * srm->ranges_per_adjacency)
+          return VNET_API_ERROR_EXCEEDED_NUMBER_OF_RANGES_CAPACITY;
+
+        j = k = 0;
+
+        for (i = 0; i < vec_len (low_ports); i++)
+          {
+            for (; j < srm->ranges_per_adjacency; j++)
+              {
+                for (; k < 8; k++)
+                  {
+                    if (range->low.as_u16[k] == 0)
+                      {
+                        range->low.as_u16[k] = low_ports[i];
+                        range->hi.as_u16[k] = hi_ports[i];
+                        k++;
+                        if (k == 7)
+                          {
+                            k = 0;
+                            j++;
+                          }
+                        goto doublebreak2;
+                      }
+                  }
+                k = 0;
+                range++;
+              }
+            j = 0;
+            /* Too many ports specified... */
+            return VNET_API_ERROR_EXCEEDED_NUMBER_OF_PORTS_CAPACITY;
+
+          doublebreak2: ;
+          }
+
+        memset (&a, 0, sizeof(a));
+        a.flags = IP4_ROUTE_FLAG_FIB_INDEX;
+        a.table_index_or_table_id = fib_index;
+        a.dst_address = address[0];
+        a.dst_address_length = length;
+        a.add_adj = &template_adj;
+        a.n_add_adj = 1;
+
+        ip4_add_del_route (im, &a);
+      }
+    }
+
+  return 0;
+}
+
+static clib_error_t *
+ip_source_and_port_range_check_command_fn (vlib_main_t * vm,
+                                           unformat_input_t * input,
+                                           vlib_cli_command_t * cmd)
+{
+  u16 * low_ports = 0;
+  u16 * high_ports = 0;
+  u16 this_low;
+  u16 this_hi;
+  ip4_address_t addr;
+  u32 length;
+  u32 tmp, tmp2;
+  u8 prefix_set = 0;
+  u32 vrf_id = ~0;
+  int is_add = 1;
+  int rv;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "%U/%d", unformat_ip4_address, &addr, &length))
+        prefix_set = 1;
+      else if (unformat (input, "vrf %d", &vrf_id))
+        ;
+      else if (unformat (input, "del"))
+        is_add = 0;
+      else if (unformat (input, "port %d", &tmp))
+        {
+          if (tmp == 0 || tmp > 65535)
+            return clib_error_return (0, "port %d out of range", tmp);
+          this_low = tmp;
+          this_hi = this_low + 1;
+          vec_add1 (low_ports, this_low);
+          vec_add1 (high_ports, this_hi);
+        }
+      else if (unformat (input, "range %d - %d", &tmp, &tmp2))
+        {
+          if (tmp > tmp2)
+            return clib_error_return (0, "ports %d and %d out of order",
+                                      tmp, tmp2);
+          if (tmp == 0 || tmp > 65535)
+            return clib_error_return (0, "low port %d out of range", tmp);
+          if (tmp2 == 0 || tmp2 > 65535)
+            return clib_error_return (0, "hi port %d out of range", tmp2);
+          this_low = tmp;
+          this_hi = tmp2+1;
+          vec_add1 (low_ports, this_low);
+          vec_add1 (high_ports, this_hi);
+        }
+      else
+        break;
+    }
+
+  if (prefix_set == 0)
+    return clib_error_return (0, "<address>/<mask> not specified");
+
+  if (vrf_id == ~0)
+    return clib_error_return (0, "VRF ID required, not specified");
+
+  if (vrf_id == 0)
+    return clib_error_return (0, "VRF ID should not be default. Should be distinct VRF for this purpose. ");
+
+  if (vec_len(low_ports) == 0)
+    return clib_error_return (0, "At least one port or port range required");
+
+  rv = ip4_source_and_port_range_check_add_del
+    (&addr, length, vrf_id, low_ports, high_ports, is_add);
+
+  switch(rv)
+    {
+    case 0:
+      break;
+
+    case VNET_API_ERROR_INCORRECT_ADJACENCY_TYPE:
+      return clib_error_return
+        (0, "Incorrect adjacency for add/del operation in ip4 source and port-range check.");
+
+    case VNET_API_ERROR_EXCEEDED_NUMBER_OF_PORTS_CAPACITY:
+      return clib_error_return
+        (0, "Too many ports in add/del operation in ip4 source and port-range check.");
+
+    case VNET_API_ERROR_EXCEEDED_NUMBER_OF_RANGES_CAPACITY:
+      return clib_error_return
+        (0, "Too many ranges requested for add operation in ip4 source and port-range check.");
+
+    default:
+      return clib_error_return
+        (0, "ip4_source_and_port_range_check_add returned an unexpected value: %d", rv);
+    }
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (ip_source_and_port_range_check_command, static) = {
+  .path = "set ip source-and-port-range-check",
+  .function = ip_source_and_port_range_check_command_fn,
+  .short_help =
+  "set ip source-and-port-range-check <ip-addr>/<mask> range <nn>-<nn> vrf <id>",
+};
+
+
+static clib_error_t *
+show_source_and_port_range_check_fn (vlib_main_t * vm,
+                                     unformat_input_t * input,
+                                     vlib_cli_command_t * cmd)
+{
+  source_range_check_main_t * srm = & source_range_check_main;
+  ip4_main_t * im = &ip4_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  port_range_t * range;
+  u32 fib_index;
+  ip4_address_t addr;
+  u8 addr_set = 0;
+  u32 vrf_id = ~0;
+  int rv, i, j;
+  u32 adj_index;
+  ip_adjacency_t *adj;
+  u32 port = 0;
+  u8 * rwh;
+  uword * p;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "%U", unformat_ip4_address, &addr))
+        addr_set = 1;
+      else if (unformat (input, "vrf %d", &vrf_id))
+        ;
+      else if (unformat (input, "port %d", &port))
+        ;
+      else
+        break;
+    }
+
+  if (addr_set == 0)
+    return clib_error_return (0, "<address> not specified");
+
+  if (vrf_id == ~0)
+    return clib_error_return (0, "VRF ID required, not specified");
+
+  p = hash_get (im->fib_index_by_table_id, vrf_id);
+  if (p == 0)
+    return clib_error_return (0, "VRF %d not found", vrf_id);
+  fib_index = p[0];
+
+  adj_index = ip4_fib_lookup_with_table
+    (im, fib_index, &addr, 0 /* disable_default_route */);
+
+  adj = ip_get_adjacency (lm, adj_index);
+
+  if (adj->lookup_next_index != IP_LOOKUP_NEXT_ICMP_ERROR)
+    {
+      vlib_cli_output (vm, "%U: src address drop", format_ip4_address, &addr);
+      return 0;
+    }
+
+  if (port)
+    {
+      rv = check_adj_port_range_x1 (adj, (u16) port, 1234);
+      if (rv == 1234)
+        vlib_cli_output (vm, "%U port %d PASS", format_ip4_address,
+                         &addr, port);
+      else
+        vlib_cli_output (vm, "%U port %d FAIL", format_ip4_address,
+                         &addr, port);
+      return 0;
+    }
+  else
+    {
+      u8 * s;
+      rwh = (u8 *) (&adj->rewrite_header);
+
+      s = format (0, "%U: ", format_ip4_address, &addr);
+
+      range = (port_range_t *) rwh;
+
+      for (i = 0; i < srm->ranges_per_adjacency; i++)
+        {
+          for (j = 0; j < 8; j++)
+            {
+              if (range->low.as_u16[j])
+                s = format (s, "%d - %d ", (u32) range->low.as_u16[j],
+                            (u32) range->hi.as_u16[j]);
+            }
+          range++;
+        }
+      vlib_cli_output (vm, "%s", s);
+      vec_free(s);
+    }
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (show_source_and_port_range_check, static) = {
+  .path = "show ip source-and-port-range-check",
+  .function = show_source_and_port_range_check_fn,
+  .short_help =
+  "show ip source-and-port-range-check vrf <nn> <ip-addr> <port>",
+};
diff --git a/vnet/vnet/ip/ip_source_and_port_range_check.h b/vnet/vnet/ip/ip_source_and_port_range_check.h
new file mode 100644
index 0000000..7fbb2b0
--- /dev/null
+++ b/vnet/vnet/ip/ip_source_and_port_range_check.h
@@ -0,0 +1,31 @@
+/*
+ * 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_ip_source_and_port_range_check_h
+#define included_ip_ip_source_and_port_range_check_h
+
+int ip4_source_and_port_range_check_add_del (ip4_address_t * address,
+                                             u32 length,
+                                             u32 vrf_id,
+                                             u16 * low_ports,
+                                             u16 * hi_ports,
+                                             int is_add);
+
+int set_ip_source_and_port_range_check (vlib_main_t * vm,
+                                        u32 fib_index,
+                                        u32 sw_if_index,
+                                        u32 is_add);
+
+#endif /* included ip_source_and_port_range_check_h */
diff --git a/vnet/vnet/ip/lookup.c b/vnet/vnet/ip/lookup.c
index 126783a..8531f64 100644
--- a/vnet/vnet/ip/lookup.c
+++ b/vnet/vnet/ip/lookup.c
@@ -237,9 +237,10 @@
       adj[i].mcast_group_index = ~0;
       adj[i].classify.table_index = ~0;
       adj[i].saved_lookup_next_index = 0;
+      adj[i].special_adjacency_format_function_index = 0;
 
       if (copy_adj)
-	adj[i] = copy_adj[i];
+        adj[i] = copy_adj[i];
 
       adj[i].heap_handle = handle;
       adj[i].n_adj = n_adj;
@@ -950,7 +951,7 @@
 u8 * format_ip_flow_hash_config (u8 * s, va_list * args)
 {
   u32 flow_hash_config = va_arg (*args, u32);
-    
+
 #define _(n,v) if (flow_hash_config & v) s = format (s, "%s ", #n);
   foreach_flow_hash_bit;
 #undef _
@@ -973,8 +974,6 @@
       reg = vec_elt_at_index(lm->registered_adjacencies, n);
       if (reg->node_name) {
         s = format (s, "%s:", reg->node_name);
-      } else {
-        s = format (s, "unknown %d", n);
       }
       return s;
 
@@ -1010,6 +1009,33 @@
     return format (s, "%U", format_ip4_address_and_length, a, ia->address_length);
 }
 
+u32 vnet_register_special_adjacency_format_function
+(ip_lookup_main_t * lm, format_function_t * fp)
+{
+    u32 rv;
+    /*
+     * Initialize the format function registration vector
+     * Index 0 must be invalid, to avoid finding and fixing trivial bugs
+     * all over the place
+     */
+    if (vec_len (lm->special_adjacency_format_functions) == 0)
+      {
+        vec_add1 (lm->special_adjacency_format_functions,
+                  (format_function_t *) 0);
+      }
+
+    rv = vec_len (lm->special_adjacency_format_functions);
+    vec_add1 (lm->special_adjacency_format_functions, fp);
+    return rv;
+}
+
+/** @brief Pretty print helper function for formatting specific adjacencies.
+    @param s - input string to format
+    @param args - other args passed to format function such as:
+                  - vnet_main_t
+                  - ip_lookup_main_t
+                  - adj_index
+*/
 u8 * format_ip_adjacency (u8 * s, va_list * args)
 {
   vnet_main_t * vnm = va_arg (*args, vnet_main_t *);
@@ -1018,54 +1044,54 @@
   ip_adjacency_t * adj = ip_get_adjacency (lm, adj_index);
   ip_adj_register_t *reg;
 
+  if (adj->lookup_next_index < vec_len (lm->registered_adjacencies))
+    {
+      reg = vec_elt_at_index(lm->registered_adjacencies, 
+			     adj->lookup_next_index);
+      if (reg->fn) 
+	{
+	  s = format(s, " %U", reg->fn, lm, adj);
+	  goto format_done;
+	}
+    }
+  
   switch (adj->lookup_next_index)
     {
     case IP_LOOKUP_NEXT_REWRITE:
       s = format (s, "%U",
 		  format_vnet_rewrite,
-		  vnm->vlib_main, &adj->rewrite_header, sizeof (adj->rewrite_data));
+		  vnm->vlib_main, &adj->rewrite_header, 
+		  sizeof (adj->rewrite_data));
       break;
-
+      
+    case IP_LOOKUP_NEXT_ARP:
+      if (adj->if_address_index != ~0)
+	s = format (s, " %U", format_ip_interface_address, lm, 
+		    adj->if_address_index);
+      if (adj->arp.next_hop.ip6.as_u64[0] || adj->arp.next_hop.ip6.as_u64[1])
+	s = format (s, " via %U", format_ip46_address,
+		    &adj->arp.next_hop, IP46_TYPE_ANY);
+      break;
+    case IP_LOOKUP_NEXT_LOCAL:
+      if (adj->if_address_index != ~0)
+	s = format (s, " %U", format_ip_interface_address, lm, 
+		    adj->if_address_index);
+      break;
+      
+    case IP_LOOKUP_NEXT_CLASSIFY:
+      s = format (s, " table %d", adj->classify.table_index);
+      break;
+    case IP_LOOKUP_NEXT_INDIRECT:
+      s = format (s, " via %U", format_ip46_address,
+		  &adj->indirect.next_hop, IP46_TYPE_ANY);
+      break;
+      
     default:
-      s = format (s, "%U", format_ip_lookup_next, lm, adj->lookup_next_index);
-      if (adj->lookup_next_index == IP_LOOKUP_NEXT_ARP)
-	s = format (s, " %U",
-		    format_vnet_sw_interface_name,
-		    vnm,
-		    vnet_get_sw_interface (vnm, adj->rewrite_header.sw_if_index));
-      switch (adj->lookup_next_index)
-	{
-	case IP_LOOKUP_NEXT_ARP:
-	  if (adj->if_address_index != ~0)
-	    s = format (s, " %U", format_ip_interface_address, lm, adj->if_address_index);
-	  if (adj->arp.next_hop.ip6.as_u64[0] || adj->arp.next_hop.ip6.as_u64[1])
-	    s = format (s, " via %U", format_ip46_address,
-			&adj->arp.next_hop, IP46_TYPE_ANY);
-	  break;
-	case IP_LOOKUP_NEXT_LOCAL:
-	  if (adj->if_address_index != ~0)
-	    s = format (s, " %U", format_ip_interface_address, lm, adj->if_address_index);
-	  break;
-
-        case IP_LOOKUP_NEXT_CLASSIFY:
-            s = format (s, " table %d", adj->classify.table_index);
-            break;
-        case IP_LOOKUP_NEXT_INDIRECT:
-	    s = format (s, " via %U", format_ip46_address,
-			&adj->indirect.next_hop, IP46_TYPE_ANY);
-	    break;
-	default:
-	  //Fallback to registered format functions
-	  vec_validate(lm->registered_adjacencies, adj->lookup_next_index);
-	  reg = vec_elt_at_index(lm->registered_adjacencies, adj->lookup_next_index);
-	  if (reg->fn) {
-	    s = format(s, " ");
-	    s = reg->fn(s, lm, adj);
-	  }
-	  break;
-	}
+      s = format (s, " unknown %d", adj->lookup_next_index);
       break;
     }
+
+ format_done:
   if (adj->explicit_fib_index != ~0 && adj->explicit_fib_index != 0)
     s = format (s, " lookup fib index %d", adj->explicit_fib_index);
   if (adj->share_count > 0)
diff --git a/vnet/vnet/ip/lookup.h b/vnet/vnet/ip/lookup.h
index a66b9ed..7a6e856 100644
--- a/vnet/vnet/ip/lookup.h
+++ b/vnet/vnet/ip/lookup.h
@@ -45,40 +45,42 @@
 #include <vnet/ip/ip4_packet.h>
 #include <vnet/ip/ip6_packet.h>
 
-/* Common (IP4/IP6) next index stored in adjacency. */
+/** @brief Common (IP4/IP6) next index stored in adjacency. */
 typedef enum {
-  /* Packet does not match any route in table. */
+  /** Packet does not match any route in table. */
   IP_LOOKUP_NEXT_MISS,
 
-  /* Adjacency says to drop or punt this packet. */
+  /** Adjacency to drop this packet. */
   IP_LOOKUP_NEXT_DROP,
+  /** Adjacency to punt this packet. */
   IP_LOOKUP_NEXT_PUNT,
 
-  /* This packet is for one of our own IP addresses. */
+  /** This packet is for one of our own IP addresses. */
   IP_LOOKUP_NEXT_LOCAL,
 
-  /* This packet matches an "interface route" and packets
+  /** This packet matches an "interface route" and packets
      need to be passed to ARP to find rewrite string for
      this destination. */
   IP_LOOKUP_NEXT_ARP,
 
-  /* This packet is to be rewritten and forwarded to the next
+  /** This packet is to be rewritten and forwarded to the next
      processing node.  This is typically the output interface but
      might be another node for further output processing. */
   IP_LOOKUP_NEXT_REWRITE,
 
-  /* This packet needs to be classified */
+  /** This packet needs to be classified */
   IP_LOOKUP_NEXT_CLASSIFY,
 
-  /* This packet needs to go to MAP - RFC7596, RFC7597 */
+  /** This packet needs to go to MAP - RFC7596, RFC7597 */
   IP_LOOKUP_NEXT_MAP,
 
-  /* This packet needs to go to MAP with Translation - RFC7599 */
+  /** This packet needs to go to MAP with Translation - RFC7599 */
   IP_LOOKUP_NEXT_MAP_T,
 
-  /* This packets needs to go to indirect next hop */
+  /** This packets needs to go to indirect next hop */
   IP_LOOKUP_NEXT_INDIRECT,
 
+  /** This packets needs to go to ICMP error */
   IP_LOOKUP_NEXT_ICMP_ERROR,
 
   IP_LOOKUP_N_NEXT,
@@ -191,6 +193,13 @@
     u8 opaque[IP_ADJACENCY_OPAQUE_SZ];
   };
 
+  /* 
+   * Special format function for this adjacency.
+   * Specifically good for cases which use the entire rewrite
+   * for their own purposes. Can easily reduce to a u16 or a u8 if/when
+   * the first cache line reads "full" on the free space gas gauge.
+   */
+  u32 special_adjacency_format_function_index;  /* 0 is invalid */
   STRUCT_MARK(signature_end);
 
   /* Number of FIB entries sharing this adjacency */
@@ -429,6 +438,9 @@
   /* Either format_ip4_address_and_length or format_ip6_address_and_length. */
   format_function_t * format_address_and_length;
 
+  /* Special adjacency format functions */
+  format_function_t ** special_adjacency_format_functions;
+
   /* Table mapping ip protocol to ip[46]-local node next index. */
   u8 local_next_by_ip_protocol[256];
 
@@ -610,5 +622,7 @@
 } while (0)
 
 void ip_lookup_init (ip_lookup_main_t * lm, u32 ip_lookup_node_index);
+u32 vnet_register_special_adjacency_format_function 
+(ip_lookup_main_t * lm, format_function_t * fp);
 
 #endif /* included_ip_lookup_h */