dpdk: initial support with workers runmode

Register a new runmode - DPDK. This enables a new flag on Suricata start
(--dpdk).

With the flag given, DPDK runmode is enabled.

Runmode loads the configuration and then initializes EAL.

If successful, it configures the physical NICs according to the configuration
file. After that, worker threads are initialized and then are in continuous
receive loop.
pull/6708/head
Lukas Sismis 4 years ago committed by Victor Julien
parent fcfee6994e
commit a7faed1245

@ -424,6 +424,7 @@ noinst_HEADERS = \
respond-reject.h \
respond-reject-libnet11.h \
runmode-af-packet.h \
runmode-dpdk.h \
runmode-erf-dag.h \
runmode-erf-file.h \
runmode-ipfw.h \
@ -441,6 +442,7 @@ noinst_HEADERS = \
rust-context.h \
rust.h \
source-af-packet.h \
source-dpdk.h \
source-erf-dag.h \
source-erf-file.h \
source-ipfw.h \
@ -505,6 +507,7 @@ noinst_HEADERS = \
util-decode-mime.h \
util-detect.h \
util-device.h \
util-dpdk.h \
util-ebpf.h \
util-enum.h \
util-error.h \
@ -999,6 +1002,7 @@ libsuricata_c_a_SOURCES = \
respond-reject.c \
respond-reject-libnet11.c \
runmode-af-packet.c \
runmode-dpdk.c \
runmode-erf-dag.c \
runmode-erf-file.c \
runmode-ipfw.c \
@ -1015,6 +1019,7 @@ libsuricata_c_a_SOURCES = \
runmode-windivert.c \
rust-context.c \
source-af-packet.c \
source-dpdk.c \
source-erf-dag.c \
source-erf-file.c \
source-ipfw.c \
@ -1067,6 +1072,7 @@ libsuricata_c_a_SOURCES = \
util-decode-mime.c \
util-detect.c \
util-device.c \
util-dpdk.c \
util-ebpf.c \
util-enum.c \
util-error.c \

@ -976,6 +976,61 @@ int ConfNodeIsSequence(const ConfNode *node)
return node->is_seq == 0 ? 0 : 1;
}
/**
* @brief Finds an interface from the list of interfaces.
* @param ifaces_node_name - name of the node which holds a list of intefaces
* @param iface - interfaces name
* @return NULL on failure otherwise a valid pointer
*/
ConfNode *ConfSetIfaceNode(const char *ifaces_node_name, const char *iface)
{
ConfNode *if_node;
ConfNode *ifaces_list_node;
/* Find initial node which holds all interfaces */
ifaces_list_node = ConfGetNode(ifaces_node_name);
if (ifaces_list_node == NULL) {
SCLogError(SC_ERR_CONF_YAML_ERROR, "unable to find %s config", ifaces_node_name);
return NULL;
}
if_node = ConfFindDeviceConfig(ifaces_list_node, iface);
if (if_node == NULL)
SCLogNotice("unable to find interface %s in DPDK config", iface);
return if_node;
}
/**
* @brief Finds and sets root and default node of the interface.
* @param ifaces_node_name Node which holds list of interfaces
* @param iface Name of the interface e.g. eth3
* @param if_root Node which will hold the interface configuration
* @param if_default Node which is the default configuration in the given list of interfaces
* @return 0 on success, -ENODEV when neither the root interface nor the default interface was found
*/
int ConfSetRootAndDefaultNodes(
const char *ifaces_node_name, const char *iface, ConfNode **if_root, ConfNode **if_default)
{
const char *default_iface = "default";
*if_root = ConfSetIfaceNode(ifaces_node_name, iface);
*if_default = ConfSetIfaceNode(ifaces_node_name, default_iface);
if (*if_root == NULL && *if_default == NULL) {
SCLogError(SC_ERR_CONF_YAML_ERROR,
"unable to find configuration for the interface \"%s\" or the default "
"configuration (\"%s\")",
iface, default_iface);
return (-ENODEV);
}
/* If there is no setting for current interface use default one as main iface */
if (*if_root == NULL) {
*if_root = *if_default;
*if_default = NULL;
}
return 0;
}
#ifdef UNITTESTS
/**

@ -95,5 +95,8 @@ int ConfGetChildValueIntWithDefault(const ConfNode *base, const ConfNode *dflt,
int ConfGetChildValueBoolWithDefault(const ConfNode *base, const ConfNode *dflt, const char *name, int *val);
char *ConfLoadCompleteIncludePath(const char *);
int ConfNodeIsSequence(const ConfNode *node);
ConfNode *ConfSetIfaceNode(const char *ifaces_node_name, const char *iface);
int ConfSetRootAndDefaultNodes(
const char *ifaces_node_name, const char *iface, ConfNode **if_root, ConfNode **if_default);
#endif /* ! __CONF_H__ */

