ipsec: perf improvement of ipsec4_input_node using flow cache
[vpp.git] / src / vnet / ipsec / ipsec_input.c
index aa7627d..09166bc 100644 (file)
@@ -71,8 +71,86 @@ format_ipsec_input_trace (u8 * s, va_list * args)
   return s;
 }
 
+always_inline void
+ipsec4_input_spd_add_flow_cache_entry (ipsec_main_t *im, u32 sa, u32 da,
+                                      ipsec_spd_policy_type_t policy_type,
+                                      u32 pol_id)
+{
+  u64 hash;
+  u8 is_overwrite = 0, is_stale_overwrite = 0;
+  /* Store in network byte order to avoid conversion on lookup */
+  ipsec4_inbound_spd_tuple_t ip4_tuple = {
+    .ip4_src_addr = (ip4_address_t) clib_host_to_net_u32 (sa),
+    .ip4_dest_addr = (ip4_address_t) clib_host_to_net_u32 (da),
+    .policy_type = policy_type
+  };
+
+  ip4_tuple.kv_16_8.value =
+    (((u64) pol_id) << 32) | ((u64) im->input_epoch_count);
+
+  hash = ipsec4_hash_16_8 (&ip4_tuple.kv_16_8);
+  hash &= (im->ipsec4_in_spd_hash_num_buckets - 1);
+
+  ipsec_spinlock_lock (&im->ipsec4_in_spd_hash_tbl[hash].bucket_lock);
+  /* Check if we are overwriting an existing entry so we know
+    whether to increment the flow cache counter. Since flow
+    cache counter is reset on any policy add/remove, but
+    hash table values are not, we need to check if the entry
+    we are overwriting is stale or not. If it's a stale entry
+    overwrite, we still want to increment flow cache counter */
+  is_overwrite = (im->ipsec4_in_spd_hash_tbl[hash].value != 0);
+  /* Check if we are overwriting a stale entry by comparing
+     with current epoch count */
+  if (PREDICT_FALSE (is_overwrite))
+    is_stale_overwrite =
+      (im->input_epoch_count !=
+       ((u32) (im->ipsec4_in_spd_hash_tbl[hash].value & 0xFFFFFFFF)));
+  clib_memcpy_fast (&im->ipsec4_in_spd_hash_tbl[hash], &ip4_tuple.kv_16_8,
+                   sizeof (ip4_tuple.kv_16_8));
+  ipsec_spinlock_unlock (&im->ipsec4_in_spd_hash_tbl[hash].bucket_lock);
+
+  /* Increment the counter to track active flow cache entries
+    when entering a fresh entry or overwriting a stale one */
+  if (!is_overwrite || is_stale_overwrite)
+    clib_atomic_fetch_add_relax (&im->ipsec4_in_spd_flow_cache_entries, 1);
+
+  return;
+}
+
+always_inline ipsec_policy_t *
+ipsec4_input_spd_find_flow_cache_entry (ipsec_main_t *im, u32 sa, u32 da,
+                                       ipsec_spd_policy_type_t policy_type)
+{
+  ipsec_policy_t *p = NULL;
+  ipsec4_hash_kv_16_8_t kv_result;
+  u64 hash;
+  ipsec4_inbound_spd_tuple_t ip4_tuple = { .ip4_src_addr = (ip4_address_t) sa,
+                                          .ip4_dest_addr = (ip4_address_t) da,
+                                          .policy_type = policy_type };
+
+  hash = ipsec4_hash_16_8 (&ip4_tuple.kv_16_8);
+  hash &= (im->ipsec4_in_spd_hash_num_buckets - 1);
+
+  ipsec_spinlock_lock (&im->ipsec4_in_spd_hash_tbl[hash].bucket_lock);
+  kv_result = im->ipsec4_in_spd_hash_tbl[hash];
+  ipsec_spinlock_unlock (&im->ipsec4_in_spd_hash_tbl[hash].bucket_lock);
+
+  if (ipsec4_hash_key_compare_16_8 ((u64 *) &ip4_tuple.kv_16_8,
+                                   (u64 *) &kv_result))
+    {
+      if (im->input_epoch_count == ((u32) (kv_result.value & 0xFFFFFFFF)))
+       {
+         /* Get the policy based on the index */
+         p =
+           pool_elt_at_index (im->policies, ((u32) (kv_result.value >> 32)));
+       }
+    }
+
+  return p;
+}
+
 always_inline ipsec_policy_t *
