From: Adi Nissim <a...@mellanox.com> Until now there were 2 hw_offload modes: no/yes * hw_offload = no : Configure the SA without HW offload. * hw_offload = yes : Configure the SA with HW offload. In this case, if the device does not support offloading, SA creation will fail.
This commit introduces a new mode: hw_offload = auto ---------------------------------------------------- If the device and kernel support HW offload, configure the SA with HW offload, but do not fail SA creation otherwise. Signed-off-by: Adi Nissim <a...@mellanox.com> Reviewed-by: Aviv Heller <av...@mellanox.com> --- v1 -> v2: -Fix the FD leak --- .../plugins/kernel_netlink/kernel_netlink_ipsec.c | 286 +++++++++++++++++++-- 1 file changed, 263 insertions(+), 23 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 4e79dfc..dbeccf8 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -18,15 +18,40 @@ * for more details. */ +/* + * Copyright (C) 2018 Mellanox Technologies. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + #define _GNU_SOURCE #include <sys/types.h> #include <sys/socket.h> +#include <sys/ioctl.h> #include <stdint.h> #include <linux/ipsec.h> #include <linux/netlink.h> #include <linux/rtnetlink.h> #include <linux/xfrm.h> #include <linux/udp.h> +#include <linux/ethtool.h> +#include <linux/sockios.h> #include <net/if.h> #include <unistd.h> #include <time.h> @@ -237,6 +262,21 @@ static kernel_algorithm_t compression_algs[] = { }; /** + * IPsec offload + */ +enum nic_offload_state { + NIC_OFFLOAD_UNKNOWN, + NIC_OFFLOAD_UNSUPPORTED, + NIC_OFFLOAD_SUPPORTED +}; + +static struct { + unsigned int bit; + unsigned int total_blocks; + enum nic_offload_state state; +} netlink_esp_hw_offload; + +/** * Look up a kernel algorithm name and its key size */ static const char* lookup_algorithm(transform_type_t type, int ikev2) @@ -1290,6 +1330,224 @@ static bool add_mark(struct nlmsghdr *hdr, int buflen, mark_t mark) return TRUE; } +/** + * check if kernel supported HW offload + */ +static void netlink_find_offload_feature(const char *ifname, int fd_for_socket) +{ + struct ethtool_sset_info *sset_info = NULL; + struct ethtool_gstrings *cmd = NULL; + struct ifreq ifr; + uint32_t sset_len, i; + char *str; + int err; + + netlink_esp_hw_offload.state = NIC_OFFLOAD_UNSUPPORTED; + + /* Determine number of device-features */ + sset_info = malloc(sizeof(*sset_info)); + if (sset_info == NULL) + { + goto out; + } + memset(sset_info, 0, sizeof(*sset_info)); + + sset_info->cmd = ETHTOOL_GSSET_INFO; + sset_info->sset_mask = 1ULL << ETH_SS_FEATURES; + strcpy(ifr.ifr_name, ifname); + + ifr.ifr_data = (void *)sset_info; + + err = ioctl(fd_for_socket, SIOCETHTOOL, &ifr); + if (err != 0) + { + goto out; + } + + if (sset_info->sset_mask != 1ULL << ETH_SS_FEATURES) + { + goto out; + } + sset_len = sset_info->data[0]; + + /* Retrieve names of device-features */ + cmd = malloc(sizeof(*cmd) + ETH_GSTRING_LEN * sset_len); + if (cmd == NULL) + { + goto out; + } + + memset(cmd, 0, sizeof(*cmd)); + cmd->cmd = ETHTOOL_GSTRINGS; + cmd->string_set = ETH_SS_FEATURES; + strcpy(ifr.ifr_name, ifname); + ifr.ifr_data = (void *)cmd; + err = ioctl(fd_for_socket, SIOCETHTOOL, &ifr); + if (err) + { + goto out; + } + + /* Look for the ESP_HW feature bit */ + str = (char *)cmd->data; + for (i = 0; i < cmd->len; i++) + { + if (strneq(str, "esp-hw-offload", ETH_GSTRING_LEN) == 1) + break; + str += ETH_GSTRING_LEN; + } + if (i >= cmd->len) + { + goto out; + } + + netlink_esp_hw_offload.bit = i; + netlink_esp_hw_offload.total_blocks = (sset_len + 31) / 32; + netlink_esp_hw_offload.state = NIC_OFFLOAD_SUPPORTED; + +out: + if (sset_info != NULL) + { + free(sset_info); + } + if (cmd != NULL) + { + free(cmd); + } +} + +/** + * Check if interface supported HW offload + */ +static bool netlink_detect_offload(const char *ifname) +{ + struct ethtool_gfeatures *cmd; + uint32_t feature_bit; + struct ifreq ifr; + bool ret = FALSE; + int nl_send_fd; + int block; + + nl_send_fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM); + + if (nl_send_fd < 0) + { + return FALSE; + } + + /* + * Kernel requires a real interface in order to query the kernel-wide + * capability, so we do it here on first invocation. + */ + if (netlink_esp_hw_offload.state == NIC_OFFLOAD_UNKNOWN) + { + netlink_find_offload_feature(ifname, nl_send_fd); + } + + if (netlink_esp_hw_offload.state == NIC_OFFLOAD_UNSUPPORTED) + { + DBG1(DBG_KNL, "HW offload is not supported by kernel"); + goto out; + } + + /* Feature is supported by kernel. Query device features */ + cmd = malloc(sizeof(cmd->features[0]) * + netlink_esp_hw_offload.total_blocks); + if (cmd == NULL) + { + goto out; + } + + strcpy(ifr.ifr_name, ifname); + + ifr.ifr_data = (void *)cmd; + cmd->cmd = ETHTOOL_GFEATURES; + cmd->size = netlink_esp_hw_offload.total_blocks; + + if (ioctl(nl_send_fd, SIOCETHTOOL, &ifr)) + { + goto out_free; + } + + block = netlink_esp_hw_offload.bit / 32; + feature_bit = 1U << (netlink_esp_hw_offload.bit % 32); + if (cmd->features[block].active & feature_bit) + { + ret = TRUE; + } + +out_free: + free(cmd); + if (ret == FALSE) + { + DBG1(DBG_KNL, "HW offload is not supported by device"); + } +out: + close(nl_send_fd); + return ret; +} + +/** + * There are 3 configuration options: + * 1. HW_OFFLOAD_NO : Do not configure HW offload.. + * 2. HW_OFFLOAD_YES : Configure HW offload. + * Fail SA addition if offload is not supported. + * 3. HW_OFFLOAD_AUTO : Configure HW offload if supported by the kernel + * and device. + * Do not fail SA addition otherwise. + */ +static bool config_hw_offload(kernel_ipsec_sa_id_t *id, + kernel_ipsec_add_sa_t *data, struct nlmsghdr *hdr, int buflen) +{ + bool cfg_hw_offload_is_yes = (data->hw_offload == HW_OFFLOAD_YES); + host_t *local = data->inbound ? id->dst : id->src; + struct xfrm_user_offload *offload; + bool hw_offload_support; + bool ret = FALSE; + char *ifname; + + /* Do Ipsec configuration without offload */ + if (data->hw_offload == HW_OFFLOAD_NO) + { + return TRUE; + } + + if (!charon->kernel->get_interface(charon->kernel, local, &ifname)) + { + return !cfg_hw_offload_is_yes; + } + + /* Check if interface supports hw_offload */ + hw_offload_support = netlink_detect_offload(ifname); + + if (!hw_offload_support) + { + ret = !cfg_hw_offload_is_yes; + goto out; + } + + /* Activate HW offload */ + offload = netlink_reserve(hdr, buflen, + XFRMA_OFFLOAD_DEV, sizeof(*offload)); + if (!offload) + { + ret = !cfg_hw_offload_is_yes; + goto out; + } + offload->ifindex = if_nametoindex(ifname); + if (local->get_family(local) == AF_INET6) + { + offload->flags |= XFRM_OFFLOAD_IPV6; + } + offload->flags |= data->inbound ? XFRM_OFFLOAD_INBOUND : 0; + + ret = TRUE; + +out: + free(ifname); + return ret; +} + METHOD(kernel_ipsec_t, add_sa, status_t, private_kernel_netlink_ipsec_t *this, kernel_ipsec_sa_id_t *id, kernel_ipsec_add_sa_t *data) @@ -1650,30 +1908,12 @@ METHOD(kernel_ipsec_t, add_sa, status_t, data->replay_window); sa->replay_window = data->replay_window; } - if (data->hw_offload) - { - host_t *local = data->inbound ? id->dst : id->src; - char *ifname; - if (charon->kernel->get_interface(charon->kernel, local, &ifname)) - { - struct xfrm_user_offload *offload; - - offload = netlink_reserve(hdr, sizeof(request), - XFRMA_OFFLOAD_DEV, sizeof(*offload)); - if (!offload) - { - free(ifname); - goto failed; - } - offload->ifindex = if_nametoindex(ifname); - if (local->get_family(local) == AF_INET6) - { - offload->flags |= XFRM_OFFLOAD_IPV6; - } - offload->flags |= data->inbound ? XFRM_OFFLOAD_INBOUND : 0; - free(ifname); - } + DBG2(DBG_KNL, " HW offload mode = %N", hw_offload_names, data->hw_offload); + if (!config_hw_offload(id, data, hdr, sizeof(request))) + { + DBG1(DBG_KNL, "failed to configure HW offload"); + goto failed; } } -- 1.8.3.1