ipsec: Dedicated IPSec interface type 95/27795/4
authorNeale Ranns <nranns@cisco.com>
Tue, 30 Jun 2020 07:47:14 +0000 (07:47 +0000)
committerDave Barach <openvpp@barachs.net>
Tue, 21 Jul 2020 18:42:25 +0000 (18:42 +0000)
Type: feature

Signed-off-by: Neale Ranns <nranns@cisco.com>
Change-Id: Ie8bd50df163aea2798e9f9d35a13dcadc4a4a4b2

src/vnet/CMakeLists.txt
src/vnet/ipsec/ipsec.api
src/vnet/ipsec/ipsec_api.c
src/vnet/ipsec/ipsec_format.c
src/vnet/ipsec/ipsec_itf.c [new file with mode: 0644]
src/vnet/ipsec/ipsec_itf.h [new file with mode: 0644]
src/vnet/ipsec/ipsec_tun.c
src/vnet/ipsec/ipsec_tun.h
test/template_ipsec.py
test/test_ipsec_tun_if_esp.py
test/vpp_ipsec.py

index 9ee0eeb..e418218 100644 (file)
@@ -581,6 +581,7 @@ list(APPEND VNET_SOURCES
   ipsec/ipsec_format.c
   ipsec/ipsec_handoff.c
   ipsec/ipsec_input.c
+  ipsec/ipsec_itf.c
   ipsec/ipsec_punt.c
   ipsec/ipsec_sa.c
   ipsec/ipsec_spd.c
index dee9144..488df1c 100644 (file)
@@ -20,6 +20,7 @@ import "vnet/ipsec/ipsec_types.api";
 import "vnet/interface_types.api";
 import "vnet/ip/ip_types.api";
 import "vnet/interface_types.api";
+import "vnet/tunnel/tunnel_types.api";
 
 /** \brief IPsec: Add/delete Security Policy Database
     @param client_index - opaque cookie to identify the sender
@@ -379,12 +380,60 @@ define ipsec_tunnel_if_add_del_reply {
   vl_api_interface_index_t sw_if_index;
 };
 
+typedef ipsec_itf
+{
+  u32 user_instance [default=0xffffffff];
+  vl_api_tunnel_mode_t mode;
+  vl_api_interface_index_t sw_if_index;
+};
+
+/** \brief Create an IPSec interface
+ */
+define ipsec_itf_create {
+  u32 client_index;
+  u32 context;
+  vl_api_ipsec_itf_t itf;
+};
+
+/** \brief Add IPsec interface interface response
+    @param context - sender context, to match reply w/ request
+    @param retval - return status
+    @param sw_if_index - sw_if_index of new interface (for successful add)
+*/
+define ipsec_itf_create_reply
+{
+  u32 context;
+  i32 retval;
+  vl_api_interface_index_t sw_if_index;
+};
+
+autoreply define ipsec_itf_delete
+{
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+};
+
+define ipsec_itf_dump
+{
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+};
+
+define ipsec_itf_details
+{
+  u32 context;
+  vl_api_ipsec_itf_t itf;
+};
+
 /** \brief Dump IPsec security association
     @param client_index - opaque cookie to identify the sender
     @param context - sender context, to match reply w/ request
     @param sa_id - optional ID of an SA to dump, if ~0 dump all SAs in SAD
 */
-define ipsec_sa_dump {
+define ipsec_sa_dump
+{
   u32 client_index;
   u32 context;
   u32 sa_id;
index ef58f7a..667d9b2 100644 (file)
@@ -25,6 +25,7 @@
 #include <vnet/ip/ip.h>
 #include <vnet/ip/ip_types_api.h>
 #include <vnet/ipsec/ipsec_types_api.h>
+#include <vnet/tunnel/tunnel_types_api.h>
 #include <vnet/fib/fib.h>
 #include <vnet/ipip/ipip.h>
 
@@ -33,6 +34,7 @@
 #if WITH_LIBSSL > 0
 #include <vnet/ipsec/ipsec.h>
 #include <vnet/ipsec/ipsec_tun.h>
+#include <vnet/ipsec/ipsec_itf.h>
 #endif /* IPSEC */
 
 #define vl_typedefs            /* define message structures */
@@ -60,6 +62,9 @@ _(IPSEC_SA_DUMP, ipsec_sa_dump)                                 \
 _(IPSEC_SPDS_DUMP, ipsec_spds_dump)                             \
 _(IPSEC_SPD_DUMP, ipsec_spd_dump)                               \
 _(IPSEC_SPD_INTERFACE_DUMP, ipsec_spd_interface_dump)          \
+_(IPSEC_ITF_CREATE, ipsec_itf_create)                           \
+_(IPSEC_ITF_DELETE, ipsec_itf_delete)                           \
+_(IPSEC_ITF_DUMP, ipsec_itf_dump)                               \
 _(IPSEC_TUNNEL_IF_ADD_DEL, ipsec_tunnel_if_add_del)             \
 _(IPSEC_TUNNEL_IF_SET_SA, ipsec_tunnel_if_set_sa)               \
 _(IPSEC_SELECT_BACKEND, ipsec_select_backend)                   \
@@ -736,6 +741,43 @@ done:
   /* *INDENT-ON* */
 }
 
+static void
+vl_api_ipsec_itf_create_t_handler (vl_api_ipsec_itf_create_t * mp)
+{
+  vl_api_ipsec_itf_create_reply_t *rmp;
+  tunnel_mode_t mode;
+  u32 sw_if_index = ~0;
+  int rv;
+
+  rv = tunnel_mode_decode (mp->itf.mode, &mode);
+
+  if (!rv)
+    rv = ipsec_itf_create (ntohl (mp->itf.user_instance), mode, &sw_if_index);
+
+  /* *INDENT-OFF* */
+  REPLY_MACRO2 (VL_API_IPSEC_ITF_CREATE_REPLY,
+  ({
+    rmp->sw_if_index = htonl (sw_if_index);
+  }));
+  /* *INDENT-ON* */
+}
+
+static void
+vl_api_ipsec_itf_delete_t_handler (vl_api_ipsec_itf_delete_t * mp)
+{
+  vl_api_ipsec_itf_delete_reply_t *rmp;
+  int rv;
+
+  rv = ipsec_itf_delete (ntohl (mp->sw_if_index));
+
+  REPLY_MACRO (VL_API_IPSEC_ITF_DELETE_REPLY);
+}
+
+static void
+vl_api_ipsec_itf_dump_t_handler (vl_api_ipsec_itf_dump_t * mp)
+{
+}
+
 typedef struct ipsec_sa_dump_match_ctx_t_
 {
   index_t sai;
index 98de779..e3c6f22 100644 (file)
@@ -353,6 +353,21 @@ format_ipsec_tun_protect_index (u8 * s, va_list * args)
   return (format (s, "%U", format_ipsec_tun_protect, itp));
 }
 
+u8 *
+format_ipsec_tun_protect_flags (u8 * s, va_list * args)
+{
+  ipsec_protect_flags_t flags = va_arg (*args, int);
+
+  if (IPSEC_PROTECT_NONE == flags)
+    s = format (s, "none");
+#define _(a,b,c)                                \
+  else if (flags & IPSEC_PROTECT_##a)           \
+    s = format (s, "%s", c);                    \
+  foreach_ipsec_protect_flags
+#undef _
+
+  return (s);
+}
 
 u8 *
 format_ipsec_tun_protect (u8 * s, va_list * args)
@@ -360,8 +375,9 @@ format_ipsec_tun_protect (u8 * s, va_list * args)
   ipsec_tun_protect_t *itp = va_arg (*args, ipsec_tun_protect_t *);
   u32 sai;
 
-  s = format (s, "%U", format_vnet_sw_if_index_name,
-             vnet_get_main (), itp->itp_sw_if_index);
+  s = format (s, "%U flags:[%U]", format_vnet_sw_if_index_name,
+             vnet_get_main (), itp->itp_sw_if_index,
+             format_ipsec_tun_protect_flags, itp->itp_flags);
   if (!ip_address_is_zero (itp->itp_key))
     s = format (s, ": %U", format_ip_address, itp->itp_key);
   s = format (s, "\n output-sa:");
diff --git a/src/vnet/ipsec/ipsec_itf.c b/src/vnet/ipsec/ipsec_itf.c
new file mode 100644 (file)
index 0000000..756bc19
--- /dev/null
@@ -0,0 +1,462 @@
+/*
+ * ipsec_itf.c: IPSec dedicated interface type
+ *
+ * Copyright (c) 2020 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vnet/ip/ip.h>
+#include <vnet/ipsec/ipsec_itf.h>
+#include <vnet/ipsec/ipsec_tun.h>
+#include <vnet/ipsec/ipsec.h>
+#include <vnet/adj/adj_midchain.h>
+
+/* bitmap of Allocated IPSEC_ITF instances */
+static uword *ipsec_itf_instances;
+
+/* pool of interfaces */
+static ipsec_itf_t *ipsec_itf_pool;
+
+static u32 *ipsec_itf_index_by_sw_if_index;
+
+static ipsec_itf_t *
+ipsec_itf_find_by_sw_if_index (u32 sw_if_index)
+{
+  if (vec_len (ipsec_itf_index_by_sw_if_index) <= sw_if_index)
+    return NULL;
+  u32 ti = ipsec_itf_index_by_sw_if_index[sw_if_index];
+  if (ti == ~0)
+    return NULL;
+  return pool_elt_at_index (ipsec_itf_pool, ti);
+}
+
+static u8 *
+format_ipsec_itf_name (u8 * s, va_list * args)
+{
+  u32 dev_instance = va_arg (*args, u32);
+  return format (s, "ipsec%d", dev_instance);
+}
+
+void
+ipsec_itf_adj_unstack (adj_index_t ai)
+{
+  adj_midchain_delegate_unstack (ai);
+}
+
+void
+ipsec_itf_adj_stack (adj_index_t ai, u32 sai)
+{
+  const vnet_hw_interface_t *hw;
+
+  hw = vnet_get_sup_hw_interface (vnet_get_main (), adj_get_sw_if_index (ai));
+
+  if (hw->flags & VNET_HW_INTERFACE_FLAG_LINK_UP)
+    {
+      const ipsec_sa_t *sa;
+
+      sa = ipsec_sa_get (sai);
+
+      /* *INDENT-OFF* */
+      const fib_prefix_t dst = {
+        .fp_len = (ipsec_sa_is_set_IS_TUNNEL_V6(sa) ? 128 : 32),
+        .fp_proto = (ipsec_sa_is_set_IS_TUNNEL_V6(sa)?
+                     FIB_PROTOCOL_IP6 :
+                     FIB_PROTOCOL_IP4),
+        .fp_addr = sa->tunnel_dst_addr,
+      };
+      /* *INDENT-ON* */
+
+      adj_midchain_delegate_stack (ai, 0, &dst);
+    }
+  else
+    adj_midchain_delegate_unstack (ai);
+}
+
+static adj_walk_rc_t
+ipsec_itf_adj_stack_cb (adj_index_t ai, void *arg)
+{
+  ipsec_tun_protect_t *itp = arg;
+
+  ipsec_itf_adj_stack (ai, itp->itp_out_sa);
+
+  return (ADJ_WALK_RC_CONTINUE);
+}
+
+static void
+ipsec_itf_restack (index_t itpi, const ipsec_itf_t * itf)
+{
+  ipsec_tun_protect_t *itp;
+  fib_protocol_t proto;
+
+  itp = ipsec_tun_protect_get (itpi);
+
+  /*
+   * walk all the adjacencies on the interface and restack them
+   */
+  FOR_EACH_FIB_IP_PROTOCOL (proto)
+  {
+    adj_nbr_walk (itf->ii_sw_if_index, proto, ipsec_itf_adj_stack_cb, itp);
+  }
+}
+
+static walk_rc_t
+ipsec_tun_protect_walk_state_change (index_t itpi, void *arg)
+{
+  const ipsec_itf_t *itf = arg;
+
+  ipsec_itf_restack (itpi, itf);
+
+  return (WALK_CONTINUE);
+}
+
+static clib_error_t *
+ipsec_itf_admin_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
+{
+  vnet_hw_interface_t *hi;
+  ipsec_itf_t *itf;
+  u32 hw_flags;
+
+  hi = vnet_get_hw_interface (vnm, hw_if_index);
+  hw_flags = (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP ?
+             VNET_HW_INTERFACE_FLAG_LINK_UP : 0);
+  vnet_hw_interface_set_flags (vnm, hw_if_index, hw_flags);
+
+  itf = ipsec_itf_find_by_sw_if_index (hi->sw_if_index);
+
+  if (itf)
+    ipsec_tun_protect_walk_itf (itf->ii_sw_if_index,
+                               ipsec_tun_protect_walk_state_change, itf);
+
+  return (NULL);
+}
+
+static int
+ipsec_itf_tunnel_desc (u32 sw_if_index,
+                      ip46_address_t * src, ip46_address_t * dst, u8 * is_l2)
+{
+  ip46_address_reset (src);
+  ip46_address_reset (dst);
+  *is_l2 = 0;
+
+  return (0);
+}
+
+static u8 *
+ipsec_itf_build_rewrite (void)
+{
+  /*
+   * passing the adj code a NULL rewrite means 'i don't have one cos
+   * t'other end is unresolved'. That's not the case here. For the ipsec
+   * tunnel there are just no bytes of encap to apply in the adj.
+   * So return a zero length rewrite. Encap will be added by a tunnel mode SA.
+   */
+  u8 *rewrite = NULL;
+
+  vec_validate (rewrite, 0);
+  vec_reset_length (rewrite);
+
+  return (rewrite);
+}
+
+static u8 *
+ipsec_itf_build_rewrite_i (vnet_main_t * vnm,
+                          u32 sw_if_index,
+                          vnet_link_t link_type, const void *dst_address)
+{
+  return (ipsec_itf_build_rewrite ());
+}
+
+void
+ipsec_itf_update_adj (vnet_main_t * vnm, u32 sw_if_index, adj_index_t ai)
+{
+  adj_nbr_midchain_update_rewrite
+    (ai, NULL, NULL, ADJ_FLAG_MIDCHAIN_IP_STACK, ipsec_itf_build_rewrite ());
+}
+
+/* *INDENT-OFF* */
+VNET_DEVICE_CLASS (ipsec_itf_device_class) = {
+  .name = "IPSEC Tunnel",
+  .format_device_name = format_ipsec_itf_name,
+  .admin_up_down_function = ipsec_itf_admin_up_down,
+  .ip_tun_desc = ipsec_itf_tunnel_desc,
+};
+
+VNET_HW_INTERFACE_CLASS(ipsec_hw_interface_class) = {
+  .name = "IPSec",
+  .build_rewrite = ipsec_itf_build_rewrite_i,
+  .update_adjacency = ipsec_itf_update_adj,
+  .flags = VNET_HW_INTERFACE_CLASS_FLAG_P2P,
+};
+/* *INDENT-ON* */
+
+/*
+ * Maintain a bitmap of allocated ipsec_itf instance numbers.
+ */
+#define IPSEC_ITF_MAX_INSTANCE         (16 * 1024)
+
+static u32
+ipsec_itf_instance_alloc (u32 want)
+{
+  /*
+   * Check for dynamically allocated instance number.
+   */
+  if (~0 == want)
+    {
+      u32 bit;
+
+      bit = clib_bitmap_first_clear (ipsec_itf_instances);
+      if (bit >= IPSEC_ITF_MAX_INSTANCE)
+       {
+         return ~0;
+       }
+      ipsec_itf_instances = clib_bitmap_set (ipsec_itf_instances, bit, 1);
+      return bit;
+    }
+
+  /*
+   * In range?
+   */
+  if (want >= IPSEC_ITF_MAX_INSTANCE)
+    {
+      return ~0;
+    }
+
+  /*
+   * Already in use?
+   */
+  if (clib_bitmap_get (ipsec_itf_instances, want))
+    {
+      return ~0;
+    }
+
+  /*
+   * Grant allocation request.
+   */
+  ipsec_itf_instances = clib_bitmap_set (ipsec_itf_instances, want, 1);
+
+  return want;
+}
+
+static int
+ipsec_itf_instance_free (u32 instance)
+{
+  if (instance >= IPSEC_ITF_MAX_INSTANCE)
+    {
+      return -1;
+    }
+
+  if (clib_bitmap_get (ipsec_itf_instances, instance) == 0)
+    {
+      return -1;
+    }
+
+  ipsec_itf_instances = clib_bitmap_set (ipsec_itf_instances, instance, 0);
+  return 0;
+}
+
+int
+ipsec_itf_create (u32 user_instance, tunnel_mode_t mode, u32 * sw_if_indexp)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  u32 instance, hw_if_index;
+  vnet_hw_interface_t *hi;
+  ipsec_itf_t *ipsec_itf;
+
+  ASSERT (sw_if_indexp);
+
+  *sw_if_indexp = (u32) ~ 0;
+
+  if (mode != TUNNEL_MODE_P2P)
+    return VNET_API_ERROR_UNSUPPORTED;
+
+  /*
+   * Allocate a ipsec_itf instance.  Either select on dynamically
+   * or try to use the desired user_instance number.
+   */
+  instance = ipsec_itf_instance_alloc (user_instance);
+  if (instance == ~0)
+    return VNET_API_ERROR_INVALID_REGISTRATION;
+
+  pool_get (ipsec_itf_pool, ipsec_itf);
+
+  /* tunnel index (or instance) */
+  u32 t_idx = ipsec_itf - ipsec_itf_pool;
+
+  ipsec_itf->ii_mode = mode;
+  ipsec_itf->ii_user_instance = instance;
+  if (~0 == ipsec_itf->ii_user_instance)
+    ipsec_itf->ii_user_instance = t_idx;
+
+  hw_if_index = vnet_register_interface (vnm,
+                                        ipsec_itf_device_class.index,
+                                        t_idx,
+                                        ipsec_hw_interface_class.index,
+                                        t_idx);
+
+  hi = vnet_get_hw_interface (vnm, hw_if_index);
+
+  vec_validate_init_empty (ipsec_itf_index_by_sw_if_index, hi->sw_if_index,
+                          INDEX_INVALID);
+  ipsec_itf_index_by_sw_if_index[hi->sw_if_index] = t_idx;
+
+  ipsec_itf->ii_sw_if_index = *sw_if_indexp = hi->sw_if_index;
+
+  return 0;
+}
+
+int
+ipsec_itf_delete (u32 sw_if_index)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+
+  if (pool_is_free_index (vnm->interface_main.sw_interfaces, sw_if_index))
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  vnet_hw_interface_t *hw = vnet_get_sup_hw_interface (vnm, sw_if_index);
+  if (hw == 0 || hw->dev_class_index != ipsec_itf_device_class.index)
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  ipsec_itf_t *ipsec_itf;
+  ipsec_itf = ipsec_itf_find_by_sw_if_index (sw_if_index);
+  if (NULL == ipsec_itf)
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  if (ipsec_itf_instance_free (hw->dev_instance) < 0)
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  vnet_delete_hw_interface (vnm, hw->hw_if_index);
+  pool_put (ipsec_itf_pool, ipsec_itf);
+
+  return 0;
+}
+
+static clib_error_t *
+ipsec_itf_create_cli (vlib_main_t * vm,
+                     unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  u32 instance, sw_if_index;
+  clib_error_t *error;
+  mac_address_t mac;
+  int rv;
+
+  error = NULL;
+  instance = sw_if_index = ~0;
+  mac_address_set_zero (&mac);
+
+  if (unformat_user (input, unformat_line_input, line_input))
+    {
+      while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+       {
+         if (unformat (line_input, "instance %d", &instance))
+           ;
+         else
+           {
+             error = clib_error_return (0, "unknown input: %U",
+                                        format_unformat_error, line_input);
+             break;
+           }
+       }
+
+      unformat_free (line_input);
+
+      if (error)
+       return error;
+    }
+
+  rv = ipsec_itf_create (instance, TUNNEL_MODE_P2P, &sw_if_index);
+
+  if (rv)
+    return clib_error_return (0, "iPSec interface create failed");
+
+  vlib_cli_output (vm, "%U\n", format_vnet_sw_if_index_name, vnet_get_main (),
+                  sw_if_index);
+  return 0;
+}
+
+/*?
+ * Create a IPSec interface.
+ *
+ * @cliexpar
+ * The following two command syntaxes are equivalent:
+ * @cliexcmd{ipsec itf create [instance <instance>]}
+ * Example of how to create a ipsec interface:
+ * @cliexcmd{ipsec itf create}
+?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (ipsec_itf_create_command, static) = {
+  .path = "ipsec itf create",
+  .short_help = "ipsec itf create [instance <instance>]",
+  .function = ipsec_itf_create_cli,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+ipsec_itf_delete_cli (vlib_main_t * vm,
+                     unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  vnet_main_t *vnm;
+  u32 sw_if_index;
+  int rv;
+
+  vnm = vnet_get_main ();
+  sw_if_index = ~0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat
+         (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index))
+       ;
+      else
+       break;
+    }
+
+  if (~0 != sw_if_index)
+    {
+      rv = ipsec_itf_delete (sw_if_index);
+
+      if (rv)
+       return clib_error_return (0, "ipsec interface delete failed");
+    }
+  else
+    return clib_error_return (0, "no such interface: %U",
+                             format_unformat_error, input);
+
+  return 0;
+}
+
+/*?
+ * Delete a IPSEC_ITF interface.
+ *
+ * @cliexpar
+ * The following two command syntaxes are equivalent:
+ * @cliexcmd{ipsec itf delete <interface>}
+ * Example of how to create a ipsec_itf interface:
+ * @cliexcmd{ipsec itf delete ipsec0}
+?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (ipsec_itf_delete_command, static) = {
+  .path = "ipsec itf delete",
+  .short_help = "ipsec itf delete <interface>",
+  .function = ipsec_itf_delete_cli,
+};
+/* *INDENT-ON* */
+
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/ipsec/ipsec_itf.h b/src/vnet/ipsec/ipsec_itf.h
new file mode 100644 (file)
index 0000000..93e03f7
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * ipsec_itf.c: IPSec dedicated interface type
+ *
+ * Copyright (c) 2020 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __IPSEC_ITF_H__
+#define __IPSEC_ITF_H__
+
+#include <vnet/tunnel/tunnel.h>
+#include <vnet/ipsec/ipsec_sa.h>
+
+/**
+ * @brief A dedicated IPSec interface type
+ *
+ * In order to support route based VPNs one needs 3 elements: an interface,
+ * for routing to resolve routes through, an SA from the peer to describe
+ * security, and encap, to describe how to reach the peer. There are two
+ * ways one could model this:
+ *
+ *  interface + encap + SA = (interface + encap) + SA =
+ *          ipip-interface + SA transport mode
+ *
+ * or
+ *
+ *  interface + encap + SA = interface + (encap + SA) =
+ *          IPSec-interface + SA tunnel mode
+ *
+ * It's a question of where you add the parenthesis, from the perspective
+ * of the external user the effect is identical.
+ *
+ * The IPsec interface serves as the encap-free interface to be used
+ * in conjunction with an encap-describing tunnel mode SA.
+ *
+ * VPP supports both models, which modelshould you pick?
+ * A route based VPN could impose 0, 1 or 2 encaps. the support matrix for
+ * these use cases is:
+ *
+ *        |  0  |  1  |  2  |
+ *  --------------------------
+ *  ipip  |  N  |  Y  |  Y  |
+ *  ipsec |  P  |  Y  |  P  |
+ *
+ * Where P = potentially.
+ * ipsec could potnetially support 0 encap (i.e. transport mode) since neither
+ * the interface nor the SA *requires* encap. However, for a route beased VPN
+ * to use transport mode is probably wrong since one shouldn't use thransport
+ * mode for transit traffic, since without encap it is not guaranteed to return.
+ * ipsec could potnetially support 2 encaps, but that would require the SA to
+ * describe both, something it does not do at this time.
+ *
+ * ipsec currently does not support:
+ *   - multipoint interfaces
+ * but this is only because it is not yet implemented, rather than it cannot
+ * be done.
+ *
+ * Internally the difference is that the midchain adjacency for the IPSec
+ * interface has no associated encap (whereas for an ipip tunnel it describes
+ * the peer). Consequently, features on the output arc see packets without
+ * any encap. Since the protecting SAs are in tunnel mode,
+ * they apply the encap. The midchain adj is stacked only once the proctecting
+ * SA is known, since only then is the peer known. Otherwise the VLIB graph
+ * nodes used are the same:
+ *    (routing) --> ipX-michain --> espX-encrypt --> adj-midchain-tx --> (routing)
+ * where X = 4 or 6.
+ *
+ * Some benefits to the ipsec interface:
+ *   - it is slightly more efficient since the encapsulating IP header has
+ *     its checksum updated only once.
+ *   - even when the interface is admin up traffic cannot be sent to a peer
+ *     unless the SA is available (since it's the SA that determines the
+ *     encap). With ipip interfaces a client must use the admin state to
+ *     prevent sending until the SA is available.
+ *
+ * The best recommendations i can make are:
+ *   - pick a model that supports your use case
+ *   - make sure any other features you wish to use are supported by the model
+ *   - choose the model that best fits your control plane's model.
+ *
+ *
+ * gun reloaded, fire away.
+ */
+typedef struct ipsec_itf_t_
+{
+  tunnel_mode_t ii_mode;
+  int ii_user_instance;
+  u32 ii_sw_if_index;
+} __clib_packed ipsec_itf_t;
+
+
+extern int ipsec_itf_create (u32 user_instance,
+                            tunnel_mode_t mode, u32 * sw_if_indexp);
+extern int ipsec_itf_delete (u32 sw_if_index);
+
+extern void ipsec_itf_adj_stack (adj_index_t ai, u32 sai);
+extern void ipsec_itf_adj_unstack (adj_index_t ai);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
+
+#endif
index 9b76d31..fb530c8 100644 (file)
@@ -16,6 +16,7 @@
  */
 
 #include <vnet/ipsec/ipsec_tun.h>
+#include <vnet/ipsec/ipsec_itf.h>
 #include <vnet/ipsec/esp.h>
 #include <vnet/udp/udp.h>
 #include <vnet/adj/adj_delegate.h>
@@ -126,14 +127,20 @@ ipsec_tun_protect_from_const_base (const adj_delegate_t * ad)
 }
 
 static u32
