pull/16292/merge
Lukas Sismis 13 hours ago committed by GitHub
commit 59de7a2a86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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, "

@ -203,8 +203,9 @@ 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, 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

@ -2489,6 +2489,13 @@ 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, ``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
@ -2526,8 +2533,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

@ -6972,6 +6972,61 @@
}
}
},
"dpdk": {
"type": "object",
"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":
"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"
@ -6987,6 +7042,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"
}
}
},

@ -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

@ -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"

@ -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

@ -567,6 +567,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 \
@ -1127,6 +1128,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 \

@ -110,6 +110,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"
@ -136,6 +137,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",
@ -529,22 +531,50 @@ static int ConfigSetTxQueues(
SCReturnInt(0);
}
static uint32_t MempoolSizeCalculateAutosize(
const DPDKIfaceConfig *iconf, const struct rte_eth_dev_info *dev_info)
static int ConfigSetRxBacklogSize(DPDKIfaceConfig *iconf, const char *entry_str)
{
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
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);
}
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);
}
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 +
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) {
SCLogError("%s: mempool requirement exceeds the supported per-queue size", iconf->iface);
return -ERANGE;
}
return mp_size -
1; // mempool size should be n = (2^q - 1) to have all descriptors available for use
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);
}
*size = mp_size - 1;
return 0;
}
static uint32_t MempoolSizeDistributeToQueues(uint32_t global_mp_size, uint16_t nic_queues)
@ -558,24 +588,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(
@ -583,9 +599,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
@ -603,8 +616,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;
@ -616,12 +629,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);
}
@ -988,6 +1006,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)
@ -1330,10 +1356,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)
@ -1346,28 +1370,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)
@ -1499,6 +1507,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);

@ -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;

@ -86,6 +86,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"
@ -144,6 +145,7 @@ static void RegisterUnittests(void)
HashTableRegisterTests();
HashListTableRegisterTests();
PoolRegisterTests();
SCRingBufferRegisterTests();
ByteRegisterTests();
MpmRegisterTests();
FlowBitRegisterTests();

@ -93,9 +93,9 @@ 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 <numa.h>
#define BURST_SIZE 32
// interrupt mode constants
#define MIN_ZERO_POLL_COUNT 10U
#define MIN_ZERO_POLL_COUNT_TO_SLEEP 10U
@ -135,8 +135,15 @@ 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;
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 **);
@ -210,12 +217,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);
@ -277,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. */
@ -413,7 +446,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)) {
@ -425,7 +459,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;
@ -480,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);
@ -536,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
@ -572,7 +760,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) {
@ -584,26 +772,35 @@ 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);
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]);
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);
}
}
} 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);
}
}
}
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) {
TmqhOutputPacketpool(ptv->tv, p);
DPDKFreeMbufArray(ptv->received_mbufs, nb_rx - i - 1, i + 1);
SCReturnInt(EXIT_FAILURE);
if (SCRingBufferCount(ptv->rx_backlog) > 0 && DPDKBacklogPoll(ptv) != TM_ECODE_OK) {
DPDKBacklogClear(ptv);
DPDKDumpBacklogCounters(ptv);
StatsSyncCounters(&tv->stats);
SCReturnInt(TM_ECODE_FAILED);
}
}
@ -678,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);
@ -710,17 +911,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;
@ -756,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);
}
@ -773,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);

@ -35,6 +35,11 @@ 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_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
#define DPDK_PROMISC (1 << 0) /**< Promiscuous mode */
@ -71,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

@ -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)
{

@ -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(

@ -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;

@ -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 */

@ -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
}
/**

@ -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
}

@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/** 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 */

@ -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 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

Loading…
Cancel
Save