]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
Add an option for compressing pcap-log files
authorMax Fillinger <maximilian.fillinger@fox-it.com>
Mon, 15 Jan 2018 09:18:53 +0000 (10:18 +0100)
committerVictor Julien <victor@inliniac.net>
Thu, 3 May 2018 11:29:28 +0000 (13:29 +0200)
Introduces the option 'outputs.pcap-log.compression' which can be set
to 'none' or 'lz4', plus options to set the compression level and to
enable checksums. SCFmemopen is used to make pcap_dump() write to a
buffer which is then compressed using liblz4.

configure.ac
src/log-pcap.c
src/util-error.c
src/util-error.h
suricata.yaml.in

index 87cd6c0476a10d09fc73ca132bbbb325d6365a6f..ba833fb4382123ce3a22b5d10713e71d348f65b4 100644 (file)
         fi
     fi
 
+# Check for lz4
+AC_CHECK_LIB(lz4, LZ4F_createCompressionContext, , )
+
 # get cache line size
     AC_PATH_PROG(HAVE_GETCONF_CMD, getconf, "no")
     if test "$HAVE_GETCONF_CMD" != "no"; then
index dacae667c3264e1bd1e00866afdf012e2c5b5e7b..9b882c1c84386dc01f477817ac4d67d8543b8db6 100644 (file)
  */
 
 #include "suricata-common.h"
+#include "util-fmemopen.h"
+
+#ifdef HAVE_LIBLZ4
+#include <lz4frame.h>
+#endif /* HAVE_LIBLZ4 */
 
 #if defined(HAVE_DIRENT_H) && defined(HAVE_FNMATCH_H)
 #define INIT_RING_BUFFER
@@ -62,7 +67,7 @@
 
 #define DEFAULT_LOG_FILENAME            "pcaplog"
 #define MODULE_NAME                     "PcapLog"
-#define MIN_LIMIT                       1 * 1024 * 1024
+#define MIN_LIMIT                       4 * 1024 * 1024
 #define DEFAULT_LIMIT                   100 * 1024 * 1024
 #define DEFAULT_FILE_LIMIT              0
 
