diff options
Diffstat (limited to 'src/vnet/ipsec/ipsec_itf.c')
-rw-r--r-- | src/vnet/ipsec/ipsec_itf.c | 462 |
1 files changed, 462 insertions, 0 deletions
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 <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: + */ |