wireshark/wiretap/pppdump.c

839 lines
21 KiB
C

/* pppdump.c
*
* Copyright (c) 2000 by Gilbert Ramirez <gram@alumni.rice.edu>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#include "wtap-int.h"
#include "pppdump.h"
#include "file_wrappers.h"
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <wsutil/ws_assert.h>
/*
pppdump records
Daniel Thompson (STMicroelectronics) <daniel.thompson@st.com>
+------+
| 0x07 | Reset time
+------+------+------+------+
| t3 | t2 | t1 | t0 | t = time_t
+------+------+------+------+
+------+
| 0x06 | Time step (short)
+------+
| ts | ts = time step (tenths of seconds)
+------+
+------+
| 0x05 | Time step (long)
+------+------+------+------+
| ts3 | ts2 | ts1 | ts0 | ts = time step (tenths of seconds)
+------+------+------+------+
+------+
| 0x04 | Receive deliminator (not seen in practice)
+------+
+------+
| 0x03 | Send deliminator (not seen in practice)
+------+
+------+
| 0x02 | Received data
+------+------+
| n1 | n0 | n = number of bytes following
+------+------+
| data |
| |
+------+
| 0x01 | Sent data
+------+------+
| n1 | n0 | n = number of bytes following
+------+------+
| data |
| |
*/
#define PPPD_SENT_DATA 0x01
#define PPPD_RECV_DATA 0x02
#define PPPD_SEND_DELIM 0x03
#define PPPD_RECV_DELIM 0x04
#define PPPD_TIME_STEP_LONG 0x05
#define PPPD_TIME_STEP_SHORT 0x06
#define PPPD_RESET_TIME 0x07
/* this buffer must be at least (2*PPPD_MTU) + sizeof(ppp_header) +
* sizeof(lcp_header) + sizeof(ipcp_header). PPPD_MTU is *very* rarely
* larger than 1500 so this value is fine.
*
* It's less than WTAP_MAX_PACKET_SIZE_STANDARD, so we don't have to worry about
* too-large packets.
*/
#define PPPD_BUF_SIZE 8192
typedef enum {
DIRECTION_SENT,
DIRECTION_RECV
} direction_enum;
static gboolean pppdump_read(wtap *wth, wtap_rec *rec, Buffer *buf,
int *err, gchar **err_info, gint64 *data_offset);
static gboolean pppdump_seek_read(wtap *wth, gint64 seek_off,
wtap_rec *rec, Buffer *buf, int *err, gchar **err_info);
static int pppdump_file_type_subtype = -1;
void register_pppdump(void);
/*
* Information saved about a packet, during the initial sequential pass
* through the file, to allow us to later re-read it when randomly
* reading packets.
*
* "offset" is the offset in the file of the first data chunk containing data
* from that packet; note that it may also contain data from previous
* packets.
*
* "num_bytes_to_skip" is the number of bytes from previous packets in that
* first data chunk.
*
* "dir" is the direction of the packet.
*/
typedef struct {
gint64 offset;
gint64 num_bytes_to_skip;
direction_enum dir;
} pkt_id;
/*
* Information about a packet currently being processed. There is one of
* these for the sent packet being processed and one of these for the
* received packet being processed, as we could be in the middle of
* processing both a received packet and a sent packet.
*
* "dir" is the direction of the packet.
*
* "cnt" is the number of bytes of packet data we've accumulated.
*
* "esc" is TRUE if the next byte we see is escaped (and thus must be XORed
* with 0x20 before saving it), FALSE otherwise.
*
* "buf" is a buffer containing the packet data we've accumulated.
*
* "id_offset" is the offset in the file of the first data chunk
* containing data from the packet we're processing.
*
* "sd_offset" is the offset in the file of the first data byte from
* the packet we're processing - which isn't necessarily right after
* the header of the first data chunk, as we may already have assembled
* packets from that chunk.
*
* "cd_offset" is the offset in the file of the current data chunk we're
* processing.
*/
typedef struct {
direction_enum dir;
int cnt;
gboolean esc;
guint8 buf[PPPD_BUF_SIZE];
gint64 id_offset;
gint64 sd_offset;
gint64 cd_offset;
} pkt_t;
/*
* This keeps state used while processing records.
*
* "timestamp" is the seconds portion of the current time stamp value,
* as updated from PPPD_RESET_TIME, PPPD_TIME_STEP_LONG, and
* PPPD_TIME_STEP_SHORT records. "tenths" is the tenths-of-seconds
* portion.
*
* "spkt" and "rpkt" are "pkt_t" structures for the sent and received
* packets we're currently working on.
*
* "offset" is the current offset in the file.
*
* "num_bytes" and "pkt" are information saved when we finish accumulating
* the data for a packet, if the data chunk we're working on still has more
* data in it:
*
* "num_bytes" is the number of bytes of additional data remaining
* in the chunk after we've finished accumulating the data for the
* packet.
*
* "pkt" is the "pkt_t" for the type of packet the data chunk is for
* (sent or received packet).
*
* "seek_state" is another state structure used while processing records
* when doing a seek-and-read. (That structure doesn't itself have a
* "seek_state" structure.)
*
* "pids" is a GPtrArray of pointers to "pkt_id" structures for all the
* packets we've seen during the initial sequential pass, to allow us to
* later retrieve them with random accesses.
*
* "pkt_cnt" is the number of packets we've seen up to this point in the
* sequential pass.
*/
typedef struct _pppdump_t {
time_t timestamp;
guint tenths;
pkt_t spkt;
pkt_t rpkt;
gint64 offset;
int num_bytes;
pkt_t *pkt;
struct _pppdump_t *seek_state;
GPtrArray *pids;
guint pkt_cnt;
} pppdump_t;
static int
process_data(pppdump_t *state, FILE_T fh, pkt_t *pkt, int n, guint8 *pd,
int *err, gchar **err_info, pkt_id *pid);
static gboolean
collate(pppdump_t *state, FILE_T fh, int *err, gchar **err_info, guint8 *pd,
int *num_bytes, direction_enum *direction, pkt_id *pid,
gint64 num_bytes_to_skip);
static void
pppdump_close(wtap *wth);
static void
init_state(pppdump_t *state)
{
state->num_bytes = 0;
state->pkt = NULL;
state->spkt.dir = DIRECTION_SENT;
state->spkt.cnt = 0;
state->spkt.esc = FALSE;
state->spkt.id_offset = 0;
state->spkt.sd_offset = 0;
state->spkt.cd_offset = 0;
state->rpkt.dir = DIRECTION_RECV;
state->rpkt.cnt = 0;
state->rpkt.esc = FALSE;
state->rpkt.id_offset = 0;
state->rpkt.sd_offset = 0;
state->rpkt.cd_offset = 0;
state->seek_state = NULL;
state->offset = 0x100000; /* to detect errors during development */
}
wtap_open_return_val
pppdump_open(wtap *wth, int *err, gchar **err_info)
{
guint8 buffer[6]; /* Looking for: 0x07 t3 t2 t1 t0 ID */
pppdump_t *state;
/* There is no file header, only packet records. Fortunately for us,
* timestamp records are separated from packet records, so we should
* find an "initial time stamp" (i.e., a "reset time" record, or
* record type 0x07) at the beginning of the file. We'll check for
* that, plus a valid record following the 0x07 and the four bytes
* representing the timestamp.
*/
if (!wtap_read_bytes(wth->fh, buffer, sizeof(buffer),
err, err_info)) {
if (*err != WTAP_ERR_SHORT_READ)
return WTAP_OPEN_ERROR;
return WTAP_OPEN_NOT_MINE;
}
if (buffer[0] == PPPD_RESET_TIME &&
(buffer[5] == PPPD_SENT_DATA ||
buffer[5] == PPPD_RECV_DATA ||
buffer[5] == PPPD_TIME_STEP_LONG ||
buffer[5] == PPPD_TIME_STEP_SHORT ||
buffer[5] == PPPD_RESET_TIME)) {
goto my_file_type;
}
else {
return WTAP_OPEN_NOT_MINE;
}
my_file_type:
if (file_seek(wth->fh, 5, SEEK_SET, err) == -1)
return WTAP_OPEN_ERROR;
state = g_new(pppdump_t, 1);
wth->priv = (void *)state;
state->timestamp = pntoh32(&buffer[1]);
state->tenths = 0;
init_state(state);
state->offset = 5;
wth->file_encap = WTAP_ENCAP_PPP_WITH_PHDR;
wth->file_type_subtype = pppdump_file_type_subtype;
wth->snapshot_length = PPPD_BUF_SIZE; /* just guessing */
wth->subtype_read = pppdump_read;
wth->subtype_seek_read = pppdump_seek_read;
wth->subtype_close = pppdump_close;
wth->file_tsprec = WTAP_TSPREC_DSEC;
state->seek_state = g_new(pppdump_t,1);
/* If we have a random stream open, we're going to be reading
the file randomly; set up a GPtrArray of pointers to
information about how to retrieve the data for each packet. */
if (wth->random_fh != NULL)
state->pids = g_ptr_array_new();
else
state->pids = NULL;
state->pkt_cnt = 0;
/*
* 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;
}
/* Set part of the struct wtap_rec. */
static void
pppdump_set_phdr(wtap_rec *rec, int num_bytes,
direction_enum direction)
{
rec->rec_type = REC_TYPE_PACKET;
rec->block = wtap_block_create(WTAP_BLOCK_PACKET);
rec->rec_header.packet_header.len = num_bytes;
rec->rec_header.packet_header.caplen = num_bytes;
rec->rec_header.packet_header.pkt_encap = WTAP_ENCAP_PPP_WITH_PHDR;
rec->rec_header.packet_header.pseudo_header.p2p.sent = (direction == DIRECTION_SENT ? TRUE : FALSE);
}
/* Find the next packet and parse it; called from wtap_read(). */
static gboolean
pppdump_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err, gchar **err_info,
gint64 *data_offset)
{
int num_bytes;
direction_enum direction;
pppdump_t *state;
pkt_id *pid;
state = (pppdump_t *)wth->priv;
/* If we have a random stream open, allocate a structure to hold
the information needed to read this packet's data again. */
if (wth->random_fh != NULL) {
pid = g_new(pkt_id, 1);
if (!pid) {
*err = errno; /* assume a malloc failed and set "errno" */
return FALSE;
}
pid->offset = 0;
} else
pid = NULL; /* sequential only */
ws_buffer_assure_space(buf, PPPD_BUF_SIZE);
if (!collate(state, wth->fh, err, err_info, ws_buffer_start_ptr(buf),
&num_bytes, &direction, pid, 0)) {
g_free(pid);
return FALSE;
}
if (pid != NULL)
pid->dir = direction;
if (pid != NULL)
g_ptr_array_add(state->pids, pid);
/* The user's data_offset is not really an offset, but a packet number. */
*data_offset = state->pkt_cnt;
state->pkt_cnt++;
pppdump_set_phdr(rec, num_bytes, direction);
rec->presence_flags = WTAP_HAS_TS;
rec->ts.secs = state->timestamp;
rec->ts.nsecs = state->tenths * 100000000;
return TRUE;
}
/* Returns number of bytes copied for record, -1 if failure.
*
* This is modeled after pppdump.c, the utility to parse pppd log files; it
* comes with the ppp distribution.
*/
static int
process_data(pppdump_t *state, FILE_T fh, pkt_t *pkt, int n, guint8 *pd,
int *err, gchar **err_info, pkt_id *pid)
{
int c;
int num_bytes = n;
int num_written;
for (; num_bytes > 0; --num_bytes) {
c = file_getc(fh);
if (c == EOF) {
*err = file_error(fh, err_info);
if (*err == 0) {
*err = WTAP_ERR_SHORT_READ;
}
return -1;
}
state->offset++;
switch (c) {
case 0x7e:
/*
* Flag Sequence for RFC 1662 HDLC-like
* framing.
*
* As this is a raw trace of octets going
* over the wire, and that might include
* the login sequence, there is no
* guarantee that *only* PPP traffic
* appears in this file, so there is no
* guarantee that the first 0x7e we see is
* a start flag sequence, and therefore we
* cannot safely ignore all characters up
* to the first 0x7e, and therefore we
* might end up with some bogus PPP
* packets.
*/
if (pkt->cnt > 0) {
/*
* We've seen stuff before this,
* so this is the end of a frame.
* Make a frame out of that stuff.
*/
pkt->esc = FALSE;
num_written = pkt->cnt;
pkt->cnt = 0;
if (num_written <= 0) {
return 0;
}
if (num_written > PPPD_BUF_SIZE) {
*err = WTAP_ERR_BAD_FILE;
*err_info = g_strdup_printf("pppdump: File has %u-byte packet, bigger than maximum of %u",
num_written, PPPD_BUF_SIZE);
return -1;
}
memcpy(pd, pkt->buf, num_written);
/*
* Remember the offset of the
* first record containing data
* for this packet, and how far
* into that record to skip to
* get to the beginning of the
* data for this packet; the number
* of bytes to skip into that record
* is the file offset of the first
* byte of this packet minus the
* file offset of the first byte of
* this record, minus 3 bytes for the
* header of this record (which, if
* we re-read this record, we will
* process, not skip).
*/
if (pid) {
pid->offset = pkt->id_offset;
pid->num_bytes_to_skip =
pkt->sd_offset - pkt->id_offset - 3;
ws_assert(pid->num_bytes_to_skip >= 0);
}
num_bytes--;
if (num_bytes > 0) {
/*
* There's more data in this
* record.
* Set the initial data offset
* for the next packet.
*/
pkt->id_offset = pkt->cd_offset;
pkt->sd_offset = state->offset;
} else {
/*
* There is no more data in
* this record.
* Thus, we don't have the
* initial data offset for
* the next packet.
*/
pkt->id_offset = 0;
pkt->sd_offset = 0;
}
state->num_bytes = num_bytes;
state->pkt = pkt;
return num_written;
}
break;
case 0x7d:
/*
* Control Escape octet for octet-stuffed
* RFC 1662 HDLC-like framing.
*/
if (!pkt->esc) {
/*
* Control Escape not preceded by
* Control Escape; discard it
* but XOR the next octet with
* 0x20.
*/
pkt->esc = TRUE;
break;
}
/*
* Control Escape preceded by Control Escape;
* treat it as an ordinary character,
* by falling through.
*/
/* FALL THROUGH */
default:
if (pkt->esc) {
/*
* This character was preceded by
* Control Escape, so XOR it with
* 0x20, as per RFC 1662's octet-
* stuffed framing, and clear
* the flag saying that the
* character should be escaped.
*/
c ^= 0x20;
pkt->esc = FALSE;
}
if (pkt->cnt >= PPPD_BUF_SIZE) {
*err = WTAP_ERR_BAD_FILE;
*err_info = g_strdup_printf("pppdump: File has %u-byte packet, bigger than maximum of %u",
pkt->cnt - 1, PPPD_BUF_SIZE);
return -1;
}
pkt->buf[pkt->cnt++] = c;
break;
}
}
/* we could have run out of bytes to read */
return 0;
}
/* Returns TRUE if packet data copied, FALSE if error occurred or EOF (no more records). */
static gboolean
collate(pppdump_t* state, FILE_T fh, int *err, gchar **err_info, guint8 *pd,
int *num_bytes, direction_enum *direction, pkt_id *pid,
gint64 num_bytes_to_skip)
{
int id;
pkt_t *pkt = NULL;
int byte0, byte1;
int n, num_written = 0;
gint64 start_offset;
guint32 time_long;
guint8 time_short;
/*
* Process any data left over in the current record when doing
* sequential processing.
*/
if (state->num_bytes > 0) {
ws_assert(num_bytes_to_skip == 0);
pkt = state->pkt;
num_written = process_data(state, fh, pkt, state->num_bytes,
pd, err, err_info, pid);
if (num_written < 0) {
return FALSE;
}
else if (num_written > 0) {
*num_bytes = num_written;
*direction = pkt->dir;
return TRUE;
}
/* if 0 bytes written, keep processing */
} else {
/*
* We didn't have any data left over, so the packet will
* start at the beginning of a record.
*/
if (pid)
pid->num_bytes_to_skip = 0;
}
/*
* That didn't get all the data for this packet, so process
* subsequent records.
*/
start_offset = state->offset;
while ((id = file_getc(fh)) != EOF) {
state->offset++;
switch (id) {
case PPPD_SENT_DATA:
case PPPD_RECV_DATA:
pkt = id == PPPD_SENT_DATA ? &state->spkt : &state->rpkt;
/*
* Save the offset of the beginning of
* the current record.
*/
pkt->cd_offset = state->offset - 1;
/*
* Get the length of the record.
*/
byte0 = file_getc(fh);
if (byte0 == EOF)
goto done;
state->offset++;
byte1 = file_getc(fh);
if (byte1 == EOF)
goto done;
state->offset++;
n = (byte0 << 8) | byte1;
if (pkt->id_offset == 0) {
/*
* We don't have the initial data
* offset for this packet, which
* means this is the first
* data record for that packet.
* Save the offset of the
* beginning of that record and
* the offset of the first data
* byte in the packet, which is
* the first data byte in the
* record.
*/
pkt->id_offset = pkt->cd_offset;
pkt->sd_offset = state->offset;
}
if (n == 0)
continue;
ws_assert(num_bytes_to_skip < n);
while (num_bytes_to_skip) {
if (file_getc(fh) == EOF)
goto done;
state->offset++;
num_bytes_to_skip--;
n--;
}
num_written = process_data(state, fh, pkt, n,
pd, err, err_info, pid);
if (num_written < 0) {
return FALSE;
}
else if (num_written > 0) {
*num_bytes = num_written;
*direction = pkt->dir;
return TRUE;
}
/* if 0 bytes written, keep looping */
break;
case PPPD_SEND_DELIM:
case PPPD_RECV_DELIM:
/* What can we do? */
break;
case PPPD_RESET_TIME:
if (!wtap_read_bytes(fh, &time_long, sizeof(guint32), err, err_info))
return FALSE;
state->offset += sizeof(guint32);
state->timestamp = pntoh32(&time_long);
state->tenths = 0;
break;
case PPPD_TIME_STEP_LONG:
if (!wtap_read_bytes(fh, &time_long, sizeof(guint32), err, err_info))
return FALSE;
state->offset += sizeof(guint32);
state->tenths += pntoh32(&time_long);
if (state->tenths >= 10) {
state->timestamp += state->tenths / 10;
state->tenths = state->tenths % 10;
}
break;
case PPPD_TIME_STEP_SHORT:
if (!wtap_read_bytes(fh, &time_short, sizeof(guint8), err, err_info))
return FALSE;
state->offset += sizeof(guint8);
state->tenths += time_short;
if (state->tenths >= 10) {
state->timestamp += state->tenths / 10;
state->tenths = state->tenths % 10;
}
break;
default:
/* XXX - bad file */
*err = WTAP_ERR_BAD_FILE;
*err_info = g_strdup_printf("pppdump: bad ID byte 0x%02x", id);
return FALSE;
}
}
done:
*err = file_error(fh, err_info);
if (*err == 0) {
if (state->offset != start_offset) {
/*
* We read at least one byte, so we were working
* on a record; an EOF means that record was
* cut short.
*/
*err = WTAP_ERR_SHORT_READ;
}
}
return FALSE;
}
/* Used to read packets in random-access fashion */
static gboolean
pppdump_seek_read(wtap *wth,
gint64 seek_off,
wtap_rec *rec,
Buffer *buf,
int *err,
gchar **err_info)
{
int num_bytes;
guint8 *pd;
direction_enum direction;
pppdump_t *state;
pkt_id *pid;
gint64 num_bytes_to_skip;
state = (pppdump_t *)wth->priv;
pid = (pkt_id *)g_ptr_array_index(state->pids, seek_off);
if (!pid) {
*err = WTAP_ERR_BAD_FILE; /* XXX - better error? */
*err_info = g_strdup("pppdump: PID not found for record");
return FALSE;
}
if (file_seek(wth->random_fh, pid->offset, SEEK_SET, err) == -1)
return FALSE;
init_state(state->seek_state);
state->seek_state->offset = pid->offset;
ws_buffer_assure_space(buf, PPPD_BUF_SIZE);
pd = ws_buffer_start_ptr(buf);
/*
* We'll start reading at the first record containing data from
* this packet; however, that doesn't mean "collate()" will
* stop only when we've read that packet, as there might be
* data for packets going in the other direction as well, and
* we might finish processing one of those packets before we
* finish processing the packet we're reading.
*
* Therefore, we keep reading until we get a packet that's
* going in the direction we want.
*/
num_bytes_to_skip = pid->num_bytes_to_skip;
do {
if (!collate(state->seek_state, wth->random_fh, err, err_info,
pd, &num_bytes, &direction, NULL, num_bytes_to_skip))
return FALSE;
num_bytes_to_skip = 0;
} while (direction != pid->dir);
pppdump_set_phdr(rec, num_bytes, pid->dir);
return TRUE;
}
static void
pppdump_close(wtap *wth)
{
pppdump_t *state;
state = (pppdump_t *)wth->priv;
if (state->seek_state) { /* should always be TRUE */
g_free(state->seek_state);
}
if (state->pids) {
unsigned int i;
for (i = 0; i < g_ptr_array_len(state->pids); i++) {
g_free(g_ptr_array_index(state->pids, i));
}
g_ptr_array_free(state->pids, TRUE);
}
}
static const struct supported_block_type pppdump_blocks_supported[] = {
/*
* We support packet blocks, with no comments or other options.
*/
{ WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
};
static const struct file_type_subtype_info pppdump_info = {
"pppd log (pppdump format)", "pppd", NULL, NULL,
FALSE, BLOCKS_SUPPORTED(pppdump_blocks_supported),
NULL, NULL, NULL
};
void register_pppdump(void)
{
pppdump_file_type_subtype = wtap_register_file_type_subtype(&pppdump_info);
/*
* Register name for backwards compatibility with the
* wtap_filetypes table in Lua.
*/
wtap_register_backwards_compatibility_lua_name("PPPDUMP",
pppdump_file_type_subtype);
}
/*
* 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:
*/