GRE over IPv6

Refactors the GRE node to work with both IPv4 and IPv6 transports.

Note that this changes the binary configuration API to support both
address families; each address uses the same memory for either
address type and a flag to indicate which is in use.

The CLI and VAT syntax remains unchanged; the code detects whether
an IPv4 or an IPv6 address was given.

Configuration examples:

IPv4 CLI: create gre tunnel src 192.168.1.1 dst 192.168.1.2
IPv6 CLI: create gre tunnel src 2620:124:9000::1 dst 2620:124:9000::2

IPv4 VAT: gre_add_del_tunnel src 192.168.1.1 dst 192.168.1.2
IPv6 VAT: gre_add_del_tunnel src 2620:124:9000::1 dst 2620:124:9000::2

Change-Id: Ica8ee775dc101047fb8cd41617ddc8fafc2741b0
Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
diff --git a/src/vnet/gre/gre.c b/src/vnet/gre/gre.c
index 3d38138..a153c3c 100644
--- a/src/vnet/gre/gre.c
+++ b/src/vnet/gre/gre.c
@@ -28,6 +28,13 @@
   };
 } ip4_and_gre_union_t;
 
+typedef struct {
+  union {
+    ip6_and_gre_header_t ip6_and_gre;
+    u64 as_u64[3];
+  };
+} ip6_and_gre_union_t;
+
 
 /* Packet trace structure */
 typedef struct {
@@ -37,9 +44,9 @@
   /* pkt length */
   u32 length;
 
-  /* tunnel ip4 addresses */
-  ip4_address_t src;
-  ip4_address_t dst;
+  /* tunnel ip addresses */
+  ip46_address_t src;
+  ip46_address_t dst;
 } gre_tx_trace_t;
 
 u8 * format_gre_tx_trace (u8 * s, va_list * args)
@@ -50,8 +57,8 @@
 
   s = format (s, "GRE: tunnel %d len %d src %U dst %U",
 	      t->tunnel_id, clib_net_to_host_u16 (t->length),
-	      format_ip4_address, &t->src.as_u8,
-	      format_ip4_address, &t->dst.as_u8);
+	      format_ip46_address, &t->src, IP46_TYPE_ANY,
+	      format_ip46_address, &t->dst, IP46_TYPE_ANY);
   return s;
 }
 
@@ -192,10 +199,12 @@
 		   const void *dst_address)
 {
   gre_main_t * gm = &gre_main;
-  ip4_and_gre_header_t * h;
+  ip4_and_gre_header_t * h4;
+  ip6_and_gre_header_t * h6;
   u8* rewrite = NULL;
   gre_tunnel_t *t;
   u32 ti;
+  u8 is_ipv6;
 
   ti = gm->tunnel_index_by_sw_if_index[sw_if_index];
 
@@ -205,23 +214,45 @@
 
   t = pool_elt_at_index(gm->tunnels, ti);
 
-  vec_validate(rewrite, sizeof(*h)-1);
-  h = (ip4_and_gre_header_t*)rewrite;
-  h->gre.protocol = clib_host_to_net_u16(gre_proto_from_vnet_link(link_type));
+  is_ipv6 = t->tunnel_dst.fp_proto == FIB_PROTOCOL_IP6 ? 1 : 0;
 
-  h->ip4.ip_version_and_header_length = 0x45;
-  h->ip4.ttl = 254;
-  h->ip4.protocol = IP_PROTOCOL_GRE;
-  /* fixup ip4 header length and checksum after-the-fact */
-  h->ip4.src_address.as_u32 = t->tunnel_src.as_u32;
-  h->ip4.dst_address.as_u32 = t->tunnel_dst.as_u32;
-  h->ip4.checksum = ip4_header_checksum (&h->ip4);
+  if (!is_ipv6)
+    {
+      vec_validate(rewrite, sizeof(*h4)-1);
+      h4 = (ip4_and_gre_header_t*)rewrite;
+      h4->gre.protocol = clib_host_to_net_u16(gre_proto_from_vnet_link(link_type));
+
+      h4->ip4.ip_version_and_header_length = 0x45;
+      h4->ip4.ttl = 254;
+      h4->ip4.protocol = IP_PROTOCOL_GRE;
+      /* fixup ip4 header length and checksum after-the-fact */
+      h4->ip4.src_address.as_u32 = t->tunnel_src.ip4.as_u32;
+      h4->ip4.dst_address.as_u32 = t->tunnel_dst.fp_addr.ip4.as_u32;
+      h4->ip4.checksum = ip4_header_checksum (&h4->ip4);
+    }
+  else
+    {
+      vec_validate(rewrite, sizeof(*h6)-1);
+      h6 = (ip6_and_gre_header_t*)rewrite;
+      h6->gre.protocol = clib_host_to_net_u16(gre_proto_from_vnet_link(link_type));
+
+      h6->ip6.ip_version_traffic_class_and_flow_label = clib_host_to_net_u32(6 << 28);
+      h6->ip6.hop_limit = 255;
+      h6->ip6.protocol = IP_PROTOCOL_GRE;
+      /* fixup ip6 header length and checksum after-the-fact */
+      h6->ip6.src_address.as_u64[0] = t->tunnel_src.ip6.as_u64[0];
+      h6->ip6.src_address.as_u64[1] = t->tunnel_src.ip6.as_u64[1];
+      h6->ip6.dst_address.as_u64[0] = t->tunnel_dst.fp_addr.ip6.as_u64[0];
+      h6->ip6.dst_address.as_u64[1] = t->tunnel_dst.fp_addr.ip6.as_u64[1];
+    }
 
   return (rewrite);
 }
 
+#define is_v4_packet(_h) ((*(u8*) _h) & 0xF0) == 0x40
+
 void
