examples/lib/live: a lib example with live capture

Simple libpcap example for live capture. Allows listening on multiple
interfaces to show how multiple threads (workers) can be used.

Ticket: #8096
(cherry picked from commit f711e57e8e)
pull/15003/head
Jason Ish 2 months ago committed by Victor Julien
parent 5eae2993ee
commit 44e75573ac

@ -226,6 +226,10 @@ jobs:
test $(cat eve.json |jq 'select(.stats) | .stats.decoder.pkts') = 110
working-directory: examples/lib/custom
- name: Build live library example
run: make
working-directory: examples/lib/live
- name: Cleaning source directory for standalone plugin test.
run: make clean

@ -14,7 +14,8 @@ EXTRA_DIST = ChangeLog COPYING LICENSE suricata.yaml.in \
examples/plugins
SUBDIRS = rust src plugins qa rules doc etc python ebpf \
$(SURICATA_UPDATE_DIR)
DIST_SUBDIRS = $(SUBDIRS) examples/lib/simple examples/lib/custom
DIST_SUBDIRS = $(SUBDIRS) examples/lib/simple examples/lib/custom \
examples/lib/live
CLEANFILES = stamp-h[0-9]*

@ -2583,6 +2583,7 @@ AC_CONFIG_FILES(examples/plugins/ci-capture/Makefile)
AC_CONFIG_FILES(examples/lib/simple/Makefile examples/lib/simple/Makefile.example)
AC_CONFIG_FILES(examples/lib/custom/Makefile examples/lib/custom/Makefile.example)
AC_CONFIG_FILES(examples/lib/cplusplus/Makefile.example)
AC_CONFIG_FILES(examples/lib/live/Makefile examples/lib/live/Makefile.example)
AC_CONFIG_FILES(plugins/Makefile)
AC_CONFIG_FILES(plugins/pfring/Makefile)
AC_CONFIG_FILES(plugins/napatech/Makefile)

@ -0,0 +1,3 @@
!/Makefile.example.in
Makefile.example
/live

@ -0,0 +1,9 @@
bin_PROGRAMS = live
live_SOURCES = main.c
AM_CPPFLAGS = -I$(top_srcdir)/src
live_LDFLAGS = $(all_libraries) $(SECLDFLAGS)
live_LDADD = "-Wl,--start-group,$(top_builddir)/src/libsuricata_c.a,../../$(RUST_SURICATA_LIB),--end-group" $(RUST_LDADD)
live_DEPENDENCIES = $(top_builddir)/src/libsuricata_c.a ../../$(RUST_SURICATA_LIB)

@ -0,0 +1,17 @@
LIBSURICATA_CONFIG ?= @CONFIGURE_PREFIX@/bin/libsuricata-config
# Define STATIC=1 to request static linking where available
SURICATA_LIBS = `$(LIBSURICATA_CONFIG) --libs $${STATIC:+--static}`
SURICATA_CFLAGS := `$(LIBSURICATA_CONFIG) --cflags`
# Currently the Suricata logging system requires this to be even for
# plugins.
CPPFLAGS += "-D__SCFILENAME__=\"$(*F)\""
all: live
live: main.c
$(CC) -o $@ $^ $(CPPFLAGS) $(CFLAGS) $(SURICATA_CFLAGS) $(SURICATA_LIBS)
clean:
rm -f live

