Dumpcap: Fix writing SHBs and IDBs.

If we have a single capture source and that capture source is pcapng and
we're writing a pcapng file, do the following:

- Pass its SHB and IDBs through unmodified. Don't save or write command
  line interface IDBs.

- Save the most recent SHB and IDBs so that we can write them when we're
  writing multiple output files.

If we have multiple capture sources, do the following:

- Write Dumpcap's SHB.

- Keep a global list of IDBs, consisting of both command line interfaces
  and IDBs read from pcapng sources.

- When reading an EPB or ISB, remap its local interface number to its
  corresponding global number.

Add Dumpcap pcapng section tests. Make the application IDs in the
"many_interfaces" captures unique.

Change-Id: I2005934c1f83d839727421960005f106d6c682dd
Reviewed-on: https://code.wireshark.org/review/30085
Petri-Dish: Gerald Combs <gerald@wireshark.org>
Tested-by: Petri Dish Buildbot
Reviewed-by: Gerald Combs <gerald@wireshark.org>
This commit is contained in:
Gerald Combs 2018-10-08 13:25:36 -07:00
parent 377f5d0de7
commit f300676bec
6 changed files with 473 additions and 157 deletions

431
dumpcap.c
View File

@ -227,8 +227,7 @@ typedef struct _pcap_pipe_info {
typedef struct _pcapng_pipe_info { typedef struct _pcapng_pipe_info {
struct pcapng_block_header_s bh; /**< Pcapng general block header when capturing from a pipe */ struct pcapng_block_header_s bh; /**< Pcapng general block header when capturing from a pipe */
struct pcapng_section_header_block_s shb; /**< Pcapng section header when capturing from a pipe */ GArray *src_iface_to_global; /**< Int array mapping local IDB numbers to global_ld.interface_data */
GList *saved_blocks; /**< Pcapng block list of SHB and IDBs for multi_file_on */
} pcapng_pipe_info_t; } pcapng_pipe_info_t;
struct _loop_data; /* forward declaration so we can use it in the cap_pipe_dispatch function pointer */ struct _loop_data; /* forward declaration so we can use it in the cap_pipe_dispatch function pointer */
@ -284,6 +283,13 @@ typedef struct _capture_src {
#endif #endif
} capture_src; } capture_src;
typedef struct _saved_idb {
gboolean deleted;
guint interface_id; /* capture_src->interface_id for the associated SHB */
guint8 *idb; /* If non-NULL, IDB read from capture_src. This is an interface specified on the command line otherwise. */
guint idb_len;
} saved_idb_t;
/* /*
* Global capture loop state. * Global capture loop state.
*/ */
@ -297,6 +303,10 @@ typedef struct _loop_data {
gboolean report_packet_count; /**< Set by SIGINFO handler; print packet count */ gboolean report_packet_count; /**< Set by SIGINFO handler; print packet count */
#endif #endif
GArray *pcaps; /**< Array of capture_src's on which we're capturing */ GArray *pcaps; /**< Array of capture_src's on which we're capturing */
gboolean pcapng_passthrough; /**< We have one source and it's pcapng. Pass its SHB and IDBs through. */
guint8 *saved_shb; /**< SHB to write when we have one pcapng input */
GArray *saved_idbs; /**< Array of saved_idb_t, written when we have a new section or output file. */
GRWLock saved_shb_idb_lock; /**< Saved IDB RW mutex */
/* output file(s) */ /* output file(s) */
FILE *pdh; FILE *pdh;
int save_file_fd; int save_file_fd;
@ -329,7 +339,7 @@ static const char please_report[] =
/* /*
* This needs to be static, so that the SIGINT handler can clear the "go" * This needs to be static, so that the SIGINT handler can clear the "go"
* flag. * flag and for saved_shb_idb_lock.
*/ */
static loop_data global_ld; static loop_data global_ld;
@ -1846,6 +1856,7 @@ cap_pipe_open_live(char *pipename,
/* This isn't pcap, it's pcapng. */ /* This isn't pcap, it's pcapng. */
pcap_src->from_pcapng = TRUE; pcap_src->from_pcapng = TRUE;
pcap_src->cap_pipe_dispatch = pcapng_pipe_dispatch; pcap_src->cap_pipe_dispatch = pcapng_pipe_dispatch;
pcap_src->cap_pipe_info.pcapng.src_iface_to_global = g_array_new(FALSE, FALSE, sizeof(guint32));
global_capture_opts.use_pcapng = TRUE; /* we can only output in pcapng format */ global_capture_opts.use_pcapng = TRUE; /* we can only output in pcapng format */
pcapng_pipe_open_live(fd, pcap_src, errmsg, errmsgl); pcapng_pipe_open_live(fd, pcap_src, errmsg, errmsgl);
return; return;
@ -1967,7 +1978,7 @@ pcapng_read_shb(capture_src *pcap_src,
char *errmsg, char *errmsg,
int errmsgl) int errmsgl)
{ {
struct pcapng_section_header_block_s *shb = &pcap_src->cap_pipe_info.pcapng.shb; struct pcapng_section_header_block_s shb;
#ifdef _WIN32 #ifdef _WIN32
if (pcap_src->from_cap_socket) if (pcap_src->from_cap_socket)
@ -1997,8 +2008,8 @@ pcapng_read_shb(capture_src *pcap_src,
pcap_src->cap_pipe_bytes_read = sizeof(struct pcapng_block_header_s) + sizeof(struct pcapng_section_header_block_s); pcap_src->cap_pipe_bytes_read = sizeof(struct pcapng_block_header_s) + sizeof(struct pcapng_section_header_block_s);
} }
#endif #endif
memcpy(shb, pcap_src->cap_pipe_databuf + sizeof(struct pcapng_block_header_s), sizeof(struct pcapng_section_header_block_s)); memcpy(&shb, pcap_src->cap_pipe_databuf + sizeof(struct pcapng_block_header_s), sizeof(struct pcapng_section_header_block_s));
switch (shb->magic) switch (shb.magic)
{ {
case PCAPNG_MAGIC: case PCAPNG_MAGIC:
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "pcapng SHB MAGIC"); g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "pcapng SHB MAGIC");
@ -2034,29 +2045,101 @@ pcapng_read_shb(capture_src *pcap_src,
return 0; return 0;
} }
/* Save SHB and IDB blocks to playback whenever we change output files. */ /*
/* The list is saved in reverse order of blocks added */ * Save IDB blocks for playback whenever we change output files.
* Rewrite EPB and ISB interface IDs.
*/
static gboolean static gboolean
pcapng_block_save(capture_src *pcap_src) pcapng_adjust_block(loop_data *ld, capture_src *pcap_src)
{ {
pcapng_pipe_info_t *pcapng = &pcap_src->cap_pipe_info.pcapng; pcapng_pipe_info_t *pcapng = &pcap_src->cap_pipe_info.pcapng;
struct pcapng_block_header_s *bh = &pcapng->bh; struct pcapng_block_header_s *bh = &pcapng->bh;
/* Delete all the old blocks first whenever we get a SHB */ switch(bh->block_type) {
if (bh->block_type == BLOCK_TYPE_SHB) { case BLOCK_TYPE_SHB:
g_list_free_full(pcapng->saved_blocks, g_free); {
pcapng->saved_blocks = NULL; g_rw_lock_writer_lock (&ld->saved_shb_idb_lock);
} else if (bh->block_type != BLOCK_TYPE_IDB) { if (ld->pcapng_passthrough) {
return TRUE; /*
} * We have a single pcapng input. We pass the SHB through when
* writing a single output file and for the first ring buffer
* file. We need to save it for the second and subsequent ring
* buffer files.
*/
g_free(ld->saved_shb);
ld->saved_shb = (guint8 *) g_memdup(pcap_src->cap_pipe_databuf, bh->block_total_length);
gpointer data = g_malloc(bh->block_total_length); /*
if (data == NULL) { * We're dealing with one section at a time, so we can (and must)
return FALSE; * get rid of our old IDBs.
} */
memcpy(data, pcap_src->cap_pipe_databuf, bh->block_total_length); for (unsigned i = 0; i < ld->saved_idbs->len; i++) {
saved_idb_t *idb_source = &g_array_index(ld->saved_idbs, saved_idb_t, i);
g_free(idb_source->idb);
}
g_array_set_size(ld->saved_idbs, 0);
} else {
/*
* We have a new SHB from this capture source. We need to keep
* global_ld.saved_idbs intact, so we mark IDBs we previously
* collected from this source as deleted.
*/
for (unsigned i = 0; i < pcap_src->cap_pipe_info.pcapng.src_iface_to_global->len; i++) {
guint32 iface_id = g_array_index(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, guint32, i);
saved_idb_t *idb_source = &g_array_index(ld->saved_idbs, saved_idb_t, iface_id);
g_assert(idb_source->interface_id == pcap_src->interface_id);
g_free(idb_source->idb);
memset(idb_source, 0, sizeof(saved_idb_t));
idb_source->deleted = TRUE;
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: deleted pcapng IDB %u", G_STRFUNC, iface_id);
}
}
g_rw_lock_writer_unlock (&ld->saved_shb_idb_lock);
pcapng->saved_blocks = g_list_prepend(pcapng->saved_blocks, data); g_array_set_size(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, 0);
}
break;
case BLOCK_TYPE_IDB:
{
/*
* Always gather IDBs. We can remove them or mark them as deleted
* when we get a new SHB.
*/
saved_idb_t idb_source = { 0 };
idb_source.interface_id = pcap_src->interface_id;
idb_source.idb_len = bh->block_total_length;
idb_source.idb = (guint8 *) g_memdup(pcap_src->cap_pipe_databuf, idb_source.idb_len);
g_rw_lock_writer_lock (&ld->saved_shb_idb_lock);
g_array_append_val(ld->saved_idbs, idb_source);
guint32 iface_id = ld->saved_idbs->len - 1;
g_rw_lock_writer_unlock (&ld->saved_shb_idb_lock);
g_array_append_val(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, iface_id);
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: saved pcapng IDB %u -> %u from source %u",
G_STRFUNC, pcap_src->cap_pipe_info.pcapng.src_iface_to_global->len - 1, iface_id, pcap_src->interface_id);
}
break;
case BLOCK_TYPE_EPB:
case BLOCK_TYPE_ISB:
{
if (ld->pcapng_passthrough) {
/* Our input and output interface IDs are the same. */
break;
}
/* The interface ID is the first 32-bit field after the BH for both EPBs and ISBs. */
guint32 iface_id;
memcpy(&iface_id, pcap_src->cap_pipe_databuf + sizeof(struct pcapng_block_header_s), 4);
if (iface_id < pcap_src->cap_pipe_info.pcapng.src_iface_to_global->len) {
memcpy(pcap_src->cap_pipe_databuf + sizeof(struct pcapng_block_header_s),
&g_array_index(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, guint32, iface_id), 4);
} else {
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: pcapng EPB or ISB interface id %u > max %u", G_STRFUNC, iface_id, pcap_src->cap_pipe_info.pcapng.src_iface_to_global->len);
return FALSE;
}
}
break;
default:
break;
}
return TRUE; return TRUE;
} }
@ -2545,7 +2628,7 @@ pcapng_pipe_dispatch(loop_data *ld, capture_src *pcap_src, char *errmsg, int err
return 0; return 0;
case PD_DATA_READ: case PD_DATA_READ:
if (!pcapng_block_save(pcap_src)) { if (!pcapng_adjust_block(ld, pcap_src)) {
g_snprintf(errmsg, errmsgl, "pcapng_pipe_dispatch block save failed"); g_snprintf(errmsg, errmsgl, "pcapng_pipe_dispatch block save failed");
return -1; return -1;
} }
@ -2656,6 +2739,7 @@ capture_loop_open_input(capture_options *capture_opts, loop_data *ld,
return FALSE; return FALSE;
} }
int pcapng_src_count = 0;
for (i = 0; i < capture_opts->ifaces->len; i++) { for (i = 0; i < capture_opts->ifaces->len; i++) {
interface_opts = &g_array_index(capture_opts->ifaces, interface_options, i); interface_opts = &g_array_index(capture_opts->ifaces, interface_options, i);
pcap_src = (capture_src *)g_malloc0(sizeof (capture_src)); pcap_src = (capture_src *)g_malloc0(sizeof (capture_src));
@ -2664,6 +2748,19 @@ capture_loop_open_input(capture_options *capture_opts, loop_data *ld,
"Could not allocate memory."); "Could not allocate memory.");
return FALSE; return FALSE;
} }
/*
* Add our pcapng interface entry. This will be deleted further
* down if pcapng_passthrough == TRUE.
*/
saved_idb_t idb_source = { 0 };
idb_source.interface_id = i;
g_rw_lock_writer_lock (&ld->saved_shb_idb_lock);
g_array_append_val(global_ld.saved_idbs, idb_source);
g_rw_lock_writer_unlock (&ld->saved_shb_idb_lock);
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: saved capture_opts IDB %u",
G_STRFUNC, i);
#ifdef MUST_DO_SELECT #ifdef MUST_DO_SELECT
pcap_src->pcap_fd = -1; pcap_src->pcap_fd = -1;
#endif #endif
@ -2801,6 +2898,17 @@ capture_loop_open_input(capture_options *capture_opts, loop_data *ld,
report_capture_error(sync_msg_str, ""); report_capture_error(sync_msg_str, "");
g_free(sync_msg_str); g_free(sync_msg_str);
} }
if (pcap_src->from_pcapng) {
pcapng_src_count++;
}
}
if (capture_opts->ifaces->len == 1 && pcapng_src_count == 1) {
ld->pcapng_passthrough = TRUE;
g_rw_lock_writer_lock (&ld->saved_shb_idb_lock);
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: Clearing %u interfaces for passthrough",
G_STRFUNC, global_ld.saved_idbs->len);
g_array_set_size(global_ld.saved_idbs, 0);
g_rw_lock_writer_unlock (&ld->saved_shb_idb_lock);
} }
/* If not using libcap: we now can now set euid/egid to ruid/rgid */ /* If not using libcap: we now can now set euid/egid to ruid/rgid */
@ -2845,8 +2953,8 @@ static void capture_loop_close_input(loop_data *ld)
pcap_src->cap_pipe_databuf = NULL; pcap_src->cap_pipe_databuf = NULL;
} }
if (pcap_src->from_pcapng) { if (pcap_src->from_pcapng) {
g_list_free_full(pcap_src->cap_pipe_info.pcapng.saved_blocks, g_free); g_array_free(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, TRUE);
pcap_src->cap_pipe_info.pcapng.saved_blocks = NULL; pcap_src->cap_pipe_info.pcapng.src_iface_to_global = NULL;
} }
} else { } else {
/* Capture device. If open, close the pcap_t. */ /* Capture device. If open, close the pcap_t. */
@ -2899,15 +3007,124 @@ capture_loop_init_filter(pcap_t *pcap_h, gboolean from_cap_pipe,
return INITFILTER_NO_ERROR; return INITFILTER_NO_ERROR;
} }
/*
* Write the dumpcap pcapng SHB and IDBs if needed.
* Called from capture_loop_init_output and do_file_switch_or_stop.
*/
static gboolean
capture_loop_init_pcapng_output(capture_options *capture_opts, loop_data *ld)
{
g_rw_lock_reader_lock (&ld->saved_shb_idb_lock);
if (ld->pcapng_passthrough && !ld->saved_shb) {
/* We have a single pcapng capture interface and this is the first or only output file. */
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: skipping dumpcap SHB and IDBs in favor of source", G_STRFUNC);
g_rw_lock_reader_unlock (&ld->saved_shb_idb_lock);
return TRUE;
}
gboolean successful = TRUE;
int err;
GString *os_info_str = g_string_new("");
get_os_version_info(os_info_str);
if (ld->saved_shb) {
/* We have a single pcapng capture interface and multiple output files. */
struct pcapng_block_header_s bh;
memcpy(&bh, ld->saved_shb, sizeof(struct pcapng_block_header_s));
successful = pcapng_write_block(ld->pdh, ld->saved_shb, bh.block_total_length, &ld->bytes_written, &err);
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: wrote saved passthrough SHB %d", G_STRFUNC, successful);
} else {
GString *cpu_info_str = g_string_new("");
get_cpu_info(cpu_info_str);
char *appname = g_strdup_printf("Dumpcap (Wireshark) %s", get_ws_vcs_version_info());
successful = pcapng_write_session_header_block(ld->pdh,
(const char *)capture_opts->capture_comment, /* Comment */
cpu_info_str->str, /* HW */
os_info_str->str, /* OS */
appname,
-1, /* section_length */
&ld->bytes_written,
&err);
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: wrote dumpcap SHB %d", G_STRFUNC, successful);
g_string_free(cpu_info_str, TRUE);
g_free(appname);
}
for (unsigned i = 0; successful && (i < ld->saved_idbs->len); i++) {
saved_idb_t idb_source = g_array_index(ld->saved_idbs, saved_idb_t, i);
if (idb_source.deleted) {
/*
* Our interface is out of scope. Suppose we're writing multiple
* files and a source switches sections. We currently write dummy
* IDBs like so:
*
* File 1: IDB0, IDB1, IDB2
* [ The source of IDBs 1 and 2 writes an SHB with two new IDBs ]
* [ We switch output files ]
* File 2: IDB0, dummy IDB, dummy IDB, IDB3, IDB4
*
* It might make more sense to write the original data so that
* so that our IDB lists are more consistent across files.
*/
successful = pcapng_write_interface_description_block(global_ld.pdh,
"Interface went out of scope", /* OPT_COMMENT 1 */
"dummy", /* IDB_NAME 2 */
"Dumpcap dummy interface", /* IDB_DESCRIPTION 3 */
NULL, /* IDB_FILTER 11 */
os_info_str->str, /* IDB_OS 12 */
-1,
0,
&(global_ld.bytes_written),
0, /* IDB_IF_SPEED 8 */
6, /* IDB_TSRESOL 9 */
&global_ld.err);
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: skipping deleted pcapng IDB %u", G_STRFUNC, i);
} else if (idb_source.idb && idb_source.idb_len) {
successful = pcapng_write_block(global_ld.pdh, idb_source.idb, idb_source.idb_len, &ld->bytes_written, &err);
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: wrote pcapng IDB %d", G_STRFUNC, successful);
} else if (idb_source.interface_id < capture_opts->ifaces->len) {
unsigned if_id = idb_source.interface_id;
interface_options *interface_opts = &g_array_index(capture_opts->ifaces, interface_options, if_id);
capture_src *pcap_src = g_array_index(ld->pcaps, capture_src *, if_id);
if (pcap_src->from_cap_pipe) {
pcap_src->snaplen = pcap_src->cap_pipe_info.pcap.hdr.snaplen;
} else {
pcap_src->snaplen = pcap_snapshot(pcap_src->pcap_h);
}
successful = pcapng_write_interface_description_block(global_ld.pdh,
NULL, /* OPT_COMMENT 1 */
interface_opts->name, /* IDB_NAME 2 */
interface_opts->descr, /* IDB_DESCRIPTION 3 */
interface_opts->cfilter, /* IDB_FILTER 11 */
os_info_str->str, /* IDB_OS 12 */
pcap_src->linktype,
pcap_src->snaplen,
&(global_ld.bytes_written),
0, /* IDB_IF_SPEED 8 */
pcap_src->ts_nsec ? 9 : 6, /* IDB_TSRESOL 9 */
&global_ld.err);
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "%s: wrote capture_opts IDB %d: %d", G_STRFUNC, if_id, successful);
}
}
g_rw_lock_reader_unlock (&ld->saved_shb_idb_lock);
g_string_free(os_info_str, TRUE);
return successful;
}
/* set up to write to the already-opened capture output file/files */ /* set up to write to the already-opened capture output file/files */
static gboolean static gboolean
capture_loop_init_output(capture_options *capture_opts, loop_data *ld, char *errmsg, int errmsg_len) capture_loop_init_output(capture_options *capture_opts, loop_data *ld, char *errmsg, int errmsg_len)
{ {
int err; int err;
guint i;
capture_src *pcap_src;
interface_options *interface_opts;
gboolean successful; gboolean successful;
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_init_output"); g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_init_output");
@ -2929,58 +3146,10 @@ capture_loop_init_output(capture_options *capture_opts, loop_data *ld, char *err
} }
} }
if (ld->pdh) { if (ld->pdh) {
pcap_src = g_array_index(ld->pcaps, capture_src *, 0);
if (pcap_src->from_pcapng) {
/* We are just going to rewrite the source SHB and IDB blocks */
return TRUE;
}
if (capture_opts->use_pcapng) { if (capture_opts->use_pcapng) {
char *appname; successful = capture_loop_init_pcapng_output(capture_opts, ld);
GString *cpu_info_str;
GString *os_info_str;
cpu_info_str = g_string_new("");
os_info_str = g_string_new("");
get_cpu_info(cpu_info_str);
get_os_version_info(os_info_str);
appname = g_strdup_printf("Dumpcap (Wireshark) %s", get_ws_vcs_version_info());
successful = pcapng_write_session_header_block(ld->pdh,
(const char *)capture_opts->capture_comment, /* Comment */
cpu_info_str->str, /* HW */
os_info_str->str, /* OS */
appname,
-1, /* section_length */
&ld->bytes_written,
&err);
g_string_free(cpu_info_str, TRUE);
g_free(appname);
for (i = 0; successful && (i < capture_opts->ifaces->len); i++) {
interface_opts = &g_array_index(capture_opts->ifaces, interface_options, i);
pcap_src = g_array_index(ld->pcaps, capture_src *, i);
if (pcap_src->from_cap_pipe) {
pcap_src->snaplen = pcap_src->cap_pipe_info.pcap.hdr.snaplen;
} else {
pcap_src->snaplen = pcap_snapshot(pcap_src->pcap_h);
}
successful = pcapng_write_interface_description_block(global_ld.pdh,
NULL, /* OPT_COMMENT 1 */
interface_opts->name, /* IDB_NAME 2 */
interface_opts->descr, /* IDB_DESCRIPTION 3 */
interface_opts->cfilter, /* IDB_FILTER 11 */
os_info_str->str, /* IDB_OS 12 */
pcap_src->linktype,
pcap_src->snaplen,
&(global_ld.bytes_written),
0, /* IDB_IF_SPEED 8 */
pcap_src->ts_nsec ? 9 : 6, /* IDB_TSRESOL 9 */
&global_ld.err);
}
g_string_free(os_info_str, TRUE);
} else { } else {
capture_src *pcap_src;
pcap_src = g_array_index(ld->pcaps, capture_src *, 0); pcap_src = g_array_index(ld->pcaps, capture_src *, 0);
if (pcap_src->from_cap_pipe) { if (pcap_src->from_cap_pipe) {
pcap_src->snaplen = pcap_src->cap_pipe_info.pcap.hdr.snaplen; pcap_src->snaplen = pcap_src->cap_pipe_info.pcap.hdr.snaplen;
@ -3471,9 +3640,6 @@ static time_t get_next_time_interval(int interval_s) {
static gboolean static gboolean
do_file_switch_or_stop(capture_options *capture_opts) do_file_switch_or_stop(capture_options *capture_opts)
{ {
guint i;
capture_src *pcap_src;
interface_options *interface_opts;
gboolean successful; gboolean successful;
if (capture_opts->multi_files_on) { if (capture_opts->multi_files_on) {
@ -3491,70 +3657,15 @@ do_file_switch_or_stop(capture_options *capture_opts)
/* File switch succeeded: reset the conditions */ /* File switch succeeded: reset the conditions */
global_ld.bytes_written = 0; global_ld.bytes_written = 0;
global_ld.packets_written = 0; global_ld.packets_written = 0;
pcap_src = g_array_index(global_ld.pcaps, capture_src *, 0); if (capture_opts->use_pcapng) {
if (pcap_src->from_pcapng) { successful = capture_loop_init_pcapng_output(capture_opts, &global_ld);
/* Write the saved SHB and all IDBs to start of next file */
/* The blocks were saved in reverse so reverse it before iterating */
GList *rlist = g_list_reverse(pcap_src->cap_pipe_info.pcapng.saved_blocks);
GList *list = rlist;
successful = TRUE;
while (list && successful) {
struct pcapng_block_header_s *bh = (struct pcapng_block_header_s *) list->data;
successful = pcapng_write_block(global_ld.pdh,
(const guint8 *) bh,
bh->block_total_length,
&global_ld.bytes_written, &global_ld.err);
list = g_list_next(list);
}
pcap_src->cap_pipe_info.pcapng.saved_blocks = g_list_reverse(rlist);
} else { } else {
if (capture_opts->use_pcapng) { capture_src *pcap_src;
char *appname; pcap_src = g_array_index(global_ld.pcaps, capture_src *, 0);
GString *cpu_info_str; successful = libpcap_write_file_header(global_ld.pdh, pcap_src->linktype, pcap_src->snaplen,
GString *os_info_str; pcap_src->ts_nsec, &global_ld.bytes_written, &global_ld.err);
cpu_info_str = g_string_new("");
os_info_str = g_string_new("");
get_cpu_info(cpu_info_str);
get_os_version_info(os_info_str);
appname = g_strdup_printf("Dumpcap (Wireshark) %s", get_ws_vcs_version_info());
successful = pcapng_write_session_header_block(global_ld.pdh,
(const char *)capture_opts->capture_comment, /* Comment */
cpu_info_str->str, /* HW */
os_info_str->str, /* OS */
appname,
-1, /* section_length */
&(global_ld.bytes_written),
&global_ld.err);
g_string_free(cpu_info_str, TRUE);
g_free(appname);
for (i = 0; successful && (i < capture_opts->ifaces->len); i++) {
interface_opts = &g_array_index(capture_opts->ifaces, interface_options, i);
pcap_src = g_array_index(global_ld.pcaps, capture_src *, i);
successful = pcapng_write_interface_description_block(global_ld.pdh,
NULL, /* OPT_COMMENT 1 */
interface_opts->name, /* IDB_NAME 2 */
interface_opts->descr, /* IDB_DESCRIPTION 3 */
interface_opts->cfilter, /* IDB_FILTER 11 */
os_info_str->str, /* IDB_OS 12 */
pcap_src->linktype,
pcap_src->snaplen,
&(global_ld.bytes_written),
0, /* IDB_IF_SPEED 8 */
pcap_src->ts_nsec ? 9 : 6, /* IDB_TSRESOL 9 */
&global_ld.err);
}
g_string_free(os_info_str, TRUE);
} else {
pcap_src = g_array_index(global_ld.pcaps, capture_src *, 0);
successful = libpcap_write_file_header(global_ld.pdh, pcap_src->linktype, pcap_src->snaplen,
pcap_src->ts_nsec, &global_ld.bytes_written, &global_ld.err);
}
} }
if (!successful) { if (!successful) {
fclose(global_ld.pdh); fclose(global_ld.pdh);
global_ld.pdh = NULL; global_ld.pdh = NULL;
@ -4222,6 +4333,15 @@ capture_loop_write_pcapng_cb(capture_src *pcap_src, const struct pcapng_block_he
return; return;
} }
if (bh->block_type == BLOCK_TYPE_SHB && !global_ld.pcapng_passthrough) {
/*
* capture_loop_init_pcapng_output should've handled this. We need
* to write ISBs when they're initially read so we shouldn't skip
* them here.
*/
return;
}
if (global_ld.pdh) { if (global_ld.pdh) {
gboolean successful; gboolean successful;
@ -4242,15 +4362,15 @@ capture_loop_write_pcapng_cb(capture_src *pcap_src, const struct pcapng_block_he
/* count packet only if we actually have an EPB or SPB */ /* count packet only if we actually have an EPB or SPB */
#if defined(DEBUG_DUMPCAP) || defined(DEBUG_CHILD_DUMPCAP) #if defined(DEBUG_DUMPCAP) || defined(DEBUG_CHILD_DUMPCAP)
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_INFO, g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_INFO,
"Wrote a packet of length %d captured on interface %u.", "Wrote a pcapng block type %u of length %d captured on interface %u.",
bh->block_total_length, pcap_src->interface_id); bh->block_type, bh->block_total_length, pcap_src->interface_id);
#endif #endif
capture_loop_wrote_one_packet(pcap_src); capture_loop_wrote_one_packet(pcap_src);
} }
} }
} }
/* one packet was captured, process it */ /* one pcap packet was captured, process it */
static void static void
capture_loop_write_packet_cb(u_char *pcap_src_p, const struct pcap_pkthdr *phdr, capture_loop_write_packet_cb(u_char *pcap_src_p, const struct pcap_pkthdr *phdr,
const u_char *pd) const u_char *pd)
@ -4298,7 +4418,7 @@ capture_loop_write_packet_cb(u_char *pcap_src_p, const struct pcap_pkthdr *phdr,
} else { } else {
#if defined(DEBUG_DUMPCAP) || defined(DEBUG_CHILD_DUMPCAP) #if defined(DEBUG_DUMPCAP) || defined(DEBUG_CHILD_DUMPCAP)
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_INFO, g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_INFO,
"Wrote a packet of length %d captured on interface %u.", "Wrote a pcap packet of length %d captured on interface %u.",
phdr->caplen, pcap_src->interface_id); phdr->caplen, pcap_src->interface_id);
#endif #endif
capture_loop_wrote_one_packet(pcap_src); capture_loop_wrote_one_packet(pcap_src);
@ -4418,8 +4538,8 @@ capture_loop_queue_pcapng_cb(capture_src *pcap_src, const struct pcapng_block_he
} else { } else {
pcap_src->received++; pcap_src->received++;
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_INFO, g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_INFO,
"Queued a packet of length %d captured on interface %u.", "Queued a block of type 0x%08x of length %d captured on interface %u.",
bh->block_total_length, pcap_src->interface_id); bh->block_type, bh->block_total_length, pcap_src->interface_id);
} }
/* I don't want to hold the mutex over the debug output. So the /* I don't want to hold the mutex over the debug output. So the
output may be wrong */ output may be wrong */
@ -4689,8 +4809,11 @@ real_main(int argc, char *argv[])
log_flags, log_flags,
console_log_handler, NULL /* user_data */); console_log_handler, NULL /* user_data */);
/* Initialize the pcaps list */ /* Initialize the pcaps list and IDBs */
global_ld.pcaps = g_array_new(FALSE, FALSE, sizeof(capture_src *)); global_ld.pcaps = g_array_new(FALSE, FALSE, sizeof(capture_src *));
global_ld.pcapng_passthrough = FALSE;
global_ld.saved_shb = NULL;
global_ld.saved_idbs = g_array_new(FALSE, TRUE, sizeof(saved_idb_t));
#ifdef _WIN32 #ifdef _WIN32
/* Load wpcap if possible. Do this before collecting the run-time version information */ /* Load wpcap if possible. Do this before collecting the run-time version information */
@ -5382,13 +5505,13 @@ console_log_handler(const char *log_domain, GLogLevelFlags log_level,
static void static void
report_packet_count(unsigned int packet_count) report_packet_count(unsigned int packet_count)
{ {
char tmp[SP_DECISIZE+1+1]; char count_str[SP_DECISIZE+1+1];
static unsigned int count = 0; static unsigned int count = 0;
if (capture_child) { if (capture_child) {
g_snprintf(tmp, sizeof(tmp), "%u", packet_count); g_snprintf(count_str, sizeof(count_str), "%u", packet_count);
g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "Packets: %s", tmp); g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "Packets: %s", count_str);
pipe_write_block(2, SP_PACKET_COUNT, tmp); pipe_write_block(2, SP_PACKET_COUNT, count_str);
} else { } else {
count += packet_count; count += packet_count;
fprintf(stderr, "\rPackets: %u ", count); fprintf(stderr, "\rPackets: %u ", count);

View File

@ -9,7 +9,6 @@
# #
'''Subprocess test case superclass''' '''Subprocess test case superclass'''
import config
import difflib import difflib
import io import io
import os import os
@ -35,6 +34,19 @@ def cat_dhcp_command(mode):
sd_cmd += os.path.join(this_dir, 'util_dump_dhcp_pcap.py ' + mode) sd_cmd += os.path.join(this_dir, 'util_dump_dhcp_pcap.py ' + mode)
return sd_cmd return sd_cmd
def cat_cap_file_command(cap_files):
'''Create a command string for dumping one or more capture files to stdout'''
# XXX Do this in Python in a thread?
if isinstance(cap_files, str):
cap_files = [ cap_files ]
quoted_paths = ' '.join('"{}"'.format(cap_file) for cap_file in cap_files)
if sys.platform.startswith('win32'):
# https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb491026(v=technet.10)
# says that the `type` command "displays the contents of a text
# file." Copy to the console instead.
return 'copy {} CON'.format(quoted_paths)
return 'cat {}'.format(quoted_paths)
class LoggingPopen(subprocess.Popen): class LoggingPopen(subprocess.Popen):
'''Run a process using subprocess.Popen. Capture and log its output. '''Run a process using subprocess.Popen. Capture and log its output.

View File

@ -9,18 +9,20 @@
# #
'''Capture tests''' '''Capture tests'''
import fixtures
import glob import glob
import hashlib
import os import os
import subprocess import subprocess
import subprocesstest import subprocesstest
import sys import sys
import time import time
import uuid import uuid
import fixtures
capture_duration = 5 capture_duration = 5
testout_pcap = 'testout.pcap' testout_pcap = 'testout.pcap'
testout_pcapng = 'testout.pcapng'
snapshot_len = 96 snapshot_len = 96
@fixtures.fixture @fixtures.fixture
@ -71,6 +73,7 @@ def traffic_generator():
def wireshark_k(wireshark_command): def wireshark_k(wireshark_command):
return tuple(list(wireshark_command) + ['-k']) return tuple(list(wireshark_command) + ['-k'])
def capture_command(*cmd_args, shell=False): def capture_command(*cmd_args, shell=False):
if type(cmd_args[0]) != str: if type(cmd_args[0]) != str:
# Assume something like ['wireshark', '-k'] # Assume something like ['wireshark', '-k']
@ -326,6 +329,165 @@ def check_dumpcap_ringbuffer_stdin(cmd_dumpcap):
return check_dumpcap_ringbuffer_stdin_real return check_dumpcap_ringbuffer_stdin_real
@fixtures.fixture
def check_dumpcap_pcapng_sections(cmd_dumpcap, cmd_tshark, capture_file):
if sys.platform == 'win32':
fixtures.skip('Test requires OS fifo support.')
def check_dumpcap_pcapng_sections_real(self, multi_input=False, multi_output=False):
# Make sure we always test multiple SHBs in an input.
in_files_l = [ [
capture_file('many_interfaces.pcapng.1'),
capture_file('many_interfaces.pcapng.2')
] ]
if multi_input:
in_files_l.append([ capture_file('many_interfaces.pcapng.3') ])
fifo_files = []
fifo_procs = []
# Default values for our validity tests
check_val_d = {
'filename': None,
'packet_count': 0,
'idb_count': 0,
'ua_pt1_count': 0,
'ua_pt2_count': 0,
'ua_pt3_count': 0,
'ua_dc_count': 0,
}
check_vals = [ check_val_d ]
for in_files in in_files_l:
fifo_file = self.filename_from_id('dumpcap_pcapng_sections_{}.fifo'.format(len(fifo_files) + 1))
fifo_files.append(fifo_file)
# If a previous test left its fifo laying around, e.g. from a failure, remove it.
try:
os.unlink(fifo_file)
except: pass
os.mkfifo(fifo_file)
cat_cmd = subprocesstest.cat_cap_file_command(in_files)
fifo_procs.append(self.startProcess(('{0} > {1}'.format(cat_cmd, fifo_file)), shell=True))
if multi_output:
rb_unique = 'sections_rb_' + uuid.uuid4().hex[:6] # Random ID
testout_glob = '{}.{}_*.pcapng'.format(self.id(), rb_unique)
testout_file = '{}.{}.pcapng'.format(self.id(), rb_unique)
check_vals.append(check_val_d)
# check_vals[]['filename'] will be filled in below
else:
testout_file = self.filename_from_id(testout_pcapng)
check_vals[0]['filename'] = testout_file
# Capture commands
if not multi_input and not multi_output:
# Passthrough SHBs, single output file
capture_cmd_args = (
'-i', fifo_files[0],
'-w', testout_file
)
check_vals[0]['packet_count'] = 79
check_vals[0]['idb_count'] = 22
check_vals[0]['ua_pt1_count'] = 1
check_vals[0]['ua_pt2_count'] = 1
elif not multi_input and multi_output:
# Passthrough SHBs, multiple output files
capture_cmd_args = (
'-i', fifo_files[0],
'-w', testout_file,
'-a', 'files:2',
'-b', 'packets:53'
)
check_vals[0]['packet_count'] = 53
check_vals[0]['idb_count'] = 22
check_vals[0]['ua_pt1_count'] = 1
check_vals[1]['packet_count'] = 26
check_vals[1]['idb_count'] = 22
check_vals[1]['ua_pt1_count'] = 1
check_vals[1]['ua_pt2_count'] = 1
elif multi_input and not multi_output:
# Dumpcap SHBs, single output file
capture_cmd_args = (
'-i', fifo_files[0],
'-i', fifo_files[1],
'-w', testout_file
)
check_vals[0]['packet_count'] = 88
check_vals[0]['idb_count'] = 35
check_vals[0]['ua_dc_count'] = 1
else:
# Dumpcap SHBs, multiple output files
capture_cmd_args = (
'-i', fifo_files[0],
'-i', fifo_files[1],
'-w', testout_file,
'-a', 'files:2',
'-b', 'packets:53'
)
check_vals[0]['packet_count'] = 53
check_vals[0]['idb_count'] = 35
check_vals[0]['ua_dc_count'] = 1
check_vals[1]['packet_count'] = 35
check_vals[1]['idb_count'] = 35
check_vals[1]['ua_dc_count'] = 1
capture_cmd = capture_command(cmd_dumpcap, *capture_cmd_args)
capture_proc = self.runProcess(capture_cmd)
for fifo_proc in fifo_procs: fifo_proc.kill()
rb_files = []
if multi_output:
rb_files = sorted(glob.glob(testout_glob))
self.assertEqual(len(rb_files), 2)
check_vals[0]['filename'] = rb_files[0]
check_vals[1]['filename'] = rb_files[1]
for rbf in rb_files:
self.cleanup_files.append(rbf)
self.assertTrue(os.path.isfile(rbf))
returncode = capture_proc.returncode
self.assertEqual(returncode, 0)
if (returncode != 0):
return
# Output tests
if not multi_input and not multi_output:
# Check strict bit-for-bit passthrough.
in_hash = hashlib.sha256()
out_hash = hashlib.sha256()
for in_file in in_files_l[0]:
in_cap_file = capture_file(in_file)
with open(in_cap_file, 'rb') as f:
in_hash.update(f.read())
with open(testout_file, 'rb') as f:
out_hash.update(f.read())
self.assertEqual(in_hash.hexdigest(), out_hash.hexdigest())
# many_interfaces.pcapng.1 : 64 packets written by "Passthrough test #1"
# many_interfaces.pcapng.2 : 15 packets written by "Passthrough test #2"
# many_interfaces.pcapng.3 : 9 packets written by "Passthrough test #3"
for check_val in check_vals:
self.checkPacketCount(check_val['packet_count'], cap_file=check_val['filename'])
tshark_proc = self.runProcess(capture_command(cmd_tshark,
'-r', check_val['filename'],
'-V',
'-X', 'read_format:MIME Files Format'
))
# XXX Are there any other sanity checks we should run?
self.assertEqual(self.countOutput('Block: Interface Description Block',
proc=tshark_proc), check_val['idb_count'])
self.assertEqual(self.countOutput('Option: User Application = Passthrough test #1',
proc=tshark_proc), check_val['ua_pt1_count'])
self.assertEqual(self.countOutput('Option: User Application = Passthrough test #2',
proc=tshark_proc), check_val['ua_pt2_count'])
self.assertEqual(self.countOutput('Option: User Application = Passthrough test #3',
proc=tshark_proc), check_val['ua_pt3_count'])
self.assertEqual(self.countOutput('Option: User Application = Dumpcap \(Wireshark\)',
proc=tshark_proc), check_val['ua_dc_count'])
return check_dumpcap_pcapng_sections_real
@fixtures.mark_usefixtures('test_env') @fixtures.mark_usefixtures('test_env')
@fixtures.uses_fixtures @fixtures.uses_fixtures
class case_wireshark_capture(subprocesstest.SubprocessTestCase): class case_wireshark_capture(subprocesstest.SubprocessTestCase):
@ -416,7 +578,6 @@ class case_dumpcap_autostop(subprocesstest.SubprocessTestCase):
@fixtures.uses_fixtures @fixtures.uses_fixtures
class case_dumpcap_ringbuffer(subprocesstest.SubprocessTestCase): class case_dumpcap_ringbuffer(subprocesstest.SubprocessTestCase):
# duration, interval, filesize, packets, files # duration, interval, filesize, packets, files
# Need a function that finds ringbuffer file names.
def test_dumpcap_ringbuffer_filesize(self, check_dumpcap_ringbuffer_stdin): def test_dumpcap_ringbuffer_filesize(self, check_dumpcap_ringbuffer_stdin):
'''Capture from stdin using Dumpcap and write multiple files until we reach a file size limit''' '''Capture from stdin using Dumpcap and write multiple files until we reach a file size limit'''
check_dumpcap_ringbuffer_stdin(self, filesize=15) check_dumpcap_ringbuffer_stdin(self, filesize=15)
@ -424,3 +585,23 @@ class case_dumpcap_ringbuffer(subprocesstest.SubprocessTestCase):
def test_dumpcap_ringbuffer_packets(self, check_dumpcap_ringbuffer_stdin): def test_dumpcap_ringbuffer_packets(self, check_dumpcap_ringbuffer_stdin):
'''Capture from stdin using Dumpcap and write multiple files until we reach a packet limit''' '''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. check_dumpcap_ringbuffer_stdin(self, packets=47) # Last prime before 50. Arbitrary.
@fixtures.mark_usefixtures('base_env')
@fixtures.uses_fixtures
class case_dumpcap_pcapng_sections(subprocesstest.SubprocessTestCase):
def test_dumpcap_pcapng_single_in_single_out(self, check_dumpcap_pcapng_sections):
'''Capture from a single pcapng source using Dumpcap and write a single file'''
check_dumpcap_pcapng_sections(self)
def test_dumpcap_pcapng_single_in_multi_out(self, check_dumpcap_pcapng_sections):
'''Capture from a single pcapng source using Dumpcap and write two files'''
check_dumpcap_pcapng_sections(self, multi_output=True)
def test_dumpcap_pcapng_multi_in_single_out(self, check_dumpcap_pcapng_sections):
'''Capture from two pcapng sources using Dumpcap and write a single file'''
check_dumpcap_pcapng_sections(self, multi_input=True)
def test_dumpcap_pcapng_multi_in_multi_out(self, check_dumpcap_pcapng_sections):
'''Capture from two pcapng sources using Dumpcap and write two files'''
check_dumpcap_pcapng_sections(self, multi_input=True, multi_output=True)