You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/src/runmode-af-xdp.c

389 lines
11 KiB
C

/* Copyright (C) 2022 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.
*/
/**
* \ingroup afxdppacket
*
* @{
*/
/**
* \file
*
* \author Richard McConnell <richard_mcconnell@rapid7.com>
*
* AF_XDP socket runmode
*
*/
#define PCAP_DONT_INCLUDE_PCAP_BPF_H 1
#define SC_PCAP_DONT_INCLUDE_PCAP_H 1
#include "suricata-common.h"
#include "tm-threads.h"
#include "conf.h"
#include "runmodes.h"
#include "runmode-af-xdp.h"
#include "output.h"
#include "log-httplog.h"
#include "detect-engine-mpm.h"
#include "alert-fastlog.h"
#include "alert-debuglog.h"
#include "flow-bypass.h"
#include "util-conf.h"
#include "util-debug.h"
#include "util-time.h"
#include "util-cpu.h"
#include "util-affinity.h"
#include "util-device.h"
#include "util-runmodes.h"
#include "util-ioctl.h"
#include "util-ebpf.h"
#include "util-byte.h"
#include "source-af-xdp.h"
#ifdef HAVE_AF_XDP
#include <linux/if_xdp.h>
#include <linux/if_link.h>
#include <xdp/xsk.h>
#endif
const char *RunModeAFXDPGetDefaultMode(void)
{
return "workers";
}
void RunModeIdsAFXDPRegister(void)
{
RunModeRegisterNewRunMode(RUNMODE_AFXDP_DEV, "single", "Single threaded af-xdp mode",
RunModeIdsAFXDPSingle, NULL);
RunModeRegisterNewRunMode(RUNMODE_AFXDP_DEV, "workers",
"Workers af-xdp mode, each thread does all"
" tasks from acquisition to logging",
RunModeIdsAFXDPWorkers, NULL);
return;
}
#ifdef HAVE_AF_XDP
#define DEFAULT_BUSY_POLL_TIME 20
#define DEFAULT_BUSY_POLL_BUDGET 64
#define DEFAULT_GRO_FLUSH_TIMEOUT 2000000
#define DEFAULT_NAPI_HARD_IRQS 2
static void AFXDPDerefConfig(void *conf)
{
AFXDPIfaceConfig *pfp = (AFXDPIfaceConfig *)conf;
/* Pcap config is used only once but cost of this low. */
if (SC_ATOMIC_SUB(pfp->ref, 1) <= 1) {
SCFree(pfp);
}
}
static TmEcode ConfigSetThreads(AFXDPIfaceConfig *aconf, const char *entry_str)
{
SCEnter();
const char *active_runmode = RunmodeGetActive();
if (active_runmode && !strcmp("single", active_runmode)) {
aconf->threads = 1;
SCReturnInt(0);
}
if (entry_str == NULL) {
SCLogError("Number of threads for interface \"%s\" not specified", aconf->iface);
SCReturnInt(TM_ECODE_FAILED);
}
const int nr_queues = GetIfaceRSSQueuesNum(aconf->iface);
if (strcmp(entry_str, "auto") == 0) {
const int nr_cores = (int)UtilCpuGetNumProcessorsOnline();
/* Threads limited to MIN(cores vs queues) */
aconf->threads = (nr_cores <= nr_queues) ? nr_cores : nr_queues;
const char *sys_type = nr_cores <= nr_queues ? "cores" : "queues";
SCLogPerf("%u %s, so using %u threads", aconf->threads, sys_type, aconf->threads);
SCReturnInt(TM_ECODE_OK);
}
if (StringParseInt32(&aconf->threads, 10, 0, entry_str) < 0) {
SCLogError("Threads entry for interface %s contain non-numerical characters - \"%s\"",
aconf->iface, entry_str);
SCReturnInt(TM_ECODE_FAILED);
}
if (aconf->threads < 0) {
SCLogError("Interface %s has a negative number of threads", aconf->iface);
SCReturnInt(TM_ECODE_FAILED);
}
if (aconf->threads > nr_queues) {
SCLogWarning(
"Selected threads greater than configured queues, using: %d thread(s)", nr_queues);
aconf->threads = nr_queues;
}
SCReturnInt(TM_ECODE_OK);
}
/**
* \brief extract information from config file
*
* The returned structure will be freed by the thread init function.
* This is thus necessary to copy the structure before giving it
* to thread or to reparse the file for each thread (and thus have
* new structure.
*
* \return a AFXDPIfaceConfig corresponding to the interface name
*/
static void *ParseAFXDPConfig(const char *iface)
{
const char *confstr = NULL;
ConfNode *if_root;
ConfNode *if_default = NULL;
ConfNode *af_xdp_node = NULL;
int conf_val = 0;
intmax_t conf_val_int = 0;
bool boolval = false;
if (iface == NULL) {
return NULL;
}
AFXDPIfaceConfig *aconf = SCCalloc(1, sizeof(*aconf));
if (unlikely(aconf == NULL)) {
return NULL;
}
/* default/basic config setup */
strlcpy(aconf->iface, iface, sizeof(aconf->iface));
aconf->DerefFunc = AFXDPDerefConfig;
aconf->threads = 1;
aconf->promisc = 1;
aconf->enable_busy_poll = true;
aconf->busy_poll_time = DEFAULT_BUSY_POLL_TIME;
aconf->busy_poll_budget = DEFAULT_BUSY_POLL_BUDGET;
aconf->mode = XDP_FLAGS_UPDATE_IF_NOEXIST;
aconf->gro_flush_timeout = DEFAULT_GRO_FLUSH_TIMEOUT;
aconf->napi_defer_hard_irqs = DEFAULT_NAPI_HARD_IRQS;
aconf->mem_alignment = XSK_UMEM__DEFAULT_FLAGS;
/* Find initial node */
af_xdp_node = ConfGetNode("af-xdp");
if (af_xdp_node == NULL) {
SCLogInfo("unable to find af-xdp config using default values");
goto finalize;
}
if_root = ConfFindDeviceConfig(af_xdp_node, iface);
if_default = ConfFindDeviceConfig(af_xdp_node, "default");
if (if_root == NULL && if_default == NULL) {
SCLogInfo("unable to find af-xdp config for "
"interface \"%s\" or \"default\", using default values",
iface);
goto finalize;
}
/* 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;
}
/* Threading */
confstr = "auto";
(void)ConfGetChildValueWithDefault(if_root, if_default, "threads", &confstr);
if (ConfigSetThreads(aconf, confstr) != TM_ECODE_OK) {
aconf->DerefFunc(aconf);
return NULL;
}
SC_ATOMIC_RESET(aconf->ref);
(void)SC_ATOMIC_ADD(aconf->ref, aconf->threads);
/* Promisc Mode */
(void)ConfGetChildValueBoolWithDefault(if_root, if_default, "disable-promisc", (int *)&boolval);
if (boolval) {
SCLogConfig("Disabling promiscuous mode on iface %s", aconf->iface);
aconf->promisc = 0;
}
#ifdef HAVE_AF_XDP
/* AF_XDP socket mode options */
if (ConfGetChildValueWithDefault(if_root, if_default, "force-xdp-mode", &confstr) == 1) {
if (strncasecmp(confstr, "drv", 3) == 0) {
aconf->mode |= XDP_FLAGS_DRV_MODE;
} else if (strncasecmp(confstr, "skb", 3) == 0) {
aconf->mode |= XDP_FLAGS_SKB_MODE;
} else if (strncasecmp(confstr, "none", 4) == 0) {
} else {
SCLogWarning("Incorrect af-xdp xdp-mode setting, default (none) shall be applied");
}
}
/* copy and zerocopy binding options */
if (ConfGetChildValueWithDefault(if_root, if_default, "force-bind-mode", &confstr) == 1) {
if (strncasecmp(confstr, "zero", 4) == 0) {
aconf->bind_flags |= XDP_ZEROCOPY;
} else if (strncasecmp(confstr, "copy", 4) == 0) {
aconf->bind_flags |= XDP_COPY;
} else if (strncasecmp(confstr, "none", 4) == 0) {
} else {
SCLogWarning("Incorrect af-xdp copy-mode setting, default (none) shall be applied");
}
}
/* memory alignment mode selection */
if (ConfGetChildValueWithDefault(if_root, if_default, "mem-unaligned", &confstr) == 1) {
if (strncasecmp(confstr, "yes", 3) == 0) {
aconf->mem_alignment = XDP_UMEM_UNALIGNED_CHUNK_FLAG;
}
}
/* Busy polling options */
if (ConfGetChildValueBoolWithDefault(if_root, if_default, "enable-busy-poll", &conf_val) == 1) {
if (conf_val == 0) {
aconf->enable_busy_poll = false;
}
}
if (aconf->enable_busy_poll) {
if (ConfGetChildValueIntWithDefault(if_root, if_default, "busy-poll-time", &conf_val_int) ==
1) {
if (conf_val_int) {
aconf->busy_poll_time = conf_val_int;
}
}
if (ConfGetChildValueIntWithDefault(
if_root, if_default, "busy-poll-budget", &conf_val_int) == 1) {
if (conf_val_int) {
aconf->busy_poll_budget = conf_val_int;
}
}
/* 0 value is valid for these Linux tunable's */
if (ConfGetChildValueIntWithDefault(
if_root, if_default, "gro-flush-timeout", &conf_val_int) == 1) {
aconf->gro_flush_timeout = conf_val_int;
}
if (ConfGetChildValueIntWithDefault(
if_root, if_default, "napi-defer-hard-irq", &conf_val_int) == 1) {
aconf->napi_defer_hard_irqs = conf_val_int;
}
}
#endif
finalize:
if (LiveGetOffload() == 0) {
if (GetIfaceOffloading(iface, 0, 1) == 1) {
SCLogWarning("Using AF_XDP with offloading activated leads to capture problems");
}
} else {
DisableIfaceOffloading(LiveGetDevice(iface), 0, 1);
}
return aconf;
}
static int AFXDPConfigGetThreadsCount(void *conf)
{
if (conf == NULL)
FatalError("Configuration file is NULL");
AFXDPIfaceConfig *afxdp_conf = (AFXDPIfaceConfig *)conf;
return afxdp_conf->threads;
}
#endif /* HAVE_AF_XDP */
/**
* \brief Single thread version of the AF_XDP processing.
*/
int RunModeIdsAFXDPSingle(void)
{
SCEnter();
#ifdef HAVE_AF_XDP
int ret;
const char *live_dev = NULL;
TimeModeSetLive();
(void)ConfGet("af-xdp.live-interface", &live_dev);
if (AFXDPQueueProtectionInit() != TM_ECODE_OK) {
FatalError("Unable to init AF_XDP queue protection.");
}
ret = RunModeSetLiveCaptureSingle(ParseAFXDPConfig, AFXDPConfigGetThreadsCount, "ReceiveAFXDP",
"DecodeAFXDP", thread_name_single, live_dev);
if (ret != 0) {
FatalError("Unable to start runmode");
}
SCLogDebug("RunModeIdsAFXDPSingle initialised");
#endif /* HAVE_AF_XDP */
SCReturnInt(0);
}
/**
* \brief Workers version of the AF_XDP processing.
*
* Start N threads with each thread doing all the work.
*
*/
int RunModeIdsAFXDPWorkers(void)
{
SCEnter();
#ifdef HAVE_AF_XDP
int ret;
const char *live_dev = NULL;
TimeModeSetLive();
(void)ConfGet("af-xdp.live-interface", &live_dev);
if (AFXDPQueueProtectionInit() != TM_ECODE_OK) {
FatalError("Unable to init AF_XDP queue protection.");
}
ret = RunModeSetLiveCaptureWorkers(ParseAFXDPConfig, AFXDPConfigGetThreadsCount, "ReceiveAFXDP",
"DecodeAFXDP", thread_name_workers, live_dev);
if (ret != 0) {
FatalError("Unable to start runmode");
}
SCLogDebug("RunModeIdsAFXDPWorkers initialised");
#endif /* HAVE_AF_XDP */
SCReturnInt(0);
}
/**
* @}
*/