@ -0,0 +1,75 @@
# Live Capture Library Example
This is an example of using the Suricata library to capture live
traffic from a network interface with custom packet handling and
threading.
## Building In Tree
The Suricata build system has created a Makefile that should allow you
to build this application in-tree on most supported platforms. To
build simply run:
```
make
```
## Running
```
./live -i eth0 -l .
```
This example requires at least one `-i` option to specify the network
interface to capture from. You can specify multiple interfaces to
capture from multiple sources simultaneously - a separate worker thread
will be created for each interface:
```
./live -i eth0 -i eth1
```
Any additional arguments are passed directly to Suricata as command
line arguments.
Example with common options:
```
sudo ./live -i eth0 -- -l . -S rules.rules
```
Example capturing from multiple interfaces:
```
sudo ./live -i eth0 -i wlan0 -- -l . -S rules.rules
```
Shutdown: each worker thread may call EngineStop when its capture ends; the
main loop waits for this signal, performs SuricataShutdown concurrently with
per-thread SCTmThreadsSlotPacketLoopFinish, then joins all worker threads
before GlobalsDestroy.
The example supports up to 16 interfaces simultaneously.
## Building Out of Tree
A Makefile.example has also been generated to use as an example on how
to build against the library in a standalone application.
First build and install the Suricata library including:
```
make install-library
make install-headers
```
Then run:
```
make -f Makefile.example
```
If you installed to a non-standard location, you need to ensure that
`libsuricata-config` is in your path, for example:
```
PATH=/opt/suricata/bin:$PATH make -f Makefile.example
```

