mirror of https://github.com/OISF/suricata
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.
897 lines
32 KiB
C
897 lines
32 KiB
C
/* Copyright (C) 2011-2024 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 afppacket
|
|
*
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* \author Eric Leblond <eric@regit.org>
|
|
*
|
|
* AF_PACKET socket runmode
|
|
*
|
|
*/
|
|
|
|
#include "suricata-common.h"
|
|
#include "suricata.h"
|
|
#include "tm-threads.h"
|
|
#include "conf.h"
|
|
#include "runmodes.h"
|
|
#include "runmode-af-packet.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-private.h"
|
|
#include "util-runmodes.h"
|
|
#include "util-ioctl.h"
|
|
#include "util-ebpf.h"
|
|
#include "util-byte.h"
|
|
|
|
#include "source-af-packet.h"
|
|
#include "util-bpf.h"
|
|
|
|
extern uint32_t max_pending_packets;
|
|
|
|
const char *RunModeAFPGetDefaultMode(void)
|
|
{
|
|
return "workers";
|
|
}
|
|
|
|
static bool AFPRunModeIsIPS(void)
|
|
{
|
|
int nlive = LiveGetDeviceCount();
|
|
bool has_ips = false;
|
|
bool has_ids = false;
|
|
|
|
SCConfNode *af_packet_node = SCConfGetNode("af-packet");
|
|
if (af_packet_node == NULL) {
|
|
SCLogConfig("no 'af-packet' section in the yaml, default to IDS");
|
|
return false;
|
|
}
|
|
|
|
SCConfNode *if_default = SCConfNodeLookupKeyValue(af_packet_node, "interface", "default");
|
|
|
|
for (int ldev = 0; ldev < nlive; ldev++) {
|
|
const char *live_dev = LiveGetDeviceName(ldev);
|
|
if (live_dev == NULL) {
|
|
SCLogConfig("invalid livedev at index %d, default to IDS", ldev);
|
|
return false;
|
|
}
|
|
SCConfNode *if_root = ConfFindDeviceConfig(af_packet_node, live_dev);
|
|
if (if_root == NULL) {
|
|
if (if_default == NULL) {
|
|
SCLogConfig(
|
|
"no 'af-packet' section for '%s' or 'default' in the yaml, default to IDS",
|
|
live_dev);
|
|
return false;
|
|
}
|
|
if_root = if_default;
|
|
}
|
|
|
|
const char *copymodestr = NULL;
|
|
const char *copyifacestr = NULL;
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "copy-mode", ©modestr) == 1 &&
|
|
SCConfGetChildValue(if_root, "copy-iface", ©ifacestr) == 1) {
|
|
if (strcmp(copymodestr, "ips") == 0) {
|
|
has_ips = true;
|
|
} else {
|
|
has_ids = true;
|
|
}
|
|
} else {
|
|
has_ids = true;
|
|
}
|
|
}
|
|
|
|
if (has_ids && has_ips) {
|
|
SCLogError("using both IPS and TAP/IDS mode is not allowed due to undefined behavior. See "
|
|
"ticket #5588.");
|
|
return false;
|
|
}
|
|
|
|
return has_ips;
|
|
}
|
|
|
|
static int AFPRunModeEnableIPS(void)
|
|
{
|
|
bool r = AFPRunModeIsIPS();
|
|
if (r) {
|
|
SCLogInfo("Setting IPS mode");
|
|
EngineModeSetIPS();
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void RunModeIdsAFPRegister(void)
|
|
{
|
|
RunModeRegisterNewRunMode(RUNMODE_AFP_DEV, "single", "Single threaded af-packet mode",
|
|
RunModeIdsAFPSingle, AFPRunModeEnableIPS);
|
|
RunModeRegisterNewRunMode(RUNMODE_AFP_DEV, "workers",
|
|
"Workers af-packet mode, each thread does all"
|
|
" tasks from acquisition to logging",
|
|
RunModeIdsAFPWorkers, AFPRunModeEnableIPS);
|
|
RunModeRegisterNewRunMode(RUNMODE_AFP_DEV, "autofp",
|
|
"Multi socket AF_PACKET mode. Packets from "
|
|
"each flow are assigned to a single detect "
|
|
"thread.",
|
|
RunModeIdsAFPAutoFp, AFPRunModeEnableIPS);
|
|
}
|
|
|
|
|
|
#ifdef HAVE_AF_PACKET
|
|
|
|
static void AFPDerefConfig(void *conf)
|
|
{
|
|
AFPIfaceConfig *pfp = (AFPIfaceConfig *)conf;
|
|
/* Pcap config is used only once but cost of this low. */
|
|
if (SC_ATOMIC_SUB(pfp->ref, 1) == 1) {
|
|
SCFree(pfp);
|
|
}
|
|
}
|
|
|
|
/* if cluster id is not set, assign it automagically, uniq value per
|
|
* interface. */
|
|
static int cluster_id_auto = 1;
|
|
|
|
/**
|
|
* \brief extract information from config file
|
|
*
|
|
* The returned structure will be freed by the thread init function.
|
|
* This is thus necessary to or copy the structure before giving it
|
|
* to thread or to reparse the file for each thread (and thus have
|
|
* new structure.
|
|
*
|
|
* \return a AFPIfaceConfig corresponding to the interface name
|
|
*/
|
|
static void *ParseAFPConfig(const char *iface)
|
|
{
|
|
const char *threadsstr = NULL;
|
|
SCConfNode *if_root;
|
|
SCConfNode *if_default = NULL;
|
|
SCConfNode *af_packet_node;
|
|
const char *tmpclusterid;
|
|
const char *tmpctype;
|
|
const char *copymodestr;
|
|
intmax_t value;
|
|
int boolval;
|
|
const char *out_iface = NULL;
|
|
int cluster_type = PACKET_FANOUT_HASH;
|
|
const char *ebpf_file = NULL;
|
|
const char *active_runmode = RunmodeGetActive();
|
|
|
|
if (iface == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
AFPIfaceConfig *aconf = SCCalloc(1, sizeof(*aconf));
|
|
if (unlikely(aconf == NULL)) {
|
|
return NULL;
|
|
}
|
|
|
|
strlcpy(aconf->iface, iface, sizeof(aconf->iface));
|
|
aconf->threads = 0;
|
|
SC_ATOMIC_INIT(aconf->ref);
|
|
(void) SC_ATOMIC_ADD(aconf->ref, 1);
|
|
aconf->buffer_size = 0;
|
|
aconf->cluster_id = 1;
|
|
aconf->cluster_type = cluster_type | PACKET_FANOUT_FLAG_DEFRAG;
|
|
aconf->promisc = 1;
|
|
aconf->checksum_mode = CHECKSUM_VALIDATION_KERNEL;
|
|
aconf->DerefFunc = AFPDerefConfig;
|
|
aconf->flags = 0;
|
|
aconf->bpf_filter = NULL;
|
|
aconf->ebpf_lb_file = NULL;
|
|
aconf->ebpf_lb_fd = -1;
|
|
aconf->ebpf_filter_file = NULL;
|
|
aconf->ebpf_filter_fd = -1;
|
|
aconf->out_iface = NULL;
|
|
aconf->copy_mode = AFP_COPY_MODE_NONE;
|
|
aconf->block_timeout = 10;
|
|
aconf->block_size = getpagesize() << AFP_BLOCK_SIZE_DEFAULT_ORDER;
|
|
aconf->v2_block_size = 0;
|
|
#ifdef HAVE_PACKET_EBPF
|
|
aconf->ebpf_t_config.cpus_count = UtilCpuGetNumProcessorsConfigured();
|
|
#endif
|
|
|
|
/* Find initial node */
|
|
af_packet_node = SCConfGetNode("af-packet");
|
|
if (af_packet_node == NULL) {
|
|
SCLogInfo("%s: unable to find af-packet config using default values", iface);
|
|
goto finalize;
|
|
}
|
|
|
|
if_root = ConfFindDeviceConfig(af_packet_node, iface);
|
|
if_default = ConfFindDeviceConfig(af_packet_node, "default");
|
|
|
|
if (if_root == NULL && if_default == NULL) {
|
|
SCLogInfo("%s: unable to find af-packet config for "
|
|
"interface \"%s\" or \"default\", using default values",
|
|
iface, 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;
|
|
}
|
|
|
|
if (active_runmode && !strcmp("single", active_runmode)) {
|
|
aconf->threads = 1;
|
|
} else if (SCConfGetChildValueWithDefault(if_root, if_default, "threads", &threadsstr) != 1) {
|
|
aconf->threads = 0;
|
|
} else {
|
|
if (threadsstr != NULL) {
|
|
if (strcmp(threadsstr, "auto") == 0) {
|
|
aconf->threads = 0;
|
|
} else {
|
|
if (StringParseInt32(&aconf->threads, 10, 0, (const char *)threadsstr) < 0) {
|
|
SCLogWarning("%s: invalid number of "
|
|
"threads, resetting to default",
|
|
iface);
|
|
aconf->threads = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "copy-iface", &out_iface) == 1) {
|
|
if (out_iface != NULL) {
|
|
if (strlen(out_iface) > 0) {
|
|
aconf->out_iface = out_iface;
|
|
if (strcmp(iface, out_iface) == 0) {
|
|
FatalError(
|
|
"Invalid config: interface (%s) and copy-iface (%s) can't be the same",
|
|
iface, out_iface);
|
|
}
|
|
}
|
|
} else {
|
|
SCLogWarning("copy-iface corresponding to %s interface cannot be NULL", iface);
|
|
}
|
|
}
|
|
|
|
(void)SCConfGetChildValueBoolWithDefault(if_root, if_default, "mmap-locked", &boolval);
|
|
if (boolval) {
|
|
SCLogConfig("%s: enabling locked memory for mmap", aconf->iface);
|
|
aconf->flags |= AFP_MMAP_LOCKED;
|
|
}
|
|
|
|
(void)SCConfGetChildValueBoolWithDefault(if_root, if_default, "use-emergency-flush", &boolval);
|
|
if (boolval) {
|
|
SCLogConfig("%s: using emergency ring flush", aconf->iface);
|
|
aconf->flags |= AFP_EMERGENCY_MODE;
|
|
}
|
|
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "copy-mode", ©modestr) == 1) {
|
|
if (aconf->out_iface == NULL) {
|
|
SCLogWarning("%s: copy mode activated but no destination"
|
|
" iface. Disabling feature",
|
|
iface);
|
|
} else if (strlen(copymodestr) <= 0) {
|
|
aconf->out_iface = NULL;
|
|
} else if (strcmp(copymodestr, "ips") == 0) {
|
|
SCLogInfo("%s: AF_PACKET IPS mode activated %s->%s", iface, iface, aconf->out_iface);
|
|
aconf->copy_mode = AFP_COPY_MODE_IPS;
|
|
} else if (strcmp(copymodestr, "tap") == 0) {
|
|
SCLogInfo("%s: AF_PACKET TAP mode activated %s->%s", iface, iface, aconf->out_iface);
|
|
aconf->copy_mode = AFP_COPY_MODE_TAP;
|
|
} else {
|
|
SCLogWarning("Invalid 'copy-mode' (not in tap, ips)");
|
|
}
|
|
}
|
|
|
|
if (SCConfGetChildValueBoolWithDefault(if_root, if_default, "tpacket-v3", &boolval) == 1) {
|
|
if (boolval) {
|
|
if (strcasecmp(RunmodeGetActive(), "workers") == 0) {
|
|
SCLogConfig("%s: enabling tpacket v3", aconf->iface);
|
|
aconf->flags |= AFP_TPACKET_V3;
|
|
} else {
|
|
SCLogWarning("%s: tpacket v3 is only implemented for 'workers' runmode."
|
|
" Switching to tpacket v2.",
|
|
iface);
|
|
aconf->flags &= ~AFP_TPACKET_V3;
|
|
}
|
|
} else {
|
|
aconf->flags &= ~AFP_TPACKET_V3;
|
|
}
|
|
} else if (aconf->copy_mode == AFP_COPY_MODE_NONE) {
|
|
// If copy mode is none (passive IDS) and "tpacket-v3" is not
|
|
// present, default to TPACKET_V3.
|
|
SCLogConfig("%s: enabling tpacket v3", aconf->iface);
|
|
aconf->flags |= AFP_TPACKET_V3;
|
|
}
|
|
|
|
if (aconf->flags & AFP_TPACKET_V3 && aconf->copy_mode) {
|
|
SCLogWarning("%s: using tpacket-v3 in IPS or TAP mode will result in high latency", iface);
|
|
}
|
|
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "cluster-id", &tmpclusterid) != 1) {
|
|
aconf->cluster_id = (uint16_t)(cluster_id_auto++);
|
|
} else {
|
|
if (StringParseUint16(&aconf->cluster_id, 10, 0, (const char *)tmpclusterid) < 0) {
|
|
SCLogWarning("%s: invalid cluster_id, resetting to 0", iface);
|
|
aconf->cluster_id = 0;
|
|
}
|
|
SCLogDebug("Going to use cluster-id %" PRIu16, aconf->cluster_id);
|
|
}
|
|
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "cluster-type", &tmpctype) != 1) {
|
|
SCLogConfig("%s: using default cluster-type of \"cluster_flow\"", aconf->iface);
|
|
tmpctype = "cluster_flow";
|
|
}
|
|
|
|
if (strcmp(tmpctype, "cluster_rollover") == 0) {
|
|
SCLogWarning(
|
|
"%s: cluster_rollover deprecated; using \"cluster_flow\" instead. See ticket #6128",
|
|
aconf->iface);
|
|
tmpctype = "cluster_flow";
|
|
}
|
|
|
|
if (strcmp(tmpctype, "cluster_round_robin") == 0) {
|
|
SCLogConfig("%s: using round-robin cluster mode for AF_PACKET", aconf->iface);
|
|
aconf->cluster_type = PACKET_FANOUT_LB;
|
|
cluster_type = PACKET_FANOUT_LB;
|
|
} else if (strcmp(tmpctype, "cluster_flow") == 0) {
|
|
/* Check for defrag. If not set by the user enable it when not
|
|
* inline. */
|
|
uint16_t defrag = 0;
|
|
int conf_val = 0;
|
|
SCLogConfig("%s: using flow cluster mode for AF_PACKET", aconf->iface);
|
|
if (SCConfGetChildValueBoolWithDefault(if_root, if_default, "defrag", &conf_val)) {
|
|
if (conf_val) {
|
|
SCLogConfig("%s: using defrag kernel functionality for AF_PACKET", aconf->iface);
|
|
defrag = PACKET_FANOUT_FLAG_DEFRAG;
|
|
}
|
|
} else {
|
|
/* "defrag" not provided in config, default to true if not inline. */
|
|
if (aconf->copy_mode == 0) {
|
|
SCLogConfig("%s: enabling defrag for AF_PACKET cluster_flow", aconf->iface);
|
|
defrag = PACKET_FANOUT_FLAG_DEFRAG;
|
|
}
|
|
}
|
|
aconf->cluster_type = PACKET_FANOUT_HASH | defrag;
|
|
cluster_type = PACKET_FANOUT_HASH;
|
|
} else if (strcmp(tmpctype, "cluster_cpu") == 0) {
|
|
SCLogConfig("%s: using cpu cluster mode for AF_PACKET", aconf->iface);
|
|
aconf->cluster_type = PACKET_FANOUT_CPU;
|
|
cluster_type = PACKET_FANOUT_CPU;
|
|
} else if (strcmp(tmpctype, "cluster_qm") == 0) {
|
|
SCLogConfig("%s: using queue based cluster mode for AF_PACKET", aconf->iface);
|
|
aconf->cluster_type = PACKET_FANOUT_QM;
|
|
cluster_type = PACKET_FANOUT_QM;
|
|
} else if (strcmp(tmpctype, "cluster_random") == 0) {
|
|
SCLogConfig("%s: using random based cluster mode for AF_PACKET", aconf->iface);
|
|
aconf->cluster_type = PACKET_FANOUT_RND;
|
|
cluster_type = PACKET_FANOUT_RND;
|
|
#ifdef HAVE_PACKET_EBPF
|
|
} else if (strcmp(tmpctype, "cluster_ebpf") == 0) {
|
|
SCLogInfo("%s: using ebpf based cluster mode for AF_PACKET", aconf->iface);
|
|
aconf->cluster_type = PACKET_FANOUT_EBPF;
|
|
cluster_type = PACKET_FANOUT_EBPF;
|
|
#endif
|
|
} else {
|
|
SCLogWarning("invalid cluster-type %s", tmpctype);
|
|
}
|
|
|
|
int conf_val = 0;
|
|
SCConfGetChildValueBoolWithDefault(if_root, if_default, "rollover", &conf_val);
|
|
if (conf_val) {
|
|
SCLogConfig("%s: Rollover requested for AF_PACKET but ignored -- see ticket #6128.",
|
|
aconf->iface);
|
|
SCLogWarning("%s: rollover option has been deprecated and will be ignored as it can cause "
|
|
"severe flow "
|
|
"tracking issues; see ticket #6128.",
|
|
iface);
|
|
}
|
|
|
|
ConfSetBPFFilter(if_root, if_default, iface, &aconf->bpf_filter);
|
|
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "ebpf-lb-file", &ebpf_file) != 1) {
|
|
aconf->ebpf_lb_file = NULL;
|
|
} else {
|
|
#ifdef HAVE_PACKET_EBPF
|
|
SCLogConfig("%s: af-packet will use '%s' as eBPF load balancing file", iface, ebpf_file);
|
|
aconf->ebpf_lb_file = ebpf_file;
|
|
aconf->ebpf_t_config.flags |= EBPF_SOCKET_FILTER;
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_PACKET_EBPF
|
|
boolval = false;
|
|
if (SCConfGetChildValueBoolWithDefault(if_root, if_default, "pinned-maps", &boolval) == 1) {
|
|
if (boolval) {
|
|
SCLogConfig("%s: using pinned maps", aconf->iface);
|
|
aconf->ebpf_t_config.flags |= EBPF_PINNED_MAPS;
|
|
}
|
|
const char *pinned_maps_name = NULL;
|
|
if (SCConfGetChildValueWithDefault(
|
|
if_root, if_default, "pinned-maps-name", &pinned_maps_name) != 1) {
|
|
aconf->ebpf_t_config.pinned_maps_name = pinned_maps_name;
|
|
} else {
|
|
aconf->ebpf_t_config.pinned_maps_name = NULL;
|
|
}
|
|
} else {
|
|
aconf->ebpf_t_config.pinned_maps_name = NULL;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_PACKET_EBPF
|
|
/* One shot loading of the eBPF file */
|
|
if (aconf->ebpf_lb_file && cluster_type == PACKET_FANOUT_EBPF) {
|
|
int ret = EBPFLoadFile(aconf->iface, aconf->ebpf_lb_file, "loadbalancer",
|
|
&aconf->ebpf_lb_fd,
|
|
&aconf->ebpf_t_config);
|
|
if (ret != 0) {
|
|
SCLogWarning("%s: failed to load eBPF lb file", iface);
|
|
}
|
|
}
|
|
#else
|
|
if (aconf->ebpf_lb_file) {
|
|
SCLogError("%s: eBPF support is not built-in", iface);
|
|
}
|
|
#endif
|
|
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "ebpf-filter-file", &ebpf_file) != 1) {
|
|
aconf->ebpf_filter_file = NULL;
|
|
} else {
|
|
#ifdef HAVE_PACKET_EBPF
|
|
SCLogConfig("%s: af-packet will use '%s' as eBPF filter file", iface, ebpf_file);
|
|
aconf->ebpf_filter_file = ebpf_file;
|
|
aconf->ebpf_t_config.mode = AFP_MODE_EBPF_BYPASS;
|
|
aconf->ebpf_t_config.flags |= EBPF_SOCKET_FILTER;
|
|
#endif
|
|
SCConfGetChildValueBoolWithDefault(if_root, if_default, "bypass", &conf_val);
|
|
if (conf_val) {
|
|
#ifdef HAVE_PACKET_EBPF
|
|
SCLogConfig("%s: using bypass kernel functionality for AF_PACKET", aconf->iface);
|
|
aconf->flags |= AFP_BYPASS;
|
|
BypassedFlowManagerRegisterUpdateFunc(EBPFUpdateFlow, NULL);
|
|
#else
|
|
SCLogError("%s: bypass set but eBPF support is not built-in", iface);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* One shot loading of the eBPF file */
|
|
if (aconf->ebpf_filter_file) {
|
|
#ifdef HAVE_PACKET_EBPF
|
|
int ret = EBPFLoadFile(aconf->iface, aconf->ebpf_filter_file, "filter",
|
|
&aconf->ebpf_filter_fd,
|
|
&aconf->ebpf_t_config);
|
|
if (ret != 0) {
|
|
SCLogWarning("%s: failed to load eBPF filter file", iface);
|
|
}
|
|
#else
|
|
SCLogError("%s: eBPF support is not built-in", iface);
|
|
#endif
|
|
}
|
|
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "xdp-filter-file", &ebpf_file) != 1) {
|
|
aconf->xdp_filter_file = NULL;
|
|
} else {
|
|
#ifdef HAVE_PACKET_XDP
|
|
aconf->ebpf_t_config.mode = AFP_MODE_XDP_BYPASS;
|
|
aconf->ebpf_t_config.flags |= EBPF_XDP_CODE;
|
|
aconf->xdp_filter_file = ebpf_file;
|
|
SCConfGetChildValueBoolWithDefault(if_root, if_default, "bypass", &conf_val);
|
|
if (conf_val) {
|
|
SCLogConfig("%s: using bypass kernel functionality for AF_PACKET", aconf->iface);
|
|
aconf->flags |= AFP_XDPBYPASS;
|
|
/* if maps are pinned we need to read them at start */
|
|
if (aconf->ebpf_t_config.flags & EBPF_PINNED_MAPS) {
|
|
RunModeEnablesBypassManager();
|
|
struct ebpf_timeout_config *ebt = SCCalloc(1, sizeof(struct ebpf_timeout_config));
|
|
if (ebt == NULL) {
|
|
SCLogError("%s: flow bypass alloc error", iface);
|
|
} else {
|
|
memcpy(ebt, &(aconf->ebpf_t_config), sizeof(struct ebpf_timeout_config));
|
|
BypassedFlowManagerRegisterCheckFunc(NULL,
|
|
EBPFCheckBypassedFlowCreate,
|
|
(void *)ebt);
|
|
}
|
|
}
|
|
BypassedFlowManagerRegisterUpdateFunc(EBPFUpdateFlow, NULL);
|
|
}
|
|
#else
|
|
SCLogWarning("%s: XDP filter set but XDP support is not built-in", iface);
|
|
#endif
|
|
#ifdef HAVE_PACKET_XDP
|
|
const char *xdp_mode;
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "xdp-mode", &xdp_mode) != 1) {
|
|
aconf->xdp_mode = XDP_FLAGS_SKB_MODE;
|
|
} else {
|
|
if (!strcmp(xdp_mode, "soft")) {
|
|
aconf->xdp_mode = XDP_FLAGS_SKB_MODE;
|
|
} else if (!strcmp(xdp_mode, "driver")) {
|
|
aconf->xdp_mode = XDP_FLAGS_DRV_MODE;
|
|
} else if (!strcmp(xdp_mode, "hw")) {
|
|
aconf->xdp_mode = XDP_FLAGS_HW_MODE;
|
|
aconf->ebpf_t_config.flags |= EBPF_XDP_HW_MODE;
|
|
} else {
|
|
SCLogWarning("Invalid xdp-mode value: '%s'", xdp_mode);
|
|
}
|
|
}
|
|
|
|
boolval = true;
|
|
if (SCConfGetChildValueBoolWithDefault(if_root, if_default, "use-percpu-hash", &boolval) ==
|
|
1) {
|
|
if (!boolval) {
|
|
SCLogConfig("%s: not using percpu hash", aconf->iface);
|
|
aconf->ebpf_t_config.cpus_count = 1;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* One shot loading of the eBPF file */
|
|
if (aconf->xdp_filter_file) {
|
|
#ifdef HAVE_PACKET_XDP
|
|
int ret = EBPFLoadFile(aconf->iface, aconf->xdp_filter_file, "xdp",
|
|
&aconf->xdp_filter_fd,
|
|
&aconf->ebpf_t_config);
|
|
switch (ret) {
|
|
case 1:
|
|
SCLogInfo("%s: loaded pinned maps from sysfs", iface);
|
|
break;
|
|
case -1:
|
|
SCLogWarning("%s: failed to load XDP filter file", iface);
|
|
break;
|
|
case 0:
|
|
ret = EBPFSetupXDP(aconf->iface, aconf->xdp_filter_fd, aconf->xdp_mode);
|
|
if (ret != 0) {
|
|
SCLogWarning("%s: failed to set up XDP", iface);
|
|
} else {
|
|
/* Try to get the xdp-cpu-redirect key */
|
|
const char *cpuset;
|
|
if (SCConfGetChildValueWithDefault(
|
|
if_root, if_default, "xdp-cpu-redirect", &cpuset) == 1) {
|
|
SCLogConfig("%s: Setting up CPU map XDP", iface);
|
|
SCConfNode *node =
|
|
SCConfGetChildWithDefault(if_root, if_default, "xdp-cpu-redirect");
|
|
if (node == NULL) {
|
|
SCLogError("Previously found node has disappeared");
|
|
} else {
|
|
EBPFBuildCPUSet(node, aconf->iface);
|
|
}
|
|
} else {
|
|
/* It will just set CPU count to 0 */
|
|
EBPFBuildCPUSet(NULL, aconf->iface);
|
|
}
|
|
}
|
|
/* we have a peer and we use bypass so we can set up XDP iface redirect */
|
|
if (aconf->out_iface) {
|
|
EBPFSetPeerIface(aconf->iface, aconf->out_iface);
|
|
}
|
|
}
|
|
#else
|
|
SCLogError("%s: XDP support is not built-in", iface);
|
|
#endif
|
|
}
|
|
|
|
if ((SCConfGetChildValueIntWithDefault(if_root, if_default, "buffer-size", &value)) == 1) {
|
|
aconf->buffer_size = value;
|
|
} else {
|
|
aconf->buffer_size = 0;
|
|
}
|
|
if ((SCConfGetChildValueIntWithDefault(if_root, if_default, "ring-size", &value)) == 1) {
|
|
aconf->ring_size = value;
|
|
}
|
|
|
|
if ((SCConfGetChildValueIntWithDefault(if_root, if_default, "block-size", &value)) == 1) {
|
|
if (value % getpagesize()) {
|
|
SCLogWarning("%s: block-size %" PRIuMAX " must be a multiple of pagesize (%u).", iface,
|
|
value, getpagesize());
|
|
} else {
|
|
aconf->block_size = value;
|
|
}
|
|
}
|
|
|
|
if ((SCConfGetChildValueIntWithDefault(if_root, if_default, "block-timeout", &value)) == 1) {
|
|
aconf->block_timeout = value;
|
|
} else {
|
|
aconf->block_timeout = 10;
|
|
}
|
|
|
|
if ((SCConfGetChildValueIntWithDefault(if_root, if_default, "v2-block-size", &value)) == 1) {
|
|
if (value % getpagesize()) {
|
|
SCLogWarning("%s: v2-block-size %" PRIuMAX " must be a multiple of pagesize (%u).",
|
|
iface, value, getpagesize());
|
|
} else {
|
|
aconf->v2_block_size = value;
|
|
}
|
|
}
|
|
|
|
(void)SCConfGetChildValueBoolWithDefault(if_root, if_default, "disable-promisc", &boolval);
|
|
if (boolval) {
|
|
SCLogConfig("%s: disabling promiscuous mode", aconf->iface);
|
|
aconf->promisc = 0;
|
|
}
|
|
|
|
if (SCConfGetChildValueWithDefault(if_root, if_default, "checksum-checks", &tmpctype) == 1) {
|
|
if (strcmp(tmpctype, "auto") == 0) {
|
|
aconf->checksum_mode = CHECKSUM_VALIDATION_AUTO;
|
|
} else if (SCConfValIsTrue(tmpctype)) {
|
|
aconf->checksum_mode = CHECKSUM_VALIDATION_ENABLE;
|
|
} else if (SCConfValIsFalse(tmpctype)) {
|
|
aconf->checksum_mode = CHECKSUM_VALIDATION_DISABLE;
|
|
} else if (strcmp(tmpctype, "kernel") == 0) {
|
|
aconf->checksum_mode = CHECKSUM_VALIDATION_KERNEL;
|
|
} else {
|
|
SCLogWarning("%s: invalid value for checksum-checks", aconf->iface);
|
|
}
|
|
}
|
|
|
|
finalize:
|
|
|
|
/* if the number of threads is not 1, we need to first check if fanout
|
|
* functions on this system. */
|
|
if (aconf->threads != 1) {
|
|
if (AFPIsFanoutSupported(aconf->cluster_id) == 0) {
|
|
if (aconf->threads != 0) {
|
|
SCLogNotice("%s: fanout not supported on this system, falling "
|
|
"back to 1 capture thread",
|
|
iface);
|
|
}
|
|
aconf->threads = 1;
|
|
}
|
|
}
|
|
|
|
/* try to automagically set the proper number of threads */
|
|
if (aconf->threads == 0) {
|
|
/* for cluster_flow use core count */
|
|
if (cluster_type == PACKET_FANOUT_HASH) {
|
|
aconf->threads = (int)UtilCpuGetNumProcessorsOnline();
|
|
SCLogPerf("%s: cluster_flow: %u cores, using %u threads", iface, aconf->threads,
|
|
aconf->threads);
|
|
|
|
/* for cluster_qm use RSS queue count */
|
|
} else if (cluster_type == PACKET_FANOUT_QM) {
|
|
int rss_queues = GetIfaceRSSQueuesNum(iface);
|
|
if (rss_queues > 0) {
|
|
aconf->threads = rss_queues;
|
|
SCLogPerf("%s: cluster_qm: %d RSS queues, using %u threads", iface, rss_queues,
|
|
aconf->threads);
|
|
}
|
|
}
|
|
|
|
if (aconf->threads) {
|
|
SCLogDebug("using %d threads for interface %s", aconf->threads, iface);
|
|
}
|
|
}
|
|
if (aconf->threads <= 0) {
|
|
aconf->threads = 1;
|
|
}
|
|
SC_ATOMIC_RESET(aconf->ref);
|
|
(void) SC_ATOMIC_ADD(aconf->ref, aconf->threads);
|
|
|
|
if (aconf->ring_size != 0) {
|
|
if (aconf->ring_size * aconf->threads < (int)max_pending_packets) {
|
|
aconf->ring_size = max_pending_packets / aconf->threads + 1;
|
|
SCLogWarning("%s: inefficient setup: ring-size < max_pending_packets. "
|
|
"Resetting to decent value %d.",
|
|
iface, aconf->ring_size);
|
|
/* We want at least that max_pending_packets packets can be handled by the
|
|
* interface. This is generous if we have multiple interfaces listening. */
|
|
}
|
|
} else {
|
|
/* We want that max_pending_packets packets can be handled by suricata
|
|
* for this interface. To take burst into account we multiply the obtained
|
|
* size by 2. */
|
|
aconf->ring_size = max_pending_packets * 2 / aconf->threads;
|
|
}
|
|
|
|
int ltype = AFPGetLinkType(iface);
|
|
switch (ltype) {
|
|
case LINKTYPE_ETHERNET:
|
|
/* af-packet can handle csum offloading */
|
|
if (LiveGetOffload() == 0) {
|
|
if (GetIfaceOffloading(iface, 0, 1) == 1) {
|
|
SCLogWarning(
|
|
"%s: using AF_PACKET with offloads enabled leads to capture problems",
|
|
iface);
|
|
}
|
|
} else {
|
|
DisableIfaceOffloading(LiveGetDevice(iface), 0, 1);
|
|
}
|
|
break;
|
|
case -1:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (active_runmode == NULL || strcmp("workers", active_runmode) != 0) {
|
|
/* If we are using copy mode we need a lock */
|
|
aconf->flags |= AFP_SOCK_PROTECT;
|
|
aconf->flags |= AFP_NEED_PEER;
|
|
}
|
|
|
|
/* Warn if inline and defrag is enabled. */
|
|
if (aconf->copy_mode != AFP_COPY_MODE_NONE && aconf->cluster_type & PACKET_FANOUT_FLAG_DEFRAG) {
|
|
SCLogWarning(
|
|
"%s: AF_PACKET defrag is not recommended for inline use, please disable", iface);
|
|
}
|
|
|
|
/* Warn if not inline and defrag is disabled. */
|
|
if (aconf->copy_mode == AFP_COPY_MODE_NONE && cluster_type == PACKET_FANOUT_HASH &&
|
|
((aconf->cluster_type & PACKET_FANOUT_FLAG_DEFRAG) == 0)) {
|
|
SCLogWarning("%s: AF_PACKET defrag is recommended for IDS cluster_flow", iface);
|
|
}
|
|
|
|
/* For tpacket-v2, warn if defrag is enabled and block-size is
|
|
* less than max defragmented packet size. */
|
|
if ((aconf->flags & AFP_TPACKET_V3) == 0 && (aconf->cluster_type & PACKET_FANOUT_FLAG_DEFRAG) &&
|
|
aconf->v2_block_size > 0 && aconf->v2_block_size < MAX_PACKET_SIZE) {
|
|
SCLogWarning("%s: AF_PACKET v2-block-size is not large enough for max fragmented IP packet "
|
|
"size (%u)",
|
|
iface, MAX_PACKET_SIZE);
|
|
}
|
|
|
|
/* For tpacket-v3, warn if defrag is enabled and block-block-size
|
|
* is less than max defragmented packet size. */
|
|
if ((aconf->flags & AFP_TPACKET_V3) && (aconf->cluster_type & PACKET_FANOUT_FLAG_DEFRAG) &&
|
|
(aconf->block_size < MAX_PACKET_SIZE)) {
|
|
SCLogWarning("%s: AF_PACKET block-size is not large enough for max fragmented IP packet "
|
|
"size (%u)",
|
|
iface, MAX_PACKET_SIZE);
|
|
}
|
|
|
|
/* Warn that if not-inline, tpacket-v3 is the better choice. */
|
|
if (aconf->copy_mode == AFP_COPY_MODE_NONE && (aconf->flags & AFP_TPACKET_V3) == 0) {
|
|
SCLogWarning("%s: AF_PACKET tpacket-v3 is recommended for non-inline operation", iface);
|
|
}
|
|
|
|
return aconf;
|
|
}
|
|
|
|
static int AFPConfigGeThreadsCount(void *conf)
|
|
{
|
|
AFPIfaceConfig *afp = (AFPIfaceConfig *)conf;
|
|
return afp->threads;
|
|
}
|
|
|
|
#endif /* HAVE_AF_PACKET */
|
|
|
|
int RunModeIdsAFPAutoFp(void)
|
|
{
|
|
SCEnter();
|
|
|
|
/* We include only if AF_PACKET is enabled */
|
|
#ifdef HAVE_AF_PACKET
|
|
int ret;
|
|
const char *live_dev = NULL;
|
|
|
|
TimeModeSetLive();
|
|
|
|
(void)SCConfGet("af-packet.live-interface", &live_dev);
|
|
|
|
SCLogDebug("live_dev %s", live_dev);
|
|
|
|
if (AFPPeersListInit() != TM_ECODE_OK) {
|
|
FatalError("Unable to init peers list.");
|
|
}
|
|
|
|
ret = RunModeSetLiveCaptureAutoFp(ParseAFPConfig, AFPConfigGeThreadsCount, "ReceiveAFP",
|
|
"DecodeAFP", thread_name_autofp, live_dev);
|
|
if (ret != 0) {
|
|
FatalError("Unable to start runmode");
|
|
}
|
|
|
|
/* In IPS mode each threads must have a peer */
|
|
if (AFPPeersListCheck() != TM_ECODE_OK) {
|
|
FatalError("Some IPS capture threads did not peer.");
|
|
}
|
|
|
|
SCLogDebug("RunModeIdsAFPAutoFp initialised");
|
|
#endif /* HAVE_AF_PACKET */
|
|
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* \brief Single thread version of the AF_PACKET processing.
|
|
*/
|
|
int RunModeIdsAFPSingle(void)
|
|
{
|
|
SCEnter();
|
|
#ifdef HAVE_AF_PACKET
|
|
int ret;
|
|
const char *live_dev = NULL;
|
|
|
|
TimeModeSetLive();
|
|
|
|
(void)SCConfGet("af-packet.live-interface", &live_dev);
|
|
|
|
if (AFPPeersListInit() != TM_ECODE_OK) {
|
|
FatalError("Unable to init peers list.");
|
|
}
|
|
|
|
ret = RunModeSetLiveCaptureSingle(ParseAFPConfig,
|
|
AFPConfigGeThreadsCount,
|
|
"ReceiveAFP",
|
|
"DecodeAFP", thread_name_single,
|
|
live_dev);
|
|
if (ret != 0) {
|
|
FatalError("Unable to start runmode");
|
|
}
|
|
|
|
/* In IPS mode each threads must have a peer */
|
|
if (AFPPeersListCheck() != TM_ECODE_OK) {
|
|
FatalError("Some IPS capture threads did not peer.");
|
|
}
|
|
|
|
SCLogDebug("RunModeIdsAFPSingle initialised");
|
|
|
|
#endif /* HAVE_AF_PACKET */
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* \brief Workers version of the AF_PACKET processing.
|
|
*
|
|
* Start N threads with each thread doing all the work.
|
|
*
|
|
*/
|
|
int RunModeIdsAFPWorkers(void)
|
|
{
|
|
SCEnter();
|
|
#ifdef HAVE_AF_PACKET
|
|
int ret;
|
|
const char *live_dev = NULL;
|
|
|
|
TimeModeSetLive();
|
|
|
|
(void)SCConfGet("af-packet.live-interface", &live_dev);
|
|
|
|
if (AFPPeersListInit() != TM_ECODE_OK) {
|
|
FatalError("Unable to init peers list.");
|
|
}
|
|
|
|
ret = RunModeSetLiveCaptureWorkers(ParseAFPConfig, AFPConfigGeThreadsCount, "ReceiveAFP",
|
|
"DecodeAFP", thread_name_workers, live_dev);
|
|
if (ret != 0) {
|
|
FatalError("Unable to start runmode");
|
|
}
|
|
|
|
/* In IPS mode each threads must have a peer */
|
|
if (AFPPeersListCheck() != TM_ECODE_OK) {
|
|
FatalError("Some IPS capture threads did not peer.");
|
|
}
|
|
|
|
SCLogDebug("RunModeIdsAFPWorkers initialised");
|
|
|
|
#endif /* HAVE_AF_PACKET */
|
|
SCReturnInt(0);
|
|
}
|
|
|
|
/**
|
|
* @}
|
|
*/
|