Initial commit of vpp code.
[vpp.git] / vnet / vnet / sr / sr.c
diff --git a/vnet/vnet/sr/sr.c b/vnet/vnet/sr/sr.c
new file mode 100644 (file)
index 0000000..5cc33dc
--- /dev/null
@@ -0,0 +1,2228 @@
+/*
+ * sr.c: ipv6 segment routing
+ *
+ * Copyright (c) 2013 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vnet/vnet.h>
+#include <vnet/sr/sr.h>
+
+#include <openssl/hmac.h>
+
+ip6_sr_main_t sr_main;
+static vlib_node_registration_t sr_local_node;
+
+static void sr_fix_hmac (ip6_sr_main_t * sm, ip6_header_t * ip, 
+                         ip6_sr_header_t * sr)
+{
+  u32 key_index;
+  static u8 * keybuf;
+  u8 * copy_target;
+  int first_segment;
+  ip6_address_t *addrp;
+  int i;
+  ip6_sr_hmac_key_t * hmac_key;
+  u32 sig_len;
+
+  key_index = sr->hmac_key;
+
+  /* No signature? Pass... */
+  if (key_index == 0)
+    return;
+
+  /* We don't know about this key? Fail... */
+  if (key_index >= vec_len (sm->hmac_keys))
+    return;
+
+  hmac_key = sm->hmac_keys + key_index;
+
+  vec_reset_length (keybuf);
+
+  /* pkt ip6 src address */
+  vec_add2 (keybuf, copy_target, sizeof (ip6_address_t));
+  memcpy (copy_target, ip->src_address.as_u8, sizeof (ip6_address_t));
+
+  /* first segment */
+  vec_add2 (keybuf, copy_target, 1);
+  copy_target[0] = sr->first_segment;
+
+  /* octet w/ bit 0 = "clean" flag */
+  vec_add2 (keybuf, copy_target, 1);
+  copy_target[0] 
+    = (sr->flags & clib_host_to_net_u16 (IP6_SR_HEADER_FLAG_CLEANUP)) 
+    ? 0x80 : 0;
+
+  /* hmac key id */
+  vec_add2 (keybuf, copy_target, 1);
+  copy_target[0] = sr->hmac_key;
+
+  first_segment = sr->first_segment;
+
+  addrp = sr->segments;
+
+  /* segments */
+  for (i = 0; i <= first_segment; i++)
+    {
+      vec_add2 (keybuf, copy_target, sizeof (ip6_address_t));
+      memcpy (copy_target, addrp->as_u8, sizeof (ip6_address_t));
+      addrp++;
+    }
+
+  addrp++;
+
+  HMAC_CTX_init(sm->hmac_ctx);
+  if (!HMAC_Init(sm->hmac_ctx, hmac_key->shared_secret,
+                 vec_len(hmac_key->shared_secret),sm->md))
+      clib_warning ("barf1");
+  if (!HMAC_Update(sm->hmac_ctx,keybuf,vec_len(keybuf)))
+      clib_warning ("barf2");
+  if (!HMAC_Final(sm->hmac_ctx, (unsigned char *) addrp, &sig_len))
+      clib_warning ("barf3");
+  HMAC_CTX_cleanup(sm->hmac_ctx);
+}
+
+u8 * format_ip6_sr_header_flags (u8 * s, va_list * args)
+{
+  u16 flags = (u16) va_arg (*args, int);
+  u8 pl_flag;
+  int bswap_needed = va_arg (*args, int);
+  int i;
+
+  if (bswap_needed)
+      flags = clib_host_to_net_u16 (flags);
+
+  if (flags & IP6_SR_HEADER_FLAG_CLEANUP)
+      s = format (s, "cleanup ");
+
+  if (flags & IP6_SR_HEADER_FLAG_PROTECTED)
+      s = format (s, "reroute ");
+
+  s = format (s, "pl: ");
+  for (i = 1; i <= 4; i++)
+    {
+      pl_flag = ip6_sr_policy_list_flags (flags, i);
+      s = format (s, "[%d] ", i);
+
+      switch (pl_flag)
+        {
+        case IP6_SR_HEADER_FLAG_PL_ELT_NOT_PRESENT:
+          s = format (s, "NotPr ");
+          break;
+        case IP6_SR_HEADER_FLAG_PL_ELT_INGRESS_PE:
+          s = format (s, "InPE ");
+          break;
+        case IP6_SR_HEADER_FLAG_PL_ELT_EGRESS_PE:
+          s = format (s, "EgPE ");
+          break;
+
+        case IP6_SR_HEADER_FLAG_PL_ELT_ORIG_SRC_ADDR:
+          s = format (s, "OrgSrc ");
+          break;
+        }
+    }
+  return s;
+}
+
+u8 * format_ip6_sr_header (u8 * s, va_list * args)
+{
+  ip6_sr_header_t * h = va_arg (*args, ip6_sr_header_t *);
+  int print_hmac = va_arg (*args, int);
+  int i, pl_index, max_segs;
+  int flags_host_byte_order = clib_net_to_host_u16(h->flags);
+
+  s = format (s, "next proto %d, len %d, type %d",
+              h->protocol, (h->length<<3)+8, h->type);
+  s = format (s, "\n      segs left %d, first_segment %d, hmac key %d",
+              h->segments_left, h->first_segment, h->hmac_key);
+  s = format (s, "\n      flags %U", format_ip6_sr_header_flags, 
+              flags_host_byte_order, 0 /* bswap needed */ );
+
+  /* 
+   * Header length is in 8-byte units (minus one), so
+   * divide by 2 to ascertain the number of ip6 addresses in the
+   * segment list
+   */
+  max_segs = (h->length>>1);
+
+  if (!print_hmac && h->hmac_key)
+    max_segs -= 2;
+
+  s = format (s, "\n  Segments (in processing order):");
+
+  for (i = h->first_segment; i >= 0; i--)
+    s = format (s, "\n  %U", format_ip6_address, h->segments + i);
+
+  s = format (s, "\n  Policy List:");
+
+  pl_index = 1;                 /* to match the RFC text */
+  for (i = (h->first_segment+1); i < max_segs; i++, pl_index++)
+    {
+      char * tag;
+      char * tags[] = {" ", "InPE: ", "EgPE: ", "OrgSrc: "};
+
+      tag = tags[0];
+      if (pl_index >=1 && pl_index <= 4)
+        {
+          int this_pl_flag = ip6_sr_policy_list_flags 
+            (flags_host_byte_order, pl_index);
+          tag = tags[this_pl_flag];
+        }
+
+      s = format (s, "\n  %s%U", tag, format_ip6_address, h->segments + i);
+    }
+
+  return s;
+}
+
+u8 * format_ip6_sr_header_with_length (u8 * s, va_list * args)
+{
+  ip6_header_t * h = va_arg (*args, ip6_header_t *);
+  u32 max_header_bytes = va_arg (*args, u32);
+  uword header_bytes;
+
+  header_bytes = sizeof (h[0]) + sizeof (ip6_sr_header_t);
+  if (max_header_bytes != 0 && header_bytes > max_header_bytes)
+    return format (s, "ip6_sr header truncated");
+
+  s = format (s, "IP6: %U\n", format_ip6_header, h, max_header_bytes);
+  s = format (s, "SR: %U\n", format_ip6_sr_header, (ip6_sr_header_t *)(h+1), 
+              0 /* print_hmac */, max_header_bytes);
+  return s;
+}
+
+#define foreach_sr_rewrite_next                 \
+_(ERROR, "error-drop")                          \
+_(IP6_LOOKUP, "ip6-lookup")                     \
+_(SR_LOCAL, "sr-local")
+
+typedef enum {
+#define _(s,n) SR_REWRITE_NEXT_##s,
+  foreach_sr_rewrite_next
+#undef _
+  SR_REWRITE_N_NEXT,
+} sr_rewrite_next_t;
+
+typedef struct {
+  ip6_address_t src, dst;
+  u16 length;
+  u32 next_index;
+  u32 tunnel_index;
+  u8 sr[256];
+} sr_rewrite_trace_t;
+
+static char * sr_rewrite_error_strings[] = {
+#define sr_error(n,s) s,
+#include "sr_error.def"
+#undef sr_error
+};
+
+typedef enum {
+#define sr_error(n,s) SR_REWRITE_ERROR_##n,
+#include "sr_error.def"
+#undef sr_error
+  SR_REWRITE_N_ERROR,
+} sr_rewrite_error_t;
+
+
+u8 * format_sr_rewrite_trace (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+  sr_rewrite_trace_t * t = va_arg (*args, sr_rewrite_trace_t *);
+  ip6_main_t * im = &ip6_main;
+  ip6_sr_main_t * sm = &sr_main;
+  ip6_sr_tunnel_t *tun = pool_elt_at_index (sm->tunnels, t->tunnel_index);
+  ip6_fib_t * rx_fib, * tx_fib;
+  
+  rx_fib = find_ip6_fib_by_table_index_or_id (im, tun->rx_fib_index, 
+                                              IP6_ROUTE_FLAG_FIB_INDEX);
+
+  tx_fib = find_ip6_fib_by_table_index_or_id (im, tun->tx_fib_index, 
+                                              IP6_ROUTE_FLAG_FIB_INDEX);
+
+  s = format 
+    (s, "SR-REWRITE: next %s ip6 src %U dst %U len %u\n" 
+     "           rx-fib-id %d tx-fib-id %d\n%U", 
+     (t->next_index == SR_REWRITE_NEXT_SR_LOCAL) 
+     ? "sr-local" : "ip6-lookup",
+     format_ip6_address, &t->src, 
+     format_ip6_address, &t->dst, t->length, 
+     rx_fib->table_id, tx_fib->table_id, 
+     format_ip6_sr_header, t->sr, 0 /* print_hmac */);
+  return s;
+}
+
+static uword
+sr_rewrite (vlib_main_t * vm,
+                   vlib_node_runtime_t * node,
+                   vlib_frame_t * from_frame)
+{
+  u32 n_left_from, next_index, * from, * to_next;
+  ip6_main_t * im = &ip6_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  ip6_sr_main_t * sm = &sr_main;
+  u32 (*sr_local_cb) (vlib_main_t *, vlib_node_runtime_t *,
+                      vlib_buffer_t *, ip6_header_t *, 
+                      ip6_sr_header_t *);
+  sr_local_cb = sm->sr_local_cb;
+
+  from = vlib_frame_vector_args (from_frame);
+  n_left_from = 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)
+       {
+         u32 bi0, bi1;
+         vlib_buffer_t * b0, * b1;
+          ip6_header_t * ip0, * ip1;
+          ip_adjacency_t * adj0, * adj1;
+          ip6_sr_header_t * sr0, * sr1;
+          ip6_sr_tunnel_t * t0, *t1;
+          u64 * copy_src0, * copy_dst0;
+          u64 * copy_src1, * copy_dst1;
+         u32 next0 = SR_REWRITE_NEXT_IP6_LOOKUP;
+         u32 next1 = SR_REWRITE_NEXT_IP6_LOOKUP;
+          u16 new_l0, new_l1;
+
+         /* 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);
+         }
+
+         bi0 = from[0];
+         bi1 = from[1];
+         to_next[0] = bi0;
+         to_next[1] = bi1;
+         from += 2;
+         to_next += 2;
+         n_left_to_next -= 2;
+         n_left_from -= 2;
+
+         b0 = vlib_get_buffer (vm, bi0);
+         b1 = vlib_get_buffer (vm, bi1);
+
+          /* 
+           * $$$ parse through header(s) to pick the point
+           * where we punch in the SR extention header
+           */
+          
+          adj0 = ip_get_adjacency (lm, vnet_buffer(b0)->ip.adj_index[VLIB_TX]);
+          adj1 = ip_get_adjacency (lm, vnet_buffer(b1)->ip.adj_index[VLIB_TX]);
+          t0 = pool_elt_at_index (sm->tunnels, 
+                                  adj0->rewrite_header.sw_if_index);
+          t1 = pool_elt_at_index (sm->tunnels, 
+                                  adj1->rewrite_header.sw_if_index);
+
+          ASSERT (VLIB_BUFFER_PRE_DATA_SIZE
+                  >= ((word) vec_len (t0->rewrite)) + b0->current_data);
+          ASSERT (VLIB_BUFFER_PRE_DATA_SIZE
+                  >= ((word) vec_len (t1->rewrite)) + b1->current_data);
+          
+          vnet_buffer(b0)->sw_if_index[VLIB_TX] = t0->tx_fib_index;
+          vnet_buffer(b1)->sw_if_index[VLIB_TX] = t1->tx_fib_index;
+
+          ip0 = vlib_buffer_get_current (b0);
+          ip1 = vlib_buffer_get_current (b1);
+
+          /* 
+           * SR-unaware service chaining case: pkt coming back from
+           * service has the original dst address, and will already
+           * have an SR header. If so, send it to sr-local 
+           */
+          if (PREDICT_FALSE(ip0->protocol == 43))
+            {
+              vlib_buffer_advance (b0, sizeof(ip0));
+              sr0 = (ip6_sr_header_t *) (ip0+1);
+              new_l0 = clib_net_to_host_u16(ip0->payload_length);
+              next0 = SR_REWRITE_NEXT_SR_LOCAL;
+            }
+          else
+            {
+              copy_dst0 = (u64 *)(((u8 *)ip0) - vec_len (t0->rewrite));
+              copy_src0 = (u64 *) ip0;
+
+              /* 
+               * Copy data before the punch-in point left by the 
+               * required amount. Assume (for the moment) that only 
+               * the main packet header needs to be copied.
+               */
+              copy_dst0 [0] = copy_src0 [0];
+              copy_dst0 [1] = copy_src0 [1];
+              copy_dst0 [2] = copy_src0 [2];
+              copy_dst0 [3] = copy_src0 [3];
+              copy_dst0 [4] = copy_src0 [4];
+              vlib_buffer_advance (b0, - (word) vec_len(t0->rewrite));
+              ip0 = vlib_buffer_get_current (b0);
+              sr0 = (ip6_sr_header_t *) (ip0+1);
+              /* $$$ tune */
+              memcpy (sr0, t0->rewrite, vec_len (t0->rewrite));
+              /* Fix the next header chain */
+              sr0->protocol = ip0->protocol;
+              ip0->protocol = 43; /* routing extension header */
+              new_l0 = clib_net_to_host_u16(ip0->payload_length) +
+                vec_len (t0->rewrite);
+              ip0->payload_length = clib_host_to_net_u16(new_l0);
+              /* Rewrite the ip6 dst address */
+              ip0->dst_address.as_u64[0] = t0->first_hop.as_u64[0];
+              ip0->dst_address.as_u64[1] = t0->first_hop.as_u64[1];
+
+              sr_fix_hmac (sm, ip0, sr0);
+
+              next0 = sr_local_cb ? sr_local_cb (vm, node, b0, ip0, sr0) :
+                  next0;
+
+              /* 
+               * Ignore "do not rewrite" shtik in this path
+               */
+              if (PREDICT_FALSE (next0 & 0x80000000))
+              {
+                  next0 ^= 0xFFFFFFFF;
+                  if (PREDICT_FALSE(next0 == SR_REWRITE_NEXT_ERROR))
+                      b0->error = 
+                          node->errors[SR_REWRITE_ERROR_APP_CALLBACK];
+              }
+            }
+
+          if (PREDICT_FALSE(ip1->protocol == 43))
+            {
+              vlib_buffer_advance (b1, sizeof(ip1));
+              sr1 = (ip6_sr_header_t *) (ip1+1);
+              new_l1 = clib_net_to_host_u16(ip1->payload_length);
+              next1 = SR_REWRITE_NEXT_SR_LOCAL;
+            }
+          else
+            {
+              copy_dst1 = (u64 *)(((u8 *)ip1) - vec_len (t1->rewrite));
+              copy_src1 = (u64 *) ip1;
+              
+              copy_dst1 [0] = copy_src1 [0];
+              copy_dst1 [1] = copy_src1 [1];
+              copy_dst1 [2] = copy_src1 [2];
+              copy_dst1 [3] = copy_src1 [3];
+              copy_dst1 [4] = copy_src1 [4];
+              vlib_buffer_advance (b1, - (word) vec_len(t1->rewrite));
+              ip1 = vlib_buffer_get_current (b1);
+              sr1 = (ip6_sr_header_t *) (ip1+1);
+              memcpy (sr1, t1->rewrite, vec_len (t1->rewrite));
+              sr1->protocol = ip1->protocol;
+              ip1->protocol = 43; 
+              new_l1 = clib_net_to_host_u16(ip1->payload_length) +
+                vec_len (t1->rewrite);
+              ip1->payload_length = clib_host_to_net_u16(new_l1);
+              ip1->dst_address.as_u64[0] = t1->first_hop.as_u64[0];
+              ip1->dst_address.as_u64[1] = t1->first_hop.as_u64[1];
+
+              sr_fix_hmac (sm, ip1, sr1);
+
+              next1 = sr_local_cb ? sr_local_cb (vm, node, b1, ip1, sr1) :
+                  next1;
+
+              /* 
+               * Ignore "do not rewrite" shtik in this path
+               */
+              if (PREDICT_FALSE (next1 & 0x80000000))
+              {
+                  next1 ^= 0xFFFFFFFF;
+                  if (PREDICT_FALSE(next1 == SR_REWRITE_NEXT_ERROR))
+                      b1->error = 
+                          node->errors[SR_REWRITE_ERROR_APP_CALLBACK];
+              }
+            }
+
+          if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) 
+            {
+              sr_rewrite_trace_t *tr = vlib_add_trace (vm, node, 
+                                                       b0, sizeof (*tr));
+              tr->tunnel_index = t0 - sm->tunnels;
+              memcpy (tr->src.as_u8, ip0->src_address.as_u8,
+                      sizeof (tr->src.as_u8));
+              memcpy (tr->dst.as_u8, ip0->dst_address.as_u8,
+                      sizeof (tr->dst.as_u8));
+              tr->length = new_l0;
+              tr->next_index = next0;
+              memcpy (tr->sr, sr0, sizeof (tr->sr));
+            }
+          if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED)) 
+            {
+              sr_rewrite_trace_t *tr = vlib_add_trace (vm, node, 
+                                                       b1, sizeof (*tr));
+              tr->tunnel_index = t1 - sm->tunnels;
+              memcpy (tr->src.as_u8, ip1->src_address.as_u8,
+                      sizeof (tr->src.as_u8));
+              memcpy (tr->dst.as_u8, ip1->dst_address.as_u8,
+                      sizeof (tr->dst.as_u8));
+              tr->length = new_l1;
+              tr->next_index = next1;
+              memcpy (tr->sr, sr1, sizeof (tr->sr));
+            }
+
+         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)
+       {
+         u32 bi0;
+         vlib_buffer_t * b0;
+          ip6_header_t * ip0;
+          ip_adjacency_t * adj0;
+          ip6_sr_header_t * sr0;
+          ip6_sr_tunnel_t * t0;
+          u64 * copy_src0, * copy_dst0;
+         u32 next0 = SR_REWRITE_NEXT_IP6_LOOKUP;
+          u16 new_l0;
+
+         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);
+
+          /* 
+           * $$$ parse through header(s) to pick the point
+           * where we punch in the SR extention header
+           */
+          
+          adj0 = ip_get_adjacency (lm, vnet_buffer(b0)->ip.adj_index[VLIB_TX]);
+          t0 = pool_elt_at_index (sm->tunnels, 
+                                  adj0->rewrite_header.sw_if_index);
+
+          ASSERT (VLIB_BUFFER_PRE_DATA_SIZE
+                  >= ((word) vec_len (t0->rewrite)) + b0->current_data);
+          
+          vnet_buffer(b0)->sw_if_index[VLIB_TX] = t0->tx_fib_index;
+
+          ip0 = vlib_buffer_get_current (b0);
+
+          /* 
+           * SR-unaware service chaining case: pkt coming back from
+           * service has the original dst address, and will already
+           * have an SR header. If so, send it to sr-local 
+           */
+          if (PREDICT_FALSE(ip0->protocol == 43))
+            {
+              vlib_buffer_advance (b0, sizeof(ip0));
+              sr0 = (ip6_sr_header_t *) (ip0+1);
+              new_l0 = clib_net_to_host_u16(ip0->payload_length);
+              next0 = SR_REWRITE_NEXT_SR_LOCAL;
+            }
+          else
+            {
+              copy_dst0 = (u64 *)(((u8 *)ip0) - vec_len (t0->rewrite));
+              copy_src0 = (u64 *) ip0;
+
+              /* 
+               * Copy data before the punch-in point left by the 
+               * required amount. Assume (for the moment) that only 
+               * the main packet header needs to be copied.
+               */
+              copy_dst0 [0] = copy_src0 [0];
+              copy_dst0 [1] = copy_src0 [1];
+              copy_dst0 [2] = copy_src0 [2];
+              copy_dst0 [3] = copy_src0 [3];
+              copy_dst0 [4] = copy_src0 [4];
+              vlib_buffer_advance (b0, - (word) vec_len(t0->rewrite));
+              ip0 = vlib_buffer_get_current (b0);
+              sr0 = (ip6_sr_header_t *) (ip0+1);
+              /* $$$ tune */
+              memcpy (sr0, t0->rewrite, vec_len (t0->rewrite));
+              /* Fix the next header chain */
+              sr0->protocol = ip0->protocol;
+              ip0->protocol = 43; /* routing extension header */
+              new_l0 = clib_net_to_host_u16(ip0->payload_length) +
+                vec_len (t0->rewrite);
+              ip0->payload_length = clib_host_to_net_u16(new_l0);
+              /* Rewrite the ip6 dst address */
+              ip0->dst_address.as_u64[0] = t0->first_hop.as_u64[0];
+              ip0->dst_address.as_u64[1] = t0->first_hop.as_u64[1];
+
+              sr_fix_hmac (sm, ip0, sr0);
+
+              next0 = sr_local_cb ? sr_local_cb (vm, node, b0, ip0, sr0) :
+                  next0;
+
+              /* 
+               * Ignore "do not rewrite" shtik in this path
+               */
+              if (PREDICT_FALSE (next0 & 0x80000000))
+              {
+                  next0 ^= 0xFFFFFFFF;
+                  if (PREDICT_FALSE(next0 == SR_REWRITE_NEXT_ERROR))
+                      b0->error = 
+                          node->errors[SR_REWRITE_ERROR_APP_CALLBACK];
+              }
+            }
+
+          if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) 
+            {
+              sr_rewrite_trace_t *tr = vlib_add_trace (vm, node, 
+                                                       b0, sizeof (*tr));
+              tr->tunnel_index = t0 - sm->tunnels;
+              memcpy (tr->src.as_u8, ip0->src_address.as_u8,
+                      sizeof (tr->src.as_u8));
+              memcpy (tr->dst.as_u8, ip0->dst_address.as_u8,
+                      sizeof (tr->dst.as_u8));
+              tr->length = new_l0;
+              tr->next_index = next0;
+              memcpy (tr->sr, sr0, sizeof (tr->sr));
+            }
+
+         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);
+    }
+  return from_frame->n_vectors;
+}
+
+VLIB_REGISTER_NODE (sr_rewrite_node) = {
+  .function = sr_rewrite,
+  .name = "sr-rewrite",
+  /* Takes a vector of packets. */
+  .vector_size = sizeof (u32),
+  .format_trace = format_sr_rewrite_trace,
+  .format_buffer = format_ip6_sr_header_with_length,
+
+  .n_errors = SR_REWRITE_N_ERROR,
+  .error_strings = sr_rewrite_error_strings,
+
+  .runtime_data_bytes = 0,
+
+  .n_next_nodes = SR_REWRITE_N_NEXT,
+  .next_nodes = {
+#define _(s,n) [SR_REWRITE_NEXT_##s] = n,
+    foreach_sr_rewrite_next
+#undef _
+  },
+};
+
+static int ip6_delete_route_no_next_hop (ip6_address_t *dst_address_arg, 
+                                         u32 dst_address_length, 
+                                         u32 rx_table_id)
+{
+  ip6_add_del_route_args_t a;
+  ip6_address_t dst_address;
+  ip6_fib_t * fib;
+  ip6_main_t * im6 = &ip6_main;
+  BVT(clib_bihash_kv) kv, value;
+  
+  fib = find_ip6_fib_by_table_index_or_id (im6, rx_table_id, 
+                                           IP6_ROUTE_FLAG_TABLE_ID);
+  memset (&a, 0, sizeof (a));
+  a.flags |= IP4_ROUTE_FLAG_DEL;
+  a.dst_address_length = dst_address_length;
+
+  dst_address = *dst_address_arg;
+  
+  ip6_address_mask (&dst_address, 
+                    &im6->fib_masks[dst_address_length]);
+  
+  kv.key[0] = dst_address.as_u64[0];
+  kv.key[1] = dst_address.as_u64[1];
+  kv.key[2] = ((u64)((fib - im6->fibs))<<32) | dst_address_length;
+  
+  if (BV(clib_bihash_search)(&im6->ip6_lookup_table, &kv, &value) < 0)
+    {
+      clib_warning ("%U/%d not in FIB",
+                    format_ip6_address, &a.dst_address,
+                    a.dst_address_length);
+      return -10;
+    }
+
+  a.adj_index = value.value;
+  a.dst_address = dst_address;
+  
+  ip6_add_del_route (im6, &a);
+  ip6_maybe_remap_adjacencies (im6, rx_table_id, IP6_ROUTE_FLAG_TABLE_ID);
+  return 0;
+}
+
+static ip6_sr_hmac_key_t * 
+find_or_add_shared_secret (ip6_sr_main_t * sm, u8 * secret, u32 * indexp)
+{
+  uword * p;
+  ip6_sr_hmac_key_t * key = 0;
+  int i;
+
+  p = hash_get_mem (sm->hmac_key_by_shared_secret, secret);
+
+  if (p)
+    {
+      key = vec_elt_at_index (sm->hmac_keys, p[0]);
+      if (indexp)
+        *indexp = p[0];
+      return (key);
+    }
+
+  /* Specific key ID? */
+  if (indexp && *indexp)
+    {
+      vec_validate (sm->hmac_keys, *indexp);
+      key = sm->hmac_keys + *indexp;
+    }
+  else
+    {
+      for (i = 0; i < vec_len (sm->hmac_keys); i++)
+        {
+          if (sm->hmac_keys[i].shared_secret == 0)
+            key = sm->hmac_keys + i;
+          goto found;
+        }
+      vec_validate (sm->hmac_keys, i);
+      key = sm->hmac_keys + i;
+    found:
+      ;
+    }
+
+  key->shared_secret = vec_dup (secret);
+
+  hash_set_mem (sm->hmac_key_by_shared_secret, key->shared_secret, 
+                key - sm->hmac_keys);
+
+  if (indexp)
+    *indexp = key - sm->hmac_keys;
+  return (key);
+}
+
+int ip6_sr_add_del_tunnel (ip6_sr_add_del_tunnel_args_t * a)
+{
+  ip6_main_t * im = &ip6_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  ip6_sr_tunnel_key_t key;
+  ip6_sr_tunnel_t * t;
+  uword * p;
+  ip6_sr_header_t * h = 0;
+  u32 header_length;
+  ip6_address_t * addrp, *this_address;
+  ip_adjacency_t adj, * ap, * add_adj = 0;
+  u32 adj_index;
+  ip6_sr_main_t * sm = &sr_main;
+  u8 * key_copy;
+  u32 rx_fib_index, tx_fib_index;
+  ip6_add_del_route_args_t aa;
+  u32 hmac_key_index_u32;
+  u8 hmac_key_index = 0;
+
+  /* Make sure that the rx FIB exists */
+  p = hash_get (im->fib_index_by_table_id, a->rx_table_id);
+  
+  if (p == 0)
+    return -3;
+
+  /* remember the FIB index */
+  rx_fib_index = p[0];
+
+  /* Make sure that the supplied FIB exists */
+  p = hash_get (im->fib_index_by_table_id, a->tx_table_id);
+  
+  if (p == 0)
+    return -4;
+
+  /* remember the FIB index */
+  tx_fib_index = p[0];
+
+  memcpy (key.src.as_u8, a->src_address->as_u8, sizeof (key.src));
+  memcpy (key.dst.as_u8, a->dst_address->as_u8, sizeof (key.dst));
+
+  p = hash_get_mem (sm->tunnel_index_by_key, &key);
+
+  if (p)
+    {
+      if (a->is_del)
+        {
+          hash_pair_t *hp;
+          
+          /* Delete existing tunnel */
+          t = pool_elt_at_index (sm->tunnels, p[0]);
+
+          ip6_delete_route_no_next_hop (&t->key.dst, t->dst_mask_width, 
+                                        a->rx_table_id);
+          vec_free (t->rewrite);
+          pool_put (sm->tunnels, t);
+          hp = hash_get_pair (sm->tunnel_index_by_key, &key);
+          key_copy = (void *)(hp->key);
+          hash_unset_mem (sm->tunnel_index_by_key, &key);
+          vec_free (key_copy);
+          return 0;
+        }
+      else /* create; tunnel already exists; complain */
+        return -1;
+    }
+  else
+    {
+      /* delete; tunnel does not exist; complain */
+      if (a->is_del)
+        return -2;
+    }
+
+  /* create a new tunnel */
+  pool_get (sm->tunnels, t);
+  memset (t, 0, sizeof (*t));
+
+  memcpy (&t->key, &key, sizeof (t->key));
+  t->dst_mask_width = a->dst_mask_width;
+  t->rx_fib_index = rx_fib_index;
+  t->tx_fib_index = tx_fib_index;
+  
+  /* The first specified hop goes right into the dst address */
+  if (vec_len(a->segments))
+    {
+      t->first_hop = a->segments[0];
+      /* It won't feel nice if we do it twice */
+      vec_delete (a->segments, 1, 0);
+    }
+  else  /* there must be at least one segment... */
+      return -4;
+
+  /* 
+   * Create the sr header rewrite string
+   * We append the dst address to the set of next hops
+   * so the ultimate recipient can tell where the
+   * packet entered the SR domain
+   */
+  header_length = sizeof (*h) + 
+    sizeof (ip6_address_t) * (vec_len (a->segments) + vec_len (a->tags));
+  
+  if (a->shared_secret)
+    {
+      /* Allocate a new key slot if we don't find the secret key */
+      hmac_key_index_u32 = 0;
+      (void) find_or_add_shared_secret (sm, a->shared_secret, 
+                                        &hmac_key_index_u32);
+
+      /* Hey Vinz Clortho: Gozzer is pissed.. you're out of keys! */
+      if (hmac_key_index_u32 >= 256)
+        return -5;
+      hmac_key_index = hmac_key_index_u32;
+      header_length += SHA256_DIGEST_LENGTH;
+    }
+
+  vec_validate (t->rewrite, header_length-1);
+
+  h = (ip6_sr_header_t *) t->rewrite;
+
+  h->protocol = 0xFF; /* we don't know yet */
+
+  h->length = (header_length/8) - 1;
+  h->type = ROUTING_HEADER_TYPE_SR;
+  h->segments_left = vec_len (a->segments);
+  h->first_segment = vec_len(a->segments) -1;
+  if (a->shared_secret)
+    h->hmac_key = hmac_key_index & 0xFF;
+
+  h->flags = a->flags_net_byte_order;
+
+  /* Paint on the segment list, in reverse */
+  addrp = h->segments + (vec_len (a->segments) - 1);
+
+  vec_foreach (this_address, a->segments)
+    {
+      memcpy (addrp->as_u8, this_address->as_u8, sizeof (ip6_address_t));
+      addrp--;
+    }
+
+  /* Paint on the tag list, not reversed */
+  addrp = h->segments + vec_len(a->segments);
+
+  vec_foreach (this_address, a->tags)
+    {
+      memcpy (addrp->as_u8, this_address->as_u8, sizeof (ip6_address_t));
+      addrp++;
+    }
+
+  key_copy = vec_new (ip6_sr_tunnel_key_t, 1);
+  memcpy (key_copy, &key, sizeof (ip6_sr_tunnel_key_t));
+  hash_set_mem (sm->tunnel_index_by_key, key_copy, t - sm->tunnels);
+
+  memset(&adj, 0, sizeof (adj));
+
+  /* Create an adjacency and add to v6 fib */
+  adj.lookup_next_index = IP_LOOKUP_NEXT_REWRITE;
+  adj.lookup_next_index = sm->ip6_lookup_sr_next_index;
+  adj.explicit_fib_index = ~0;
+  
+  ap = ip_add_adjacency (lm, &adj, 1 /* one adj */,
+                         &adj_index);
+
+  /* 
+   * Stick the tunnel index into the rewrite header.
+   * 
+   * Unfortunately, inserting an SR header according to the various
+   * RFC's requires parsing through the ip6 header, perhaps consing a
+   * buffer onto the head of the vlib_buffer_t, etc. We don't use the
+   * normal reverse bcopy rewrite code.
+   * 
+   * We don't handle ugly RFC-related cases yet, but I'm sure PL will complain
+   * at some point...
+   */
+  ap->rewrite_header.sw_if_index = t - sm->tunnels;
+  
+  vec_add1 (add_adj, ap[0]);
+  
+  memcpy (aa.dst_address.as_u8, a->dst_address, sizeof (aa.dst_address.as_u8));
+  aa.dst_address_length = a->dst_mask_width;
+
+  aa.flags = (a->is_del ? IP6_ROUTE_FLAG_DEL : IP6_ROUTE_FLAG_ADD);
+  aa.flags |= IP6_ROUTE_FLAG_FIB_INDEX;
+  aa.table_index_or_table_id = rx_fib_index;
+  aa.add_adj = add_adj;
+  aa.adj_index = adj_index;
+  aa.n_add_adj = 1;
+  ip6_add_del_route (im, &aa);
+  vec_free (add_adj);
+
+  return 0;
+}
+
+static clib_error_t *
+sr_add_del_tunnel_command_fn (vlib_main_t * vm,
+                              unformat_input_t * input,
+                              vlib_cli_command_t * cmd)
+{
+  int is_del = 0;
+  ip6_address_t src_address;
+  int src_address_set = 0;
+  ip6_address_t dst_address;
+  u32 dst_mask_width;
+  int dst_address_set = 0;
+  u16 flags = 0;
+  u8 *shared_secret = 0;
+  u32 rx_table_id = 0;
+  u32 tx_table_id = 0;
+  ip6_address_t * segments = 0;
+  ip6_address_t * this_seg;
+  ip6_address_t * tags = 0;
+  ip6_address_t * this_tag;
+  ip6_sr_add_del_tunnel_args_t _a, *a=&_a;
+  ip6_address_t next_address, tag;
+  int pl_index;
+  int rv;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "del"))
+        is_del = 1;
+      else if (unformat (input, "rx-fib-id %d", &rx_table_id))
+        ;
+      else if (unformat (input, "tx-fib-id %d", &tx_table_id))
+        ;
+      else if (unformat (input, "src %U", unformat_ip6_address, &src_address))
+        src_address_set = 1;
+      else if (unformat (input, "dst %U/%d", 
+                         unformat_ip6_address, &dst_address,
+                         &dst_mask_width))
+        dst_address_set = 1;
+      else if (unformat (input, "next %U", unformat_ip6_address,
+                         &next_address))
+        {
+          vec_add2 (segments, this_seg, 1);
+          memcpy (this_seg->as_u8, next_address.as_u8, sizeof (*this_seg));
+        }
+      else if (unformat (input, "tag %U", unformat_ip6_address,
+                         &tag))
+        {
+          vec_add2 (tags, this_tag, 1);
+          memcpy (this_tag->as_u8, tag.as_u8, sizeof (*this_tag));
+        }
+      else if (unformat (input, "clean"))
+        flags |= IP6_SR_HEADER_FLAG_CLEANUP;
+      else if (unformat (input, "protected"))
+        flags |= IP6_SR_HEADER_FLAG_PROTECTED;
+      else if (unformat (input, "key %s", &shared_secret))
+          /* Do not include the trailing NULL byte. Guaranteed interop issue */
+          _vec_len (shared_secret) -= 1;
+      else if (unformat (input, "InPE %d", &pl_index))
+        {
+          if (pl_index <= 0 || pl_index > 4)
+            {
+            pl_index_range_error:
+              return clib_error_return 
+                (0, "Policy List Element Index %d out of range (1-4)", pl_index);
+              
+            }
+          flags |= IP6_SR_HEADER_FLAG_PL_ELT_INGRESS_PE 
+            << ip6_sr_policy_list_shift_from_index (pl_index);
+        }
+      else if (unformat (input, "EgPE %d", &pl_index))
+        {
+          if (pl_index <= 0 || pl_index > 4)
+            goto pl_index_range_error;
+          flags |= IP6_SR_HEADER_FLAG_PL_ELT_EGRESS_PE 
+            << ip6_sr_policy_list_shift_from_index (pl_index);
+        }
+      else if (unformat (input, "OrgSrc %d", &pl_index))
+        {
+          if (pl_index <= 0 || pl_index > 4)
+            goto pl_index_range_error;
+          flags |= IP6_SR_HEADER_FLAG_PL_ELT_ORIG_SRC_ADDR
+            << ip6_sr_policy_list_shift_from_index (pl_index);
+        }
+      else 
+        break;
+    }
+
+  if (!src_address_set)
+    return clib_error_return (0, "src address required");
+
+  if (!dst_address_set)
+    return clib_error_return (0, "dst address required");
+
+  if (!segments)
+    return clib_error_return (0, "at least one sr segment required");
+
+  memset (a, 0, sizeof (*a));
+  a->src_address = &src_address;
+  a->dst_address = &dst_address;
+  a->dst_mask_width = dst_mask_width;
+  a->segments = segments;
+  a->tags = tags;
+  a->flags_net_byte_order = clib_host_to_net_u16(flags);
+  a->is_del = is_del;
+  a->rx_table_id = rx_table_id;
+  a->tx_table_id = tx_table_id;
+  a->shared_secret = shared_secret;
+
+  rv = ip6_sr_add_del_tunnel (a);
+  
+  vec_free (segments);
+  vec_free (tags);
+  vec_free (shared_secret);
+
+  switch (rv)
+    {
+    case 0:
+      break;
+
+    case -1:
+      return clib_error_return (0, "SR tunnel src %U dst %U already exists",
+                                format_ip6_address, &src_address,
+                                format_ip6_address, &dst_address);
+
+    case -2:
+      return clib_error_return (0, "SR tunnel src %U dst %U does not exist",
+                                format_ip6_address, &src_address,
+                                format_ip6_address, &dst_address);
+      
+    case -3:
+      return clib_error_return (0, "FIB table %d does not exist", rx_table_id);
+
+    case -4:
+      return clib_error_return (0, "At least one segment is required");
+
+    default:
+      return clib_error_return (0, "BUG: ip6_sr_add_del_tunnel returns %d",
+                                rv);
+    }
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (sr_tunnel_command, static) = {
+    .path = "sr tunnel",
+    .short_help = 
+    "sr tunnel [del] <src> <dst> [next <addr>] [cleanup] [reroute] [key %s]",
+    .function = sr_add_del_tunnel_command_fn,
+};
+
+
+static clib_error_t *
+show_sr_tunnel_fn (vlib_main_t * vm,
+                   unformat_input_t * input,
+                   vlib_cli_command_t * cmd)
+{
+  static ip6_sr_tunnel_t ** tunnels;
+  ip6_sr_tunnel_t * t;
+  ip6_sr_main_t * sm = &sr_main;
+  ip6_main_t * im = &ip6_main;
+  ip6_fib_t * rx_fib, * tx_fib;
+  int i;
+
+  vec_reset_length (tunnels);
+
+  pool_foreach (t, sm->tunnels, 
+  ({
+    vec_add1 (tunnels, t);
+  }));
+
+  if (vec_len (tunnels) == 0)
+    vlib_cli_output (vm, "No SR tunnels configured");
+
+  for (i = 0; i < vec_len (tunnels); i++)
+    {
+      t = tunnels [i];
+
+      rx_fib = find_ip6_fib_by_table_index_or_id (im, t->rx_fib_index, 
+                                                  IP6_ROUTE_FLAG_FIB_INDEX);
+      
+      tx_fib = find_ip6_fib_by_table_index_or_id (im, t->tx_fib_index, 
+                                                  IP6_ROUTE_FLAG_FIB_INDEX);
+
+      vlib_cli_output (vm, "src %U dst %U first hop %U", 
+                       format_ip6_address, &t->key.src,
+                       format_ip6_address, &t->key.dst,
+                       format_ip6_address, &t->first_hop);
+      vlib_cli_output (vm, "    rx-fib-id %d tx-fib-id %d",
+                       rx_fib->table_id, tx_fib->table_id);
+      vlib_cli_output (vm, "  sr: %U", format_ip6_sr_header, t->rewrite, 
+                       0 /* print_hmac */);
+      vlib_cli_output (vm, "-------");
+    }
+  
+  return 0;
+}
+
+VLIB_CLI_COMMAND (show_sr_tunnel_command, static) = {
+    .path = "show sr tunnel",
+    .short_help = "show sr tunnel",
+    .function = show_sr_tunnel_fn,
+};
+
+#define foreach_sr_fix_dst_addr_next            \
+_(DROP, "error-drop")
+
+typedef enum {
+#define _(s,n) SR_FIX_DST_ADDR_NEXT_##s,
+foreach_sr_fix_dst_addr_next
+#undef _
+  SR_FIX_DST_ADDR_N_NEXT,
+} sr_fix_dst_addr_next_t;
+
+static char * sr_fix_dst_error_strings[] = {
+#define sr_fix_dst_error(n,s) s,
+#include "sr_fix_dst_error.def"
+#undef sr_fix_dst_error
+};
+
+typedef enum {
+#define sr_fix_dst_error(n,s) SR_FIX_DST_ERROR_##n,
+#include "sr_fix_dst_error.def"
+#undef sr_fix_dst_error
+  SR_FIX_DST_N_ERROR,
+} sr_fix_dst_error_t;
+
+typedef struct {
+  ip6_address_t src, dst;
+  u32 next_index;
+  u32 adj_index;
+  u8 sr[256];
+} sr_fix_addr_trace_t;
+
+u8 * format_sr_fix_addr_trace (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+  sr_fix_addr_trace_t * t = va_arg (*args, sr_fix_addr_trace_t *);
+  vnet_hw_interface_t * hi = 0;
+  ip_adjacency_t * adj;
+  ip6_main_t * im = &ip6_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  vnet_main_t * vnm = vnet_get_main();
+
+  if (t->adj_index != ~0)
+    {
+      adj = ip_get_adjacency (lm, t->adj_index);
+      hi = vnet_get_sup_hw_interface (vnm, adj->rewrite_header.sw_if_index);
+    }
+
+  s = format (s, "SR-FIX_ADDR: next %s ip6 src %U dst %U\n", 
+              (t->next_index == SR_FIX_DST_ADDR_NEXT_DROP) 
+              ? "drop" : "output",
+              format_ip6_address, &t->src, 
+              format_ip6_address, &t->dst);
+  if (t->next_index != SR_FIX_DST_ADDR_NEXT_DROP)
+    {
+      s = format (s, "%U\n", format_ip6_sr_header, t->sr, 1 /* print_hmac */);
+      s = format (s, "   output via %s", hi ? (char *)(hi->name) 
+                  : "Invalid adj");
+    }
+  return s;
+}
+
+static uword
+sr_fix_dst_addr (vlib_main_t * vm,
+                   vlib_node_runtime_t * node,
+                   vlib_frame_t * from_frame)
+{
+  u32 n_left_from, next_index, * from, * to_next;
+  ip6_main_t * im = &ip6_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+
+  from = vlib_frame_vector_args (from_frame);
+  n_left_from = 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);
+
+#if 0
+      while (0 && n_left_from >= 4 && n_left_to_next >= 2)
+       {
+         u32 bi0, bi1;
+         __attribute__((unused)) vlib_buffer_t * b0, * b1;
+         u32 next0 = SR_FIX_DST_ADDR_NEXT_DROP;
+         u32 next1 = SR_FIX_DST_ADDR_NEXT_DROP;
+
+         /* 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);
+         }
+
+         bi0 = from[0];
+         bi1 = from[1];
+         to_next[0] = bi0;
+         to_next[1] = bi1;
+         from += 2;
+         to_next += 2;
+         n_left_to_next -= 2;
+         n_left_from -= 2;
+
+         b0 = vlib_get_buffer (vm, bi0);
+         b1 = vlib_get_buffer (vm, bi1);
+
+
+         vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
+                                          to_next, n_left_to_next,
+                                          bi0, bi1, next0, next1);
+        }
+#endif
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         u32 bi0;
+         vlib_buffer_t * b0;
+          ip6_header_t * ip0;
+          ip_adjacency_t * adj0;
+          ip6_sr_header_t * sr0;
+         u32 next0 = SR_FIX_DST_ADDR_NEXT_DROP;
+          ip6_address_t *new_dst0;
+          ethernet_header_t * eh0;
+
+         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);
+          
+          adj0 = ip_get_adjacency (lm, vnet_buffer(b0)->ip.adj_index[VLIB_TX]);
+          next0 = adj0->mcast_group_index;
+
+          /* We should be pointing at an Ethernet header... */
+          eh0 = vlib_buffer_get_current (b0);
+          ip0 = (ip6_header_t *)(eh0+1);
+          sr0 = (ip6_sr_header_t *) (ip0+1);
+          
+          /* We'd better find an SR header... */
+          if (PREDICT_FALSE(ip0->protocol != 43))
+            {
+              b0->error = node->errors[SR_FIX_DST_ERROR_NO_SR_HEADER];
+              goto do_trace0;
+            }
+          else
+            {
+              /* 
+               * We get here from sr_rewrite or sr_local, with
+               * sr->segments_left pointing at the (copy of the original) dst
+               * address. Use it, then increment sr0->segments_left.
+               */
+              
+              /* Out of segments? Turf the packet */
+              if (PREDICT_FALSE (sr0->segments_left == 0))
+                {
+                  b0->error = node->errors[SR_FIX_DST_ERROR_NO_MORE_SEGMENTS];
+                  goto do_trace0;
+                }
+              
+              /* 
+               * Rewrite the packet with the original dst address 
+               * We assume that the last segment (in processing order) contains 
+               * the original dst address. The list is reversed, so sr0->segments
+               * contains the original dst address.
+               */
+              new_dst0 = sr0->segments;
+              ip0->dst_address.as_u64[0] = new_dst0->as_u64[0];
+              ip0->dst_address.as_u64[1] = new_dst0->as_u64[1];
+            }
+          
+        do_trace0:
+
+          if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) 
+            {
+              sr_fix_addr_trace_t *t = vlib_add_trace (vm, node, 
+                                                       b0, sizeof (*t));
+              t->next_index = next0;
+              t->adj_index = ~0;
+
+              if (next0 != SR_FIX_DST_ADDR_NEXT_DROP)
+                {
+                  t->adj_index = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
+                  memcpy (t->src.as_u8, ip0->src_address.as_u8,
+                          sizeof (t->src.as_u8));
+                  memcpy (t->dst.as_u8, ip0->dst_address.as_u8,
+                          sizeof (t->dst.as_u8));
+                  memcpy (t->sr, sr0, sizeof (t->sr));
+                }
+            }
+
+         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);
+    }
+  return from_frame->n_vectors;
+}
+
+
+VLIB_REGISTER_NODE (sr_fix_dst_addr_node) = {
+  .function = sr_fix_dst_addr,
+  .name = "sr-fix-dst-addr",
+  /* Takes a vector of packets. */
+  .vector_size = sizeof (u32),
+  .format_trace = format_sr_fix_addr_trace,
+  .format_buffer = format_ip6_sr_header_with_length,
+
+  .runtime_data_bytes = 0,
+
+  .n_errors = SR_FIX_DST_N_ERROR,
+  .error_strings = sr_fix_dst_error_strings,
+
+  .n_next_nodes = SR_FIX_DST_ADDR_N_NEXT,
+  .next_nodes = {
+#define _(s,n) [SR_FIX_DST_ADDR_NEXT_##s] = n,
+    foreach_sr_fix_dst_addr_next
+#undef _
+  },
+};
+
+static clib_error_t * sr_init (vlib_main_t * vm)
+{
+  ip6_sr_main_t * sm = &sr_main;
+  clib_error_t * error = 0;
+  vlib_node_t * ip6_lookup_node, * ip6_rewrite_node;
+  vlib_node_t * ip6_rewrite_local_node;
+  u32 verify_next_index;
+
+  if ((error = vlib_call_init_function (vm, ip_main_init)))
+    return error;
+
+  if ((error = vlib_call_init_function (vm, ip6_lookup_init)))
+    return error;
+
+  sm->vlib_main = vm;
+  sm->vnet_main = vnet_get_main();
+  
+  vec_validate (sm->hmac_keys, 0);
+  sm->hmac_keys[0].shared_secret = (u8 *) 0xdeadbeef;
+
+  sm->tunnel_index_by_key = 
+    hash_create_mem (0, sizeof (ip6_sr_tunnel_key_t), sizeof (uword));
+
+  sm->hmac_key_by_shared_secret = hash_create_string (0, sizeof(uword));
+
+  ip6_register_protocol (43, sr_local_node.index);
+
+  ip6_lookup_node = vlib_get_node_by_name (vm, (u8 *)"ip6-lookup");
+  ASSERT(ip6_lookup_node);
+
+  ip6_rewrite_node = vlib_get_node_by_name (vm, (u8 *)"ip6-rewrite");
+  ASSERT(ip6_rewrite_node);
+
+  ip6_rewrite_local_node = vlib_get_node_by_name (vm, 
+                                                  (u8 *)"ip6-rewrite-local");
+  ASSERT(ip6_rewrite_local_node);
+  
+  /* Add a disposition to ip6_lookup for the sr rewrite node */
+  sm->ip6_lookup_sr_next_index = 
+    vlib_node_add_next (vm, ip6_lookup_node->index, sr_rewrite_node.index);
+
+  /* Add a disposition to ip6_rewrite for the sr dst address hack node */
+  sm->ip6_rewrite_sr_next_index = 
+    vlib_node_add_next (vm, ip6_rewrite_node->index, 
+                        sr_fix_dst_addr_node.index);
+  /* 
+   * Fix ip6-rewrite-local, sibling of the above. The sibling bitmap
+   * isn't set up at this point, so we have to do it manually
+   */
+  verify_next_index = vlib_node_add_next 
+      (vm, ip6_rewrite_local_node->index, 
+       sr_fix_dst_addr_node.index);
+
+  ASSERT(sm->ip6_rewrite_sr_next_index == verify_next_index);
+
+  OpenSSL_add_all_digests();
+
+  sm->md = (void *) EVP_get_digestbyname ("sha1");
+  sm->hmac_ctx = clib_mem_alloc (sizeof (HMAC_CTX));
+
+  return error;
+}
+
+VLIB_INIT_FUNCTION (sr_init);
+
+#define foreach_sr_local_next                   \
+  _ (ERROR, "error-drop")                       \
+  _ (IP6_LOOKUP, "ip6-lookup")
+
+typedef enum {
+#define _(s,n) SR_LOCAL_NEXT_##s,
+  foreach_sr_local_next
+#undef _
+  SR_LOCAL_N_NEXT,
+} sr_local_next_t;
+
+typedef struct {
+  u8 next_index;
+  u8 sr_valid;
+  ip6_address_t src, dst;
+  u16 length;
+  u8 sr[256];
+} sr_local_trace_t;
+
+static char * sr_local_error_strings[] = {
+#define sr_error(n,s) s,
+#include "sr_error.def"
+#undef sr_error
+};
+
+typedef enum {
+#define sr_error(n,s) SR_LOCAL_ERROR_##n,
+#include "sr_error.def"
+#undef sr_error
+  SR_LOCAL_N_ERROR,
+} sr_local_error_t;
+
+u8 * format_sr_local_trace (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+  sr_local_trace_t * t = va_arg (*args, sr_local_trace_t *);
+    
+  s = format (s, "SR-LOCAL: src %U dst %U len %u next_index %d", 
+              format_ip6_address, &t->src, 
+              format_ip6_address, &t->dst, t->length, t->next_index);
+  if (t->sr_valid)
+    s = format (s, "\n  %U", format_ip6_sr_header, t->sr, 1 /* print_hmac */);
+  else
+    s = format (s, "\n  popped SR header");
+
+  return s;
+}
+
+
+/* $$$$ fixme: smp, don't copy data, cache input, output (maybe) */
+
+static int sr_validate_hmac (ip6_sr_main_t * sm, ip6_header_t * ip, 
+                             ip6_sr_header_t * sr)
+{
+  u32 key_index;
+  static u8 * keybuf;
+  u8 * copy_target;
+  int first_segment;
+  ip6_address_t *addrp;
+  int i;
+  ip6_sr_hmac_key_t * hmac_key;
+  static u8 * signature;
+  u32 sig_len;
+
+  key_index = sr->hmac_key;
+
+  /* No signature? Pass... */
+  if (key_index == 0)
+    return 0;
+
+  /* We don't know about this key? Fail... */
+  if (key_index >= vec_len (sm->hmac_keys))
+    return 1;
+
+  vec_validate (signature, SHA256_DIGEST_LENGTH-1);
+
+  hmac_key = sm->hmac_keys + key_index;
+
+  vec_reset_length (keybuf);
+
+  /* pkt ip6 src address */
+  vec_add2 (keybuf, copy_target, sizeof (ip6_address_t));
+  memcpy (copy_target, ip->src_address.as_u8, sizeof (ip6_address_t));
+
+  /* last segment */
+  vec_add2 (keybuf, copy_target, 1);
+  copy_target[0] = sr->first_segment;
+
+  /* octet w/ bit 0 = "clean" flag */
+  vec_add2 (keybuf, copy_target, 1);
+  copy_target[0] 
+    = (sr->flags & clib_host_to_net_u16 (IP6_SR_HEADER_FLAG_CLEANUP)) 
+    ? 0x80 : 0;
+
+  /* hmac key id */
+  vec_add2 (keybuf, copy_target, 1);
+  copy_target[0] = sr->hmac_key;
+
+  first_segment = sr->first_segment;
+
+  addrp = sr->segments;
+
+  /* segments */
+  for (i = 0; i <= first_segment; i++)
+    {
+      vec_add2 (keybuf, copy_target, sizeof (ip6_address_t));
+      memcpy (copy_target, addrp->as_u8, sizeof (ip6_address_t));
+      addrp++;
+    }
+
+  if (sm->is_debug)
+      clib_warning ("verify key index %d keybuf: %U", key_index, 
+                    format_hex_bytes, keybuf, vec_len(keybuf));
+
+  /* shared secret */
+
+  /* SHA1 is shorter than SHA-256 */
+  memset (signature, 0, vec_len(signature));
+
+  HMAC_CTX_init(sm->hmac_ctx);
+  if (!HMAC_Init(sm->hmac_ctx, hmac_key->shared_secret,
+                 vec_len(hmac_key->shared_secret),sm->md))
+      clib_warning ("barf1");
+  if (!HMAC_Update(sm->hmac_ctx,keybuf,vec_len(keybuf)))
+      clib_warning ("barf2");
+  if (!HMAC_Final(sm->hmac_ctx,signature,&sig_len))
+      clib_warning ("barf3");
+  HMAC_CTX_cleanup(sm->hmac_ctx);
+
+  if (sm->is_debug)
+      clib_warning ("computed signature len %d, value %U", sig_len, 
+                    format_hex_bytes, signature, vec_len(signature));
+
+  /* Point at the SHA signature in the packet */
+  addrp++;
+  if (sm->is_debug)
+      clib_warning ("read signature %U", format_hex_bytes, addrp, 
+                    SHA256_DIGEST_LENGTH);
+
+  return memcmp (signature, addrp, SHA256_DIGEST_LENGTH);
+}
+
+static uword
+sr_local (vlib_main_t * vm,
+          vlib_node_runtime_t * node,
+          vlib_frame_t * from_frame)
+{
+  u32 n_left_from, next_index, * from, * to_next;
+  ip6_sr_main_t * sm = &sr_main;
+  u32 (*sr_local_cb) (vlib_main_t *, vlib_node_runtime_t *,
+                      vlib_buffer_t *, ip6_header_t *, 
+                      ip6_sr_header_t *);
+  sr_local_cb = sm->sr_local_cb;
+
+  from = vlib_frame_vector_args (from_frame);
+  n_left_from = 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)
+       {
+         u32 bi0, bi1;
+         vlib_buffer_t * b0, * b1;
+          ip6_header_t * ip0, *ip1;
+          ip6_sr_header_t * sr0, *sr1;
+          ip6_address_t * new_dst0, * new_dst1;
+         u32 next0 = SR_LOCAL_NEXT_IP6_LOOKUP;
+         u32 next1 = SR_LOCAL_NEXT_IP6_LOOKUP;
+         /* Prefetch next iteration. */
+         {
+           vlib_buffer_t * p2, * p3;
+
+           p2 = vlib_get_buffer (vm, from[2]);
+           p3 = vlib_get_buffer (vm, from[3]);
+
+           vlib_prefetch_buffer_header (p2, LOAD);
+           vlib_prefetch_buffer_header (p3, LOAD);
+
+           CLIB_PREFETCH (p2->data, 2*CLIB_CACHE_LINE_BYTES, LOAD);
+           CLIB_PREFETCH (p3->data, 2*CLIB_CACHE_LINE_BYTES, LOAD);
+         }
+
+         bi0 = from[0];
+         bi1 = from[1];
+         to_next[0] = bi0;
+         to_next[1] = bi1;
+         from += 2;
+         to_next += 2;
+         n_left_to_next -= 2;
+         n_left_from -= 2;
+
+
+         b0 = vlib_get_buffer (vm, bi0);
+          ip0 = vlib_buffer_get_current (b0);
+          sr0 = (ip6_sr_header_t *)(ip0+1);
+
+          if (PREDICT_FALSE(sr0->type != ROUTING_HEADER_TYPE_SR))
+            {
+              next0 = SR_LOCAL_NEXT_ERROR;
+              b0->error = node->errors[SR_LOCAL_ERROR_BAD_ROUTING_HEADER_TYPE];
+              goto do_trace0;
+            }
+
+          /* Out of segments? Turf the packet */
+          if (PREDICT_FALSE (sr0->segments_left == 0))
+            {
+              next0 = SR_LOCAL_NEXT_ERROR;
+              b0->error = node->errors[SR_LOCAL_ERROR_NO_MORE_SEGMENTS];
+              goto do_trace0;
+            }
+
+          if (PREDICT_FALSE(sm->validate_hmac))
+            {
+              if (sr_validate_hmac (sm, ip0, sr0))
+                {
+                  next0 = SR_LOCAL_NEXT_ERROR;
+                  b0->error = node->errors[SR_LOCAL_ERROR_HMAC_INVALID];
+                  goto do_trace0;
+                }
+            }
+
+          next0 = sr_local_cb ? sr_local_cb (vm, node, b0, ip0, sr0) :
+              next0;
+
+          /* 
+           * To suppress rewrite, return ~SR_LOCAL_NEXT_xxx 
+           */
+          if (PREDICT_FALSE (next0 & 0x80000000))
+          {
+              next0 ^= 0xFFFFFFFF;
+              if (PREDICT_FALSE(next0 == SR_LOCAL_NEXT_ERROR))
+                  b0->error = 
+                  node->errors[SR_LOCAL_ERROR_APP_CALLBACK];
+            }
+          else
+            {
+              u32 segment_index0;
+
+              segment_index0 = sr0->segments_left - 1;
+              
+              /* Rewrite the packet */
+              new_dst0 = (ip6_address_t *)(sr0->segments + segment_index0);
+              ip0->dst_address.as_u64[0] = new_dst0->as_u64[0];
+              ip0->dst_address.as_u64[1] = new_dst0->as_u64[1];
+              
+              if (PREDICT_TRUE (sr0->segments_left > 0))
+              {
+                  sr0->segments_left -= 1;
+                  goto do_trace0;
+              }
+            }
+
+          /* End of the path. Clean up the SR header, or not */
+          if (sr0->flags & clib_host_to_net_u16(IP6_SR_HEADER_FLAG_CLEANUP))
+          {
+              u64 *copy_dst0, *copy_src0;
+              u16 new_l0;
+              /* 
+               * Copy the ip6 header right by the (real) length of the
+               * sr header. Here's another place which assumes that
+               * the sr header is the only extention header.
+               */
+              
+              ip0->protocol = sr0->protocol;
+              vlib_buffer_advance (b0, (sr0->length+1)*8);
+
+              new_l0 = clib_net_to_host_u16(ip0->payload_length) -
+                  (sr0->length+1)*8;
+              ip0->payload_length = clib_host_to_net_u16(new_l0);
+              
+              copy_src0 = (u64 *)ip0;
+              copy_dst0 = copy_src0 + (sr0->length + 1);
+
+              copy_dst0 [4] = copy_src0[4];
+              copy_dst0 [3] = copy_src0[3];
+              copy_dst0 [2] = copy_src0[2];
+              copy_dst0 [1] = copy_src0[1];
+              copy_dst0 [0] = copy_src0[0];
+              
+              sr0 = 0;
+            }
+
+        do_trace0:
+          if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) 
+            {
+              sr_local_trace_t *tr = vlib_add_trace (vm, node, 
+                                                     b0, sizeof (*tr));
+              memcpy (tr->src.as_u8, ip0->src_address.as_u8,
+                      sizeof (tr->src.as_u8));
+              memcpy (tr->dst.as_u8, ip0->dst_address.as_u8,
+                      sizeof (tr->dst.as_u8));
+              tr->length = vlib_buffer_length_in_chain (vm, b0);
+              tr->next_index = next0;
+              tr->sr_valid = sr0 != 0;
+              if (tr->sr_valid)
+                memcpy (tr->sr, sr0, sizeof (tr->sr));
+            }
+
+         b1 = vlib_get_buffer (vm, bi1);
+          ip1 = vlib_buffer_get_current (b1);
+          sr1 = (ip6_sr_header_t *)(ip1+1);
+
+          if (PREDICT_FALSE(sr1->type != ROUTING_HEADER_TYPE_SR))
+            {
+              next1 = SR_LOCAL_NEXT_ERROR;
+              b1->error = node->errors[SR_LOCAL_ERROR_BAD_ROUTING_HEADER_TYPE];
+              goto do_trace1;
+            }
+
+          /* Out of segments? Turf the packet */
+          if (PREDICT_FALSE (sr1->segments_left == 0))
+            {
+              next1 = SR_LOCAL_NEXT_ERROR;
+              b1->error = node->errors[SR_LOCAL_ERROR_NO_MORE_SEGMENTS];
+              goto do_trace1;
+            }
+
+          if (PREDICT_FALSE(sm->validate_hmac))
+            {
+              if (sr_validate_hmac (sm, ip1, sr1))
+                {
+                  next1 = SR_LOCAL_NEXT_ERROR;
+                  b1->error = node->errors[SR_LOCAL_ERROR_HMAC_INVALID];
+                  goto do_trace1;
+                }
+            }
+
+          next1 = sr_local_cb ? sr_local_cb (vm, node, b1, ip1, sr1) :
+              next1;
+
+          /* 
+           * To suppress rewrite, return ~SR_LOCAL_NEXT_xxx 
+           */
+          if (PREDICT_FALSE (next1 & 0x80000000))
+          {
+              next1 ^= 0xFFFFFFFF;
+              if (PREDICT_FALSE(next1 == SR_LOCAL_NEXT_ERROR))
+                  b1->error = 
+                  node->errors[SR_LOCAL_ERROR_APP_CALLBACK];
+            }
+          else
+            {
+              u32 segment_index1;
+
+              segment_index1 = sr1->segments_left - 1;
+
+              /* Rewrite the packet */
+              new_dst1 = (ip6_address_t *)(sr1->segments + segment_index1);
+              ip1->dst_address.as_u64[0] = new_dst1->as_u64[0];
+              ip1->dst_address.as_u64[1] = new_dst1->as_u64[1];
+              
+              if (PREDICT_TRUE (sr1->segments_left > 0))
+                {
+                  sr1->segments_left -= 1;
+                  goto do_trace1;
+                }
+            }
+
+          /* End of the path. Clean up the SR header, or not */
+          if (sr1->flags & clib_host_to_net_u16(IP6_SR_HEADER_FLAG_CLEANUP))
+            {
+              u64 *copy_dst1, *copy_src1;
+              u16 new_l1;
+              /* 
+               * Copy the ip6 header right by the (real) length of the
+               * sr header. Here's another place which assumes that
+               * the sr header is the only extention header.
+               */
+              
+              ip1->protocol = sr1->protocol;
+              vlib_buffer_advance (b1, (sr1->length+1)*8);
+
+              new_l1 = clib_net_to_host_u16(ip1->payload_length) -
+                  (sr1->length+1)*8;
+              ip1->payload_length = clib_host_to_net_u16(new_l1);
+              
+              copy_src1 = (u64 *)ip1;
+              copy_dst1 = copy_src1 + (sr1->length + 1);
+
+              copy_dst1 [4] = copy_src1[4];
+              copy_dst1 [3] = copy_src1[3];
+              copy_dst1 [2] = copy_src1[2];
+              copy_dst1 [1] = copy_src1[1];
+              copy_dst1 [0] = copy_src1[0];
+              
+              sr1 = 0;
+            }
+
+        do_trace1:
+          if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED)) 
+            {
+              sr_local_trace_t *tr = vlib_add_trace (vm, node, 
+                                                     b1, sizeof (*tr));
+              memcpy (tr->src.as_u8, ip1->src_address.as_u8,
+                      sizeof (tr->src.as_u8));
+              memcpy (tr->dst.as_u8, ip1->dst_address.as_u8,
+                      sizeof (tr->dst.as_u8));
+              tr->length = vlib_buffer_length_in_chain (vm, b1);
+              tr->next_index = next1;
+              tr->sr_valid = sr1 != 0;
+              if (tr->sr_valid)
+                memcpy (tr->sr, sr1, sizeof (tr->sr));
+            }
+
+         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)
+       {
+         u32 bi0;
+         vlib_buffer_t * b0;
+          ip6_header_t * ip0;
+          ip6_sr_header_t * sr0;
+          ip6_address_t * new_dst0;
+         u32 next0 = SR_LOCAL_NEXT_IP6_LOOKUP;
+
+         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);
+          sr0 = (ip6_sr_header_t *)(ip0+1);
+
+          if (PREDICT_FALSE(sr0->type != ROUTING_HEADER_TYPE_SR))
+            {
+              next0 = SR_LOCAL_NEXT_ERROR;
+              b0->error = node->errors[SR_LOCAL_ERROR_BAD_ROUTING_HEADER_TYPE];
+              goto do_trace;
+            }
+
+          /* Out of segments? Turf the packet */
+          if (PREDICT_FALSE (sr0->segments_left == 0))
+            {
+              next0 = SR_LOCAL_NEXT_ERROR;
+              b0->error = node->errors[SR_LOCAL_ERROR_NO_MORE_SEGMENTS];
+              goto do_trace;
+            }
+
+          if (PREDICT_FALSE(sm->validate_hmac))
+            {
+              if (sr_validate_hmac (sm, ip0, sr0))
+                {
+                  next0 = SR_LOCAL_NEXT_ERROR;
+                  b0->error = node->errors[SR_LOCAL_ERROR_HMAC_INVALID];
+                  goto do_trace;
+                }
+            }
+
+          next0 = sr_local_cb ? sr_local_cb (vm, node, b0, ip0, sr0) :
+            next0;
+
+          /* 
+           * To suppress rewrite, return ~SR_LOCAL_NEXT_xxx 
+           */
+          if (PREDICT_FALSE (next0 & 0x80000000))
+            {
+              next0 ^= 0xFFFFFFFF;
+              if (PREDICT_FALSE(next0 == SR_LOCAL_NEXT_ERROR))
+                b0->error = 
+                  node->errors[SR_LOCAL_ERROR_APP_CALLBACK];
+            }
+          else
+            {
+              u32 segment_index0;
+
+              segment_index0 = sr0->segments_left - 1;
+
+              /* Rewrite the packet */
+              new_dst0 = (ip6_address_t *)(sr0->segments + segment_index0);
+              ip0->dst_address.as_u64[0] = new_dst0->as_u64[0];
+              ip0->dst_address.as_u64[1] = new_dst0->as_u64[1];
+              
+              if (PREDICT_TRUE (sr0->segments_left > 0))
+                {
+                  sr0->segments_left -= 1;
+                  goto do_trace;
+                }
+            }
+
+          /* End of the path. Clean up the SR header, or not */
+          if (sr0->flags & clib_host_to_net_u16(IP6_SR_HEADER_FLAG_CLEANUP))
+            {
+              u64 *copy_dst0, *copy_src0;
+              u16 new_l0;
+              /* 
+               * Copy the ip6 header right by the (real) length of the
+               * sr header. Here's another place which assumes that
+               * the sr header is the only extention header.
+               */
+              
+              ip0->protocol = sr0->protocol;
+              vlib_buffer_advance (b0, (sr0->length+1)*8);
+
+              new_l0 = clib_net_to_host_u16(ip0->payload_length) -
+                  (sr0->length+1)*8;
+              ip0->payload_length = clib_host_to_net_u16(new_l0);
+              
+              copy_src0 = (u64 *)ip0;
+              copy_dst0 = copy_src0 + (sr0->length + 1);
+
+              copy_dst0 [4] = copy_src0[4];
+              copy_dst0 [3] = copy_src0[3];
+              copy_dst0 [2] = copy_src0[2];
+              copy_dst0 [1] = copy_src0[1];
+              copy_dst0 [0] = copy_src0[0];
+              
+              sr0 = 0;
+            }
+
+        do_trace:
+          if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) 
+            {
+              sr_local_trace_t *tr = vlib_add_trace (vm, node, 
+                                                     b0, sizeof (*tr));
+              memcpy (tr->src.as_u8, ip0->src_address.as_u8,
+                      sizeof (tr->src.as_u8));
+              memcpy (tr->dst.as_u8, ip0->dst_address.as_u8,
+                      sizeof (tr->dst.as_u8));
+              tr->length = vlib_buffer_length_in_chain (vm, b0);
+              tr->next_index = next0;
+              tr->sr_valid = sr0 != 0;
+              if (tr->sr_valid)
+                memcpy (tr->sr, sr0, sizeof (tr->sr));
+            }
+
+         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, sr_local_node.index,
+                               SR_LOCAL_ERROR_PKTS_PROCESSED, 
+                               from_frame->n_vectors);
+  return from_frame->n_vectors;
+}
+
+VLIB_REGISTER_NODE (sr_local_node, static) = {
+  .function = sr_local,
+  .name = "sr-local",
+  /* Takes a vector of packets. */
+  .vector_size = sizeof (u32),
+  .format_trace = format_sr_local_trace,
+
+  .runtime_data_bytes = 0,
+
+  .n_errors = SR_LOCAL_N_ERROR,
+  .error_strings = sr_local_error_strings,
+
+  .n_next_nodes = SR_LOCAL_N_NEXT,
+  .next_nodes = {
+#define _(s,n) [SR_LOCAL_NEXT_##s] = n,
+    foreach_sr_local_next
+#undef _
+  },
+};
+
+ip6_sr_main_t * sr_get_main (vlib_main_t * vm)
+{
+  vlib_call_init_function (vm, sr_init);
+  ASSERT(sr_local_node.index);
+  return &sr_main;
+}
+
+
+static clib_error_t *
+set_ip6_sr_rewrite_fn (vlib_main_t * vm,
+                       unformat_input_t * input,
+                       vlib_cli_command_t * cmd)
+{
+  ip6_address_t a;
+  ip6_main_t * im = &ip6_main;
+  ip_lookup_main_t * lm = &im->lookup_main;
+  u32 fib_index = 0;
+  u32 fib_id = 0;
+  u32 adj_index;
+  uword * p;
+  ip_adjacency_t * adj;
+  vnet_hw_interface_t * hi;
+  u32 sw_if_index;
+  ip6_sr_main_t * sm = &sr_main;
+  vnet_main_t * vnm = vnet_get_main();
+
+  if (!unformat (input, "%U", unformat_ip6_address, &a))
+    return clib_error_return (0, "ip6 address missing in '%U'", 
+                              format_unformat_error, input);
+
+  if (unformat (input, "rx-table-id %d", &fib_id))
+    {
+      p = hash_get (im->fib_index_by_table_id, fib_id);
+      if (p == 0)
+        return clib_error_return (0, "fib-id %d not found");
+      fib_index = p[0];
+    }
+
+  adj_index = ip6_fib_lookup_with_table (im, fib_index, &a);
+
+  if (adj_index == lm->miss_adj_index)
+    return clib_error_return (0, "no match for %U", 
+                              format_ip6_address, &a);
+
+  adj = ip_get_adjacency (lm, adj_index);
+
+  if (adj->lookup_next_index != IP_LOOKUP_NEXT_REWRITE)
+    return clib_error_return (0, "%U unresolved (not a rewrite adj)",
+                              format_ip6_address, &a);
+
+  adj->rewrite_header.next_index = sm->ip6_rewrite_sr_next_index;
+
+  sw_if_index = adj->rewrite_header.sw_if_index;
+  hi = vnet_get_sup_hw_interface (vnm, sw_if_index);
+  adj->rewrite_header.node_index = sr_fix_dst_addr_node.index;
+
+  /* $$$$$ hack... steal the mcast group index */
+  adj->mcast_group_index = 
+    vlib_node_add_next (vm, sr_fix_dst_addr_node.index, hi->output_node_index);
+  
+  return 0;
+}
+
+VLIB_CLI_COMMAND (set_ip6_sr_rewrite, static) = {
+    .path = "set ip6 sr rewrite",
+    .short_help = "set ip6 sr rewrite <ip6-address> [fib-id <id>]",
+    .function = set_ip6_sr_rewrite_fn,
+};
+
+void vnet_register_sr_app_callback (void *cb)
+{
+  ip6_sr_main_t * sm = &sr_main;
+  sm->sr_local_cb = cb;
+}
+
+static clib_error_t *
+test_sr_hmac_validate_fn (vlib_main_t * vm,
+                    unformat_input_t * input,
+                    vlib_cli_command_t * cmd)
+{
+  ip6_sr_main_t * sm = &sr_main;
+  
+  if (unformat (input, "validate on"))
+    sm->validate_hmac = 1;
+  else if (unformat (input, "chunk-offset off"))
+    sm->validate_hmac = 0;
+  else
+    return clib_error_return (0, "expected validate on|off in '%U'", 
+                              format_unformat_error, input);
+
+  vlib_cli_output (vm, "hmac signature validation %s",
+                   sm->validate_hmac ? 
+                   "on" : "off");
+  return 0;
+}
+
+VLIB_CLI_COMMAND (test_sr_hmac_validate, static) = {
+    .path = "test sr hmac",
+    .short_help = "test sr hmac validate [on|off]",
+    .function = test_sr_hmac_validate_fn,
+};
+
+i32 sr_hmac_add_del_key (ip6_sr_main_t * sm, u32 key_id, u8 * shared_secret,
+                         u8 is_del)
+{
+  u32 index;
+  ip6_sr_hmac_key_t * key;
+
+  if (is_del == 0)
+    {
+      /* Specific key in use? Fail. */
+      if (key_id && vec_len (sm->hmac_keys) > key_id
+          && sm->hmac_keys[key_id].shared_secret)
+        return -2;
+      
+      index = key_id;
+      key = find_or_add_shared_secret (sm, shared_secret, &index);
+      ASSERT(index == key_id);
+      return 0;
+    }
+
+  /* delete */
+
+  if (key_id)                   /* delete by key ID */
+    {
+      if (vec_len (sm->hmac_keys) <= key_id)
+        return -3;
+
+      key = sm->hmac_keys + key_id;
+
+      hash_unset_mem (sm->hmac_key_by_shared_secret, key->shared_secret);
+      vec_free (key->shared_secret);
+      return 0;
+    }
+  
+  index = 0;
+  key = find_or_add_shared_secret (sm, shared_secret, &index);
+  hash_unset_mem (sm->hmac_key_by_shared_secret, key->shared_secret);
+  vec_free (key->shared_secret);
+  return 0;
+}
+
+
+static clib_error_t *
+sr_hmac_add_del_key_fn (vlib_main_t * vm,
+                        unformat_input_t * input,
+                        vlib_cli_command_t * cmd)
+{
+  ip6_sr_main_t * sm = &sr_main;
+  u8 is_del = 0;
+  u32 key_id = 0;
+  u8 key_id_set = 0;
+  u8 * shared_secret = 0;
+  i32 rv;
+  
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "del"))
+        is_del = 1;
+      else if (unformat (input, "id %d", &key_id))
+          key_id_set = 1;
+      else if (unformat (input, "key %s", &shared_secret))
+        {
+          /* Do not include the trailing NULL byte. Guaranteed interop issue */
+          _vec_len (shared_secret) -= 1;
+        }
+      else 
+        break;
+    }
+
+  if (is_del == 0 && shared_secret == 0)
+    return clib_error_return (0, "shared secret must be set to add a key");
+
+  if (shared_secret == 0 && key_id_set == 0)
+    return clib_error_return (0, "shared secret and key id both unset");
+
+  rv = sr_hmac_add_del_key (sm, key_id, shared_secret, is_del);
+
+  vec_free (shared_secret);
+
+  switch (rv)
+    {
+    case 0:
+      break;
+
+    default:
+      return clib_error_return (0, "sr_hmac_add_del_key returned %d", 
+                                rv);
+    }
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (sr_hmac, static) = {
+    .path = "sr hmac",
+    .short_help = "sr hmac [del] id <nn> key <str>",
+    .function = sr_hmac_add_del_key_fn,
+};
+
+
+static clib_error_t *
+show_sr_hmac_fn (vlib_main_t * vm,
+                 unformat_input_t * input,
+                 vlib_cli_command_t * cmd)
+{
+  ip6_sr_main_t * sm = &sr_main;
+  int i;
+
+  for (i = 1; i < vec_len (sm->hmac_keys); i++)
+    {
+      if (sm->hmac_keys[i].shared_secret)
+          vlib_cli_output (vm, "[%d]: %v", i, sm->hmac_keys[i].shared_secret);
+    }
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (show_sr_hmac, static) = {
+    .path = "show sr hmac",
+    .short_help = "show sr hmac", 
+    .function = show_sr_hmac_fn,
+};
+
+static clib_error_t *
+test_sr_debug_fn (vlib_main_t * vm,
+                    unformat_input_t * input,
+                    vlib_cli_command_t * cmd)
+{
+  ip6_sr_main_t * sm = &sr_main;
+  
+  if (unformat (input, "on"))
+    sm->is_debug = 1;
+  else if (unformat (input, "off"))
+    sm->is_debug = 0;
+  else
+    return clib_error_return (0, "expected on|off in '%U'", 
+                              format_unformat_error, input);
+
+  vlib_cli_output (vm, "debug trace now %s", sm->is_debug ? "on" : "off");
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (test_sr_debug, static) = {
+    .path = "test sr debug",
+    .short_help = "test sr debug on|off",
+    .function = test_sr_debug_fn,
+};