You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/examples/lib/live/main.c

356 lines
11 KiB
C

/* 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-example", "Live capture application run mode",
AppRunModeSetup, NULL);
if (!SCConfSetFromString("runmode=live-example", 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;
}