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