/src/suricata/src/log-pcap.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2021 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 William Metcalf <William.Metcalf@gmail.com> |
22 | | * \author Victor Julien <victor@inliniac.net> |
23 | | * |
24 | | * Pcap packet logging module. |
25 | | */ |
26 | | |
27 | | #include "suricata-common.h" |
28 | | #ifdef HAVE_LIBLZ4 |
29 | | #include <lz4frame.h> |
30 | | #include "util-fmemopen.h" |
31 | | #endif /* HAVE_LIBLZ4 */ |
32 | | |
33 | | #if defined(HAVE_DIRENT_H) && defined(HAVE_FNMATCH_H) |
34 | | #define INIT_RING_BUFFER |
35 | | #include <dirent.h> |
36 | | #include <fnmatch.h> |
37 | | #endif |
38 | | |
39 | | #include "log-pcap.h" |
40 | | |
41 | | #include "threads.h" |
42 | | #include "threadvars.h" |
43 | | #include "decode.h" |
44 | | #include "stream.h" |
45 | | #include "stream-tcp-reassemble.h" |
46 | | |
47 | | #include "output.h" |
48 | | |
49 | | #include "util-buffer.h" |
50 | | #include "util-byte.h" |
51 | | #include "util-conf.h" |
52 | | #include "util-cpu.h" |
53 | | #include "util-datalink.h" |
54 | | #include "util-misc.h" |
55 | | #include "util-path.h" |
56 | | #include "util-time.h" |
57 | | |
58 | 0 | #define DEFAULT_LOG_FILENAME "pcaplog" |
59 | 78 | #define MODULE_NAME "PcapLog" |
60 | 0 | #define MIN_LIMIT 4 * 1024 * 1024 |
61 | 0 | #define DEFAULT_LIMIT 100 * 1024 * 1024 |
62 | 0 | #define DEFAULT_FILE_LIMIT 0 |
63 | | |
64 | 0 | #define LOGMODE_NORMAL 0 |
65 | 0 | #define LOGMODE_MULTI 1 |
66 | | |
67 | | typedef enum LogModeConditionalType_ { |
68 | | LOGMODE_COND_ALL, |
69 | | LOGMODE_COND_ALERTS, |
70 | | LOGMODE_COND_TAG |
71 | | } LogModeConditionalType; |
72 | | |
73 | 0 | #define RING_BUFFER_MODE_DISABLED 0 |
74 | 0 | #define RING_BUFFER_MODE_ENABLED 1 |
75 | | |
76 | 0 | #define TS_FORMAT_SEC 0 |
77 | 0 | #define TS_FORMAT_USEC 1 |
78 | | |
79 | 0 | #define USE_STREAM_DEPTH_DISABLED 0 |
80 | 0 | #define USE_STREAM_DEPTH_ENABLED 1 |
81 | | |
82 | 0 | #define HONOR_PASS_RULES_DISABLED 0 |
83 | 0 | #define HONOR_PASS_RULES_ENABLED 1 |
84 | | |
85 | 0 | #define PCAP_SNAPLEN 262144 |
86 | 0 | #define PCAP_BUFFER_TIMEOUT 1000000 // microseconds |
87 | 0 | #define PCAP_PKTHDR_SIZE 16 |
88 | | |
89 | | /* Defined since libpcap 1.1.0. */ |
90 | | #ifndef PCAP_NETMASK_UNKNOWN |
91 | | #define PCAP_NETMASK_UNKNOWN 0xffffffff |
92 | | #endif |
93 | | |
94 | | SC_ATOMIC_DECLARE(uint32_t, thread_cnt); |
95 | | |
96 | | typedef struct PcapFileName_ { |
97 | | char *filename; |
98 | | char *dirname; |
99 | | |
100 | | /* Like a struct timeval, but with fixed size. This is only used when |
101 | | * seeding the ring buffer on start. */ |
102 | | struct { |
103 | | uint64_t secs; |
104 | | uint32_t usecs; |
105 | | }; |
106 | | |
107 | | TAILQ_ENTRY(PcapFileName_) next; /**< Pointer to next Pcap File for tailq. */ |
108 | | } PcapFileName; |
109 | | |
110 | | thread_local char *pcap_file_thread = NULL; |
111 | | |
112 | | typedef struct PcapLogProfileData_ { |
113 | | uint64_t total; |
114 | | uint64_t cnt; |
115 | | } PcapLogProfileData; |
116 | | |
117 | 0 | #define MAX_TOKS 9 |
118 | 0 | #define MAX_FILENAMELEN 513 |
119 | | |
120 | | enum PcapLogCompressionFormat { |
121 | | PCAP_LOG_COMPRESSION_FORMAT_NONE, |
122 | | PCAP_LOG_COMPRESSION_FORMAT_LZ4, |
123 | | }; |
124 | | |
125 | | typedef struct PcapLogCompressionData_ { |
126 | | enum PcapLogCompressionFormat format; |
127 | | uint8_t *buffer; |
128 | | uint64_t buffer_size; |
129 | | #ifdef HAVE_LIBLZ4 |
130 | | LZ4F_compressionContext_t lz4f_context; |
131 | | LZ4F_preferences_t lz4f_prefs; |
132 | | FILE *pcap_buf_wrapper; |
133 | | #endif /* HAVE_LIBLZ4 */ |
134 | | FILE *file; |
135 | | uint8_t *pcap_buf; |
136 | | uint64_t pcap_buf_size; |
137 | | uint64_t bytes_in_block; |
138 | | } PcapLogCompressionData; |
139 | | |
140 | | /** |
141 | | * PcapLog thread vars |
142 | | * |
143 | | * Used for storing file options. |
144 | | */ |
145 | | typedef struct PcapLogData_ { |
146 | | int use_stream_depth; /**< use stream depth i.e. ignore packets that reach limit */ |
147 | | int honor_pass_rules; /**< don't log if pass rules have matched */ |
148 | | char *bpf_filter; /**< bpf filter to apply to output */ |
149 | | SCMutex plog_lock; |
150 | | uint64_t pkt_cnt; /**< total number of packets */ |
151 | | struct pcap_pkthdr *h; /**< pcap header struct */ |
152 | | char *filename; /**< current filename */ |
153 | | int mode; /**< normal or multi */ |
154 | | int prev_day; /**< last day, for finding out when */ |
155 | | uint64_t size_current; /**< file current size */ |
156 | | uint64_t size_limit; /**< file size limit */ |
157 | | pcap_t *pcap_dead_handle; /**< pcap_dumper_t needs a handle */ |
158 | | pcap_dumper_t *pcap_dumper; /**< actually writes the packets */ |
159 | | struct bpf_program *bpfp; /**< compiled bpf program */ |
160 | | uint64_t profile_data_size; /**< track in bytes how many bytes we wrote */ |
161 | | uint32_t file_cnt; /**< count of pcap files we currently have */ |
162 | | uint32_t max_files; /**< maximum files to use in ring buffer mode */ |
163 | | bool is_private; /**< true if ctx is thread local */ |
164 | | LogModeConditionalType |
165 | | conditional; /**< log all packets or just packets and flows with alerts */ |
166 | | |
167 | | PcapLogProfileData profile_lock; |
168 | | PcapLogProfileData profile_write; |
169 | | PcapLogProfileData profile_unlock; |
170 | | PcapLogProfileData profile_handles; // open handles |
171 | | PcapLogProfileData profile_close; |
172 | | PcapLogProfileData profile_open; |
173 | | PcapLogProfileData profile_rotate; |
174 | | |
175 | | TAILQ_HEAD(, PcapFileName_) pcap_file_list; |
176 | | |
177 | | uint32_t thread_number; /**< thread number, first thread is 1, second 2, etc */ |
178 | | int use_ringbuffer; /**< ring buffer mode enabled or disabled */ |
179 | | int timestamp_format; /**< timestamp format sec or usec */ |
180 | | char *prefix; /**< filename prefix */ |
181 | | const char *suffix; /**< filename suffix */ |
182 | | char dir[PATH_MAX]; /**< pcap log directory */ |
183 | | int reported; |
184 | | int threads; /**< number of threads (only set in the global) */ |
185 | | char *filename_parts[MAX_TOKS]; |
186 | | int filename_part_cnt; |
187 | | struct timeval last_pcap_dump; |
188 | | int fopen_err; /**< set to the last fopen error */ |
189 | | bool pcap_open_err; /**< true if the last pcap open errored */ |
190 | | |
191 | | PcapLogCompressionData compression; |
192 | | } PcapLogData; |
193 | | |
194 | | typedef struct PcapLogThreadData_ { |
195 | | PcapLogData *pcap_log; |
196 | | MemBuffer *buf; |
197 | | StatsCounterId counter_written; /**< Counter for number of packets written */ |
198 | | StatsCounterId |
199 | | counter_filtered_bpf; /**< Counter for number of packets filtered out and not writen */ |
200 | | } PcapLogThreadData; |
201 | | |
202 | | /* Pattern for extracting timestamp from pcap log files. */ |
203 | | static const char timestamp_pattern[] = ".*?(\\d+)(\\.(\\d+))?"; |
204 | | static pcre2_code *pcre_timestamp_code = NULL; |
205 | | static pcre2_match_data *pcre_timestamp_match = NULL; |
206 | | |
207 | | /* global pcap data for when we're using multi mode. At exit we'll |
208 | | * merge counters into this one and then report counters. */ |
209 | | static PcapLogData *g_pcap_data = NULL; |
210 | | |
211 | | static int PcapLogOpenFileCtx(PcapLogData *); |
212 | | static int PcapLog(ThreadVars *, void *, const Packet *); |
213 | | static TmEcode PcapLogDataInit(ThreadVars *, const void *, void **); |
214 | | static TmEcode PcapLogDataDeinit(ThreadVars *, void *); |
215 | | static void PcapLogFileDeInitCtx(OutputCtx *); |
216 | | static OutputInitResult PcapLogInitCtx(SCConfNode *); |
217 | | static void PcapLogProfilingDump(PcapLogData *); |
218 | | static bool PcapLogCondition(ThreadVars *, void *, const Packet *); |
219 | | |
220 | | void PcapLogRegister(void) |
221 | 78 | { |
222 | 78 | OutputPacketLoggerFunctions output_logger_functions = { |
223 | 78 | .LogFunc = PcapLog, |
224 | 78 | .ConditionFunc = PcapLogCondition, |
225 | 78 | .ThreadInitFunc = PcapLogDataInit, |
226 | 78 | .ThreadDeinitFunc = PcapLogDataDeinit, |
227 | 78 | .ThreadExitPrintStatsFunc = NULL, |
228 | 78 | }; |
229 | 78 | OutputRegisterPacketModule( |
230 | 78 | LOGGER_PCAP, MODULE_NAME, "pcap-log", PcapLogInitCtx, &output_logger_functions); |
231 | 78 | PcapLogProfileSetup(); |
232 | 78 | SC_ATOMIC_INIT(thread_cnt); |
233 | 78 | SC_ATOMIC_SET(thread_cnt, 1); /* first id is 1 */ |
234 | 78 | } |
235 | | |
236 | | #define PCAPLOG_PROFILE_START \ |
237 | 0 | uint64_t pcaplog_profile_ticks = UtilCpuGetTicks() |
238 | | |
239 | | #define PCAPLOG_PROFILE_END(prof) \ |
240 | 0 | (prof).total += (UtilCpuGetTicks() - pcaplog_profile_ticks); \ |
241 | 0 | (prof).cnt++ |
242 | | |
243 | | static bool PcapLogCondition(ThreadVars *tv, void *thread_data, const Packet *p) |
244 | 0 | { |
245 | 0 | PcapLogThreadData *ptd = (PcapLogThreadData *)thread_data; |
246 | | |
247 | | /* Log alerted flow or tagged flow */ |
248 | 0 | switch (ptd->pcap_log->conditional) { |
249 | 0 | case LOGMODE_COND_ALL: |
250 | 0 | break; |
251 | 0 | case LOGMODE_COND_ALERTS: |
252 | 0 | return (p->alerts.cnt || (p->flow && FlowHasAlerts(p->flow))); |
253 | 0 | case LOGMODE_COND_TAG: |
254 | 0 | return (p->flags & (PKT_HAS_TAG | PKT_FIRST_TAG)); |
255 | 0 | } |
256 | | |
257 | 0 | if (p->flags & PKT_PSEUDO_STREAM_END) { |
258 | 0 | return false; |
259 | 0 | } |
260 | | |
261 | 0 | return !PacketIsTunnelChild(p); |
262 | 0 | } |
263 | | |
264 | | /** |
265 | | * \brief Function to close pcaplog file |
266 | | * |
267 | | * \param t Thread Variable containing input/output queue, cpu affinity etc. |
268 | | * \param pl PcapLog thread variable. |
269 | | */ |
270 | | static int PcapLogCloseFile(ThreadVars *t, PcapLogData *pl) |
271 | 0 | { |
272 | 0 | if (pl != NULL) { |
273 | 0 | PCAPLOG_PROFILE_START; |
274 | |
|
275 | 0 | if (pl->pcap_dumper != NULL) { |
276 | 0 | pcap_dump_close(pl->pcap_dumper); |
277 | 0 | #ifdef HAVE_LIBLZ4 |
278 | 0 | PcapLogCompressionData *comp = &pl->compression; |
279 | 0 | if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) { |
280 | 0 | comp->pcap_buf_wrapper = NULL; |
281 | 0 | } |
282 | 0 | #endif /* HAVE_LIBLZ4 */ |
283 | 0 | } |
284 | 0 | pl->size_current = 0; |
285 | 0 | pl->pcap_dumper = NULL; |
286 | |
|
287 | 0 | if (pl->pcap_dead_handle != NULL) |
288 | 0 | pcap_close(pl->pcap_dead_handle); |
289 | 0 | pl->pcap_dead_handle = NULL; |
290 | |
|
291 | 0 | #ifdef HAVE_LIBLZ4 |
292 | 0 | PcapLogCompressionData *comp = &pl->compression; |
293 | 0 | if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) { |
294 | | /* pcap_dump_close did not write any data because we call |
295 | | * pcap_dump_flush() after every write when writing |
296 | | * compressed output. */ |
297 | 0 | uint64_t bytes_written = LZ4F_compressEnd(comp->lz4f_context, |
298 | 0 | comp->buffer, comp->buffer_size, NULL); |
299 | 0 | if (LZ4F_isError(bytes_written)) { |
300 | 0 | SCLogError("LZ4F_compressEnd: %s", LZ4F_getErrorName(bytes_written)); |
301 | 0 | return TM_ECODE_FAILED; |
302 | 0 | } |
303 | 0 | if (fwrite(comp->buffer, 1, bytes_written, comp->file) < bytes_written) { |
304 | 0 | SCLogError("fwrite failed: %s", strerror(errno)); |
305 | 0 | return TM_ECODE_FAILED; |
306 | 0 | } |
307 | 0 | fclose(comp->file); |
308 | 0 | comp->bytes_in_block = 0; |
309 | 0 | } |
310 | 0 | #endif /* HAVE_LIBLZ4 */ |
311 | | |
312 | 0 | PCAPLOG_PROFILE_END(pl->profile_close); |
313 | 0 | } |
314 | | |
315 | 0 | return 0; |
316 | 0 | } |
317 | | |
318 | | static void PcapFileNameFree(PcapFileName *pf) |
319 | 0 | { |
320 | 0 | if (pf != NULL) { |
321 | 0 | if (pf->filename != NULL) { |
322 | 0 | SCFree(pf->filename); |
323 | 0 | } |
324 | 0 | if (pf->dirname != NULL) { |
325 | 0 | SCFree(pf->dirname); |
326 | 0 | } |
327 | 0 | SCFree(pf); |
328 | 0 | } |
329 | 0 | } |
330 | | |
331 | | /** |
332 | | * \brief Function to rotate pcaplog file |
333 | | * |
334 | | * \param t Thread Variable containing input/output queue, cpu affinity etc. |
335 | | * \param pl PcapLog thread variable. |
336 | | * |
337 | | * \retval 0 on success |
338 | | * \retval -1 on failure |
339 | | */ |
340 | | static int PcapLogRotateFile(ThreadVars *t, PcapLogData *pl) |
341 | 0 | { |
342 | 0 | PcapFileName *pf; |
343 | |
|
344 | 0 | PCAPLOG_PROFILE_START; |
345 | |
|
346 | 0 | if (PcapLogCloseFile(t,pl) < 0) { |
347 | 0 | SCLogDebug("PcapLogCloseFile failed"); |
348 | 0 | return -1; |
349 | 0 | } |
350 | | |
351 | 0 | if (pl->use_ringbuffer == RING_BUFFER_MODE_ENABLED && pl->file_cnt >= pl->max_files) { |
352 | 0 | pf = TAILQ_FIRST(&pl->pcap_file_list); |
353 | 0 | SCLogDebug("Removing pcap file %s", pf->filename); |
354 | |
|
355 | 0 | if (remove(pf->filename) != 0) { |
356 | | // VJ remove can fail because file is already gone |
357 | | // SCLogWarning("failed to remove log file %s: %s", |
358 | | // pf->filename, strerror( errno )); |
359 | 0 | } |
360 | |
|
361 | 0 | TAILQ_REMOVE(&pl->pcap_file_list, pf, next); |
362 | 0 | DEBUG_VALIDATE_BUG_ON(TAILQ_FIRST(&pl->pcap_file_list) == pf); |
363 | 0 | PcapFileNameFree(pf); |
364 | 0 | pl->file_cnt--; |
365 | 0 | } |
366 | | |
367 | 0 | if (PcapLogOpenFileCtx(pl) < 0) { |
368 | 0 | SCLogError("opening new pcap log file failed"); |
369 | 0 | return -1; |
370 | 0 | } |
371 | 0 | pl->file_cnt++; |
372 | 0 | SCLogDebug("file_cnt %u", pl->file_cnt); |
373 | |
|
374 | 0 | PCAPLOG_PROFILE_END(pl->profile_rotate); |
375 | 0 | return 0; |
376 | 0 | } |
377 | | |
378 | | static int PcapLogOpenHandles(PcapLogData *pl, const Packet *p) |
379 | 0 | { |
380 | 0 | PCAPLOG_PROFILE_START; |
381 | |
|
382 | 0 | int datalink = p->datalink; |
383 | 0 | if (PacketIsTunnelChild(p)) { |
384 | 0 | Packet *real_p = p->root; |
385 | 0 | datalink = real_p->datalink; |
386 | 0 | } |
387 | 0 | if (pl->pcap_dead_handle == NULL) { |
388 | 0 | SCLogDebug("Setting pcap-log link type to %u", datalink); |
389 | 0 | if ((pl->pcap_dead_handle = pcap_open_dead(datalink, PCAP_SNAPLEN)) == NULL) { |
390 | 0 | SCLogDebug("Error opening dead pcap handle"); |
391 | 0 | return TM_ECODE_FAILED; |
392 | 0 | } |
393 | | |
394 | 0 | if (pl->bpfp == NULL && pl->bpf_filter) { |
395 | 0 | struct bpf_program bpfp; |
396 | 0 | if (pcap_compile(pl->pcap_dead_handle, &bpfp, pl->bpf_filter, 0, |
397 | 0 | PCAP_NETMASK_UNKNOWN) == PCAP_ERROR) { |
398 | 0 | FatalError("Failed to compile BPF filter, aborting: %s: %s", pl->bpf_filter, |
399 | 0 | pcap_geterr(pl->pcap_dead_handle)); |
400 | 0 | } else { |
401 | 0 | pl->bpfp = SCCalloc(1, sizeof(*pl->bpfp)); |
402 | 0 | if (pl->bpfp == NULL) { |
403 | 0 | FatalError("Failed to allocate memory for BPF filter, aborting"); |
404 | 0 | } |
405 | 0 | *pl->bpfp = bpfp; |
406 | 0 | } |
407 | 0 | } |
408 | 0 | } |
409 | | |
410 | 0 | if (pl->pcap_dumper == NULL) { |
411 | 0 | if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_NONE) { |
412 | 0 | if ((pl->pcap_dumper = pcap_dump_open(pl->pcap_dead_handle, |
413 | 0 | pl->filename)) == NULL) { |
414 | 0 | if (!pl->pcap_open_err) { |
415 | 0 | SCLogError("Error opening dump file %s", pcap_geterr(pl->pcap_dead_handle)); |
416 | 0 | pl->pcap_open_err = true; |
417 | 0 | } |
418 | 0 | return TM_ECODE_FAILED; |
419 | 0 | } else { |
420 | 0 | pl->pcap_open_err = false; |
421 | 0 | } |
422 | 0 | } |
423 | 0 | #ifdef HAVE_LIBLZ4 |
424 | 0 | else if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) { |
425 | 0 | PcapLogCompressionData *comp = &pl->compression; |
426 | |
|
427 | 0 | comp->file = fopen(pl->filename, "w"); |
428 | 0 | if (comp->file == NULL) { |
429 | 0 | if (errno != pl->fopen_err) { |
430 | 0 | SCLogError("Error opening file for compressed output: %s", strerror(errno)); |
431 | 0 | pl->fopen_err = errno; |
432 | 0 | } |
433 | 0 | return TM_ECODE_FAILED; |
434 | 0 | } else { |
435 | 0 | pl->fopen_err = 0; |
436 | 0 | } |
437 | | |
438 | 0 | comp->pcap_buf_wrapper = SCFmemopen(comp->pcap_buf, comp->pcap_buf_size, "w"); |
439 | 0 | if (comp->pcap_buf_wrapper == NULL) { |
440 | 0 | fclose(comp->file); |
441 | 0 | comp->file = NULL; |
442 | 0 | return TM_ECODE_FAILED; |
443 | 0 | } |
444 | 0 | if ((pl->pcap_dumper = pcap_dump_fopen(pl->pcap_dead_handle, comp->pcap_buf_wrapper)) == |
445 | 0 | NULL) { |
446 | 0 | if (!pl->pcap_open_err) { |
447 | 0 | SCLogError("Error opening dump file %s", pcap_geterr(pl->pcap_dead_handle)); |
448 | 0 | pl->pcap_open_err = true; |
449 | 0 | } |
450 | 0 | fclose(comp->file); |
451 | 0 | comp->file = NULL; |
452 | 0 | fclose(comp->pcap_buf_wrapper); |
453 | 0 | comp->pcap_buf_wrapper = NULL; |
454 | 0 | return TM_ECODE_FAILED; |
455 | 0 | } else { |
456 | 0 | pl->pcap_open_err = false; |
457 | 0 | } |
458 | | |
459 | 0 | uint64_t bytes_written = LZ4F_compressBegin(comp->lz4f_context, |
460 | 0 | comp->buffer, comp->buffer_size, NULL); |
461 | 0 | if (LZ4F_isError(bytes_written)) { |
462 | 0 | SCLogError("LZ4F_compressBegin: %s", LZ4F_getErrorName(bytes_written)); |
463 | 0 | return TM_ECODE_FAILED; |
464 | 0 | } |
465 | 0 | if (fwrite(comp->buffer, 1, bytes_written, comp->file) < bytes_written) { |
466 | 0 | SCLogError("fwrite failed: %s", strerror(errno)); |
467 | 0 | return TM_ECODE_FAILED; |
468 | 0 | } |
469 | 0 | } |
470 | 0 | #endif /* HAVE_LIBLZ4 */ |
471 | 0 | } |
472 | | |
473 | 0 | PCAPLOG_PROFILE_END(pl->profile_handles); |
474 | 0 | return TM_ECODE_OK; |
475 | 0 | } |
476 | | |
477 | | /** \internal |
478 | | * \brief lock wrapper for main PcapLog() function |
479 | | * NOTE: only meant for use in main PcapLog() function. |
480 | | */ |
481 | | static void PcapLogLock(PcapLogData *pl) |
482 | 0 | { |
483 | 0 | if (!(pl->is_private)) { |
484 | 0 | PCAPLOG_PROFILE_START; |
485 | 0 | SCMutexLock(&pl->plog_lock); |
486 | 0 | PCAPLOG_PROFILE_END(pl->profile_lock); |
487 | 0 | } |
488 | 0 | } |
489 | | |
490 | | /** \internal |
491 | | * \brief unlock wrapper for main PcapLog() function |
492 | | * NOTE: only meant for use in main PcapLog() function. |
493 | | */ |
494 | | static void PcapLogUnlock(PcapLogData *pl) |
495 | 0 | { |
496 | 0 | if (!(pl->is_private)) { |
497 | 0 | PCAPLOG_PROFILE_START; |
498 | 0 | SCMutexUnlock(&pl->plog_lock); |
499 | 0 | PCAPLOG_PROFILE_END(pl->profile_unlock); |
500 | 0 | } |
501 | 0 | } |
502 | | |
503 | | static inline int PcapWrite( |
504 | | ThreadVars *tv, PcapLogThreadData *td, const uint8_t *data, const size_t len) |
505 | 0 | { |
506 | 0 | struct timeval current_dump; |
507 | 0 | gettimeofday(¤t_dump, NULL); |
508 | 0 | PcapLogData *pl = td->pcap_log; |
509 | |
|
510 | 0 | if (pl->bpfp) { |
511 | 0 | if (pcap_offline_filter(pl->bpfp, pl->h, data) == 0) { |
512 | 0 | SCLogDebug("Packet doesn't match filter, will not be logged."); |
513 | 0 | StatsCounterIncr(&tv->stats, td->counter_filtered_bpf); |
514 | 0 | return TM_ECODE_OK; |
515 | 0 | } |
516 | 0 | } |
517 | | |
518 | 0 | StatsCounterIncr(&tv->stats, td->counter_written); |
519 | |
|
520 | 0 | pcap_dump((u_char *)pl->pcap_dumper, pl->h, data); |
521 | 0 | if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_NONE) { |
522 | 0 | pl->size_current += len; |
523 | 0 | } |
524 | 0 | #ifdef HAVE_LIBLZ4 |
525 | 0 | else if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) { |
526 | 0 | PcapLogCompressionData *comp = &pl->compression; |
527 | 0 | pcap_dump_flush(pl->pcap_dumper); |
528 | 0 | long in_size = ftell(comp->pcap_buf_wrapper); |
529 | 0 | if (in_size < 0) { |
530 | 0 | SCLogError("ftell failed with: %s", strerror(errno)); |
531 | 0 | return TM_ECODE_FAILED; |
532 | 0 | } |
533 | 0 | uint64_t out_size = LZ4F_compressUpdate(comp->lz4f_context, comp->buffer, comp->buffer_size, |
534 | 0 | comp->pcap_buf, (uint64_t)in_size, NULL); |
535 | 0 | if (LZ4F_isError(len)) { |
536 | 0 | SCLogError("LZ4F_compressUpdate: %s", LZ4F_getErrorName(len)); |
537 | 0 | return TM_ECODE_FAILED; |
538 | 0 | } |
539 | 0 | if (fseek(comp->pcap_buf_wrapper, 0, SEEK_SET) != 0) { |
540 | 0 | SCLogError("fseek failed: %s", strerror(errno)); |
541 | 0 | return TM_ECODE_FAILED; |
542 | 0 | } |
543 | 0 | if (fwrite(comp->buffer, 1, out_size, comp->file) < out_size) { |
544 | 0 | SCLogError("fwrite failed: %s", strerror(errno)); |
545 | 0 | return TM_ECODE_FAILED; |
546 | 0 | } |
547 | 0 | if (out_size > 0) { |
548 | 0 | pl->size_current += out_size; |
549 | 0 | comp->bytes_in_block = len; |
550 | 0 | } else { |
551 | 0 | comp->bytes_in_block += len; |
552 | 0 | } |
553 | 0 | } |
554 | 0 | #endif /* HAVE_LIBLZ4 */ |
555 | 0 | if (TimeDifferenceMicros(pl->last_pcap_dump, current_dump) >= PCAP_BUFFER_TIMEOUT) { |
556 | 0 | pcap_dump_flush(pl->pcap_dumper); |
557 | 0 | } |
558 | 0 | pl->last_pcap_dump = current_dump; |
559 | 0 | return TM_ECODE_OK; |
560 | 0 | } |
561 | | |
562 | | struct PcapLogCallbackContext { |
563 | | ThreadVars *tv; |
564 | | PcapLogThreadData *td; |
565 | | }; |
566 | | |
567 | | static int PcapLogSegmentCallback( |
568 | | const Packet *p, TcpSegment *seg, void *data, const uint8_t *buf, uint32_t buflen) |
569 | 0 | { |
570 | 0 | struct PcapLogCallbackContext *pctx = (struct PcapLogCallbackContext *)data; |
571 | |
|
572 | 0 | if (seg->pcap_hdr_storage->pktlen) { |
573 | 0 | struct timeval tv; |
574 | 0 | SCTIME_TO_TIMEVAL(&tv, seg->pcap_hdr_storage->ts); |
575 | 0 | pctx->td->pcap_log->h->ts.tv_sec = tv.tv_sec; |
576 | 0 | pctx->td->pcap_log->h->ts.tv_usec = tv.tv_usec; |
577 | | |
578 | | /* Ensure the buffer can hold the full packet: headers + payload. |
579 | | */ |
580 | 0 | const uint32_t pktlen = seg->pcap_hdr_storage->pktlen; |
581 | 0 | const uint32_t total_len = pktlen + buflen; |
582 | |
|
583 | 0 | if (unlikely(total_len >= MEMBUFFER_SIZE(pctx->td->buf))) { |
584 | 0 | uint32_t expand_by = total_len + 1 - MEMBUFFER_SIZE(pctx->td->buf); |
585 | 0 | if (expand_by % 4096 != 0) { |
586 | 0 | expand_by = expand_by - (expand_by % 4096) + 4096; |
587 | 0 | } |
588 | 0 | if (unlikely(MemBufferExpand(&pctx->td->buf, expand_by) < 0)) { |
589 | 0 | SCLogWarning("Failed to expand pcap-log buffer for segment " |
590 | 0 | "of size %u", |
591 | 0 | total_len); |
592 | 0 | return 1; |
593 | 0 | } |
594 | 0 | } |
595 | | |
596 | 0 | pctx->td->pcap_log->h->len = total_len; |
597 | 0 | pctx->td->pcap_log->h->caplen = total_len; |
598 | 0 | MemBufferReset(pctx->td->buf); |
599 | 0 | MemBufferWriteRaw(pctx->td->buf, seg->pcap_hdr_storage->pkt_hdr, pktlen); |
600 | 0 | MemBufferWriteRaw(pctx->td->buf, buf, buflen); |
601 | |
|
602 | 0 | PcapWrite(pctx->tv, pctx->td, (uint8_t *)pctx->td->buf->buffer, total_len); |
603 | 0 | } |
604 | 0 | return 1; |
605 | 0 | } |
606 | | |
607 | | static void PcapLogDumpSegments(ThreadVars *tv, PcapLogThreadData *td, const Packet *p) |
608 | 0 | { |
609 | 0 | uint8_t flag = STREAM_DUMP_HEADERS; |
610 | | |
611 | | /* Loop on segment from this side */ |
612 | 0 | struct PcapLogCallbackContext data = { tv, td }; |
613 | 0 | StreamSegmentForSession(p, flag, PcapLogSegmentCallback, (void *)&data); |
614 | 0 | } |
615 | | |
616 | | /** |
617 | | * \brief Pcap logging main function |
618 | | * |
619 | | * \param t threadvar |
620 | | * \param p packet |
621 | | * \param thread_data thread module specific data |
622 | | * |
623 | | * \retval TM_ECODE_OK on succes |
624 | | * \retval TM_ECODE_FAILED on serious error |
625 | | */ |
626 | | static int PcapLog(ThreadVars *tv, void *thread_data, const Packet *p) |
627 | 0 | { |
628 | 0 | size_t len; |
629 | 0 | int ret = 0; |
630 | 0 | Packet *rp = NULL; |
631 | |
|
632 | 0 | PcapLogThreadData *td = (PcapLogThreadData *)thread_data; |
633 | 0 | PcapLogData *pl = td->pcap_log; |
634 | |
|
635 | 0 | if (((p->flags & PKT_STREAM_NOPCAPLOG) && (pl->use_stream_depth == USE_STREAM_DEPTH_ENABLED)) || |
636 | 0 | (pl->honor_pass_rules && (p->flags & PKT_NOPACKET_INSPECTION))) { |
637 | 0 | return TM_ECODE_OK; |
638 | 0 | } |
639 | | |
640 | 0 | PcapLogLock(pl); |
641 | |
|
642 | 0 | pl->pkt_cnt++; |
643 | 0 | pl->h->ts.tv_sec = SCTIME_SECS(p->ts); |
644 | 0 | pl->h->ts.tv_usec = SCTIME_USECS(p->ts); |
645 | 0 | if (PacketIsTunnelChild(p)) { |
646 | 0 | rp = p->root; |
647 | 0 | pl->h->caplen = GET_PKT_LEN(rp); |
648 | 0 | pl->h->len = GET_PKT_LEN(rp); |
649 | 0 | len = PCAP_PKTHDR_SIZE + GET_PKT_LEN(rp); |
650 | 0 | } else { |
651 | 0 | pl->h->caplen = GET_PKT_LEN(p); |
652 | 0 | pl->h->len = GET_PKT_LEN(p); |
653 | 0 | len = PCAP_PKTHDR_SIZE + GET_PKT_LEN(p); |
654 | 0 | } |
655 | |
|
656 | 0 | if (pl->filename == NULL) { |
657 | 0 | ret = PcapLogOpenFileCtx(pl); |
658 | 0 | if (ret < 0) { |
659 | 0 | PcapLogUnlock(pl); |
660 | 0 | return TM_ECODE_FAILED; |
661 | 0 | } |
662 | 0 | SCLogDebug("Opening PCAP log file %s", pl->filename); |
663 | 0 | } |
664 | | |
665 | 0 | PcapLogCompressionData *comp = &pl->compression; |
666 | 0 | if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_NONE) { |
667 | 0 | if ((pl->size_current + len) > pl->size_limit) { |
668 | 0 | if (PcapLogRotateFile(tv, pl) < 0) { |
669 | 0 | PcapLogUnlock(pl); |
670 | 0 | SCLogDebug("rotation of pcap failed"); |
671 | 0 | return TM_ECODE_FAILED; |
672 | 0 | } |
673 | 0 | } |
674 | 0 | } |
675 | 0 | #ifdef HAVE_LIBLZ4 |
676 | 0 | else if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) { |
677 | | /* When writing compressed pcap logs, we have no way of knowing |
678 | | * for sure whether adding this packet would cause the current |
679 | | * file to exceed the size limit. Thus, we record the number of |
680 | | * bytes that have been fed into lz4 since the last write, and |
681 | | * act as if they would be written uncompressed. */ |
682 | |
|
683 | 0 | if ((pl->size_current + comp->bytes_in_block + len) > pl->size_limit) { |
684 | 0 | if (PcapLogRotateFile(tv, pl) < 0) { |
685 | 0 | PcapLogUnlock(pl); |
686 | 0 | SCLogDebug("rotation of pcap failed"); |
687 | 0 | return TM_ECODE_FAILED; |
688 | 0 | } |
689 | 0 | } |
690 | 0 | } |
691 | 0 | #endif /* HAVE_LIBLZ4 */ |
692 | | |
693 | | /* XXX pcap handles, nfq, pfring, can only have one link type ipfw? we do |
694 | | * this here as we don't know the link type until we get our first packet */ |
695 | 0 | if (pl->pcap_dead_handle == NULL || pl->pcap_dumper == NULL) { |
696 | 0 | if (PcapLogOpenHandles(pl, p) != TM_ECODE_OK) { |
697 | 0 | PcapLogUnlock(pl); |
698 | 0 | return TM_ECODE_FAILED; |
699 | 0 | } |
700 | 0 | } |
701 | | |
702 | 0 | PCAPLOG_PROFILE_START; |
703 | | |
704 | | /* if we are using alerted logging and if packet is first one with alert in flow |
705 | | * then we need to dump in the pcap the stream acked by the packet */ |
706 | 0 | if ((p->flags & PKT_FIRST_ALERTS) && (td->pcap_log->conditional != LOGMODE_COND_ALL)) { |
707 | 0 | if (PacketIsTCP(p)) { |
708 | | /* dump fake packets for all segments we have on acked by packet */ |
709 | 0 | PcapLogDumpSegments(tv, td, p); |
710 | |
|
711 | 0 | if (p->flags & PKT_PSEUDO_STREAM_END) { |
712 | 0 | PcapLogUnlock(pl); |
713 | 0 | return TM_ECODE_OK; |
714 | 0 | } |
715 | | |
716 | | /* PcapLogDumpSegment has written over the PcapLogData variables so need to update */ |
717 | 0 | pl->h->ts.tv_sec = SCTIME_SECS(p->ts); |
718 | 0 | pl->h->ts.tv_usec = SCTIME_USECS(p->ts); |
719 | 0 | if (PacketIsTunnelChild(p)) { |
720 | 0 | rp = p->root; |
721 | 0 | pl->h->caplen = GET_PKT_LEN(rp); |
722 | 0 | pl->h->len = GET_PKT_LEN(rp); |
723 | 0 | len = PCAP_PKTHDR_SIZE + GET_PKT_LEN(rp); |
724 | 0 | } else { |
725 | 0 | pl->h->caplen = GET_PKT_LEN(p); |
726 | 0 | pl->h->len = GET_PKT_LEN(p); |
727 | 0 | len = PCAP_PKTHDR_SIZE + GET_PKT_LEN(p); |
728 | 0 | } |
729 | 0 | } |
730 | 0 | } |
731 | | |
732 | 0 | if (PacketIsTunnelChild(p)) { |
733 | 0 | rp = p->root; |
734 | 0 | ret = PcapWrite(tv, td, GET_PKT_DATA(rp), len); |
735 | 0 | } else { |
736 | 0 | ret = PcapWrite(tv, td, GET_PKT_DATA(p), len); |
737 | 0 | } |
738 | 0 | if (ret != TM_ECODE_OK) { |
739 | 0 | PCAPLOG_PROFILE_END(pl->profile_write); |
740 | 0 | PcapLogUnlock(pl); |
741 | 0 | return ret; |
742 | 0 | } |
743 | | |
744 | 0 | PCAPLOG_PROFILE_END(pl->profile_write); |
745 | 0 | pl->profile_data_size += len; |
746 | |
|
747 | 0 | SCLogDebug("pl->size_current %"PRIu64", pl->size_limit %"PRIu64, |
748 | 0 | pl->size_current, pl->size_limit); |
749 | |
|
750 | 0 | PcapLogUnlock(pl); |
751 | 0 | return TM_ECODE_OK; |
752 | 0 | } |
753 | | |
754 | | static PcapLogData *PcapLogDataCopy(const PcapLogData *pl) |
755 | 0 | { |
756 | 0 | BUG_ON(pl->mode != LOGMODE_MULTI); |
757 | 0 | PcapLogData *copy = SCCalloc(1, sizeof(*copy)); |
758 | 0 | if (unlikely(copy == NULL)) { |
759 | 0 | return NULL; |
760 | 0 | } |
761 | | |
762 | 0 | copy->h = SCCalloc(1, sizeof(*copy->h)); |
763 | 0 | if (unlikely(copy->h == NULL)) { |
764 | 0 | SCFree(copy); |
765 | 0 | return NULL; |
766 | 0 | } |
767 | | |
768 | 0 | copy->prefix = SCStrdup(pl->prefix); |
769 | 0 | if (unlikely(copy->prefix == NULL)) { |
770 | 0 | SCFree(copy->h); |
771 | 0 | SCFree(copy); |
772 | 0 | return NULL; |
773 | 0 | } |
774 | | |
775 | 0 | copy->suffix = pl->suffix; |
776 | | |
777 | | /* settings TODO move to global cfg struct */ |
778 | 0 | copy->is_private = true; |
779 | 0 | copy->mode = pl->mode; |
780 | 0 | copy->max_files = pl->max_files; |
781 | 0 | copy->use_ringbuffer = pl->use_ringbuffer; |
782 | 0 | copy->timestamp_format = pl->timestamp_format; |
783 | 0 | copy->use_stream_depth = pl->use_stream_depth; |
784 | 0 | copy->size_limit = pl->size_limit; |
785 | 0 | copy->conditional = pl->conditional; |
786 | 0 | copy->bpf_filter = pl->bpf_filter; |
787 | |
|
788 | 0 | const PcapLogCompressionData *comp = &pl->compression; |
789 | 0 | PcapLogCompressionData *copy_comp = ©->compression; |
790 | 0 | copy_comp->format = comp->format; |
791 | 0 | #ifdef HAVE_LIBLZ4 |
792 | 0 | if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) { |
793 | | /* We need to allocate a new compression context and buffers for |
794 | | * the copy. First copy the things that can simply be copied. */ |
795 | |
|
796 | 0 | copy_comp->buffer_size = comp->buffer_size; |
797 | 0 | copy_comp->pcap_buf_size = comp->pcap_buf_size; |
798 | 0 | copy_comp->lz4f_prefs = comp->lz4f_prefs; |
799 | | |
800 | | /* Allocate the buffers. */ |
801 | |
|
802 | 0 | copy_comp->buffer = SCMalloc(copy_comp->buffer_size); |
803 | 0 | if (copy_comp->buffer == NULL) { |
804 | 0 | SCLogError("SCMalloc failed: %s", strerror(errno)); |
805 | 0 | SCFree(copy->prefix); |
806 | 0 | SCFree(copy->h); |
807 | 0 | SCFree(copy); |
808 | 0 | return NULL; |
809 | 0 | } |
810 | 0 | copy_comp->pcap_buf = SCMalloc(copy_comp->pcap_buf_size); |
811 | 0 | if (copy_comp->pcap_buf == NULL) { |
812 | 0 | SCLogError("SCMalloc failed: %s", strerror(errno)); |
813 | 0 | SCFree(copy_comp->buffer); |
814 | 0 | SCFree(copy->prefix); |
815 | 0 | SCFree(copy->h); |
816 | 0 | SCFree(copy); |
817 | 0 | return NULL; |
818 | 0 | } |
819 | 0 | copy_comp->pcap_buf_wrapper = SCFmemopen(copy_comp->pcap_buf, |
820 | 0 | copy_comp->pcap_buf_size, "w"); |
821 | 0 | if (copy_comp->pcap_buf_wrapper == NULL) { |
822 | 0 | SCLogError("SCFmemopen failed: %s", strerror(errno)); |
823 | 0 | SCFree(copy_comp->buffer); |
824 | 0 | SCFree(copy_comp->pcap_buf); |
825 | 0 | SCFree(copy->prefix); |
826 | 0 | SCFree(copy->h); |
827 | 0 | SCFree(copy); |
828 | 0 | return NULL; |
829 | 0 | } |
830 | | |
831 | | /* Initialize a new compression context. */ |
832 | | |
833 | 0 | LZ4F_errorCode_t errcode = |
834 | 0 | LZ4F_createCompressionContext(©_comp->lz4f_context, 1); |
835 | 0 | if (LZ4F_isError(errcode)) { |
836 | 0 | SCLogError("LZ4F_createCompressionContext failed: %s", LZ4F_getErrorName(errcode)); |
837 | 0 | fclose(copy_comp->pcap_buf_wrapper); |
838 | 0 | SCFree(copy_comp->buffer); |
839 | 0 | SCFree(copy_comp->pcap_buf); |
840 | 0 | SCFree(copy->prefix); |
841 | 0 | SCFree(copy->h); |
842 | 0 | SCFree(copy); |
843 | 0 | return NULL; |
844 | 0 | } |
845 | | |
846 | | /* Initialize the rest. */ |
847 | | |
848 | 0 | copy_comp->file = NULL; |
849 | 0 | copy_comp->bytes_in_block = 0; |
850 | 0 | } |
851 | 0 | #endif /* HAVE_LIBLZ4 */ |
852 | | |
853 | 0 | TAILQ_INIT(©->pcap_file_list); |
854 | 0 | SCMutexInit(©->plog_lock, NULL); |
855 | |
|
856 | 0 | strlcpy(copy->dir, pl->dir, sizeof(copy->dir)); |
857 | |
|
858 | 0 | for (int i = 0; i < pl->filename_part_cnt && i < MAX_TOKS; i++) |
859 | 0 | copy->filename_parts[i] = pl->filename_parts[i]; |
860 | 0 | copy->filename_part_cnt = pl->filename_part_cnt; |
861 | | |
862 | | /* set thread number, first thread is 1 */ |
863 | 0 | copy->thread_number = SC_ATOMIC_ADD(thread_cnt, 1); |
864 | |
|
865 | 0 | SCLogDebug("copied, returning %p", copy); |
866 | 0 | return copy; |
867 | 0 | } |
868 | | |
869 | | #ifdef INIT_RING_BUFFER |
870 | | static int PcapLogGetTimeOfFile(const char *filename, uint64_t *secs, |
871 | | uint32_t *usecs) |
872 | 0 | { |
873 | 0 | char buf[PATH_MAX]; |
874 | 0 | size_t copylen; |
875 | |
|
876 | 0 | int n = pcre2_match(pcre_timestamp_code, (PCRE2_SPTR8)filename, strlen(filename), 0, 0, |
877 | 0 | pcre_timestamp_match, NULL); |
878 | 0 | if (n != 2 && n != 4) { |
879 | | /* No match. */ |
880 | 0 | return 0; |
881 | 0 | } |
882 | | |
883 | 0 | if (n >= 2) { |
884 | | /* Extract seconds. */ |
885 | 0 | copylen = sizeof(buf); |
886 | 0 | if (pcre2_substring_copy_bynumber(pcre_timestamp_match, 1, (PCRE2_UCHAR8 *)buf, ©len) < |
887 | 0 | 0) { |
888 | 0 | return 0; |
889 | 0 | } |
890 | 0 | if (StringParseUint64(secs, 10, 0, buf) < 0) { |
891 | 0 | return 0; |
892 | 0 | } |
893 | 0 | } |
894 | 0 | if (n == 4) { |
895 | | /* Extract microseconds. */ |
896 | 0 | copylen = sizeof(buf); |
897 | 0 | if (pcre2_substring_copy_bynumber(pcre_timestamp_match, 3, (PCRE2_UCHAR8 *)buf, ©len) < |
898 | 0 | 0) { |
899 | 0 | return 0; |
900 | 0 | } |
901 | 0 | if (StringParseUint32(usecs, 10, 0, buf) < 0) { |
902 | 0 | return 0; |
903 | 0 | } |
904 | 0 | } |
905 | | |
906 | 0 | return 1; |
907 | 0 | } |
908 | | |
909 | | static TmEcode PcapLogInitRingBuffer(PcapLogData *pl) |
910 | 0 | { |
911 | 0 | char pattern[PATH_MAX]; |
912 | |
|
913 | 0 | SCLogInfo("Initializing PCAP ring buffer for %s/%s.", |
914 | 0 | pl->dir, pl->prefix); |
915 | |
|
916 | 0 | strlcpy(pattern, pl->dir, PATH_MAX); |
917 | 0 | if (pattern[strlen(pattern) - 1] != '/') { |
918 | 0 | strlcat(pattern, "/", PATH_MAX); |
919 | 0 | } |
920 | 0 | if (pl->mode == LOGMODE_MULTI) { |
921 | 0 | for (int i = 0; i < pl->filename_part_cnt; i++) { |
922 | 0 | char *part = pl->filename_parts[i]; |
923 | 0 | if (part == NULL || strlen(part) == 0) { |
924 | 0 | continue; |
925 | 0 | } |
926 | 0 | if (part[0] != '%' || strlen(part) < 2) { |
927 | 0 | strlcat(pattern, part, PATH_MAX); |
928 | 0 | continue; |
929 | 0 | } |
930 | 0 | switch (part[1]) { |
931 | 0 | case 'i': |
932 | 0 | SCLogError("Thread ID not allowed in ring buffer mode."); |
933 | 0 | return TM_ECODE_FAILED; |
934 | 0 | case 'n': { |
935 | 0 | char tmp[PATH_MAX]; |
936 | 0 | snprintf(tmp, PATH_MAX, "%"PRIu32, pl->thread_number); |
937 | 0 | strlcat(pattern, tmp, PATH_MAX); |
938 | 0 | break; |
939 | 0 | } |
940 | 0 | case 't': |
941 | 0 | strlcat(pattern, "*", PATH_MAX); |
942 | 0 | break; |
943 | 0 | default: |
944 | 0 | SCLogError("Unsupported format character: %%%s", part); |
945 | 0 | return TM_ECODE_FAILED; |
946 | 0 | } |
947 | 0 | } |
948 | 0 | } else { |
949 | 0 | strlcat(pattern, pl->prefix, PATH_MAX); |
950 | 0 | strlcat(pattern, ".*", PATH_MAX); |
951 | 0 | } |
952 | 0 | strlcat(pattern, pl->suffix, PATH_MAX); |
953 | |
|
954 | 0 | char *basename = strrchr(pattern, '/'); |
955 | 0 | *basename++ = '\0'; |
956 | | |
957 | | /* Pattern is now just the directory name. */ |
958 | 0 | DIR *dir = opendir(pattern); |
959 | 0 | if (dir == NULL) { |
960 | 0 | SCLogWarning("Failed to open directory %s: %s", pattern, strerror(errno)); |
961 | 0 | return TM_ECODE_FAILED; |
962 | 0 | } |
963 | | |
964 | 0 | for (;;) { |
965 | 0 | struct dirent *entry = readdir(dir); |
966 | 0 | if (entry == NULL) { |
967 | 0 | break; |
968 | 0 | } |
969 | 0 | if (fnmatch(basename, entry->d_name, 0) != 0) { |
970 | 0 | continue; |
971 | 0 | } |
972 | | |
973 | 0 | uint64_t secs = 0; |
974 | 0 | uint32_t usecs = 0; |
975 | |
|
976 | 0 | if (!PcapLogGetTimeOfFile(entry->d_name, &secs, &usecs)) { |
977 | | /* Failed to get time stamp out of file name. Not necessarily a |
978 | | * failure as the file might just not be a pcap log file. */ |
979 | 0 | continue; |
980 | 0 | } |
981 | | |
982 | 0 | PcapFileName *pf = SCCalloc(sizeof(*pf), 1); |
983 | 0 | if (unlikely(pf == NULL)) { |
984 | 0 | goto fail; |
985 | 0 | } |
986 | 0 | char path[PATH_MAX]; |
987 | 0 | if (PathMerge(path, sizeof(path), pattern, entry->d_name) < 0) |
988 | 0 | goto fail; |
989 | | |
990 | 0 | if ((pf->filename = SCStrdup(path)) == NULL) { |
991 | 0 | goto fail; |
992 | 0 | } |
993 | 0 | if ((pf->dirname = SCStrdup(pattern)) == NULL) { |
994 | 0 | goto fail; |
995 | 0 | } |
996 | 0 | pf->secs = secs; |
997 | 0 | pf->usecs = usecs; |
998 | |
|
999 | 0 | if (TAILQ_EMPTY(&pl->pcap_file_list)) { |
1000 | 0 | TAILQ_INSERT_TAIL(&pl->pcap_file_list, pf, next); |
1001 | 0 | } else { |
1002 | | /* Ordered insert. */ |
1003 | 0 | PcapFileName *it = NULL; |
1004 | 0 | TAILQ_FOREACH(it, &pl->pcap_file_list, next) { |
1005 | 0 | if (pf->secs < it->secs) { |
1006 | 0 | break; |
1007 | 0 | } else if (pf->secs == it->secs && pf->usecs < it->usecs) { |
1008 | 0 | break; |
1009 | 0 | } |
1010 | 0 | } |
1011 | 0 | if (it == NULL) { |
1012 | 0 | TAILQ_INSERT_TAIL(&pl->pcap_file_list, pf, next); |
1013 | 0 | } else { |
1014 | 0 | TAILQ_INSERT_BEFORE(it, pf, next); |
1015 | 0 | } |
1016 | 0 | } |
1017 | 0 | pl->file_cnt++; |
1018 | 0 | continue; |
1019 | | |
1020 | 0 | fail: |
1021 | 0 | if (pf != NULL) { |
1022 | 0 | if (pf->filename != NULL) { |
1023 | 0 | SCFree(pf->filename); |
1024 | 0 | } |
1025 | 0 | if (pf->dirname != NULL) { |
1026 | 0 | SCFree(pf->dirname); |
1027 | 0 | } |
1028 | 0 | SCFree(pf); |
1029 | 0 | } |
1030 | 0 | break; |
1031 | 0 | } |
1032 | | |
1033 | 0 | if (pl->file_cnt > pl->max_files) { |
1034 | 0 | PcapFileName *pf = TAILQ_FIRST(&pl->pcap_file_list); |
1035 | 0 | while (pf != NULL && pl->file_cnt > pl->max_files) { |
1036 | 0 | TAILQ_REMOVE(&pl->pcap_file_list, pf, next); |
1037 | 0 | DEBUG_VALIDATE_BUG_ON(TAILQ_FIRST(&pl->pcap_file_list) == pf); |
1038 | | |
1039 | 0 | SCLogDebug("Removing PCAP file %s", pf->filename); |
1040 | 0 | if (remove(pf->filename) != 0) { |
1041 | 0 | SCLogWarning("Failed to remove PCAP file %s: %s", pf->filename, strerror(errno)); |
1042 | 0 | } |
1043 | 0 | PcapFileNameFree(pf); |
1044 | 0 | pl->file_cnt--; |
1045 | |
|
1046 | 0 | pf = TAILQ_FIRST(&pl->pcap_file_list); |
1047 | 0 | } |
1048 | 0 | } |
1049 | | |
1050 | 0 | closedir(dir); |
1051 | | |
1052 | | /* For some reason file count is initialized at one, instead of 0. */ |
1053 | 0 | SCLogNotice("Ring buffer initialized with %d files.", pl->file_cnt - 1); |
1054 | |
|
1055 | 0 | return TM_ECODE_OK; |
1056 | 0 | } |
1057 | | #endif /* INIT_RING_BUFFER */ |
1058 | | |
1059 | | static TmEcode PcapLogDataInit(ThreadVars *t, const void *initdata, void **data) |
1060 | 0 | { |
1061 | 0 | if (initdata == NULL) { |
1062 | 0 | SCLogDebug("Error getting context for LogPcap. \"initdata\" argument NULL"); |
1063 | 0 | return TM_ECODE_FAILED; |
1064 | 0 | } |
1065 | | |
1066 | 0 | PcapLogData *pl = ((OutputCtx *)initdata)->data; |
1067 | |
|
1068 | 0 | PcapLogThreadData *td = SCCalloc(1, sizeof(*td)); |
1069 | 0 | if (unlikely(td == NULL)) |
1070 | 0 | return TM_ECODE_FAILED; |
1071 | | |
1072 | 0 | td->counter_written = StatsRegisterCounter("pcap_log.written", &t->stats); |
1073 | 0 | td->counter_filtered_bpf = StatsRegisterCounter("pcap_log.filtered_bpf", &t->stats); |
1074 | |
|
1075 | 0 | if (pl->mode == LOGMODE_MULTI) |
1076 | 0 | td->pcap_log = PcapLogDataCopy(pl); |
1077 | 0 | else |
1078 | 0 | td->pcap_log = pl; |
1079 | 0 | BUG_ON(td->pcap_log == NULL); |
1080 | | |
1081 | 0 | if (DatalinkHasMultipleValues()) { |
1082 | 0 | if (pl->mode != LOGMODE_MULTI) { |
1083 | 0 | FatalError("Pcap logging with multiple link type is not supported."); |
1084 | 0 | } else { |
1085 | | /* In multi mode, only pcap conditional is not supported as a flow timeout |
1086 | | * will trigger packet logging with potentially invalid datalink. In regular |
1087 | | * pcap logging, the logging should be done in the same thread if we |
1088 | | * have a proper load balancing. So no mix of datalink should occur. But we need a |
1089 | | * proper load balancing so this needs at least a warning. |
1090 | | */ |
1091 | 0 | switch (pl->conditional) { |
1092 | 0 | case LOGMODE_COND_ALERTS: |
1093 | 0 | case LOGMODE_COND_TAG: |
1094 | 0 | FatalError("Can't have multiple link types in pcap conditional mode."); |
1095 | 0 | break; |
1096 | 0 | default: |
1097 | 0 | SCLogWarning("Using multiple link types can result in invalid pcap output"); |
1098 | 0 | } |
1099 | 0 | } |
1100 | 0 | } |
1101 | | |
1102 | 0 | PcapLogLock(td->pcap_log); |
1103 | | |
1104 | | /** Use the Output Context (file pointer and mutex) */ |
1105 | 0 | td->pcap_log->pkt_cnt = 0; |
1106 | 0 | td->pcap_log->pcap_dead_handle = NULL; |
1107 | 0 | td->pcap_log->pcap_dumper = NULL; |
1108 | 0 | if (td->pcap_log->file_cnt < 1) { |
1109 | 0 | td->pcap_log->file_cnt = 1; |
1110 | 0 | } |
1111 | |
|
1112 | 0 | SCTime_t ts = TimeGet(); |
1113 | 0 | struct tm local_tm; |
1114 | 0 | struct tm *tms = SCLocalTime(SCTIME_SECS(ts), &local_tm); |
1115 | 0 | td->pcap_log->prev_day = tms->tm_mday; |
1116 | |
|
1117 | 0 | PcapLogUnlock(td->pcap_log); |
1118 | | |
1119 | | /* count threads in the global structure */ |
1120 | 0 | SCMutexLock(&pl->plog_lock); |
1121 | 0 | pl->threads++; |
1122 | 0 | SCMutexUnlock(&pl->plog_lock); |
1123 | |
|
1124 | 0 | *data = (void *)td; |
1125 | |
|
1126 | 0 | if (IsTcpSessionDumpingEnabled()) { |
1127 | 0 | td->buf = MemBufferCreateNew(PCAP_OUTPUT_BUFFER_SIZE); |
1128 | 0 | } else { |
1129 | 0 | td->buf = NULL; |
1130 | 0 | } |
1131 | |
|
1132 | 0 | if (pl->max_files && (pl->mode == LOGMODE_MULTI || pl->threads == 1)) { |
1133 | 0 | #ifdef INIT_RING_BUFFER |
1134 | 0 | if (PcapLogInitRingBuffer(td->pcap_log) == TM_ECODE_FAILED) { |
1135 | 0 | return TM_ECODE_FAILED; |
1136 | 0 | } |
1137 | | #else |
1138 | | SCLogInfo("Unable to initialize ring buffer on this platform."); |
1139 | | #endif /* INIT_RING_BUFFER */ |
1140 | 0 | } |
1141 | | |
1142 | | /* Don't early initialize output files if in a PCAP file (offline) |
1143 | | * mode. */ |
1144 | 0 | if (!IsRunModeOffline(SCRunmodeGet())) { |
1145 | 0 | if (pl->mode == LOGMODE_MULTI) { |
1146 | 0 | PcapLogOpenFileCtx(td->pcap_log); |
1147 | 0 | } else { |
1148 | 0 | if (pl->filename == NULL) { |
1149 | 0 | PcapLogOpenFileCtx(pl); |
1150 | 0 | } |
1151 | 0 | } |
1152 | 0 | } |
1153 | |
|
1154 | 0 | return TM_ECODE_OK; |
1155 | 0 | } |
1156 | | |
1157 | | static void StatsMerge(PcapLogData *dst, PcapLogData *src) |
1158 | 0 | { |
1159 | 0 | dst->profile_open.total += src->profile_open.total; |
1160 | 0 | dst->profile_open.cnt += src->profile_open.cnt; |
1161 | |
|
1162 | 0 | dst->profile_close.total += src->profile_close.total; |
1163 | 0 | dst->profile_close.cnt += src->profile_close.cnt; |
1164 | |
|
1165 | 0 | dst->profile_write.total += src->profile_write.total; |
1166 | 0 | dst->profile_write.cnt += src->profile_write.cnt; |
1167 | |
|
1168 | 0 | dst->profile_rotate.total += src->profile_rotate.total; |
1169 | 0 | dst->profile_rotate.cnt += src->profile_rotate.cnt; |
1170 | |
|
1171 | 0 | dst->profile_handles.total += src->profile_handles.total; |
1172 | 0 | dst->profile_handles.cnt += src->profile_handles.cnt; |
1173 | |
|
1174 | 0 | dst->profile_lock.total += src->profile_lock.total; |
1175 | 0 | dst->profile_lock.cnt += src->profile_lock.cnt; |
1176 | |
|
1177 | 0 | dst->profile_unlock.total += src->profile_unlock.total; |
1178 | 0 | dst->profile_unlock.cnt += src->profile_unlock.cnt; |
1179 | |
|
1180 | 0 | dst->profile_data_size += src->profile_data_size; |
1181 | 0 | } |
1182 | | |
1183 | | static void PcapLogDataFree(PcapLogData *pl) |
1184 | 0 | { |
1185 | |
|
1186 | 0 | PcapFileName *pf; |
1187 | 0 | while ((pf = TAILQ_FIRST(&pl->pcap_file_list)) != NULL) { |
1188 | 0 | TAILQ_REMOVE(&pl->pcap_file_list, pf, next); |
1189 | 0 | DEBUG_VALIDATE_BUG_ON(TAILQ_FIRST(&pl->pcap_file_list) == pf); |
1190 | 0 | PcapFileNameFree(pf); |
1191 | 0 | } |
1192 | 0 | if (pl == g_pcap_data) { |
1193 | 0 | for (int i = 0; i < MAX_TOKS; i++) { |
1194 | 0 | if (pl->filename_parts[i] != NULL) { |
1195 | 0 | SCFree(pl->filename_parts[i]); |
1196 | 0 | } |
1197 | 0 | } |
1198 | 0 | } |
1199 | 0 | SCFree(pl->h); |
1200 | 0 | SCFree(pl->filename); |
1201 | 0 | SCFree(pl->prefix); |
1202 | |
|
1203 | 0 | if (pl->pcap_dead_handle) { |
1204 | 0 | pcap_close(pl->pcap_dead_handle); |
1205 | 0 | } |
1206 | |
|
1207 | 0 | if (pl->bpfp) { |
1208 | 0 | pcap_freecode(pl->bpfp); |
1209 | 0 | SCFree(pl->bpfp); |
1210 | 0 | } |
1211 | |
|
1212 | 0 | #ifdef HAVE_LIBLZ4 |
1213 | 0 | if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) { |
1214 | 0 | SCFree(pl->compression.buffer); |
1215 | 0 | if (pl->compression.pcap_buf_wrapper) |
1216 | 0 | fclose(pl->compression.pcap_buf_wrapper); |
1217 | 0 | SCFree(pl->compression.pcap_buf); |
1218 | 0 | LZ4F_errorCode_t errcode = |
1219 | 0 | LZ4F_freeCompressionContext(pl->compression.lz4f_context); |
1220 | 0 | if (LZ4F_isError(errcode)) { |
1221 | 0 | SCLogWarning("Error freeing lz4 context."); |
1222 | 0 | } |
1223 | 0 | } |
1224 | 0 | #endif /* HAVE_LIBLZ4 */ |
1225 | 0 | SCFree(pl); |
1226 | 0 | } |
1227 | | |
1228 | | /** |
1229 | | * \brief Thread deinit function. |
1230 | | * |
1231 | | * \param t Thread Variable containing input/output queue, cpu affinity etc. |
1232 | | * \param data PcapLog thread data. |
1233 | | * \retval TM_ECODE_OK on success |
1234 | | * \retval TM_ECODE_FAILED on failure |
1235 | | */ |
1236 | | static TmEcode PcapLogDataDeinit(ThreadVars *t, void *thread_data) |
1237 | 0 | { |
1238 | 0 | PcapLogThreadData *td = (PcapLogThreadData *)thread_data; |
1239 | 0 | PcapLogData *pl = td->pcap_log; |
1240 | |
|
1241 | 0 | if (pl->pcap_dumper != NULL) { |
1242 | 0 | if (PcapLogCloseFile(t, pl) != TM_ECODE_OK) { |
1243 | 0 | SCLogDebug("PcapLogCloseFile failed"); |
1244 | 0 | } |
1245 | 0 | } |
1246 | |
|
1247 | 0 | if (pl->mode == LOGMODE_MULTI) { |
1248 | 0 | SCMutexLock(&g_pcap_data->plog_lock); |
1249 | 0 | StatsMerge(g_pcap_data, pl); |
1250 | 0 | g_pcap_data->reported++; |
1251 | 0 | if (g_pcap_data->threads == g_pcap_data->reported) |
1252 | 0 | PcapLogProfilingDump(g_pcap_data); |
1253 | 0 | SCMutexUnlock(&g_pcap_data->plog_lock); |
1254 | 0 | } else { |
1255 | 0 | if (pl->reported == 0) { |
1256 | 0 | PcapLogProfilingDump(pl); |
1257 | 0 | pl->reported = 1; |
1258 | 0 | } |
1259 | 0 | } |
1260 | |
|
1261 | 0 | if (pl != g_pcap_data) { |
1262 | 0 | PcapLogDataFree(pl); |
1263 | 0 | } |
1264 | |
|
1265 | 0 | if (td->buf) |
1266 | 0 | MemBufferFree(td->buf); |
1267 | |
|
1268 | 0 | SCFree(td); |
1269 | 0 | return TM_ECODE_OK; |
1270 | 0 | } |
1271 | | |
1272 | | |
1273 | | static int ParseFilename(PcapLogData *pl, const char *filename) |
1274 | 0 | { |
1275 | 0 | char *toks[MAX_TOKS] = { NULL }; |
1276 | 0 | int tok = 0; |
1277 | 0 | char str[MAX_FILENAMELEN] = ""; |
1278 | 0 | int s = 0; |
1279 | 0 | char *p = NULL; |
1280 | 0 | size_t filename_len = 0; |
1281 | |
|
1282 | 0 | if (filename) { |
1283 | 0 | filename_len = strlen(filename); |
1284 | 0 | if (filename_len > (MAX_FILENAMELEN-1)) { |
1285 | 0 | SCLogError("invalid filename option. Max filename-length: %d", MAX_FILENAMELEN - 1); |
1286 | 0 | goto error; |
1287 | 0 | } |
1288 | | |
1289 | 0 | for (int i = 0; i < (int)strlen(filename); i++) { |
1290 | 0 | if (tok >= MAX_TOKS) { |
1291 | 0 | SCLogError("invalid filename option. Max 2 %%-sign options"); |
1292 | 0 | goto error; |
1293 | 0 | } |
1294 | | |
1295 | 0 | str[s++] = filename[i]; |
1296 | |
|
1297 | 0 | if (filename[i] == '%') { |
1298 | 0 | str[s-1] = '\0'; |
1299 | 0 | SCLogDebug("filename with %%-sign: %s", str); |
1300 | |
|
1301 | 0 | p = SCStrdup(str); |
1302 | 0 | if (p == NULL) |
1303 | 0 | goto error; |
1304 | 0 | toks[tok++] = p; |
1305 | |
|
1306 | 0 | s = 0; |
1307 | |
|
1308 | 0 | if (i+1 < (int)strlen(filename)) { |
1309 | 0 | if (tok >= MAX_TOKS) { |
1310 | 0 | SCLogError("invalid filename option. Max 2 %%-sign options"); |
1311 | 0 | goto error; |
1312 | 0 | } |
1313 | | |
1314 | 0 | if (filename[i+1] != 'n' && filename[i+1] != 't' && filename[i+1] != 'i') { |
1315 | 0 | SCLogError( |
1316 | 0 | "invalid filename option. Valid %%-sign options: %%n, %%i and %%t"); |
1317 | 0 | goto error; |
1318 | 0 | } |
1319 | 0 | str[0] = '%'; |
1320 | 0 | str[1] = filename[i+1]; |
1321 | 0 | str[2] = '\0'; |
1322 | 0 | p = SCStrdup(str); |
1323 | 0 | if (p == NULL) |
1324 | 0 | goto error; |
1325 | 0 | toks[tok++] = p; |
1326 | 0 | i++; |
1327 | 0 | } |
1328 | 0 | } |
1329 | 0 | } |
1330 | | |
1331 | 0 | if ((tok == 0) && (pl->mode == LOGMODE_MULTI)) { |
1332 | 0 | SCLogError("Invalid filename for multimode. Need at least one %%-sign option"); |
1333 | 0 | goto error; |
1334 | 0 | } |
1335 | | |
1336 | 0 | if (s) { |
1337 | 0 | if (tok >= MAX_TOKS) { |
1338 | 0 | SCLogError("invalid filename option. Max 3 %%-sign options"); |
1339 | 0 | goto error; |
1340 | |
|
1341 | 0 | } |
1342 | 0 | str[s++] = '\0'; |
1343 | 0 | p = SCStrdup(str); |
1344 | 0 | if (p == NULL) |
1345 | 0 | goto error; |
1346 | 0 | toks[tok++] = p; |
1347 | 0 | } |
1348 | | |
1349 | | /* finally, store tokens in the pl */ |
1350 | 0 | for (int i = 0; i < tok; i++) { |
1351 | 0 | if (toks[i] == NULL) |
1352 | 0 | goto error; |
1353 | | |
1354 | 0 | SCLogDebug("toks[%d] %s", i, toks[i]); |
1355 | 0 | pl->filename_parts[i] = toks[i]; |
1356 | 0 | } |
1357 | 0 | pl->filename_part_cnt = tok; |
1358 | 0 | } |
1359 | 0 | return 0; |
1360 | 0 | error: |
1361 | 0 | for (int x = 0; x < MAX_TOKS; x++) { |
1362 | 0 | if (toks[x] != NULL) |
1363 | 0 | SCFree(toks[x]); |
1364 | 0 | } |
1365 | 0 | return -1; |
1366 | 0 | } |
1367 | | |
1368 | | /** \brief Fill in pcap logging struct from the provided ConfNode. |
1369 | | * \param conf The configuration node for this output. |
1370 | | * \retval output_ctx |
1371 | | * */ |
1372 | | static OutputInitResult PcapLogInitCtx(SCConfNode *conf) |
1373 | 0 | { |
1374 | 0 | OutputInitResult result = { NULL, false }; |
1375 | 0 | int en; |
1376 | 0 | PCRE2_SIZE eo = 0; |
1377 | |
|
1378 | 0 | if (g_pcap_data) { |
1379 | 0 | FatalError("A pcap-log instance is already active, only one can be enabled."); |
1380 | 0 | } |
1381 | | |
1382 | 0 | PcapLogData *pl = SCCalloc(1, sizeof(PcapLogData)); |
1383 | 0 | if (unlikely(pl == NULL)) { |
1384 | 0 | FatalError("Failed to allocate Memory for PcapLogData"); |
1385 | 0 | } |
1386 | | |
1387 | 0 | pl->h = SCMalloc(sizeof(*pl->h)); |
1388 | 0 | if (pl->h == NULL) { |
1389 | 0 | FatalError("Failed to allocate Memory for pcap header struct"); |
1390 | 0 | } |
1391 | | |
1392 | | /* Set the defaults */ |
1393 | 0 | pl->mode = LOGMODE_NORMAL; |
1394 | 0 | pl->max_files = DEFAULT_FILE_LIMIT; |
1395 | 0 | pl->use_ringbuffer = RING_BUFFER_MODE_DISABLED; |
1396 | 0 | pl->timestamp_format = TS_FORMAT_SEC; |
1397 | 0 | pl->use_stream_depth = USE_STREAM_DEPTH_DISABLED; |
1398 | 0 | pl->honor_pass_rules = HONOR_PASS_RULES_DISABLED; |
1399 | 0 | pl->conditional = LOGMODE_COND_ALL; |
1400 | |
|
1401 | 0 | TAILQ_INIT(&pl->pcap_file_list); |
1402 | |
|
1403 | 0 | SCMutexInit(&pl->plog_lock, NULL); |
1404 | | |
1405 | | /* Initialize PCREs. */ |
1406 | 0 | pcre_timestamp_code = |
1407 | 0 | pcre2_compile((PCRE2_SPTR8)timestamp_pattern, PCRE2_ZERO_TERMINATED, 0, &en, &eo, NULL); |
1408 | 0 | if (pcre_timestamp_code == NULL) { |
1409 | 0 | PCRE2_UCHAR errbuffer[256]; |
1410 | 0 | pcre2_get_error_message(en, errbuffer, sizeof(errbuffer)); |
1411 | 0 | FatalError( |
1412 | 0 | "Failed to compile \"%s\" at offset %d: %s", timestamp_pattern, (int)eo, errbuffer); |
1413 | 0 | } |
1414 | 0 | pcre_timestamp_match = pcre2_match_data_create_from_pattern(pcre_timestamp_code, NULL); |
1415 | | |
1416 | | /* conf params */ |
1417 | |
|
1418 | 0 | const char *filename = NULL; |
1419 | |
|
1420 | 0 | if (conf != NULL) { /* To facilitate unit tests. */ |
1421 | 0 | filename = SCConfNodeLookupChildValue(conf, "filename"); |
1422 | 0 | } |
1423 | |
|
1424 | 0 | if (filename == NULL) |
1425 | 0 | filename = DEFAULT_LOG_FILENAME; |
1426 | |
|
1427 | 0 | if ((pl->prefix = SCStrdup(filename)) == NULL) { |
1428 | 0 | exit(EXIT_FAILURE); |
1429 | 0 | } |
1430 | | |
1431 | 0 | pl->suffix = ""; |
1432 | |
|
1433 | 0 | pl->size_limit = DEFAULT_LIMIT; |
1434 | 0 | if (conf != NULL) { |
1435 | 0 | const char *s_limit = NULL; |
1436 | 0 | s_limit = SCConfNodeLookupChildValue(conf, "limit"); |
1437 | 0 | if (s_limit != NULL) { |
1438 | 0 | if (ParseSizeStringU64(s_limit, &pl->size_limit) < 0) { |
1439 | 0 | SCLogError("Failed to initialize pcap output, invalid limit: %s", s_limit); |
1440 | 0 | exit(EXIT_FAILURE); |
1441 | 0 | } |
1442 | 0 | if (pl->size_limit < 4096) { |
1443 | 0 | SCLogInfo("pcap-log \"limit\" value of %"PRIu64" assumed to be pre-1.2 " |
1444 | 0 | "style: setting limit to %"PRIu64"mb", pl->size_limit, pl->size_limit); |
1445 | 0 | uint64_t size = pl->size_limit * 1024 * 1024; |
1446 | 0 | pl->size_limit = size; |
1447 | 0 | } else if (pl->size_limit < MIN_LIMIT) { |
1448 | 0 | FatalError("Fail to initialize pcap-log output, limit less than " |
1449 | 0 | "allowed minimum of %d bytes.", |
1450 | 0 | MIN_LIMIT); |
1451 | 0 | } |
1452 | 0 | } |
1453 | 0 | } |
1454 | | |
1455 | 0 | if (conf != NULL) { |
1456 | 0 | const char *s_mode = NULL; |
1457 | 0 | s_mode = SCConfNodeLookupChildValue(conf, "mode"); |
1458 | 0 | if (s_mode != NULL) { |
1459 | 0 | if (strcasecmp(s_mode, "multi") == 0) { |
1460 | 0 | pl->mode = LOGMODE_MULTI; |
1461 | 0 | } else if (strcasecmp(s_mode, "normal") != 0) { |
1462 | 0 | FatalError("log-pcap: invalid mode \"%s\". Valid options: \"normal\"" |
1463 | 0 | "or \"multi\" mode ", |
1464 | 0 | s_mode); |
1465 | 0 | } |
1466 | 0 | } |
1467 | | |
1468 | 0 | const char *s_dir = NULL; |
1469 | 0 | s_dir = SCConfNodeLookupChildValue(conf, "dir"); |
1470 | 0 | if (s_dir == NULL) { |
1471 | 0 | const char *log_dir = NULL; |
1472 | 0 | log_dir = SCConfigGetLogDirectory(); |
1473 | |
|
1474 | 0 | strlcpy(pl->dir, log_dir, sizeof(pl->dir)); |
1475 | 0 | SCLogInfo("Using log dir %s", pl->dir); |
1476 | 0 | } else { |
1477 | 0 | if (PathIsAbsolute(s_dir)) { |
1478 | 0 | strlcpy(pl->dir, |
1479 | 0 | s_dir, sizeof(pl->dir)); |
1480 | 0 | } else { |
1481 | 0 | const char *log_dir = NULL; |
1482 | 0 | log_dir = SCConfigGetLogDirectory(); |
1483 | |
|
1484 | 0 | snprintf(pl->dir, sizeof(pl->dir), "%s/%s", |
1485 | 0 | log_dir, s_dir); |
1486 | 0 | } |
1487 | |
|
1488 | 0 | struct stat stat_buf; |
1489 | 0 | if (stat(pl->dir, &stat_buf) != 0) { |
1490 | 0 | FatalError("The dir directory \"%s\" " |
1491 | 0 | "supplied doesn't exist. Shutting down the engine", |
1492 | 0 | pl->dir); |
1493 | 0 | } |
1494 | 0 | SCLogInfo("Using log dir %s", pl->dir); |
1495 | 0 | } |
1496 | | |
1497 | 0 | const char *compression_str = SCConfNodeLookupChildValue(conf, "compression"); |
1498 | |
|
1499 | 0 | PcapLogCompressionData *comp = &pl->compression; |
1500 | 0 | if (compression_str == NULL || strcmp(compression_str, "none") == 0) { |
1501 | 0 | comp->format = PCAP_LOG_COMPRESSION_FORMAT_NONE; |
1502 | 0 | comp->buffer = NULL; |
1503 | 0 | comp->buffer_size = 0; |
1504 | 0 | comp->file = NULL; |
1505 | 0 | comp->pcap_buf = NULL; |
1506 | 0 | comp->pcap_buf_size = 0; |
1507 | 0 | #ifdef HAVE_LIBLZ4 |
1508 | 0 | comp->pcap_buf_wrapper = NULL; |
1509 | 0 | #endif |
1510 | 0 | } else if (strcmp(compression_str, "lz4") == 0) { |
1511 | 0 | #ifdef HAVE_LIBLZ4 |
1512 | 0 | pl->compression.format = PCAP_LOG_COMPRESSION_FORMAT_LZ4; |
1513 | | |
1514 | | /* Use SCFmemopen so we can make pcap_dump write to a buffer. */ |
1515 | |
|
1516 | 0 | comp->pcap_buf_size = sizeof(struct pcap_file_header) + |
1517 | 0 | sizeof(struct pcap_pkthdr) + PCAP_SNAPLEN; |
1518 | 0 | comp->pcap_buf = SCMalloc(comp->pcap_buf_size); |
1519 | 0 | if (comp->pcap_buf == NULL) { |
1520 | 0 | SCLogError("SCMalloc failed: %s", strerror(errno)); |
1521 | 0 | exit(EXIT_FAILURE); |
1522 | 0 | } |
1523 | 0 | comp->pcap_buf_wrapper = SCFmemopen(comp->pcap_buf, |
1524 | 0 | comp->pcap_buf_size, "w"); |
1525 | 0 | if (comp->pcap_buf_wrapper == NULL) { |
1526 | 0 | SCLogError("SCFmemopen failed: %s", strerror(errno)); |
1527 | 0 | exit(EXIT_FAILURE); |
1528 | 0 | } |
1529 | | |
1530 | | /* Set lz4 preferences. */ |
1531 | | |
1532 | 0 | memset(&comp->lz4f_prefs, '\0', sizeof(comp->lz4f_prefs)); |
1533 | 0 | comp->lz4f_prefs.frameInfo.blockSizeID = LZ4F_max4MB; |
1534 | 0 | comp->lz4f_prefs.frameInfo.blockMode = LZ4F_blockLinked; |
1535 | 0 | if (SCConfNodeChildValueIsTrue(conf, "lz4-checksum")) { |
1536 | 0 | comp->lz4f_prefs.frameInfo.contentChecksumFlag = 1; |
1537 | 0 | } else { |
1538 | 0 | comp->lz4f_prefs.frameInfo.contentChecksumFlag = 0; |
1539 | 0 | } |
1540 | 0 | intmax_t lvl = 0; |
1541 | 0 | if (SCConfGetChildValueInt(conf, "lz4-level", &lvl)) { |
1542 | 0 | if (lvl > 16) { |
1543 | 0 | lvl = 16; |
1544 | 0 | } else if (lvl < 0) { |
1545 | 0 | lvl = 0; |
1546 | 0 | } |
1547 | 0 | } else { |
1548 | 0 | lvl = 0; |
1549 | 0 | } |
1550 | 0 | comp->lz4f_prefs.compressionLevel = (int)lvl; |
1551 | | |
1552 | | /* Allocate resources for lz4. */ |
1553 | |
|
1554 | 0 | LZ4F_errorCode_t errcode = |
1555 | 0 | LZ4F_createCompressionContext(&pl->compression.lz4f_context, 1); |
1556 | |
|
1557 | 0 | if (LZ4F_isError(errcode)) { |
1558 | 0 | SCLogError("LZ4F_createCompressionContext failed: %s", LZ4F_getErrorName(errcode)); |
1559 | 0 | exit(EXIT_FAILURE); |
1560 | 0 | } |
1561 | | |
1562 | | /* Calculate the size of the lz4 output buffer. */ |
1563 | | |
1564 | 0 | comp->buffer_size = LZ4F_compressBound(comp->pcap_buf_size, |
1565 | 0 | &comp->lz4f_prefs); |
1566 | |
|
1567 | 0 | comp->buffer = SCMalloc(comp->buffer_size); |
1568 | 0 | if (unlikely(comp->buffer == NULL)) { |
1569 | 0 | FatalError("Failed to allocate memory for " |
1570 | 0 | "lz4 output buffer."); |
1571 | 0 | } |
1572 | | |
1573 | 0 | comp->bytes_in_block = 0; |
1574 | | |
1575 | | /* Add the lz4 file extension to the log files. */ |
1576 | |
|
1577 | 0 | pl->suffix = ".lz4"; |
1578 | | #else |
1579 | | SCLogError("lz4 compression was selected " |
1580 | | "in pcap-log, but suricata was not compiled with lz4 " |
1581 | | "support."); |
1582 | | PcapLogDataFree(pl); |
1583 | | return result; |
1584 | | #endif /* HAVE_LIBLZ4 */ |
1585 | 0 | } |
1586 | 0 | else { |
1587 | 0 | SCLogError("Unsupported pcap-log " |
1588 | 0 | "compression format: %s", |
1589 | 0 | compression_str); |
1590 | 0 | PcapLogDataFree(pl); |
1591 | 0 | return result; |
1592 | 0 | } |
1593 | | |
1594 | 0 | SCLogInfo("Selected pcap-log compression method: %s", |
1595 | 0 | compression_str ? compression_str : "none"); |
1596 | |
|
1597 | 0 | const char *s_conditional = SCConfNodeLookupChildValue(conf, "conditional"); |
1598 | 0 | if (s_conditional != NULL) { |
1599 | 0 | if (strcasecmp(s_conditional, "alerts") == 0) { |
1600 | 0 | pl->conditional = LOGMODE_COND_ALERTS; |
1601 | 0 | EnableTcpSessionDumping(); |
1602 | 0 | } else if (strcasecmp(s_conditional, "tag") == 0) { |
1603 | 0 | pl->conditional = LOGMODE_COND_TAG; |
1604 | 0 | EnableTcpSessionDumping(); |
1605 | 0 | } else if (strcasecmp(s_conditional, "all") != 0) { |
1606 | 0 | FatalError("log-pcap: invalid conditional \"%s\". Valid options: \"all\", " |
1607 | 0 | "\"alerts\", or \"tag\" mode ", |
1608 | 0 | s_conditional); |
1609 | 0 | } |
1610 | 0 | } |
1611 | | |
1612 | 0 | SCLogInfo( |
1613 | 0 | "Selected pcap-log conditional logging: %s", s_conditional ? s_conditional : "all"); |
1614 | 0 | } |
1615 | | |
1616 | 0 | if (ParseFilename(pl, filename) != 0) |
1617 | 0 | exit(EXIT_FAILURE); |
1618 | | |
1619 | 0 | SCLogInfo("using %s logging", (pl->mode == LOGMODE_MULTI ? "multi" : "normal")); |
1620 | |
|
1621 | 0 | uint32_t max_file_limit = DEFAULT_FILE_LIMIT; |
1622 | 0 | if (conf != NULL) { |
1623 | 0 | const char *max_number_of_files_s = NULL; |
1624 | 0 | max_number_of_files_s = SCConfNodeLookupChildValue(conf, "max-files"); |
1625 | 0 | if (max_number_of_files_s != NULL) { |
1626 | 0 | if (StringParseUint32(&max_file_limit, 10, 0, |
1627 | 0 | max_number_of_files_s) == -1) { |
1628 | 0 | SCLogError("Failed to initialize " |
1629 | 0 | "pcap-log output, invalid number of files limit: %s", |
1630 | 0 | max_number_of_files_s); |
1631 | 0 | exit(EXIT_FAILURE); |
1632 | 0 | } else if (max_file_limit < 1) { |
1633 | 0 | FatalError("Failed to initialize pcap-log output, limit less than " |
1634 | 0 | "allowed minimum."); |
1635 | 0 | } else { |
1636 | 0 | pl->max_files = max_file_limit; |
1637 | 0 | pl->use_ringbuffer = RING_BUFFER_MODE_ENABLED; |
1638 | 0 | } |
1639 | 0 | } |
1640 | 0 | } |
1641 | | |
1642 | 0 | const char *ts_format = NULL; |
1643 | 0 | if (conf != NULL) { /* To facilitate unit tests. */ |
1644 | 0 | ts_format = SCConfNodeLookupChildValue(conf, "ts-format"); |
1645 | 0 | } |
1646 | 0 | if (ts_format != NULL) { |
1647 | 0 | if (strcasecmp(ts_format, "usec") == 0) { |
1648 | 0 | pl->timestamp_format = TS_FORMAT_USEC; |
1649 | 0 | } else if (strcasecmp(ts_format, "sec") != 0) { |
1650 | 0 | SCLogError("log-pcap ts_format specified %s is invalid must be" |
1651 | 0 | " \"sec\" or \"usec\"", |
1652 | 0 | ts_format); |
1653 | 0 | exit(EXIT_FAILURE); |
1654 | 0 | } |
1655 | 0 | } |
1656 | | |
1657 | 0 | const char *use_stream_depth = NULL; |
1658 | 0 | if (conf != NULL) { /* To facilitate unit tests. */ |
1659 | 0 | use_stream_depth = SCConfNodeLookupChildValue(conf, "use-stream-depth"); |
1660 | 0 | } |
1661 | 0 | if (use_stream_depth != NULL) { |
1662 | 0 | if (SCConfValIsFalse(use_stream_depth)) { |
1663 | 0 | pl->use_stream_depth = USE_STREAM_DEPTH_DISABLED; |
1664 | 0 | } else if (SCConfValIsTrue(use_stream_depth)) { |
1665 | 0 | pl->use_stream_depth = USE_STREAM_DEPTH_ENABLED; |
1666 | 0 | } else { |
1667 | 0 | FatalError("log-pcap use_stream_depth specified is invalid must be"); |
1668 | 0 | } |
1669 | 0 | } |
1670 | | |
1671 | 0 | const char *honor_pass_rules = NULL; |
1672 | 0 | if (conf != NULL) { /* To facilitate unit tests. */ |
1673 | 0 | honor_pass_rules = SCConfNodeLookupChildValue(conf, "honor-pass-rules"); |
1674 | 0 | } |
1675 | 0 | if (honor_pass_rules != NULL) { |
1676 | 0 | if (SCConfValIsFalse(honor_pass_rules)) { |
1677 | 0 | pl->honor_pass_rules = HONOR_PASS_RULES_DISABLED; |
1678 | 0 | } else if (SCConfValIsTrue(honor_pass_rules)) { |
1679 | 0 | pl->honor_pass_rules = HONOR_PASS_RULES_ENABLED; |
1680 | 0 | } else { |
1681 | 0 | FatalError("log-pcap honor-pass-rules specified is invalid"); |
1682 | 0 | } |
1683 | 0 | } |
1684 | | |
1685 | 0 | pl->bpf_filter = conf == NULL ? NULL : (char *)SCConfNodeLookupChildValue(conf, "bpf-filter"); |
1686 | | |
1687 | | /* create the output ctx and send it back */ |
1688 | |
|
1689 | 0 | OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); |
1690 | 0 | if (unlikely(output_ctx == NULL)) { |
1691 | 0 | FatalError("Failed to allocate memory for OutputCtx."); |
1692 | 0 | } |
1693 | 0 | output_ctx->data = pl; |
1694 | 0 | output_ctx->DeInit = PcapLogFileDeInitCtx; |
1695 | 0 | g_pcap_data = pl; |
1696 | |
|
1697 | 0 | result.ctx = output_ctx; |
1698 | 0 | result.ok = true; |
1699 | 0 | return result; |
1700 | 0 | } |
1701 | | |
1702 | | static void PcapLogFileDeInitCtx(OutputCtx *output_ctx) |
1703 | 0 | { |
1704 | 0 | if (output_ctx == NULL) |
1705 | 0 | return; |
1706 | | |
1707 | 0 | PcapLogData *pl = output_ctx->data; |
1708 | |
|
1709 | 0 | PcapFileName *pf = NULL; |
1710 | 0 | TAILQ_FOREACH(pf, &pl->pcap_file_list, next) { |
1711 | 0 | SCLogDebug("PCAP files left at exit: %s\n", pf->filename); |
1712 | 0 | } |
1713 | 0 | PcapLogDataFree(pl); |
1714 | 0 | SCFree(output_ctx); |
1715 | |
|
1716 | 0 | pcre2_code_free(pcre_timestamp_code); |
1717 | 0 | pcre2_match_data_free(pcre_timestamp_match); |
1718 | 0 | } |
1719 | | |
1720 | | /** |
1721 | | * \brief Read the config set the file pointer, open the file |
1722 | | * |
1723 | | * \param PcapLogData. |
1724 | | * |
1725 | | * \retval -1 if failure |
1726 | | * \retval 0 if succesful |
1727 | | */ |
1728 | | static int PcapLogOpenFileCtx(PcapLogData *pl) |
1729 | 0 | { |
1730 | 0 | char *path = NULL; |
1731 | |
|
1732 | 0 | PCAPLOG_PROFILE_START; |
1733 | |
|
1734 | 0 | if (pl->filename != NULL) |
1735 | 0 | path = pl->filename; |
1736 | 0 | else { |
1737 | 0 | path = SCMalloc(PATH_MAX); |
1738 | 0 | if (unlikely(path == NULL)) { |
1739 | 0 | return -1; |
1740 | 0 | } |
1741 | 0 | pl->filename = path; |
1742 | 0 | } |
1743 | | |
1744 | | /** get the time so we can have a filename with seconds since epoch */ |
1745 | 0 | SCTime_t ts = TimeGet(); |
1746 | | |
1747 | | /* Place to store the name of our PCAP file */ |
1748 | 0 | PcapFileName *pf = SCCalloc(1, sizeof(PcapFileName)); |
1749 | 0 | if (unlikely(pf == NULL)) { |
1750 | 0 | return -1; |
1751 | 0 | } |
1752 | | |
1753 | 0 | char file[PATH_MAX] = ""; |
1754 | 0 | if (pl->mode == LOGMODE_NORMAL) { |
1755 | 0 | int ret; |
1756 | | /* create the filename to use */ |
1757 | 0 | if (pl->timestamp_format == TS_FORMAT_SEC) { |
1758 | 0 | ret = snprintf(file, sizeof(file), "%s.%" PRIu32 "%s", pl->prefix, |
1759 | 0 | (uint32_t)SCTIME_SECS(ts), pl->suffix); |
1760 | 0 | } else { |
1761 | 0 | ret = snprintf(file, sizeof(file), "%s.%" PRIu32 ".%" PRIu32 "%s", pl->prefix, |
1762 | 0 | (uint32_t)SCTIME_SECS(ts), (uint32_t)SCTIME_USECS(ts), pl->suffix); |
1763 | 0 | } |
1764 | 0 | if (ret < 0 || (size_t)ret >= PATH_MAX) { |
1765 | 0 | SCLogError("failed to construct path"); |
1766 | 0 | goto error; |
1767 | 0 | } |
1768 | 0 | } else if (pl->mode == LOGMODE_MULTI) { |
1769 | 0 | if (pl->filename_part_cnt > 0) { |
1770 | | /* assemble filename from stored tokens */ |
1771 | |
|
1772 | 0 | for (int i = 0; i < pl->filename_part_cnt; i++) { |
1773 | 0 | if (pl->filename_parts[i] == NULL ||strlen(pl->filename_parts[i]) == 0) |
1774 | 0 | continue; |
1775 | | |
1776 | | /* handle variables */ |
1777 | 0 | if (pl->filename_parts[i][0] == '%') { |
1778 | 0 | char str[64] = ""; |
1779 | 0 | if (strlen(pl->filename_parts[i]) < 2) |
1780 | 0 | continue; |
1781 | | |
1782 | 0 | switch(pl->filename_parts[i][1]) { |
1783 | 0 | case 'n': |
1784 | 0 | snprintf(str, sizeof(str), "%u", pl->thread_number); |
1785 | 0 | break; |
1786 | 0 | case 'i': |
1787 | 0 | { |
1788 | 0 | long thread_id = SCGetThreadIdLong(); |
1789 | 0 | snprintf(str, sizeof(str), "%"PRIu64, (uint64_t)thread_id); |
1790 | 0 | break; |
1791 | 0 | } |
1792 | 0 | case 't': |
1793 | | /* create the filename to use */ |
1794 | 0 | if (pl->timestamp_format == TS_FORMAT_SEC) { |
1795 | 0 | snprintf(str, sizeof(str), "%" PRIu32, (uint32_t)SCTIME_SECS(ts)); |
1796 | 0 | } else { |
1797 | 0 | snprintf(str, sizeof(str), "%" PRIu32 ".%" PRIu32, |
1798 | 0 | (uint32_t)SCTIME_SECS(ts), (uint32_t)SCTIME_USECS(ts)); |
1799 | 0 | } |
1800 | 0 | } |
1801 | 0 | strlcat(file, str, sizeof(file)); |
1802 | | |
1803 | | /* copy the rest over */ |
1804 | 0 | } else { |
1805 | 0 | strlcat(file, pl->filename_parts[i], sizeof(file)); |
1806 | 0 | } |
1807 | 0 | } |
1808 | 0 | strlcat(file, pl->suffix, sizeof(file)); |
1809 | 0 | } else { |
1810 | 0 | int ret; |
1811 | | /* create the filename to use */ |
1812 | 0 | if (pl->timestamp_format == TS_FORMAT_SEC) { |
1813 | 0 | ret = snprintf(file, sizeof(file), "%s.%u.%" PRIu32 "%s", pl->prefix, |
1814 | 0 | pl->thread_number, (uint32_t)SCTIME_SECS(ts), pl->suffix); |
1815 | 0 | } else { |
1816 | 0 | ret = snprintf(file, sizeof(file), "%s.%u.%" PRIu32 ".%" PRIu32 "%s", pl->prefix, |
1817 | 0 | pl->thread_number, (uint32_t)SCTIME_SECS(ts), (uint32_t)SCTIME_USECS(ts), |
1818 | 0 | pl->suffix); |
1819 | 0 | } |
1820 | 0 | if (ret < 0 || (size_t)ret >= PATH_MAX) { |
1821 | 0 | SCLogError("failed to construct path"); |
1822 | 0 | goto error; |
1823 | 0 | } |
1824 | 0 | } |
1825 | 0 | SCLogDebug("multi-mode: filename %s", file); |
1826 | 0 | } |
1827 | 0 | if (PathMerge(path, PATH_MAX, pl->dir, file) < 0) { |
1828 | 0 | SCLogError("failed to construct path"); |
1829 | 0 | goto error; |
1830 | 0 | } |
1831 | | |
1832 | 0 | if ((pf->filename = SCStrdup(pl->filename)) == NULL) { |
1833 | 0 | SCLogError("Error allocating memory. For filename"); |
1834 | 0 | goto error; |
1835 | 0 | } |
1836 | 0 | SCLogDebug("Opening pcap file log %s", pf->filename); |
1837 | 0 | TAILQ_INSERT_TAIL(&pl->pcap_file_list, pf, next); |
1838 | |
|
1839 | 0 | if (pl->mode == LOGMODE_MULTI || pl->mode == LOGMODE_NORMAL) { |
1840 | 0 | pcap_file_thread = pl->filename; |
1841 | 0 | } |
1842 | 0 | PCAPLOG_PROFILE_END(pl->profile_open); |
1843 | 0 | return 0; |
1844 | | |
1845 | 0 | error: |
1846 | 0 | PcapFileNameFree(pf); |
1847 | 0 | return -1; |
1848 | 0 | } |
1849 | | |
1850 | | char *PcapLogGetFilename(void) |
1851 | 574k | { |
1852 | | /* return pcap filename per thread */ |
1853 | 574k | if (pcap_file_thread != NULL) { |
1854 | 0 | return pcap_file_thread; |
1855 | 0 | } |
1856 | 574k | return NULL; |
1857 | 574k | } |
1858 | | |
1859 | | static int profiling_pcaplog_enabled = 0; |
1860 | | static int profiling_pcaplog_output_to_file = 0; |
1861 | | static char *profiling_pcaplog_file_name = NULL; |
1862 | | static const char *profiling_pcaplog_file_mode = "a"; |
1863 | | |
1864 | | static void FormatNumber(uint64_t num, char *str, size_t size) |
1865 | 0 | { |
1866 | 0 | if (num < 1000UL) |
1867 | 0 | snprintf(str, size, "%"PRIu64, num); |
1868 | 0 | else if (num < 1000000UL) |
1869 | 0 | snprintf(str, size, "%3.1fk", (float)num/1000UL); |
1870 | 0 | else if (num < 1000000000UL) |
1871 | 0 | snprintf(str, size, "%3.1fm", (float)num/1000000UL); |
1872 | 0 | else |
1873 | 0 | snprintf(str, size, "%3.1fb", (float)num/1000000000UL); |
1874 | 0 | } |
1875 | | |
1876 | | static void ProfileReportPair(FILE *fp, const char *name, const PcapLogProfileData *p) |
1877 | 0 | { |
1878 | 0 | char ticks_str[32] = "n/a"; |
1879 | 0 | char cnt_str[32] = "n/a"; |
1880 | 0 | char avg_str[32] = "n/a"; |
1881 | |
|
1882 | 0 | FormatNumber((uint64_t)p->cnt, cnt_str, sizeof(cnt_str)); |
1883 | 0 | FormatNumber((uint64_t)p->total, ticks_str, sizeof(ticks_str)); |
1884 | 0 | if (p->cnt && p->total) |
1885 | 0 | FormatNumber((uint64_t)(p->total/p->cnt), avg_str, sizeof(avg_str)); |
1886 | |
|
1887 | 0 | fprintf(fp, "%-28s %-10s %-10s %-10s\n", name, cnt_str, avg_str, ticks_str); |
1888 | 0 | } |
1889 | | |
1890 | | static void ProfileReport(FILE *fp, const PcapLogData *pl) |
1891 | 0 | { |
1892 | 0 | ProfileReportPair(fp, "open", &pl->profile_open); |
1893 | 0 | ProfileReportPair(fp, "close", &pl->profile_close); |
1894 | 0 | ProfileReportPair(fp, "write", &pl->profile_write); |
1895 | 0 | ProfileReportPair(fp, "rotate (incl open/close)", &pl->profile_rotate); |
1896 | 0 | ProfileReportPair(fp, "handles", &pl->profile_handles); |
1897 | 0 | ProfileReportPair(fp, "lock", &pl->profile_lock); |
1898 | 0 | ProfileReportPair(fp, "unlock", &pl->profile_unlock); |
1899 | 0 | } |
1900 | | |
1901 | | static void FormatBytes(uint64_t num, char *str, size_t size) |
1902 | 0 | { |
1903 | 0 | if (num < 1000UL) |
1904 | 0 | snprintf(str, size, "%"PRIu64, num); |
1905 | 0 | else if (num < 1048576UL) |
1906 | 0 | snprintf(str, size, "%3.1fKiB", (float)num/1000UL); |
1907 | 0 | else if (num < 1073741824UL) |
1908 | 0 | snprintf(str, size, "%3.1fMiB", (float)num/1000000UL); |
1909 | 0 | else |
1910 | 0 | snprintf(str, size, "%3.1fGiB", (float)num/1000000000UL); |
1911 | 0 | } |
1912 | | |
1913 | | static void DoDump(const PcapLogData *pl, FILE *fp) |
1914 | 0 | { |
1915 | | /* counters */ |
1916 | 0 | fprintf(fp, "\n\nOperation Cnt Avg ticks Total ticks\n"); |
1917 | 0 | fprintf(fp, "---------------------------- ---------- ---------- -----------\n"); |
1918 | |
|
1919 | 0 | ProfileReport(fp, pl); |
1920 | 0 | uint64_t total = pl->profile_write.total + pl->profile_rotate.total + |
1921 | 0 | pl->profile_handles.total + pl->profile_open.total + |
1922 | 0 | pl->profile_close.total + pl->profile_lock.total + |
1923 | 0 | pl->profile_unlock.total; |
1924 | | |
1925 | | /* overall stats */ |
1926 | 0 | fprintf(fp, "\nOverall: %"PRIu64" bytes written, average %d bytes per write.\n", |
1927 | 0 | pl->profile_data_size, pl->profile_write.cnt ? |
1928 | 0 | (int)(pl->profile_data_size / pl->profile_write.cnt) : 0); |
1929 | 0 | fprintf(fp, " PCAP data structure overhead: %"PRIuMAX" per write.\n", |
1930 | 0 | (uintmax_t)sizeof(struct pcap_pkthdr)); |
1931 | | |
1932 | | /* print total bytes written */ |
1933 | 0 | char bytes_str[32]; |
1934 | 0 | FormatBytes(pl->profile_data_size, bytes_str, sizeof(bytes_str)); |
1935 | 0 | fprintf(fp, " Size written: %s\n", bytes_str); |
1936 | | |
1937 | | /* ticks per MiB and GiB */ |
1938 | 0 | uint64_t ticks_per_mib = 0, ticks_per_gib = 0; |
1939 | 0 | uint64_t mib = pl->profile_data_size/(1024*1024); |
1940 | 0 | if (mib) |
1941 | 0 | ticks_per_mib = total/mib; |
1942 | 0 | char ticks_per_mib_str[32] = "n/a"; |
1943 | 0 | if (ticks_per_mib > 0) |
1944 | 0 | FormatNumber(ticks_per_mib, ticks_per_mib_str, sizeof(ticks_per_mib_str)); |
1945 | 0 | fprintf(fp, " Ticks per MiB: %s\n", ticks_per_mib_str); |
1946 | |
|
1947 | 0 | uint64_t gib = pl->profile_data_size/(1024*1024*1024); |
1948 | 0 | if (gib) |
1949 | 0 | ticks_per_gib = total/gib; |
1950 | 0 | char ticks_per_gib_str[32] = "n/a"; |
1951 | 0 | if (ticks_per_gib > 0) |
1952 | 0 | FormatNumber(ticks_per_gib, ticks_per_gib_str, sizeof(ticks_per_gib_str)); |
1953 | 0 | fprintf(fp, " Ticks per GiB: %s\n", ticks_per_gib_str); |
1954 | 0 | } |
1955 | | |
1956 | | static void PcapLogProfilingDump(PcapLogData *pl) |
1957 | 0 | { |
1958 | 0 | if (profiling_pcaplog_enabled == 0) |
1959 | 0 | return; |
1960 | | |
1961 | 0 | if (profiling_pcaplog_output_to_file == 1) { |
1962 | 0 | FILE *fp = fopen(profiling_pcaplog_file_name, profiling_pcaplog_file_mode); |
1963 | 0 | if (fp == NULL) { |
1964 | 0 | SCLogError("failed to open %s: %s", profiling_pcaplog_file_name, strerror(errno)); |
1965 | 0 | return; |
1966 | 0 | } |
1967 | 0 | DoDump(pl, fp); |
1968 | 0 | fclose(fp); |
1969 | 0 | } else { |
1970 | 0 | DoDump(pl, stdout); |
1971 | 0 | } |
1972 | 0 | } |
1973 | | |
1974 | | void PcapLogProfileSetup(void) |
1975 | 78 | { |
1976 | 78 | SCConfNode *conf = SCConfGetNode("profiling.pcap-log"); |
1977 | 78 | if (conf != NULL && SCConfNodeChildValueIsTrue(conf, "enabled")) { |
1978 | 0 | profiling_pcaplog_enabled = 1; |
1979 | 0 | SCLogInfo("pcap-log profiling enabled"); |
1980 | |
|
1981 | 0 | const char *filename = SCConfNodeLookupChildValue(conf, "filename"); |
1982 | 0 | if (filename != NULL) { |
1983 | 0 | const char *log_dir; |
1984 | 0 | log_dir = SCConfigGetLogDirectory(); |
1985 | |
|
1986 | 0 | profiling_pcaplog_file_name = SCMalloc(PATH_MAX); |
1987 | 0 | if (unlikely(profiling_pcaplog_file_name == NULL)) { |
1988 | 0 | FatalError("can't duplicate file name"); |
1989 | 0 | } |
1990 | | |
1991 | 0 | snprintf(profiling_pcaplog_file_name, PATH_MAX, "%s/%s", log_dir, filename); |
1992 | |
|
1993 | 0 | const char *v = SCConfNodeLookupChildValue(conf, "append"); |
1994 | 0 | if (v == NULL || SCConfValIsTrue(v)) { |
1995 | 0 | profiling_pcaplog_file_mode = "a"; |
1996 | 0 | } else { |
1997 | 0 | profiling_pcaplog_file_mode = "w"; |
1998 | 0 | } |
1999 | |
|
2000 | 0 | profiling_pcaplog_output_to_file = 1; |
2001 | 0 | SCLogInfo("pcap-log profiling output goes to %s (mode %s)", |
2002 | 0 | profiling_pcaplog_file_name, profiling_pcaplog_file_mode); |
2003 | 0 | } |
2004 | 0 | } |
2005 | 78 | } |