From 2fce106aecacb2690cc2761e100f4892a733f32f Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Tue, 1 Apr 2025 15:18:54 -0600 Subject: [PATCH] pcap-log: add bpf filter for packets that are logged Add an optional bpf filter to pcap-log. If set, packets must match the filter to be logged, otherwise they will be ignored. This allows a user to limit what is logged to disk if they have pcap-log enabled, but still inspect all data captured. Ticket: #6832 --- doc/userguide/configuration/suricata-yaml.rst | 7 +++++ src/log-pcap.c | 29 +++++++++++++++++++ suricata.yaml.in | 5 ++++ 3 files changed, 41 insertions(+) diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index fe3e7e59f1..16b208613b 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -497,6 +497,8 @@ By default all packets are logged except: - TCP streams beyond stream.reassembly.depth - encrypted streams after the key exchange +- If a ``bpf-filter`` is set, packets that don't match the filter will + not be logged It is possible to do conditional pcap logging by using the `conditional` option in the pcap-log section. By default the variable is set to `all` @@ -520,6 +522,11 @@ the alert. mode: normal # "normal" or multi conditional: alerts + # A BPF filter that will be applied to all packets being + # logged. If set, packets must match this filter otherwise they + # will not be logged. + #bpf-filter: + In ``normal`` mode a pcap file "filename" is created in the default-log-dir or as specified by "dir". ``normal`` mode is generally not as performant as ``multi`` mode. diff --git a/src/log-pcap.c b/src/log-pcap.c index c92eb399de..48261b3fe0 100644 --- a/src/log-pcap.c +++ b/src/log-pcap.c @@ -140,6 +140,7 @@ typedef struct PcapLogCompressionData_ { typedef struct PcapLogData_ { int use_stream_depth; /**< use stream depth i.e. ignore packets that reach limit */ int honor_pass_rules; /**< don't log if pass rules have matched */ + char *bpf_filter; /**< bpf filter to apply to output */ SCMutex plog_lock; uint64_t pkt_cnt; /**< total number of packets */ struct pcap_pkthdr *h; /**< pcap header struct */ @@ -150,6 +151,7 @@ typedef struct PcapLogData_ { uint64_t size_limit; /**< file size limit */ pcap_t *pcap_dead_handle; /**< pcap_dumper_t needs a handle */ pcap_dumper_t *pcap_dumper; /**< actually writes the packets */ + struct bpf_program *bpfp; /**< compiled bpf program */ uint64_t profile_data_size; /**< track in bytes how many bytes we wrote */ uint32_t file_cnt; /**< count of pcap files we currently have */ uint32_t max_files; /**< maximum files to use in ring buffer mode */ @@ -391,6 +393,18 @@ static int PcapLogOpenHandles(PcapLogData *pl, const Packet *p) SCLogDebug("Error opening dead pcap handle"); return TM_ECODE_FAILED; } + + if (pl->bpfp == NULL && pl->bpf_filter) { + struct bpf_program bpfp; + if (pcap_compile(pl->pcap_dead_handle, &bpfp, pl->bpf_filter, 0, + PCAP_NETMASK_UNKNOWN) == PCAP_ERROR) { + FatalError("Failed to compile BPF filter, aborting: %s: %s", pl->bpf_filter, + pcap_geterr(pl->pcap_dead_handle)); + } else { + pl->bpfp = SCCalloc(1, sizeof(*pl->bpfp)); + *pl->bpfp = bpfp; + } + } } if (pl->pcap_dumper == NULL) { @@ -483,6 +497,14 @@ static inline int PcapWrite( { struct timeval current_dump; gettimeofday(¤t_dump, NULL); + + if (pl->bpfp) { + if (pcap_offline_filter(pl->bpfp, pl->h, data) == 0) { + SCLogDebug("Packet doesn't match filter, will not be logged."); + return TM_ECODE_OK; + } + } + pcap_dump((u_char *)pl->pcap_dumper, pl->h, data); if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_NONE) { pl->size_current += len; @@ -1142,6 +1164,11 @@ static void PcapLogDataFree(PcapLogData *pl) pcap_close(pl->pcap_dead_handle); } + if (pl->bpfp) { + pcap_freecode(pl->bpfp); + SCFree(pl->bpfp); + } + #ifdef HAVE_LIBLZ4 if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) { SCFree(pl->compression.buffer); @@ -1608,6 +1635,8 @@ static OutputInitResult PcapLogInitCtx(SCConfNode *conf) } } + pl->bpf_filter = conf == NULL ? NULL : (char *)SCConfNodeLookupChildValue(conf, "bpf-filter"); + /* create the output ctx and send it back */ OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); diff --git a/suricata.yaml.in b/suricata.yaml.in index 8ecab2d95d..66c9ef5fdc 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -448,6 +448,11 @@ outputs: # to log only flow tagged via the "tag" keyword #conditional: all + # A BPF filter that will be applied to all packets being + # logged. If set, packets must match this filter otherwise they + # will not be logged. + #bpf-filter: + # a full alert log containing much information for signature writers # or for investigating suspected false positives. - alert-debug: