From 16e442d59f14d6d77c70b885e54ff10d89718387 Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Fri, 18 Sep 2026 16:04:49 +0200 Subject: [PATCH 1/9] schema: document generic DPDK capture statistics Define the DPDK packet and error counters emitted by the capture module so that EVE statistics validate against the schema. --- etc/schema.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/etc/schema.json b/etc/schema.json index ae32e07509..d7c20bc546 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -6941,6 +6941,28 @@ } } }, + "dpdk": { + "type": "object", + "description": "Statistics for the DPDK capture module", + "additionalProperties": false, + "properties": { + "ierrors": { + "type": "integer", + "description": + "Number of erroneous received packets reported by DPDK" + }, + "imissed": { + "type": "integer", + "description": + "Number of packets dropped by the NIC because receive buffers were unavailable" + }, + "no_mbufs": { + "type": "integer", + "description": + "Number of RX packet-buffer allocation failures reported by DPDK" + } + } + }, "errors": { "type": "integer", "description": "Number of Suricata errors reported while reading capture module" @@ -6956,6 +6978,20 @@ "kernel_packets": { "type": "integer", "description": "Number of packets received from the kernel" + }, + "packets": { + "type": "integer", + "description": + "Number of received packets plus receive errors reported by the DPDK capture module" + }, + "rx_errors": { + "type": "integer", + "description": + "Total DPDK missed packets, erroneous received packets, and RX packet-buffer allocation failures" + }, + "tx_errors": { + "type": "integer", + "description": "Number of packet transmission errors reported by DPDK" } } }, From 9f7bcd5af6320e30dc3be3042be034b99714901b Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Thu, 17 Sep 2026 12:45:49 +0200 Subject: [PATCH 2/9] dpdk: require DPDK 21.11 Raise the minimum supported DPDK version to 21.11 and remove compatibility paths for older releases, including legacy bond library detection and RSS configuration. Update the documented version requirement. --- configure.ac | 10 +- doc/userguide/configuration/suricata-yaml.rst | 3 +- src/runmode-dpdk.c | 18 --- src/source-dpdk.c | 15 +- src/util-dpdk-bonding.c | 18 --- src/util-dpdk-bonding.h | 1 - src/util-dpdk-common.h | 74 --------- src/util-dpdk-i40e.c | 149 ------------------ src/util-dpdk-ice.c | 6 - 9 files changed, 6 insertions(+), 288 deletions(-) diff --git a/configure.ac b/configure.ac index 85416b18fc..e16586d982 100644 --- a/configure.ac +++ b/configure.ac @@ -1434,10 +1434,10 @@ fi AC_DEFINE([HAVE_DPDK],[1],(DPDK support enabled)) - PKG_CHECK_EXISTS(libdpdk >= 19.11, , [with_pkgconfig_libdpdk=no]) + PKG_CHECK_EXISTS(libdpdk >= 21.11, , [with_pkgconfig_libdpdk=no]) if test "$with_pkgconfig_libdpdk" = "no"; then echo - echo " ERROR! libdpdk >= 19.11 not found by pkg-config, go get it" + echo " ERROR! libdpdk >= 21.11 not found by pkg-config, go get it" echo " from https://www.dpdk.org/ or your distribution:" echo echo " Ubuntu: apt-get install dpdk-dev" @@ -1452,11 +1452,7 @@ if test ! -z "$(ldconfig -p | grep librte_net_bond)"; then AC_DEFINE([HAVE_DPDK_BOND],[1],(DPDK Bond PMD support enabled)) enable_dpdk_bond_pmd="yes" - LIBS="${LIBS} -lrte_net_bond" # 20.11+ - elif test ! -z "$(ldconfig -p | grep librte_pmd_bond)"; then - AC_DEFINE([HAVE_DPDK_BOND],[1],(DPDK Bond PMD support enabled)) - enable_dpdk_bond_pmd="yes" - LIBS="${LIBS} -lrte_pmd_bond" + LIBS="${LIBS} -lrte_net_bond" else echo echo " WARNING: DPDK Bond PMD was not found on your system, " diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index c0f6e493da..e47eec2ada 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -2526,8 +2526,7 @@ Other NICs using the same driver as mentioned above should work as well. The DPDK capture interface has not been tested neither with the virtual interfaces nor in the virtual environments like VMs, Docker or similar. -The minimal supported DPDK is version 19.11 which should be available in most -repositories of major distributions. +The minimal supported DPDK version is 21.11. Alternatively, it is also possible to use ``meson`` and ``ninja`` to build and install DPDK from source files. It is required to have correctly configured tool ``pkg-config`` as it is used to diff --git a/src/runmode-dpdk.c b/src/runmode-dpdk.c index 0152ceee2a..1123514cd0 100644 --- a/src/runmode-dpdk.c +++ b/src/runmode-dpdk.c @@ -1331,10 +1331,8 @@ static void DumpRXOffloadCapabilities(const uint64_t rx_offld_capa) rx_offld_capa & RTE_ETH_RX_OFFLOAD_OUTER_UDP_CKSUM ? "" : "NOT "); SCLogConfig("RTE_ETH_RX_OFFLOAD_RSS_HASH - %savailable", rx_offld_capa & RTE_ETH_RX_OFFLOAD_RSS_HASH ? "" : "NOT "); -#if RTE_VERSION >= RTE_VERSION_NUM(20, 11, 0, 0) SCLogConfig("RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT - %savailable", rx_offld_capa & RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT ? "" : "NOT "); -#endif } static int DeviceValidateMTU(const DPDKIfaceConfig *iconf, const struct rte_eth_dev_info *dev_info) @@ -1347,28 +1345,12 @@ static int DeviceValidateMTU(const DPDKIfaceConfig *iconf, const struct rte_eth_ SCReturnInt(-ERANGE); } -#if RTE_VERSION < RTE_VERSION_NUM(21, 11, 0, 0) - // check if jumbo frames are set and are available - if (iconf->mtu > RTE_ETHER_MAX_LEN && - !(dev_info->rx_offload_capa & DEV_RX_OFFLOAD_JUMBO_FRAME)) { - SCLogError("%s: jumbo frames not supported, set MTU to 1500", iconf->iface); - SCReturnInt(-EINVAL); - } -#endif - SCReturnInt(0); } static void DeviceSetMTU(struct rte_eth_conf *port_conf, uint16_t mtu) { -#if RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0) port_conf->rxmode.mtu = mtu; -#else - port_conf->rxmode.max_rx_pkt_len = mtu; - if (mtu > RTE_ETHER_MAX_LEN) { - port_conf->rxmode.offloads |= DEV_RX_OFFLOAD_JUMBO_FRAME; - } -#endif } static void PortConfSetInterruptMode(const DPDKIfaceConfig *iconf, struct rte_eth_conf *port_conf) diff --git a/src/source-dpdk.c b/src/source-dpdk.c index 7cffebb9d0..0ee29f4e68 100644 --- a/src/source-dpdk.c +++ b/src/source-dpdk.c @@ -210,12 +210,8 @@ static void DevicePreClosePMDSpecificActions(DPDKThreadVars *ptv, const char *dr driver_name = BondingDeviceDriverGet(ptv->port_id); } - if ( -#if RTE_VERSION > RTE_VERSION_NUM(20, 0, 0, 0) - strcmp(driver_name, "net_i40e") == 0 || -#endif /* RTE_VERSION > RTE_VERSION_NUM(20, 0, 0, 0) */ - strcmp(driver_name, "net_ixgbe") == 0 || strcmp(driver_name, "net_ice") == 0 || - strcmp(driver_name, "mlx5_pci") == 0) { + if (strcmp(driver_name, "net_i40e") == 0 || strcmp(driver_name, "net_ixgbe") == 0 || + strcmp(driver_name, "net_ice") == 0 || strcmp(driver_name, "mlx5_pci") == 0) { // Flush the RSS rules that have been inserted in the post start section struct rte_flow_error flush_error = { 0 }; int32_t retval = rte_flow_flush(ptv->port_id, &flush_error); @@ -710,17 +706,10 @@ static TmEcode ReceiveDPDKThreadInit(ThreadVars *tv, const void *initdata, void } if (link.link_status) { char link_status_str[RTE_ETH_LINK_MAX_STR_LEN]; -#if RTE_VERSION >= RTE_VERSION_NUM(20, 11, 0, 0) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" rte_eth_link_to_str(link_status_str, sizeof(link_status_str), &link); #pragma GCC diagnostic pop -#else - snprintf(link_status_str, sizeof(link_status_str), - "Link Up, speed %u Mbps, %s", // 22 chars + 10 for digits + 11 for duplex - link.link_speed, - (link.link_duplex == ETH_LINK_FULL_DUPLEX) ? "full-duplex" : "half-duplex"); -#endif SCLogInfo("%s: %s", dpdk_config->iface, link_status_str); break; diff --git a/src/util-dpdk-bonding.c b/src/util-dpdk-bonding.c index 991d68fd34..febbabc8ff 100644 --- a/src/util-dpdk-bonding.c +++ b/src/util-dpdk-bonding.c @@ -29,24 +29,6 @@ #include "util-dpdk.h" #include "util-debug.h" -/** - * Determines if the port is Bond or not by evaluating device driver name - * @param pid port ID - * @return 0 - the device si Bond PMD, 1 - regular device, <0 error - */ -int32_t BondingIsBond(uint16_t pid) -{ - struct rte_eth_dev_info di; - int32_t ret = rte_eth_dev_info_get(pid, &di); - if (ret < 0) { - SCLogError("%s: unable to get device info (err: %s)", DPDKGetPortNameByPortID(pid), - rte_strerror(-ret)); - return ret; - } - - return strcmp(di.driver_name, "net_bonding") == 0 ? 0 : 1; -} - uint16_t BondingMemberDevicesGet( uint16_t bond_pid, uint16_t bonded_devs[], uint16_t bonded_devs_length) { diff --git a/src/util-dpdk-bonding.h b/src/util-dpdk-bonding.h index 7e998278c4..8694f1f068 100644 --- a/src/util-dpdk-bonding.h +++ b/src/util-dpdk-bonding.h @@ -29,7 +29,6 @@ #ifdef HAVE_DPDK -int32_t BondingIsBond(uint16_t pid); uint16_t BondingMemberDevicesGet( uint16_t bond_pid, uint16_t bonded_devs[], uint16_t bonded_devs_length); uint32_t BondingMempoolSizeCalculate( diff --git a/src/util-dpdk-common.h b/src/util-dpdk-common.h index 269ebc195c..22ad89dac4 100644 --- a/src/util-dpdk-common.h +++ b/src/util-dpdk-common.h @@ -44,80 +44,6 @@ #define RTE_ETH_MQ_RX_RSS ETH_MQ_RX_RSS #endif -#if RTE_VERSION < RTE_VERSION_NUM(21, 11, 0, 0) -#define RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE DEV_TX_OFFLOAD_MBUF_FAST_FREE - -#define RTE_ETH_RX_OFFLOAD_CHECKSUM DEV_RX_OFFLOAD_CHECKSUM - -#define RTE_ETH_RX_OFFLOAD_VLAN_STRIP DEV_RX_OFFLOAD_VLAN_STRIP -#define RTE_ETH_RX_OFFLOAD_IPV4_CKSUM DEV_RX_OFFLOAD_IPV4_CKSUM -#define RTE_ETH_RX_OFFLOAD_UDP_CKSUM DEV_RX_OFFLOAD_UDP_CKSUM -#define RTE_ETH_RX_OFFLOAD_TCP_CKSUM DEV_RX_OFFLOAD_TCP_CKSUM -#define RTE_ETH_RX_OFFLOAD_TCP_LRO DEV_RX_OFFLOAD_TCP_LRO -#define RTE_ETH_RX_OFFLOAD_QINQ_STRIP DEV_RX_OFFLOAD_QINQ_STRIP -#define RTE_ETH_RX_OFFLOAD_OUTER_IPV4_CKSUM DEV_RX_OFFLOAD_OUTER_IPV4_CKSUM -#define RTE_ETH_RX_OFFLOAD_MACSEC_STRIP DEV_RX_OFFLOAD_MACSEC_STRIP -#define RTE_ETH_RX_OFFLOAD_HEADER_SPLIT DEV_RX_OFFLOAD_HEADER_SPLIT -#define RTE_ETH_RX_OFFLOAD_VLAN_FILTER DEV_RX_OFFLOAD_VLAN_FILTER -#define RTE_ETH_RX_OFFLOAD_VLAN_EXTEND DEV_RX_OFFLOAD_VLAN_EXTEND -#define RTE_ETH_RX_OFFLOAD_SCATTER DEV_RX_OFFLOAD_SCATTER -#define RTE_ETH_RX_OFFLOAD_TIMESTAMP DEV_RX_OFFLOAD_TIMESTAMP -#define RTE_ETH_RX_OFFLOAD_SECURITY DEV_RX_OFFLOAD_SECURITY -#define RTE_ETH_RX_OFFLOAD_KEEP_CRC DEV_RX_OFFLOAD_KEEP_CRC -#define RTE_ETH_RX_OFFLOAD_SCTP_CKSUM DEV_RX_OFFLOAD_SCTP_CKSUM -#define RTE_ETH_RX_OFFLOAD_OUTER_UDP_CKSUM DEV_RX_OFFLOAD_OUTER_UDP_CKSUM -#define RTE_ETH_RX_OFFLOAD_RSS_HASH DEV_RX_OFFLOAD_RSS_HASH - -#define RTE_ETH_MQ_TX_NONE ETH_MQ_TX_NONE - -#define RTE_ETH_MQ_RX_NONE ETH_MQ_RX_NONE - -#define RTE_ETH_RSS_IP ETH_RSS_IP -#define RTE_ETH_RSS_UDP ETH_RSS_UDP -#define RTE_ETH_RSS_TCP ETH_RSS_TCP -#define RTE_ETH_RSS_SCTP ETH_RSS_SCTP -#define RTE_ETH_RSS_TUNNEL ETH_RSS_TUNNEL - -#define RTE_ETH_RSS_L3_SRC_ONLY ETH_RSS_L3_SRC_ONLY -#define RTE_ETH_RSS_L3_DST_ONLY ETH_RSS_L3_DST_ONLY -#define RTE_ETH_RSS_L4_SRC_ONLY ETH_RSS_L4_SRC_ONLY -#define RTE_ETH_RSS_L4_DST_ONLY ETH_RSS_L4_DST_ONLY - -#define RTE_ETH_RSS_IPV4 ETH_RSS_IPV4 -#define RTE_ETH_RSS_FRAG_IPV4 ETH_RSS_FRAG_IPV4 -#define RTE_ETH_RSS_NONFRAG_IPV4_TCP ETH_RSS_NONFRAG_IPV4_TCP -#define RTE_ETH_RSS_NONFRAG_IPV4_UDP ETH_RSS_NONFRAG_IPV4_UDP -#define RTE_ETH_RSS_NONFRAG_IPV4_SCTP ETH_RSS_NONFRAG_IPV4_SCTP -#define RTE_ETH_RSS_NONFRAG_IPV4_OTHER ETH_RSS_NONFRAG_IPV4_OTHER -#define RTE_ETH_RSS_IPV6 ETH_RSS_IPV6 -#define RTE_ETH_RSS_FRAG_IPV6 ETH_RSS_FRAG_IPV6 -#define RTE_ETH_RSS_NONFRAG_IPV6_TCP ETH_RSS_NONFRAG_IPV6_TCP -#define RTE_ETH_RSS_NONFRAG_IPV6_UDP ETH_RSS_NONFRAG_IPV6_UDP -#define RTE_ETH_RSS_NONFRAG_IPV6_SCTP ETH_RSS_NONFRAG_IPV6_SCTP -#define RTE_ETH_RSS_NONFRAG_IPV6_OTHER ETH_RSS_NONFRAG_IPV6_OTHER -#define RTE_ETH_RSS_L2_PAYLOAD ETH_RSS_L2_PAYLOAD -#define RTE_ETH_RSS_IPV6_EX ETH_RSS_IPV6_EX -#define RTE_ETH_RSS_IPV6_TCP_EX ETH_RSS_IPV6_TCP_EX -#define RTE_ETH_RSS_IPV6_UDP_EX ETH_RSS_IPV6_UDP_EX -#define RTE_ETH_RSS_PORT ETH_RSS_PORT -#define RTE_ETH_RSS_VXLAN ETH_RSS_VXLAN -#define RTE_ETH_RSS_NVGRE ETH_RSS_NVGRE -#define RTE_ETH_RSS_GTPU ETH_RSS_GTPU - -#define RTE_MBUF_F_RX_IP_CKSUM_MASK PKT_RX_IP_CKSUM_MASK -#define RTE_MBUF_F_RX_IP_CKSUM_NONE PKT_RX_IP_CKSUM_NONE -#define RTE_MBUF_F_RX_IP_CKSUM_GOOD PKT_RX_IP_CKSUM_GOOD -#define RTE_MBUF_F_RX_IP_CKSUM_BAD PKT_RX_IP_CKSUM_BAD - -#define RTE_MBUF_F_RX_L4_CKSUM_MASK PKT_RX_L4_CKSUM_MASK -#define RTE_MBUF_F_RX_L4_CKSUM_GOOD PKT_RX_L4_CKSUM_GOOD -#define RTE_MBUF_F_RX_L4_CKSUM_BAD PKT_RX_L4_CKSUM_BAD -#endif - -#if RTE_VERSION < RTE_VERSION_NUM(20, 11, 0, 0) -#define RTE_ETH_LINK_MAX_STR_LEN 40 -#endif - typedef struct { struct rte_mempool **pkt_mp; uint16_t pkt_mp_cnt; diff --git a/src/util-dpdk-i40e.c b/src/util-dpdk-i40e.c index 18aed1e030..e51f6661fb 100644 --- a/src/util-dpdk-i40e.c +++ b/src/util-dpdk-i40e.c @@ -33,145 +33,12 @@ #include "util-dpdk-i40e.h" #include "util-dpdk.h" #include "util-debug.h" -#include "util-dpdk-bonding.h" #include "util-dpdk-rss.h" #ifdef HAVE_DPDK -#if RTE_VERSION < RTE_VERSION_NUM(21, 0, 0, 0) -#define I40E_RSS_HKEY_LEN 40 -#define I40E_RSS_HASH_FUNCTION RTE_ETH_HASH_FUNCTION_SYMMETRIC_TOEPLITZ -#else #define I40E_RSS_HKEY_LEN 52 #define I40E_RSS_HASH_FUNCTION RTE_ETH_HASH_FUNCTION_TOEPLITZ -#endif // RTE_VERSION < RTE_VERSION_NUM(21, 0, 0, 0) - -#if RTE_VERSION < RTE_VERSION_NUM(20, 0, 0, 0) -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("%s: RTE_ETH_FILTER_HASH not supported", 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("%s: cannot set global hash configurations", 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("%s: RTE_ETH_FILTER_HASH not supported", 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("%s: cannot set symmetric hash enable per port", port_name); - return ret; - } - - return 0; -} - -static int i40eDeviceApplyRSSFilter(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; -} - -static int32_t i40eDeviceSetRSSWithFilter(int port_id, const char *port_name) -{ - int32_t ret = BondingIsBond(port_id); - if (ret < 0) - return -ret; - - if (ret == 1) { // regular device - i40eDeviceApplyRSSFilter(port_id, port_name); - } else if (ret == 0) { // the device is Bond PMD - uint16_t bonded_devs[RTE_MAX_ETHPORTS]; - ret = BondingMemberDevicesGet(port_id, bonded_devs, RTE_MAX_ETHPORTS); - for (int i = 0; i < ret; i++) { - i40eDeviceApplyRSSFilter(bonded_devs[i], port_name); - } - } else { - FatalError("Unknown return value from BondingIsBond()"); - } - - return 0; -} - -#else static int i40eDeviceSetRSSFlowIPv4( int port_id, const char *port_name, struct rte_flow_action_rss rss_conf) @@ -297,34 +164,18 @@ static int i40eDeviceSetRSSWithFlows(int port_id, const char *port_name, int nb_ return 0; } -#endif /* RTE_VERSION < RTE_VERSION_NUM(20,0,0,0) */ - int i40eDeviceSetRSS(int port_id, uint16_t nb_rx_queues, char *port_name) { - (void)nb_rx_queues; // avoid unused variable warnings - -#if RTE_VERSION >= RTE_VERSION_NUM(20, 0, 0, 0) i40eDeviceSetRSSWithFlows(port_id, port_name, nb_rx_queues); -#else - i40eDeviceSetRSSWithFilter(port_id, port_name); -#endif return 0; } void i40eDeviceSetRSSConf(struct rte_eth_rss_conf *rss_conf) { -#if RTE_VERSION >= RTE_VERSION_NUM(20, 0, 0, 0) rss_conf->rss_hf = RTE_ETH_RSS_FRAG_IPV4 | RTE_ETH_RSS_NONFRAG_IPV4_OTHER | RTE_ETH_RSS_FRAG_IPV6 | RTE_ETH_RSS_NONFRAG_IPV6_OTHER; rss_conf->rss_key = NULL; rss_conf->rss_key_len = 0; -#else - rss_conf->rss_hf = - RTE_ETH_RSS_FRAG_IPV4 | RTE_ETH_RSS_NONFRAG_IPV4_TCP | RTE_ETH_RSS_NONFRAG_IPV4_UDP | - RTE_ETH_RSS_NONFRAG_IPV4_SCTP | RTE_ETH_RSS_NONFRAG_IPV4_OTHER | RTE_ETH_RSS_FRAG_IPV6 | - RTE_ETH_RSS_NONFRAG_IPV6_TCP | RTE_ETH_RSS_NONFRAG_IPV6_UDP | - RTE_ETH_RSS_NONFRAG_IPV6_SCTP | RTE_ETH_RSS_NONFRAG_IPV6_OTHER | RTE_ETH_RSS_SCTP; -#endif } #endif /* HAVE_DPDK */ diff --git a/src/util-dpdk-ice.c b/src/util-dpdk-ice.c index f1b9e965d5..b13395484c 100644 --- a/src/util-dpdk-ice.c +++ b/src/util-dpdk-ice.c @@ -34,19 +34,13 @@ #include "util-dpdk.h" #include "util-dpdk-rss.h" #include "util-debug.h" -#include "util-dpdk-bonding.h" #ifdef HAVE_DPDK static void iceDeviceSetRSSHashFunction(uint64_t *rss_hf) { -#if RTE_VERSION < RTE_VERSION_NUM(20, 0, 0, 0) - *rss_hf = RTE_ETH_RSS_FRAG_IPV4 | RTE_ETH_RSS_NONFRAG_IPV4_OTHER | RTE_ETH_RSS_FRAG_IPV6 | - RTE_ETH_RSS_NONFRAG_IPV6_OTHER; -#else *rss_hf = RTE_ETH_RSS_IPV4 | RTE_ETH_RSS_FRAG_IPV4 | RTE_ETH_RSS_NONFRAG_IPV4_OTHER | RTE_ETH_RSS_IPV6 | RTE_ETH_RSS_FRAG_IPV6 | RTE_ETH_RSS_NONFRAG_IPV6_OTHER; -#endif } /** From 969861182212dd1ba9adf5b9d2e5d0fcc7be6a5c Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Thu, 17 Sep 2026 12:45:49 +0200 Subject: [PATCH 3/9] dpdk: timestamp packets once per RX burst Record the acquisition time immediately after each successful receive and pass it to packet initialization. All packets in a burst retain that time instead of including the processing delay of preceding packets. --- src/source-dpdk.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/source-dpdk.c b/src/source-dpdk.c index 0ee29f4e68..b8f1a91248 100644 --- a/src/source-dpdk.c +++ b/src/source-dpdk.c @@ -409,7 +409,8 @@ static inline bool RXPacketCountHeuristic(ThreadVars *tv, DPDKThreadVars *ptv, u * \brief Initializes a packet from an mbuf * \return true if the packet was initialized successfully, false otherwise */ -static inline Packet *PacketInitFromMbuf(DPDKThreadVars *ptv, struct rte_mbuf *mbuf) +static inline Packet *PacketInitFromMbuf( + DPDKThreadVars *ptv, struct rte_mbuf *mbuf, SCTime_t timestamp) { Packet *p = PacketGetFromQueueOrAlloc(); if (unlikely(p == NULL)) { @@ -421,7 +422,7 @@ static inline Packet *PacketInitFromMbuf(DPDKThreadVars *ptv, struct rte_mbuf *m p->flags |= PKT_IGNORE_CHECKSUM; } - p->ts = TimeGet(); + p->ts = timestamp; p->dpdk_v.mbuf = mbuf; p->ReleasePacket = DPDKReleasePacket; p->dpdk_v.copy_mode = ptv->copy_mode; @@ -582,13 +583,17 @@ static TmEcode ReceiveDPDKLoop(ThreadVars *tv, void *data, void *slot) uint16_t nb_rx = rte_eth_rx_burst(ptv->port_id, ptv->queue_id, ptv->received_mbufs, burst_size); + SCTime_t timestamp = { 0 }; + if (nb_rx > 0) { + timestamp = TimeGet(); + } if (RXPacketCountHeuristic(tv, ptv, nb_rx)) { continue; } ptv->pkts += (uint64_t)nb_rx; for (uint16_t i = 0; i < nb_rx; i++) { - Packet *p = PacketInitFromMbuf(ptv, ptv->received_mbufs[i]); + Packet *p = PacketInitFromMbuf(ptv, ptv->received_mbufs[i], timestamp); if (p == NULL) { rte_pktmbuf_free(ptv->received_mbufs[i]); continue; From 43ad2ff4dc7fcdf670cec473d68733399b9bf6ad Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Thu, 17 Sep 2026 12:45:50 +0200 Subject: [PATCH 4/9] dpdk: fix cleanup after packet processing failure The processing pipeline already releases the failed Packet. Avoid releasing it twice, and free every remaining received mbuf using the array's exclusive end index. Return the packet acquisition failure code consistently. --- src/source-dpdk.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/source-dpdk.c b/src/source-dpdk.c index b8f1a91248..fd0eec84c6 100644 --- a/src/source-dpdk.c +++ b/src/source-dpdk.c @@ -602,9 +602,8 @@ static TmEcode ReceiveDPDKLoop(ThreadVars *tv, void *data, void *slot) PacketSetData(p, rte_pktmbuf_mtod(p->dpdk_v.mbuf, uint8_t *), rte_pktmbuf_pkt_len(p->dpdk_v.mbuf)); if (TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p) != TM_ECODE_OK) { - TmqhOutputPacketpool(ptv->tv, p); - DPDKFreeMbufArray(ptv->received_mbufs, nb_rx - i - 1, i + 1); - SCReturnInt(EXIT_FAILURE); + DPDKFreeMbufArray(ptv->received_mbufs, nb_rx, i + 1); + SCReturnInt(TM_ECODE_FAILED); } } From bc1340f4bda35b49e0932748d1e429968fb0d6ca Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Thu, 17 Sep 2026 12:45:50 +0200 Subject: [PATCH 5/9] dpdk: account for in-flight packets in mempool sizing Share the RX burst size with the configuration code and reserve one burst in addition to RX and TX descriptors for both automatic sizing and manual minimum validation. Consolidate the two requirement calculations. Part of this work the tests had to be updated to include the RX burst size. --- doc/userguide/capture-hardware/dpdk.rst | 4 +- doc/userguide/configuration/suricata-yaml.rst | 2 + qa/live/dpdk/dpdk-testsuite.sh | 57 +++++++-------- src/runmode-dpdk.c | 70 ++++++++----------- src/source-dpdk.c | 5 +- src/source-dpdk.h | 2 + suricata.yaml.in | 2 +- 7 files changed, 68 insertions(+), 74 deletions(-) diff --git a/doc/userguide/capture-hardware/dpdk.rst b/doc/userguide/capture-hardware/dpdk.rst index 21f5e198a9..f3971218dd 100644 --- a/doc/userguide/capture-hardware/dpdk.rst +++ b/doc/userguide/capture-hardware/dpdk.rst @@ -203,8 +203,8 @@ by the NIC. Number of TX descriptors depends on the configured ``copy-mode``. IDS (none) mode uses no TX descriptors and does not create any TX queues by default. IPS and TAP mode uses the same number of TX descriptors as RX descriptors. -The number of mempool and its cache is then derived from the count of -descriptors. +The size of each queue's mempool and its cache is then derived from the count +of descriptors and an allowance for in-flight packets. Rx (and Tx) descriptors are set to the highest possible value to allow more buffer room when traffic spikes occur. However, it requires more memory. diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index e47eec2ada..16764f163d 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -2489,6 +2489,8 @@ auto-calculated by setting ``mempool-size: auto``. If ``mempool-size`` is set manually (to e.g. ``mempool-size: 65536``), the value is divided by the number of worker cores of the interface (on 4 worker threads, each worker is assigned with a mempool containing 16383 packet objects). +Both auto sizing and manual minimum validation include each worker's RX and +TX descriptors and a 32-mbuf in-flight margin. Memory (in bytes) for interface's memory pools is calculated as: ``mempool-size`` * ``mtu``. The sum of memory pool requirements divided by the size of one hugepage results diff --git a/qa/live/dpdk/dpdk-testsuite.sh b/qa/live/dpdk/dpdk-testsuite.sh index 6360d4eecd..adad590c3f 100755 --- a/qa/live/dpdk/dpdk-testsuite.sh +++ b/qa/live/dpdk/dpdk-testsuite.sh @@ -36,7 +36,7 @@ run_test "IDS: auto mempool, auto cache (1 thread)" \ --interface-cfg-set net_null0.threads=1 \ --interface-cfg-set net_null0.mempool-size=auto \ --interface-cfg-set net_null0.mempool-cache-size=auto \ - --suricata-log-check-grep "1 packet mempools of size 63, cache size 21" \ + --suricata-log-check-grep "1 packet mempools of size 127, cache size 1" \ --expect-start \ "$IDS_YAML" @@ -44,23 +44,23 @@ run_test "IDS: auto mempool, auto cache (2 threads)" \ --interface-cfg-set net_null0.threads=2 \ --interface-cfg-set net_null0.mempool-size=auto \ --interface-cfg-set net_null0.mempool-cache-size=auto \ - --suricata-log-check-grep "2 packet mempools of size 63, cache size 21" \ + --suricata-log-check-grep "2 packet mempools of size 127, cache size 1" \ --expect-start \ "$IDS_YAML" -run_test "IDS: auto mempool, static cache=1 (1 thread)" \ +run_test "IDS: static mempool=1023, static cache=31 (1 thread)" \ --interface-cfg-set net_null0.threads=1 \ - --interface-cfg-set net_null0.mempool-size=auto \ - --interface-cfg-set net_null0.mempool-cache-size=1 \ - --suricata-log-check-grep "1 packet mempools of size 63, cache size 1" \ + --interface-cfg-set net_null0.mempool-size=1023 \ + --interface-cfg-set net_null0.mempool-cache-size=31 \ + --suricata-log-check-grep "1 packet mempools of size 1023, cache size 31" \ --expect-start \ "$IDS_YAML" -run_test "IDS: auto mempool, static cache=1 (2 threads)" \ +run_test "IDS: static mempool=1023, static cache=7 (2 threads)" \ --interface-cfg-set net_null0.threads=2 \ - --interface-cfg-set net_null0.mempool-size=auto \ - --interface-cfg-set net_null0.mempool-cache-size=1 \ - --suricata-log-check-grep "2 packet mempools of size 63, cache size 1" \ + --interface-cfg-set net_null0.mempool-size=1023 \ + --interface-cfg-set net_null0.mempool-cache-size=7 \ + --suricata-log-check-grep "2 packet mempools of size 511, cache size 7" \ --expect-start \ "$IDS_YAML" @@ -101,30 +101,31 @@ run_test "IDS: static mempool=1023, auto cache (2 threads)" \ run_test "IDS: mempool too small (fail)" \ --interface-cfg-set net_null0.threads=1 \ --interface-cfg-set net_null0.mempool-size=15 \ - --suricata-log-check-grep "mempool size is likely too small" \ + --suricata-log-check-grep "mempool size is too small; set to \"auto\" or adjust to the value of \"127\"" \ --expect-fail \ "$IDS_YAML" run_test "IDS: mempool too small for queues (fail)" \ --interface-cfg-set net_null0.threads=2 \ --interface-cfg-set net_null0.mempool-size=1 \ - --suricata-log-check-grep "mempool size is likely too small" \ + --suricata-log-check-grep "mempool size is too small; set to \"auto\" or adjust to the value of \"255\"" \ --expect-fail \ "$IDS_YAML" -# power-of-two boundary: 32 is exactly nb_rx_desc + nb_tx_desc, must not undercount -run_test "IDS: static mempool=32, power-of-two boundary (fail)" \ +# power-of-two boundary: 64 is exactly nb_rx_desc + nb_tx_desc + one RX burst, +# must not undercount +run_test "IDS: static mempool=64, power-of-two boundary (fail)" \ --interface-cfg-set net_null0.threads=1 \ - --interface-cfg-set net_null0.mempool-size=32 \ - --suricata-log-check-grep "mempool size is likely too small" \ + --interface-cfg-set net_null0.mempool-size=64 \ + --suricata-log-check-grep "mempool size is too small; set to \"auto\" or adjust to the value of \"127\"" \ --expect-fail \ "$IDS_YAML" -run_test "IDS: static mempool=64, power-of-two boundary (1 thread)" \ +run_test "IDS: static mempool=128, power-of-two boundary (1 thread)" \ --interface-cfg-set net_null0.threads=1 \ - --interface-cfg-set net_null0.mempool-size=64 \ + --interface-cfg-set net_null0.mempool-size=128 \ --interface-cfg-set net_null0.mempool-cache-size=auto \ - --suricata-log-check-grep "1 packet mempools of size 63, cache size 21" \ + --suricata-log-check-grep "1 packet mempools of size 127, cache size 1" \ --expect-start \ "$IDS_YAML" @@ -145,7 +146,7 @@ run_test "Bond: auto mempool, auto cache (1 thread)" \ --interface-cfg-set net_bonding0.threads=1 \ --interface-cfg-set net_bonding0.mempool-size=auto \ --interface-cfg-set net_bonding0.mempool-cache-size=auto \ - --suricata-log-check-grep "1 packet mempools of size 127, cache size 1" \ + --suricata-log-check-grep "1 packet mempools of size 255, cache size 85" \ --expect-start \ "$BOND_YAML" @@ -153,23 +154,23 @@ run_test "Bond: auto mempool, auto cache (2 threads)" \ --interface-cfg-set net_bonding0.threads=2 \ --interface-cfg-set net_bonding0.mempool-size=auto \ --interface-cfg-set net_bonding0.mempool-cache-size=auto \ - --suricata-log-check-grep "2 packet mempools of size 127, cache size 1" \ + --suricata-log-check-grep "2 packet mempools of size 255, cache size 85" \ --expect-start \ "$BOND_YAML" -run_test "Bond: auto mempool, static cache=7 (1 thread)" \ +run_test "Bond: auto mempool, static cache=17 (1 thread)" \ --interface-cfg-set net_bonding0.threads=1 \ --interface-cfg-set net_bonding0.mempool-size=auto \ - --interface-cfg-set net_bonding0.mempool-cache-size=7 \ - --suricata-log-check-grep "1 packet mempools of size 127, cache size 7" \ + --interface-cfg-set net_bonding0.mempool-cache-size=17 \ + --suricata-log-check-grep "1 packet mempools of size 255, cache size 17" \ --expect-start \ "$BOND_YAML" -run_test "Bond: auto mempool, static cache=7 (2 threads)" \ +run_test "Bond: auto mempool, static cache=17 (2 threads)" \ --interface-cfg-set net_bonding0.threads=2 \ --interface-cfg-set net_bonding0.mempool-size=auto \ - --interface-cfg-set net_bonding0.mempool-cache-size=7 \ - --suricata-log-check-grep "2 packet mempools of size 127, cache size 7" \ + --interface-cfg-set net_bonding0.mempool-cache-size=17 \ + --suricata-log-check-grep "2 packet mempools of size 255, cache size 17" \ --expect-start \ "$BOND_YAML" @@ -200,7 +201,7 @@ run_test "Bond: static mempool=1023, auto cache (2 threads)" \ run_test "Bond: mempool too small (fail)" \ --interface-cfg-set net_bonding0.threads=1 \ --interface-cfg-set net_bonding0.mempool-size=15 \ - --suricata-log-check-grep "mempool size is likely too small" \ + --suricata-log-check-grep "mempool size is too small; set to \"auto\" or adjust to the value of \"255\"" \ --expect-fail \ "$BOND_YAML" diff --git a/src/runmode-dpdk.c b/src/runmode-dpdk.c index 1123514cd0..b34c5947fc 100644 --- a/src/runmode-dpdk.c +++ b/src/runmode-dpdk.c @@ -530,22 +530,24 @@ static int ConfigSetTxQueues( SCReturnInt(0); } -static uint32_t MempoolSizeCalculateAutosize( - const DPDKIfaceConfig *iconf, const struct rte_eth_dev_info *dev_info) +static int MempoolSizeCalculate( + const DPDKIfaceConfig *iconf, const struct rte_eth_dev_info *dev_info, uint32_t *size) { - uint32_t next_p2 = - rte_align32pow2(iconf->nb_rx_desc + iconf->nb_tx_desc + - 1); // + 1 in case number of descriptors is already a power of 2 + const uint64_t required = (uint64_t)iconf->nb_rx_desc + iconf->nb_tx_desc + DPDK_RX_BURST_SIZE; + /* Add one so that rounding to 2^q - 1 still covers a power-of-two requirement. */ + const uint64_t next_p2 = rte_align64pow2(required + 1); + if (next_p2 > UINT32_MAX) { + SCLogError("%s: mempool requirement exceeds the supported per-queue size", iconf->iface); + return -ERANGE; + } - uint32_t mp_size = next_p2; - if (dev_info != NULL) { - if (strcmp(dev_info->driver_name, "net_bonding") == 0) { - mp_size = BondingMempoolSizeCalculate(iconf->port_id, dev_info, mp_size); - } + uint32_t mp_size = (uint32_t)next_p2; + if (dev_info != NULL && strcmp(dev_info->driver_name, "net_bonding") == 0) { + mp_size = BondingMempoolSizeCalculate(iconf->port_id, dev_info, mp_size); } - return mp_size - - 1; // mempool size should be n = (2^q - 1) to have all descriptors available for use + *size = mp_size - 1; + return 0; } static uint32_t MempoolSizeDistributeToQueues(uint32_t global_mp_size, uint16_t nic_queues) @@ -559,24 +561,10 @@ static uint32_t MempoolSizeDistributeToQueues(uint32_t global_mp_size, uint16_t return 0; } - uint32_t next_p2 = rte_align32pow2(mp_size_per_queue); - return (mp_size_per_queue == next_p2 || mp_size_per_queue == next_p2 - 1) - ? next_p2 - 1 - : (next_p2 >> 1) - 1; // we must fit in the globally specified mempool size -} - -static uint32_t MempoolSizeCalculateMinimal( - const DPDKIfaceConfig *iconf, const struct rte_eth_dev_info *dev_info) -{ - uint32_t mp_size = - rte_align32pow2(iconf->nb_rx_desc + iconf->nb_tx_desc + - 1); // + 1 in case number of descriptors is already a power of 2 - if (dev_info != NULL) { - if (strcmp(dev_info->driver_name, "net_bonding") == 0) { - mp_size = BondingMempoolSizeCalculate(iconf->port_id, dev_info, mp_size); - } - } - return mp_size - 1; + uint64_t next_p2 = rte_align64pow2(mp_size_per_queue); + return (uint32_t)((mp_size_per_queue == next_p2 || mp_size_per_queue == next_p2 - 1) + ? next_p2 - 1 + : (next_p2 >> 1) - 1); // fit in the globally specified mempool size } static int ConfigSetMempoolSize( @@ -584,9 +572,6 @@ static int ConfigSetMempoolSize( { SCEnter(); if (entry_str == NULL || entry_str[0] == '\0' || strcmp(entry_str, "auto") == 0) { - // calculate the mempool size based on the number of: - // - RX / TX queues - // - RX / TX descriptors bool err = false; if (iconf->nb_rx_queues == 0) { // in IDS mode, we don't need TX queues @@ -604,8 +589,8 @@ static int ConfigSetMempoolSize( SCReturnInt(-EINVAL); } - iconf->queue_mempool_size = MempoolSizeCalculateAutosize(iconf, dev_info); - SCReturnInt(0); + int retval = MempoolSizeCalculate(iconf, dev_info, &iconf->queue_mempool_size); + SCReturnInt(retval); } uint32_t global_mempool_size; @@ -617,12 +602,17 @@ static int ConfigSetMempoolSize( iconf->queue_mempool_size = MempoolSizeDistributeToQueues(global_mempool_size, iconf->nb_rx_queues); - uint32_t required_mp_size = MempoolSizeCalculateMinimal(iconf, dev_info); + uint32_t required_mp_size; + int retval = MempoolSizeCalculate(iconf, dev_info, &required_mp_size); + if (retval < 0) { + SCReturnInt(retval); + } + if (required_mp_size > iconf->queue_mempool_size) { - uint32_t required_global_mp_size = - required_mp_size * iconf->nb_rx_queues + iconf->nb_rx_queues - 1; - SCLogError("%s: mempool size is likely too small for the number of descriptors and queues, " - "set to \"auto\" or adjust to the value of \"%" PRIu32 "\"", + uint64_t required_global_mp_size = + (uint64_t)required_mp_size * iconf->nb_rx_queues + iconf->nb_rx_queues - 1; + SCLogError("%s: mempool size is too small; set to \"auto\" or adjust " + "to the value of \"%" PRIu64 "\"", iconf->iface, required_global_mp_size); SCReturnInt(-ERANGE); } diff --git a/src/source-dpdk.c b/src/source-dpdk.c index fd0eec84c6..6a89b4474c 100644 --- a/src/source-dpdk.c +++ b/src/source-dpdk.c @@ -95,7 +95,6 @@ TmEcode NoDPDKSupportExit(ThreadVars *tv, const void *initdata, void **data) #include "util-dpdk-bonding.h" #include -#define BURST_SIZE 32 // interrupt mode constants #define MIN_ZERO_POLL_COUNT 10U #define MIN_ZERO_POLL_COUNT_TO_SLEEP 10U @@ -135,7 +134,7 @@ typedef struct DPDKThreadVars_ { uint16_t port_id; uint16_t queue_id; int32_t port_socket_id; - struct rte_mbuf *received_mbufs[BURST_SIZE]; + struct rte_mbuf *received_mbufs[DPDK_RX_BURST_SIZE]; DPDKWorkerSync *workers_sync; } DPDKThreadVars; @@ -569,7 +568,7 @@ static TmEcode ReceiveDPDKLoop(ThreadVars *tv, void *data, void *slot) DPDKThreadVars *ptv = (DPDKThreadVars *)data; ptv->slot = ((TmSlot *)slot)->slot_next; uint32_t mp_sz = ptv->livedev->dpdk_vars->pkt_mp[ptv->queue_id]->size; - uint16_t burst_size = (uint16_t)MIN(BURST_SIZE, mp_sz); + uint16_t burst_size = (uint16_t)MIN(DPDK_RX_BURST_SIZE, mp_sz); TmEcode ret = ReceiveDPDKLoopInit(tv, ptv); if (ret != TM_ECODE_OK) { diff --git a/src/source-dpdk.h b/src/source-dpdk.h index 4748601de9..f9e13acf47 100644 --- a/src/source-dpdk.h +++ b/src/source-dpdk.h @@ -35,6 +35,8 @@ typedef enum { DPDK_COPY_MODE_NONE, DPDK_COPY_MODE_TAP, DPDK_COPY_MODE_IPS } Dpd #define DPDK_BURST_TX_WAIT_US 1 +#define DPDK_RX_BURST_SIZE 32U + /* DPDK Flags */ // General flags #define DPDK_PROMISC (1 << 0) /**< Promiscuous mode */ diff --git a/suricata.yaml.in b/suricata.yaml.in index bb96a4fa27..512310ca13 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -832,7 +832,7 @@ dpdk: # To approximately calculate required amount of space (in bytes) for interface's mempool: mempool-size * mtu # Make sure you have enough allocated hugepages. - mempool-size: auto # autocalculated based on Rx/Tx descriptors and threads + mempool-size: auto # autocalculated based on Rx/Tx descriptors, in-flight packets and threads mempool-cache-size: auto # autocalculated from the mempool size rx-descriptors: auto # max number of descriptors tx-descriptors: auto # max number of descriptors From d84b715344ec6cd6973d3935db46e573c31d99c3 Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Tue, 22 Sep 2026 20:15:33 +0200 Subject: [PATCH 6/9] qa/live: fail the dpdk test when no packets are captured When Suricata exits during startup it never writes a stats event, so jq returns an empty string and the test aborted the comparison with "unary operator expected". Quote the value and compare it against the expected outcome, so an empty result fails the same way a false one does. --- qa/live/dpdk.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qa/live/dpdk.sh b/qa/live/dpdk.sh index 83c0440b21..94bc495dc6 100755 --- a/qa/live/dpdk.sh +++ b/qa/live/dpdk.sh @@ -42,7 +42,7 @@ sleep 15 # check stats and alerts STATSCHECK=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.capture.packets > 0') -if [ $STATSCHECK = false ]; then +if [ "$STATSCHECK" != true ]; then echo "ERROR no packets captured" RES=1 fi @@ -59,7 +59,7 @@ sleep 15 # check stats and alerts STATSCHECK=$(jq -c 'select(.event_type == "stats")' ./eve.json | tail -n1 | jq '.stats.capture.packets > 0') -if [ $STATSCHECK = false ]; then +if [ "$STATSCHECK" != true ]; then echo "ERROR no packets captured" RES=1 fi From 05dd3ba06173d1d3417b1ffe692301d78dfc2816 Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Tue, 22 Sep 2026 20:15:33 +0200 Subject: [PATCH 7/9] qa/live: let the dpdk IDS test size its own mempool The configured mempool of 511 mbufs is split between the worker queues, which leaves enough mbufs for at most four workers now that a queue also reserves an RX burst. As the test runs with "threads: auto", it refuses to start on any host with more cores than a CI runner. --- qa/live/dpdk/suricata-null-ids.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/live/dpdk/suricata-null-ids.yaml b/qa/live/dpdk/suricata-null-ids.yaml index dac398d7f0..8dc1be81f2 100644 --- a/qa/live/dpdk/suricata-null-ids.yaml +++ b/qa/live/dpdk/suricata-null-ids.yaml @@ -21,7 +21,7 @@ dpdk: interfaces: - interface: net_null0 # PCIe address of the NIC port threads: auto - mempool-size: 511 + mempool-size: auto mempool-cache-size: auto rx-descriptors: 16 tx-descriptors: 16 From 2b01cc493caca87a5b58eaf8b22ca5b4cc51481d Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Mon, 21 Sep 2026 16:30:51 +0200 Subject: [PATCH 8/9] util/ring-buffer: add a bounded FIFO for fixed-size elements Provide reusable single-thread storage for callers that need a bounded FIFO and direct writes into contiguous free space. Existing queues are specialized or do not expose writable spans. Use every configured slot, validate allocation sizes, and support all-or-none batch transfers and explicit commits of direct writes. Stored contents remain owned by the caller. Add nine unit tests covering capacity, wraparound, transfer rejection, write spans and ownership, and document the API in the developer guide. Ticket: 9111 --- src/Makefile.am | 2 + src/runmode-unittests.c | 2 + src/util-ring-buffer.c | 355 ++++++++++++++++++++++++++++++++++++++++ src/util-ring-buffer.h | 53 ++++++ 4 files changed, 412 insertions(+) create mode 100644 src/util-ring-buffer.c create mode 100644 src/util-ring-buffer.h diff --git a/src/Makefile.am b/src/Makefile.am index cea03c08a8..b8e9fca6e6 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -568,6 +568,7 @@ noinst_HEADERS = \ util-radix6-tree.h \ util-random.h \ util-reference-config.h \ + util-ring-buffer.h \ util-rohash.h \ util-rule-vars.h \ util-runmodes.h \ @@ -1129,6 +1130,7 @@ libsuricata_c_a_SOURCES = \ util-radix6-tree.c \ util-random.c \ util-reference-config.c \ + util-ring-buffer.c \ util-rohash.c \ util-rule-vars.c \ util-runmodes.c \ diff --git a/src/runmode-unittests.c b/src/runmode-unittests.c index 3d31c884fa..3ce92b6055 100644 --- a/src/runmode-unittests.c +++ b/src/runmode-unittests.c @@ -87,6 +87,7 @@ #include "util-hash.h" #include "util-hashlist.h" #include "util-pool.h" +#include "util-ring-buffer.h" #include "util-byte.h" #include "util-file.h" #include "util-proto-name.h" @@ -145,6 +146,7 @@ static void RegisterUnittests(void) HashTableRegisterTests(); HashListTableRegisterTests(); PoolRegisterTests(); + SCRingBufferRegisterTests(); ByteRegisterTests(); MpmRegisterTests(); FlowBitRegisterTests(); diff --git a/src/util-ring-buffer.c b/src/util-ring-buffer.c new file mode 100644 index 0000000000..e8da1d9e11 --- /dev/null +++ b/src/util-ring-buffer.c @@ -0,0 +1,355 @@ +/* Copyright (C) 2026 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. + */ + +#include "suricata-common.h" +#include "util-ring-buffer.h" +#include "util-unittest.h" + +// Bit-mask to wrap the index around the ring buffer capacity (power of 2) +#define SCRINGBUFFER_ADVANCE(index, count, capacity) (((index) + (count)) & ((capacity)-1)) + +struct SCRingBuffer_ { + uint8_t *data; + size_t element_size; + uint32_t capacity; + uint32_t head; + uint32_t tail; + uint32_t count; +}; + +SCRingBuffer *SCRingBufferInit(uint32_t capacity, size_t element_size) +{ + if (capacity == 0 || (capacity & (capacity - 1)) != 0 || element_size == 0 || + element_size > SIZE_MAX / capacity) { + return NULL; + } + SCRingBuffer *rb = SCCalloc(1, sizeof(*rb)); + if (rb == NULL) { + return NULL; + } + rb->data = SCMalloc(capacity * element_size); + if (rb->data == NULL) { + SCFree(rb); + return NULL; + } + rb->capacity = capacity; + rb->element_size = element_size; + return rb; +} + +void SCRingBufferFree(SCRingBuffer *rb) +{ + if (rb != NULL) { + SCFree(rb->data); + SCFree(rb); + } +} + +uint32_t SCRingBufferCount(const SCRingBuffer *rb) +{ + return rb == NULL ? 0 : rb->count; +} + +uint32_t SCRingBufferSpace(const SCRingBuffer *rb) +{ + return rb == NULL ? 0 : rb->capacity - rb->count; +} + +bool SCRingBufferEnqueue(SCRingBuffer *rb, const void *items, uint32_t count) +{ + if (rb == NULL || count > (rb->capacity - rb->count) || (count > 0 && items == NULL)) { + return false; + } + if (count == 0) { + return true; + } + uint32_t first_chunk_count = MIN(count, rb->capacity - rb->tail); + size_t first_chunk_size = first_chunk_count * rb->element_size; + memcpy(rb->data + rb->tail * rb->element_size, items, first_chunk_size); + if (first_chunk_count < count) { + memcpy(rb->data, (const uint8_t *)items + first_chunk_size, + (count - first_chunk_count) * rb->element_size); + } + rb->tail = SCRINGBUFFER_ADVANCE(rb->tail, count, rb->capacity); + rb->count += count; + return true; +} + +bool SCRingBufferDequeue(SCRingBuffer *rb, void *items, uint32_t count) +{ + if (rb == NULL || count > rb->count || (count > 0 && items == NULL)) { + return false; + } + if (count == 0) { + return true; + } + uint32_t first_chunk_count = MIN(count, rb->capacity - rb->head); + size_t first_chunk_size = first_chunk_count * rb->element_size; + memcpy(items, rb->data + rb->head * rb->element_size, first_chunk_size); + if (first_chunk_count < count) { + memcpy((uint8_t *)items + first_chunk_size, rb->data, + (count - first_chunk_count) * rb->element_size); + } + rb->head = SCRINGBUFFER_ADVANCE(rb->head, count, rb->capacity); + rb->count -= count; + return true; +} + +void *SCRingBufferEnqueueSpanGet(SCRingBuffer *rb, uint32_t *count) +{ + *count = 0; + if (rb == NULL || rb->count == rb->capacity) { + return NULL; + } + *count = MIN(rb->capacity - rb->count, rb->capacity - rb->tail); + return rb->data + rb->tail * rb->element_size; +} + +bool SCRingBufferEnqueueSpanCommit(SCRingBuffer *rb, uint32_t count) +{ + if (rb == NULL || count > MIN(rb->capacity - rb->count, rb->capacity - rb->tail)) { + return false; + } + rb->tail = SCRINGBUFFER_ADVANCE(rb->tail, count, rb->capacity); + rb->count += count; + return true; +} + +#ifdef UNITTESTS +static int SCRingBufferTestInitialization(void) +{ + FAIL_IF_NOT_NULL(SCRingBufferInit(0, sizeof(uint32_t))); + FAIL_IF_NOT_NULL(SCRingBufferInit(3, sizeof(uint32_t))); + FAIL_IF_NOT_NULL(SCRingBufferInit(4, 0)); + FAIL_IF_NOT_NULL(SCRingBufferInit(2, SIZE_MAX / 2 + 1)); + FAIL_IF(SCRingBufferCount(NULL) != 0); + FAIL_IF(SCRingBufferSpace(NULL) != 0); + SCRingBufferFree(NULL); + + SCRingBuffer *rb = SCRingBufferInit(8, sizeof(uint32_t)); + FAIL_IF_NULL(rb); + FAIL_IF(SCRingBufferCount(rb) != 0); + FAIL_IF(SCRingBufferSpace(rb) != 8); + SCRingBufferFree(rb); + PASS; +} + +static int SCRingBufferTestFullCapacity(void) +{ + struct { + uint8_t bytes[3]; + } input[] = { { { 1, 2, 3 } }, { { 4, 5, 6 } }, { { 7, 8, 9 } }, { { 10, 11, 12 } } }; + uint8_t output[sizeof(input)] = { 0 }; + SCRingBuffer *rb = SCRingBufferInit(4, sizeof(input[0])); + FAIL_IF_NULL(rb); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, input, 4)); + FAIL_IF(SCRingBufferCount(rb) != 4 || SCRingBufferSpace(rb) != 0); + FAIL_IF(SCRingBufferEnqueue(rb, input, 1)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 4)); + FAIL_IF(memcmp(input, output, sizeof(input)) != 0); + FAIL_IF(SCRingBufferCount(rb) != 0 || SCRingBufferSpace(rb) != 4); + SCRingBufferFree(rb); + + rb = SCRingBufferInit(1, sizeof(input[0])); + FAIL_IF_NULL(rb); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, input, 1)); + FAIL_IF(SCRingBufferCount(rb) != 1 || SCRingBufferSpace(rb) != 0); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 1)); + FAIL_IF(memcmp(input, output, sizeof(input[0])) != 0); + SCRingBufferFree(rb); + PASS; +} + +static int SCRingBufferTestWrapFifo(void) +{ + const uint32_t input[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + uint32_t output[8]; + SCRingBuffer *rb = SCRingBufferInit(8, sizeof(input[0])); + FAIL_IF_NULL(rb); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, input, 6)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 5)); + FAIL_IF(memcmp(output, input, 5 * sizeof(input[0])) != 0); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, input + 6, 7)); + FAIL_IF(SCRingBufferCount(rb) != 8 || SCRingBufferSpace(rb) != 0); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 8)); + FAIL_IF(memcmp(output, input + 5, sizeof(output)) != 0); + FAIL_IF(SCRingBufferCount(rb) != 0 || SCRingBufferSpace(rb) != 8); + SCRingBufferFree(rb); + PASS; +} + +static int SCRingBufferTestRejectedTransfers(void) +{ + const uint32_t input[] = { 1, 2, 3, 4, 5 }; + const uint32_t sentinel[] = { 99, 99, 99, 99 }; + uint32_t output[4]; + memcpy(output, sentinel, sizeof(output)); + SCRingBuffer *rb = SCRingBufferInit(4, sizeof(input[0])); + FAIL_IF_NULL(rb); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, input, 3)); + FAIL_IF(SCRingBufferEnqueue(rb, input + 3, 2)); + FAIL_IF(SCRingBufferDequeue(rb, output, 4)); + FAIL_IF(memcmp(output, sentinel, sizeof(output)) != 0); + FAIL_IF(SCRingBufferEnqueue(rb, NULL, 1)); + FAIL_IF(SCRingBufferDequeue(rb, NULL, 1)); + FAIL_IF(SCRingBufferCount(rb) != 3 || SCRingBufferSpace(rb) != 1); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 3)); + FAIL_IF(memcmp(output, input, 3 * sizeof(input[0])) != 0); + SCRingBufferFree(rb); + PASS; +} + +static int SCRingBufferTestZeroLength(void) +{ + uint32_t item = 7; + SCRingBuffer *rb = SCRingBufferInit(2, sizeof(item)); + FAIL_IF_NULL(rb); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, &item, 1)); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, NULL, 0)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, NULL, 0)); + FAIL_IF_NOT(SCRingBufferEnqueueSpanCommit(rb, 0)); + FAIL_IF(SCRingBufferCount(rb) != 1 || SCRingBufferSpace(rb) != 1); + uint32_t output = 0; + FAIL_IF_NOT(SCRingBufferDequeue(rb, &output, 1)); + FAIL_IF(output != item); + SCRingBufferFree(rb); + PASS; +} + +static int SCRingBufferTestWriteSpan(void) +{ + SCRingBuffer *rb = SCRingBufferInit(4, sizeof(uint32_t)); + FAIL_IF_NULL(rb); + uint32_t count = 0; + uint32_t *span = SCRingBufferEnqueueSpanGet(rb, &count); + FAIL_IF_NULL(span); + FAIL_IF(count != 4); + span[0] = 1; + span[1] = 2; + span[2] = 3; + uint32_t output[4] = { 99, 99, 99, 99 }; + FAIL_IF(SCRingBufferDequeue(rb, output, 1)); + FAIL_IF(output[0] != 99); + FAIL_IF_NOT(SCRingBufferEnqueueSpanCommit(rb, 0)); + FAIL_IF(SCRingBufferCount(rb) != 0); + FAIL_IF_NOT(SCRingBufferEnqueueSpanCommit(rb, 3)); + span = SCRingBufferEnqueueSpanGet(rb, &count); + FAIL_IF_NULL(span); + FAIL_IF(count != 1); + span[0] = 4; + FAIL_IF(SCRingBufferEnqueueSpanCommit(rb, 2)); + FAIL_IF(SCRingBufferCount(rb) != 3 || SCRingBufferSpace(rb) != 1); + FAIL_IF_NOT(SCRingBufferEnqueueSpanCommit(rb, 1)); + FAIL_IF_NOT_NULL(SCRingBufferEnqueueSpanGet(rb, &count)); + FAIL_IF(count != 0); + FAIL_IF_NOT(SCRingBufferEnqueueSpanCommit(rb, 0)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 3)); + FAIL_IF(output[0] != 1 || output[1] != 2 || output[2] != 3); + + span = SCRingBufferEnqueueSpanGet(rb, &count); + FAIL_IF_NULL(span); + FAIL_IF(count != 3); + span[0] = 5; + span[1] = 6; + FAIL_IF_NOT(SCRingBufferEnqueueSpanCommit(rb, 2)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 3)); + FAIL_IF(output[0] != 4 || output[1] != 5 || output[2] != 6); + SCRingBufferFree(rb); + PASS; +} + +static int SCRingBufferTestCallerOwnership(void) +{ + uint32_t values[] = { 11, 22 }; + uint32_t *input[] = { &values[0], &values[1] }; + uint32_t *output = NULL; + SCRingBuffer *rb = SCRingBufferInit(2, sizeof(input[0])); + FAIL_IF_NULL(rb); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, input, 2)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, &output, 1)); + FAIL_IF(output != input[0]); + SCRingBufferFree(rb); + FAIL_IF(values[0] != 11 || values[1] != 22); + PASS; +} + +static int SCRingBufferTestSpanBoundary(void) +{ + const uint32_t input[] = { 1, 2, 3 }; + uint32_t output[4]; + SCRingBuffer *rb = SCRingBufferInit(4, sizeof(input[0])); + FAIL_IF_NULL(rb); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, input, 3)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 2)); + uint32_t count = 0; + uint32_t *span = SCRingBufferEnqueueSpanGet(rb, &count); + FAIL_IF_NULL(span); + FAIL_IF(count != 1 || SCRingBufferSpace(rb) != 3); + span[0] = 4; + FAIL_IF(SCRingBufferEnqueueSpanCommit(rb, 2)); + FAIL_IF(SCRingBufferCount(rb) != 1 || SCRingBufferSpace(rb) != 3); + FAIL_IF_NOT(SCRingBufferEnqueueSpanCommit(rb, 1)); + span = SCRingBufferEnqueueSpanGet(rb, &count); + FAIL_IF_NULL(span); + FAIL_IF(count != 2); + span[0] = 5; + span[1] = 6; + FAIL_IF_NOT(SCRingBufferEnqueueSpanCommit(rb, 2)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 4)); + FAIL_IF(output[0] != 3 || output[1] != 4 || output[2] != 5 || output[3] != 6); + SCRingBufferFree(rb); + PASS; +} + +static int SCRingBufferTestRepeatedWrap(void) +{ + SCRingBuffer *rb = SCRingBufferInit(8, sizeof(uint32_t)); + FAIL_IF_NULL(rb); + for (uint32_t cycle = 0; cycle < 32; cycle++) { + uint32_t input[7]; + uint32_t output[4]; + for (uint32_t i = 0; i < 7; i++) { + input[i] = cycle * 7 + i; + } + FAIL_IF_NOT(SCRingBufferEnqueue(rb, input, 4)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 3)); + FAIL_IF(memcmp(output, input, 3 * sizeof(input[0])) != 0); + FAIL_IF(SCRingBufferCount(rb) != 1 || SCRingBufferSpace(rb) != 7); + FAIL_IF_NOT(SCRingBufferEnqueue(rb, input + 4, 3)); + FAIL_IF_NOT(SCRingBufferDequeue(rb, output, 4)); + FAIL_IF(memcmp(output, input + 3, sizeof(output)) != 0); + FAIL_IF(SCRingBufferCount(rb) != 0 || SCRingBufferSpace(rb) != 8); + } + SCRingBufferFree(rb); + PASS; +} +#endif + +void SCRingBufferRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("SCRingBufferTestInitialization", SCRingBufferTestInitialization); + UtRegisterTest("SCRingBufferTestFullCapacity", SCRingBufferTestFullCapacity); + UtRegisterTest("SCRingBufferTestWrapFifo", SCRingBufferTestWrapFifo); + UtRegisterTest("SCRingBufferTestRejectedTransfers", SCRingBufferTestRejectedTransfers); + UtRegisterTest("SCRingBufferTestZeroLength", SCRingBufferTestZeroLength); + UtRegisterTest("SCRingBufferTestWriteSpan", SCRingBufferTestWriteSpan); + UtRegisterTest("SCRingBufferTestCallerOwnership", SCRingBufferTestCallerOwnership); + UtRegisterTest("SCRingBufferTestSpanBoundary", SCRingBufferTestSpanBoundary); + UtRegisterTest("SCRingBufferTestRepeatedWrap", SCRingBufferTestRepeatedWrap); +#endif +} diff --git a/src/util-ring-buffer.h b/src/util-ring-buffer.h new file mode 100644 index 0000000000..c9d279bdbf --- /dev/null +++ b/src/util-ring-buffer.h @@ -0,0 +1,53 @@ +/* Copyright (C) 2026 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. + */ + +#ifndef SURICATA_UTIL_RING_BUFFER_H +#define SURICATA_UTIL_RING_BUFFER_H + +#include +#include +#include + +/** Bounded FIFO of fixed-size elements, used by one thread without synchronization. + * Element contents remain owned by the caller; freeing the FIFO only frees storage. */ +typedef struct SCRingBuffer_ SCRingBuffer; + +/** Capacity must be a nonzero power of two and element_size must be nonzero. + * Every slot is usable. Returns NULL for invalid sizes or allocation failure. */ +SCRingBuffer *SCRingBufferInit(uint32_t capacity, size_t element_size); +void SCRingBufferFree(SCRingBuffer *rb); +uint32_t SCRingBufferCount(const SCRingBuffer *rb); +uint32_t SCRingBufferSpace(const SCRingBuffer *rb); + +/** Transfer all count elements or return false without changing state or output. + * Zero count succeeds for a valid buffer, including with NULL items. Nonzero + * transfers require non-NULL items that do not overlap the FIFO's storage. */ +bool SCRingBufferEnqueue(SCRingBuffer *rb, const void *items, uint32_t count); +bool SCRingBufferDequeue(SCRingBuffer *rb, void *items, uint32_t count); + +/** Return writable contiguous elements and their count, or NULL/count=0 if full + * or rb is NULL. count must be non-NULL. No storage is reserved, the pointer is + * valid until the next state-changing enqueue, dequeue, or commit. */ +void *SCRingBufferEnqueueSpanGet(SCRingBuffer *rb, uint32_t *count); + +/** Publish elements filled through EnqueueSpanGet. Reject counts beyond the current + * writable contiguous span without changing state. Zero count changes nothing. */ +bool SCRingBufferEnqueueSpanCommit(SCRingBuffer *rb, uint32_t count); + +void SCRingBufferRegisterTests(void); + +#endif /* SURICATA_UTIL_RING_BUFFER_H */ From 3be820bfbf032c43f4358eafac8648ad0f9b9b6f Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Tue, 22 Sep 2026 15:33:02 +0200 Subject: [PATCH 9/9] dpdk: add per-worker RX backlog Add an optional bounded FIFO owned by each RX worker. Short bursts process directly, while a full burst triggers elastic draining into an exact-capacity single-producer/single-consumer zero-copy ring, followed by a small FIFO processing quantum. Handle short wraparound spans through the receive array. Ticket: 9111 --- doc/userguide/capture-hardware/dpdk.rst | 56 +++- doc/userguide/configuration/suricata-yaml.rst | 7 +- etc/schema.json | 33 +++ src/runmode-dpdk.c | 44 ++- src/runmode-dpdk.h | 1 + src/source-dpdk.c | 253 ++++++++++++++++-- src/source-dpdk.h | 6 +- suricata.yaml.in | 6 +- 8 files changed, 378 insertions(+), 28 deletions(-) diff --git a/doc/userguide/capture-hardware/dpdk.rst b/doc/userguide/capture-hardware/dpdk.rst index f3971218dd..4b7d00865f 100644 --- a/doc/userguide/capture-hardware/dpdk.rst +++ b/doc/userguide/capture-hardware/dpdk.rst @@ -204,7 +204,8 @@ IDS (none) mode uses no TX descriptors and does not create any TX queues by default. IPS and TAP mode uses the same number of TX descriptors as RX descriptors. The size of each queue's mempool and its cache is then derived from the count -of descriptors and an allowance for in-flight packets. +of descriptors, the configured RX backlog, and an allowance for in-flight +packets. Rx (and Tx) descriptors are set to the highest possible value to allow more buffer room when traffic spikes occur. However, it requires more memory. @@ -213,6 +214,59 @@ Individual properties can still be set manually if needed. .. note:: Mellanox ConnectX-4 NICs may not support auto-configuration of ``RX /TX descriptors``. Instead it can be set to a fixed value (e.g. 16384). +.. _dpdk-rx-backlog: + +Per-worker RX backlog +--------------------- + +The optional ``rx-backlog-size`` interface setting allows a worker to drain +its NIC RX queue into a software first-in-first-out queue before processing +the packets. This helps absorb traffic bursts while the worker catches up +with packet processing. + +.. code-block:: yaml + + dpdk: + interfaces: + - interface: 0000:05:00.1 + rx-backlog-size: 65536 + +The value is the number of packets (mbufs) **per RX queue / worker**. Zero +disables the backlog and is the default when the setting is absent from both +the interface and the default interface configuration. A nonzero value +must be a power of two of at least 32. + +.. note:: TAP and IPS can experience extra forwarding latency in exchange for + fewer packet drops during bursts. + +Packets waiting in the backlog receive a timestamp when the worker starts +processing their batch. Their timestamps therefore include time spent in the +backlog, which can affect correlation with other capture sources during bursts. + +Choosing the backlog size +~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Start with a large ``rx-backlog-size`` that fits the sensor's memory budget. +2. Monitor the backlog counters during representative traffic, including the + busiest periods and expected bursts. Use ``capture.dpdk.backlog.max`` to + find the highest number of packets queued by any worker since startup. + A quiet period alone is not sufficient to size the backlog. +3. If the maximum stays well below ``rx-backlog-size``, you can reduce the size + to save memory. Choose a power of two above the observed peak and leave room + for larger bursts. For example, a peak of 40000 packets could justify + reducing a backlog of 131072 to 65536. Restart Suricata to apply the change + and potentially repeat the measurement. +4. If the maximum approaches the configured size or ``capacity_hits`` + increases, check whether the backlog empties between bursts by observing + the ``capture.dpdk.backlog.current`` counter. If it does, a larger backlog + may help absorb those bursts. Otherwise, a high aggregate ``current`` value + across successive samples indicates that the workers may not be keeping up + with their traffic. + +See ``stats.capture.dpdk.backlog`` in the +:doc:`EVE Index <../appendix/eve-index>` for each counter's interpretation +and recommended action. + .. _dpdk-link-state-change-timeout: Link State Change timeout diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index 16764f163d..c53179960e 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -2490,7 +2490,12 @@ manually (to e.g. ``mempool-size: 65536``), the value is divided by the number o worker cores of the interface (on 4 worker threads, each worker is assigned with a mempool containing 16383 packet objects). Both auto sizing and manual minimum validation include each worker's RX and -TX descriptors and a 32-mbuf in-flight margin. +TX descriptors, ``rx-backlog-size`` mbufs, and a 32-mbuf in-flight margin. +The optional :ref:`dpdk-rx-backlog` can help to overcome short-term traffic +bursts. The backlog is configured per worker/RX queue and requires an integer +that is a power of two, with a minimum value of 32. +By default, the backlog is disabled and can be disabled when absent or set to +zero. A good value to start with could be, e.g., ``131072``. Memory (in bytes) for interface's memory pools is calculated as: ``mempool-size`` * ``mtu``. The sum of memory pool requirements divided by the size of one hugepage results diff --git a/etc/schema.json b/etc/schema.json index d7c20bc546..30b2dead27 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -6946,6 +6946,39 @@ "description": "Statistics for the DPDK capture module", "additionalProperties": false, "properties": { + "backlog": { + "type": "object", + "description": + "RX backlog statistics, available when ``rx-backlog-size`` YAML setting is nonzero. In totals, ``max`` is the highest value from any single worker; other backlog counters are summed across workers. The ``enqueued``, ``capacity_hits``, and ``drain_budget_hits`` counters are cumulative since startup.", + "additionalProperties": false, + "properties": { + "capacity_hits": { + "type": "integer", + "description": + "Number of episodes when a backlog could not receive another batch. Increases indicate that available space limited receiving: consider a larger backlog for short bursts, or address processing capacity if the backlog stays high. A continuously near-full backlog can count as one episode, so also monitor ``current``." + }, + "current": { + "type": "integer", + "description": + "Number of packets waiting for processing at the last sample. A worker returning to zero between bursts indicates that it is catching up. A value that stays near its configured capacity signals a sustained overload." + }, + "drain_budget_hits": { + "type": "integer", + "description": + "Number of times a worker received the full drain allowance into the backlog. Increases indicate bursty traffic as the NIC received many packets inbetween the drain periods." + }, + "enqueued": { + "type": "integer", + "description": + "Total packets placed in the backlog. An increase shows that the backlog is being used." + }, + "max": { + "type": "integer", + "description": + "Highest occupancy reached by any single worker since startup. Compare it directly with that worker's ``rx-backlog-size`` YAML option to assess spare capacity." + } + } + }, "ierrors": { "type": "integer", "description": diff --git a/src/runmode-dpdk.c b/src/runmode-dpdk.c index b34c5947fc..6f33891ea8 100644 --- a/src/runmode-dpdk.c +++ b/src/runmode-dpdk.c @@ -111,6 +111,7 @@ static void DPDKDerefConfig(void *conf); #define DPDK_CONFIG_DEFAULT_THREADS "auto" #define DPDK_CONFIG_DEFAULT_INTERRUPT_MODE false +#define DPDK_CONFIG_DEFAULT_RX_BACKLOG_SIZE "0" #define DPDK_CONFIG_DEFAULT_MEMPOOL_SIZE "auto" #define DPDK_CONFIG_DEFAULT_MEMPOOL_CACHE_SIZE "auto" #define DPDK_CONFIG_DEFAULT_RX_DESCRIPTORS "auto" @@ -137,6 +138,7 @@ DPDKIfaceConfigAttributes dpdk_yaml = { .vlan_strip_offload = "vlan-strip-offload", .rss_hf = "rss-hash-functions", .linkup_timeout = "linkup-timeout", + .rx_backlog_size = "rx-backlog-size", .mempool_size = "mempool-size", .mempool_cache_size = "mempool-cache-size", .rx_descriptors = "rx-descriptors", @@ -530,10 +532,36 @@ static int ConfigSetTxQueues( SCReturnInt(0); } +static int ConfigSetRxBacklogSize(DPDKIfaceConfig *iconf, const char *entry_str) +{ + SCEnter(); + uint32_t size; + if (StringParseUint32(&size, 10, 0, entry_str) < 0) { + SCLogError("%s: rx-backlog-size requires a non-negative integer - \"%s\"", iconf->iface, + entry_str); + SCReturnInt(-EINVAL); + } + + if (size != 0 && (size < DPDK_RX_BURST_SIZE || !rte_is_power_of_2(size))) { + SCLogError("%s: rx-backlog-size must be zero or a power of two of at least %u", + iconf->iface, DPDK_RX_BURST_SIZE); + SCReturnInt(-ERANGE); + } + + if ((uint64_t)size * sizeof(struct rte_mbuf *) > SIZE_MAX) { + SCLogError("%s: rx-backlog-size exceeds the supported allocation size", iconf->iface); + SCReturnInt(-ERANGE); + } + + iconf->rx_backlog_size = size; + SCReturnInt(0); +} + static int MempoolSizeCalculate( const DPDKIfaceConfig *iconf, const struct rte_eth_dev_info *dev_info, uint32_t *size) { - const uint64_t required = (uint64_t)iconf->nb_rx_desc + iconf->nb_tx_desc + DPDK_RX_BURST_SIZE; + const uint64_t required = (uint64_t)iconf->nb_rx_desc + iconf->nb_tx_desc + + iconf->rx_backlog_size + DPDK_RX_BURST_SIZE; /* Add one so that rounding to 2^q - 1 still covers a power-of-two requirement. */ const uint64_t next_p2 = rte_align64pow2(required + 1); if (next_p2 > UINT32_MAX) { @@ -979,6 +1007,14 @@ static int ConfigLoad(DPDKIfaceConfig *iconf, const char *iface) SCReturnInt(retval); } + retval = SCConfGetChildValueWithDefault( + if_root, if_default, dpdk_yaml.rx_backlog_size, &entry_str) != 1 + ? ConfigSetRxBacklogSize(iconf, DPDK_CONFIG_DEFAULT_RX_BACKLOG_SIZE) + : ConfigSetRxBacklogSize(iconf, entry_str); + if (retval < 0) { + SCReturnInt(retval); + } + retval = SCConfGetChildValueWithDefault( if_root, if_default, dpdk_yaml.mempool_size, &entry_str) != 1 ? ConfigSetMempoolSize(iconf, DPDK_CONFIG_DEFAULT_MEMPOOL_SIZE, &dev_info) @@ -1472,6 +1508,12 @@ static int DeviceConfigureQueues(DPDKIfaceConfig *iconf, const struct rte_eth_de : iconf->mempool_cache_size; SCLogInfo("%s: creating %u packet mempools of size %u, cache size %u, mbuf size %u", iconf->iface, iconf->nb_rx_queues, iconf->queue_mempool_size, q_mp_cache_sz, mbuf_size); + if (iconf->rx_backlog_size != 0) { + SCLogInfo("%s: RX backlog size %" PRIu32 " per queue, RX burst %u, drain budget %u, " + "processing quantum %u", + iconf->iface, iconf->rx_backlog_size, DPDK_RX_BURST_SIZE, DPDK_RX_DRAIN_BUDGET, + DPDK_BACKLOG_PROCESS_QUANTUM); + } for (int i = 0; i < iconf->nb_rx_queues; i++) { char mempool_name[64]; snprintf(mempool_name, sizeof(mempool_name), "mp_%d_%.20s", i, iconf->iface); diff --git a/src/runmode-dpdk.h b/src/runmode-dpdk.h index b91838915c..9c397bda57 100644 --- a/src/runmode-dpdk.h +++ b/src/runmode-dpdk.h @@ -34,6 +34,7 @@ typedef struct DPDKIfaceConfigAttributes_ { const char *vlan_strip_offload; const char *rss_hf; const char *linkup_timeout; + const char *rx_backlog_size; const char *mempool_size; const char *mempool_cache_size; const char *rx_descriptors; diff --git a/src/source-dpdk.c b/src/source-dpdk.c index 6a89b4474c..5d23550e55 100644 --- a/src/source-dpdk.c +++ b/src/source-dpdk.c @@ -93,6 +93,7 @@ TmEcode NoDPDKSupportExit(ThreadVars *tv, const void *initdata, void **data) #include "util-dpdk-ixgbe.h" #include "util-dpdk-mlx5.h" #include "util-dpdk-bonding.h" +#include "util-ring-buffer.h" #include // interrupt mode constants @@ -136,6 +137,13 @@ typedef struct DPDKThreadVars_ { int32_t port_socket_id; struct rte_mbuf *received_mbufs[DPDK_RX_BURST_SIZE]; DPDKWorkerSync *workers_sync; + SCRingBuffer *rx_backlog; + bool backlog_capacity_limited; + StatsCounterId capture_dpdk_backlog_current; + StatsCounterMaxId capture_dpdk_backlog_max; + StatsCounterId capture_dpdk_backlog_enqueued; + StatsCounterId capture_dpdk_backlog_capacity_hits; + StatsCounterId capture_dpdk_backlog_drain_budget_hits; } DPDKThreadVars; static TmEcode ReceiveDPDKThreadInit(ThreadVars *, const void *, void **); @@ -272,8 +280,38 @@ void TmModuleDecodeDPDKRegister(void) tmm_modules[TMM_DECODEDPDK].flags = TM_FLAG_DECODE_TM; } +static inline void DPDKDumpBacklogCounters(DPDKThreadVars *ptv) +{ + if (ptv->rx_backlog == NULL) { + return; + } + + StatsCounterSetI64( + &ptv->tv->stats, ptv->capture_dpdk_backlog_current, SCRingBufferCount(ptv->rx_backlog)); +} + +static void DPDKBacklogClear(DPDKThreadVars *ptv) +{ + if (ptv->rx_backlog == NULL) { + return; + } + + struct rte_mbuf *mbuf; + while (SCRingBufferDequeue(ptv->rx_backlog, &mbuf, 1)) { + rte_pktmbuf_free(mbuf); + } +} + +static void DPDKBacklogFree(DPDKThreadVars *ptv) +{ + DPDKBacklogClear(ptv); + SCRingBufferFree(ptv->rx_backlog); + ptv->rx_backlog = NULL; +} + static inline void DPDKDumpCounters(DPDKThreadVars *ptv) { + DPDKDumpBacklogCounters(ptv); /* Some NICs (e.g. Intel) do not support queue statistics and the drops can be fetched only on * the port level. Therefore setting it to the first worker to have at least continuous update * on the dropped packets. */ @@ -476,6 +514,159 @@ static inline void DPDKSegmentedMbufWarning(struct rte_mbuf *mbuf) } } +static inline TmEcode DPDKProcessMbuf( + DPDKThreadVars *ptv, struct rte_mbuf *mbuf, SCTime_t timestamp) +{ + Packet *p = PacketInitFromMbuf(ptv, mbuf, timestamp); + if (p == NULL) { + rte_pktmbuf_free(mbuf); + return TM_ECODE_OK; + } + DPDKSegmentedMbufWarning(mbuf); + PacketSetData(p, rte_pktmbuf_mtod(mbuf, uint8_t *), rte_pktmbuf_pkt_len(mbuf)); + /* The processing pipeline releases the Packet on both success and failure. */ + return TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p); +} + +static inline TmEcode DPDKProcessMbufArray(DPDKThreadVars *ptv, uint16_t nb_rx) +{ + SCTime_t timestamp = TimeGet(); + for (uint16_t i = 0; i < nb_rx; i++) { + if (DPDKProcessMbuf(ptv, ptv->received_mbufs[i], timestamp) != TM_ECODE_OK) { + DPDKFreeMbufArray(ptv->received_mbufs, nb_rx, i + 1); + return TM_ECODE_FAILED; + } + } + return TM_ECODE_OK; +} + +static inline void DPDKBacklogUpdateCapacity(DPDKThreadVars *ptv) +{ + uint32_t free_slots = SCRingBufferSpace(ptv->rx_backlog); + if (free_slots >= DPDK_RX_BURST_SIZE) { + // ends episode of the full backlog + ptv->backlog_capacity_limited = false; + } else if (free_slots < DPDK_RX_BURST_ALIGNMENT && !ptv->backlog_capacity_limited) { + // starts episode of the full backlog + StatsCounterIncr(&ptv->tv->stats, ptv->capture_dpdk_backlog_capacity_hits); + ptv->backlog_capacity_limited = true; + } +} + +static inline void DPDKBacklogRecordBurst(DPDKThreadVars *ptv, uint16_t count) +{ + StatsCounterAddI64(&ptv->tv->stats, ptv->capture_dpdk_backlog_enqueued, count); + StatsCounterMaxUpdateI64( + &ptv->tv->stats, ptv->capture_dpdk_backlog_max, SCRingBufferCount(ptv->rx_backlog)); + DPDKDumpBacklogCounters(ptv); + DPDKBacklogUpdateCapacity(ptv); +} + +static inline void DPDKBacklogEnqueueBurst(DPDKThreadVars *ptv, uint16_t count) +{ + bool written = SCRingBufferEnqueue(ptv->rx_backlog, ptv->received_mbufs, count); + BUG_ON(!written); + DPDKBacklogRecordBurst(ptv, count); +} + +/** + * \brief Receive into backlog storage, using the receive array only for an unaligned wrap. + * \param ptv The DPDK thread variables. + * \param[in,out] request Pointer to the number of requested packets to receive. + * \return The number of packets actually received into the backlog. + */ +static inline uint16_t DPDKBacklogReceive(DPDKThreadVars *ptv, uint16_t *request) +{ + uint32_t contiguous; + struct rte_mbuf **rx = SCRingBufferEnqueueSpanGet(ptv->rx_backlog, &contiguous); + uint16_t nb_rx = 0; + if (contiguous >= DPDK_RX_BURST_ALIGNMENT) { + // Bit-mask to find closest lower multiple of DPDK_RX_BURST_ALIGNMENT + *request = MIN(*request, contiguous) & ~(DPDK_RX_BURST_ALIGNMENT - 1); + nb_rx = rte_eth_rx_burst(ptv->port_id, ptv->queue_id, rx, *request); + if (nb_rx > 0) { + bool committed = SCRingBufferEnqueueSpanCommit(ptv->rx_backlog, nb_rx); + BUG_ON(!committed); + DPDKBacklogRecordBurst(ptv, nb_rx); + } + } else { + // Keep RX requests aligned when only 1..7 slots remain before wrap. + *request = DPDK_RX_BURST_ALIGNMENT; + rx = ptv->received_mbufs; + nb_rx = rte_eth_rx_burst(ptv->port_id, ptv->queue_id, rx, *request); + if (nb_rx > 0) { + DPDKBacklogEnqueueBurst(ptv, nb_rx); + } + } + ptv->pkts += nb_rx; + return nb_rx; +} + +static inline void DPDKBacklogDrain(DPDKThreadVars *ptv) +{ + uint16_t drained = 0; + while (drained < DPDK_RX_DRAIN_BUDGET) { + uint32_t free_slots = SCRingBufferSpace(ptv->rx_backlog); + if (free_slots < DPDK_RX_BURST_ALIGNMENT) { + break; + } + uint16_t available = MIN(free_slots, DPDK_RX_DRAIN_BUDGET - drained); + if (available < DPDK_RX_BURST_ALIGNMENT) { + break; + } + uint16_t nb_rx = DPDKBacklogReceive(ptv, &available); + drained += nb_rx; + if (nb_rx < available) { + break; + } + } + if (drained == DPDK_RX_DRAIN_BUDGET) { + StatsCounterIncr(&ptv->tv->stats, ptv->capture_dpdk_backlog_drain_budget_hits); + } +} + +static inline TmEcode DPDKBacklogProcess(DPDKThreadVars *ptv) +{ + uint16_t count = MIN(SCRingBufferCount(ptv->rx_backlog), DPDK_BACKLOG_PROCESS_QUANTUM); + if (unlikely(!SCRingBufferDequeue(ptv->rx_backlog, ptv->received_mbufs, count))) { + return TM_ECODE_FAILED; + } + TmEcode ret = DPDKProcessMbufArray(ptv, count); + DPDKBacklogUpdateCapacity(ptv); + DPDKDumpBacklogCounters(ptv); + return ret; +} + +static inline TmEcode DPDKBacklogPoll(DPDKThreadVars *ptv) +{ + DPDKBacklogDrain(ptv); + return DPDKBacklogProcess(ptv); +} + +static TmEcode DPDKBacklogInit(DPDKThreadVars *ptv, uint32_t size) +{ + if (size == 0) { + return TM_ECODE_OK; + } + + ptv->rx_backlog = SCRingBufferInit(size, sizeof(struct rte_mbuf *)); + if (ptv->rx_backlog == NULL) { + SCLogError("%s-Q%u: failed to allocate RX backlog", ptv->livedev->dev, ptv->queue_id); + return TM_ECODE_FAILED; + } + ptv->capture_dpdk_backlog_current = + StatsRegisterCounter("capture.dpdk.backlog.current", &ptv->tv->stats); + ptv->capture_dpdk_backlog_max = + StatsRegisterMaxCounter("capture.dpdk.backlog.max", &ptv->tv->stats); + ptv->capture_dpdk_backlog_enqueued = + StatsRegisterCounter("capture.dpdk.backlog.enqueued", &ptv->tv->stats); + ptv->capture_dpdk_backlog_capacity_hits = + StatsRegisterCounter("capture.dpdk.backlog.capacity_hits", &ptv->tv->stats); + ptv->capture_dpdk_backlog_drain_budget_hits = + StatsRegisterCounter("capture.dpdk.backlog.drain_budget_hits", &ptv->tv->stats); + return TM_ECODE_OK; +} + static void PrintDPDKPortXstats(uint16_t port_id, const char *port_name) { int ret = rte_eth_xstats_get(port_id, NULL, 0); @@ -532,6 +723,7 @@ static void HandleShutdown(DPDKThreadVars *ptv) // Dump counters while device is still running - some drivers (e.g. BNXT) fail // to report stats after the device is stopped DPDKDumpCounters(ptv); + StatsSyncCounters(&ptv->tv->stats); if (ptv->queue_id == 0) { PrintDPDKPortXstats(ptv->port_id, ptv->livedev->dev); rte_delay_us(20); // wait for all threads to get out of the sync loop @@ -580,28 +772,34 @@ static TmEcode ReceiveDPDKLoop(ThreadVars *tv, void *data, void *slot) break; } - uint16_t nb_rx = - rte_eth_rx_burst(ptv->port_id, ptv->queue_id, ptv->received_mbufs, burst_size); - SCTime_t timestamp = { 0 }; - if (nb_rx > 0) { - timestamp = TimeGet(); - } - if (RXPacketCountHeuristic(tv, ptv, nb_rx)) { - continue; - } - - ptv->pkts += (uint64_t)nb_rx; - for (uint16_t i = 0; i < nb_rx; i++) { - Packet *p = PacketInitFromMbuf(ptv, ptv->received_mbufs[i], timestamp); - if (p == NULL) { - rte_pktmbuf_free(ptv->received_mbufs[i]); - continue; + if (ptv->rx_backlog == NULL) { + uint16_t nb_rx = + rte_eth_rx_burst(ptv->port_id, ptv->queue_id, ptv->received_mbufs, burst_size); + if (RXPacketCountHeuristic(tv, ptv, nb_rx) == false) { + ptv->pkts += nb_rx; + if (nb_rx > 0 && DPDKProcessMbufArray(ptv, nb_rx) != TM_ECODE_OK) { + SCReturnInt(TM_ECODE_FAILED); + } } - DPDKSegmentedMbufWarning(ptv->received_mbufs[i]); - PacketSetData(p, rte_pktmbuf_mtod(p->dpdk_v.mbuf, uint8_t *), - rte_pktmbuf_pkt_len(p->dpdk_v.mbuf)); - if (TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p) != TM_ECODE_OK) { - DPDKFreeMbufArray(ptv->received_mbufs, nb_rx, i + 1); + } else { + if (SCRingBufferCount(ptv->rx_backlog) == 0) { + uint16_t nb_rx = rte_eth_rx_burst( + ptv->port_id, ptv->queue_id, ptv->received_mbufs, burst_size); + if (RXPacketCountHeuristic(tv, ptv, nb_rx) == false) { + ptv->pkts += nb_rx; + if (nb_rx < burst_size) { + if (nb_rx > 0 && DPDKProcessMbufArray(ptv, nb_rx) != TM_ECODE_OK) { + SCReturnInt(TM_ECODE_FAILED); + } + } else { + DPDKBacklogEnqueueBurst(ptv, nb_rx); + } + } + } + if (SCRingBufferCount(ptv->rx_backlog) > 0 && DPDKBacklogPoll(ptv) != TM_ECODE_OK) { + DPDKBacklogClear(ptv); + DPDKDumpBacklogCounters(ptv); + StatsSyncCounters(&tv->stats); SCReturnInt(TM_ECODE_FAILED); } } @@ -677,6 +875,10 @@ static TmEcode ReceiveDPDKThreadInit(ThreadVars *tv, const void *initdata, void uint16_t queue_id = SC_ATOMIC_ADD(dpdk_config->queue_id, 1); ptv->queue_id = queue_id; + if (DPDKBacklogInit(ptv, dpdk_config->rx_backlog_size) != TM_ECODE_OK) { + goto fail; + } + // the last thread starts the device if (queue_id == dpdk_config->threads - 1) { retval = rte_eth_dev_start(ptv->port_id); @@ -748,10 +950,13 @@ static TmEcode ReceiveDPDKThreadInit(ThreadVars *tv, const void *initdata, void SCReturnInt(TM_ECODE_OK); fail: - if (dpdk_config != NULL) + if (dpdk_config != NULL) { dpdk_config->DerefFunc(dpdk_config); - if (ptv != NULL) + } + if (ptv != NULL) { + DPDKBacklogFree(ptv); SCFree(ptv); + } SCReturnInt(TM_ECODE_FAILED); } @@ -765,6 +970,8 @@ static TmEcode ReceiveDPDKThreadDeinit(ThreadVars *tv, void *data) SCEnter(); DPDKThreadVars *ptv = (DPDKThreadVars *)data; + DPDKBacklogFree(ptv); + if (ptv->queue_id == 0) { struct rte_eth_dev_info dev_info; int retval = rte_eth_dev_info_get(ptv->port_id, &dev_info); diff --git a/src/source-dpdk.h b/src/source-dpdk.h index f9e13acf47..4c7d325f8b 100644 --- a/src/source-dpdk.h +++ b/src/source-dpdk.h @@ -35,7 +35,10 @@ typedef enum { DPDK_COPY_MODE_NONE, DPDK_COPY_MODE_TAP, DPDK_COPY_MODE_IPS } Dpd #define DPDK_BURST_TX_WAIT_US 1 -#define DPDK_RX_BURST_SIZE 32U +#define DPDK_RX_BURST_SIZE 32U +#define DPDK_RX_BURST_ALIGNMENT 8U // vectorized PMDs require power of 2, 4+ +#define DPDK_RX_DRAIN_BUDGET 1024U +#define DPDK_BACKLOG_PROCESS_QUANTUM 8U /* DPDK Flags */ // General flags @@ -73,6 +76,7 @@ typedef struct DPDKIfaceConfig_ { uint16_t nb_rx_desc; uint16_t nb_tx_queues; uint16_t nb_tx_desc; + uint32_t rx_backlog_size; uint32_t queue_mempool_size; uint32_t mempool_cache_size; bool mempool_cache_size_auto; // auto cache size based on mempool size diff --git a/suricata.yaml.in b/suricata.yaml.in index 512310ca13..fc604fea29 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -832,10 +832,13 @@ dpdk: # To approximately calculate required amount of space (in bytes) for interface's mempool: mempool-size * mtu # Make sure you have enough allocated hugepages. - mempool-size: auto # autocalculated based on Rx/Tx descriptors, in-flight packets and threads + mempool-size: auto # autocalculated based on Rx/Tx descriptors, backlog, in-flight packets and threads mempool-cache-size: auto # autocalculated from the mempool size rx-descriptors: auto # max number of descriptors tx-descriptors: auto # max number of descriptors + # Backlog helps workers to overcome heavy bursts of traffic (a.k.a. ring-size from af-packet) + # Backlog must be a power of two of at least 32, zero disables the backlog. + # rx-backlog-size: 0 # # IPS mode for Suricata works in 3 modes - none, tap, ips # - none: IDS mode only - disables IPS functionality (does not further forward packets) @@ -854,6 +857,7 @@ dpdk: vlan-strip-offload: false rss-hash-functions: auto linkup-timeout: 0 + rx-backlog-size: 0 mempool-size: auto mempool-cache-size: auto rx-descriptors: auto