diff --git a/dumpcap.c b/dumpcap.c index 4e04b4e4c9..185edcd380 100644 --- a/dumpcap.c +++ b/dumpcap.c @@ -227,8 +227,7 @@ typedef struct _pcap_pipe_info { typedef struct _pcapng_pipe_info { 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 */ - GList *saved_blocks; /**< Pcapng block list of SHB and IDBs for multi_file_on */ + GArray *src_iface_to_global; /**< Int array mapping local IDB numbers to global_ld.interface_data */ } pcapng_pipe_info_t; 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 } 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. */ @@ -297,6 +303,10 @@ typedef struct _loop_data { gboolean report_packet_count; /**< Set by SIGINFO handler; print packet count */ #endif 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) */ FILE *pdh; 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" - * flag. + * flag and for saved_shb_idb_lock. */ static loop_data global_ld; @@ -1846,6 +1856,7 @@ cap_pipe_open_live(char *pipename, /* This isn't pcap, it's pcapng. */ pcap_src->from_pcapng = TRUE; 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 */ pcapng_pipe_open_live(fd, pcap_src, errmsg, errmsgl); return; @@ -1967,7 +1978,7 @@ pcapng_read_shb(capture_src *pcap_src, char *errmsg, 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 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); } #endif - memcpy(shb, pcap_src->cap_pipe_databuf + sizeof(struct pcapng_block_header_s), sizeof(struct pcapng_section_header_block_s)); - switch (shb->magic) + memcpy(&shb, pcap_src->cap_pipe_databuf + sizeof(struct pcapng_block_header_s), sizeof(struct pcapng_section_header_block_s)); + switch (shb.magic) { case PCAPNG_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; } -/* 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 -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; struct pcapng_block_header_s *bh = &pcapng->bh; - /* Delete all the old blocks first whenever we get a SHB */ - if (bh->block_type == BLOCK_TYPE_SHB) { - g_list_free_full(pcapng->saved_blocks, g_free); - pcapng->saved_blocks = NULL; - } else if (bh->block_type != BLOCK_TYPE_IDB) { - return TRUE; - } + switch(bh->block_type) { + case BLOCK_TYPE_SHB: + { + g_rw_lock_writer_lock (&ld->saved_shb_idb_lock); + if (ld->pcapng_passthrough) { + /* + * 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) { - return FALSE; - } - memcpy(data, pcap_src->cap_pipe_databuf, bh->block_total_length); + /* + * We're dealing with one section at a time, so we can (and must) + * get rid of our old IDBs. + */ + 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; } @@ -2545,7 +2628,7 @@ pcapng_pipe_dispatch(loop_data *ld, capture_src *pcap_src, char *errmsg, int err return 0; 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"); return -1; } @@ -2656,6 +2739,7 @@ capture_loop_open_input(capture_options *capture_opts, loop_data *ld, return FALSE; } + int pcapng_src_count = 0; for (i = 0; i < capture_opts->ifaces->len; i++) { interface_opts = &g_array_index(capture_opts->ifaces, interface_options, i); 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."); 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 pcap_src->pcap_fd = -1; #endif @@ -2801,6 +2898,17 @@ capture_loop_open_input(capture_options *capture_opts, loop_data *ld, report_capture_error(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 */ @@ -2845,8 +2953,8 @@ static void capture_loop_close_input(loop_data *ld) pcap_src->cap_pipe_databuf = NULL; } if (pcap_src->from_pcapng) { - g_list_free_full(pcap_src->cap_pipe_info.pcapng.saved_blocks, g_free); - pcap_src->cap_pipe_info.pcapng.saved_blocks = NULL; + g_array_free(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, TRUE); + pcap_src->cap_pipe_info.pcapng.src_iface_to_global = NULL; } } else { /* 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; } +/* + * 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 */ static gboolean capture_loop_init_output(capture_options *capture_opts, loop_data *ld, char *errmsg, int errmsg_len) { int err; - guint i; - capture_src *pcap_src; - interface_options *interface_opts; gboolean successful; 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) { - 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) { - char *appname; - 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); - + successful = capture_loop_init_pcapng_output(capture_opts, ld); } else { + capture_src *pcap_src; pcap_src = g_array_index(ld->pcaps, capture_src *, 0); if (pcap_src->from_cap_pipe) { 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 do_file_switch_or_stop(capture_options *capture_opts) { - guint i; - capture_src *pcap_src; - interface_options *interface_opts; gboolean successful; 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 */ 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 */ - /* 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); + if (capture_opts->use_pcapng) { + successful = capture_loop_init_pcapng_output(capture_opts, &global_ld); } else { - if (capture_opts->use_pcapng) { - char *appname; - 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(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); - } + capture_src *pcap_src; + 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) { fclose(global_ld.pdh); global_ld.pdh = NULL; @@ -4222,6 +4333,15 @@ capture_loop_write_pcapng_cb(capture_src *pcap_src, const struct pcapng_block_he 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) { 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 */ #if defined(DEBUG_DUMPCAP) || defined(DEBUG_CHILD_DUMPCAP) g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_INFO, - "Wrote a packet of length %d captured on interface %u.", - bh->block_total_length, pcap_src->interface_id); + "Wrote a pcapng block type %u of length %d captured on interface %u.", + bh->block_type, bh->block_total_length, pcap_src->interface_id); #endif capture_loop_wrote_one_packet(pcap_src); } } } -/* one packet was captured, process it */ +/* one pcap packet was captured, process it */ static void capture_loop_write_packet_cb(u_char *pcap_src_p, const struct pcap_pkthdr *phdr, const u_char *pd) @@ -4298,7 +4418,7 @@ capture_loop_write_packet_cb(u_char *pcap_src_p, const struct pcap_pkthdr *phdr, } else { #if defined(DEBUG_DUMPCAP) || defined(DEBUG_CHILD_DUMPCAP) 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); #endif 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 { pcap_src->received++; g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_INFO, - "Queued a packet of length %d captured on interface %u.", - bh->block_total_length, pcap_src->interface_id); + "Queued a block of type 0x%08x of length %d captured on interface %u.", + 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 output may be wrong */ @@ -4689,8 +4809,11 @@ real_main(int argc, char *argv[]) log_flags, 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.pcapng_passthrough = FALSE; + global_ld.saved_shb = NULL; + global_ld.saved_idbs = g_array_new(FALSE, TRUE, sizeof(saved_idb_t)); #ifdef _WIN32 /* 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 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; if (capture_child) { - g_snprintf(tmp, sizeof(tmp), "%u", packet_count); - g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "Packets: %s", tmp); - pipe_write_block(2, SP_PACKET_COUNT, tmp); + g_snprintf(count_str, sizeof(count_str), "%u", packet_count); + g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "Packets: %s", count_str); + pipe_write_block(2, SP_PACKET_COUNT, count_str); } else { count += packet_count; fprintf(stderr, "\rPackets: %u ", count); diff --git a/test/captures/many_interfaces.pcapng.1 b/test/captures/many_interfaces.pcapng.1 index 6fa742f47d..960e35d913 100644 Binary files a/test/captures/many_interfaces.pcapng.1 and b/test/captures/many_interfaces.pcapng.1 differ diff --git a/test/captures/many_interfaces.pcapng.2 b/test/captures/many_interfaces.pcapng.2 index 653a1edc6b..7056f46c34 100644 Binary files a/test/captures/many_interfaces.pcapng.2 and b/test/captures/many_interfaces.pcapng.2 differ diff --git a/test/captures/many_interfaces.pcapng.3 b/test/captures/many_interfaces.pcapng.3 index bd848d2e9e..48367c0a54 100644 Binary files a/test/captures/many_interfaces.pcapng.3 and b/test/captures/many_interfaces.pcapng.3 differ diff --git a/test/subprocesstest.py b/test/subprocesstest.py index 9f6b2f001f..2510ce8992 100644 --- a/test/subprocesstest.py +++ b/test/subprocesstest.py @@ -9,7 +9,6 @@ # '''Subprocess test case superclass''' -import config import difflib import io import os @@ -35,6 +34,19 @@ def cat_dhcp_command(mode): sd_cmd += os.path.join(this_dir, 'util_dump_dhcp_pcap.py ' + mode) 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): '''Run a process using subprocess.Popen. Capture and log its output. diff --git a/test/suite_capture.py b/test/suite_capture.py index 1eaf4ad621..adc1e91221 100644 --- a/test/suite_capture.py +++ b/test/suite_capture.py @@ -9,18 +9,20 @@ # '''Capture tests''' +import fixtures import glob +import hashlib import os import subprocess import subprocesstest import sys import time import uuid -import fixtures capture_duration = 5 testout_pcap = 'testout.pcap' +testout_pcapng = 'testout.pcapng' snapshot_len = 96 @fixtures.fixture @@ -71,6 +73,7 @@ def traffic_generator(): def wireshark_k(wireshark_command): return tuple(list(wireshark_command) + ['-k']) + def capture_command(*cmd_args, shell=False): if type(cmd_args[0]) != str: # Assume something like ['wireshark', '-k'] @@ -326,6 +329,165 @@ def check_dumpcap_ringbuffer_stdin(cmd_dumpcap): 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.uses_fixtures class case_wireshark_capture(subprocesstest.SubprocessTestCase): @@ -416,7 +578,6 @@ class case_dumpcap_autostop(subprocesstest.SubprocessTestCase): @fixtures.uses_fixtures 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, check_dumpcap_ringbuffer_stdin): '''Capture from stdin using Dumpcap and write multiple files until we reach a file size limit''' 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): '''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. + + +@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)