]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
source-pcap-file: Pcap Directory Mode (Feature #2222)
authorDana Helwig <dana.helwig@protectwise.com>
Thu, 27 Apr 2017 17:17:16 +0000 (11:17 -0600)
committerVictor Julien <victor@inliniac.net>
Mon, 11 Dec 2017 08:21:32 +0000 (09:21 +0100)
https://redmine.openinfosecfoundation.org/issues/2222

Pcap file mode that when passed a directory will process all files in
that directory. If --pcap-file-continuous or continuous option is passed
in json, the directory will be monitored  until the directory is
moved/deleted, suricata is interrupted, or the pcap-interrupt command
is used with unix command socket. Existing file implementation and new
directory implementation has moved from source-pcap-file into
pcap-file-helper and pcap-directory-helper.

Engine state will not reset between files.

Also satisfies:
 * https://redmine.openinfosecfoundation.org/issues/2299
 * https://redmine.openinfosecfoundation.org/issues/724
 * https://redmine.openinfosecfoundation.org/issues/1476

Co-Authors: Dana Helwig <dana.helwig@protectwise.com> and
Danny Browning <danny.browning@protectwise.com>

13 files changed:
doc/userguide/partials/options.rst
doc/userguide/unix-socket.rst
scripts/suricatasc/src/suricatasc.py
src/Makefile.am
src/runmode-unix-socket.c
src/runmode-unix-socket.h
src/source-pcap-file-directory-helper.c [new file with mode: 0644]
src/source-pcap-file-directory-helper.h [new file with mode: 0644]
src/source-pcap-file-helper.c [new file with mode: 0644]
src/source-pcap-file-helper.h [new file with mode: 0644]
src/source-pcap-file.c
src/suricata-common.h
src/suricata.c

index 70e2ae985d0e0eca57c8dea47725d43e8b24a3c9..d150fe58489e577cfcafc461930ea055f78ec1d6 100644 (file)
 
 .. option:: -r <path>
 
-   Run in pcap offline mode reading files from pcap file.
+   Run in pcap offline mode reading files from pcap file. If <path> specifies
+   a directory, all files in that directory will be processed in order of
+   modified time maintaining flow state between files.
+
+.. option:: --pcap-file-continuous
+
+   Used with the -r option to indicate that the mode should stay alive until
+   interrupted. This is useful with directories to add new files and not reset
+   flow state between files.
 
 .. option::  -i <interface>
 
index 5704e7323db63edc1913306fe239a9d0dd4e6973..36f962935095f23efe05bfe6b573e687ab9c573b 100644 (file)
@@ -48,7 +48,11 @@ example to write custom scripts:
 
 Commands in standard running mode
 ---------------------------------
+You may need to install suricatasc if you have not done so, running the following command from scripts/suricatasc
 
+::
+
+  sudo python setup.py install
 
 The set of existing commands is the following:
 
@@ -152,14 +156,20 @@ treatment:
   Success: Successfully added file to list
   >>> pcap-file /home/benches/file2.pcap /tmp/file2
   Success: Successfully added file to list
+  >>> pcap-file-continuous /home/pcaps /tmp/dirout
+  Success: Successfully added file to list
 
 You can add multiple files without waiting the result: they will be
 sequentially processed and the generated log/alert files will be put
 into the directory specified as second arguments of the pcap-file
 command. You need to provide absolute path to the files and directory
-as suricata don’t know from where the script has been run.
+as suricata doesn’t know from where the script has been run. If you pass
+a directory instead of a file, all files in the directory will be processed. If
+using ``pcap-file-continuous`` and passing in a directory, the directory will
+be monitored for new files being added until you use ``pcap-interrupt`` or
+delete/move the directory.
 
-To know how much files are waiting to get processed, you can do:
+To know how many files are waiting to get processed, you can do:
   
 ::
   
@@ -181,6 +191,22 @@ To get current processed file:
   Success:
   "/tmp/test.pcap"
 
+When passing in a directory, you can see last processed time (modified time of last file) in milliseconds since epoch:
+
+::
+
+  >>> pcap-last-processed
+  Success:
+  1509138964000
+
+To interrupt directory processing which terminates the current state:
+
+::
+
+  >>> pcap-interrupt
+  Success:
+  "Interrupted"
+
 Build your own client
 ---------------------
 
index ae62cf205ea3398cbad4819bdda5758221923d15..71a942dd338d0177140f82383bbaf85b1a08375d 100644 (file)
@@ -80,7 +80,7 @@ class SuricataCompleter:
 
 class SuricataSC:
     def __init__(self, sck_path, verbose=False):
-        self.cmd_list=['shutdown','quit','pcap-file','pcap-file-number','pcap-file-list','iface-list','iface-stat','register-tenant','unregister-tenant','register-tenant-handler','unregister-tenant-handler', 'add-hostbit', 'remove-hostbit', 'list-hostbit']
+        self.cmd_list=['shutdown','quit','pcap-file','pcap-file-continuous','pcap-file-number','pcap-file-list','pcap-last-processed','pcap-interrupt','iface-list','iface-stat','register-tenant','unregister-tenant','register-tenant-handler','unregister-tenant-handler', 'add-hostbit', 'remove-hostbit', 'list-hostbit']
         self.sck_path = sck_path
         self.verbose = verbose
 
@@ -192,6 +192,24 @@ class SuricataSC:
                     arguments["output-dir"] = output
                     if tenant != None:
                         arguments["tenant"] = int(tenant)
+            elif "pcap-file-continuous " in command:
+                try:
+                    parts = command.split(' ');
+                except:
+                    raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
+                cmd, filename, output = parts[0], parts[1], parts[2]
+                tenant = None
+                if len(parts) > 3:
+                    tenant = parts[3]
+                if cmd != "pcap-file":
+                    raise SuricataCommandException("Invalid command '%s'" % (command))
+                else:
+                    arguments = {}
+                    arguments["filename"] = filename
+                    arguments["output-dir"] = output
+                    arguments["continuous"] = True
+                    if tenant != None:
+                        arguments["tenant"] = int(tenant)
             elif "iface-stat" in command:
                 try:
                     [cmd, iface] = command.split(' ', 1)
index a97e4f05c8b46c09034c7ed5c1266afdee85150e..76726125ed95339e1b1df839a90ad9ad30559869 100644 (file)
@@ -344,6 +344,8 @@ source-nfq.c source-nfq.h \
 source-nflog.c source-nflog.h \
 source-pcap.c source-pcap.h \
 source-pcap-file.c source-pcap-file.h \
+source-pcap-file-directory-helper.c source-pcap-file-directory-helper.h \
+source-pcap-file-helper.c source-pcap-file-helper.h \
 source-pfring.c source-pfring.h \
 stream.c stream.h \
 stream-tcp.c stream-tcp.h stream-tcp-private.h \
index 79eb787c8e2b3fdc9180c7457b3ec4dff50567dc..48bbecf625f30491fc552203fed37a09b875e22d 100644 (file)
@@ -51,13 +51,16 @@ typedef struct PcapFiles_ {
     char *filename;
     char *output_dir;
     int tenant_id;
+    time_t delay;
+    time_t poll_interval;
+    bool continuous;
     TAILQ_ENTRY(PcapFiles_) next;
 } PcapFiles;
 
 typedef struct PcapCommand_ {
     TAILQ_HEAD(, PcapFiles_) files;
     int running;
-    char *currentfile;
+    PcapFiles *current_file;
 } PcapCommand;
 
 const char *RunModeUnixSocketGetDefaultMode(void)
@@ -68,8 +71,10 @@ const char *RunModeUnixSocketGetDefaultMode(void)
 #ifdef BUILD_UNIX_SOCKET
 
 static int RunModeUnixSocketMaster(void);
-static int unix_manager_file_task_running = 0;
-static int unix_manager_file_task_failed = 0;
+static int unix_manager_pcap_task_running = 0;
+static int unix_manager_pcap_task_failed = 0;
+static int unix_manager_pcap_task_interrupted = 0;
+static struct timespec unix_manager_pcap_last_processed;
 
 /**
  * \brief return list of files in the queue
@@ -124,15 +129,33 @@ static TmEcode UnixSocketPcapCurrent(json_t *cmd, json_t* answer, void *data)
 {
     PcapCommand *this = (PcapCommand *) data;
 
-    if (this->currentfile) {
-        json_object_set_new(answer, "message", json_string(this->currentfile));
+    if (this->current_file && this->current_file->filename) {
+        json_object_set_new(answer, "message",
+                            json_string(this->current_file->filename));
     } else {
         json_object_set_new(answer, "message", json_string("None"));
     }
     return TM_ECODE_OK;
 }
 
+static TmEcode UnixSocketPcapLastProcessed(json_t *cmd, json_t *answer, void *data)
+{
+    uint64_t epoch_millis = unix_manager_pcap_last_processed.tv_sec * 1000l +
+                        unix_manager_pcap_last_processed.tv_nsec / 100000l;
+    json_object_set_new(answer, "message",
+                        json_integer(epoch_millis));
+
+    return TM_ECODE_OK;
+}
+
+static TmEcode UnixSocketPcapInterrupt(json_t *cmd, json_t *answer, void *data)
+{
+    unix_manager_pcap_task_interrupted = 1;
 
+    json_object_set_new(answer, "message", json_string("Interrupted"));
+
+    return TM_ECODE_OK;
+}
 
 static void PcapFilesFree(PcapFiles *cfile)
 {
@@ -151,11 +174,22 @@ static void PcapFilesFree(PcapFiles *cfile)
  * \param this a UnixCommand:: structure
  * \param filename absolute filename
  * \param output_dir absolute name of directory where log will be put
+ * \param tenant_id Id of tenant associated with this file
+ * \param continuous If file should be run in continuous mode
+ * \param delay Delay required for file modified time before being processed
+ * \param poll_interval How frequently directory mode polls for new files
  *
  * \retval 0 in case of error, 1 in case of success
  */
-static TmEcode UnixListAddFile(PcapCommand *this,
-        const char *filename, const char *output_dir, int tenant_id)
+static TmEcode UnixListAddFile(
+    PcapCommand *this,
+    const char *filename,
+    const char *output_dir,
+    int tenant_id,
+    bool continuous,
+    time_t delay,
+    time_t poll_interval
+)
 {
     PcapFiles *cfile = NULL;
     if (filename == NULL || this == NULL)
@@ -185,6 +219,9 @@ static TmEcode UnixListAddFile(PcapCommand *this,
     }
 
     cfile->tenant_id = tenant_id;
+    cfile->continuous = continuous;
+    cfile->delay = delay;
+    cfile->poll_interval = poll_interval;
 
     TAILQ_INSERT_TAIL(&this->files, cfile, next);
     return TM_ECODE_OK;
@@ -200,10 +237,12 @@ static TmEcode UnixListAddFile(PcapCommand *this,
 static TmEcode UnixSocketAddPcapFile(json_t *cmd, json_t* answer, void *data)
 {
     PcapCommand *this = (PcapCommand *) data;
-    int ret;
     const char *filename;
     const char *output_dir;
     int tenant_id = 0;
+    bool continuous = false;
+    time_t delay = 30;
+    time_t poll_interval = 5;
 #ifdef OS_WIN32
     struct _stat st;
 #else
@@ -211,61 +250,100 @@ static TmEcode UnixSocketAddPcapFile(json_t *cmd, json_t* answer, void *data)
 #endif /* OS_WIN32 */
 
     json_t *jarg = json_object_get(cmd, "filename");
-    if(!json_is_string(jarg)) {
-        SCLogInfo("error: command is not a string");
-        json_object_set_new(answer, "message", json_string("command is not a string"));
+    if (!json_is_string(jarg)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "filename is not a string");
+        json_object_set_new(answer, "message",
+                            json_string("filename is not a string"));
         return TM_ECODE_FAILED;
     }
     filename = json_string_value(jarg);
 #ifdef OS_WIN32
-    if(_stat(filename, &st) != 0) {
+    if (_stat(filename, &st) != 0) {
 #else
-    if(stat(filename, &st) != 0) {
+    if (stat(filename, &st) != 0) {
 #endif /* OS_WIN32 */
-        json_object_set_new(answer, "message", json_string("File does not exist"));
+        json_object_set_new(answer, "message",
+                            json_string("filename does not exist"));
         return TM_ECODE_FAILED;
     }
 
     json_t *oarg = json_object_get(cmd, "output-dir");
     if (oarg != NULL) {
-        if(!json_is_string(oarg)) {
-            SCLogInfo("error: output dir is not a string");
-            json_object_set_new(answer, "message", json_string("output dir is not a string"));
+        if (!json_is_string(oarg)) {
+            SCLogError(SC_ERR_INVALID_ARGUMENT, "output-dir is not a string");
+
+            json_object_set_new(answer, "message",
+                                json_string("output-dir is not a string"));
             return TM_ECODE_FAILED;
         }
         output_dir = json_string_value(oarg);
     } else {
-        SCLogInfo("error: can't get output-dir");
-        json_object_set_new(answer, "message", json_string("output dir param is mandatory"));
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "can't get output-dir");
+
+        json_object_set_new(answer, "message",
+                            json_string("output-dir param is mandatory"));
         return TM_ECODE_FAILED;
     }
 
 #ifdef OS_WIN32
-    if(_stat(output_dir, &st) != 0) {
+    if (_stat(output_dir, &st) != 0) {
 #else
-    if(stat(output_dir, &st) != 0) {
+    if (stat(output_dir, &st) != 0) {
 #endif /* OS_WIN32 */
-        json_object_set_new(answer, "message", json_string("Output directory does not exist"));
+        json_object_set_new(answer, "message",
+                            json_string("output-dir does not exist"));
         return TM_ECODE_FAILED;
     }
 
     json_t *targ = json_object_get(cmd, "tenant");
     if (targ != NULL) {
-        if(!json_is_number(targ)) {
-            json_object_set_new(answer, "message", json_string("tenant is not a number"));
+        if (!json_is_integer(targ)) {
+            json_object_set_new(answer, "message",
+                                json_string("tenant is not a number"));
             return TM_ECODE_FAILED;
         }
         tenant_id = json_number_value(targ);
     }
 
-    ret = UnixListAddFile(this, filename, output_dir, tenant_id);
-    switch(ret) {
+    json_t *cont_arg = json_object_get(cmd, "continuous");
+    if (cont_arg != NULL) {
+        continuous = json_is_true(cont_arg);
+    }
+
+    json_t *delay_arg = json_object_get(cmd, "delay");
+    if (delay_arg != NULL) {
+        if (!json_is_integer(delay_arg)) {
+            SCLogError(SC_ERR_INVALID_ARGUMENT, "delay is not a integer");
+            json_object_set_new(answer, "message",
+                                json_string("delay is not a integer"));
+            return TM_ECODE_FAILED;
+        }
+        delay = json_integer_value(delay_arg);
+    }
+
+    json_t *interval_arg = json_object_get(cmd, "poll-interval");
+    if (interval_arg != NULL) {
+        if (!json_is_integer(interval_arg)) {
+            SCLogError(SC_ERR_INVALID_ARGUMENT, "poll-interval is not a integer");
+
+            json_object_set_new(answer, "message",
+                                json_string("poll-interval is not a integer"));
+            return TM_ECODE_FAILED;
+        }
+        poll_interval = json_integer_value(interval_arg);
+    }
+
+    switch (UnixListAddFile(this, filename, output_dir, tenant_id, continuous,
+                           delay, poll_interval)) {
         case TM_ECODE_FAILED:
-            json_object_set_new(answer, "message", json_string("Unable to add file to list"));
+        case TM_ECODE_DONE:
+            json_object_set_new(answer, "message",
+                                json_string("Unable to add file to list"));
             return TM_ECODE_FAILED;
         case TM_ECODE_OK:
             SCLogInfo("Added file '%s' to list", filename);
-            json_object_set_new(answer, "message", json_string("Successfully added file to list"));
+            json_object_set_new(answer, "message",
+                                json_string("Successfully added file to list"));
             return TM_ECODE_OK;
     }
     return TM_ECODE_OK;
@@ -287,21 +365,23 @@ static TmEcode UnixSocketAddPcapFile(json_t *cmd, json_t* answer, void *data)
 static TmEcode UnixSocketPcapFilesCheck(void *data)
 {
     PcapCommand *this = (PcapCommand *) data;
-    if (unix_manager_file_task_running == 1) {
+    if (unix_manager_pcap_task_running == 1) {
         return TM_ECODE_OK;
     }
-    if ((unix_manager_file_task_failed == 1) || (this->running == 1)) {
-        if (unix_manager_file_task_failed) {
+    if ((unix_manager_pcap_task_failed == 1) || (this->running == 1)) {
+        if (unix_manager_pcap_task_failed) {
             SCLogInfo("Preceeding task failed, cleaning the running mode");
         }
-        unix_manager_file_task_failed = 0;
+        unix_manager_pcap_task_failed = 0;
         this->running = 0;
-        if (this->currentfile) {
-            SCFree(this->currentfile);
-        }
-        this->currentfile = NULL;
 
+        SCLogInfo("Resetting engine state");
         PostRunDeinit(RUNMODE_PCAP_FILE, NULL /* no ts */);
+
+        if (this->current_file) {
+            PcapFilesFree(this->current_file);
+        }
+        this->current_file = NULL;
     }
     if (TAILQ_EMPTY(&this->files)) {
         // nothing to do
@@ -310,41 +390,73 @@ static TmEcode UnixSocketPcapFilesCheck(void *data)
 
     PcapFiles *cfile = TAILQ_FIRST(&this->files);
     TAILQ_REMOVE(&this->files, cfile, next);
-    SCLogInfo("Starting run for '%s'", cfile->filename);
-    unix_manager_file_task_running = 1;
+
+    unix_manager_pcap_task_running = 1;
     this->running = 1;
-    if (ConfSet("pcap-file.file", cfile->filename) != 1) {
-        SCLogError(SC_ERR_INVALID_ARGUMENTS,
-                "Can not set working file to '%s'", cfile->filename);
+
+    if (ConfSetFinal("pcap-file.file", cfile->filename) != 1) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "Can not set working file to '%s'",
+                   cfile->filename);
         PcapFilesFree(cfile);
         return TM_ECODE_FAILED;
     }
-    if (cfile->output_dir) {
-        if (ConfSet("default-log-dir", cfile->output_dir) != 1) {
-            SCLogError(SC_ERR_INVALID_ARGUMENTS,
-                    "Can not set output dir to '%s'", cfile->output_dir);
+
+    int set_res = 0;
+    if (cfile->continuous) {
+        set_res = ConfSetFinal("pcap-file.continuous", "true");
+    } else {
+        set_res = ConfSetFinal("pcap-file.continuous", "false");
+    }
+    if (set_res != 1) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "Can not set continuous mode for pcap processing");
+        PcapFilesFree(cfile);
+        return TM_ECODE_FAILED;
+    }
+
+    if (cfile->delay > 0) {
+        char tstr[32];
+        snprintf(tstr, sizeof(tstr), "%" PRIuMAX, (uintmax_t)cfile->delay);
+        if (ConfSetFinal("pcap-file.delay", tstr) != 1) {
+            SCLogError(SC_ERR_INVALID_ARGUMENT, "Can not set delay to '%s'", tstr);
             PcapFilesFree(cfile);
             return TM_ECODE_FAILED;
         }
     }
+
+    if (cfile->poll_interval > 0) {
+        char tstr[32];
+        snprintf(tstr, sizeof(tstr), "%" PRIuMAX, (uintmax_t)cfile->poll_interval);
+        if (ConfSetFinal("pcap-file.poll-interval", tstr) != 1) {
+            SCLogError(SC_ERR_INVALID_ARGUMENT,
+                       "Can not set poll-interval to '%s'", tstr);
+            PcapFilesFree(cfile);
+            return TM_ECODE_FAILED;
+        }
+    }
+
     if (cfile->tenant_id > 0) {
         char tstr[16];
         snprintf(tstr, sizeof(tstr), "%d", cfile->tenant_id);
-        if (ConfSet("pcap-file.tenant-id", tstr) != 1) {
-            SCLogError(SC_ERR_INVALID_ARGUMENTS,
-                    "Can not set working tenant-id to '%s'", tstr);
+        if (ConfSetFinal("pcap-file.tenant-id", tstr) != 1) {
+            SCLogError(SC_ERR_INVALID_ARGUMENT,
+                       "Can not set working tenant-id to '%s'", tstr);
             PcapFilesFree(cfile);
             return TM_ECODE_FAILED;
         }
     } else {
         SCLogInfo("pcap-file.tenant-id not set");
     }
-    this->currentfile = SCStrdup(cfile->filename);
-    if (unlikely(this->currentfile == NULL)) {
-        SCLogError(SC_ERR_MEM_ALLOC, "Failed file name allocation");
-        return TM_ECODE_FAILED;
+
+    if (cfile->output_dir) {
+        if (ConfSetFinal("default-log-dir", cfile->output_dir) != 1) {
+            SCLogError(SC_ERR_INVALID_ARGUMENT,
+                       "Can not set output dir to '%s'", cfile->output_dir);
+            PcapFilesFree(cfile);
+            return TM_ECODE_FAILED;
+        }
     }
-    PcapFilesFree(cfile);
+
+    this->current_file = cfile;
 
     PreRunInit(RUNMODE_PCAP_FILE);
     PreRunPostPrivsDropInit(RUNMODE_PCAP_FILE);
@@ -353,6 +465,9 @@ static TmEcode UnixSocketPcapFilesCheck(void *data)
     /* Un-pause all the paused threads */
     TmThreadWaitOnThreadInit();
     TmThreadContinueThreads();
+
+    SCLogInfo("Starting run for '%s'", this->current_file->filename);
+
     return TM_ECODE_OK;
 }
 #endif
@@ -369,24 +484,34 @@ void RunModeUnixSocketRegister(void)
                               RunModeUnixSocketMaster);
     default_mode = "autofp";
 #endif
-    return;
 }
 
-void UnixSocketPcapFile(TmEcode tm)
+TmEcode UnixSocketPcapFile(TmEcode tm, struct timespec *last_processed)
 {
 #ifdef BUILD_UNIX_SOCKET
+    unix_manager_pcap_last_processed.tv_sec = last_processed->tv_sec;
+    unix_manager_pcap_last_processed.tv_nsec = last_processed->tv_nsec;
     switch (tm) {
         case TM_ECODE_DONE:
-            unix_manager_file_task_running = 0;
-            break;
+            SCLogInfo("Marking current task as done");
+            unix_manager_pcap_task_running = 0;
+            return TM_ECODE_DONE;
         case TM_ECODE_FAILED:
-            unix_manager_file_task_running = 0;
-            unix_manager_file_task_failed = 1;
-            break;
+            SCLogInfo("Marking current task as failed");
+            unix_manager_pcap_task_running = 0;
+            unix_manager_pcap_task_failed = 1;
+            //if we return failed, we can't stop the thread and suricata will fail to close
+            return TM_ECODE_DONE;
         case TM_ECODE_OK:
-            break;
+            if (unix_manager_pcap_task_interrupted == 1) {
+                SCLogInfo("Interrupting current run mode");
+                return TM_ECODE_DONE;
+            } else {
+                return TM_ECODE_OK;
+            }
     }
 #endif
+    return TM_ECODE_FAILED;
 }
 
 #ifdef BUILD_UNIX_SOCKET
@@ -591,9 +716,9 @@ TmEcode UnixSocketRegisterTenant(json_t *cmd, json_t* answer, void *data)
     }
     filename = json_string_value(jarg);
 #ifdef OS_WIN32
-    if(_stat(filename, &st) != 0) {
+    if (_stat(filename, &st) != 0) {
 #else
-    if(stat(filename, &st) != 0) {
+    if (stat(filename, &st) != 0) {
 #endif /* OS_WIN32 */
         json_object_set_new(answer, "message", json_string("file does not exist"));
         return TM_ECODE_FAILED;
@@ -667,9 +792,9 @@ TmEcode UnixSocketReloadTenant(json_t *cmd, json_t* answer, void *data)
     }
     filename = json_string_value(jarg);
 #ifdef OS_WIN32
-    if(_stat(filename, &st) != 0) {
+    if (_stat(filename, &st) != 0) {
 #else
-    if(stat(filename, &st) != 0) {
+    if (stat(filename, &st) != 0) {
 #endif /* OS_WIN32 */
         json_object_set_new(answer, "message", json_string("file does not exist"));
         return TM_ECODE_FAILED;
@@ -1029,11 +1154,13 @@ static int RunModeUnixSocketMaster(void)
     }
     TAILQ_INIT(&pcapcmd->files);
     pcapcmd->running = 0;
-    pcapcmd->currentfile = NULL;
+    pcapcmd->current_file = NULL;
 
     UnixManagerRegisterCommand("pcap-file", UnixSocketAddPcapFile, pcapcmd, UNIX_CMD_TAKE_ARGS);
     UnixManagerRegisterCommand("pcap-file-number", UnixSocketPcapFilesNumber, pcapcmd, 0);
     UnixManagerRegisterCommand("pcap-file-list", UnixSocketPcapFilesList, pcapcmd, 0);
+    UnixManagerRegisterCommand("pcap-last-processed", UnixSocketPcapLastProcessed, pcapcmd, 0);
+    UnixManagerRegisterCommand("pcap-interrupt", UnixSocketPcapInterrupt, pcapcmd, 0);
     UnixManagerRegisterCommand("pcap-current", UnixSocketPcapCurrent, pcapcmd, 0);
 
     UnixManagerRegisterBackgroundTask(UnixSocketPcapFilesCheck, pcapcmd);
index 91de1c254fdb1cb4d2789bf7265bdc36b8ee552f..352cc41373877a58ce1644fc014794a0a5514030 100644 (file)
@@ -28,7 +28,7 @@ const char *RunModeUnixSocketGetDefaultMode(void);
 
 int RunModeUnixSocketIsActive(void);
 
-void UnixSocketPcapFile(TmEcode tm);
+TmEcode UnixSocketPcapFile(TmEcode tm, struct timespec *last_processed);
 
 #ifdef BUILD_UNIX_SOCKET
 TmEcode UnixSocketRegisterTenantHandler(json_t *cmd, json_t* answer, void *data);
diff --git a/src/source-pcap-file-directory-helper.c b/src/source-pcap-file-directory-helper.c
new file mode 100644 (file)
index 0000000..cd15a47
--- /dev/null
@@ -0,0 +1,535 @@
+/* Copyright (C) 2007-2016 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Danny Browning <danny.browning@protectwise.com>
+ *
+ * Helper methods for directory based packet acquisition
+ */
+
+#include "source-pcap-file-directory-helper.h"
+#include "runmode-unix-socket.h"
+#include "util-mem.h"
+
+static void GetTime(struct timespec *tm);
+static void CopyTime(struct timespec *from, struct timespec *to);
+static int CompareTimes(struct timespec *left, struct timespec *right);
+static TmEcode PcapRunStatus(PcapFileDirectoryVars *);
+static TmEcode PcapDirectoryFailure(PcapFileDirectoryVars *ptv);
+static TmEcode PcapDirectoryDone(PcapFileDirectoryVars *ptv);
+static int PcapDirectoryGetModifiedTime(char const * file, struct timespec * out);
+static TmEcode PcapDirectoryInsertFile(PcapFileDirectoryVars *pv,
+                                       PendingFile *file_to_add,
+                                       struct timespec *file_to_add_modified_time);
+static TmEcode PcapDirectoryPopulateBuffer(PcapFileDirectoryVars *ptv,
+                                           struct timespec * newer_than,
+                                           struct timespec * older_than);
+static TmEcode PcapDirectoryDispatchForTimeRange(PcapFileDirectoryVars *pv,
+                                                 struct timespec *newer_than,
+                                                 struct timespec *older_than);
+
+void GetTime(struct timespec *tm)
+{
+    struct timeval now;
+    if(gettimeofday(&now, NULL) == 0) {
+        tm->tv_sec  = now.tv_sec;
+        tm->tv_nsec = now.tv_usec * 1000;
+    }
+}
+
+void CopyTime(struct timespec *from, struct timespec *to)
+{
+    to->tv_sec = from->tv_sec;
+    to->tv_nsec = from->tv_nsec;
+}
+
+int CompareTimes(struct timespec *left, struct timespec *right)
+{
+    if (left->tv_sec < right->tv_sec) {
+        return -1;
+    } else if (left->tv_sec > right->tv_sec) {
+        return 1;
+    } else {
+        if (left->tv_nsec < right->tv_nsec) {
+            return -1;
+        } else if (left->tv_nsec > right->tv_nsec) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+}
+
+/**
+ * Pcap Folder Utilities
+ */
+TmEcode PcapRunStatus(PcapFileDirectoryVars *ptv)
+{
+    if (RunModeUnixSocketIsActive()) {
+        TmEcode done = UnixSocketPcapFile(TM_ECODE_OK, &ptv->shared->last_processed);
+        if ( (suricata_ctl_flags & SURICATA_STOP) || done != TM_ECODE_OK) {
+            SCReturnInt(TM_ECODE_DONE);
+        }
+    } else {
+        if (suricata_ctl_flags & SURICATA_STOP) {
+            SCReturnInt(TM_ECODE_DONE);
+        }
+    }
+    SCReturnInt(TM_ECODE_OK);
+}
+
+void CleanupPendingFile(PendingFile *pending) {
+    if (pending != NULL) {
+        if (pending->filename != NULL) {
+            SCFree(pending->filename);
+        }
+        SCFree(pending);
+    }
+}
+
+void CleanupPcapFileDirectoryVars(PcapFileDirectoryVars *ptv)
+{
+    if (ptv != NULL) {
+        if (ptv->current_file != NULL) {
+            CleanupPcapFileFileVars(ptv->current_file);
+            ptv->current_file = NULL;
+        }
+        if (ptv->directory != NULL) {
+            closedir(ptv->directory);
+            ptv->directory = NULL;
+        }
+        if (ptv->filename != NULL) {
+            SCFree(ptv->filename);
+        }
+        ptv->shared = NULL;
+        PendingFile *current_file = NULL;
+        while (!TAILQ_EMPTY(&ptv->directory_content)) {
+            current_file = TAILQ_FIRST(&ptv->directory_content);
+            TAILQ_REMOVE(&ptv->directory_content, current_file, next);
+            CleanupPendingFile(current_file);
+        }
+        SCFree(ptv);
+    }
+}
+
+TmEcode PcapDirectoryFailure(PcapFileDirectoryVars *ptv)
+{
+    TmEcode status = TM_ECODE_FAILED;
+
+    if (unlikely(ptv == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "Directory vars was null");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+    if (unlikely(ptv->shared == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "Directory shared vars was null");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    if (RunModeUnixSocketIsActive()) {
+        status = UnixSocketPcapFile(status, &ptv->shared->last_processed);
+    }
+
+    SCReturnInt(status);
+}
+
+TmEcode PcapDirectoryDone(PcapFileDirectoryVars *ptv)
+{
+    TmEcode status = TM_ECODE_DONE;
+
+    if (unlikely(ptv == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "Directory vars was null");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+    if (unlikely(ptv->shared == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "Directory shared vars was null");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    if (RunModeUnixSocketIsActive()) {
+        status = UnixSocketPcapFile(status, &ptv->shared->last_processed);
+    }
+
+    SCReturnInt(status);
+}
+
+TmEcode PcapDetermineDirectoryOrFile(char *filename, DIR **directory)
+{
+    DIR *temp_dir = NULL;
+    TmEcode return_code = TM_ECODE_FAILED;
+
+    temp_dir = opendir(filename);
+
+    if (temp_dir == NULL) {//if null, our filename may just be a normal file
+        switch (errno) {
+            case EACCES:
+                SCLogError(SC_ERR_FOPEN, "%s: Permission denied", filename);
+                break;
+
+            case EBADF:
+                SCLogError(SC_ERR_FOPEN,
+                           "%s: Not a valid file descriptor opened for reading",
+                           filename);
+                break;
+
+            case EMFILE:
+                SCLogError(SC_ERR_FOPEN,
+                           "%s: Per process open file descriptor limit reached",
+                           filename);
+                break;
+
+            case ENFILE:
+                SCLogError(SC_ERR_FOPEN,
+                           "%s: System wide open file descriptor limit reached",
+                           filename);
+                break;
+
+            case ENOENT:
+                SCLogError(SC_ERR_FOPEN,
+                           "%s: Does not exist, or name is an empty string",
+                           filename);
+                break;
+            case ENOMEM:
+                SCLogError(SC_ERR_FOPEN,
+                           "%s: Insufficient memory to complete the operation",
+                           filename);
+                break;
+
+            case ENOTDIR: //no error checking the directory, just is a plain file
+                SCLogInfo("%s: Plain file, not a directory", filename);
+                return_code = TM_ECODE_OK;
+                break;
+
+            default:
+                SCLogError(SC_ERR_FOPEN, "%s: %" PRId32, filename, errno);
+        }
+    } else {
+        //no error, filename references a directory
+        *directory = temp_dir;
+        return_code = TM_ECODE_OK;
+    }
+
+    return return_code;
+}
+
+int PcapDirectoryGetModifiedTime(char const *file, struct timespec *out)
+{
+    struct stat buf;
+    int ret;
+
+    if (file == NULL)
+        return -1;
+
+    if ((ret = stat(file, &buf)) != 0)
+        return ret;
+
+#ifdef OS_DARWIN
+    *out = buf.st_mtimespec;
+#else
+    *out = buf.st_mtim;
+#endif
+
+    return ret;
+}
+
+TmEcode PcapDirectoryInsertFile(PcapFileDirectoryVars *pv,
+                                PendingFile *file_to_add,
+                                struct timespec *file_to_add_modified_time
+) {
+    PendingFile *file_to_compare = NULL;
+    PendingFile *next_file_to_compare = NULL;
+
+    if (unlikely(pv == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "No directory vars passed");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    if (unlikely(file_to_add == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "File passed was null");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    if (unlikely(file_to_add->filename == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "File was passed with null filename");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    SCLogDebug("Inserting %s into directory buffer", file_to_add->filename);
+
+    if (TAILQ_EMPTY(&pv->directory_content)) {
+        TAILQ_INSERT_TAIL(&pv->directory_content, file_to_add, next);
+    } else {
+        file_to_compare = TAILQ_FIRST(&pv->directory_content);
+        while(file_to_compare != NULL) {
+            struct timespec modified_time;
+            memset(&modified_time, 0, sizeof(struct timespec));
+
+            if (PcapDirectoryGetModifiedTime(file_to_compare->filename,
+                                             &modified_time) == TM_ECODE_FAILED) {
+                SCReturnInt(TM_ECODE_FAILED);
+            }
+            if (CompareTimes(file_to_add_modified_time, &modified_time) < 0) {
+                TAILQ_INSERT_BEFORE(file_to_compare, file_to_add, next);
+                file_to_compare = NULL;
+            } else {
+                next_file_to_compare = TAILQ_NEXT(file_to_compare, next);
+                if (next_file_to_compare == NULL) {
+                    TAILQ_INSERT_AFTER(&pv->directory_content, file_to_compare,
+                                       file_to_add, next);
+                }
+                file_to_compare = next_file_to_compare;
+            }
+        }
+    }
+
+    SCReturnInt(TM_ECODE_OK);
+}
+
+TmEcode PcapDirectoryPopulateBuffer(PcapFileDirectoryVars *pv,
+                                    struct timespec *newer_than,
+                                    struct timespec *older_than
+) {
+    if (unlikely(pv == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "No directory vars passed");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+    if (unlikely(pv->filename == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "No directory filename was passed");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+    struct dirent * dir = NULL;
+    PendingFile *file_to_add = NULL;
+
+    while ((dir = readdir(pv->directory)) != NULL) {
+        if (dir->d_type != DT_REG) {
+            continue;
+        }
+
+        char pathbuff[PATH_MAX] = {0};
+
+        int written = 0;
+
+        written = snprintf(pathbuff, PATH_MAX, "%s/%s", pv->filename, dir->d_name);
+
+        if (written <= 0 || written >= PATH_MAX) {
+            SCLogError(SC_ERR_SPRINTF, "Could not write path");
+
+            SCReturnInt(TM_ECODE_FAILED);
+        } else {
+            struct timespec temp_time;
+            memset(&temp_time, 0, sizeof(struct timespec));
+
+            if (PcapDirectoryGetModifiedTime(pathbuff, &temp_time) == 0) {
+                SCLogDebug("File %s time (%lu > %lu < %lu)", pathbuff,
+                           newer_than->tv_sec, temp_time.tv_sec, older_than->tv_sec);
+
+                // Skip files outside of our time range
+                if (CompareTimes(&temp_time, newer_than) < 0) {
+                    SCLogDebug("Skipping old file %s", pathbuff);
+                    continue;
+                }
+                else if (CompareTimes(&temp_time, older_than) >= 0) {
+                    SCLogDebug("Skipping new file %s", pathbuff);
+                    continue;
+                }
+            } else {
+                SCLogDebug("Unable to get modified time on %s, skipping", pathbuff);
+                continue;
+            }
+
+            file_to_add = SCCalloc(1, sizeof(PendingFile));
+            if (unlikely(file_to_add == NULL)) {
+                SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate pending file");
+
+                SCReturnInt(TM_ECODE_FAILED);
+            }
+
+            file_to_add->filename = SCStrdup(pathbuff);
+            if (unlikely(file_to_add->filename == NULL)) {
+                SCLogError(SC_ERR_MEM_ALLOC, "Failed to copy filename");
+                CleanupPendingFile(file_to_add);
+
+                SCReturnInt(TM_ECODE_FAILED);
+            }
+
+            SCLogDebug("Found \"%s\"", file_to_add->filename);
+
+            if (PcapDirectoryInsertFile(pv, file_to_add, &temp_time) == TM_ECODE_FAILED) {
+                SCLogError(SC_ERR_INVALID_ARGUMENT, "Failed to add file");
+                CleanupPendingFile(file_to_add);
+
+                SCReturnInt(TM_ECODE_FAILED);
+            }
+        }
+    }
+
+    SCReturnInt(TM_ECODE_OK);
+}
+
+
+TmEcode PcapDirectoryDispatchForTimeRange(PcapFileDirectoryVars *pv,
+                                          struct timespec *newer_than,
+                                          struct timespec *older_than)
+{
+    if (PcapDirectoryPopulateBuffer(pv, newer_than, older_than) == TM_ECODE_FAILED) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "Failed to populate directory buffer");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    TmEcode status = TM_ECODE_OK;
+
+    if (TAILQ_EMPTY(&pv->directory_content)) {
+        SCLogInfo("Directory %s has no files to process", pv->filename);
+        GetTime(older_than);
+        older_than->tv_sec = older_than->tv_sec - pv->delay;
+        rewinddir(pv->directory);
+        status = TM_ECODE_OK;
+    } else {
+        PendingFile *current_file = NULL;
+
+        while (status == TM_ECODE_OK && !TAILQ_EMPTY(&pv->directory_content)) {
+            current_file = TAILQ_FIRST(&pv->directory_content);
+            TAILQ_REMOVE(&pv->directory_content, current_file, next);
+
+            if (unlikely(current_file == NULL)) {
+                SCLogWarning(SC_ERR_PCAP_DISPATCH, "Current file was null");
+            } else if (unlikely(current_file->filename == NULL)) {
+                SCLogWarning(SC_ERR_PCAP_DISPATCH, "Current file filename was null");
+            } else {
+                SCLogDebug("Processing file %s", current_file->filename);
+
+                PcapFileFileVars *pftv = SCMalloc(sizeof(PcapFileFileVars));
+                if (unlikely(pftv == NULL)) {
+                    SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate PcapFileFileVars");
+                    SCReturnInt(TM_ECODE_FAILED);
+                }
+                memset(pftv, 0, sizeof(PcapFileFileVars));
+
+                pftv->filename = SCStrdup(current_file->filename);
+                if (unlikely(pftv->filename == NULL)) {
+                    SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate filename");
+                    SCReturnInt(TM_ECODE_FAILED);
+                }
+                pftv->shared = pv->shared;
+
+                if (InitPcapFile(pftv) == TM_ECODE_FAILED) {
+                    SCLogWarning(SC_ERR_PCAP_DISPATCH,
+                                 "Failed to init pcap file %s, skipping",
+                                 current_file->filename);
+                    CleanupPendingFile(current_file);
+                    CleanupPcapFileFileVars(pftv);
+                    status = TM_ECODE_OK;
+                } else {
+                    pv->current_file = pftv;
+
+                    status = PcapFileDispatch(pftv);
+
+                    CleanupPcapFileFileVars(pftv);
+
+                    if (status == TM_ECODE_FAILED) {
+                        CleanupPendingFile(current_file);
+                        SCReturnInt(status);
+                    }
+
+                    struct timespec temp_time;
+                    memset(&temp_time, 0, sizeof(struct timespec));
+
+                    if (PcapDirectoryGetModifiedTime(current_file->filename,
+                                                     &temp_time) != 0) {
+                        CopyTime(&temp_time, newer_than);
+                    }
+                    SCLogDebug("Processed file %s, processed up to %ld",
+                               current_file->filename, temp_time.tv_sec);
+                    CopyTime(&temp_time, &pv->shared->last_processed);
+
+                    CleanupPendingFile(current_file);
+                    pv->current_file = NULL;
+
+                    status = PcapRunStatus(pv);
+                }
+            }
+        }
+
+        *newer_than = *older_than;
+    }
+    GetTime(older_than);
+    older_than->tv_sec = older_than->tv_sec - pv->delay;
+
+    SCReturnInt(status);
+}
+
+TmEcode PcapDirectoryDispatch(PcapFileDirectoryVars *ptv)
+{
+    SCEnter();
+
+    DIR *directory_check = NULL;
+
+    struct timespec newer_than;
+    memset(&newer_than, 0, sizeof(struct timespec));
+    struct timespec older_than;
+    memset(&older_than, 0, sizeof(struct timespec));
+    older_than.tv_sec = LONG_MAX;
+    uint32_t poll_seconds = (uint32_t)localtime(&ptv->poll_interval)->tm_sec;
+
+    if (ptv->should_loop) {
+        GetTime(&older_than);
+        older_than.tv_sec = older_than.tv_sec - ptv->delay;
+    }
+    TmEcode status = TM_ECODE_OK;
+
+    while (status == TM_ECODE_OK) {
+        //loop while directory is ok
+        SCLogInfo("Processing pcaps directory %s, files must be newer than %" PRIuMAX " and older than %" PRIuMAX,
+                  ptv->filename, (uintmax_t)newer_than.tv_sec, (uintmax_t)older_than.tv_sec);
+        status = PcapDirectoryDispatchForTimeRange(ptv, &newer_than, &older_than);
+        if (ptv->should_loop && status == TM_ECODE_OK) {
+            sleep(poll_seconds);
+            //update our status based on suricata control flags or unix command socket
+            status = PcapRunStatus(ptv);
+            if (status == TM_ECODE_OK) {
+                SCLogDebug("Checking if directory %s still exists", ptv->filename);
+                //check directory
+                if (PcapDetermineDirectoryOrFile(ptv->filename,
+                                                 &directory_check) == TM_ECODE_FAILED) {
+                    SCLogInfo("Directory %s no longer exists, stopping",
+                              ptv->filename);
+                    status = TM_ECODE_DONE;
+                } else {
+                    closedir(directory_check);
+                }
+            }
+        } else if (status == TM_ECODE_OK) { //not looping, mark done
+            SCLogDebug("Not looping, stopping directory mode");
+            status = TM_ECODE_DONE;
+        }
+    }
+
+    StatsSyncCountersIfSignalled(ptv->shared->tv);
+
+    if (status == TM_ECODE_FAILED) {
+        SCLogError(SC_ERR_PCAP_DISPATCH, "Directory %s run mode failed", ptv->filename);
+        status = PcapDirectoryFailure(ptv);
+    } else {
+        SCLogInfo("Directory run mode complete");
+        status = PcapDirectoryDone(ptv);
+    }
+
+    SCReturnInt(status);
+}
+
+/* eof */
diff --git a/src/source-pcap-file-directory-helper.h b/src/source-pcap-file-directory-helper.h
new file mode 100644 (file)
index 0000000..496e837
--- /dev/null
@@ -0,0 +1,83 @@
+/* Copyright (C) 2007-2010 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Danny Browning <danny.browning@protectwise.com>
+ */
+
+#include "suricata-common.h"
+#include "source-pcap-file-helper.h"
+#include "queue.h"
+
+#ifndef __SOURCE_PCAP_FILE_DIRECTORY_HELPER_H__
+#define __SOURCE_PCAP_FILE_DIRECTORY_HELPER_H__
+
+typedef struct PendingFile_
+{
+    char *filename;
+    TAILQ_ENTRY(PendingFile_) next;
+} PendingFile;
+/**
+ * Data specific to a directory of pcap files
+ */
+typedef struct PcapFileDirectoryVars_
+{
+    char *filename;
+    DIR *directory;
+    PcapFileFileVars *current_file;
+    bool should_loop;
+    time_t delay;
+    time_t poll_interval;
+
+    TAILQ_HEAD(PendingFiles, PendingFile_) directory_content;
+
+    PcapFileSharedVars *shared;
+} PcapFileDirectoryVars;
+
+/**
+ * Cleanup resources associated with a PendingFile object
+ * @param pending Object to be cleaned up
+ */
+void CleanupPendingFile(PendingFile *pending);
+
+/**
+ * Cleanup resources associated with a PcapFileDirectoryVars object
+ * @param ptv Object to be cleaned up
+ */
+void CleanupPcapFileDirectoryVars(PcapFileDirectoryVars *ptv);
+
+/**
+ * Determine if a given string represents a file or directory. If a directory,
+ * populate the directory object.
+ * @param filename String to check
+ * @param directory Directory point to populate if directory
+ * @return TM_ECODE_OK if string or directory
+ */
+TmEcode PcapDetermineDirectoryOrFile(char *filename, DIR **directory);
+
+/**
+ * Dispatch a directory for processing, where information for processing the
+ * directory is contained in a PcapFileDirectoryVars object
+ * @param ptv PcapFileDirectoryVars object containing information for processing
+ * the directory
+ * @return
+ */
+TmEcode PcapDirectoryDispatch(PcapFileDirectoryVars *ptv);
+
+#endif /* __SOURCE_PCAP_FILE_DIRECTORY_HELPER_H__ */
diff --git a/src/source-pcap-file-helper.c b/src/source-pcap-file-helper.c
new file mode 100644 (file)
index 0000000..40b753f
--- /dev/null
@@ -0,0 +1,235 @@
+/* Copyright (C) 2007-2016 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Danny Browning <danny.browning@protectwise.com>
+ *
+ * File based pcap packet acquisition support
+ */
+
+#include "source-pcap-file-helper.h"
+#include "util-checksum.h"
+#include "util-profiling.h"
+
+#ifdef __SC_CUDA_SUPPORT__
+
+#include "util-cuda.h"
+#include "util-cuda-buffer.h"
+#include "util-mpm-ac.h"
+#include "util-cuda-handlers.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "util-cuda-vars.h"
+
+#endif /* __SC_CUDA_SUPPORT__ */
+
+extern int max_pending_packets;
+extern PcapFileGlobalVars pcap_g;
+
+static void PcapFileCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt);
+
+void CleanupPcapFileFileVars(PcapFileFileVars *pfv)
+{
+    if (pfv != NULL) {
+        if (pfv->pcap_handle != NULL) {
+            pcap_close(pfv->pcap_handle);
+            pfv->pcap_handle = NULL;
+        }
+        if (pfv->filename != NULL) {
+            SCFree(pfv->filename);
+            pfv->filename = NULL;
+        }
+        pfv->shared = NULL;
+        SCFree(pfv);
+    }
+}
+
+void PcapFileCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt)
+{
+    SCEnter();
+
+    PcapFileFileVars *ptv = (PcapFileFileVars *)user;
+    Packet *p = PacketGetFromQueueOrAlloc();
+
+    if (unlikely(p == NULL)) {
+        SCReturn;
+    }
+    PACKET_PROFILING_TMM_START(p, TMM_RECEIVEPCAPFILE);
+
+    PKT_SET_SRC(p, PKT_SRC_WIRE);
+    p->ts.tv_sec = h->ts.tv_sec;
+    p->ts.tv_usec = h->ts.tv_usec;
+    SCLogDebug("p->ts.tv_sec %"PRIuMAX"", (uintmax_t)p->ts.tv_sec);
+    p->datalink = ptv->datalink;
+    p->pcap_cnt = ++pcap_g.cnt;
+
+    p->pcap_v.tenant_id = ptv->shared->tenant_id;
+    ptv->shared->pkts++;
+    ptv->shared->bytes += h->caplen;
+
+    if (unlikely(PacketCopyData(p, pkt, h->caplen))) {
+        TmqhOutputPacketpool(ptv->shared->tv, p);
+        PACKET_PROFILING_TMM_END(p, TMM_RECEIVEPCAPFILE);
+        SCReturn;
+    }
+
+    /* We only check for checksum disable */
+    if (pcap_g.checksum_mode == CHECKSUM_VALIDATION_DISABLE) {
+        p->flags |= PKT_IGNORE_CHECKSUM;
+    } else if (pcap_g.checksum_mode == CHECKSUM_VALIDATION_AUTO) {
+        if (ChecksumAutoModeCheck(ptv->shared->pkts, p->pcap_cnt,
+                                  SC_ATOMIC_GET(pcap_g.invalid_checksums))) {
+            pcap_g.checksum_mode = CHECKSUM_VALIDATION_DISABLE;
+            p->flags |= PKT_IGNORE_CHECKSUM;
+        }
+    }
+
+    PACKET_PROFILING_TMM_END(p, TMM_RECEIVEPCAPFILE);
+
+    if (TmThreadsSlotProcessPkt(ptv->shared->tv, ptv->shared->slot, p) != TM_ECODE_OK) {
+        pcap_breakloop(ptv->pcap_handle);
+        ptv->shared->cb_result = TM_ECODE_FAILED;
+    }
+
+    SCReturn;
+}
+
+/**
+ *  \brief Main PCAP file reading Loop function
+ */
+TmEcode PcapFileDispatch(PcapFileFileVars *ptv)
+{
+    SCEnter();
+
+    int packet_q_len = 64;
+    int r;
+    TmEcode loop_result = TM_ECODE_OK;
+
+    while (loop_result == TM_ECODE_OK) {
+        if (suricata_ctl_flags & SURICATA_STOP) {
+            SCReturnInt(TM_ECODE_OK);
+        }
+
+        /* make sure we have at least one packet in the packet pool, to prevent
+         * us from alloc'ing packets at line rate */
+        PacketPoolWait();
+
+        /* Right now we just support reading packets one at a time. */
+        r = pcap_dispatch(ptv->pcap_handle, packet_q_len,
+                          (pcap_handler)PcapFileCallbackLoop, (u_char *)ptv);
+        if (unlikely(r == -1)) {
+            SCLogError(SC_ERR_PCAP_DISPATCH, "error code %" PRId32 " %s for %s",
+                       r, pcap_geterr(ptv->pcap_handle), ptv->filename);
+            if (ptv->shared->cb_result == TM_ECODE_FAILED) {
+                SCReturnInt(TM_ECODE_FAILED);
+            }
+            loop_result = TM_ECODE_DONE;
+        } else if (unlikely(r == 0)) {
+            SCLogInfo("pcap file %s end of file reached (pcap err code %" PRId32 ")",
+                      ptv->filename, r);
+            ptv->shared->files++;
+            loop_result = TM_ECODE_DONE;
+        } else if (ptv->shared->cb_result == TM_ECODE_FAILED) {
+            SCLogError(SC_ERR_PCAP_DISPATCH,
+                       "Pcap callback PcapFileCallbackLoop failed for %s", ptv->filename);
+            loop_result = TM_ECODE_FAILED;
+        }
+        StatsSyncCountersIfSignalled(ptv->shared->tv);
+    }
+
+    SCReturnInt(loop_result);
+}
+
+TmEcode InitPcapFile(PcapFileFileVars *pfv)
+{
+    char errbuf[PCAP_ERRBUF_SIZE] = "";
+
+    if(unlikely(pfv->filename == NULL)) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT, "Filename was null");
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    pfv->pcap_handle = pcap_open_offline(pfv->filename, errbuf);
+    if (pfv->pcap_handle == NULL) {
+        SCLogError(SC_ERR_FOPEN, "%s", errbuf);
+        if (!RunModeUnixSocketIsActive()) {
+            SCReturnInt(TM_ECODE_FAILED);
+        } else {
+            UnixSocketPcapFile(TM_ECODE_FAILED, 0);
+            SCReturnInt(TM_ECODE_DONE);
+        }
+    }
+
+    if (pfv->shared != NULL && pfv->shared->bpf_string != NULL) {
+        SCLogInfo("using bpf-filter \"%s\"", pfv->shared->bpf_string);
+
+        if (pcap_compile(pfv->pcap_handle, &pfv->filter, pfv->shared->bpf_string, 1, 0) < 0) {
+            SCLogError(SC_ERR_BPF, "bpf compilation error %s for %s",
+                       pcap_geterr(pfv->pcap_handle), pfv->filename);
+            SCReturnInt(TM_ECODE_FAILED);
+        }
+
+        if (pcap_setfilter(pfv->pcap_handle, &pfv->filter) < 0) {
+            SCLogError(SC_ERR_BPF,"could not set bpf filter %s for %s",
+                       pcap_geterr(pfv->pcap_handle), pfv->filename);
+            SCReturnInt(TM_ECODE_FAILED);
+        }
+    }
+
+    pfv->datalink = pcap_datalink(pfv->pcap_handle);
+    SCLogDebug("datalink %" PRId32 "", pfv->datalink);
+
+    Decoder temp;
+    TmEcode validated = ValidateLinkType(pfv->datalink, &temp);
+    SCReturnInt(validated);
+}
+
+TmEcode ValidateLinkType(int datalink, Decoder *decoder)
+{
+    switch (datalink) {
+        case LINKTYPE_LINUX_SLL:
+            *decoder = DecodeSll;
+            break;
+        case LINKTYPE_ETHERNET:
+            *decoder = DecodeEthernet;
+            break;
+        case LINKTYPE_PPP:
+            *decoder = DecodePPP;
+            break;
+        case LINKTYPE_IPV4:
+        case LINKTYPE_RAW:
+        case LINKTYPE_RAW2:
+            *decoder = DecodeRaw;
+            break;
+        case LINKTYPE_NULL:
+            *decoder = DecodeNull;
+            break;
+
+        default:
+            SCLogError(
+                    SC_ERR_UNIMPLEMENTED,
+                    "datalink type %" PRId32 " not (yet) supported in module PcapFile.",
+                    datalink
+            );
+            SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    SCReturnInt(TM_ECODE_OK);
+}
+/* eof */
diff --git a/src/source-pcap-file-helper.h b/src/source-pcap-file-helper.h
new file mode 100644 (file)
index 0000000..6c9a705
--- /dev/null
@@ -0,0 +1,109 @@
+/* Copyright (C) 2007-2010 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Danny Browning <danny.browning@protectwise.com>
+ */
+
+#include "suricata-common.h"
+#include "tm-threads.h"
+
+#ifndef __SOURCE_PCAP_FILE_HELPER_H__
+#define __SOURCE_PCAP_FILE_HELPER_H__
+
+typedef struct PcapFileGlobalVars_ {
+    uint64_t cnt; /** packet counter */
+    ChecksumValidationMode conf_checksum_mode;
+    ChecksumValidationMode checksum_mode;
+    SC_ATOMIC_DECLARE(unsigned int, invalid_checksums);
+} PcapFileGlobalVars;
+
+/**
+ * Data that is shared amongst File, Directory, and Thread level vars
+ */
+typedef struct PcapFileSharedVars_
+{
+    char *bpf_string;
+
+    uint32_t tenant_id;
+
+    struct timespec last_processed;
+
+    ThreadVars *tv;
+    TmSlot *slot;
+
+    /* counters */
+    uint64_t pkts;
+    uint64_t bytes;
+    uint64_t files;
+
+    uint8_t done;
+    uint32_t errs;
+
+    /** callback result -- set if one of the thread module failed. */
+    int cb_result;
+} PcapFileSharedVars;
+
+/**
+ * Data specific to a single pcap file
+ */
+typedef struct PcapFileFileVars_
+{
+    char *filename;
+    pcap_t *pcap_handle;
+
+    int datalink;
+    struct bpf_program filter;
+
+    PcapFileSharedVars *shared;
+} PcapFileFileVars;
+
+typedef int (*Decoder)(ThreadVars *, DecodeThreadVars *, Packet *, uint8_t *, uint16_t, PacketQueue *);
+
+/**
+ * Dispatch a file for processing, where the information necessary to process that
+ * file is as PcapFileFileVars object.
+ * @param ptv PcapFileFileVars object to be processed
+ * @return
+ */
+TmEcode PcapFileDispatch(PcapFileFileVars *ptv);
+
+/**
+ * From a PcapFileFileVars, prepare the filename for processing by setting
+ * pcap_handle, datalink, and filter
+ * @param pfv PcapFileFileVars object to populate
+ * @return
+ */
+TmEcode InitPcapFile(PcapFileFileVars *pfv);
+
+/**
+ * Cleanup resources associated with a PcapFileFileVars object.
+ * @param pfv Object to be cleaned up
+ */
+void CleanupPcapFileFileVars(PcapFileFileVars *pfv);
+
+/**
+ * Determine if a datalink type is valid, setting a decoder function if valid.
+ * @param datalink Datalink type to validate
+ * @param decoder Pointer to decoder to set if valid
+ * @return TM_ECODE_OK if valid datalink type and decoder has been set.
+ */
+TmEcode ValidateLinkType(int datalink, Decoder *decoder);
+
+#endif /* __SOURCE_PCAP_FILE_HELPER_H__ */
index c71bb6bccac723cd01436f3022295aa58a458ace..cbb7bcef36da0933c21dec9eb9277047f17058b9 100644 (file)
  */
 
 #include "suricata-common.h"
-#include "suricata.h"
-#include "decode.h"
-#include "packet-queue.h"
-#include "threads.h"
-#include "threadvars.h"
-#include "tm-queuehandlers.h"
 #include "source-pcap-file.h"
-#include "util-time.h"
-#include "util-debug.h"
-#include "conf.h"
-#include "util-error.h"
-#include "util-privs.h"
-#include "tmqh-packetpool.h"
-#include "tm-threads.h"
-#include "util-optimize.h"
+#include "source-pcap-file-helper.h"
+#include "source-pcap-file-directory-helper.h"
 #include "flow-manager.h"
-#include "util-profiling.h"
-#include "runmode-unix-socket.h"
 #include "util-checksum.h"
-#include "util-atomic.h"
 
 #ifdef __SC_CUDA_SUPPORT__
 
 #endif /* __SC_CUDA_SUPPORT__ */
 
 extern int max_pending_packets;
+PcapFileGlobalVars pcap_g;
 
-typedef struct PcapFileGlobalVars_ {
-    pcap_t *pcap_handle;
-    int (*Decoder)(ThreadVars *, DecodeThreadVars *, Packet *, uint8_t *, uint16_t, PacketQueue *);
-    int datalink;
-    struct bpf_program filter;
-    uint64_t cnt; /** packet counter */
-    ChecksumValidationMode conf_checksum_mode;
-    ChecksumValidationMode checksum_mode;
-    SC_ATOMIC_DECLARE(unsigned int, invalid_checksums);
-
-} PcapFileGlobalVars;
+/**
+ * Union determining whether the behavior of the thread is file or directory
+ */
+typedef union PcapFileBehaviorVar_
+{
+    PcapFileDirectoryVars *directory;
+    PcapFileFileVars *file;
+} PcapFileBehaviorVar;
 
+/**
+ * Data specific to the thread
+ */
 typedef struct PcapFileThreadVars_
 {
-    uint32_t tenant_id;
+    PcapFileBehaviorVar behavior;
+    bool is_directory;
 
-    /* counters */
-    uint32_t pkts;
-    uint64_t bytes;
-
-    ThreadVars *tv;
-    TmSlot *slot;
+    PcapFileSharedVars shared;
+} PcapFileThreadVars;
 
-    /** callback result -- set if one of the thread module failed. */
-    int cb_result;
+static TmEcode ReceivePcapFileLoop(ThreadVars *, void *, void *);
+static TmEcode ReceivePcapFileThreadInit(ThreadVars *, const void *, void **);
+static void ReceivePcapFileThreadExitStats(ThreadVars *, void *);
+static TmEcode ReceivePcapFileThreadDeinit(ThreadVars *, void *);
 
-    uint8_t done;
-    uint32_t errs;
-} PcapFileThreadVars;
+static TmEcode DecodePcapFile(ThreadVars *, Packet *, void *, PacketQueue *,
+                              PacketQueue *);
+static TmEcode DecodePcapFileThreadInit(ThreadVars *, const void *, void **);
+static TmEcode DecodePcapFileThreadDeinit(ThreadVars *tv, void *data);
 
-static PcapFileGlobalVars pcap_g;
+static void CleanupPcapDirectoryFromThreadVars(PcapFileThreadVars *tv,
+                                               PcapFileDirectoryVars *ptv);
+static void CleanupPcapFileFromThreadVars(PcapFileThreadVars *tv, PcapFileFileVars *pfv);
+static void CleanupPcapFileThreadVars(PcapFileThreadVars *tv);
 
-TmEcode ReceivePcapFileLoop(ThreadVars *, void *, void *);
+void CleanupPcapFileFromThreadVars(PcapFileThreadVars *tv, PcapFileFileVars *pfv)
+{
+    CleanupPcapFileFileVars(pfv);
+    if (tv->is_directory == 0) {
+        tv->behavior.file = NULL;
+    }
+}
 
-TmEcode ReceivePcapFileThreadInit(ThreadVars *, const void *, void **);
-void ReceivePcapFileThreadExitStats(ThreadVars *, void *);
-TmEcode ReceivePcapFileThreadDeinit(ThreadVars *, void *);
+void CleanupPcapDirectoryFromThreadVars(PcapFileThreadVars *tv, PcapFileDirectoryVars *ptv)
+{
+    CleanupPcapFileDirectoryVars(ptv);
+    if (tv->is_directory == 1) {
+        tv->behavior.directory = NULL;
+    }
+}
 
-TmEcode DecodePcapFile(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
-TmEcode DecodePcapFileThreadInit(ThreadVars *, const void *, void **);
-TmEcode DecodePcapFileThreadDeinit(ThreadVars *tv, void *data);
+void CleanupPcapFileThreadVars(PcapFileThreadVars *tv)
+{
+    if(tv != NULL) {
+        if (tv->is_directory == 0) {
+            if (tv->behavior.file != NULL) {
+                CleanupPcapFileFromThreadVars(tv, tv->behavior.file);
+            }
+            tv->behavior.file = NULL;
+        } else {
+            if (tv->behavior.directory != NULL) {
+                CleanupPcapDirectoryFromThreadVars(tv, tv->behavior.directory);
+            }
+            tv->behavior.directory = NULL;
+        }
+        if (tv->shared.bpf_string != NULL) {
+            SCFree(tv->shared.bpf_string);
+            tv->shared.bpf_string = NULL;
+        }
+        SCFree(tv);
+    }
+}
 
+/**
+ * Pcap File Functionality
+ */
 void TmModuleReceivePcapFileRegister (void)
 {
     tmm_modules[TMM_RECEIVEPCAPFILE].name = "ReceivePcapFile";
@@ -133,139 +153,54 @@ void PcapFileGlobalInit()
     SC_ATOMIC_INIT(pcap_g.invalid_checksums);
 }
 
-static void PcapFileCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt)
-{
-    SCEnter();
-
-    PcapFileThreadVars *ptv = (PcapFileThreadVars *)user;
-    Packet *p = PacketGetFromQueueOrAlloc();
-
-    if (unlikely(p == NULL)) {
-        SCReturn;
-    }
-    PACKET_PROFILING_TMM_START(p, TMM_RECEIVEPCAPFILE);
-
-    PKT_SET_SRC(p, PKT_SRC_WIRE);
-    p->ts.tv_sec = h->ts.tv_sec;
-    p->ts.tv_usec = h->ts.tv_usec;
-    SCLogDebug("p->ts.tv_sec %"PRIuMAX"", (uintmax_t)p->ts.tv_sec);
-    p->datalink = pcap_g.datalink;
-    p->pcap_cnt = ++pcap_g.cnt;
-
-    p->pcap_v.tenant_id = ptv->tenant_id;
-    ptv->pkts++;
-    ptv->bytes += h->caplen;
-
-    if (unlikely(PacketCopyData(p, pkt, h->caplen))) {
-        TmqhOutputPacketpool(ptv->tv, p);
-        PACKET_PROFILING_TMM_END(p, TMM_RECEIVEPCAPFILE);
-        SCReturn;
-    }
-
-    /* We only check for checksum disable */
-    if (pcap_g.checksum_mode == CHECKSUM_VALIDATION_DISABLE) {
-        p->flags |= PKT_IGNORE_CHECKSUM;
-    } else if (pcap_g.checksum_mode == CHECKSUM_VALIDATION_AUTO) {
-        if (ChecksumAutoModeCheck(ptv->pkts, p->pcap_cnt,
-                                  SC_ATOMIC_GET(pcap_g.invalid_checksums))) {
-            pcap_g.checksum_mode = CHECKSUM_VALIDATION_DISABLE;
-            p->flags |= PKT_IGNORE_CHECKSUM;
-        }
-    }
-
-    PACKET_PROFILING_TMM_END(p, TMM_RECEIVEPCAPFILE);
-
-    if (TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p) != TM_ECODE_OK) {
-        pcap_breakloop(pcap_g.pcap_handle);
-        ptv->cb_result = TM_ECODE_FAILED;
-    }
-
-    SCReturn;
-}
-
-/**
- *  \brief Main PCAP file reading Loop function
- */
 TmEcode ReceivePcapFileLoop(ThreadVars *tv, void *data, void *slot)
 {
     SCEnter();
 
-    int packet_q_len = 64;
-    PcapFileThreadVars *ptv = (PcapFileThreadVars *)data;
-    int r;
+    TmEcode status = TM_ECODE_OK;
+    PcapFileThreadVars *ptv = (PcapFileThreadVars *) data;
     TmSlot *s = (TmSlot *)slot;
 
-    ptv->slot = s->slot_next;
-    ptv->cb_result = TM_ECODE_OK;
+    ptv->shared.slot = s->slot_next;
+    ptv->shared.cb_result = TM_ECODE_OK;
 
-    while (1) {
-        if (suricata_ctl_flags & SURICATA_STOP) {
-            SCReturnInt(TM_ECODE_OK);
-        }
-
-        /* make sure we have at least one packet in the packet pool, to prevent
-         * us from alloc'ing packets at line rate */
-        PacketPoolWait();
-
-        /* Right now we just support reading packets one at a time. */
-        r = pcap_dispatch(pcap_g.pcap_handle, packet_q_len,
-                          (pcap_handler)PcapFileCallbackLoop, (u_char *)ptv);
-        if (unlikely(r == -1)) {
-            SCLogError(SC_ERR_PCAP_DISPATCH, "error code %" PRId32 " %s",
-                       r, pcap_geterr(pcap_g.pcap_handle));
-            if (ptv->cb_result == TM_ECODE_FAILED) {
-                SCReturnInt(TM_ECODE_FAILED);
-            }
-            if (! RunModeUnixSocketIsActive()) {
-                EngineStop();
-            } else {
-                pcap_close(pcap_g.pcap_handle);
-                pcap_g.pcap_handle = NULL;
-                UnixSocketPcapFile(TM_ECODE_DONE);
-                SCReturnInt(TM_ECODE_DONE);
-            }
-        } else if (unlikely(r == 0)) {
-            SCLogInfo("pcap file end of file reached (pcap err code %" PRId32 ")", r);
-            if (! RunModeUnixSocketIsActive()) {
-                EngineStop();
-            } else {
-                pcap_close(pcap_g.pcap_handle);
-                pcap_g.pcap_handle = NULL;
-                UnixSocketPcapFile(TM_ECODE_DONE);
-                SCReturnInt(TM_ECODE_DONE);
-            }
-            break;
-        } else if (ptv->cb_result == TM_ECODE_FAILED) {
-            SCLogError(SC_ERR_PCAP_DISPATCH, "Pcap callback PcapFileCallbackLoop failed");
-            if (! RunModeUnixSocketIsActive()) {
-                SCReturnInt(TM_ECODE_FAILED);
-            } else {
-                pcap_close(pcap_g.pcap_handle);
-                pcap_g.pcap_handle = NULL;
-                UnixSocketPcapFile(TM_ECODE_DONE);
-                SCReturnInt(TM_ECODE_DONE);
-            }
+    if(ptv->is_directory == 0) {
+        SCLogInfo("Starting file run for %s", ptv->behavior.file->filename);
+        status = PcapFileDispatch(ptv->behavior.file);
+        if (!RunModeUnixSocketIsActive()) {
+            EngineStop();
+        } else {
+            UnixSocketPcapFile(status, &ptv->shared.last_processed);
         }
-        StatsSyncCountersIfSignalled(tv);
+        CleanupPcapFileFromThreadVars(ptv, ptv->behavior.file);
+    } else {
+        SCLogInfo("Starting directory run for %s", ptv->behavior.directory->filename);
+        PcapDirectoryDispatch(ptv->behavior.directory);
+        CleanupPcapDirectoryFromThreadVars(ptv, ptv->behavior.directory);
     }
 
-    SCReturnInt(TM_ECODE_OK);
+    SCLogDebug("Pcap file loop complete with status %u", status);
+
+    if(RunModeUnixSocketIsActive()) {
+        SCReturnInt(TM_ECODE_DONE);
+    } else {
+        EngineStop();
+        SCReturnInt(TM_ECODE_OK);
+    }
 }
 
 TmEcode ReceivePcapFileThreadInit(ThreadVars *tv, const void *initdata, void **data)
 {
     SCEnter();
 
-    const char *tmpbpfstring = NULL;
     const char *tmpstring = NULL;
+    const char *tmp_bpf_string = NULL;
 
     if (initdata == NULL) {
         SCLogError(SC_ERR_INVALID_ARGUMENT, "error: initdata == NULL");
         SCReturnInt(TM_ECODE_FAILED);
     }
 
-    SCLogInfo("reading pcap file %s", (char *)initdata);
-
     PcapFileThreadVars *ptv = SCMalloc(sizeof(PcapFileThreadVars));
     if (unlikely(ptv == NULL))
         SCReturnInt(TM_ECODE_FAILED);
@@ -274,79 +209,114 @@ TmEcode ReceivePcapFileThreadInit(ThreadVars *tv, const void *initdata, void **d
     intmax_t tenant = 0;
     if (ConfGetInt("pcap-file.tenant-id", &tenant) == 1) {
         if (tenant > 0 && tenant < UINT_MAX) {
-            ptv->tenant_id = (uint32_t)tenant;
-            SCLogInfo("tenant %u", ptv->tenant_id);
+            ptv->shared.tenant_id = (uint32_t)tenant;
+            SCLogInfo("tenant %u", ptv->shared.tenant_id);
         } else {
             SCLogError(SC_ERR_INVALID_ARGUMENT, "tenant out of range");
         }
     }
 
-    char errbuf[PCAP_ERRBUF_SIZE] = "";
-    pcap_g.pcap_handle = pcap_open_offline((char *)initdata, errbuf);
-    if (pcap_g.pcap_handle == NULL) {
-        SCLogError(SC_ERR_FOPEN, "%s\n", errbuf);
-        SCFree(ptv);
-        if (! RunModeUnixSocketIsActive()) {
-            return TM_ECODE_FAILED;
-        } else {
-            UnixSocketPcapFile(TM_ECODE_FAILED);
-            SCReturnInt(TM_ECODE_DONE);
+    if (ConfGet("bpf-filter", &(tmp_bpf_string)) != 1) {
+        SCLogDebug("could not get bpf or none specified");
+    } else {
+        ptv->shared.bpf_string = SCStrdup(tmp_bpf_string);
+        if (unlikely(ptv->shared.bpf_string == NULL)) {
+            SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate bpf_string");
+            CleanupPcapFileThreadVars(ptv);
+            SCReturnInt(TM_ECODE_FAILED);
         }
     }
 
-    if (ConfGet("bpf-filter", &tmpbpfstring) != 1) {
-        SCLogDebug("could not get bpf or none specified");
+    DIR *directory = NULL;
+    SCLogInfo("Checking file or directory %s", (char*)initdata);
+    if(PcapDetermineDirectoryOrFile((char *)initdata, &directory) == TM_ECODE_FAILED) {
+        CleanupPcapFileThreadVars(ptv);
+        SCReturnInt(TM_ECODE_FAILED);
+    }
+
+    if(directory == NULL) {
+        SCLogInfo("Argument %s was a file", (char *)initdata);
+        PcapFileFileVars *pv = SCMalloc(sizeof(PcapFileFileVars));
+        if (unlikely(pv == NULL)) {
+            SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate file vars");
+            CleanupPcapFileThreadVars(ptv);
+            SCReturnInt(TM_ECODE_FAILED);
+        }
+        memset(pv, 0, sizeof(PcapFileFileVars));
+
+        pv->filename = SCStrdup((char *)initdata);
+        if (unlikely(pv->filename == NULL)) {
+            SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate filename");
+            CleanupPcapFileFileVars(pv);
+            CleanupPcapFileThreadVars(ptv);
+            SCReturnInt(TM_ECODE_FAILED);
+        }
+
+        TmEcode init_file_return = InitPcapFile(pv);
+        if(init_file_return == TM_ECODE_OK) {
+            pv->shared = &ptv->shared;
+
+            ptv->is_directory = 0;
+            ptv->behavior.file = pv;
+        } else {
+            SCLogWarning(SC_ERR_PCAP_DISPATCH,
+                         "Failed to init pcap file %s, skipping", (char *)initdata);
+            CleanupPcapFileFileVars(pv);
+            CleanupPcapFileThreadVars(ptv);
+            SCReturnInt(init_file_return);
+        }
     } else {
-        SCLogInfo("using bpf-filter \"%s\"", tmpbpfstring);
+        SCLogInfo("Argument %s was a directory", (char *)initdata);
+        PcapFileDirectoryVars *pv = SCMalloc(sizeof(PcapFileDirectoryVars));
+        if (unlikely(pv == NULL)) {
+            SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate directory vars");
+            CleanupPcapFileThreadVars(ptv);
+            SCReturnInt(TM_ECODE_FAILED);
+        }
+        memset(pv, 0, sizeof(PcapFileDirectoryVars));
+
+        pv->filename = SCStrdup((char*)initdata);
+        if (unlikely(pv->filename == NULL)) {
+            SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate filename");
+            CleanupPcapFileDirectoryVars(pv);
+            CleanupPcapFileThreadVars(ptv);
+            SCReturnInt(TM_ECODE_FAILED);
+        }
 
-        if (pcap_compile(pcap_g.pcap_handle, &pcap_g.filter, (char *)tmpbpfstring, 1, 0) < 0) {
-            SCLogError(SC_ERR_BPF,"bpf compilation error %s",
-                    pcap_geterr(pcap_g.pcap_handle));
-            SCFree(ptv);
-            return TM_ECODE_FAILED;
+        int should_loop = 0;
+        pv->should_loop = false;
+        if (ConfGetBool("pcap-file.continuous", &should_loop) == 1) {
+            pv->should_loop = should_loop == 1;
         }
 
-        if (pcap_setfilter(pcap_g.pcap_handle, &pcap_g.filter) < 0) {
-            SCLogError(SC_ERR_BPF,"could not set bpf filter %s", pcap_geterr(pcap_g.pcap_handle));
-            SCFree(ptv);
-            return TM_ECODE_FAILED;
+        pv->delay = 30;
+        intmax_t delay = 0;
+        if (ConfGetInt("pcap-file.delay", &delay) == 1) {
+            if (delay > 0 && delay < UINT_MAX) {
+                pv->delay = (time_t)delay;
+                SCLogDebug("delay %lu", pv->delay);
+            } else {
+                SCLogError(SC_ERR_INVALID_ARGUMENT, "delay out of range");
+            }
         }
-    }
 
-    pcap_g.datalink = pcap_datalink(pcap_g.pcap_handle);
-    SCLogDebug("datalink %" PRId32 "", pcap_g.datalink);
-
-    switch (pcap_g.datalink) {
-        case LINKTYPE_LINUX_SLL:
-            pcap_g.Decoder = DecodeSll;
-            break;
-        case LINKTYPE_ETHERNET:
-            pcap_g.Decoder = DecodeEthernet;
-            break;
-        case LINKTYPE_PPP:
-            pcap_g.Decoder = DecodePPP;
-            break;
-        case LINKTYPE_IPV4:
-        case LINKTYPE_RAW:
-        case LINKTYPE_RAW2:
-            pcap_g.Decoder = DecodeRaw;
-            break;
-        case LINKTYPE_NULL:
-            pcap_g.Decoder = DecodeNull;
-            break;
-
-        default:
-            SCLogError(SC_ERR_UNIMPLEMENTED, "datalink type %" PRId32 " not "
-                      "(yet) supported in module PcapFile.\n", pcap_g.datalink);
-            SCFree(ptv);
-            if (! RunModeUnixSocketIsActive()) {
-                SCReturnInt(TM_ECODE_FAILED);
+        pv->poll_interval = 5;
+        intmax_t poll_interval = 0;
+        if (ConfGetInt("pcap-file.poll-interval", &poll_interval) == 1) {
+            if (poll_interval > 0 && poll_interval < UINT_MAX) {
+                pv->poll_interval = (time_t)poll_interval;
+                SCLogDebug("poll-interval %lu", pv->delay);
             } else {
-                pcap_close(pcap_g.pcap_handle);
-                pcap_g.pcap_handle = NULL;
-                UnixSocketPcapFile(TM_ECODE_DONE);
-                SCReturnInt(TM_ECODE_DONE);
+                SCLogError(SC_ERR_INVALID_ARGUMENT, "poll-interval out of range");
             }
+        }
+
+        pv->shared = &ptv->shared;
+        pv->directory = directory;
+        TAILQ_INIT(&pv->directory_content);
+
+        ptv->is_directory = 1;
+        ptv->behavior.directory = pv;
     }
 
     if (ConfGet("pcap-file.checksum-checks", &tmpstring) != 1) {
@@ -362,7 +332,7 @@ TmEcode ReceivePcapFileThreadInit(ThreadVars *tv, const void *initdata, void **d
     }
     pcap_g.checksum_mode = pcap_g.conf_checksum_mode;
 
-    ptv->tv = tv;
+    ptv->shared.tv = tv;
     *data = (void *)ptv;
 
     SCReturnInt(TM_ECODE_OK);
@@ -374,30 +344,32 @@ void ReceivePcapFileThreadExitStats(ThreadVars *tv, void *data)
     PcapFileThreadVars *ptv = (PcapFileThreadVars *)data;
 
     if (pcap_g.conf_checksum_mode == CHECKSUM_VALIDATION_AUTO &&
-            pcap_g.cnt < CHECKSUM_SAMPLE_COUNT &&
-            SC_ATOMIC_GET(pcap_g.invalid_checksums)) {
+        pcap_g.cnt < CHECKSUM_SAMPLE_COUNT &&
+        SC_ATOMIC_GET(pcap_g.invalid_checksums)) {
         uint64_t chrate = pcap_g.cnt / SC_ATOMIC_GET(pcap_g.invalid_checksums);
         if (chrate < CHECKSUM_INVALID_RATIO)
             SCLogWarning(SC_ERR_INVALID_CHECKSUM,
                          "1/%" PRIu64 "th of packets have an invalid checksum,"
-                         " consider setting pcap-file.checksum-checks variable to no"
-                         " or use '-k none' option on command line.",
+                                 " consider setting pcap-file.checksum-checks variable to no"
+                                 " or use '-k none' option on command line.",
                          chrate);
         else
             SCLogInfo("1/%" PRIu64 "th of packets have an invalid checksum",
                       chrate);
     }
-    SCLogNotice("Pcap-file module read %" PRIu32 " packets, %" PRIu64 " bytes", ptv->pkts, ptv->bytes);
-    return;
+    SCLogNotice(
+            "Pcap-file module read %" PRIu64 " files, %" PRIu64 " packets, %" PRIu64 " bytes",
+            ptv->shared.files,
+            ptv->shared.pkts,
+            ptv->shared.bytes
+    );
 }
 
 TmEcode ReceivePcapFileThreadDeinit(ThreadVars *tv, void *data)
 {
     SCEnter();
     PcapFileThreadVars *ptv = (PcapFileThreadVars *)data;
-    if (ptv) {
-        SCFree(ptv);
-    }
+    CleanupPcapFileThreadVars(ptv);
     SCReturnInt(TM_ECODE_OK);
 }
 
@@ -422,16 +394,22 @@ TmEcode DecodePcapFile(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, P
         FlowWakeupFlowManagerThread();
     }
 
-    /* call the decoder */
-    pcap_g.Decoder(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq);
+    Decoder decoder;
+    if(ValidateLinkType(p->datalink, &decoder) == TM_ECODE_OK) {
+
+        /* call the decoder */
+        decoder(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq);
 
 #ifdef DEBUG
-    BUG_ON(p->pkt_src != PKT_SRC_WIRE && p->pkt_src != PKT_SRC_FFR);
+        BUG_ON(p->pkt_src != PKT_SRC_WIRE && p->pkt_src != PKT_SRC_FFR);
 #endif
 
-    PacketDecodeFinalize(tv, dtv, p);
+        PacketDecodeFinalize(tv, dtv, p);
 
-    SCReturnInt(TM_ECODE_OK);
+        SCReturnInt(TM_ECODE_OK);
+    } else {
+        SCReturnInt(TM_ECODE_FAILED);
+    }
 }
 
 TmEcode DecodePcapFileThreadInit(ThreadVars *tv, const void *initdata, void **data)
@@ -468,4 +446,3 @@ void PcapIncreaseInvalidChecksum()
 }
 
 /* eof */
-
index 31c3993652bb06c1feebfb2e4474bc99156c0b84..50a5ade86bd81fa77c7791d82a2e99f28e8300e3 100644 (file)
 #define CLS 64
 #endif
 
+#if HAVE_DIRENT_H
+#include <dirent.h>
+#endif
+
 #if HAVE_STDIO_H
 #include <stdio.h>
 #endif
index 339801d3500db3be4629eda6e7c2c096f6c44449..09e8d23a4949ee05ee09ec66e9a2d43abd7bf15f 100644 (file)
@@ -655,6 +655,7 @@ static void PrintUsage(const char *progname)
     printf("\t--dump-config                        : show the running configuration\n");
     printf("\t--build-info                         : display build information\n");
     printf("\t--pcap[=<dev>]                       : run in pcap mode, no value select interfaces from suricata.yaml\n");
+    printf("\t--pcap-file-continuous               : when running in pcap mode with a directory, continue checking directory for pcaps until interrupted");
 #ifdef HAVE_PCAP_SET_BUFF
     printf("\t--pcap-buffer-size                   : size of the pcap buffer value from 0 - %i\n",INT_MAX);
 #endif /* HAVE_SET_PCAP_BUFF */
@@ -1497,6 +1498,7 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri)
         {"af-packet", optional_argument, 0, 0},
         {"netmap", optional_argument, 0, 0},
         {"pcap", optional_argument, 0, 0},
+        {"pcap-file-continuous", 0, 0, 0},
         {"simulate-ips", 0, 0 , 0},
         {"no-random", 0, &g_disable_randomness, 1},
 
@@ -1873,6 +1875,13 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri)
                     }
                 }
             }
+            else if (strcmp((long_opts[option_index]).name, "pcap-file-continuous") == 0) {
+                if(ConfSetFinal("pcap-file.continuous", "true") != 1) {
+                    SCLogError(SC_ERR_CMD_LINE, "Failed to set pcap-file.continuous");
+                    return TM_ECODE_FAILED;
+                }
+                return TM_ECODE_OK;
+            }
             break;
         case 'c':
             suri->conf_filename = optarg;
@@ -2010,6 +2019,7 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri)
                 fprintf(stderr, "ERROR: Failed to set pcap-file.file\n");
                 return TM_ECODE_FAILED;
             }
+
             break;
         case 's':
             if (suri->sig_file != NULL) {