diff --git a/src/Makefile.am b/src/Makefile.am index bfdead9fb8..b206382846 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -508,6 +508,7 @@ noinst_HEADERS = \ util-detect.h \ util-device.h \ util-dpdk.h \ + util-dpdk-i40e.h \ util-ebpf.h \ util-enum.h \ util-error.h \ @@ -1073,6 +1074,7 @@ libsuricata_c_a_SOURCES = \ util-detect.c \ util-device.c \ util-dpdk.c \ + util-dpdk-i40e.c \ util-ebpf.c \ util-enum.c \ util-error.c \ diff --git a/src/runmode-dpdk.c b/src/runmode-dpdk.c index 9c7c9cb71d..def0a33207 100644 --- a/src/runmode-dpdk.c +++ b/src/runmode-dpdk.c @@ -38,6 +38,7 @@ #include "util-byte.h" #include "util-cpu.h" #include "util-dpdk.h" +#include "util-dpdk-i40e.h" #ifdef HAVE_DPDK @@ -742,6 +743,9 @@ static DPDKIfaceConfig *ConfigParse(const char *iface) static void DeviceSetPMDSpecificRSS(struct rte_eth_rss_conf *rss_conf, const char *driver_name) { + // RSS is configured in a specific way for a driver i40e and DPDK version <= 19.xx + if (strcmp(driver_name, "net_i40e") == 0) + i40eDeviceSetRSSHashFunction(&rss_conf->rss_hf); } static void DumpRSSFlags(const uint64_t requested, const uint64_t actual) diff --git a/src/source-dpdk.c b/src/source-dpdk.c index 202e335e5b..fc50de0ef0 100644 --- a/src/source-dpdk.c +++ b/src/source-dpdk.c @@ -84,6 +84,7 @@ TmEcode NoDPDKSupportExit(ThreadVars *tv, const void *initdata, void **data) #else /* We have DPDK support */ #include "util-dpdk.h" +#include "util-dpdk-i40e.h" #include #define BURST_SIZE 32 @@ -188,10 +189,25 @@ static uint64_t DPDKGetSeconds() static void DevicePostStartPMDSpecificActions(DPDKThreadVars *ptv, const char *driver_name) { + // The PMD Driver i40e has a special way to set the RSS, it can be set via rte_flow rules + // and only after the start of the port + if (strcmp(driver_name, "net_i40e") == 0) + i40eDeviceSetRSS(ptv->port_id, ptv->threads); } static void DevicePreStopPMDSpecificActions(DPDKThreadVars *ptv, const char *driver_name) { + int retval; + + if (strcmp(driver_name, "net_i40e") == 0) { + // Flush the RSS rules that have been inserted in the post start section + struct rte_flow_error flush_error = { 0 }; + retval = rte_flow_flush(ptv->port_id, &flush_error); + if (retval != 0) { + SCLogError(SC_ERR_DPDK_CONF, "Unable to flush rte_flow rules: %s Flush error msg: %s", + rte_strerror(-retval), flush_error.message); + } + } } /** diff --git a/src/util-dpdk-i40e.c b/src/util-dpdk-i40e.c new file mode 100644 index 0000000000..70e18f4710 --- /dev/null +++ b/src/util-dpdk-i40e.c @@ -0,0 +1,382 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \defgroup dpdk DPDK Intel I40E driver helpers functions + * + * @{ + */ + +/** + * \file + * + * \author Lukas Sismis + * + * DPDK driver's helper functions + * + */ + +#include "util-dpdk-i40e.h" + +#ifdef HAVE_DPDK + +#define I40E_RSS_HKEY_LEN 52 + +#if RTE_VER_YEAR <= 19 +static int i40eDeviceEnableSymHash( + int port_id, const char *port_name, uint32_t ftype, enum rte_eth_hash_function function) +{ + struct rte_eth_hash_filter_info info; + int retval; + uint32_t idx, offset; + + memset(&info, 0, sizeof(info)); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + retval = rte_eth_dev_filter_supported(port_id, RTE_ETH_FILTER_HASH); +#pragma GCC diagnostic pop + if (retval < 0) { + SCLogError(SC_ERR_DPDK_CONF, "RTE_ETH_FILTER_HASH not supported on port: %s", port_name); + return retval; + } + + info.info_type = RTE_ETH_HASH_FILTER_GLOBAL_CONFIG; + info.info.global_conf.hash_func = function; + + idx = ftype / UINT64_BIT; + offset = ftype % UINT64_BIT; + info.info.global_conf.valid_bit_mask[idx] |= (1ULL << offset); + info.info.global_conf.sym_hash_enable_mask[idx] |= (1ULL << offset); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + retval = rte_eth_dev_filter_ctrl(port_id, RTE_ETH_FILTER_HASH, RTE_ETH_FILTER_SET, &info); +#pragma GCC diagnostic pop + + if (retval < 0) { + SCLogError(SC_ERR_DPDK_CONF, "Cannot set global hash configurations on port %s", port_name); + return retval; + } + + return 0; +} + +static int i40eDeviceSetSymHash(int port_id, const char *port_name, int enable) +{ + int ret; + struct rte_eth_hash_filter_info info; + + memset(&info, 0, sizeof(info)); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + ret = rte_eth_dev_filter_supported(port_id, RTE_ETH_FILTER_HASH); +#pragma GCC diagnostic pop + + if (ret < 0) { + SCLogError(SC_ERR_DPDK_CONF, "RTE_ETH_FILTER_HASH not supported on port: %s", port_name); + return ret; + } + + info.info_type = RTE_ETH_HASH_FILTER_SYM_HASH_ENA_PER_PORT; + info.info.enable = enable; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + ret = rte_eth_dev_filter_ctrl(port_id, RTE_ETH_FILTER_HASH, RTE_ETH_FILTER_SET, &info); +#pragma GCC diagnostic pop + + if (ret < 0) { + SCLogError(SC_ERR_DPDK_CONF, "Cannot set symmetric hash enable per port on port %s", + port_name); + return ret; + } + + return 0; +} + +static int i40eDeviceSetRSSWithFilter(int port_id, const char *port_name) +{ + int retval = 0; + + // Behavior of RTE_FLOW in DPDK version 19.xx and less is different than on versions + // above. For that reason RSS on i40e driver is set differently. + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_FRAG_IPV4, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_NONFRAG_IPV4_TCP, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_NONFRAG_IPV4_UDP, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_NONFRAG_IPV4_SCTP, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_NONFRAG_IPV4_OTHER, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_FRAG_IPV6, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_NONFRAG_IPV6_TCP, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_NONFRAG_IPV6_UDP, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_NONFRAG_IPV6_SCTP, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + retval |= i40eDeviceEnableSymHash( + port_id, port_name, RTE_ETH_FLOW_NONFRAG_IPV6_OTHER, RTE_ETH_HASH_FUNCTION_TOEPLITZ); + + retval |= i40eDeviceSetSymHash(port_id, port_name, 1); + return retval; +} + +#else + +static int i40eDeviceSetRSSFlowQueues( + int port_id, const char *port_name, struct rte_eth_rss_conf rss_conf, int nb_rx_queues) +{ + struct rte_flow_action_rss rss_action_conf = { 0 }; + struct rte_flow_attr attr = { 0 }; + struct rte_flow_item pattern[] = { { 0 }, { 0 }, { 0 }, { 0 } }; + struct rte_flow_action action[] = { { 0 }, { 0 } }; + struct rte_flow *flow; + struct rte_flow_error flow_error = { 0 }; + uint16_t queues[RTE_MAX_QUEUES_PER_PORT]; + + for (int i = 0; i < nb_rx_queues; ++i) + queues[i] = i; + + rss_action_conf.func = RTE_ETH_HASH_FUNCTION_DEFAULT; + rss_action_conf.level = 0; + rss_action_conf.types = 0; // queues region can not be configured with types + rss_action_conf.key = rss_conf.rss_key; + rss_action_conf.key_len = rss_conf.rss_key_len; + rss_action_conf.queue_num = nb_rx_queues; + rss_action_conf.queue = queues; + + attr.ingress = 1; + pattern[0].type = RTE_FLOW_ITEM_TYPE_END; + action[0].type = RTE_FLOW_ACTION_TYPE_RSS; + action[0].conf = &rss_action_conf; + action[1].type = RTE_FLOW_ACTION_TYPE_END; + + flow = rte_flow_create(port_id, &attr, pattern, action, &flow_error); + if (flow == NULL) { + SCLogError(SC_ERR_DPDK_CONF, "Error when creating rte_flow rule on %s: %s", port_name, + flow_error.message); + int ret = rte_flow_validate(port_id, &attr, pattern, action, &flow_error); + SCLogError(SC_ERR_DPDK_CONF, "Error on rte_flow validation for port %s: %s errmsg: %s", + port_name, rte_strerror(-ret), flow_error.message); + return ret; + } else { + SCLogInfo("RTE_FLOW queue region created for port %s", port_name); + } + return 0; +} + +static int i40eDeviceCreateRSSFlow(int port_id, const char *port_name, + struct rte_eth_rss_conf rss_conf, uint64_t rss_type, struct rte_flow_item *pattern) +{ + struct rte_flow_action_rss rss_action_conf = { 0 }; + struct rte_flow_attr attr = { 0 }; + struct rte_flow_action action[] = { { 0 }, { 0 } }; + struct rte_flow *flow; + struct rte_flow_error flow_error = { 0 }; + + rss_action_conf.func = RTE_ETH_HASH_FUNCTION_SYMMETRIC_TOEPLITZ; + rss_action_conf.level = 0; + rss_action_conf.types = rss_type; + rss_action_conf.key_len = rss_conf.rss_key_len; + rss_action_conf.key = rss_conf.rss_key; + rss_action_conf.queue_num = 0; + rss_action_conf.queue = NULL; + + attr.ingress = 1; + action[0].type = RTE_FLOW_ACTION_TYPE_RSS; + action[0].conf = &rss_action_conf; + action[1].type = RTE_FLOW_ACTION_TYPE_END; + + flow = rte_flow_create(port_id, &attr, pattern, action, &flow_error); + if (flow == NULL) { + SCLogError(SC_ERR_DPDK_CONF, "Error when creating rte_flow rule on %s: %s", port_name, + flow_error.message); + int ret = rte_flow_validate(port_id, &attr, pattern, action, &flow_error); + SCLogError(SC_ERR_DPDK_CONF, "Error on rte_flow validation for port %s: %s errmsg: %s", + port_name, rte_strerror(-ret), flow_error.message); + return ret; + } else { + SCLogInfo("RTE_FLOW flow rule created for port %s", port_name); + } + + return 0; +} + +static int i40eDeviceSetRSSFlowIPv4( + int port_id, const char *port_name, struct rte_eth_rss_conf rss_conf) +{ + int ret = 0; + struct rte_flow_item pattern[] = { { 0 }, { 0 }, { 0 }, { 0 } }; + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4; + pattern[2].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow( + port_id, port_name, rss_conf, ETH_RSS_NONFRAG_IPV4_OTHER, pattern); + memset(pattern, 0, sizeof(pattern)); + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4; + pattern[2].type = RTE_FLOW_ITEM_TYPE_UDP; + pattern[3].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow(port_id, port_name, rss_conf, ETH_RSS_NONFRAG_IPV4_UDP, pattern); + memset(pattern, 0, sizeof(pattern)); + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4; + pattern[2].type = RTE_FLOW_ITEM_TYPE_TCP; + pattern[3].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow(port_id, port_name, rss_conf, ETH_RSS_NONFRAG_IPV4_TCP, pattern); + memset(pattern, 0, sizeof(pattern)); + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4; + pattern[2].type = RTE_FLOW_ITEM_TYPE_SCTP; + pattern[3].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow( + port_id, port_name, rss_conf, ETH_RSS_NONFRAG_IPV4_SCTP, pattern); + memset(pattern, 0, sizeof(pattern)); + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4; + pattern[2].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow(port_id, port_name, rss_conf, ETH_RSS_FRAG_IPV4, pattern); + + return ret; +} + +static int i40eDeviceSetRSSFlowIPv6( + int port_id, const char *port_name, struct rte_eth_rss_conf rss_conf) +{ + int ret = 0; + struct rte_flow_item pattern[] = { { 0 }, { 0 }, { 0 }, { 0 } }; + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV6; + pattern[2].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow( + port_id, port_name, rss_conf, ETH_RSS_NONFRAG_IPV6_OTHER, pattern); + memset(pattern, 0, sizeof(pattern)); + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV6; + pattern[2].type = RTE_FLOW_ITEM_TYPE_UDP; + pattern[3].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow(port_id, port_name, rss_conf, ETH_RSS_NONFRAG_IPV6_UDP, pattern); + memset(pattern, 0, sizeof(pattern)); + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV6; + pattern[2].type = RTE_FLOW_ITEM_TYPE_TCP; + pattern[3].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow(port_id, port_name, rss_conf, ETH_RSS_NONFRAG_IPV6_TCP, pattern); + memset(pattern, 0, sizeof(pattern)); + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV6; + pattern[2].type = RTE_FLOW_ITEM_TYPE_SCTP; + pattern[3].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow( + port_id, port_name, rss_conf, ETH_RSS_NONFRAG_IPV6_SCTP, pattern); + memset(pattern, 0, sizeof(pattern)); + + pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH; + pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV6; + pattern[2].type = RTE_FLOW_ITEM_TYPE_END; + ret |= i40eDeviceCreateRSSFlow(port_id, port_name, rss_conf, ETH_RSS_FRAG_IPV6, pattern); + + return ret; +} + +static int i40eDeviceSetRSSWithFlows(int port_id, const char *port_name, int nb_rx_queues) +{ + int retval; + uint8_t rss_key[I40E_RSS_HKEY_LEN]; + struct rte_flow_error flush_error = { 0 }; + struct rte_eth_rss_conf rss_conf = { + .rss_key = rss_key, + .rss_key_len = I40E_RSS_HKEY_LEN, + }; + + retval = rte_eth_dev_rss_hash_conf_get(port_id, &rss_conf); + if (retval != 0) { + SCLogError(SC_ERR_DPDK_CONF, "Unable to get RSS hash configuration of port %s", port_name); + return retval; + } + + retval = 0; + retval |= i40eDeviceSetRSSFlowQueues(port_id, port_name, rss_conf, nb_rx_queues); + retval |= i40eDeviceSetRSSFlowIPv4(port_id, port_name, rss_conf); + retval |= i40eDeviceSetRSSFlowIPv6(port_id, port_name, rss_conf); + if (retval != 0) { + retval = rte_flow_flush(port_id, &flush_error); + if (retval != 0) { + SCLogError(SC_ERR_DPDK_CONF, + "Unable to flush rte_flow rules of %s: %s Flush error msg: %s", port_name, + rte_strerror(-retval), flush_error.message); + } + return retval; + } + + return 0; +} + +#endif /* RTE_VER_YEAR < 19 */ + +int i40eDeviceSetRSS(int port_id, int nb_rx_queues) +{ + int retval; + (void)nb_rx_queues; // avoid unused variable warnings + char port_name[RTE_ETH_NAME_MAX_LEN]; + + retval = rte_eth_dev_get_name_by_port(port_id, port_name); + if (unlikely(retval != 0)) { + SCLogError(SC_ERR_STAT, "Failed to convert port id %d to the interface name: %s", port_id, + strerror(-retval)); + return retval; + } + +#if RTE_VER_YEAR <= 19 + i40eDeviceSetRSSWithFilter(port_id, port_name); +#else + i40eDeviceSetRSSWithFlows(port_id, port_name, nb_rx_queues); +#endif + return 0; +} + +void i40eDeviceSetRSSHashFunction(uint64_t *rss_hf) +{ + if (RTE_VER_YEAR <= 19) + *rss_hf = ETH_RSS_FRAG_IPV4 | ETH_RSS_NONFRAG_IPV4_TCP | ETH_RSS_NONFRAG_IPV4_UDP | + ETH_RSS_NONFRAG_IPV4_SCTP | ETH_RSS_NONFRAG_IPV4_OTHER | ETH_RSS_FRAG_IPV6 | + ETH_RSS_NONFRAG_IPV6_TCP | ETH_RSS_NONFRAG_IPV6_UDP | ETH_RSS_NONFRAG_IPV6_SCTP | + ETH_RSS_NONFRAG_IPV6_OTHER | ETH_RSS_SCTP; + else + *rss_hf = ETH_RSS_FRAG_IPV4 | ETH_RSS_NONFRAG_IPV4_OTHER | ETH_RSS_FRAG_IPV6 | + ETH_RSS_NONFRAG_IPV6_OTHER; +} + +#endif /* HAVE_DPDK */ +/** + * @} + */ diff --git a/src/util-dpdk-i40e.h b/src/util-dpdk-i40e.h new file mode 100644 index 0000000000..3d77b1152f --- /dev/null +++ b/src/util-dpdk-i40e.h @@ -0,0 +1,37 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Lukas Sismis + */ + +#ifndef UTIL_DPDK_I40E_H +#define UTIL_DPDK_I40E_H + +#include "suricata-common.h" +#include "util-dpdk.h" + +#ifdef HAVE_DPDK + +int i40eDeviceSetRSS(int port_id, int nb_rx_queues); +void i40eDeviceSetRSSHashFunction(uint64_t *rss_conf); + +#endif /* HAVE_DPDK */ + +#endif /* UTIL_DPDK_I40E_H */ diff --git a/src/util-dpdk.h b/src/util-dpdk.h index 34820db79f..3c2e6da6bd 100644 --- a/src/util-dpdk.h +++ b/src/util-dpdk.h @@ -33,6 +33,7 @@ #include #include #include +#include #endif /* HAVE_DPDK */