mirror of
https://github.com/OISF/suricata.git
synced 2026-02-18 18:18:04 -05:00
pcap: refactor delete-when-done to support non-alerts
Some checks are pending
builds / Prepare dependencies (push) Waiting to run
builds / Prepare cbindgen (push) Waiting to run
builds / AlmaLinux 10 (schema, plugins) (push) Blocked by required conditions
builds / AlmaLinux 9 (schema, rust-checks) (push) Blocked by required conditions
builds / AlmaLinux 9 Test Templates (push) Blocked by required conditions
builds / Build RPMs (push) Blocked by required conditions
builds / AlmaLinux 8 (push) Blocked by required conditions
builds / CentOS Stream 9 (push) Blocked by required conditions
builds / Fedora 43 (Suricata Verify codecov) (push) Blocked by required conditions
builds / Fedora 43 (clang, debug, asan, wshadow, rust-strict, systemd) (push) Blocked by required conditions
builds / Fedora 43 (gcc, debug, flto, asan, wshadow, rust-strict) (push) Blocked by required conditions
builds / Fedora (non-root, debug, clang, asan, wshadow, rust-strict, no-ja) (push) Blocked by required conditions
builds / AlmaLinux 9 (no jansson) (push) Blocked by required conditions
builds / AlmaLinux 9 (Minimal/Recommended Build) (push) Blocked by required conditions
builds / Ubuntu 24.04 (cocci) (push) Blocked by required conditions
builds / Ubuntu 24.04 (RUSTC+CARGO vars) (push) Blocked by required conditions
builds / Ubuntu 24.04 (unittests coverage) (push) Blocked by required conditions
builds / Ubuntu 22.04 (unix socket mode coverage) (push) Blocked by required conditions
builds / Ubuntu 22.04 (afpacket and dpdk coverage) (push) Blocked by required conditions
builds / Ubuntu 24.04 (pcap unix socket ASAN) (push) Blocked by required conditions
builds / Ubuntu 24.04 (afpacket IPS tests in namespaces) (push) Blocked by required conditions
builds / Ubuntu 24.04 (afpacket and dpdk live tests with ASAN) (push) Blocked by required conditions
builds / Ubuntu 22.04 (fuzz corpus coverage) (push) Blocked by required conditions
builds / Ubuntu 20.04 (-DNDEBUG) (push) Blocked by required conditions
builds / Ubuntu 20.04 (unsupported rust) (push) Blocked by required conditions
builds / Ubuntu 22.04 (Debug Validation) (push) Blocked by required conditions
builds / Ubuntu 22.04 (Fuzz) (push) Blocked by required conditions
builds / Ubuntu 22.04 (Netmap build) (push) Blocked by required conditions
builds / Ubuntu 22.04 (Minimal/Recommended Build) (push) Blocked by required conditions
builds / Ubuntu 22.04 (DPDK Build) (push) Blocked by required conditions
builds / Debian 12 (xdp) (push) Blocked by required conditions
builds / Debian 13 (xdp) (push) Blocked by required conditions
builds / Ubuntu 22.04 Dist Builder (push) Blocked by required conditions
builds / Debian 12 MSRV (push) Blocked by required conditions
builds / Debian 11 (push) Blocked by required conditions
builds / MacOS Latest (push) Blocked by required conditions
builds / Windows MSYS2 MINGW64 (NPcap) (push) Blocked by required conditions
builds / Windows MSYS2 MINGW64 (libpcap) (push) Blocked by required conditions
builds / Windows MSYS2 UCRT64 (libpcap) (push) Blocked by required conditions
builds / Windows MSYS2 MINGW64 (WinDivert) (push) Blocked by required conditions
builds / PF_RING (push) Blocked by required conditions
CodeQL (Rust/C) / Analyze (push) Waiting to run
docs / Prepare dependencies (push) Waiting to run
docs / Prepare cbindgen (push) Waiting to run
docs / Ubuntu 22.04 Dist Builder (push) Blocked by required conditions
Nix Env Build / tests (push) Waiting to run
Scan-build / Scan-build (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Some checks are pending
builds / Prepare dependencies (push) Waiting to run
builds / Prepare cbindgen (push) Waiting to run
builds / AlmaLinux 10 (schema, plugins) (push) Blocked by required conditions
builds / AlmaLinux 9 (schema, rust-checks) (push) Blocked by required conditions
builds / AlmaLinux 9 Test Templates (push) Blocked by required conditions
builds / Build RPMs (push) Blocked by required conditions
builds / AlmaLinux 8 (push) Blocked by required conditions
builds / CentOS Stream 9 (push) Blocked by required conditions
builds / Fedora 43 (Suricata Verify codecov) (push) Blocked by required conditions
builds / Fedora 43 (clang, debug, asan, wshadow, rust-strict, systemd) (push) Blocked by required conditions
builds / Fedora 43 (gcc, debug, flto, asan, wshadow, rust-strict) (push) Blocked by required conditions
builds / Fedora (non-root, debug, clang, asan, wshadow, rust-strict, no-ja) (push) Blocked by required conditions
builds / AlmaLinux 9 (no jansson) (push) Blocked by required conditions
builds / AlmaLinux 9 (Minimal/Recommended Build) (push) Blocked by required conditions
builds / Ubuntu 24.04 (cocci) (push) Blocked by required conditions
builds / Ubuntu 24.04 (RUSTC+CARGO vars) (push) Blocked by required conditions
builds / Ubuntu 24.04 (unittests coverage) (push) Blocked by required conditions
builds / Ubuntu 22.04 (unix socket mode coverage) (push) Blocked by required conditions
builds / Ubuntu 22.04 (afpacket and dpdk coverage) (push) Blocked by required conditions
builds / Ubuntu 24.04 (pcap unix socket ASAN) (push) Blocked by required conditions
builds / Ubuntu 24.04 (afpacket IPS tests in namespaces) (push) Blocked by required conditions
builds / Ubuntu 24.04 (afpacket and dpdk live tests with ASAN) (push) Blocked by required conditions
builds / Ubuntu 22.04 (fuzz corpus coverage) (push) Blocked by required conditions
builds / Ubuntu 20.04 (-DNDEBUG) (push) Blocked by required conditions
builds / Ubuntu 20.04 (unsupported rust) (push) Blocked by required conditions
builds / Ubuntu 22.04 (Debug Validation) (push) Blocked by required conditions
builds / Ubuntu 22.04 (Fuzz) (push) Blocked by required conditions
builds / Ubuntu 22.04 (Netmap build) (push) Blocked by required conditions
builds / Ubuntu 22.04 (Minimal/Recommended Build) (push) Blocked by required conditions
builds / Ubuntu 22.04 (DPDK Build) (push) Blocked by required conditions
builds / Debian 12 (xdp) (push) Blocked by required conditions
builds / Debian 13 (xdp) (push) Blocked by required conditions
builds / Ubuntu 22.04 Dist Builder (push) Blocked by required conditions
builds / Debian 12 MSRV (push) Blocked by required conditions
builds / Debian 11 (push) Blocked by required conditions
builds / MacOS Latest (push) Blocked by required conditions
builds / Windows MSYS2 MINGW64 (NPcap) (push) Blocked by required conditions
builds / Windows MSYS2 MINGW64 (libpcap) (push) Blocked by required conditions
builds / Windows MSYS2 UCRT64 (libpcap) (push) Blocked by required conditions
builds / Windows MSYS2 MINGW64 (WinDivert) (push) Blocked by required conditions
builds / PF_RING (push) Blocked by required conditions
CodeQL (Rust/C) / Analyze (push) Waiting to run
docs / Prepare dependencies (push) Waiting to run
docs / Prepare cbindgen (push) Waiting to run
docs / Ubuntu 22.04 Dist Builder (push) Blocked by required conditions
Nix Env Build / tests (push) Waiting to run
Scan-build / Scan-build (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Refactor pcap file deletion to use a single delete-when-done option with three values instead of separate boolean options: - false (default): No deletion - true: Always delete files - "non-alerts": Delete only files with no alerts Also account for alerts produced by pseudo packets (flow timeout / shutdown flush): - Introduce small capture hooks and invoke on pseudo-packet creation so the capture layer can retain references and observe alerts emitted after the last live packet - Call the hook from both TmThreadDisableReceiveThreads and TmThreadDrainPacketThreads Key changes: - Replace should_delete/delete_non_alerts_only bools with enum - Move alert counter from global to per-file PcapFileFileVars - Relocate alert counting from PacketAlertFinalize to pcap module - Ensure thread safety for both single and continuous pcap modes - Add unit tests for configuration parsing and pseudo-packet alert path The --pcap-file-delete command line option overrides YAML config and forces "always delete" mode for backward compatibility. Documentation updated to reflect the new three-value configuration. Fixes OISF#7786
This commit is contained in:
parent
539e4ee665
commit
e1f9d8a067
13 changed files with 996 additions and 33 deletions
|
|
@ -15,6 +15,8 @@ Configuration
|
|||
checksum-checks: auto
|
||||
# buffer-size: 128 KiB
|
||||
# tenant-id: none
|
||||
# Applies to file and directory. Options: false (no deletion), true (always delete),
|
||||
# "non-alerts" (delete only files with no alerts)
|
||||
# delete-when-done: false
|
||||
# recursive: false
|
||||
# continuous: false
|
||||
|
|
@ -85,9 +87,22 @@ Other options
|
|||
|
||||
**delete-when-done**
|
||||
|
||||
- If ``true``, Suricata deletes the PCAP file after processing.
|
||||
- The command-line option is
|
||||
:ref:`--pcap-file-delete <cmdline-option-pcap-file-delete>`
|
||||
Controls when PCAP files are deleted after processing. Three values are supported:
|
||||
|
||||
- ``false`` (default): Files are never deleted
|
||||
- ``true``: Files are always deleted after processing
|
||||
- ``"non-alerts"``: Files are deleted only if they didn't generate any alerts
|
||||
|
||||
.. note::
|
||||
|
||||
The command-line option :ref:`--pcap-file-delete <cmdline-option-pcap-file-delete>`
|
||||
overrides this configuration and forces "always delete" mode (``true``).
|
||||
|
||||
.. warning::
|
||||
|
||||
When using ``"non-alerts"`` mode, file deletion is deferred until thread
|
||||
cleanup to ensure alert counts are finalized. This may delay deletion
|
||||
compared to other modes.
|
||||
|
||||
**BPF filter**
|
||||
|
||||
|
|
|
|||
|
|
@ -80,10 +80,23 @@
|
|||
|
||||
.. option:: --pcap-file-delete
|
||||
|
||||
Used with the -r option to indicate that the mode should delete pcap files
|
||||
after they have been processed. This is useful with pcap-file-continuous to
|
||||
continuously feed files to a directory and have them cleaned up when done. If
|
||||
this option is not set, pcap files will not be deleted after processing.
|
||||
Used with the -r option to force deletion of pcap files after they have been
|
||||
processed. This is useful with pcap-file-continuous to continuously feed files
|
||||
to a directory and have them cleaned up when done.
|
||||
|
||||
**command-line vs Configuration**: This command-line option overrides the
|
||||
``pcap-file.delete-when-done`` configuration option in ``suricata.yaml`` and
|
||||
forces "always delete" mode (equivalent to ``delete-when-done: true``).
|
||||
|
||||
**For more control**, use the ``pcap-file.delete-when-done`` configuration
|
||||
option instead, which supports three values:
|
||||
|
||||
- ``false`` (default): No files are deleted
|
||||
- ``true``: All files are deleted after processing
|
||||
- ``"non-alerts"``: Only files that generated no alerts are deleted
|
||||
|
||||
If neither ``--pcap-file-delete`` nor ``delete-when-done`` is configured,
|
||||
pcap files will not be deleted after processing.
|
||||
|
||||
.. _cmdline-option-pcap-file-buffer-size:
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ noinst_HEADERS = \
|
|||
app-layer.h \
|
||||
bindgen.h \
|
||||
build-info.h \
|
||||
capture-hooks.h \
|
||||
conf-yaml-loader.h \
|
||||
conf.h \
|
||||
counters.h \
|
||||
|
|
@ -636,6 +637,7 @@ libsuricata_c_a_SOURCES = \
|
|||
app-layer-ssl.c \
|
||||
app-layer-tftp.c \
|
||||
app-layer.c \
|
||||
capture-hooks.c \
|
||||
conf-yaml-loader.c \
|
||||
conf.c \
|
||||
counters.c \
|
||||
|
|
|
|||
65
src/capture-hooks.c
Normal file
65
src/capture-hooks.c
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* Copyright (C) 2007-2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Lightweight indirection layer for capture-related callbacks.
|
||||
*
|
||||
* This module lets the capture implementation register small hooks that the
|
||||
* generic engine can invoke without hard dependencies. Two hooks are used:
|
||||
* - on-alerts: invoked when a packet produced alerts so capture can update
|
||||
* per-input stats (e.g., deciding if a pcap should be deleted or kept).
|
||||
* - on-pseudo-created: invoked when the engine creates pseudo packets (e.g.,
|
||||
* flow timeout or shutdown flush). This allows capture to retain references
|
||||
* or track alert outcomes tied to those pseudo packets.
|
||||
*/
|
||||
|
||||
#include "suricata-common.h"
|
||||
#include "capture-hooks.h"
|
||||
|
||||
static CaptureOnPacketWithAlertsHook g_on_alerts_hook = NULL;
|
||||
static CaptureOnPseudoPacketCreatedHook g_on_pseudo_created_hook = NULL;
|
||||
|
||||
void CaptureHooksSet(
|
||||
CaptureOnPacketWithAlertsHook OnAlerts, CaptureOnPseudoPacketCreatedHook OnPseudoCreated)
|
||||
{
|
||||
/* Allow re-setting the same hooks (e.g. during pcap file reload), but BUG_ON
|
||||
* if overwriting with different ones without clearing. */
|
||||
if (g_on_alerts_hook != NULL) {
|
||||
BUG_ON(g_on_alerts_hook != OnAlerts);
|
||||
}
|
||||
if (g_on_pseudo_created_hook != NULL) {
|
||||
BUG_ON(g_on_pseudo_created_hook != OnPseudoCreated);
|
||||
}
|
||||
|
||||
g_on_alerts_hook = OnAlerts;
|
||||
g_on_pseudo_created_hook = OnPseudoCreated;
|
||||
}
|
||||
|
||||
void CaptureHooksOnPacketWithAlerts(const Packet *p)
|
||||
{
|
||||
if (g_on_alerts_hook != NULL) {
|
||||
g_on_alerts_hook(p);
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureHooksOnPseudoPacketCreated(Packet *p)
|
||||
{
|
||||
if (g_on_pseudo_created_hook != NULL) {
|
||||
g_on_pseudo_created_hook(p);
|
||||
}
|
||||
}
|
||||
43
src/capture-hooks.h
Normal file
43
src/capture-hooks.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* Copyright (C) 2007-2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file capture-hooks.h
|
||||
* Small hook interface for capture modules to react to events in the
|
||||
* generic engine without creating circular dependencies.
|
||||
*/
|
||||
|
||||
#ifndef SURICATA_CAPTURE_HOOKS_H
|
||||
#define SURICATA_CAPTURE_HOOKS_H
|
||||
|
||||
#include "suricata-common.h"
|
||||
|
||||
struct Packet_;
|
||||
typedef struct Packet_ Packet;
|
||||
|
||||
typedef void (*CaptureOnPacketWithAlertsHook)(const Packet *p);
|
||||
typedef void (*CaptureOnPseudoPacketCreatedHook)(Packet *p);
|
||||
|
||||
/* Register/clear hooks (called by capture implementations) */
|
||||
void CaptureHooksSet(
|
||||
CaptureOnPacketWithAlertsHook OnAlerts, CaptureOnPseudoPacketCreatedHook OnPseudoCreated);
|
||||
|
||||
/* Invoke hooks (called from generic code, safe if unset) */
|
||||
void CaptureHooksOnPacketWithAlerts(const Packet *p);
|
||||
void CaptureHooksOnPseudoPacketCreated(Packet *p);
|
||||
|
||||
#endif /* SURICATA_CAPTURE_HOOKS_H */
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
#include "util-validate.h"
|
||||
|
||||
#include "action-globals.h"
|
||||
#include "capture-hooks.h"
|
||||
|
||||
/** tag signature we use for tag alerts */
|
||||
static Signature g_tag_signature;
|
||||
|
|
@ -582,6 +583,12 @@ static inline void PacketAlertFinalizeProcessQueue(
|
|||
p->flags |= PKT_FIRST_ALERTS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Notify capture layer about packets with real alerts (not pass-only),
|
||||
* so capture impl can update per-capture context (e.g. pcap-file alert counts). */
|
||||
if (alerted) {
|
||||
CaptureHooksOnPacketWithAlerts(p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@
|
|||
#include "decode-vntag.h"
|
||||
#include "decode-vxlan.h"
|
||||
#include "decode-pppoe.h"
|
||||
#include "source-pcap-file-helper.h"
|
||||
|
||||
#include "output-json-stats.h"
|
||||
|
||||
|
|
@ -212,6 +213,7 @@ static void RegisterUnittests(void)
|
|||
StreamingBufferRegisterTests();
|
||||
MacSetRegisterTests();
|
||||
FlowRateRegisterTests();
|
||||
SourcePcapFileHelperRegisterTests();
|
||||
#ifdef OS_WIN32
|
||||
Win32SyscallRegisterTests();
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -28,34 +28,77 @@
|
|||
#include "util-datalink.h"
|
||||
#include "util-checksum.h"
|
||||
#include "util-profiling.h"
|
||||
#include "source-pcap-file.h"
|
||||
#include "util-exception-policy.h"
|
||||
#include "conf-yaml-loader.h"
|
||||
#include "capture-hooks.h"
|
||||
#include "threads.h"
|
||||
|
||||
extern uint32_t max_pending_packets;
|
||||
extern PcapFileGlobalVars pcap_g;
|
||||
|
||||
static PcapFileFileVars *pcap_current_pfv = NULL;
|
||||
|
||||
static void PcapFileCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt);
|
||||
|
||||
static void PcapFileReleasePacket(Packet *p)
|
||||
{
|
||||
PcapFileFileVars *pfv = p->pcap_v.pfv;
|
||||
if (pfv != NULL) {
|
||||
PcapFileFinalizePacket(pfv);
|
||||
}
|
||||
|
||||
PacketFreeOrRelease(p);
|
||||
}
|
||||
|
||||
void PcapFileReleasePseudoPacket(Packet *p)
|
||||
{
|
||||
PcapFileFileVars *pfv = p->pcap_v.pfv;
|
||||
/* Alerts are counted in PacketAlertFinalize via PcapFileAddAlertCount, so
|
||||
* avoid double-counting here. Decrement refcount if we held one. */
|
||||
if (pfv != NULL) {
|
||||
uint32_t prev = SC_ATOMIC_SUB(pfv->ref_cnt, 1);
|
||||
if (prev == 1 && pfv->cleanup_requested) {
|
||||
CleanupPcapFileFileVars(pfv);
|
||||
}
|
||||
}
|
||||
PacketFreeOrRelease(p);
|
||||
}
|
||||
|
||||
void CleanupPcapFileFileVars(PcapFileFileVars *pfv)
|
||||
{
|
||||
if (pfv != NULL) {
|
||||
if (pfv->pcap_handle != NULL) {
|
||||
pcap_close(pfv->pcap_handle);
|
||||
pfv->pcap_handle = NULL;
|
||||
}
|
||||
if (pfv->filename != NULL) {
|
||||
if (pfv->shared != NULL && pfv->shared->should_delete) {
|
||||
SCLogDebug("Deleting pcap file %s", pfv->filename);
|
||||
if (unlink(pfv->filename) != 0) {
|
||||
SCLogWarning("Failed to delete %s: %s", pfv->filename, strerror(errno));
|
||||
}
|
||||
}
|
||||
SCFree(pfv->filename);
|
||||
pfv->filename = NULL;
|
||||
}
|
||||
pfv->shared = NULL;
|
||||
SCFree(pfv);
|
||||
if (pfv == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If there are still packets in flight, defer ALL cleanup actions, including
|
||||
* the deletion decision, until the last packet completes. */
|
||||
if (SC_ATOMIC_GET(pfv->ref_cnt) != 0) {
|
||||
pfv->cleanup_requested = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/* No packets in flight anymore: it's now safe to close, decide, and delete. */
|
||||
if (pfv->pcap_handle != NULL) {
|
||||
pcap_close(pfv->pcap_handle);
|
||||
pfv->pcap_handle = NULL;
|
||||
}
|
||||
|
||||
if (pfv->filename != NULL) {
|
||||
if (PcapFileShouldDeletePcapFile(pfv)) {
|
||||
SCLogDebug("Deleting pcap file %s", pfv->filename);
|
||||
if (unlink(pfv->filename) != 0) {
|
||||
SCLogWarning("Failed to delete %s: %s", pfv->filename, strerror(errno));
|
||||
}
|
||||
}
|
||||
SCFree(pfv->filename);
|
||||
pfv->filename = NULL;
|
||||
}
|
||||
|
||||
pfv->shared = NULL;
|
||||
if (pcap_current_pfv == pfv) {
|
||||
pcap_current_pfv = NULL;
|
||||
}
|
||||
SCFree(pfv);
|
||||
}
|
||||
|
||||
void PcapFileCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt)
|
||||
|
|
@ -74,6 +117,10 @@ void PcapFileCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt)
|
|||
if (unlikely(p == NULL)) {
|
||||
SCReturn;
|
||||
}
|
||||
SC_ATOMIC_ADD(ptv->ref_cnt, 1);
|
||||
SCLogDebug("pcap-file: got packet, pfv=%p filename=%s ref_cnt now=%u p=%p", (void *)ptv,
|
||||
ptv->filename, SC_ATOMIC_GET(ptv->ref_cnt), (void *)p);
|
||||
|
||||
PACKET_PROFILING_TMM_START(p, TMM_RECEIVEPCAPFILE);
|
||||
|
||||
PKT_SET_SRC(p, PKT_SRC_WIRE);
|
||||
|
|
@ -83,9 +130,12 @@ void PcapFileCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt)
|
|||
p->pcap_v.pcap_cnt = ++pcap_g.cnt;
|
||||
|
||||
p->pcap_v.tenant_id = ptv->shared->tenant_id;
|
||||
p->pcap_v.pfv = ptv;
|
||||
ptv->shared->pkts++;
|
||||
ptv->shared->bytes += h->caplen;
|
||||
|
||||
p->ReleasePacket = PcapFileReleasePacket;
|
||||
|
||||
if (unlikely(PacketCopyData(p, pkt, h->caplen))) {
|
||||
TmqhOutputPacketpool(ptv->shared->tv, p);
|
||||
PACKET_PROFILING_TMM_END(p, TMM_RECEIVEPCAPFILE);
|
||||
|
|
@ -120,6 +170,16 @@ const char *PcapFileGetFilename(void)
|
|||
return pcap_filename;
|
||||
}
|
||||
|
||||
void PcapFileSetCurrentPfv(PcapFileFileVars *pfv)
|
||||
{
|
||||
pcap_current_pfv = pfv;
|
||||
}
|
||||
|
||||
PcapFileFileVars *PcapFileGetCurrentPfv(void)
|
||||
{
|
||||
return pcap_current_pfv;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Main PCAP file reading Loop function
|
||||
*/
|
||||
|
|
@ -127,6 +187,7 @@ TmEcode PcapFileDispatch(PcapFileFileVars *ptv)
|
|||
{
|
||||
SCEnter();
|
||||
|
||||
PcapFileSetCurrentPfv(ptv);
|
||||
/* initialize all the thread's initial timestamp */
|
||||
if (likely(ptv->first_pkt_hdr != NULL)) {
|
||||
TmThreadsInitThreadsTimestamp(SCTIME_FROM_TIMEVAL(&ptv->first_pkt_ts));
|
||||
|
|
@ -236,6 +297,14 @@ TmEcode InitPcapFile(PcapFileFileVars *pfv)
|
|||
pcap_freecode(&pfv->filter);
|
||||
}
|
||||
|
||||
SC_ATOMIC_INIT(pfv->alerts_count);
|
||||
SC_ATOMIC_SET(pfv->alerts_count, 0);
|
||||
|
||||
SC_ATOMIC_INIT(pfv->ref_cnt);
|
||||
SC_ATOMIC_SET(pfv->ref_cnt, 0);
|
||||
|
||||
pfv->cleanup_requested = false;
|
||||
|
||||
pfv->datalink = pcap_datalink(pfv->pcap_handle);
|
||||
SCLogDebug("datalink %" PRId32 "", pfv->datalink);
|
||||
DatalinkSetGlobalType(pfv->datalink);
|
||||
|
|
@ -285,3 +354,687 @@ TmEcode ValidateLinkType(int datalink, DecoderFunc *DecoderFn)
|
|||
|
||||
SCReturnInt(TM_ECODE_OK);
|
||||
}
|
||||
|
||||
bool PcapFileShouldDeletePcapFile(PcapFileFileVars *pfv)
|
||||
{
|
||||
if (pfv == NULL || pfv->shared == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pfv->shared->delete_mode == PCAP_FILE_DELETE_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pfv->shared->delete_mode == PCAP_FILE_DELETE_ALWAYS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* PCAP_FILE_DELETE_NON_ALERTS mode */
|
||||
uint64_t file_alerts = SC_ATOMIC_GET(pfv->alerts_count);
|
||||
|
||||
if (file_alerts != 0) {
|
||||
SCLogDebug("Skipping deletion of %s due to %" PRIu64 " alert(s) generated.", pfv->filename,
|
||||
file_alerts);
|
||||
return false;
|
||||
}
|
||||
|
||||
SCLogDebug("pcap-file: will delete %s (no alerts counted)", pfv->filename);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcapFileFinalizePacket(PcapFileFileVars *pfv)
|
||||
{
|
||||
if (pfv != NULL) {
|
||||
/* decrease ref count as packet is done */
|
||||
uint32_t prev = SC_ATOMIC_SUB(pfv->ref_cnt, 1);
|
||||
SCLogDebug("pcap-file: packet done pfv=%p filename=%s ref_cnt was=%u now=%u", (void *)pfv,
|
||||
pfv->filename, prev, prev - 1);
|
||||
if (prev == 1) {
|
||||
if (pfv->cleanup_requested) {
|
||||
CleanupPcapFileFileVars(pfv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PcapFileAddAlertCount(PcapFileFileVars *pfv, uint16_t alert_count)
|
||||
{
|
||||
if (pfv != NULL && alert_count > 0) {
|
||||
SC_ATOMIC_ADD(pfv->alerts_count, alert_count);
|
||||
}
|
||||
}
|
||||
|
||||
static void PcapCaptureOnPacketWithAlerts(const Packet *p)
|
||||
{
|
||||
PcapFileFileVars *pfv = p->pcap_v.pfv;
|
||||
if (pfv == NULL) {
|
||||
pfv = PcapFileGetCurrentPfv();
|
||||
}
|
||||
if (pfv != NULL) {
|
||||
/* alerts.cnt is uint16_t; count alerts for delete-on-non-alerts logic */
|
||||
PcapFileAddAlertCount(pfv, p->alerts.cnt);
|
||||
}
|
||||
}
|
||||
|
||||
static void PcapCaptureOnPseudoPacketCreated(Packet *p)
|
||||
{
|
||||
/* For pseudo packets created by generic layers, associate with current pfv
|
||||
* and ensure refcount held so deletion defers. */
|
||||
if (p->pcap_v.pfv == NULL) {
|
||||
PcapFileFileVars *pfv = PcapFileGetCurrentPfv();
|
||||
if (pfv != NULL) {
|
||||
p->pcap_v.pfv = pfv;
|
||||
p->ReleasePacket = PcapFileReleasePseudoPacket;
|
||||
SC_ATOMIC_ADD(pfv->ref_cnt, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PcapFileInstallCaptureHooks(void)
|
||||
{
|
||||
CaptureHooksSet(PcapCaptureOnPacketWithAlerts, PcapCaptureOnPseudoPacketCreated);
|
||||
}
|
||||
|
||||
PcapFileDeleteMode PcapFileParseDeleteMode(void)
|
||||
{
|
||||
PcapFileDeleteMode delete_mode = PCAP_FILE_DELETE_NONE;
|
||||
const char *delete_when_done_str = NULL;
|
||||
|
||||
if (SCConfGet("pcap-file.delete-when-done", &delete_when_done_str) == 1) {
|
||||
if (strcmp(delete_when_done_str, "non-alerts") == 0) {
|
||||
delete_mode = PCAP_FILE_DELETE_NON_ALERTS;
|
||||
} else {
|
||||
int delete_always = 0;
|
||||
if (SCConfGetBool("pcap-file.delete-when-done", &delete_always) == 1) {
|
||||
if (delete_always == 1) {
|
||||
delete_mode = PCAP_FILE_DELETE_ALWAYS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return delete_mode;
|
||||
}
|
||||
|
||||
#ifdef UNITTESTS
|
||||
#include "util-unittest-helper.h"
|
||||
/**
|
||||
* \test Tests that the PcapFileShouldDeletePcapFile function correctly applies the
|
||||
* delete mode configuration.
|
||||
*/
|
||||
static int SourcePcapFileHelperTest01(void)
|
||||
{
|
||||
PcapFileSharedVars shared;
|
||||
memset(&shared, 0, sizeof(shared));
|
||||
shared.delete_mode = PCAP_FILE_DELETE_ALWAYS;
|
||||
|
||||
PcapFileFileVars pfv;
|
||||
memset(&pfv, 0, sizeof(pfv));
|
||||
pfv.shared = &shared;
|
||||
pfv.filename = SCStrdup("test.pcap");
|
||||
SC_ATOMIC_INIT(pfv.alerts_count);
|
||||
SC_ATOMIC_SET(pfv.alerts_count, 0);
|
||||
|
||||
/* Test case 1: Always delete mode */
|
||||
int result1 = PcapFileShouldDeletePcapFile(&pfv);
|
||||
FAIL_IF_NOT(result1);
|
||||
|
||||
/* Test case 2: Non-alerts mode with no alerts */
|
||||
shared.delete_mode = PCAP_FILE_DELETE_NON_ALERTS;
|
||||
int result2 = PcapFileShouldDeletePcapFile(&pfv);
|
||||
FAIL_IF_NOT(result2);
|
||||
|
||||
/* Test case 3: Non-alerts mode with alerts */
|
||||
SC_ATOMIC_ADD(pfv.alerts_count, 1);
|
||||
int result3 = PcapFileShouldDeletePcapFile(&pfv);
|
||||
FAIL_IF(result3);
|
||||
|
||||
/* Test case 4: Always delete mode with alerts */
|
||||
shared.delete_mode = PCAP_FILE_DELETE_ALWAYS;
|
||||
int result4 = PcapFileShouldDeletePcapFile(&pfv);
|
||||
FAIL_IF_NOT(result4);
|
||||
|
||||
/* Test case 5: No delete mode */
|
||||
shared.delete_mode = PCAP_FILE_DELETE_NONE;
|
||||
int result5 = PcapFileShouldDeletePcapFile(&pfv);
|
||||
FAIL_IF(result5);
|
||||
|
||||
SCFree(pfv.filename);
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Test PcapFileFinalizePacket function with reference counting
|
||||
*/
|
||||
static int SourcePcapFileHelperTest02(void)
|
||||
{
|
||||
PcapFileFileVars pfv;
|
||||
memset(&pfv, 0, sizeof(pfv));
|
||||
SC_ATOMIC_INIT(pfv.alerts_count);
|
||||
SC_ATOMIC_SET(pfv.alerts_count, 0);
|
||||
SC_ATOMIC_INIT(pfv.ref_cnt);
|
||||
SC_ATOMIC_SET(pfv.ref_cnt, 0);
|
||||
pfv.cleanup_requested = false;
|
||||
|
||||
/* Test adding alerts with reference counting */
|
||||
SC_ATOMIC_ADD(pfv.ref_cnt, 1); /* simulate packet in flight */
|
||||
PcapFileAddAlertCount(&pfv, 5);
|
||||
PcapFileFinalizePacket(&pfv);
|
||||
uint64_t count = SC_ATOMIC_GET(pfv.alerts_count);
|
||||
FAIL_IF_NOT(count == 5);
|
||||
FAIL_IF_NOT(SC_ATOMIC_GET(pfv.ref_cnt) == 0); /* should be decremented */
|
||||
|
||||
/* Test adding more alerts */
|
||||
SC_ATOMIC_ADD(pfv.ref_cnt, 1);
|
||||
PcapFileAddAlertCount(&pfv, 3);
|
||||
PcapFileFinalizePacket(&pfv);
|
||||
count = SC_ATOMIC_GET(pfv.alerts_count);
|
||||
FAIL_IF_NOT(count == 8);
|
||||
|
||||
/* Test with zero alerts (should not increment count) */
|
||||
SC_ATOMIC_ADD(pfv.ref_cnt, 1);
|
||||
PcapFileFinalizePacket(&pfv);
|
||||
count = SC_ATOMIC_GET(pfv.alerts_count);
|
||||
FAIL_IF_NOT(count == 8);
|
||||
|
||||
/* Test with NULL pfv (should not crash) */
|
||||
PcapFileFinalizePacket(NULL);
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
/* Mock for configuration testing */
|
||||
static int SetupYamlConf(const char *conf_string)
|
||||
{
|
||||
SCConfCreateContextBackup();
|
||||
SCConfInit();
|
||||
|
||||
return SCConfYamlLoadString(conf_string, strlen(conf_string));
|
||||
}
|
||||
|
||||
static void CleanupYamlConf(void)
|
||||
{
|
||||
SCConfDeInit();
|
||||
SCConfRestoreContextBackup();
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Test PcapFileParseDeleteMode with all configuration combinations
|
||||
*/
|
||||
static int SourcePcapFileHelperTest03(void)
|
||||
{
|
||||
/* Test 1: No configuration (should default to NONE) */
|
||||
SCConfCreateContextBackup();
|
||||
SCConfInit();
|
||||
|
||||
PcapFileDeleteMode result = PcapFileParseDeleteMode();
|
||||
FAIL_IF_NOT(result == PCAP_FILE_DELETE_NONE);
|
||||
|
||||
SCConfDeInit();
|
||||
SCConfRestoreContextBackup();
|
||||
|
||||
/* Test 2: "false" configuration */
|
||||
const char *conf_false = "%YAML 1.1\n"
|
||||
"---\n"
|
||||
"pcap-file:\n"
|
||||
" delete-when-done: false\n";
|
||||
|
||||
SetupYamlConf(conf_false);
|
||||
result = PcapFileParseDeleteMode();
|
||||
FAIL_IF_NOT(result == PCAP_FILE_DELETE_NONE);
|
||||
CleanupYamlConf();
|
||||
|
||||
/* Test 3: "true" configuration */
|
||||
const char *conf_true = "%YAML 1.1\n"
|
||||
"---\n"
|
||||
"pcap-file:\n"
|
||||
" delete-when-done: true\n";
|
||||
|
||||
SetupYamlConf(conf_true);
|
||||
result = PcapFileParseDeleteMode();
|
||||
FAIL_IF_NOT(result == PCAP_FILE_DELETE_ALWAYS);
|
||||
CleanupYamlConf();
|
||||
|
||||
/* Test 4: "non-alerts" configuration */
|
||||
const char *conf_non_alerts = "%YAML 1.1\n"
|
||||
"---\n"
|
||||
"pcap-file:\n"
|
||||
" delete-when-done: \"non-alerts\"\n";
|
||||
|
||||
SetupYamlConf(conf_non_alerts);
|
||||
result = PcapFileParseDeleteMode();
|
||||
FAIL_IF_NOT(result == PCAP_FILE_DELETE_NON_ALERTS);
|
||||
CleanupYamlConf();
|
||||
|
||||
/* Test 5: Invalid configuration (should default to NONE) */
|
||||
const char *conf_invalid = "%YAML 1.1\n"
|
||||
"---\n"
|
||||
"pcap-file:\n"
|
||||
" delete-when-done: \"invalid-value\"\n";
|
||||
|
||||
SetupYamlConf(conf_invalid);
|
||||
result = PcapFileParseDeleteMode();
|
||||
FAIL_IF_NOT(result == PCAP_FILE_DELETE_NONE);
|
||||
CleanupYamlConf();
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test pfv is NULL.
|
||||
*/
|
||||
static int SourcePcapFileHelperTest04(void)
|
||||
{
|
||||
int rc = PcapFileShouldDeletePcapFile(NULL);
|
||||
FAIL_IF(rc);
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test pfv->shared is NULL.
|
||||
*/
|
||||
static int SourcePcapFileHelperTest05(void)
|
||||
{
|
||||
PcapFileFileVars pfv;
|
||||
memset(&pfv, 0, sizeof(pfv));
|
||||
|
||||
int rc = PcapFileShouldDeletePcapFile(&pfv);
|
||||
FAIL_IF(rc);
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Test cleanup with reference counting and deferred deletion
|
||||
*/
|
||||
static int SourcePcapFileHelperTest06(void)
|
||||
{
|
||||
PcapFileFileVars pfv;
|
||||
memset(&pfv, 0, sizeof(pfv));
|
||||
SC_ATOMIC_INIT(pfv.alerts_count);
|
||||
SC_ATOMIC_SET(pfv.alerts_count, 0);
|
||||
SC_ATOMIC_INIT(pfv.ref_cnt);
|
||||
SC_ATOMIC_SET(pfv.ref_cnt, 2); /* simulate 2 packets in flight */
|
||||
pfv.cleanup_requested = false;
|
||||
|
||||
/* Simulate first packet completion - should not cleanup yet */
|
||||
SC_ATOMIC_SUB(pfv.ref_cnt, 1);
|
||||
FAIL_IF_NOT(SC_ATOMIC_GET(pfv.ref_cnt) == 1);
|
||||
|
||||
/* Request cleanup while packets are still in flight */
|
||||
pfv.cleanup_requested = true;
|
||||
|
||||
/* Simulate second packet completion - should trigger cleanup */
|
||||
if (SC_ATOMIC_SUB(pfv.ref_cnt, 1) == 1) {
|
||||
FAIL_IF_NOT(pfv.cleanup_requested); /* cleanup should have been requested */
|
||||
/* In real code, CleanupPcapFileFileVars would be called here */
|
||||
}
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Test edge cases and error conditions
|
||||
*/
|
||||
static int SourcePcapFileHelperTest07(void)
|
||||
{
|
||||
/* Test 1: PcapFileShouldDeletePcapFile with very high alert count */
|
||||
PcapFileSharedVars shared;
|
||||
memset(&shared, 0, sizeof(shared));
|
||||
shared.delete_mode = PCAP_FILE_DELETE_NON_ALERTS;
|
||||
|
||||
PcapFileFileVars pfv;
|
||||
memset(&pfv, 0, sizeof(pfv));
|
||||
pfv.shared = &shared;
|
||||
pfv.filename = SCStrdup("test.pcap");
|
||||
SC_ATOMIC_INIT(pfv.alerts_count);
|
||||
SC_ATOMIC_SET(pfv.alerts_count, UINT64_MAX); /* max value */
|
||||
|
||||
int result = PcapFileShouldDeletePcapFile(&pfv);
|
||||
FAIL_IF(result); /* should not delete with max alerts */
|
||||
|
||||
/* Test 2: PcapFileFinalizePacket with max alert count */
|
||||
SC_ATOMIC_INIT(pfv.ref_cnt);
|
||||
SC_ATOMIC_ADD(pfv.ref_cnt, 1);
|
||||
PcapFileAddAlertCount(&pfv, UINT16_MAX); /* max uint16_t */
|
||||
PcapFileFinalizePacket(&pfv);
|
||||
/* Should not overflow or crash */
|
||||
|
||||
SCFree(pfv.filename);
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Test command-line --pcap-file-delete override behavior
|
||||
*/
|
||||
static int SourcePcapFileHelperTest08(void)
|
||||
{
|
||||
/* Test 1: Command line overrides YAML "false" */
|
||||
const char *conf_false = "%YAML 1.1\n"
|
||||
"---\n"
|
||||
"pcap-file:\n"
|
||||
" delete-when-done: false\n";
|
||||
|
||||
SetupYamlConf(conf_false);
|
||||
|
||||
/* Simulate --pcap-file-delete command line option */
|
||||
int set_result = SCConfSetFinal("pcap-file.delete-when-done", "true");
|
||||
FAIL_IF_NOT(set_result == 1);
|
||||
|
||||
PcapFileDeleteMode result = PcapFileParseDeleteMode();
|
||||
FAIL_IF_NOT(result == PCAP_FILE_DELETE_ALWAYS); /* Should override YAML false */
|
||||
CleanupYamlConf();
|
||||
|
||||
/* Test 2: Command line overrides YAML "non-alerts" */
|
||||
const char *conf_non_alerts = "%YAML 1.1\n"
|
||||
"---\n"
|
||||
"pcap-file:\n"
|
||||
" delete-when-done: \"non-alerts\"\n";
|
||||
|
||||
SetupYamlConf(conf_non_alerts);
|
||||
|
||||
/* Simulate --pcap-file-delete command line option */
|
||||
set_result = SCConfSetFinal("pcap-file.delete-when-done", "true");
|
||||
FAIL_IF_NOT(set_result == 1);
|
||||
|
||||
result = PcapFileParseDeleteMode();
|
||||
FAIL_IF_NOT(result == PCAP_FILE_DELETE_ALWAYS); /* Should override YAML "non-alerts" */
|
||||
CleanupYamlConf();
|
||||
|
||||
/* Test 3: Command line overrides no YAML config */
|
||||
SCConfCreateContextBackup();
|
||||
SCConfInit();
|
||||
|
||||
/* Simulate --pcap-file-delete command line option with no YAML config */
|
||||
set_result = SCConfSetFinal("pcap-file.delete-when-done", "true");
|
||||
FAIL_IF_NOT(set_result == 1);
|
||||
|
||||
result = PcapFileParseDeleteMode();
|
||||
FAIL_IF_NOT(result == PCAP_FILE_DELETE_ALWAYS); /* Should set to always delete */
|
||||
|
||||
SCConfDeInit();
|
||||
SCConfRestoreContextBackup();
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Test that cleanup defers while packets are in flight and that a file
|
||||
* with alerts is not deleted in NON_ALERTS mode.
|
||||
*/
|
||||
static int SourcePcapFileHelperTest09(void)
|
||||
{
|
||||
PcapFileSharedVars *shared = SCCalloc(1, sizeof(*shared));
|
||||
FAIL_IF_NULL(shared);
|
||||
shared->delete_mode = PCAP_FILE_DELETE_NON_ALERTS;
|
||||
|
||||
PcapFileFileVars *pfv = SCCalloc(1, sizeof(*pfv));
|
||||
FAIL_IF_NULL(pfv);
|
||||
pfv->shared = shared;
|
||||
pfv->filename = SCStrdup("unit_del_test.pcap");
|
||||
FAIL_IF_NULL(pfv->filename);
|
||||
|
||||
SC_ATOMIC_INIT(pfv->alerts_count);
|
||||
SC_ATOMIC_SET(pfv->alerts_count, 0);
|
||||
SC_ATOMIC_INIT(pfv->ref_cnt);
|
||||
SC_ATOMIC_SET(pfv->ref_cnt, 2); /* two packets in flight */
|
||||
pfv->cleanup_requested = false;
|
||||
|
||||
/* Request cleanup while packets still in flight: should defer. */
|
||||
CleanupPcapFileFileVars(pfv);
|
||||
FAIL_IF_NOT(pfv->cleanup_requested);
|
||||
FAIL_IF_NULL(pfv->filename); /* not freed yet */
|
||||
|
||||
/* First packet completes and generates an alert. */
|
||||
PcapFileAddAlertCount(pfv, 1);
|
||||
PcapFileFinalizePacket(pfv);
|
||||
FAIL_IF_NOT(SC_ATOMIC_GET(pfv->alerts_count) == 1);
|
||||
|
||||
/* Second (last) packet completes: triggers final cleanup. */
|
||||
PcapFileFinalizePacket(pfv);
|
||||
|
||||
/* pfv memory is freed at this point; only free shared. */
|
||||
SCFree(shared);
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Cover unlink-on-ALWAYS branch (ref_cnt == 0) and deferred deletion when ref_cnt > 0
|
||||
*/
|
||||
static int SourcePcapFileHelperTest10(void)
|
||||
{
|
||||
/* Create a temporary file that we expect to be deleted. */
|
||||
const char *tmpname = "suri_ut_delete_always.pcap";
|
||||
const uint8_t dummy[] = { 0x00 };
|
||||
int rc = TestHelperBufferToFile(tmpname, dummy, sizeof(dummy));
|
||||
FAIL_IF_NOT(rc >= 0);
|
||||
|
||||
/* Case 1: delete ALWAYS with no packets in flight -> file unlinked immediately */
|
||||
PcapFileSharedVars *shared1 = SCCalloc(1, sizeof(*shared1));
|
||||
FAIL_IF_NULL(shared1);
|
||||
shared1->delete_mode = PCAP_FILE_DELETE_ALWAYS;
|
||||
|
||||
PcapFileFileVars *pfv1 = SCCalloc(1, sizeof(*pfv1));
|
||||
FAIL_IF_NULL(pfv1);
|
||||
pfv1->shared = shared1;
|
||||
pfv1->filename = SCStrdup(tmpname);
|
||||
FAIL_IF_NULL(pfv1->filename);
|
||||
|
||||
SC_ATOMIC_INIT(pfv1->alerts_count);
|
||||
SC_ATOMIC_SET(pfv1->alerts_count, 0);
|
||||
SC_ATOMIC_INIT(pfv1->ref_cnt);
|
||||
SC_ATOMIC_SET(pfv1->ref_cnt, 0);
|
||||
|
||||
/* Provide a closable handle to cover close path. */
|
||||
pfv1->pcap_handle = pcap_open_dead(DLT_EN10MB, 65535);
|
||||
FAIL_IF_NULL(pfv1->pcap_handle);
|
||||
|
||||
CleanupPcapFileFileVars(pfv1);
|
||||
|
||||
/* File should be gone. */
|
||||
FILE *f = fopen(tmpname, "rb");
|
||||
FAIL_IF_NOT_NULL(f);
|
||||
if (f != NULL)
|
||||
fclose(f);
|
||||
|
||||
/* Case 2: delete ALWAYS but ref_cnt > 0 -> defer until finalize. */
|
||||
/* Recreate the file. */
|
||||
rc = TestHelperBufferToFile(tmpname, dummy, sizeof(dummy));
|
||||
FAIL_IF_NOT(rc >= 0);
|
||||
|
||||
PcapFileSharedVars *shared2 = SCCalloc(1, sizeof(*shared2));
|
||||
FAIL_IF_NULL(shared2);
|
||||
shared2->delete_mode = PCAP_FILE_DELETE_ALWAYS;
|
||||
|
||||
PcapFileFileVars *pfv2 = SCCalloc(1, sizeof(*pfv2));
|
||||
FAIL_IF_NULL(pfv2);
|
||||
pfv2->shared = shared2;
|
||||
pfv2->filename = SCStrdup(tmpname);
|
||||
FAIL_IF_NULL(pfv2->filename);
|
||||
|
||||
SC_ATOMIC_INIT(pfv2->alerts_count);
|
||||
SC_ATOMIC_SET(pfv2->alerts_count, 0);
|
||||
SC_ATOMIC_INIT(pfv2->ref_cnt);
|
||||
SC_ATOMIC_SET(pfv2->ref_cnt, 1);
|
||||
pfv2->pcap_handle = pcap_open_dead(DLT_EN10MB, 65535);
|
||||
FAIL_IF_NULL(pfv2->pcap_handle);
|
||||
|
||||
CleanupPcapFileFileVars(pfv2);
|
||||
|
||||
/* Still exists now because deletion should be deferred. */
|
||||
f = fopen(tmpname, "rb");
|
||||
FAIL_IF_NULL(f);
|
||||
if (f != NULL)
|
||||
fclose(f);
|
||||
|
||||
/* Finalize the last packet which should trigger final cleanup & unlink. */
|
||||
PcapFileFinalizePacket(pfv2);
|
||||
|
||||
/* Now the file should be gone. */
|
||||
f = fopen(tmpname, "rb");
|
||||
FAIL_IF_NOT_NULL(f);
|
||||
if (f != NULL)
|
||||
fclose(f);
|
||||
|
||||
SCFree(shared1);
|
||||
SCFree(shared2);
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Test PcapFileReleasePseudoPacket refcount decrement without cleanup
|
||||
*/
|
||||
static int SourcePcapFileHelperTest11(void)
|
||||
{
|
||||
/* Setup pfv with ref_cnt=2 so release does not trigger cleanup */
|
||||
PcapFileFileVars pfv;
|
||||
memset(&pfv, 0, sizeof(pfv));
|
||||
SC_ATOMIC_INIT(pfv.ref_cnt);
|
||||
SC_ATOMIC_SET(pfv.ref_cnt, 2);
|
||||
pfv.cleanup_requested = false;
|
||||
|
||||
/* Allocate a packet from the pool to allow safe release */
|
||||
Packet *p = PacketGetFromAlloc();
|
||||
FAIL_IF_NULL(p);
|
||||
p->pcap_v.pfv = &pfv;
|
||||
|
||||
/* Call release and ensure ref count decremented by 1 */
|
||||
PcapFileReleasePseudoPacket(p);
|
||||
FAIL_IF_NOT(SC_ATOMIC_GET(pfv.ref_cnt) == 1);
|
||||
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Test adding alert count and that cleanup after refcnt reaches zero
|
||||
* does not delete when alerts exist in NON_ALERTS mode.
|
||||
*/
|
||||
static int SourcePcapFileHelperTest12(void)
|
||||
{
|
||||
PcapFileSharedVars shared;
|
||||
memset(&shared, 0, sizeof(shared));
|
||||
shared.delete_mode = PCAP_FILE_DELETE_NON_ALERTS;
|
||||
|
||||
PcapFileFileVars *pfv = SCCalloc(1, sizeof(*pfv));
|
||||
FAIL_IF_NULL(pfv);
|
||||
pfv->shared = &shared;
|
||||
pfv->filename = SCStrdup("ut_non_alerts.pcap");
|
||||
FAIL_IF_NULL(pfv->filename);
|
||||
|
||||
SC_ATOMIC_INIT(pfv->alerts_count);
|
||||
SC_ATOMIC_SET(pfv->alerts_count, 0);
|
||||
SC_ATOMIC_INIT(pfv->ref_cnt);
|
||||
SC_ATOMIC_SET(pfv->ref_cnt, 1);
|
||||
pfv->cleanup_requested = true;
|
||||
|
||||
/* Simulate pseudo alert */
|
||||
PcapFileAddAlertCount(pfv, 2);
|
||||
FAIL_IF_NOT(SC_ATOMIC_GET(pfv->alerts_count) == 2);
|
||||
|
||||
/* Simulate last ref release triggering cleanup; file shouldn't be deleted
|
||||
* due to alerts > 0. We cannot check unlink here; rely on return value. */
|
||||
if (SC_ATOMIC_SUB(pfv->ref_cnt, 1) == 1) {
|
||||
CleanupPcapFileFileVars(pfv);
|
||||
}
|
||||
/* Success if no crash. */
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Test global current pfv pointer lifecycle
|
||||
*/
|
||||
static int SourcePcapFileHelperTest13(void)
|
||||
{
|
||||
PcapFileFileVars *pfv = SCCalloc(1, sizeof(*pfv));
|
||||
FAIL_IF_NULL(pfv);
|
||||
pfv->filename = SCStrdup("ut_global_clear.pcap");
|
||||
FAIL_IF_NULL(pfv->filename);
|
||||
|
||||
PcapFileSetCurrentPfv(pfv);
|
||||
FAIL_IF_NOT(PcapFileGetCurrentPfv() == pfv);
|
||||
|
||||
/* Cleanup should clear global reference when pointing to this pfv */
|
||||
CleanupPcapFileFileVars(pfv);
|
||||
/* Global accessor must be NULL after cleanup. */
|
||||
FAIL_IF_NOT(PcapFileGetCurrentPfv() == NULL);
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Exercise unlink failure branch in CleanupPcapFileFileVars
|
||||
*/
|
||||
static int SourcePcapFileHelperTest14(void)
|
||||
{
|
||||
PcapFileSharedVars shared;
|
||||
memset(&shared, 0, sizeof(shared));
|
||||
shared.delete_mode = PCAP_FILE_DELETE_ALWAYS;
|
||||
|
||||
PcapFileFileVars *pfv = SCCalloc(1, sizeof(*pfv));
|
||||
FAIL_IF_NULL(pfv);
|
||||
pfv->shared = &shared;
|
||||
pfv->filename = SCStrdup("does-not-exist-ut.pcap");
|
||||
FAIL_IF_NULL(pfv->filename);
|
||||
|
||||
SC_ATOMIC_INIT(pfv->ref_cnt);
|
||||
SC_ATOMIC_SET(pfv->ref_cnt, 0);
|
||||
|
||||
/* Attempt cleanup; unlink should fail but must not crash */
|
||||
CleanupPcapFileFileVars(pfv);
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \test Cover alerts hook fallback using current PFV
|
||||
*/
|
||||
static int SourcePcapFileHelperTest15(void)
|
||||
{
|
||||
PcapFileInstallCaptureHooks();
|
||||
|
||||
PcapFileFileVars *pfv = SCCalloc(1, sizeof(*pfv));
|
||||
FAIL_IF_NULL(pfv);
|
||||
SC_ATOMIC_INIT(pfv->alerts_count);
|
||||
SC_ATOMIC_SET(pfv->alerts_count, 0);
|
||||
|
||||
/* Set current PFV to exercise fallback path */
|
||||
PcapFileSetCurrentPfv(pfv);
|
||||
|
||||
Packet *p = PacketGetFromAlloc();
|
||||
FAIL_IF_NULL(p);
|
||||
p->alerts.cnt = 3; /* simulate 3 alerts */
|
||||
p->pcap_v.pfv = NULL; /* force fallback */
|
||||
|
||||
/* Call hook: it should update pfv->alerts_count */
|
||||
CaptureHooksOnPacketWithAlerts(p);
|
||||
FAIL_IF_NOT(SC_ATOMIC_GET(pfv->alerts_count) == 3);
|
||||
|
||||
/* Cleanup */
|
||||
PacketFreeOrRelease(p);
|
||||
CleanupPcapFileFileVars(pfv);
|
||||
PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Register unit tests for pcap file helper
|
||||
*/
|
||||
void SourcePcapFileHelperRegisterTests(void)
|
||||
{
|
||||
UtRegisterTest("SourcePcapFileHelperTest01", SourcePcapFileHelperTest01);
|
||||
UtRegisterTest("SourcePcapFileHelperTest02", SourcePcapFileHelperTest02);
|
||||
UtRegisterTest("SourcePcapFileHelperTest03", SourcePcapFileHelperTest03);
|
||||
UtRegisterTest("SourcePcapFileHelperTest04", SourcePcapFileHelperTest04);
|
||||
UtRegisterTest("SourcePcapFileHelperTest05", SourcePcapFileHelperTest05);
|
||||
UtRegisterTest("SourcePcapFileHelperTest06", SourcePcapFileHelperTest06);
|
||||
UtRegisterTest("SourcePcapFileHelperTest07", SourcePcapFileHelperTest07);
|
||||
UtRegisterTest("SourcePcapFileHelperTest08", SourcePcapFileHelperTest08);
|
||||
UtRegisterTest("SourcePcapFileHelperTest09", SourcePcapFileHelperTest09);
|
||||
UtRegisterTest("SourcePcapFileHelperTest10", SourcePcapFileHelperTest10);
|
||||
UtRegisterTest("SourcePcapFileHelperTest11", SourcePcapFileHelperTest11);
|
||||
UtRegisterTest("SourcePcapFileHelperTest12", SourcePcapFileHelperTest12);
|
||||
UtRegisterTest("SourcePcapFileHelperTest13", SourcePcapFileHelperTest13);
|
||||
UtRegisterTest("SourcePcapFileHelperTest14", SourcePcapFileHelperTest14);
|
||||
UtRegisterTest("SourcePcapFileHelperTest15", SourcePcapFileHelperTest15);
|
||||
}
|
||||
#endif /* UNITTESTS */
|
||||
|
|
|
|||
|
|
@ -23,10 +23,17 @@
|
|||
|
||||
#include "suricata-common.h"
|
||||
#include "tm-threads.h"
|
||||
#include "util-atomic.h"
|
||||
|
||||
#ifndef SURICATA_SOURCE_PCAP_FILE_HELPER_H
|
||||
#define SURICATA_SOURCE_PCAP_FILE_HELPER_H
|
||||
|
||||
typedef enum {
|
||||
PCAP_FILE_DELETE_NONE = 0,
|
||||
PCAP_FILE_DELETE_ALWAYS,
|
||||
PCAP_FILE_DELETE_NON_ALERTS
|
||||
} PcapFileDeleteMode;
|
||||
|
||||
typedef struct PcapFileGlobalVars_ {
|
||||
uint64_t cnt; /** packet counter */
|
||||
ChecksumValidationMode conf_checksum_mode;
|
||||
|
|
@ -46,7 +53,7 @@ typedef struct PcapFileSharedVars_
|
|||
|
||||
struct timespec last_processed;
|
||||
|
||||
bool should_delete;
|
||||
PcapFileDeleteMode delete_mode;
|
||||
|
||||
ThreadVars *tv;
|
||||
TmSlot *slot;
|
||||
|
|
@ -76,6 +83,14 @@ typedef struct PcapFileFileVars_
|
|||
|
||||
PcapFileSharedVars *shared;
|
||||
|
||||
SC_ATOMIC_DECLARE(uint64_t, alerts_count);
|
||||
/* Reference count for outstanding users (e.g., pseudo packets created
|
||||
* during shutdown/timeout handling). Ensures the per-file state is not
|
||||
* cleaned up or the file deleted before all dependent processing that may
|
||||
* still raise alerts has completed. */
|
||||
SC_ATOMIC_DECLARE(uint32_t, ref_cnt);
|
||||
bool cleanup_requested;
|
||||
|
||||
/* fields used to get the first packet's timestamp early,
|
||||
* so it can be used to setup the time subsys. */
|
||||
const u_char *first_pkt_data;
|
||||
|
|
@ -121,4 +136,23 @@ TmEcode ValidateLinkType(int datalink, DecoderFunc *decoder);
|
|||
|
||||
const char *PcapFileGetFilename(void);
|
||||
|
||||
bool PcapFileShouldDeletePcapFile(PcapFileFileVars *pfv);
|
||||
|
||||
void PcapFileFinalizePacket(PcapFileFileVars *pfv);
|
||||
|
||||
PcapFileDeleteMode PcapFileParseDeleteMode(void);
|
||||
|
||||
void PcapFileAddAlertCount(PcapFileFileVars *pfv, uint16_t alert_count);
|
||||
|
||||
void PcapFileReleasePseudoPacket(Packet *p);
|
||||
|
||||
void PcapFileSetCurrentPfv(PcapFileFileVars *pfv);
|
||||
PcapFileFileVars *PcapFileGetCurrentPfv(void);
|
||||
|
||||
void PcapFileInstallCaptureHooks(void);
|
||||
|
||||
#ifdef UNITTESTS
|
||||
void SourcePcapFileHelperRegisterTests(void);
|
||||
#endif
|
||||
|
||||
#endif /* SURICATA_SOURCE_PCAP_FILE_HELPER_H */
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include "suricata.h"
|
||||
#include "conf.h"
|
||||
#include "util-misc.h"
|
||||
#include "capture-hooks.h"
|
||||
|
||||
extern uint32_t max_pending_packets;
|
||||
PcapFileGlobalVars pcap_g;
|
||||
|
|
@ -148,6 +149,8 @@ void PcapFileGlobalInit(void)
|
|||
memset(&pcap_g, 0x00, sizeof(pcap_g));
|
||||
SC_ATOMIC_INIT(pcap_g.invalid_checksums);
|
||||
|
||||
PcapFileInstallCaptureHooks();
|
||||
|
||||
#if defined(HAVE_SETVBUF) && defined(OS_LINUX)
|
||||
pcap_g.read_buffer_size = PCAP_FILE_BUFFER_SIZE_DEFAULT;
|
||||
|
||||
|
|
@ -204,8 +207,11 @@ TmEcode ReceivePcapFileLoop(ThreadVars *tv, void *data, void *slot)
|
|||
|
||||
if(ptv->is_directory == 0) {
|
||||
SCLogInfo("Starting file run for %s", ptv->behavior.file->filename);
|
||||
/* Hold a reference for the duration of file processing, including
|
||||
* post-dispatch flow draining during EngineStop, so deletion decision
|
||||
* is deferred until all pseudo packets have been processed. */
|
||||
SC_ATOMIC_ADD(ptv->behavior.file->ref_cnt, 1);
|
||||
status = PcapFileDispatch(ptv->behavior.file);
|
||||
CleanupPcapFileFromThreadVars(ptv, ptv->behavior.file);
|
||||
} else {
|
||||
SCLogInfo("Starting directory run for %s", ptv->behavior.directory->filename);
|
||||
PcapDirectoryDispatch(ptv->behavior.directory);
|
||||
|
|
@ -215,6 +221,19 @@ TmEcode ReceivePcapFileLoop(ThreadVars *tv, void *data, void *slot)
|
|||
SCLogDebug("Pcap file loop complete with status %u", status);
|
||||
|
||||
status = PcapFileExit(status, &ptv->shared.last_processed);
|
||||
|
||||
/* Release the hold set before dispatch and trigger deferred cleanup
|
||||
* if requested and this was the final reference. */
|
||||
if (ptv->is_directory == 0) {
|
||||
/* Ensure current pfv global is set for any pseudo packets emitted
|
||||
* during EngineStop/flow drain. */
|
||||
PcapFileSetCurrentPfv(ptv->behavior.file);
|
||||
uint32_t prev = SC_ATOMIC_SUB(ptv->behavior.file->ref_cnt, 1);
|
||||
if (prev == 1 && ptv->behavior.file->cleanup_requested) {
|
||||
CleanupPcapFileFileVars(ptv->behavior.file);
|
||||
ptv->behavior.file = NULL;
|
||||
}
|
||||
}
|
||||
SCReturnInt(status);
|
||||
}
|
||||
|
||||
|
|
@ -261,11 +280,7 @@ TmEcode ReceivePcapFileThreadInit(ThreadVars *tv, const void *initdata, void **d
|
|||
}
|
||||
}
|
||||
|
||||
int should_delete = 0;
|
||||
ptv->shared.should_delete = false;
|
||||
if (SCConfGetBool("pcap-file.delete-when-done", &should_delete) == 1) {
|
||||
ptv->shared.should_delete = should_delete == 1;
|
||||
}
|
||||
ptv->shared.delete_mode = PcapFileParseDeleteMode();
|
||||
|
||||
DIR *directory = NULL;
|
||||
SCLogDebug("checking file or directory %s", (char*)initdata);
|
||||
|
|
@ -422,6 +437,11 @@ TmEcode ReceivePcapFileThreadDeinit(ThreadVars *tv, void *data)
|
|||
SCEnter();
|
||||
if(data != NULL) {
|
||||
PcapFileThreadVars *ptv = (PcapFileThreadVars *) data;
|
||||
|
||||
if (!ptv->is_directory && ptv->behavior.file != NULL) {
|
||||
CleanupPcapFileFromThreadVars(ptv, ptv->behavior.file);
|
||||
}
|
||||
|
||||
CleanupPcapFileThreadVars(ptv);
|
||||
}
|
||||
SCReturnInt(TM_ECODE_OK);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@
|
|||
#ifndef SURICATA_SOURCE_PCAP_H
|
||||
#define SURICATA_SOURCE_PCAP_H
|
||||
|
||||
typedef struct PcapFileFileVars_ PcapFileFileVars;
|
||||
|
||||
void TmModuleReceivePcapRegister (void);
|
||||
void TmModuleDecodePcapRegister (void);
|
||||
void PcapTranslateIPToDevice(char *pcap_dev, size_t len);
|
||||
|
|
@ -35,6 +37,7 @@ void PcapTranslateIPToDevice(char *pcap_dev, size_t len);
|
|||
typedef struct PcapPacketVars_ {
|
||||
uint64_t pcap_cnt;
|
||||
uint32_t tenant_id;
|
||||
PcapFileFileVars *pfv;
|
||||
} PcapPacketVars;
|
||||
|
||||
/** needs to be able to contain Windows adapter id's, so
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "tm-queues.h"
|
||||
#include "tm-queuehandlers.h"
|
||||
#include "tm-threads.h"
|
||||
#include "capture-hooks.h"
|
||||
#include "tmqh-packetpool.h"
|
||||
#include "threads.h"
|
||||
#include "util-affinity.h"
|
||||
|
|
@ -46,6 +47,7 @@
|
|||
#include "util-signal.h"
|
||||
#include "queue.h"
|
||||
#include "util-validate.h"
|
||||
#include "source-pcap-file-helper.h"
|
||||
|
||||
#ifdef PROFILE_LOCKING
|
||||
thread_local uint64_t mutex_lock_contention;
|
||||
|
|
@ -1358,6 +1360,7 @@ again:
|
|||
if (p != NULL) {
|
||||
p->flags |= PKT_PSEUDO_STREAM_END;
|
||||
PKT_SET_SRC(p, PKT_SRC_SHUTDOWN_FLUSH);
|
||||
CaptureHooksOnPseudoPacketCreated(p);
|
||||
PacketQueue *q = tv->stream_pq;
|
||||
SCMutexLock(&q->mutex_q);
|
||||
PacketEnqueue(q, p);
|
||||
|
|
@ -1441,6 +1444,7 @@ again:
|
|||
if (p != NULL) {
|
||||
p->flags |= PKT_PSEUDO_STREAM_END;
|
||||
PKT_SET_SRC(p, PKT_SRC_SHUTDOWN_FLUSH);
|
||||
CaptureHooksOnPseudoPacketCreated(p);
|
||||
PacketQueue *q = tv->stream_pq;
|
||||
SCMutexLock(&q->mutex_q);
|
||||
PacketEnqueue(q, p);
|
||||
|
|
|
|||
|
|
@ -896,7 +896,9 @@ pcap-file:
|
|||
# buffer-size: 128 KiB
|
||||
|
||||
# tenant-id: none # applies in multi-tenant environment with "direct" selector
|
||||
# delete-when-done: false # applies to file and directory
|
||||
# Applies to file and directory. Options: false (no deletion), true (always delete),
|
||||
# "non-alerts" (delete only files with no alerts)
|
||||
# delete-when-done: false
|
||||
|
||||
# PCAP Directory Processing options
|
||||
# recursive: false
|
||||
|
|
|
|||
Loading…
Reference in a new issue