--- /dev/null
+# If building a plugin out of the Suricata source tree, you can use
+# libsuricata-config --cflags.
+#LIBSURICATA_CONFIG ?= libsuricata-config
+#CPPFLAGS += `$(LIBSURICATA_CONFIG) --cflags`
+
+# But as this is an example in the Suricata source tree we'll look for
+# includes in the source tree.
+CPPFLAGS += -I@top_srcdir@/src -DHAVE_CONFIG_H
+
+# Currently the Suricata logging system requires this to be even for
+# plugins.
+CPPFLAGS += "-D__SCFILENAME__=\"$(*F)\""
+
+all: Makefile custom-logger.so
+
+custom-logger.so: custom-logger.c
+ $(CC) $(CPPFLAGS) -fPIC -shared -o $@ $^
+
+clean:
+ rm -f *.so *.o *.lo
+ rm -rf .deps
+
+distclean: clean
+ rm -f Makefile.am
+
+# Regenerate Makefile on change of Makefile.in since we're not using
+# Makefile.am.
+Makefile: Makefile.in
+ cd @top_builddir@ && ./config.status examples/plugins/c-custom-logger/Makefile
+
+# Dummy rules to satisfy make dist.
+dist distdir:
--- /dev/null
+# Example Custom Logging Plugin
+
+This is an example of a low level logging plugin.
+
+Currently implemented are packet and flow loggers.
+
+## Building
+
+If in the Suricata source directory, this plugin can be built by
+running `make`'.
+
+## Building Standalone
+
+This Makefile is not generated by automake so it can serve as an
+example for plugins created outside of the Suricata source tree.
+
+Building a standalone plugin has the following dependencies:
+
+- Suricata is installed
+- The Suricata library is installed: `make install-library`
+- The Suricata development headers are installed: `make install-headers`
+- The program `libsuricata-config` is in your path (installed with
+ `make install-library`)
+
+Modify the Makefile to use `libsuricata-config`.
+
+Before building this plugin you will need to build and install Suricata from the
+git master branch and install the development tools and headers:
+
+- `make install-library`
+- `make install-headers`
+
+then make sure the newly installed tool `libsuricata-config` can be
+found in your path, for example:
+```
+libsuricata-config --cflags
+```
+
+Then a simple `make` should build this plugin.
+
+Or if the Suricata installation is not in the path, a command like the following
+can be used:
+
+```
+PATH=/opt/suricata/bin:$PATH make
+```
+
+## Usage
+
+To run the plugin, first add the path to the plugin you just compiled to
+your `suricata.yaml`, for example:
+```
+plugins:
+ - /usr/lib/suricata/plugins/c-custom-loggers/custom-loggers.so
+```
--- /dev/null
+/* Copyright (C) 2023-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 "output-packet.h"
+#include "output-flow.h"
+#include "util-print.h"
+
+static int CustomPacketLogger(ThreadVars *tv, void *thread_data, const Packet *p)
+{
+ char src_ip[46] = { 0 }, dst_ip[46] = { 0 };
+
+ if (PacketIsIPv4(p)) {
+ PrintInet(AF_INET, (const void *)&(p->src.addr_data32[0]), src_ip, sizeof(src_ip));
+ PrintInet(AF_INET, (const void *)&(p->dst.addr_data32[0]), dst_ip, sizeof(dst_ip));
+ } else if (PacketIsIPv6(p)) {
+ PrintInet(AF_INET6, (const void *)&(p->src.address), src_ip, sizeof(src_ip));
+ PrintInet(AF_INET6, (const void *)&(p->dst.address), dst_ip, sizeof(dst_ip));
+ } else {
+ SCLogNotice("Packet is not IP");
+ return 0;
+ }
+ SCLogNotice("Packet: %s -> %s", src_ip, dst_ip);
+ return 0;
+}
+
+static bool CustomPacketLoggerCondition(ThreadVars *tv, void *thread_data, const Packet *)
+{
+ /* Always true for this example. */
+ return true;
+}
+
+static int CustomFlowLogger(ThreadVars *tv, void *thread_data, Flow *f)
+{
+ char src_ip[46] = { 0 }, dst_ip[46] = { 0 };
+ Port sp, dp;
+
+ if ((f->flags & FLOW_DIR_REVERSED) == 0) {
+ if (FLOW_IS_IPV4(f)) {
+ PrintInet(AF_INET, (const void *)&(f->src.addr_data32[0]), src_ip, sizeof(src_ip));
+ PrintInet(AF_INET, (const void *)&(f->dst.addr_data32[0]), dst_ip, sizeof(dst_ip));
+ } else if (FLOW_IS_IPV6(f)) {
+ PrintInet(AF_INET6, (const void *)&(f->src.address), src_ip, sizeof(src_ip));
+ PrintInet(AF_INET6, (const void *)&(f->dst.address), dst_ip, sizeof(dst_ip));
+ }
+ sp = f->sp;
+ dp = f->dp;
+ } else {
+ if (FLOW_IS_IPV4(f)) {
+ PrintInet(AF_INET, (const void *)&(f->dst.addr_data32[0]), src_ip, sizeof(src_ip));
+ PrintInet(AF_INET, (const void *)&(f->src.addr_data32[0]), dst_ip, sizeof(dst_ip));
+ } else if (FLOW_IS_IPV6(f)) {
+ PrintInet(AF_INET6, (const void *)&(f->dst.address), src_ip, sizeof(src_ip));
+ PrintInet(AF_INET6, (const void *)&(f->src.address), dst_ip, sizeof(dst_ip));
+ }
+ sp = f->dp;
+ dp = f->sp;
+ }
+
+ SCLogNotice("Flow: %s:%u -> %s:%u", src_ip, sp, dst_ip, dp);
+
+ return 0;
+}
+
+static TmEcode ThreadInit(ThreadVars *tv, const void *initdata, void **data)
+{
+ return TM_ECODE_OK;
+}
+
+static TmEcode ThreadDeinit(ThreadVars *tv, void *data)
+{
+ // Nothing to do. If we allocated data in ThreadInit we would free
+ // it here.
+ return TM_ECODE_OK;
+}
+
+static void Init(void)
+{
+ OutputRegisterPacketLogger(LOGGER_USER, "custom-packet-logger", CustomPacketLogger,
+ CustomPacketLoggerCondition, NULL, ThreadInit, ThreadDeinit, NULL);
+ OutputRegisterFlowLogger("custom-flow-logger", CustomFlowLogger, NULL, ThreadInit, ThreadDeinit);
+}
+
+const SCPlugin PluginRegistration = {
+ .name = "CustomLogger",
+ .author = "Firstname Lastname",
+ .license = "GPLv2",
+ .Init = Init,
+};
+
+const SCPlugin *SCPluginRegister(void)
+{
+ return &PluginRegistration;
+}