Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions doc/userguide/capture-hardware/dpdk.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------------------------

Drop filter can improve the performance of Suricata by filtering
used-predefined flows directly in the Network interface card. The user can

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/used-predefined flows/user-predefined traffic patterns/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, since NIC is mentioned previously, you could either use acronym (NIC) or lowercase the "Network".

specify unwanted flows before the start of Suricata. These flows are not going to be

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, I would avoid mentioning "flows" and focus more on the traffic patterns (or a more suitable term).
Potentially "traffic filter expression/rule"

inspected by Suricata and will be ignored for the whole run of the program.
On some PMDs, the statistics of the dropped flows are gathered and stored in eve.json.

The syntax for drop filter in Suricata is similar to the dpdk-testpmd application
rule syntax, although in Suricata, only the "pattern" section is applicable.
The user can define multiple rules, either to match specific flow
or a range of flows (e.g. using ip or port masks).

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 flows
(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 all of the rules match one specific flow (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 the suricata.yaml file.

Below is a sample configuration that demonstrates how to filter 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"
4 changes: 4 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,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 \
Expand Down Expand Up @@ -1094,6 +1096,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 \
Expand Down
16 changes: 12 additions & 4 deletions src/runmode-dpdk.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -142,6 +143,7 @@ DPDKIfaceConfigAttributes dpdk_yaml = {
.tx_descriptors = "tx-descriptors",
.copy_mode = "copy-mode",
.copy_iface = "copy-iface",
.drop_filter = "drop-filter",
};

/**
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -1835,10 +1843,10 @@ static void *ParseDpdkConfigAndConfigureDevice(const char *iface)
if (ldev_instance == NULL) {
FatalError("Device %s is not registered as a live device", iface);
}
for (uint16_t i = 0; i < iconf->threads; i++) {
ldev_instance->dpdk_vars = iconf->pkt_mempools;
iconf->pkt_mempools = NULL;
}

ldev_instance->dpdk_vars = iconf->pkt_mempools;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have been moved out of this commit.

iconf->pkt_mempools = NULL;

return iconf;
}

Expand Down
3 changes: 2 additions & 1 deletion src/runmode-dpdk.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);
Expand Down
40 changes: 36 additions & 4 deletions src/source-dpdk.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -115,13 +116,15 @@ 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;
uint16_t capture_dpdk_imissed;
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 */
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 4 additions & 1 deletion src/source-dpdk.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/util-dpdk-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
58 changes: 58 additions & 0 deletions src/util-dpdk-ice.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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 patterns array of loaded rte_flow rule patterns from suricata.yaml
* \param rule_count number of loaded rte_flow rule patterns
* \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 */
/**
* @}
Expand Down
Loading
Loading