vcl: fix epoll connected events sid
[vpp.git] / src / vnet / ipsec / ipsec_if_in.c
index b8610f4..974227f 100644 (file)
 
 #include <vnet/ipsec/ipsec.h>
 #include <vnet/ipsec/esp.h>
+#include <vnet/ipsec/ipsec_io.h>
+#include <vnet/ipsec/ipsec_punt.h>
 
 /* Statistics (not really errors) */
 #define foreach_ipsec_if_input_error                             \
 _(RX, "good packets received")                                   \
 _(DISABLED, "ipsec packets received on disabled interface")       \
-_(NO_TUNNEL, "no matching tunnel")
+_(NO_TUNNEL, "no matching tunnel")                                \
+_(NAT_KEEPALIVE, "NAT Keepalive")                                 \
+_(TOO_SHORT, "Too Short")                                         \
+_(SPI_0, "SPI 0")
 
 static char *ipsec_if_input_error_strings[] = {
 #define _(sym,string) string,
@@ -45,7 +50,12 @@ typedef enum
 
 typedef struct
 {
-  u32 spi;
+  union
+  {
+    ipsec4_tunnel_key_t key4;
+    ipsec6_tunnel_key_t key6;
+  };
+  u8 is_ip6;
   u32 seq;
 } ipsec_if_input_trace_t;
 
@@ -56,191 +66,652 @@ format_ipsec_if_input_trace (u8 * s, va_list * args)
   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
   ipsec_if_input_trace_t *t = va_arg (*args, ipsec_if_input_trace_t *);
 
-  s = format (s, "IPSec: spi %u seq %u", t->spi, t->seq);
+  if (t->is_ip6)
+    s = format (s, "IPSec: %U seq %u",
+               format_ipsec6_tunnel_key, &t->key6, t->seq);
+  else
+    s = format (s, "IPSec: %U seq %u",
+               format_ipsec4_tunnel_key, &t->key4, t->seq);
   return s;
 }
 
