pull/13950/merge
Adam Kiripolsky 3 days ago committed by GitHub
commit 1129096b74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -239,3 +239,71 @@ Encapsulation stripping
Suricata supports stripping the hardware-offloaded encapsulation stripping on
the supported NICs. Currently, VLAN encapsulation stripping is supported.
VLAN encapsulation stripping can be enabled with `vlan-strip-offload`.
Drop filter
-----------------------------
The drop filter can improve Suricata's performance by filtering
user-predefined traffic patterns directly in the NIC. The user can
specify unwanted traffic patterns before starting Suricata. The specified
traffic is not going to be inspected by Suricata and will be ignored for the whole run of the program.
On some PMDs, the statistics of matched rules are gathered and stored in eve.json.
The syntax for the drop filter in Suricata is similar to the dpdk-testpmd application
rule syntax, although only the "pattern" section is applicable in Suricata.
The user can define multiple rules, either to match a specific flow
or a broad section of incoming network traffic (e.g., using ip and port masks, matching on specific protocols ...).
Patterns currently supported by this feature are listed in
"src/util-dpdk-rte-flow-pattern.c" in "enum index next_item[]"
and their corresponding attributes in "enum index item_<pattern>[]".
.. literalinclude:: ../../../src/util-dpdk-rte-flow-pattern.c
:language: c
:start-at: /* --- start pattern enum --- */
:end-at: /* --- end pattern enum --- */
This feature is supported and tested only on NICs with mlx5, ice, and i40e drivers.
Some of the drivers also support collecting statistics about dropped traffic
(visible in dpdk.rte_flow_filtered in eve.json).
The level of functionality varies between these cards, as specified below:
* ice:
The driver does not support broad (wildcard) patterns; some pattern item has to have
specification, e.g., ``pattern eth / ipv4 / end`` raises an error but
``pattern eth / ipv4 src is x / end`` or ``pattern eth / ipv4 / tcp src is x`` works fine.
It also supports gathering statistics of the filtered packets, but only
when none of the rules use wildcard patterns (e.g., mask can not be used).
* i40e:
The driver does not support different item sets on the same pattern item type,
e.g., if the first rule is in the form ``pattern eth / ipv4 src is x / end``,
then any other rule containing an ipv4 pattern type must exclusively use the src attribute.
Statistics of the filtered packets are not supported.
* mlx5:
The driver is the most versatile PMD, supporting a wide range of patterns.
It also supports gathering statistics of the filtered packets without any other constraints.
The configuration for the drop filter can be found and modified in the
DPDK section of suricata.yaml file.
Below is a sample configuration that demonstrates how to filter a specific flow and a range of flows:
::
...
dpdk:
eal-params:
proc-type: primary
interfaces:
- interface: 0000:3b:00.0
drop-filter:
- rule: "pattern eth / ipv4 src is 192.11.120.50 / tcp / end"
- rule: "pattern eth / ipv4 src is 170.22.40.0 src mask 255.255.255.0 / tcp / end"

@ -508,6 +508,8 @@ noinst_HEADERS = \
util-dpdk-mlx5.h \
util-dpdk-rss.h \
util-dpdk.h \
util-dpdk-rte-flow.h \
util-dpdk-rte-flow-pattern.h \
util-ebpf.h \
util-enum.h \
util-error.h \
@ -1090,6 +1092,8 @@ libsuricata_c_a_SOURCES = \
util-dpdk-mlx5.c \
util-dpdk-rss.c \
util-dpdk.c \
util-dpdk-rte-flow.c \
util-dpdk-rte-flow-pattern.c \
util-ebpf.c \
util-enum.c \
util-error.c \