-ipsec_tun_protect_get_adj_next (const ipsec_tun_protect_t * itp)
+ipsec_tun_protect_get_adj_next (vnet_link_t linkt,
+                               const ipsec_tun_protect_t * itp)
 {
   ipsec_main_t *im;
   ipsec_sa_t *sa;
   bool is_ip4;
   u32 next;
 
-  is_ip4 = ip46_address_is_ip4 (&itp->itp_tun.src);
+
+  if (itp->itp_flags & IPSEC_PROTECT_ITF)
+    is_ip4 = linkt == VNET_LINK_IP4;
+  else
+    is_ip4 = ip46_address_is_ip4 (&itp->itp_tun.src);
+
   sa = ipsec_sa_get (itp->itp_out_sa);
   im = &ipsec_main;
 
@@ -169,7 +176,7 @@ ipsec_tun_protect_add_adj (adj_index_t ai, const ipsec_tun_protect_t * itp)
     {
       ipsec_tun_protect_sa_by_adj_index[ai] = itp->itp_out_sa;
       adj_nbr_midchain_update_next_node
-       (ai, ipsec_tun_protect_get_adj_next (itp));
+       (ai, ipsec_tun_protect_get_adj_next (adj_get_link_type (ai), itp));
     }
 }
 
@@ -249,6 +256,9 @@ ipsec_tun_protect_adj_add (adj_index_t ai, void *arg)
                    itp - ipsec_tun_protect_pool);
   ipsec_tun_protect_add_adj (ai, itp);
 
