ipsec: Dedicated IPSec interface type

Type: feature

Signed-off-by: Neale Ranns <nranns@cisco.com>
Change-Id: Ie8bd50df163aea2798e9f9d35a13dcadc4a4a4b2
diff --git a/src/vnet/ipsec/ipsec.api b/src/vnet/ipsec/ipsec.api
index dee9144..488df1c 100644
--- a/src/vnet/ipsec/ipsec.api
+++ b/src/vnet/ipsec/ipsec.api
@@ -20,6 +20,7 @@
 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 @@
   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;
diff --git a/src/vnet/ipsec/ipsec_api.c b/src/vnet/ipsec/ipsec_api.c
index ef58f7a..667d9b2 100644
--- a/src/vnet/ipsec/ipsec_api.c
+++ b/src/vnet/ipsec/ipsec_api.c
@@ -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_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 @@
   /* *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;
diff --git a/src/vnet/ipsec/ipsec_format.c b/src/vnet/ipsec/ipsec_format.c
index 98de779..e3c6f22 100644
--- a/src/vnet/ipsec/ipsec_format.c
+++ b/src/vnet/ipsec/ipsec_format.c
@@ -353,6 +353,21 @@
   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 @@
   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
index 0000000..756bc19
--- /dev/null
+++ b/src/vnet/ipsec/ipsec_itf.c
@@ -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
index 0000000..93e03f7
--- /dev/null
+++ b/src/vnet/ipsec/ipsec_itf.h
@@ -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
diff --git a/src/vnet/ipsec/ipsec_tun.c b/src/vnet/ipsec/ipsec_tun.c
index 9b76d31..fb530c8 100644
--- a/src/vnet/ipsec/ipsec_tun.c
+++ b/src/vnet/ipsec/ipsec_tun.c
@@ -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 @@
 }
 
 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_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 @@
 		    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 @@
 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 @@
       {
         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 @@
       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 @@
       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 @@
 
       /*
        * 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 @@
   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 @@
   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 *
diff --git a/src/vnet/ipsec/ipsec_tun.h b/src/vnet/ipsec/ipsec_tun.h
index 90f2996..c5fbe59 100644
--- a/src/vnet/ipsec/ipsec_tun.h
+++ b/src/vnet/ipsec/ipsec_tun.h
@@ -47,12 +47,21 @@
 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 @@
   ipsec_ep_t itp_crypto;
 
   ipsec_protect_flags_t itp_flags;
+  adj_index_t itp_ai;
 
   ipsec_ep_t itp_tun;