@@ -108,6 +113,26 @@ typedef struct PcapLogProfileData_ {
 #define MAX_TOKS 9
 #define MAX_FILENAMELEN 513
 
+enum PcapLogCompressionFormat {
+    PCAP_LOG_COMPRESSION_FORMAT_NONE,
+    PCAP_LOG_COMPRESSION_FORMAT_LZ4,
+};
+
+typedef struct PcapLogCompressionData_ {
+    enum PcapLogCompressionFormat format;
+    uint8_t *buffer;
+    uint64_t buffer_size;
+#ifdef HAVE_LIBLZ4
+    LZ4F_compressionContext_t lz4f_context;
+    LZ4F_preferences_t lz4f_prefs;
+#endif /* HAVE_LIBLZ4 */
+    FILE *file;
+    uint8_t *pcap_buf;
+    uint64_t pcap_buf_size;
+    FILE *pcap_buf_wrapper;
+    uint64_t bytes_in_block;
+} PcapLogCompressionData;
+
 /**
  * PcapLog thread vars
  *
@@ -145,11 +170,14 @@ typedef struct PcapLogData_ {
     int use_ringbuffer;         /**< ring buffer mode enabled or disabled */
     int timestamp_format;       /**< timestamp format sec or usec */
     char *prefix;               /**< filename prefix */
+    const char *suffix;         /**< filename suffix */
     char dir[PATH_MAX];         /**< pcap log directory */
     int reported;
     int threads;                /**< number of threads (only set in the global) */
     char *filename_parts[MAX_TOKS];
     int filename_part_cnt;
+
+    PcapLogCompressionData compression;
 } PcapLogData;
 
 typedef struct PcapLogThreadData_ {
@@ -213,8 +241,24 @@ static int PcapLogCloseFile(ThreadVars *t, PcapLogData *pl)
     if (pl != NULL) {
         PCAPLOG_PROFILE_START;
 
-        if (pl->pcap_dumper != NULL)
+        if (pl->pcap_dumper != NULL) {
             pcap_dump_close(pl->pcap_dumper);
+#ifdef HAVE_LIBLZ4
+            PcapLogCompressionData *comp = &pl->compression;
+            if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) {
+                /* pcap_dump_close() has closed its output ``file'',
+                 * so we need to call fmemopen again. */
+
+                comp->pcap_buf_wrapper = SCFmemopen(comp->pcap_buf,
+                        comp->pcap_buf_size, "w");
+                if (comp->pcap_buf_wrapper == NULL) {
+                    SCLogError(SC_ERR_FOPEN, "SCFmemopen failed: %s",
+                            strerror(errno));
+                    return TM_ECODE_FAILED;
+                }
+            }
+#endif /* HAVE_LIBLZ4 */
+        }
         pl->size_current = 0;
         pl->pcap_dumper = NULL;
 
@@ -222,6 +266,28 @@ static int PcapLogCloseFile(ThreadVars *t, PcapLogData *pl)
             pcap_close(pl->pcap_dead_handle);
         pl->pcap_dead_handle = NULL;
 
+#ifdef HAVE_LIBLZ4
+        PcapLogCompressionData *comp = &pl->compression;
+        if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) {
+            /* pcap_dump_close did not write any data because we call
+             * pcap_dump_flush() after every write when writing
+            * compressed output. */
+            uint64_t bytes_written = LZ4F_compressEnd(comp->lz4f_context,
+                    comp->buffer, comp->buffer_size, NULL);
+            if (LZ4F_isError(bytes_written)) {
+                SCLogError(SC_ERR_PCAP_LOG_COMPRESS, "LZ4F_compressEnd: %s",
+                        LZ4F_getErrorName(bytes_written));
+                return TM_ECODE_FAILED;
+            }
+            if (fwrite(comp->buffer, 1, bytes_written, comp->file) < bytes_written) {
+                SCLogError(SC_ERR_FWRITE, "fwrite failed: %s", strerror(errno));
+                return TM_ECODE_FAILED;
+            }
+            fclose(comp->file);
+            comp->bytes_in_block = 0;
+        }
+#endif /* HAVE_LIBLZ4 */
+
         PCAPLOG_PROFILE_END(pl->profile_close);
     }
 
@@ -320,18 +386,50 @@ static int PcapLogOpenHandles(PcapLogData *pl, const Packet *p)
 
     if (pl->pcap_dead_handle == NULL) {
         if ((pl->pcap_dead_handle = pcap_open_dead(p->datalink,
-                        PCAP_SNAPLEN)) == NULL) {
+                PCAP_SNAPLEN)) == NULL) {
             SCLogDebug("Error opening dead pcap handle");
             return TM_ECODE_FAILED;
         }
     }
 
     if (pl->pcap_dumper == NULL) {
-        if ((pl->pcap_dumper = pcap_dump_open(pl->pcap_dead_handle,
-                        pl->filename)) == NULL) {
-            SCLogInfo("Error opening dump file %s", pcap_geterr(pl->pcap_dead_handle));
-            return TM_ECODE_FAILED;
+        if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_NONE) {
+            if ((pl->pcap_dumper = pcap_dump_open(pl->pcap_dead_handle,
+                    pl->filename)) == NULL) {
+                SCLogInfo("Error opening dump file %s", pcap_geterr(pl->pcap_dead_handle));
+                return TM_ECODE_FAILED;
+            }
         }
+#ifdef HAVE_LIBLZ4
+        else if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) {
+            PcapLogCompressionData *comp = &pl->compression;
+            if ((pl->pcap_dumper = pcap_dump_fopen(pl->pcap_dead_handle,
+                    comp->pcap_buf_wrapper)) == NULL) {
+                SCLogError(SC_ERR_OPENING_FILE, "Error opening dump file %s",
+                        pcap_geterr(pl->pcap_dead_handle));
+                return TM_ECODE_FAILED;
+            }
+            comp->file = fopen(pl->filename, "w");
+            if (comp->file == NULL) {
+                SCLogError(SC_ERR_OPENING_FILE,
+                        "Error opening file for compressed output: %s",
+                        strerror(errno));
+                return TM_ECODE_FAILED;
+            }
+
+            uint64_t bytes_written = LZ4F_compressBegin(comp->lz4f_context,
+                    comp->buffer, comp->buffer_size, NULL);
+            if (LZ4F_isError(bytes_written)) {
+                SCLogError(SC_ERR_PCAP_LOG_COMPRESS, "LZ4F_compressBegin: %s",
+                        LZ4F_getErrorName(bytes_written));
+                return TM_ECODE_FAILED;
+            }
+            if (fwrite(comp->buffer, 1, bytes_written, comp->file) < bytes_written) {
+                SCLogError(SC_ERR_FWRITE, "fwrite failed: %s", strerror(errno));
+                return TM_ECODE_FAILED;
+            }
+        }
+#endif /* HAVE_LIBLZ4 */
     }
 
     PCAPLOG_PROFILE_END(pl->profile_handles);
