Coverage Report

Created: 2026-01-16 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata/src/source-pcap-file.c
Line
Count
Source
1
/* Copyright (C) 2007-2016 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
/**
19
 * \file
20
 *
21
 * \author Victor Julien <victor@inliniac.net>
22
 *
23
 * File based pcap packet acquisition support
24
 */
25
26
#include "suricata-common.h"
27
#include "source-pcap-file.h"
28
#include "source-pcap-file-helper.h"
29
#include "source-pcap-file-directory-helper.h"
30
#include "flow-manager.h"
31
#include "util-checksum.h"
32
#include "runmode-unix-socket.h"
33
#include "suricata.h"
34
#include "conf.h"
35
#include "util-misc.h"
36
37
extern uint32_t max_pending_packets;
38
PcapFileGlobalVars pcap_g;
39
40
/**
41
 * Union determining whether the behavior of the thread is file or directory
42
 */
43
typedef union PcapFileBehaviorVar_
44
{
45
    PcapFileDirectoryVars *directory;
46
    PcapFileFileVars *file;
47
} PcapFileBehaviorVar;
48
49
/**
50
 * Data specific to the thread
51
 */
52
typedef struct PcapFileThreadVars_
53
{
54
    PcapFileBehaviorVar behavior;
55
    bool is_directory;
56
57
    PcapFileSharedVars shared;
58
} PcapFileThreadVars;
59
60
static TmEcode ReceivePcapFileLoop(ThreadVars *, void *, void *);
61
static TmEcode ReceivePcapFileThreadInit(ThreadVars *, const void *, void **);
62
static void ReceivePcapFileThreadExitStats(ThreadVars *, void *);
63
static TmEcode ReceivePcapFileThreadDeinit(ThreadVars *, void *);
64
65
static TmEcode DecodePcapFile(ThreadVars *, Packet *, void *);
66
static TmEcode DecodePcapFileThreadInit(ThreadVars *, const void *, void **);
67
static TmEcode DecodePcapFileThreadDeinit(ThreadVars *tv, void *data);
68
69
static void CleanupPcapDirectoryFromThreadVars(PcapFileThreadVars *tv,
70
                                               PcapFileDirectoryVars *ptv);
71
static void CleanupPcapFileFromThreadVars(PcapFileThreadVars *tv, PcapFileFileVars *pfv);
72
static void CleanupPcapFileThreadVars(PcapFileThreadVars *tv);
73
static TmEcode PcapFileExit(TmEcode status, struct timespec *last_processed);
74
75
void CleanupPcapFileFromThreadVars(PcapFileThreadVars *tv, PcapFileFileVars *pfv)
76
18.8k
{
77
18.8k
    CleanupPcapFileFileVars(pfv);
78
18.8k
    if (tv->is_directory == 0) {
79
18.8k
        tv->behavior.file = NULL;
80
18.8k
    }
81
18.8k
}
82
83
void CleanupPcapDirectoryFromThreadVars(PcapFileThreadVars *tv, PcapFileDirectoryVars *ptv)
84
0
{
85
0
    CleanupPcapFileDirectoryVars(ptv);
86
0
    if (tv->is_directory == 1) {
87
0
        tv->behavior.directory = NULL;
88
0
    }
89
0
}
90
91
void CleanupPcapFileThreadVars(PcapFileThreadVars *ptv)
92
22.6k
{
93
22.6k
    if (ptv != NULL) {
94
22.6k
        if (ptv->is_directory == 0) {
95
22.6k
            if (ptv->behavior.file != NULL) {
96
0
                CleanupPcapFileFromThreadVars(ptv, ptv->behavior.file);
97
0
            }
98
22.6k
            ptv->behavior.file = NULL;
99
22.6k
        } else {
100
0
            if (ptv->behavior.directory != NULL) {
101
0
                CleanupPcapDirectoryFromThreadVars(ptv, ptv->behavior.directory);
102
0
            }
103
0
            ptv->behavior.directory = NULL;
104
0
        }
105
22.6k
        if (ptv->shared.bpf_string != NULL) {
106
0
            SCFree(ptv->shared.bpf_string);
107
0
            ptv->shared.bpf_string = NULL;
108
0
        }
109
22.6k
        SCFree(ptv);
110
22.6k
    }
111
22.6k
}
112
113
/**
114
 * Pcap File Functionality
115
 */
