From: Jason Ish Date: Wed, 28 Feb 2024 21:21:57 +0000 (-0600) Subject: examples: minimal example capture plugin for ci X-Git-Tag: suricata-8.0.0-beta1~1691 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0dc3de332a5a584350f2c669f0c5438d4f0ffa9a;p=thirdparty%2Fsuricata.git examples: minimal example capture plugin for ci Create a mininal capture plugin that injects one packet. While it can also be a template, we should be able to run this in CI to test the loading and registration of the capture plugin mechanisms. --- diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index d358ecfed5..dc8228c43c 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -280,7 +280,7 @@ jobs: - name: Building Rust documentation run: make doc working-directory: rust - - run: make install + - run: make install install-conf - run: suricatasc -h - run: suricata-update -V - name: Check if Suricata-Update example configuration files are installed @@ -292,6 +292,14 @@ jobs: test -e /usr/local/lib/suricata/python/suricata/update/configs/threshold.in test -e /usr/local/lib/suricata/python/suricata/update/configs/update.yaml + - name: Test capture plugin + working-directory: examples/plugins/ci-capture + run: | + make + ../../../src/suricata -S /dev/null --set plugins.0=./capture.so --capture-plugin=ci-capture --runmode=single -l . -c ../../../suricata.yaml + cat eve.json | jq -c 'select(.dns)' + test $(cat eve.json | jq -c 'select(.dns)' | wc -l) = "1" + - name: Test library build in tree working-directory: examples/lib/simple run: make clean all diff --git a/configure.ac b/configure.ac index e2b437bc20..c510e7fccb 100644 --- a/configure.ac +++ b/configure.ac @@ -2641,6 +2641,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/ci-capture/Makefile) AC_CONFIG_FILES(examples/lib/simple/Makefile examples/lib/simple/Makefile.example) AC_OUTPUT diff --git a/examples/plugins/README.md b/examples/plugins/README.md index 8e47b6e7ff..5300f75034 100644 --- a/examples/plugins/README.md +++ b/examples/plugins/README.md @@ -4,3 +4,8 @@ An example plugin of an EVE/JSON filetype plugin. This type of plugin is useful if you want to send EVE output to custom destinations. + +## ci-capture + +A minimal capture plugin that can be used as a template, but also used +for testing capture plugin loading and registration in CI. diff --git a/examples/plugins/ci-capture/.gitignore b/examples/plugins/ci-capture/.gitignore new file mode 100644 index 0000000000..ee845fbb0f --- /dev/null +++ b/examples/plugins/ci-capture/.gitignore @@ -0,0 +1,4 @@ +!/Makefile.in +*.o +*.so +*.la diff --git a/examples/plugins/ci-capture/Makefile.in b/examples/plugins/ci-capture/Makefile.in new file mode 100644 index 0000000000..413abf5cc1 --- /dev/null +++ b/examples/plugins/ci-capture/Makefile.in @@ -0,0 +1,41 @@ +SRCS := plugin.c \ + runmode.c \ + source.c + +# 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)\"" + +OBJS := $(SRCS:.c=.o) + +all: Makefile capture.so + +%.o: %.c + $(CC) $(CPPFLAGS) -fPIC -c $< -o $@ + +capture.so: $(OBJS) + $(CC) $(CPPFLAGS) -fPIC -shared -o $@ $(OBJS) + +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/ci-capture/Makefile + +# Dummy rules to satisfy make dist. +dist distdir: diff --git a/examples/plugins/ci-capture/README.md b/examples/plugins/ci-capture/README.md new file mode 100644 index 0000000000..43e96ed19b --- /dev/null +++ b/examples/plugins/ci-capture/README.md @@ -0,0 +1,2 @@ +# Minimal Capture Plugin for CI + diff --git a/examples/plugins/ci-capture/plugin.c b/examples/plugins/ci-capture/plugin.c new file mode 100644 index 0000000000..26a2a749ef --- /dev/null +++ b/examples/plugins/ci-capture/plugin.c @@ -0,0 +1,56 @@ +/* Copyright (C) 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-plugin.h" +#include "suricata-common.h" +#include "util-debug.h" + +#include "runmode.h" +#include "source.h" + +static void InitCapturePlugin(const char *args, int plugin_slot, int receive_slot, int decode_slot) +{ + SCLogNotice("..."); + CiCaptureIdsRegister(plugin_slot); + TmModuleReceiveCiCaptureRegister(receive_slot); + TmModuleDecodeCiCaptureRegister(decode_slot); +} + +static void SCPluginInit(void) +{ + SCLogNotice("..."); + SCCapturePlugin *plugin = SCCalloc(1, sizeof(SCCapturePlugin)); + if (plugin == NULL) { + FatalError("Failed to allocate memory for capture plugin"); + } + plugin->name = "ci-capture"; + plugin->Init = InitCapturePlugin; + plugin->GetDefaultMode = CiCaptureIdsGetDefaultRunMode; + SCPluginRegisterCapture(plugin); +} + +const SCPlugin PluginRegistration = { + .name = "ci-capture", + .author = "OISF Developer", + .license = "GPL-2.0-only", + .Init = SCPluginInit, +}; + +const SCPlugin *SCPluginRegister() +{ + return &PluginRegistration; +} diff --git a/examples/plugins/ci-capture/runmode.c b/examples/plugins/ci-capture/runmode.c new file mode 100644 index 0000000000..7328fb4e63 --- /dev/null +++ b/examples/plugins/ci-capture/runmode.c @@ -0,0 +1,73 @@ +/* Copyright (C) 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 "runmodes.h" +#include "tm-threads.h" +#include "util-affinity.h" + +#include "runmode.h" + +const char *CiCaptureIdsGetDefaultRunMode(void) +{ + return "autofp"; +} + +static int RunModeSingle(void) +{ + SCLogNotice("..."); + + char thread_name[TM_THREAD_NAME_MAX]; + snprintf(thread_name, sizeof(thread_name), "%s#01", thread_name_single); + ThreadVars *tv = TmThreadCreatePacketHandler( + thread_name, "packetpool", "packetpool", "packetpool", "packetpool", "pktacqloop"); + if (tv == NULL) { + SCLogError("TmThreadCreatePacketHandler failed"); + return -1; + } + + TmModule *tm_module = TmModuleGetByName("ReceiveCiCapture"); + if (tm_module == NULL) { + FatalError("TmModuleGetByName failed for ReceiveCiCapture"); + } + TmSlotSetFuncAppend(tv, tm_module, NULL); + + tm_module = TmModuleGetByName("DecodeCiCapture"); + if (tm_module == NULL) { + FatalError("TmModuleGetByName DecodeCiCapture failed"); + } + TmSlotSetFuncAppend(tv, tm_module, NULL); + + tm_module = TmModuleGetByName("FlowWorker"); + if (tm_module == NULL) { + FatalError("TmModuleGetByName for FlowWorker failed"); + } + TmSlotSetFuncAppend(tv, tm_module, NULL); + + TmThreadSetCPU(tv, WORKER_CPU_SET); + + if (TmThreadSpawn(tv) != TM_ECODE_OK) { + FatalError("TmThreadSpawn failed"); + } + + return 0; +} + +void CiCaptureIdsRegister(int slot) +{ + RunModeRegisterNewRunMode(slot, "single", "Single threaded", RunModeSingle, NULL); +} diff --git a/examples/plugins/ci-capture/runmode.h b/examples/plugins/ci-capture/runmode.h new file mode 100644 index 0000000000..0a9070bf94 --- /dev/null +++ b/examples/plugins/ci-capture/runmode.h @@ -0,0 +1,24 @@ +/* Copyright (C) 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. + */ + +#ifndef CI_CAPTURE_PLUGIN_RUNMODE_H +#define CI_CAPTURE_PLUGIN_RUNMODE_H + +const char *CiCaptureIdsGetDefaultRunMode(void); +void CiCaptureIdsRegister(int slot); + +#endif /* CI_CAPTURE_PLUGIN_RUNMODE_H */ diff --git a/examples/plugins/ci-capture/source.c b/examples/plugins/ci-capture/source.c new file mode 100644 index 0000000000..4f4ddf6a23 --- /dev/null +++ b/examples/plugins/ci-capture/source.c @@ -0,0 +1,154 @@ +/* Copyright (C) 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.h" +#include "threadvars.h" +#include "tm-modules.h" +#include "tm-threads-common.h" +#include "tm-threads.h" + +#include "source.h" + +/* DNS request for suricata.io. */ +static const unsigned char DNS_REQUEST[94] = { + 0xa0, 0x36, 0x9f, 0x4c, 0x4c, 0x28, 0x50, 0xeb, /* .6.LL(P. */ + 0xf6, 0x7d, 0xea, 0x54, 0x08, 0x00, 0x45, 0x00, /* .}.T..E. */ + 0x00, 0x50, 0x19, 0xae, 0x00, 0x00, 0x40, 0x11, /* .P....@. */ + 0x4a, 0xc4, 0x0a, 0x10, 0x01, 0x0b, 0x0a, 0x10, /* J....... */ + 0x01, 0x01, 0x95, 0x97, 0x00, 0x35, 0x00, 0x3c, /* .....5.< */ + 0x90, 0x6e, 0xdb, 0x12, 0x01, 0x20, 0x00, 0x01, /* .n... .. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x73, /* .......s */ + 0x75, 0x72, 0x69, 0x63, 0x61, 0x74, 0x61, 0x02, /* uricata. */ + 0x69, 0x6f, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, /* io...... */ + 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00, 0x00, 0x00, /* .)...... */ + 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x08, 0x88, 0x51, /* .......Q */ + 0x20, 0xaf, 0x46, 0xc5, 0xdc, 0xce /* .F... */ +}; + +static TmEcode ReceiveThreadInit(ThreadVars *tv, const void *initdata, void **data) +{ + SCLogNotice("..."); + return TM_ECODE_OK; +} + +static TmEcode ReceiveThreadDeinit(ThreadVars *tv, void *data) +{ + SCLogNotice("..."); + return TM_ECODE_OK; +} + +static TmEcode ReceiveLoop(ThreadVars *tv, void *data, void *slot) +{ + SCLogNotice("..."); + + if (suricata_ctl_flags & SURICATA_STOP) { + SCReturnInt(TM_ECODE_OK); + } + + TmSlot *s = (TmSlot *)slot; + + /* Notify we are running and processing packets. */ + TmThreadsSetFlag(tv, THV_RUNNING); + + PacketPoolWait(); + Packet *p = PacketGetFromQueueOrAlloc(); + if (unlikely(p == NULL)) { + return TM_ECODE_FAILED; + } + PKT_SET_SRC(p, PKT_SRC_WIRE); + struct timeval now; + gettimeofday(&now, NULL); + p->ts = SCTIME_FROM_TIMEVAL(&now); + p->datalink = LINKTYPE_ETHERNET; + p->flags |= PKT_IGNORE_CHECKSUM; + + if (unlikely(PacketCopyData(p, DNS_REQUEST, sizeof(DNS_REQUEST)) != 0)) { + TmqhOutputPacketpool(tv, p); + return TM_ECODE_FAILED; + } + + if (TmThreadsSlotProcessPkt(tv, s, p) != TM_ECODE_OK) { + return TM_ECODE_FAILED; + } + + EngineStop(); + return TM_ECODE_DONE; +} + +static void ReceiveThreadExitPrintStats(ThreadVars *tv, void *data) +{ + SCLogNotice("..."); +} + +static TmEcode DecodeThreadInit(ThreadVars *tv, const void *initdata, void **data) +{ + SCLogNotice("..."); + + DecodeThreadVars *dtv = DecodeThreadVarsAlloc(tv); + if (dtv == NULL) { + SCReturnInt(TM_ECODE_FAILED); + } + DecodeRegisterPerfCounters(dtv, tv); + *data = (void *)dtv; + + return TM_ECODE_OK; +} + +static TmEcode DecodeThreadDeinit(ThreadVars *tv, void *data) +{ + SCLogNotice("..."); + + if (data != NULL) { + DecodeThreadVarsFree(tv, data); + } + SCReturnInt(TM_ECODE_OK); + + return TM_ECODE_OK; +} + +static TmEcode Decode(ThreadVars *tv, Packet *p, void *data) +{ + SCLogNotice("..."); + + DecodeLinkLayer(tv, data, p->datalink, p, GET_PKT_DATA(p), GET_PKT_LEN(p)); + + return TM_ECODE_OK; +} + +void TmModuleReceiveCiCaptureRegister(int slot) +{ + tmm_modules[slot].name = "ReceiveCiCapture"; + tmm_modules[slot].ThreadInit = ReceiveThreadInit; + tmm_modules[slot].Func = NULL; + tmm_modules[slot].PktAcqLoop = ReceiveLoop; + tmm_modules[slot].PktAcqBreakLoop = NULL; + tmm_modules[slot].ThreadExitPrintStats = ReceiveThreadExitPrintStats; + tmm_modules[slot].ThreadDeinit = ReceiveThreadDeinit; + tmm_modules[slot].cap_flags = 0; + tmm_modules[slot].flags = TM_FLAG_RECEIVE_TM; +} + +void TmModuleDecodeCiCaptureRegister(int slot) +{ + tmm_modules[slot].name = "DecodeCiCapture"; + tmm_modules[slot].ThreadInit = DecodeThreadInit; + tmm_modules[slot].Func = Decode; + tmm_modules[slot].ThreadExitPrintStats = NULL; + tmm_modules[slot].ThreadDeinit = DecodeThreadDeinit; + tmm_modules[slot].cap_flags = 0; + tmm_modules[slot].flags = TM_FLAG_DECODE_TM; +} diff --git a/examples/plugins/ci-capture/source.h b/examples/plugins/ci-capture/source.h new file mode 100644 index 0000000000..57111e2f40 --- /dev/null +++ b/examples/plugins/ci-capture/source.h @@ -0,0 +1,24 @@ +/* Copyright (C) 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. + */ + +#ifndef CI_CAPTURE_PLUGIN_SOURCE_H +#define CI_CAPTURE_PLUGIN_SOURCE_H + +void TmModuleReceiveCiCaptureRegister(int slot); +void TmModuleDecodeCiCaptureRegister(int slot); + +#endif /* CI_CAPTURE_PLUGIN_SOURCE_H */