-VLIB_NODE_FN (ipsec_if_input_node) (vlib_main_t * vm,
-                                   vlib_node_runtime_t * node,
-                                   vlib_frame_t * from_frame)
+always_inline u16
+ipsec_ip4_if_no_tunnel (vlib_node_runtime_t * node,
+                       vlib_buffer_t * b,
+                       const esp_header_t * esp,
+                       const ip4_header_t * ip4, u16 offset)
+{
+  if (PREDICT_FALSE (0 == esp->spi))
+    {
+      b->error = node->errors[IPSEC_IF_INPUT_ERROR_SPI_0];
+      b->punt_reason =
+       ipsec_punt_reason[(ip4->protocol == IP_PROTOCOL_UDP ?
+                          IPSEC_PUNT_IP4_SPI_UDP_0 :
+                          IPSEC_PUNT_IP4_NO_SUCH_TUNNEL)];
+    }
+  else
+    {
+      b->error = node->errors[IPSEC_IF_INPUT_ERROR_NO_TUNNEL];
+      b->punt_reason = ipsec_punt_reason[IPSEC_PUNT_IP4_NO_SUCH_TUNNEL];
+    }
+  vlib_buffer_advance (b, -offset);
+  return IPSEC_INPUT_NEXT_PUNT;
+}
+
+always_inline u16
+ipsec_ip6_if_no_tunnel (vlib_node_runtime_t * node,
+                       vlib_buffer_t * b,
+                       const esp_header_t * esp, u16 offset)
+{
+  b->error = node->errors[IPSEC_IF_INPUT_ERROR_NO_TUNNEL];
+  b->punt_reason = ipsec_punt_reason[IPSEC_PUNT_IP6_NO_SUCH_TUNNEL];
+
+  vlib_buffer_advance (b, -offset);
+  return (IPSEC_INPUT_NEXT_PUNT);
+}
+
+always_inline uword
+ipsec_if_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
+                      vlib_frame_t * from_frame, int is_ip6)
 {
   ipsec_main_t *im = &ipsec_main;
   vnet_main_t *vnm = im->vnet_main;
   vnet_interface_main_t *vim = &vnm->interface_main;
-  ipsec_proto_main_t *em = &ipsec_proto_main;
-  u32 *from, *to_next = 0, next_index;
-  u32 n_left_from, last_sw_if_index = ~0;
+
+  int is_trace = node->flags & VLIB_NODE_FLAG_TRACE;
   u32 thread_index = vm->thread_index;
+
+  u32 n_left_from, *from;
+  u16 nexts[VLIB_FRAME_SIZE], *next;
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+
+  from = vlib_frame_vector_args (from_frame);
+  n_left_from = from_frame->n_vectors;
+
+  vlib_get_buffers (vm, from, bufs, n_left_from);
+  b = bufs;
+  next = nexts;
+
+  clib_memset_u16 (nexts, im->esp4_decrypt_next_index, n_left_from);
+
   u64 n_bytes = 0, n_packets = 0;
-  u8 icv_len;
-  ipsec_tunnel_if_t *last_t = NULL;
-  ipsec_sa_t *sa0;
+  u32 n_disabled = 0, n_no_tunnel = 0;
+
+  u32 last_sw_if_index = ~0;
+  u32 last_tunnel_id = ~0;
+  ipsec4_tunnel_key_t last_key4;
+  ipsec6_tunnel_key_t last_key6;
+
   vlib_combined_counter_main_t *rx_counter;
   vlib_combined_counter_main_t *drop_counter;
-  u32 n_disabled = 0, n_no_tunnel = 0;
+
+  if (is_ip6)
+    clib_memset (&last_key6, 0xff, sizeof (last_key6));
+  else
+    last_key4.as_u64 = ~0;
 
   rx_counter = vim->combined_sw_if_counters + VNET_INTERFACE_COUNTER_RX;
   drop_counter = vim->combined_sw_if_counters + VNET_INTERFACE_COUNTER_DROP;
 
-  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)
+  while (n_left_from >= 2)
     {
-      u32 n_left_to_next;
+      u32 sw_if_index0, sw_if_index1;
+      ip4_header_t *ip40, *ip41;
+      ip6_header_t *ip60, *ip61;
+      esp_header_t *esp0, *esp1;
+      u32 len0, len1;
+      u16 buf_adv0, buf_adv1;
+      u16 buf_rewind0, buf_rewind1;
+      u32 tid0, tid1;
+      ipsec_tunnel_if_t *t0, *t1;
+      ipsec4_tunnel_key_t key40, key41;
+      ipsec6_tunnel_key_t key60, key61;
+
+      if (n_left_from >= 4)
+       {
+         CLIB_PREFETCH (b[2], CLIB_CACHE_LINE_BYTES, STORE);
+         CLIB_PREFETCH (b[2]->data, CLIB_CACHE_LINE_BYTES, LOAD);
+         CLIB_PREFETCH (b[3], CLIB_CACHE_LINE_BYTES, STORE);
+         CLIB_PREFETCH (b[3]->data, CLIB_CACHE_LINE_BYTES, LOAD);
+       }
 
-      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+      ip40 =
+       (ip4_header_t *) (b[0]->data + vnet_buffer (b[0])->l3_hdr_offset);
+      ip41 =
+       (ip4_header_t *) (b[1]->data + vnet_buffer (b[1])->l3_hdr_offset);
 
-      while (n_left_from > 0 && n_left_to_next > 0)
+      if (is_ip6)
+       {
+         ip60 = (ip6_header_t *) ip40;
+         ip61 = (ip6_header_t *) ip41;
+         esp0 = (esp_header_t *) ((u8 *) ip60 + sizeof (ip6_header_t));
+         esp1 = (esp_header_t *) ((u8 *) ip61 + sizeof (ip6_header_t));
+         buf_adv0 = sizeof (ip6_header_t);
+         buf_adv1 = sizeof (ip6_header_t);
+       }
+      else
        {
-         u32 bi0, next0, sw_if_index0;
-         vlib_buffer_t *b0;
-         ip4_header_t *ip0;
-         esp_header_t *esp0;
-         uword *p;
-         u32 len0;
+         /* NAT UDP port 4500 case, don't advance any more */
+         if (ip40->protocol == IP_PROTOCOL_UDP)
+           {
+             esp0 =
+               (esp_header_t *) ((u8 *) ip40 + ip4_header_bytes (ip40) +
+                                 sizeof (udp_header_t));
+             buf_adv0 = 0;
+             buf_rewind0 = ip4_header_bytes (ip40) + sizeof (udp_header_t);
+           }
+         else
+           {
+             esp0 = (esp_header_t *) ((u8 *) ip40 + ip4_header_bytes (ip40));
+             buf_rewind0 = buf_adv0 = ip4_header_bytes (ip40);
+           }
+         /* NAT UDP port 4500 case, don't advance any more */
+         if (ip41->protocol == IP_PROTOCOL_UDP)
+           {
+             esp1 =
+               (esp_header_t *) ((u8 *) ip41 + ip4_header_bytes (ip41) +
+                                 sizeof (udp_header_t));
+             buf_adv1 = 0;
+             buf_rewind1 = ip4_header_bytes (ip41) + sizeof (udp_header_t);
+           }
+         else
+           {
+             esp1 = (esp_header_t *) ((u8 *) ip41 + ip4_header_bytes (ip41));
+             buf_rewind1 = buf_adv1 = ip4_header_bytes (ip41);
+           }
+       }
 
-         bi0 = to_next[0] = from[0];
-         from += 1;
-         n_left_from -= 1;
-         to_next += 1;
-         n_left_to_next -= 1;
-         b0 = vlib_get_buffer (vm, bi0);
-         ip0 = vlib_buffer_get_current (b0);
-         esp0 = (esp_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0));
+      vlib_buffer_advance (b[0], buf_adv0);
+      vlib_buffer_advance (b[1], buf_adv1);
 