-ipsec_input_policy_match (ipsec_spd_t * spd, u32 sa, u32 da,
+ipsec_input_policy_match (ipsec_spd_t *spd, u32 sa, u32 da,
                          ipsec_spd_policy_type_t policy_type)
 {
   ipsec_main_t *im = &ipsec_main;
@@ -95,13 +173,18 @@ ipsec_input_policy_match (ipsec_spd_t * spd, u32 sa, u32 da,
     if (sa > clib_net_to_host_u32 (p->raddr.stop.ip4.as_u32))
       continue;
 
+    if (im->input_flow_cache_flag)
+      {
+       /* Add an Entry in Flow cache */
+       ipsec4_input_spd_add_flow_cache_entry (im, sa, da, policy_type, *i);
+      }
     return p;
   }
   return 0;
 }
 
 always_inline ipsec_policy_t *
-ipsec_input_protect_policy_match (ipsec_spd_t * spd, u32 sa, u32 da, u32 spi)
+ipsec_input_protect_policy_match (ipsec_spd_t *spd, u32 sa, u32 da, u32 spi)
 {
   ipsec_main_t *im = &ipsec_main;
   ipsec_policy_t *p;
@@ -111,20 +194,20 @@ ipsec_input_protect_policy_match (ipsec_spd_t * spd, u32 sa, u32 da, u32 spi)
   vec_foreach (i, spd->policies[IPSEC_SPD_POLICY_IP4_INBOUND_PROTECT])
   {
     p = pool_elt_at_index (im->policies, *i);
-    s = pool_elt_at_index (im->sad, p->sa_index);
+    s = ipsec_sa_get (p->sa_index);
 
     if (spi != s->spi)
       continue;
 
     if (ipsec_sa_is_set_IS_TUNNEL (s))
       {
-       if (da != clib_net_to_host_u32 (s->tunnel_dst_addr.ip4.as_u32))
+       if (da != clib_net_to_host_u32 (s->tunnel.t_dst.ip.ip4.as_u32))
          continue;
 
-       if (sa != clib_net_to_host_u32 (s->tunnel_src_addr.ip4.as_u32))
+       if (sa != clib_net_to_host_u32 (s->tunnel.t_src.ip.ip4.as_u32))
          continue;
 
-       return p;
+       goto return_policy;
       }
 
     if (da < clib_net_to_host_u32 (p->laddr.start.ip4.as_u32))
@@ -139,6 +222,14 @@ ipsec_input_protect_policy_match (ipsec_spd_t * spd, u32 sa, u32 da, u32 spi)
     if (sa > clib_net_to_host_u32 (p->raddr.stop.ip4.as_u32))
       continue;
 
+  return_policy:
+    if (im->input_flow_cache_flag)
+      {
+       /* Add an Entry in Flow cache */
+       ipsec4_input_spd_add_flow_cache_entry (
+         im, sa, da, IPSEC_SPD_POLICY_IP4_INBOUND_PROTECT, *i);
+      }
+
     return p;
   }
   return 0;
@@ -167,17 +258,17 @@ ipsec6_input_protect_policy_match (ipsec_spd_t * spd,
   vec_foreach (i, spd->policies[IPSEC_SPD_POLICY_IP6_INBOUND_PROTECT])
   {
     p = pool_elt_at_index (im->policies, *i);
-    s = pool_elt_at_index (im->sad, p->sa_index);
+    s = ipsec_sa_get (p->sa_index);
 
     if (spi != s->spi)
       continue;
 
     if (ipsec_sa_is_set_IS_TUNNEL (s))
       {
-       if (!ip6_address_is_equal (sa, &s->tunnel_src_addr.ip6))
+       if (!ip6_address_is_equal (sa, &s->tunnel.t_src.ip.ip6))
          continue;
 
-       if (!ip6_address_is_equal (da, &s->tunnel_dst_addr.ip6))
+       if (!ip6_address_is_equal (da, &s->tunnel.t_dst.ip.ip6))
          continue;
 
        return p;
@@ -225,6 +316,7 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
       ipsec_spd_t *spd0;
       ipsec_policy_t *p0 = NULL;
       u8 has_space0;
+      bool search_flow_cache = false;
 
       if (n_left_from > 2)
        {
@@ -252,13 +344,28 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
              esp0 = (esp_header_t *) ((u8 *) esp0 + sizeof (udp_header_t));
            }
 
-         p0 = ipsec_input_protect_policy_match (spd0,
-                                                clib_net_to_host_u32
-                                                (ip0->src_address.as_u32),
-                                                clib_net_to_host_u32
-                                                (ip0->dst_address.as_u32),
-                                                clib_net_to_host_u32
-                                                (esp0->spi));
+         // if flow cache is enabled, first search through flow cache for a
+         // policy match for either protect, bypass or discard rules, in that
+         // order. if no match is found search_flow_cache is set to false (1)
+         // and we revert back to linear search
+         search_flow_cache = im->input_flow_cache_flag;
+
+       esp_or_udp:
+         if (search_flow_cache) // attempt to match policy in flow cache
+           {
+             p0 = ipsec4_input_spd_find_flow_cache_entry (
+               im, ip0->src_address.as_u32, ip0->dst_address.as_u32,
+               IPSEC_SPD_POLICY_IP4_INBOUND_PROTECT);
+           }
+
+         else // linear search if flow cache is not enabled,
+              // or flow cache search just failed
+           {
+             p0 = ipsec_input_protect_policy_match (
+               spd0, clib_net_to_host_u32 (ip0->src_address.as_u32),
+               clib_net_to_host_u32 (ip0->dst_address.as_u32),
+               clib_net_to_host_u32 (esp0->spi));
+           }
 
          has_space0 =
            vlib_buffer_has_space (b[0],
@@ -285,16 +392,30 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
              pi0 = ~0;
            };
 
-         p0 = ipsec_input_policy_match (spd0,
-                                        clib_net_to_host_u32
-                                        (ip0->src_address.as_u32),
-                                        clib_net_to_host_u32
-                                        (ip0->dst_address.as_u32),
-                                        IPSEC_SPD_POLICY_IP4_INBOUND_BYPASS);
+         if (search_flow_cache)
+           {
+             p0 = ipsec4_input_spd_find_flow_cache_entry (
+               im, ip0->src_address.as_u32, ip0->dst_address.as_u32,
+               IPSEC_SPD_POLICY_IP4_INBOUND_BYPASS);
+           }
+
+         else
+           {
+             p0 = ipsec_input_policy_match (
+               spd0, clib_net_to_host_u32 (ip0->src_address.as_u32),
+               clib_net_to_host_u32 (ip0->dst_address.as_u32),
+               IPSEC_SPD_POLICY_IP4_INBOUND_BYPASS);
+           }
+
          if (PREDICT_TRUE ((p0 != NULL)))
            {
              ipsec_bypassed += 1;
+
              pi0 = p0 - im->policies;
+             vlib_increment_combined_counter (
+               &ipsec_spd_policy_counters, thread_index, pi0, 1,
+               clib_net_to_host_u16 (ip0->length));
+
              goto trace0;
            }
          else
@@ -303,16 +424,30 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
              pi0 = ~0;
            };
 
-         p0 = ipsec_input_policy_match (spd0,
-                                        clib_net_to_host_u32
-                                        (ip0->src_address.as_u32),
-                                        clib_net_to_host_u32
-                                        (ip0->dst_address.as_u32),
-                                        IPSEC_SPD_POLICY_IP4_INBOUND_DISCARD);
+         if (search_flow_cache)
+           {
+             p0 = ipsec4_input_spd_find_flow_cache_entry (
+               im, ip0->src_address.as_u32, ip0->dst_address.as_u32,
+               IPSEC_SPD_POLICY_IP4_INBOUND_DISCARD);
+           }
+
+         else
+           {
+             p0 = ipsec_input_policy_match (
+               spd0, clib_net_to_host_u32 (ip0->src_address.as_u32),
+               clib_net_to_host_u32 (ip0->dst_address.as_u32),
+               IPSEC_SPD_POLICY_IP4_INBOUND_DISCARD);
+           }
+
          if (PREDICT_TRUE ((p0 != NULL)))
            {
              ipsec_dropped += 1;
+
              pi0 = p0 - im->policies;
+             vlib_increment_combined_counter (
+               &ipsec_spd_policy_counters, thread_index, pi0, 1,
+               clib_net_to_host_u16 (ip0->length));
+
              next[0] = IPSEC_INPUT_NEXT_DROP;
              goto trace0;
            }
@@ -321,6 +456,18 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
              p0 = 0;
              pi0 = ~0;
            };
+
+         // flow cache search failed, try again with linear search
+         if (search_flow_cache && p0 == NULL)
+           {
+             search_flow_cache = false;
+             goto esp_or_udp;
+           }
+
+         /* Drop by default if no match on PROTECT, BYPASS or DISCARD */
+         ipsec_unprocessed += 1;
+         next[0] = IPSEC_INPUT_NEXT_DROP;
+
        trace0:
          if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE) &&
              PREDICT_FALSE (b[0]->flags & VLIB_BUFFER_IS_TRACED))
@@ -339,13 +486,26 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
       else if (ip0->protocol == IP_PROTOCOL_IPSEC_AH)
        {
          ah0 = (ah_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0));
-         p0 = ipsec_input_protect_policy_match (spd0,
-                                                clib_net_to_host_u32
-                                                (ip0->src_address.as_u32),
-                                                clib_net_to_host_u32
-                                                (ip0->dst_address.as_u32),
-                                                clib_net_to_host_u32
-                                                (ah0->spi));
+
+         // if flow cache is enabled, first search through flow cache for a
+         // policy match and revert back to linear search on failure
+         search_flow_cache = im->input_flow_cache_flag;
+
+       ah:
+         if (search_flow_cache)
+           {
+             p0 = ipsec4_input_spd_find_flow_cache_entry (
+               im, ip0->src_address.as_u32, ip0->dst_address.as_u32,
+               IPSEC_SPD_POLICY_IP4_INBOUND_PROTECT);
+           }
+
+         else
+           {
+             p0 = ipsec_input_protect_policy_match (
+               spd0, clib_net_to_host_u32 (ip0->src_address.as_u32),
+               clib_net_to_host_u32 (ip0->dst_address.as_u32),
+               clib_net_to_host_u32 (ah0->spi));
+           }
 
          has_space0 =
            vlib_buffer_has_space (b[0],
@@ -371,16 +531,30 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
              pi0 = ~0;
            }
 
-         p0 = ipsec_input_policy_match (spd0,
-                                        clib_net_to_host_u32
-                                        (ip0->src_address.as_u32),
-                                        clib_net_to_host_u32
-                                        (ip0->dst_address.as_u32),
-                                        IPSEC_SPD_POLICY_IP4_INBOUND_BYPASS);
+         if (search_flow_cache)
+           {
+             p0 = ipsec4_input_spd_find_flow_cache_entry (
+               im, ip0->src_address.as_u32, ip0->dst_address.as_u32,
+               IPSEC_SPD_POLICY_IP4_INBOUND_BYPASS);
+           }
+
+         else
+           {
+             p0 = ipsec_input_policy_match (
+               spd0, clib_net_to_host_u32 (ip0->src_address.as_u32),
+               clib_net_to_host_u32 (ip0->dst_address.as_u32),
+               IPSEC_SPD_POLICY_IP4_INBOUND_BYPASS);
+           }
+
          if (PREDICT_TRUE ((p0 != NULL)))
            {
              ipsec_bypassed += 1;
+
              pi0 = p0 - im->policies;
+             vlib_increment_combined_counter (
+               &ipsec_spd_policy_counters, thread_index, pi0, 1,
+               clib_net_to_host_u16 (ip0->length));
+
              goto trace1;
            }
          else
@@ -389,16 +563,30 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
              pi0 = ~0;
            };
 