@ -69,6 +69,9 @@ enum PktSrcEnum {
#include "source-af-packet.h"
#include "source-netmap.h"
#include "source-windivert.h"
#ifdef HAVE_DPDK
#include "source-dpdk.h"
#endif
#ifdef HAVE_PF_RING_FLOW_OFFLOAD
#include "source-pfring.h"
#endif
@ -481,6 +484,9 @@ typedef struct Packet_
#ifdef WINDIVERT
WinDivertPacketVars windivert_v;
#endif /* WINDIVERT */
#ifdef HAVE_DPDK
DPDKPacketVars dpdk_v;
#endif
/* A chunk of memory that a plugin can use for its packet vars. */
uint8_t plugin_v[PLUGIN_VAR_SIZE];

File diff suppressed because it is too large Load Diff

@ -0,0 +1,45 @@
/* Copyright (C) 2021 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/** \file
*
* \author Lukas Sismis <lukas.sismis@gmail.com>
*/
#ifndef __RUNMODE_DPDK_H__
#define __RUNMODE_DPDK_H__
typedef struct DPDKIfaceConfigAttributes_ {
const char *threads;
const char *promisc;
const char *multicast;
const char *checksum_checks;
const char *checksum_checks_offload;
const char *mtu;
const char *mempool_size;
const char *mempool_cache_size;
const char *rx_descriptors;
const char *tx_descriptors;
const char *copy_mode;
const char *copy_iface;
} DPDKIfaceConfigAttributes;
int RunModeIdsDpdkWorkers(void);
void RunModeDpdkRegister(void);
const char *RunModeDpdkGetDefaultMode(void);
#endif /* __RUNMODE_DPDK_H__ */

@ -156,6 +156,13 @@ static const char *RunModeTranslateModeToName(int runmode)
#else
return "WINDIVERT(DISABLED)";
#endif
case RUNMODE_DPDK:
#ifdef HAVE_DPDK
return "DPDK";
#else
return "DPDK(DISABLED)";
#endif
default:
FatalError(SC_ERR_UNKNOWN_RUN_MODE, "Unknown runtime mode. Aborting");
}
@ -227,6 +234,7 @@ void RunModeRegisterRunModes(void)
RunModeIdsNflogRegister();
RunModeUnixSocketRegister();
RunModeIpsWinDivertRegister();
RunModeDpdkRegister();
#ifdef UNITTESTS
UtRunModeRegister();
#endif
@ -348,6 +356,11 @@ void RunModeDispatch(int runmode, const char *custom_mode,
case RUNMODE_WINDIVERT:
custom_mode = RunModeIpsWinDivertGetDefaultMode();
break;
#endif
#ifdef HAVE_DPDK
case RUNMODE_DPDK:
custom_mode = RunModeDpdkGetDefaultMode();
break;
#endif
default:
FatalError(SC_ERR_FATAL, "Unknown runtime mode. Aborting");

@ -36,6 +36,7 @@ enum RunModes {
RUNMODE_DAG,
RUNMODE_AFP_DEV,
RUNMODE_NETMAP,
RUNMODE_DPDK,
RUNMODE_UNITTEST,
RUNMODE_NAPATECH,
RUNMODE_UNIX_SOCKET,
@ -110,6 +111,7 @@ int RunModeNeedsBypassManager(void);
#include "runmode-unix-socket.h"
#include "runmode-netmap.h"
#include "runmode-windivert.h"
#include "runmode-dpdk.h"
extern int threading_set_cpu_affinity;
extern float threading_detect_ratio;

@ -0,0 +1,615 @@
/* Copyright (C) 2021 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \defgroup dpdk DPDK running mode
*
* @{
*/
/**
* \file
*
* \author Lukas Sismis <lukas.sismis@gmail.com>
*
* DPDK capture interface
*
*/
#include "suricata-common.h"
#include "runmodes.h"
#include "source-dpdk.h"
#include "suricata.h"
#include "threads.h"
#include "threadvars.h"
#include "tm-threads.h"
#include "tmqh-packetpool.h"
#include "util-privs.h"
#ifndef HAVE_DPDK
TmEcode NoDPDKSupportExit(ThreadVars *, const void *, void **);
void TmModuleReceiveDPDKRegister(void)
{
tmm_modules[TMM_RECEIVEDPDK].name = "ReceiveDPDK";
tmm_modules[TMM_RECEIVEDPDK].ThreadInit = NoDPDKSupportExit;
tmm_modules[TMM_RECEIVEDPDK].Func = NULL;
tmm_modules[TMM_RECEIVEDPDK].ThreadExitPrintStats = NULL;
tmm_modules[TMM_RECEIVEDPDK].ThreadDeinit = NULL;
tmm_modules[TMM_RECEIVEDPDK].cap_flags = 0;
tmm_modules[TMM_RECEIVEDPDK].flags = TM_FLAG_RECEIVE_TM;
}
/**
* \brief Registration Function for DecodeDPDK.
*/
void TmModuleDecodeDPDKRegister(void)
{
tmm_modules[TMM_DECODEDPDK].name = "DecodeDPDK";
tmm_modules[TMM_DECODEDPDK].ThreadInit = NoDPDKSupportExit;
tmm_modules[TMM_DECODEDPDK].Func = NULL;
tmm_modules[TMM_DECODEDPDK].ThreadExitPrintStats = NULL;
tmm_modules[TMM_DECODEDPDK].ThreadDeinit = NULL;
tmm_modules[TMM_DECODEDPDK].cap_flags = 0;
tmm_modules[TMM_DECODEDPDK].flags = TM_FLAG_DECODE_TM;
}
/**
* \brief this function prints an error message and exits.
*/
TmEcode NoDPDKSupportExit(ThreadVars *tv, const void *initdata, void **data)
{
FatalError(SC_ERR_NO_DPDK,
"Error creating thread %s: you do not have "
"support for DPDK enabled, on Linux host please recompile "
"with --enable-dpdk",
tv->name);
}
#else /* We have DPDK support */
#include "util-dpdk.h"
#include <numa.h>
#define BURST_SIZE 32
/**
* \brief Structure to hold thread specific variables.
*/
typedef struct DPDKThreadVars_ {
/* counters */
uint64_t pkts;
ThreadVars *tv;
TmSlot *slot;
LiveDevice *livedev;
ChecksumValidationMode checksum_mode;
/* references to packet and drop counters */
uint16_t capture_dpdk_packets;
uint16_t capture_dpdk_rx_errs;
uint16_t capture_dpdk_tx_errs;
unsigned int flags;
int threads;
/* for IPS */
DpdkCopyModeEnum copy_mode;
uint16_t out_port_id;
/* Entry in the peers_list */
uint64_t bytes;
uint64_t accepted;
uint64_t dropped;
uint16_t port_id;
uint16_t queue_id;
struct rte_mempool *pkt_mempool;
struct rte_mbuf *received_mbufs[BURST_SIZE];
struct timeval machine_start_time;
} DPDKThreadVars;
static TmEcode ReceiveDPDKThreadInit(ThreadVars *, const void *, void **);
static void ReceiveDPDKThreadExitStats(ThreadVars *, void *);
static TmEcode ReceiveDPDKThreadDeinit(ThreadVars *, void *);
static TmEcode ReceiveDPDKLoop(ThreadVars *tv, void *data, void *slot);
static TmEcode DecodeDPDKThreadInit(ThreadVars *, const void *, void **);
static TmEcode DecodeDPDKThreadDeinit(ThreadVars *tv, void *data);
static TmEcode DecodeDPDK(ThreadVars *, Packet *, void *);
static uint64_t CyclesToMicroseconds(uint64_t cycles);
static uint64_t CyclesToSeconds(uint64_t cycles);
static void DPDKFreeMbufArray(struct rte_mbuf **mbuf_array, uint16_t mbuf_cnt, uint16_t offset);
static uint64_t DPDKGetSeconds(void);
static void DPDKFreeMbufArray(struct rte_mbuf **mbuf_array, uint16_t mbuf_cnt, uint16_t offset)
{
for (int i = offset; i < mbuf_cnt; i++) {
rte_pktmbuf_free(mbuf_array[i]);
}
}
static uint64_t CyclesToMicroseconds(const uint64_t cycles)
{
const uint64_t ticks_per_us = rte_get_tsc_hz() / 1000000;
return cycles / ticks_per_us;
}
static uint64_t CyclesToSeconds(const uint64_t cycles)
{
const uint64_t ticks_per_s = rte_get_tsc_hz();
return cycles / ticks_per_s;
}
static void CyclesAddToTimeval(
const uint64_t cycles, struct timeval *orig_tv, struct timeval *new_tv)
{
uint64_t usec = CyclesToMicroseconds(cycles) + orig_tv->tv_usec;
new_tv->tv_sec = orig_tv->tv_sec + usec / 1000000;
new_tv->tv_usec = (usec % 1000000);
}
static void DPDKSetTimevalOfMachineStart(struct timeval *tv)
{
gettimeofday(tv, NULL);
tv->tv_sec -= DPDKGetSeconds();
}
/**
* Initializes real_tv to the correct real time. Adds TSC counter value to the timeval of
* the machine start
* @param machine_start_tv - timestamp when the machine was started
* @param real_tv
*/
static void DPDKSetTimevalReal(struct timeval *machine_start_tv, struct timeval *real_tv)
{
CyclesAddToTimeval(rte_get_tsc_cycles(), machine_start_tv, real_tv);
}
/* get number of seconds from the reset of TSC counter (typically from the machine start) */
static uint64_t DPDKGetSeconds()
{
return CyclesToSeconds(rte_get_tsc_cycles());
}
static void DevicePostStartPMDSpecificActions(DPDKThreadVars *ptv, const char *driver_name)
{
}
static void DevicePreStopPMDSpecificActions(DPDKThreadVars *ptv, const char *driver_name)
{
}
/**
* Attempts to retrieve NUMA node id on which the caller runs
* @return NUMA id on success, -1 otherwise
*/
static int GetNumaNode(void)
{
int cpu = 0;
int node = -1;
#if defined(__linux__)
cpu = sched_getcpu();
node = numa_node_of_cpu(cpu);
#else
SCLogWarning(SC_ERR_TM_THREADS_ERROR, "NUMA node retrieval is not supported on this OS.");
#endif
return node;
}
/**
* \brief Registration Function for ReceiveDPDK.
* \todo Unit tests are needed for this module.
*/
void TmModuleReceiveDPDKRegister(void)
{
tmm_modules[TMM_RECEIVEDPDK].name = "ReceiveDPDK";
tmm_modules[TMM_RECEIVEDPDK].ThreadInit = ReceiveDPDKThreadInit;
tmm_modules[TMM_RECEIVEDPDK].Func = NULL;
tmm_modules[TMM_RECEIVEDPDK].PktAcqLoop = ReceiveDPDKLoop;
tmm_modules[TMM_RECEIVEDPDK].PktAcqBreakLoop = NULL;
tmm_modules[TMM_RECEIVEDPDK].ThreadExitPrintStats = ReceiveDPDKThreadExitStats;
tmm_modules[TMM_RECEIVEDPDK].ThreadDeinit = ReceiveDPDKThreadDeinit;
tmm_modules[TMM_RECEIVEDPDK].cap_flags = SC_CAP_NET_RAW;
tmm_modules[TMM_RECEIVEDPDK].flags = TM_FLAG_RECEIVE_TM;
}
/**
* \brief Registration Function for DecodeDPDK.
* \todo Unit tests are needed for this module.
*/
void TmModuleDecodeDPDKRegister(void)
{
tmm_modules[TMM_DECODEDPDK].name = "DecodeDPDK";
tmm_modules[TMM_DECODEDPDK].ThreadInit = DecodeDPDKThreadInit;
tmm_modules[TMM_DECODEDPDK].Func = DecodeDPDK;
tmm_modules[TMM_DECODEDPDK].ThreadExitPrintStats = NULL;
tmm_modules[TMM_DECODEDPDK].ThreadDeinit = DecodeDPDKThreadDeinit;
tmm_modules[TMM_DECODEDPDK].cap_flags = 0;
tmm_modules[TMM_DECODEDPDK].flags = TM_FLAG_DECODE_TM;
}
static inline void DPDKDumpCounters(DPDKThreadVars *ptv)
{
struct rte_eth_stats eth_stats;
int retval = rte_eth_stats_get(ptv->port_id, &eth_stats);
if (unlikely(retval != 0)) {
SCLogError(SC_ERR_STAT, "Failed to get stats for port id %d: %s", ptv->port_id,
strerror(-retval));
return;
}
uint64_t th_pkts = StatsGetLocalCounterValue(ptv->tv, ptv->capture_dpdk_packets);
StatsAddUI64(ptv->tv, ptv->capture_dpdk_packets, ptv->pkts - th_pkts);
SC_ATOMIC_ADD(ptv->livedev->pkts, ptv->pkts - th_pkts);
/* 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. */
if (ptv->queue_id == 0) {
StatsSetUI64(ptv->tv, ptv->capture_dpdk_rx_errs,
eth_stats.imissed + eth_stats.ierrors + eth_stats.rx_nombuf + ptv->pkts);
StatsSetUI64(ptv->tv, ptv->capture_dpdk_tx_errs, eth_stats.oerrors);
SC_ATOMIC_SET(ptv->livedev->drop, eth_stats.imissed + eth_stats.ierrors +
eth_stats.rx_nombuf + eth_stats.oerrors +
ptv->pkts);
}
}
static void DPDKReleasePacket(Packet *p)
{
int retval;
/* Need to be in copy mode and need to detect early release
where Ethernet header could not be set (and pseudo packet)
When enabling promiscuous mode on Intel cards, 2 ICMPv6 packets are generated.
These get into the infinite cycle between the NIC and the switch in some cases */
if ((p->dpdk_v.copy_mode == DPDK_COPY_MODE_TAP ||
(p->dpdk_v.copy_mode == DPDK_COPY_MODE_IPS && !PacketTestAction(p, ACTION_DROP)))
#if defined(RTE_LIBRTE_I40E_PMD) || defined(RTE_LIBRTE_IXGBE_PMD) || defined(RTE_LIBRTE_ICE_PMD)
&& !(PKT_IS_ICMPV6(p) && p->icmpv6h->type == 143)
#endif
) {
BUG_ON(PKT_IS_PSEUDOPKT(p));
retval =
rte_eth_tx_burst(p->dpdk_v.out_port_id, p->dpdk_v.out_queue_id, &p->dpdk_v.mbuf, 1);
// rte_eth_tx_burst can return only 0 (failure) or 1 (success) because we are only
// transmitting burst of size 1 and the function rte_eth_tx_burst returns number of
// successfully sent packets.
if (unlikely(retval < 1)) {
// sometimes a repeated transmit can help to send out the packet
rte_delay_us(DPDK_BURST_TX_WAIT_US);
retval = rte_eth_tx_burst(
p->dpdk_v.out_port_id, p->dpdk_v.out_queue_id, &p->dpdk_v.mbuf, 1);
if (unlikely(retval < 1)) {
SCLogDebug("Unable to transmit the packet on port %u queue %u",
p->dpdk_v.out_port_id, p->dpdk_v.out_queue_id);
rte_pktmbuf_free(p->dpdk_v.mbuf);
p->dpdk_v.mbuf = NULL;
}
}
} else {
rte_pktmbuf_free(p->dpdk_v.mbuf);
p->dpdk_v.mbuf = NULL;
}
PacketFreeOrRelease(p);
}
/**
* \brief Main DPDK reading Loop function
*/
static TmEcode ReceiveDPDKLoop(ThreadVars *tv, void *data, void *slot)
{
SCEnter();
Packet *p;
uint16_t nb_rx;
time_t last_dump = 0;
time_t current_time;
DPDKThreadVars *ptv = (DPDKThreadVars *)data;
TmSlot *s = (TmSlot *)slot;
ptv->slot = s->slot_next;
PacketPoolWait();
while (1) {
if (unlikely(suricata_ctl_flags != 0)) {
SCLogDebug("Stopping Suricata!");
DPDKDumpCounters(ptv);
break;
}
nb_rx = rte_eth_rx_burst(ptv->port_id, ptv->queue_id, ptv->received_mbufs, BURST_SIZE);
if (unlikely(nb_rx == 0)) {
continue;
}
ptv->pkts += (uint64_t)nb_rx;
for (uint16_t i = 0; i < nb_rx; i++) {
p = PacketGetFromQueueOrAlloc();
if (unlikely(p == NULL)) {
continue;
}
PKT_SET_SRC(p, PKT_SRC_WIRE);
p->datalink = LINKTYPE_ETHERNET;
if (ptv->checksum_mode == CHECKSUM_VALIDATION_DISABLE) {
p->flags |= PKT_IGNORE_CHECKSUM;
}
DPDKSetTimevalReal(&ptv->machine_start_time, &p->ts);
p->dpdk_v.mbuf = ptv->received_mbufs[i];
p->ReleasePacket = DPDKReleasePacket;
p->dpdk_v.copy_mode = ptv->copy_mode;
p->dpdk_v.out_port_id = ptv->out_port_id;
p->dpdk_v.out_queue_id = ptv->queue_id;
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);
}
}
/* Trigger one dump of stats every second */
current_time = DPDKGetSeconds();
if (current_time != last_dump) {
DPDKDumpCounters(ptv);
last_dump = current_time;
}
StatsSyncCountersIfSignalled(tv);
}
SCReturnInt(TM_ECODE_OK);
}
/**
* \brief Init function for ReceiveDPDK.
*
* \param tv pointer to ThreadVars
* \param initdata pointer to the interface passed from the user
* \param data pointer gets populated with DPDKThreadVars
*
*/
static TmEcode ReceiveDPDKThreadInit(ThreadVars *tv, const void *initdata, void **data)
{
SCEnter();
int retval, thread_numa;
DPDKThreadVars *ptv = NULL;
DPDKIfaceConfig *dpdk_config = (DPDKIfaceConfig *)initdata;
if (initdata == NULL) {
SCLogError(SC_ERR_INVALID_ARGUMENT, "DPDK configuration is NULL in thread initialization");
goto fail;
}
ptv = SCCalloc(1, sizeof(DPDKThreadVars));
if (unlikely(ptv == NULL)) {
SCLogError(SC_ERR_MEM_ALLOC, "Unable to allocate memory");
goto fail;
}
ptv->tv = tv;
ptv->pkts = 0;
ptv->bytes = 0;
ptv->livedev = LiveGetDevice(dpdk_config->iface);
DPDKSetTimevalOfMachineStart(&ptv->machine_start_time);
ptv->capture_dpdk_packets = StatsRegisterCounter("capture.packets", ptv->tv);
ptv->capture_dpdk_rx_errs = StatsRegisterCounter("capture.rx_errors", ptv->tv);
ptv->capture_dpdk_tx_errs = StatsRegisterCounter("capture.tx_errors", ptv->tv);
ptv->copy_mode = dpdk_config->copy_mode;
ptv->checksum_mode = dpdk_config->checksum_mode;
ptv->threads = dpdk_config->threads;
ptv->port_id = dpdk_config->port_id;
ptv->out_port_id = dpdk_config->out_port_id;
uint16_t queue_id = SC_ATOMIC_ADD(dpdk_config->queue_id, 1);
ptv->queue_id = queue_id;
// pass the pointer to the mempool and then forget about it. Mempool is freed in thread deinit.
ptv->pkt_mempool = dpdk_config->pkt_mempool;
dpdk_config->pkt_mempool = NULL;
// the last thread starts the device
if (queue_id == dpdk_config->threads - 1) {
retval = rte_eth_dev_start(ptv->port_id);
if (retval < 0) {
SCLogError(SC_ERR_DPDK_INIT, "Error (%s) during device startup of %s",
rte_strerror(-retval), dpdk_config->iface);
goto fail;
}
struct rte_eth_dev_info dev_info;
retval = rte_eth_dev_info_get(ptv->port_id, &dev_info);
if (retval != 0) {
SCLogError(SC_ERR_DPDK_INIT, "Error (%s) when getting device info of %s",
rte_strerror(-retval), dpdk_config->iface);
goto fail;
}
// some PMDs requires additional actions only after the device has started
DevicePostStartPMDSpecificActions(ptv, dev_info.driver_name);
}
thread_numa = GetNumaNode();
if (thread_numa >= 0 && thread_numa != rte_eth_dev_socket_id(ptv->port_id)) {
SCLogWarning(SC_WARN_DPDK_CONF,
"NIC on NUMA %d but thread on NUMA %d. Decreased performance expected",
rte_eth_dev_socket_id(ptv->port_id), thread_numa);
}
*data = (void *)ptv;
dpdk_config->DerefFunc(dpdk_config);
SCReturnInt(TM_ECODE_OK);
fail:
if (dpdk_config != NULL)
dpdk_config->DerefFunc(dpdk_config);
if (ptv != NULL)
SCFree(ptv);
SCReturnInt(TM_ECODE_FAILED);
}
/**
* \brief This function prints stats to the screen at exit.
* \param tv pointer to ThreadVars
* \param data pointer that gets cast into DPDKThreadVars for ptv
*/
static void ReceiveDPDKThreadExitStats(ThreadVars *tv, void *data)
{
SCEnter();
int retval;
DPDKThreadVars *ptv = (DPDKThreadVars *)data;
if (ptv->queue_id == 0) {
struct rte_eth_stats eth_stats;
char port_name[RTE_ETH_NAME_MAX_LEN];
retval = rte_eth_dev_get_name_by_port(ptv->port_id, port_name);
if (unlikely(retval != 0)) {
SCLogError(SC_ERR_STAT, "Failed to convert port id %d to the interface name: %s",
ptv->port_id, strerror(-retval));
SCReturn;
}
retval = rte_eth_stats_get(ptv->port_id, &eth_stats);
if (unlikely(retval != 0)) {
SCLogError(SC_ERR_STAT, "Failed to get stats for interface %s: %s", port_name,
strerror(-retval));
SCReturn;
}
SCLogPerf("Total RX stats of %s: packets %" PRIu64 " bytes: %" PRIu64 " missed: %" PRIu64
" errors: %" PRIu64 " nombufs: %" PRIu64,
port_name, eth_stats.ipackets, eth_stats.ibytes, eth_stats.imissed,
eth_stats.ierrors, eth_stats.rx_nombuf);
if (ptv->copy_mode == DPDK_COPY_MODE_TAP || ptv->copy_mode == DPDK_COPY_MODE_IPS)
SCLogPerf("Total TX stats of %s: packets %" PRIu64 " bytes: %" PRIu64
" errors: %" PRIu64,
port_name, eth_stats.opackets, eth_stats.obytes, eth_stats.oerrors);
}
DPDKDumpCounters(ptv);
SCLogPerf("(%s) received packets %" PRIu64, tv->name, ptv->pkts);
}
/**
* \brief DeInit function closes dpdk at exit.
* \param tv pointer to ThreadVars
* \param data pointer that gets cast into DPDKThreadVars for ptv
*/
static TmEcode ReceiveDPDKThreadDeinit(ThreadVars *tv, void *data)
{
SCEnter();
DPDKThreadVars *ptv = (DPDKThreadVars *)data;
int retval;
if (ptv->queue_id == 0) {
struct rte_eth_dev_info dev_info;
char iface[RTE_ETH_NAME_MAX_LEN];
retval = rte_eth_dev_get_name_by_port(ptv->port_id, iface);
if (retval != 0) {
SCLogError(SC_ERR_DPDK_INIT, "Error (err=%d) when getting device name (port %d)",
retval, ptv->port_id);
SCReturnInt(TM_ECODE_FAILED);
}
retval = rte_eth_dev_info_get(ptv->port_id, &dev_info);
if (retval != 0) {
SCLogError(SC_ERR_DPDK_INIT, "Error (err=%d) during getting device info (port %s)",
retval, iface);
SCReturnInt(TM_ECODE_FAILED);
}
DevicePreStopPMDSpecificActions(ptv, dev_info.driver_name);
}
rte_eth_dev_stop(ptv->port_id);
if (ptv->copy_mode == DPDK_COPY_MODE_TAP || ptv->copy_mode == DPDK_COPY_MODE_IPS) {
rte_eth_dev_stop(ptv->out_port_id);
}
if (ptv->queue_id == 0 && ptv->pkt_mempool != NULL) {
rte_mempool_free(ptv->pkt_mempool);
ptv->pkt_mempool = NULL;
}
SCFree(ptv);
SCReturnInt(TM_ECODE_OK);
}
/**
* \brief This function passes off to link type decoders.
*
* DecodeDPDK decodes packets from DPDK and passes
* them off to the proper link type decoder.
*
* \param t pointer to ThreadVars
* \param p pointer to the current packet
* \param data pointer that gets cast into DPDKThreadVars for ptv
*/
static TmEcode DecodeDPDK(ThreadVars *tv, Packet *p, void *data)
{
SCEnter();
DecodeThreadVars *dtv = (DecodeThreadVars *)data;
BUG_ON(PKT_IS_PSEUDOPKT(p));
/* update counters */
DecodeUpdatePacketCounters(tv, dtv, p);
/* If suri has set vlan during reading, we increase vlan counter */
if (p->vlan_idx) {
StatsIncr(tv, dtv->counter_vlan);
}
/* call the decoder */
DecodeLinkLayer(tv, dtv, p->datalink, p, GET_PKT_DATA(p), GET_PKT_LEN(p));
PacketDecodeFinalize(tv, dtv, p);
SCReturnInt(TM_ECODE_OK);
}
static TmEcode DecodeDPDKThreadInit(ThreadVars *tv, const void *initdata, void **data)
{
SCEnter();
DecodeThreadVars *dtv = NULL;
dtv = DecodeThreadVarsAlloc(tv);
if (dtv == NULL)
SCReturnInt(TM_ECODE_FAILED);
DecodeRegisterPerfCounters(dtv, tv);
*data = (void *)dtv;
SCReturnInt(TM_ECODE_OK);
}
static TmEcode DecodeDPDKThreadDeinit(ThreadVars *tv, void *data)
{
SCEnter();
if (data != NULL)
DecodeThreadVarsFree(tv, data);
SCReturnInt(TM_ECODE_OK);
}
#endif /* HAVE_DPDK */
/* eof */
/**
* @}
*/