-         next0 = IPSEC_INPUT_NEXT_DROP;
+      len0 = vlib_buffer_length_in_chain (vm, b[0]);
+      len1 = vlib_buffer_length_in_chain (vm, b[1]);
 
-         u64 key = (u64) ip0->src_address.as_u32 << 32 | (u64) esp0->spi;
+      /* If the packet is too short to contain an SPI, then maybe
+       * it's a keep alive */
+      if (len0 < sizeof (esp_header_t))
+       {
+         if (esp0->spi_bytes[0] == 0xff)
+           b[0]->error = node->errors[IPSEC_IF_INPUT_ERROR_NAT_KEEPALIVE];
+         else
+           b[0]->error = node->errors[IPSEC_IF_INPUT_ERROR_TOO_SHORT];
 
-         p = hash_get (im->ipsec_if_pool_index_by_key, key);
+         next[0] = IPSEC_INPUT_NEXT_DROP;
+         goto pkt1;
+       }
 
-         len0 = vlib_buffer_length_in_chain (vm, b0);
+      if (is_ip6)
+       {
+         key60.remote_ip = ip60->src_address;
+         key60.spi = esp0->spi;
+
+         if (memcmp (&key60, &last_key6, sizeof (last_key6)) == 0)
+           {
+             tid0 = last_tunnel_id;
+           }
+         else
+           {
+             uword *p =
+               hash_get_mem (im->ipsec6_if_pool_index_by_key, &key60);
+             if (p)
+               {
+                 tid0 = p[0];
+                 last_tunnel_id = tid0;
+                 clib_memcpy_fast (&last_key6, &key60, sizeof (key60));
+               }
+             else
+               {
+                 next[0] =
+                   ipsec_ip6_if_no_tunnel (node, b[0], esp0, buf_adv0);
+                 n_no_tunnel++;
+                 goto pkt1;
+               }
+           }
+       }
+      else                     /* !is_ip6 */
+       {
+         key40.remote_ip = ip40->src_address;
+         key40.spi = esp0->spi;
+
+         if (key40.as_u64 == last_key4.as_u64)
+           {
+             tid0 = last_tunnel_id;
+           }
+         else
+           {
+             uword *p =
+               hash_get (im->ipsec4_if_pool_index_by_key, key40.as_u64);
+             if (p)
+               {
+                 tid0 = p[0];
+                 last_tunnel_id = tid0;
+                 last_key4.as_u64 = key40.as_u64;
+               }
+             else
+               {
+                 next[0] =
+                   ipsec_ip4_if_no_tunnel (node, b[0], esp0, ip40,
+                                           buf_rewind0);
+                 n_no_tunnel++;
+                 goto pkt1;
+               }
+           }
+       }
+
+      t0 = pool_elt_at_index (im->tunnel_interfaces, tid0);
+      vnet_buffer (b[0])->ipsec.sad_index = t0->input_sa_index;
+
+      if (PREDICT_TRUE (t0->hw_if_index != ~0))
+       {
+         sw_if_index0 = t0->sw_if_index;
+         vnet_buffer (b[0])->sw_if_index[VLIB_RX] = sw_if_index0;
+
+         if (PREDICT_FALSE (!(t0->flags & VNET_HW_INTERFACE_FLAG_LINK_UP)))
+           {
+             vlib_increment_combined_counter
+               (drop_counter, thread_index, sw_if_index0, 1, len0);
+             n_disabled++;
+             b[0]->error = node->errors[IPSEC_IF_INPUT_ERROR_DISABLED];
+             next[0] = IPSEC_INPUT_NEXT_DROP;
+             goto pkt1;
+           }
+
+         if (PREDICT_TRUE (sw_if_index0 == last_sw_if_index))
+           {
+             n_packets++;
+             n_bytes += len0;
+           }
+         else
+           {
+             if (n_packets)
+               {
+                 vlib_increment_combined_counter
+                   (rx_counter, thread_index, last_sw_if_index,
+                    n_packets, n_bytes);
+               }
+
+             last_sw_if_index = sw_if_index0;
+             n_packets = 1;
+             n_bytes = len0;
+           }
+       }
+
+    pkt1:
+
+      if (len1 < sizeof (esp_header_t))
+       {
+         if (esp0->spi_bytes[0] == 0xff)
+           b[1]->error = node->errors[IPSEC_IF_INPUT_ERROR_NAT_KEEPALIVE];
+         else
+           b[1]->error = node->errors[IPSEC_IF_INPUT_ERROR_TOO_SHORT];
+
+         next[1] = IPSEC_INPUT_NEXT_DROP;
+         goto trace1;
+       }
+
+      if (is_ip6)
+       {
+         key61.remote_ip = ip61->src_address;
+         key61.spi = esp1->spi;
+
+         if (memcmp (&key61, &last_key6, sizeof (last_key6)) == 0)
+           {
+             tid1 = last_tunnel_id;
+           }
+         else
+           {
+             uword *p =
+               hash_get_mem (im->ipsec6_if_pool_index_by_key, &key61);
+             if (p)
+               {
+                 tid1 = p[0];
+                 last_tunnel_id = tid1;
+                 clib_memcpy_fast (&last_key6, &key61, sizeof (key61));
+               }
+             else
+               {
+                 next[1] =
+                   ipsec_ip6_if_no_tunnel (node, b[1], esp1, buf_adv1);
+                 n_no_tunnel++;
+                 goto trace1;
+               }
+           }
+       }
+      else                     /* !is_ip6 */
+       {
+         key41.remote_ip = ip41->src_address;
+         key41.spi = esp1->spi;
 
-         if (p)
+         if (key41.as_u64 == last_key4.as_u64)
            {
-             ipsec_tunnel_if_t *t;
-             t = pool_elt_at_index (im->tunnel_interfaces, p[0]);
-             vnet_buffer (b0)->ipsec.sad_index = t->input_sa_index;
-             if (t->hw_if_index != ~0)
+             tid1 = last_tunnel_id;
+           }
+         else
+           {
+             uword *p =
+               hash_get (im->ipsec4_if_pool_index_by_key, key41.as_u64);
+             if (p)
                {
-                 vnet_hw_interface_t *hi;
-
-                 vnet_buffer (b0)->ipsec.flags = 0;
-                 hi = vnet_get_hw_interface (vnm, t->hw_if_index);
-                 sw_if_index0 = hi->sw_if_index;
-                 vnet_buffer (b0)->sw_if_index[VLIB_RX] = sw_if_index0;
-
-                 if (PREDICT_FALSE
-                     (!(hi->flags & VNET_HW_INTERFACE_FLAG_LINK_UP)))
-                   {
-                     vlib_increment_combined_counter
-                       (drop_counter, thread_index, sw_if_index0, 1, len0);
-                     b0->error = node->errors[IPSEC_IF_INPUT_ERROR_DISABLED];
-                     n_disabled++;
-                     goto trace;
-                   }
-
-                 if (PREDICT_TRUE (sw_if_index0 == last_sw_if_index))
-                   {
-                     n_packets++;
-                     n_bytes += len0;
-                   }
-                 else
-                   {
-                     sa0 = pool_elt_at_index (im->sad, t->input_sa_index);
-                     icv_len =
-                       em->ipsec_proto_main_integ_algs[sa0->
-                                                       integ_alg].trunc_size;
-
-                     /* length = packet length - ESP/tunnel overhead */
-                     n_bytes -= n_packets * (sizeof (ip4_header_t) +
-                                             sizeof (esp_header_t) +
-                                             sizeof (esp_footer_t) +
-                                             16 /* aes-cbc IV */  + icv_len);
-
-                     if (last_t)
-                       {
-                         vlib_increment_combined_counter
-                           (rx_counter, thread_index, sw_if_index0,
-                            n_packets, n_bytes);
-                       }
-
-                     last_sw_if_index = sw_if_index0;
-                     last_t = t;
-                     n_packets = 1;
-                     n_bytes = len0;
-                   }
+                 tid1 = p[0];
+                 last_tunnel_id = tid1;
+                 last_key4.as_u64 = key41.as_u64;
                }
              else
                {
-                 vnet_buffer (b0)->ipsec.flags = IPSEC_FLAG_IPSEC_GRE_TUNNEL;
+                 next[1] =
+                   ipsec_ip4_if_no_tunnel (node, b[1], esp1, ip41,
+                                           buf_rewind1);
+                 n_no_tunnel++;
+                 goto trace1;
                }
+           }
+       }
+
+      t1 = pool_elt_at_index (im->tunnel_interfaces, tid1);
+      vnet_buffer (b[1])->ipsec.sad_index = t1->input_sa_index;
+
+      if (PREDICT_TRUE (t1->hw_if_index != ~0))
+       {
+         sw_if_index1 = t1->sw_if_index;
+         vnet_buffer (b[1])->sw_if_index[VLIB_RX] = sw_if_index1;
 
-             vlib_buffer_advance (b0, ip4_header_bytes (ip0));
-             next0 = im->esp4_decrypt_next_index;
+         if (PREDICT_FALSE (!(t1->flags & VNET_HW_INTERFACE_FLAG_LINK_UP)))
+           {
+             vlib_increment_combined_counter
+               (drop_counter, thread_index, sw_if_index1, 1, len1);
+             n_disabled++;
+             b[1]->error = node->errors[IPSEC_IF_INPUT_ERROR_DISABLED];
+             next[1] = IPSEC_INPUT_NEXT_DROP;
+             goto trace1;
+           }
+
+         if (PREDICT_TRUE (sw_if_index1 == last_sw_if_index))
+           {
+             n_packets++;
+             n_bytes += len1;
            }
          else
            {
-             b0->error = node->errors[IPSEC_IF_INPUT_ERROR_NO_TUNNEL];
-             n_no_tunnel++;
+             if (n_packets)
+               {
+                 vlib_increment_combined_counter
+                   (rx_counter, thread_index, last_sw_if_index,
+                    n_packets, n_bytes);
+               }
+
+             last_sw_if_index = sw_if_index1;
+             n_packets = 1;
+             n_bytes = len1;
            }
+       }
 
