summaryrefslogtreecommitdiffstats
path: root/src/runmode-netmap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/runmode-netmap.c')
-rw-r--r--src/runmode-netmap.c492
1 files changed, 492 insertions, 0 deletions
diff --git a/src/runmode-netmap.c b/src/runmode-netmap.c
new file mode 100644
index 0000000..927dc71
--- /dev/null
+++ b/src/runmode-netmap.c
@@ -0,0 +1,492 @@
+/* Copyright (C) 2014-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 netmap
+*
+* @{
+*/
+
+/**
+ * \file
+ *
+ * \author Aleksey Katargin <gureedo@gmail.com>
+ * \author Bill Meeks <billmeeks8@gmail.com>
+ *
+ * Netmap runmode
+ *
+ */
+
+#include "suricata-common.h"
+#include "decode.h"
+#include "runmodes.h"
+#include "runmode-netmap.h"
+#include "util-runmodes.h"
+#include "util-ioctl.h"
+#include "util-byte.h"
+#include "util-time.h"
+
+#ifdef HAVE_NETMAP
+#define NETMAP_WITH_LIBS
+#include <net/netmap_user.h>
+#endif /* HAVE_NETMAP */
+
+#include "source-netmap.h"
+#include "util-conf.h"
+#include "suricata.h"
+#include "util-bpf.h"
+
+extern uint16_t max_pending_packets;
+
+const char *RunModeNetmapGetDefaultMode(void)
+{
+ return "workers";
+}
+
+static int NetmapRunModeIsIPS(void)
+{
+ int nlive = LiveGetDeviceCount();
+ int ldev;
+ ConfNode *if_root;
+ ConfNode *if_default = NULL;
+ ConfNode *netmap_node;
+ int has_ips = 0;
+ int has_ids = 0;
+
+ /* Find initial node */
+ netmap_node = ConfGetNode("netmap");
+ if (netmap_node == NULL) {
+ return 0;
+ }
+
+ if_default = ConfNodeLookupKeyValue(netmap_node, "interface", "default");
+
+ for (ldev = 0; ldev < nlive; ldev++) {
+ const char *live_dev = LiveGetDeviceName(ldev);
+ if (live_dev == NULL) {
+ SCLogError("Problem with config file");
+ return 0;
+ }
+ const char *copymodestr = NULL;
+ if_root = ConfNodeLookupKeyValue(netmap_node, "interface", live_dev);
+
+ if (if_root == NULL) {
+ if (if_default == NULL) {
+ SCLogError("Problem with config file");
+ return 0;
+ }
+ if_root = if_default;
+ }
+
+ if (ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", &copymodestr) == 1) {
+ if (strcmp(copymodestr, "ips") == 0) {
+ has_ips = 1;
+ } else {
+ has_ids = 1;
+ }
+ } else {
+ has_ids = 1;
+ }
+ }
+
+ if (has_ids && has_ips) {
+ SCLogWarning("Netmap using both IPS and TAP/IDS mode, this will not be "
+ "allowed in Suricata 8 due to undefined behavior. See ticket #5588.");
+ for (ldev = 0; ldev < nlive; ldev++) {
+ const char *live_dev = LiveGetDeviceName(ldev);
+ if (live_dev == NULL) {
+ SCLogError("Problem with config file");
+ return 0;
+ }
+ if_root = ConfNodeLookupKeyValue(netmap_node, "interface", live_dev);
+ const char *copymodestr = NULL;
+
+ if (if_root == NULL) {
+ if (if_default == NULL) {
+ SCLogError("Problem with config file");
+ return 0;
+ }
+ if_root = if_default;
+ }
+
+ if (!((ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", &copymodestr) ==
+ 1) &&
+ (strcmp(copymodestr, "ips") == 0))) {
+ SCLogError("Netmap IPS mode used and interface '%s' is in IDS or TAP mode. "
+ "Sniffing '%s' but expect bad result as stream-inline is activated.",
+ live_dev, live_dev);
+ }
+ }
+ }
+
+ return has_ips;
+}
+
+static void NetmapRunModeEnableIPS(void)
+{
+ if (NetmapRunModeIsIPS()) {
+ SCLogInfo("Netmap: Setting IPS mode");
+ EngineModeSetIPS();
+ }
+}
+
+void RunModeIdsNetmapRegister(void)
+{
+ RunModeRegisterNewRunMode(RUNMODE_NETMAP, "single", "Single threaded netmap mode",
+ RunModeIdsNetmapSingle, NetmapRunModeEnableIPS);
+ RunModeRegisterNewRunMode(RUNMODE_NETMAP, "workers",
+ "Workers netmap mode, each thread does all"
+ " tasks from acquisition to logging",
+ RunModeIdsNetmapWorkers, NetmapRunModeEnableIPS);
+ RunModeRegisterNewRunMode(RUNMODE_NETMAP, "autofp",
+ "Multi-threaded netmap mode. Packets from "
+ "each flow are assigned to a single detect "
+ "thread.",
+ RunModeIdsNetmapAutoFp, NetmapRunModeEnableIPS);
+ return;
+}
+
+#ifdef HAVE_NETMAP
+
+static void NetmapDerefConfig(void *conf)
+{
+ NetmapIfaceConfig *pfp = (NetmapIfaceConfig *)conf;
+ /* config is used only once but cost of this low. */
+ if (SC_ATOMIC_SUB(pfp->ref, 1) == 1) {
+ SCFree(pfp);
+ }
+}
+
+static int ParseNetmapSettings(NetmapIfaceSettings *ns, const char *iface,
+ ConfNode *if_root, ConfNode *if_default)
+{
+ ns->threads = 0;
+ ns->promisc = true;
+ ns->checksum_mode = CHECKSUM_VALIDATION_AUTO;
+ ns->copy_mode = NETMAP_COPY_MODE_NONE;
+ strlcpy(ns->iface, iface, sizeof(ns->iface));
+
+ if (ns->iface[0]) {
+ size_t len = strlen(ns->iface);
+ if (ns->iface[len-1] == '+') {
+ SCLogWarning("%s: interface uses obsolete '+' notation. Using '^' instead", ns->iface);
+ ns->iface[len-1] = '^';
+ ns->sw_ring = true;
+ } else if (ns->iface[len-1] == '^') {
+ ns->sw_ring = true;
+ }
+ }
+
+ /* we will need the base interface name for later */
+ char base_name[IFNAMSIZ];
+ strlcpy(base_name, ns->iface, sizeof(base_name));
+ if (strlen(base_name) > 0 &&
+ (base_name[strlen(base_name) - 1] == '^' || base_name[strlen(base_name) - 1] == '*')) {
+ base_name[strlen(base_name) - 1] = '\0';
+ }
+
+ /* prefixed with netmap or vale means it's not a real interface
+ * and we don't check offloading. */
+ if (strncmp(ns->iface, "netmap:", 7) != 0 &&
+ strncmp(ns->iface, "vale", 4) != 0) {
+ ns->real = true;
+ }
+
+ if (if_root == NULL && if_default == NULL) {
+ SCLogInfo("%s: unable to find netmap 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 */
+ } else if (if_root == NULL) {
+ if_root = if_default;
+ if_default = NULL;
+ }
+
+ const char *threadsstr = NULL;
+ if (ConfGetChildValueWithDefault(if_root, if_default, "threads", &threadsstr) != 1) {
+ ns->threads = 0;
+ ns->threads_auto = true;
+ } else {
+ if (strcmp(threadsstr, "auto") == 0) {
+ ns->threads = 0;
+ ns->threads_auto = true;
+ } else {
+ if (StringParseUint16(&ns->threads, 10, 0, threadsstr) < 0) {
+ SCLogWarning("%s: invalid config value for threads: %s, resetting to 0", iface,
+ threadsstr);
+ ns->threads = 0;
+ }
+ }
+ }
+
+ ConfSetBPFFilter(if_root, if_default, iface, &ns->bpf_filter);
+
+ int boolval = 0;
+ (void)ConfGetChildValueBoolWithDefault(if_root, if_default, "disable-promisc", (int *)&boolval);
+ if (boolval) {
+ SCLogInfo("%s: disabling promiscuous mode", ns->iface);
+ ns->promisc = false;
+ }
+
+ const char *tmpctype;
+ if (ConfGetChildValueWithDefault(if_root, if_default,
+ "checksum-checks", &tmpctype) == 1)
+ {
+ if (strcmp(tmpctype, "auto") == 0) {
+ ns->checksum_mode = CHECKSUM_VALIDATION_AUTO;
+ } else if (ConfValIsTrue(tmpctype)) {
+ ns->checksum_mode = CHECKSUM_VALIDATION_ENABLE;
+ } else if (ConfValIsFalse(tmpctype)) {
+ ns->checksum_mode = CHECKSUM_VALIDATION_DISABLE;
+ } else {
+ SCLogWarning("%s: invalid value for checksum-checks '%s'", iface, tmpctype);
+ }
+ }
+
+ const char *copymodestr;
+ if (ConfGetChildValueWithDefault(if_root, if_default,
+ "copy-mode", &copymodestr) == 1)
+ {
+ if (strcmp(copymodestr, "ips") == 0) {
+ ns->copy_mode = NETMAP_COPY_MODE_IPS;
+ } else if (strcmp(copymodestr, "tap") == 0) {
+ ns->copy_mode = NETMAP_COPY_MODE_TAP;
+ } else {
+ SCLogWarning("%s: invalid copy-mode %s (valid are tap, ips)", iface, copymodestr);
+ }
+ }
+
+finalize:
+
+ ns->ips = (ns->copy_mode != NETMAP_COPY_MODE_NONE);
+
+ if (ns->threads_auto) {
+ /* As NetmapGetRSSCount used to be broken on Linux,
+ * fall back to GetIfaceRSSQueuesNum if needed. */
+ ns->threads = NetmapGetRSSCount(base_name);
+ if (ns->threads == 0) {
+ /* need to use base_name of interface here */
+ ns->threads = GetIfaceRSSQueuesNum(base_name);
+ }
+ }
+ if (ns->threads <= 0) {
+ ns->threads = 1;
+ }
+
+ return 0;
+}
+
+/**
+ * \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 NetmapIfaceConfig corresponding to the interface name
+ */
+static void *ParseNetmapConfig(const char *iface_name)
+{
+ ConfNode *if_root = NULL;
+ ConfNode *if_default = NULL;
+ const char *out_iface = NULL;
+
+ if (iface_name == NULL) {
+ return NULL;
+ }
+
+ NetmapIfaceConfig *aconf = SCCalloc(1, sizeof(*aconf));
+ if (unlikely(aconf == NULL)) {
+ return NULL;
+ }
+
+ aconf->DerefFunc = NetmapDerefConfig;
+ strlcpy(aconf->iface_name, iface_name, sizeof(aconf->iface_name));
+ SC_ATOMIC_INIT(aconf->ref);
+ (void) SC_ATOMIC_ADD(aconf->ref, 1);
+
+ /* Find initial node */
+ ConfNode *netmap_node = ConfGetNode("netmap");
+ if (netmap_node == NULL) {
+ SCLogInfo("%s: unable to find netmap config using default value", iface_name);
+ } else {
+ if_root = ConfFindDeviceConfig(netmap_node, aconf->iface_name);
+ if_default = ConfFindDeviceConfig(netmap_node, "default");
+ }
+
+ /* parse settings for capture iface */
+ ParseNetmapSettings(&aconf->in, aconf->iface_name, if_root, if_default);
+
+ /* if we have a copy iface, parse that as well */
+ if (netmap_node != NULL &&
+ ConfGetChildValueWithDefault(if_root, if_default, "copy-iface", &out_iface) == 1)
+ {
+ if (strlen(out_iface) > 0) {
+ if_root = ConfFindDeviceConfig(netmap_node, out_iface);
+ ParseNetmapSettings(&aconf->out, out_iface, if_root, if_default);
+ }
+ }
+
+ int ring_count = NetmapGetRSSCount(aconf->iface_name);
+ if (strlen(aconf->iface_name) > 0 &&
+ (aconf->iface_name[strlen(aconf->iface_name) - 1] == '^' ||
+ aconf->iface_name[strlen(aconf->iface_name) - 1] == '*')) {
+ SCLogDebug("%s -- using %d netmap host ring pair%s", aconf->iface_name, ring_count,
+ ring_count == 1 ? "" : "s");
+ } else {
+ SCLogDebug("%s -- using %d netmap ring pair%s", aconf->iface_name, ring_count,
+ ring_count == 1 ? "" : "s");
+ }
+
+ for (int i = 0; i < ring_count; i++) {
+ char live_buf[32] = { 0 };
+ snprintf(live_buf, sizeof(live_buf), "netmap%d", i);
+ LiveRegisterDevice(live_buf);
+ }
+
+ /* we need the base interface name with any trailing software
+ * ring marker stripped for HW offloading checks */
+ char base_name[sizeof(aconf->in.iface)];
+ strlcpy(base_name, aconf->in.iface, sizeof(base_name));
+ /* for a sw_ring enabled device name, strip the trailing char */
+ if (aconf->in.sw_ring) {
+ base_name[strlen(base_name) - 1] = '\0';
+ }
+
+ /* netmap needs all offloading to be disabled */
+ if (aconf->in.real) {
+ if (LiveGetOffload() == 0) {
+ (void)GetIfaceOffloading(base_name, 1, 1);
+ } else {
+ DisableIfaceOffloading(LiveGetDevice(base_name), 1, 1);
+ }
+ }
+
+ SC_ATOMIC_RESET(aconf->ref);
+ (void) SC_ATOMIC_ADD(aconf->ref, aconf->in.threads);
+ SCLogPerf("%s: using %d threads", aconf->iface_name, aconf->in.threads);
+
+ LiveDeviceHasNoStats();
+ return aconf;
+}
+
+static int NetmapConfigGeThreadsCount(void *conf)
+{
+ NetmapIfaceConfig *aconf = (NetmapIfaceConfig *)conf;
+ return aconf->in.threads;
+}
+
+typedef enum { NETMAP_AUTOFP, NETMAP_WORKERS, NETMAP_SINGLE } NetmapRunMode_t;
+
+static int NetmapRunModeInit(NetmapRunMode_t runmode)
+{
+ SCEnter();
+
+ TimeModeSetLive();
+
+ const char *live_dev = NULL;
+ (void)ConfGet("netmap.live-interface", &live_dev);
+
+ const char *runmode_str = "unknown";
+ int ret;
+ switch (runmode) {
+ case NETMAP_AUTOFP:
+ runmode_str = "autofp";
+ ret = RunModeSetLiveCaptureAutoFp(ParseNetmapConfig, NetmapConfigGeThreadsCount,
+ "ReceiveNetmap", "DecodeNetmap", thread_name_autofp, live_dev);
+ break;
+ case NETMAP_WORKERS:
+ runmode_str = "workers";
+ ret = RunModeSetLiveCaptureWorkers(ParseNetmapConfig, NetmapConfigGeThreadsCount,
+ "ReceiveNetmap", "DecodeNetmap", thread_name_workers, live_dev);
+ break;
+ case NETMAP_SINGLE:
+ runmode_str = "single";
+ ret = RunModeSetLiveCaptureSingle(ParseNetmapConfig, NetmapConfigGeThreadsCount,
+ "ReceiveNetmap", "DecodeNetmap", thread_name_single, live_dev);
+ break;
+ }
+ if (ret != 0) {
+ FatalError("Unable to start runmode %s", runmode_str);
+ }
+
+ SCLogDebug("%s initialized", runmode_str);
+
+ SCReturnInt(0);
+}
+
+int RunModeIdsNetmapAutoFp(void)
+{
+ return NetmapRunModeInit(NETMAP_AUTOFP);
+}
+
+/**
+* \brief Single thread version of the netmap processing.
+*/
+int RunModeIdsNetmapSingle(void)
+{
+ return NetmapRunModeInit(NETMAP_SINGLE);
+}
+
+/**
+ * \brief Workers version of the netmap processing.
+ *
+ * Start N threads with each thread doing all the work.
+ *
+ */
+int RunModeIdsNetmapWorkers(void)
+{
+ return NetmapRunModeInit(NETMAP_WORKERS);
+}
+#else
+int RunModeIdsNetmapAutoFp(void)
+{
+ SCEnter();
+ FatalError("Netmap not configured");
+ SCReturnInt(0);
+}
+
+/**
+ * \brief Single thread version of the netmap processing.
+ */
+int RunModeIdsNetmapSingle(void)
+{
+ SCEnter();
+ FatalError("Netmap not configured");
+ SCReturnInt(0);
+}
+
+/**
+* \brief Workers version of the netmap processing.
+*
+* Start N threads with each thread doing all the work.
+*
+*/
+int RunModeIdsNetmapWorkers(void)
+{
+ SCEnter();
+ FatalError("Netmap not configured");
+ SCReturnInt(0);
+}
+#endif // #ifdef HAVE_NETMAP
+
+/**
+* @}
+*/