116
void TmModuleReceivePcapFileRegister (void)
117
71
{
118
71
    tmm_modules[TMM_RECEIVEPCAPFILE].name = "ReceivePcapFile";
119
71
    tmm_modules[TMM_RECEIVEPCAPFILE].ThreadInit = ReceivePcapFileThreadInit;
120
71
    tmm_modules[TMM_RECEIVEPCAPFILE].Func = NULL;
121
71
    tmm_modules[TMM_RECEIVEPCAPFILE].PktAcqLoop = ReceivePcapFileLoop;
122
71
    tmm_modules[TMM_RECEIVEPCAPFILE].PktAcqBreakLoop = NULL;
123
71
    tmm_modules[TMM_RECEIVEPCAPFILE].ThreadExitPrintStats = ReceivePcapFileThreadExitStats;
124
71
    tmm_modules[TMM_RECEIVEPCAPFILE].ThreadDeinit = ReceivePcapFileThreadDeinit;
125
71
    tmm_modules[TMM_RECEIVEPCAPFILE].cap_flags = 0;
126
71
    tmm_modules[TMM_RECEIVEPCAPFILE].flags = TM_FLAG_RECEIVE_TM;
127
71
}
128
129
void TmModuleDecodePcapFileRegister (void)
130
71
{
131
71
    tmm_modules[TMM_DECODEPCAPFILE].name = "DecodePcapFile";
132
71
    tmm_modules[TMM_DECODEPCAPFILE].ThreadInit = DecodePcapFileThreadInit;
133
71
    tmm_modules[TMM_DECODEPCAPFILE].Func = DecodePcapFile;
134
71
    tmm_modules[TMM_DECODEPCAPFILE].ThreadExitPrintStats = NULL;
135
71
    tmm_modules[TMM_DECODEPCAPFILE].ThreadDeinit = DecodePcapFileThreadDeinit;
136
71
    tmm_modules[TMM_DECODEPCAPFILE].cap_flags = 0;
137
71
    tmm_modules[TMM_DECODEPCAPFILE].flags = TM_FLAG_DECODE_TM;
138
71
}
139
140
#if defined(HAVE_SETVBUF) && defined(OS_LINUX)
141
1
#define PCAP_FILE_BUFFER_SIZE_DEFAULT 131072U   // 128 KiB
142
0
#define PCAP_FILE_BUFFER_SIZE_MIN     4096U     // 4 KiB
143
0
#define PCAP_FILE_BUFFER_SIZE_MAX     67108864U // 64MiB
144
#endif
145
146
void PcapFileGlobalInit(void)
147
1
{
148
1
    memset(&pcap_g, 0x00, sizeof(pcap_g));
149
1
    SC_ATOMIC_INIT(pcap_g.invalid_checksums);
150
151
1
#if defined(HAVE_SETVBUF) && defined(OS_LINUX)
152
1
    pcap_g.read_buffer_size = PCAP_FILE_BUFFER_SIZE_DEFAULT;
153
154
1
    const char *str = NULL;
155
1
    if (SCConfGet("pcap-file.buffer-size", &str) == 1) {
156
0
        uint32_t value = 0;
157
0
        if (ParseSizeStringU32(str, &value) < 0) {
158
0
            SCLogWarning("failed to parse pcap-file.buffer-size %s", str);
159
0
        }
160
0
        if (value >= PCAP_FILE_BUFFER_SIZE_MIN && value <= PCAP_FILE_BUFFER_SIZE_MAX) {
161
0
            SCLogInfo("Pcap-file will use %u buffer size", value);
162
0
            pcap_g.read_buffer_size = value;
163
0
        } else {
164
0
            SCLogWarning("pcap-file.buffer-size value of %u is invalid. Valid range is %u-%u",
165
0
                    value, PCAP_FILE_BUFFER_SIZE_MIN, PCAP_FILE_BUFFER_SIZE_MAX);
166
0
        }
167
0
    }
168
1
#endif
169
1
}
170
171
TmEcode PcapFileExit(TmEcode status, struct timespec *last_processed)
172
18.8k
{
173
18.8k
    if(RunModeUnixSocketIsActive()) {
174
0
        status = UnixSocketPcapFile(status, last_processed);
175
0
        SCReturnInt(status);
176
18.8k
    } else {
177
18.8k
        EngineStop();
178
18.8k
        SCReturnInt(status);
179
18.8k
    }
180
18.8k
}
181
182
TmEcode ReceivePcapFileLoop(ThreadVars *tv, void *data, void *slot)
183
18.8k
{
184
18.8k
    SCEnter();
185
186
18.8k
    if(unlikely(data == NULL)) {
187
0
        SCLogError("pcap file reader thread failed to initialize");
188
189
0
        PcapFileExit(TM_ECODE_FAILED, NULL);
190
191
0
        SCReturnInt(TM_ECODE_DONE);
192
0
    }
193
194
18.8k
    TmEcode status = TM_ECODE_OK;
195
18.8k
    PcapFileThreadVars *ptv = (PcapFileThreadVars *) data;
196
18.8k
    TmSlot *s = (TmSlot *)slot;
197
198
18.8k
    ptv->shared.slot = s->slot_next;
199
18.8k
    ptv->shared.cb_result = TM_ECODE_OK;
200
201
    // Indicate that the thread is actually running its application level code (i.e., it can poll
202
    // packets)
203
18.8k
    TmThreadsSetFlag(tv, THV_RUNNING);
204
205
18.8k
    if(ptv->is_directory == 0) {
206
18.8k
        SCLogInfo("Starting file run for %s", ptv->behavior.file->filename);
207
18.8k
        status = PcapFileDispatch(ptv->behavior.file);
208
18.8k
        CleanupPcapFileFromThreadVars(ptv, ptv->behavior.file);
209
18.8k
    } else {
210
0
        SCLogInfo("Starting directory run for %s", ptv->behavior.directory->filename);
211
0
        PcapDirectoryDispatch(ptv->behavior.directory);
212
0
        CleanupPcapDirectoryFromThreadVars(ptv, ptv->behavior.directory);
213
0
    }
214
215
18.8k
    SCLogDebug("Pcap file loop complete with status %u", status);
216
217
18.8k
    status = PcapFileExit(status, &ptv->shared.last_processed);
218
18.8k
    SCReturnInt(status);
219
18.8k
}
220
221
TmEcode ReceivePcapFileThreadInit(ThreadVars *tv, const void *initdata, void **data)
222
11.4k
{
223
11.4k
    SCEnter();
224
225
11.4k
    TmEcode status = TM_ECODE_OK;
226
11.4k
    const char *tmpstring = NULL;
227
11.4k
    const char *tmp_bpf_string = NULL;
228
229
11.4k
    if (initdata == NULL) {
230
0
        SCLogError("error: initdata == NULL");
231
232
0
        SCReturnInt(TM_ECODE_OK);
233
0
    }
234
235
11.4k
    PcapFileThreadVars *ptv = SCCalloc(1, sizeof(PcapFileThreadVars));
236
11.4k
    if (unlikely(ptv == NULL)) {
237
0
        SCReturnInt(TM_ECODE_OK);
238
0
    }
239
11.4k
    memset(&ptv->shared.last_processed, 0, sizeof(struct timespec));
240
241
11.4k
    intmax_t tenant = 0;
242
11.4k
    if (SCConfGetInt("pcap-file.tenant-id", &tenant) == 1) {
243
0
        if (tenant > 0 && tenant < UINT_MAX) {
244
0
            ptv->shared.tenant_id = (uint32_t)tenant;
245
0
            SCLogInfo("tenant %u", ptv->shared.tenant_id);
246
0
        } else {
247
0
            SCLogError("tenant out of range");
248
0
        }
249
0
    }
250
251
11.4k
    if (SCConfGet("bpf-filter", &(tmp_bpf_string)) != 1) {
252
11.4k
        SCLogDebug("could not get bpf or none specified");
253
11.4k
    } else {
254
0
        ptv->shared.bpf_string = SCStrdup(tmp_bpf_string);
255
0
        if (unlikely(ptv->shared.bpf_string == NULL)) {
256
0
            SCLogError("Failed to allocate bpf_string");
257
258
0
            CleanupPcapFileThreadVars(ptv);
259
260
0
            SCReturnInt(TM_ECODE_OK);
261
0
        }
262
0
    }
263
264
11.4k
    int should_delete = 0;
265
11.4k
    ptv->shared.should_delete = false;
266
11.4k
    if (SCConfGetBool("pcap-file.delete-when-done", &should_delete) == 1) {
267
0
        ptv->shared.should_delete = should_delete == 1;
268
0
    }
269
270
11.4k
    DIR *directory = NULL;
271
11.4k
    SCLogDebug("checking file or directory %s", (char*)initdata);
272
11.4k
    if(PcapDetermineDirectoryOrFile((char *)initdata, &directory) == TM_ECODE_FAILED) {
273
68
        CleanupPcapFileThreadVars(ptv);
274
68
        SCReturnInt(TM_ECODE_OK);
275
68
    }
276
277
11.3k
    if(directory == NULL) {
278
11.3k
        SCLogDebug("argument %s was a file", (char *)initdata);
279
11.3k
        const size_t toalloc = sizeof(PcapFileFileVars) + pcap_g.read_buffer_size;
280
11.3k
        PcapFileFileVars *pv = SCCalloc(1, toalloc);
281
11.3k
        if (unlikely(pv == NULL)) {
282
0
            SCLogError("Failed to allocate file vars");
283
0
            CleanupPcapFileThreadVars(ptv);
284
0
            SCReturnInt(TM_ECODE_OK);
285
0
        }
286
287
11.3k
        pv->filename = SCStrdup((char *)initdata);
288
11.3k
        if (unlikely(pv->filename == NULL)) {
289
0
            SCLogError("Failed to allocate filename");
290
0
            CleanupPcapFileFileVars(pv);
291
0
            CleanupPcapFileThreadVars(ptv);
292
0
            SCReturnInt(TM_ECODE_OK);
293
0
        }
294
295
11.3k
        pv->shared = &ptv->shared;
296
11.3k
        status = InitPcapFile(pv);
297
11.3k
        if(status == TM_ECODE_OK) {
298
9.57k
            ptv->is_directory = 0;
299
9.57k
            ptv->behavior.file = pv;
300
9.57k
        } else {
301
1.82k
            SCLogWarning("Failed to init pcap file %s, skipping", pv->filename);
302
1.82k
            CleanupPcapFileFileVars(pv);
303
1.82k
            CleanupPcapFileThreadVars(ptv);
304
1.82k
            SCReturnInt(TM_ECODE_OK);
305
1.82k
        }
306
11.3k
    } else {
307
0
        SCLogInfo("Argument %s was a directory", (char *)initdata);
308
0
        PcapFileDirectoryVars *pv = SCCalloc(1, sizeof(PcapFileDirectoryVars));
309
0
        if (unlikely(pv == NULL)) {
310
0
            SCLogError("Failed to allocate directory vars");
311
0
            closedir(directory);
312
0
            CleanupPcapFileThreadVars(ptv);
313
0
            SCReturnInt(TM_ECODE_OK);
314
0
        }
315
316
0
        pv->filename = SCStrdup((char*)initdata);
317
0
        if (unlikely(pv->filename == NULL)) {
318
0
            SCLogError("Failed to allocate filename");
319
0
            closedir(directory);
320
0
            CleanupPcapFileDirectoryVars(pv);
321
0
            CleanupPcapFileThreadVars(ptv);
322
0
            SCReturnInt(TM_ECODE_OK);
323
0
        }
324
325
0
        int should_recurse;
326
0
        pv->should_recurse = false;
327
0
        if (SCConfGetBool("pcap-file.recursive", &should_recurse) == 1) {
328
0
            pv->should_recurse = (should_recurse == 1);
329
0
        }
330
331
0
        int should_loop = 0;
332
0
        pv->should_loop = false;
333
0
        if (SCConfGetBool("pcap-file.continuous", &should_loop) == 1) {
334
0
            pv->should_loop = (should_loop == 1);
335
0
        }
336
337
0
        if (pv->should_recurse && pv->should_loop) {
338
0
            SCLogError("Error, --pcap-file-continuous and --pcap-file-recursive "
339
0
                       "cannot be used together.");
340
0
            closedir(directory);
341
0
            CleanupPcapFileDirectoryVars(pv);
342
0
            CleanupPcapFileThreadVars(ptv);
343
0
            SCReturnInt(TM_ECODE_FAILED);
344
0
        }
345
346
0
        pv->delay = 30;
347
0
        intmax_t delay = 0;
348
0
        if (SCConfGetInt("pcap-file.delay", &delay) == 1) {
349
0
            if (delay > 0 && delay < UINT_MAX) {
350
0
                pv->delay = (time_t)delay;
351
0
                SCLogDebug("delay %lu", pv->delay);
352
0
            } else {
353
0
                SCLogError("delay out of range");
354
0
            }
355
0
        }
356
357
0
        pv->poll_interval = 5;
358
0
        intmax_t poll_interval = 0;
359
0
        if (SCConfGetInt("pcap-file.poll-interval", &poll_interval) == 1) {
360
0
            if (poll_interval > 0 && poll_interval < UINT_MAX) {
361
0
                pv->poll_interval = (time_t)poll_interval;
362
0
                SCLogDebug("poll-interval %lu", pv->delay);
363
0
            } else {
364
0
                SCLogError("poll-interval out of range");
365
0
            }
366
0
        }
367
368
0
        pv->shared = &ptv->shared;
369
0
        pv->directory = directory;
370
0
        TAILQ_INIT(&pv->directory_content);
371
372
0
        ptv->is_directory = 1;
373
0
        ptv->behavior.directory = pv;
374
0
    }
375
376
9.57k
    if (SCConfGet("pcap-file.checksum-checks", &tmpstring) != 1) {
377
0
        pcap_g.conf_checksum_mode = CHECKSUM_VALIDATION_AUTO;
378
9.57k
    } else {
379
9.57k
        if (strcmp(tmpstring, "auto") == 0) {
380
0
            pcap_g.conf_checksum_mode = CHECKSUM_VALIDATION_AUTO;
381
9.57k
        } else if (SCConfValIsTrue(tmpstring)) {
382
0
            pcap_g.conf_checksum_mode = CHECKSUM_VALIDATION_ENABLE;
383
9.57k
        } else if (SCConfValIsFalse(tmpstring)) {
384
9.57k
            pcap_g.conf_checksum_mode = CHECKSUM_VALIDATION_DISABLE;
385
9.57k
        }
386
9.57k
    }
387
9.57k
    pcap_g.checksum_mode = pcap_g.conf_checksum_mode;
388
389
9.57k
    ptv->shared.tv = tv;
390
9.57k
    *data = (void *)ptv;
391
392
9.57k
    SCReturnInt(TM_ECODE_OK);
393
11.3k
}
394
395
void ReceivePcapFileThreadExitStats(ThreadVars *tv, void *data)
396
0
{
397
0
    SCEnter();
398
0
    if(data != NULL) {
399
0
        PcapFileThreadVars *ptv = (PcapFileThreadVars *)data;
400
401
0
        if (pcap_g.conf_checksum_mode == CHECKSUM_VALIDATION_AUTO &&
402
0
            pcap_g.cnt < CHECKSUM_SAMPLE_COUNT &&
403
0
            SC_ATOMIC_GET(pcap_g.invalid_checksums)) {
404
0
            uint64_t chrate = pcap_g.cnt / SC_ATOMIC_GET(pcap_g.invalid_checksums);
405
0
            if (chrate < CHECKSUM_INVALID_RATIO)
406
0
                SCLogWarning("1/%" PRIu64 "th of packets have an invalid checksum,"
407
0
                             " consider setting pcap-file.checksum-checks variable to no"
408
0
                             " or use '-k none' option on command line.",
409
0
                        chrate);
410
0
            else
411
0
                SCLogInfo("1/%" PRIu64 "th of packets have an invalid checksum",
412
0
                      chrate);
413
0
        }
414
0
        SCLogNotice("read %" PRIu64 " file%s, %" PRIu64 " packets, %" PRIu64 " bytes",
415
0
                ptv->shared.files, ptv->shared.files == 1 ? "" : "s", ptv->shared.pkts,
416
0
                ptv->shared.bytes);
417
0
    }
418
0
}
419
420
TmEcode ReceivePcapFileThreadDeinit(ThreadVars *tv, void *data)
421
18.8k
{
422
18.8k
    SCEnter();
423
18.8k
    if(data != NULL) {
424
18.8k
        PcapFileThreadVars *ptv = (PcapFileThreadVars *) data;
425
18.8k
        CleanupPcapFileThreadVars(ptv);
426
18.8k
    }
427
18.8k
    SCReturnInt(TM_ECODE_OK);
428
18.8k
}
429
430
static TmEcode DecodePcapFile(ThreadVars *tv, Packet *p, void *data)
431
19.1M
{
432
19.1M
    SCEnter();
433
19.1M
    DecodeThreadVars *dtv = (DecodeThreadVars *)data;
434
435
19.1M
    DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p));
