diff options
Diffstat (limited to 'wiretap/pcapng.c')
-rw-r--r-- | wiretap/pcapng.c | 6876 |
1 files changed, 6876 insertions, 0 deletions
diff --git a/wiretap/pcapng.c b/wiretap/pcapng.c new file mode 100644 index 00000000..85c635fc --- /dev/null +++ b/wiretap/pcapng.c @@ -0,0 +1,6876 @@ +/* pcapng.c + * + * Wiretap Library + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * File format support for pcapng file format + * Copyright (c) 2007 by Ulf Lamping <ulf.lamping@web.de> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* File format specification: + * https://github.com/pcapng/pcapng + * Related Wiki page: + * https://gitlab.com/wireshark/wireshark/-/wikis/Development/PcapNg + */ + +#include "config.h" +#include "wtap_opttypes.h" + +#define WS_LOG_DOMAIN LOG_DOMAIN_WIRETAP + +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <wsutil/wslog.h> +#include <wsutil/strtoi.h> +#include <wsutil/glib-compat.h> +#include <wsutil/ws_assert.h> +#include <wsutil/ws_roundup.h> +#include <wsutil/unicode-utils.h> + +#include "wtap-int.h" +#include "file_wrappers.h" +#include "required_file_handlers.h" +#include "pcap-common.h" +#include "pcap-encap.h" +#include "pcapng.h" +#include "pcapng_module.h" +#include "secrets-types.h" + +#define ROUND_TO_4BYTE(len) WS_ROUNDUP_4(len) + +static gboolean +pcapng_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err, + gchar **err_info, gint64 *data_offset); +static gboolean +pcapng_seek_read(wtap *wth, gint64 seek_off, + wtap_rec *rec, Buffer *buf, int *err, gchar **err_info); +static void +pcapng_close(wtap *wth); + +static gboolean +pcapng_encap_is_ft_specific(int encap); + +static gboolean +pcapng_write_if_descr_block(wtap_dumper *wdh, wtap_block_t int_data, int *err); + +/* + * Minimum block size = size of block header + size of block trailer. + */ +#define MIN_BLOCK_SIZE ((guint32)(sizeof(pcapng_block_header_t) + sizeof(guint32))) + +/* + * Minimum SHB size = minimum block size + size of fixed length portion of SHB. + */ +#define MIN_SHB_SIZE ((guint32)(MIN_BLOCK_SIZE + sizeof(pcapng_section_header_block_t))) + +/* pcapng: packet block file encoding (obsolete) */ +typedef struct pcapng_packet_block_s { + guint16 interface_id; + guint16 drops_count; + guint32 timestamp_high; + guint32 timestamp_low; + guint32 captured_len; + guint32 packet_len; + /* ... Packet Data ... */ + /* ... Padding ... */ + /* ... Options ... */ +} pcapng_packet_block_t; + +/* + * Minimum PB size = minimum block size + size of fixed length portion of PB. + */ +#define MIN_PB_SIZE ((guint32)(MIN_BLOCK_SIZE + sizeof(pcapng_packet_block_t))) + +/* pcapng: enhanced packet block file encoding */ +typedef struct pcapng_enhanced_packet_block_s { + guint32 interface_id; + guint32 timestamp_high; + guint32 timestamp_low; + guint32 captured_len; + guint32 packet_len; + /* ... Packet Data ... */ + /* ... Padding ... */ + /* ... Options ... */ +} pcapng_enhanced_packet_block_t; + +/* + * Minimum EPB size = minimum block size + size of fixed length portion of EPB. + */ +#define MIN_EPB_SIZE ((guint32)(MIN_BLOCK_SIZE + sizeof(pcapng_enhanced_packet_block_t))) + +/* pcapng: simple packet block file encoding */ +typedef struct pcapng_simple_packet_block_s { + guint32 packet_len; + /* ... Packet Data ... */ + /* ... Padding ... */ +} pcapng_simple_packet_block_t; + +/* + * Minimum SPB size = minimum block size + size of fixed length portion of SPB. + */ +#define MIN_SPB_SIZE ((guint32)(MIN_BLOCK_SIZE + sizeof(pcapng_simple_packet_block_t))) + +/* pcapng: name resolution block file encoding */ +typedef struct pcapng_name_resolution_block_s { + guint16 record_type; + guint16 record_len; + /* ... Record ... */ +} pcapng_name_resolution_block_t; + +/* + * Minimum NRB size = minimum block size + size of smallest NRB record + * (there must at least be an "end of records" record). + */ +#define MIN_NRB_SIZE ((guint32)(MIN_BLOCK_SIZE + sizeof(pcapng_name_resolution_block_t))) + +/* pcapng: custom block file encoding */ +typedef struct pcapng_custom_block_s { + guint32 pen; + /* Custom data and options */ +} pcapng_custom_block_t; + +/* + * Minimum CB size = minimum block size + size of fixed length portion of CB. + */ + +#define MIN_CB_SIZE ((guint32)(MIN_BLOCK_SIZE + sizeof(pcapng_custom_block_t))) + +/* + * Minimum ISB size = minimum block size + size of fixed length portion of ISB. + */ +#define MIN_ISB_SIZE ((guint32)(MIN_BLOCK_SIZE + sizeof(pcapng_interface_statistics_block_t))) + +/* + * Minimum Sysdig size = minimum block size + packed size of sysdig_event_phdr. + * Minimum Sysdig event v2 header size = minimum block size + packed size of sysdig_event_v2_phdr (which, in addition + * to sysdig_event_phdr, includes the nparams 32bit value). + */ +#define SYSDIG_EVENT_HEADER_SIZE ((16 + 64 + 64 + 32 + 16)/8) /* CPU ID + TS + TID + Event len + Event type */ +#define MIN_SYSDIG_EVENT_SIZE ((guint32)(MIN_BLOCK_SIZE + SYSDIG_EVENT_HEADER_SIZE)) +#define SYSDIG_EVENT_V2_HEADER_SIZE ((16 + 64 + 64 + 32 + 16 + 32)/8) /* CPU ID + TS + TID + Event len + Event type + nparams */ +#define MIN_SYSDIG_EVENT_V2_SIZE ((guint32)(MIN_BLOCK_SIZE + SYSDIG_EVENT_V2_HEADER_SIZE)) + +/* + * We require __REALTIME_TIMESTAMP in the Journal Export Format reader in + * order to set each packet timestamp. Require it here as well, although + * it's not strictly necessary. + */ +#define SDJ__REALTIME_TIMESTAMP "__REALTIME_TIMESTAMP=" +#define MIN_SYSTEMD_JOURNAL_EXPORT_ENTRY_SIZE 23 // "__REALTIME_TIMESTAMP=0\n" +#define MIN_SYSTEMD_JOURNAL_EXPORT_BLOCK_SIZE ((guint32)(MIN_SYSTEMD_JOURNAL_EXPORT_ENTRY_SIZE + MIN_BLOCK_SIZE)) + +/* pcapng: common option header file encoding for every option type */ +typedef struct pcapng_option_header_s { + guint16 option_code; + guint16 option_length; + /* ... x bytes Option Body ... */ + /* ... Padding ... */ +} pcapng_option_header_t; + +struct pcapng_option { + guint16 type; + guint16 value_length; +}; + +/* Option codes: 16-bit field */ +#define OPT_EPB_FLAGS 0x0002 +#define OPT_EPB_HASH 0x0003 +#define OPT_EPB_DROPCOUNT 0x0004 +#define OPT_EPB_PACKETID 0x0005 +#define OPT_EPB_QUEUE 0x0006 +#define OPT_EPB_VERDICT 0x0007 + +#define OPT_NRB_DNSNAME 0x0002 +#define OPT_NRB_DNSV4ADDR 0x0003 +#define OPT_NRB_DNSV6ADDR 0x0004 + +/* MSBit of option code means "local type" */ +#define OPT_LOCAL_FLAG 0x8000 + +/* OPT_EPB_VERDICT sub-types */ +#define OPT_VERDICT_TYPE_HW 0 +#define OPT_VERDICT_TYPE_TC 1 +#define OPT_VERDICT_TYPE_XDP 2 + +/* OPT_EPB_HASH sub-types */ +#define OPT_HASH_2COMP 0 +#define OPT_HASH_XOR 1 +#define OPT_HASH_CRC32 2 +#define OPT_HASH_MD5 3 +#define OPT_HASH_SHA1 4 +#define OPT_HASH_TOEPLITZ 5 + +/* + * In order to keep from trying to allocate large chunks of memory, + * which could either fail or, even if it succeeds, chew up so much + * address space or memory+backing store as not to leave room for + * anything else, we impose upper limits on the size of blocks we're + * willing to handle. + * + * We pick a limit of an EPB with a maximum-sized D-Bus packet and 128 KiB + * worth of options; we use the maximum D-Bus packet size as that's larger + * than the maximum packet size for other link-layer types, and the maximum + * packet size for other link-layer types is currently small enough that + * the resulting block size would be less than the previous 16 MiB limit. + */ +#define MAX_BLOCK_SIZE (MIN_EPB_SIZE + WTAP_MAX_PACKET_SIZE_DBUS + 131072) + +/* Note: many of the defined structures for block data are defined in wtap.h */ + +/* Packet data - used for both Enhanced Packet Block and the obsolete Packet Block data */ +typedef struct wtapng_packet_s { + /* mandatory */ + guint32 ts_high; /* seconds since 1.1.1970 */ + guint32 ts_low; /* fraction of seconds, depends on if_tsresol */ + guint32 cap_len; /* data length in the file */ + guint32 packet_len; /* data length on the wire */ + guint32 interface_id; /* identifier of the interface. */ + guint16 drops_count; /* drops count, only valid for packet block */ + /* 0xffff if information no available */ + /* pack_hash */ + /* XXX - put the packet data / pseudo_header here as well? */ +} wtapng_packet_t; + +/* Simple Packet data */ +typedef struct wtapng_simple_packet_s { + /* mandatory */ + guint32 cap_len; /* data length in the file */ + guint32 packet_len; /* data length on the wire */ + /* XXX - put the packet data / pseudo_header here as well? */ +} wtapng_simple_packet_t; + +/* Interface data in private struct */ +typedef struct interface_info_s { + int wtap_encap; + guint32 snap_len; + guint64 time_units_per_second; + int tsprecision; + gint64 tsoffset; + int fcslen; +} interface_info_t; + +typedef struct { + guint current_section_number; /**< Section number of the current section being read sequentially */ + GArray *sections; /**< Sections found in the capture file. */ +} pcapng_t; + +/* + * Table for plugins to handle particular block types. + * + * A handler has a "read" routine and a "write" routine. + * + * A "read" routine returns a block as a libwiretap record, filling + * in the wtap_rec structure with the appropriate record type and + * other information, and filling in the supplied Buffer with + * data for which there's no place in the wtap_rec structure. + * + * A "write" routine takes a libwiretap record and Buffer and writes + * out a block. + */ +typedef struct { + block_reader reader; + block_writer writer; +} block_handler; + +static GHashTable *block_handlers; + +void +register_pcapng_block_type_handler(guint block_type, block_reader reader, + block_writer writer) +{ + block_handler *handler; + + /* + * Is this a known block type? + */ + switch (block_type) { + + case BLOCK_TYPE_SHB: + case BLOCK_TYPE_IDB: + case BLOCK_TYPE_PB: + case BLOCK_TYPE_SPB: + case BLOCK_TYPE_NRB: + case BLOCK_TYPE_ISB: + case BLOCK_TYPE_EPB: + case BLOCK_TYPE_DSB: + case BLOCK_TYPE_CB_COPY: + case BLOCK_TYPE_CB_NO_COPY: + case BLOCK_TYPE_SYSDIG_MI: + case BLOCK_TYPE_SYSDIG_PL_V1: + case BLOCK_TYPE_SYSDIG_FDL_V1: + case BLOCK_TYPE_SYSDIG_EVENT: + case BLOCK_TYPE_SYSDIG_IL_V1: + case BLOCK_TYPE_SYSDIG_UL_V1: + case BLOCK_TYPE_SYSDIG_PL_V2: + case BLOCK_TYPE_SYSDIG_EVF: + case BLOCK_TYPE_SYSDIG_PL_V3: + case BLOCK_TYPE_SYSDIG_PL_V4: + case BLOCK_TYPE_SYSDIG_PL_V5: + case BLOCK_TYPE_SYSDIG_PL_V6: + case BLOCK_TYPE_SYSDIG_PL_V7: + case BLOCK_TYPE_SYSDIG_PL_V8: + case BLOCK_TYPE_SYSDIG_PL_V9: + case BLOCK_TYPE_SYSDIG_EVENT_V2: + case BLOCK_TYPE_SYSDIG_EVF_V2: + case BLOCK_TYPE_SYSDIG_FDL_V2: + case BLOCK_TYPE_SYSDIG_IL_V2: + case BLOCK_TYPE_SYSDIG_UL_V2: + case BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT: + /* + * Yes; we already handle it, and don't allow a replacement to + * be registered (if there's a bug in our code, or there's + * something we don't handle in that block, submit a change + * to the main Wireshark source). + */ + ws_warning("Attempt to register plugin for block type 0x%08x not allowed", + block_type); + return; + + case BLOCK_TYPE_IRIG_TS: + case BLOCK_TYPE_ARINC_429: + /* + * Yes, and we don't already handle it. Allow a plugin to + * handle it. + * + * (But why not submit the plugin source to Wireshark?) + */ + break; + + default: + /* + * No; is it a local block type? + */ + if (!(block_type & 0x80000000)) { + /* + * No; don't allow a plugin to be registered for it, as + * the block type needs to be registered before it's used. + */ + ws_warning("Attempt to register plugin for reserved block type 0x%08x not allowed", + block_type); + return; + } + + /* + * Yes; allow the registration. + */ + break; + } + + if (block_handlers == NULL) { + /* + * Create the table of block handlers. + * + * XXX - there's no "g_uint_hash()" or "g_uint_equal()", + * so we use "g_direct_hash()" and "g_direct_equal()". + */ + block_handlers = g_hash_table_new_full(g_direct_hash, + g_direct_equal, + NULL, g_free); + } + handler = g_new(block_handler, 1); + handler->reader = reader; + handler->writer = writer; + g_hash_table_insert(block_handlers, GUINT_TO_POINTER(block_type), + handler); +} + +/* + * Tables for plugins to handle particular options for particular block + * types. + * + * An option has three handler routines: + * + * An option parser, used when reading an option from a file: + * + * The option parser is passed an indication of whether this section + * of the file is byte-swapped, the length of the option, the data of + * the option, a pointer to an error code, and a pointer to a pointer + * variable for an error string. + * + * It checks whether the length and option are valid, and, if they + * aren't, returns FALSE, setting the error code to the appropriate + * error (normally WTAP_ERR_BAD_FILE) and the error string to an + * appropriate string indicating the problem. + * + * Otherwise, if this section of the file is byte-swapped, it byte-swaps + * multi-byte numerical values, so that it's in the host byte order. + * + * An option sizer, used when writing an option to a file: + * + * The option sizer is passed the option identifier for the option + * and a wtap_optval_t * that points to the data for the option. + * + * It calculates how many bytes the option's data requires, not + * including any padding bytes, and returns that value. + * + * An option writer, used when writing an option to a file: + * + * The option writer is passed a wtap_dumper * to which the + * option data should be written, the option identifier for + * the option, a wtap_optval_t * that points to the data for + * the option, and an int * into which an error code should + * be stored if an error occurs when writing the option. + * + * It returns a gboolean value of TRUE if the attempt to + * write the option succeeds and FALSE if the attempt to + * write the option gets an error. + */ + +/* + * Block types indices in the table of tables of option handlers. + * + * Block types are not guaranteed to be sequential, so we map the + * block types we support to a sequential set. Furthermore, all + * packet block types have the same set of options. + */ +#define BT_INDEX_SHB 0 +#define BT_INDEX_IDB 1 +#define BT_INDEX_PBS 2 /* all packet blocks */ +#define BT_INDEX_NRB 3 +#define BT_INDEX_ISB 4 +#define BT_INDEX_EVT 5 +#define BT_INDEX_DSB 6 + +#define NUM_BT_INDICES 7 + +typedef struct { + option_parser parser; + option_sizer sizer; + option_writer writer; +} option_handler; + +static GHashTable *option_handlers[NUM_BT_INDICES]; + +/* Return whether this block type is handled interally, or + * if it is returned to the caller in pcapng_read(). + * This is used by pcapng_open() to decide if it can process + * the block. + * Note that for block types that are registered from plugins, + * we don't know the true answer without actually reading the block, + * or even if there is a fixed answer for all blocks of that type, + * so we err on the side of not processing. + */ +static bool +get_block_type_internal(unsigned block_type) +{ + switch (block_type) { + + case BLOCK_TYPE_SHB: + case BLOCK_TYPE_IDB: + case BLOCK_TYPE_NRB: + case BLOCK_TYPE_DSB: + case BLOCK_TYPE_ISB: /* XXX: ISBs should probably not be internal. */ + case BLOCK_TYPE_SYSDIG_MI: + case BLOCK_TYPE_SYSDIG_PL_V1: + case BLOCK_TYPE_SYSDIG_FDL_V1: + case BLOCK_TYPE_SYSDIG_IL_V1: + case BLOCK_TYPE_SYSDIG_UL_V1: + case BLOCK_TYPE_SYSDIG_PL_V2: + case BLOCK_TYPE_SYSDIG_PL_V3: + case BLOCK_TYPE_SYSDIG_PL_V4: + case BLOCK_TYPE_SYSDIG_PL_V5: + case BLOCK_TYPE_SYSDIG_PL_V6: + case BLOCK_TYPE_SYSDIG_PL_V7: + case BLOCK_TYPE_SYSDIG_PL_V8: + case BLOCK_TYPE_SYSDIG_PL_V9: + case BLOCK_TYPE_SYSDIG_FDL_V2: + case BLOCK_TYPE_SYSDIG_IL_V2: + case BLOCK_TYPE_SYSDIG_UL_V2: + return true; + + case BLOCK_TYPE_PB: + case BLOCK_TYPE_EPB: + case BLOCK_TYPE_SPB: + return false; + + case BLOCK_TYPE_CB_COPY: + case BLOCK_TYPE_CB_NO_COPY: + case BLOCK_TYPE_SYSDIG_EVENT: + case BLOCK_TYPE_SYSDIG_EVENT_V2: + case BLOCK_TYPE_SYSDIG_EVENT_V2_LARGE: + case BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT: + return false; + + default: +#ifdef HAVE_PLUGINS + /* + * Do we have a handler for this block type? + */ + if (block_handlers != NULL && + (g_hash_table_lookup(block_handlers, GUINT_TO_POINTER(block_type))) != NULL) { + /* Yes. We don't know if the handler sets this block internal + * or needs to return it to the pcap_read() caller without + * reading it. Since this is called by pcap_open(), play it + * safe and tell pcap_open() to stop processing blocks. + * (XXX: Maybe the block type handler registration interface + * should include some way of indicating whether blocks are + * handled internally, which should hopefully be the same + * for all blocks of a type.) + */ + return FALSE; + } +#endif + return TRUE; + } + return FALSE; +} + +static gboolean +get_block_type_index(guint block_type, guint *bt_index) +{ + ws_assert(bt_index); + + switch (block_type) { + + case BLOCK_TYPE_SHB: + *bt_index = BT_INDEX_SHB; + break; + + case BLOCK_TYPE_IDB: + *bt_index = BT_INDEX_IDB; + break; + + case BLOCK_TYPE_PB: + case BLOCK_TYPE_EPB: + case BLOCK_TYPE_SPB: + *bt_index = BT_INDEX_PBS; + break; + + case BLOCK_TYPE_NRB: + *bt_index = BT_INDEX_NRB; + break; + + case BLOCK_TYPE_ISB: + *bt_index = BT_INDEX_ISB; + break; + + case BLOCK_TYPE_SYSDIG_EVENT: + case BLOCK_TYPE_SYSDIG_EVENT_V2: + case BLOCK_TYPE_SYSDIG_EVENT_V2_LARGE: + case BLOCK_TYPE_SYSDIG_MI: + case BLOCK_TYPE_SYSDIG_PL_V1: + case BLOCK_TYPE_SYSDIG_FDL_V1: + case BLOCK_TYPE_SYSDIG_IL_V1: + case BLOCK_TYPE_SYSDIG_UL_V1: + case BLOCK_TYPE_SYSDIG_PL_V2: + case BLOCK_TYPE_SYSDIG_PL_V3: + case BLOCK_TYPE_SYSDIG_PL_V4: + case BLOCK_TYPE_SYSDIG_PL_V5: + case BLOCK_TYPE_SYSDIG_PL_V6: + case BLOCK_TYPE_SYSDIG_PL_V7: + case BLOCK_TYPE_SYSDIG_PL_V8: + case BLOCK_TYPE_SYSDIG_PL_V9: + case BLOCK_TYPE_SYSDIG_FDL_V2: + case BLOCK_TYPE_SYSDIG_IL_V2: + case BLOCK_TYPE_SYSDIG_UL_V2: + *bt_index = BT_INDEX_EVT; + break; + + case BLOCK_TYPE_DSB: + *bt_index = BT_INDEX_DSB; + break; + + default: + /* + * This is a block type we don't process; either we ignore it, + * in which case the options don't get processed, or there's + * a plugin routine to handle it, in which case that routine + * will do the option processing itself. + * + * XXX - report an error? + */ + return FALSE; + } + + return TRUE; +} + +void +register_pcapng_option_handler(guint block_type, guint option_code, + option_parser parser, + option_sizer sizer, + option_writer writer) +{ + guint bt_index; + option_handler *handler; + + if (!get_block_type_index(block_type, &bt_index)) + return; + + if (option_handlers[bt_index] == NULL) { + /* + * Create the table of option handlers for this block type. + * + * XXX - there's no "g_uint_hash()" or "g_uint_equal()", + * so we use "g_direct_hash()" and "g_direct_equal()". + */ + option_handlers[bt_index] = g_hash_table_new_full(g_direct_hash, + g_direct_equal, + NULL, g_free); + } + handler = g_new(option_handler, 1); + handler->parser = parser; + handler->sizer = sizer; + handler->writer = writer; + g_hash_table_insert(option_handlers[bt_index], + GUINT_TO_POINTER(option_code), handler); +} + +void +pcapng_process_uint8_option(wtapng_block_t *wblock, + guint16 option_code, guint16 option_length, + const guint8 *option_content) +{ + if (option_length == 1) { + /* + * If this option can appear only once in a block, this call + * will fail on the second and later occurrences of the option; + * we silently ignore the failure. + */ + wtap_block_add_uint8_option(wblock->block, option_code, option_content[0]); + } +} + +void +pcapng_process_uint32_option(wtapng_block_t *wblock, + const section_info_t *section_info, + pcapng_opt_byte_order_e byte_order, + guint16 option_code, guint16 option_length, + const guint8 *option_content) +{ + guint32 uint32; + + if (option_length == 4) { + /* Don't cast a guint8 * into a guint32 *--the + * guint8 * may not point to something that's + * aligned correctly. + * + * XXX - options are aligned on 32-bit boundaries, so, while + * it may be true that 64-bit options aren't guaranteed to be + * aligned on 64-bit bounaries, it shouldn't be true that 32-bit + * options aren't guaranteed to be aligned on 32-bit boundaries. + */ + memcpy(&uint32, option_content, sizeof(guint32)); + switch (byte_order) { + + case OPT_SECTION_BYTE_ORDER: + if (section_info->byte_swapped) { + uint32 = GUINT32_SWAP_LE_BE(uint32); + } + break; + + case OPT_BIG_ENDIAN: + uint32 = GUINT32_FROM_BE(uint32); + break; + + case OPT_LITTLE_ENDIAN: + uint32 = GUINT32_FROM_LE(uint32); + break; + + default: + /* + * This should not happen - this is called by pcapng_process_options(), + * which returns an error for an invalid byte_order argument, and + * otherwise passes the known-to-be-valid byte_order argument to + * us. + * + * Just ignore the option. + */ + return; + } + + /* + * If this option can appear only once in a block, this call + * will fail on the second and later occurrences of the option; + * we silently ignore the failure. + */ + wtap_block_add_uint32_option(wblock->block, option_code, uint32); + } +} + +void +pcapng_process_timestamp_option(wtapng_block_t *wblock, + const section_info_t *section_info, + pcapng_opt_byte_order_e byte_order, + guint16 option_code, guint16 option_length, + const guint8 *option_content) +{ + if (option_length == 8) { + guint32 high, low; + guint64 timestamp; + + /* Don't cast a guint8 * into a guint32 *--the + * guint8 * may not point to something that's + * aligned correctly. + */ + memcpy(&high, option_content, sizeof(guint32)); + memcpy(&low, option_content + sizeof(guint32), sizeof(guint32)); + switch (byte_order) { + + case OPT_SECTION_BYTE_ORDER: + if (section_info->byte_swapped) { + high = GUINT32_SWAP_LE_BE(high); + low = GUINT32_SWAP_LE_BE(low); + } + break; + + case OPT_BIG_ENDIAN: + high = GUINT32_FROM_BE(high); + low = GUINT32_FROM_BE(low); + break; + + case OPT_LITTLE_ENDIAN: + high = GUINT32_FROM_LE(high); + low = GUINT32_FROM_LE(low); + break; + + default: + /* + * This should not happen - this is called by pcapng_process_options(), + * which returns an error for an invalid byte_order argument, and + * otherwise passes the known-to-be-valid byte_order argument to + * us. + * + * Just ignore the option. + */ + return; + } + timestamp = (guint64)high; + timestamp <<= 32; + timestamp += (guint64)low; + /* + * If this option can appear only once in a block, this call + * will fail on the second and later occurrences of the option; + * we silently ignore the failure. + */ + wtap_block_add_uint64_option(wblock->block, option_code, timestamp); + } +} + +void +pcapng_process_uint64_option(wtapng_block_t *wblock, + const section_info_t *section_info, + pcapng_opt_byte_order_e byte_order, + guint16 option_code, guint16 option_length, + const guint8 *option_content) +{ + guint64 uint64; + + if (option_length == 8) { + /* Don't cast a guint8 * into a guint64 *--the + * guint8 * may not point to something that's + * aligned correctly. + */ + memcpy(&uint64, option_content, sizeof(guint64)); + switch (byte_order) { + + case OPT_SECTION_BYTE_ORDER: + if (section_info->byte_swapped) { + uint64 = GUINT64_SWAP_LE_BE(uint64); + } + break; + + case OPT_BIG_ENDIAN: + uint64 = GUINT64_FROM_BE(uint64); + break; + + case OPT_LITTLE_ENDIAN: + uint64 = GUINT64_FROM_LE(uint64); + break; + + default: + /* + * This should not happen - this is called by pcapng_process_options(), + * which returns an error for an invalid byte_order argument, and + * otherwise passes the known-to-be-valid byte_order argument to + * us. + * + * Just ignore the option. + */ + return; + } + + /* + * If this option can appear only once in a block, this call + * will fail on the second and later occurrences of the option; + * we silently ignore the failure. + */ + wtap_block_add_uint64_option(wblock->block, option_code, uint64); + } +} + +void +pcapng_process_int64_option(wtapng_block_t *wblock, + const section_info_t *section_info, + pcapng_opt_byte_order_e byte_order, + guint16 option_code, guint16 option_length, + const guint8 *option_content) +{ + gint64 int64; + + if (option_length == 8) { + /* Don't cast a gint8 * into a gint64 *--the + * guint8 * may not point to something that's + * aligned correctly. + */ + memcpy(&int64, option_content, sizeof(gint64)); + switch (byte_order) { + + case OPT_SECTION_BYTE_ORDER: + if (section_info->byte_swapped) { + int64 = GUINT64_SWAP_LE_BE(int64); + } + break; + + case OPT_BIG_ENDIAN: + int64 = GUINT64_FROM_BE(int64); + break; + + case OPT_LITTLE_ENDIAN: + int64 = GUINT64_FROM_LE(int64); + break; + + default: + /* + * This should not happen - this is called by pcapng_process_options(), + * which returns an error for an invalid byte_order argument, and + * otherwise passes the known-to-be-valid byte_order argument to + * us. + * + * Just ignore the option. + */ + return; + } + + /* + * If this option can appear only once in a block, this call + * will fail on the second and later occurrences of the option; + * we silently ignore the failure. + */ + wtap_block_add_int64_option(wblock->block, option_code, int64); + } +} + +void +pcapng_process_string_option(wtapng_block_t *wblock, guint16 option_code, + guint16 option_length, const guint8 *option_content) +{ + const char *opt = (const char *)option_content; + size_t optlen = option_length; + char *str; + + /* Validate UTF-8 encoding. */ + str = ws_utf8_make_valid(NULL, opt, optlen); + + wtap_block_add_string_option_owned(wblock->block, option_code, str); +} + +void +pcapng_process_bytes_option(wtapng_block_t *wblock, guint16 option_code, + guint16 option_length, const guint8 *option_content) +{ + wtap_block_add_bytes_option(wblock->block, option_code, (const char *)option_content, option_length); +} + +static gboolean +pcapng_process_nflx_custom_option(wtapng_block_t *wblock, + section_info_t *section_info, + const guint8 *value, guint16 length) +{ + struct nflx_dumpinfo dumpinfo; + guint32 type, version; + gint64 dumptime, temp; + + if (length < 4) { + ws_debug("Length = %u too small", length); + return FALSE; + } + memcpy(&type, value, sizeof(guint32)); + type = GUINT32_FROM_LE(type); + value += 4; + length -= 4; + ws_debug("Handling type = %u, payload of length = %u", type, length); + switch (type) { + case NFLX_OPT_TYPE_VERSION: + if (length == sizeof(guint32)) { + memcpy(&version, value, sizeof(guint32)); + version = GUINT32_FROM_LE(version); + ws_debug("BBLog version: %u", version); + section_info->bblog_version = version; + } else { + ws_debug("BBLog version parameter has strange length: %u", length); + } + break; + case NFLX_OPT_TYPE_TCPINFO: + ws_debug("BBLog tcpinfo of length: %u", length); + if (wblock->type == BLOCK_TYPE_CB_COPY) { + ws_buffer_assure_space(wblock->frame_buffer, length); + wblock->rec->rec_header.custom_block_header.length = length + 4; + memcpy(ws_buffer_start_ptr(wblock->frame_buffer), value, length); + memcpy(&temp, value, sizeof(guint64)); + temp = GUINT64_FROM_LE(temp); + wblock->rec->ts.secs = section_info->bblog_offset_tv_sec + temp; + memcpy(&temp, value + sizeof(guint64), sizeof(guint64)); + temp = GUINT64_FROM_LE(temp); + wblock->rec->ts.nsecs = (guint32)(section_info->bblog_offset_tv_usec + temp) * 1000; + if (wblock->rec->ts.nsecs >= 1000000000) { + wblock->rec->ts.secs += 1; + wblock->rec->ts.nsecs -= 1000000000; + } + wblock->rec->presence_flags = WTAP_HAS_TS; + wblock->internal = FALSE; + } + break; + case NFLX_OPT_TYPE_DUMPINFO: + if (length == sizeof(struct nflx_dumpinfo)) { + memcpy(&dumpinfo, value, sizeof(struct nflx_dumpinfo)); + section_info->bblog_offset_tv_sec = GUINT64_FROM_LE(dumpinfo.tlh_offset_tv_sec); + section_info->bblog_offset_tv_usec = GUINT64_FROM_LE(dumpinfo.tlh_offset_tv_usec); + ws_debug("BBLog dumpinfo time offset: %" PRIu64, section_info->bblog_offset_tv_sec); + } else { + ws_debug("BBLog dumpinfo parameter has strange length: %u", length); + } + break; + case NFLX_OPT_TYPE_DUMPTIME: + if (length == sizeof(gint64)) { + memcpy(&dumptime, value, sizeof(gint64)); + dumptime = GINT64_FROM_LE(dumptime); + ws_debug("BBLog dumpinfo time offset: %" PRIu64, dumptime); + } else { + ws_debug("BBLog dumptime parameter has strange length: %u", length); + } + break; + case NFLX_OPT_TYPE_STACKNAME: + if (length >= 2) { + ws_debug("BBLog stack name: %.*s(%u)", length - 1, value + 1, *(guint8 *)value); + } else { + ws_debug("BBLog stack name has strange length: %u)", length); + } + break; + default: + ws_debug("Unknown type: %u, length: %u", type, length); + break; + } + return wtap_block_add_nflx_custom_option(wblock->block, type, value, length) == WTAP_OPTTYPE_SUCCESS; +} + +static gboolean +pcapng_process_custom_option(wtapng_block_t *wblock, + section_info_t *section_info, + guint16 option_code, guint16 option_length, + const guint8 *option_content, + pcapng_opt_byte_order_e byte_order, + int *err, gchar **err_info) +{ + guint32 pen; + gboolean ret; + + if (option_length < 4) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: option length (%d) too small for custom option", + option_length); + return FALSE; + } + memcpy(&pen, option_content, sizeof(guint32)); + switch (byte_order) { + + case OPT_SECTION_BYTE_ORDER: + if (section_info->byte_swapped) { + pen = GUINT32_SWAP_LE_BE(pen); + } + break; + + case OPT_BIG_ENDIAN: + pen = GUINT32_FROM_BE(pen); + break; + + case OPT_LITTLE_ENDIAN: + pen = GUINT32_FROM_LE(pen); + break; + + default: + /* + * This should not happen - this is called by pcapng_process_options(), + * which returns an error for an invalid byte_order argument, and + * otherwise passes the known-to-be-valid byte_order argument to + * us. + */ + *err = WTAP_ERR_INTERNAL; + *err_info = ws_strdup_printf("pcapng: invalid byte order %d passed to pcapng_process_custom_option()", + byte_order); + return FALSE; + } + switch (pen) { + case PEN_NFLX: + ret = pcapng_process_nflx_custom_option(wblock, section_info, option_content + 4, option_length - 4); + break; + default: + ret = wtap_block_add_custom_option(wblock->block, option_code, pen, option_content + 4, option_length - 4) == WTAP_OPTTYPE_SUCCESS; + ws_debug("Custom option type %u (0x%04x) with unknown pen %u with custom data of length %u", option_code, option_code, pen, option_length - 4); + break; + } + ws_debug("returning %d", ret); + return ret; +} + +#ifdef HAVE_PLUGINS +static gboolean +pcapng_process_unhandled_option(wtapng_block_t *wblock, + guint bt_index, + const section_info_t *section_info, + guint16 option_code, guint16 option_length, + const guint8 *option_content, + int *err, gchar **err_info) +{ + option_handler *handler; + + /* + * Do we have a handler for this packet block option code? + */ + if (option_handlers[bt_index] != NULL && + (handler = (option_handler *)g_hash_table_lookup(option_handlers[bt_index], + GUINT_TO_POINTER((guint)option_code))) != NULL) { + /* Yes - call the handler. */ + if (!handler->parser(wblock->block, section_info->byte_swapped, + option_length, option_content, err, err_info)) + /* XXX - free anything? */ + return FALSE; + } + return TRUE; +} +#else +static gboolean +pcapng_process_unhandled_option(wtapng_block_t *wblock _U_, + guint bt_index _U_, + const section_info_t *section_info _U_, + guint16 option_code _U_, guint16 option_length _U_, + const guint8 *option_content _U_, + int *err _U_, gchar **err_info _U_) +{ + return TRUE; +} +#endif + +gboolean +pcapng_process_options(FILE_T fh, wtapng_block_t *wblock, + section_info_t *section_info, + guint opt_cont_buf_len, + gboolean (*process_option)(wtapng_block_t *, + const section_info_t *, + guint16, guint16, + const guint8 *, + int *, gchar **), + pcapng_opt_byte_order_e byte_order, + int *err, gchar **err_info) +{ + guint8 *option_content; /* Allocate as large as the options block */ + guint opt_bytes_remaining; + const guint8 *option_ptr; + const pcapng_option_header_t *oh; + guint16 option_code, option_length; + guint rounded_option_length; + + ws_debug("Options %u bytes", opt_cont_buf_len); + if (opt_cont_buf_len == 0) { + /* No options, so nothing to do */ + return TRUE; + } + + /* Allocate enough memory to hold all options */ + option_content = (guint8 *)g_try_malloc(opt_cont_buf_len); + if (option_content == NULL) { + *err = ENOMEM; /* we assume we're out of memory */ + return FALSE; + } + + /* Read all the options into the buffer */ + if (!wtap_read_bytes(fh, option_content, opt_cont_buf_len, err, err_info)) { + ws_debug("failed to read options"); + g_free(option_content); + return FALSE; + } + + /* + * Now process them. + * option_ptr starts out aligned on at least a 4-byte boundary, as + * that's what g_try_malloc() gives us, and each option is padded + * to a length that's a multiple of 4 bytes, so it remains aligned. + */ + option_ptr = &option_content[0]; + opt_bytes_remaining = opt_cont_buf_len; + while (opt_bytes_remaining != 0) { + /* Get option header. */ + oh = (const pcapng_option_header_t *)(const void *)option_ptr; + /* Sanity check: don't run past the end of the options. */ + if (sizeof (*oh) > opt_bytes_remaining) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: Not enough data for option header"); + g_free(option_content); + return FALSE; + } + option_code = oh->option_code; + option_length = oh->option_length; + switch (byte_order) { + + case OPT_SECTION_BYTE_ORDER: + if (section_info->byte_swapped) { + option_code = GUINT16_SWAP_LE_BE(option_code); + option_length = GUINT16_SWAP_LE_BE(option_length); + } + break; + + case OPT_BIG_ENDIAN: + option_code = GUINT16_FROM_BE(option_code); + option_length = GUINT16_FROM_BE(option_length); + break; + + case OPT_LITTLE_ENDIAN: + option_code = GUINT16_FROM_LE(option_code); + option_length = GUINT16_FROM_LE(option_length); + break; + + default: + /* Don't do that. */ + *err = WTAP_ERR_INTERNAL; + *err_info = ws_strdup_printf("pcapng: invalid byte order %d passed to pcapng_process_options()", + byte_order); + return FALSE; + } + option_ptr += sizeof (*oh); /* 4 bytes, so it remains aligned */ + opt_bytes_remaining -= sizeof (*oh); + + /* Round up option length to a multiple of 4. */ + rounded_option_length = ROUND_TO_4BYTE(option_length); + + /* Sanity check: don't run past the end of the options. */ + if (rounded_option_length > opt_bytes_remaining) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: Not enough data to handle option of length %u", + option_length); + g_free(option_content); + return FALSE; + } + + switch (option_code) { + case(OPT_EOFOPT): /* opt_endofopt */ + if (opt_bytes_remaining != 0) { + ws_debug("%u bytes after opt_endofopt", opt_bytes_remaining); + } + /* padding should be ok here, just get out of this */ + opt_bytes_remaining = rounded_option_length; + break; + case(OPT_COMMENT): + pcapng_process_string_option(wblock, option_code, option_length, + option_ptr); + break; + case(OPT_CUSTOM_STR_COPY): + case(OPT_CUSTOM_BIN_COPY): + case(OPT_CUSTOM_STR_NO_COPY): + case(OPT_CUSTOM_BIN_NO_COPY): + if (!pcapng_process_custom_option(wblock, section_info, + option_code, option_length, + option_ptr, + byte_order, + err, err_info)) { + g_free(option_content); + return FALSE; + } + break; + + default: + if (process_option == NULL || + !(*process_option)(wblock, (const section_info_t *)section_info, option_code, + option_length, option_ptr, + err, err_info)) { + g_free(option_content); + return FALSE; + } + } + option_ptr += rounded_option_length; /* multiple of 4 bytes, so it remains aligned */ + opt_bytes_remaining -= rounded_option_length; + } + g_free(option_content); + return TRUE; +} + +typedef enum { + PCAPNG_BLOCK_OK, + PCAPNG_BLOCK_NOT_SHB, + PCAPNG_BLOCK_ERROR +} block_return_val; + +static gboolean +pcapng_process_section_header_block_option(wtapng_block_t *wblock, + const section_info_t *section_info, + guint16 option_code, + guint16 option_length, + const guint8 *option_content, + int *err, gchar **err_info) +{ + /* + * Handle option content. + * + * ***DO NOT*** add any items to this table that are not + * standardized option codes in either section 3.5 "Options" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-options + * + * or in the list of options in section 4.1 "Section Header Block" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-section-header-block + * + * All option codes in this switch statement here must be listed + * in one of those places as standardized option types. + */ + switch (option_code) { + case(OPT_SHB_HARDWARE): + pcapng_process_string_option(wblock, option_code, option_length, + option_content); + break; + case(OPT_SHB_OS): + pcapng_process_string_option(wblock, option_code, option_length, + option_content); + break; + case(OPT_SHB_USERAPPL): + pcapng_process_string_option(wblock, option_code, option_length, + option_content); + break; + default: + if (!pcapng_process_unhandled_option(wblock, BT_INDEX_SHB, + section_info, option_code, + option_length, option_content, + err, err_info)) + return FALSE; + break; + } + return TRUE; +} + +static block_return_val +pcapng_read_section_header_block(FILE_T fh, pcapng_block_header_t *bh, + section_info_t *section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + gboolean byte_swapped; + guint16 version_major; + guint16 version_minor; + guint opt_cont_buf_len; + pcapng_section_header_block_t shb; + wtapng_section_mandatory_t* section_data; + + /* read fixed-length part of the block */ + if (!wtap_read_bytes(fh, &shb, sizeof shb, err, err_info)) { + /* + * Even if this is just a short read, report it as an error. + * It *is* a read error except when we're doing an open, in + * which case it's a "this isn't a pcapng file" indication. + * The open code will call us directly, and treat a short + * read error as such an indication. + */ + return PCAPNG_BLOCK_ERROR; + } + + /* is the magic number one we expect? */ + switch (shb.magic) { + case(0x1A2B3C4D): + /* this seems pcapng with correct byte order */ + byte_swapped = FALSE; + version_major = shb.version_major; + version_minor = shb.version_minor; + + ws_debug("SHB (our byte order) V%u.%u, len %u", + version_major, version_minor, bh->block_total_length); + break; + case(0x4D3C2B1A): + /* this seems pcapng with swapped byte order */ + byte_swapped = TRUE; + version_major = GUINT16_SWAP_LE_BE(shb.version_major); + version_minor = GUINT16_SWAP_LE_BE(shb.version_minor); + + /* tweak the block length to meet current swapping that we know now */ + bh->block_total_length = GUINT32_SWAP_LE_BE(bh->block_total_length); + + ws_debug("SHB (byte-swapped) V%u.%u, len %u", + version_major, version_minor, bh->block_total_length); + break; + default: + /* Not a "pcapng" magic number we know about. */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: unknown byte-order magic number 0x%08x", shb.magic); + + /* + * See above comment about PCAPNG_BLOCK_NOT_SHB. + */ + return PCAPNG_BLOCK_NOT_SHB; + } + + /* + * Add padding bytes to the block total length. + * + * See the comment in pcapng_read_block() for a long discussion + * of this. + */ + bh->block_total_length = ROUND_TO_4BYTE(bh->block_total_length); + + /* + * Is this block long enough to be an SHB? + */ + if (bh->block_total_length < MIN_SHB_SIZE) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of an SHB is less than the minimum SHB size %u", + bh->block_total_length, MIN_SHB_SIZE); + return PCAPNG_BLOCK_ERROR; + } + + /* OK, at this point we assume it's a pcapng file. + + Don't try to allocate memory for a huge number of options, as + that might fail and, even if it succeeds, it might not leave + any address space or memory+backing store for anything else. + + We do that by imposing a maximum block size of MAX_BLOCK_SIZE. + We check for this *after* checking the SHB for its byte + order magic number, so that non-pcapng files are less + likely to be treated as bad pcapng files. */ + if (bh->block_total_length > MAX_BLOCK_SIZE) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u is too large (> %u)", + bh->block_total_length, MAX_BLOCK_SIZE); + return PCAPNG_BLOCK_ERROR; + } + + /* Currently only SHB versions 1.0 and 1.2 are supported; + version 1.2 is treated as being the same as version 1.0. + See the current version of the pcapng specification. + + Version 1.2 is written by some programs that write additional + block types (which can be read by any code that handles them, + regarless of whether the minor version if 0 or 2, so that's + not a reason to change the minor version number). + + XXX - the pcapng specification says that readers should + just ignore sections with an unsupported version number; + presumably they can also report an error if they skip + all the way to the end of the file without finding + any versions that they support. */ + if (!(version_major == 1 && + (version_minor == 0 || version_minor == 2))) { + *err = WTAP_ERR_UNSUPPORTED; + *err_info = ws_strdup_printf("pcapng: unknown SHB version %u.%u", + version_major, version_minor); + return PCAPNG_BLOCK_ERROR; + } + + memset(section_info, 0, sizeof(section_info_t)); + section_info->byte_swapped = byte_swapped; + section_info->version_major = version_major; + section_info->version_minor = version_minor; + + /* + * Set wblock->block to a newly-allocated section header block. + */ + wblock->block = wtap_block_create(WTAP_BLOCK_SECTION); + + /* + * Set the mandatory values for the block. + */ + section_data = (wtapng_section_mandatory_t*)wtap_block_get_mandatory_data(wblock->block); + /* 64bit section_length (currently unused) */ + if (section_info->byte_swapped) { + section_data->section_length = GUINT64_SWAP_LE_BE(shb.section_length); + } else { + section_data->section_length = shb.section_length; + } + + /* Options */ + opt_cont_buf_len = bh->block_total_length - MIN_SHB_SIZE; + if (!pcapng_process_options(fh, wblock, section_info, opt_cont_buf_len, + pcapng_process_section_header_block_option, + OPT_SECTION_BYTE_ORDER, err, err_info)) + return PCAPNG_BLOCK_ERROR; + + /* + * We don't return these to the caller in pcapng_read(). + */ + wblock->internal = TRUE; + + return PCAPNG_BLOCK_OK; +} + +static gboolean +pcapng_process_if_descr_block_option(wtapng_block_t *wblock, + const section_info_t *section_info, + guint16 option_code, + guint16 option_length, + const guint8 *option_content, + int *err, gchar **err_info) +{ + if_filter_opt_t if_filter; + + /* + * Handle option content. + * + * ***DO NOT*** add any items to this table that are not + * standardized option codes in either section 3.5 "Options" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-options + * + * or in the list of options in section 4.1 "Section Header Block" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-section-header-block + * + * All option codes in this switch statement here must be listed + * in one of those places as standardized option types. + */ + switch (option_code) { + case(OPT_IDB_NAME): /* if_name */ + pcapng_process_string_option(wblock, option_code, option_length, + option_content); + break; + case(OPT_IDB_DESCRIPTION): /* if_description */ + pcapng_process_string_option(wblock, option_code, option_length, + option_content); + break; + case(OPT_IDB_SPEED): /* if_speed */ + pcapng_process_uint64_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_IDB_TSRESOL): /* if_tsresol */ + pcapng_process_uint8_option(wblock, option_code, option_length, + option_content); + break; + /* + * if_tzone 10 Time zone for GMT support (TODO: specify better). TODO: give a good example + */ + case(OPT_IDB_FILTER): /* if_filter */ + if (option_length < 1) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: packet block verdict option length %u is < 1", + option_length); + /* XXX - free anything? */ + return FALSE; + } + /* The first byte of the Option Data keeps a code of the filter used (e.g. if this is a libpcap string, + * or BPF bytecode. + */ + if (option_content[0] == 0) { + if_filter.type = if_filter_pcap; + if_filter.data.filter_str = g_strndup((char *)option_content+1, option_length-1); + ws_debug("filter_str %s option_length %u", + if_filter.data.filter_str, option_length); + /* Fails with multiple options; we silently ignore the failure */ + wtap_block_add_if_filter_option(wblock->block, option_code, &if_filter); + g_free(if_filter.data.filter_str); + } else if (option_content[0] == 1) { + /* + * XXX - byte-swap the code and k fields + * of each instruction as needed! + * + * XXX - what if option_length-1 is not a + * multiple of the size of a BPF instruction? + */ + guint num_insns; + const guint8 *insn_in; + + if_filter.type = if_filter_bpf; + num_insns = (option_length-1)/8; + insn_in = option_content+1; + if_filter.data.bpf_prog.bpf_prog_len = num_insns; + if_filter.data.bpf_prog.bpf_prog = g_new(wtap_bpf_insn_t, num_insns); + for (guint i = 0; i < num_insns; i++) { + wtap_bpf_insn_t *insn = &if_filter.data.bpf_prog.bpf_prog[i]; + + memcpy(&insn->code, insn_in, 2); + if (section_info->byte_swapped) + insn->code = GUINT16_SWAP_LE_BE(insn->code); + insn_in += 2; + memcpy(&insn->jt, insn_in, 1); + insn_in += 1; + memcpy(&insn->jf, insn_in, 1); + insn_in += 1; + memcpy(&insn->k, insn_in, 4); + if (section_info->byte_swapped) + insn->k = GUINT32_SWAP_LE_BE(insn->k); + insn_in += 4; + } + /* Fails with multiple options; we silently ignore the failure */ + wtap_block_add_if_filter_option(wblock->block, option_code, &if_filter); + g_free(if_filter.data.bpf_prog.bpf_prog); + } + break; + case(OPT_IDB_OS): /* if_os */ + /* + * if_os 12 A UTF-8 string containing the name of the operating system of the machine in which this interface is installed. + * This can be different from the same information that can be contained by the Section Header Block (Section 3.1 (Section Header Block (mandatory))) + * because the capture can have been done on a remote machine. "Windows XP SP2" / "openSUSE 10.2" / ... + */ + pcapng_process_string_option(wblock, option_code, option_length, + option_content); + break; + case(OPT_IDB_FCSLEN): /* if_fcslen */ + pcapng_process_uint8_option(wblock, option_code, option_length, + option_content); + break; + case(OPT_IDB_HARDWARE): /* if_hardware */ + pcapng_process_string_option(wblock, option_code, option_length, + option_content); + break; + /* TODO: process these! */ + case(OPT_IDB_IP4ADDR): + /* + * Interface network address and netmask. This option can be + * repeated multiple times within the same Interface + * Description Block when multiple IPv4 addresses are assigned + * to the interface. 192 168 1 1 255 255 255 0 + */ + break; + case(OPT_IDB_IP6ADDR): + /* + * Interface network address and prefix length (stored in the + * last byte). This option can be repeated multiple times + * within the same Interface Description Block when multiple + * IPv6 addresses are assigned to the interface. + * 2001:0db8:85a3:08d3:1319:8a2e:0370:7344/64 is written (in + * hex) as "20 01 0d b8 85 a3 08 d3 13 19 8a 2e 03 70 73 44 + * 40" + */ + break; + case(OPT_IDB_MACADDR): + /* + * Interface Hardware MAC address (48 bits). 00 01 02 03 04 05 + */ + break; + case(OPT_IDB_EUIADDR): + /* + * Interface Hardware EUI address (64 bits), if available. + * TODO: give a good example + */ + break; + case(OPT_IDB_TZONE): + /* + * Time zone for GMT support. This option has never been + * specified in greater detail and, unless it were to identify + * something such as an IANA time zone database timezone, + * would be insufficient for converting between UTC and local + * time. Therefore, it SHOULD NOT be used; instead, the + * if_iana_tzname option SHOULD be used if time zone + * information is to be specified. + * + * Given that, we don't do anything with it. + */ + break; + case(OPT_IDB_TSOFFSET): + /* + * A 64-bit integer value that specifies an offset (in + * seconds) that must be added to the timestamp of each packet + * to obtain the absolute timestamp of a packet. If this optio + * is not present, an offset of 0 is assumed (i.e., timestamps + * in blocks are absolute timestamps.) + */ + pcapng_process_int64_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + default: + if (!pcapng_process_unhandled_option(wblock, BT_INDEX_IDB, + section_info, option_code, + option_length, option_content, + err, err_info)) + return FALSE; + break; + } + return TRUE; +} + +/* "Interface Description Block" */ +static gboolean +pcapng_read_if_descr_block(wtap *wth, FILE_T fh, pcapng_block_header_t *bh, + section_info_t *section_info, + wtapng_block_t *wblock, int *err, gchar **err_info) +{ + /* Default time stamp resolution is 10^6 */ + guint64 time_units_per_second = 1000000; + int tsprecision = 6; + guint opt_cont_buf_len; + pcapng_interface_description_block_t idb; + wtapng_if_descr_mandatory_t* if_descr_mand; + guint link_type; + guint8 if_tsresol; + + /* + * Is this block long enough to be an IDB? + */ + if (bh->block_total_length < MIN_IDB_SIZE) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of an IDB is less than the minimum IDB size %u", + bh->block_total_length, MIN_IDB_SIZE); + return FALSE; + } + + /* read block content */ + if (!wtap_read_bytes(fh, &idb, sizeof idb, err, err_info)) { + ws_debug("failed to read IDB"); + return FALSE; + } + + /* + * Set wblock->block to a newly-allocated interface ID and information + * block. + */ + wblock->block = wtap_block_create(WTAP_BLOCK_IF_ID_AND_INFO); + + /* + * Set the mandatory values for the block. + */ + if_descr_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(wblock->block); + if (section_info->byte_swapped) { + link_type = GUINT16_SWAP_LE_BE(idb.linktype); + if_descr_mand->snap_len = GUINT32_SWAP_LE_BE(idb.snaplen); + } else { + link_type = idb.linktype; + if_descr_mand->snap_len = idb.snaplen; + } + + if_descr_mand->wtap_encap = wtap_pcap_encap_to_wtap_encap(link_type); + + ws_debug("IDB link_type %u (%s), snap %u", + link_type, + wtap_encap_description(if_descr_mand->wtap_encap), + if_descr_mand->snap_len); + + if (if_descr_mand->snap_len > wtap_max_snaplen_for_encap(if_descr_mand->wtap_encap)) { + /* + * We do not use this value, maybe we should check the + * snap_len of the packets against it. For now, only warn. + */ + ws_debug("snapshot length %u unrealistic.", + if_descr_mand->snap_len); + /*if_descr_mand->snap_len = WTAP_MAX_PACKET_SIZE_STANDARD;*/ + } + + /* Options */ + opt_cont_buf_len = bh->block_total_length - MIN_IDB_SIZE; + if (!pcapng_process_options(fh, wblock, section_info, opt_cont_buf_len, + pcapng_process_if_descr_block_option, + OPT_SECTION_BYTE_ORDER, err, err_info)) + return FALSE; + + /* + * Did we get a time stamp precision option? + */ + if (wtap_block_get_uint8_option_value(wblock->block, OPT_IDB_TSRESOL, + &if_tsresol) == WTAP_OPTTYPE_SUCCESS) { + /* + * Yes. Set time_units_per_second appropriately. + */ + guint8 exponent; + + exponent = (guint8)(if_tsresol & 0x7f); + if (if_tsresol & 0x80) { + /* + * 2^63 fits in a 64-bit unsigned number; 2^64 does not. + * + * ((2^64-1)/(2^63) is about 1.99, so, in practice, that + * fine a time stamp resolution works only if you start + * capturing at the Unix/POSIX epoch and capture for about + * 1.9 seconds, so the maximum useful power-of-2 exponent + * in a pcapng file is less than 63.) + */ + if (exponent > 63) { + /* + * Time units per second won't fit in a 64-bit integer, + * so Wireshark's current code can't read the file. + */ + *err = WTAP_ERR_UNSUPPORTED; + *err_info = ws_strdup_printf("pcapng: IDB power-of-2 time stamp resolution %u > 63", + exponent); + return FALSE; + } + + /* 2^exponent */ + time_units_per_second = G_GUINT64_CONSTANT(1) << exponent; + + /* + * Set the display precision to a value large enough to + * show the fractional time units we get, so that we + * don't display more digits than are justified. + * + * (That's also used as the base-10 if_tsresol value we use + * if we write this file as a pcapng file. Yes, that means + * that we won't write out the exact value we read in. + * + * Dealing with base-2 time stamps is a bit of a mess, + * thanks to humans counting with their fingers rather + * than their hands, and it applies to mroe files than + * pcapng files, e.g. ERF files.) + */ + if (time_units_per_second >= 1000000000) + tsprecision = WTAP_TSPREC_NSEC; + else if (time_units_per_second >= 100000000) + tsprecision = WTAP_TSPREC_10_NSEC; + else if (time_units_per_second >= 10000000) + tsprecision = WTAP_TSPREC_100_NSEC; + else if (time_units_per_second >= 1000000) + tsprecision = WTAP_TSPREC_USEC; + else if (time_units_per_second >= 100000) + tsprecision = WTAP_TSPREC_10_USEC; + else if (time_units_per_second >= 10000) + tsprecision = WTAP_TSPREC_100_USEC; + else if (time_units_per_second >= 1000) + tsprecision = WTAP_TSPREC_MSEC; + else if (time_units_per_second >= 100) + tsprecision = WTAP_TSPREC_10_MSEC; + else if (time_units_per_second >= 10) + tsprecision = WTAP_TSPREC_100_MSEC; + else + tsprecision = WTAP_TSPREC_SEC; + } else { + /* + * 10^19 fits in a 64-bit unsigned number; 10^20 does not. + * + * ((2^64-1)/(10^19) is about 1.84, so, in practice, that + * fine a time stamp resolution works only if you start + * capturing at the Unix/POSIX epoch and capture for about + * 1.8 seconds, so the maximum useful power-of-10 exponent + * in a pcapng file is less than 19.) + */ + guint64 result; + + if (exponent > 19) { + /* + * Time units per second won't fit in a 64-bit integer, + * so Wireshark's current code can't read the file. + */ + *err = WTAP_ERR_UNSUPPORTED; + *err_info = ws_strdup_printf("pcapng: IDB power-of-10 time stamp resolution %u > 19", + exponent); + return FALSE; + } + + /* 10^exponent */ + result = 1; + for (guint i = 0; i < exponent; i++) { + result *= 10U; + } + time_units_per_second = result; + + /* + * Set the display precision to min(exponent, WS_TSPREC_MAX), + * so that we don't display more digits than are justified. + * (That's also used as the base-10 if_tsresol value we use + * if we write this file as a pcapng file.) + */ + if (exponent <= WS_TSPREC_MAX) { + tsprecision = exponent; + } else { + tsprecision = WS_TSPREC_MAX; + } + } + if (time_units_per_second > (((guint64)1) << 32)) { + ws_debug("time conversion might be inaccurate"); + } + } + + /* + * Set the time units per second for this interface. + */ + if_descr_mand->time_units_per_second = time_units_per_second; + + /* + * Set the number of digits of precision to display (and the + * number to use for this interface if saving to a pcapng + * file). + */ + if_descr_mand->tsprecision = tsprecision; + + /* + * If the per-file encapsulation isn't known, set it to this + * interface's encapsulation. + * + * If it *is* known, and it isn't this interface's encapsulation, + * set it to WTAP_ENCAP_PER_PACKET, as this file doesn't + * have a single encapsulation for all interfaces in the file, + * so it probably doesn't have a single encapsulation for all + * packets in the file. + */ + if (wth->file_encap == WTAP_ENCAP_NONE) { + wth->file_encap = if_descr_mand->wtap_encap; + } else { + if (wth->file_encap != if_descr_mand->wtap_encap) { + wth->file_encap = WTAP_ENCAP_PER_PACKET; + } + } + + /* + * The same applies to the per-file time stamp resolution. + */ + if (wth->file_tsprec == WTAP_TSPREC_UNKNOWN) { + wth->file_tsprec = if_descr_mand->tsprecision; + } else { + if (wth->file_tsprec != if_descr_mand->tsprecision) { + wth->file_tsprec = WTAP_TSPREC_PER_PACKET; + } + } + + /* + * We don't return these to the caller in pcapng_read(). + */ + wblock->internal = TRUE; + + return TRUE; +} + +static gboolean +pcapng_read_decryption_secrets_block(FILE_T fh, pcapng_block_header_t *bh, + const section_info_t *section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + guint to_read; + pcapng_decryption_secrets_block_t dsb; + wtapng_dsb_mandatory_t *dsb_mand; + + /* read block content */ + if (!wtap_read_bytes(fh, &dsb, sizeof(dsb), err, err_info)) { + ws_debug("failed to read DSB"); + return FALSE; + } + + /* + * Set wblock->block to a newly-allocated decryption secrets block. + */ + wblock->block = wtap_block_create(WTAP_BLOCK_DECRYPTION_SECRETS); + + /* + * Set the mandatory values for the block. + */ + dsb_mand = (wtapng_dsb_mandatory_t *)wtap_block_get_mandatory_data(wblock->block); + if (section_info->byte_swapped) { + dsb_mand->secrets_type = GUINT32_SWAP_LE_BE(dsb.secrets_type); + dsb_mand->secrets_len = GUINT32_SWAP_LE_BE(dsb.secrets_len); + } else { + dsb_mand->secrets_type = dsb.secrets_type; + dsb_mand->secrets_len = dsb.secrets_len; + } + /* Sanity check: assume the secrets are not larger than 1 GiB */ + if (dsb_mand->secrets_len > 1024 * 1024 * 1024) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: secrets block is too large: %u", dsb_mand->secrets_len); + return FALSE; + } + dsb_mand->secrets_data = (guint8 *)g_malloc0(dsb_mand->secrets_len); + if (!wtap_read_bytes(fh, dsb_mand->secrets_data, dsb_mand->secrets_len, err, err_info)) { + ws_debug("failed to read DSB"); + return FALSE; + } + + /* Skip past padding and discard options (not supported yet). */ + to_read = bh->block_total_length - MIN_DSB_SIZE - dsb_mand->secrets_len; + if (!wtap_read_bytes(fh, NULL, to_read, err, err_info)) { + ws_debug("failed to read DSB options"); + return FALSE; + } + + /* + * We don't return these to the caller in pcapng_read(). + */ + wblock->internal = TRUE; + + return TRUE; +} + +static bool +pcapng_read_sysdig_meta_event_block(FILE_T fh, pcapng_block_header_t *bh, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + guint to_read; + wtapng_sysdig_mev_mandatory_t *mev_mand; + + /* + * Set wblock->block to a newly-allocated Sysdig meta event block. + */ + wblock->block = wtap_block_create(WTAP_BLOCK_SYSDIG_META_EVENT); + + /* + * Set the mandatory values for the block. + */ + mev_mand = (wtapng_sysdig_mev_mandatory_t *)wtap_block_get_mandatory_data(wblock->block); + mev_mand->mev_type = bh->block_type; + mev_mand->mev_data_len = bh->block_total_length - + (int)sizeof(pcapng_block_header_t) - + (int)sizeof(bh->block_total_length); + + /* Sanity check: assume event data can't be larger than 1 GiB */ + if (mev_mand->mev_data_len > 1024 * 1024 * 1024) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: Sysdig mev block is too large: %u", mev_mand->mev_data_len); + return false; + } + mev_mand->mev_data = (uint8_t *)g_malloc(mev_mand->mev_data_len); + if (!wtap_read_bytes(fh, mev_mand->mev_data, mev_mand->mev_data_len, err, err_info)) { + ws_debug("failed to read Sysdig mev"); + return false; + } + + /* Skip past padding and discard options (not supported yet). */ + to_read = bh->block_total_length - MIN_BLOCK_SIZE - mev_mand->mev_data_len; + if (!wtap_read_bytes(fh, NULL, to_read, err, err_info)) { + ws_debug("failed to read Sysdig mev options"); + return FALSE; + } + + /* + * We don't return these to the caller in pcapng_read(). + */ + wblock->internal = true; + + return true; +} + +static gboolean +pcapng_process_packet_block_option(wtapng_block_t *wblock, + const section_info_t *section_info, + guint16 option_code, + guint16 option_length, + const guint8 *option_content, + int *err, gchar **err_info) +{ + guint64 tmp64; + packet_verdict_opt_t packet_verdict; + packet_hash_opt_t packet_hash; + + /* + * Handle option content. + * + * ***DO NOT*** add any items to this table that are not + * standardized option codes in either section 3.5 "Options" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-options + * + * or in the list of options in section 4.3 "Enhanced Packet Block" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-enhanced-packet-block + * + * All option codes in this switch statement here must be listed + * in one of those places as standardized option types. + */ + switch (option_code) { + case(OPT_EPB_FLAGS): + if (option_length != 4) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: packet block flags option length %u is not 4", + option_length); + /* XXX - free anything? */ + return FALSE; + } + pcapng_process_uint32_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_EPB_HASH): + if (option_length < 1) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: packet block hash option length %u is < 1", + option_length); + /* XXX - free anything? */ + return FALSE; + } + packet_hash.type = option_content[0]; + packet_hash.hash_bytes = + g_byte_array_new_take((guint8 *)g_memdup2(&option_content[1], + option_length - 1), + option_length - 1); + wtap_block_add_packet_hash_option(wblock->block, option_code, &packet_hash); + wtap_packet_hash_free(&packet_hash); + ws_debug("hash type %u, data len %u", + option_content[0], option_length - 1); + break; + case(OPT_EPB_DROPCOUNT): + if (option_length != 8) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: packet block drop count option length %u is not 8", + option_length); + /* XXX - free anything? */ + return FALSE; + } + pcapng_process_uint64_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_EPB_PACKETID): + if (option_length != 8) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: packet block packet id option length %u is not 8", + option_length); + /* XXX - free anything? */ + return FALSE; + } + pcapng_process_uint64_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_EPB_QUEUE): + if (option_length != 4) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: packet block queue option length %u is not 4", + option_length); + /* XXX - free anything? */ + return FALSE; + } + pcapng_process_uint32_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_EPB_VERDICT): + if (option_length < 1) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: packet block verdict option length %u is < 1", + option_length); + /* XXX - free anything? */ + return FALSE; + } + switch (option_content[0]) { + + case(OPT_VERDICT_TYPE_HW): + packet_verdict.type = packet_verdict_hardware; + packet_verdict.data.verdict_bytes = + g_byte_array_new_take((guint8 *)g_memdup2(&option_content[1], + option_length - 1), + option_length - 1); + break; + + case(OPT_VERDICT_TYPE_TC): + if (option_length != 9) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: packet block TC verdict option length %u is != 9", + option_length); + /* XXX - free anything? */ + return FALSE; + } + /* Don't cast a guint8 * into a guint64 *--the + * guint8 * may not point to something that's + * aligned correctly. + */ + memcpy(&tmp64, &option_content[1], sizeof(guint64)); + if (section_info->byte_swapped) + tmp64 = GUINT64_SWAP_LE_BE(tmp64); + packet_verdict.type = packet_verdict_linux_ebpf_tc; + packet_verdict.data.verdict_linux_ebpf_tc = tmp64; + break; + + case(OPT_VERDICT_TYPE_XDP): + if (option_length != 9) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: packet block XDP verdict option length %u is != 9", + option_length); + /* XXX - free anything? */ + return FALSE; + } + /* Don't cast a guint8 * into a guint64 *--the + * guint8 * may not point to something that's + * aligned correctly. + */ + memcpy(&tmp64, &option_content[1], sizeof(guint64)); + if (section_info->byte_swapped) + tmp64 = GUINT64_SWAP_LE_BE(tmp64); + packet_verdict.type = packet_verdict_linux_ebpf_xdp; + packet_verdict.data.verdict_linux_ebpf_xdp = tmp64; + break; + + default: + /* Silently ignore unknown verdict types */ + return TRUE; + } + wtap_block_add_packet_verdict_option(wblock->block, option_code, &packet_verdict); + wtap_packet_verdict_free(&packet_verdict); + ws_debug("verdict type %u, data len %u", + option_content[0], option_length - 1); + break; + default: + if (!pcapng_process_unhandled_option(wblock, BT_INDEX_PBS, + section_info, option_code, + option_length, option_content, + err, err_info)) + return FALSE; + break; + } + return TRUE; +} + +static gboolean +pcapng_read_packet_block(FILE_T fh, pcapng_block_header_t *bh, + section_info_t *section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info, gboolean enhanced) +{ + guint block_read; + guint opt_cont_buf_len; + pcapng_enhanced_packet_block_t epb; + pcapng_packet_block_t pb; + wtapng_packet_t packet; + guint32 padding; + guint32 flags; + guint64 tmp64; + interface_info_t iface_info; + guint64 ts; + int pseudo_header_len; + int fcslen; + + wblock->block = wtap_block_create(WTAP_BLOCK_PACKET); + + /* "(Enhanced) Packet Block" read fixed part */ + if (enhanced) { + /* + * Is this block long enough to be an EPB? + */ + if (bh->block_total_length < MIN_EPB_SIZE) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of an EPB is less than the minimum EPB size %u", + bh->block_total_length, MIN_EPB_SIZE); + return FALSE; + } + if (!wtap_read_bytes(fh, &epb, sizeof epb, err, err_info)) { + ws_debug("failed to read packet data"); + return FALSE; + } + block_read = (guint)sizeof epb; + + if (section_info->byte_swapped) { + packet.interface_id = GUINT32_SWAP_LE_BE(epb.interface_id); + packet.drops_count = 0xFFFF; /* invalid */ + packet.ts_high = GUINT32_SWAP_LE_BE(epb.timestamp_high); + packet.ts_low = GUINT32_SWAP_LE_BE(epb.timestamp_low); + packet.cap_len = GUINT32_SWAP_LE_BE(epb.captured_len); + packet.packet_len = GUINT32_SWAP_LE_BE(epb.packet_len); + } else { + packet.interface_id = epb.interface_id; + packet.drops_count = 0xFFFF; /* invalid */ + packet.ts_high = epb.timestamp_high; + packet.ts_low = epb.timestamp_low; + packet.cap_len = epb.captured_len; + packet.packet_len = epb.packet_len; + } + ws_debug("EPB on interface_id %d, cap_len %d, packet_len %d", + packet.interface_id, packet.cap_len, packet.packet_len); + } else { + /* + * Is this block long enough to be a PB? + */ + if (bh->block_total_length < MIN_PB_SIZE) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of a PB is less than the minimum PB size %u", + bh->block_total_length, MIN_PB_SIZE); + return FALSE; + } + if (!wtap_read_bytes(fh, &pb, sizeof pb, err, err_info)) { + ws_debug("failed to read packet data"); + return FALSE; + } + block_read = (guint)sizeof pb; + + if (section_info->byte_swapped) { + packet.interface_id = GUINT16_SWAP_LE_BE(pb.interface_id); + packet.drops_count = GUINT16_SWAP_LE_BE(pb.drops_count); + packet.ts_high = GUINT32_SWAP_LE_BE(pb.timestamp_high); + packet.ts_low = GUINT32_SWAP_LE_BE(pb.timestamp_low); + packet.cap_len = GUINT32_SWAP_LE_BE(pb.captured_len); + packet.packet_len = GUINT32_SWAP_LE_BE(pb.packet_len); + } else { + packet.interface_id = pb.interface_id; + packet.drops_count = pb.drops_count; + packet.ts_high = pb.timestamp_high; + packet.ts_low = pb.timestamp_low; + packet.cap_len = pb.captured_len; + packet.packet_len = pb.packet_len; + } + ws_debug("PB on interface_id %d, cap_len %d, packet_len %d", + packet.interface_id, packet.cap_len, packet.packet_len); + } + + /* + * How much padding is there at the end of the packet data? + */ + if ((packet.cap_len % 4) != 0) + padding = 4 - (packet.cap_len % 4); + else + padding = 0; + + /* + * Is this block long enough to hold the packet data? + */ + if (enhanced) { + if (bh->block_total_length < + MIN_EPB_SIZE + packet.cap_len + padding) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of an EPB is too small for %u bytes of packet data", + bh->block_total_length, packet.cap_len); + return FALSE; + } + } else { + if (bh->block_total_length < + MIN_PB_SIZE + packet.cap_len + padding) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of a PB is too small for %u bytes of packet data", + bh->block_total_length, packet.cap_len); + return FALSE; + } + } + + ws_debug("packet data: packet_len %u captured_len %u interface_id %u", + packet.packet_len, + packet.cap_len, + packet.interface_id); + + if (packet.interface_id >= section_info->interfaces->len) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: interface index %u is not less than section interface count %u", + packet.interface_id, + section_info->interfaces->len); + return FALSE; + } + iface_info = g_array_index(section_info->interfaces, interface_info_t, + packet.interface_id); + + if (packet.cap_len > wtap_max_snaplen_for_encap(iface_info.wtap_encap)) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: cap_len %u is larger than %u", + packet.cap_len, + wtap_max_snaplen_for_encap(iface_info.wtap_encap)); + return FALSE; + } + + wblock->rec->rec_type = REC_TYPE_PACKET; + wblock->rec->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN|WTAP_HAS_INTERFACE_ID; + + ws_debug("encapsulation = %d (%s), pseudo header size = %d.", + iface_info.wtap_encap, + wtap_encap_description(iface_info.wtap_encap), + pcap_get_phdr_size(iface_info.wtap_encap, &wblock->rec->rec_header.packet_header.pseudo_header)); + wblock->rec->rec_header.packet_header.interface_id = packet.interface_id; + wblock->rec->rec_header.packet_header.pkt_encap = iface_info.wtap_encap; + wblock->rec->tsprec = iface_info.tsprecision; + + memset((void *)&wblock->rec->rec_header.packet_header.pseudo_header, 0, sizeof(union wtap_pseudo_header)); + pseudo_header_len = pcap_process_pseudo_header(fh, + FALSE, /* not a Nokia pcap - not a pcap at all */ + iface_info.wtap_encap, + packet.cap_len, + wblock->rec, + err, + err_info); + if (pseudo_header_len < 0) { + return FALSE; + } + block_read += pseudo_header_len; + wblock->rec->rec_header.packet_header.caplen = packet.cap_len - pseudo_header_len; + wblock->rec->rec_header.packet_header.len = packet.packet_len - pseudo_header_len; + + /* Combine the two 32-bit pieces of the timestamp into one 64-bit value */ + ts = (((guint64)packet.ts_high) << 32) | ((guint64)packet.ts_low); + + /* Convert it to seconds and nanoseconds. */ + wblock->rec->ts.secs = (time_t)(ts / iface_info.time_units_per_second); + wblock->rec->ts.nsecs = (int)(((ts % iface_info.time_units_per_second) * 1000000000) / iface_info.time_units_per_second); + + /* Add the time stamp offset. */ + wblock->rec->ts.secs = (time_t)(wblock->rec->ts.secs + iface_info.tsoffset); + + /* "(Enhanced) Packet Block" read capture data */ + if (!wtap_read_packet_bytes(fh, wblock->frame_buffer, + packet.cap_len - pseudo_header_len, err, err_info)) + return FALSE; + block_read += packet.cap_len - pseudo_header_len; + + /* jump over potential padding bytes at end of the packet data */ + if (padding != 0) { + if (!wtap_read_bytes(fh, NULL, padding, err, err_info)) + return FALSE; + block_read += padding; + } + + /* FCS length default */ + fcslen = iface_info.fcslen; + + /* Options */ + opt_cont_buf_len = bh->block_total_length - + (int)sizeof(pcapng_block_header_t) - + block_read - /* fixed and variable part, including padding */ + (int)sizeof(bh->block_total_length); + if (!pcapng_process_options(fh, wblock, section_info, opt_cont_buf_len, + pcapng_process_packet_block_option, + OPT_SECTION_BYTE_ORDER, err, err_info)) + return FALSE; + + /* + * Did we get a packet flags option? + */ + if (WTAP_OPTTYPE_SUCCESS == wtap_block_get_uint32_option_value(wblock->block, OPT_PKT_FLAGS, &flags)) { + if (PACK_FLAGS_FCS_LENGTH(flags) != 0) { + /* + * The FCS length is present, but in units of octets, not + * bits; convert it to bits. + */ + fcslen = PACK_FLAGS_FCS_LENGTH(flags)*8; + } + } + /* + * How about a drop_count option? If not, set it from other sources + */ + if (WTAP_OPTTYPE_SUCCESS != wtap_block_get_uint64_option_value(wblock->block, OPT_PKT_DROPCOUNT, &tmp64) && packet.drops_count != 0xFFFF) { + wtap_block_add_uint64_option(wblock->block, OPT_PKT_DROPCOUNT, (guint64)packet.drops_count); + } + + pcap_read_post_process(FALSE, iface_info.wtap_encap, + wblock->rec, ws_buffer_start_ptr(wblock->frame_buffer), + section_info->byte_swapped, fcslen); + + /* + * We return these to the caller in pcapng_read(). + */ + wblock->internal = FALSE; + + /* + * We want dissectors (particularly packet_frame) to be able to + * access packet comments and whatnot that are in the block. wblock->block + * will be unref'd by pcapng_seek_read(), so move the block to where + * dissectors can find it. + */ + wblock->rec->block = wblock->block; + wblock->block = NULL; + + return TRUE; +} + + +static gboolean +pcapng_read_simple_packet_block(FILE_T fh, pcapng_block_header_t *bh, + const section_info_t *section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + interface_info_t iface_info; + pcapng_simple_packet_block_t spb; + wtapng_simple_packet_t simple_packet; + guint32 padding; + int pseudo_header_len; + + /* + * Is this block long enough to be an SPB? + */ + if (bh->block_total_length < MIN_SPB_SIZE) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of an SPB is less than the minimum SPB size %u", + bh->block_total_length, MIN_SPB_SIZE); + return FALSE; + } + + /* "Simple Packet Block" read fixed part */ + if (!wtap_read_bytes(fh, &spb, sizeof spb, err, err_info)) { + ws_debug("failed to read packet data"); + return FALSE; + } + + if (0 >= section_info->interfaces->len) { + *err = WTAP_ERR_BAD_FILE; + *err_info = g_strdup("pcapng: SPB appeared before any IDBs in the section"); + return FALSE; + } + iface_info = g_array_index(section_info->interfaces, interface_info_t, 0); + + if (section_info->byte_swapped) { + simple_packet.packet_len = GUINT32_SWAP_LE_BE(spb.packet_len); + } else { + simple_packet.packet_len = spb.packet_len; + } + + /* + * The captured length is not a field in the SPB; it can be + * calculated as the minimum of the snapshot length from the + * IDB and the packet length, as per the pcapng spec. An IDB + * snapshot length of 0 means no limit. + */ + simple_packet.cap_len = simple_packet.packet_len; + if (simple_packet.cap_len > iface_info.snap_len && iface_info.snap_len != 0) + simple_packet.cap_len = iface_info.snap_len; + + /* + * How much padding is there at the end of the packet data? + */ + if ((simple_packet.cap_len % 4) != 0) + padding = 4 - (simple_packet.cap_len % 4); + else + padding = 0; + + /* + * Is this block long enough to hold the packet data? + */ + if (bh->block_total_length < MIN_SPB_SIZE + simple_packet.cap_len + padding) { + /* + * No. That means that the problem is with the packet + * length; the snapshot length can be bigger than the amount + * of packet data in the block, as it's a *maximum* length, + * not a *minimum* length. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of an SPB is too small for %u bytes of packet data", + bh->block_total_length, simple_packet.packet_len); + return FALSE; + } + + if (simple_packet.cap_len > wtap_max_snaplen_for_encap(iface_info.wtap_encap)) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: cap_len %u is larger than %u", + simple_packet.cap_len, + wtap_max_snaplen_for_encap(iface_info.wtap_encap)); + return FALSE; + } + ws_debug("packet data: packet_len %u", + simple_packet.packet_len); + + ws_debug("Need to read pseudo header of size %d", + pcap_get_phdr_size(iface_info.wtap_encap, &wblock->rec->rec_header.packet_header.pseudo_header)); + + /* No time stamp in a simple packet block; no options, either */ + wblock->rec->rec_type = REC_TYPE_PACKET; + wblock->rec->presence_flags = WTAP_HAS_CAP_LEN|WTAP_HAS_INTERFACE_ID; + wblock->rec->rec_header.packet_header.interface_id = 0; + wblock->rec->rec_header.packet_header.pkt_encap = iface_info.wtap_encap; + wblock->rec->tsprec = iface_info.tsprecision; + wblock->rec->ts.secs = 0; + wblock->rec->ts.nsecs = 0; + wblock->rec->rec_header.packet_header.interface_id = 0; + + memset((void *)&wblock->rec->rec_header.packet_header.pseudo_header, 0, sizeof(union wtap_pseudo_header)); + pseudo_header_len = pcap_process_pseudo_header(fh, + FALSE, + iface_info.wtap_encap, + simple_packet.cap_len, + wblock->rec, + err, + err_info); + if (pseudo_header_len < 0) { + return FALSE; + } + wblock->rec->rec_header.packet_header.caplen = simple_packet.cap_len - pseudo_header_len; + wblock->rec->rec_header.packet_header.len = simple_packet.packet_len - pseudo_header_len; + + memset((void *)&wblock->rec->rec_header.packet_header.pseudo_header, 0, sizeof(union wtap_pseudo_header)); + + /* "Simple Packet Block" read capture data */ + if (!wtap_read_packet_bytes(fh, wblock->frame_buffer, + simple_packet.cap_len, err, err_info)) + return FALSE; + + /* jump over potential padding bytes at end of the packet data */ + if ((simple_packet.cap_len % 4) != 0) { + if (!wtap_read_bytes(fh, NULL, 4 - (simple_packet.cap_len % 4), err, err_info)) + return FALSE; + } + + pcap_read_post_process(FALSE, iface_info.wtap_encap, + wblock->rec, ws_buffer_start_ptr(wblock->frame_buffer), + section_info->byte_swapped, iface_info.fcslen); + + /* + * We return these to the caller in pcapng_read(). + */ + wblock->internal = FALSE; + + return TRUE; +} + +#define NRES_ENDOFRECORD 0 +#define NRES_IP4RECORD 1 +#define NRES_IP6RECORD 2 +#define PADDING4(x) ((((x + 3) >> 2) << 2) - x) +/* IPv6 + MAXNAMELEN */ +#define INITIAL_NRB_REC_SIZE (16 + 64) + +/* + * Find the end of the NUL-terminated name the beginning of which is pointed + * to by p; record_len is the number of bytes remaining in the record. + * + * Return the length of the name, including the terminating NUL. + * + * If we don't find a terminating NUL, return -1 and set *err and + * *err_info appropriately. + */ +static int +name_resolution_block_find_name_end(const char *p, guint record_len, int *err, + gchar **err_info) +{ + int namelen; + + namelen = 0; + for (;;) { + if (record_len == 0) { + /* + * We ran out of bytes in the record without + * finding a NUL. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = g_strdup("pcapng: NRB record has non-null-terminated host name"); + return -1; + } + if (*p == '\0') + break; /* that's the terminating NUL */ + p++; + record_len--; + namelen++; /* count this byte */ + } + + /* Include the NUL in the name length. */ + return namelen + 1; +} + +static gboolean +pcapng_process_name_resolution_block_option(wtapng_block_t *wblock, + const section_info_t *section_info, + guint16 option_code, + guint16 option_length, + const guint8 *option_content, + int *err, gchar **err_info) +{ + /* + * Handle option content. + * + * ***DO NOT*** add any items to this table that are not + * standardized option codes in either section 3.5 "Options" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-options + * + * or in the list of options in section 4.1 "Section Header Block" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-section-header-block + * + * All option codes in this switch statement here must be listed + * in one of those places as standardized option types. + */ + switch (option_code) { + /* TODO: + * ns_dnsname 2 + * ns_dnsIP4addr 3 + * ns_dnsIP6addr 4 + */ + default: + if (!pcapng_process_unhandled_option(wblock, BT_INDEX_NRB, + section_info, option_code, + option_length, option_content, + err, err_info)) + return FALSE; + break; + } + return TRUE; +} + +static gboolean +pcapng_read_name_resolution_block(FILE_T fh, pcapng_block_header_t *bh, + section_info_t *section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + int block_read; + int to_read; + pcapng_name_resolution_block_t nrb; + Buffer nrb_rec; + guint32 v4_addr; + guint record_len, opt_cont_buf_len; + char *namep; + int namelen; + wtapng_nrb_mandatory_t *nrb_mand; + + /* + * Is this block long enough to be an NRB? + */ + if (bh->block_total_length < MIN_NRB_SIZE) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of an NRB is less than the minimum NRB size %u", + bh->block_total_length, MIN_NRB_SIZE); + return FALSE; + } + + to_read = bh->block_total_length - 8 - 4; /* We have read the header and should not read the final block_total_length */ + + ws_debug("total %d bytes", bh->block_total_length); + + /* Ensure we have a name resolution block */ + if (wblock->block == NULL) { + wblock->block = wtap_block_create(WTAP_BLOCK_NAME_RESOLUTION); + } + + /* + * Set the mandatory values for the block. + */ + nrb_mand = (wtapng_nrb_mandatory_t *)wtap_block_get_mandatory_data(wblock->block); + + /* + * Start out with a buffer big enough for an IPv6 address and one + * 64-byte name; we'll make the buffer bigger if necessary. + */ + ws_buffer_init(&nrb_rec, INITIAL_NRB_REC_SIZE); + block_read = 0; + while (block_read < to_read) { + /* + * There must be at least one record's worth of data + * here. + */ + if ((size_t)(to_read - block_read) < sizeof nrb) { + ws_buffer_free(&nrb_rec); + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: %d bytes left in the block < NRB record header size %u", + to_read - block_read, + (guint)sizeof nrb); + return FALSE; + } + if (!wtap_read_bytes(fh, &nrb, sizeof nrb, err, err_info)) { + ws_buffer_free(&nrb_rec); + ws_debug("failed to read record header"); + return FALSE; + } + block_read += (int)sizeof nrb; + + if (section_info->byte_swapped) { + nrb.record_type = GUINT16_SWAP_LE_BE(nrb.record_type); + nrb.record_len = GUINT16_SWAP_LE_BE(nrb.record_len); + } + + if (to_read - block_read < nrb.record_len + PADDING4(nrb.record_len)) { + ws_buffer_free(&nrb_rec); + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: %d bytes left in the block < NRB record length + padding %u", + to_read - block_read, + nrb.record_len + PADDING4(nrb.record_len)); + return FALSE; + } + switch (nrb.record_type) { + case NRES_ENDOFRECORD: + /* There shouldn't be any more data - but there MAY be options */ + goto read_options; + break; + case NRES_IP4RECORD: + /* + * The smallest possible record must have + * a 4-byte IPv4 address, hence a minimum + * of 4 bytes. + * + * (The pcapng spec really indicates + * that it must be at least 5 bytes, + * as there must be at least one name, + * and it really must be at least 6 + * bytes, as the name mustn't be null, + * but there's no need to fail if there + * aren't any names at all, and we + * should report a null name as such.) + */ + if (nrb.record_len < 4) { + ws_buffer_free(&nrb_rec); + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: NRB record length for IPv4 record %u < minimum length 4", + nrb.record_len); + return FALSE; + } + ws_buffer_assure_space(&nrb_rec, nrb.record_len); + if (!wtap_read_bytes(fh, ws_buffer_start_ptr(&nrb_rec), + nrb.record_len, err, err_info)) { + ws_buffer_free(&nrb_rec); + ws_debug("failed to read IPv4 record data"); + return FALSE; + } + block_read += nrb.record_len; + + /* + * Scan through all the names in + * the record and add them. + */ + memcpy(&v4_addr, + ws_buffer_start_ptr(&nrb_rec), 4); + /* IPv4 address is in big-endian order in the file always, which is how we store + it internally as well, so don't byte-swap it */ + for (namep = (char *)ws_buffer_start_ptr(&nrb_rec) + 4, record_len = nrb.record_len - 4; + record_len != 0; + namep += namelen, record_len -= namelen) { + /* + * Scan forward for a null + * byte. + */ + namelen = name_resolution_block_find_name_end(namep, record_len, err, err_info); + if (namelen == -1) { + ws_buffer_free(&nrb_rec); + return FALSE; /* fail */ + } + hashipv4_t *tp = g_new0(hashipv4_t, 1); + tp->addr = v4_addr; + (void) g_strlcpy(tp->name, namep, MAXNAMELEN); + nrb_mand->ipv4_addr_list = g_list_prepend(nrb_mand->ipv4_addr_list, tp); + } + + if (!wtap_read_bytes(fh, NULL, PADDING4(nrb.record_len), err, err_info)) { + ws_buffer_free(&nrb_rec); + return FALSE; + } + block_read += PADDING4(nrb.record_len); + break; + case NRES_IP6RECORD: + /* + * The smallest possible record must have + * a 16-byte IPv6 address, hence a minimum + * of 16 bytes. + * + * (The pcapng spec really indicates + * that it must be at least 17 bytes, + * as there must be at least one name, + * and it really must be at least 18 + * bytes, as the name mustn't be null, + * but there's no need to fail if there + * aren't any names at all, and we + * should report a null name as such.) + */ + if (nrb.record_len < 16) { + ws_buffer_free(&nrb_rec); + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: NRB record length for IPv6 record %u < minimum length 16", + nrb.record_len); + return FALSE; + } + if (to_read < nrb.record_len) { + ws_buffer_free(&nrb_rec); + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: NRB record length for IPv6 record %u > remaining data in NRB", + nrb.record_len); + return FALSE; + } + ws_buffer_assure_space(&nrb_rec, nrb.record_len); + if (!wtap_read_bytes(fh, ws_buffer_start_ptr(&nrb_rec), + nrb.record_len, err, err_info)) { + ws_buffer_free(&nrb_rec); + return FALSE; + } + block_read += nrb.record_len; + + for (namep = (char *)ws_buffer_start_ptr(&nrb_rec) + 16, record_len = nrb.record_len - 16; + record_len != 0; + namep += namelen, record_len -= namelen) { + /* + * Scan forward for a null + * byte. + */ + namelen = name_resolution_block_find_name_end(namep, record_len, err, err_info); + if (namelen == -1) { + ws_buffer_free(&nrb_rec); + return FALSE; /* fail */ + } + hashipv6_t *tp = g_new0(hashipv6_t, 1); + memcpy(tp->addr, ws_buffer_start_ptr(&nrb_rec), sizeof tp->addr); + (void) g_strlcpy(tp->name, namep, MAXNAMELEN); + nrb_mand->ipv6_addr_list = g_list_prepend(nrb_mand->ipv6_addr_list, tp); + } + + if (!wtap_read_bytes(fh, NULL, PADDING4(nrb.record_len), err, err_info)) { + ws_buffer_free(&nrb_rec); + return FALSE; + } + block_read += PADDING4(nrb.record_len); + break; + default: + ws_debug("unknown record type 0x%x", nrb.record_type); + if (!wtap_read_bytes(fh, NULL, nrb.record_len + PADDING4(nrb.record_len), err, err_info)) { + ws_buffer_free(&nrb_rec); + return FALSE; + } + block_read += nrb.record_len + PADDING4(nrb.record_len); + break; + } + } + +read_options: + to_read -= block_read; + + /* Options */ + opt_cont_buf_len = to_read; + if (!pcapng_process_options(fh, wblock, section_info, opt_cont_buf_len, + pcapng_process_name_resolution_block_option, + OPT_SECTION_BYTE_ORDER, err, err_info)) + return FALSE; + + ws_buffer_free(&nrb_rec); + + /* + * We don't return these to the caller in pcapng_read(). + */ + wblock->internal = TRUE; + + return TRUE; +} + +static gboolean +pcapng_process_interface_statistics_block_option(wtapng_block_t *wblock, + const section_info_t *section_info, + guint16 option_code, + guint16 option_length, + const guint8 *option_content, + int *err, gchar **err_info) +{ + /* + * Handle option content. + * + * ***DO NOT*** add any items to this table that are not + * standardized option codes in either section 3.5 "Options" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-options + * + * or in the list of options in section 4.1 "Section Header Block" + * of the current pcapng spec, at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#name-section-header-block + * + * All option codes in this switch statement here must be listed + * in one of those places as standardized option types. + */ + switch (option_code) { + case(OPT_ISB_STARTTIME): /* isb_starttime */ + pcapng_process_timestamp_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_ISB_ENDTIME): /* isb_endtime */ + pcapng_process_timestamp_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_ISB_IFRECV): /* isb_ifrecv */ + pcapng_process_uint64_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_ISB_IFDROP): /* isb_ifdrop */ + pcapng_process_uint64_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_ISB_FILTERACCEPT): /* isb_filteraccept 6 */ + pcapng_process_uint64_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_ISB_OSDROP): /* isb_osdrop 7 */ + pcapng_process_uint64_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + case(OPT_ISB_USRDELIV): /* isb_usrdeliv 8 */ + pcapng_process_uint64_option(wblock, section_info, + OPT_SECTION_BYTE_ORDER, + option_code, option_length, + option_content); + break; + default: + if (!pcapng_process_unhandled_option(wblock, BT_INDEX_ISB, + section_info, option_code, + option_length, option_content, + err, err_info)) + return FALSE; + break; + } + return TRUE; +} + +static gboolean +pcapng_read_interface_statistics_block(FILE_T fh, pcapng_block_header_t *bh, + section_info_t *section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + guint opt_cont_buf_len; + pcapng_interface_statistics_block_t isb; + wtapng_if_stats_mandatory_t* if_stats_mand; + + /* + * Is this block long enough to be an ISB? + */ + if (bh->block_total_length < MIN_ISB_SIZE) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of an ISB is too small (< %u)", + bh->block_total_length, MIN_ISB_SIZE); + return FALSE; + } + + /* "Interface Statistics Block" read fixed part */ + if (!wtap_read_bytes(fh, &isb, sizeof isb, err, err_info)) { + ws_debug("failed to read packet data"); + return FALSE; + } + + /* + * Set wblock->block to a newly-allocated interface statistics block. + */ + wblock->block = wtap_block_create(WTAP_BLOCK_IF_STATISTICS); + + /* + * Set the mandatory values for the block. + */ + if_stats_mand = (wtapng_if_stats_mandatory_t*)wtap_block_get_mandatory_data(wblock->block); + if (section_info->byte_swapped) { + if_stats_mand->interface_id = GUINT32_SWAP_LE_BE(isb.interface_id); + if_stats_mand->ts_high = GUINT32_SWAP_LE_BE(isb.timestamp_high); + if_stats_mand->ts_low = GUINT32_SWAP_LE_BE(isb.timestamp_low); + } else { + if_stats_mand->interface_id = isb.interface_id; + if_stats_mand->ts_high = isb.timestamp_high; + if_stats_mand->ts_low = isb.timestamp_low; + } + ws_debug("interface_id %u", if_stats_mand->interface_id); + + /* Options */ + opt_cont_buf_len = bh->block_total_length - + (MIN_BLOCK_SIZE + (guint)sizeof isb); /* fixed and variable part, including padding */ + if (!pcapng_process_options(fh, wblock, section_info, opt_cont_buf_len, + pcapng_process_interface_statistics_block_option, + OPT_SECTION_BYTE_ORDER, err, err_info)) + return FALSE; + + /* + * We don't return these to the caller in pcapng_read(). + */ + wblock->internal = TRUE; + + return TRUE; +} + +#define NFLX_BLOCK_TYPE_EVENT 1 +#define NFLX_BLOCK_TYPE_SKIP 2 + +typedef struct pcapng_nflx_custom_block_s { + guint32 nflx_type; +} pcapng_nflx_custom_block_t; + +#define MIN_NFLX_CB_SIZE ((guint32)(MIN_CB_SIZE + sizeof(pcapng_nflx_custom_block_t))) + +static gboolean +pcapng_read_nflx_custom_block(FILE_T fh, pcapng_block_header_t *bh, + section_info_t *section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + pcapng_nflx_custom_block_t nflx_cb; + guint opt_cont_buf_len; + guint32 type, skipped; + + if (bh->block_total_length < MIN_NFLX_CB_SIZE) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of a Netflix CB is too small (< %u)", + bh->block_total_length, MIN_NFLX_CB_SIZE); + return FALSE; + } + + wblock->rec->rec_type = REC_TYPE_CUSTOM_BLOCK; + wblock->rec->rec_header.custom_block_header.pen = PEN_NFLX; + /* "NFLX Custom Block" read fixed part */ + if (!wtap_read_bytes(fh, &nflx_cb, sizeof nflx_cb, err, err_info)) { + ws_debug("Failed to read nflx type"); + return FALSE; + } + type = GUINT32_FROM_LE(nflx_cb.nflx_type); + ws_debug("BBLog type: %u", type); + switch (type) { + case NFLX_BLOCK_TYPE_EVENT: + /* + * The fixed-length portion is MIN_NFLX_CB_SIZE bytes. + * We already know we have that much data in the block. + */ + wblock->rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.type = BBLOG_TYPE_EVENT_BLOCK; + opt_cont_buf_len = bh->block_total_length - MIN_NFLX_CB_SIZE; + ws_debug("event"); + break; + case NFLX_BLOCK_TYPE_SKIP: + /* + * The fixed-length portion is MIN_NFLX_CB_SIZE bytes plus a + * 32-bit value. + * + * Make sure we have that much data in the block. + */ + if (bh->block_total_length < MIN_NFLX_CB_SIZE + (guint32)sizeof(guint32)) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of a Netflix skip CB is too small (< %u)", + bh->block_total_length, + MIN_NFLX_CB_SIZE + (guint32)sizeof(guint32)); + return FALSE; + } + if (!wtap_read_bytes(fh, &skipped, sizeof(guint32), err, err_info)) { + ws_debug("Failed to read skipped"); + return FALSE; + } + wblock->rec->presence_flags = 0; + wblock->rec->rec_header.custom_block_header.length = 4; + wblock->rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.type = BBLOG_TYPE_SKIPPED_BLOCK; + wblock->rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.skipped = GUINT32_FROM_LE(skipped); + wblock->internal = FALSE; + opt_cont_buf_len = bh->block_total_length - MIN_NFLX_CB_SIZE - sizeof(guint32); + ws_debug("skipped: %u", wblock->rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.skipped); + break; + default: + ws_debug("Unknown type %u", type); + return FALSE; + } + + /* Options */ + if (!pcapng_process_options(fh, wblock, section_info, opt_cont_buf_len, + NULL, OPT_LITTLE_ENDIAN, err, err_info)) + return FALSE; + + return TRUE; +} + +static gboolean +pcapng_handle_generic_custom_block(FILE_T fh, pcapng_block_header_t *bh, + guint32 pen, wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + guint to_read; + + ws_debug("unknown pen %u", pen); + if (bh->block_total_length % 4) { + to_read = bh->block_total_length + 4 - (bh->block_total_length % 4); + } else { + to_read = bh->block_total_length; + } + to_read -= MIN_CB_SIZE; + wblock->rec->rec_type = REC_TYPE_CUSTOM_BLOCK; + wblock->rec->presence_flags = 0; + wblock->rec->rec_header.custom_block_header.length = bh->block_total_length - MIN_CB_SIZE; + wblock->rec->rec_header.custom_block_header.pen = pen; + wblock->rec->rec_header.custom_block_header.copy_allowed = (bh->block_type == BLOCK_TYPE_CB_COPY); + if (!wtap_read_packet_bytes(fh, wblock->frame_buffer, to_read, err, err_info)) { + return FALSE; + } + /* + * We return these to the caller in pcapng_read(). + */ + wblock->internal = FALSE; + return TRUE; +} + +static gboolean +pcapng_read_custom_block(FILE_T fh, pcapng_block_header_t *bh, + section_info_t *section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + pcapng_custom_block_t cb; + guint32 pen; + + /* Is this block long enough to be an CB? */ + if (bh->block_total_length < MIN_CB_SIZE) { + /* + * No. + */ + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of a CB is too small (< %u)", + bh->block_total_length, MIN_CB_SIZE); + return FALSE; + } + + wblock->block = wtap_block_create(WTAP_BLOCK_CUSTOM); + + /* Custom block read fixed part */ + if (!wtap_read_bytes(fh, &cb, sizeof cb, err, err_info)) { + ws_debug("failed to read pen"); + return FALSE; + } + if (section_info->byte_swapped) { + pen = GUINT32_SWAP_LE_BE(cb.pen); + } else { + pen = cb.pen; + } + ws_debug("pen %u, custom data and option length %u", pen, bh->block_total_length - MIN_CB_SIZE); + + switch (pen) { + case PEN_NFLX: + if (!pcapng_read_nflx_custom_block(fh, bh, section_info, wblock, err, err_info)) + return FALSE; + break; + default: + if (!pcapng_handle_generic_custom_block(fh, bh, pen, wblock, err, err_info)) { + return FALSE; + } + break; + } + + wblock->rec->block = wblock->block; + wblock->block = NULL; + wblock->internal = FALSE; + + return TRUE; +} + +static gboolean +pcapng_read_sysdig_event_block(FILE_T fh, pcapng_block_header_t *bh, + const section_info_t *section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + unsigned block_read; + guint16 cpu_id; + guint64 wire_ts; + guint64 ts; + guint64 thread_id; + guint32 event_len; + guint16 event_type; + guint32 nparams = 0; + guint min_event_size; + + switch (bh->block_type) { + case BLOCK_TYPE_SYSDIG_EVENT_V2_LARGE: + case BLOCK_TYPE_SYSDIG_EVENT_V2: + min_event_size = MIN_SYSDIG_EVENT_V2_SIZE; + break; + default: + min_event_size = MIN_SYSDIG_EVENT_SIZE; + break; + } + + if (bh->block_total_length < min_event_size) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of a Sysdig event block is too small (< %u)", + bh->block_total_length, min_event_size); + return FALSE; + } + + wblock->rec->rec_type = REC_TYPE_SYSCALL; + wblock->rec->rec_header.syscall_header.record_type = bh->block_type; + wblock->rec->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN /*|WTAP_HAS_INTERFACE_ID */; + wblock->rec->tsprec = WTAP_TSPREC_NSEC; + + if (!wtap_read_bytes(fh, &cpu_id, sizeof cpu_id, err, err_info)) { + ws_debug("failed to read sysdig event cpu id"); + return FALSE; + } + if (!wtap_read_bytes(fh, &wire_ts, sizeof wire_ts, err, err_info)) { + ws_debug("failed to read sysdig event timestamp"); + return FALSE; + } + if (!wtap_read_bytes(fh, &thread_id, sizeof thread_id, err, err_info)) { + ws_debug("failed to read sysdig event thread id"); + return FALSE; + } + if (!wtap_read_bytes(fh, &event_len, sizeof event_len, err, err_info)) { + ws_debug("failed to read sysdig event length"); + return FALSE; + } + if (!wtap_read_bytes(fh, &event_type, sizeof event_type, err, err_info)) { + ws_debug("failed to read sysdig event type"); + return FALSE; + } + if (bh->block_type == BLOCK_TYPE_SYSDIG_EVENT_V2 || bh->block_type == BLOCK_TYPE_SYSDIG_EVENT_V2_LARGE) { + if (!wtap_read_bytes(fh, &nparams, sizeof nparams, err, err_info)) { + ws_debug("failed to read sysdig number of parameters"); + return FALSE; + } + } + + wblock->rec->rec_header.syscall_header.byte_order = G_BYTE_ORDER; + + /* XXX Use Gxxx_FROM_LE macros instead? */ + if (section_info->byte_swapped) { + wblock->rec->rec_header.syscall_header.byte_order = +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + G_BIG_ENDIAN; +#else + G_LITTLE_ENDIAN; +#endif + wblock->rec->rec_header.syscall_header.cpu_id = GUINT16_SWAP_LE_BE(cpu_id); + ts = GUINT64_SWAP_LE_BE(wire_ts); + wblock->rec->rec_header.syscall_header.thread_id = GUINT64_SWAP_LE_BE(thread_id); + wblock->rec->rec_header.syscall_header.event_len = GUINT32_SWAP_LE_BE(event_len); + wblock->rec->rec_header.syscall_header.event_type = GUINT16_SWAP_LE_BE(event_type); + wblock->rec->rec_header.syscall_header.nparams = GUINT32_SWAP_LE_BE(nparams); + } else { + wblock->rec->rec_header.syscall_header.cpu_id = cpu_id; + ts = wire_ts; + wblock->rec->rec_header.syscall_header.thread_id = thread_id; + wblock->rec->rec_header.syscall_header.event_len = event_len; + wblock->rec->rec_header.syscall_header.event_type = event_type; + wblock->rec->rec_header.syscall_header.nparams = nparams; + } + + wblock->rec->ts.secs = (time_t) (ts / 1000000000); + wblock->rec->ts.nsecs = (int) (ts % 1000000000); + + block_read = bh->block_total_length - min_event_size; + + wblock->rec->rec_header.syscall_header.event_filelen = block_read; + + /* "Sysdig Event Block" read event data */ + if (!wtap_read_packet_bytes(fh, wblock->frame_buffer, + block_read, err, err_info)) + return FALSE; + + /* XXX Read comment? */ + + /* + * We return these to the caller in pcapng_read(). + */ + wblock->internal = FALSE; + + return TRUE; +} + +static gboolean +pcapng_read_systemd_journal_export_block(wtap *wth, FILE_T fh, pcapng_block_header_t *bh, pcapng_t *pn _U_, wtapng_block_t *wblock, int *err, gchar **err_info) +{ + guint32 entry_length; + guint64 rt_ts; + gboolean have_ts = FALSE; + + if (bh->block_total_length < MIN_SYSTEMD_JOURNAL_EXPORT_BLOCK_SIZE) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of a systemd journal export block is too small (< %u)", + bh->block_total_length, MIN_SYSTEMD_JOURNAL_EXPORT_BLOCK_SIZE); + return FALSE; + } + + entry_length = bh->block_total_length - MIN_BLOCK_SIZE; + + /* Includes padding bytes. */ + if (!wtap_read_packet_bytes(fh, wblock->frame_buffer, + entry_length, err, err_info)) { + return FALSE; + } + + /* + * We don't have memmem available everywhere, so we get to add space for + * a trailing \0 for strstr below. + */ + ws_buffer_assure_space(wblock->frame_buffer, entry_length+1); + + gchar *buf_ptr = (gchar *) ws_buffer_start_ptr(wblock->frame_buffer); + while (entry_length > 0 && buf_ptr[entry_length-1] == '\0') { + entry_length--; + } + + if (entry_length < MIN_SYSTEMD_JOURNAL_EXPORT_ENTRY_SIZE) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: entry length %u is too small (< %u)", + bh->block_total_length, MIN_SYSTEMD_JOURNAL_EXPORT_ENTRY_SIZE); + return FALSE; + } + + ws_debug("entry_length %u", entry_length); + + size_t rt_ts_len = strlen(SDJ__REALTIME_TIMESTAMP); + + buf_ptr[entry_length] = '\0'; + char *ts_pos = strstr(buf_ptr, SDJ__REALTIME_TIMESTAMP); + + if (!ts_pos) { + ws_debug("no timestamp"); + } else if (ts_pos+rt_ts_len >= (char *) buf_ptr+entry_length) { + ws_debug("timestamp past end of buffer"); + } else { + const char *ts_end; + have_ts = ws_strtou64(ts_pos+rt_ts_len, &ts_end, &rt_ts); + + if (!have_ts) { + ws_debug("invalid timestamp"); + } + } + + wblock->rec->rec_type = REC_TYPE_SYSTEMD_JOURNAL_EXPORT; + wblock->rec->rec_header.systemd_journal_export_header.record_len = entry_length; + wblock->rec->presence_flags = WTAP_HAS_CAP_LEN; + if (have_ts) { + wblock->rec->presence_flags |= WTAP_HAS_TS; + wblock->rec->tsprec = WTAP_TSPREC_USEC; + wblock->rec->ts.secs = (time_t) (rt_ts / 1000000); + wblock->rec->ts.nsecs = (rt_ts % 1000000) * 1000; + } + + /* + * We return these to the caller in pcapng_read(). + */ + wblock->internal = FALSE; + + if (wth->file_encap == WTAP_ENCAP_NONE) { + /* + * Nothing (most notably an IDB) has set a file encap at this point. + * Do so here. + * XXX Should we set WTAP_ENCAP_SYSTEMD_JOURNAL if appropriate? + */ + wth->file_encap = WTAP_ENCAP_PER_PACKET; + } + + return TRUE; +} + +static gboolean +pcapng_read_unknown_block(FILE_T fh, pcapng_block_header_t *bh, +#ifdef HAVE_PLUGINS + const section_info_t *section_info, +#else + const section_info_t *section_info _U_, +#endif + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + guint32 block_read; +#ifdef HAVE_PLUGINS + block_handler *handler; +#endif + + if (bh->block_total_length < MIN_BLOCK_SIZE) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u of an unknown block type is less than the minimum block size %u", + bh->block_total_length, MIN_BLOCK_SIZE); + return FALSE; + } + + block_read = bh->block_total_length - MIN_BLOCK_SIZE; + +#ifdef HAVE_PLUGINS + /* + * Do we have a handler for this block type? + */ + if (block_handlers != NULL && + (handler = (block_handler *)g_hash_table_lookup(block_handlers, + GUINT_TO_POINTER(bh->block_type))) != NULL) { + /* Yes - call it to read this block type. */ + if (!handler->reader(fh, block_read, section_info->byte_swapped, wblock, + err, err_info)) + return FALSE; + } else +#endif + { + /* No. Skip over this unknown block. */ + if (!wtap_read_bytes(fh, NULL, block_read, err, err_info)) { + return FALSE; + } + + /* + * We're skipping this, so we won't return these to the caller + * in pcapng_read(). + */ + wblock->internal = TRUE; + } + + return TRUE; +} + +static gboolean +pcapng_read_and_check_block_trailer(FILE_T fh, pcapng_block_header_t *bh, + section_info_t *section_info, + int *err, gchar **err_info) +{ + guint32 block_total_length; + + /* sanity check: first and second block lengths must match */ + if (!wtap_read_bytes(fh, &block_total_length, sizeof block_total_length, + err, err_info)) { + ws_debug("couldn't read second block length"); + return FALSE; + } + + if (section_info->byte_swapped) + block_total_length = GUINT32_SWAP_LE_BE(block_total_length); + + /* + * According to the pcapng spec, this should equal the block total + * length value at the beginning of the block, which MUST (in the + * IANA sense) be a multiple of 4. + * + * We round the value at the beginning of the block to a multiple + * of 4, so do so with this value as well. This *does* mean that + * the two values, if they're not both multiples of 4, can differ + * and this code won't detect that, but we're already not detecting + * non-multiple-of-4 total lengths. + */ + block_total_length = ROUND_TO_4BYTE(block_total_length); + + if (block_total_length != bh->block_total_length) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block lengths (first %u and second %u) don't match", + bh->block_total_length, block_total_length); + return FALSE; + } + return TRUE; +} + +static gboolean +pcapng_read_block(wtap *wth, FILE_T fh, pcapng_t *pn, + section_info_t *section_info, + section_info_t *new_section_info, + wtapng_block_t *wblock, + int *err, gchar **err_info) +{ + block_return_val ret; + pcapng_block_header_t bh; + + wblock->block = NULL; + + /* Try to read the (next) block header */ + if (!wtap_read_bytes_or_eof(fh, &bh, sizeof bh, err, err_info)) { + ws_debug("wtap_read_bytes_or_eof() failed, err = %d.", *err); + return FALSE; + } + + /* + * SHBs have to be treated differently from other blocks, because + * the byte order of the fields in the block can only be determined + * by looking at the byte-order magic number inside the block, not + * by using the byte order of the section to which it belongs, as + * it is the block that *defines* the byte order of the section to + * which it belongs. + */ + if (bh.block_type == BLOCK_TYPE_SHB) { + /* + * BLOCK_TYPE_SHB has the same value regardless of byte order, + * so we don't need to byte-swap it. + * + * We *might* need to byte-swap the total length, but we + * can't determine whether we do until we look inside the + * block and find the byte-order magic number, so we rely + * on pcapng_read_section_header_block() to do that and + * to swap the total length (as it needs to get the total + * length in the right byte order in order to read the + * entire block). + */ + wblock->type = bh.block_type; + + ws_debug("block_type BLOCK_TYPE_SHB (0x%08x)", bh.block_type); + + /* + * Fill in the section_info_t passed to us for use when + * there's a new SHB; don't overwrite the existing SHB, + * if there is one. + */ + ret = pcapng_read_section_header_block(fh, &bh, new_section_info, + wblock, err, err_info); + if (ret != PCAPNG_BLOCK_OK) { + return FALSE; + } + + /* + * This is the current section; use its byte order, not that + * of the section pointed to by section_info (which could be + * null). + */ + section_info = new_section_info; + } else { + /* + * Not an SHB. + */ + if (section_info->byte_swapped) { + bh.block_type = GUINT32_SWAP_LE_BE(bh.block_type); + bh.block_total_length = GUINT32_SWAP_LE_BE(bh.block_total_length); + } + + /* + * Add padding bytes to the block total length. + * (The "block total length" fields of some example files + * don't contain the packet data padding bytes!) + * + * For all block types currently defined in the pcapng + * specification, the portion of the block that precedes + * the options is, if necessary, padded to be a multiple + * of 4 octets, the header of an option is 4 octets long, + * and the value of an option is also padded to be a + * multiple of 4 octets, so the total length of a block + * is always a multiple of 4 octets. + * + * If you have defined a block where that is not true, you + * have violated the pcapng specification - hwere it says + * that "[The value fo the Block Total Length] MUST be a + * multiple of 4.", with MUST as described in BCP 14 (RFC 2119/ + * RFC 8174). + * + * Therefore, if adjusting the block total length causes the + * code to read your block type not to work, that's your + * problem. It's bad enough that some blocks were written + * out with the block total length not including the padding. + * (Please note that libpcap is less forgiving that we are; + * it reports an error if the block total length isn't a + * multiple of 4.) + */ + bh.block_total_length = ROUND_TO_4BYTE(bh.block_total_length); + + wblock->type = bh.block_type; + + ws_noisy("block_type 0x%08x", bh.block_type); + + /* Don't try to allocate memory for a huge number of options, as + that might fail and, even if it succeeds, it might not leave + any address space or memory+backing store for anything else. + + We do that by imposing a maximum block size of MAX_BLOCK_SIZE. */ + if (bh.block_total_length > MAX_BLOCK_SIZE) { + *err = WTAP_ERR_BAD_FILE; + *err_info = ws_strdup_printf("pcapng: total block length %u is too large (> %u)", + bh.block_total_length, MAX_BLOCK_SIZE); + return FALSE; + } + + /* + * ***DO NOT*** add any items to this table that are not + * standardized block types in the current pcapng spec at + * + * https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html + * + * All block types in this switch statement here must be + * listed there as standardized block types, ideally with + * a description. + */ + switch (bh.block_type) { + case(BLOCK_TYPE_IDB): + if (!pcapng_read_if_descr_block(wth, fh, &bh, section_info, wblock, err, err_info)) + return FALSE; + break; + case(BLOCK_TYPE_PB): + if (!pcapng_read_packet_block(fh, &bh, section_info, wblock, err, err_info, FALSE)) + return FALSE; + break; + case(BLOCK_TYPE_SPB): + if (!pcapng_read_simple_packet_block(fh, &bh, section_info, wblock, err, err_info)) + return FALSE; + break; + case(BLOCK_TYPE_EPB): + if (!pcapng_read_packet_block(fh, &bh, section_info, wblock, err, err_info, TRUE)) + return FALSE; + break; + case(BLOCK_TYPE_NRB): + if (!pcapng_read_name_resolution_block(fh, &bh, section_info, wblock, err, err_info)) + return FALSE; + break; + case(BLOCK_TYPE_ISB): + if (!pcapng_read_interface_statistics_block(fh, &bh, section_info, wblock, err, err_info)) + return FALSE; + break; + case(BLOCK_TYPE_DSB): + if (!pcapng_read_decryption_secrets_block(fh, &bh, section_info, wblock, err, err_info)) + return FALSE; + break; + case BLOCK_TYPE_SYSDIG_MI: + case BLOCK_TYPE_SYSDIG_PL_V1: + case BLOCK_TYPE_SYSDIG_FDL_V1: + case BLOCK_TYPE_SYSDIG_IL_V1: + case BLOCK_TYPE_SYSDIG_UL_V1: + case BLOCK_TYPE_SYSDIG_PL_V2: + case BLOCK_TYPE_SYSDIG_PL_V3: + case BLOCK_TYPE_SYSDIG_PL_V4: + case BLOCK_TYPE_SYSDIG_PL_V5: + case BLOCK_TYPE_SYSDIG_PL_V6: + case BLOCK_TYPE_SYSDIG_PL_V7: + case BLOCK_TYPE_SYSDIG_PL_V8: + case BLOCK_TYPE_SYSDIG_PL_V9: + case BLOCK_TYPE_SYSDIG_FDL_V2: + case BLOCK_TYPE_SYSDIG_IL_V2: + case BLOCK_TYPE_SYSDIG_UL_V2: + if (!pcapng_read_sysdig_meta_event_block(fh, &bh, wblock, err, err_info)) + return FALSE; + break; + case(BLOCK_TYPE_CB_COPY): + case(BLOCK_TYPE_CB_NO_COPY): + if (!pcapng_read_custom_block(fh, &bh, section_info, wblock, err, err_info)) + return FALSE; + break; + case(BLOCK_TYPE_SYSDIG_EVENT): + case(BLOCK_TYPE_SYSDIG_EVENT_V2): + case(BLOCK_TYPE_SYSDIG_EVENT_V2_LARGE): + /* case(BLOCK_TYPE_SYSDIG_EVF): */ + if (!pcapng_read_sysdig_event_block(fh, &bh, section_info, wblock, err, err_info)) + return FALSE; + break; + case(BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT): + if (!pcapng_read_systemd_journal_export_block(wth, fh, &bh, pn, wblock, err, err_info)) + return FALSE; + break; + default: + ws_debug("Unknown block_type: 0x%08x (block ignored), block total length %d", + bh.block_type, bh.block_total_length); + if (!pcapng_read_unknown_block(fh, &bh, section_info, wblock, err, err_info)) + return FALSE; + break; + } + } + + /* + * Read and check the block trailer. + */ + if (!pcapng_read_and_check_block_trailer(fh, &bh, section_info, err, err_info)) { + /* Not readable or not valid. */ + return FALSE; + } + return TRUE; +} + +/* Process an IDB that we've just read. The contents of wblock are copied as needed. */ +static void +pcapng_process_idb(wtap *wth, section_info_t *section_info, + wtapng_block_t *wblock) +{ + wtap_block_t int_data = wtap_block_create(WTAP_BLOCK_IF_ID_AND_INFO); + interface_info_t iface_info; + wtapng_if_descr_mandatory_t *if_descr_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(int_data), + *wblock_if_descr_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(wblock->block); + guint8 if_fcslen; + + wtap_block_copy(int_data, wblock->block); + + /* XXX if_tsoffset; opt 14 A 64 bits integer value that specifies an offset (in seconds)...*/ + /* Interface statistics */ + if_descr_mand->num_stat_entries = 0; + if_descr_mand->interface_statistics = NULL; + + wtap_add_idb(wth, int_data); + + iface_info.wtap_encap = wblock_if_descr_mand->wtap_encap; + iface_info.snap_len = wblock_if_descr_mand->snap_len; + iface_info.time_units_per_second = wblock_if_descr_mand->time_units_per_second; + iface_info.tsprecision = wblock_if_descr_mand->tsprecision; + + /* + * Did we get an FCS length option? + */ + if (wtap_block_get_uint8_option_value(wblock->block, OPT_IDB_FCSLEN, + &if_fcslen) == WTAP_OPTTYPE_SUCCESS) { + /* + * Yes. + */ + iface_info.fcslen = if_fcslen; + } else { + /* + * No. Mark the FCS length as unknown. + */ + iface_info.fcslen = -1; + } + + /* + * Did we get a time stamp offset option? + */ + if (wtap_block_get_int64_option_value(wblock->block, OPT_IDB_TSOFFSET, + &iface_info.tsoffset) == WTAP_OPTTYPE_SUCCESS) { + /* + * Yes. + * + * Remove the option, as the time stamps we provide will be + * absolute time stamps, with the offset added in, so it will + * appear as if there were no such option. + */ + wtap_block_remove_option(wblock->block, OPT_IDB_TSOFFSET); + } else { + /* + * No. Default to 0, meahing that time stamps in the file are + * absolute time stamps. + */ + iface_info.tsoffset = 0; + } + + g_array_append_val(section_info->interfaces, iface_info); +} + +/* Process an NRB that we have just read. */ +static void +pcapng_process_nrb(wtap *wth, wtapng_block_t *wblock) +{ + wtapng_process_nrb(wth, wblock->block); + + if (wth->nrbs == NULL) { + wth->nrbs = g_array_new(FALSE, FALSE, sizeof(wtap_block_t)); + } + /* Store NRB such that it can be saved by the dumper. */ + g_array_append_val(wth->nrbs, wblock->block); +} + +/* Process a DSB that we have just read. */ +static void +pcapng_process_dsb(wtap *wth, wtapng_block_t *wblock) +{ + wtapng_process_dsb(wth, wblock->block); + + /* Store DSB such that it can be saved by the dumper. */ + g_array_append_val(wth->dsbs, wblock->block); +} + +/* Process a Sysdig meta event block that we have just read. */ +static void +pcapng_process_sysdig_mev(wtap *wth, wtapng_block_t *wblock) +{ + // XXX add wtapng_process_sysdig_meb(wth, wblock->block); + + /* Store meta event such that it can be saved by the dumper. */ + g_array_append_val(wth->sysdig_meta_events, wblock->block); +} + +static void +pcapng_process_internal_block(wtap *wth, pcapng_t *pcapng, section_info_t *current_section, section_info_t new_section, wtapng_block_t *wblock, const gint64 *data_offset) +{ + wtap_block_t wtapng_if_descr; + wtap_block_t if_stats; + wtapng_if_stats_mandatory_t *if_stats_mand_block, *if_stats_mand; + wtapng_if_descr_mandatory_t *wtapng_if_descr_mand; + + switch (wblock->type) { + + case(BLOCK_TYPE_SHB): + ws_debug("another section header block"); + + /* + * Add this SHB to the table of SHBs. + */ + g_array_append_val(wth->shb_hdrs, wblock->block); + + /* + * Update the current section number, and add + * the updated section_info_t to the array of + * section_info_t's for this file. + */ + pcapng->current_section_number++; + new_section.interfaces = g_array_new(FALSE, FALSE, sizeof(interface_info_t)); + new_section.shb_off = *data_offset; + g_array_append_val(pcapng->sections, new_section); + break; + + case(BLOCK_TYPE_IDB): + /* A new interface */ + ws_debug("block type BLOCK_TYPE_IDB"); + pcapng_process_idb(wth, current_section, wblock); + wtap_block_unref(wblock->block); + break; + + case(BLOCK_TYPE_DSB): + /* Decryption secrets. */ + ws_debug("block type BLOCK_TYPE_DSB"); + pcapng_process_dsb(wth, wblock); + /* Do not free wblock->block, it is consumed by pcapng_process_dsb */ + break; + + case(BLOCK_TYPE_NRB): + /* More name resolution entries */ + ws_debug("block type BLOCK_TYPE_NRB"); + pcapng_process_nrb(wth, wblock); + /* Do not free wblock->block, it is consumed by pcapng_process_nrb */ + break; + + case(BLOCK_TYPE_ISB): + /* + * Another interface statistics report + * + * XXX - given that they're reports, we should be + * supplying them in read calls, and displaying them + * in the "packet" list, so you can see what the + * statistics were *at the time when the report was + * made*. + * + * The statistics from the *last* ISB could be displayed + * in the summary, but if there are packets after the + * last ISB, that could be misleading. + * + * If we only display them if that ISB has an isb_endtime + * option, which *should* only appear when capturing ended + * on that interface (so there should be no more packet + * blocks or ISBs for that interface after that point, + * that would be the best way of showing "summary" + * statistics. + */ + ws_debug("block type BLOCK_TYPE_ISB"); + if_stats_mand_block = (wtapng_if_stats_mandatory_t*)wtap_block_get_mandatory_data(wblock->block); + if (wth->interface_data->len <= if_stats_mand_block->interface_id) { + ws_debug("BLOCK_TYPE_ISB wblock.if_stats.interface_id %u >= number_of_interfaces", + if_stats_mand_block->interface_id); + } else { + /* Get the interface description */ + wtapng_if_descr = g_array_index(wth->interface_data, wtap_block_t, if_stats_mand_block->interface_id); + wtapng_if_descr_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(wtapng_if_descr); + if (wtapng_if_descr_mand->num_stat_entries == 0) { + /* First ISB found, no previous entry */ + ws_debug("block type BLOCK_TYPE_ISB. First ISB found, no previous entry"); + wtapng_if_descr_mand->interface_statistics = g_array_new(FALSE, FALSE, sizeof(wtap_block_t)); + } + + if_stats = wtap_block_create(WTAP_BLOCK_IF_STATISTICS); + if_stats_mand = (wtapng_if_stats_mandatory_t*)wtap_block_get_mandatory_data(if_stats); + if_stats_mand->interface_id = if_stats_mand_block->interface_id; + if_stats_mand->ts_high = if_stats_mand_block->ts_high; + if_stats_mand->ts_low = if_stats_mand_block->ts_low; + + wtap_block_copy(if_stats, wblock->block); + g_array_append_val(wtapng_if_descr_mand->interface_statistics, if_stats); + wtapng_if_descr_mand->num_stat_entries++; + } + wtap_block_unref(wblock->block); + break; + + case BLOCK_TYPE_SYSDIG_MI: + case BLOCK_TYPE_SYSDIG_PL_V1: + case BLOCK_TYPE_SYSDIG_FDL_V1: + case BLOCK_TYPE_SYSDIG_IL_V1: + case BLOCK_TYPE_SYSDIG_UL_V1: + case BLOCK_TYPE_SYSDIG_PL_V2: + case BLOCK_TYPE_SYSDIG_PL_V3: + case BLOCK_TYPE_SYSDIG_PL_V4: + case BLOCK_TYPE_SYSDIG_PL_V5: + case BLOCK_TYPE_SYSDIG_PL_V6: + case BLOCK_TYPE_SYSDIG_PL_V7: + case BLOCK_TYPE_SYSDIG_PL_V8: + case BLOCK_TYPE_SYSDIG_PL_V9: + case BLOCK_TYPE_SYSDIG_FDL_V2: + case BLOCK_TYPE_SYSDIG_IL_V2: + case BLOCK_TYPE_SYSDIG_UL_V2: + /* Decryption secrets. */ + ws_debug("block type Sysdig meta event"); + pcapng_process_sysdig_mev(wth, wblock); + /* Do not free wblock->block, it is consumed by pcapng_process_sysdig_meb */ + break; + + default: + /* XXX - improve handling of "unknown" blocks */ + ws_debug("Unknown block type 0x%08x", wblock->type); + break; + } +} + +/* classic wtap: open capture file */ +wtap_open_return_val +pcapng_open(wtap *wth, int *err, gchar **err_info) +{ + wtapng_block_t wblock; + pcapng_t *pcapng; + pcapng_block_header_t bh; + gint64 saved_offset; + section_info_t first_section, new_section, *current_section; + + ws_debug("opening file"); + /* + * Read first block. + * + * First, try to read the block header. + */ + if (!wtap_read_bytes_or_eof(wth->fh, &bh, sizeof bh, err, err_info)) { + ws_debug("wtap_read_bytes_or_eof() failed, err = %d.", *err); + if (*err == 0 || *err == WTAP_ERR_SHORT_READ) { + /* + * Short read or EOF. + * + * We're reading this as part of an open, so + * the file is too short to be a pcapng file. + */ + *err = 0; + g_free(*err_info); + *err_info = NULL; + return WTAP_OPEN_NOT_MINE; + } + return WTAP_OPEN_ERROR; + } + + /* + * If this is a pcapng file, the first block must be a + * Section Header Block. + */ + if (bh.block_type != BLOCK_TYPE_SHB) { + /* + * Not an SHB, so this isn't a pcapng file. + * + * XXX - check for damage from transferring a file + * between Windows and UN*X as text rather than + * binary data? + */ + ws_debug("first block type 0x%08x not SHB", bh.block_type); + return WTAP_OPEN_NOT_MINE; + } + + ws_debug("got an SHB"); + + /* + * Now try to read the block body, filling in the section_info_t + * for the first section. + */ + wblock.type = bh.block_type; + wblock.block = NULL; + /* we don't expect any packet blocks yet */ + wblock.frame_buffer = NULL; + wblock.rec = NULL; + + switch (pcapng_read_section_header_block(wth->fh, &bh, &first_section, + &wblock, err, err_info)) { + case PCAPNG_BLOCK_OK: + /* No problem */ + break; + + case PCAPNG_BLOCK_NOT_SHB: + /* This doesn't look like an SHB, so this isn't a pcapng file. */ + wtap_block_unref(wblock.block); + *err = 0; + g_free(*err_info); + *err_info = NULL; + return WTAP_OPEN_NOT_MINE; + + case PCAPNG_BLOCK_ERROR: + wtap_block_unref(wblock.block); + if (*err == WTAP_ERR_SHORT_READ) { + /* + * Short read. + * + * We're reading this as part of an open, so + * the file is too short to be a pcapng file. + */ + *err = 0; + g_free(*err_info); + *err_info = NULL; + return WTAP_OPEN_NOT_MINE; + } + /* An I/O error. */ + return WTAP_OPEN_ERROR; + } + + /* + * Read and check the block trailer. + */ + if (!pcapng_read_and_check_block_trailer(wth->fh, &bh, &first_section, err, err_info)) { + /* Not readable or not valid. */ + wtap_block_unref(wblock.block); + return WTAP_OPEN_ERROR; + } + + /* + * At this point, we've decided this is a pcapng file, not + * some other type of file, so we can't return WTAP_OPEN_NOT_MINE + * past this point. + * + * Copy the SHB that we just read to the first entry in the table of + * SHBs for this file. + */ + wtap_block_copy(g_array_index(wth->shb_hdrs, wtap_block_t, 0), wblock.block); + wtap_block_unref(wblock.block); + wblock.block = NULL; + + wth->file_encap = WTAP_ENCAP_NONE; + wth->snapshot_length = 0; + wth->file_tsprec = WTAP_TSPREC_UNKNOWN; + pcapng = g_new(pcapng_t, 1); + wth->priv = (void *)pcapng; + /* + * We're currently processing the first section; as this is written + * in C, that's section 0. :-) + */ + pcapng->current_section_number = 0; + + /* + * Create the array of interfaces for the first section. + */ + first_section.interfaces = g_array_new(FALSE, FALSE, sizeof(interface_info_t)); + + /* + * The first section is at the very beginning of the file. + */ + first_section.shb_off = 0; + + /* + * Allocate the sections table with space reserved for the first + * section, and add that section. + */ + pcapng->sections = g_array_sized_new(FALSE, FALSE, sizeof(section_info_t), 1); + g_array_append_val(pcapng->sections, first_section); + + wth->subtype_read = pcapng_read; + wth->subtype_seek_read = pcapng_seek_read; + wth->subtype_close = pcapng_close; + wth->file_type_subtype = pcapng_file_type_subtype; + + /* Always initialize the lists of Decryption Secret Blocks, Name + * Resolution Blocks, and Sysdig meta event blocks such that a + * wtap_dumper can refer to them right after opening the capture + * file. */ + wth->dsbs = g_array_new(FALSE, FALSE, sizeof(wtap_block_t)); + wth->nrbs = g_array_new(FALSE, FALSE, sizeof(wtap_block_t)); + wth->sysdig_meta_events = g_array_new(FALSE, FALSE, sizeof(wtap_block_t)); + + /* Most other capture types (such as pcap) support a single link-layer + * type, indicated in the header, and don't support WTAP_ENCAP_PER_PACKET. + * Most programs that write such capture files want to know the link-layer + * type when initially opening the destination file, and (unlike Wireshark) + * don't want to read the entire source file to find all the link-layer + * types before writing (particularly if reading from a pipe or FIFO.) + * + * In support of this, read all the internally-processed, non packet + * blocks that appear before the first packet block (EPB or SPB). + * + * Note that such programs will still have issues when trying to read + * a pcapng that has a new link-layer type in an IDB in the middle of + * the file, as they will discover in the middle that no, they can't + * successfully write the output file as desired. + */ + while (1) { + /* peek at next block */ + /* Try to read the (next) block header */ + saved_offset = file_tell(wth->fh); + if (!wtap_read_bytes_or_eof(wth->fh, &bh, sizeof bh, err, err_info)) { + if (*err == 0) { + /* EOF */ + ws_debug("No more blocks available..."); + break; + } + ws_debug("Check for more initial blocks, wtap_read_bytes_or_eof() failed, err = %d.", + *err); + return WTAP_OPEN_ERROR; + } + + /* go back to where we were */ + file_seek(wth->fh, saved_offset, SEEK_SET, err); + + /* + * Get a pointer to the current section's section_info_t. + */ + current_section = &g_array_index(pcapng->sections, section_info_t, + pcapng->current_section_number); + + if (current_section->byte_swapped) { + bh.block_type = GUINT32_SWAP_LE_BE(bh.block_type); + } + + ws_debug("Check for more initial internal blocks, block_type 0x%08x", + bh.block_type); + + if (!get_block_type_internal(bh.block_type)) { + break; /* Next block has to be returned in pcap_read */ + } + /* Note that some custom block types, unlike packet blocks, + * don't need to be preceded by an IDB and so theoretically + * we could skip past them here. However, then there's no good + * way to both later return those blocks in pcap_read() and + * ensure that we don't read and process the IDBs (and other + * internal block types) a second time. + * + * pcapng_read_systemd_journal_export_block() sets the file level + * link-layer type if it's still UNKNOWN. We could do the same here + * for it and possibly other types based on block type, even without + * reading them. + */ + if (!pcapng_read_block(wth, wth->fh, pcapng, current_section, + &new_section, &wblock, err, err_info)) { + wtap_block_unref(wblock.block); + if (*err == 0) { + ws_debug("No more initial blocks available..."); + break; + } else { + ws_debug("couldn't read block"); + return WTAP_OPEN_ERROR; + } + } + pcapng_process_internal_block(wth, pcapng, current_section, new_section, &wblock, &saved_offset); + ws_debug("Read IDB number_of_interfaces %u, wtap_encap %i", + wth->interface_data->len, wth->file_encap); + } + return WTAP_OPEN_MINE; +} + +/* classic wtap: read packet */ +static gboolean +pcapng_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err, + gchar **err_info, gint64 *data_offset) +{ + pcapng_t *pcapng = (pcapng_t *)wth->priv; + section_info_t *current_section, new_section; + wtapng_block_t wblock; + + wblock.frame_buffer = buf; + wblock.rec = rec; + + /* read next block */ + while (1) { + *data_offset = file_tell(wth->fh); + ws_noisy("data_offset is %" PRId64, *data_offset); + + /* + * Get the section_info_t for the current section. + */ + current_section = &g_array_index(pcapng->sections, section_info_t, + pcapng->current_section_number); + + /* + * Read the next block. + */ + if (!pcapng_read_block(wth, wth->fh, pcapng, current_section, + &new_section, &wblock, err, err_info)) { + ws_noisy("data_offset is finally %" PRId64, *data_offset); + ws_debug("couldn't read packet block"); + wtap_block_unref(wblock.block); + return FALSE; + } + + if (!wblock.internal) { + /* + * This is a block type we return to the caller to process. + */ + ws_noisy("rec_type %u", wblock.rec->rec_type); + break; + } + + /* + * This is a block type we process internally, rather than + * returning it for the caller to process. + */ + pcapng_process_internal_block(wth, pcapng, current_section, new_section, &wblock, data_offset); + } + + /*ws_debug("Read length: %u Packet length: %u", bytes_read, rec->rec_header.packet_header.caplen);*/ + ws_noisy("data_offset is finally %" PRId64, *data_offset); + + /* Provide the section number */ + rec->presence_flags |= WTAP_HAS_SECTION_NUMBER; + rec->section_number = pcapng->current_section_number; + + return TRUE; +} + +/* classic wtap: seek to file position and read packet */ +static gboolean +pcapng_seek_read(wtap *wth, gint64 seek_off, + wtap_rec *rec, Buffer *buf, + int *err, gchar **err_info) +{ + pcapng_t *pcapng = (pcapng_t *)wth->priv; + section_info_t *section_info, new_section; + wtapng_block_t wblock; + + + /* seek to the right file position */ + if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) < 0) { + return FALSE; /* Seek error */ + } + ws_noisy("reading at offset %" PRIu64, seek_off); + + /* + * Find the section_info_t for the section in which this block + * appears. + * + * First, make sure we have at least one section; if we don't, that's + * an internal error. + */ + ws_assert(pcapng->sections->len >= 1); + + /* + * Now scan backwards through the array to find the first section + * that begins at or before the offset of the block we're reading. + * + * Yes, that's O(n) in the number of blocks, but we're unlikely to + * have many blocks and pretty unlikely to have more than one. + */ + guint section_number = pcapng->sections->len - 1; + for (;;) { + section_info = &g_array_index(pcapng->sections, section_info_t, + section_number); + if (section_info->shb_off <= seek_off) + break; + + /* + * If that's section 0, something's wrong; that section should + * have an offset of 0. + */ + ws_assert(section_number != 0); + section_number--; + } + + wblock.frame_buffer = buf; + wblock.rec = rec; + + /* read the block */ + if (!pcapng_read_block(wth, wth->random_fh, pcapng, section_info, + &new_section, &wblock, err, err_info)) { + ws_debug("couldn't read packet block (err=%d).", *err); + wtap_block_unref(wblock.block); + return FALSE; + } + + /* block must not be one we process internally rather than supplying */ + if (wblock.internal) { + ws_debug("block type 0x%08x is not one we return", + wblock.type); + wtap_block_unref(wblock.block); + return FALSE; + } + + wtap_block_unref(wblock.block); + + /* Provide the section number */ + rec->presence_flags |= WTAP_HAS_SECTION_NUMBER; + rec->section_number = section_number; + + return TRUE; +} + +/* classic wtap: close capture file */ +static void +pcapng_close(wtap *wth) +{ + pcapng_t *pcapng = (pcapng_t *)wth->priv; + + ws_debug("closing file"); + + /* + * Free up the interfaces tables for all the sections. + */ + for (guint i = 0; i < pcapng->sections->len; i++) { + section_info_t *section_info = &g_array_index(pcapng->sections, + section_info_t, i); + g_array_free(section_info->interfaces, TRUE); + } + g_array_free(pcapng->sections, TRUE); +} + +typedef guint32 (*compute_option_size_func)(wtap_block_t, guint, wtap_opttype_e, wtap_optval_t*); + +typedef struct compute_options_size_t +{ + guint32 size; + compute_option_size_func compute_option_size; +} compute_options_size_t; + +static guint32 pcapng_compute_string_option_size(wtap_optval_t *optval) +{ + guint32 size = 0, pad; + + size = (guint32)strlen(optval->stringval) & 0xffff; + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + + size += pad; + + return size; +} + +#if 0 +static guint32 pcapng_compute_bytes_option_size(wtap_optval_t *optval) +{ + guint32 size = 0, pad; + + size = (guint32)g_bytes_get_size(optval->byteval) & 0xffff; + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + + size += pad; + + return size; +} +#endif + +static guint32 pcapng_compute_if_filter_option_size(wtap_optval_t *optval) +{ + if_filter_opt_t* filter = &optval->if_filterval; + guint32 size; + guint32 pad; + + if (filter->type == if_filter_pcap) { + size = (guint32)(strlen(filter->data.filter_str) + 1) & 0xffff; + } else if (filter->type == if_filter_bpf) { + size = (guint32)((filter->data.bpf_prog.bpf_prog_len * 8) + 1) & 0xffff; + } else { + /* Unknown type; don't write it */ + size = 0; + } + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + size += pad; + return size; +} + +static guint32 pcapng_compute_custom_option_size(wtap_optval_t *optval) +{ + size_t size, pad; + + /* PEN */ + size = sizeof(guint32); + switch (optval->custom_opt.pen) { + case PEN_NFLX: + /* NFLX type */ + size += sizeof(guint32); + size += optval->custom_opt.data.nflx_data.custom_data_len; + break; + default: + size += optval->custom_opt.data.generic_data.custom_data_len; + break; + } + if (size > 65535) { + size = 65535; + } + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + + size += pad; + + return (guint32)size; +} + +static guint32 pcapng_compute_packet_hash_option_size(wtap_optval_t *optval) +{ + packet_hash_opt_t* hash = &optval->packet_hash; + guint32 size; + guint32 pad; + + switch (hash->type) { + case OPT_HASH_CRC32: + size = 4; + break; + case OPT_HASH_MD5: + size = 16; + break; + case OPT_HASH_SHA1: + size = 20; + break; + case OPT_HASH_TOEPLITZ: + size = 4; + break; + default: + /* 2COMP and XOR size not defined in standard (yet) */ + size = hash->hash_bytes->len; + break; + } + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + size += pad; + return size; +} + +static guint32 pcapng_compute_packet_verdict_option_size(wtap_optval_t *optval) +{ + packet_verdict_opt_t* verdict = &optval->packet_verdictval; + guint32 size; + guint32 pad; + + switch (verdict->type) { + + case packet_verdict_hardware: + size = verdict->data.verdict_bytes->len; + break; + + case packet_verdict_linux_ebpf_tc: + size = 9; + break; + + case packet_verdict_linux_ebpf_xdp: + size = 9; + break; + + default: + size = 0; + break; + } + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + size += pad; + return size; +} + +static gboolean +compute_block_option_size(wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type, wtap_optval_t *optval, void *user_data) +{ + compute_options_size_t* options_size = (compute_options_size_t*)user_data; + guint32 size = 0; + + /* + * Process the option IDs that are the same for all block types here; + * call the block-type-specific compute_size function for others. + */ + switch(option_id) + { + case OPT_COMMENT: + size = pcapng_compute_string_option_size(optval); + break; + case OPT_CUSTOM_STR_COPY: + case OPT_CUSTOM_BIN_COPY: + size = pcapng_compute_custom_option_size(optval); + break; + case OPT_CUSTOM_STR_NO_COPY: + case OPT_CUSTOM_BIN_NO_COPY: + /* + * Do not count these, as they're not supposed to be copied to + * new files. + * + * XXX - what if we're writing out a file that's *not* based on + * another file, so that we're *not* copying it from that file? + */ + break; + default: + /* Block-type dependent; call the callback. */ + size = (*options_size->compute_option_size)(block, option_id, option_type, optval); + break; + } + + /* + * Are we writing this option? + */ + if (size != 0) { + /* + * Yes. Add the size of the option header to the size of the + * option data. + */ + options_size->size += 4; + + /* Now add the size of the option value. */ + options_size->size += size; + + /* Add optional padding to 32 bits */ + if ((size & 0x03) != 0) + { + options_size->size += 4 - (size & 0x03); + } + } + return TRUE; /* we always succeed */ +} + +static guint32 +compute_options_size(wtap_block_t block, compute_option_size_func compute_option_size) +{ + compute_options_size_t compute_options_size; + + /* + * Compute the total size of all the options in the block. + * This always suceeds, so we don't check the return value. + */ + compute_options_size.size = 0; + compute_options_size.compute_option_size = compute_option_size; + wtap_block_foreach_option(block, compute_block_option_size, &compute_options_size); + + /* Are we writing any options? */ + if (compute_options_size.size != 0) { + /* Yes, add the size of the End-of-options tag. */ + compute_options_size.size += 4; + } + return compute_options_size.size; +} + +static guint32 compute_shb_option_size(wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t* optval) +{ + guint32 size; + + switch(option_id) + { + case OPT_SHB_HARDWARE: + case OPT_SHB_OS: + case OPT_SHB_USERAPPL: + size = pcapng_compute_string_option_size(optval); + break; + default: + /* Unknown options - size by datatype? */ + size = 0; + break; + } + return size; +} + +typedef gboolean (*write_option_func)(wtap_dumper *, wtap_block_t, guint, wtap_opttype_e, wtap_optval_t*, int*); + +typedef struct write_options_t +{ + wtap_dumper *wdh; + int *err; + write_option_func write_option; +} +write_options_t; + +static gboolean pcapng_write_option_eofopt(wtap_dumper *wdh, int *err) +{ + struct pcapng_option_header option_hdr; + + /* Write end of options */ + option_hdr.type = OPT_EOFOPT; + option_hdr.value_length = 0; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + return TRUE; +} + +static gboolean pcapng_write_uint8_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + struct pcapng_option_header option_hdr; + const guint32 zero_pad = 0; + + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)1; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &optval->uint8val, 1, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &zero_pad, 3, err)) + return FALSE; + + return TRUE; +} + +static gboolean pcapng_write_uint32_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + struct pcapng_option_header option_hdr; + + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)4; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &optval->uint32val, 4, err)) + return FALSE; + + return TRUE; +} + +static gboolean pcapng_write_uint64_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + struct pcapng_option_header option_hdr; + + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)8; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &optval->uint64val, 8, err)) + return FALSE; + + return TRUE; +} + +static gboolean pcapng_write_timestamp_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + struct pcapng_option_header option_hdr; + guint32 high, low; + + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)8; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + high = (guint32)(optval->uint64val >> 32); + low = (guint32)(optval->uint64val >> 0); + if (!wtap_dump_file_write(wdh, &high, 4, err)) + return FALSE; + if (!wtap_dump_file_write(wdh, &low, 4, err)) + return FALSE; + + return TRUE; +} + +static gboolean pcapng_write_string_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + struct pcapng_option_header option_hdr; + size_t size = strlen(optval->stringval); + const guint32 zero_pad = 0; + guint32 pad; + + if (size == 0) + return TRUE; + if (size > 65535) { + /* + * Too big to fit in the option. + * Don't write anything. + * + * XXX - truncate it? Report an error? + */ + return TRUE; + } + + /* String options don't consider pad bytes part of the length */ + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)size; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, optval->stringval, size, err)) + return FALSE; + + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + + /* write padding (if any) */ + if (pad != 0) { + if (!wtap_dump_file_write(wdh, &zero_pad, pad, err)) + return FALSE; + } + + return TRUE; +} + +#if 0 +static gboolean pcapng_write_bytes_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + struct pcapng_option_header option_hdr; + size_t size = g_bytes_get_size(optval->byteval); + const guint32 zero_pad = 0; + guint32 pad; + + if (size == 0) + return TRUE; + if (size > 65535) { + /* + * Too big to fit in the option. + * Don't write anything. + * + * XXX - truncate it? Report an error? + */ + return TRUE; + } + + /* Bytes options don't consider pad bytes part of the length */ + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)size; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, optval->stringval, size, err)) + return FALSE; + + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + + /* write padding (if any) */ + if (pad != 0) { + if (!wtap_dump_file_write(wdh, &zero_pad, pad, err)) + return FALSE; + } + + return TRUE; +} + +static gboolean pcapng_write_ipv4_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + struct pcapng_option_header option_hdr; + + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)4; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &optval->ipv4val, 1, err)) + return FALSE; + + return TRUE; +} + +static gboolean pcapng_write_ipv6_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + struct pcapng_option_header option_hdr; + + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)IPv6_ADDR_SIZE; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &optval->ipv6val.bytes, IPv6_ADDR_SIZE, err)) + return FALSE; + + return TRUE; +} +#endif + +static gboolean pcapng_write_if_filter_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + if_filter_opt_t* filter = &optval->if_filterval; + guint32 size, pad; + guint8 filter_type; + size_t filter_data_len; + struct pcapng_option_header option_hdr; + const guint32 zero_pad = 0; + + switch (filter->type) { + + case if_filter_pcap: + filter_type = 0; /* pcap filter string */ + filter_data_len = strlen(filter->data.filter_str); + if (filter_data_len > 65534) { + /* + * Too big to fit in the option. + * Don't write anything. + * + * XXX - truncate it? Report an error? + */ + return TRUE; + } + break; + + case if_filter_bpf: + filter_type = 1; /* BPF filter program */ + filter_data_len = filter->data.bpf_prog.bpf_prog_len*8; + if (filter_data_len > 65528) { + /* + * Too big to fit in the option. (The filter length + * must be a multiple of 8, as that's the length + * of a BPF instruction.) Don't write anything. + * + * XXX - truncate it? Report an error? + */ + return TRUE; + } + break; + + default: + /* Unknown filter type; don't write anything. */ + return TRUE; + } + size = (guint32)(filter_data_len + 1); + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + + option_hdr.type = option_id; + option_hdr.value_length = size; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + /* Write the filter type */ + if (!wtap_dump_file_write(wdh, &filter_type, 1, err)) + return FALSE; + + switch (filter->type) { + + case if_filter_pcap: + /* Write the filter string */ + if (!wtap_dump_file_write(wdh, filter->data.filter_str, filter_data_len, err)) + return FALSE; + break; + + case if_filter_bpf: + if (!wtap_dump_file_write(wdh, filter->data.bpf_prog.bpf_prog, filter_data_len, err)) + return FALSE; + break; + + default: + ws_assert_not_reached(); + return TRUE; + } + + /* write padding (if any) */ + if (pad != 0) { + if (!wtap_dump_file_write(wdh, &zero_pad, pad, err)) + return FALSE; + } + return TRUE; +} + +static gboolean pcapng_write_custom_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + struct pcapng_option_header option_hdr; + gsize pad; + gsize size; + const guint32 zero_pad = 0; + guint32 pen, type; + gboolean use_little_endian; + + if ((option_id == OPT_CUSTOM_STR_NO_COPY) || + (option_id == OPT_CUSTOM_BIN_NO_COPY)) + return TRUE; + ws_debug("PEN %d", optval->custom_opt.pen); + switch (optval->custom_opt.pen) { + case PEN_NFLX: + size = sizeof(guint32) + sizeof(guint32) + optval->custom_opt.data.nflx_data.custom_data_len; + use_little_endian = optval->custom_opt.data.nflx_data.use_little_endian; + break; + default: + size = sizeof(guint32) + optval->custom_opt.data.generic_data.custom_data_len; + use_little_endian = false; + break; + } + ws_debug("use_little_endian %d", use_little_endian); + if (size > 65535) { + /* + * Too big to fit in the option. + * Don't write anything. + * + * XXX - truncate it? Report an error? + */ + return TRUE; + } + + /* write option header */ + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)size; + if (use_little_endian) { + option_hdr.type = GUINT16_TO_LE(option_hdr.type); + option_hdr.value_length = GUINT16_TO_LE(option_hdr.value_length); + } + if (!wtap_dump_file_write(wdh, &option_hdr, sizeof(struct pcapng_option_header), err)) + return FALSE; + + /* write PEN */ + pen = optval->custom_opt.pen; + if (use_little_endian) { + pen = GUINT32_TO_LE(pen); + } + if (!wtap_dump_file_write(wdh, &pen, sizeof(guint32), err)) + return FALSE; + + switch (optval->custom_opt.pen) { + case PEN_NFLX: + /* write NFLX type */ + type = GUINT32_TO_LE(optval->custom_opt.data.nflx_data.type); + ws_debug("type=%d", type); + if (!wtap_dump_file_write(wdh, &type, sizeof(guint32), err)) + return FALSE; + /* write custom data */ + if (!wtap_dump_file_write(wdh, optval->custom_opt.data.nflx_data.custom_data, optval->custom_opt.data.nflx_data.custom_data_len, err)) { + return FALSE; + } + break; + default: + /* write custom data */ + if (!wtap_dump_file_write(wdh, optval->custom_opt.data.generic_data.custom_data, optval->custom_opt.data.generic_data.custom_data_len, err)) { + return FALSE; + } + break; + } + + /* write padding (if any) */ + if (size % 4 != 0) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + if (pad != 0) { + if (!wtap_dump_file_write(wdh, &zero_pad, pad, err)) { + return FALSE; + } + } + ws_debug("Wrote custom option: type %u, length %u", option_hdr.type, option_hdr.value_length); + + return TRUE; +} + +static gboolean pcapng_write_packet_verdict_option(wtap_dumper *wdh, guint option_id, wtap_optval_t *optval, int *err) +{ + packet_verdict_opt_t* verdict = &optval->packet_verdictval; + struct pcapng_option_header option_hdr; + guint8 type; + size_t size; + const guint32 zero_pad = 0; + guint32 pad; + + switch (verdict->type) { + + case packet_verdict_hardware: + size = verdict->data.verdict_bytes->len; + if (size > 65535) { + /* + * Too big to fit in the option. + * Don't write anything. + * + * XXX - truncate it? Report an error? + */ + return TRUE; + } + option_hdr.type = option_id; + option_hdr.value_length = (guint16)size; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + type = packet_verdict_hardware; + if (!wtap_dump_file_write(wdh, &type, sizeof(guint8), err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, verdict->data.verdict_bytes->data, size, + err)) + return FALSE; + break; + + case packet_verdict_linux_ebpf_tc: + size = 9; + option_hdr.type = option_id; + option_hdr.value_length = (guint16)size; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + type = packet_verdict_linux_ebpf_tc; + if (!wtap_dump_file_write(wdh, &type, sizeof(guint8), err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &verdict->data.verdict_linux_ebpf_tc, + sizeof(guint64), err)) + return FALSE; + break; + + case packet_verdict_linux_ebpf_xdp: + size = 9; + option_hdr.type = option_id; + option_hdr.value_length = (guint16)size; + if (!wtap_dump_file_write(wdh, &option_hdr, 4, err)) + return FALSE; + + type = packet_verdict_linux_ebpf_xdp; + if (!wtap_dump_file_write(wdh, &type, sizeof(guint8), err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &verdict->data.verdict_linux_ebpf_xdp, + sizeof(guint64), err)) + return FALSE; + break; + + default: + /* Unknown - don't write it out. */ + return TRUE; + } + + /* write padding (if any) */ + if ((size % 4)) { + pad = 4 - (size % 4); + if (!wtap_dump_file_write(wdh, &zero_pad, pad, err)) + return FALSE; + } + return TRUE; +} + +static gboolean write_block_option(wtap_block_t block, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t *optval, void* user_data) +{ + write_options_t* options = (write_options_t*)user_data; + + /* + * Process the option IDs that are the same for all block types here; + * call the block-type-specific compute_size function for others. + */ + switch(option_id) + { + case OPT_COMMENT: + if (!pcapng_write_string_option(options->wdh, option_id, optval, options->err)) + return FALSE; + break; + case OPT_CUSTOM_STR_COPY: + case OPT_CUSTOM_BIN_COPY: + if (!pcapng_write_custom_option(options->wdh, option_id, optval, options->err)) + return FALSE; + break; + case OPT_CUSTOM_STR_NO_COPY: + case OPT_CUSTOM_BIN_NO_COPY: + /* + * Do not write these, as they're not supposed to be copied to + * new files. + * + * XXX - what if we're writing out a file that's *not* based on + * another file, so that we're *not* copying it from that file? + */ + break; + default: + /* Block-type dependent; call the callback, if we have one. */ + if (options->write_option != NULL && + !(*options->write_option)(options->wdh, block, option_id, option_type, optval, options->err)) + return FALSE; + break; + } + return TRUE; +} + +static gboolean +write_options(wtap_dumper *wdh, wtap_block_t block, write_option_func write_option, int *err) +{ + write_options_t options; + + options.wdh = wdh; + options.err = err; + options.write_option = write_option; + if (!wtap_block_foreach_option(block, write_block_option, &options)) + return FALSE; + + /* Write end of options */ + return pcapng_write_option_eofopt(wdh, err); +} + +static gboolean write_wtap_shb_option(wtap_dumper *wdh, wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t *optval, int *err) +{ + switch(option_id) + { + case OPT_SHB_HARDWARE: + case OPT_SHB_OS: + case OPT_SHB_USERAPPL: + if (!pcapng_write_string_option(wdh, option_id, optval, err)) + return FALSE; + break; + default: + /* Unknown options - write by datatype? */ + break; + } + return TRUE; /* success */ +} + +/* Write a section header block. + * If we don't have a section block header already, create a default + * one with no options. + */ +static gboolean +pcapng_write_section_header_block(wtap_dumper *wdh, int *err) +{ + pcapng_block_header_t bh; + pcapng_section_header_block_t shb; + guint32 options_size; + wtap_block_t wdh_shb = NULL; + + if (wdh->shb_hdrs && (wdh->shb_hdrs->len > 0)) { + wdh_shb = g_array_index(wdh->shb_hdrs, wtap_block_t, 0); + } + + bh.block_total_length = (guint32)(sizeof(bh) + sizeof(shb) + 4); + options_size = 0; + if (wdh_shb) { + ws_debug("Have shb_hdr"); + + /* Compute size of all the options */ + options_size = compute_options_size(wdh_shb, compute_shb_option_size); + + bh.block_total_length += options_size; + } + + ws_debug("Total len %u", bh.block_total_length); + + /* write block header */ + bh.block_type = BLOCK_TYPE_SHB; + + if (!wtap_dump_file_write(wdh, &bh, sizeof bh, err)) + return FALSE; + + /* write block fixed content */ + shb.magic = 0x1A2B3C4D; + shb.version_major = 1; + shb.version_minor = 0; + if (wdh_shb) { + wtapng_section_mandatory_t* section_data = (wtapng_section_mandatory_t*)wtap_block_get_mandatory_data(wdh_shb); + shb.section_length = section_data->section_length; + } else { + shb.section_length = -1; + } + + if (!wtap_dump_file_write(wdh, &shb, sizeof shb, err)) + return FALSE; + + if (wdh_shb) { + /* Write options, if we have any */ + if (options_size != 0) { + if (!write_options(wdh, wdh_shb, write_wtap_shb_option, err)) + return FALSE; + } + } + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) + return FALSE; + + return TRUE; +} + +/* options defined in Section 2.5 (Options) + * Name Code Length Description + * opt_comment 1 variable A UTF-8 string containing a comment that is associated to the current block. + * + * Enhanced Packet Block options + * epb_flags 2 4 A flags word containing link-layer information. A complete specification of + * the allowed flags can be found in Appendix A (Packet Block Flags Word). + * epb_hash 3 variable This option contains a hash of the packet. The first byte specifies the hashing algorithm, + * while the following bytes contain the actual hash, whose size depends on the hashing algorithm, + * and hence from the value in the first bit. The hashing algorithm can be: 2s complement + * (algorithm byte = 0, size=XXX), XOR (algorithm byte = 1, size=XXX), CRC32 (algorithm byte = 2, size = 4), + * MD-5 (algorithm byte = 3, size=XXX), SHA-1 (algorithm byte = 4, size=XXX). + * The hash covers only the packet, not the header added by the capture driver: + * this gives the possibility to calculate it inside the network card. + * The hash allows easier comparison/merging of different capture files, and reliable data transfer between the + * data acquisition system and the capture library. + * epb_dropcount 4 8 A 64bit integer value specifying the number of packets lost (by the interface and the operating system) + * between this packet and the preceding one. + * epb_packetid 5 8 The epb_packetid option is a 64-bit unsigned integer that + * uniquely identifies the packet. If the same packet is seen + * by multiple interfaces and there is a way for the capture + * application to correlate them, the same epb_packetid value + * must be used. An example could be a router that captures + * packets on all its interfaces in both directions. When a + * packet hits interface A on ingress, an EPB entry gets + * created, TTL gets decremented, and right before it egresses + * on interface B another EPB entry gets created in the trace + * file. In this case, two packets are in the capture file, + * which are not identical but the epb_packetid can be used to + * correlate them. + * epb_queue 6 4 The epb_queue option is a 32-bit unsigned integer that + * identifies on which queue of the interface the specific + * packet was received. + * epb_verdict 7 variable The epb_verdict option stores a verdict of the packet. The + * verdict indicates what would be done with the packet after + * processing it. For example, a firewall could drop the + * packet. This verdict can be set by various components, i.e. + * Hardware, Linux's eBPF TC or XDP framework, etc. etc. The + * first octet specifies the verdict type, while the following + * octets contain the actual verdict data, whose size depends on + * the verdict type, and hence from the value in the first + * octet. The verdict type can be: Hardware (type octet = 0, + * size = variable), Linux_eBPF_TC (type octet = 1, size = 8 + * (64-bit unsigned integer), value = TC_ACT_* as defined in the + * Linux pck_cls.h include), Linux_eBPF_XDP (type octet = 2, + * size = 8 (64-bit unsigned integer), value = xdp_action as + * defined in the Linux pbf.h include). + * opt_endofopt 0 0 It delimits the end of the optional fields. This block cannot be repeated within a given list of options. + */ +static guint32 +compute_epb_option_size(wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t* optval) +{ + guint32 size; + + switch(option_id) + { + case OPT_EPB_FLAGS: + size = 4; + break; + case OPT_EPB_DROPCOUNT: + size = 8; + break; + case OPT_EPB_PACKETID: + size = 8; + break; + case OPT_EPB_QUEUE: + size = 4; + break; + case OPT_EPB_VERDICT: + size = pcapng_compute_packet_verdict_option_size(optval); + break; + case OPT_EPB_HASH: + size = pcapng_compute_packet_hash_option_size(optval); + break; + default: + /* Unknown options - size by datatype? */ + size = 0; + break; + } + return size; +} + +static gboolean write_wtap_epb_option(wtap_dumper *wdh, wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t *optval, int *err) +{ + switch(option_id) + { + case OPT_PKT_FLAGS: + if (!pcapng_write_uint32_option(wdh, OPT_EPB_FLAGS, optval, err)) + return FALSE; + break; + case OPT_PKT_DROPCOUNT: + if (!pcapng_write_uint64_option(wdh, OPT_EPB_DROPCOUNT, optval, err)) + return FALSE; + break; + case OPT_PKT_PACKETID: + if (!pcapng_write_uint64_option(wdh, OPT_EPB_PACKETID, optval, err)) + return FALSE; + break; + case OPT_PKT_QUEUE: + if (!pcapng_write_uint32_option(wdh, OPT_EPB_QUEUE, optval, err)) + return FALSE; + break; + case OPT_PKT_VERDICT: + if (!pcapng_write_packet_verdict_option(wdh, OPT_EPB_QUEUE, optval, + err)) + break; + default: + /* Unknown options - write by datatype? */ + break; + } + return TRUE; /* success */ +} + +static gboolean +pcapng_write_enhanced_packet_block(wtap_dumper *wdh, const wtap_rec *rec, + const guint8 *pd, int *err, gchar **err_info) +{ + const union wtap_pseudo_header *pseudo_header = &rec->rec_header.packet_header.pseudo_header; + pcapng_block_header_t bh; + pcapng_enhanced_packet_block_t epb; + guint32 options_size = 0; + guint64 ts; + const guint32 zero_pad = 0; + guint32 pad_len; + guint32 phdr_len; + guint32 options_total_length = 0; + wtap_block_t int_data; + wtapng_if_descr_mandatory_t *int_data_mand; + + /* Don't write anything we're not willing to read. */ + if (rec->rec_header.packet_header.caplen > wtap_max_snaplen_for_encap(wdh->file_encap)) { + *err = WTAP_ERR_PACKET_TOO_LARGE; + return FALSE; + } + + phdr_len = (guint32)pcap_get_phdr_size(rec->rec_header.packet_header.pkt_encap, pseudo_header); + if ((phdr_len + rec->rec_header.packet_header.caplen) % 4) { + pad_len = 4 - ((phdr_len + rec->rec_header.packet_header.caplen) % 4); + } else { + pad_len = 0; + } + + if (rec->block != NULL) { + /* Compute size of all the options */ + options_size = compute_options_size(rec->block, compute_epb_option_size); + } + + /* + * Check the interface ID. Do this before writing the header, + * in case we need to add a new IDB. + */ + if (rec->presence_flags & WTAP_HAS_INTERFACE_ID) + epb.interface_id = rec->rec_header.packet_header.interface_id; + else { + /* + * The source isn't sending us IDBs. See if we already have a + * matching interface, and use it if so. + */ + for (epb.interface_id = 0; epb.interface_id < wdh->interface_data->len; ++epb.interface_id) { + int_data = g_array_index(wdh->interface_data, wtap_block_t, + epb.interface_id); + int_data_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(int_data); + if (int_data_mand->wtap_encap == rec->rec_header.packet_header.pkt_encap) { + if (int_data_mand->tsprecision == rec->tsprec || (!(rec->presence_flags & WTAP_HAS_TS))) { + break; + } + } + } + if (epb.interface_id == wdh->interface_data->len) { + /* + * We don't have a matching IDB. Generate a new one + * and write it to the file. + */ + int_data = wtap_rec_generate_idb(rec); + g_array_append_val(wdh->interface_data, int_data); + if (!pcapng_write_if_descr_block(wdh, int_data, err)) { + return FALSE; + } + } + } + if (epb.interface_id >= wdh->interface_data->len) { + /* + * Our caller is doing something bad. + */ + *err = WTAP_ERR_INTERNAL; + *err_info = ws_strdup_printf("pcapng: epb.interface_id (%u) >= wdh->interface_data->len (%u)", + epb.interface_id, wdh->interface_data->len); + return FALSE; + } + int_data = g_array_index(wdh->interface_data, wtap_block_t, + epb.interface_id); + int_data_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(int_data); + if (int_data_mand->wtap_encap != rec->rec_header.packet_header.pkt_encap) { + /* + * Our caller is doing something bad. + */ + *err = WTAP_ERR_INTERNAL; + *err_info = ws_strdup_printf("pcapng: interface %u encap %d != packet encap %d", + epb.interface_id, + int_data_mand->wtap_encap, + rec->rec_header.packet_header.pkt_encap); + return FALSE; + } + + /* write (enhanced) packet block header */ + bh.block_type = BLOCK_TYPE_EPB; + bh.block_total_length = (guint32)sizeof(bh) + (guint32)sizeof(epb) + phdr_len + rec->rec_header.packet_header.caplen + pad_len + options_total_length + options_size + 4; + + if (!wtap_dump_file_write(wdh, &bh, sizeof bh, err)) + return FALSE; + + /* write block fixed content */ + /* Calculate the time stamp as a 64-bit integer. */ + ts = ((guint64)rec->ts.secs) * int_data_mand->time_units_per_second + + (((guint64)rec->ts.nsecs) * int_data_mand->time_units_per_second) / 1000000000; + /* + * Split the 64-bit timestamp into two 32-bit pieces, using + * the time stamp resolution for the interface. + */ + epb.timestamp_high = (guint32)(ts >> 32); + epb.timestamp_low = (guint32)ts; + epb.captured_len = rec->rec_header.packet_header.caplen + phdr_len; + epb.packet_len = rec->rec_header.packet_header.len + phdr_len; + + if (!wtap_dump_file_write(wdh, &epb, sizeof epb, err)) + return FALSE; + + /* write pseudo header */ + if (!pcap_write_phdr(wdh, rec->rec_header.packet_header.pkt_encap, pseudo_header, err)) { + return FALSE; + } + + /* write packet data */ + if (!wtap_dump_file_write(wdh, pd, rec->rec_header.packet_header.caplen, err)) + return FALSE; + + /* write padding (if any) */ + if (pad_len != 0) { + if (!wtap_dump_file_write(wdh, &zero_pad, pad_len, err)) + return FALSE; + } + + /* Write options, if we have any */ + if (options_size != 0) { + if (!write_options(wdh, rec->block, write_wtap_epb_option, err)) + return FALSE; + } + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) + return FALSE; + + return TRUE; +} + +static gboolean +pcapng_write_sysdig_event_block(wtap_dumper *wdh, const wtap_rec *rec, + const guint8 *pd, int *err) +{ + pcapng_block_header_t bh; + const guint32 zero_pad = 0; + guint32 pad_len; +#if 0 + gboolean have_options = FALSE; + struct pcapng_option option_hdr; + guint32 comment_len = 0, comment_pad_len = 0; +#endif + guint32 options_total_length = 0; + guint16 cpu_id; + guint64 hdr_ts; + guint64 ts; + guint64 thread_id; + guint32 event_len; + guint16 event_type; + + /* Don't write anything we're not willing to read. */ + if (rec->rec_header.syscall_header.event_filelen > WTAP_MAX_PACKET_SIZE_STANDARD) { + *err = WTAP_ERR_PACKET_TOO_LARGE; + return FALSE; + } + + if (rec->rec_header.syscall_header.event_filelen % 4) { + pad_len = 4 - (rec->rec_header.syscall_header.event_filelen % 4); + } else { + pad_len = 0; + } + +#if 0 + /* Check if we should write comment option */ + if (rec->opt_comment) { + have_options = TRUE; + comment_len = (guint32)strlen(rec->opt_comment) & 0xffff; + if ((comment_len % 4)) { + comment_pad_len = 4 - (comment_len % 4); + } else { + comment_pad_len = 0; + } + options_total_length = options_total_length + comment_len + comment_pad_len + 4 /* comment options tag */ ; + } + if (have_options) { + /* End-of options tag */ + options_total_length += 4; + } +#endif + + /* write sysdig event block header */ + bh.block_type = BLOCK_TYPE_SYSDIG_EVENT; + bh.block_total_length = (guint32)sizeof(bh) + SYSDIG_EVENT_HEADER_SIZE + rec->rec_header.syscall_header.event_filelen + pad_len + options_total_length + 4; + + if (!wtap_dump_file_write(wdh, &bh, sizeof bh, err)) + return FALSE; + + /* Sysdig is always LE? */ + cpu_id = GUINT16_TO_LE(rec->rec_header.syscall_header.cpu_id); + hdr_ts = (((guint64)rec->ts.secs) * 1000000000) + rec->ts.nsecs; + ts = GUINT64_TO_LE(hdr_ts); + thread_id = GUINT64_TO_LE(rec->rec_header.syscall_header.thread_id); + event_len = GUINT32_TO_LE(rec->rec_header.syscall_header.event_len); + event_type = GUINT16_TO_LE(rec->rec_header.syscall_header.event_type); + + if (!wtap_dump_file_write(wdh, &cpu_id, sizeof cpu_id, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &ts, sizeof ts, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &thread_id, sizeof thread_id, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &event_len, sizeof event_len, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, &event_type, sizeof event_type, err)) + return FALSE; + + /* write event data */ + if (!wtap_dump_file_write(wdh, pd, rec->rec_header.syscall_header.event_filelen, err)) + return FALSE; + + /* write padding (if any) */ + if (pad_len != 0) { + if (!wtap_dump_file_write(wdh, &zero_pad, pad_len, err)) + return FALSE; + } + + /* XXX Write comment? */ + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) + return FALSE; + + return TRUE; + +} + +static gboolean +pcapng_write_systemd_journal_export_block(wtap_dumper *wdh, const wtap_rec *rec, + const guint8 *pd, int *err) +{ + pcapng_block_header_t bh; + const guint32 zero_pad = 0; + guint32 pad_len; + + /* Don't write anything we're not willing to read. */ + if (rec->rec_header.systemd_journal_export_header.record_len > WTAP_MAX_PACKET_SIZE_STANDARD) { + *err = WTAP_ERR_PACKET_TOO_LARGE; + return FALSE; + } + + if (rec->rec_header.systemd_journal_export_header.record_len % 4) { + pad_len = 4 - (rec->rec_header.systemd_journal_export_header.record_len % 4); + } else { + pad_len = 0; + } + + /* write systemd journal export block header */ + bh.block_type = BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT; + bh.block_total_length = (guint32)sizeof(bh) + rec->rec_header.systemd_journal_export_header.record_len + pad_len + 4; + + ws_debug("writing %u bytes, %u padded", + rec->rec_header.systemd_journal_export_header.record_len, + bh.block_total_length); + + if (!wtap_dump_file_write(wdh, &bh, sizeof bh, err)) + return FALSE; + + /* write entry data */ + if (!wtap_dump_file_write(wdh, pd, rec->rec_header.systemd_journal_export_header.record_len, err)) + return FALSE; + + /* write padding (if any) */ + if (pad_len != 0) { + if (!wtap_dump_file_write(wdh, &zero_pad, pad_len, err)) + return FALSE; + } + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) + return FALSE; + + return TRUE; + +} + +static gboolean +pcapng_write_custom_block(wtap_dumper *wdh, const wtap_rec *rec, + const guint8 *pd, int *err) +{ + pcapng_block_header_t bh; + pcapng_custom_block_t cb; + const guint32 zero_pad = 0; + guint32 pad_len; + + /* Don't write anything we are not supposed to. */ + if (!rec->rec_header.custom_block_header.copy_allowed) { + return TRUE; + } + + /* Don't write anything we're not willing to read. */ + if (rec->rec_header.custom_block_header.length > WTAP_MAX_PACKET_SIZE_STANDARD) { + *err = WTAP_ERR_PACKET_TOO_LARGE; + return FALSE; + } + + if (rec->rec_header.custom_block_header.length % 4) { + pad_len = 4 - (rec->rec_header.custom_block_header.length % 4); + } else { + pad_len = 0; + } + + /* write block header */ + bh.block_type = BLOCK_TYPE_CB_COPY; + bh.block_total_length = (guint32)sizeof(bh) + (guint32)sizeof(cb) + rec->rec_header.custom_block_header.length + pad_len + 4; + ws_debug("writing %u bytes, %u padded, PEN %u", + rec->rec_header.custom_block_header.length, + bh.block_total_length, rec->rec_header.custom_block_header.pen); + if (!wtap_dump_file_write(wdh, &bh, sizeof bh, err)) { + return FALSE; + } + + /* write custom block header */ + cb.pen = rec->rec_header.custom_block_header.pen; + if (!wtap_dump_file_write(wdh, &cb, sizeof cb, err)) { + return FALSE; + } + ws_debug("wrote PEN = %u", cb.pen); + + /* write custom data */ + if (!wtap_dump_file_write(wdh, pd, rec->rec_header.custom_block_header.length, err)) { + return FALSE; + } + + /* write padding (if any) */ + if (pad_len > 0) { + if (!wtap_dump_file_write(wdh, &zero_pad, pad_len, err)) { + return FALSE; + } + } + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) { + return FALSE; + } + + return TRUE; +} + +static gboolean +pcapng_write_bblog_block(wtap_dumper *wdh, const wtap_rec *rec, + const guint8 *pd _U_, int *err) +{ + pcapng_block_header_t bh; + guint32 options_size = 0; + guint32 pen, skipped, type; + + /* Compute size of all the options */ + options_size = compute_options_size(rec->block, compute_epb_option_size); + + /* write block header */ + bh.block_type = BLOCK_TYPE_CB_COPY; + bh.block_total_length = (guint32)(sizeof(bh) + sizeof(guint32) + sizeof(guint32) + options_size + 4); + if (rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.type == BBLOG_TYPE_SKIPPED_BLOCK) { + bh.block_total_length += (guint32)sizeof(guint32); + } + ws_debug("writing %u bytes, type %u", + bh.block_total_length, rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.type); + if (!wtap_dump_file_write(wdh, &bh, sizeof(bh), err)) { + return FALSE; + } + + /* write PEN */ + pen = PEN_NFLX; + if (!wtap_dump_file_write(wdh, &pen, sizeof(guint32), err)) { + return FALSE; + } + ws_debug("wrote PEN = %u", pen); + + /* write type */ + type = GUINT32_TO_LE(rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.type); + if (!wtap_dump_file_write(wdh, &type, sizeof(guint32), err)) { + return FALSE; + } + ws_debug("wrote type = %u", rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.type); + + if (rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.type == BBLOG_TYPE_SKIPPED_BLOCK) { + skipped = GUINT32_TO_LE(rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.skipped); + if (!wtap_dump_file_write(wdh, &skipped, sizeof(guint32), err)) { + return FALSE; + } + ws_debug("wrote skipped = %u", rec->rec_header.custom_block_header.custom_data_header.nflx_custom_data_header.skipped); + } + + /* Write options, if we have any */ + if (options_size != 0) { + /* + * This block type supports only comments and custom options, + * so it doesn't need a callback. + */ + if (!write_options(wdh, rec->block, NULL, err)) + return FALSE; + } + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) { + return FALSE; + } + + return TRUE; +} + +static gboolean +pcapng_write_decryption_secrets_block(wtap_dumper *wdh, wtap_block_t sdata, int *err) +{ + pcapng_block_header_t bh; + pcapng_decryption_secrets_block_t dsb; + wtapng_dsb_mandatory_t *mand_data = (wtapng_dsb_mandatory_t *)wtap_block_get_mandatory_data(sdata); + guint pad_len = (4 - (mand_data->secrets_len & 3)) & 3; + + /* write block header */ + bh.block_type = BLOCK_TYPE_DSB; + bh.block_total_length = MIN_DSB_SIZE + mand_data->secrets_len + pad_len; + ws_debug("Total len %u", bh.block_total_length); + + if (!wtap_dump_file_write(wdh, &bh, sizeof bh, err)) + return FALSE; + + /* write block fixed content */ + dsb.secrets_type = mand_data->secrets_type; + dsb.secrets_len = mand_data->secrets_len; + if (!wtap_dump_file_write(wdh, &dsb, sizeof dsb, err)) + return FALSE; + + if (!wtap_dump_file_write(wdh, mand_data->secrets_data, mand_data->secrets_len, err)) + return FALSE; + if (pad_len) { + const guint32 zero_pad = 0; + if (!wtap_dump_file_write(wdh, &zero_pad, pad_len, err)) + return FALSE; + } + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) + return FALSE; + + return TRUE; +} + +static bool +pcapng_write_sysdig_meta_event_block(wtap_dumper *wdh, wtap_block_t mev_data, int *err) +{ + pcapng_block_header_t bh; + wtapng_sysdig_mev_mandatory_t *mand_data = (wtapng_sysdig_mev_mandatory_t *)wtap_block_get_mandatory_data(mev_data); + unsigned pad_len = (4 - (mand_data->mev_data_len & 3)) & 3; + + /* write block header */ + bh.block_type = mand_data->mev_type; + bh.block_total_length = MIN_BLOCK_SIZE + mand_data->mev_data_len + pad_len; + ws_debug("Sysdig mev total len %u", bh.block_total_length); + + if (!wtap_dump_file_write(wdh, &bh, sizeof bh, err)) + return false; + + /* write block fixed content */ + if (!wtap_dump_file_write(wdh, mand_data->mev_data, mand_data->mev_data_len, err)) + return false; + + if (pad_len) { + const uint32_t zero_pad = 0; + if (!wtap_dump_file_write(wdh, &zero_pad, pad_len, err)) + return false; + } + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) + return false; + + return true; +} + +/* + * libpcap's maximum pcapng block size is currently 16MB. + * + * The maximum pcapng block size in macOS's private pcapng reading code + * is 1MB. (Yes, this means that a program using the standard pcap + * code to read pcapng files can handle bigger blocks than can programs + * using the private code, such as Apple's tcpdump, can handle.) + * + * The pcapng reading code here can handle NRBs of arbitrary size (less + * than 4GB, obviously), as they read each NRB record independently, + * rather than reading the entire block into memory. + * + * So, for now, we set the maximum NRB block size we write as 1 MB. + * + * (Yes, for the benefit of the fussy, "MB" is really "MiB".) + */ + +#define NRES_BLOCK_MAX_SIZE (1024*1024) + +static guint32 +compute_nrb_option_size(wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t* optval) +{ + guint32 size; + + switch(option_id) + { + case OPT_NS_DNSNAME: + size = pcapng_compute_string_option_size(optval); + break; + case OPT_NS_DNSIP4ADDR: + size = 4; + break; + case OPT_NS_DNSIP6ADDR: + size = 16; + break; + default: + /* Unknown options - size by datatype? */ + size = 0; + break; + } + return size; +} + +static gboolean +put_nrb_option(wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t* optval, void* user_data) +{ + guint8 **opt_ptrp = (guint8 **)user_data; + guint32 size = 0; + struct pcapng_option_header option_hdr; + guint32 pad; + + switch(option_id) + { + case OPT_COMMENT: + case OPT_NS_DNSNAME: + /* String options don't consider pad bytes part of the length */ + size = (guint32)strlen(optval->stringval) & 0xffff; + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)size; + memcpy(*opt_ptrp, &option_hdr, 4); + *opt_ptrp += 4; + + memcpy(*opt_ptrp, optval->stringval, size); + *opt_ptrp += size; + + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + + /* put padding (if any) */ + if (pad != 0) { + memset(*opt_ptrp, 0, pad); + *opt_ptrp += pad; + } + break; + case OPT_CUSTOM_STR_COPY: + case OPT_CUSTOM_BIN_COPY: + /* Custom options don't consider pad bytes part of the length */ + size = (guint32)(optval->custom_opt.data.generic_data.custom_data_len + sizeof(guint32)) & 0xffff; + option_hdr.type = (guint16)option_id; + option_hdr.value_length = (guint16)size; + memcpy(*opt_ptrp, &option_hdr, 4); + *opt_ptrp += 4; + + memcpy(*opt_ptrp, &optval->custom_opt.pen, sizeof(guint32)); + *opt_ptrp += sizeof(guint32); + + memcpy(*opt_ptrp, optval->custom_opt.data.generic_data.custom_data, optval->custom_opt.data.generic_data.custom_data_len); + *opt_ptrp += optval->custom_opt.data.generic_data.custom_data_len; + + if ((size % 4)) { + pad = 4 - (size % 4); + } else { + pad = 0; + } + + /* put padding (if any) */ + if (pad != 0) { + memset(*opt_ptrp, 0, pad); + *opt_ptrp += pad; + } + break; + case OPT_NS_DNSIP4ADDR: + option_hdr.type = (guint16)option_id; + option_hdr.value_length = 4; + memcpy(*opt_ptrp, &option_hdr, 4); + *opt_ptrp += 4; + + memcpy(*opt_ptrp, &optval->ipv4val, 4); + *opt_ptrp += 4; + break; + case OPT_NS_DNSIP6ADDR: + option_hdr.type = (guint16)option_id; + option_hdr.value_length = 16; + memcpy(*opt_ptrp, &option_hdr, 4); + *opt_ptrp += 4; + + memcpy(*opt_ptrp, &optval->ipv6val, 16); + *opt_ptrp += 16; + break; + default: + /* Unknown options - size by datatype? */ + break; + } + return TRUE; /* we always succeed */ +} + +static void +put_nrb_options(wtap_dumper *wdh _U_, wtap_block_t nrb, guint8 *opt_ptr) +{ + struct pcapng_option option_hdr; + + wtap_block_foreach_option(nrb, put_nrb_option, &opt_ptr); + + /* Put end of options */ + option_hdr.type = OPT_EOFOPT; + option_hdr.value_length = 0; + memcpy(opt_ptr, &option_hdr, 4); +} + +static gboolean +pcapng_write_name_resolution_block(wtap_dumper *wdh, wtap_block_t sdata, int *err) +{ + pcapng_block_header_t bh; + pcapng_name_resolution_block_t nrb; + wtapng_nrb_mandatory_t *mand_data = (wtapng_nrb_mandatory_t *)wtap_block_get_mandatory_data(sdata); + guint32 options_size; + size_t max_rec_data_size; + guint8 *block_data; + guint32 block_off; + size_t hostnamelen; + guint16 namelen; + guint32 tot_rec_len; + hashipv4_t *ipv4_hash_list_entry; + hashipv6_t *ipv6_hash_list_entry; + int i; + + if (!mand_data) { + /* + * No name/address pairs to write. + * XXX - what if we have options? + */ + return TRUE; + } + + /* Calculate the space needed for options. */ + options_size = compute_options_size(sdata, compute_nrb_option_size); + + /* + * Make sure we can fit at least one maximum-sized record, plus + * an end-of-records record, plus the options, into a maximum-sized + * block. + * + * That requires that there be enough space for the block header + * (8 bytes), a maximum-sized record (2 bytes of record type, 2 + * bytes of record value length, 65535 bytes of record value, + * and 1 byte of padding), an end-of-records record (4 bytes), + * the options (options_size bytes), and the block trailer (4 + * bytes). + */ + if (8 + 2 + 2 + 65535 + 1 + 4 + options_size + 4 > NRES_BLOCK_MAX_SIZE) { + /* + * XXX - we can't even fit the options in the largest NRB size + * we're willing to write and still have room enough for a + * maximum-sized record. Just discard the information for now. + */ + return TRUE; + } + + /* + * Allocate a buffer for the largest block we'll write. + */ + block_data = (guint8 *)g_malloc(NRES_BLOCK_MAX_SIZE); + + /* + * Calculate the maximum amount of record data we'll be able to + * fit into such a block, after taking into account the block header + * (8 bytes), the end-of-records record (4 bytes), the options + * (options_size bytes), and the block trailer (4 bytes). + */ + max_rec_data_size = NRES_BLOCK_MAX_SIZE - (8 + 4 + options_size + 4); + + block_off = 8; /* block type + block total length */ + bh.block_type = BLOCK_TYPE_NRB; + bh.block_total_length = 12; /* block header + block trailer */ + + /* + * Write out the IPv4 resolved addresses, if any. + */ + if (mand_data->ipv4_addr_list){ + i = 0; + ipv4_hash_list_entry = (hashipv4_t *)g_list_nth_data(mand_data->ipv4_addr_list, i); + while(ipv4_hash_list_entry != NULL){ + + nrb.record_type = NRES_IP4RECORD; + hostnamelen = strlen(ipv4_hash_list_entry->name); + if (hostnamelen > (G_MAXUINT16 - 4) - 1) { + /* + * This won't fit in the largest possible NRB record; + * discard it. + */ + i++; + ipv4_hash_list_entry = (hashipv4_t *)g_list_nth_data(mand_data->ipv4_addr_list, i); + continue; + } + namelen = (guint16)(hostnamelen + 1); + nrb.record_len = 4 + namelen; /* 4 bytes IPv4 address length */ + /* 2 bytes record type, 2 bytes length field */ + tot_rec_len = 4 + nrb.record_len + PADDING4(nrb.record_len); + + if (block_off + tot_rec_len > max_rec_data_size) { + /* + * This record would overflow our maximum size for Name + * Resolution Blocks; write out all the records we created + * before it, and start a new NRB. + */ + + /* Append the end-of-records record */ + memset(block_data + block_off, 0, 4); + block_off += 4; + bh.block_total_length += 4; + + /* + * Put the options into the block. + */ + put_nrb_options(wdh, sdata, block_data + block_off); + block_off += options_size; + bh.block_total_length += options_size; + + /* Copy the block header. */ + memcpy(block_data, &bh, sizeof(bh)); + + /* Copy the block trailer. */ + memcpy(block_data + block_off, &bh.block_total_length, sizeof(bh.block_total_length)); + + ws_debug("Write bh.block_total_length bytes %d, block_off %u", + bh.block_total_length, block_off); + + if (!wtap_dump_file_write(wdh, block_data, bh.block_total_length, err)) { + g_free(block_data); + return FALSE; + } + + /*Start a new NRB */ + block_off = 8; /* block type + block total length */ + bh.block_type = BLOCK_TYPE_NRB; + bh.block_total_length = 12; /* block header + block trailer */ + } + + bh.block_total_length += tot_rec_len; + memcpy(block_data + block_off, &nrb, sizeof(nrb)); + block_off += 4; + memcpy(block_data + block_off, &(ipv4_hash_list_entry->addr), 4); + block_off += 4; + memcpy(block_data + block_off, ipv4_hash_list_entry->name, namelen); + block_off += namelen; + memset(block_data + block_off, 0, PADDING4(namelen)); + block_off += PADDING4(namelen); + ws_debug("added IPv4 record for %s", ipv4_hash_list_entry->name); + + i++; + ipv4_hash_list_entry = (hashipv4_t *)g_list_nth_data(mand_data->ipv4_addr_list, i); + } + } + + if (mand_data->ipv6_addr_list){ + i = 0; + ipv6_hash_list_entry = (hashipv6_t *)g_list_nth_data(mand_data->ipv6_addr_list, i); + while(ipv6_hash_list_entry != NULL){ + + nrb.record_type = NRES_IP6RECORD; + hostnamelen = strlen(ipv6_hash_list_entry->name); + if (hostnamelen > (G_MAXUINT16 - 16) - 1) { + /* + * This won't fit in the largest possible NRB record; + * discard it. + */ + i++; + ipv6_hash_list_entry = (hashipv6_t *)g_list_nth_data(mand_data->ipv6_addr_list, i); + continue; + } + namelen = (guint16)(hostnamelen + 1); + nrb.record_len = 16 + namelen; /* 16 bytes IPv6 address length */ + /* 2 bytes record type, 2 bytes length field */ + tot_rec_len = 4 + nrb.record_len + PADDING4(nrb.record_len); + + if (block_off + tot_rec_len > max_rec_data_size) { + /* + * This record would overflow our maximum size for Name + * Resolution Blocks; write out all the records we created + * before it, and start a new NRB. + */ + + /* Append the end-of-records record */ + memset(block_data + block_off, 0, 4); + block_off += 4; + bh.block_total_length += 4; + + /* + * Put the options into the block. + */ + put_nrb_options(wdh, sdata, block_data + block_off); + block_off += options_size; + bh.block_total_length += options_size; + + /* Copy the block header. */ + memcpy(block_data, &bh, sizeof(bh)); + + /* Copy the block trailer. */ + memcpy(block_data + block_off, &bh.block_total_length, sizeof(bh.block_total_length)); + + ws_debug("write bh.block_total_length bytes %d, block_off %u", + bh.block_total_length, block_off); + + if (!wtap_dump_file_write(wdh, block_data, bh.block_total_length, err)) { + g_free(block_data); + return FALSE; + } + + /*Start a new NRB */ + block_off = 8; /* block type + block total length */ + bh.block_type = BLOCK_TYPE_NRB; + bh.block_total_length = 12; /* block header + block trailer */ + } + + bh.block_total_length += tot_rec_len; + memcpy(block_data + block_off, &nrb, sizeof(nrb)); + block_off += 4; + memcpy(block_data + block_off, &(ipv6_hash_list_entry->addr), 16); + block_off += 16; + memcpy(block_data + block_off, ipv6_hash_list_entry->name, namelen); + block_off += namelen; + memset(block_data + block_off, 0, PADDING4(namelen)); + block_off += PADDING4(namelen); + ws_debug("added IPv6 record for %s", ipv6_hash_list_entry->name); + + i++; + ipv6_hash_list_entry = (hashipv6_t *)g_list_nth_data(mand_data->ipv6_addr_list, i); + } + } + + /* Append the end-of-records record */ + memset(block_data + block_off, 0, 4); + block_off += 4; + bh.block_total_length += 4; + + /* + * Put the options into the block. + */ + put_nrb_options(wdh, sdata, block_data + block_off); + block_off += options_size; + bh.block_total_length += options_size; + + /* Copy the block header. */ + memcpy(block_data, &bh, sizeof(bh)); + + /* Copy the block trailer. */ + memcpy(block_data + block_off, &bh.block_total_length, sizeof(bh.block_total_length)); + + ws_debug("Write bh.block_total_length bytes %d, block_off %u", + bh.block_total_length, block_off); + + if (!wtap_dump_file_write(wdh, block_data, bh.block_total_length, err)) { + g_free(block_data); + return FALSE; + } + + g_free(block_data); + + return TRUE; +} + +static guint32 compute_isb_option_size(wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t *optval _U_) +{ + guint32 size; + + switch(option_id) + { + case OPT_ISB_STARTTIME: + case OPT_ISB_ENDTIME: + size = 8; + break; + case OPT_ISB_IFRECV: + case OPT_ISB_IFDROP: + case OPT_ISB_FILTERACCEPT: + case OPT_ISB_OSDROP: + case OPT_ISB_USRDELIV: + size = 8; + break; + default: + /* Unknown options - size by datatype? */ + size = 0; + break; + } + return size; +} + +static gboolean write_wtap_isb_option(wtap_dumper *wdh, wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t *optval, int *err) +{ + switch(option_id) + { + case OPT_ISB_STARTTIME: + case OPT_ISB_ENDTIME: + if (!pcapng_write_timestamp_option(wdh, option_id, optval, err)) + return FALSE; + break; + case OPT_ISB_IFRECV: + case OPT_ISB_IFDROP: + case OPT_ISB_FILTERACCEPT: + case OPT_ISB_OSDROP: + case OPT_ISB_USRDELIV: + if (!pcapng_write_uint64_option(wdh, option_id, optval, err)) + return FALSE; + break; + default: + /* Unknown options - write by datatype? */ + break; + } + return TRUE; /* success */ +} + +static gboolean +pcapng_write_interface_statistics_block(wtap_dumper *wdh, wtap_block_t if_stats, int *err) +{ + pcapng_block_header_t bh; + pcapng_interface_statistics_block_t isb; + guint32 options_size; + wtapng_if_stats_mandatory_t* mand_data = (wtapng_if_stats_mandatory_t*)wtap_block_get_mandatory_data(if_stats); + + ws_debug("entering function"); + + /* Compute size of all the options */ + options_size = compute_options_size(if_stats, compute_isb_option_size); + + /* write block header */ + bh.block_type = BLOCK_TYPE_ISB; + bh.block_total_length = (guint32)(sizeof(bh) + sizeof(isb) + options_size + 4); + ws_debug("Total len %u", bh.block_total_length); + + if (!wtap_dump_file_write(wdh, &bh, sizeof bh, err)) + return FALSE; + + /* write block fixed content */ + isb.interface_id = mand_data->interface_id; + isb.timestamp_high = mand_data->ts_high; + isb.timestamp_low = mand_data->ts_low; + + if (!wtap_dump_file_write(wdh, &isb, sizeof isb, err)) + return FALSE; + + /* Write options */ + if (options_size != 0) { + if (!write_options(wdh, if_stats, write_wtap_isb_option, err)) + return FALSE; + } + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) + return FALSE; + return TRUE; +} + +static guint32 compute_idb_option_size(wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t *optval) +{ + guint32 size; + + switch(option_id) + { + case OPT_IDB_NAME: + case OPT_IDB_DESCRIPTION: + case OPT_IDB_OS: + case OPT_IDB_HARDWARE: + size = pcapng_compute_string_option_size(optval); + break; + case OPT_IDB_SPEED: + size = 8; + break; + case OPT_IDB_TSRESOL: + size = 1; + break; + case OPT_IDB_FILTER: + size = pcapng_compute_if_filter_option_size(optval); + break; + case OPT_IDB_FCSLEN: + size = 1; + break; + case OPT_IDB_TSOFFSET: + /* + * The time stamps handed to us when writing a file are + * absolute time staps, so the time stamp offset is + * zero. + * + * We do not adjust them when writing, so we should not + * write if_tsoffset options; that is interpreted as + * the offset is zero, i.e. the time stamps in the file + * are absolute. + */ + size = 0; + break; + default: + /* Unknown options - size by datatype? */ + size = 0; + break; + } + return size; +} + +static gboolean write_wtap_idb_option(wtap_dumper *wdh, wtap_block_t block _U_, guint option_id, wtap_opttype_e option_type _U_, wtap_optval_t *optval, int *err) +{ + switch(option_id) + { + case OPT_IDB_NAME: + case OPT_IDB_DESCRIPTION: + case OPT_IDB_OS: + case OPT_IDB_HARDWARE: + if (!pcapng_write_string_option(wdh, option_id, optval, err)) + return FALSE; + break; + case OPT_IDB_SPEED: + if (!pcapng_write_uint64_option(wdh, option_id, optval, err)) + return FALSE; + break; + case OPT_IDB_TSRESOL: + if (!pcapng_write_uint8_option(wdh, option_id, optval, err)) + return FALSE; + break; + case OPT_IDB_FILTER: + if (!pcapng_write_if_filter_option(wdh, option_id, optval, err)) + return FALSE; + break; + case OPT_IDB_FCSLEN: + if (!pcapng_write_uint8_option(wdh, option_id, optval, err)) + return FALSE; + break; + case OPT_IDB_TSOFFSET: + /* + * As noted above, we discard these. + */ + break; + default: + /* Unknown options - size by datatype? */ + break; + } + return TRUE; +} + +static gboolean +pcapng_write_if_descr_block(wtap_dumper *wdh, wtap_block_t int_data, int *err) +{ + pcapng_block_header_t bh; + pcapng_interface_description_block_t idb; + guint32 options_size; + wtapng_if_descr_mandatory_t* mand_data = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(int_data); + int link_type; + + ws_debug("encap = %d (%s), snaplen = %d", + mand_data->wtap_encap, + wtap_encap_description(mand_data->wtap_encap), + mand_data->snap_len); + + link_type = wtap_wtap_encap_to_pcap_encap(mand_data->wtap_encap); + if (link_type == -1) { + if (!pcapng_encap_is_ft_specific(mand_data->wtap_encap)) { + *err = WTAP_ERR_UNWRITABLE_ENCAP; + return FALSE; + } + } + + /* Compute size of all the options */ + options_size = compute_options_size(int_data, compute_idb_option_size); + + /* write block header */ + bh.block_type = BLOCK_TYPE_IDB; + bh.block_total_length = (guint32)(sizeof(bh) + sizeof(idb) + options_size + 4); + ws_debug("Total len %u", bh.block_total_length); + + if (!wtap_dump_file_write(wdh, &bh, sizeof bh, err)) + return FALSE; + + /* write block fixed content */ + idb.linktype = link_type; + idb.reserved = 0; + idb.snaplen = mand_data->snap_len; + + if (!wtap_dump_file_write(wdh, &idb, sizeof idb, err)) + return FALSE; + + if (options_size != 0) { + /* Write options */ + if (!write_options(wdh, int_data, write_wtap_idb_option, err)) + return FALSE; + } + + /* write block footer */ + if (!wtap_dump_file_write(wdh, &bh.block_total_length, + sizeof bh.block_total_length, err)) + return FALSE; + + return TRUE; +} + +static gboolean pcapng_add_idb(wtap_dumper *wdh, wtap_block_t idb, + int *err, gchar **err_info _U_) +{ + wtap_block_t idb_copy; + + /* + * Add a copy of this IDB to our array of IDBs. + */ + idb_copy = wtap_block_create(WTAP_BLOCK_IF_ID_AND_INFO); + wtap_block_copy(idb_copy, idb); + g_array_append_val(wdh->interface_data, idb_copy); + + /* + * And write it to the output file. + */ + return pcapng_write_if_descr_block(wdh, idb_copy, err); +} + +static gboolean pcapng_write_internal_blocks(wtap_dumper *wdh, int *err) +{ + + /* Write (optional) Decryption Secrets Blocks that were collected while + * reading packet blocks. */ + if (wdh->dsbs_growing) { + for (guint i = wdh->dsbs_growing_written; i < wdh->dsbs_growing->len; i++) { + ws_debug("writing DSB %u", i); + wtap_block_t dsb = g_array_index(wdh->dsbs_growing, wtap_block_t, i); + if (!pcapng_write_decryption_secrets_block(wdh, dsb, err)) { + return FALSE; + } + ++wdh->dsbs_growing_written; + } + } + + /* Write (optional) Sysdig Meta Event Blocks that were collected while + * reading packet blocks. */ + if (wdh->sysdig_mev_growing) { + for (unsigned i = wdh->sysdig_mev_growing_written; i < wdh->sysdig_mev_growing->len; i++) { + ws_debug("writing Sysdig mev %u", i); + wtap_block_t mev = g_array_index(wdh->sysdig_mev_growing, wtap_block_t, i); + if (!pcapng_write_sysdig_meta_event_block(wdh, mev, err)) { + return false; + } + ++wdh->sysdig_mev_growing_written; + } + } + + /* Write any hostname resolution info from wtap_dump_set_addrinfo_list() */ + if (!wtap_addrinfo_list_empty(wdh->addrinfo_lists)) { + /* + * XXX: get_addrinfo_list() returns a list of all known and used + * resolved addresses, regardless of origin: existing NRBs, externally + * resolved, DNS packet data, a hosts file, and manual host resolution + * through the GUI. It does not include the source for each. + * + * If it did, we could instead create multiple NRBs, one for each + * server (as the options can only be included once per block.) + * Instead, we copy the options from the first already existing NRB + * (if there is one), since some of the name resolutions may be + * from that block. + */ + wtap_block_t nrb; + if (wdh->nrbs_growing && wdh->nrbs_growing->len) { + nrb = wtap_block_make_copy(g_array_index(wdh->nrbs_growing, wtap_block_t, 0)); + } else { + nrb = wtap_block_create(WTAP_BLOCK_NAME_RESOLUTION); + } + wtapng_nrb_mandatory_t *mand_data = (wtapng_nrb_mandatory_t *)wtap_block_get_mandatory_data(nrb); + mand_data->ipv4_addr_list = wdh->addrinfo_lists->ipv4_addr_list; + mand_data->ipv6_addr_list = wdh->addrinfo_lists->ipv6_addr_list; + + if (!pcapng_write_name_resolution_block(wdh, nrb, err)) { + return FALSE; + } + mand_data->ipv4_addr_list = NULL; + mand_data->ipv6_addr_list = NULL; + wtap_block_unref(nrb); + g_list_free(wdh->addrinfo_lists->ipv4_addr_list); + wdh->addrinfo_lists->ipv4_addr_list = NULL; + g_list_free(wdh->addrinfo_lists->ipv6_addr_list); + wdh->addrinfo_lists->ipv6_addr_list = NULL; + /* Since the addrinfo lists include information from existing NRBs, + * avoid writing them to avoid duplication. + * + * XXX: Perhaps we don't want to include information from the NRBs + * in get_addrinfo_list at all, so that we could write existing + * NRBs as-is. + * + * This is still not well oriented for one-pass programs, where we + * don't have addrinfo_lists until we've already written the + * NRBs. We should not write both in such a situation. See bug 15502. + */ + wtap_dump_discard_name_resolution(wdh); + } + + /* Write (optional) Name Resolution Blocks that were collected while + * reading packet blocks. */ + if (wdh->nrbs_growing) { + for (guint i = wdh->nrbs_growing_written; i < wdh->nrbs_growing->len; i++) { + wtap_block_t nrb = g_array_index(wdh->nrbs_growing, wtap_block_t, i); + if (!pcapng_write_name_resolution_block(wdh, nrb, err)) { + return FALSE; + } + ++wdh->nrbs_growing_written; + } + } + + return TRUE; +} + +static gboolean pcapng_dump(wtap_dumper *wdh, + const wtap_rec *rec, + const guint8 *pd, int *err, gchar **err_info) +{ +#ifdef HAVE_PLUGINS + block_handler *handler; +#endif + + if (!pcapng_write_internal_blocks(wdh, err)) { + return FALSE; + } + + ws_debug("encap = %d (%s) rec type = %u", + rec->rec_header.packet_header.pkt_encap, + wtap_encap_description(rec->rec_header.packet_header.pkt_encap), + rec->rec_type); + + switch (rec->rec_type) { + + case REC_TYPE_PACKET: + /* + * XXX - write a Simple Packet Block if there's no time + * stamp or other information that doesn't appear in an + * SPB? + */ + if (!pcapng_write_enhanced_packet_block(wdh, rec, pd, err, + err_info)) { + return FALSE; + } + break; + + case REC_TYPE_FT_SPECIFIC_EVENT: + case REC_TYPE_FT_SPECIFIC_REPORT: +#ifdef HAVE_PLUGINS + /* + * Do we have a handler for this block type? + */ + if (block_handlers != NULL && + (handler = (block_handler *)g_hash_table_lookup(block_handlers, + GUINT_TO_POINTER(rec->rec_header.ft_specific_header.record_type))) != NULL) { + /* Yes. Call it to write out this record. */ + if (!handler->writer(wdh, rec, pd, err)) + return FALSE; + } else +#endif + { + /* No. */ + *err = WTAP_ERR_UNWRITABLE_REC_TYPE; + return FALSE; + } + break; + + case REC_TYPE_SYSCALL: + if (!pcapng_write_sysdig_event_block(wdh, rec, pd, err)) { + return FALSE; + } + break; + + case REC_TYPE_SYSTEMD_JOURNAL_EXPORT: + if (!pcapng_write_systemd_journal_export_block(wdh, rec, pd, err)) { + return FALSE; + } + break; + + case REC_TYPE_CUSTOM_BLOCK: + switch (rec->rec_header.custom_block_header.pen) { + case PEN_NFLX: + if (!pcapng_write_bblog_block(wdh, rec, pd, err)) { + return FALSE; + } + break; + default: + if (!pcapng_write_custom_block(wdh, rec, pd, err)) { + return FALSE; + } + break; + } + break; + + default: + /* We don't support writing this record type. */ + *err = WTAP_ERR_UNWRITABLE_REC_TYPE; + return FALSE; + } + + return TRUE; +} + +/* Finish writing to a dump file. + Returns TRUE on success, FALSE on failure. */ +static gboolean pcapng_dump_finish(wtap_dumper *wdh, int *err, + gchar **err_info _U_) +{ + guint i, j; + + /* Flush any hostname resolution or decryption secrets info we may have */ + if (!pcapng_write_internal_blocks(wdh, err)) { + return FALSE; + } + + for (i = 0; i < wdh->interface_data->len; i++) { + + /* Get the interface description */ + wtap_block_t int_data; + wtapng_if_descr_mandatory_t *int_data_mand; + + int_data = g_array_index(wdh->interface_data, wtap_block_t, i); + int_data_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(int_data); + + for (j = 0; j < int_data_mand->num_stat_entries; j++) { + wtap_block_t if_stats; + + if_stats = g_array_index(int_data_mand->interface_statistics, wtap_block_t, j); + ws_debug("write ISB for interface %u", + ((wtapng_if_stats_mandatory_t*)wtap_block_get_mandatory_data(if_stats))->interface_id); + if (!pcapng_write_interface_statistics_block(wdh, if_stats, err)) { + return FALSE; + } + } + } + + ws_debug("leaving function"); + return TRUE; +} + +/* Returns TRUE on success, FALSE on failure; sets "*err" to an error code on + failure */ +static gboolean +pcapng_dump_open(wtap_dumper *wdh, int *err, gchar **err_info _U_) +{ + guint i; + + ws_debug("entering function"); + /* This is a pcapng file */ + wdh->subtype_add_idb = pcapng_add_idb; + wdh->subtype_write = pcapng_dump; + wdh->subtype_finish = pcapng_dump_finish; + + /* write the section header block */ + if (!pcapng_write_section_header_block(wdh, err)) { + return FALSE; + } + ws_debug("wrote section header block."); + + /* Write the Interface description blocks */ + ws_debug("Number of IDBs to write (number of interfaces) %u", + wdh->interface_data->len); + + for (i = 0; i < wdh->interface_data->len; i++) { + + /* Get the interface description */ + wtap_block_t idb; + + idb = g_array_index(wdh->interface_data, wtap_block_t, i); + + if (!pcapng_write_if_descr_block(wdh, idb, err)) { + return FALSE; + } + + } + + /* Write (optional) fixed Decryption Secrets Blocks. */ + if (wdh->dsbs_initial) { + for (i = 0; i < wdh->dsbs_initial->len; i++) { + wtap_block_t dsb = g_array_index(wdh->dsbs_initial, wtap_block_t, i); + if (!pcapng_write_decryption_secrets_block(wdh, dsb, err)) { + return FALSE; + } + } + } + + return TRUE; +} + +/* Returns 0 if we could write the specified encapsulation type, + an error indication otherwise. */ +static int pcapng_dump_can_write_encap(int wtap_encap) +{ + ws_debug("encap = %d (%s)", + wtap_encap, + wtap_encap_description(wtap_encap)); + + /* Per-packet encapsulation is supported. */ + if (wtap_encap == WTAP_ENCAP_PER_PACKET) + return 0; + + /* No encapsulation type (yet) is supported. */ + if (wtap_encap == WTAP_ENCAP_NONE) + return 0; + + /* Is it a filetype-specific encapsulation that we support? */ + if (pcapng_encap_is_ft_specific(wtap_encap)) { + return 0; + } + + /* Make sure we can figure out this DLT type */ + if (wtap_wtap_encap_to_pcap_encap(wtap_encap) == -1) + return WTAP_ERR_UNWRITABLE_ENCAP; + + return 0; +} + +/* + * Returns TRUE if the specified encapsulation type is filetype-specific + * and one that we support. + */ +gboolean pcapng_encap_is_ft_specific(int encap) +{ + switch (encap) { + case WTAP_ENCAP_SYSTEMD_JOURNAL: + return TRUE; + } + return FALSE; +} + +/* + * pcapng supports several block types, and supports more than one + * of them. + * + * It also supports comments for many block types, as well as other + * option types. + */ + +/* Options for section blocks. */ +static const struct supported_option_type section_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_SHB_HARDWARE, ONE_OPTION_SUPPORTED }, + { OPT_SHB_USERAPPL, ONE_OPTION_SUPPORTED } +}; + +/* Options for interface blocks. */ +static const struct supported_option_type interface_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_IDB_NAME, ONE_OPTION_SUPPORTED }, + { OPT_IDB_DESCRIPTION, ONE_OPTION_SUPPORTED }, + { OPT_IDB_IP4ADDR, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_IDB_IP6ADDR, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_IDB_MACADDR, ONE_OPTION_SUPPORTED }, + { OPT_IDB_EUIADDR, ONE_OPTION_SUPPORTED }, + { OPT_IDB_SPEED, ONE_OPTION_SUPPORTED }, + { OPT_IDB_TSRESOL, ONE_OPTION_SUPPORTED }, + { OPT_IDB_TZONE, ONE_OPTION_SUPPORTED }, + { OPT_IDB_FILTER, ONE_OPTION_SUPPORTED }, + { OPT_IDB_OS, ONE_OPTION_SUPPORTED }, + { OPT_IDB_FCSLEN, ONE_OPTION_SUPPORTED }, + { OPT_IDB_TSOFFSET, ONE_OPTION_SUPPORTED }, + { OPT_IDB_HARDWARE, ONE_OPTION_SUPPORTED } +}; + +/* Options for name resolution blocks. */ +static const struct supported_option_type name_resolution_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_NS_DNSNAME, ONE_OPTION_SUPPORTED }, + { OPT_NS_DNSIP4ADDR, ONE_OPTION_SUPPORTED }, + { OPT_NS_DNSIP6ADDR, ONE_OPTION_SUPPORTED } +}; + +/* Options for interface statistics blocks. */ +static const struct supported_option_type interface_statistics_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_ISB_STARTTIME, ONE_OPTION_SUPPORTED }, + { OPT_ISB_ENDTIME, ONE_OPTION_SUPPORTED }, + { OPT_ISB_IFRECV, ONE_OPTION_SUPPORTED }, + { OPT_ISB_IFDROP, ONE_OPTION_SUPPORTED }, + { OPT_ISB_FILTERACCEPT, ONE_OPTION_SUPPORTED }, + { OPT_ISB_OSDROP, ONE_OPTION_SUPPORTED }, + { OPT_ISB_USRDELIV, ONE_OPTION_SUPPORTED } +}; + +/* Options for decryption secrets blocks. */ +static const struct supported_option_type decryption_secrets_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED } +}; + +/* Options for Sysdig meta event blocks. */ +static const struct supported_option_type sysdig_meta_events_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED } +}; + +/* Options for packet blocks. */ +static const struct supported_option_type packet_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_PKT_FLAGS, ONE_OPTION_SUPPORTED }, + { OPT_PKT_DROPCOUNT, ONE_OPTION_SUPPORTED }, + { OPT_PKT_PACKETID, ONE_OPTION_SUPPORTED }, + { OPT_PKT_QUEUE, ONE_OPTION_SUPPORTED }, + { OPT_PKT_HASH, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_PKT_VERDICT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED } +}; + +/* Options for file-type-sepcific reports. */ +static const struct supported_option_type ft_specific_report_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED } +}; + +/* Options for file-type-sepcific event. */ +static const struct supported_option_type ft_specific_event_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED } +}; + +/* Options for systemd journal entry. */ +static const struct supported_option_type systemd_journal_export_block_options_supported[] = { + { OPT_COMMENT, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_STR_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED }, + { OPT_CUSTOM_BIN_NO_COPY, MULTIPLE_OPTIONS_SUPPORTED } +}; + +static const struct supported_block_type pcapng_blocks_supported[] = { + /* Multiple sections. */ + { WTAP_BLOCK_SECTION, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(section_block_options_supported) }, + + /* Multiple interfaces. */ + { WTAP_BLOCK_IF_ID_AND_INFO, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(interface_block_options_supported) }, + + /* Multiple blocks of name resolution information */ + { WTAP_BLOCK_NAME_RESOLUTION, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(name_resolution_block_options_supported) }, + + /* Multiple blocks of interface statistics. */ + { WTAP_BLOCK_IF_STATISTICS, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(interface_statistics_block_options_supported) }, + + /* Multiple blocks of decryption secrets. */ + { WTAP_BLOCK_DECRYPTION_SECRETS, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(decryption_secrets_block_options_supported) }, + + /* Multiple blocks of decryption secrets. */ + { WTAP_BLOCK_SYSDIG_META_EVENT, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(sysdig_meta_events_block_options_supported) }, + + /* And, obviously, multiple packets. */ + { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(packet_block_options_supported) }, + + /* Multiple file-type specific reports (including local ones). */ + { WTAP_BLOCK_FT_SPECIFIC_REPORT, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(ft_specific_report_block_options_supported) }, + + /* Multiple file-type specific events (including local ones). */ + { WTAP_BLOCK_FT_SPECIFIC_EVENT, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(ft_specific_event_block_options_supported) }, + + /* Multiple systemd journal export records. */ + { WTAP_BLOCK_SYSTEMD_JOURNAL_EXPORT, MULTIPLE_BLOCKS_SUPPORTED, OPTION_TYPES_SUPPORTED(systemd_journal_export_block_options_supported) }, + + /* Multiple custom blocks. */ + { WTAP_BLOCK_CUSTOM, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }, +}; + +static const struct file_type_subtype_info pcapng_info = { + "Wireshark/... - pcapng", "pcapng", "pcapng", "ntar", + FALSE, BLOCKS_SUPPORTED(pcapng_blocks_supported), + pcapng_dump_can_write_encap, pcapng_dump_open, NULL +}; + +void register_pcapng(void) +{ + pcapng_file_type_subtype = wtap_register_file_type_subtype(&pcapng_info); + + wtap_register_backwards_compatibility_lua_name("PCAPNG", + pcapng_file_type_subtype); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ |