Ethereal-dev: [ethereal-dev] Wiretap patch for "pppdump" files

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: Gilbert Ramirez <gram@xxxxxxxxxx>
Date: Sun, 17 Sep 2000 00:51:49 -0400
Here's an initial patch to the wiretap library to read "pppdump"
format files, which are the binary logs from pppd.

This turned out to be very difficult to code becuase the format is
unlike all other packet-capture formats that are handled by wiretap.
pppd logs character I/O, which is a layer lower than PPP. That's
why there's a special "-p" option to pppdump; pppdump has to reassemble
the various records into a single PPP packet; in this wiretap
module I have to do the same as "pppdump -p"; there's a lot of
state to keep track of to do so.

Anyway, this first patch works only with tethereal, since
tethereal just reads through the file sequentially. The random-access
seeking that ethereal does will not work with this module yet; I have
to keep track of even more state for that to work. That's next on
my list.

Secondly, I haven't even tried to do anything with timestamps in
this first patch. They're all 0.

For those of you who are interested in using [t]ethereal on your
pppd log files, please test this patch against your log files.

Take tethereal 0.8.12 (or CVS image) and apply the patch. Copy
pppdump.[ch] into the ethereal/wiretap directory.  You'll have to
have autoconf and automake installed, since the Makefiles have to
be rebuilt; but that should happen automatically, just make sure
they're installed.

Run:

tethereal -r file > out

(or "tethereal -n -r file > out" for faster runs)

It should run through the whole file, and maybe at the end say that
the file ended in the middle of a packet (depends on your pppd log
file). It should not assert or segfault. If it does, please let me
know, and send the pppd log file if possible.

This code will produce reams of debugging output.

Good luck!

--gilbert
/* pppdump.c
 *
 * $Id$
 *
 * Copyright (c) 2000 by Gilbert Ramirez <gram@xxxxxxxxxx>
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "wtap-int.h"
#include "buffer.h"
#include "pppdump.h"
#include "file_wrappers.h"

#include <glib.h>
#include <stdio.h>
#include <stdlib.h>

/*
pppdump records
Daniel Thompson (STMicroelectronics) <daniel.thompson@xxxxxx>

+------+
| 0x07 +------+------+------+         Reset time
|  t3  |  t2  |  t1  |  t0  |         t = time_t
+------+------+------+------+

+------+
| 0x06 |                              Time step (short)
|  ts  |                              ts = time step (tenths)
+------+

+------+
| 0x05 +------+------+------+         Time step (long)
| ts3  | ts2  | ts1  | ts0  |         ts = time step (tenths)
+------+------+------+------+

+------+
| 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

#define PPPD_NULL		0x00	/* For my own use */

typedef enum {
	DIRECTION_SENT,
	DIRECTION_RECV
} direction_enum;

static gboolean pppdump_read(wtap *wth, int *err, int *data_offset);
static int pppdump_seek_read(wtap *wth, int seek_off,
	union wtap_pseudo_header *pseudo_header, guint8 *pd, int len);


typedef struct {
	direction_enum	dir;
	int		cnt;
	gboolean	esc;
	guint8		buf[8192];
	long		id_offset;
} pkt_t;

/* Partial-record state */
typedef struct {
	int		num_bytes;
	pkt_t		*pkt;
} prec_state;

struct _pppdump_t;

typedef struct _pppdump_t {
	time_t			start_time;
	pkt_t			spkt;
	pkt_t			rpkt;
	long			offset;
	GList			*precs;
	struct _pppdump_t	*seek_state;
} pppdump_t;

static int
process_data(pppdump_t *state, FILE_T fh, pkt_t *pkt, int n, guint8 *pd, int *err);

static gboolean
collate(pppdump_t*, FILE_T fh, int *err, guint8 *pd, int *num_bytes, direction_enum *direction,
		long *data_offset);


static void
init_state(pppdump_t *state)
{

	g_print("INITIALIZING STATE 0x%08x\n", (unsigned int) state);
	state->precs = NULL;

	state->spkt.dir = DIRECTION_SENT;
	state->spkt.cnt = 0;
	state->spkt.esc = FALSE;
	state->spkt.id_offset = 0;

	state->rpkt.dir = DIRECTION_RECV;
	state->rpkt.cnt = 0;
	state->rpkt.esc = FALSE;
	state->rpkt.id_offset = 0;

	state->seek_state = NULL;
	state->offset = 0x100000; /* to detect errors during development */
}