-       trace:
-         if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+    trace1:
+      if (PREDICT_FALSE (is_trace))
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             ipsec_if_input_trace_t *tr =
+               vlib_add_trace (vm, node, b[0], sizeof (*tr));
+             if (is_ip6)
+               clib_memcpy (&tr->key6, &key60, sizeof (tr->key6));
+             else
+               clib_memcpy (&tr->key4, &key40, sizeof (tr->key4));
+             tr->is_ip6 = is_ip6;
+             tr->seq =
+               len0 >=
+               sizeof (*esp0) ? clib_host_to_net_u32 (esp0->seq) : ~0;
+           }
+         if (b[1]->flags & VLIB_BUFFER_IS_TRACED)
            {
              ipsec_if_input_trace_t *tr =
-               vlib_add_trace (vm, node, b0, sizeof (*tr));
-             tr->spi = clib_host_to_net_u32 (esp0->spi);
-             tr->seq = clib_host_to_net_u32 (esp0->seq);
+               vlib_add_trace (vm, node, b[1], sizeof (*tr));
+             if (is_ip6)
+               clib_memcpy (&tr->key6, &key61, sizeof (tr->key6));
+             else
+               clib_memcpy (&tr->key4, &key41, sizeof (tr->key4));
+             tr->is_ip6 = is_ip6;
+             tr->seq =
+               len1 >=
+               sizeof (*esp1) ? clib_host_to_net_u32 (esp1->seq) : ~0;
            }
