]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
pf-ring: add as plugin
authorJason Ish <jason.ish@oisf.net>
Tue, 9 Jul 2024 21:12:29 +0000 (15:12 -0600)
committerVictor Julien <victor@inliniac.net>
Mon, 15 Jul 2024 12:25:34 +0000 (14:25 +0200)
Ticket: #7162

Makefile.am
configure.ac
plugins/Makefile.am [new file with mode: 0644]
plugins/pfring/Makefile.am [new file with mode: 0644]
plugins/pfring/README.md [new file with mode: 0644]
plugins/pfring/plugin.c [new file with mode: 0644]
plugins/pfring/runmode-pfring.c [new file with mode: 0644]
plugins/pfring/runmode-pfring.h [new file with mode: 0644]
plugins/pfring/source-pfring.c [new file with mode: 0644]
plugins/pfring/source-pfring.h [new file with mode: 0644]

index 9b09ab007d19e131f64a4ae8759178e8fc4db746..6e2f77cadc2415b36ef50b3377267afb57eeb4d8 100644 (file)
@@ -11,7 +11,7 @@ EXTRA_DIST = ChangeLog COPYING LICENSE suricata.yaml.in \
             scripts/docs-almalinux9-minimal-build.sh \
             scripts/docs-ubuntu-debian-minimal-build.sh \
             examples/plugins
-SUBDIRS = $(HTP_DIR) rust src qa rules doc contrib etc python ebpf \
+SUBDIRS = $(HTP_DIR) rust src plugins qa rules doc contrib etc python ebpf \
           $(SURICATA_UPDATE_DIR)
 DIST_SUBDIRS = $(SUBDIRS) examples/lib/simple
 
index 7a8be3b6faea8a33d42bd62892373746e0196fce..0626a16f58ac748823d8ea3ff5eb060a7123fe97 100644 (file)
         fi
     ])
 