+  if (itp->itp_flags & IPSEC_PROTECT_ITF)
+    ipsec_itf_adj_stack (ai, itp->itp_out_sa);
+
   return (ADJ_WALK_RC_CONTINUE);
 }
 
@@ -349,9 +359,14 @@ ipsec_tun_protect_rx_db_remove (ipsec_main_t * im,
 static adj_walk_rc_t
 ipsec_tun_protect_adj_remove (adj_index_t ai, void *arg)
 {
+  ipsec_tun_protect_t *itp = arg;
+
   adj_delegate_remove (ai, ipsec_tun_adj_delegate_type);
   ipsec_tun_protect_add_adj (ai, NULL);
 
+  if (itp->itp_flags & IPSEC_PROTECT_ITF)
+    ipsec_itf_adj_unstack (ai);
+
   return (ADJ_WALK_RC_CONTINUE);
 }
 
@@ -404,8 +419,11 @@ ipsec_tun_protect_set_crypto_addr (ipsec_tun_protect_t * itp)
       {
         itp->itp_crypto.src = sa->tunnel_dst_addr;
         itp->itp_crypto.dst = sa->tunnel_src_addr;
-        ipsec_sa_set_IS_PROTECT (sa);
-        itp->itp_flags |= IPSEC_PROTECT_ENCAPED;
+        if (!(itp->itp_flags & IPSEC_PROTECT_ITF))
+          {
+            ipsec_sa_set_IS_PROTECT (sa);
+            itp->itp_flags |= IPSEC_PROTECT_ENCAPED;
+          }
       }
     else
       {
@@ -657,6 +675,7 @@ ipsec_tun_protect_update (u32 sw_if_index,
       pool_get_zero (ipsec_tun_protect_pool, itp);
 
       itp->itp_sw_if_index = sw_if_index;
+      itp->itp_ai = ADJ_INDEX_INVALID;
 
       itp->itp_n_sa_in = vec_len (sas_in);
       for (ii = 0; ii < itp->itp_n_sa_in; ii++)
@@ -673,7 +692,24 @@ ipsec_tun_protect_update (u32 sw_if_index,
       if (rv)
        goto out;
 
-      if (ip46_address_is_zero (&itp->itp_tun.dst))
+      if (ip46_address_is_zero (&itp->itp_tun.src))
+       {
+         /* must be one of thos pesky ipsec interfaces that has no encap.
+          * the encap then MUST comefrom the tunnel mode SA.
+          */
+         ipsec_sa_t *sa;
+
+         sa = ipsec_sa_get (itp->itp_out_sa);
+
+         if (!ipsec_sa_is_set_IS_TUNNEL (sa))
+           {
+             rv = VNET_API_ERROR_INVALID_DST_ADDRESS;
+             goto out;
+           }
+
+         itp->itp_flags |= IPSEC_PROTECT_ITF;
+       }
+      else if (ip46_address_is_zero (&itp->itp_tun.dst))
        {
          /* tunnel has no destination address, presumably because it's p2mp
             in which case we use the nh that this is protection for */
@@ -690,7 +726,7 @@ ipsec_tun_protect_update (u32 sw_if_index,
 
       /*
        * add to the tunnel DB for ingress
-       *  - if the SA is in trasnport mode, then the packates will arrivw
+       *  - if the SA is in trasnport mode, then the packates will arrive
        *    with the IP src,dst of the protected tunnel, in which case we can
        *    simply strip the IP header and hand the payload to the protocol
        *    appropriate input handler
@@ -752,6 +788,9 @@ ipsec_tun_protect_del (u32 sw_if_index, const ip_address_t * nh)
   itp = ipsec_tun_protect_get (itpi);
   ipsec_tun_protect_unconfig (im, itp);
 
+  if (ADJ_INDEX_INVALID != itp->itp_ai)
+    adj_unlock (itp->itp_ai);
+
   clib_mem_free (itp->itp_key);
   pool_put (ipsec_tun_protect_pool, itp);
 
@@ -828,13 +867,7 @@ ipsec_tun_protect_adj_delegate_adj_created (adj_index_t ai)
   itpi = ipsec_tun_protect_find (adj->rewrite_header.sw_if_index, &ip);
 
   if (INDEX_INVALID != itpi)
-    {
-      const ipsec_tun_protect_t *itp;
-
-      itp = ipsec_tun_protect_get (itpi);
-      adj_delegate_add (adj_get (ai), ipsec_tun_adj_delegate_type, itpi);
-      ipsec_tun_protect_add_adj (ai, itp);
-    }
+    ipsec_tun_protect_adj_add (ai, ipsec_tun_protect_get (itpi));
 }
 
 static u8 *
index 90f2996..c5fbe59 100644 (file)
@@ -47,12 +47,21 @@ typedef CLIB_PACKED(struct {
 extern u8 *format_ipsec4_tunnel_key (u8 * s, va_list * args);
 extern u8 *format_ipsec6_tunnel_key (u8 * s, va_list * args);
 
+#define foreach_ipsec_protect_flags \
+  _(L2, 1, "l2")                    \
+  _(ENCAPED, 2, "encapped")         \
+  _(ITF, 4, "itf")                  \
+
 typedef enum ipsec_protect_flags_t_
 {
-  IPSEC_PROTECT_L2 = (1 << 0),
-  IPSEC_PROTECT_ENCAPED = (1 << 1),
+  IPSEC_PROTECT_NONE = 0,
+#define _(a,b,c) IPSEC_PROTECT_##a = b,
+  foreach_ipsec_protect_flags
+#undef _
 } __clib_packed ipsec_protect_flags_t;
 
+extern u8 *format_ipsec_tun_protect_flags (u8 * s, va_list * args);
+
 typedef struct ipsec_ep_t_
 {
   ip46_address_t src;
@@ -76,6 +85,7 @@ typedef struct ipsec_tun_protect_t_
   ipsec_ep_t itp_crypto;
 
   ipsec_protect_flags_t itp_flags;
+  adj_index_t itp_ai;
 
   ipsec_ep_t itp_tun;
 
index 1caed0d..c3484e1 100644 (file)
@@ -914,6 +914,7 @@ class IpsecTun4(object):
 
     def verify_tun_64(self, p, count=1):
         self.vapi.cli("clear errors")
+        self.vapi.cli("clear ipsec sa")
         try:
             send_pkts = self.gen_encrypt_pkts6(p, p.scapy_tun_sa, self.tun_if,
                                                src=p.remote_tun_if_host6,
@@ -1104,6 +1105,7 @@ class IpsecTun6(object):
     def verify_tun_46(self, p, count=1):
         """ ipsec 4o6 tunnel basic test """
         self.vapi.cli("clear errors")
+        self.vapi.cli("clear ipsec sa")
         try:
             send_pkts = self.gen_encrypt_pkts(p, p.scapy_tun_sa, self.tun_if,
                                               src=p.remote_tun_if_host4,
index a59baf1..1830126 100644 (file)
@@ -15,7 +15,7 @@ from vpp_ipsec_tun_interface import VppIpsecTunInterface
 from vpp_gre_interface import VppGreInterface
 from vpp_ipip_tun_interface import VppIpIpTunInterface
 from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto
-from vpp_ipsec import VppIpsecSA, VppIpsecTunProtect
+from vpp_ipsec import VppIpsecSA, VppIpsecTunProtect, VppIpsecInterface
 from vpp_l2 import VppBridgeDomain, VppBridgeDomainPort
 from vpp_sub_interface import L2_VTR_OP, VppDot1QSubint
 from vpp_teib import VppTeib
@@ -24,13 +24,18 @@ from vpp_papi import VppEnum
 from vpp_acl import AclRule, VppAcl, VppAclInterface
 
 
-def config_tun_params(p, encryption_type, tun_if):
+def config_tun_params(p, encryption_type, tun_if, src=None, dst=None):
     ip_class_by_addr_type = {socket.AF_INET: IP, socket.AF_INET6: IPv6}
     esn_en = bool(p.flags & (VppEnum.vl_api_ipsec_sad_flags_t.
                              IPSEC_API_SAD_FLAG_USE_ESN))
     crypt_key = mk_scapy_crypt_key(p)
-    p.tun_dst = tun_if.remote_ip
-    p.tun_src = tun_if.local_ip
+    if tun_if:
+        p.tun_dst = tun_if.remote_ip
+        p.tun_src = tun_if.local_ip
+    else:
+        p.tun_dst = dst
+        p.tun_src = src
+
     p.scapy_tun_sa = SecurityAssociation(
         encryption_type, spi=p.vpp_tun_spi,
         crypt_algo=p.crypt_algo,
@@ -2473,5 +2478,298 @@ class TestIpsec6TunProtectTunDrop(TemplateIpsec,
         self.unconfig_network(p)
 
 
+class TemplateIpsecItf4(object):
+    """ IPsec Interface IPv4 """
+
+    encryption_type = ESP
+    tun4_encrypt_node_name = "esp4-encrypt-tun"
+    tun4_decrypt_node_name = "esp4-decrypt-tun"
+    tun4_input_node = "ipsec4-tun-input"
+
+    def config_sa_tun(self, p, src, dst):
+        config_tun_params(p, self.encryption_type, None, src, dst)
+
+        p.tun_sa_out = VppIpsecSA(self, p.scapy_tun_sa_id, p.scapy_tun_spi,
+                                  p.auth_algo_vpp_id, p.auth_key,
+                                  p.crypt_algo_vpp_id, p.crypt_key,
+                                  self.vpp_esp_protocol,
+                                  src, dst,
+                                  flags=p.flags)
+        p.tun_sa_out.add_vpp_config()
+
+        p.tun_sa_in = VppIpsecSA(self, p.vpp_tun_sa_id, p.vpp_tun_spi,
+                                 p.auth_algo_vpp_id, p.auth_key,
+                                 p.crypt_algo_vpp_id, p.crypt_key,
+                                 self.vpp_esp_protocol,
+                                 dst, src,
+                                 flags=p.flags)
+        p.tun_sa_in.add_vpp_config()
+
+    def config_protect(self, p):
+        p.tun_protect = VppIpsecTunProtect(self,
+                                           p.tun_if,
+                                           p.tun_sa_out,
+                                           [p.tun_sa_in])
+        p.tun_protect.add_vpp_config()
+
+    def config_network(self, p):
+        p.tun_if = VppIpsecInterface(self)
+
+        p.tun_if.add_vpp_config()
+        p.tun_if.admin_up()
+        p.tun_if.config_ip4()
+        p.tun_if.config_ip6()
+
+        p.route = VppIpRoute(self, p.remote_tun_if_host, 32,
+                             [VppRoutePath(p.tun_if.remote_ip4,
+                                           0xffffffff)])
+        p.route.add_vpp_config()
+        r = VppIpRoute(self, p.remote_tun_if_host6, 128,
+                       [VppRoutePath(p.tun_if.remote_ip6,
+                                     0xffffffff,
+                                     proto=DpoProto.DPO_PROTO_IP6)])
+        r.add_vpp_config()
+
+    def unconfig_network(self, p):
+        p.route.remove_vpp_config()
+        p.tun_if.remove_vpp_config()
+
+    def unconfig_protect(self, p):
+        p.tun_protect.remove_vpp_config()
+
+    def unconfig_sa(self, p):
+        p.tun_sa_out.remove_vpp_config()
+        p.tun_sa_in.remove_vpp_config()
+
+
+class TestIpsecItf4(TemplateIpsec,
+                    TemplateIpsecItf4,
+                    IpsecTun4):
+    """ IPsec Interface IPv4 """
+
+    def setUp(self):
+        super(TestIpsecItf4, self).setUp()
+
+        self.tun_if = self.pg0
+
+    def tearDown(self):
+        super(TestIpsecItf4, self).tearDown()
+
+    def test_tun_44(self):
+        """IPSEC interface IPv4"""
+
+        n_pkts = 127
+        p = self.ipv4_params
+
+        self.config_network(p)
+        self.config_sa_tun(p,
+                           self.pg0.local_ip4,
+                           self.pg0.remote_ip4)
+        self.config_protect(p)
+
+        self.verify_tun_44(p, count=n_pkts)
+        c = p.tun_if.get_rx_stats()
+        self.assertEqual(c['packets'], n_pkts)
+        c = p.tun_if.get_tx_stats()
+        self.assertEqual(c['packets'], n_pkts)
+
+        p.tun_if.admin_down()
+        self.verify_tun_dropped_44(p, count=n_pkts)
+        p.tun_if.admin_up()
+        self.verify_tun_44(p, count=n_pkts)
+
+        c = p.tun_if.get_rx_stats()
+        self.assertEqual(c['packets'], 3*n_pkts)
+        c = p.tun_if.get_tx_stats()
+        self.assertEqual(c['packets'], 2*n_pkts)
+
+        # it's a v6 packet when its encrypted
+        self.tun4_encrypt_node_name = "esp6-encrypt-tun"
+
+        self.verify_tun_64(p, count=n_pkts)
+        c = p.tun_if.get_rx_stats()
+        self.assertEqual(c['packets'], 4*n_pkts)
+        c = p.tun_if.get_tx_stats()
+        self.assertEqual(c['packets'], 3*n_pkts)
+
+        self.tun4_encrypt_node_name = "esp4-encrypt-tun"
+
+        self.vapi.cli("clear interfaces")
+
+        # rekey - create new SAs and update the tunnel protection
+        np = copy.copy(p)
+        np.crypt_key = b'X' + p.crypt_key[1:]
+        np.scapy_tun_spi += 100
+        np.scapy_tun_sa_id += 1
+        np.vpp_tun_spi += 100
+        np.vpp_tun_sa_id += 1
+        np.tun_if.local_spi = p.vpp_tun_spi
+        np.tun_if.remote_spi = p.scapy_tun_spi
+
+        self.config_sa_tun(np,
+                           self.pg0.local_ip4,
+                           self.pg0.remote_ip4)
+        self.config_protect(np)
+        self.unconfig_sa(p)
+
+        self.verify_tun_44(np, count=n_pkts)
+        c = p.tun_if.get_rx_stats()
+        self.assertEqual(c['packets'], n_pkts)
+        c = p.tun_if.get_tx_stats()
+        self.assertEqual(c['packets'], n_pkts)
+
+        # teardown
+        self.unconfig_protect(np)
+        self.unconfig_sa(np)
+        self.unconfig_network(p)
+
+
+class TemplateIpsecItf6(object):
+    """ IPsec Interface IPv6 """
+
+    encryption_type = ESP
+    tun6_encrypt_node_name = "esp6-encrypt-tun"
+    tun6_decrypt_node_name = "esp6-decrypt-tun"
+    tun6_input_node = "ipsec6-tun-input"
+
+    def config_sa_tun(self, p, src, dst):
+        config_tun_params(p, self.encryption_type, None, src, dst)
+
+        p.tun_sa_out = VppIpsecSA(self, p.scapy_tun_sa_id, p.scapy_tun_spi,
+                                  p.auth_algo_vpp_id, p.auth_key,
+                                  p.crypt_algo_vpp_id, p.crypt_key,
+                                  self.vpp_esp_protocol,
+                                  src, dst,
+                                  flags=p.flags)
+        p.tun_sa_out.add_vpp_config()
+
+        p.tun_sa_in = VppIpsecSA(self, p.vpp_tun_sa_id, p.vpp_tun_spi,
+                                 p.auth_algo_vpp_id, p.auth_key,
+                                 p.crypt_algo_vpp_id, p.crypt_key,
+                                 self.vpp_esp_protocol,
+                                 dst, src,
+                                 flags=p.flags)
+        p.tun_sa_in.add_vpp_config()
+
+    def config_protect(self, p):
+        p.tun_protect = VppIpsecTunProtect(self,
+                                           p.tun_if,
+                                           p.tun_sa_out,
+                                           [p.tun_sa_in])
+        p.tun_protect.add_vpp_config()
+
+    def config_network(self, p):
+        p.tun_if = VppIpsecInterface(self)
+
+        p.tun_if.add_vpp_config()
+        p.tun_if.admin_up()
+        p.tun_if.config_ip4()
+        p.tun_if.config_ip6()
+
+        r = VppIpRoute(self, p.remote_tun_if_host4, 32,
+                       [VppRoutePath(p.tun_if.remote_ip4,
+                                     0xffffffff)])
+        r.add_vpp_config()
+
+        p.route = VppIpRoute(self, p.remote_tun_if_host, 128,
+                             [VppRoutePath(p.tun_if.remote_ip6,
+                                           0xffffffff,
+                                           proto=DpoProto.DPO_PROTO_IP6)])
+        p.route.add_vpp_config()
+
+    def unconfig_network(self, p):
+        p.route.remove_vpp_config()
+        p.tun_if.remove_vpp_config()
+
+    def unconfig_protect(self, p):
+        p.tun_protect.remove_vpp_config()
+
+    def unconfig_sa(self, p):
+        p.tun_sa_out.remove_vpp_config()
+        p.tun_sa_in.remove_vpp_config()
+
+
+class TestIpsecItf6(TemplateIpsec,
+                    TemplateIpsecItf6,
+                    IpsecTun6):
+    """ IPsec Interface IPv6 """
+
+    def setUp(self):
+        super(TestIpsecItf6, self).setUp()
+
+        self.tun_if = self.pg0
+
+    def tearDown(self):
+        super(TestIpsecItf6, self).tearDown()
+
+    def test_tun_44(self):
+        """IPSEC interface IPv6"""
+
+        n_pkts = 127
+        p = self.ipv6_params
+
+        self.config_network(p)
+        self.config_sa_tun(p,
+                           self.pg0.local_ip6,
+                           self.pg0.remote_ip6)
+        self.config_protect(p)
+
+        self.verify_tun_66(p, count=n_pkts)
+        c = p.tun_if.get_rx_stats()
+        self.assertEqual(c['packets'], n_pkts)
+        c = p.tun_if.get_tx_stats()
+        self.assertEqual(c['packets'], n_pkts)
+
+        p.tun_if.admin_down()
+        self.verify_drop_tun_66(p, count=n_pkts)
+        p.tun_if.admin_up()
+        self.verify_tun_66(p, count=n_pkts)
+
+        c = p.tun_if.get_rx_stats()
+        self.assertEqual(c['packets'], 3*n_pkts)
+        c = p.tun_if.get_tx_stats()
+        self.assertEqual(c['packets'], 2*n_pkts)
+
+        # it's a v4 packet when its encrypted
+        self.tun6_encrypt_node_name = "esp4-encrypt-tun"
+
+        self.verify_tun_46(p, count=n_pkts)
+        c = p.tun_if.get_rx_stats()
+        self.assertEqual(c['packets'], 4*n_pkts)
+        c = p.tun_if.get_tx_stats()
+        self.assertEqual(c['packets'], 3*n_pkts)
+
+        self.tun6_encrypt_node_name = "esp6-encrypt-tun"
+
+        self.vapi.cli("clear interfaces")
+
+        # rekey - create new SAs and update the tunnel protection
+        np = copy.copy(p)
+        np.crypt_key = b'X' + p.crypt_key[1:]
+        np.scapy_tun_spi += 100
+        np.scapy_tun_sa_id += 1
+        np.vpp_tun_spi += 100
+        np.vpp_tun_sa_id += 1
+        np.tun_if.local_spi = p.vpp_tun_spi
+        np.tun_if.remote_spi = p.scapy_tun_spi
+
+        self.config_sa_tun(np,
+                           self.pg0.local_ip6,
+                           self.pg0.remote_ip6)
+        self.config_protect(np)
+        self.unconfig_sa(p)
+
+        self.verify_tun_66(np, count=n_pkts)
+        c = p.tun_if.get_rx_stats()
+        self.assertEqual(c['packets'], n_pkts)
+        c = p.tun_if.get_tx_stats()
+        self.assertEqual(c['packets'], n_pkts)
+
+        # teardown
+        self.unconfig_protect(np)
+        self.unconfig_sa(np)
+        self.unconfig_network(p)
+
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
index 985f6d4..f012a4a 100644 (file)
@@ -1,6 +1,7 @@
 from vpp_object import VppObject
 from ipaddress import ip_address
 from vpp_papi import VppEnum
+from vpp_interface import VppInterface
 
 try:
     text_type = unicode
@@ -368,3 +369,41 @@ class VppIpsecTunProtect(VppObject):
                self.nh == str(b.tun.nh):
                 return True
         return False
+
+
+class VppIpsecInterface(VppInterface):
+    """
+    VPP IPSec interface
+    """
+
+    def __init__(self, test, mode=None):
+        super(VppIpsecInterface, self).__init__(test)
+
+        # only p2p mode is supported currently
+        self.mode = (VppEnum.vl_api_tunnel_mode_t.
+                     TUNNEL_API_MODE_P2P)
+
+    def add_vpp_config(self):
+        r = self.test.vapi.ipsec_itf_create(itf={
+            'user_instance': 0xffffffff,
+            'mode': self.mode,
+        })
+        self.set_sw_if_index(r.sw_if_index)
+        self.test.registry.register(self, self.test.logger)
+        return self
+
+    def remove_vpp_config(self):
+        self.test.vapi.ipsec_itf_delete(sw_if_index=self._sw_if_index)
+
+    def query_vpp_config(self):
+        ts = self.test.vapi.ipsec_itf_dump(sw_if_index=0xffffffff)
+        for t in ts:
+            if t.tunnel.sw_if_index == self._sw_if_index:
+                return True
+        return False
+
+    def __str__(self):
+        return self.object_id()
+
+    def object_id(self):
+        return "ipsec-%d" % self._sw_if_index