@ -47,6 +47,7 @@
#include "util-dpdk-ice.h"
#include "util-dpdk-ixgbe.h"
#include "util-dpdk-rss.h"
#include "util-dpdk-rte-flow.h"
#include "util-time.h"
#include "util-conf.h"
#include "suricata.h"
@ -142,6 +143,7 @@ DPDKIfaceConfigAttributes dpdk_yaml = {
.tx_descriptors = "tx-descriptors",
.copy_mode = "copy-mode",
.copy_iface = "copy-iface",
.drop_filter = "drop-filter",
};
/**
@ -339,6 +341,7 @@ static void DPDKDerefConfig(void *conf)
if (SC_ATOMIC_SUB(iconf->ref, 1) == 1) {
DPDKDeviceResourcesDeinit(&iconf->pkt_mempools);
iconf->RteRulesFree(&iconf->drop_filter);
SCFree(iconf);
}
SCReturn;
@ -356,6 +359,7 @@ static void ConfigInit(DPDKIfaceConfig **iconf)
SC_ATOMIC_INIT(ptr->ref);
(void)SC_ATOMIC_ADD(ptr->ref, 1);
ptr->DerefFunc = DPDKDerefConfig;
ptr->RteRulesFree = RteFlowRuleStorageFree;
ptr->flags = 0;
*iconf = ptr;
@ -1027,6 +1031,10 @@ static int ConfigLoad(DPDKIfaceConfig *iconf, const char *iface)
if (retval < 0)
SCReturnInt(retval);
retval = ConfigLoadRteFlowRules(if_root, dpdk_yaml.drop_filter, &iconf->drop_filter);
if (retval < 0)
SCReturnInt(retval);
SCReturnInt(0);
}

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Open Information Security Foundation
/* Copyright (C) 2021-2025 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
@ -40,6 +40,7 @@ typedef struct DPDKIfaceConfigAttributes_ {
const char *tx_descriptors;
const char *copy_mode;
const char *copy_iface;
const char *drop_filter;
} DPDKIfaceConfigAttributes;
int RunModeIdsDpdkWorkers(void);

@ -42,6 +42,7 @@
#include "tmqh-packetpool.h"
#include "util-privs.h"
#include "util-device-private.h"
#include "util-dpdk-rte-flow.h"
#include "action-globals.h"
#ifndef HAVE_DPDK
@ -115,6 +116,7 @@ typedef struct DPDKThreadVars_ {
LiveDevice *livedev;
ChecksumValidationMode checksum_mode;
bool intr_enabled;
bool port_stopped;
/* references to packet and drop counters */
uint16_t capture_dpdk_packets;
uint16_t capture_dpdk_rx_errs;
@ -122,6 +124,7 @@ typedef struct DPDKThreadVars_ {
uint16_t capture_dpdk_rx_no_mbufs;
uint16_t capture_dpdk_ierrors;
uint16_t capture_dpdk_tx_errs;
uint16_t capture_dpdk_rte_flow_filtered;
unsigned int flags;
uint16_t threads;
/* for IPS */
@ -191,7 +194,8 @@ static inline void DPDKFreeMbufArray(
}
}
static void DevicePostStartPMDSpecificActions(DPDKThreadVars *ptv, const char *driver_name)
static int DevicePostStartPMDSpecificActions(
DPDKThreadVars *ptv, DPDKIfaceConfig *dpdk_config, const char *driver_name)
{
if (strcmp(driver_name, "net_bonding") == 0)
driver_name = BondingDeviceDriverGet(ptv->port_id);
@ -203,6 +207,16 @@ static void DevicePostStartPMDSpecificActions(DPDKThreadVars *ptv, const char *d
iceDeviceSetRSS(ptv->port_id, ptv->threads, ptv->livedev->dev);
else if (strcmp(driver_name, "mlx5_pci") == 0)
mlx5DeviceSetRSS(ptv->port_id, ptv->threads, ptv->livedev->dev);
if ((strcmp(driver_name, "mlx5_pci") == 0 || strcmp(driver_name, "net_ice") == 0 ||
strcmp(driver_name, "net_i40e") == 0)) {
int retval =
RteFlowRulesCreate(dpdk_config->port_id, &dpdk_config->drop_filter, driver_name);
if (retval != 0)
SCReturnInt(retval);
ptv->livedev->dpdk_vars->drop_filter = &dpdk_config->drop_filter;
}
SCReturnInt(0);
}
static void DevicePreClosePMDSpecificActions(DPDKThreadVars *ptv, const char *driver_name)
@ -291,6 +305,16 @@ static inline void DPDKDumpCounters(DPDKThreadVars *ptv)
return;
}
if (!ptv->port_stopped) {
uint64_t filtered_packets = 0;
filtered_packets =
RteFlowFilteredPacketsQuery(ptv->livedev->dpdk_vars->drop_filter->rule_handlers,
ptv->livedev->dpdk_vars->drop_filter->rule_cnt, ptv->livedev->dev,
ptv->port_id);
if (retval == 0)
StatsSetUI64(ptv->tv, ptv->capture_dpdk_rte_flow_filtered, filtered_packets);
}
StatsSetUI64(ptv->tv, ptv->capture_dpdk_packets,
ptv->pkts + eth_stats.imissed + eth_stats.ierrors + eth_stats.rx_nombuf);
SC_ATOMIC_SET(ptv->livedev->pkts,
@ -488,6 +512,8 @@ static void HandleShutdown(DPDKThreadVars *ptv)
while (SC_ATOMIC_GET(ptv->workers_sync->worker_checked_in) < ptv->workers_sync->worker_cnt) {
rte_delay_us(10);
}
DPDKDumpCounters(ptv);
if (ptv->queue_id == 0) {
rte_delay_us(20); // wait for all threads to get out of the sync loop
SC_ATOMIC_SET(ptv->workers_sync->worker_checked_in, 0);
@ -500,8 +526,8 @@ static void HandleShutdown(DPDKThreadVars *ptv)
// in IDS we stop our port - no peer threads are running
rte_eth_dev_stop(ptv->port_id);
}
ptv->port_stopped = true;
}
DPDKDumpCounters(ptv);
}
static void PeriodicDPDKDumpCounters(DPDKThreadVars *ptv)
@ -600,12 +626,15 @@ static TmEcode ReceiveDPDKThreadInit(ThreadVars *tv, const void *initdata, void
ptv->capture_dpdk_imissed = StatsRegisterCounter("capture.dpdk.imissed", ptv->tv);
ptv->capture_dpdk_rx_no_mbufs = StatsRegisterCounter("capture.dpdk.no_mbufs", ptv->tv);
ptv->capture_dpdk_ierrors = StatsRegisterCounter("capture.dpdk.ierrors", ptv->tv);
ptv->capture_dpdk_rte_flow_filtered =
StatsRegisterCounter("capture.dpdk.rte_flow_filtered", ptv->tv);
ptv->copy_mode = dpdk_config->copy_mode;
ptv->checksum_mode = dpdk_config->checksum_mode;
ptv->threads = dpdk_config->threads;
ptv->intr_enabled = (dpdk_config->flags & DPDK_IRQ_MODE) ? true : false;
ptv->port_stopped = false;
ptv->port_id = dpdk_config->port_id;
ptv->out_port_id = dpdk_config->out_port_id;
ptv->port_socket_id = dpdk_config->socket_id;
@ -678,8 +707,11 @@ static TmEcode ReceiveDPDKThreadInit(ThreadVars *tv, const void *initdata, void
SCLogWarning("%s: link is down, trying to continue anyway", dpdk_config->iface);
}
// some PMDs requires additional actions only after the device has started
DevicePostStartPMDSpecificActions(ptv, dev_info.driver_name);
/* some PMDs requires additional actions only after the device has started */
retval = DevicePostStartPMDSpecificActions(ptv, dpdk_config, dev_info.driver_name);
if (retval != 0) {
goto fail;
}
uint16_t inconsistent_numa_cnt = SC_ATOMIC_GET(dpdk_config->inconsistent_numa_cnt);
if (inconsistent_numa_cnt > 0 && ptv->port_socket_id != SOCKET_ID_ANY) {

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Open Information Security Foundation
/* Copyright (C) 2021-2025 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
@ -26,6 +26,7 @@
#include "suricata-common.h"
#include "util-dpdk.h"
#include "util-dpdk-rte-flow.h"
#ifdef HAVE_DPDK
#include <rte_ethdev.h>
@ -75,12 +76,14 @@ typedef struct DPDKIfaceConfig_ {
uint32_t mempool_cache_size;
DPDKDeviceResources *pkt_mempools;
uint16_t linkup_timeout; // in seconds how long to wait for link to come up
RteFlowRuleStorage drop_filter;
SC_ATOMIC_DECLARE(uint16_t, ref);
/* threads bind queue id one by one */
SC_ATOMIC_DECLARE(uint16_t, queue_id);
SC_ATOMIC_DECLARE(uint16_t, inconsistent_numa_cnt);
DPDKWorkerSync *workers_sync;
void (*DerefFunc)(void *);
void (*RteRulesFree)(RteFlowRuleStorage *);
struct rte_flow *flow[100];
#endif

@ -26,6 +26,7 @@
#ifdef HAVE_DPDK
#include "util-dpdk-rte-flow.h"
#include <rte_eal.h>
#include <rte_ethdev.h>
#ifdef HAVE_DPDK_BOND
@ -121,6 +122,7 @@ typedef struct {
struct rte_mempool **pkt_mp;
uint16_t pkt_mp_cnt;
uint16_t pkt_mp_capa;
RteFlowRuleStorage *drop_filter;
} DPDKDeviceResources;
int DPDKDeviceResourcesInit(DPDKDeviceResources **dpdk_vars, uint16_t mp_cnt);

@ -35,8 +35,10 @@
#include "util-dpdk-rss.h"
#include "util-debug.h"
#include "util-dpdk-bonding.h"
#include "util-dpdk-rte-flow.h"
#ifdef HAVE_DPDK
static bool RteFlowRulesContainPatternWildcard(RteFlowRuleStorage *);
static void iceDeviceSetRSSHashFunction(uint64_t *rss_hf)
{
@ -124,6 +126,62 @@ void iceDeviceSetRSSConf(struct rte_eth_rss_conf *rss_conf)
rss_conf->rss_key_len = 52;
}
/**
* \brief Check and log whether pattern is broad / not-specific
* as ice does not support these patterns with counter
* enabled
* \param items array of pattern items
* \return true if pattern is broad / wildcard, false otherwise
*/
bool iceDeviceRteFlowPatternError(struct rte_flow_item *items)
{
SCEnter();
int i = 0;
while (items[i].type != RTE_FLOW_ITEM_TYPE_END) {
if (items[i].spec != NULL) {
SCReturnBool(false);
}
++i;
}
SCReturnBool(true);
}
/**
* \brief Checks whether at least one pattern contains wildcard matching
*
* \param rule_storage struct contaning number of rules, their string instances and rte_flow
* handlers
* \return true if any pattern contains wildcard matching, false otherwise
*/
static bool RteFlowRulesContainPatternWildcard(RteFlowRuleStorage *rule_storage)
{
for (size_t i = 0; i < rule_storage->rule_cnt; i++) {
char *pattern = rule_storage->rules[i];
if (strstr(pattern, " mask ") != NULL || (strstr(pattern, " last ") != NULL))
return true;
}
return false;
}
/**
* \brief Decide based on the pattern whether rte_flow rules should
* support gathering statistic or not
* \param rule_storage struct contaning number of rules and their string instances
* \param port_name name of the port
* \return true if any pattern contains wildcard, false otherwise
*/
bool iceDeviceDecideRteFlowActionType(RteFlowRuleStorage *rule_storage, const char *port_name)
{
if (RteFlowRulesContainPatternWildcard(rule_storage)) {
SCLogWarning(
"%s: gathering statistic for the rte_flow rule is disabled because of wildcard "
"pattern (ice PMD specific)",
port_name);
return false;
}
return true;
}
#endif /* HAVE_DPDK */
/**
* @}

@ -29,9 +29,12 @@
#ifdef HAVE_DPDK
#include "util-dpdk.h"
#include "util-dpdk-rte-flow.h"
int iceDeviceSetRSS(int port_id, uint16_t nb_rx_queues, char *port_name);
void iceDeviceSetRSSConf(struct rte_eth_rss_conf *rss_conf);
bool iceDeviceRteFlowPatternError(struct rte_flow_item *items);
bool iceDeviceDecideRteFlowActionType(RteFlowRuleStorage *rule_storage, const char *port_name);
#endif /* HAVE_DPDK */

@ -39,6 +39,9 @@
#ifdef HAVE_DPDK
#define MLX5_RSS_HKEY_LEN 40
/* Although 2^16 rules are supported on mlx5 in rte_flow group 0,
rules in approximately top 10% of the capacity do not support counters */
#define MLX5_MAX_RTE_FLOW_RULES 57000
int mlx5DeviceSetRSS(int port_id, uint16_t nb_rx_queues, char *port_name)
{
@ -70,6 +73,19 @@ int mlx5DeviceSetRSS(int port_id, uint16_t nb_rx_queues, char *port_name)
return 0;
}
int mlx5DeviceCheckDropFilterLimits(uint32_t rte_flow_rule_count, char **err_msg)
{
if (rte_flow_rule_count > MLX5_MAX_RTE_FLOW_RULES) {
static char msg_buffer[1024];
snprintf(msg_buffer, sizeof(msg_buffer),
"Maximum number of rte_flow rules reached, curr: %i, max %i", rte_flow_rule_count,
MLX5_MAX_RTE_FLOW_RULES);
*err_msg = msg_buffer;
return -ENOSPC;
}
return 0;
}
#endif /* HAVE_DPDK */
/**
* @}

@ -29,6 +29,7 @@
#ifdef HAVE_DPDK
int mlx5DeviceSetRSS(int port_id, uint16_t nb_rx_queues, char *port_name);
int mlx5DeviceCheckDropFilterLimits(uint32_t rte_flow_rule_count, char **err_msg);
#endif /* HAVE_DPDK */

@ -104,6 +104,7 @@ int DPDKCreateRSSFlowGeneric(
rss_conf.types = RTE_ETH_RSS_IPV4 | RTE_ETH_RSS_IPV6;
attr.ingress = 1;
attr.priority = 1;
action[0].type = RTE_FLOW_ACTION_TYPE_RSS;
action[0].conf = &rss_conf;
action[1].type = RTE_FLOW_ACTION_TYPE_END;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,46 @@
/* Copyright (C) 2025 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 rte_flow rules util functions
*
* @{
*/
/**
* \file
*
* \author Adam Kiripolsky <adam.kiripolsky@cesnet.cz>
*
* DPDK rte_flow rules util functions
*
*/
#ifndef SURICATA_RTE_FLOW_RULES_PATTERN_H
#define SURICATA_RTE_FLOW_RULES_PATTERN_H
#ifdef HAVE_DPDK
#include "util-dpdk.h"
int ParsePattern(char *pattern, struct rte_flow_item **items);
#endif /* HAVE_DPDK */
#endif /* SURICATA_RTE_FLOW_RULES_PATTERN_H */
/**
* @}
*/

@ -0,0 +1,426 @@
/* Copyright (C) 2025 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 rte_flow rules util functions
*
* @{
*/
/**
* \file
*
* \author Adam Kiripolsky <adam.kiripolsky@cesnet.cz>
*
* DPDK rte_flow rules util functions
*
*/
#include "decode.h"
#include "runmode-dpdk.h"
#include "util-debug.h"
#include "util-dpdk.h"
#include "util-dpdk-ice.h"
#include "util-dpdk-mlx5.h"
#include "util-dpdk-rte-flow.h"
#include "util-dpdk-rte-flow-pattern.h"
#ifdef HAVE_DPDK
#define RULE_STORAGE_INIT_SIZE 8
#define RULE_STORAGE_SIZE_INC 16
#define COUNT_ACTION_ID 1
#if RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0)
static int RteFlowRuleStorageInit(RteFlowRuleStorage *);
static int RteFlowRuleStorageAddRule(RteFlowRuleStorage *, const char *);
static int RteFlowRuleStorageExtendCapacity(RteFlowRuleStorage *, int);
static char *DriverSpecificErrorMessage(const char *, struct rte_flow_item *);
static int DeviceCheckDropFilterLimits(RteFlowRuleStorage *, const char *, char **);
static void RteFlowDropFilterInitAttr(const char *, struct rte_flow_attr *);
static void RteFlowDropFilterInitAction(
RteFlowRuleStorage *, const char *, const char *, struct rte_flow_action *);
static bool RteFlowShouldGatherStats(RteFlowRuleStorage *, const char *, const char *);
/**
* \brief Specify ambiguous error messages as some drivers have specific
* behaviour when creating rte_flow rules.
*
* \param driver_name name of a driver
* \param items array of pattern items
* \return error message if error present, NULL otherwise
*/
static char *DriverSpecificErrorMessage(const char *driver_name, struct rte_flow_item *items)
{
if (strcmp(driver_name, "net_ice") == 0) {
if (iceDeviceRteFlowPatternError(items) == true) {
char msg[] = "Driver specific errmsg: ice driver does not support broad patterns";
char *ret = SCCalloc((strlen(msg) + 1), sizeof(msg[0]));
strlcpy(ret, msg, sizeof(msg[0]) * (strlen(msg) + 1));
return ret;
}
}
return NULL;
}
static int DeviceCheckDropFilterLimits(
RteFlowRuleStorage *rule_storage, const char *driver_name, char **err_msg)
{
if (strcmp(driver_name, "mlx5_pci") == 0)
return mlx5DeviceCheckDropFilterLimits(rule_storage->rule_cnt, err_msg);
return 0;
}
/**
* \brief Initializes the attributes of rte_flow rules
*
* \param driver_name name of the driver
* \param[out] attr attributes which configure how the rte_flow rules will behave
*/
static void RteFlowDropFilterInitAttr(const char *driver_name, struct rte_flow_attr *attr)
{
attr->ingress = 1;
attr->priority = 0;
attr->group = 0;
/* ICE PMD has to have attribute group set to 2 on DPDK 23.11 and higher for the count action to
* work properly */
if (strcmp(driver_name, "net_ice") == 0) {
#if RTE_VERSION >= RTE_VERSION_NUM(23, 11, 0, 0)
attr->group = 2;
#endif /* RTE_VERSION >= RTE_VERSION_NUM(23, 11, 0, 0) */
}
}
/**
* \brief Configures the action which will rte_flow rules perform and
* decides whether statistic will be gathered or not
*
* \param rule_storage struct contaning number of rules and their string instances
* \param port_name name of the port
* \param driver_name name of the driver
* \param[out] action types of actions to be used in the rte_flow rules
*/
static void RteFlowDropFilterInitAction(RteFlowRuleStorage *rule_storage, const char *port_name,
const char *driver_name, struct rte_flow_action *action)
{
/* ICE PMD does not support count action with wildcard pattern (mask and last pattern item
* types). The count action is omitted when wildcard pattern is detected */
if (strcmp(driver_name, "net_ice") == 0 &&
!iceDeviceDecideRteFlowActionType(rule_storage, port_name)) {
action[0].type = RTE_FLOW_ACTION_TYPE_DROP;
action[1].type = RTE_FLOW_ACTION_TYPE_END;
return;
}
if (strcmp(driver_name, "net_ice") == 0 || strcmp(driver_name, "mlx5_pci") == 0) {
action[0].type = RTE_FLOW_ACTION_TYPE_COUNT;
static uint32_t counter_id = COUNT_ACTION_ID;
action[0].conf = &counter_id;
action[1].type = RTE_FLOW_ACTION_TYPE_DROP;
action[2].type = RTE_FLOW_ACTION_TYPE_END;
return;
}
action[0].type = RTE_FLOW_ACTION_TYPE_DROP;
action[1].type = RTE_FLOW_ACTION_TYPE_END;
return;
}
/**
* \brief Function decides, based on the driver and type of rte_flow rules,
* whether to gather statistics with counter in rte_flow rules or no.
*
* \param rule_storage rules loaded from suricata.yam
* \param driver_name name of the driver
* \param port_name name of the port
* \return true if gathering stats from rte_flow rules is possible, false otherwise
*/
static bool RteFlowShouldGatherStats(
RteFlowRuleStorage *rule_storage, const char *driver_name, const char *port_name)
{
if (strcmp(driver_name, "net_ice") == 0 &&
!iceDeviceDecideRteFlowActionType(rule_storage, port_name))
return false;
return true;
}
#endif /* RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0) */
static int RteFlowRuleStorageInit(RteFlowRuleStorage *rule_storage)
{
SCEnter();
rule_storage->rule_cnt = 0;
rule_storage->rule_size = RULE_STORAGE_INIT_SIZE;
rule_storage->rules = SCCalloc(rule_storage->rule_size, sizeof(char *));
if (rule_storage->rules == NULL) {
SCLogError("Setup memory allocation for rte_flow rule storage failed");
SCReturnInt(-ENOMEM);
}
SCReturnInt(0);
}
#if RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0)
static int RteFlowRuleStorageAddRule(RteFlowRuleStorage *rule_storage, const char *rule)
{
SCEnter();
if (rule_storage->rule_cnt == rule_storage->rule_size) {
int retval = RteFlowRuleStorageExtendCapacity(rule_storage, RULE_STORAGE_SIZE_INC);
if (retval != 0)
SCReturnInt(retval);
}
rule_storage->rules[rule_storage->rule_cnt] = SCCalloc(strlen(rule) + 1, sizeof(rule[0]));
if (rule_storage->rules[rule_storage->rule_cnt] == NULL) {
SCLogError("Memory allocation for rte_flow rule string failed");
SCReturnInt(-ENOMEM);
}
strlcpy(rule_storage->rules[rule_storage->rule_cnt], rule,
(strlen(rule) + 1) * sizeof(rule[0]));
rule_storage->rule_cnt++;
SCReturnInt(0);
}
static int RteFlowRuleStorageExtendCapacity(RteFlowRuleStorage *rule_storage, int inc)
{
SCEnter();
char **tmp_rules;
rule_storage->rule_size += inc;
tmp_rules = SCRealloc(rule_storage->rules, rule_storage->rule_size * sizeof(char *));
if (tmp_rules == NULL) {
SCLogError("Memory reallocation for rte_flow rule storage failed");
SCReturnInt(-ENOMEM);
}
rule_storage->rules = tmp_rules;
SCReturnInt(0);
}
#endif /* RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0) */
/**
* \brief Deallocation of memory containing user set rte_flow rules
*
* \param rule_storage rules loaded from suricata.yaml
*/
void RteFlowRuleStorageFree(RteFlowRuleStorage *rule_storage)
{
#if RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0)
if (rule_storage->rules == NULL) {
SCReturn;
}
for (uint32_t i = 0; i < rule_storage->rule_cnt; i++) {
SCFree(rule_storage->rules[i]);
}
SCFree(rule_storage->rules);
rule_storage->rules = NULL;
#endif /* RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0) */
}
/**
* \brief Load rte_flow rules patterns from suricata.yaml
*
* \param if_root root node in suricata.yaml
* \param drop_filter_str value to look for in suricata.yaml
* \param rule_storage pointer to structure to load rte_flow rules into
* \return 0 on success, -1 on error
*/
int ConfigLoadRteFlowRules(
SCConfNode *if_root, const char *drop_filter_str, RteFlowRuleStorage *rule_storage)
{
SCEnter();
SCConfNode *node = SCConfNodeLookupChild(if_root, drop_filter_str);
if (node == NULL) {
SCLogInfo("No configuration node found for %s", drop_filter_str);
} else {
SCConfNode *rule_node;
const char *rule = NULL;
/* Suppress unused variable warning in case of DPDK version < 21.11 */
(void)rule;
int retval = RteFlowRuleStorageInit(rule_storage);
if (retval != 0) {
SCReturnInt(retval);
}
TAILQ_FOREACH (rule_node, &node->head, next) {
#if RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0)
if (strcmp(rule_node->val, "rule") == 0) {
SCConfGetChildValue(rule_node, "rule", &rule);
retval = RteFlowRuleStorageAddRule(rule_storage, rule);
if (retval != 0) {
RteFlowRuleStorageFree(rule_storage);
SCReturnInt(retval);
}
} else {
SCLogError("DPDK .%s contains unrecognized key, only \"rule\" is supported",
drop_filter_str);
SCReturnInt(-1);
}
#else
if (strcmp(rule_node->val, "rule") == 0) {
SCLogError("DPDK .%s is supported from DPDK version 21.11 and higher, "
"filter not applied",
drop_filter_str);
RteFlowRuleStorageFree(rule_storage);
SCReturnInt(0);
}
#endif /* RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0) */
}
}
SCReturnInt(0);
}
/**
* \brief Query the number of packets filtered by rte_flow rules defined by user in suricata.yaml
*
* \param rules array of rte_flow rule handlers
* \param rule_count number of existing rules
* \param device_name name of the device
* \param port_id id of a port
* \return 0 on success, a negative errno value otherwise and rte_errno is set
*/
uint64_t RteFlowFilteredPacketsQuery(
struct rte_flow **rules, uint32_t rule_count, const char *device_name, int port_id)
{
uint64_t retval = 0;
#if RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0)
struct rte_flow_query_count query_count = { 0 };
struct rte_flow_action action[] = { { 0 }, { 0 }, { 0 } };
struct rte_flow_error flow_error = { 0 };
uint32_t counter_id = COUNT_ACTION_ID;
bool err = false;
int query_retval = 0;
query_count.reset = 0;
action[0].type = RTE_FLOW_ACTION_TYPE_COUNT;
action[0].conf = &counter_id;
action[1].type = RTE_FLOW_ACTION_TYPE_END;
for (uint32_t i = 0; i < rule_count; i++) {
query_retval =
rte_flow_query(port_id, rules[i], &(action[0]), (void *)&query_count, &flow_error);
if (query_retval != 0 && !err) {
err = true;
SCLogError("%s: rte_flow count query error %s errmsg: %s", device_name,
rte_strerror(-retval), flow_error.message);
} else
retval += query_count.hits;
}
#endif /* RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0) */
SCReturnInt(retval);
}
/**
* \brief Create rte_flow drop rules with patterns stored in rule_storage on a port with id
* port_id
*
* \param port_id identificator of a port
* \param rule_storage pointer to structure containing rte_flow rule patterns
* \param driver_name name of a driver
* \return 0 on success, -1 on error
*/
int RteFlowRulesCreate(uint16_t port_id, RteFlowRuleStorage *rule_storage, const char *driver_name)
{
#if RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0)
SCEnter();
uint32_t failed_rule_count = 0;
struct rte_flow_error flush_error = { 0 };
struct rte_flow_attr attr = { 0 };
struct rte_flow_action action[] = { { 0 }, { 0 }, { 0 } };
const char *port_name = DPDKGetPortNameByPortID(port_id);
char *err_msg;
int retval = DeviceCheckDropFilterLimits(rule_storage, driver_name, &err_msg);
if (retval != 0) {
SCLogError("%s: Can't configure drop-filter: %s", port_name, err_msg);
SCReturnInt(-ENOSPC);
}
RteFlowDropFilterInitAttr(driver_name, &attr);
RteFlowDropFilterInitAction(rule_storage, port_name, driver_name, action);
rule_storage->rule_handlers = SCCalloc(rule_storage->rule_size, sizeof(struct rte_flow *));
if (rule_storage->rule_handlers == NULL) {
SCLogError("%s: Memory allocation for rte_flow rule string failed", port_name);
RteFlowRuleStorageFree(rule_storage);
SCReturnInt(-ENOMEM);
}
SCLogInfo("%s: loading %i rte_flow drop-filter rules into hardware", port_name,
rule_storage->rule_cnt);
for (uint32_t i = 0; i < rule_storage->rule_cnt; i++) {
struct rte_flow_item *items = { 0 };
struct rte_flow_error flow_error = { 0 };
int retval = ParsePattern(rule_storage->rules[i], &items);
if (retval != 0) {
failed_rule_count++;
SCLogError("%s: Error when parsing rte_flow rule \"%s\"", port_name,
rule_storage->rules[i]);
continue;
}
retval = rte_flow_validate(port_id, &attr, items, action, &flow_error);
if (retval != 0) {
failed_rule_count++;
char *driver_specific_err = DriverSpecificErrorMessage(driver_name, items);
SCLogError("%s: Error when validating rte_flow rule \"%s\": %s, errmsg: "
"%s. %s",
port_name, rule_storage->rules[i], rte_strerror(-retval), flow_error.message,
driver_specific_err != NULL ? driver_specific_err : "");
if (driver_specific_err != NULL) {
SCFree(driver_specific_err);
}
continue;
}
struct rte_flow *flow_handler = rte_flow_create(port_id, &attr, items, action, &flow_error);
if (flow_handler == NULL) {
failed_rule_count++;
SCLogError("%s: Error when creating rte_flow rule \"%s\": %s", port_name,
rule_storage->rules[i], flow_error.message);
continue;
}
rule_storage->rule_handlers[i] = flow_handler;
}
if (failed_rule_count) {
SCLogError("%s: Error parsing/creating %i rte_flow rule(s), flushing rules", port_name,
failed_rule_count);
int retval = rte_flow_flush(port_id, &flush_error);
if (retval != 0) {
SCLogError("%s Unable to flush rte_flow rules: %s Flush error msg: %s", port_name,
rte_strerror(-retval), flush_error.message);
}
SCReturnInt(-ENOTSUP);
}
SCLogInfo("%s: %i rte_flow rules created for drop-filter", port_name, rule_storage->rule_cnt);
if (!RteFlowShouldGatherStats(rule_storage, driver_name, port_name)) {
SCFree(rule_storage->rule_handlers);
rule_storage->rule_cnt = 0;
}
#endif /* RTE_VERSION >= RTE_VERSION_NUM(21, 11, 0, 0)*/
SCReturnInt(0);
}
#endif /* HAVE_DPDK */
/**
* @}
*/

@ -0,0 +1,59 @@
/* Copyright (C) 2025 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 rte_flow rules util functions
*
* @{
*/
/**
* \file
*
* \author Adam Kiripolsky <adam.kiripolsky@cesnet.cz>
*
* DPDK rte_flow rules util functions
*
*/
#ifndef SURICATA_RTE_FLOW_RULES_H
#define SURICATA_RTE_FLOW_RULES_H
#ifdef HAVE_DPDK
#include "conf.h"
#include "util-dpdk.h"
typedef struct RteFlowRuleStorage_ {
uint32_t rule_cnt;
uint32_t rule_size;
char **rules;
struct rte_flow **rule_handlers;
} RteFlowRuleStorage;
void RteFlowRuleStorageFree(RteFlowRuleStorage *rule_storage);
int ConfigLoadRteFlowRules(
SCConfNode *if_root, const char *drop_filter_str, RteFlowRuleStorage *rule_storage);
int RteFlowRulesCreate(uint16_t port_id, RteFlowRuleStorage *rule_storage, const char *driver_name);
uint64_t RteFlowFilteredPacketsQuery(
struct rte_flow **rules, uint32_t rule_count, const char *device_name, int port_id);
#endif /* HAVE_DPDK */
#endif /* SURICATA_RTE_FLOW_RULES_H */
/**
* @}
*/

@ -61,6 +61,11 @@ void DPDKFreeDevice(LiveDevice *ldev)
(void)ldev; // avoid warnings of unused variable
#ifdef HAVE_DPDK
if (SCRunmodeGet() == RUNMODE_DPDK) {
if (ldev->dpdk_vars->drop_filter->rule_handlers != NULL &&
ldev->dpdk_vars->drop_filter->rule_cnt != 0) {
SCLogDebug("%s: releasing rte_flow rule handlers", ldev->dev);
SCFree(ldev->dpdk_vars->drop_filter->rule_handlers);
}
SCLogDebug("%s: releasing packet mempools", ldev->dev);
DPDKDeviceResourcesDeinit(&ldev->dpdk_vars);
}

@ -827,6 +827,11 @@ dpdk:
# - ips: the same as tap mode but it also drops packets that are flagged by rules to be dropped
copy-mode: none
copy-iface: none # or PCIe address of the second interface
# Flow patterns in dpdk-testpmd syntax for flows that are supposed to be dropped directly in the NIC.
# If the pattern is invalid or unsupported, Suricata will not start.
# Example: "rule: pattern eth / ipv4 src is 16.0.25.0 / udp / end"
# "rule: pattern eth / ipv4 dst is 19.1.2.15 / tcp / end"
drop-filter: none
- interface: default
threads: auto
@ -844,6 +849,7 @@ dpdk:
tx-descriptors: auto
copy-mode: none
copy-iface: none
drop-filter: none
# Cross platform libpcap capture support

Loading…
Cancel
Save