@@ -421,13 +519,34 @@ static int PcapLog (ThreadVars *t, void *thread_data, const Packet *p)
         }
     }
 
-    if ((pl->size_current + len) > pl->size_limit || rotate) {
-        if (PcapLogRotateFile(t,pl) < 0) {
-            PcapLogUnlock(pl);
-            SCLogDebug("rotation of pcap failed");
-            return TM_ECODE_FAILED;
+    PcapLogCompressionData *comp = &pl->compression;
+    if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_NONE) {
+        if ((pl->size_current + len) > pl->size_limit || rotate) {
+            if (PcapLogRotateFile(t,pl) < 0) {
+                PcapLogUnlock(pl);
+                SCLogDebug("rotation of pcap failed");
+                return TM_ECODE_FAILED;
+            }
         }
     }
+#ifdef HAVE_LIBLZ4
+    else if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) {
+        /* When writing compressed pcap logs, we have no way of knowing
+         * for sure whether adding this packet would cause the current
+         * file to exceed the size limit. Thus, we record the number of
+         * bytes that have been fed into lz4 since the last write, and
+         * act as if they would be written uncompressed. */
+
+        if ((pl->size_current + comp->bytes_in_block + len) > pl->size_limit ||
+                rotate) {
+            if (PcapLogRotateFile(t,pl) < 0) {
+                PcapLogUnlock(pl);
+                SCLogDebug("rotation of pcap failed");
+                return TM_ECODE_FAILED;
+            }
+        }
+    }
+#endif /* HAVE_LIBLZ4 */
 
     /* XXX pcap handles, nfq, pfring, can only have one link type ipfw? we do
      * this here as we don't know the link type until we get our first packet */
@@ -440,7 +559,37 @@ static int PcapLog (ThreadVars *t, void *thread_data, const Packet *p)
 
     PCAPLOG_PROFILE_START;
     pcap_dump((u_char *)pl->pcap_dumper, pl->h, GET_PKT_DATA(p));
-    pl->size_current += len;
+    if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_NONE) {
+        pl->size_current += len;
+    }
+#ifdef HAVE_LIBLZ4
+    else if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) {
+        pcap_dump_flush(pl->pcap_dumper);
+        uint64_t in_size = (uint64_t)ftell(comp->pcap_buf_wrapper);
+        uint64_t out_size = LZ4F_compressUpdate(comp->lz4f_context,
+                comp->buffer, comp->buffer_size, comp->pcap_buf, in_size, NULL);
+        if (LZ4F_isError(len)) {
+            SCLogError(SC_ERR_PCAP_LOG_COMPRESS, "LZ4F_compressUpdate: %s",
+                    LZ4F_getErrorName(len));
+            return TM_ECODE_FAILED;
+        }
+        if (fseek(pl->compression.pcap_buf_wrapper, 0, SEEK_SET) != 0) {
+            SCLogError(SC_ERR_FSEEK, "fseek failed: %s", strerror(errno));
+            return TM_ECODE_FAILED;
+        }
+        if (fwrite(comp->buffer, 1, out_size, comp->file) < out_size) {
+            SCLogError(SC_ERR_FWRITE, "fwrite failed: %s", strerror(errno));
+            return TM_ECODE_FAILED;
+        }
+        if (out_size > 0) {
+            pl->size_current += out_size;
+            comp->bytes_in_block = len;
+        } else {
+            comp->bytes_in_block += len;
+        }
+    }
+#endif /* HAVE_LIBLZ4 */
+
     PCAPLOG_PROFILE_END(pl->profile_write);
     pl->profile_data_size += len;
 