static
void print_hex_data_text(const u_char *cp, unsigned int length)
{
        register int ad, i, j, k;
        u_char c;
        u_char line[60];
	static u_char binhex[16] = {
		'0', '1', '2', '3', '4', '5', '6', '7',
		'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

        memset (line, ' ', sizeof line);
        line[sizeof (line)-1] = 0;
        for (ad=i=j=k=0; i<length; i++) {
                c = *cp++;
                line[j++] = binhex[c>>4];
                line[j++] = binhex[c&0xf];
                if (i&1) j++;
                line[42+k++] = c >= ' ' && c < 0x7f ? c : '.';
                if ((i & 15) == 15) {
                        printf ("\n%4x  %s", ad, line);
                        /*if (i==15) printf (" %d", length);*/
                        memset (line, ' ', sizeof line);
                        line[sizeof (line)-1] = j = k = 0;
                        ad += 16;
                }
        }

        if (line[0] != ' ') printf ("\n%4x  %s", ad, line);
        printf("\n");
        return;

}

	
int
pppdump_open(wtap *wth, int *err)
{
	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.
	*/

	file_seek(wth->fh, 0, SEEK_SET); 
	wtap_file_read_unknown_bytes(buffer, sizeof(buffer), wth->fh, err);

	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 0;
	}

  my_file_type:

	state = wth->capture.generic = g_malloc(sizeof(pppdump_t));
	state->start_time = pntohl(&buffer[1]);
	g_print("pppdump time is %lu\n", state->start_time);

	init_state(state);

	state->offset = 5; 
	file_seek(wth->fh, 5, SEEK_SET); 
	wth->file_encap = WTAP_ENCAP_PPP; 
	wth->file_type = WTAP_FILE_PPPDUMP; 

	wth->snapshot_length = 8192; /* just guessing */ 
	wth->subtype_read = pppdump_read; 
	wth->subtype_seek_read = pppdump_seek_read; 

	state->seek_state = g_malloc(sizeof(pppdump_t));

	return 1;
}

/* Find the next packet and parse it; called from wtap_loop(). */
static gboolean
pppdump_read(wtap *wth, int *err, int *data_offset)
{
	gboolean	retval;
	int		num_bytes;
	direction_enum	direction;
	guint8		*buf;

	g_print("======================================================\n");

	buffer_assure_space(wth->frame_buffer, 8192);
	buf = buffer_start_ptr(wth->frame_buffer);
	retval = collate(wth->capture.generic, wth->fh, err, buf, &num_bytes, &direction, &wth->data_offset);

	g_print("Record ended with wth->data_offset = 0x%lx\n", wth->data_offset);

	if (!retval) {
		return FALSE;
	}

	*data_offset = wth->data_offset;

	wth->phdr.len		= num_bytes;
	wth->phdr.caplen	= num_bytes;
	wth->phdr.ts.tv_sec	= 0;	/* TODO */
	wth->phdr.ts.tv_usec	= 0;	/* TODO */
	wth->phdr.pkt_encap	= WTAP_ENCAP_PPP;

	return TRUE;
}

#define PKT(x)	(x)->dir == DIRECTION_SENT ? "SENT" : "RECV"

static gboolean
save_prec_state(pppdump_t *state, int num_bytes, pkt_t *pkt)
{
	prec_state	*prec;

	prec = g_new(prec_state, 1);
	if (!prec) {
		return FALSE;
	}
	prec->num_bytes = num_bytes;
	prec->pkt = pkt;

	g_print("saved state of num_bytes=%d pkt=0x%08x (%s) pkt->cnt=%d\n",
			num_bytes, (unsigned int) pkt, PKT(pkt), pkt->cnt);
	state->precs = g_list_append(state->precs, prec);
	return TRUE;
}

static int
process_data_from_prec_state(pppdump_t *state, FILE_T fh, guint8* pd, int *err, pkt_t **ppkt)
{
	prec_state	*prec;

	prec = state->precs->data;

	state->precs = g_list_remove(state->precs, prec);

	g_print("retrieved state of num_bytes=%d ", prec->num_bytes);
	*ppkt = prec->pkt;
	g_print("pkt=0x%08x (%s) pkt->cnt = %d\n", (unsigned int) prec->pkt, PKT(prec->pkt), prec->pkt->cnt);

	return process_data(state, fh, prec->pkt, prec->num_bytes, pd, err);
}
	