+    if test "x$enable_pfring" = "xyes"; then
+        AM_CONDITIONAL([BUILD_PFRING], [true])
+    else
+        AM_CONDITIONAL([BUILD_PFRING], [false])
+    fi
+
   # AF_PACKET support
     AC_ARG_ENABLE(af-packet,
            AS_HELP_STRING([--enable-af-packet], [Enable AF_PACKET support [default=yes]]),
@@ -2482,6 +2488,8 @@ AC_CONFIG_FILES(libsuricata-config)
 AC_CONFIG_FILES(examples/plugins/c-json-filetype/Makefile)
 AC_CONFIG_FILES(examples/plugins/ci-capture/Makefile)
 AC_CONFIG_FILES(examples/lib/simple/Makefile examples/lib/simple/Makefile.example)
+AC_CONFIG_FILES(plugins/Makefile)
+AC_CONFIG_FILES(plugins/pfring/Makefile)
 
 AC_OUTPUT
 
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
new file mode 100644 (file)
index 0000000..c2200d1
--- /dev/null
@@ -0,0 +1,5 @@
+SUBDIRS =
+
+if BUILD_PFRING
+SUBDIRS += pfring
+endif
diff --git a/plugins/pfring/Makefile.am b/plugins/pfring/Makefile.am
new file mode 100644 (file)
index 0000000..10cb22b
--- /dev/null
@@ -0,0 +1,12 @@
+pkglib_LTLIBRARIES = pfring.la
+
+pfring_la_SOURCES = runmode-pfring.c source-pfring.c plugin.c
+pfring_la_LDFLAGS = -module -avoid-version -shared
+pfring_la_LIBADD = -lpfring
+
+noinst_HEADERS = \
+       runmode-pfring.h \
+       source-pfring.h
+
+install-exec-hook:
+       cd $(DESTDIR)$(pkglibdir) && $(RM) $(pkglib_LTLIBRARIES)
diff --git a/plugins/pfring/README.md b/plugins/pfring/README.md
new file mode 100644 (file)
index 0000000..d7888f4
--- /dev/null
@@ -0,0 +1,41 @@
+# PF_RING Plugin Capture Plugin
+
+## Building
+
+To build this plugin, built Suricata with the `--enable-pfring` and
+optionally the `--with-libpfring-includes` and
+`--with-libpfring-libraries` command line options.
+
+## Running
+```
+/usr/local/suricata/bin/suricata \
+    --set plugins.0=/usr/local/lib/suricata/pfring.so \
+    --capture-plugin=pfring-plugin \
+    --set pfring.0.interface=eno1
+```
+
+### --set plugins.0=/usr/local/lib/suricata/pfring.so
+
+This command line option tells Suricata about this plugin. This could also
+be done in `suricata.yaml` with the following section:
+```
+plugins:
+  - /usr/local/lib/suricata/pfring.so
+```
+
+### --capture-plugin=pfring-plugin
+
+This is the option that tells Suricata to use a plugin for capture, much like
+`--pcap` tells Suricata to use libpcap or `--af-packet` tells Suricata to use
+AF_PACKET. Here we are telling it to look for a loaded plugin of the name
+`pfring-plugin` to provide the capture method.
+
+### --set pfring.0.interface=eno1
+
+This is just overriding the interface name in the example `pfring` configuration
+found in the default suricata.yaml, which this plugin knows how to read already
+as its based off the PF_RING support in Suricata proper.
+
+There is another command line option `--capture-plugin-args` to pass arbitrary
+data on the command line a capture plugin, but this plugin does not yet handle
+data provided through this command line parameter.
diff --git a/plugins/pfring/plugin.c b/plugins/pfring/plugin.c
new file mode 100644 (file)
index 0000000..0f41ee4
--- /dev/null
@@ -0,0 +1,56 @@
+/* Copyright (C) 2020-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.
+ */
+
+#include "suricata-common.h"
+#include "suricata-plugin.h"
+
+#include "decode.h"
+#include "source-pfring.h"
+#include "runmode-pfring.h"
+#include "util-device.h"
+
+void InitCapturePlugin(const char *args, int plugin_slot, int receive_slot, int decode_slot)
+{
+    LiveBuildDeviceList("plugin");
+    RunModeIdsPfringRegister(plugin_slot);
+    TmModuleReceivePfringRegister(receive_slot);
+    TmModuleDecodePfringRegister(decode_slot);
+}
+
+void SCPluginInit(void)
+{
+    SCCapturePlugin *plugin = SCCalloc(1, sizeof(SCCapturePlugin));
+    if (plugin == NULL) {
+        FatalError("Failed to allocate memory for capture plugin");
+    }
+    plugin->name = "pfring";
+    plugin->Init = InitCapturePlugin;
+    plugin->GetDefaultMode = RunModeIdsPfringGetDefaultMode;
+    SCPluginRegisterCapture(plugin);
+}
+
+const SCPlugin PluginRegistration = {
+    .name = "pfring",
+    .author = "Open Information Security Foundation",
+    .license = "GPLv2",
+    .Init = SCPluginInit,
+};
+
+const SCPlugin *SCPluginRegister()
+{
+    return &PluginRegistration;
+}
diff --git a/plugins/pfring/runmode-pfring.c b/plugins/pfring/runmode-pfring.c
new file mode 100644 (file)
index 0000000..cf64c1b
--- /dev/null
@@ -0,0 +1,520 @@
+/* Copyright (C) 2007-2018 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 "runmode-pfring.h"
+#include "suricata-plugin.h"
+
+#include "flow.h"
+#include "tm-threads.h"
+#include "conf.h"
+#include "runmodes.h"
+#include "source-pfring.h"
+#include "suricata.h"
+#include "util-bpf.h"
+#include "util-debug.h"
+#include "util-time.h"
+#include "util-cpu.h"
+#include "util-runmodes.h"
+#include "util-device.h"
+#include "util-ioctl.h"
+#include "util-byte.h"
+#include "util-conf.h"
+
+#include <pfring.h>
+
+#define PFRING_CONF_V1 1
+#define PFRING_CONF_V2 2
+
+const char *RunModeIdsPfringGetDefaultMode(void)
+{
+    return "workers";
+}
+
+void RunModeIdsPfringRegister(int slot)
+{
+    RunModeRegisterNewRunMode(slot, "autofp",
+            "Multi threaded pfring mode.  Packets from "
+            "each flow are assigned to a single detect "
+            "thread, unlike \"pfring_auto\" where packets "
+            "from the same flow can be processed by any "
+            "detect thread",
+            RunModeIdsPfringAutoFp, NULL);
+    RunModeRegisterNewRunMode(
+            slot, "single", "Single threaded pfring mode", RunModeIdsPfringSingle, NULL);
+    RunModeRegisterNewRunMode(slot, "workers",
+            "Workers pfring mode, each thread does all"
+            " tasks from acquisition to logging",
+            RunModeIdsPfringWorkers, NULL);
+}
+
+static void PfringDerefConfig(void *conf)
+{
+    PfringIfaceConfig *pfp = (PfringIfaceConfig *)conf;
+    if (SC_ATOMIC_SUB(pfp->ref, 1) == 1) {
+        SCFree(pfp);
+    }
+}
+
+/**
+ * \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.
+ *
+ * If old config system is used, then return the same parameters
+ * value for each interface.
+ *
+ * \return a PfringIfaceConfig corresponding to the interface name
+ */
+static void *OldParsePfringConfig(const char *iface)
+{
+    const char *threadsstr = NULL;
+    PfringIfaceConfig *pfconf = SCMalloc(sizeof(*pfconf));
+    const char *tmpclusterid;
+    const char *tmpctype = NULL;
+    cluster_type default_ctype = CLUSTER_FLOW;
+
+    if (unlikely(pfconf == NULL)) {
+        return NULL;
+    }
+
+    if (iface == NULL) {
+        SCFree(pfconf);
+        return NULL;
+    }
+
+    strlcpy(pfconf->iface, iface, sizeof(pfconf->iface));
+    pfconf->flags = 0;
+    pfconf->threads = 1;
+    pfconf->cluster_id = 1;
+    pfconf->ctype = default_ctype;
+    pfconf->DerefFunc = PfringDerefConfig;
+    pfconf->checksum_mode = CHECKSUM_VALIDATION_AUTO;
+    SC_ATOMIC_INIT(pfconf->ref);
+    (void)SC_ATOMIC_ADD(pfconf->ref, 1);
+
+    /* Find initial node */
+    if (ConfGet("pfring.threads", &threadsstr) != 1) {
+        pfconf->threads = 1;
+    } else {
+        if (threadsstr != NULL) {
+            if (StringParseInt32(&pfconf->threads, 10, 0, threadsstr) < 0) {
+                SCLogWarning("Invalid value for "
+                             "pfring.threads: '%s'. Resetting to 1.",
+                        threadsstr);
+                pfconf->threads = 1;
+            }
+        }
+    }
+    if (pfconf->threads == 0) {
+        pfconf->threads = 1;
+    }
+
+    SC_ATOMIC_RESET(pfconf->ref);
+    (void)SC_ATOMIC_ADD(pfconf->ref, pfconf->threads);
+
+    if (strncmp(pfconf->iface, "zc", 2) == 0) {
+        SCLogInfo("%s: ZC interface detected, not setting cluster-id", pfconf->iface);
+    } else if ((pfconf->threads == 1) && (strncmp(pfconf->iface, "dna", 3) == 0)) {
+        SCLogInfo("DNA interface detected, not setting cluster-id");
+    } else if (ConfGet("pfring.cluster-id", &tmpclusterid) != 1) {
+        SCLogError("Could not get cluster-id from config");
+    } else {
+        if (StringParseInt32(&pfconf->cluster_id, 10, 0, (const char *)tmpclusterid) < 0) {
+            SCLogWarning("Invalid value for "
+                         "pfring.cluster_id: '%s'. Resetting to 1.",
+                    tmpclusterid);
+            pfconf->cluster_id = 1;
+        }
+        pfconf->flags |= PFRING_CONF_FLAGS_CLUSTER;
+        SCLogDebug("Going to use cluster-id %" PRId32, pfconf->cluster_id);
+    }
+
+    if (strncmp(pfconf->iface, "zc", 2) == 0) {
+        SCLogInfo("%s: ZC interface detected, not setting cluster type for PF_RING", pfconf->iface);
+    } else if ((pfconf->threads == 1) && (strncmp(pfconf->iface, "dna", 3) == 0)) {
+        SCLogInfo(
+                "%s: DNA interface detected, not setting cluster type for PF_RING", pfconf->iface);
+    } else if (ConfGet("pfring.cluster-type", &tmpctype) != 1) {
+        SCLogError("Could not get cluster-type from config");
+    } else if (strcmp(tmpctype, "cluster_round_robin") == 0) {
+        SCLogInfo("%s: Using round-robin cluster mode for PF_RING", pfconf->iface);
+        pfconf->ctype = (cluster_type)tmpctype;
+    } else if (strcmp(tmpctype, "cluster_flow") == 0) {
+        SCLogInfo("%s: Using flow cluster mode for PF_RING", pfconf->iface);
+        pfconf->ctype = (cluster_type)tmpctype;
+    } else {
+        SCLogError("invalid cluster-type %s", tmpctype);
+        SCFree(pfconf);
+        return NULL;
+    }
+
+    return pfconf;
+}
+
+/**
+ * \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.
+ *
+ * If old config system is used, then return the same parameters
+ * value for each interface.
+ *
+ * \return a PfringIfaceConfig corresponding to the interface name
+ */
+static void *ParsePfringConfig(const char *iface)
+{
+    const char *threadsstr = NULL;
+    ConfNode *if_root;
+    ConfNode *if_default = NULL;
+    ConfNode *pf_ring_node;
+    PfringIfaceConfig *pfconf = SCMalloc(sizeof(*pfconf));
+    const char *tmpclusterid;
+    const char *tmpctype = NULL;
+    cluster_type default_ctype = CLUSTER_FLOW;
+    int getctype = 0;
+    int bool_val;
+    const char *active_runmode = RunmodeGetActive();
+
+    if (unlikely(pfconf == NULL)) {
+        return NULL;
+    }
+
+    if (iface == NULL) {
+        SCFree(pfconf);
+        return NULL;
+    }
+
+    memset(pfconf, 0, sizeof(PfringIfaceConfig));
+    strlcpy(pfconf->iface, iface, sizeof(pfconf->iface));
+    pfconf->threads = 1;
+    pfconf->cluster_id = 1;
+    pfconf->ctype = (cluster_type)default_ctype;
+    pfconf->DerefFunc = PfringDerefConfig;
+    SC_ATOMIC_INIT(pfconf->ref);
+    (void)SC_ATOMIC_ADD(pfconf->ref, 1);
+
+    /* Find initial node */
+    pf_ring_node = ConfGetNode("pfring");
+    if (pf_ring_node == NULL) {
+        SCLogInfo("Unable to find pfring config using default value");
+        return pfconf;
+    }
+
+    if_root = ConfFindDeviceConfig(pf_ring_node, iface);
+
+    if_default = ConfFindDeviceConfig(pf_ring_node, "default");
+
+    if (if_root == NULL && if_default == NULL) {
+        SCLogInfo("Unable to find pfring config for "
+                  "interface %s, using default value or 1.0 "
+                  "configuration system. ",
+                iface);
+        return pfconf;
+    }
+
+    /* 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)) {
+        pfconf->threads = 1;
+    } else if (ConfGetChildValueWithDefault(if_root, if_default, "threads", &threadsstr) != 1) {
+        pfconf->threads = 1;
+    } else if (threadsstr != NULL) {
+        if (strcmp(threadsstr, "auto") == 0) {
+            pfconf->threads = (int)UtilCpuGetNumProcessorsOnline();
+            if (pfconf->threads > 0) {
+                SCLogPerf("%u cores, so using %u threads", pfconf->threads, pfconf->threads);
+            } else {
+                pfconf->threads = GetIfaceRSSQueuesNum(iface);
+                if (pfconf->threads > 0) {
+                    SCLogPerf(
+                            "%d RSS queues, so using %u threads", pfconf->threads, pfconf->threads);
+                }
+            }
+        } else {
+            uint16_t threads = 0;
+            if (StringParseUint16(&threads, 10, 0, (const char *)threadsstr) < 0) {
+                SCLogWarning("Invalid value for "
+                             "pfring.threads: '%s'. Resetting to 1.",
+                        threadsstr);
+                pfconf->threads = 1;
+            } else {
+                pfconf->threads = threads;
+            }
+        }
+    }
+    if (pfconf->threads <= 0) {
+        pfconf->threads = 1;
+    }
+
+    SC_ATOMIC_RESET(pfconf->ref);
+    (void)SC_ATOMIC_ADD(pfconf->ref, pfconf->threads);
+
+    /* command line value has precedence */
+    if (ConfGet("pfring.cluster-id", &tmpclusterid) == 1) {
+        if (StringParseInt32(&pfconf->cluster_id, 10, 0, (const char *)tmpclusterid) < 0) {
+            SCLogWarning("Invalid value for "
+                         "pfring.cluster-id: '%s'. Resetting to 1.",
+                    tmpclusterid);
+            pfconf->cluster_id = 1;
+        }
+        pfconf->flags |= PFRING_CONF_FLAGS_CLUSTER;
+        SCLogDebug("Going to use command-line provided cluster-id %" PRId32, pfconf->cluster_id);
+    } else {
+
+        if (strncmp(pfconf->iface, "zc", 2) == 0) {
+            SCLogInfo(
+                    "%s: ZC interface detected, not setting cluster-id for PF_RING", pfconf->iface);
+        } else if ((pfconf->threads == 1) && (strncmp(pfconf->iface, "dna", 3) == 0)) {
+            SCLogInfo("%s: DNA interface detected, not setting cluster-id for PF_RING",
+                    pfconf->iface);
+        } else if (ConfGetChildValueWithDefault(if_root, if_default, "cluster-id", &tmpclusterid) !=
+                   1) {
+            SCLogError("Could not get cluster-id from config");
+        } else {
+            if (StringParseInt32(&pfconf->cluster_id, 10, 0, (const char *)tmpclusterid) < 0) {
+                SCLogWarning("Invalid value for "
+                             "pfring.cluster-id: '%s'. Resetting to 1.",
+                        tmpclusterid);
+                pfconf->cluster_id = 1;
+            }
+            pfconf->flags |= PFRING_CONF_FLAGS_CLUSTER;
+            SCLogDebug("Going to use cluster-id %" PRId32, pfconf->cluster_id);
+        }
+    }
+
+    ConfSetBPFFilter(if_root, if_default, pfconf->iface, &pfconf->bpf_filter);
+
+    if (EngineModeIsIPS()) {
+        FatalError("IPS mode not supported in PF_RING.");
+    }
+
+    if (ConfGet("pfring.cluster-type", &tmpctype) == 1) {
+        SCLogDebug("Going to use command-line provided cluster-type");
+        getctype = 1;
+    } else {
+        if (strncmp(pfconf->iface, "zc", 2) == 0) {
+            SCLogInfo("%s: ZC interface detected, not setting cluster type for PF_RING",
+                    pfconf->iface);
+        } else if ((pfconf->threads == 1) && (strncmp(pfconf->iface, "dna", 3) == 0)) {
+            SCLogInfo("%s: DNA interface detected, not setting cluster type for PF_RING",
+                    pfconf->iface);
+        } else if (ConfGetChildValueWithDefault(if_root, if_default, "cluster-type", &tmpctype) !=
+                   1) {
+            SCLogError("Could not get cluster-type from config");
+        } else {
+            getctype = 1;
+        }
+    }
+
+    if (getctype) {
+        if (strcmp(tmpctype, "cluster_round_robin") == 0) {
+            SCLogInfo("%s: Using round-robin cluster mode for PF_RING."
+                      " This mode is not recommended.",
+                    pfconf->iface);
+            pfconf->ctype = CLUSTER_ROUND_ROBIN;
+        } else if (strcmp(tmpctype, "cluster_flow") == 0) {
+            SCLogInfo("%s: Using flow cluster mode for PF_RING", pfconf->iface);
+            pfconf->ctype = CLUSTER_FLOW;
+        } else if (strcmp(tmpctype, "cluster_inner_flow") == 0) {
+            SCLogInfo("%s: Using flow cluster mode inner mode for PF_RING", pfconf->iface);
+            pfconf->ctype = CLUSTER_INNER_FLOW;
+        } else if (strcmp(tmpctype, "cluster_inner_flow_2_tuple") == 0) {
+            SCLogInfo("%s: Using flow cluster inner 2 tuple mode for PF_RING", pfconf->iface);
+            pfconf->ctype = CLUSTER_INNER_FLOW_2_TUPLE;
+        } else if (strcmp(tmpctype, "cluster_inner_flow_4_tuple") == 0) {
+            SCLogInfo("%s: Using flow cluster inner 4 tuple mode for PF_RING", pfconf->iface);
+            pfconf->ctype = CLUSTER_INNER_FLOW_4_TUPLE;
+        } else if (strcmp(tmpctype, "cluster_inner_flow_5_tuple") == 0) {
+            SCLogInfo("%s: Using flow cluster inner 5 tuple mode for PF_RING", pfconf->iface);
+            pfconf->ctype = CLUSTER_INNER_FLOW_5_TUPLE;
+        } else {
+            SCLogError("invalid cluster-type %s", tmpctype);
+            SCFree(pfconf);
+            return NULL;
+        }
+    }
+    if (ConfGetChildValueWithDefault(if_root, if_default, "checksum-checks", &tmpctype) == 1) {
+        if (strcmp(tmpctype, "auto") == 0) {
+            pfconf->checksum_mode = CHECKSUM_VALIDATION_AUTO;
+        } else if (ConfValIsTrue(tmpctype)) {
+            pfconf->checksum_mode = CHECKSUM_VALIDATION_ENABLE;
+        } else if (ConfValIsFalse(tmpctype)) {
+            pfconf->checksum_mode = CHECKSUM_VALIDATION_DISABLE;
+        } else if (strcmp(tmpctype, "rx-only") == 0) {
+            pfconf->checksum_mode = CHECKSUM_VALIDATION_RXONLY;
+        } else {
+            SCLogError("%s: Invalid value for checksum-checks", pfconf->iface);
+        }
+    }
+
+    if (ConfGetChildValueBoolWithDefault(if_root, if_default, "bypass", &bool_val) == 1) {
+        if (bool_val) {
+#ifdef HAVE_PF_RING_FLOW_OFFLOAD
+            SCLogConfig("%s: Enabling bypass support in PF_RING (if supported by underlying hw)",
+                    pfconf->iface);
+            pfconf->flags |= PFRING_CONF_FLAGS_BYPASS;
+#else
+            SCLogError("Bypass is not supported by this Pfring version, please upgrade");
+            SCFree(pfconf);
+            return NULL;
+#endif
+        }
+    }
+
+    if (LiveGetOffload() == 0) {
+        if (GetIfaceOffloading(iface, 0, 1) == 1) {
+            SCLogWarning("Using PF_RING with offloading activated leads to capture problems");
+        }
+    } else {
+        DisableIfaceOffloading(LiveGetDevice(iface), 0, 1);
+    }
+    return pfconf;
+}
+
+static int PfringConfigGetThreadsCount(void *conf)
+{
+    PfringIfaceConfig *pfp = (PfringIfaceConfig *)conf;
+    return pfp->threads;
+}
+
+static int PfringConfLevel(void)
+{
+    const char *def_dev = NULL;
+    /* 1.0 config should return a string */
+    if (ConfGet("pfring.interface", &def_dev) != 1) {
+        return PFRING_CONF_V2;
+    } else {
+        return PFRING_CONF_V1;
+    }
+}
+
+static int GetDevAndParser(const char **live_dev, ConfigIfaceParserFunc *parser)
+{
+    ConfGet("pfring.live-interface", live_dev);
+
+    /* determine which config type we have */
+    if (PfringConfLevel() > PFRING_CONF_V1) {
+        *parser = ParsePfringConfig;
+    } else {
+        SCLogInfo("Using 1.0 style configuration for pfring");
+        *parser = OldParsePfringConfig;
+        /* In v1: try to get interface name from config */
+        if (*live_dev == NULL) {
+            if (ConfGet("pfring.interface", live_dev) == 1) {
+                SCLogInfo("Using interface %s", *live_dev);
+                LiveRegisterDevice(*live_dev);
+            } else {
+                SCLogInfo("No interface found, problem incoming");
+                *live_dev = NULL;
+            }
+        }
+    }
+
+    return 0;
+}
+
+int RunModeIdsPfringAutoFp(void)
+{
+    SCEnter();
+
+    /* We include only if pfring is enabled */
+    int ret;
+    const char *live_dev = NULL;
+    ConfigIfaceParserFunc tparser;
+
+    TimeModeSetLive();
+
+    ret = GetDevAndParser(&live_dev, &tparser);
+    if (ret != 0) {
+        FatalError("Unable to get parser and interface params");
+    }
+
+    ret = RunModeSetLiveCaptureAutoFp(tparser, PfringConfigGetThreadsCount, "ReceivePfring",
+            "DecodePfring", thread_name_autofp, live_dev);
+    if (ret != 0) {
+        FatalError("Runmode start failed");
+    }
+
+    SCLogInfo("RunModeIdsPfringAutoFp initialised");
+
+    return 0;
+}
+
+int RunModeIdsPfringSingle(void)
+{
+    SCEnter();
+
+    /* We include only if pfring is enabled */
+    int ret;
+    const char *live_dev = NULL;
+    ConfigIfaceParserFunc tparser;
+
+    TimeModeSetLive();
+
+    ret = GetDevAndParser(&live_dev, &tparser);
+    if (ret != 0) {
+        FatalError("Unable to get parser and interface params");
+    }
+
+    ret = RunModeSetLiveCaptureSingle(tparser, PfringConfigGetThreadsCount, "ReceivePfring",
+            "DecodePfring", thread_name_single, live_dev);
+    if (ret != 0) {
+        FatalError("Runmode start failed");
+    }
+
+    SCLogInfo("RunModeIdsPfringSingle initialised");
+
+    return 0;
+}
+
+int RunModeIdsPfringWorkers(void)
+{
+    SCEnter();
+
+    /* We include only if pfring is enabled */
+    int ret;
+    const char *live_dev = NULL;
+    ConfigIfaceParserFunc tparser;
+
+    TimeModeSetLive();
+
+    ret = GetDevAndParser(&live_dev, &tparser);
+    if (ret != 0) {
+        FatalError("Unable to get parser and interface params");
+    }
+
+    ret = RunModeSetLiveCaptureWorkers(tparser, PfringConfigGetThreadsCount, "ReceivePfring",
+            "DecodePfring", thread_name_workers, live_dev);
+    if (ret != 0) {
+        FatalError("Runmode start failed");
+    }
+
+    SCLogInfo("RunModeIdsPfringWorkers initialised");
+
+    return 0;
+}
diff --git a/plugins/pfring/runmode-pfring.h b/plugins/pfring/runmode-pfring.h
new file mode 100644 (file)
index 0000000..76fe61b
--- /dev/null
@@ -0,0 +1,34 @@
+/* Copyright (C) 2007-2010 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 Victor Julien <victor@inliniac.net>
+ */
+
+#ifndef SURICATA_RUNMODE_PFRING_H
+#define SURICATA_RUNMODE_PFRING_H
+
+#include "suricata-common.h"
+
+int RunModeIdsPfringAutoFp(void);
+int RunModeIdsPfringSingle(void);
+int RunModeIdsPfringWorkers(void);
+void RunModeIdsPfringRegister(int plugin_slot);
+const char *RunModeIdsPfringGetDefaultMode(void);
+
+#endif /* SURICATA_RUNMODE_PFRING_H */
diff --git a/plugins/pfring/source-pfring.c b/plugins/pfring/source-pfring.c
new file mode 100644 (file)
index 0000000..7ac9b24
--- /dev/null
@@ -0,0 +1,724 @@
+/* Copyright (C) 2007-2019 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 William Metcalf <william.metcalf@gmail.com>
+ * \author Eric Leblond <eric@regit.org>
+ *
+ * PF_RING packet acquisition support
+ *
+ * \todo remove requirement for setting cluster so old 3.x versions are supported
+ * \todo implement DNA support
+ * \todo Allow ring options such as snaplen etc, to be user configurable.
+ */
+
+#include "suricata-common.h"
+#include "suricata-plugin.h"
+
+#include "suricata.h"
+#include "conf.h"
+#include "decode.h"
+#include "packet-queue.h"
+#include "threads.h"
+#include "threadvars.h"
+#include "tm-queuehandlers.h"
+#include "tm-threads.h"
+#include "source-pfring.h"
+#include "util-debug.h"
+#include "util-checksum.h"
+#include "util-privs.h"
+#include "util-datalink.h"
+#include "util-device.h"
+#include "util-host-info.h"
+#include "runmodes.h"
+#include "util-profiling.h"
+
+TmEcode ReceivePfringLoop(ThreadVars *tv, void *data, void *slot);
+TmEcode PfringBreakLoop(ThreadVars *tv, void *data);
+TmEcode ReceivePfringThreadInit(ThreadVars *, const void *, void **);
+void ReceivePfringThreadExitStats(ThreadVars *, void *);
+TmEcode ReceivePfringThreadDeinit(ThreadVars *, void *);
+
+TmEcode DecodePfringThreadInit(ThreadVars *, const void *, void **);
+TmEcode DecodePfring(ThreadVars *, Packet *, void *);
+TmEcode DecodePfringThreadDeinit(ThreadVars *tv, void *data);
+
+extern uint16_t max_pending_packets;
+
+#include <pfring.h>
+
+/** protect pfring_set_bpf_filter, as it is not thread safe */
+static SCMutex pfring_bpf_set_filter_lock = SCMUTEX_INITIALIZER;
+
+/* XXX replace with user configurable options */
+#define LIBPFRING_PROMISC           1
+#define LIBPFRING_REENTRANT         0
+#define LIBPFRING_WAIT_FOR_INCOMING 1
+
+/* PfringThreadVars flags */
+#define PFRING_FLAGS_ZERO_COPY (1 << 0)
+#define PFRING_FLAGS_BYPASS    (1 << 1)
+
+/**
+ * \brief Structure to hold thread specific variables.
+ */
+struct PfringThreadVars_ {
+    /* thread specific handle */
+    pfring *pd;
+
+    /* counters */
+    uint64_t bytes;
+    uint64_t pkts;
+
+    uint16_t capture_kernel_packets;
+    uint16_t capture_kernel_drops;
+    uint16_t capture_bypassed;
+
+    uint32_t flags;
+
+    ThreadVars *tv;
+    TmSlot *slot;
+
+    int vlan_in_ext_header;
+
+    /* threads count */
+    int threads;
+
+    cluster_type ctype;
+
+    uint8_t cluster_id;
+    char *interface;
+    LiveDevice *livedev;
+
+    char *bpf_filter;
+
+    ChecksumValidationMode checksum_mode;
+
+    bool vlan_hdr_warned;
+};
+
+/**
+ * \brief Registration Function for ReceivePfring.
+ * \todo Unit tests are needed for this module.
+ */
+void TmModuleReceivePfringRegister(int slot)
+{
+    tmm_modules[slot].name = "ReceivePfring";
+    tmm_modules[slot].ThreadInit = ReceivePfringThreadInit;
+    tmm_modules[slot].Func = NULL;
+    tmm_modules[slot].PktAcqLoop = ReceivePfringLoop;
+    tmm_modules[slot].PktAcqBreakLoop = PfringBreakLoop;
+    tmm_modules[slot].ThreadExitPrintStats = ReceivePfringThreadExitStats;
+    tmm_modules[slot].ThreadDeinit = ReceivePfringThreadDeinit;
+    tmm_modules[slot].flags = TM_FLAG_RECEIVE_TM;
+}
+
+/**
+ * \brief Registration Function for DecodePfring.
+ * \todo Unit tests are needed for this module.
+ */
+void TmModuleDecodePfringRegister(int slot)
+{
+    tmm_modules[slot].name = "DecodePfring";
+    tmm_modules[slot].ThreadInit = DecodePfringThreadInit;
+    tmm_modules[slot].Func = DecodePfring;
+    tmm_modules[slot].ThreadExitPrintStats = NULL;
+    tmm_modules[slot].ThreadDeinit = DecodePfringThreadDeinit;
+    tmm_modules[slot].flags = TM_FLAG_DECODE_TM;
+}
+
+static inline void PfringDumpCounters(PfringThreadVars *ptv)
+{
+    pfring_stat pfring_s;
+    if (likely((pfring_stats(ptv->pd, &pfring_s) >= 0))) {
+        /* pfring counter is per socket and is not cleared after read.
+         * So to get the number of packet on the interface we can add
+         * the newly seen packets and drops for this thread and add it
+         * to the interface counter */
+        uint64_t th_pkts = StatsGetLocalCounterValue(ptv->tv, ptv->capture_kernel_packets);
+        uint64_t th_drops = StatsGetLocalCounterValue(ptv->tv, ptv->capture_kernel_drops);
+        SC_ATOMIC_ADD(ptv->livedev->pkts, pfring_s.recv - th_pkts);
+        SC_ATOMIC_ADD(ptv->livedev->drop, pfring_s.drop - th_drops);
+        StatsSetUI64(ptv->tv, ptv->capture_kernel_packets, pfring_s.recv);
+        StatsSetUI64(ptv->tv, ptv->capture_kernel_drops, pfring_s.drop);
+
+#ifdef HAVE_PF_RING_FLOW_OFFLOAD
+        if (ptv->flags & PFRING_FLAGS_BYPASS) {
+            uint64_t th_bypassed = StatsGetLocalCounterValue(ptv->tv, ptv->capture_bypassed);
+            SC_ATOMIC_ADD(ptv->livedev->bypassed, pfring_s.shunt - th_bypassed);
+            StatsSetUI64(ptv->tv, ptv->capture_bypassed, pfring_s.shunt);
+        }
+#endif
+    }
+}
+
+/**
+ * \brief Pfring Packet Process function.
+ *
+ * This function fills in our packet structure from libpfring.
+ * From here the packets are picked up by the DecodePfring thread.
+ *
+ * \param user pointer to PfringThreadVars
+ * \param h pointer to pfring packet header
+ * \param p pointer to the current packet
+ */
+static inline void PfringProcessPacket(void *user, struct pfring_pkthdr *h, Packet *p)
+{
+    PfringThreadVars *ptv = (PfringThreadVars *)user;
+
+    ptv->bytes += h->caplen;
+    ptv->pkts++;
+    p->livedev = ptv->livedev;
+
+    /* PF_RING may fail to set timestamp */
+    if (h->ts.tv_sec == 0) {
+        struct timeval tmp_ts;
+        gettimeofday(&tmp_ts, NULL);
+        h->ts = tmp_ts;
+    }
+
+    p->ts = SCTIME_FROM_TIMEVAL(&h->ts);
+
+    /* PF_RING all packets are marked as a link type of ethernet
+     * so that is what we do here. */
+    p->datalink = LINKTYPE_ETHERNET;
+
+    /* In the past, we needed this vlan handling in cases
+     * where the vlan header was stripped from the raw packet.
+     * With modern (at least >= 6) versions of PF_RING, the
+     * 'copy_data_to_ring' function (kernel/pf_ring.c) makes
+     * sure that if the hardware stripped the vlan header,
+     * it is put back by PF_RING.
+     *
+     * PF_RING should put it back in all cases, but as a extra
+     * precaution keep the check here. If the vlan header is
+     * part of the raw packet, the vlan_offset will be set.
+     * So if it is not set, use the parsed info from PF_RING's
+     * extended header.
+     */
+    if (ptv->vlan_in_ext_header && h->extended_hdr.parsed_pkt.offset.vlan_offset == 0 &&
+            h->extended_hdr.parsed_pkt.vlan_id) {
+        p->vlan_id[0] = h->extended_hdr.parsed_pkt.vlan_id & 0x0fff;
+        p->vlan_idx = 1;
+
+        if (!ptv->vlan_hdr_warned) {
+            SCLogWarning("no VLAN header in the raw "
+                         "packet. See ticket #2355.");
+            ptv->vlan_hdr_warned = true;
+        }
+    }
+
+    switch (ptv->checksum_mode) {
+        case CHECKSUM_VALIDATION_RXONLY:
+            if (h->extended_hdr.rx_direction == 0) {
+                p->flags |= PKT_IGNORE_CHECKSUM;
+            }
+            break;
+        case CHECKSUM_VALIDATION_DISABLE:
+            p->flags |= PKT_IGNORE_CHECKSUM;
+            break;
+        case CHECKSUM_VALIDATION_AUTO:
+            if (ChecksumAutoModeCheck(ptv->pkts, SC_ATOMIC_GET(ptv->livedev->pkts),
+                        SC_ATOMIC_GET(ptv->livedev->invalid_checksums))) {
+                ptv->checksum_mode = CHECKSUM_VALIDATION_DISABLE;
+                p->flags |= PKT_IGNORE_CHECKSUM;
+            }
+            break;
+        default:
+            break;
+    }
+
+    SET_PKT_LEN(p, h->caplen);
+}
+
+#ifdef HAVE_PF_RING_FLOW_OFFLOAD
+/**
+ * \brief Pfring bypass callback function
+ *
+ * \param p a Packet to use information from to trigger bypass
+ * \return 1 if bypass is successful, 0 if not
+ */
+static int PfringBypassCallback(Packet *p)
+{
+    hw_filtering_rule r;
+
+    /* Only bypass TCP and UDP */
+    if (!(PacketIsTCP(p) || PacketIsUDP(p))) {
+        return 0;
+    }
+
+    /* Bypassing tunneled packets is currently not supported */
+    if (PacketIsTunnel(p)) {
+        return 0;
+    }
+
+    PfringPacketVars *vars = (PfringPacketVars *)&p->plugin_v;
+
+    r.rule_family_type = generic_flow_id_rule;
+    r.rule_family.flow_id_rule.action = flow_drop_rule;
+    r.rule_family.flow_id_rule.thread = 0;
+    r.rule_family.flow_id_rule.flow_id = vars->flow_id;
+
+    SCLogDebug("Bypass set for flow ID = %u", vars->flow_id);
+
+    if (pfring_add_hw_rule(vars->ptv->pd, &r) < 0) {
+        return 0;
+    }
+
+    return 1;
+}
+#endif
+
+/**
+ * \brief Receives packets from an interface via libpfring.
+ *
+ *  This function receives packets from an interface and passes
+ *  the packet on to the pfring callback function.
+ *
+ * \param tv pointer to ThreadVars
+ * \param data pointer that gets cast into PfringThreadVars for ptv
+ * \param slot slot containing task information
+ * \retval TM_ECODE_OK on success
+ * \retval TM_ECODE_FAILED on failure
+ */
+TmEcode ReceivePfringLoop(ThreadVars *tv, void *data, void *slot)
+{
+    SCEnter();
+
+    PfringThreadVars *ptv = (PfringThreadVars *)data;
+    Packet *p = NULL;
+    struct pfring_pkthdr hdr;
+    TmSlot *s = (TmSlot *)slot;
+    SCTime_t last_dump = SCTIME_INITIALIZER;
+    u_int buffer_size;
+    u_char *pkt_buffer;
+
+    ptv->slot = s->slot_next;
+
+    /* we have to enable the ring here as we need to do it after all
+     * the threads have called pfring_set_cluster(). */
+    int rc = pfring_enable_ring(ptv->pd);
+    if (rc != 0) {
+        SCLogError("pfring_enable_ring failed returned %d ", rc);
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    // Indicate that the thread is actually running its application level code (i.e., it can poll
+    // packets)
+    TmThreadsSetFlag(tv, THV_RUNNING);
+
+    while (1) {
+        if (suricata_ctl_flags & SURICATA_STOP) {
+            SCReturnInt(TM_ECODE_OK);
+        }
+
+        /* make sure we have at least one packet in the packet pool, to prevent
+         * us from alloc'ing packets at line rate */
+        PacketPoolWait();
+
+        p = PacketGetFromQueueOrAlloc();
+        if (p == NULL) {
+            SCReturnInt(TM_ECODE_FAILED);
+        }
+        PKT_SET_SRC(p, PKT_SRC_WIRE);
+
+        /* Some flavours of PF_RING may fail to set timestamp - see PF-RING-enabled libpcap code*/
+        hdr.ts.tv_sec = hdr.ts.tv_usec = 0;
+
+        /* Check for Zero-copy mode */
+        if (ptv->flags & PFRING_FLAGS_ZERO_COPY) {
+            buffer_size = 0;
+            pkt_buffer = NULL;
+        } else {
+            buffer_size = GET_PKT_DIRECT_MAX_SIZE(p);
+            pkt_buffer = GET_PKT_DIRECT_DATA(p);
+        }
+
+        int r = pfring_recv(ptv->pd, &pkt_buffer, buffer_size, &hdr, LIBPFRING_WAIT_FOR_INCOMING);
+        if (likely(r == 1)) {
+            /* profiling started before blocking pfring_recv call, so
+             * reset it here */
+            PACKET_PROFILING_RESTART(p);
+
+#ifdef HAVE_PF_RING_FLOW_OFFLOAD
+            if (ptv->flags & PFRING_FLAGS_BYPASS) {
+                PfringPacketVars *vars = (PfringPacketVars *)&p->plugin_v;
+                /* pkt hash contains the flow id in this configuration */
+                vars->flow_id = hdr.extended_hdr.pkt_hash;
+                vars->ptv = ptv;
+                p->BypassPacketsFlow = PfringBypassCallback;
+            }
+#endif
+
+            /* Check for Zero-copy mode */
+            if (ptv->flags & PFRING_FLAGS_ZERO_COPY) {
+                PacketSetData(p, pkt_buffer, hdr.caplen);
+            }
+
+            PfringProcessPacket(ptv, &hdr, p);
+
+            if (TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p) != TM_ECODE_OK) {
+                SCReturnInt(TM_ECODE_FAILED);
+            }
+
+            /* Trigger one dump of stats every second */
+            if (SCTIME_CMP_NEQ(p->ts, last_dump)) {
+                PfringDumpCounters(ptv);
+                last_dump = p->ts;
+            }
+        } else if (unlikely(r == 0)) {
+            if (suricata_ctl_flags & SURICATA_STOP) {
+                TmqhOutputPacketpool(ptv->tv, p);
+                SCReturnInt(TM_ECODE_OK);
+            }
+
+            /* pfring didn't use the packet yet */
+            TmThreadsCaptureHandleTimeout(tv, p);
+
+        } else {
+            SCLogError("pfring_recv error  %" PRId32 "", r);
+            TmqhOutputPacketpool(ptv->tv, p);
+            SCReturnInt(TM_ECODE_FAILED);
+        }
+        StatsSyncCountersIfSignalled(tv);
+    }
+
+    return TM_ECODE_OK;
+}
+
+/**
+ * \brief Stop function for ReceivePfringLoop.
+ *
+ * This function forces ReceivePfringLoop to stop the
+ * execution, exiting the packet capture loop.
+ *
+ * \param tv pointer to ThreadVars
+ * \param data pointer that gets cast into PfringThreadVars for ptv
+ * \retval TM_ECODE_OK on success
+ * \retval TM_ECODE_FAILED on failure
+ */
+TmEcode PfringBreakLoop(ThreadVars *tv, void *data)
+{
+    PfringThreadVars *ptv = (PfringThreadVars *)data;
+
+    /* Safety check */
+    if (ptv->pd == NULL) {
+        return TM_ECODE_FAILED;
+    }
+
+    pfring_breakloop(ptv->pd);
+
+    return TM_ECODE_OK;
+}
+
+/**
+ * \brief Init function for ReceivePfring.
+ *
+ * This is a setup function for receiving packets
+ * via libpfring.
+ *
+ * \param tv pointer to ThreadVars
+ * \param initdata pointer to the interface passed from the user
+ * \param data pointer gets populated with PfringThreadVars
+ * \todo add a config option for setting cluster id
+ * \todo Create a general pfring setup function.
+ * \retval TM_ECODE_OK on success
+ * \retval TM_ECODE_FAILED on error
+ */
+TmEcode ReceivePfringThreadInit(ThreadVars *tv, const void *initdata, void **data)
+{
+    int rc;
+    u_int32_t version = 0;
+    PfringIfaceConfig *pfconf = (PfringIfaceConfig *)initdata;
+    unsigned int opflag;
+    char const *active_runmode = RunmodeGetActive();
+
+    if (pfconf == NULL)
+        return TM_ECODE_FAILED;
+
+    PfringThreadVars *ptv = SCCalloc(1, sizeof(PfringThreadVars));
+    if (unlikely(ptv == NULL)) {
+        pfconf->DerefFunc(pfconf);
+        return TM_ECODE_FAILED;
+    }
+
+    ptv->tv = tv;
+    ptv->threads = 1;
+
+    ptv->interface = SCStrdup(pfconf->iface);
+    if (unlikely(ptv->interface == NULL)) {
+        SCLogError("Unable to allocate device string");
+        SCFree(ptv);
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    ptv->livedev = LiveGetDevice(pfconf->iface);
+    if (ptv->livedev == NULL) {
+        SCLogError("Unable to find Live device");
+        SCFree(ptv);
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    /* enable zero-copy mode for workers runmode */
+    if (active_runmode && strcmp("workers", active_runmode) == 0) {
+        ptv->flags |= PFRING_FLAGS_ZERO_COPY;
+        SCLogPerf("Enabling zero-copy for %s", ptv->interface);
+    }
+
+    ptv->checksum_mode = pfconf->checksum_mode;
+
+    opflag = PF_RING_PROMISC;
+
+    /* if we have a recent kernel, we need to use parsed_pkt to get VLAN info */
+    if (ptv->vlan_in_ext_header) {
+        opflag |= PF_RING_LONG_HEADER;
+    }
+
+    if (ptv->checksum_mode == CHECKSUM_VALIDATION_RXONLY) {
+        if (strncmp(ptv->interface, "dna", 3) == 0) {
+            SCLogWarning("Can't use rxonly checksum-checks on DNA interface,"
+                         " resetting to auto");
+            ptv->checksum_mode = CHECKSUM_VALIDATION_AUTO;
+        } else {
+            opflag |= PF_RING_LONG_HEADER;
+        }
+    }
+
+#ifdef HAVE_PF_RING_FLOW_OFFLOAD
+    if (pfconf->flags & PFRING_CONF_FLAGS_BYPASS) {
+        opflag |= PF_RING_FLOW_OFFLOAD | PF_RING_FLOW_OFFLOAD_NOUPDATES;
+        ptv->flags |= PFRING_FLAGS_BYPASS;
+    }
+#endif
+
+    ptv->pd = pfring_open(ptv->interface, (uint32_t)default_packet_size, opflag);
+    if (ptv->pd == NULL) {
+        SCLogError("Failed to open %s: pfring_open error."
+                   " Check if %s exists and pf_ring module is loaded.",
+                ptv->interface, ptv->interface);
+        pfconf->DerefFunc(pfconf);
+        SCFree(ptv);
+        return TM_ECODE_FAILED;
+    }
+
+    pfring_set_application_name(ptv->pd, (char *)PROG_NAME);
+    pfring_version(ptv->pd, &version);
+
+    /* We only set cluster info if the number of pfring threads is greater than 1 */
+    ptv->threads = pfconf->threads;
+
+    ptv->cluster_id = pfconf->cluster_id;
+
+    if ((ptv->threads == 1) && (strncmp(ptv->interface, "dna", 3) == 0)) {
+        SCLogInfo("DNA interface detected, not adding thread to cluster");
+    } else if (strncmp(ptv->interface, "zc", 2) == 0) {
+        SCLogInfo("ZC interface detected, not adding thread to cluster");
+    } else {
+        ptv->ctype = (cluster_type)pfconf->ctype;
+        rc = pfring_set_cluster(ptv->pd, ptv->cluster_id, ptv->ctype);
+
+        if (rc != 0) {
+            SCLogError("pfring_set_cluster "
+                       "returned %d for cluster-id: %d",
+                    rc, ptv->cluster_id);
+            if (rc != PF_RING_ERROR_NOT_SUPPORTED || (pfconf->flags & PFRING_CONF_FLAGS_CLUSTER)) {
+                /* cluster is mandatory as explicitly specified in the configuration */
+                pfconf->DerefFunc(pfconf);
+                return TM_ECODE_FAILED;
+            }
+        }
+    }
+
+    if (ptv->threads > 1) {
+        SCLogPerf("(%s) Using PF_RING v.%d.%d.%d, interface %s, cluster-id %d", tv->name,
+                (version & 0xFFFF0000) >> 16, (version & 0x0000FF00) >> 8, version & 0x000000FF,
+                ptv->interface, ptv->cluster_id);
+    } else {
+        SCLogPerf(
+                "(%s) Using PF_RING v.%d.%d.%d, interface %s, cluster-id %d, single-pfring-thread",
+                tv->name, (version & 0xFFFF0000) >> 16, (version & 0x0000FF00) >> 8,
+                version & 0x000000FF, ptv->interface, ptv->cluster_id);
+    }
+
+    if (pfconf->bpf_filter) {
+        ptv->bpf_filter = SCStrdup(pfconf->bpf_filter);
+        if (unlikely(ptv->bpf_filter == NULL)) {
+            SCLogError("Set PF_RING bpf filter failed.");
+        } else {
+            SCMutexLock(&pfring_bpf_set_filter_lock);
+            rc = pfring_set_bpf_filter(ptv->pd, ptv->bpf_filter);
+            SCMutexUnlock(&pfring_bpf_set_filter_lock);
+
+            if (rc < 0) {
+                SCLogError("Failed to compile BPF \"%s\"", ptv->bpf_filter);
+                return TM_ECODE_FAILED;
+            }
+        }
+    }
+
+    ptv->capture_kernel_packets = StatsRegisterCounter("capture.kernel_packets", ptv->tv);
+    ptv->capture_kernel_drops = StatsRegisterCounter("capture.kernel_drops", ptv->tv);
+#ifdef HAVE_PF_RING_FLOW_OFFLOAD
+    ptv->capture_bypassed = StatsRegisterCounter("capture.bypassed", ptv->tv);
+#endif
+
+    /* If kernel is older than 3.0, VLAN is not stripped so we don't
+     * get the info from packet extended header but we will use a standard
+     * parsing */
+    ptv->vlan_in_ext_header = 1;
+    if (!SCKernelVersionIsAtLeast(3, 0)) {
+        ptv->vlan_in_ext_header = 0;
+    }
+
+    /* If VLAN tags are not in the extended header, set cluster type to 5-tuple
+     * or in case of a ZC interface, do nothing */
+    if ((!ptv->vlan_in_ext_header) && ptv->ctype == CLUSTER_FLOW &&
+            strncmp(ptv->interface, "zc", 2) != 0) {
+        SCLogPerf("VLAN not in extended header, setting cluster type to CLUSTER_FLOW_5_TUPLE");
+        rc = pfring_set_cluster(ptv->pd, ptv->cluster_id, CLUSTER_FLOW_5_TUPLE);
+
+        if (rc != 0) {
+            SCLogError("pfring_set_cluster "
+                       "returned %d for cluster-id: %d",
+                    rc, ptv->cluster_id);
+            pfconf->DerefFunc(pfconf);
+            return TM_ECODE_FAILED;
+        }
+    }
+
+    DatalinkSetGlobalType(LINKTYPE_ETHERNET);
+
+    *data = (void *)ptv;
+    pfconf->DerefFunc(pfconf);
+
+    return TM_ECODE_OK;
+}
+
+/**
+ * \brief This function prints stats to the screen at exit.
+ * \param tv pointer to ThreadVars
+ * \param data pointer that gets cast into PfringThreadVars for ptv
+ */
+void ReceivePfringThreadExitStats(ThreadVars *tv, void *data)
+{
+    PfringThreadVars *ptv = (PfringThreadVars *)data;
+
+    PfringDumpCounters(ptv);
+    SCLogPerf("(%s) Kernel: Packets %" PRIu64 ", dropped %" PRIu64 "", tv->name,
+            StatsGetLocalCounterValue(tv, ptv->capture_kernel_packets),
+            StatsGetLocalCounterValue(tv, ptv->capture_kernel_drops));
+    SCLogPerf("(%s) Packets %" PRIu64 ", bytes %" PRIu64 "", tv->name, ptv->pkts, ptv->bytes);
+#ifdef HAVE_PF_RING_FLOW_OFFLOAD
+    if (ptv->flags & PFRING_FLAGS_BYPASS) {
+        SCLogPerf("(%s) Bypass: Packets %" PRIu64 "", tv->name,
+                StatsGetLocalCounterValue(tv, ptv->capture_bypassed));
+    }
+#endif
+}
+
+/**
+ * \brief DeInit function closes pd at exit.
+ * \param tv pointer to ThreadVars
+ * \param data pointer that gets cast into PfringThreadVars for ptvi
+ * \retval TM_ECODE_OK is always returned
+ */
+TmEcode ReceivePfringThreadDeinit(ThreadVars *tv, void *data)
+{
+    PfringThreadVars *ptv = (PfringThreadVars *)data;
+    if (ptv->interface)
+        SCFree(ptv->interface);
+    pfring_remove_from_cluster(ptv->pd);
+
+    if (ptv->bpf_filter) {
+        pfring_remove_bpf_filter(ptv->pd);
+        SCFree(ptv->bpf_filter);
+    }
+
+    pfring_close(ptv->pd);
+    SCFree(ptv);
+    return TM_ECODE_OK;
+}
+
+/**
+ * \brief This function passes off to link type decoders.
+ *
+ * DecodePfring decodes raw packets from PF_RING. Inside of libpcap version of
+ * PF_RING all packets are marked as a link type of ethernet so that is what we do here.
+ *
+ * \param tv pointer to ThreadVars
+ * \param p pointer to the current packet
+ * \param data pointer that gets cast into PfringThreadVars for ptv
+ *
+ * \todo Verify that PF_RING only deals with ethernet traffic
+ *
+ * \warning This function bypasses the pkt buf and len macro's
+ *
+ * \retval TM_ECODE_OK is always returned
+ */
+TmEcode DecodePfring(ThreadVars *tv, Packet *p, void *data)
+{
+    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);
+    }
+
+    DecodeEthernet(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p));
+
+    PacketDecodeFinalize(tv, dtv, p);
+
+    return TM_ECODE_OK;
+}
+
+/**
+ * \brief This an Init function for DecodePfring
+ *
+ * \param tv pointer to ThreadVars
+ * \param initdata pointer to initialization data.
+ * \param data pointer that gets cast into PfringThreadVars for ptv
+ * \retval TM_ECODE_OK is returned on success
+ * \retval TM_ECODE_FAILED is returned on error
+ */
+TmEcode DecodePfringThreadInit(ThreadVars *tv, const void *initdata, void **data)
+{
+    DecodeThreadVars *dtv = NULL;
+
+    dtv = DecodeThreadVarsAlloc(tv);
+    if (dtv == NULL)
+        SCReturnInt(TM_ECODE_FAILED);
+
+    DecodeRegisterPerfCounters(dtv, tv);
+
+    *data = (void *)dtv;
+
+    return TM_ECODE_OK;
+}
+
+TmEcode DecodePfringThreadDeinit(ThreadVars *tv, void *data)
+{
+    if (data != NULL)
+        DecodeThreadVarsFree(tv, data);
+    SCReturnInt(TM_ECODE_OK);
+}
diff --git a/plugins/pfring/source-pfring.h b/plugins/pfring/source-pfring.h
new file mode 100644 (file)
index 0000000..6ec9611
--- /dev/null
@@ -0,0 +1,81 @@
+/* Copyright (C) 2007-2010 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 William Metcalf <william.metcalf@gmail.com>
+ */
+
+#ifndef SURICATA_SOURCE_PFRING_H
+#define SURICATA_SOURCE_PFRING_H
+
+#define PFRING_IFACE_NAME_LENGTH 48
+
+typedef struct PfringThreadVars_ PfringThreadVars;
+
+/* PfringIfaceConfig flags */
+#define PFRING_CONF_FLAGS_CLUSTER (1 << 0)
+#define PFRING_CONF_FLAGS_BYPASS  (1 << 1)
+
+typedef struct PfringIfaceConfig_ {
+    uint32_t flags;
+
+    /* cluster param */
+    int cluster_id;
+    unsigned int ctype;
+
+    char iface[PFRING_IFACE_NAME_LENGTH];
+    /* number of threads */
+    int threads;
+
+    const char *bpf_filter;
+
+    ChecksumValidationMode checksum_mode;
+    SC_ATOMIC_DECLARE(unsigned int, ref);
+    void (*DerefFunc)(void *);
+} PfringIfaceConfig;
+
+/**
+ * \brief per packet Pfring vars
+ *
+ * This structure is used to pass packet metadata in callbacks.
+ */
+typedef struct PfringPacketVars_ {
+    PfringThreadVars *ptv;
+    uint32_t flow_id;
+} PfringPacketVars;
+
+void TmModuleReceivePfringRegister(int slot);
+void TmModuleDecodePfringRegister(int slot);
+
+int PfringConfGetThreads(void);
+void PfringLoadConfig(void);
+
+/*
+ * We don't have to use an enum that sucks in our code
+ * these values must match with cluster_type in the kernel
+ * include file pf_ring.h
+ */
+#define CLUSTER_FLOW               0
+#define CLUSTER_ROUND_ROBIN        1
+#define CLUSTER_FLOW_5_TUPLE       4
+#define CLUSTER_INNER_FLOW         6
+#define CLUSTER_INNER_FLOW_2_TUPLE 7
+#define CLUSTER_INNER_FLOW_4_TUPLE 8
+#define CLUSTER_INNER_FLOW_5_TUPLE 9
+#endif /* SURICATA_SOURCE_PFRING_H */