ipsec: manually binding an SA to a worker
[vpp.git] / src / vnet / ipsec / ipsec_input.c
index c47ea34..4412ff3 100644 (file)
@@ -19,6 +19,7 @@
 #include <vnet/api_errno.h>
 #include <vnet/ip/ip.h>
 #include <vnet/feature/feature.h>
+#include <vnet/ipsec/ipsec_spd_fp_lookup.h>
 
 #include <vnet/ipsec/ipsec.h>
 #include <vnet/ipsec/esp.h>
@@ -51,6 +52,7 @@ typedef struct
   ip_protocol_t proto;
   u32 spd;
   u32 policy_index;
+  u32 policy_type;
   u32 sa_id;
   u32 spi;
   u32 seq;
@@ -64,15 +66,119 @@ format_ipsec_input_trace (u8 * s, va_list * args)
   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
   ipsec_input_trace_t *t = va_arg (*args, ipsec_input_trace_t *);
 
-  s = format (s, "%U: sa_id %u spd %u policy %d spi %u (0x%08x) seq %u",
-             format_ip_protocol, t->proto, t->sa_id,
-             t->spd, t->policy_index, t->spi, t->spi, t->seq);
+  s =
+    format (s, "%U: sa_id %u type: %u spd %u policy %d spi %u (0x%08x) seq %u",
+           format_ip_protocol, t->proto, t->sa_id, t->policy_type, t->spd,
+           t->policy_index, t->spi, t->spi, t->seq);
 
   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 void
+ipsec_fp_in_5tuple_from_ip4_range (ipsec_fp_5tuple_t *tuple, u32 sa, u32 da,
+                                  u32 spi, u8 action)
+{
+  clib_memset (tuple->l3_zero_pad, 0, sizeof (tuple->l3_zero_pad));
+  tuple->laddr.as_u32 = da;
+  tuple->raddr.as_u32 = sa;
+  tuple->spi = spi;
+  tuple->action = action;
+  tuple->is_ipv6 = 0;
+}
+
+always_inline void
+ipsec_fp_in_5tuple_from_ip6_range (ipsec_fp_5tuple_t *tuple, ip6_address_t *sa,
+                                  ip6_address_t *da, u32 spi, u8 action)
+
+{
+  clib_memcpy (&tuple->ip6_laddr, da, sizeof (ip6_address_t));
+  clib_memcpy (&tuple->ip6_raddr, sa, sizeof (ip6_address_t));
+
+  tuple->spi = spi;
+  tuple->action = action;
+  tuple->is_ipv6 = 1;
+}
+
 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 +201,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;
@@ -124,7 +235,7 @@ ipsec_input_protect_policy_match (ipsec_spd_t * spd, u32 sa, u32 da, u32 spi)
        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 +250,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;
@@ -225,6 +344,10 @@ 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;
+      ipsec_policy_t *policies[1];
+      ipsec_fp_5tuple_t tuples[1];
+      bool ip_v6 = true;
 
       if (n_left_from > 2)
        {
@@ -252,13 +375,40 @@ 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 (im->fp_spd_ipv4_in_is_enabled &&
+             PREDICT_TRUE (INDEX_INVALID !=
+                           spd0->fp_spd.ip4_in_lookup_hash_idx))
+           {
+             ipsec_fp_in_5tuple_from_ip4_range (
+               &tuples[0], ip0->src_address.as_u32, ip0->dst_address.as_u32,
+               clib_net_to_host_u32 (esp0->spi),
+               IPSEC_SPD_POLICY_IP4_INBOUND_PROTECT);
+             ipsec_fp_in_policy_match_n (&spd0->fp_spd, !ip_v6, tuples,
+                                         policies, 1);
+             p0 = policies[0];
+           }
+         else 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,12 +435,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 (im->fp_spd_ipv4_in_is_enabled &&
+             PREDICT_TRUE (INDEX_INVALID !=
+                           spd0->fp_spd.ip4_in_lookup_hash_idx))
+           {
+             tuples->action = IPSEC_SPD_POLICY_IP4_INBOUND_BYPASS;
+             ipsec_fp_in_policy_match_n (&spd0->fp_spd, !ip_v6, tuples,
+                                         policies, 1);
+             p0 = policies[0];
+           }
+         else 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;
@@ -308,12 +476,32 @@ 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 (im->fp_spd_ipv4_in_is_enabled &&
+             PREDICT_TRUE (INDEX_INVALID !=
+                           spd0->fp_spd.ip4_in_lookup_hash_idx))
+           {
+             tuples->action = IPSEC_SPD_POLICY_IP4_INBOUND_DISCARD;
+             ipsec_fp_in_policy_match_n (&spd0->fp_spd, !ip_v6, tuples,
+                                         policies, 1);
+             p0 = policies[0];
+           }
+         else
+
+           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;
@@ -332,6 +520,13 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
              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;
@@ -354,13 +549,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],
@@ -386,12 +594,21 @@ 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;
@@ -409,12 +626,21 @@ 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;
@@ -433,6 +659,13 @@ VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,
              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;
