diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index f91effdc89..0d86c6857b 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -194,6 +194,10 @@ jobs: working-directory: examples/plugins/c-json-filetype run: make clean all + - name: Build example C custom logger plugin + working-directory: examples/plugins/c-custom-loggers + run: make clean all + - name: Install Suricata and library run: make install install-headers install-library diff --git a/configure.ac b/configure.ac index 24dc01430a..03ffadd89b 100644 --- a/configure.ac +++ b/configure.ac @@ -2505,6 +2505,7 @@ AC_CONFIG_FILES(python/Makefile python/suricata/config/defaults.py) AC_CONFIG_FILES(ebpf/Makefile) AC_CONFIG_FILES(libsuricata-config) AC_CONFIG_FILES(examples/plugins/c-json-filetype/Makefile) +AC_CONFIG_FILES(examples/plugins/c-custom-loggers/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) diff --git a/examples/plugins/c-custom-loggers/.gitignore b/examples/plugins/c-custom-loggers/.gitignore new file mode 100644 index 0000000000..53ceb6a4ae --- /dev/null +++ b/examples/plugins/c-custom-loggers/.gitignore @@ -0,0 +1,3 @@ +!/Makefile.in +*.la +*.so diff --git a/examples/plugins/c-custom-loggers/Makefile.in b/examples/plugins/c-custom-loggers/Makefile.in new file mode 100644 index 0000000000..a30bc8a385 --- /dev/null +++ b/examples/plugins/c-custom-loggers/Makefile.in @@ -0,0 +1,32 @@ +# 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: diff --git a/examples/plugins/c-custom-loggers/README.md b/examples/plugins/c-custom-loggers/README.md new file mode 100644 index 0000000000..412cc4f2df --- /dev/null +++ b/examples/plugins/c-custom-loggers/README.md @@ -0,0 +1,55 @@ +# 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 +``` diff --git a/examples/plugins/c-custom-loggers/custom-logger.c b/examples/plugins/c-custom-loggers/custom-logger.c new file mode 100644 index 0000000000..a9cca80110 --- /dev/null +++ b/examples/plugins/c-custom-loggers/custom-logger.c @@ -0,0 +1,110 @@ +/* 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; +}