From 11a9a501fb004bd3259f457714670ffb6d3d21e9 Mon Sep 17 00:00:00 2001 From: Gerald Combs Date: Wed, 31 Oct 2018 10:03:04 +0100 Subject: [PATCH] Dumpcap+Qt: Add support for `-a packets:NUM` and `-b packets:NUM`. Add the ability to rotate files after a specified number of packets (`-b packets:NUM`). Move some condition checks to capture_loop_write_packet_cb. Add `-a packets:NUM` in order to be consistent. It is functionally equivalent to the `-c` flag. Add a corresponding "packets" option to the Capture Interfaces dialog Output tab. Add initial tests for autostop and ringbuffer conditions. Change-Id: I66eb968927ed287deb8edb96db96d7c73526c257 Reviewed-on: https://code.wireshark.org/review/30534 Reviewed-by: Gerald Combs Petri-Dish: Gerald Combs Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman --- capchild/capture_sync.c | 39 ++++---- capture_opts.c | 10 +++ capture_opts.h | 5 +- doc/dumpcap.pod | 24 +++-- doc/tshark.pod | 25 ++++-- doc/wireshark.pod.template | 24 +++-- docbook/release-notes.asciidoc | 1 + dumpcap.c | 133 +++++++++++++++------------- test/suite_capture.py | 108 ++++++++++++++++++++-- test/util_dump_dhcp_pcap.py | 11 ++- ui/qt/capture_interfaces_dialog.cpp | 15 +++- ui/qt/capture_interfaces_dialog.ui | 120 +++++++++++++++---------- 12 files changed, 348 insertions(+), 167 deletions(-) diff --git a/capchild/capture_sync.c b/capchild/capture_sync.c index c3ae9d31c4..6da5f63f4b 100644 --- a/capchild/capture_sync.c +++ b/capchild/capture_sync.c @@ -194,26 +194,6 @@ init_pipe_args(int *argc) { gboolean sync_pipe_start(capture_options *capture_opts, capture_session *cap_session, info_data_t* cap_data, void (*update_cb)(void)) { - char ssnap[ARGV_NUMBER_LEN]; - char scount[ARGV_NUMBER_LEN]; - char sfilesize[ARGV_NUMBER_LEN]; - char sfile_duration[ARGV_NUMBER_LEN]; - char sfile_interval[ARGV_NUMBER_LEN]; - char sring_num_files[ARGV_NUMBER_LEN]; - char sautostop_files[ARGV_NUMBER_LEN]; - char sautostop_filesize[ARGV_NUMBER_LEN]; - char sautostop_duration[ARGV_NUMBER_LEN]; -#ifdef HAVE_PCAP_REMOTE - char sauth[256]; -#endif -#ifdef HAVE_PCAP_SETSAMPLING - char ssampling[ARGV_NUMBER_LEN]; -#endif - -#ifdef CAN_SET_CAPTURE_BUFFER_SIZE - char buffer_size[ARGV_NUMBER_LEN]; -#endif - #ifdef _WIN32 HANDLE sync_pipe_read; /* pipe used to send messages from child to parent */ HANDLE sync_pipe_write; /* pipe used to send messages from child to parent */ @@ -272,36 +252,49 @@ sync_pipe_start(capture_options *capture_opts, capture_session *cap_session, inf if (capture_opts->multi_files_on) { if (capture_opts->has_autostop_filesize) { + char sfilesize[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-b"); g_snprintf(sfilesize, ARGV_NUMBER_LEN, "filesize:%u",capture_opts->autostop_filesize); argv = sync_pipe_add_arg(argv, &argc, sfilesize); } if (capture_opts->has_file_duration) { + char sfile_duration[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-b"); g_snprintf(sfile_duration, ARGV_NUMBER_LEN, "duration:%f",capture_opts->file_duration); argv = sync_pipe_add_arg(argv, &argc, sfile_duration); } if (capture_opts->has_file_interval) { + char sfile_interval[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-b"); g_snprintf(sfile_interval, ARGV_NUMBER_LEN, "interval:%d",capture_opts->file_interval); argv = sync_pipe_add_arg(argv, &argc, sfile_interval); } + if (capture_opts->has_file_packets) { + char sfile_packets[ARGV_NUMBER_LEN]; + argv = sync_pipe_add_arg(argv, &argc, "-b"); + g_snprintf(sfile_packets, ARGV_NUMBER_LEN, "packets:%d",capture_opts->file_packets); + argv = sync_pipe_add_arg(argv, &argc, sfile_packets); + } + if (capture_opts->has_ring_num_files) { + char sring_num_files[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-b"); g_snprintf(sring_num_files, ARGV_NUMBER_LEN, "files:%d",capture_opts->ring_num_files); argv = sync_pipe_add_arg(argv, &argc, sring_num_files); } if (capture_opts->has_autostop_files) { + char sautostop_files[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-a"); g_snprintf(sautostop_files, ARGV_NUMBER_LEN, "files:%d",capture_opts->autostop_files); argv = sync_pipe_add_arg(argv, &argc, sautostop_files); } } else { if (capture_opts->has_autostop_filesize) { + char sautostop_filesize[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-a"); g_snprintf(sautostop_filesize, ARGV_NUMBER_LEN, "filesize:%u",capture_opts->autostop_filesize); argv = sync_pipe_add_arg(argv, &argc, sautostop_filesize); @@ -309,12 +302,14 @@ sync_pipe_start(capture_options *capture_opts, capture_session *cap_session, inf } if (capture_opts->has_autostop_packets) { + char scount[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-c"); g_snprintf(scount, ARGV_NUMBER_LEN, "%d",capture_opts->autostop_packets); argv = sync_pipe_add_arg(argv, &argc, scount); } if (capture_opts->has_autostop_duration) { + char sautostop_duration[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-a"); g_snprintf(sautostop_duration, ARGV_NUMBER_LEN, "duration:%f",capture_opts->autostop_duration); argv = sync_pipe_add_arg(argv, &argc, sautostop_duration); @@ -338,6 +333,7 @@ sync_pipe_start(capture_options *capture_opts, capture_session *cap_session, inf argv = sync_pipe_add_arg(argv, &argc, interface_opts->cfilter); } if (interface_opts->has_snaplen) { + char ssnap[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-s"); g_snprintf(ssnap, ARGV_NUMBER_LEN, "%d", interface_opts->snaplen); argv = sync_pipe_add_arg(argv, &argc, ssnap); @@ -358,6 +354,7 @@ sync_pipe_start(capture_options *capture_opts, capture_session *cap_session, inf #ifdef CAN_SET_CAPTURE_BUFFER_SIZE if (interface_opts->buffer_size != DEFAULT_CAPTURE_BUFFER_SIZE) { + char buffer_size[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-B"); if(interface_opts->buffer_size == 0x00) interface_opts->buffer_size = DEFAULT_CAPTURE_BUFFER_SIZE; @@ -380,6 +377,7 @@ sync_pipe_start(capture_options *capture_opts, capture_session *cap_session, inf argv = sync_pipe_add_arg(argv, &argc, "-r"); if (interface_opts->auth_type == CAPTURE_AUTH_PWD) { + char sauth[256]; argv = sync_pipe_add_arg(argv, &argc, "-A"); g_snprintf(sauth, sizeof(sauth), "%s:%s", interface_opts->auth_username, @@ -390,6 +388,7 @@ sync_pipe_start(capture_options *capture_opts, capture_session *cap_session, inf #ifdef HAVE_PCAP_SETSAMPLING if (interface_opts->sampling_method != CAPTURE_SAMP_NONE) { + char ssampling[ARGV_NUMBER_LEN]; argv = sync_pipe_add_arg(argv, &argc, "-m"); g_snprintf(ssampling, ARGV_NUMBER_LEN, "%s:%d", interface_opts->sampling_method == CAPTURE_SAMP_BY_COUNT ? "count" : diff --git a/capture_opts.c b/capture_opts.c index 1a81b7fc32..4750cddd19 100644 --- a/capture_opts.c +++ b/capture_opts.c @@ -101,6 +101,8 @@ capture_opts_init(capture_options *capture_opts) capture_opts->file_duration = 60.0; /* 1 min */ capture_opts->has_file_interval = FALSE; capture_opts->file_interval = 60; /* 1 min */ + capture_opts->has_file_packets = FALSE; + capture_opts->file_packets = 0; capture_opts->has_ring_num_files = FALSE; capture_opts->ring_num_files = RINGBUFFER_MIN_NUM_FILES; @@ -243,6 +245,7 @@ capture_opts_log(const char *log_domain, GLogLevelFlags log_level, capture_optio g_log(log_domain, log_level, "MultiFilesOn : %u", capture_opts->multi_files_on); g_log(log_domain, log_level, "FileDuration (%u) : %.3f", capture_opts->has_file_duration, capture_opts->file_duration); g_log(log_domain, log_level, "FileInterval (%u) : %u", capture_opts->has_file_interval, capture_opts->file_interval); + g_log(log_domain, log_level, "FilePackets (%u) : %u", capture_opts->has_file_packets, capture_opts->file_packets); g_log(log_domain, log_level, "RingNumFiles (%u) : %u", capture_opts->has_ring_num_files, capture_opts->ring_num_files); g_log(log_domain, log_level, "AutostopFiles (%u) : %u", capture_opts->has_autostop_files, capture_opts->autostop_files); @@ -295,6 +298,9 @@ set_autostop_criterion(capture_options *capture_opts, const char *autostoparg) capture_opts->multi_files_on = TRUE; capture_opts->has_autostop_files = TRUE; capture_opts->autostop_files = get_positive_int(p,"autostop files"); + } else if (strcmp(autostoparg,"packets") == 0) { + capture_opts->has_autostop_packets = TRUE; + capture_opts->autostop_packets = get_positive_int(p,"packet count"); } else { return FALSE; } @@ -401,6 +407,9 @@ get_ring_arguments(capture_options *capture_opts, const char *arg) } else if (strcmp(arg,"interval") == 0) { capture_opts->has_file_interval = TRUE; capture_opts->file_interval = get_positive_int(p, "ring buffer interval"); + } else if (strcmp(arg,"packets") == 0) { + capture_opts->has_file_packets = TRUE; + capture_opts->file_packets = get_positive_int(p, "ring buffer packet count"); } *colonp = ':'; /* put the colon back */ @@ -805,6 +814,7 @@ capture_opts_add_opt(capture_options *capture_opts, int opt, const char *optarg_ break; #endif case 'c': /* Capture n packets */ + /* XXX Use set_autostop_criterion instead? */ capture_opts->has_autostop_packets = TRUE; capture_opts->autostop_packets = get_positive_int(optarg_str_p, "packet count"); break; diff --git a/capture_opts.h b/capture_opts.h index a403b495a2..8f2e9648ea 100644 --- a/capture_opts.h +++ b/capture_opts.h @@ -293,13 +293,16 @@ typedef struct capture_options_tag { gdouble file_duration; /**< Switch file after n seconds */ gboolean has_file_interval; /**< TRUE if ring interval specified */ gint32 file_interval; /**< Create time intervals of n seconds */ + gboolean has_file_packets; /**< TRUE if ring packet count is + specified */ + int file_packets; /**< Switch file after n packets */ gboolean has_ring_num_files; /**< TRUE if ring num_files specified */ guint32 ring_num_files; /**< Number of multiple buffer files */ /* autostop conditions */ gboolean has_autostop_files; /**< TRUE if maximum number of capture files are specified */ - gint32 autostop_files; /**< Maximum number of capture files */ + int autostop_files; /**< Maximum number of capture files */ gboolean has_autostop_packets; /**< TRUE if maximum packet count is specified */ diff --git a/doc/dumpcap.pod b/doc/dumpcap.pod index 87ec453ff8..ae2e024ff4 100644 --- a/doc/dumpcap.pod +++ b/doc/dumpcap.pod @@ -74,13 +74,16 @@ where I is one of: B:I Stop writing to a capture file after I seconds have elapsed. Floating point values (e.g. 0.5) are allowed. +B:I Stop writing to capture files after I number of files +were written. + B:I Stop writing to a capture file after it reaches a size of I kB. If this option is used together with the -b option, dumpcap will stop writing to the current capture file and switch to the next one if filesize is reached. Note that the filesize is limited to a maximum value of 2 GiB. -B:I Stop writing to capture files after I number of files -were written. +B:I Stop writing to a capture file after I packets +have been written. Same as B<-c> Ecapture packet countE. =item -b Ecapture ring buffer optionE @@ -106,12 +109,6 @@ B:I switch to the next file after I seconds have elapsed, even if the current file is not completely filled up. Floating point values (e.g. 0.5) are allowed. -B:I switch to the next file when the time is an exact -multiple of I seconds - -B:I switch to the next file after it reaches a size of -I kB. Note that the filesize is limited to a maximum value of 2 GiB. - B:I begin again with the first file after I number of files were written (form a ring buffer). This value must be less than 100000. Caution should be used when using large numbers of files: some filesystems do @@ -121,6 +118,15 @@ control when to go to the next file. It should be noted that each B<-b> parameter takes exactly one criterion; to specify two criterion, each must be preceded by the B<-b> option. +B:I switch to the next file after it reaches a size of +I kB. Note that the filesize is limited to a maximum value of 2 GiB. + +B:I switch to the next file when the time is an exact +multiple of I seconds + +B:I switch to the next file after it contains I +packets. + Example: B<-b filesize:1000 -b files:5> results in a ring buffer of five files of size one megabyte each. @@ -148,7 +154,7 @@ the default capture buffer size is used instead. =item -c Ecapture packet countE Set the maximum number of packets to read when capturing live -data. +data. Same as B<-a packets:>Ecapture packet countE. =item -C Ebyte limitE diff --git a/doc/tshark.pod b/doc/tshark.pod index 7fa123efaf..b3e0924b74 100644 --- a/doc/tshark.pod +++ b/doc/tshark.pod @@ -218,6 +218,9 @@ where I is one of: B:I Stop writing to a capture file after I seconds have elapsed. Floating point values (e.g. 0.5) are allowed. +B:I Stop writing to capture files after I number of files +were written. + B:I Stop writing to a capture file after it reaches a size of I kB. If this option is used together with the -b option, B will stop writing to the current capture file and switch to the next one if @@ -226,8 +229,8 @@ the file after the number of bytes read exceeds this number (the complete packet will be read, so more bytes than this number may be read). Note that the filesize is limited to a maximum value of 2 GiB. -B:I Stop writing to capture files after I number of files -were written. +B:I switch to the next file after it contains I +packets. Same as B<-c>Ecapture packet countE. =item -b Ecapture ring buffer optionE @@ -253,12 +256,6 @@ B:I switch to the next file after I seconds have elapsed, even if the current file is not completely filled up. Floating point values (e.g. 0.5) are allowed. -B:I switch to the next file when the time is an exact -multiple of I seconds - -B:I switch to the next file after it reaches a size of -I kB. Note that the filesize is limited to a maximum value of 2 GiB. - B:I begin again with the first file after I number of files were written (form a ring buffer). This value must be less than 100000. Caution should be used when using large numbers of files: some filesystems do @@ -268,6 +265,15 @@ control when to go to the next file. It should be noted that each B<-b> parameter takes exactly one criterion; to specify two criterion, each must be preceded by the B<-b> option. +B:I switch to the next file after it reaches a size of +I kB. Note that the filesize is limited to a maximum value of 2 GiB. + +B:I switch to the next file when the time is an exact +multiple of I seconds + +B:I switch to the next file after it contains I +packets. + Example: B results in a ring buffer of five files of size one megabyte each. @@ -295,7 +301,8 @@ the default capture buffer size is used instead. =item -c Ecapture packet countE Set the maximum number of packets to read when capturing live -data. If reading a capture file, set the maximum number of packets to read. +data. Same as B<-a packets:>Ecapture packet countE. +If reading a capture file, set the maximum number of packets to read. =item -C Econfiguration profileE diff --git a/doc/wireshark.pod.template b/doc/wireshark.pod.template index 77eaeda232..7cee21502f 100644 --- a/doc/wireshark.pod.template +++ b/doc/wireshark.pod.template @@ -242,14 +242,17 @@ where I is one of: B:I Stop writing to a capture file after I seconds have elapsed. Floating point values (e.g. 0.5) are allowed. +B:I Stop writing to capture files after I number of files +were written. + B:I Stop writing to a capture file after it reaches a size of I kB. If this option is used together with the -b option, Wireshark will stop writing to the current capture file and switch to the next one if filesize is reached. Note that the filesize is limited to a maximum value of 2 GiB. -B:I Stop writing to capture files after I number of files -were written. +B:I switch to the next file after it contains I +packets. Same as B<-c>Ecapture packet countE. =item -b Ecapture ring buffer optionE @@ -275,12 +278,6 @@ B:I switch to the next file after I seconds have elapsed, even if the current file is not completely filled up. Floating point values (e.g. 0.5) are allowed. -B:I switch to the next file when the time is an exact -multiple of I seconds - -B:I switch to the next file after it reaches a size of -I kB. Note that the filesize is limited to a maximum value of 2 GiB. - B:I begin again with the first file after I number of files were written (form a ring buffer). This value must be less than 100000. Caution should be used when using large numbers of files: some filesystems do @@ -290,6 +287,15 @@ control when to go to the next file. It should be noted that each B<-b> parameter takes exactly one criterion; to specify two criterion, each must be preceded by the B<-b> option. +B:I switch to the next file after it reaches a size of +I kB. Note that the filesize is limited to a maximum value of 2 GiB. + +B:I switch to the next file when the time is an exact +multiple of I seconds + +B:I switch to the next file after it contains I +packets. + Example: B<-b filesize:1000 -b files:5> results in a ring buffer of five files of size one megabyte each. @@ -317,7 +323,7 @@ the default capture buffer size is used instead. =item -c Ecapture packet countE Set the maximum number of packets to read when capturing live -data. +data. Same as B<-a packets:>Ecapture packet countE. =item -C Econfiguration profileE diff --git a/docbook/release-notes.asciidoc b/docbook/release-notes.asciidoc index b66743f201..4fad9febd7 100644 --- a/docbook/release-notes.asciidoc +++ b/docbook/release-notes.asciidoc @@ -60,6 +60,7 @@ since version 2.6.0: * When importing from hex dump, it's now possible to add an ExportPDU header with a payload name. This calls the specific dissector directly without lower protocols. * sshdump and ciscodump can now use a proxy for the ssh connection. +* Dumpcap now supports the `-a packets:NUM` and `-b packets:NUM` options. === Removed Features and Support diff --git a/dumpcap.c b/dumpcap.c index b66e964f79..7590338422 100644 --- a/dumpcap.c +++ b/dumpcap.c @@ -291,8 +291,7 @@ typedef struct _loop_data { /* common */ gboolean go; /**< TRUE as long as we're supposed to keep capturing */ int err; /**< if non-zero, error seen while capturing */ - gint packet_count; /**< Number of packets we have already captured */ - gint packet_max; /**< Number of packets we're supposed to capture - 0 means infinite */ + gint packets_captured; /**< Number of packets we have already captured */ guint inpkts_to_sync_pipe; /**< Packets not already send out to the sync_pipe */ #ifdef SIGINFO gboolean report_packet_count; /**< Set by SIGINFO handler; print packet count */ @@ -301,8 +300,14 @@ typedef struct _loop_data { /* output file(s) */ FILE *pdh; int save_file_fd; - guint64 bytes_written; - guint32 autostop_files; + guint64 bytes_written; /**< Bytes written for the current file. */ + /* autostop conditions */ + int packets_written; /**< Packets written for the current file. */ + int file_count; + /* ring buffer conditions */ + GTimer *file_duration_timer; + time_t next_interval_time; + int interval_s; } loop_data; typedef struct _pcap_queue_element { @@ -314,14 +319,6 @@ typedef struct _pcap_queue_element { u_char *pd; } pcap_queue_element; -/* Conditions required by do_file_switch_or_stop */ -typedef struct _condition_data { - GTimer *file_duration_timer; - time_t next_interval_time; - int interval_s; - unsigned autostop_files; -} condition_data; - /* * Standard secondary message for unexpected errors. */ @@ -455,6 +452,7 @@ print_usage(FILE *output) fprintf(output, " -a ... duration:NUM - stop after NUM seconds\n"); fprintf(output, " filesize:NUM - stop this file after NUM kB\n"); fprintf(output, " files:NUM - stop after NUM files\n"); + fprintf(output, " packets:NUM - stop after NUM packets\n"); /*fprintf(output, "\n");*/ fprintf(output, "Output (files):\n"); fprintf(output, " -w name of file to save (def: tempfile)\n"); @@ -463,6 +461,7 @@ print_usage(FILE *output) fprintf(output, " interval:NUM - create time intervals of NUM secs\n"); fprintf(output, " filesize:NUM - switch to next file after NUM kB\n"); fprintf(output, " files:NUM - ringbuffer: replace after NUM files\n"); + fprintf(output, " packets:NUM - ringbuffer: replace after NUM packets\n"); fprintf(output, " -n use pcapng format instead of pcap (default)\n"); fprintf(output, " -P use libpcap format instead of pcapng\n"); fprintf(output, " --capture-comment \n"); @@ -1097,7 +1096,7 @@ report_capture_count(gboolean reportit) { /* Don't print this if we're a capture child. */ if (!capture_child && reportit) { - fprintf(stderr, "\rPackets captured: %d\n", global_ld.packet_count); + fprintf(stderr, "\rPackets captured: %d\n", global_ld.packets_captured); /* stderr could be line buffered */ fflush(stderr); } @@ -2275,7 +2274,7 @@ pcap_pipe_dispatch(loop_data *ld, capture_src *pcap_src, char *errmsg, int errms * instead stop with an error. */ g_snprintf(errmsg, errmsgl, "Frame %u too long (%d bytes)", - ld->packet_count+1, pcap_info->rechdr.hdr.incl_len); + ld->packets_captured+1, pcap_info->rechdr.hdr.incl_len); break; } @@ -2505,7 +2504,7 @@ pcapng_pipe_dispatch(loop_data *ld, capture_src *pcap_src, char *errmsg, int err * instead stop with an error. */ g_snprintf(errmsg, errmsgl, "Frame %u too long (%d bytes)", - ld->packet_count+1, bh->block_total_length); + ld->packets_captured+1, bh->block_total_length); break; } @@ -3086,7 +3085,7 @@ capture_loop_dispatch(loop_data *ld, gint packet_count_before; int sel_ret; - packet_count_before = ld->packet_count; + packet_count_before = ld->packets_captured; if (pcap_src->from_cap_pipe) { /* dispatch from capture pipe */ #ifdef LOG_CAPTURE_VERBOSE @@ -3254,7 +3253,7 @@ capture_loop_dispatch(loop_data *ld, g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: %d new packet%s", inpkts, plurality(inpkts, "", "s")); #endif - return ld->packet_count - packet_count_before; + return ld->packets_captured - packet_count_before; } #ifdef _WIN32 @@ -3462,8 +3461,7 @@ static time_t get_next_time_interval(int interval_s) { /* Do the work of handling either the file size or file duration capture conditions being reached, and switching files or stopping. */ static gboolean -do_file_switch_or_stop(capture_options *capture_opts, - condition_data *conditions) +do_file_switch_or_stop(capture_options *capture_opts) { guint i; capture_src *pcap_src; @@ -3471,8 +3469,8 @@ do_file_switch_or_stop(capture_options *capture_opts, gboolean successful; if (capture_opts->multi_files_on) { - if (conditions->autostop_files && - ++global_ld.autostop_files >= conditions->autostop_files) { + if (capture_opts->has_autostop_files && + ++global_ld.file_count >= capture_opts->autostop_files) { /* no files left: stop here */ global_ld.go = FALSE; return FALSE; @@ -3484,6 +3482,7 @@ do_file_switch_or_stop(capture_options *capture_opts, /* File switch succeeded: reset the conditions */ global_ld.bytes_written = 0; + global_ld.packets_written = 0; pcap_src = g_array_index(global_ld.pcaps, capture_src *, 0); if (pcap_src->from_pcapng) { /* Write the saved SHB and all IDBs to start of next file */ @@ -3554,11 +3553,11 @@ do_file_switch_or_stop(capture_options *capture_opts, global_ld.go = FALSE; return FALSE; } - if (conditions->file_duration_timer) { - g_timer_reset(conditions->file_duration_timer); + if (global_ld.file_duration_timer) { + g_timer_reset(global_ld.file_duration_timer); } - if (conditions->next_interval_time) { - conditions->next_interval_time = get_next_time_interval(conditions->interval_s); + if (global_ld.next_interval_time) { + global_ld.next_interval_time = get_next_time_interval(global_ld.interval_s); } fflush(global_ld.pdh); if (!quiet) @@ -3611,7 +3610,6 @@ capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct #endif int err_close; int inpkts; - condition_data conditions; GTimer *autostop_duration_timer = NULL; gboolean write_ok; gboolean close_ok; @@ -3627,19 +3625,18 @@ capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct /* init the loop data */ global_ld.go = TRUE; - global_ld.packet_count = 0; + global_ld.packets_captured = 0; #ifdef SIGINFO global_ld.report_packet_count = FALSE; #endif - if (capture_opts->has_autostop_packets) - global_ld.packet_max = capture_opts->autostop_packets; - else - global_ld.packet_max = 0; /* no limit */ global_ld.inpkts_to_sync_pipe = 0; global_ld.err = 0; /* no error seen yet */ global_ld.pdh = NULL; - global_ld.autostop_files = 0; global_ld.save_file_fd = -1; + global_ld.file_count = 0; + global_ld.file_duration_timer = NULL; + global_ld.next_interval_time = 0; + global_ld.interval_s = 0; /* We haven't yet gotten the capture statistics. */ *stats_known = FALSE; @@ -3713,11 +3710,9 @@ capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct report_new_capture_file(capture_opts->save_file); } - /* initialize capture stop (and alike) conditions */ - memset(&conditions, 0, sizeof(conditions)); if (capture_opts->has_file_interval) { - conditions.interval_s = capture_opts->file_interval; - conditions.next_interval_time = get_next_time_interval(conditions.interval_s); + global_ld.interval_s = capture_opts->file_interval; + global_ld.next_interval_time = get_next_time_interval(global_ld.interval_s); } /* create stop conditions */ if (capture_opts->has_autostop_filesize) { @@ -3731,11 +3726,8 @@ capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct if (capture_opts->multi_files_on) { if (capture_opts->has_file_duration) { - conditions.file_duration_timer = g_timer_new(); + global_ld.file_duration_timer = g_timer_new(); } - - if (capture_opts->has_autostop_files) - conditions.autostop_files = capture_opts->autostop_files; } /* init the time values */ @@ -3809,8 +3801,8 @@ capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct #ifdef SIGINFO /* Were we asked to print packet counts by the SIGINFO handler? */ if (global_ld.report_packet_count) { - fprintf(stderr, "%u packet%s captured\n", global_ld.packet_count, - plurality(global_ld.packet_count, "", "s")); + fprintf(stderr, "%u packet%s captured\n", global_ld.packets_captured, + plurality(global_ld.packets_captured, "", "s")); global_ld.report_packet_count = FALSE; } #endif @@ -3825,14 +3817,6 @@ capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct if (inpkts > 0) { global_ld.inpkts_to_sync_pipe += inpkts; - /* check capture size condition */ - if (capture_opts->has_autostop_filesize && - capture_opts->autostop_filesize > 0 && - global_ld.bytes_written / 1000 >= capture_opts->autostop_filesize) { - /* Capture size limit reached, do we have another file? */ - if (!do_file_switch_or_stop(capture_opts, &conditions)) - continue; - } /* cnd_autostop_size */ if (capture_opts->output_to_pipe) { fflush(global_ld.pdh); } @@ -3882,16 +3866,16 @@ capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct } /* check capture file duration condition */ - if (conditions.file_duration_timer != NULL && g_timer_elapsed(conditions.file_duration_timer, NULL) >= capture_opts->file_duration) { + if (global_ld.file_duration_timer != NULL && g_timer_elapsed(global_ld.file_duration_timer, NULL) >= capture_opts->file_duration) { /* duration limit reached, do we have another file? */ - if (!do_file_switch_or_stop(capture_opts, &conditions)) + if (!do_file_switch_or_stop(capture_opts)) continue; } /* cnd_file_duration */ /* check capture file interval condition */ - if (conditions.interval_s && time(NULL) >= conditions.next_interval_time) { + if (global_ld.interval_s && time(NULL) >= global_ld.next_interval_time) { /* end of interval reached, do we have another file? */ - if (!do_file_switch_or_stop(capture_opts, &conditions)) + if (!do_file_switch_or_stop(capture_opts)) continue; } /* cnd_file_interval */ } @@ -3937,8 +3921,8 @@ capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct /* delete stop conditions */ - if (conditions.file_duration_timer != NULL) - g_timer_destroy(conditions.file_duration_timer); + if (global_ld.file_duration_timer != NULL) + g_timer_destroy(global_ld.file_duration_timer); if (autostop_duration_timer != NULL) g_timer_destroy(autostop_duration_timer); @@ -4210,10 +4194,13 @@ capture_loop_write_pcapng_cb(capture_src *pcap_src, const struct pcapng_block_he "Wrote a packet of length %d captured on interface %u.", bh->block_total_length, pcap_src->interface_id); #endif - global_ld.packet_count++; + global_ld.packets_captured++; + global_ld.packets_written++; pcap_src->received++; + /* if the user told us to stop after x packets, do we already have enough? */ - if ((global_ld.packet_max > 0) && (global_ld.packet_count >= global_ld.packet_max)) { + if (global_capture_opts.has_autostop_packets && global_ld.packets_captured >= global_capture_opts.autostop_packets) { + fflush(global_ld.pdh); global_ld.go = FALSE; } } @@ -4271,11 +4258,28 @@ capture_loop_write_packet_cb(u_char *pcap_src_p, const struct pcap_pkthdr *phdr, "Wrote a packet of length %d captured on interface %u.", phdr->caplen, pcap_src->interface_id); #endif - global_ld.packet_count++; + global_ld.packets_captured++; + global_ld.packets_written++; pcap_src->received++; - /* if the user told us to stop after x packets, do we already have enough? */ - if ((global_ld.packet_max > 0) && (global_ld.packet_count >= global_ld.packet_max)) { + + /* check -c NUM / -a packets:NUM */ + if (global_capture_opts.has_autostop_packets && global_ld.packets_captured >= global_capture_opts.autostop_packets) { + fflush(global_ld.pdh); global_ld.go = FALSE; + return; + } + /* check -b packets:NUM */ + if (global_capture_opts.has_file_packets && global_ld.packets_written >= global_capture_opts.file_packets) { + do_file_switch_or_stop(&global_capture_opts); + return; + } + /* check -a filesize:NUM */ + if (global_capture_opts.has_autostop_filesize && + global_capture_opts.autostop_filesize > 0 && + global_ld.bytes_written / 1000 >= global_capture_opts.autostop_filesize) { + /* Capture size limit reached, do we have another file? */ + do_file_switch_or_stop(&global_capture_opts); + return; } } } @@ -5034,9 +5038,10 @@ real_main(int argc, char *argv[]) } if (!global_capture_opts.has_autostop_filesize && !global_capture_opts.has_file_duration && - !global_capture_opts.has_file_interval) { - cmdarg_err("Ring buffer requested, but no maximum capture file size, duration" - "or interval were specified."); + !global_capture_opts.has_file_interval && + !global_capture_opts.has_file_packets) { + cmdarg_err("Ring buffer requested, but no maximum capture file size, duration " + "interval, or packets were specified."); #if 0 /* XXX - this must be redesigned as the conditions changed */ global_capture_opts.multi_files_on = FALSE; diff --git a/test/suite_capture.py b/test/suite_capture.py index cdb78b4a2f..eb1a4ffcab 100644 --- a/test/suite_capture.py +++ b/test/suite_capture.py @@ -10,6 +10,7 @@ '''Capture tests''' import config +import glob import os import re import subprocess @@ -17,6 +18,7 @@ import subprocesstest import sys import time import unittest +import uuid capture_duration = 5 @@ -139,11 +141,6 @@ def check_capture_stdin(self, cmd=None): if (pipe_returncode == 0): self.checkPacketCount(8) -def check_capture_2multi_10packets(self, cmd=None): - # This was present in the Bash version but was incorrect and not part of any suite. - # It's apparently intended to test file rotation. - self.skipTest('Not yet implemented') - def check_capture_read_filter(self, cmd=None): if not config.canCapture(): self.skipTest('Test requires capture privileges and an interface.') @@ -207,6 +204,86 @@ def check_capture_snapshot_len(self, cmd=None): if (capture_returncode == 0): self.checkPacketCount(0, cap_file=testout2_file) +def check_dumpcap_autostop_stdin(self, packets=None, filesize=None): + # Similar to check_capture_stdin. + cmd = config.cmd_dumpcap + capture_file = os.path.join(config.capture_dir, 'dhcp.pcap') + testout_file = self.filename_from_id(testout_pcap) + cat100_dhcp_cmd = subprocesstest.cat_dhcp_command('cat100') + condition='oops:invalid' + + self.assertTrue(packets is not None or filesize is not None, 'Need one of packets or filesize') + self.assertFalse(packets is not None and filesize is not None, 'Need one of packets or filesize') + + if packets is not None: + condition = 'packets:{}'.format(packets) + elif filesize is not None: + condition = 'filesize:{}'.format(filesize) + + capture_cmd = subprocesstest.capture_command(cmd, + '-i', '-', + '-w', testout_file, + '-a', condition, + shell=True + ) + pipe_proc = self.runProcess(cat100_dhcp_cmd + ' | ' + capture_cmd, shell=True) + pipe_returncode = pipe_proc.returncode + self.assertEqual(pipe_returncode, 0) + self.assertTrue(os.path.isfile(testout_file)) + if (pipe_returncode != 0): + return + + if packets is not None: + self.checkPacketCount(packets) + elif filesize is not None: + capturekb = os.path.getsize(testout_file) / 1000 + self.assertGreaterEqual(capturekb, filesize) + +def check_dumpcap_ringbuffer_stdin(self, packets=None, filesize=None): + # Similar to check_capture_stdin. + cmd = config.cmd_dumpcap + capture_file = os.path.join(config.capture_dir, 'dhcp.pcap') + rb_unique = 'dhcp_rb_' + uuid.uuid4().hex[:6] # Random ID + testout_file = '{}.{}.pcapng'.format(self.id(), rb_unique) + testout_glob = '{}.{}_*.pcapng'.format(self.id(), rb_unique) + cat100_dhcp_cmd = subprocesstest.cat_dhcp_command('cat100') + condition='oops:invalid' + + self.assertTrue(packets is not None or filesize is not None, 'Need one of packets or filesize') + self.assertFalse(packets is not None and filesize is not None, 'Need one of packets or filesize') + + if packets is not None: + condition = 'packets:{}'.format(packets) + elif filesize is not None: + condition = 'filesize:{}'.format(filesize) + + capture_cmd = subprocesstest.capture_command(cmd, + '-i', '-', + '-w', testout_file, + '-a', 'files:2', + '-b', condition, + shell=True + ) + pipe_proc = self.runProcess(cat100_dhcp_cmd + ' | ' + capture_cmd, shell=True) + pipe_returncode = pipe_proc.returncode + self.assertEqual(pipe_returncode, 0) + if (pipe_returncode != 0): + return + + rb_files = glob.glob(testout_glob) + for rbf in rb_files: + self.cleanup_files.append(rbf) + + self.assertEqual(len(rb_files), 2) + + for rbf in rb_files: + self.assertTrue(os.path.isfile(rbf)) + if packets is not None: + self.checkPacketCount(packets, cap_file=rbf) + elif filesize is not None: + capturekb = os.path.getsize(rbf) / 1000 + self.assertGreaterEqual(capturekb, filesize) + class case_wireshark_capture(subprocesstest.SubprocessTestCase): def test_wireshark_capture_10_packets_to_file(self): '''Capture 10 packets from the network to a file using Wireshark''' @@ -270,3 +347,24 @@ class case_dumpcap_capture(subprocesstest.SubprocessTestCase): def test_dumpcap_capture_snapshot_len(self): '''Capture truncated packets using Dumpcap''' check_capture_snapshot_len(self, cmd=config.cmd_dumpcap) + +class case_dumpcap_autostop(subprocesstest.SubprocessTestCase): + # duration, filesize, packets, files + def test_dumpcap_autostop_filesize(self): + '''Capture from stdin using Dumpcap until we reach a file size limit''' + check_dumpcap_autostop_stdin(self, filesize=15) + + def test_dumpcap_autostop_packets(self): + '''Capture from stdin using Dumpcap until we reach a packet limit''' + check_dumpcap_autostop_stdin(self, packets=97) # Last prime before 100. Arbitrary. + +class case_dumpcap_ringbuffer(subprocesstest.SubprocessTestCase): + # duration, interval, filesize, packets, files + # Need a function that finds ringbuffer file names. + def test_dumpcap_ringbuffer_filesize(self): + '''Capture from stdin using Dumpcap and write multiple files until we reach a file size limit''' + check_dumpcap_ringbuffer_stdin(self, filesize=15) + + def test_dumpcap_ringbuffer_packets(self): + '''Capture from stdin using Dumpcap and write multiple files until we reach a packet limit''' + check_dumpcap_ringbuffer_stdin(self, packets=47) # Last prime before 50. Arbitrary. diff --git a/test/util_dump_dhcp_pcap.py b/test/util_dump_dhcp_pcap.py index 2e0b242818..28b3823767 100755 --- a/test/util_dump_dhcp_pcap.py +++ b/test/util_dump_dhcp_pcap.py @@ -17,8 +17,8 @@ import sys def main(): parser = argparse.ArgumentParser(description='Dump dhcp.pcap') - parser.add_argument('dump_type', choices=['cat', 'slow', 'raw'], - help='cat: Just dump the file. slow: Dump the file, pause, and dump its packet records. raw: Dump only the packet records.') + parser.add_argument('dump_type', choices=['cat', 'cat100', 'slow', 'raw'], + help='cat: Just dump the file. cat100: Dump 100 packet records. slow: Dump the file, pause, and dump its packet records. raw: Dump only the packet records.') args = parser.parse_args() dhcp_pcap = os.path.join(os.path.dirname(__file__), 'captures', 'dhcp.pcap') @@ -27,12 +27,17 @@ def main(): contents = dhcp_fd.read() if args.dump_type != 'raw': os.write(1, contents) - if args.dump_type == 'cat': + if args.dump_type == 'cat100': + # The capture contains 4 packets. Write 96 more. + for _ in range(24): + os.write(1, contents[24:]) + if args.dump_type.startswith('cat'): sys.exit(0) if args.dump_type == 'slow': time.sleep(1.5) # slow, raw os.write(1, contents[24:]) + sys.exit(0) if __name__ == '__main__': diff --git a/ui/qt/capture_interfaces_dialog.cpp b/ui/qt/capture_interfaces_dialog.cpp index 0bef6cba84..b9dc86ff9c 100644 --- a/ui/qt/capture_interfaces_dialog.cpp +++ b/ui/qt/capture_interfaces_dialog.cpp @@ -549,6 +549,10 @@ void CaptureInterfacesDialog::updateInterfaces() } ui->gbNewFileAuto->setChecked(global_capture_opts.multi_files_on); + ui->PktCheckBox->setChecked(global_capture_opts.has_file_packets); + if (global_capture_opts.has_file_packets) { + ui->PktSpinBox->setValue(global_capture_opts.file_packets); + } ui->MBCheckBox->setChecked(global_capture_opts.has_autostop_filesize); ui->SecsCheckBox->setChecked(global_capture_opts.has_file_interval); if (global_capture_opts.has_autostop_filesize) { @@ -850,6 +854,10 @@ bool CaptureInterfacesDialog::saveOptionsToPreferences() break; } } + global_capture_opts.has_file_packets = ui->PktCheckBox->isChecked(); + if (global_capture_opts.has_file_packets) { + global_capture_opts.file_packets = ui->PktSpinBox->value(); + } global_capture_opts.has_autostop_filesize = ui->MBCheckBox->isChecked(); if (global_capture_opts.has_autostop_filesize) { global_capture_opts.autostop_filesize = ui->MBSpinBox->value(); @@ -878,9 +886,12 @@ bool CaptureInterfacesDialog::saveOptionsToPreferences() QMessageBox::warning(this, tr("Error"), tr("Multiple files: No capture file name given. You must specify a filename if you want to use multiple files.")); return false; - } else if (!global_capture_opts.has_autostop_filesize && !global_capture_opts.has_file_interval) { + } else if (!global_capture_opts.has_autostop_filesize && + !global_capture_opts.has_file_interval && + !global_capture_opts.has_file_duration && + !global_capture_opts.has_file_packets) { QMessageBox::warning(this, tr("Error"), - tr("Multiple files: No file limit given. You must specify a file size or interval at which is switched to the next capture file\n if you want to use multiple files.")); + tr("Multiple files: No file limit given. You must specify a file size, interval, or number of packets for each file.")); g_free(global_capture_opts.save_file); global_capture_opts.save_file = NULL; return false; diff --git a/ui/qt/capture_interfaces_dialog.ui b/ui/qt/capture_interfaces_dialog.ui index 7d3c416f27..181f5df332 100644 --- a/ui/qt/capture_interfaces_dialog.ui +++ b/ui/qt/capture_interfaces_dialog.ui @@ -262,10 +262,38 @@ true - - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + - <html><head/><body><p>If the selected file size is exceeded, capturing switches to the next file.</p><p>PLEASE NOTE: One option MUST be selected.</p></body></html> + If the selected file size is exceeded, capturing switches to the next file. +PLEASE NOTE: One option MUST be selected. true @@ -284,30 +312,7 @@ - - - - If the selected file size is exceeded, capturing switches to the next file. -PLEASE NOTE: One option MUST be selected. - - - - kilobytes - - - - - megabytes - - - - - gigabytes - - - - - + If the selected file size is exceeded, capturing switches to the next file. @@ -331,10 +336,9 @@ PLEASE NOTE: One option MUST be selected. - + - If the selected file size is exceeded, capturing switches to the next file. -PLEASE NOTE: One option MUST be selected. + <html><head/><body><p>If the selected file size is exceeded, capturing switches to the next file.</p><p>PLEASE NOTE: One option MUST be selected.</p></body></html> true @@ -353,30 +357,56 @@ PLEASE NOTE: One option MUST be selected. - - - - Qt::Horizontal + + + + If the selected file size is exceeded, capturing switches to the next file. +PLEASE NOTE: One option MUST be selected. - - - 40 - 20 - - - + + + kilobytes + + + + + megabytes + + + + + gigabytes + + + - + - - + + + + Switch to the next file after the specified number of packets have been captured. + + + QAbstractSpinBox::PlusMinus + + + 2147483647 + + + 100000 + + + + + - + packets