@@ -514,6 +747,9 @@ VLIB_NODE_FN (ipsec6_input_node) (vlib_main_t * vm,
   ipsec_main_t *im = &ipsec_main;
   u32 ipsec_unprocessed = 0;
   u32 ipsec_matched = 0;
+  ipsec_policy_t *policies[1];
+  ipsec_fp_5tuple_t tuples[1];
+  bool ip_v6 = true;
 
   from = vlib_frame_vector_args (from_frame);
   n_left_from = from_frame->n_vectors;
@@ -529,7 +765,7 @@ VLIB_NODE_FN (ipsec6_input_node) (vlib_main_t * vm,
 
       while (n_left_from > 0 && n_left_to_next > 0)
        {
-         u32 bi0, next0, pi0;
+         u32 bi0, next0, pi0 = ~0;
          vlib_buffer_t *b0;
          ip6_header_t *ip0;
          esp_header_t *esp0;
@@ -566,11 +802,22 @@ VLIB_NODE_FN (ipsec6_input_node) (vlib_main_t * vm,
                 clib_net_to_host_u16 (ip0->payload_length) + header_size,
                 spd0->id);
 #endif
-             p0 = ipsec6_input_protect_policy_match (spd0,
-                                                     &ip0->src_address,
-                                                     &ip0->dst_address,
-                                                     clib_net_to_host_u32
-                                                     (esp0->spi));
+             if (im->fp_spd_ipv6_in_is_enabled &&
+                 PREDICT_TRUE (INDEX_INVALID !=
+                               spd0->fp_spd.ip6_in_lookup_hash_idx))
+               {
+                 ipsec_fp_in_5tuple_from_ip6_range (
+                   &tuples[0], &ip0->src_address, &ip0->dst_address,
+                   clib_net_to_host_u32 (esp0->spi),
+                   IPSEC_SPD_POLICY_IP6_INBOUND_PROTECT);
+                 ipsec_fp_in_policy_match_n (&spd0->fp_spd, ip_v6, tuples,
+                                             policies, 1);
+                 p0 = policies[0];
+               }
+             else
+               p0 = ipsec6_input_protect_policy_match (
+                 spd0, &ip0->src_address, &ip0->dst_address,
+                 clib_net_to_host_u32 (esp0->spi));
 
              if (PREDICT_TRUE (p0 != 0))
                {
@@ -586,6 +833,8 @@ VLIB_NODE_FN (ipsec6_input_node) (vlib_main_t * vm,
                  vnet_buffer (b0)->ipsec.sad_index = p0->sa_index;
                  next0 = im->esp6_decrypt_next_index;
                  vlib_buffer_advance (b0, header_size);
+                 /* TODO Add policy matching for bypass and discard policy
+                  * type */
                  goto trace0;
                }
              else
@@ -637,11 +886,16 @@ VLIB_NODE_FN (ipsec6_input_node) (vlib_main_t * vm,
                vlib_add_trace (vm, node, b0, sizeof (*tr));
 
              if (p0)
-               tr->sa_id = p0->sa_id;
+               {
+                 tr->sa_id = p0->sa_id;
+                 tr->policy_type = p0->type;
+               }
+
              tr->proto = ip0->protocol;
              tr->spi = clib_net_to_host_u32 (esp0->spi);
              tr->seq = clib_net_to_host_u32 (esp0->seq);
              tr->spd = spd0->id;
+             tr->policy_index = pi0;
            }
 
          vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,