@@ -472,6 +621,8 @@ static PcapLogData *PcapLogDataCopy(const PcapLogData *pl)
         return NULL;
     }
 
+    copy->suffix = pl->suffix;
+
     /* settings TODO move to global cfg struct */
     copy->is_private = TRUE;
     copy->mode = pl->mode;
@@ -481,6 +632,63 @@ static PcapLogData *PcapLogDataCopy(const PcapLogData *pl)
     copy->use_stream_depth = pl->use_stream_depth;
     copy->size_limit = pl->size_limit;
 
+    const PcapLogCompressionData *comp = &pl->compression;
+    PcapLogCompressionData *copy_comp = &copy->compression;
+    copy_comp->format = comp->format;
+#ifdef HAVE_LIBLZ4
+    if (comp->format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) {
+        /* We need to allocate a new compression context and buffers for
+         * the copy. First copy the things that can simply be copied. */
+
+        copy_comp->buffer_size = comp->buffer_size;
+        copy_comp->pcap_buf_size = comp->pcap_buf_size;
+        copy_comp->lz4f_prefs = comp->lz4f_prefs;
+
+        /* Allocate the buffers. */
+
+        copy_comp->buffer = SCMalloc(copy_comp->buffer_size);
+        if (copy_comp->buffer == NULL) {
+            SCLogError(SC_ERR_MEM_ALLOC, "SCMalloc failed: %s",
+                    strerror(errno));
+            return NULL;
+        }
+        copy_comp->pcap_buf = SCMalloc(copy_comp->pcap_buf_size);
+        if (copy_comp->pcap_buf == NULL) {
+            SCLogError(SC_ERR_MEM_ALLOC, "SCMalloc failed: %s",
+                    strerror(errno));
+            SCFree(copy_comp->buffer);
+            return NULL;
+        }
+        copy_comp->pcap_buf_wrapper = SCFmemopen(copy_comp->pcap_buf,
+                copy_comp->pcap_buf_size, "w");
+        if (copy_comp->pcap_buf_wrapper == NULL) {
+            SCLogError(SC_ERR_FOPEN, "SCFmemopen failed: %s", strerror(errno));
+            SCFree(copy_comp->buffer);
+            SCFree(copy_comp->pcap_buf);
+            return NULL;
+        }
+
+        /* Initialize a new compression context. */
+
+        LZ4F_errorCode_t errcode =
+               LZ4F_createCompressionContext(&copy_comp->lz4f_context, 1);
+        if (LZ4F_isError(errcode)) {
+            SCLogError(SC_ERR_PCAP_LOG_COMPRESS,
+                    "LZ4F_createCompressionContext failed: %s",
+                    LZ4F_getErrorName(errcode));
+            fclose(copy_comp->pcap_buf_wrapper);
+            SCFree(copy_comp->buffer);
+            SCFree(copy_comp->pcap_buf);
+            return NULL;
+        }
+
+        /* Initialize the rest. */
+
+        copy_comp->file = NULL;
+        copy_comp->bytes_in_block = 0;
+    }
+#endif /* HAVE_LIBLZ4 */
+
     TAILQ_INIT(&copy->pcap_file_list);
     SCMutexInit(&copy->plog_lock, NULL);
 
@@ -583,6 +791,7 @@ static TmEcode PcapLogInitRingBuffer(PcapLogData *pl)
         strlcat(pattern, pl->prefix, PATH_MAX);
         strlcat(pattern, ".*", PATH_MAX);
     }
+    strlcat(pattern, pl->suffix, PATH_MAX);
 
     char *basename = strrchr(pattern, '/');
     *basename++ = '\0';
@@ -791,6 +1000,19 @@ static void PcapLogDataFree(PcapLogData *pl)
     SCFree(pl->h);
     SCFree(pl->filename);
     SCFree(pl->prefix);
