forked from osmocom/wireshark
![Guy Harris](/assets/img/avatar_default.png)
That makes them work as input to a mergecap that writes pcapng files. File types that don't have a single per-file encapsulation type need more work, with multiple fake IDBs, one for each packet encapsulation type seen in the file, unless we can generate real IDBs. Change-Id: I2859e4f7fb15ec0c0f31a4044dc15638e5db7826 Reviewed-on: https://code.wireshark.org/review/37983 Petri-Dish: Guy Harris <gharris@sonic.net> Tested-by: Petri Dish Buildbot Reviewed-by: Guy Harris <gharris@sonic.net>
1062 lines
36 KiB
C
1062 lines
36 KiB
C
/* iseries.c
|
|
*
|
|
* Wiretap Library
|
|
* Copyright (c) 2011 by Martin Warnes <Martin_Warnes@uk.ibm.com>
|
|
*
|
|
* Based on toshiba.c and vms.c
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
/*
|
|
* This module will read the contents of the iSeries (OS/400) Communication trace
|
|
* Both ASCII & Unicode (little-endian UCS-2) formatted traces are supported.
|
|
*
|
|
* iSeries Comms traces consist of a header page and a subsequent number of packet records
|
|
*
|
|
* The header page contains details on the options set during running of the trace,
|
|
* currently the following options are a requirement for this module:
|
|
*
|
|
* 1. Object protocol = ETHERNET (Default)
|
|
* 2. ASCII or Unicode file formats.
|
|
*
|
|
* The above can be acheived by passing option ASCII(*YES) with the trace command
|
|
*
|
|
*/
|
|
|
|
/* iSeries header page
|
|
|
|
COMMUNICATIONS TRACE Title: OS400 - OS400 trace 10/28/05 11:44:50 Page: 1
|
|
Trace Description . . . . . : OS400 - OS400 trace
|
|
Configuration object . . . . : ETH0
|
|
Type . . . . . . . . . . . . : 1 1=Line, 2=Network Interface
|
|
3=Network server
|
|
Object protocol . . . . . . : ETHERNET
|
|
Start date/Time . . . . . . : 10/28/05 11:43:00.341
|
|
End date/Time . . . . . . . : 10/28/05 11:44:22.148
|
|
Bytes collected . . . . . . : 11999
|
|
Buffer size . . . . . . . . : 2048 kilobytes
|
|
Data direction . . . . . . . : 3 1=Sent, 2=Received, 3=Both
|
|
Stop on buffer full . . . . : Y Y=Yes, N=No
|
|
Number of bytes to trace
|
|
Beginning bytes . . . . . : *MAX Value, *CALC, *MAX
|
|
Ending bytes . . . . . . : *CALC Value, *CALC
|
|
Controller name . . . . . . : *ALL *ALL, name
|
|
Data representation . . . . : 1 1=ASCII, 2=EBCDIC, 3=*CALC
|
|
Format SNA data only . . . . : N Y=Yes, N=No
|
|
Format RR, RNR commands . . : N Y=Yes, N=No
|
|
Format TCP/IP data only . . : Y Y=Yes, N=No
|
|
IP address . . . . . . . . : *ALL *ALL, address
|
|
IP address . . . . . . . . : *ALL *ALL, address
|
|
IP port . . . . . . . . . : *ALL *ALL, IP port
|
|
Format UI data only . . . . : N Y=Yes, N=No
|
|
Select Ethernet data . . . . : 3 1=802.3, 2=ETHV2, 3=Both
|
|
Format Broadcast data . . . : Y Y=Yes, N=No
|
|
*/
|
|
|
|
/* iSeries IPv4 formatted packet records consist of a packet header line
|
|
* identifying the packet number, direction, size, timestamp,
|
|
* source/destination MAC addresses and packet type.
|
|
*
|
|
* Thereafter there will be a formated display of the headers above
|
|
* the link layer, such as ARP, IP, TCP, UDP, and ICMP (all but
|
|
* ICMP have either been seen in captures or on pages such as the ones
|
|
* at
|
|
*
|
|
* http://www-912.ibm.com/s_dir/SLKBase.nsf/1ac66549a21402188625680b0002037e/e05fb0515bc3449686256ce600512c37?OpenDocument
|
|
*
|
|
* and
|
|
*
|
|
* http://publib.boulder.ibm.com/infocenter/javasdk/v5r0/index.jsp?topic=%2Fcom.ibm.java.doc.diagnostics.50%2Fdiag%2Fproblem_determination%2Fi5os_perf_io_commstrace.html
|
|
*
|
|
* so we cannot assume that "IP Header" or "TCP Header" will appear). The
|
|
* formatted display includes lines that show the contents of some of the
|
|
* fields in the header, as well as hex strings dumps of the headers
|
|
* themselves, with tags such as "IP Header :", "ARP Header :",
|
|
* "TCP Header :", "UDP Header :", and (presumably) "ICMP Header:".
|
|
*
|
|
* If the packet contains data this is displayed as 4 groups of 16 hex digits
|
|
* followed by an ASCII representaion of the data line.
|
|
*
|
|
* Information from the packet header line, higher-level headers and, if
|
|
* available, data lines are extracted by the module for displaying.
|
|
*
|
|
*
|
|
Record Data Record Controller Destination Source Frame
|
|
Number S/R Length Timer Name MAC Address MAC Address Format
|
|
------ --- ------ --------------- ---------- ------------ ------------ ------
|
|
8 S 145 11:43:59.82956 0006299C14AE 0006299C14FE ETHV2 Type: 0800
|
|
Frame Type : IP DSCP: 0 ECN: 00-NECT Length: 145 Protocol: TCP Datagram ID: 388B
|
|
Src Addr: 10.20.144.150 Dest Addr: 10.20.144.151 Fragment Flags: DON'T,LAST
|
|
IP Header : 45000091388B40004006CC860A1490960A149097
|
|
IP Options : NONE
|
|
TCP . . . : Src Port: 6006,Unassigned Dest Port: 35366,Unassigned
|
|
SEQ Number: 2666470699 ('9EEF1D2B'X) ACK Number: 2142147535 ('7FAE93CF'X)
|
|
Code Bits: ACK PSH Window: 32648 TCP Option: NO OP
|
|
TCP Header : 17768A269EEF1D2B7FAE93CF80187F885B5600000101080A0517E0F805166DE0
|
|
Data . . . . . : 5443503200020010 0000004980000000 B800000080470103 01001E0000002000 *TCP2.......I*...*...*G........ .*
|
|
002F010080000004 0300800700C00600 4002008000000304 00800000060FB067 *./..*.....*..*..@..*.....*....*G*
|
|
FC276228786B3EB0 EF34F5F1D27EF8DF 20926820E7B322AA 739F1FB20D **'B(XK>**4***.** *H **"*S*.*. *
|
|
*/
|
|
|
|
/* iSeries IPv6 formatted traces are similar to the IPv4 version above,
|
|
* except that the higher-level headers have "IPv6 Header:" and
|
|
* "ICMPv6 Hdr:", and data data is no longer output in groups of 16 hex
|
|
* digits.
|
|
*
|
|
|
|
Record Data Record Destination Source Frame
|
|
Number S/R Length Timer MAC Address MAC Address Format
|
|
------ --- ------ ------------ ------------ ------------ ------
|
|
218 S 1488 15:01:14.389 0011BC358680 00096B6BD918 ETHV2 Type: 86DD
|
|
IPv6 Data: Ver: 06 Traffic Class: 00 Flow Label: 000000
|
|
Payload Length: 1448 Next Header: 06,TCP Hop Limit: 64
|
|
Src Addr: fd00:0:0:20f2::122
|
|
Dest Addr: fd00:0:0:20a0::155
|
|
IPv6 Header: 6000000005A80640FD000000000020F20000000000000122FD000000000020A0
|
|
0000000000000155
|
|
TCP . . . : Src Port: 21246,Unassigned Dest Port: 13601,Unassigned
|
|
SEQ Number: 2282300877 ('880925CD'X) ACK Number: 3259003715 ('C2407343'X)
|
|
Code Bits: ACK Window: 65535 TCP Option: NO OP
|
|
TCP Header : 52FE3521880925CDC24073438010FFFFCFBB00000101080A0E15127000237A08
|
|
Data . . . . . : 54435032000200140000061880000000ECBEB867F0000000004CE640E6C1D9D5 *TCP2........*...***g*....L*@*****
|
|
C9D5C740E3C8C9E240C9E240E3C8C540E6C1D9D5C9D5C740C6C9C5D3C4404040 ****@****@**@***@*******@*****@@@*
|
|
4040404040404040404040404040404040404040404040404040404040404040 *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*
|
|
*/
|
|
|
|
/* iSeries unformatted packet record consist of the same header record as
|
|
* the formatted trace but all other records are simply unformatted data
|
|
* containing higher-level headers and packet data combined.
|
|
*
|
|
Record Data Record Controller Destination Source Frame Number Number Poll/
|
|
Number S/R Length Timer Name MAC Address MAC Address Format Command Sent Received Final DSAP SSAP
|
|
------ --- ------ --------------- ---------- ------------ ------------ ------ ------- ------ -------- ----- ---- ----
|
|
1 R 64 12:19:29.97108 000629ECF48E 0006D78E23C2 ETHV2 Type: 0800
|
|
Data . . . . . : 4500003C27954000 3A06CE3D9797440F 0A5964EAC4F50554 58C9915500000000 *E..<'*@.:.*=**D..YD***.TX**U....*
|
|
A00216D06A200000 020405B40402080A 1104B6C000000000 010303000B443BF1 **..*J .....*......**.........D;**
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "wtap-int.h"
|
|
#include "iseries.h"
|
|
#include "file_wrappers.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <wsutil/str_util.h>
|
|
#include <wsutil/strtoi.h>
|
|
|
|
#define ISERIES_LINE_LENGTH 270
|
|
#define ISERIES_HDR_LINES_TO_CHECK 100
|
|
#define ISERIES_PKT_LINES_TO_CHECK 4
|
|
#define ISERIES_MAX_TRACE_LEN 99999999
|
|
#define ISERIES_FORMAT_ASCII 1
|
|
#define ISERIES_FORMAT_UNICODE 2
|
|
|
|
/*
|
|
* Magic strings - "COMMUNICATIONS TRACE", in ASCII and little-endian UCS-2.
|
|
*/
|
|
static const char iseries_hdr_magic_ascii[] = {
|
|
'C', 'O', 'M', 'M',
|
|
'U', 'N', 'I', 'C',
|
|
'A', 'T', 'I', 'O',
|
|
'N', 'S', ' ', 'T',
|
|
'R', 'A', 'C', 'E'
|
|
};
|
|
static const char iseries_hdr_magic_le_ucs_2[] = {
|
|
'C', 0x0, 'O', 0x0, 'M', 0x0, 'M', 0x0,
|
|
'U', 0x0, 'N', 0x0, 'I', 0x0, 'C', 0x0,
|
|
'A', 0x0, 'T', 0x0, 'I', 0x0, 'O', 0x0,
|
|
'N', 0x0, 'S', 0x0, ' ', 0x0, 'T', 0x0,
|
|
'R', 0x0, 'A', 0x0, 'C', 0x0, 'E', 0x0
|
|
};
|
|
|
|
typedef struct {
|
|
gboolean have_date; /* TRUE if we found a capture start date */
|
|
int year, month, day; /* The start date */
|
|
int format; /* Trace format type */
|
|
} iseries_t;
|
|
|
|
static gboolean iseries_read (wtap * wth, wtap_rec *rec, Buffer *buf,
|
|
int *err, gchar ** err_info, gint64 *data_offset);
|
|
static gboolean iseries_seek_read (wtap * wth, gint64 seek_off,
|
|
wtap_rec *rec,
|
|
Buffer * buf, int *err, gchar ** err_info);
|
|
static gboolean iseries_check_file_type (wtap * wth, int *err, gchar **err_info,
|
|
int format);
|
|
static gint64 iseries_seek_next_packet (wtap * wth, int *err, gchar **err_info);
|
|
static gboolean iseries_parse_packet (wtap * wth, FILE_T fh,
|
|
wtap_rec *rec,
|
|
Buffer * buf, int *err, gchar ** err_info);
|
|
static int iseries_UNICODE_to_ASCII (guint8 * buf, guint bytes);
|
|
static gboolean iseries_parse_hex_string (const char * ascii, guint8 * buf,
|
|
size_t len);
|
|
|
|
/*
|
|
* XXX - it would probably be cleaner to use a UCS-2 flavor of file_gets(),
|
|
* rather than file_gets(), if we're reading a UCS-2 file.
|
|
*/
|
|
wtap_open_return_val
|
|
iseries_open (wtap * wth, int *err, gchar ** err_info)
|
|
{
|
|
gint offset;
|
|
char magic[ISERIES_LINE_LENGTH];
|
|
|
|
/*
|
|
* Check that file starts with a valid iSeries COMMS TRACE header
|
|
* by scanning for it in the first line
|
|
*/
|
|
if (!wtap_read_bytes (wth->fh, &magic, sizeof magic, err, err_info))
|
|
{
|
|
if (*err != WTAP_ERR_SHORT_READ)
|
|
return WTAP_OPEN_ERROR;
|
|
return WTAP_OPEN_NOT_MINE;
|
|
}
|
|
|
|
/*
|
|
* Check if this is a little-endian UCS-2 Unicode formatted file by scanning
|
|
* for the magic string
|
|
*/
|
|
offset=0;
|
|
while ((unsigned int)offset < (ISERIES_LINE_LENGTH - (sizeof iseries_hdr_magic_le_ucs_2)))
|
|
{
|
|
if (memcmp (magic + offset, iseries_hdr_magic_le_ucs_2, sizeof iseries_hdr_magic_le_ucs_2) == 0) {
|
|
if (file_seek (wth->fh, 0, SEEK_SET, err) == -1)
|
|
{
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
/*
|
|
* Do some basic sanity checking to ensure we can handle the
|
|
* contents of this trace
|
|
*/
|
|
if (!iseries_check_file_type (wth, err, err_info, ISERIES_FORMAT_UNICODE))
|
|
{
|
|
if (*err == 0)
|
|
return WTAP_OPEN_NOT_MINE;
|
|
else
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
wth->file_encap = WTAP_ENCAP_ETHERNET;
|
|
wth->file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_ISERIES;
|
|
wth->snapshot_length = 0;
|
|
wth->subtype_read = iseries_read;
|
|
wth->subtype_seek_read = iseries_seek_read;
|
|
wth->file_tsprec = WTAP_TSPREC_USEC;
|
|
|
|
if (file_seek (wth->fh, 0, SEEK_SET, err) == -1)
|
|
{
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Add an IDB; we don't know how many interfaces were
|
|
* involved, so we just say one interface, about which
|
|
* we only know the link-layer type, snapshot length,
|
|
* and time stamp resolution.
|
|
*/
|
|
wtap_add_generated_idb(wth);
|
|
|
|
return WTAP_OPEN_MINE;
|
|
}
|
|
offset += 1;
|
|
}
|
|
|
|
/*
|
|
* Check if this is a ASCII formatted file by scanning for the magic string
|
|
*/
|
|
offset=0;
|
|
while ((unsigned int)offset < (ISERIES_LINE_LENGTH - sizeof iseries_hdr_magic_ascii))
|
|
{
|
|
if (memcmp (magic + offset, iseries_hdr_magic_ascii, sizeof iseries_hdr_magic_ascii) == 0)
|
|
{
|
|
if (file_seek (wth->fh, 0, SEEK_SET, err) == -1)
|
|
{
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
/*
|
|
* Do some basic sanity checking to ensure we can handle the
|
|
* contents of this trace
|
|
*/
|
|
if (!iseries_check_file_type (wth, err, err_info, ISERIES_FORMAT_ASCII))
|
|
{
|
|
if (*err == 0)
|
|
return WTAP_OPEN_NOT_MINE;
|
|
else
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
wth->file_encap = WTAP_ENCAP_ETHERNET;
|
|
wth->file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_ISERIES;
|
|
wth->snapshot_length = 0;
|
|
wth->subtype_read = iseries_read;
|
|
wth->subtype_seek_read = iseries_seek_read;
|
|
wth->file_tsprec = WTAP_TSPREC_USEC;
|
|
|
|
if (file_seek (wth->fh, 0, SEEK_SET, err) == -1)
|
|
{
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Add an IDB; we don't know how many interfaces were
|
|
* involved, so we just say one interface, about which
|
|
* we only know the link-layer type, snapshot length,
|
|
* and time stamp resolution.
|
|
*/
|
|
wtap_add_generated_idb(wth);
|
|
|
|
return WTAP_OPEN_MINE;
|
|
}
|
|
offset += 1;
|
|
}
|
|
|
|
/* Neither ASCII or UNICODE so not supported */
|
|
return WTAP_OPEN_NOT_MINE;
|
|
}
|
|
|
|
/*
|
|
* Do some basic sanity checking to ensure we can handle the
|
|
* contents of this trace by checking the header page for
|
|
* requisit requirements and additional information.
|
|
*/
|
|
static gboolean
|
|
iseries_check_file_type (wtap * wth, int *err, gchar **err_info, int format)
|
|
{
|
|
gboolean is_iseries = FALSE;
|
|
guint line;
|
|
int num_items_scanned;
|
|
char buf[ISERIES_LINE_LENGTH], protocol[9];
|
|
iseries_t *iseries;
|
|
|
|
/* Save trace format for passing between packets */
|
|
iseries = (iseries_t *) g_malloc (sizeof (iseries_t));
|
|
iseries->have_date = FALSE;
|
|
iseries->format = format;
|
|
|
|
for (line = 0; line < ISERIES_HDR_LINES_TO_CHECK; line++)
|
|
{
|
|
memset(buf, 0x0, sizeof(buf));
|
|
if (file_gets (buf, ISERIES_LINE_LENGTH, wth->fh) == NULL)
|
|
{
|
|
/* EOF or error. */
|
|
*err = file_error (wth->fh, err_info);
|
|
if (*err == WTAP_ERR_SHORT_READ)
|
|
*err = 0;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Check that we are dealing with an ETHERNET trace
|
|
*/
|
|
if (iseries->format == ISERIES_FORMAT_UNICODE)
|
|
{
|
|
iseries_UNICODE_to_ASCII ((guint8 *)buf, ISERIES_LINE_LENGTH);
|
|
}
|
|
ascii_strup_inplace (buf);
|
|
num_items_scanned = sscanf (buf,
|
|
"%*[ \n\t]OBJECT PROTOCOL%*[ .:\n\t]%8s",
|
|
protocol);
|
|
if (num_items_scanned == 1)
|
|
{
|
|
if (memcmp (protocol, "ETHERNET", 8) == 0)
|
|
{
|
|
*err = 0;
|
|
is_iseries = TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The header is the only place where the date part of the timestamp is held, so
|
|
* extract it here and store for all packets to access
|
|
*/
|
|
num_items_scanned = sscanf (buf,
|
|
"%*[ \n\t]START DATE/TIME%*[ .:\n\t]%2d/%2d/%2d",
|
|
&iseries->month, &iseries->day,
|
|
&iseries->year);
|
|
if (num_items_scanned == 3)
|
|
{
|
|
iseries->have_date = TRUE;
|
|
}
|
|
}
|
|
|
|
if (is_iseries)
|
|
wth->priv = (void *) iseries;
|
|
else
|
|
g_free(iseries);
|
|
|
|
return is_iseries;
|
|
}
|
|
|
|
/*
|
|
* Find the next packet and parse it; called from wtap_read().
|
|
*/
|
|
static gboolean
|
|
iseries_read (wtap * wth, wtap_rec *rec, Buffer *buf, int *err,
|
|
gchar ** err_info, gint64 *data_offset)
|
|
{
|
|
gint64 offset;
|
|
|
|
/*
|
|
* Locate the next packet
|
|
*/
|
|
offset = iseries_seek_next_packet (wth, err, err_info);
|
|
if (offset < 0)
|
|
return FALSE;
|
|
*data_offset = offset;
|
|
|
|
/*
|
|
* Parse the packet and extract the various fields
|
|
*/
|
|
return iseries_parse_packet (wth, wth->fh, rec, buf, err, err_info);
|
|
}
|
|
|
|
/*
|
|
* Seeks to the beginning of the next packet, and returns the
|
|
* byte offset. Returns -1 on failure or EOF; on EOF, sets
|
|
* *err to 0, and, on failure, sets *err to the error and *err_info
|
|
* to null or an additional error string.
|
|
*/
|
|
static gint64
|
|
iseries_seek_next_packet (wtap * wth, int *err, gchar **err_info)
|
|
{
|
|
iseries_t *iseries = (iseries_t *)wth->priv;
|
|
char buf[ISERIES_LINE_LENGTH],type[5];
|
|
int line, num_items_scanned;
|
|
gint64 cur_off;
|
|
long buflen;
|
|
|
|
for (line = 0; line < ISERIES_MAX_TRACE_LEN; line++)
|
|
{
|
|
if (file_gets (buf, ISERIES_LINE_LENGTH, wth->fh) == NULL)
|
|
{
|
|
/* EOF or error. */
|
|
*err = file_error (wth->fh, err_info);
|
|
return -1;
|
|
}
|
|
/* Convert UNICODE to ASCII if required and determine */
|
|
/* the number of bytes to rewind to beginning of record. */
|
|
if (iseries->format == ISERIES_FORMAT_UNICODE)
|
|
{
|
|
/* buflen is #bytes to 1st 0x0A */
|
|
buflen = iseries_UNICODE_to_ASCII ((guint8 *) buf, ISERIES_LINE_LENGTH);
|
|
}
|
|
else
|
|
{
|
|
/* Else buflen is just length of the ASCII string */
|
|
buflen = (long) strlen (buf);
|
|
}
|
|
ascii_strup_inplace (buf);
|
|
/* Check we have enough data in the line */
|
|
if (buflen < 78)
|
|
{
|
|
continue;
|
|
}
|
|
/* If packet header found return the offset */
|
|
num_items_scanned =
|
|
sscanf (buf+78,
|
|
"%*[ \n\t]ETHV2%*[ .:\n\t]TYPE%*[ .:\n\t]%4s",type);
|
|
if (num_items_scanned == 1)
|
|
{
|
|
/* Rewind to beginning of line */
|
|
cur_off = file_tell (wth->fh);
|
|
if (cur_off == -1)
|
|
{
|
|
*err = file_error (wth->fh, err_info);
|
|
return -1;
|
|
}
|
|
if (file_seek (wth->fh, cur_off - buflen, SEEK_SET, err) == -1)
|
|
{
|
|
return -1;
|
|
}
|
|
return cur_off - buflen;
|
|
}
|
|
}
|
|
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info =
|
|
g_strdup_printf ("iseries: next packet header not found within %d lines",
|
|
ISERIES_MAX_TRACE_LEN);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Read packets in random-access fashion
|
|
*/
|
|
static gboolean
|
|
iseries_seek_read (wtap * wth, gint64 seek_off, wtap_rec *rec,
|
|
Buffer * buf, int *err, gchar ** err_info)
|
|
{
|
|
|
|
/* seek to packet location */
|
|
if (file_seek (wth->random_fh, seek_off - 1, SEEK_SET, err) == -1)
|
|
return FALSE;
|
|
|
|
/*
|
|
* Parse the packet and extract the various fields
|
|
*/
|
|
return iseries_parse_packet (wth, wth->random_fh, rec, buf,
|
|
err, err_info);
|
|
}
|
|
|
|
static int
|
|
append_hex_digits(char *ascii_buf, int ascii_offset, int max_offset,
|
|
char *data, int *err, gchar **err_info)
|
|
{
|
|
int in_offset, out_offset;
|
|
int c;
|
|
unsigned int i;
|
|
gboolean overflow = FALSE;
|
|
|
|
in_offset = 0;
|
|
out_offset = ascii_offset;
|
|
for (;;)
|
|
{
|
|
/*
|
|
* Process a block of up to 16 hex digits.
|
|
* The block is terminated early by an end-of-line indication (NUL,
|
|
* CR, or LF), by a space (which terminates the last block of the
|
|
* data we're processing), or by a "*", which introduces the ASCII representation
|
|
* of the data.
|
|
* All characters in the block must be upper-case hex digits;
|
|
* there might or might not be a space *after* a block, but, if so,
|
|
* that will be skipped over after the block is processed.
|
|
*/
|
|
for (i = 0; i < 16; i++, in_offset++)
|
|
{
|
|
/*
|
|
* If we see an end-of-line indication, or an early-end-of-block
|
|
* indication (space), we're done. (Only the last block ends
|
|
* early.)
|
|
*/
|
|
c = data[in_offset] & 0xFF;
|
|
if (c == '\0' || c == ' ' || c == '*' || c == '\r' || c == '\n')
|
|
{
|
|
goto done;
|
|
}
|
|
if (!g_ascii_isxdigit(c) || g_ascii_islower(c))
|
|
{
|
|
/*
|
|
* Not a hex digit, or a lower-case hex digit.
|
|
* Treat this as an indication that the line isn't a data
|
|
* line, so we just ignore it.
|
|
*
|
|
* XXX - do so only for continuation lines; treat non-hex-digit
|
|
* characters as errors for other lines?
|
|
*/
|
|
return ascii_offset; /* pretend we appended nothing */
|
|
}
|
|
if (out_offset >= max_offset)
|
|
overflow = TRUE;
|
|
else
|
|
{
|
|
ascii_buf[out_offset] = c;
|
|
out_offset++;
|
|
}
|
|
}
|
|
/*
|
|
* Skip blanks, if any.
|
|
*/
|
|
for (; (data[in_offset] & 0xFF) == ' '; in_offset++)
|
|
;
|
|
}
|
|
done:
|
|
/*
|
|
* If we processed an *odd* number of hex digits, report an error.
|
|
*/
|
|
if ((i % 2) != 0)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup("iseries: odd number of hex digits in a line");
|
|
return -1;
|
|
}
|
|
if (overflow)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup("iseries: more packet data than the packet length indicated");
|
|
return -1;
|
|
}
|
|
return out_offset;
|
|
}
|
|
|
|
/* return the multiplier for nanoseconds */
|
|
static guint32
|
|
csec_multiplier(guint32 csec)
|
|
{
|
|
if (csec < 10) return 100000000;
|
|
if (csec < 100) return 10000000;
|
|
if (csec < 1000) return 1000000;
|
|
if (csec < 10000) return 100000;
|
|
if (csec < 100000) return 10000;
|
|
if (csec < 1000000) return 1000;
|
|
if (csec < 10000000) return 100;
|
|
if (csec < 100000000) return 10;
|
|
return 1;
|
|
}
|
|
|
|
/* Parses a packet. */
|
|
static gboolean
|
|
iseries_parse_packet (wtap * wth, FILE_T fh, wtap_rec *rec,
|
|
Buffer *buf, int *err, gchar **err_info)
|
|
{
|
|
iseries_t *iseries = (iseries_t *)wth->priv;
|
|
gint64 cur_off;
|
|
gboolean isValid, isCurrentPacket;
|
|
int num_items_scanned, line, pktline, buflen;
|
|
int pkt_len, pktnum, hr, min, sec;
|
|
char direction[2], destmac[13], srcmac[13], type[5];
|
|
guint32 csec;
|
|
char data[ISERIES_LINE_LENGTH * 2];
|
|
int offset;
|
|
char *ascii_buf;
|
|
int ascii_offset;
|
|
struct tm tm;
|
|
|
|
/*
|
|
* Check for packet headers in first 3 lines this should handle page breaks
|
|
* situations and the header lines output at each page throw and ensure we
|
|
* read both the captured and packet lengths.
|
|
*/
|
|
isValid = FALSE;
|
|
for (line = 1; line < ISERIES_PKT_LINES_TO_CHECK; line++)
|
|
{
|
|
if (file_gets (data, ISERIES_LINE_LENGTH, fh) == NULL)
|
|
{
|
|
*err = file_error (fh, err_info);
|
|
return FALSE;
|
|
}
|
|
/* Convert UNICODE data to ASCII */
|
|
if (iseries->format == ISERIES_FORMAT_UNICODE)
|
|
{
|
|
iseries_UNICODE_to_ASCII ((guint8 *)data, ISERIES_LINE_LENGTH);
|
|
}
|
|
ascii_strup_inplace (data);
|
|
num_items_scanned =
|
|
sscanf (data,
|
|
"%*[ \n\t]%6d%*[ *\n\t]%1s%*[ \n\t]%6d%*[ \n\t]%2d:%2d:%2d.%9u%*[ \n\t]"
|
|
"%12s%*[ \n\t]%12s%*[ \n\t]ETHV2%*[ \n\t]TYPE:%*[ \n\t]%4s",
|
|
&pktnum, direction, &pkt_len, &hr, &min, &sec, &csec, destmac,
|
|
srcmac, type);
|
|
if (num_items_scanned == 10)
|
|
{
|
|
if (pktnum < 0)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a negative packet number");
|
|
return FALSE;
|
|
}
|
|
|
|
if (pkt_len < 0)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a negative packet length");
|
|
return FALSE;
|
|
}
|
|
|
|
if (hr < 0)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a negative hour in the time stamp");
|
|
return FALSE;
|
|
}
|
|
|
|
if (hr > 23)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a hour in the time stamp greater than 23");
|
|
return FALSE;
|
|
}
|
|
|
|
if (min < 0)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a negative minute in the time stamp");
|
|
return FALSE;
|
|
}
|
|
|
|
if (min > 59)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a minute in the time stamp greater than 59");
|
|
return FALSE;
|
|
}
|
|
|
|
if (sec < 0)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a negative second in the time stamp");
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Yes, 60, even though the time-conversion routines on most OSes
|
|
* might not handle leap seconds.
|
|
*/
|
|
if (sec > 60)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a second in the time stamp greater than 60");
|
|
return FALSE;
|
|
}
|
|
|
|
if (strlen(destmac) != 12)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a destination MAC address shorter than 6 bytes");
|
|
return FALSE;
|
|
}
|
|
|
|
if (strlen(srcmac) != 12)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has a source MAC address shorter than 6 bytes");
|
|
return FALSE;
|
|
}
|
|
|
|
if (strlen(type) != 4)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header has an Ethernet type/length field than 2 bytes");
|
|
return FALSE;
|
|
}
|
|
|
|
/* OK! We found the packet header line */
|
|
isValid = TRUE;
|
|
/*
|
|
* XXX - The Capture length returned by the iSeries trace doesn't
|
|
* seem to include the Ethernet header, so we add its length here.
|
|
*
|
|
* Check the length first, just in case it's *so* big that, after
|
|
* adding the Ethernet header length, it overflows.
|
|
*/
|
|
if (pkt_len > WTAP_MAX_PACKET_SIZE_STANDARD - 14)
|
|
{
|
|
/*
|
|
* Probably a corrupt capture file; don't blow up trying
|
|
* to allocate space for an immensely-large packet, and
|
|
* don't think it's a really *small* packet because it
|
|
* overflowed. (Calculate the size as a 64-bit value in
|
|
* the error message, to avoid an overflow.)
|
|
*/
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup_printf("iseries: File has %" G_GUINT64_FORMAT "-byte packet, bigger than maximum of %u",
|
|
(guint64)pkt_len + 14,
|
|
WTAP_MAX_PACKET_SIZE_STANDARD);
|
|
return FALSE;
|
|
}
|
|
pkt_len += 14;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If no packet header found we exit at this point and inform the user.
|
|
*/
|
|
if (!isValid)
|
|
{
|
|
*err = WTAP_ERR_BAD_FILE;
|
|
*err_info = g_strdup ("iseries: packet header isn't valid");
|
|
return FALSE;
|
|
}
|
|
|
|
rec->rec_type = REC_TYPE_PACKET;
|
|
rec->presence_flags = WTAP_HAS_CAP_LEN;
|
|
|
|
/*
|
|
* If we have Wiretap Header then populate it here
|
|
*
|
|
* Timer resolution on the iSeries is hardware dependent. We determine
|
|
* the resolution based on how many digits we see.
|
|
*/
|
|
if (iseries->have_date)
|
|
{
|
|
rec->presence_flags |= WTAP_HAS_TS;
|
|
tm.tm_year = 100 + iseries->year;
|
|
tm.tm_mon = iseries->month - 1;
|
|
tm.tm_mday = iseries->day;
|
|
tm.tm_hour = hr;
|
|
tm.tm_min = min;
|
|
tm.tm_sec = sec;
|
|
tm.tm_isdst = -1;
|
|
rec->ts.secs = mktime (&tm);
|
|
rec->ts.nsecs = csec * csec_multiplier(csec);
|
|
}
|
|
|
|
rec->rec_header.packet_header.len = pkt_len;
|
|
rec->rec_header.packet_header.pkt_encap = WTAP_ENCAP_ETHERNET;
|
|
rec->rec_header.packet_header.pseudo_header.eth.fcs_len = -1;
|
|
|
|
/*
|
|
* Allocate a buffer big enough to hold the claimed packet length
|
|
* worth of byte values; each byte will be two hex digits, so the
|
|
* buffer's size should be twice the packet length.
|
|
*
|
|
* (There is no need to null-terminate the buffer.)
|
|
*/
|
|
ascii_buf = (char *)g_malloc (pkt_len*2);
|
|
ascii_offset = 0;
|
|
|
|
/*
|
|
* Copy in the Ethernet header.
|
|
*
|
|
* The three fields have already been checked to have the right length
|
|
* (6 bytes, hence 12 characters, of hex-dump destination and source
|
|
* addresses, and 2 bytes, hence 4 characters, of hex-dump type/length).
|
|
*
|
|
* pkt_len is guaranteed to be >= 14, so 2*pkt_len is guaranteed to be
|
|
* >= 28, so we don't need to do any bounds checking.
|
|
*/
|
|
memcpy(&ascii_buf[0], destmac, 12);
|
|
ascii_offset += 12;
|
|
memcpy(&ascii_buf[12], srcmac, 12);
|
|
ascii_offset += 12;
|
|
memcpy(&ascii_buf[24], type, 4);
|
|
ascii_offset += 4;
|
|
|
|
/*
|
|
* Start reading packet contents
|
|
*/
|
|
isCurrentPacket = TRUE;
|
|
|
|
/* loop through packet lines and breakout when the next packet header is read */
|
|
pktline = 0;
|
|
while (isCurrentPacket)
|
|
{
|
|
pktline++;
|
|
/* Read the next line */
|
|
if (file_gets (data, ISERIES_LINE_LENGTH, fh) == NULL)
|
|
{
|
|
*err = file_error (fh, err_info);
|
|
if (*err == 0)
|
|
{
|
|
/* Hit the EOF without an error */
|
|
break;
|
|
}
|
|
goto errxit;
|
|
}
|
|
|
|
/* Convert UNICODE data to ASCII and determine line length */
|
|
if (iseries->format == ISERIES_FORMAT_UNICODE)
|
|
{
|
|
buflen = iseries_UNICODE_to_ASCII ((guint8 *)data, ISERIES_LINE_LENGTH);
|
|
}
|
|
else
|
|
{
|
|
/* Else bytes to rewind is just length of ASCII string */
|
|
buflen = (int) strlen (data);
|
|
}
|
|
|
|
/*
|
|
* Skip leading white space.
|
|
*/
|
|
for (offset = 0; g_ascii_isspace(data[offset]); offset++)
|
|
;
|
|
|
|
/*
|
|
* The higher-level header information starts at an offset of
|
|
* 22 characters. The header tags are 14 characters long.
|
|
*
|
|
* XXX - for IPv6, if the next header isn't the last header,
|
|
* the intermediate headers do *NOT* appear to be shown in
|
|
* the dump file *at all*, so the packet *cannot* be
|
|
* reconstructed!
|
|
*/
|
|
if (offset == 22)
|
|
{
|
|
if (strncmp(data + 22, "IP Header : ", 14) == 0 ||
|
|
strncmp(data + 22, "IPv6 Header: ", 14) == 0 ||
|
|
strncmp(data + 22, "ARP Header : ", 14) == 0 ||
|
|
strncmp(data + 22, "TCP Header : ", 14) == 0 ||
|
|
strncmp(data + 22, "UDP Header : ", 14) == 0 ||
|
|
strncmp(data + 22, "ICMP Header: ", 14) == 0 ||
|
|
strncmp(data + 22, "ICMPv6 Hdr: ", 14) == 0 ||
|
|
strncmp(data + 22, "Option Hdr: ", 14) == 0)
|
|
{
|
|
ascii_offset = append_hex_digits(ascii_buf, ascii_offset,
|
|
pkt_len*2,
|
|
data + 22 + 14, err,
|
|
err_info);
|
|
if (ascii_offset == -1)
|
|
{
|
|
/* Bad line. */
|
|
return FALSE;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Is this a data line?
|
|
*
|
|
* The "Data" starts at an offset of 8.
|
|
*/
|
|
if (offset == 9)
|
|
{
|
|
if (strncmp(data + 9, "Data . . . . . : ", 18) == 0)
|
|
{
|
|
ascii_offset = append_hex_digits(ascii_buf, ascii_offset,
|
|
pkt_len*2,
|
|
data + 9 + 18, err,
|
|
err_info);
|
|
if (ascii_offset == -1)
|
|
{
|
|
/* Bad line. */
|
|
return FALSE;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Is this a continuation of a previous header or data line?
|
|
* That's blanks followed by hex digits; first try the
|
|
* "no column separators" form.
|
|
*
|
|
* Continuations of header lines begin at an offset of 36;
|
|
* continuations of data lines begin at an offset of 27.
|
|
*/
|
|
if (offset == 36 || offset == 27)
|
|
{
|
|
ascii_offset = append_hex_digits(ascii_buf, ascii_offset,
|
|
pkt_len*2,
|
|
data + offset, err,
|
|
err_info);
|
|
if (ascii_offset == -1)
|
|
{
|
|
/* Bad line. */
|
|
return FALSE;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* If we see the identifier for the next packet then rewind and set
|
|
* isCurrentPacket FALSE
|
|
*/
|
|
ascii_strup_inplace (data);
|
|
/* If packet header found return the offset */
|
|
num_items_scanned =
|
|
sscanf (data+78,
|
|
"%*[ \n\t]ETHV2%*[ .:\n\t]TYPE%*[ .:\n\t]%4s",type);
|
|
if ((num_items_scanned == 1) && pktline > 1)
|
|
{
|
|
isCurrentPacket = FALSE;
|
|
cur_off = file_tell( fh);
|
|
if (cur_off == -1)
|
|
{
|
|
/* Error. */
|
|
*err = file_error (fh, err_info);
|
|
goto errxit;
|
|
}
|
|
if (file_seek (fh, cur_off - buflen, SEEK_SET, err) == -1)
|
|
{
|
|
/* XXX: need to set err_info ?? */
|
|
goto errxit;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make the captured length be the amount of bytes we've read (which
|
|
* is half the number of characters of hex dump we have).
|
|
*
|
|
* XXX - this can happen for IPv6 packets if the next header isn't the
|
|
* last header.
|
|
*/
|
|
rec->rec_header.packet_header.caplen = ((guint32) ascii_offset)/2;
|
|
|
|
/* Make sure we have enough room for the packet. */
|
|
ws_buffer_assure_space (buf, rec->rec_header.packet_header.caplen);
|
|
/* Convert ascii data to binary and return in the frame buffer */
|
|
iseries_parse_hex_string (ascii_buf, ws_buffer_start_ptr (buf), ascii_offset);
|
|
|
|
/* free buffer allocs and return */
|
|
*err = 0;
|
|
g_free (ascii_buf);
|
|
return TRUE;
|
|
|
|
errxit:
|
|
g_free (ascii_buf);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Simple routine to convert an UNICODE buffer to ASCII
|
|
*
|
|
* XXX - This may be possible with iconv or similar
|
|
*/
|
|
static int
|
|
iseries_UNICODE_to_ASCII (guint8 * buf, guint bytes)
|
|
{
|
|
guint i;
|
|
guint8 *bufptr;
|
|
|
|
bufptr = buf;
|
|
|
|
for (i = 0; i < bytes; i++)
|
|
{
|
|
switch (buf[i])
|
|
{
|
|
case 0xFE:
|
|
case 0xFF:
|
|
case 0x00:
|
|
break;
|
|
default:
|
|
*bufptr = buf[i];
|
|
bufptr++;
|
|
}
|
|
if (buf[i] == 0x0A)
|
|
break;
|
|
}
|
|
g_assert(bufptr < buf + bytes);
|
|
*bufptr = '\0';
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* Simple routine to convert an ASCII hex string to binary data
|
|
* Requires ASCII hex data and buffer to populate with binary data
|
|
*/
|
|
static gboolean
|
|
iseries_parse_hex_string (const char * ascii, guint8 * buf, size_t len)
|
|
{
|
|
size_t i;
|
|
int byte;
|
|
gint hexvalue;
|
|
guint8 bytevalue;
|
|
|
|
byte = 0;
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
hexvalue = g_ascii_xdigit_value(ascii[i]);
|
|
i++;
|
|
if (hexvalue == -1)
|
|
return FALSE; /* not a valid hex digit */
|
|
bytevalue = (guint8)(hexvalue << 4);
|
|
if (i >= len)
|
|
return FALSE; /* only one hex digit of the byte is present */
|
|
hexvalue = g_ascii_xdigit_value(ascii[i]);
|
|
if (hexvalue == -1)
|
|
return FALSE; /* not a valid hex digit */
|
|
bytevalue |= (guint8) hexvalue;
|
|
buf[byte] = bytevalue;
|
|
byte++;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Editor modelines - https://www.wireshark.org/tools/modelines.html
|
|
*
|
|
* Local variables:
|
|
* c-basic-offset: 2
|
|
* tab-width: 8
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vi: set shiftwidth=2 tabstop=8 expandtab:
|
|
* :indentSize=2:tabSize=8:noTabs=true:
|
|
*/
|