-         p0 = ipsec_input_policy_match (spd0,
-                                        clib_net_to_host_u32
-                                        (ip0->src_address.as_u32),
-                                        clib_net_to_host_u32
-                                        (ip0->dst_address.as_u32),
-                                        IPSEC_SPD_POLICY_IP4_INBOUND_DISCARD);
+         if (search_flow_cache)
+           {
+             p0 = ipsec4_input_spd_find_flow_cache_entry (
+               im, ip0->src_address.as_u32, ip0->dst_address.as_u32,
+               IPSEC_SPD_POLICY_IP4_INBOUND_DISCARD);
+           }
+
+         else
+           {
+             p0 = ipsec_input_policy_match (
+               spd0, clib_net_to_host_u32 (ip0->src_address.as_u32),
+               clib_net_to_host_u32 (ip0->dst_address.as_u32),
+               IPSEC_SPD_POLICY_IP4_INBOUND_DISCARD);
+           }
+
          if (PREDICT_TRUE ((p0 != NULL)))
            {
              ipsec_dropped += 1;
+
              pi0 = p0 - im->policies;
+             vlib_increment_combined_counter (
+               &ipsec_spd_policy_counters, thread_index, pi0, 1,
+               clib_net_to_host_u16 (ip0->length));
+
              next[0] = IPSEC_INPUT_NEXT_DROP;
              goto trace1;
            }