@ -0,0 +1,355 @@
/* Copyright (C) 2025 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 "detect.h"
#include "detect-engine.h"
#include "runmodes.h"
#include "conf.h"
#include "pcap.h"
#include "runmode-lib.h"
#include "tm-threads.h"
#include "threadvars.h"
#include "action-globals.h"
#include "packet.h"
#include "util-device.h"
#include <getopt.h>
#include <unistd.h>
/* Support up to 16 interfaces */
#define MAX_INTERFACES 16
static int worker_id = 1;
/* Interfaces parsed from command line, used by the runmode setup
* callback. */
static char *g_interfaces[MAX_INTERFACES];
static int g_interface_count = 0;
/* ThreadVars created during runmode setup, before thread sealing. */
static ThreadVars *g_worker_tvs[MAX_INTERFACES];
/**
* Struct to pass arguments into a worker thread.
*/
struct WorkerArgs {
ThreadVars *tv;
char *interface;
char *device_name;
};
/**
* Release packet callback.
*
* If there is any cleanup that needs to be done when Suricata is done
* with a packet, this is the place to do it.
*
* Important: If using a custom release function, you must also
* release or free the packet.
*
* Optionally this is where you would handle IPS like functionality
* such as forwarding the packet, or triggering some other mechanism
* to forward the packet.
*/
static void ReleasePacket(Packet *p)
{
if (PacketCheckAction(p, ACTION_DROP)) {
SCLogNotice("Dropping packet!");
}
/* As we overode the default release function, we must release or
* free the packet. */
PacketFreeOrRelease(p);
}
/**
* Suricata worker thread in library mode.
* The functions should be wrapped in an API layer.
*/
static void *SimpleWorker(void *arg)
{
struct WorkerArgs *args = arg;
ThreadVars *tv = args->tv;
int exit_code = EXIT_SUCCESS;
/* Start worker. */
if (SCRunModeLibSpawnWorker(tv) != 0) {
pthread_exit((void *)(intptr_t)EXIT_FAILURE);
}
/* Open live capture on interface. */
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t *fp = pcap_open_live(args->interface, 65535, 1, 1000, errbuf);
if (fp == NULL) {
SCLogError("Failed to open interface: %s", errbuf);
exit_code = EXIT_FAILURE;
goto done;
}
LiveDevice *device = LiveGetDevice(args->device_name);
assert(device != NULL);
int datalink = pcap_datalink(fp);
struct pcap_pkthdr *pkthdr;
const u_char *packet;
int pcap_rc;
while (1) {
/* Have we been asked to stop? */
if (suricata_ctl_flags & SURICATA_STOP) {
goto done;
}
pcap_rc = pcap_next_ex(fp, &pkthdr, &packet);
if (pcap_rc == 0) {
/* Timeout - no packet available, continue waiting */
continue;
} else if (pcap_rc == -1) {
/* Error occurred */
SCLogError("pcap_next_ex failed on %s: %s", args->interface, pcap_geterr(fp));
exit_code = EXIT_FAILURE;
goto done;
} else if (pcap_rc == -2) {
/* End of file (shouldn't happen in live capture) */
SCLogNotice("End of capture on %s", args->interface);
exit_code = EXIT_FAILURE;
goto done;
}
Packet *p = PacketGetFromQueueOrAlloc();
if (unlikely(p == NULL)) {
/* Memory allocation error: backoff and continue instead of stopping */
usleep(1000); /* brief sleep to avoid tight spin */
continue;
}
/* Setup the packet, these will become functions to avoid
* internal Packet access. */
SCPacketSetSource(p, PKT_SRC_WIRE);
SCPacketSetTime(p, SCTIME_FROM_TIMEVAL(&pkthdr->ts));
SCPacketSetDatalink(p, datalink);
SCPacketSetLiveDevice(p, device);
SCPacketSetReleasePacket(p, ReleasePacket);
if (PacketSetData(p, packet, pkthdr->len) == -1) {
TmqhOutputPacketpool(tv, p);
/* Bad packet or setup error; log and continue */
SCLogDebug("PacketSetData failed on %s", args->interface);
continue;
}
if (TmThreadsSlotProcessPkt(tv, tv->tm_slots, p) != TM_ECODE_OK) {
TmqhOutputPacketpool(tv, p);
/* Processing failure for this packet; continue capture */
SCLogDebug("TmThreadsSlotProcessPkt failed on %s", args->interface);
continue;
}
LiveDevicePktsIncr(device);
}
done:
if (fp != NULL) {
pcap_close(fp);
}
/* Signal main loop to shutdown. */
EngineStop();
/* Cleanup.
*
* Note that there is some thread synchronization between this
* function and SuricataShutdown such that they must be run
* concurrently at this time before either will exit. */
SCTmThreadsSlotPacketLoopFinish(tv);
SCLogNotice("Worker thread exiting");
pthread_exit((void *)(intptr_t)exit_code);
}
static uint8_t RateFilterCallback(const Packet *p, const uint32_t sid, const uint32_t gid,
const uint32_t rev, uint8_t original_action, uint8_t new_action, void *arg)
{
/* Don't change the action. */
return new_action;
}
/**
* Application runmode setup that creates ThreadVars before thread
* sealing. This follows the pattern used by other runmodes where
* threads are registered during the runmode callback.
*/
static int AppRunModeSetup(void)
{
for (int i = 0; i < g_interface_count; i++) {
g_worker_tvs[i] = SCRunModeLibCreateThreadVars(worker_id++);
if (!g_worker_tvs[i]) {
SCLogError("Failed to create ThreadVars for interface %s", g_interfaces[i]);
return -1;
}
}
return 0;
}
int main(int argc, char **argv)
{
int opt;
/* Parse command line options using getopt */
while ((opt = getopt(argc, argv, "i:")) != -1) {
switch (opt) {
case 'i':
if (g_interface_count >= MAX_INTERFACES) {
fprintf(stderr, "ERROR: Maximum %d interfaces supported\n", MAX_INTERFACES);
exit(EXIT_FAILURE);
}
g_interfaces[g_interface_count++] = optarg;
break;
default:
fprintf(stderr, "Usage: %s -i interface [-i interface2 ...] [suricata_options]\n",
argv[0]);
fprintf(stderr, " -i interface Network interface to capture from (can be "
"specified multiple times)\n");
exit(EXIT_FAILURE);
}
}
if (g_interface_count == 0) {
fprintf(stderr, "ERROR: At least one interface (-i) is required\n");
fprintf(stderr, "Usage: %s -i interface [-i interface2 ...] [suricata_options]\n", argv[0]);
exit(EXIT_FAILURE);
}
SuricataPreInit(argv[0]);
/* Pass through the arguments after -- to Suricata. */
char *suricata_argv[argc - optind + 2];
int suricata_argc = 0;
suricata_argv[suricata_argc++] = argv[0];
while (optind < argc) {
suricata_argv[suricata_argc++] = argv[optind++];
}
suricata_argv[suricata_argc] = NULL;
optind = 1;
/* Log the command line arguments being passed to Suricata */
if (suricata_argc > 1) {
fprintf(stderr, "Passing command line arguments to Suricata:");
for (int i = 1; i < suricata_argc; i++) {
fprintf(stderr, " %s", suricata_argv[i]);
}
fprintf(stderr, "\n");
}
SCParseCommandLine(suricata_argc, suricata_argv);
/* Set the runmode to library mode. Perhaps in the future this
* should be done in some library bootstrap function. */
SCRunmodeSet(RUNMODE_LIB);
/* Validate/finalize the runmode. */
if (SCFinalizeRunMode() != TM_ECODE_OK) {
exit(EXIT_FAILURE);
}
/* Load configuration file, could be done earlier but must be done
* before SuricataInit, but even then its still optional as you
* may be programmatically configuration Suricata. */
if (SCLoadYamlConfig() != TM_ECODE_OK) {
exit(EXIT_FAILURE);
}
/* Enable default signal handlers including SIGHUP for log file rotation,
* and SIGUSR2 for reloading rules. This should be done with care by a
* library user as the application may already have signal handlers
* loaded. */
SCEnableDefaultSignalHandlers();
/* Register our own library run mode. At this time, the ThreadVars
* for each capture thread need to be created in the provided
* callback to meet thread synchronization requirements. */
RunModeRegisterNewRunMode(
RUNMODE_LIB, "live", "Live capture application run mode", AppRunModeSetup, NULL);
if (!SCConfSetFromString("runmode=live", 1)) {
exit(EXIT_FAILURE);
}
/* Force logging to the current directory. */
SCConfSetFromString("default-log-dir=.", 1);
/* Register a LiveDevice for each interface */
for (int i = 0; i < g_interface_count; i++) {
if (LiveRegisterDevice(g_interfaces[i]) < 0) {
FatalError("LiveRegisterDevice failed for %s", g_interfaces[i]);
}
SCLogNotice("Registered device %s", g_interfaces[i]);
}
/* SuricataInit will call our AppRunModeSetup, when it returns our
* ThreadVars will be ready. */
SuricataInit();
SCDetectEngineRegisterRateFilterCallback(RateFilterCallback, NULL);
/* Spawn our worker threads, one for each interface. */
pthread_t workers[MAX_INTERFACES];
struct WorkerArgs worker_args[MAX_INTERFACES];
for (int i = 0; i < g_interface_count; i++) {
worker_args[i].tv = g_worker_tvs[i];
worker_args[i].interface = g_interfaces[i];
worker_args[i].device_name = g_interfaces[i];
if (pthread_create(&workers[i], NULL, SimpleWorker, &worker_args[i]) != 0) {
FatalError("Failed to create worker thread for interface %s", g_interfaces[i]);
}
SCLogNotice("Started worker thread for interface %s", g_interfaces[i]);
}
SuricataPostInit();
/* Run the main loop, this just waits for the worker threads to
* call EngineStop signalling Suricata that it is done capturing
* from the interfaces. */
SuricataMainLoop();
/* Shutdown engine. */
SCLogNotice("Shutting down");
/* Note that there is some thread synchronization between this
* function and SCTmThreadsSlotPacketLoopFinish that require them
* to be run concurrently at this time. */
SuricataShutdown();
/* Ensure our capture workers have fully exited before teardown. */
int exit_status = EXIT_SUCCESS;
for (int i = 0; i < g_interface_count; i++) {
void *worker_status;
pthread_join(workers[i], &worker_status);
if ((intptr_t)worker_status != EXIT_SUCCESS) {
exit_status = EXIT_FAILURE;
}
}
GlobalsDestroy();
return exit_status;
}
Loading…
Cancel
Save