forked from osmocom/wireshark
![Guy Harris](/assets/img/avatar_default.png)
Microsoft Network Monitor lets you capture on an 802.11 adapter either in monitor mode or in non-monitor mode; frames captured in non-monitor mode may have the Protected bit set in the 802.11 header, but are decrypted and don't incclude encryption information, and may have the A-MSDU Present flag set in the QoS Control field, but have just a regular frame payload, not a sequence of A-MSDUs, in the payload field. Dissect those frames correctly. Bug: 16758 Change-Id: I42b7e9ce52faa80222692403fa7276c039644343 Reviewed-on: https://code.wireshark.org/review/38082 Petri-Dish: Guy Harris <gharris@sonic.net> Tested-by: Petri Dish Buildbot Reviewed-by: Guy Harris <gharris@sonic.net>
2008 lines
60 KiB
C
2008 lines
60 KiB
C
/* netmon.c
|
|
*
|
|
* Wiretap Library
|
|
* Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <wsutil/unicode-utils.h>
|
|
#include "wtap-int.h"
|
|
#include "file_wrappers.h"
|
|
#include "atm.h"
|
|
#include "pcap-encap.h"
|
|
#include "netmon.h"
|
|
|
|
/* The file at
|
|
*
|
|
* ftp://ftp.microsoft.com/developr/drg/cifs/cifs/Bhfile.zip
|
|
*
|
|
* contains "STRUCT.H", which declares the typedef CAPTUREFILE_HEADER
|
|
* for the header of a Microsoft Network Monitor 1.x capture file.
|
|
*
|
|
* The help files for Network Monitor 3.x document the 2.x file format.
|
|
*/
|
|
|
|
/* Capture file header, *including* magic number, is padded to 128 bytes. */
|
|
#define CAPTUREFILE_HEADER_SIZE 128
|
|
|
|
/* Magic number size, for both 1.x and 2.x. */
|
|
#define MAGIC_SIZE 4
|
|
|
|
/* Magic number in Network Monitor 1.x files. */
|
|
static const char netmon_1_x_magic[MAGIC_SIZE] = {
|
|
'R', 'T', 'S', 'S'
|
|
};
|
|
|
|
/* Magic number in Network Monitor 2.x files. */
|
|
static const char netmon_2_x_magic[MAGIC_SIZE] = {
|
|
'G', 'M', 'B', 'U'
|
|
};
|
|
|
|
/* Network Monitor file header (minus magic number). */
|
|
struct netmon_hdr {
|
|
guint8 ver_minor; /* minor version number */
|
|
guint8 ver_major; /* major version number */
|
|
guint16 network; /* network type */
|
|
guint16 ts_year; /* year of capture start */
|
|
guint16 ts_month; /* month of capture start (January = 1) */
|
|
guint16 ts_dow; /* day of week of capture start (Sun = 0) */
|
|
guint16 ts_day; /* day of month of capture start */
|
|
guint16 ts_hour; /* hour of capture start */
|
|
guint16 ts_min; /* minute of capture start */
|
|
guint16 ts_sec; /* second of capture start */
|
|
guint16 ts_msec; /* millisecond of capture start */
|
|
guint32 frametableoffset; /* frame index table offset */
|
|
guint32 frametablelength; /* frame index table size */
|
|
guint32 userdataoffset; /* user data offset */
|
|
guint32 userdatalength; /* user data size */
|
|
guint32 commentdataoffset; /* comment data offset */
|
|
guint32 commentdatalength; /* comment data size */
|
|
guint32 processinfooffset; /* offset to process info structure */
|
|
guint32 processinfocount; /* number of process info structures */
|
|
guint32 networkinfooffset; /* offset to network info structure */
|
|
guint32 networkinfolength; /* length of network info structure */
|
|
};
|
|
|
|
/* Network Monitor 1.x record header; not defined in STRUCT.H, but deduced by
|
|
* looking at capture files. */
|
|
struct netmonrec_1_x_hdr {
|
|
guint32 ts_delta; /* time stamp - msecs since start of capture */
|
|
guint16 orig_len; /* actual length of packet */
|
|
guint16 incl_len; /* number of octets captured in file */
|
|
};
|
|
|
|
/*
|
|
* Network Monitor 2.x record header, as documented in NetMon 3.x's
|
|
* help files.
|
|
*/
|
|
struct netmonrec_2_x_hdr {
|
|
guint64 ts_delta; /* time stamp - usecs since start of capture */
|
|
guint32 orig_len; /* actual length of packet */
|
|
guint32 incl_len; /* number of octets captured in file */
|
|
};
|
|
|
|
/*
|
|
* Network Monitor 2.1 and later record trailers; documented in the Network
|
|
* Monitor 3.x help files, for 3.3 and later, although they don't clearly
|
|
* state how the trailer format changes from version to version.
|
|
*
|
|
* Some fields are multi-byte integers, but they're not aligned on their
|
|
* natural boundaries.
|
|
*/
|
|
struct netmonrec_2_1_trlr {
|
|
guint8 network[2]; /* network type for this packet */
|
|
};
|
|
|
|
struct netmonrec_2_2_trlr {
|
|
guint8 network[2]; /* network type for this packet */
|
|
guint8 process_info_index[4]; /* index into the process info table */
|
|
};
|
|
|
|
struct netmonrec_2_3_trlr {
|
|
guint8 network[2]; /* network type for this packet */
|
|
guint8 process_info_index[4]; /* index into the process info table */
|
|
guint8 utc_timestamp[8]; /* packet time stamp, as .1 us units since January 1, 1601, 00:00:00 UTC */
|
|
guint8 timezone_index; /* index of time zone information */
|
|
};
|
|
|
|
struct netmonrec_comment {
|
|
guint32 numFramePerComment; /* Currently, this is always set to 1. Each comment is attached to only one frame. */
|
|
guint32 frameOffset; /* Offset in the capture file table that indicates the beginning of the frame. Key used to match comment with frame */
|
|
guint8* title; /* Comment title */
|
|
guint32 descLength; /* Number of bytes in the comment description. Must be at least zero. */
|
|
guint8* description; /* Comment description */
|
|
};
|
|
|
|
/* Just the first few fields of netmonrec_comment so it can be read sequentially from file */
|
|
struct netmonrec_comment_header {
|
|
guint32 numFramePerComment;
|
|
guint32 frameOffset;
|
|
guint32 titleLength;
|
|
};
|
|
|
|
union ip_address {
|
|
guint32 ipv4;
|
|
ws_in6_addr ipv6;
|
|
};
|
|
|
|
struct netmonrec_process_info {
|
|
guint8* path; /* A Unicode string of length PathSize */
|
|
guint32 iconSize;
|
|
guint8* iconData;
|
|
guint32 pid;
|
|
guint16 localPort;
|
|
guint16 remotePort;
|
|
gboolean isIPv6;
|
|
union ip_address localAddr;
|
|
union ip_address remoteAddr;
|
|
};
|
|
|
|
/*
|
|
* The link-layer header on ATM packets.
|
|
*/
|
|
struct netmon_atm_hdr {
|
|
guint8 dest[6]; /* "Destination address" - what is it? */
|
|
guint8 src[6]; /* "Source address" - what is it? */
|
|
guint16 vpi; /* VPI */
|
|
guint16 vci; /* VCI */
|
|
};
|
|
|
|
typedef struct {
|
|
time_t start_secs;
|
|
guint32 start_nsecs;
|
|
guint8 version_major;
|
|
guint8 version_minor;
|
|
guint32 *frame_table;
|
|
guint32 frame_table_size;
|
|
GHashTable* comment_table;
|
|
GHashTable* process_info_table;
|
|
guint current_frame;
|
|
} netmon_t;
|
|
|
|
/*
|
|
* Maximum pathname length supported in the process table; the length
|
|
* is in a 32-bit field, so we impose a limit to prevent attempts to
|
|
* allocate too much memory.
|
|
*
|
|
* See
|
|
*
|
|
* https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
|
|
*
|
|
* The NetMon 3.4 "Capture File Format" documentation says "PathSize must be
|
|
* greater than 0, and less than MAX_PATH (260 characters)", but, as per that
|
|
* link above, that limit has been raised in more recent systems.
|
|
*
|
|
* We pick a limit of 65536, as that should handle a path length of 32767
|
|
* UTF-16 octet pairs plus a trailing NUL octet pair.
|
|
*/
|
|
#define MATH_PROCINFO_PATH_SIZE 65536
|
|
|
|
/*
|
|
* XXX - at least in some NetMon 3.4 VPN captures, the per-packet
|
|
* link-layer type is 0, but the packets have Ethernet headers.
|
|
* We handle this by mapping 0 to WTAP_ENCAP_ETHERNET; should we,
|
|
* instead, use the per-file link-layer type?
|
|
*/
|
|
static const int netmon_encap[] = {
|
|
WTAP_ENCAP_ETHERNET,
|
|
WTAP_ENCAP_ETHERNET,
|
|
WTAP_ENCAP_TOKEN_RING,
|
|
WTAP_ENCAP_FDDI_BITSWAPPED,
|
|
WTAP_ENCAP_ATM_PDUS, /* NDIS WAN - this is what's used for ATM */
|
|
WTAP_ENCAP_UNKNOWN, /* NDIS LocalTalk, but format 2.x uses it for IP-over-IEEE 1394 */
|
|
WTAP_ENCAP_IEEE_802_11_NETMON,
|
|
/* NDIS "DIX", but format 2.x uses it for 802.11 */
|
|
WTAP_ENCAP_RAW_IP, /* NDIS ARCNET raw, but format 2.x uses it for "Tunneling interfaces" */
|
|
WTAP_ENCAP_RAW_IP, /* NDIS ARCNET 878.2, but format 2.x uses it for "Wireless WAN" */
|
|
WTAP_ENCAP_RAW_IP, /* NDIS ATM (no, this is NOT used for ATM); format 2.x uses it for "Raw IP Frames" */
|
|
WTAP_ENCAP_UNKNOWN, /* NDIS Wireless WAN */
|
|
WTAP_ENCAP_UNKNOWN /* NDIS IrDA */
|
|
};
|
|
#define NUM_NETMON_ENCAPS (sizeof netmon_encap / sizeof netmon_encap[0])
|
|
|
|
/*
|
|
* Special link-layer types.
|
|
*/
|
|
#define NETMON_NET_PCAP_BASE 0xE000
|
|
#define NETMON_NET_NETEVENT 0xFFE0
|
|
#define NETMON_NET_NETWORK_INFO_EX 0xFFFB
|
|
#define NETMON_NET_PAYLOAD_HEADER 0xFFFC
|
|
#define NETMON_NET_NETWORK_INFO 0xFFFD
|
|
#define NETMON_NET_DNS_CACHE 0xFFFE
|
|
#define NETMON_NET_NETMON_FILTER 0xFFFF
|
|
|
|
static gboolean netmon_read(wtap *wth, wtap_rec *rec, Buffer *buf,
|
|
int *err, gchar **err_info, gint64 *data_offset);
|
|
static gboolean netmon_seek_read(wtap *wth, gint64 seek_off,
|
|
wtap_rec *rec, Buffer *buf, int *err, gchar **err_info);
|
|
static gboolean netmon_read_atm_pseudoheader(FILE_T fh,
|
|
union wtap_pseudo_header *pseudo_header, int *err, gchar **err_info);
|
|
static void netmon_close(wtap *wth);
|
|
static gboolean netmon_dump(wtap_dumper *wdh, const wtap_rec *rec,
|
|
const guint8 *pd, int *err, gchar **err_info);
|
|
static gboolean netmon_dump_finish(wtap_dumper *wdh, int *err);
|
|
|
|
/*
|
|
* Convert a counted UTF-16 string, which is probably also null-terminated
|
|
* but is not guaranteed to be null-terminated (as it came from a file),
|
|
* to a null-terminated UTF-8 string.
|
|
*/
|
|
static guint8 *
|
|
utf_16_to_utf_8(const guint8 *in, guint32 length)
|
|
{
|
|
guint8 *result, *out;
|
|
gunichar2 uchar2;
|
|
gunichar uchar;
|
|
size_t n_bytes;
|
|
guint32 i;
|
|
|
|
/*
|
|
* Get the length of the resulting UTF-8 string, and validate
|
|
* the input string in the process.
|
|
*/
|
|
n_bytes = 0;
|
|
for (i = 0; i + 1 < length && (uchar2 = pletoh16(in + i)) != '\0';
|
|
i += 2) {
|
|
if (IS_LEAD_SURROGATE(uchar2)) {
|
|
/*
|
|
* Lead surrogate. Must be followed by a trail
|
|
* surrogate.
|
|
*/
|
|
gunichar2 lead_surrogate;
|
|
|
|
i += 2;
|
|
if (i + 1 >= length) {
|
|
/*
|
|
* Oops, string ends with a lead surrogate.
|
|
* Ignore this for now.
|
|
* XXX - insert "substitute" character?
|
|
* Report the error in some other fashion?
|
|
*/
|
|
break;
|
|
}
|
|
lead_surrogate = uchar2;
|
|
uchar2 = pletoh16(in + i);
|
|
if (uchar2 == '\0') {
|
|
/*
|
|
* Oops, string ends with a lead surrogate.
|
|
* Ignore this for now.
|
|
* XXX - insert "substitute" character?
|
|
* Report the error in some other fashion?
|
|
*/
|
|
break;
|
|
}
|
|
if (IS_TRAIL_SURROGATE(uchar2)) {
|
|
/* Trail surrogate. */
|
|
uchar = SURROGATE_VALUE(lead_surrogate, uchar2);
|
|
n_bytes += g_unichar_to_utf8(uchar, NULL);
|
|
} else {
|
|
/*
|
|
* Not a trail surrogate.
|
|
* Ignore the entire pair.
|
|
* XXX - insert "substitute" character?
|
|
* Report the error in some other fashion?
|
|
*/
|
|
;
|
|
}
|
|
} else {
|
|
if (IS_TRAIL_SURROGATE(uchar2)) {
|
|
/*
|
|
* Trail surrogate without a preceding
|
|
* lead surrogate. Ignore it.
|
|
* XXX - insert "substitute" character?
|
|
* Report the error in some other fashion?
|
|
*/
|
|
;
|
|
} else {
|
|
/*
|
|
* Non-surrogate; just count it.
|
|
*/
|
|
n_bytes += g_unichar_to_utf8(uchar2, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now allocate a buffer big enough for the UTF-8 string plus a
|
|
* trailing NUL, and generate the string.
|
|
*/
|
|
result = (guint8 *)g_malloc(n_bytes + 1);
|
|
|
|
out = result;
|
|
for (i = 0; i + 1 < length && (uchar2 = pletoh16(in + i)) != '\0';
|
|
i += 2) {
|
|
if (IS_LEAD_SURROGATE(uchar2)) {
|
|
/*
|
|
* Lead surrogate. Must be followed by a trail
|
|
* surrogate.
|
|
*/
|
|
gunichar2 lead_surrogate;
|
|
|
|
i += 2;
|
|
if (i + 1 >= length) {
|
|
/*
|
|
* Oops, string ends with a lead surrogate.
|
|
* Ignore this for now.
|
|
* XXX - insert "substitute" character?
|
|
* Report the error in some other fashion?
|
|
*/
|
|
break;
|
|
}
|
|
lead_surrogate = uchar2;
|
|
uchar2 = pletoh16(in + i);
|
|
if (uchar2 == '\0') {
|
|
/*
|
|
* Oops, string ends with a lead surrogate.
|
|
* Ignore this for now.
|
|
* XXX - insert "substitute" character?
|
|
* Report the error in some other fashion?
|
|
*/
|
|
break;
|
|
}
|
|
if (IS_TRAIL_SURROGATE(uchar2)) {
|
|
/* Trail surrogate. */
|
|
uchar = SURROGATE_VALUE(lead_surrogate, uchar2);
|
|
out += g_unichar_to_utf8(uchar, out);
|
|
} else {
|
|
/*
|
|
* Not a trail surrogate.
|
|
* Ignore the entire pair.
|
|
* XXX - insert "substitute" character?
|
|
* Report the error in some other fashion?
|
|
*/
|
|
;
|
|
}
|
|
} else {
|
|
if (IS_TRAIL_SURROGATE(uchar2)) {
|
|
/*
|
|
* Trail surrogate without a preceding
|
|
* lead surrogate. Ignore it.
|
|
* XXX - insert "substitute" character?
|
|
* Report the error in some other fashion?
|
|
*/
|
|
;
|
|
} else {
|
|
/*
|
|
* Non-surrogate; just count it.
|
|
*/
|
|
out += g_unichar_to_utf8(uchar2, out);
|
|
}
|
|
}
|
|
}
|
|
*out = '\0';
|
|
|
|
/*
|
|
* XXX - if i < length, this means we were handed an odd
|
|
* number of bytes, so it was not a valid UTF-16 string.
|
|
*/
|
|
return result;
|
|
}
|
|
|
|
|
|
static void netmonrec_comment_destroy(gpointer key) {
|
|
struct netmonrec_comment *comment = (struct netmonrec_comment*) key;
|
|
|
|
g_free(comment->title);
|
|
g_free(comment->description);
|
|
g_free(comment);
|
|
}
|
|
|
|
static void netmonrec_process_info_destroy(gpointer key) {
|
|
struct netmonrec_process_info *process_info = (struct netmonrec_process_info*) key;
|
|
|
|
g_free(process_info->path);
|
|
g_free(process_info->iconData);
|
|
g_free(process_info);
|
|
}
|
|
|
|
wtap_open_return_val netmon_open(wtap *wth, int *err, gchar **err_info)
|
|
{
|
|
char magic[MAGIC_SIZE];
|
|
struct netmon_hdr hdr;
|
|
int file_type;
|
|
struct tm tm;
|
|
guint32 frame_table_offset;
|
|
guint32 frame_table_length;
|
|
guint32 frame_table_size;
|
|
guint32 *frame_table;
|
|
guint32 comment_table_offset, process_info_table_offset;
|
|
guint32 comment_table_size, process_info_table_count;
|
|
GHashTable *comment_table, *process_info_table;
|
|
struct netmonrec_comment* comment_rec;
|
|
gint64 file_size = wtap_file_size(wth, err);
|
|
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
|
unsigned int i;
|
|
#endif
|
|
netmon_t *netmon;
|
|
|
|
/* Read in the string that should be at the start of a Network
|
|
* Monitor file */
|
|
if (!wtap_read_bytes(wth->fh, magic, MAGIC_SIZE, err, err_info)) {
|
|
if (*err != WTAP_ERR_SHORT_READ)
|
|
return WTAP_OPEN_ERROR;
|
|
return WTAP_OPEN_NOT_MINE;
|
|
}
|
|
|
|
if (memcmp(magic, netmon_1_x_magic, MAGIC_SIZE) != 0 &&
|
|
memcmp(magic, netmon_2_x_magic, MAGIC_SIZE) != 0) {
|
|
return WTAP_OPEN_NOT_MINE;
|
|
}
|
|
|
|
/* Read the rest of the header. */
|
|
if (!wtap_read_bytes(wth->fh, &hdr, sizeof hdr, err, err_info))
|
|
return WTAP_OPEN_ERROR;
|
|
|
|
switch (hdr.ver_major) {
|
|
|
|
case 1:
|
|
file_type = WTAP_FILE_TYPE_SUBTYPE_NETMON_1_x;
|
|
break;
|
|
|
|
case 2:
|
|
file_type = WTAP_FILE_TYPE_SUBTYPE_NETMON_2_x;
|
|
break;
|
|
|
|
default:
|
|
*err = WTAP_ERR_UNSUPPORTED;
|
|
*err_info = g_strdup_printf("netmon: major version %u unsupported", hdr.ver_major);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
hdr.network = pletoh16(&hdr.network);
|
|
if (hdr.network >= NUM_NETMON_ENCAPS
|
|
|| netmon_encap[hdr.network] == WTAP_ENCAP_UNKNOWN) {
|
|
*err = WTAP_ERR_UNSUPPORTED;
|
|
*err_info = g_strdup_printf("netmon: network type %u unknown or unsupported",
|
|
hdr.network);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/* This is a netmon file */
|
|
wth->file_type_subtype = file_type;
|
|
netmon = (netmon_t *)g_malloc0(sizeof(netmon_t));
|
|
wth->priv = (void *)netmon;
|
|
wth->subtype_read = netmon_read;
|
|
wth->subtype_seek_read = netmon_seek_read;
|
|
wth->subtype_close = netmon_close;
|
|
|
|
/* NetMon capture file formats v2.1+ use per-packet encapsulation types. NetMon 3 sets the value in
|
|
* the header to 1 (Ethernet) for backwards compability. */
|
|
if((hdr.ver_major == 2 && hdr.ver_minor >= 1) || hdr.ver_major > 2)
|
|
wth->file_encap = WTAP_ENCAP_PER_PACKET;
|
|
else
|
|
wth->file_encap = netmon_encap[hdr.network];
|
|
|
|
wth->snapshot_length = 0; /* not available in header */
|
|
/*
|
|
* Convert the time stamp to a "time_t" and a number of
|
|
* milliseconds.
|
|
*/
|
|
tm.tm_year = pletoh16(&hdr.ts_year) - 1900;
|
|
tm.tm_mon = pletoh16(&hdr.ts_month) - 1;
|
|
tm.tm_mday = pletoh16(&hdr.ts_day);
|
|
tm.tm_hour = pletoh16(&hdr.ts_hour);
|
|
tm.tm_min = pletoh16(&hdr.ts_min);
|
|
tm.tm_sec = pletoh16(&hdr.ts_sec);
|
|
tm.tm_isdst = -1;
|
|
netmon->start_secs = mktime(&tm);
|
|
/*
|
|
* XXX - what if "secs" is -1? Unlikely, but if the capture was
|
|
* done in a time zone that switches between standard and summer
|
|
* time sometime other than when we do, and thus the time was one
|
|
* that doesn't exist here because a switch from standard to summer
|
|
* time zips over it, it could happen.
|
|
*
|
|
* On the other hand, if the capture was done in a different time
|
|
* zone, this won't work right anyway; unfortunately, the time
|
|
* zone isn't stored in the capture file (why the hell didn't
|
|
* they stuff a FILETIME, which is the number of 100-nanosecond
|
|
* intervals since 1601-01-01 00:00:00 "UTC", there, instead
|
|
* of stuffing a SYSTEMTIME, which is time-zone-dependent, there?).
|
|
*/
|
|
netmon->start_nsecs = pletoh16(&hdr.ts_msec)*1000000;
|
|
|
|
netmon->version_major = hdr.ver_major;
|
|
netmon->version_minor = hdr.ver_minor;
|
|
|
|
/*
|
|
* Get the offset of the frame index table.
|
|
*/
|
|
frame_table_offset = pletoh32(&hdr.frametableoffset);
|
|
|
|
/*
|
|
* For NetMon 2.2 format and later, get the offset and length of
|
|
* the comment index table and process info table.
|
|
*
|
|
* For earlier versions, set them to zero; they appear to be
|
|
* uninitialized, so they're not necessarily zero.
|
|
*/
|
|
if ((netmon->version_major == 2 && netmon->version_minor >= 2) ||
|
|
netmon->version_major > 2) {
|
|
comment_table_offset = pletoh32(&hdr.commentdataoffset);
|
|
comment_table_size = pletoh32(&hdr.commentdatalength);
|
|
process_info_table_offset = pletoh32(&hdr.processinfooffset);
|
|
process_info_table_count = pletoh32(&hdr.processinfocount);
|
|
} else {
|
|
comment_table_offset = 0;
|
|
comment_table_size = 0;
|
|
process_info_table_offset = 0;
|
|
process_info_table_count = 0;
|
|
}
|
|
|
|
/*
|
|
* It appears that some NetMon 2.x files don't have the
|
|
* first packet starting exactly 128 bytes into the file.
|
|
*
|
|
* Furthermore, it also appears that there are "holes" in
|
|
* the file, i.e. frame N+1 doesn't always follow immediately
|
|
* after frame N.
|
|
*
|
|
* Therefore, we must read the frame table, and use the offsets
|
|
* in it as the offsets of the frames.
|
|
*/
|
|
frame_table_length = pletoh32(&hdr.frametablelength);
|
|
frame_table_size = frame_table_length / (guint32)sizeof (guint32);
|
|
if ((frame_table_size * sizeof (guint32)) != frame_table_length) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: frame table length is %u, which is not a multiple of the size of an entry",
|
|
frame_table_length);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
if (frame_table_size == 0) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: frame table length is %u, which means it's less than one entry in size",
|
|
frame_table_length);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
/*
|
|
* XXX - clamp the size of the frame table, so that we don't
|
|
* attempt to allocate a huge frame table and fail.
|
|
*
|
|
* Given that file offsets in the frame table are 32-bit,
|
|
* a NetMon file cannot be bigger than 2^32 bytes.
|
|
* Given that a NetMon 1.x-format packet header is 8 bytes,
|
|
* that means a NetMon file cannot have more than
|
|
* 512*2^20 packets. We'll pick that as the limit for
|
|
* now; it's 1/8th of a 32-bit address space, which is
|
|
* probably not going to exhaust the address space all by
|
|
* itself, and probably won't exhaust the backing store.
|
|
*/
|
|
if (frame_table_size > 512*1024*1024) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: frame table length is %u, which is larger than we support",
|
|
frame_table_length);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
if (file_seek(wth->fh, frame_table_offset, SEEK_SET, err) == -1) {
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Sanity check the comment table information before we bother to allocate
|
|
* large chunks of memory for the frame table
|
|
*/
|
|
if (comment_table_size > 0) {
|
|
/*
|
|
* XXX - clamp the size of the comment table, so that we don't
|
|
* attempt to allocate a huge comment table and fail.
|
|
*
|
|
* Just use same size requires as frame table
|
|
*/
|
|
if (comment_table_size > 512*1024*1024) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: comment table size is %u, which is larger than we support",
|
|
comment_table_size);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
if (comment_table_size < 17) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: comment table size is %u, which is too small to use",
|
|
comment_table_size);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
if (comment_table_offset > file_size) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: comment table offset (%u) is larger than file",
|
|
comment_table_offset);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Sanity check the process info table information before we bother to allocate
|
|
* large chunks of memory for the frame table
|
|
*/
|
|
if ((process_info_table_offset > 0) && (process_info_table_count > 0)) {
|
|
/*
|
|
* XXX - clamp the size of the process info table, so that we don't
|
|
* attempt to allocate a huge process info table and fail.
|
|
*/
|
|
if (process_info_table_count > 512*1024) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: process info table size is %u, which is larger than we support",
|
|
process_info_table_count);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
if (process_info_table_offset > file_size) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: process info table offset (%u) is larger than file",
|
|
process_info_table_offset);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return back to the frame table offset
|
|
*/
|
|
if (file_seek(wth->fh, frame_table_offset, SEEK_SET, err) == -1) {
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Sanity check the process info table information before we bother to allocate
|
|
* large chunks of memory for the frame table
|
|
*/
|
|
|
|
frame_table = (guint32 *)g_try_malloc(frame_table_length);
|
|
if (frame_table_length != 0 && frame_table == NULL) {
|
|
*err = ENOMEM; /* we assume we're out of memory */
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
if (!wtap_read_bytes(wth->fh, frame_table, frame_table_length,
|
|
err, err_info)) {
|
|
g_free(frame_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
netmon->frame_table_size = frame_table_size;
|
|
netmon->frame_table = frame_table;
|
|
|
|
if (comment_table_size > 0) {
|
|
comment_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, netmonrec_comment_destroy);
|
|
if (comment_table == NULL) {
|
|
*err = ENOMEM; /* we assume we're out of memory */
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/* Make sure the file contains the full comment section */
|
|
if (file_seek(wth->fh, comment_table_offset+comment_table_size, SEEK_SET, err) == -1) {
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
if (file_seek(wth->fh, comment_table_offset, SEEK_SET, err) == -1) {
|
|
/* Shouldn't fail... */
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
while (comment_table_size > 16) {
|
|
struct netmonrec_comment_header comment_header;
|
|
guint32 title_length;
|
|
guint32 desc_length;
|
|
guint8 *utf16_str;
|
|
|
|
/* Read the first 12 bytes of the structure */
|
|
if (!wtap_read_bytes(wth->fh, &comment_header, 12, err, err_info)) {
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
comment_table_size -= 12;
|
|
|
|
/* Make sure comment size is sane */
|
|
title_length = pletoh32(&comment_header.titleLength);
|
|
if (title_length == 0) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup("netmon: comment title size can't be 0");
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
if (title_length > comment_table_size) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: comment title size is %u, which is larger than the amount remaining in the comment section (%u)",
|
|
title_length, comment_table_size);
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
comment_rec = g_new0(struct netmonrec_comment, 1);
|
|
comment_rec->numFramePerComment = pletoh32(&comment_header.numFramePerComment);
|
|
comment_rec->frameOffset = pletoh32(&comment_header.frameOffset);
|
|
|
|
g_hash_table_insert(comment_table, GUINT_TO_POINTER(comment_rec->frameOffset), comment_rec);
|
|
|
|
/*
|
|
* Read in the comment title.
|
|
*
|
|
* It is in UTF-16-encoded Unicode, and the title
|
|
* size is a count of octets, not octet pairs or
|
|
* Unicode characters.
|
|
*/
|
|
utf16_str = (guint8*)g_malloc(title_length);
|
|
if (!wtap_read_bytes(wth->fh, utf16_str, title_length,
|
|
err, err_info)) {
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
comment_table_size -= title_length;
|
|
|
|
/*
|
|
* Now convert it to UTF-8 for internal use.
|
|
*/
|
|
comment_rec->title = utf_16_to_utf_8(utf16_str,
|
|
title_length);
|
|
g_free(utf16_str);
|
|
|
|
if (comment_table_size < 4) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup("netmon: corrupt comment section");
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
if (!wtap_read_bytes(wth->fh, &desc_length, 4, err, err_info)) {
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
comment_table_size -= 4;
|
|
|
|
comment_rec->descLength = pletoh32(&desc_length);
|
|
if (comment_rec->descLength > 0) {
|
|
/* Make sure comment size is sane */
|
|
if (comment_rec->descLength > comment_table_size) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: comment description size is %u, which is larger than the amount remaining in the comment section (%u)",
|
|
comment_rec->descLength, comment_table_size);
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
comment_rec->description = (guint8*)g_malloc(comment_rec->descLength);
|
|
|
|
/* Read the comment description */
|
|
if (!wtap_read_bytes(wth->fh, comment_rec->description, comment_rec->descLength, err, err_info)) {
|
|
g_hash_table_destroy(comment_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
comment_table_size -= comment_rec->descLength;
|
|
}
|
|
}
|
|
netmon->comment_table = comment_table;
|
|
}
|
|
|
|
if ((process_info_table_offset > 0) && (process_info_table_count > 0)) {
|
|
guint16 version;
|
|
|
|
/* Go to the process table offset */
|
|
if (file_seek(wth->fh, process_info_table_offset, SEEK_SET, err) == -1) {
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
process_info_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, netmonrec_process_info_destroy);
|
|
if (process_info_table == NULL) {
|
|
*err = ENOMEM; /* we assume we're out of memory */
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/* Read the version (ignored for now) */
|
|
if (!wtap_read_bytes(wth->fh, &version, 2, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
while (process_info_table_count > 0)
|
|
{
|
|
struct netmonrec_process_info* process_info;
|
|
guint32 tmp32;
|
|
guint16 tmp16;
|
|
guint32 path_size;
|
|
guint8 *utf16_str;
|
|
|
|
process_info = g_new0(struct netmonrec_process_info, 1);
|
|
|
|
/* Read path */
|
|
if (!wtap_read_bytes(wth->fh, &tmp32, 4, err, err_info)) {
|
|
g_free(process_info);
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
path_size = pletoh32(&tmp32);
|
|
if (path_size > MATH_PROCINFO_PATH_SIZE) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: Path size for process info record is %u, which is larger than allowed max value (%u)",
|
|
path_size, MATH_PROCINFO_PATH_SIZE);
|
|
g_free(process_info);
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Read in the path string.
|
|
*
|
|
* It is in UTF-16-encoded Unicode, and the path
|
|
* size is a count of octets, not octet pairs or
|
|
* Unicode characters.
|
|
*/
|
|
utf16_str = (guint8*)g_malloc(path_size);
|
|
if (!wtap_read_bytes(wth->fh, utf16_str, path_size,
|
|
err, err_info)) {
|
|
g_free(process_info);
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Now convert it to UTF-8 for internal use.
|
|
*/
|
|
process_info->path = utf_16_to_utf_8(utf16_str,
|
|
path_size);
|
|
g_free(utf16_str);
|
|
|
|
/* Read icon (currently not saved) */
|
|
if (!wtap_read_bytes(wth->fh, &tmp32, 4, err, err_info)) {
|
|
g_free(process_info);
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
process_info->iconSize = pletoh32(&tmp32);
|
|
|
|
/* XXX - skip the icon for now */
|
|
if (file_seek(wth->fh, process_info->iconSize, SEEK_CUR, err) == -1) {
|
|
g_free(process_info);
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
process_info->iconSize = 0;
|
|
|
|
if (!wtap_read_bytes(wth->fh, &tmp32, 4, err, err_info)) {
|
|
g_free(process_info);
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
process_info->pid = pletoh32(&tmp32);
|
|
|
|
/* XXX - Currently index process information by PID */
|
|
g_hash_table_insert(process_info_table, GUINT_TO_POINTER(process_info->pid), process_info);
|
|
|
|
/* Read local port */
|
|
if (!wtap_read_bytes(wth->fh, &tmp16, 2, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
process_info->localPort = pletoh16(&tmp16);
|
|
|
|
/* Skip padding */
|
|
if (!wtap_read_bytes(wth->fh, &tmp16, 2, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/* Read remote port */
|
|
if (!wtap_read_bytes(wth->fh, &tmp16, 2, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
process_info->remotePort = pletoh16(&tmp16);
|
|
|
|
/* Skip padding */
|
|
if (!wtap_read_bytes(wth->fh, &tmp16, 2, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/* Determine IP version */
|
|
if (!wtap_read_bytes(wth->fh, &tmp32, 4, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
process_info->isIPv6 = ((pletoh32(&tmp32) == 0) ? FALSE : TRUE);
|
|
|
|
if (process_info->isIPv6) {
|
|
if (!wtap_read_bytes(wth->fh, &process_info->localAddr.ipv6, 16, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
if (!wtap_read_bytes(wth->fh, &process_info->remoteAddr.ipv6, 16, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
} else {
|
|
guint8 ipbuffer[16];
|
|
if (!wtap_read_bytes(wth->fh, ipbuffer, 16, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
process_info->localAddr.ipv4 = pletoh32(ipbuffer);
|
|
|
|
if (!wtap_read_bytes(wth->fh, ipbuffer, 16, err, err_info)) {
|
|
g_hash_table_destroy(process_info_table);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
process_info->remoteAddr.ipv4 = pletoh32(ipbuffer);
|
|
}
|
|
|
|
process_info_table_count--;
|
|
}
|
|
|
|
netmon->process_info_table = process_info_table;
|
|
}
|
|
|
|
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
|
/*
|
|
* OK, now byte-swap the frame table.
|
|
*/
|
|
for (i = 0; i < frame_table_size; i++)
|
|
frame_table[i] = pletoh32(&frame_table[i]);
|
|
#endif
|
|
|
|
/* Set up to start reading at the first frame. */
|
|
netmon->current_frame = 0;
|
|
switch (netmon->version_major) {
|
|
|
|
case 1:
|
|
/*
|
|
* Version 1.x of the file format supports
|
|
* millisecond precision.
|
|
*/
|
|
wth->file_tsprec = WTAP_TSPREC_MSEC;
|
|
break;
|
|
|
|
case 2:
|
|
/*
|
|
* Version 1.x of the file format supports
|
|
* 100-nanosecond precision; we don't
|
|
* currently support that, so say
|
|
* "nanosecond precision" for now.
|
|
*/
|
|
wth->file_tsprec = WTAP_TSPREC_NSEC;
|
|
break;
|
|
}
|
|
return WTAP_OPEN_MINE;
|
|
}
|
|
|
|
static void
|
|
netmon_set_pseudo_header_info(wtap_rec *rec, Buffer *buf)
|
|
{
|
|
switch (rec->rec_header.packet_header.pkt_encap) {
|
|
|
|
case WTAP_ENCAP_ATM_PDUS:
|
|
/*
|
|
* Attempt to guess from the packet data, the VPI, and
|
|
* the VCI information about the type of traffic.
|
|
*/
|
|
atm_guess_traffic_type(rec, ws_buffer_start_ptr(buf));
|
|
break;
|
|
|
|
case WTAP_ENCAP_ETHERNET:
|
|
/*
|
|
* We assume there's no FCS in this frame.
|
|
*/
|
|
rec->rec_header.packet_header.pseudo_header.eth.fcs_len = 0;
|
|
break;
|
|
|
|
case WTAP_ENCAP_IEEE_802_11_NETMON:
|
|
/*
|
|
* It appears to be the case that management
|
|
* frames (and control and extension frames ?) may
|
|
* or may not have an FCS and data frames don't.
|
|
* (Netmon capture files have been seen for this
|
|
* encapsulation having management frames either
|
|
* completely with or without an FCS. Also: instances have been
|
|
* seen where both Management and Control frames
|
|
* do not have an FCS).
|
|
* An "FCS length" of -2 means "NetMon weirdness".
|
|
*
|
|
* The metadata header also has a bit indicating whether
|
|
* the adapter was in monitor mode or not; if it isn't,
|
|
* we set "decrypted" to TRUE, as, for those frames, the
|
|
* Protected bit is preserved in received frames, but
|
|
* the frame is decrypted.
|
|
*/
|
|
memset(&rec->rec_header.packet_header.pseudo_header.ieee_802_11, 0, sizeof(rec->rec_header.packet_header.pseudo_header.ieee_802_11));
|
|
rec->rec_header.packet_header.pseudo_header.ieee_802_11.fcs_len = -2;
|
|
rec->rec_header.packet_header.pseudo_header.ieee_802_11.decrypted = FALSE;
|
|
rec->rec_header.packet_header.pseudo_header.ieee_802_11.datapad = FALSE;
|
|
rec->rec_header.packet_header.pseudo_header.ieee_802_11.phy = PHDR_802_11_PHY_UNKNOWN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
typedef enum {
|
|
SUCCESS,
|
|
FAILURE,
|
|
RETRY
|
|
} process_record_retval;
|
|
|
|
static process_record_retval
|
|
netmon_process_record(wtap *wth, FILE_T fh, wtap_rec *rec,
|
|
Buffer *buf, int *err, gchar **err_info)
|
|
{
|
|
netmon_t *netmon = (netmon_t *)wth->priv;
|
|
int hdr_size = 0;
|
|
union {
|
|
struct netmonrec_1_x_hdr hdr_1_x;
|
|
struct netmonrec_2_x_hdr hdr_2_x;
|
|
} hdr;
|
|
gint64 delta = 0; /* signed - frame times can be before the nominal start */
|
|
gint64 t;
|
|
time_t secs;
|
|
int nsecs;
|
|
guint32 packet_size = 0;
|
|
guint32 orig_size = 0;
|
|
int trlr_size;
|
|
union {
|
|
struct netmonrec_2_1_trlr trlr_2_1;
|
|
struct netmonrec_2_2_trlr trlr_2_2;
|
|
struct netmonrec_2_3_trlr trlr_2_3;
|
|
} trlr;
|
|
guint16 network;
|
|
int pkt_encap;
|
|
struct netmonrec_comment* comment_rec = NULL;
|
|
|
|
/* Read record header. */
|
|
switch (netmon->version_major) {
|
|
|
|
case 1:
|
|
hdr_size = sizeof (struct netmonrec_1_x_hdr);
|
|
break;
|
|
|
|
case 2:
|
|
hdr_size = sizeof (struct netmonrec_2_x_hdr);
|
|
break;
|
|
}
|
|
if (!wtap_read_bytes_or_eof(fh, &hdr, hdr_size, err, err_info))
|
|
return FAILURE;
|
|
|
|
switch (netmon->version_major) {
|
|
|
|
case 1:
|
|
orig_size = pletoh16(&hdr.hdr_1_x.orig_len);
|
|
packet_size = pletoh16(&hdr.hdr_1_x.incl_len);
|
|
break;
|
|
|
|
case 2:
|
|
orig_size = pletoh32(&hdr.hdr_2_x.orig_len);
|
|
packet_size = pletoh32(&hdr.hdr_2_x.incl_len);
|
|
break;
|
|
}
|
|
if (packet_size > WTAP_MAX_PACKET_SIZE_STANDARD) {
|
|
/*
|
|
* Probably a corrupt capture file; don't blow up trying
|
|
* to allocate space for an immensely-large packet.
|
|
*/
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: File has %u-byte packet, bigger than maximum of %u",
|
|
packet_size, WTAP_MAX_PACKET_SIZE_STANDARD);
|
|
return FAILURE;
|
|
}
|
|
|
|
rec->rec_type = REC_TYPE_PACKET;
|
|
|
|
/*
|
|
* If this is an ATM packet, the first
|
|
* "sizeof (struct netmon_atm_hdr)" bytes have destination and
|
|
* source addresses (6 bytes - MAC addresses of some sort?)
|
|
* and the VPI and VCI; read them and generate the pseudo-header
|
|
* from them.
|
|
*/
|
|
switch (wth->file_encap) {
|
|
|
|
case WTAP_ENCAP_ATM_PDUS:
|
|
if (packet_size < sizeof (struct netmon_atm_hdr)) {
|
|
/*
|
|
* Uh-oh, the packet isn't big enough to even
|
|
* have a pseudo-header.
|
|
*/
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("netmon: ATM file has a %u-byte packet, too small to have even an ATM pseudo-header",
|
|
packet_size);
|
|
return FAILURE;
|
|
}
|
|
if (!netmon_read_atm_pseudoheader(fh, &rec->rec_header.packet_header.pseudo_header,
|
|
err, err_info))
|
|
return FAILURE; /* Read error */
|
|
|
|
/*
|
|
* Don't count the pseudo-header as part of the packet.
|
|
*/
|
|
orig_size -= (guint)sizeof (struct netmon_atm_hdr);
|
|
packet_size -= (guint)sizeof (struct netmon_atm_hdr);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (netmon->version_major) {
|
|
|
|
case 1:
|
|
/*
|
|
* According to Paul Long, this offset is unsigned.
|
|
* It's 32 bits, so the maximum value will fit in
|
|
* a gint64 such as delta, even after multiplying
|
|
* it by 1000000.
|
|
*
|
|
* pletoh32() returns a guint32; we cast it to gint64
|
|
* before multiplying, so that the product doesn't
|
|
* overflow a guint32.
|
|
*/
|
|
delta = ((gint64)pletoh32(&hdr.hdr_1_x.ts_delta))*1000000;
|
|
break;
|
|
|
|
case 2:
|
|
/*
|
|
* OK, this is weird. Microsoft's documentation
|
|
* says this is in microseconds and is a 64-bit
|
|
* unsigned number, but it can be negative; they
|
|
* say what appears to amount to "treat it as an
|
|
* unsigned number, multiply it by 10, and then
|
|
* interpret the resulting 64-bit quantity as a
|
|
* signed number". That operation can turn a
|
|
* value with the uppermost bit 0 to a value with
|
|
* the uppermost bit 1, hence turning a large
|
|
* positive number-of-microseconds into a small
|
|
* negative number-of-100-nanosecond-increments.
|
|
*/
|
|
delta = pletoh64(&hdr.hdr_2_x.ts_delta)*10;
|
|
|
|
/*
|
|
* OK, it's now a signed value in 100-nanosecond
|
|
* units. Now convert it to nanosecond units.
|
|
*/
|
|
delta *= 100;
|
|
break;
|
|
}
|
|
secs = 0;
|
|
t = netmon->start_nsecs + delta;
|
|
while (t < 0) {
|
|
/*
|
|
* Propagate a borrow into the seconds.
|
|
* The seconds is a time_t, and can be < 0
|
|
* (unlikely, as Windows didn't exist before
|
|
* January 1, 1970, 00:00:00 UTC), while the
|
|
* nanoseconds should be positive, as in
|
|
* "nanoseconds since the instant of time
|
|
* represented by the seconds".
|
|
*
|
|
* We do not want t to be negative, as, according
|
|
* to the C90 standard, "if either operand [of /
|
|
* or %] is negative, whether the result of the
|
|
* / operator is the largest integer less than or
|
|
* equal to the algebraic quotient or the smallest
|
|
* greater than or equal to the algebraic quotient
|
|
* is implementation-defined, as is the sign of
|
|
* the result of the % operator", and we want
|
|
* the result of the division and remainder
|
|
* operations to be the same on all platforms.
|
|
*/
|
|
t += 1000000000;
|
|
secs--;
|
|
}
|
|
secs += (time_t)(t/1000000000);
|
|
nsecs = (int)(t%1000000000);
|
|
rec->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;
|
|
rec->ts.secs = netmon->start_secs + secs;
|
|
rec->ts.nsecs = nsecs;
|
|
rec->rec_header.packet_header.caplen = packet_size;
|
|
rec->rec_header.packet_header.len = orig_size;
|
|
|
|
/*
|
|
* Read the packet data.
|
|
*/
|
|
if (!wtap_read_packet_bytes(fh, buf, rec->rec_header.packet_header.caplen, err, err_info))
|
|
return FAILURE;
|
|
|
|
/*
|
|
* For version 2.1 and later, there's additional information
|
|
* after the frame data.
|
|
*/
|
|
if ((netmon->version_major == 2 && netmon->version_minor >= 1) ||
|
|
netmon->version_major > 2) {
|
|
if (netmon->version_major > 2) {
|
|
/*
|
|
* Asssume 2.3 format, for now.
|
|
*/
|
|
trlr_size = (int)sizeof (struct netmonrec_2_3_trlr);
|
|
} else {
|
|
switch (netmon->version_minor) {
|
|
|
|
case 1:
|
|
trlr_size = (int)sizeof (struct netmonrec_2_1_trlr);
|
|
break;
|
|
|
|
case 2:
|
|
trlr_size = (int)sizeof (struct netmonrec_2_2_trlr);
|
|
break;
|
|
|
|
default:
|
|
trlr_size = (int)sizeof (struct netmonrec_2_3_trlr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!wtap_read_bytes(fh, &trlr, trlr_size, err, err_info))
|
|
return FAILURE;
|
|
|
|
network = pletoh16(trlr.trlr_2_1.network);
|
|
if ((network >= 0xE080) && (network <= 0xE08A)) {
|
|
/* These values "violate" the LINKTYPE_ media type values
|
|
* in Microsoft Analyzer and are considered a MAExportedMediaType,
|
|
* so they need their own WTAP_ types
|
|
*/
|
|
switch (network)
|
|
{
|
|
case 0xE080: // "WiFi Message"
|
|
pkt_encap = WTAP_ENCAP_IEEE_802_11;
|
|
break;
|
|
case 0xE081: // "Ndis Etw WiFi Channel Message"
|
|
case 0xE082: // "Fiddler Netmon Message"
|
|
case 0xE089: // "Pef Ndis Msg";
|
|
case 0xE08A: // "Pef Ndis Wifi Meta Msg";
|
|
*err = WTAP_ERR_UNSUPPORTED;
|
|
*err_info = g_strdup_printf("netmon: network type %u unknown or unsupported", network);
|
|
return FAILURE;
|
|
case 0xE083:
|
|
pkt_encap = WTAP_ENCAP_MA_WFP_CAPTURE_V4;
|
|
break;
|
|
case 0xE084:
|
|
pkt_encap = WTAP_ENCAP_MA_WFP_CAPTURE_V6;
|
|
break;
|
|
case 0xE085:
|
|
pkt_encap = WTAP_ENCAP_MA_WFP_CAPTURE_2V4;
|
|
break;
|
|
case 0xE086:
|
|
pkt_encap = WTAP_ENCAP_MA_WFP_CAPTURE_2V6;
|
|
break;
|
|
case 0xE087:
|
|
pkt_encap = WTAP_ENCAP_MA_WFP_CAPTURE_AUTH_V4;
|
|
break;
|
|
case 0xE088:
|
|
pkt_encap = WTAP_ENCAP_MA_WFP_CAPTURE_AUTH_V6;
|
|
break;
|
|
default:
|
|
pkt_encap = WTAP_ENCAP_UNKNOWN;
|
|
break;
|
|
}
|
|
} else if ((network & 0xF000) == NETMON_NET_PCAP_BASE) {
|
|
/*
|
|
* Converted pcap file - the LINKTYPE_ value
|
|
* is the network value with 0xF000 masked off.
|
|
*/
|
|
network &= 0x0FFF;
|
|
pkt_encap = wtap_pcap_encap_to_wtap_encap(network);
|
|
if (pkt_encap == WTAP_ENCAP_UNKNOWN) {
|
|
*err = WTAP_ERR_UNSUPPORTED;
|
|
*err_info = g_strdup_printf("netmon: converted pcap network type %u unknown or unsupported",
|
|
network);
|
|
return FAILURE;
|
|
}
|
|
} else if (network < NUM_NETMON_ENCAPS) {
|
|
/*
|
|
* Regular NetMon encapsulation.
|
|
*/
|
|
pkt_encap = netmon_encap[network];
|
|
if (pkt_encap == WTAP_ENCAP_UNKNOWN) {
|
|
*err = WTAP_ERR_UNSUPPORTED;
|
|
*err_info = g_strdup_printf("netmon: network type %u unknown or unsupported",
|
|
network);
|
|
return FAILURE;
|
|
}
|
|
} else {
|
|
/*
|
|
* Special packet type for metadata.
|
|
*/
|
|
switch (network) {
|
|
|
|
case NETMON_NET_NETEVENT:
|
|
/*
|
|
* Event Tracing event.
|
|
*
|
|
* https://docs.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_header
|
|
*/
|
|
pkt_encap = WTAP_ENCAP_NETMON_NET_NETEVENT;
|
|
break;
|
|
|
|
case NETMON_NET_NETWORK_INFO_EX:
|
|
/*
|
|
* List of adapters on which the capture
|
|
* was done.
|
|
* XXX - this could be translated into pcapng
|
|
* blocks but for now, just treat as a frame.
|
|
*/
|
|
pkt_encap = WTAP_ENCAP_NETMON_NETWORK_INFO_EX;
|
|
break;
|
|
|
|
case NETMON_NET_PAYLOAD_HEADER:
|
|
/*
|
|
* Header for a fake frame constructed
|
|
* by reassembly.
|
|
*/
|
|
return RETRY;
|
|
|
|
case NETMON_NET_NETWORK_INFO:
|
|
/*
|
|
* List of adapters on which the capture
|
|
* was done.
|
|
*/
|
|
return RETRY;
|
|
|
|
case NETMON_NET_DNS_CACHE:
|
|
/*
|
|
* List of resolved IP addresses.
|
|
*/
|
|
return RETRY;
|
|
|
|
case NETMON_NET_NETMON_FILTER:
|
|
/*
|
|
* NetMon capture or display filter
|
|
* string.
|
|
*/
|
|
pkt_encap = WTAP_ENCAP_NETMON_NET_FILTER;
|
|
break;
|
|
|
|
default:
|
|
*err = WTAP_ERR_UNSUPPORTED;
|
|
*err_info = g_strdup_printf("netmon: network type %u unknown or unsupported",
|
|
network);
|
|
return FAILURE;
|
|
}
|
|
}
|
|
|
|
rec->rec_header.packet_header.pkt_encap = pkt_encap;
|
|
if (netmon->version_major > 2 || netmon->version_minor > 2) {
|
|
guint64 d;
|
|
|
|
d = pletoh64(trlr.trlr_2_3.utc_timestamp);
|
|
|
|
/*
|
|
* Get the time as seconds and nanoseconds.
|
|
* and overwrite the time stamp obtained
|
|
* from the record header.
|
|
*/
|
|
if (!filetime_to_nstime(&rec->ts, d)) {
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup("netmon: time stamp outside supported range");
|
|
return FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
netmon_set_pseudo_header_info(rec, buf);
|
|
|
|
/* If any header specific information is present, set it as pseudo header data
|
|
* and set the encapsulation type, so it can be handled to the netmon_header
|
|
* dissector for further processing
|
|
*/
|
|
if (netmon->comment_table != NULL) {
|
|
comment_rec = (struct netmonrec_comment*)g_hash_table_lookup(netmon->comment_table, GUINT_TO_POINTER(netmon->frame_table[netmon->current_frame-1]));
|
|
}
|
|
|
|
if (comment_rec != NULL) {
|
|
union wtap_pseudo_header temp_header;
|
|
|
|
/* These are the current encapsulation types that NetMon uses.
|
|
* Save them off so they can be copied to the NetMon pseudoheader
|
|
*/
|
|
switch (rec->rec_header.packet_header.pkt_encap)
|
|
{
|
|
case WTAP_ENCAP_ATM_PDUS:
|
|
memcpy(&temp_header.atm, &rec->rec_header.packet_header.pseudo_header.atm, sizeof(temp_header.atm));
|
|
break;
|
|
case WTAP_ENCAP_ETHERNET:
|
|
memcpy(&temp_header.eth, &rec->rec_header.packet_header.pseudo_header.eth, sizeof(temp_header.eth));
|
|
break;
|
|
case WTAP_ENCAP_IEEE_802_11_NETMON:
|
|
memcpy(&temp_header.ieee_802_11, &rec->rec_header.packet_header.pseudo_header.ieee_802_11, sizeof(temp_header.ieee_802_11));
|
|
break;
|
|
}
|
|
memset(&rec->rec_header.packet_header.pseudo_header.netmon, 0, sizeof(rec->rec_header.packet_header.pseudo_header.netmon));
|
|
|
|
/* Save the current encapsulation type to the NetMon pseudoheader */
|
|
rec->rec_header.packet_header.pseudo_header.netmon.sub_encap = rec->rec_header.packet_header.pkt_encap;
|
|
|
|
/* Copy the comment data */
|
|
rec->rec_header.packet_header.pseudo_header.netmon.title = comment_rec->title;
|
|
rec->rec_header.packet_header.pseudo_header.netmon.descLength = comment_rec->descLength;
|
|
rec->rec_header.packet_header.pseudo_header.netmon.description = comment_rec->description;
|
|
|
|
/* Copy the saved pseudoheaders to the netmon pseudoheader structure */
|
|
switch (rec->rec_header.packet_header.pkt_encap)
|
|
{
|
|
case WTAP_ENCAP_ATM_PDUS:
|
|
memcpy(&rec->rec_header.packet_header.pseudo_header.netmon.subheader.atm, &temp_header.atm, sizeof(temp_header.atm));
|
|
break;
|
|
case WTAP_ENCAP_ETHERNET:
|
|
memcpy(&rec->rec_header.packet_header.pseudo_header.netmon.subheader.eth, &temp_header.eth, sizeof(temp_header.eth));
|
|
break;
|
|
case WTAP_ENCAP_IEEE_802_11_NETMON:
|
|
memcpy(&rec->rec_header.packet_header.pseudo_header.netmon.subheader.ieee_802_11, &temp_header.ieee_802_11, sizeof(temp_header.ieee_802_11));
|
|
break;
|
|
}
|
|
|
|
/* Encapsulation type is now something that can be passed to netmon_header dissector */
|
|
rec->rec_header.packet_header.pkt_encap = WTAP_ENCAP_NETMON_HEADER;
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
/* Read the next packet */
|
|
static gboolean netmon_read(wtap *wth, wtap_rec *rec, Buffer *buf,
|
|
int *err, gchar **err_info, gint64 *data_offset)
|
|
{
|
|
netmon_t *netmon = (netmon_t *)wth->priv;
|
|
gint64 rec_offset;
|
|
|
|
for (;;) {
|
|
/* Have we reached the end of the packet data? */
|
|
if (netmon->current_frame >= netmon->frame_table_size) {
|
|
*err = 0; /* it's just an EOF, not an error */
|
|
return FALSE;
|
|
}
|
|
|
|
/* Seek to the beginning of the current record, if we're
|
|
not there already (seeking to the current position
|
|
may still cause a seek and a read of the underlying file,
|
|
so we don't want to do it unconditionally).
|
|
|
|
Yes, the current record could be before the previous
|
|
record. At least some captures put the trailer record
|
|
with statistics as the first physical record in the
|
|
file, but set the frame table up so it's the last
|
|
record in sequence. */
|
|
rec_offset = netmon->frame_table[netmon->current_frame];
|
|
if (file_tell(wth->fh) != rec_offset) {
|
|
if (file_seek(wth->fh, rec_offset, SEEK_SET, err) == -1)
|
|
return FALSE;
|
|
}
|
|
netmon->current_frame++;
|
|
|
|
*data_offset = file_tell(wth->fh);
|
|
|
|
switch (netmon_process_record(wth, wth->fh, rec, buf, err,
|
|
err_info)) {
|
|
|
|
case RETRY:
|
|
continue;
|
|
|
|
case SUCCESS:
|
|
return TRUE;
|
|
|
|
case FAILURE:
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
netmon_seek_read(wtap *wth, gint64 seek_off,
|
|
wtap_rec *rec, Buffer *buf, int *err, gchar **err_info)
|
|
{
|
|
if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
|
|
return FALSE;
|
|
|
|
switch (netmon_process_record(wth, wth->random_fh, rec, buf, err,
|
|
err_info)) {
|
|
|
|
default:
|
|
/*
|
|
* This should not happen.
|
|
*/
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup("netmon: saw metadata in netmon_seek_read");
|
|
return FALSE;
|
|
|
|
case SUCCESS:
|
|
return TRUE;
|
|
|
|
case FAILURE:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
netmon_read_atm_pseudoheader(FILE_T fh, union wtap_pseudo_header *pseudo_header,
|
|
int *err, gchar **err_info)
|
|
{
|
|
struct netmon_atm_hdr atm_phdr;
|
|
guint16 vpi, vci;
|
|
|
|
if (!wtap_read_bytes(fh, &atm_phdr, sizeof (struct netmon_atm_hdr),
|
|
err, err_info))
|
|
return FALSE;
|
|
|
|
vpi = g_ntohs(atm_phdr.vpi);
|
|
vci = g_ntohs(atm_phdr.vci);
|
|
|
|
pseudo_header->atm.vpi = vpi;
|
|
pseudo_header->atm.vci = vci;
|
|
|
|
/* We don't have this information */
|
|
pseudo_header->atm.flags = 0;
|
|
pseudo_header->atm.channel = 0;
|
|
pseudo_header->atm.cells = 0;
|
|
pseudo_header->atm.aal5t_u2u = 0;
|
|
pseudo_header->atm.aal5t_len = 0;
|
|
pseudo_header->atm.aal5t_chksum = 0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Throw away the frame table used by the sequential I/O stream. */
|
|
static void
|
|
netmon_close(wtap *wth)
|
|
{
|
|
netmon_t *netmon = (netmon_t *)wth->priv;
|
|
|
|
if (netmon->frame_table != NULL) {
|
|
g_free(netmon->frame_table);
|
|
netmon->frame_table = NULL;
|
|
}
|
|
|
|
if (netmon->comment_table != NULL) {
|
|
g_hash_table_destroy(netmon->comment_table);
|
|
netmon->comment_table = NULL;
|
|
}
|
|
|
|
if (netmon->process_info_table != NULL) {
|
|
g_hash_table_destroy(netmon->process_info_table);
|
|
netmon->process_info_table = NULL;
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
gboolean got_first_record_time;
|
|
nstime_t first_record_time;
|
|
guint32 frame_table_offset;
|
|
guint32 *frame_table;
|
|
guint frame_table_index;
|
|
guint frame_table_size;
|
|
gboolean no_more_room; /* TRUE if no more records can be written */
|
|
} netmon_dump_t;
|
|
|
|
static const int wtap_encap[] = {
|
|
-1, /* WTAP_ENCAP_UNKNOWN -> unsupported */
|
|
1, /* WTAP_ENCAP_ETHERNET -> NDIS Ethernet */
|
|
2, /* WTAP_ENCAP_TOKEN_RING -> NDIS Token Ring */
|
|
-1, /* WTAP_ENCAP_SLIP -> unsupported */
|
|
-1, /* WTAP_ENCAP_PPP -> unsupported */
|
|
3, /* WTAP_ENCAP_FDDI -> NDIS FDDI */
|
|
3, /* WTAP_ENCAP_FDDI_BITSWAPPED -> NDIS FDDI */
|
|
-1, /* WTAP_ENCAP_RAW_IP -> unsupported */
|
|
-1, /* WTAP_ENCAP_ARCNET -> unsupported */
|
|
-1, /* WTAP_ENCAP_ARCNET_LINUX -> unsupported */
|
|
-1, /* WTAP_ENCAP_ATM_RFC1483 -> unsupported */
|
|
-1, /* WTAP_ENCAP_LINUX_ATM_CLIP -> unsupported */
|
|
-1, /* WTAP_ENCAP_LAPB -> unsupported*/
|
|
4, /* WTAP_ENCAP_ATM_PDUS -> NDIS WAN (*NOT* ATM!) */
|
|
};
|
|
#define NUM_WTAP_ENCAPS (sizeof wtap_encap / sizeof wtap_encap[0])
|
|
|
|
/* Returns 0 if we could write the specified encapsulation type,
|
|
an error indication otherwise. */
|
|
int netmon_dump_can_write_encap_1_x(int encap)
|
|
{
|
|
/*
|
|
* Per-packet encapsulations are *not* supported in NetMon 1.x
|
|
* format.
|
|
*/
|
|
if (encap < 0 || (unsigned) encap >= NUM_WTAP_ENCAPS || wtap_encap[encap] == -1)
|
|
return WTAP_ERR_UNWRITABLE_ENCAP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int netmon_dump_can_write_encap_2_x(int encap)
|
|
{
|
|
/*
|
|
* Per-packet encapsulations are supported in NetMon 2.1
|
|
* format.
|
|
*/
|
|
if (encap == WTAP_ENCAP_PER_PACKET)
|
|
return 0;
|
|
|
|
if (encap < 0 || (unsigned) encap >= NUM_WTAP_ENCAPS || wtap_encap[encap] == -1)
|
|
return WTAP_ERR_UNWRITABLE_ENCAP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Returns TRUE on success, FALSE on failure; sets "*err" to an error code on
|
|
failure */
|
|
gboolean netmon_dump_open(wtap_dumper *wdh, int *err)
|
|
{
|
|
netmon_dump_t *netmon;
|
|
|
|
/* We can't fill in all the fields in the file header, as we
|
|
haven't yet written any packets. As we'll have to rewrite
|
|
the header when we've written out all the packets, we just
|
|
skip over the header for now. */
|
|
if (wtap_dump_file_seek(wdh, CAPTUREFILE_HEADER_SIZE, SEEK_SET, err) == -1)
|
|
return FALSE;
|
|
|
|
wdh->subtype_write = netmon_dump;
|
|
wdh->subtype_finish = netmon_dump_finish;
|
|
|
|
netmon = (netmon_dump_t *)g_malloc(sizeof(netmon_dump_t));
|
|
wdh->priv = (void *)netmon;
|
|
netmon->frame_table_offset = CAPTUREFILE_HEADER_SIZE;
|
|
netmon->got_first_record_time = FALSE;
|
|
netmon->frame_table = NULL;
|
|
netmon->frame_table_index = 0;
|
|
netmon->frame_table_size = 0;
|
|
netmon->no_more_room = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Write a record for a packet to a dump file.
|
|
Returns TRUE on success, FALSE on failure. */
|
|
static gboolean netmon_dump(wtap_dumper *wdh, const wtap_rec *rec,
|
|
const guint8 *pd, int *err, gchar **err_info _U_)
|
|
{
|
|
const union wtap_pseudo_header *pseudo_header = &rec->rec_header.packet_header.pseudo_header;
|
|
netmon_dump_t *netmon = (netmon_dump_t *)wdh->priv;
|
|
struct netmonrec_1_x_hdr rec_1_x_hdr;
|
|
struct netmonrec_2_x_hdr rec_2_x_hdr;
|
|
void *hdrp;
|
|
size_t rec_size;
|
|
struct netmonrec_2_1_trlr rec_2_x_trlr;
|
|
size_t hdr_size;
|
|
struct netmon_atm_hdr atm_hdr;
|
|
int atm_hdrsize;
|
|
gint64 secs;
|
|
gint32 nsecs;
|
|
|
|
/* We can only write packet records. */
|
|
if (rec->rec_type != REC_TYPE_PACKET) {
|
|
*err = WTAP_ERR_UNWRITABLE_REC_TYPE;
|
|
return FALSE;
|
|
}
|
|
|
|
switch (wdh->file_type_subtype) {
|
|
|
|
case WTAP_FILE_TYPE_SUBTYPE_NETMON_1_x:
|
|
/*
|
|
* The length fields are 16-bit, so there's a hard limit
|
|
* of 65535.
|
|
*/
|
|
if (rec->rec_header.packet_header.caplen > 65535) {
|
|
*err = WTAP_ERR_PACKET_TOO_LARGE;
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
case WTAP_FILE_TYPE_SUBTYPE_NETMON_2_x:
|
|
/* Don't write anything we're not willing to read. */
|
|
if (rec->rec_header.packet_header.caplen > WTAP_MAX_PACKET_SIZE_STANDARD) {
|
|
*err = WTAP_ERR_PACKET_TOO_LARGE;
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* We should never get here - our open routine
|
|
should only get called for the types above. */
|
|
*err = WTAP_ERR_UNWRITABLE_FILE_TYPE;
|
|
return FALSE;
|
|
}
|
|
|
|
if (wdh->encap == WTAP_ENCAP_PER_PACKET) {
|
|
/*
|
|
* Is this network type supported?
|
|
*/
|
|
if (rec->rec_header.packet_header.pkt_encap < 0 ||
|
|
(unsigned) rec->rec_header.packet_header.pkt_encap >= NUM_WTAP_ENCAPS ||
|
|
wtap_encap[rec->rec_header.packet_header.pkt_encap] == -1) {
|
|
/*
|
|
* No. Fail.
|
|
*/
|
|
*err = WTAP_ERR_UNWRITABLE_ENCAP;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Fill in the trailer with the network type.
|
|
*/
|
|
phtoles(rec_2_x_trlr.network, wtap_encap[rec->rec_header.packet_header.pkt_encap]);
|
|
}
|
|
|
|
/*
|
|
* Will the file offset of this frame fit in a 32-bit unsigned
|
|
* integer?
|
|
*/
|
|
if (netmon->no_more_room) {
|
|
/*
|
|
* No, so the file is too big for NetMon format to
|
|
* handle.
|
|
*/
|
|
*err = EFBIG;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* NetMon files have a capture start time in the file header,
|
|
* and have times relative to that in the packet headers;
|
|
* pick the time of the first packet as the capture start
|
|
* time.
|
|
*
|
|
* That time has millisecond resolution, so chop any
|
|
* sub-millisecond part of the time stamp off.
|
|
*/
|
|
if (!netmon->got_first_record_time) {
|
|
netmon->first_record_time.secs = rec->ts.secs;
|
|
netmon->first_record_time.nsecs =
|
|
(rec->ts.nsecs/1000000)*1000000;
|
|
netmon->got_first_record_time = TRUE;
|
|
}
|
|
|
|
if (wdh->encap == WTAP_ENCAP_ATM_PDUS)
|
|
atm_hdrsize = sizeof (struct netmon_atm_hdr);
|
|
else
|
|
atm_hdrsize = 0;
|
|
secs = (gint64)(rec->ts.secs - netmon->first_record_time.secs);
|
|
nsecs = rec->ts.nsecs - netmon->first_record_time.nsecs;
|
|
while (nsecs < 0) {
|
|
/*
|
|
* Propagate a borrow into the seconds.
|
|
* The seconds is a time_t, and can be < 0
|
|
* (unlikely, as neither UN*X nor DOS
|
|
* nor the original Mac System existed
|
|
* before January 1, 1970, 00:00:00 UTC),
|
|
* while the nanoseconds should be positive,
|
|
* as in "nanoseconds since the instant of time
|
|
* represented by the seconds".
|
|
*
|
|
* We do not want t to be negative, as, according
|
|
* to the C90 standard, "if either operand [of /
|
|
* or %] is negative, whether the result of the
|
|
* / operator is the largest integer less than or
|
|
* equal to the algebraic quotient or the smallest
|
|
* greater than or equal to the algebraic quotient
|
|
* is implementation-defined, as is the sign of
|
|
* the result of the % operator", and we want
|
|
* the result of the division and remainder
|
|
* operations to be the same on all platforms.
|
|
*/
|
|
nsecs += 1000000000;
|
|
secs--;
|
|
}
|
|
switch (wdh->file_type_subtype) {
|
|
|
|
case WTAP_FILE_TYPE_SUBTYPE_NETMON_1_x:
|
|
rec_1_x_hdr.ts_delta = GUINT32_TO_LE(secs*1000 + (nsecs + 500000)/1000000);
|
|
rec_1_x_hdr.orig_len = GUINT16_TO_LE(rec->rec_header.packet_header.len + atm_hdrsize);
|
|
rec_1_x_hdr.incl_len = GUINT16_TO_LE(rec->rec_header.packet_header.caplen + atm_hdrsize);
|
|
hdrp = &rec_1_x_hdr;
|
|
hdr_size = sizeof rec_1_x_hdr;
|
|
break;
|
|
|
|
case WTAP_FILE_TYPE_SUBTYPE_NETMON_2_x:
|
|
rec_2_x_hdr.ts_delta = GUINT64_TO_LE(secs*1000000 + (nsecs + 500)/1000);
|
|
rec_2_x_hdr.orig_len = GUINT32_TO_LE(rec->rec_header.packet_header.len + atm_hdrsize);
|
|
rec_2_x_hdr.incl_len = GUINT32_TO_LE(rec->rec_header.packet_header.caplen + atm_hdrsize);
|
|
hdrp = &rec_2_x_hdr;
|
|
hdr_size = sizeof rec_2_x_hdr;
|
|
break;
|
|
|
|
default:
|
|
/* We should never get here - our open routine
|
|
should only get called for the types above. */
|
|
*err = WTAP_ERR_UNWRITABLE_FILE_TYPE;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Keep track of the record size, as we need to update
|
|
* the current file offset.
|
|
*/
|
|
rec_size = 0;
|
|
|
|
if (!wtap_dump_file_write(wdh, hdrp, hdr_size, err))
|
|
return FALSE;
|
|
rec_size += hdr_size;
|
|
|
|
if (wdh->encap == WTAP_ENCAP_ATM_PDUS) {
|
|
/*
|
|
* Write the ATM header.
|
|
* We supply all-zero destination and source addresses.
|
|
*/
|
|
memset(&atm_hdr.dest, 0, sizeof atm_hdr.dest);
|
|
memset(&atm_hdr.src, 0, sizeof atm_hdr.src);
|
|
atm_hdr.vpi = g_htons(pseudo_header->atm.vpi);
|
|
atm_hdr.vci = g_htons(pseudo_header->atm.vci);
|
|
if (!wtap_dump_file_write(wdh, &atm_hdr, sizeof atm_hdr, err))
|
|
return FALSE;
|
|
rec_size += sizeof atm_hdr;
|
|
}
|
|
|
|
if (!wtap_dump_file_write(wdh, pd, rec->rec_header.packet_header.caplen, err))
|
|
return FALSE;
|
|
rec_size += rec->rec_header.packet_header.caplen;
|
|
|
|
if (wdh->encap == WTAP_ENCAP_PER_PACKET) {
|
|
/*
|
|
* Write out the trailer.
|
|
*/
|
|
if (!wtap_dump_file_write(wdh, &rec_2_x_trlr,
|
|
sizeof rec_2_x_trlr, err))
|
|
return FALSE;
|
|
rec_size += sizeof rec_2_x_trlr;
|
|
}
|
|
|
|
/*
|
|
* Stash the file offset of this frame.
|
|
*/
|
|
if (netmon->frame_table_size == 0) {
|
|
/*
|
|
* Haven't yet allocated the buffer for the frame table.
|
|
*/
|
|
netmon->frame_table = (guint32 *)g_malloc(1024 * sizeof *netmon->frame_table);
|
|
netmon->frame_table_size = 1024;
|
|
} else {
|
|
/*
|
|
* We've allocated it; are we at the end?
|
|
*/
|
|
if (netmon->frame_table_index >= netmon->frame_table_size) {
|
|
/*
|
|
* Yes - double the size of the frame table.
|
|
*/
|
|
netmon->frame_table_size *= 2;
|
|
netmon->frame_table = (guint32 *)g_realloc(netmon->frame_table,
|
|
netmon->frame_table_size * sizeof *netmon->frame_table);
|
|
}
|
|
}
|
|
|
|
netmon->frame_table[netmon->frame_table_index] =
|
|
GUINT32_TO_LE(netmon->frame_table_offset);
|
|
|
|
/*
|
|
* Is this the last record we can write?
|
|
* I.e., will the frame table offset of the next record not fit
|
|
* in a 32-bit frame table offset entry?
|
|
*
|
|
* (We don't bother checking whether the number of frames
|
|
* will fit in a 32-bit value, as, even if each record were
|
|
* 1 byte, if there were more than 2^32-1 packets, the frame
|
|
* table offset of at least one of those packets will be >
|
|
* 2^32 - 1.)
|
|
*
|
|
* Note: this also catches the unlikely possibility that
|
|
* the record itself is > 2^32 - 1 bytes long.
|
|
*/
|
|
if ((guint64)netmon->frame_table_offset + rec_size > G_MAXUINT32) {
|
|
/*
|
|
* Yup, too big.
|
|
*/
|
|
netmon->no_more_room = TRUE;
|
|
}
|
|
netmon->frame_table_index++;
|
|
netmon->frame_table_offset += (guint32) rec_size;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Finish writing to a dump file.
|
|
Returns TRUE on success, FALSE on failure. */
|
|
static gboolean netmon_dump_finish(wtap_dumper *wdh, int *err)
|
|
{
|
|
netmon_dump_t *netmon = (netmon_dump_t *)wdh->priv;
|
|
size_t n_to_write;
|
|
struct netmon_hdr file_hdr;
|
|
const char *magicp;
|
|
size_t magic_size;
|
|
struct tm *tm;
|
|
|
|
/* Write out the frame table. "netmon->frame_table_index" is
|
|
the number of entries we've put into it. */
|
|
n_to_write = netmon->frame_table_index * sizeof *netmon->frame_table;
|
|
if (!wtap_dump_file_write(wdh, netmon->frame_table, n_to_write, err))
|
|
return FALSE;
|
|
|
|
/* Now go fix up the file header. */
|
|
if (wtap_dump_file_seek(wdh, 0, SEEK_SET, err) == -1)
|
|
return FALSE;
|
|
memset(&file_hdr, '\0', sizeof file_hdr);
|
|
switch (wdh->file_type_subtype) {
|
|
|
|
case WTAP_FILE_TYPE_SUBTYPE_NETMON_1_x:
|
|
magicp = netmon_1_x_magic;
|
|
magic_size = sizeof netmon_1_x_magic;
|
|
/* NetMon file version, for 1.x, is 1.1 */
|
|
file_hdr.ver_major = 1;
|
|
file_hdr.ver_minor = 1;
|
|
break;
|
|
|
|
case WTAP_FILE_TYPE_SUBTYPE_NETMON_2_x:
|
|
magicp = netmon_2_x_magic;
|
|
magic_size = sizeof netmon_2_x_magic;
|
|
/*
|
|
* NetMon file version, for 2.x, is 2.0;
|
|
* for 3.0, it's 2.1.
|
|
*
|
|
* If the file encapsulation is WTAP_ENCAP_PER_PACKET,
|
|
* we need version 2.1.
|
|
*
|
|
* XXX - version 2.3 supports UTC time stamps; when
|
|
* should we use it? According to the file format
|
|
* documentation, NetMon 3.3 "cannot properly
|
|
* interpret" the UTC timestamp information; does
|
|
* that mean it ignores it and uses the local-time
|
|
* start time and time deltas, or mishandles them?
|
|
* Also, NetMon 3.1 and earlier can't read version
|
|
* 2.2, much less version 2.3.
|
|
*/
|
|
file_hdr.ver_major = 2;
|
|
file_hdr.ver_minor =
|
|
(wdh->encap == WTAP_ENCAP_PER_PACKET) ? 1 : 0;
|
|
break;
|
|
|
|
default:
|
|
/* We should never get here - our open routine
|
|
should only get called for the types above. */
|
|
if (err != NULL)
|
|
*err = WTAP_ERR_UNWRITABLE_FILE_TYPE;
|
|
return FALSE;
|
|
}
|
|
if (!wtap_dump_file_write(wdh, magicp, magic_size, err))
|
|
return FALSE;
|
|
|
|
if (wdh->encap == WTAP_ENCAP_PER_PACKET) {
|
|
/*
|
|
* We're writing NetMon 2.1 format, so the media
|
|
* type in the file header is irrelevant. Set it
|
|
* to 1, just as Network Monitor does.
|
|
*/
|
|
file_hdr.network = GUINT16_TO_LE(1);
|
|
} else
|
|
file_hdr.network = GUINT16_TO_LE(wtap_encap[wdh->encap]);
|
|
tm = localtime(&netmon->first_record_time.secs);
|
|
if (tm != NULL) {
|
|
file_hdr.ts_year = GUINT16_TO_LE(1900 + tm->tm_year);
|
|
file_hdr.ts_month = GUINT16_TO_LE(tm->tm_mon + 1);
|
|
file_hdr.ts_dow = GUINT16_TO_LE(tm->tm_wday);
|
|
file_hdr.ts_day = GUINT16_TO_LE(tm->tm_mday);
|
|
file_hdr.ts_hour = GUINT16_TO_LE(tm->tm_hour);
|
|
file_hdr.ts_min = GUINT16_TO_LE(tm->tm_min);
|
|
file_hdr.ts_sec = GUINT16_TO_LE(tm->tm_sec);
|
|
} else {
|
|
file_hdr.ts_year = GUINT16_TO_LE(1900 + 0);
|
|
file_hdr.ts_month = GUINT16_TO_LE(0 + 1);
|
|
file_hdr.ts_dow = GUINT16_TO_LE(0);
|
|
file_hdr.ts_day = GUINT16_TO_LE(0);
|
|
file_hdr.ts_hour = GUINT16_TO_LE(0);
|
|
file_hdr.ts_min = GUINT16_TO_LE(0);
|
|
file_hdr.ts_sec = GUINT16_TO_LE(0);
|
|
}
|
|
file_hdr.ts_msec = GUINT16_TO_LE(netmon->first_record_time.nsecs/1000000);
|
|
file_hdr.frametableoffset = GUINT32_TO_LE(netmon->frame_table_offset);
|
|
file_hdr.frametablelength =
|
|
GUINT32_TO_LE(netmon->frame_table_index * sizeof *netmon->frame_table);
|
|
if (!wtap_dump_file_write(wdh, &file_hdr, sizeof file_hdr, err))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Editor modelines - https://www.wireshark.org/tools/modelines.html
|
|
*
|
|
* Local variables:
|
|
* c-basic-offset: 8
|
|
* tab-width: 8
|
|
* indent-tabs-mode: t
|
|
* End:
|
|
*
|
|
* vi: set shiftwidth=8 tabstop=8 noexpandtab:
|
|
* :indentSize=8:tabSize=8:noTabs=false:
|
|
*/
|