/* Returns number of bytes copied for record, -1 if failure */
static int
process_data(pppdump_t *state, FILE_T fh, pkt_t *pkt, int n, guint8 *pd, int *err)
{
	int	c;
	int	num_bytes = n;
	int	num_written;

	for (; num_bytes > 0; --num_bytes) {
		c = file_getc(fh);
		g_print("PD At offset 0x%lx got %c (0x%02x)\n", state->offset, c, c);
		state->offset++;
		switch (c) {
			case EOF:
				g_print("Unexpected EOF\n");
				if (*err == 0) {
					*err = WTAP_ERR_SHORT_READ;
				}
				return -1;
				break;

			case '~':
				if (pkt->cnt > 0) {
					pkt->esc = FALSE;

					num_written = pkt->cnt - 2;
					pkt->cnt = 0;
					if (num_written <= 0) {
						return 0;
					}

					memcpy(pd, pkt->buf, num_written);
					g_print("\n%s:\n", PKT(pkt));
					print_hex_data_text(pd, num_written);

					num_bytes--;
					if (num_bytes > 0) {
						if (!save_prec_state(state, num_bytes, pkt)) {
							return -1;
						}
					}
					g_print("returning with num_bytes   = %d\n", num_bytes);
					g_print("returning with num_written = %d\n", num_written);
					return num_written;
				}
				break;

			case '}':
				if (!pkt->esc) {
					pkt->esc = TRUE;
					break;
				}
				/* else fall through */

			default:
				if (pkt->esc) {
					c ^= 0x20;
					g_print("Changed  0x%02x\t%c\n", c, c);
					pkt->esc = FALSE;
				}
		
				pkt->buf[pkt->cnt++] = c;
				break;
		}
	}

	g_print("PD returning 0; no out bytes. pkt=0x%08x (%s) pkt->cnt=%d\n",
			(unsigned int) pkt, PKT(pkt), pkt->cnt);
	/* 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, guint8 *pd, int *num_bytes, direction_enum *direction,
		long *data_offset)
{
	int		id;
	pkt_t		*pkt = NULL;
	int		n, num_written = 0;

	while (state->precs) {
		g_print("I see a saved state.\n");
		num_written = process_data_from_prec_state(state, fh, pd, err, &pkt);

		if (num_written < 0) {
			return FALSE;
		}
		else if (num_written > 0) {
			*num_bytes = num_written;
			*direction = pkt->dir;
			*data_offset = pkt->id_offset;
			pkt->id_offset = 0;
			g_print("Returning, state->offset = 0x%lx\n", state->offset);
			return TRUE;
		}
		/* if 0 bytes written, keep processing */
	}
	g_print("No saved states.\n");

	while ((id = file_getc(fh)) != EOF) {
		g_print("CL At offset 0x%lx got %c (0x%02x)\n", state->offset, id, id);
		state->offset++;
		switch (id) {
			case PPPD_SENT_DATA:
			case PPPD_RECV_DATA:
				pkt = id == PPPD_SENT_DATA ? &state->spkt : &state->rpkt;

				if (pkt->id_offset == 0) {
					pkt->id_offset = state->offset - 1;
				}

				n = file_getc(fh);
				n = (n << 8) + file_getc(fh);
				state->offset += 2;

				g_print("ID: Going to read %d bytes for pkt=0x%08x (%s)\n", n,
						(unsigned int) pkt, PKT(pkt));

				num_written = process_data(state, fh, pkt, n, pd, err);

				if (num_written < 0) {
					return FALSE;
				}
				else if (num_written > 0) {
					*num_bytes = num_written;
					*direction = pkt->dir;
					*data_offset = pkt->id_offset;
					pkt->id_offset = 0;
					g_print("Returning, state->offset = 0x%lx\n", state->offset);
					return TRUE;
				}
				/* if 0 bytes written, keep looping */
				
				break;

			case PPPD_SEND_DELIM:
			case PPPD_RECV_DELIM:
				/* What can we do? */
				g_print("GOT *_DELIM\n");
				break;

			case PPPD_TIME_STEP_LONG:
			case PPPD_RESET_TIME:
				g_print("GOT *_TIME 32\n");
				file_seek(fh, sizeof(guint32), SEEK_CUR);
				state->offset += sizeof(guint32);
				break;

			case PPPD_TIME_STEP_SHORT:
				g_print("GOT *_TIME 8\n");
				file_seek(fh, sizeof(guint8), SEEK_CUR);
				state->offset += sizeof(guint8);
				break;

			default:
				g_print("BAD ID: 0x%02x\n", id);
				/* XXX - bad file */
				g_assert_not_reached();
		}

	}

	return FALSE;
}



/* Used to read packets in random-access fashion */
static int
pppdump_seek_read (wtap *wth,
		 int seek_off,
		 union wtap_pseudo_header *pseudo_header,
		 guint8 *pd,
		 int len)
{
	int		err = 0;
	int		num_bytes;
	direction_enum	direction;
	long		offset = 0;
	gboolean	retval;
	pppdump_t	*state;

	g_print(">>>>>>>>>>>> SEEKING to offset %d (0x%x)\n", seek_off, seek_off);
	file_seek(wth->random_fh, seek_off, SEEK_SET);

	state = wth->capture.generic;
	init_state(state->seek_state);

	retval = collate(state->seek_state, wth->random_fh, &err, pd, &num_bytes, &direction, &offset);

	if (!retval) {
		return -1;
	}

	if (len != num_bytes) {
		return -1;
	}


	g_print(">>>>>>>>>>>> COPIED %d bytes\n", num_bytes);

	return 0;
}