436
437
    /* update counters */
438
19.1M
    DecodeUpdatePacketCounters(tv, dtv, p);
439
440
19.1M
    DecoderFunc decoder;
441
19.1M
    if(ValidateLinkType(p->datalink, &decoder) == TM_ECODE_OK) {
442
443
        /* call the decoder */
444
19.1M
        decoder(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p));
445
446
#ifdef DEBUG
447
        BUG_ON(p->pkt_src != PKT_SRC_WIRE && p->pkt_src != PKT_SRC_FFR);
448
#endif
449
450
19.1M
        PacketDecodeFinalize(tv, dtv, p);
451
452
19.1M
        SCReturnInt(TM_ECODE_OK);
453
19.1M
    } else {
454
134
        SCReturnInt(TM_ECODE_FAILED);
455
134
    }
456
19.1M
}
457
458
TmEcode DecodePcapFileThreadInit(ThreadVars *tv, const void *initdata, void **data)
459
2
{
460
2
    SCEnter();
461
2
    DecodeThreadVars *dtv = NULL;
462
2
    dtv = DecodeThreadVarsAlloc(tv);
463
464
2
    if (dtv == NULL)
465
0
        SCReturnInt(TM_ECODE_FAILED);
466
467
2
    DecodeRegisterPerfCounters(dtv, tv);
468
469
2
    *data = (void *)dtv;
470
471
2
    SCReturnInt(TM_ECODE_OK);
472
2
}
473
474
TmEcode DecodePcapFileThreadDeinit(ThreadVars *tv, void *data)
475
0
{
476
0
    if (data != NULL)
477
0
        DecodeThreadVarsFree(tv, data);
478
0
    SCReturnInt(TM_ECODE_OK);
479
0
}
480
481
void PcapIncreaseInvalidChecksum(void)
482
0
{
483
    (void) SC_ATOMIC_ADD(pcap_g.invalid_checksums, 1);
484
0
}
485
486
/* eof */