@ -0,0 +1,88 @@
/* Copyright (C) 2021 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Lukas Sismis <lukas.sismis@gmail.com>
*/
#ifndef __SOURCE_DPDK_H__
#define __SOURCE_DPDK_H__
#include "queue.h"
#include "util-dpdk.h"
typedef enum { DPDK_COPY_MODE_NONE, DPDK_COPY_MODE_TAP, DPDK_COPY_MODE_IPS } DpdkCopyModeEnum;
#define DPDK_BURST_TX_WAIT_US 1
/* DPDK Flags */
// General flags
#define DPDK_PROMISC (1 << 0) /**< Promiscuous mode */
#define DPDK_MULTICAST (1 << 1) /**< Enable multicast packets */
// Offloads
#define DPDK_RX_CHECKSUM_OFFLOAD (1 << 4) /**< Enable chsum offload */
typedef struct DPDKIfaceConfig_ {
#ifdef HAVE_DPDK
char iface[RTE_ETH_NAME_MAX_LEN];
uint16_t port_id;
uint16_t socket_id;
/* number of threads - zero means all available */
int threads;
/* IPS mode */
DpdkCopyModeEnum copy_mode;
const char *out_iface;
uint16_t out_port_id;
/* DPDK flags */
uint32_t flags;
ChecksumValidationMode checksum_mode;
/* set maximum transmission unit of the device in bytes */
uint16_t mtu;
uint16_t nb_rx_queues;
uint16_t nb_rx_desc;
uint16_t nb_tx_queues;
uint16_t nb_tx_desc;
uint32_t mempool_size;
uint32_t mempool_cache_size;
struct rte_mempool *pkt_mempool;
SC_ATOMIC_DECLARE(unsigned int, ref);
/* threads bind queue id one by one */
SC_ATOMIC_DECLARE(uint16_t, queue_id);
void (*DerefFunc)(void *);
struct rte_flow *flow[100];
#endif
} DPDKIfaceConfig;
/**
* \brief per packet DPDK vars
*
* This structure is used by the release data system and for IPS
*/
typedef struct DPDKPacketVars_ {
struct rte_mbuf *mbuf;
uint16_t out_port_id;
uint16_t out_queue_id;
uint8_t copy_mode;
} DPDKPacketVars;
void TmModuleReceiveDPDKRegister(void);
void TmModuleDecodeDPDKRegister(void);
#endif /* __SOURCE_DPDK_H__ */