@@ -407,6 +595,18 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
              p0 = 0;
              pi0 = ~0;
            };
+
+         // flow cache search failed, retry with linear search
+         if (search_flow_cache && p0 == NULL)
+           {
+             search_flow_cache = false;
+             goto ah;
+           }
+
+         /* Drop by default if no match on PROTECT, BYPASS or DISCARD */
+         ipsec_unprocessed += 1;
+         next[0] = IPSEC_INPUT_NEXT_DROP;
+
        trace1:
          if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE) &&
              PREDICT_FALSE (b[0]->flags & VLIB_BUFFER_IS_TRACED))
@@ -561,6 +761,8 @@ VLIB_NODE_FN (ipsec6_input_node) (vlib_main_t * vm,
              else
                {
                  pi0 = ~0;
+                 ipsec_unprocessed += 1;
+                 next0 = IPSEC_INPUT_NEXT_DROP;
                }
            }
          else if (ip0->protocol == IP_PROTOCOL_IPSEC_AH)
@@ -588,6 +790,8 @@ VLIB_NODE_FN (ipsec6_input_node) (vlib_main_t * vm,
              else
                {
                  pi0 = ~0;
+                 ipsec_unprocessed += 1;
+                 next0 = IPSEC_INPUT_NEXT_DROP;
                }
            }
          else