-gre_fixup (vlib_main_t *vm,
+gre4_fixup (vlib_main_t *vm,
 	   ip_adjacency_t *adj,
 	   vlib_buffer_t *b0)
 {
@@ -236,11 +267,36 @@
 }
 
 void
+gre6_fixup (vlib_main_t *vm,
+	   ip_adjacency_t *adj,
+	   vlib_buffer_t *b0)
+{
+    ip6_header_t * ip0;
+
+    ip0 = vlib_buffer_get_current (b0);
+
+    /* Fixup the payload length field in the GRE tunnel encap that was applied
+     * at the midchain node */
+    ip0->payload_length =
+        clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b0))
+        - sizeof(*ip0);
+}
+
+void
 gre_update_adj (vnet_main_t * vnm,
 		u32 sw_if_index,
 		adj_index_t ai)
 {
-    adj_nbr_midchain_update_rewrite (ai, gre_fixup, 
+    gre_main_t * gm = &gre_main;
+    gre_tunnel_t *t;
+    u32 ti;
+    u8 is_ipv6;
+
+    ti = gm->tunnel_index_by_sw_if_index[sw_if_index];
+    t = pool_elt_at_index(gm->tunnels, ti);
+    is_ipv6 = t->tunnel_dst.fp_proto == FIB_PROTOCOL_IP6 ? 1 : 0;
+
+    adj_nbr_midchain_update_rewrite (ai, !is_ipv6 ? gre4_fixup : gre6_fixup,
                                      (VNET_LINK_ETHERNET == adj_get_link_type (ai) ?
                                       ADJ_FLAG_MIDCHAIN_NO_COUNT :
                                       ADJ_FLAG_NONE),
@@ -264,6 +320,7 @@
   u32 * from, * to_next, n_left_from, n_left_to_next;
   vnet_interface_output_runtime_t * rd = (void *) node->runtime_data;
   const gre_tunnel_t *gt = pool_elt_at_index (gm->tunnels, rd->dev_instance);
+  u8 is_ipv6 = gt->tunnel_dst.fp_proto == FIB_PROTOCOL_IP6 ? 1 : 0;
 
   /* Vector of buffer / pkt indices we're supposed to process */
   from = vlib_frame_vector_args (frame);
@@ -303,10 +360,10 @@
 	    {
 	      gre_tx_trace_t *tr = vlib_add_trace (vm, node,
 						   b0, sizeof (*tr));
-	      tr->tunnel_id = gt - gm->tunnels;
-	      tr->length = vlib_buffer_length_in_chain (vm, b0);
-	      tr->src.as_u32 = gt->tunnel_src.as_u32;
-	      tr->dst.as_u32 = gt->tunnel_src.as_u32;
+          tr->tunnel_id = gt - gm->tunnels;
+          tr->src = gt->tunnel_src;
+          tr->dst = gt->tunnel_src;
+          tr->length = vlib_buffer_length_in_chain (vm, b0);
 	    }
 
 	  vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
@@ -317,7 +374,8 @@
       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
     }
 
-  vlib_node_increment_counter (vm, gre_input_node.index,
+  vlib_node_increment_counter (vm, !is_ipv6 ? gre4_input_node.index :
+                   gre6_input_node.index,
 			       GRE_ERROR_PKTS_ENCAP, frame->n_vectors);
 
   return frame->n_vectors;
@@ -434,6 +492,9 @@
   if ((error = vlib_call_init_function (vm, ip4_lookup_init)))
     return error;
 
+  if ((error = vlib_call_init_function (vm, ip6_lookup_init)))
+    return error;
+
   /* Set up the ip packet generator */
   pi = ip_get_protocol_info (im, IP_PROTOCOL_GRE);
   pi->format_header = format_gre_header;
@@ -441,7 +502,8 @@
 
   gm->protocol_info_by_name = hash_create_string (0, sizeof (uword));
   gm->protocol_info_by_protocol = hash_create (0, sizeof (uword));
-  gm->tunnel_by_key = hash_create (0, sizeof (uword));
+  gm->tunnel_by_key4 = hash_create (0, sizeof (uword));
+  gm->tunnel_by_key6 = hash_create_mem (0, sizeof(u64[4]), sizeof (uword));
 
 #define _(n,s) add_protocol (gm, GRE_PROTOCOL_##s, #s);
   foreach_gre_protocol
diff --git a/src/vnet/gre/gre.h b/src/vnet/gre/gre.h
index 788cba2..ad3e025 100644
--- a/src/vnet/gre/gre.h
+++ b/src/vnet/gre/gre.h
@@ -21,8 +21,6 @@
 #include <vnet/vnet.h>
 #include <vnet/gre/packet.h>
 #include <vnet/ip/ip.h>
-#include <vnet/ip/ip4.h>
-#include <vnet/ip/ip4_packet.h>
 #include <vnet/pg/pg.h>
 #include <vnet/ip/format.h>
 #include <vnet/adj/adj_types.h>
@@ -87,11 +85,11 @@
   /**
    * The tunnel's source/local address
    */
-  ip4_address_t tunnel_src;
+  ip46_address_t tunnel_src;
   /**
    * The tunnel's destination/remote address
    */
-  ip4_address_t tunnel_dst;
+  fib_prefix_t tunnel_dst;
   /**
    * The FIB in which the src.dst address are present
    */
@@ -142,10 +140,16 @@
    *  Hash tables mapping name/protocol to protocol info index.
    */
   uword * protocol_info_by_name, * protocol_info_by_protocol;
+
   /**
-   * Hash mapping src/dst addr pair to tunnel
+   * Hash mapping ipv4 src/dst addr pair to tunnel
    */
-  uword * tunnel_by_key;
+  uword * tunnel_by_key4;
+
+  /**
+     * Hash mapping ipv6 src/dst addr pair to tunnel
+     */
+    uword * tunnel_by_key6;
 
   /**
    * Free vlib hw_if_indices.
@@ -176,6 +180,14 @@
   gre_header_t gre;
 }) ip4_and_gre_header_t;
 
+/**
+ * @brief IPv6 and GRE header.
+ */
+typedef CLIB_PACKED (struct {
+  ip6_header_t ip6;
+  gre_header_t gre;
+}) ip6_and_gre_header_t;
+
 always_inline gre_protocol_info_t *
 gre_get_protocol_info (gre_main_t * em, gre_protocol_t protocol)
 {
@@ -204,7 +216,8 @@
 format_function_t format_gre_header;
 format_function_t format_gre_header_with_length;
 
-extern vlib_node_registration_t gre_input_node;
+extern vlib_node_registration_t gre4_input_node;
+extern vlib_node_registration_t gre6_input_node;
 extern vnet_device_class_t gre_device_class;
 extern vnet_device_class_t gre_device_teb_class;
 
@@ -228,7 +241,8 @@
 typedef struct {
   u8 is_add;
 
-  ip4_address_t src, dst;
+  ip46_address_t src, dst;
+  u8 is_ipv6;
   u32 outer_fib_id;
   u8 teb;
 } vnet_gre_add_del_tunnel_args_t;
diff --git a/src/vnet/gre/gre_api.c b/src/vnet/gre/gre_api.c
index 333838c..ceeb1d4 100644
--- a/src/vnet/gre/gre_api.c
+++ b/src/vnet/gre/gre_api.c
@@ -55,17 +55,17 @@
   int rv = 0;
   vnet_gre_add_del_tunnel_args_t _a, *a = &_a;
   u32 outer_fib_id;
-  uword *p;
-  ip4_main_t *im = &ip4_main;
+  u32 p;
   u32 sw_if_index = ~0;
 
-  p = hash_get (im->fib_index_by_table_id, ntohl (mp->outer_fib_id));
-  if (!p)
+  p = fib_table_find (!mp->is_ipv6 ? FIB_PROTOCOL_IP4 : FIB_PROTOCOL_IP6,
+		      ntohl (mp->outer_fib_id));
+  if (p == ~0)
     {
       rv = VNET_API_ERROR_NO_SUCH_FIB;
       goto out;
     }
-  outer_fib_id = p[0];
+  outer_fib_id = p;
 
   /* Check src & dst are different */
   if ((mp->is_ipv6 && memcmp (mp->src_address, mp->dst_address, 16) == 0) ||
@@ -78,10 +78,19 @@
 
   a->is_add = mp->is_add;
   a->teb = mp->teb;
+  a->is_ipv6 = mp->is_ipv6;
 
   /* ip addresses sent in network byte order */
-  clib_memcpy (&(a->src), mp->src_address, 4);
-  clib_memcpy (&(a->dst), mp->dst_address, 4);
+  if (!mp->is_ipv6)
+    {
+      clib_memcpy (&(a->src.ip4), mp->src_address, 4);
+      clib_memcpy (&(a->dst.ip4), mp->dst_address, 4);
+    }
+  else
+    {
+      clib_memcpy (&(a->src.ip6), mp->src_address, 16);
+      clib_memcpy (&(a->dst.ip6), mp->dst_address, 16);
+    }
 
   a->outer_fib_id = outer_fib_id;
   rv = vnet_gre_add_del_tunnel (a, &sw_if_index);
@@ -99,17 +108,30 @@
   (gre_tunnel_t * t, unix_shared_memory_queue_t * q, u32 context)
 {
   vl_api_gre_tunnel_details_t *rmp;
-  ip4_main_t *im = &ip4_main;
+  u8 is_ipv6 = t->tunnel_dst.fp_proto == FIB_PROTOCOL_IP6 ? 1 : 0;
+  fib_table_t *ft;
 
   rmp = vl_msg_api_alloc (sizeof (*rmp));
   memset (rmp, 0, sizeof (*rmp));
   rmp->_vl_msg_id = ntohs (VL_API_GRE_TUNNEL_DETAILS);
-  clib_memcpy (rmp->src_address, &(t->tunnel_src), 4);
-  clib_memcpy (rmp->dst_address, &(t->tunnel_dst), 4);
-  rmp->outer_fib_id = htonl (im->fibs[t->outer_fib_index].ft_table_id);
+  if (!is_ipv6)
+    {
+      clib_memcpy (rmp->src_address, &(t->tunnel_src.ip4.as_u8), 4);
+      clib_memcpy (rmp->dst_address, &(t->tunnel_dst.fp_addr.ip4.as_u8), 4);
+      ft = fib_table_get (t->outer_fib_index, FIB_PROTOCOL_IP4);
+      rmp->outer_fib_id = ft->ft_table_id;
+    }
+  else
+    {
+      clib_memcpy (rmp->src_address, &(t->tunnel_src.ip6.as_u8), 16);
+      clib_memcpy (rmp->dst_address, &(t->tunnel_dst.fp_addr.ip6.as_u8), 16);
+      ft = fib_table_get (t->outer_fib_index, FIB_PROTOCOL_IP6);
+      rmp->outer_fib_id = ft->ft_table_id;
+    }
   rmp->teb = (GRE_TUNNEL_TYPE_TEB == t->type);
   rmp->sw_if_index = htonl (t->sw_if_index);
   rmp->context = context;
+  rmp->is_ipv6 = is_ipv6;
 
   vl_msg_api_send_shmem (q, (u8 *) & rmp);
 }
diff --git a/src/vnet/gre/interface.c b/src/vnet/gre/interface.c
index d4476ac..91a3899 100644
--- a/src/vnet/gre/interface.c
+++ b/src/vnet/gre/interface.c
@@ -20,6 +20,7 @@
 #include <vnet/gre/gre.h>
 #include <vnet/ip/format.h>
 #include <vnet/fib/ip4_fib.h>
+#include <vnet/fib/ip6_fib.h>
 #include <vnet/adj/adj_midchain.h>
 #include <vnet/adj/adj_nbr.h>
 #include <vnet/mpls/mpls.h>
@@ -27,7 +28,7 @@
 static const char *gre_tunnel_type_names[] = GRE_TUNNEL_TYPE_NAMES;
 
 static inline u64
-gre_mk_key (const ip4_address_t *src,
+gre4_mk_key (const ip4_address_t *src,
             const ip4_address_t *dst,
             u32 out_fib_index)
 {
@@ -48,30 +49,51 @@
 {
   gre_tunnel_t * t = va_arg (*args, gre_tunnel_t *);
   gre_main_t * gm = &gre_main;
+  u8 is_ipv6 = t->tunnel_dst.fp_proto == FIB_PROTOCOL_IP6 ? 1 : 0;
 
-  s = format (s,
-              "[%d] %U (src) %U (dst) payload %U outer_fib_index %d",
-              t - gm->tunnels,
-              format_ip4_address, &t->tunnel_src,
-              format_ip4_address, &t->tunnel_dst,
-              format_gre_tunnel_type, t->type,
-              t->outer_fib_index);
+  if (!is_ipv6)
+      s = format (s,
+                  "[%d] %U (src) %U (dst) payload %U outer_fib_index %d",
+                  t - gm->tunnels,
+                  format_ip4_address, &t->tunnel_src.ip4,
+                  format_ip4_address, &t->tunnel_dst.fp_addr.ip4,
+                  format_gre_tunnel_type, t->type,
+                  t->outer_fib_index);
+  else
+      s = format (s,
+                  "[%d] %U (src) %U (dst) payload %U outer_fib_index %d",
+                  t - gm->tunnels,
+                  format_ip6_address, &t->tunnel_src.ip6,
+                  format_ip6_address, &t->tunnel_dst.fp_addr.ip6,
+                  format_gre_tunnel_type, t->type,
+                  t->outer_fib_index);
 
   return s;
 }
 
 static gre_tunnel_t *
-gre_tunnel_db_find (const ip4_address_t *src,
-                    const ip4_address_t *dst,
-                    u32 out_fib_index)
+gre_tunnel_db_find (const ip46_address_t *src,
+                    const ip46_address_t *dst,
+                    u32 out_fib_index,
+                    u8 is_ipv6)
 {
   gre_main_t * gm = &gre_main;
   uword * p;
-  u64 key;
+  u64 key4, key6[4];
 
-  key = gre_mk_key(src, dst, out_fib_index);
-
-  p = hash_get (gm->tunnel_by_key, key);
+  if (!is_ipv6)
+    {
+      key4 = gre4_mk_key(&src->ip4, &dst->ip4, out_fib_index);
+      p = hash_get (gm->tunnel_by_key4, key4);
+    }
+  else
+    {
+      key6[0] = src->ip6.as_u64[0];
+      key6[1] = src->ip6.as_u64[1];
+      key6[2] = dst->ip6.as_u64[0];
+      key6[3] = dst->ip6.as_u64[1];
+      p = hash_get_mem (gm->tunnel_by_key6, key6);
+    }
 
   if (NULL == p)
     return (NULL);
@@ -83,20 +105,49 @@
 gre_tunnel_db_add (const gre_tunnel_t *t)
 {
   gre_main_t * gm = &gre_main;
-  u64 key;
+  u64 key4, key6[4], *key6_copy;
+  u8 is_ipv6 = t->tunnel_dst.fp_proto == FIB_PROTOCOL_IP6 ? 1 : 0;
 
-  key = gre_mk_key(&t->tunnel_src, &t->tunnel_dst, t->outer_fib_index);
-  hash_set (gm->tunnel_by_key, key, t - gm->tunnels);
+  if (!is_ipv6)
+    {
+      key4 = gre4_mk_key(&t->tunnel_src.ip4, &t->tunnel_dst.fp_addr.ip4,
+                       t->outer_fib_index);
+      hash_set (gm->tunnel_by_key4, key4, t - gm->tunnels);
+    }
+  else
+    {
+      key6[0] = t->tunnel_src.ip6.as_u64[0];
+      key6[1] = t->tunnel_src.ip6.as_u64[1];
+      key6[2] = t->tunnel_dst.fp_addr.ip6.as_u64[0];
+      key6[3] = t->tunnel_dst.fp_addr.ip6.as_u64[1];
+      key6_copy = clib_mem_alloc (sizeof (key6));
+      clib_memcpy (key6_copy, key6, sizeof (key6));
+      hash_set_mem (gm->tunnel_by_key6, key6_copy, t - gm->tunnels);
+    }
 }
 
 static void
 gre_tunnel_db_remove (const gre_tunnel_t *t)
 {
   gre_main_t * gm = &gre_main;
-  u64 key;
+  u64 key4, key6[4];
+  u8 is_ipv6 = t->tunnel_dst.fp_proto == FIB_PROTOCOL_IP6 ? 1 : 0;
 
-  key = gre_mk_key(&t->tunnel_src, &t->tunnel_dst, t->outer_fib_index);
-  hash_unset (gm->tunnel_by_key, key);
+  if (!is_ipv6)
+    {
+      key4 = gre4_mk_key(&t->tunnel_src.ip4, &t->tunnel_dst.fp_addr.ip4,
+                         t->outer_fib_index);
+      hash_unset (gm->tunnel_by_key4, key4);
+    }
+  else
+    {
+      key6[0] = t->tunnel_src.ip6.as_u64[0];
+      key6[1] = t->tunnel_src.ip6.as_u64[1];
+      key6[2] = t->tunnel_dst.fp_addr.ip6.as_u64[0];
+      key6[3] = t->tunnel_dst.fp_addr.ip6.as_u64[1];
+      hash_unset_mem (gm->tunnel_by_key6, key6);
+    }
+
 }
 
 static gre_tunnel_t *
@@ -230,26 +281,31 @@
     .fnv_back_walk = gre_tunnel_back_walk,
 };
 
-static int 
+static int
 vnet_gre_tunnel_add (vnet_gre_add_del_tunnel_args_t *a,
                      u32 * sw_if_indexp)
 {
   gre_main_t * gm = &gre_main;
   vnet_main_t * vnm = gm->vnet_main;
-  ip4_main_t * im = &ip4_main;
+  ip4_main_t * im4 = &ip4_main;
+  ip6_main_t * im6 = &ip6_main;
   gre_tunnel_t * t;
   vnet_hw_interface_t * hi;
   u32 hw_if_index, sw_if_index;
   u32 outer_fib_index;
   u8 address[6];
   clib_error_t *error;
+  u8 is_ipv6 = a->is_ipv6;
 
-  outer_fib_index = ip4_fib_index_from_table_id(a->outer_fib_id);
+  if (!is_ipv6)
+    outer_fib_index = ip4_fib_index_from_table_id(a->outer_fib_id);
+  else
+    outer_fib_index = ip6_fib_index_from_table_id(a->outer_fib_id);
 
   if (~0 == outer_fib_index)
     return VNET_API_ERROR_NO_SUCH_FIB;
 
-  t = gre_tunnel_db_find(&a->src, &a->dst, a->outer_fib_id);
+  t = gre_tunnel_db_find(&a->src, &a->dst, a->outer_fib_id, a->is_ipv6);
 
   if (NULL != t)
     return VNET_API_ERROR_INVALID_VALUE;
@@ -336,37 +392,40 @@
   vec_validate_init_empty (gm->tunnel_index_by_sw_if_index, sw_if_index, ~0);
   gm->tunnel_index_by_sw_if_index[sw_if_index] = t - gm->tunnels;
 
-  vec_validate (im->fib_index_by_sw_if_index, sw_if_index);
+  if (!is_ipv6)
+    {
+      vec_validate (im4->fib_index_by_sw_if_index, sw_if_index);
+      hi->min_packet_bytes = 64 + sizeof (gre_header_t) + sizeof (ip4_header_t);
+    }
+  else
+    {
+      vec_validate (im6->fib_index_by_sw_if_index, sw_if_index);
+      hi->min_packet_bytes = 64 + sizeof (gre_header_t) + sizeof (ip6_header_t);
+    }
 
-  hi->min_packet_bytes = 64 + sizeof (gre_header_t) + sizeof (ip4_header_t);
   hi->per_packet_overhead_bytes =
       /* preamble */ 8 + /* inter frame gap */ 12;
 
   /* Standard default gre MTU. */
   hi->max_l3_packet_bytes[VLIB_RX] = hi->max_l3_packet_bytes[VLIB_TX] = 9000;
 
-  clib_memcpy (&t->tunnel_src, &a->src, sizeof (t->tunnel_src));
-  clib_memcpy (&t->tunnel_dst, &a->dst, sizeof (t->tunnel_dst));
-
-  gre_tunnel_db_add(t);
-
   /*
    * source the FIB entry for the tunnel's destination
    * and become a child thereof. The tunnel will then get poked
    * when the forwarding for the entry updates, and the tunnel can
    * re-stack accordingly
    */
-  const fib_prefix_t tun_dst_pfx = {
-      .fp_len = 32,
-      .fp_proto = FIB_PROTOCOL_IP4,
-      .fp_addr = {
-          .ip4 = t->tunnel_dst,
-      }
-  };
+
+  clib_memcpy (&t->tunnel_src, &a->src, sizeof (t->tunnel_src));
+  t->tunnel_dst.fp_len = !is_ipv6 ? 32 : 128;
+  t->tunnel_dst.fp_proto = !is_ipv6 ? FIB_PROTOCOL_IP4 : FIB_PROTOCOL_IP6;
+  t->tunnel_dst.fp_addr = a->dst;
+
+  gre_tunnel_db_add(t);
 
   t->fib_entry_index =
       fib_table_entry_special_add(outer_fib_index,
-                                  &tun_dst_pfx,
+                                  &t->tunnel_dst,
                                   FIB_SOURCE_RR,
                                   FIB_ENTRY_FLAG_NONE,
                                   ADJ_INDEX_INVALID);
@@ -375,12 +434,9 @@
                           FIB_NODE_TYPE_GRE_TUNNEL,
                           t - gm->tunnels);
 
-  clib_memcpy (&t->tunnel_src, &a->src, sizeof (t->tunnel_src));
-  clib_memcpy (&t->tunnel_dst, &a->dst, sizeof (t->tunnel_dst));
-
   if (GRE_TUNNEL_TYPE_TEB == t->type)
   {
-      t->l2_adj_index = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4,
+      t->l2_adj_index = adj_nbr_add_or_lock(t->tunnel_dst.fp_proto,
 					    VNET_LINK_ETHERNET,
 					    &zero_addr,
 					    sw_if_index);
@@ -393,7 +449,7 @@
   return 0;
 }
 
-static int 
+static int
 vnet_gre_tunnel_delete (vnet_gre_add_del_tunnel_args_t *a,
                         u32 * sw_if_indexp)
 {
@@ -402,7 +458,7 @@
   gre_tunnel_t * t;
   u32 sw_if_index;
 
-  t = gre_tunnel_db_find(&a->src, &a->dst, a->outer_fib_id);
+  t = gre_tunnel_db_find(&a->src, &a->dst, a->outer_fib_id, a->is_ipv6);
 
   if (NULL == t)
     return VNET_API_ERROR_NO_SUCH_ENTRY;
@@ -484,7 +540,7 @@
 {
   unformat_input_t _line_input, * line_input = &_line_input;
   vnet_gre_add_del_tunnel_args_t _a, * a = &_a;
-  ip4_address_t src, dst;
+  ip46_address_t src, dst;
   u32 outer_fib_id = 0;
   u8 teb = 0;
   int rv;
@@ -492,6 +548,8 @@
   u8 is_add = 1;
   u32 sw_if_index;
   clib_error_t *error = NULL;
+  u8 ipv4_set = 0;
+  u8 ipv6_set = 0;
 
   /* Get a line of input. */
   if (! unformat_user (input, unformat_line_input, line_input))
@@ -500,11 +558,19 @@
   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) {
     if (unformat (line_input, "del"))
       is_add = 0;
-    else if (unformat (line_input, "src %U", unformat_ip4_address, &src))
+    else if (unformat (line_input, "src %U", unformat_ip4_address, &src.ip4)) {
       num_m_args++;
-    else if (unformat (line_input, "dst %U", unformat_ip4_address, &dst))
+      ipv4_set = 1;
+    } else if (unformat (line_input, "dst %U", unformat_ip4_address, &dst.ip4)) {
       num_m_args++;
-    else if (unformat (line_input, "outer-fib-id %d", &outer_fib_id))
+      ipv4_set = 1;
+    } else if (unformat (line_input, "src %U", unformat_ip6_address, &src.ip6)) {
+      num_m_args++;
+      ipv6_set = 1;
+    } else if (unformat (line_input, "dst %U", unformat_ip6_address, &dst.ip6)) {
+      num_m_args++;
+      ipv6_set = 1;
+    } else if (unformat (line_input, "outer-fib-id %d", &outer_fib_id))
       ;
     else if (unformat (line_input, "teb"))
       teb = 1;
@@ -522,17 +588,37 @@
       goto done;
     }
 
-  if (memcmp (&src, &dst, sizeof(src)) == 0)
+  if ((ipv4_set && memcmp (&src.ip4, &dst.ip4, sizeof(src.ip4)) == 0) ||
+      (ipv6_set && memcmp (&src.ip6, &dst.ip6, sizeof(src.ip6)) == 0))
     {
       error = clib_error_return (0, "src and dst are identical");
       goto done;
     }
 
+  if (ipv4_set && ipv6_set)
+      return clib_error_return (0, "both IPv4 and IPv6 addresses specified");
+
+  if ((ipv4_set && memcmp (&dst.ip4, &zero_addr.ip4, sizeof(dst.ip4)) == 0) ||
+      (ipv6_set && memcmp (&dst.ip6, &zero_addr.ip6, sizeof(dst.ip6)) == 0))
+    {
+      error = clib_error_return (0, "dst address cannot be zero");
+      goto done;
+    }
+
   memset (a, 0, sizeof (*a));
   a->outer_fib_id = outer_fib_id;
   a->teb = teb;
-  clib_memcpy(&a->src, &src, sizeof(src));
-  clib_memcpy(&a->dst, &dst, sizeof(dst));
+  a->is_ipv6 = ipv6_set;
+  if (!ipv6_set)
+    {
+      clib_memcpy(&a->src.ip4, &src.ip4, sizeof(src.ip4));
+      clib_memcpy(&a->dst.ip4, &dst.ip4, sizeof(dst.ip4));
+    }
+  else
+    {
+      clib_memcpy(&a->src.ip6, &src.ip6, sizeof(src.ip6));
+      clib_memcpy(&a->dst.ip6, &dst.ip6, sizeof(dst.ip6));
+    }
 
   if (is_add)
     rv = vnet_gre_tunnel_add(a, &sw_if_index);
diff --git a/src/vnet/gre/node.c b/src/vnet/gre/node.c
index 5926e83..2683586 100644
--- a/src/vnet/gre/node.c
+++ b/src/vnet/gre/node.c
@@ -39,8 +39,9 @@
 typedef struct {
   u32 tunnel_id;
   u32 length;
-  ip4_address_t src;
-  ip4_address_t dst;
+  ip46_address_t src;
+  ip46_address_t dst;
+  u8 is_ipv6;
 } gre_rx_trace_t;
 
 u8 * format_gre_rx_trace (u8 * s, va_list * args)
@@ -48,28 +49,41 @@
   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
   gre_rx_trace_t * t = va_arg (*args, gre_rx_trace_t *);
-    
+
   s = format (s, "GRE: tunnel %d len %d src %U dst %U",
               t->tunnel_id, clib_net_to_host_u16(t->length),
-              format_ip4_address, &t->src.as_u8,
-              format_ip4_address, &t->dst.as_u8);
+              format_ip46_address, &t->src, IP46_TYPE_ANY,
+              format_ip46_address, &t->dst, IP46_TYPE_ANY);
   return s;
 }
 
-static uword
+typedef struct {
+  /* Sparse vector mapping gre protocol in network byte order
+     to next index. */
+  u16 * next_by_protocol;
+} gre_input_runtime_t;
+
+always_inline uword
 gre_input (vlib_main_t * vm,
 	   vlib_node_runtime_t * node,
-	   vlib_frame_t * from_frame)
+	   vlib_frame_t * from_frame,
+	   u8 is_ipv6)
 {
   gre_main_t * gm = &gre_main;
   __attribute__((unused)) u32 n_left_from, next_index, * from, * to_next;
-  u64 cached_tunnel_key = (u64) ~0;
+  u64 cached_tunnel_key4;
+  u64 cached_tunnel_key6[4];
   u32 cached_tunnel_sw_if_index = 0, tunnel_sw_if_index = 0;
 
   u32 cpu_index = os_get_cpu_number();
   u32 len;
   vnet_interface_main_t *im = &gm->vnet_main->interface_main;
 
+  if (!is_ipv6)
+      memset (&cached_tunnel_key4, 0xff, sizeof(cached_tunnel_key4));
+  else
+      memset (&cached_tunnel_key6, 0xff, sizeof(cached_tunnel_key6));
+
   from = vlib_frame_vector_args (from_frame);
   n_left_from = from_frame->n_vectors;
 
@@ -90,7 +104,12 @@
           u16 version0, version1;
           int verr0, verr1;
 	  u32 i0, i1, next0, next1, protocol0, protocol1;
-          ip4_header_t *ip0, *ip1;
+          ip4_header_t *ip4_0, *ip4_1;
+          ip6_header_t *ip6_0, *ip6_1;
+          u32 ip4_tun_src0, ip4_tun_dst0;
+          u32 ip4_tun_src1, ip4_tun_dst1;
+          u64 ip6_tun_src0[2], ip6_tun_dst0[2];
+          u64 ip6_tun_src1[2], ip6_tun_dst1[2];
 
 	  /* Prefetch next iteration. */
 	  {
@@ -118,18 +137,38 @@
 	  b0 = vlib_get_buffer (vm, bi0);
 	  b1 = vlib_get_buffer (vm, bi1);
 
-          /* ip4_local hands us the ip header, not the gre header */
-          ip0 = vlib_buffer_get_current (b0);
-          ip1 = vlib_buffer_get_current (b1);
+          if (!is_ipv6)
+            {
+              /* ip4_local hands us the ip header, not the gre header */
+              ip4_0 = vlib_buffer_get_current (b0);
+              ip4_1 = vlib_buffer_get_current (b1);
+              /* Save src + dst ip4 address, e.g. for mpls-o-gre */
+              ip4_tun_src0 = ip4_0->src_address.as_u32;
+              ip4_tun_dst0 = ip4_0->dst_address.as_u32;
+              ip4_tun_src1 = ip4_1->src_address.as_u32;
+              ip4_tun_dst1 = ip4_1->dst_address.as_u32;
 
-          /* Save src + dst ip4 address, e.g. for mpls-o-gre */
-          vnet_buffer(b0)->gre.src = ip0->src_address.as_u32;
-          vnet_buffer(b0)->gre.dst = ip0->dst_address.as_u32;
-          vnet_buffer(b1)->gre.src = ip1->src_address.as_u32;
-          vnet_buffer(b1)->gre.dst = ip1->dst_address.as_u32;
+              vlib_buffer_advance (b0, sizeof (*ip4_0));
+              vlib_buffer_advance (b1, sizeof (*ip4_1));
+            }
+          else
+            {
+              /* ip6_local hands us the ip header, not the gre header */
+              ip6_0 = vlib_buffer_get_current (b0);
+              ip6_1 = vlib_buffer_get_current (b1);
+              /* Save src + dst ip6 address, e.g. for mpls-o-gre */
+              ip6_tun_src0[0] = ip6_0->src_address.as_u64[0];
+              ip6_tun_src0[1] = ip6_0->src_address.as_u64[1];
+              ip6_tun_dst0[0] = ip6_0->dst_address.as_u64[0];
+              ip6_tun_dst0[1] = ip6_0->dst_address.as_u64[1];
+              ip6_tun_src1[0] = ip6_1->src_address.as_u64[0];
+              ip6_tun_src1[1] = ip6_1->src_address.as_u64[1];
+              ip6_tun_dst1[0] = ip6_1->dst_address.as_u64[0];
+              ip6_tun_dst1[1] = ip6_1->dst_address.as_u64[1];
 
-          vlib_buffer_advance (b0, sizeof (*ip0));
-          vlib_buffer_advance (b1, sizeof (*ip1));
+              vlib_buffer_advance (b0, sizeof (*ip6_0));
+              vlib_buffer_advance (b1, sizeof (*ip6_1));
+            }
 
 	  h0 = vlib_buffer_get_current (b0);
 	  h1 = vlib_buffer_get_current (b1);
@@ -164,16 +203,34 @@
 			   || next0 == GRE_INPUT_NEXT_ETHERNET_INPUT
 			   || next0 == GRE_INPUT_NEXT_MPLS_INPUT))
             {
-              u64 key = ((u64)(vnet_buffer(b0)->gre.dst) << 32) |
-                         (u64)(vnet_buffer(b0)->gre.src);
 
-              if (cached_tunnel_key != key)
+              u64 key4, key6[4];
+              if (!is_ipv6)
+        	{
+                  key4 = ((u64)(ip4_tun_dst0) << 32) | (u64)(ip4_tun_src0);
+        	}
+              else
+        	{
+                  key6[0] = ip6_tun_dst0[0];
+                  key6[1] = ip6_tun_dst0[1];
+                  key6[2] = ip6_tun_src0[0];
+                  key6[3] = ip6_tun_src0[1];
+        	}
+
+              if ((!is_ipv6 && cached_tunnel_key4 != key4) ||
+        	  (is_ipv6 && cached_tunnel_key6[0] != key6[0] &&
+        	      cached_tunnel_key6[1] != key6[1] &&
+        	      cached_tunnel_key6[2] != key6[2] &&
+        	      cached_tunnel_key6[3] != key6[3]))
                 {
                   vnet_hw_interface_t * hi;
                   gre_tunnel_t * t;
                   uword * p;
 
-                  p = hash_get (gm->tunnel_by_key, key);
+                  if (!is_ipv6)
+                    p = hash_get (gm->tunnel_by_key4, key4);
+                  else
+                    p = hash_get_mem (gm->tunnel_by_key6, key6);
                   if (!p)
                     {
                       next0 = GRE_INPUT_NEXT_DROP;
@@ -213,16 +270,34 @@
 			   || next1 == GRE_INPUT_NEXT_ETHERNET_INPUT
 			   || next1 == GRE_INPUT_NEXT_MPLS_INPUT))
             {
-              u64 key = ((u64)(vnet_buffer(b1)->gre.dst) << 32) |
-                         (u64)(vnet_buffer(b1)->gre.src);
+              u64 key4, key6[4];
+              if (!is_ipv6)
+        	{
+                  key4 = ((u64)(ip4_tun_dst1) << 32) | (u64)(ip4_tun_src1);
+        	}
+              else
+        	{
+                  key6[0] = ip6_tun_dst1[0];
+                  key6[1] = ip6_tun_dst1[1];
+                  key6[2] = ip6_tun_src1[0];
+                  key6[3] = ip6_tun_src1[1];
+        	}
 
-              if (cached_tunnel_key != key)
+              if ((!is_ipv6 && cached_tunnel_key4 != key4) ||
+        	  (is_ipv6 && cached_tunnel_key6[0] != key6[0] &&
+        	      cached_tunnel_key6[1] != key6[1] &&
+        	      cached_tunnel_key6[2] != key6[2] &&
+        	      cached_tunnel_key6[3] != key6[3]))
                 {
                   vnet_hw_interface_t * hi;
                   gre_tunnel_t * t;
                   uword * p;
 
-                  p = hash_get (gm->tunnel_by_key, key);
+                  if (!is_ipv6)
+                    p = hash_get (gm->tunnel_by_key4, key4);
+                  else
+                    p = hash_get_mem (gm->tunnel_by_key6, key6);
+
                   if (!p)
                     {
                       next1 = GRE_INPUT_NEXT_DROP;
@@ -262,9 +337,20 @@
               gre_rx_trace_t *tr = vlib_add_trace (vm, node,
                                                    b0, sizeof (*tr));
               tr->tunnel_id = tunnel_sw_if_index;
-              tr->length = ip0->length;
-              tr->src.as_u32 = ip0->src_address.as_u32;
-              tr->dst.as_u32 = ip0->dst_address.as_u32;
+              if (!is_ipv6)
+                {
+                  tr->length = ip4_0->length;
+                  tr->src.ip4.as_u32 = ip4_0->src_address.as_u32;
+                  tr->dst.ip4.as_u32 = ip4_0->dst_address.as_u32;
+                }
+              else
+                {
+                  tr->length = ip6_0->payload_length;
+                  tr->src.ip6.as_u64[0] = ip6_0->src_address.as_u64[0];
+                  tr->src.ip6.as_u64[1] = ip6_0->src_address.as_u64[1];
+                  tr->dst.ip6.as_u64[0] = ip6_0->dst_address.as_u64[0];
+                  tr->dst.ip6.as_u64[1] = ip6_0->dst_address.as_u64[1];
+                }
             }
 
           if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED))
@@ -272,9 +358,20 @@
               gre_rx_trace_t *tr = vlib_add_trace (vm, node,
                                                    b1, sizeof (*tr));
               tr->tunnel_id = tunnel_sw_if_index;
-              tr->length = ip1->length;
-              tr->src.as_u32 = ip1->src_address.as_u32;
-              tr->dst.as_u32 = ip1->dst_address.as_u32;
+              if (!is_ipv6)
+                {
+                  tr->length = ip4_1->length;
+                  tr->src.ip4.as_u32 = ip4_1->src_address.as_u32;
+                  tr->dst.ip4.as_u32 = ip4_1->dst_address.as_u32;
+                }
+              else
+                {
+                  tr->length = ip6_1->payload_length;
+                  tr->src.ip6.as_u64[0] = ip6_1->src_address.as_u64[0];
+                  tr->src.ip6.as_u64[1] = ip6_1->src_address.as_u64[1];
+                  tr->dst.ip6.as_u64[0] = ip6_1->dst_address.as_u64[0];
+                  tr->dst.ip6.as_u64[1] = ip6_1->dst_address.as_u64[1];
+                }
             }
 
           vlib_buffer_advance (b0, sizeof (*h0));
@@ -284,16 +381,19 @@
 					   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;
 	  gre_header_t * h0;
-          ip4_header_t * ip0;
+          ip4_header_t * ip4_0;
+          ip6_header_t * ip6_0;
           u16 version0;
           int verr0;
 	  u32 i0, next0;
+      u32 ip4_tun_src0, ip4_tun_dst0;
+      u32 ip6_tun_src0[4], ip6_tun_dst0[4];
 
 	  bi0 = from[0];
 	  to_next[0] = bi0;
@@ -303,22 +403,35 @@
 	  n_left_to_next -= 1;
 
 	  b0 = vlib_get_buffer (vm, bi0);
-          ip0 = vlib_buffer_get_current (b0);
+          ip4_0 = vlib_buffer_get_current (b0);
+          ip6_0 = (void *)ip4_0;
 
-          vnet_buffer(b0)->gre.src = ip0->src_address.as_u32;
-          vnet_buffer(b0)->gre.dst = ip0->dst_address.as_u32;
+          if (!is_ipv6)
+            {
+              ip4_tun_src0 = ip4_0->src_address.as_u32;
+              ip4_tun_dst0 = ip4_0->dst_address.as_u32;
 
-          vlib_buffer_advance (b0, sizeof (*ip0));
+              vlib_buffer_advance (b0, sizeof (*ip4_0));
+            }
+          else
+            {
+              ip6_tun_src0[0] = ip6_0->src_address.as_u64[0];
+              ip6_tun_src0[1] = ip6_0->src_address.as_u64[1];
+              ip6_tun_dst0[0] = ip6_0->dst_address.as_u64[0];
+              ip6_tun_dst0[1] = ip6_0->dst_address.as_u64[1];
+
+              vlib_buffer_advance (b0, sizeof (*ip6_0));
+            }
 
 	  h0 = vlib_buffer_get_current (b0);
 
 	  i0 = sparse_vec_index (gm->next_by_protocol, h0->protocol);
           next0 = vec_elt(gm->next_by_protocol, i0);
 
-	  b0->error = 
+	  b0->error =
               node->errors[i0 == SPARSE_VEC_INVALID_INDEX 
                            ? GRE_ERROR_UNKNOWN_PROTOCOL : GRE_ERROR_NONE];
-	  
+
           version0 = clib_net_to_host_u16 (h0->flags_and_version);
           verr0 =  version0 & GRE_VERSION_MASK;
           b0->error = verr0 ? node->errors[GRE_ERROR_UNSUPPORTED_VERSION] 
@@ -335,16 +448,34 @@
 			   || next0 == GRE_INPUT_NEXT_ETHERNET_INPUT
 			   || next0 == GRE_INPUT_NEXT_MPLS_INPUT))
             {
-              u64 key = ((u64)(vnet_buffer(b0)->gre.dst) << 32) |
-                         (u64)(vnet_buffer(b0)->gre.src);
-
-              if (cached_tunnel_key != key)
+              u64 key4, key6[4];
+              if (!is_ipv6)
+        	{
+                  key4 = ((u64)(ip4_tun_dst0) << 32) | (u64)(ip4_tun_src0);
+        	}
+              else
                 {
+                  key6[0] = ip6_tun_dst0[0];
+                  key6[1] = ip6_tun_dst0[1];
+                  key6[2] = ip6_tun_src0[0];
+                  key6[3] = ip6_tun_src0[1];
+                }
+
+              if ((!is_ipv6 && cached_tunnel_key4 != key4) ||
+        	  (is_ipv6 && cached_tunnel_key6[0] != key6[0] &&
+        	      cached_tunnel_key6[1] != key6[1] &&
+        	      cached_tunnel_key6[2] != key6[2] &&
+        	      cached_tunnel_key6[3] != key6[3]))
+        	{
                   vnet_hw_interface_t * hi;
                   gre_tunnel_t * t;
                   uword * p;
 
-                  p = hash_get (gm->tunnel_by_key, key);
+                  if (!is_ipv6)
+                    p = hash_get (gm->tunnel_by_key4, key4);
+                  else
+                    p = hash_get_mem (gm->tunnel_by_key6, key6);
+
                   if (!p)
                     {
                       next0 = GRE_INPUT_NEXT_DROP;
@@ -379,14 +510,25 @@
           vnet_buffer(b0)->sw_if_index[VLIB_RX] = tunnel_sw_if_index;
 
 drop:
-          if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) 
+          if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
             {
-              gre_rx_trace_t *tr = vlib_add_trace (vm, node, 
+              gre_rx_trace_t *tr = vlib_add_trace (vm, node,
                                                    b0, sizeof (*tr));
               tr->tunnel_id = tunnel_sw_if_index;
-              tr->length = ip0->length;
-              tr->src.as_u32 = ip0->src_address.as_u32;
-              tr->dst.as_u32 = ip0->dst_address.as_u32;
+              if (!is_ipv6)
+                {
+                  tr->length = ip4_0->length;
+                  tr->src.ip4.as_u32 = ip4_0->src_address.as_u32;
+                  tr->dst.ip4.as_u32 = ip4_0->dst_address.as_u32;
+                }
+              else
+                {
+                  tr->length = ip6_0->payload_length;
+                  tr->src.ip6.as_u64[0] = ip6_0->src_address.as_u64[0];
+                  tr->src.ip6.as_u64[1] = ip6_0->src_address.as_u64[1];
+                  tr->dst.ip6.as_u64[0] = ip6_0->dst_address.as_u64[0];
+                  tr->dst.ip6.as_u64[1] = ip6_0->dst_address.as_u64[1];
+                }
             }
 
           vlib_buffer_advance (b0, sizeof (*h0));
@@ -398,20 +540,36 @@
 
       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
     }
-  vlib_node_increment_counter (vm, gre_input_node.index,
+  vlib_node_increment_counter (vm, !is_ipv6 ? gre4_input_node.index : gre6_input_node.index,
                                GRE_ERROR_PKTS_DECAP, from_frame->n_vectors);
   return from_frame->n_vectors;
 }
 
+static uword
+gre4_input (vlib_main_t * vm,
+            vlib_node_runtime_t * node,
+         vlib_frame_t * from_frame)
+{
+   return gre_input(vm, node, from_frame, /* is_ip6 */ 0);
+}
+
+static uword
+gre6_input (vlib_main_t * vm,
+            vlib_node_runtime_t * node,
+            vlib_frame_t * from_frame)
+{
+   return gre_input(vm, node, from_frame, /* is_ip6 */ 1);
+}
+
 static char * gre_error_strings[] = {
 #define gre_error(n,s) s,
 #include "error.def"
 #undef gre_error
 };
 
-VLIB_REGISTER_NODE (gre_input_node) = {
-  .function = gre_input,
-  .name = "gre-input",
+VLIB_REGISTER_NODE (gre4_input_node) = {
+  .function = gre4_input,
+  .name = "gre4-input",
   /* Takes a vector of packets. */
   .vector_size = sizeof (u32),
 
@@ -430,7 +588,31 @@
   .unformat_buffer = unformat_gre_header,
 };
 
-VLIB_NODE_FUNCTION_MULTIARCH (gre_input_node, gre_input)
+VLIB_REGISTER_NODE (gre6_input_node) = {
+  .function = gre6_input,
+  .name = "gre6-input",
+  /* Takes a vector of packets. */
+  .vector_size = sizeof (u32),
+
+  .runtime_data_bytes = sizeof (gre_input_runtime_t),
+
+  .n_errors = GRE_N_ERROR,
+  .error_strings = gre_error_strings,
+
+  .n_next_nodes = GRE_INPUT_N_NEXT,
+  .next_nodes = {
+#define _(s,n) [GRE_INPUT_NEXT_##s] = n,
+    foreach_gre_input_next
+#undef _
+  },
+
+  .format_buffer = format_gre_header_with_length,
+  .format_trace = format_gre_rx_trace,
+  .unformat_buffer = unformat_gre_header,
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (gre4_input_node, gre4_input)
+VLIB_NODE_FUNCTION_MULTIARCH (gre6_input_node, gre6_input)
 
 void
 gre_register_input_protocol (vlib_main_t * vm,
@@ -440,6 +622,7 @@
   gre_main_t * em = &gre_main;
   gre_protocol_info_t * pi;
   u16 * n;
+  u32 i;
 
   {
     clib_error_t * error = vlib_call_init_function (vm, gre_input_init);
@@ -449,9 +632,9 @@
 
   pi = gre_get_protocol_info (em, protocol);
   pi->node_index = node_index;
-  pi->next_index = vlib_node_add_next (vm, 
-				       gre_input_node.index,
-				       node_index);
+  pi->next_index = vlib_node_add_next (vm, gre4_input_node.index, node_index);
+  i = vlib_node_add_next (vm, gre6_input_node.index, node_index);
+  ASSERT(i == pi->next_index);
 
   /* Setup gre protocol -> next index sparse vector mapping. */
   n = sparse_vec_validate (em->next_by_protocol,
@@ -476,13 +659,14 @@
   vlib_node_t *ethernet_input, *ip4_input, *ip6_input, *mpls_unicast_input;
 
   {
-    clib_error_t * error; 
+    clib_error_t * error;
     error = vlib_call_init_function (vm, gre_init);
     if (error)
       clib_error_report (error);
   }
 
-  gre_setup_node (vm, gre_input_node.index);
+  gre_setup_node (vm, gre4_input_node.index);
+  gre_setup_node (vm, gre6_input_node.index);
 
   gm->next_by_protocol = sparse_vec_new
     (/* elt bytes */ sizeof (gm->next_by_protocol[0]),
@@ -501,19 +685,19 @@
   gre_register_input_protocol (vm, GRE_PROTOCOL_teb,
                                ethernet_input->index);
 
-  gre_register_input_protocol (vm, GRE_PROTOCOL_ip4, 
+  gre_register_input_protocol (vm, GRE_PROTOCOL_ip4,
                                ip4_input->index);
 
-  gre_register_input_protocol (vm, GRE_PROTOCOL_ip6, 
+  gre_register_input_protocol (vm, GRE_PROTOCOL_ip6,
                                ip6_input->index);
 
   gre_register_input_protocol (vm, GRE_PROTOCOL_mpls_unicast,
                                mpls_unicast_input->index);
 
-  ip4_register_protocol (IP_PROTOCOL_GRE, gre_input_node.index);
+  ip4_register_protocol (IP_PROTOCOL_GRE, gre4_input_node.index);
+  ip6_register_protocol (IP_PROTOCOL_GRE, gre6_input_node.index);
 
   return 0;
 }
 
 VLIB_INIT_FUNCTION (gre_input_init);
-