+
+#ifdef HAVE_LIBLZ4
+    if (pl->compression.format == PCAP_LOG_COMPRESSION_FORMAT_LZ4) {
+        SCFree(pl->compression.buffer);
+        fclose(pl->compression.pcap_buf_wrapper);
+        SCFree(pl->compression.pcap_buf);
+        LZ4F_errorCode_t errcode =
+                LZ4F_freeCompressionContext(pl->compression.lz4f_context);
+        if (LZ4F_isError(errcode)) {
+            SCLogWarning(SC_ERR_MEM_ALLOC, "Error freeing lz4 context.");
+        }
+    }
+#endif /* HAVE_LIBLZ4 */
     SCFree(pl);
 }
 
@@ -993,6 +1215,8 @@ static OutputInitResult PcapLogInitCtx(ConfNode *conf)
         exit(EXIT_FAILURE);
     }
 
+    pl->suffix = "";
+
     if (filename) {
         if (ParseFilename(pl, filename) != 0)
             exit(EXIT_FAILURE);
@@ -1079,6 +1303,116 @@ static OutputInitResult PcapLogInitCtx(ConfNode *conf)
             }
             SCLogInfo("Using log dir %s", pl->dir);
         }
+
+        const char *compression_str = ConfNodeLookupChildValue(conf,
+                "compression");
+
+        PcapLogCompressionData *comp = &pl->compression;
+        if (compression_str == NULL || strcmp(compression_str, "none") == 0) {
+            comp->format = PCAP_LOG_COMPRESSION_FORMAT_NONE;
+            comp->buffer = NULL;
+            comp->buffer_size = 0;
+            comp->file = NULL;
+            comp->pcap_buf = NULL;
+            comp->pcap_buf_size = 0;
+            comp->pcap_buf_wrapper = NULL;
+        } else if (strcmp(compression_str, "lz4") == 0) {
+#ifdef HAVE_LIBLZ4
+            if (pl->mode == LOGMODE_SGUIL) {
+                SCLogError(SC_ERR_INVALID_YAML_CONF_ENTRY, "Compressed pcap "
+                        "logs are not possible in sguil mode");
+                SCFree(pl->h);
+                SCFree(pl);
+                return result;
+            }
+            pl->compression.format = PCAP_LOG_COMPRESSION_FORMAT_LZ4;
+
+            /* Use SCFmemopen so we can make pcap_dump write to a buffer. */
+
+            comp->pcap_buf_size = sizeof(struct pcap_file_header) +
+                    sizeof(struct pcap_pkthdr) + PCAP_SNAPLEN;
+            comp->pcap_buf = SCMalloc(comp->pcap_buf_size);
+            if (comp->pcap_buf == NULL) {
+                SCLogError(SC_ERR_MEM_ALLOC, "SCMalloc failed: %s",
+                        strerror(errno));
+                exit(EXIT_FAILURE);
+            }
+            comp->pcap_buf_wrapper = SCFmemopen(comp->pcap_buf,
+                    comp->pcap_buf_size, "w");
+            if (comp->pcap_buf_wrapper == NULL) {
+                SCLogError(SC_ERR_FOPEN, "SCFmemopen failed: %s",
+                        strerror(errno));
+                exit(EXIT_FAILURE);
+            }
+
+            /* Set lz4 preferences. */
+
+            memset(&comp->lz4f_prefs, '\0', sizeof(comp->lz4f_prefs));
+            comp->lz4f_prefs.frameInfo.blockSizeID = LZ4F_max4MB;
+            comp->lz4f_prefs.frameInfo.blockMode = LZ4F_blockLinked;
+            if (ConfNodeChildValueIsTrue(conf, "lz4-checksum")) {
+                comp->lz4f_prefs.frameInfo.contentChecksumFlag = 1;
+            }
+            else {
+                comp->lz4f_prefs.frameInfo.contentChecksumFlag = 0;
+            }
+            intmax_t lvl = 0;
+            if (ConfGetChildValueInt(conf, "lz4-level", &lvl)) {
+                if (lvl > 16) {
+                    lvl = 16;
+                } else if (lvl < 0) {
+                    lvl = 0;
+                }
+            } else {
+                lvl = 0;
+            }
+            comp->lz4f_prefs.compressionLevel = lvl;
+
+            /* Allocate resources for lz4. */
+
+            LZ4F_errorCode_t errcode =
+                LZ4F_createCompressionContext(&pl->compression.lz4f_context, 1);
+
+            if (LZ4F_isError(errcode)) {
+                SCLogError(SC_ERR_PCAP_LOG_COMPRESS,
+                        "LZ4F_createCompressionContext failed: %s",
+                        LZ4F_getErrorName(errcode));
+                exit(EXIT_FAILURE);
+            }
+
+            /* Calculate the size of the lz4 output buffer. */
+
+            comp->buffer_size = LZ4F_compressBound(comp->pcap_buf_size,
+                    &comp->lz4f_prefs);
+
+            comp->buffer = SCMalloc(comp->buffer_size);
+            if (unlikely(comp->buffer == NULL)) {
+                SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate memory for "
+                    "lz4 output buffer.");
+                exit(EXIT_FAILURE);
+            }
+
+            comp->bytes_in_block = 0;
+
+            /* Add the lz4 file extension to the log files. */
+
+            pl->suffix = ".lz4";
+#else
+            SCLogError(SC_ERR_INVALID_ARGUMENT, "lz4 compression was selected "
+                    "in pcap-log, but suricata was not compiled with lz4 "
+                    "support.");
+            return result;
+#endif /* HAVE_LIBLZ4 */
+        }
+        else {
+            SCLogError(SC_ERR_INVALID_ARGUMENT, "Unsupported pcap-log "
+                    "compression format: %s", compression_str);
+            SCFree(pl->h);
+            SCFree(pl);
+            return result;
+        }
+
+        SCLogInfo("Selected pcap-log compression method: %s", compression_str);
     }
 
     SCLogInfo("using %s logging", pl->mode == LOGMODE_SGUIL ?
@@ -1249,11 +1583,12 @@ static int PcapLogOpenFileCtx(PcapLogData *pl)
 
         int written;
         if (pl->timestamp_format == TS_FORMAT_SEC) {
-            written = snprintf(filename, PATH_MAX, "%s/%s.%" PRIu32, dirfull,
-                     pl->prefix, (uint32_t)ts.tv_sec);
+            written = snprintf(filename, PATH_MAX, "%s/%s.%" PRIu32 "%s",
+                     dirfull, pl->prefix, (uint32_t)ts.tv_sec, pl->suffix);
         } else {
-            written = snprintf(filename, PATH_MAX, "%s/%s.%" PRIu32 ".%" PRIu32,
-                     dirfull, pl->prefix, (uint32_t)ts.tv_sec, (uint32_t)ts.tv_usec);
+            written = snprintf(filename, PATH_MAX, "%s/%s.%" PRIu32 ".%" PRIu32 "%s",
+                     dirfull, pl->prefix, (uint32_t)ts.tv_sec,
+                     (uint32_t)ts.tv_usec, pl->suffix);
         }
         if (written == PATH_MAX) {
             SCLogError(SC_ERR_SPRINTF,"log-pcap path overflow");
@@ -1263,11 +1598,12 @@ static int PcapLogOpenFileCtx(PcapLogData *pl)
         int ret;
         /* create the filename to use */
         if (pl->timestamp_format == TS_FORMAT_SEC) {
-            ret = snprintf(filename, PATH_MAX, "%s/%s.%" PRIu32, pl->dir,
-                    pl->prefix, (uint32_t)ts.tv_sec);
+            ret = snprintf(filename, PATH_MAX, "%s/%s.%" PRIu32 "%s", pl->dir,
+                    pl->prefix, (uint32_t)ts.tv_sec, pl->suffix);
         } else {
-            ret = snprintf(filename, PATH_MAX, "%s/%s.%" PRIu32 ".%" PRIu32, pl->dir,
-                    pl->prefix, (uint32_t)ts.tv_sec, (uint32_t)ts.tv_usec);
+            ret = snprintf(filename, PATH_MAX,
+                    "%s/%s.%" PRIu32 ".%" PRIu32 "%s", pl->dir, pl->prefix,
+                    (uint32_t)ts.tv_sec, (uint32_t)ts.tv_usec, pl->suffix);
         }
         if (ret < 0 || (size_t)ret >= PATH_MAX) {
             SCLogError(SC_ERR_SPRINTF,"failed to construct path");
@@ -1317,15 +1653,19 @@ static int PcapLogOpenFileCtx(PcapLogData *pl)
                     strlcat(filename, pl->filename_parts[i], PATH_MAX);
                 }
             }
+            strlcat(filename, pl->suffix, PATH_MAX);
         } else {
             int ret;
             /* create the filename to use */
             if (pl->timestamp_format == TS_FORMAT_SEC) {
-                ret = snprintf(filename, PATH_MAX, "%s/%s.%u.%" PRIu32, pl->dir,
-                        pl->prefix, pl->thread_number, (uint32_t)ts.tv_sec);
+                ret = snprintf(filename, PATH_MAX, "%s/%s.%u.%" PRIu32 "%s",
+                        pl->dir, pl->prefix, pl->thread_number,
+                        (uint32_t)ts.tv_sec, pl->suffix);
             } else {
-                ret = snprintf(filename, PATH_MAX, "%s/%s.%u.%" PRIu32 ".%" PRIu32, pl->dir,
-                        pl->prefix, pl->thread_number, (uint32_t)ts.tv_sec, (uint32_t)ts.tv_usec);
+                ret = snprintf(filename, PATH_MAX,
+                        "%s/%s.%u.%" PRIu32 ".%" PRIu32 "%s", pl->dir,
+                        pl->prefix, pl->thread_number, (uint32_t)ts.tv_sec,
+                        (uint32_t)ts.tv_usec, pl->suffix);
             }
             if (ret < 0 || (size_t)ret >= PATH_MAX) {
                 SCLogError(SC_ERR_SPRINTF,"failed to construct path");
index 639a6cf3cb3e76dde157341cdcb043a88fd891ef..6a394da3fa8e2d46c22033de295ba1757b8cacb2 100644 (file)
@@ -350,6 +350,9 @@ const char * SCErrorToString(SCError err)
         CASE_CODE (SC_ERR_SMB_CONFIG);
         CASE_CODE (SC_WARN_NO_JA3_SUPPORT);
         CASE_CODE (SC_WARN_JA3_DISABLED);
+        CASE_CODE (SC_ERR_PCAP_LOG_COMPRESS);
+        CASE_CODE (SC_ERR_FSEEK);
+
         CASE_CODE (SC_ERR_MAX);
     }
 
index 399e96f2299d8d0c245c53faf02be8d6b82e8c30..4654f046fe69b0f505ffd2e64bdd50a20c3ced45 100644 (file)
@@ -340,7 +340,10 @@ typedef enum {
     SC_ERR_SMB_CONFIG,
     SC_WARN_NO_JA3_SUPPORT,
     SC_WARN_JA3_DISABLED,
-    SC_ERR_MAX
+    SC_ERR_PCAP_LOG_COMPRESS,
+    SC_ERR_FSEEK,
+
+    SC_ERR_MAX,
 } SCError;
 
 const char *SCErrorToString(SCError);
index cd4b15c36191bbf0260506250b58eb5388c882d7..634d1533901843e44bfd28f2b173fec2fdc4a9aa 100644 (file)
@@ -406,6 +406,17 @@ outputs:
       # If set to a value will enable ring buffer mode. Will keep Maximum of "max-files" of size "limit"
       max-files: 2000
 
+      # Compression algorithm for pcap files. Possible values: none, lz4.
+      # Enabling compression is incompatible with the sguil mode. Note also
+      # that on Windows, enabling compression will *increase* disk I/O.
+      compression: none
+
+      # Further options for lz4 compression. The compression level can be set
+      # to a value between 0 and 16, where higher values result in higher
+      # compression.
+      #lz4-checksum: no
+      #lz4-level: 0
+
       mode: normal # normal, multi or sguil.
 
       # Directory to place pcap files. If not provided the default log