/* pppdump.h
 *
 * $Id$
 *
 * Copyright (c) 2000 by Gilbert Ramirez <gram@xxxxxxxxxx>
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#ifndef __PPPDUMP_H__
#define __PPPDUMP_H__

int pppdump_open(wtap *wth, int *err);

#endif
? ppdc2
? ppdc
? pppdump.c
? pppdump.h
? ppdc1
Index: Makefile.am
===================================================================
RCS file: /usr/local/cvsroot/ethereal/wiretap/Makefile.am,v
retrieving revision 1.30
diff -u -r1.30 Makefile.am
--- Makefile.am	2000/08/08 22:16:41	1.30
+++ Makefile.am	2000/09/17 04:39:33
@@ -62,6 +62,8 @@
 	netxray.h		\
 	ngsniffer.c		\
 	ngsniffer.h		\
+	pppdump.c		\
+	pppdump.h		\
 	radcom.c		\
 	radcom.h		\
 	snoop.c			\
Index: file.c
===================================================================
RCS file: /usr/local/cvsroot/ethereal/wiretap/file.c,v
retrieving revision 1.61
diff -u -r1.61 file.c
--- file.c	2000/09/15 07:52:41	1.61
+++ file.c	2000/09/17 04:39:33
@@ -58,6 +58,7 @@
 #include "toshiba.h"
 #include "i4btrace.h"
 #include "csids.h"
+#include "pppdump.h"
 
 /* The open_file_* routines should return:
  *
@@ -94,6 +95,7 @@
 	netxray_open,
 	radcom_open,
 	nettl_open,
+	pppdump_open,
 
 	/* Files whose magic headers are in text *somewhere* in the
 	 * file (usually because the trace is just a saved copy of
@@ -337,6 +339,10 @@
 
         /* WTAP_FILE_CSIDS */
         { "CSIDS IPLog", NULL,
+          NULL, NULL },
+
+        /* WTAP_FILE_PPPDUMP */
+        { "pppd log (pppdump format)", NULL,
           NULL, NULL },
 
 };
Index: wtap-int.h
===================================================================
RCS file: /usr/local/cvsroot/ethereal/wiretap/wtap-int.h,v
retrieving revision 1.8
diff -u -r1.8 wtap-int.h
--- wtap-int.h	2000/09/07 05:34:21	1.8
+++ wtap-int.h	2000/09/17 04:39:34
@@ -141,6 +141,7 @@
 		netxray_t		*netxray;
 		ascend_t		*ascend;
 		csids_t			*csids;
+		void			*generic;
 	} capture;
 
 	subtype_read_func	subtype_read;
@@ -266,5 +267,33 @@
                     (guint32)*((guint8 *)p+1)<<8|   \
                     (guint32)*((guint8 *)p+0)<<0)
 #endif
+
+
+#define wtap_file_read_unknown_bytes(target, num_bytes, fh, err) \
+	G_STMT_START \
+	{ \
+		int _bytes_read; \
+		_bytes_read = file_read((target), 1, (num_bytes), (fh)); \
+		if (_bytes_read != (num_bytes)) { \
+			*(err) = file_error((fh)); \
+			return FALSE; \
+		} \
+	} \
+	G_STMT_END
+
+#define wtap_file_read_expected_bytes(target, num_bytes, fh, err) \
+	G_STMT_START \
+	{ \
+		int _bytes_read; \
+		_bytes_read = file_read((target), 1, (num_bytes), (fh)); \
+		if (_bytes_read != (num_bytes)) { \
+			*(err) = file_error((fh)); \
+			if (*(err) == 0 && _bytes_read > 0) { \
+				*(err) = WTAP_ERR_SHORT_READ; \
+			} \
+			return FALSE; \
+		} \
+	} \
+	G_STMT_END
 
 #endif /* __WTAP_INT_H__ */
Index: wtap.h
===================================================================
RCS file: /usr/local/cvsroot/ethereal/wiretap/wtap.h,v
retrieving revision 1.79
diff -u -r1.79 wtap.h
--- wtap.h	2000/09/15 07:52:43	1.79
+++ wtap.h	2000/09/17 04:39:34
@@ -124,9 +124,10 @@
 #define WTAP_FILE_TOSHIBA			21
 #define WTAP_FILE_I4BTRACE			22
 #define WTAP_FILE_CSIDS				23
+#define WTAP_FILE_PPPDUMP			24
 
 /* last WTAP_FILE_ value + 1 */
-#define WTAP_NUM_FILE_TYPES			24
+#define WTAP_NUM_FILE_TYPES			25
 
 /*
  * Maximum packet size we'll support.