@ -92,6 +92,8 @@
#include "source-af-packet.h"
#include "source-netmap.h"
#include "source-dpdk.h"
#include "source-windivert.h"
#include "source-windivert-prototypes.h"
@ -172,6 +174,8 @@
#include "util-plugin.h"
#include "util-dpdk.h"
#include "rust.h"
/*
@ -358,6 +362,10 @@ static void GlobalsDestroy(SCInstance *suri)
TmModuleRunDeInit();
ParseSizeDeinit();
#ifdef HAVE_DPDK
DPDKCleanupEAL();
#endif
#ifdef HAVE_AF_PACKET
AFPPeersListClean();
#endif
@ -601,6 +609,10 @@ static void PrintUsage(const char *progname)
#ifdef HAVE_PCAP_SET_BUFF
printf("\t--pcap-buffer-size : size of the pcap buffer value from 0 - %i\n",INT_MAX);
#endif /* HAVE_SET_PCAP_BUFF */
#ifdef HAVE_DPDK
printf("\t--dpdk : run in dpdk mode, uses interfaces from "
"suricata.yaml\n");
#endif
#ifdef HAVE_AF_PACKET
printf("\t--af-packet[=<dev>] : run in af-packet mode, no value select interfaces from suricata.yaml\n");
#endif
@ -895,6 +907,10 @@ void RegisterAllModules(void)
TmModuleReceiveWinDivertRegister();
TmModuleVerdictWinDivertRegister();
TmModuleDecodeWinDivertRegister();
/* Dpdk */
TmModuleReceiveDPDKRegister();
TmModuleDecodeDPDKRegister();
}
static TmEcode LoadYamlConfig(SCInstance *suri)
@ -937,6 +953,16 @@ static TmEcode ParseInterfacesList(const int runmode, char *pcap_dev)
/* not an error condition if we have a 1.0 config */
LiveBuildDeviceList("pfring");
}
#ifdef HAVE_DPDK
} else if (runmode == RUNMODE_DPDK) {
char iface_selector[] = "dpdk.interfaces";
int ret = LiveBuildDeviceList(iface_selector);
if (ret == 0) {
SCLogError(
SC_ERR_INITIALIZATION, "No interface found in config for %s", iface_selector);
SCReturnInt(TM_ECODE_FAILED);
}
#endif
#ifdef HAVE_AF_PACKET
} else if (runmode == RUNMODE_AFP_DEV) {
/* iface has been set on command line */
@ -1115,6 +1141,28 @@ static int ParseCommandLineAfpacket(SCInstance *suri, const char *in_arg)
#endif
}
static int ParseCommandLineDpdk(SCInstance *suri, const char *in_arg)
{
#ifdef HAVE_DPDK
if (suri->run_mode == RUNMODE_UNKNOWN) {
suri->run_mode = RUNMODE_DPDK;
} else if (suri->run_mode == RUNMODE_DPDK) {
SCLogInfo("Multiple dpdk options have no effect on Suricata");
} else {
SCLogError(SC_ERR_MULTIPLE_RUN_MODE, "more than one run mode "
"has been specified");
PrintUsage(suri->progname);
return TM_ECODE_FAILED;
}
return TM_ECODE_OK;
#else
SCLogError(SC_ERR_NO_DPDK, "DPDK not enabled. On Linux "
"host, make sure to pass --enable-dpdk to "
"configure when building.");
return TM_ECODE_FAILED;
#endif
}
static int ParseCommandLinePcapLive(SCInstance *suri, const char *in_arg)
{
memset(suri->pcap_dev, 0, sizeof(suri->pcap_dev));
@ -1192,6 +1240,9 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri)
{"pfring-int", required_argument, 0, 0},
{"pfring-cluster-id", required_argument, 0, 0},
{"pfring-cluster-type", required_argument, 0, 0},
#ifdef HAVE_DPDK
{"dpdk", 0, 0, 0},
#endif
{"af-packet", optional_argument, 0, 0},
{"netmap", optional_argument, 0, 0},
{"pcap", optional_argument, 0, 0},
@ -1304,13 +1355,15 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri)
}
else if (strcmp((long_opts[option_index]).name , "capture-plugin-args") == 0){
suri->capture_plugin_args = optarg;
}
else if (strcmp((long_opts[option_index]).name , "af-packet") == 0)
{
} else if (strcmp((long_opts[option_index]).name, "dpdk") == 0) {
if (ParseCommandLineDpdk(suri, optarg) != TM_ECODE_OK) {
return TM_ECODE_FAILED;
}
} else if (strcmp((long_opts[option_index]).name, "af-packet") == 0) {
if (ParseCommandLineAfpacket(suri, optarg) != TM_ECODE_OK) {
return TM_ECODE_FAILED;
}
} else if (strcmp((long_opts[option_index]).name , "netmap") == 0){
} else if (strcmp((long_opts[option_index]).name, "netmap") == 0) {
#ifdef HAVE_NETMAP
if (suri->run_mode == RUNMODE_UNKNOWN) {
suri->run_mode = RUNMODE_NETMAP;
@ -1348,14 +1401,14 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri)
SCLogError(SC_ERR_NFLOG_NOSUPPORT, "NFLOG not enabled.");
return TM_ECODE_FAILED;
#endif /* HAVE_NFLOG */
} else if (strcmp((long_opts[option_index]).name , "pcap") == 0) {
} else if (strcmp((long_opts[option_index]).name, "pcap") == 0) {
if (ParseCommandLinePcapLive(suri, optarg) != TM_ECODE_OK) {
return TM_ECODE_FAILED;
}
} else if(strcmp((long_opts[option_index]).name, "simulate-ips") == 0) {
} else if (strcmp((long_opts[option_index]).name, "simulate-ips") == 0) {
SCLogInfo("Setting IPS mode");
EngineModeSetIPS();
} else if(strcmp((long_opts[option_index]).name, "init-errors-fatal") == 0) {
} else if (strcmp((long_opts[option_index]).name, "init-errors-fatal") == 0) {
if (ConfSetFinal("engine.init-failure-fatal", "1") != 1) {
fprintf(stderr, "ERROR: Failed to set engine init-failure-fatal.\n");
return TM_ECODE_FAILED;

@ -213,6 +213,8 @@ const char * TmModuleTmmIdToString(TmmId id)
CASE_CODE (TMM_DECODEPCAPFILE);
CASE_CODE (TMM_RECEIVEPFRING);
CASE_CODE (TMM_DECODEPFRING);
CASE_CODE(TMM_RECEIVEDPDK);
CASE_CODE(TMM_DECODEDPDK);
CASE_CODE (TMM_RECEIVEPLUGIN);
CASE_CODE (TMM_DECODEPLUGIN);
CASE_CODE (TMM_RESPONDREJECT);

@ -53,6 +53,8 @@ typedef enum {
TMM_DECODEERFDAG,
TMM_RECEIVEAFP,
TMM_DECODEAFP,
TMM_RECEIVEDPDK,
TMM_DECODEDPDK,
TMM_RECEIVENETMAP,
TMM_DECODENETMAP,
TMM_ALERTPCAPINFO,

@ -20,6 +20,7 @@
#include "util-device.h"
#include "util-ioctl.h"
#include "util-misc.h"
#include "util-dpdk.h"
#include "device-storage.h"
@ -359,6 +360,7 @@ int LiveDeviceListClean()
}
RestoreIfaceOffloading(pd);
DPDKCloseDevice(pd);
if (pd->dev)
SCFree(pd->dev);

@ -0,0 +1,56 @@
/* Copyright (C) 2021 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Lukas Sismis <lukas.sismis@gmail.com>
*/
#include "suricata.h"
#include "util-dpdk.h"
void DPDKCleanupEAL(void)
{
#ifdef HAVE_DPDK
if (run_mode == RUNMODE_DPDK) {
int retval = rte_eal_cleanup();
if (retval != 0)
SCLogError(SC_ERR_DPDK_EAL_DEINIT, "EAL cleanup failed: %s", strerror(-retval));
}
#endif
}
void DPDKCloseDevice(LiveDevice *ldev)
{
(void)ldev; // avoid warnings of unused variable
#ifdef HAVE_DPDK
uint16_t port_id;
int retval;
if (run_mode == RUNMODE_DPDK) {
retval = rte_eth_dev_get_port_by_name(ldev->dev, &port_id);
if (retval < 0) {
SCLogError(SC_ERR_DPDK_EAL_DEINIT, "Unable to get port id of \"%s\", error: %s",
ldev->dev, rte_strerror(-retval));
return;
}
SCLogInfo("Closing device %s", ldev->dev);
rte_eth_dev_close(port_id);
}
#endif
}

@ -0,0 +1,44 @@
/* Copyright (C) 2021 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Lukas Sismis <lukas.sismis@gmail.com>
*/
#ifndef UTIL_DPDK_H
#define UTIL_DPDK_H
#ifdef HAVE_DPDK
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_launch.h>
#include <rte_lcore.h>
#include <rte_log.h>
#include <rte_mempool.h>
#include <rte_mbuf.h>
#endif /* HAVE_DPDK */
#include "util-device.h"
void DPDKCleanupEAL(void);
void DPDKCloseDevice(LiveDevice *ldev);
#endif /* UTIL_DPDK_H */

@ -379,6 +379,12 @@ const char * SCErrorToString(SCError err)
CASE_CODE(SC_ERR_RULE_INVALID_UTF8);
CASE_CODE(SC_ERR_HASHING_DISABLED);
CASE_CODE(SC_WARN_THRESH_CONFIG);
CASE_CODE(SC_ERR_NO_DPDK);
CASE_CODE(SC_ERR_DPDK_INIT);
CASE_CODE(SC_ERR_DPDK_EAL_INIT);
CASE_CODE(SC_ERR_DPDK_EAL_DEINIT);
CASE_CODE(SC_ERR_DPDK_CONF);
CASE_CODE(SC_WARN_DPDK_CONF);
CASE_CODE (SC_ERR_MAX);
}

@ -369,6 +369,12 @@ typedef enum {
SC_ERR_RULE_INVALID_UTF8,
SC_ERR_HASHING_DISABLED,
SC_WARN_THRESH_CONFIG,
SC_ERR_NO_DPDK,
SC_ERR_DPDK_INIT,
SC_ERR_DPDK_EAL_INIT,
SC_ERR_DPDK_EAL_DEINIT,
SC_ERR_DPDK_CONF,
SC_WARN_DPDK_CONF,
SC_ERR_MAX
} SCError;

@ -649,6 +649,63 @@ af-packet:
#use-mmap: no
#tpacket-v3: yes
dpdk:
eal-params:
proc-type: primary
# DPDK capture support
# RX queues (and TX queues in IPS mode) are assigned to cores in 1:1 ratio
interfaces:
- interface: 0000:3b:00.0 # PCIe address of the NIC port
# Threading: possible values are either "auto" or number of threads
# - auto takes all cores
# in IPS mode it is required to specify the number of cores and the numbers on both interfaces must match
threads: auto
promisc: true # promiscuous mode - capture all packets
multicast: true # enables also detection on multicast packets
checksum-checks: true # if Suricata should validate checksums
checksum-checks-offload: true # if possible offload checksum validation to the NIC (saves Suricata resources)
mtu: 1500 # Set MTU of the device in bytes
# To approximately calculate required amount of space (in bytes) for interface's mempool: mempool-size * mtu
# Make sure you have enough allocated hugepages.
# The optimum size for the packet memory pool (in terms of memory usage) is power of two minus one: n = (2^q - 1)
mempool-size: 65535 # The number of elements in the mbuf pool
# Mempool cache size must be lower or equal to:
# - RTE_MEMPOOL_CACHE_MAX_SIZE (by default 512) and
# - "mempool-size / 1.5"
# It is advised to choose cache_size to have "mempool-size modulo cache_size == 0".
# If this is not the case, some elements will always stay in the pool and will never be used.
# The cache can be disabled if the cache_size argument is set to 0, can be useful to avoid losing objects in cache
# If the value is empty or set to "auto", Suricata will attempt to set cache size of the mempool to a value
# that matches the previously mentioned recommendations
mempool-cache-size: 257
rx-descriptors: 1024
tx-descriptors: 1024
#
# IPS mode for Suricata works in 3 modes - none, tap, ips
# - none: IDS mode only - disables IPS functionality (does not further forward packets)
# - tap: forwards all packets and generates alerts (omits DROP action) This is not DPDK TAP
# - 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
- interface: default
threads: auto
promisc: true
multicast: true
checksum-checks: true
checksum-checks-offload: true
mtu: 1500
mempool-size: 65535
mempool-cache-size: 257
rx-descriptors: 1024
tx-descriptors: 1024
copy-mode: none
copy-iface: none
# Cross platform libpcap capture support
pcap:
- interface: eth0

Loading…
Cancel
Save