+       }
 
-         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
-                                          n_left_to_next, bi0, next0);
+      /* next */
+      b += 2;
+      next += 2;
+      n_left_from -= 2;
+    }
+  while (n_left_from > 0)
+    {
+      u32 sw_if_index0;
+      ip4_header_t *ip40;
+      ip6_header_t *ip60;
+      esp_header_t *esp0;
+      u32 len0;
+      u16 buf_adv0, buf_rewind0;
+      u32 tid0;
+      ipsec_tunnel_if_t *t0;
+      ipsec4_tunnel_key_t key40;
+      ipsec6_tunnel_key_t key60;
+
+      ip40 =
+       (ip4_header_t *) (b[0]->data + vnet_buffer (b[0])->l3_hdr_offset);
+
+      if (is_ip6)
+       {
+         ip60 = (ip6_header_t *) ip40;
+         esp0 = (esp_header_t *) ((u8 *) ip60 + sizeof (ip6_header_t));
+         buf_adv0 = sizeof (ip6_header_t);
        }
-      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+      else
+       {
+         /* NAT UDP port 4500 case, don't advance any more */
+         if (ip40->protocol == IP_PROTOCOL_UDP)
+           {
+             esp0 =
+               (esp_header_t *) ((u8 *) ip40 + ip4_header_bytes (ip40) +
+                                 sizeof (udp_header_t));
+             buf_adv0 = 0;
+             buf_rewind0 = ip4_header_bytes (ip40) + sizeof (udp_header_t);
+           }
+         else
+           {
+             esp0 = (esp_header_t *) ((u8 *) ip40 + ip4_header_bytes (ip40));
+             buf_rewind0 = buf_adv0 = ip4_header_bytes (ip40);
+           }
+       }
+
+      /* stats for the tunnel include all the data after the IP header
+         just like a norml IP-IP tunnel */
+      vlib_buffer_advance (b[0], buf_adv0);
+      len0 = vlib_buffer_length_in_chain (vm, b[0]);
+
+      if (len0 < sizeof (esp_header_t))
+       {
+         if (esp0->spi_bytes[0] == 0xff)
+           b[0]->error = node->errors[IPSEC_IF_INPUT_ERROR_NAT_KEEPALIVE];
+         else
+           b[0]->error = node->errors[IPSEC_IF_INPUT_ERROR_TOO_SHORT];
+
+         next[0] = IPSEC_INPUT_NEXT_DROP;
+         goto trace00;
+       }
+
+      if (is_ip6)
+       {
+         key60.remote_ip = ip60->src_address;
+         key60.spi = esp0->spi;
+
+         if (memcmp (&key60, &last_key6, sizeof (last_key6)) == 0)
+           {
+             tid0 = last_tunnel_id;
+           }
+         else
+           {
+             uword *p =
+               hash_get_mem (im->ipsec6_if_pool_index_by_key, &key60);
+             if (p)
+               {
+                 tid0 = p[0];
+                 last_tunnel_id = tid0;
+                 clib_memcpy_fast (&last_key6, &key60, sizeof (key60));
+               }
+             else
+               {
+                 next[0] =
+                   ipsec_ip6_if_no_tunnel (node, b[0], esp0, buf_adv0);
+                 n_no_tunnel++;
+                 goto trace00;
+               }
+           }
+       }
+      else                     /* !is_ip6 */
+       {
+         key40.remote_ip = ip40->src_address;
+         key40.spi = esp0->spi;
+
+         if (key40.as_u64 == last_key4.as_u64)
+           {
+             tid0 = last_tunnel_id;
+           }
+         else
+           {
+             uword *p =
+               hash_get (im->ipsec4_if_pool_index_by_key, key40.as_u64);
+             if (p)
+               {
+                 tid0 = p[0];
+                 last_tunnel_id = tid0;
+                 last_key4.as_u64 = key40.as_u64;
+               }
+             else
+               {
+                 next[0] =
+                   ipsec_ip4_if_no_tunnel (node, b[0], esp0, ip40,
+                                           buf_rewind0);
+                 n_no_tunnel++;
+                 goto trace00;
+               }
+           }
+       }
+
+      t0 = pool_elt_at_index (im->tunnel_interfaces, tid0);
+      vnet_buffer (b[0])->ipsec.sad_index = t0->input_sa_index;
+
+      if (PREDICT_TRUE (t0->hw_if_index != ~0))
+       {
+         sw_if_index0 = t0->sw_if_index;
+         vnet_buffer (b[0])->sw_if_index[VLIB_RX] = sw_if_index0;
+
+         if (PREDICT_FALSE (!(t0->flags & VNET_HW_INTERFACE_FLAG_LINK_UP)))
+           {
+             vlib_increment_combined_counter
+               (drop_counter, thread_index, sw_if_index0, 1, len0);
+             n_disabled++;
+             b[0]->error = node->errors[IPSEC_IF_INPUT_ERROR_DISABLED];
+             next[0] = IPSEC_INPUT_NEXT_DROP;
+             goto trace00;
+           }
+
+         if (PREDICT_TRUE (sw_if_index0 == last_sw_if_index))
+           {
+             n_packets++;
+             n_bytes += len0;
+           }
+         else
+           {
+             if (n_packets)
+               {
+                 vlib_increment_combined_counter
+                   (rx_counter, thread_index, last_sw_if_index,
+                    n_packets, n_bytes);
+               }
+
+             last_sw_if_index = sw_if_index0;
+             n_packets = 1;
+             n_bytes = len0;
+           }
+       }
+
+    trace00:
+      if (PREDICT_FALSE (is_trace))
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             ipsec_if_input_trace_t *tr =
+               vlib_add_trace (vm, node, b[0], sizeof (*tr));
+             if (is_ip6)
+               clib_memcpy (&tr->key6, &key60, sizeof (tr->key6));
+             else
+               clib_memcpy (&tr->key4, &key40, sizeof (tr->key4));
+             tr->is_ip6 = is_ip6;
+             tr->seq =
+               len0 >=
+               sizeof (*esp0) ? clib_host_to_net_u32 (esp0->seq) : ~0;
+           }
+       }
+
+      /* next */
+      b += 1;
+      next += 1;
+      n_left_from -= 1;
     }
 
