/* capture.c
- * Routines for packet capture windows
+ * Routines for packet capture
*
* $Id$
*
#ifdef HAVE_LIBPCAP
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "capture-wpcap.h"
#endif
#include "ui_util.h"
+#include "log.h"
-/* Win32 needs the O_BINARY flag for open() */
-#ifndef O_BINARY
-#define O_BINARY 0
-#endif
-static gboolean normal_do_capture(capture_options *capture_opts, gboolean is_tempfile);
-static void stop_capture_signal_handler(int signo);
+/**
+ * Start a capture.
+ *
+ * @return TRUE if the capture starts successfully, FALSE otherwise.
+ */
+gboolean
+capture_start(capture_options *capture_opts)
+{
+ gboolean ret;
-/* open the output file (temporary/specified name/ringbuffer) */
-/* Returns TRUE if the file opened successfully, FALSE otherwise. */
-static gboolean
-capture_open_output(capture_options *capture_opts, gboolean *is_tempfile) {
- char tmpname[128+1];
- gchar *capfile_name;
-
-
- if (capture_opts->save_file != NULL) {
- /* If the Sync option is set, we return to the caller while the capture
- * is in progress. Therefore we need to take a copy of save_file in
- * case the caller destroys it after we return.
- */
- capfile_name = g_strdup(capture_opts->save_file);
- if (capture_opts->multi_files_on) {
- /* ringbuffer is enabled */
- capture_opts->save_file_fd = ringbuf_init(capfile_name,
- (capture_opts->has_ring_num_files) ? capture_opts->ring_num_files : 0);
- } else {
- /* Try to open/create the specified file for use as a capture buffer. */
- capture_opts->save_file_fd = open(capfile_name, O_RDWR|O_BINARY|O_TRUNC|O_CREAT,
- 0600);
- }
- *is_tempfile = FALSE;
- } else {
- /* Choose a random name for the temporary capture buffer */
- capture_opts->save_file_fd = create_tempfile(tmpname, sizeof tmpname, "ether");
- capfile_name = g_strdup(tmpname);
- *is_tempfile = TRUE;
- }
+ /* close the currently loaded capture file */
+ cf_close(capture_opts->cf);
- /* did we fail to open the output file? */
- if (capture_opts->save_file_fd == -1) {
- if (*is_tempfile) {
- simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
- "The temporary file to which the capture would be saved (\"%s\") "
- "could not be opened: %s.", capfile_name, strerror(errno));
- } else {
- if (capture_opts->multi_files_on) {
- ringbuf_error_cleanup();
+ g_assert(capture_opts->state == CAPTURE_STOPPED);
+ capture_opts->state = CAPTURE_PREPARING;
+
+ g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_MESSAGE, "Capture Start ...");
+
+ /* try to start the capture child process */
+ ret = sync_pipe_start(capture_opts);
+ if(!ret) {
+ if(capture_opts->save_file != NULL) {
+ g_free(capture_opts->save_file);
+ capture_opts->save_file = NULL;
}
- open_failure_alert_box(capfile_name, errno, TRUE);
- }
- g_free(capfile_name);
- return FALSE;
- }
- if(capture_opts->save_file != NULL) {
- g_free(capture_opts->save_file);
+ capture_opts->state = CAPTURE_STOPPED;
+ } else {
+ /* the capture child might not respond shortly after bringing it up */
+ /* (especially it will block, if no input coming from an input capture pipe (e.g. mkfifo) is coming in) */
+
+ /* to prevent problems, bring the main GUI into "capture mode" right after successfully */
+ /* spawn/exec the capture child, without waiting for any response from it */
+ cf_callback_invoke(cf_cb_live_capture_prepared, capture_opts);
}
- capture_opts->save_file = capfile_name;
- /* capture_opts.save_file is "g_free"ed later, which is equivalent to
- "g_free(capfile_name)". */
- return TRUE;
+ return ret;
}
-/* close the output file (NOT the capture file) */
-static void
-capture_close_output(capture_options *capture_opts)
+void
+capture_stop(capture_options *capture_opts)
{
- if (capture_opts->multi_files_on) {
- ringbuf_free();
- } else {
- g_free(capture_opts->save_file);
- }
- capture_opts->save_file = NULL;
+ g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_MESSAGE, "Capture Stop ...");
+
+ /* stop the capture child gracefully */
+ sync_pipe_stop(capture_opts);
}
-/* Open a specified file, or create a temporary file, and start a capture
- to the file in question. */
-/* Returns TRUE if the capture starts successfully, FALSE otherwise. */
-gboolean
-do_capture(capture_options *capture_opts)
+void
+capture_restart(capture_options *capture_opts)
{
- gboolean is_tempfile;
- gboolean ret;
+ g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_MESSAGE, "Capture Restart");
+
+ capture_opts->restart = TRUE;
+ capture_stop(capture_opts);
+}
+
+
+void
+capture_kill_child(capture_options *capture_opts)
+{
+ g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_INFO, "Capture Kill");
+
+ /* kill the capture child */
+ sync_pipe_kill(capture_opts);
+}
- /* open the new output file (temporary/specified name/ringbuffer) */
- if(!capture_open_output(capture_opts, &is_tempfile)) {
+
+/* We've succeeded a (non real-time) capture, try to read it into a new capture file */
+static gboolean
+capture_input_read_all(capture_options *capture_opts, gboolean is_tempfile, gboolean drops_known,
+guint32 drops)
+{
+ int err;
+
+
+ /* Capture succeeded; attempt to open the capture file. */
+ if (cf_open(capture_opts->cf, capture_opts->save_file, is_tempfile, &err) != CF_OK) {
+ /* We're not doing a capture any more, so we don't have a save
+ file. */
return FALSE;
}
- /* close the currently loaded capture file */
- cf_close(capture_opts->cf);
+ /* Set the read filter to NULL. */
+ /* XXX - this is odd here, try to put it somewhere, where it fits better */
+ cf_set_rfcode(capture_opts->cf, NULL);
+
+ /* Get the packet-drop statistics.
+
+ XXX - there are currently no packet-drop statistics stored
+ in libpcap captures, and that's what we're reading.
+
+ At some point, we will add support in Wiretap to return
+ packet-drop statistics for capture file formats that store it,
+ and will make "cf_read()" get those statistics from Wiretap.
+ We clear the statistics (marking them as "not known") in
+ "cf_open()", and "cf_read()" will only fetch them and mark
+ them as known if Wiretap supplies them, so if we get the
+ statistics now, after calling "cf_open()" but before calling
+ "cf_read()", the values we store will be used by "cf_read()".
+
+ If a future libpcap capture file format stores the statistics,
+ we'll put them into the capture file that we write, and will
+ thus not have to set them here - "cf_read()" will get them from
+ the file and use them. */
+ if (drops_known) {
+ cf_set_drops_known(capture_opts->cf, TRUE);
+
+ /* XXX - on some systems, libpcap doesn't bother filling in
+ "ps_ifdrop" - it doesn't even set it to zero - so we don't
+ bother looking at it.
+
+ Ideally, libpcap would have an interface that gave us
+ several statistics - perhaps including various interface
+ error statistics - and would tell us which of them it
+ supplies, allowing us to display only the ones it does. */
+ cf_set_drops(capture_opts->cf, drops);
+ }
- /* We could simply use TRUE for this expression now, this will work for all
- * captures except for some of the multiple files options, as these capture
- * options currently cannot be passed through the command line to the
- * capture child.
- *
- * If this is fixed, we could always use the sync mode, throwing away the
- * normal mode completely and doing some more cleanup. */
-/* if (TRUE) {*/
- if (capture_opts->sync_mode) {
- /* sync mode: do the capture in a child process */
- ret = sync_pipe_do_capture(capture_opts, is_tempfile);
- /* capture is still running */
- cf_callback_invoke(cf_cb_live_capture_prepare, capture_opts);
- } else {
- /* normal mode: do the capture synchronously */
- cf_callback_invoke(cf_cb_live_capture_prepare, capture_opts);
- ret = normal_do_capture(capture_opts, is_tempfile);
- /* capture is finished here */
+ /* read in the packet data */
+ switch (cf_read(capture_opts->cf)) {
+
+ case CF_READ_OK:
+ case CF_READ_ERROR:
+ /* Just because we got an error, that doesn't mean we were unable
+ to read any of the file; we handle what we could get from the
+ file. */
+ break;
+
+ case CF_READ_ABORTED:
+ /* User wants to quit program. Exit by leaving the main loop,
+ so that any quit functions we registered get called. */
+ main_window_nested_quit();
+ return FALSE;
}
- return ret;
+ /* if we didn't captured even a single packet, close the file again */
+ if(cf_packet_count(capture_opts->cf) == 0 && !capture_opts->restart) {
+ simple_dialog(ESD_TYPE_INFO, ESD_BTN_OK,
+ "%sNo packets captured!%s\n\n"
+ "As no data was captured, closing the %scapture file!",
+ simple_dialog_primary_start(), simple_dialog_primary_end(),
+ (cf_is_tempfile(capture_opts->cf)) ? "temporary " : "");
+ cf_close(capture_opts->cf);
+ }
+ return TRUE;
}
-/* we've succeeded a capture, try to read it into a new capture file */
+/* capture child tells us, we have a new (or the first) capture file */
gboolean
-capture_read(capture_options *capture_opts, gboolean is_tempfile, gboolean drops_known,
-guint32 drops)
+capture_input_new_file(capture_options *capture_opts, gchar *new_file)
{
- int err;
+ gboolean is_tempfile;
+ int err;
- /* Capture succeeded; attempt to read in the capture file. */
- if (cf_open(capture_opts->cf, capture_opts->save_file, is_tempfile, &err) != CF_OK) {
- /* We're not doing a capture any more, so we don't have a save
- file. */
- return FALSE;
- }
+ if(capture_opts->state == CAPTURE_PREPARING) {
+ g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_MESSAGE, "Capture started!");
+ }
+ g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_MESSAGE, "File: \"%s\"", new_file);
- /* Set the read filter to NULL. */
- /* XXX - this is odd here, try to put it somewhere, where it fits better */
- cf_set_rfcode(capture_opts->cf, NULL);
-
- /* Get the packet-drop statistics.
-
- XXX - there are currently no packet-drop statistics stored
- in libpcap captures, and that's what we're reading.
-
- At some point, we will add support in Wiretap to return
- packet-drop statistics for capture file formats that store it,
- and will make "cf_read()" get those statistics from Wiretap.
- We clear the statistics (marking them as "not known") in
- "cf_open()", and "cf_read()" will only fetch them and mark
- them as known if Wiretap supplies them, so if we get the
- statistics now, after calling "cf_open()" but before calling
- "cf_read()", the values we store will be used by "cf_read()".
-
- If a future libpcap capture file format stores the statistics,
- we'll put them into the capture file that we write, and will
- thus not have to set them here - "cf_read()" will get them from
- the file and use them. */
- if (drops_known) {
- cf_set_drops_known(capture_opts->cf, TRUE);
-
- /* XXX - on some systems, libpcap doesn't bother filling in
- "ps_ifdrop" - it doesn't even set it to zero - so we don't
- bother looking at it.
-
- Ideally, libpcap would have an interface that gave us
- several statistics - perhaps including various interface
- error statistics - and would tell us which of them it
- supplies, allowing us to display only the ones it does. */
- cf_set_drops(capture_opts->cf, drops);
+ g_assert(capture_opts->state == CAPTURE_PREPARING || capture_opts->state == CAPTURE_RUNNING);
+
+ /* free the old filename */
+ if(capture_opts->save_file != NULL) {
+ /* we start a new capture file, close the old one (if we had one before) */
+ if( ((capture_file *) capture_opts->cf)->state != FILE_CLOSED) {
+ cf_callback_invoke(cf_cb_live_capture_update_finished, capture_opts->cf);
+ cf_finish_tail(capture_opts->cf, &err);
+ cf_close(capture_opts->cf);
}
- switch (cf_read(capture_opts->cf)) {
+ g_free(capture_opts->save_file);
+ is_tempfile = FALSE;
+ cf_set_tempfile(capture_opts->cf, FALSE);
+ } else {
+ /* we didn't had a save_file before, must be a tempfile */
+ is_tempfile = TRUE;
+ cf_set_tempfile(capture_opts->cf, TRUE);
+ }
- case CF_READ_OK:
- case CF_READ_ERROR:
- /* Just because we got an error, that doesn't mean we were unable
- to read any of the file; we handle what we could get from the
- file. */
- break;
+ /* save the new filename */
+ capture_opts->save_file = g_strdup(new_file);
- case CF_READ_ABORTED:
- /* Exit by leaving the main loop, so that any quit functions
- we registered get called. */
- main_window_nested_quit();
+ /* if we are in real-time mode, open the new file now */
+ if(capture_opts->real_time_mode) {
+ /* Attempt to open the capture file and set up to read from it. */
+ switch(cf_start_tail(capture_opts->cf, capture_opts->save_file, is_tempfile, &err)) {
+ case CF_OK:
+ break;
+ case CF_ERROR:
+ /* Don't unlink (delete) the save file - leave it around,
+ for debugging purposes. */
+ g_free(capture_opts->save_file);
+ capture_opts->save_file = NULL;
return FALSE;
+ break;
}
- /* if we didn't captured even a single packet, close the file again */
- if(cf_packet_count(capture_opts->cf) == 0) {
- simple_dialog(ESD_TYPE_INFO, ESD_BTN_OK,
- "%sNo packets captured!%s\n\n"
- "As no data was captured, closing the %scapture file!",
- simple_dialog_primary_start(), simple_dialog_primary_end(),
- (cf_is_tempfile(capture_opts->cf)) ? "temporary " : "");
- cf_close(capture_opts->cf);
- }
+ cf_callback_invoke(cf_cb_live_capture_update_started, capture_opts);
+ } else {
+ cf_callback_invoke(cf_cb_live_capture_fixed_started, capture_opts);
+ }
+
+ capture_opts->state = CAPTURE_RUNNING;
+
return TRUE;
}
-
-/* start a normal capture session */
-static gboolean
-normal_do_capture(capture_options *capture_opts, gboolean is_tempfile)
+
+/* capture child tells us, we have new packets to read */
+void
+capture_input_new_packets(capture_options *capture_opts, int to_read)
{
- gboolean succeeded;
- gboolean stats_known;
- struct pcap_stat stats;
+ int err;
- /* Not sync mode. */
- succeeded = capture_loop_start(capture_opts, &stats_known, &stats);
- if (capture_opts->quit_after_cap) {
- /* DON'T unlink the save file. Presumably someone wants it. */
- main_window_exit();
- }
- if (succeeded) {
- /* We succeed in doing the capture, try to read it in. */
- succeeded = capture_read(capture_opts, is_tempfile, stats_known, stats.ps_drop);
- }
+ g_assert(capture_opts->save_file);
- /* wether the capture suceeded or not, we have to close the output file here */
- capture_close_output(capture_opts);
- return succeeded;
-}
+ if(capture_opts->real_time_mode) {
+ /* Read from the capture file the number of records the child told us
+ it added.
+ XXX - do something if this fails? */
+ switch (cf_continue_tail(capture_opts->cf, to_read, &err)) {
+
+ case CF_READ_OK:
+ case CF_READ_ERROR:
+ /* Just because we got an error, that doesn't mean we were unable
+ to read any of the file; we handle what we could get from the
+ file.
+ XXX - abort on a read error? */
+ cf_callback_invoke(cf_cb_live_capture_update_continue, capture_opts->cf);
+ main_window_update();
+ break;
-static void
-stop_capture_signal_handler(int signo _U_)
-{
- capture_loop_stop();
+ case CF_READ_ABORTED:
+ /* Kill the child capture process; the user wants to exit, and we
+ shouldn't just leave it running. */
+ capture_kill_child(capture_opts);
+ break;
+ }
+ }
}
-int
-capture_child_start(capture_options *capture_opts, gboolean *stats_known, struct pcap_stat *stats)
+/* capture child closed it's side ot the pipe, do the required cleanup */
+void
+capture_input_closed(capture_options *capture_opts)
{
- gchar *err_msg;
+ int err;
- g_assert(capture_opts->capture_child);
-#ifndef _WIN32
- /*
- * Catch SIGUSR1, so that we exit cleanly if the parent process
- * kills us with it due to the user selecting "Capture->Stop".
- */
- signal(SIGUSR1, stop_capture_signal_handler);
-#endif
+ g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_MESSAGE, "Capture stopped!");
+ g_assert(capture_opts->state == CAPTURE_PREPARING || capture_opts->state == CAPTURE_RUNNING);
- /* parent must have send us a file descriptor for the opened output file */
- if (capture_opts->save_file_fd == -1) {
- /* send this to the standard output as something our parent
- should put in an error message box */
- err_msg = g_strdup_printf("%s: \"-W\" flag not specified (internal error)\n", CHILD_NAME);
- sync_pipe_errmsg_to_parent(err_msg);
- g_free(err_msg);
- return FALSE;
+ /* if we didn't started the capture (happens if an error occured), do a fake start */
+ if(capture_opts->state == CAPTURE_PREPARING) {
+ if(capture_opts->real_time_mode) {
+ cf_callback_invoke(cf_cb_live_capture_update_started, capture_opts);
+ } else {
+ cf_callback_invoke(cf_cb_live_capture_fixed_started, capture_opts);
+ }
}
- return capture_loop_start(capture_opts, stats_known, stats);
-}
+ if(capture_opts->real_time_mode) {
+ /* first of all, we are not doing a capture any more */
+ cf_callback_invoke(cf_cb_live_capture_update_finished, capture_opts->cf);
+
+ /* Read what remains of the capture file, and finish the capture.
+ XXX - do something if this fails? */
+ switch (cf_finish_tail(capture_opts->cf, &err)) {
+
+ case CF_READ_OK:
+ if(cf_packet_count(capture_opts->cf) == 0 && !capture_opts->restart) {
+ simple_dialog(ESD_TYPE_INFO, ESD_BTN_OK,
+ "%sNo packets captured!%s\n\n"
+ "As no data was captured, closing the %scapture file!",
+ simple_dialog_primary_start(), simple_dialog_primary_end(),
+ cf_is_tempfile(capture_opts->cf) ? "temporary " : "");
+ cf_close(capture_opts->cf);
+ }
+ break;
+ case CF_READ_ERROR:
+ /* Just because we got an error, that doesn't mean we were unable
+ to read any of the file; we handle what we could get from the
+ file. */
+ break;
+
+ case CF_READ_ABORTED:
+ /* Exit by leaving the main loop, so that any quit functions
+ we registered get called. */
+ main_window_quit();
+ }
-void
-capture_stop(capture_options *capture_opts)
-{
- /* stop the capture child, if we have one */
- if (!capture_opts->capture_child) {
- sync_pipe_stop(capture_opts);
- }
+ } else {
+ /* first of all, we are not doing a capture any more */
+ cf_callback_invoke(cf_cb_live_capture_fixed_finished, capture_opts->cf);
+
+ /* this is a normal mode capture and if no error happened, read in the capture file data */
+ if(capture_opts->save_file != NULL) {
+ capture_input_read_all(capture_opts, cf_is_tempfile(capture_opts->cf),
+ cf_get_drops_known(capture_opts->cf), cf_get_drops(capture_opts->cf));
+ }
+ }
- /* stop the capture loop */
- capture_loop_stop();
-}
+ capture_opts->state = CAPTURE_STOPPED;
-void
-capture_kill_child(capture_options *capture_opts)
-{
- /* kill the capture child, if we have one */
- if (!capture_opts->capture_child) {
- sync_pipe_kill(capture_opts);
- }
+ /* if we couldn't open a capture file, there's nothing more for us to do */
+ if(capture_opts->save_file == NULL) {
+ cf_close(capture_opts->cf);
+ return;
+ }
+
+ /* does the user wants to restart the current capture? */
+ if(capture_opts->restart) {
+ capture_opts->restart = FALSE;
+
+ unlink(capture_opts->save_file);
+
+ /* if it was a tempfile, throw away the old filename (so it will become a tempfile again) */
+ if(cf_is_tempfile(capture_opts->cf)) {
+ g_free(capture_opts->save_file);
+ capture_opts->save_file = NULL;
+ }
+
+ /* ... and start the capture again */
+ capture_start(capture_opts);
+ } else {
+ /* We're not doing a capture any more, so we don't have a save file. */
+ g_free(capture_opts->save_file);
+ capture_opts->save_file = NULL;
+ }
}