From dd4ccf2623b547654d215ffcf42f9813e42aa90c Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Tue, 30 Jun 2020 07:47:14 +0000 Subject: [PATCH] ipsec: Dedicated IPSec interface type Type: feature Signed-off-by: Neale Ranns Change-Id: Ie8bd50df163aea2798e9f9d35a13dcadc4a4a4b2 --- src/vnet/CMakeLists.txt | 1 + src/vnet/ipsec/ipsec.api | 51 ++++- src/vnet/ipsec/ipsec_api.c | 42 ++++ src/vnet/ipsec/ipsec_format.c | 20 +- src/vnet/ipsec/ipsec_itf.c | 462 ++++++++++++++++++++++++++++++++++++++++++ src/vnet/ipsec/ipsec_itf.h | 117 +++++++++++ src/vnet/ipsec/ipsec_tun.c | 61 ++++-- src/vnet/ipsec/ipsec_tun.h | 14 +- test/template_ipsec.py | 2 + test/test_ipsec_tun_if_esp.py | 306 +++++++++++++++++++++++++++- test/vpp_ipsec.py | 39 ++++ 11 files changed, 1092 insertions(+), 23 deletions(-) create mode 100644 src/vnet/ipsec/ipsec_itf.c create mode 100644 src/vnet/ipsec/ipsec_itf.h diff --git a/src/vnet/CMakeLists.txt b/src/vnet/CMakeLists.txt index 9ee0eeba070..e4182186e60 100644 --- a/src/vnet/CMakeLists.txt +++ b/src/vnet/CMakeLists.txt @@ -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 diff --git a/src/vnet/ipsec/ipsec.api b/src/vnet/ipsec/ipsec.api index dee9144dda1..488df1cc4ea 100644 --- a/src/vnet/ipsec/ipsec.api +++ b/src/vnet/ipsec/ipsec.api @@ -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; diff --git a/src/vnet/ipsec/ipsec_api.c b/src/vnet/ipsec/ipsec_api.c index ef58f7a6010..667d9b2fd11 100644 --- a/src/vnet/ipsec/ipsec_api.c +++ b/src/vnet/ipsec/ipsec_api.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,7 @@ #if WITH_LIBSSL > 0 #include #include +#include #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; diff --git a/src/vnet/ipsec/ipsec_format.c b/src/vnet/ipsec/ipsec_format.c index 98de779b0de..e3c6f22bf00 100644 --- a/src/vnet/ipsec/ipsec_format.c +++ b/src/vnet/ipsec/ipsec_format.c @@ -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 index 00000000000..756bc19fbef --- /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 +#include +#include +#include +#include + +/* 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 ]} + * 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 ]", + .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 } + * 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 ", + .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 00000000000..93e03f7b477 --- /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 +#include + +/** + * @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 9b76d311bc2..fb530c8dc9f 100644 --- a/src/vnet/ipsec/ipsec_tun.c +++ b/src/vnet/ipsec/ipsec_tun.c @@ -16,6 +16,7 @@ */ #include +#include #include #include #include @@ -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 * diff --git a/src/vnet/ipsec/ipsec_tun.h b/src/vnet/ipsec/ipsec_tun.h index 90f299668dc..c5fbe59f2b6 100644 --- a/src/vnet/ipsec/ipsec_tun.h +++ b/src/vnet/ipsec/ipsec_tun.h @@ -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; diff --git a/test/template_ipsec.py b/test/template_ipsec.py index 1caed0dd932..c3484e1ad0b 100644 --- a/test/template_ipsec.py +++ b/test/template_ipsec.py @@ -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, diff --git a/test/test_ipsec_tun_if_esp.py b/test/test_ipsec_tun_if_esp.py index a59baf1dfbf..183012608fe 100644 --- a/test/test_ipsec_tun_if_esp.py +++ b/test/test_ipsec_tun_if_esp.py @@ -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) diff --git a/test/vpp_ipsec.py b/test/vpp_ipsec.py index 985f6d4dc4e..f012a4a1e84 100644 --- a/test/vpp_ipsec.py +++ b/test/vpp_ipsec.py @@ -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 -- 2.16.6