-  if (last_t)
+  if (n_packets)
     {
-      sa0 = pool_elt_at_index (im->sad, last_t->input_sa_index);
-      icv_len = em->ipsec_proto_main_integ_algs[sa0->integ_alg].trunc_size;
-
-      n_bytes -= n_packets * (sizeof (ip4_header_t) + sizeof (esp_header_t) +
-                             sizeof (esp_footer_t) + 16 /* aes-cbc IV */  +
-                             icv_len);
       vlib_increment_combined_counter (rx_counter,
                                       thread_index,
                                       last_sw_if_index, n_packets, n_bytes);
     }
 
-  vlib_node_increment_counter (vm, ipsec_if_input_node.index,
+  vlib_node_increment_counter (vm, node->node_index,
                               IPSEC_IF_INPUT_ERROR_RX,
-                              from_frame->n_vectors - n_disabled);
+                              from_frame->n_vectors - (n_disabled +
+                                                       n_no_tunnel));
 
-  vlib_node_increment_counter (vm, ipsec_if_input_node.index,
-                              IPSEC_IF_INPUT_ERROR_DISABLED, n_disabled);
-  vlib_node_increment_counter (vm, ipsec_if_input_node.index,
-                              IPSEC_IF_INPUT_ERROR_DISABLED, n_no_tunnel);
+  vlib_buffer_enqueue_to_next (vm, node, from, nexts, from_frame->n_vectors);
 
   return from_frame->n_vectors;
 }
 
+VLIB_NODE_FN (ipsec4_if_input_node) (vlib_main_t * vm,
+                                    vlib_node_runtime_t * node,
+                                    vlib_frame_t * from_frame)
+{
+  return ipsec_if_input_inline (vm, node, from_frame, 0 /* is_ip6 */ );
+}
+
 /* *INDENT-OFF* */
-VLIB_REGISTER_NODE (ipsec_if_input_node) = {
-  .name = "ipsec-if-input",
+VLIB_REGISTER_NODE (ipsec4_if_input_node) = {
+  .name = "ipsec4-if-input",
   .vector_size = sizeof (u32),
   .format_trace = format_ipsec_if_input_trace,
   .type = VLIB_NODE_TYPE_INTERNAL,
-
   .n_errors = ARRAY_LEN(ipsec_if_input_error_strings),
   .error_strings = ipsec_if_input_error_strings,
-
   .sibling_of = "ipsec4-input-feature",
 };
 /* *INDENT-ON* */
 
+VLIB_NODE_FN (ipsec6_if_input_node) (vlib_main_t * vm,
+                                    vlib_node_runtime_t * node,
+                                    vlib_frame_t * from_frame)
+{
+  return ipsec_if_input_inline (vm, node, from_frame, 1 /* is_ip6 */ );
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (ipsec6_if_input_node) = {
+  .name = "ipsec6-if-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_ipsec_if_input_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN(ipsec_if_input_error_strings),
+  .error_strings = ipsec_if_input_error_strings,
+  .sibling_of = "ipsec6-input-feature",
+};
+/* *INDENT-ON* */
+
 /*
  * fd.io coding-style-patch-verification: ON
  *