/* -*-  Mode:C; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */
/*
 * Copyright (C) 2004-2005 by the University of Southern California
 * $Id$
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * 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.
 *
 */

/* setup environment */
#define _GNU_SOURCE		1
#define _FILE_OFFSET_BITS	64

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <inttypes.h>

#include <netinet/in.h>
#include <ctype.h>

#include <stddef.h>
#include <byteswap.h>
#include <endian.h>
#include <assert.h>
#include <getopt.h>

#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp6.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>

#include <pcap.h>

#include "scramble_crypt.h"

#define MAX_PROTO_NAME		15	/* chars in proto sym name */
#define ERF_REC_LEN_MAX 	(1<<16) /* buffer is large enough to get zeroed out even if len=0xffff */
#define ERF_FLAGS_IFACE_MAX 3

#define MAX_IP_PROTO		255
#define	ETH_ADDR_LEN		6	/* length of an ethernet address */

#ifndef IPPROTO_EIGRP
#define IPPROTO_EIGRP	88
#elif IPPROTO_EIGRP != 88
#error IPPROTO_EIGRP is not 88
#endif /* IPPROTO_EIGRP */

#ifndef IPPROTO_IGP
#define IPPROTO_IGP	9
#elif IPPROTO_IGP != 9
#error IPPROTO_EIGRP is not 9
#endif /* IPPROTO_IGP */

#ifndef IPPROTO_OSPFIGP
#define IPPROTO_OSPFIGP	89
#elif IPPROTO_OSPFIGP != 89
#error IPPROTO_OSPFIGP is not 89
#endif /* IPPROTO_OSPFIGP */

#ifndef IPPROTO_IPMP
#define IPPROTO_IPMP	169 /* unassigned, but seems common */
#endif /* IPPROTO_IPMP */

#ifndef IPPROTO_TESTING_253_RFC3692
#define IPPROTO_TESTING_253_RFC3692 253 /* used for testing */
#endif /* IPPROTO_TESTING_253_RFC3692 */

/* timestamp handling macro: they are in little-endian b.o. */
#if __BYTE_ORDER == __BIG_ENDIAN
#define TS(x)			__bswap_64(x)
#endif
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define TS(x)			(x)
#endif
#ifndef TS
#error __BYTE_ORDER is neither __BIG_ENDIAN nor __LITTLE_ENDIAN
#endif

/* Ethernet types */
#define ETHERTYPE_IP		0x0800
#define ETHERTYPE_IP6		0x86dd
#define ETHERTYPE_8021Q		0x8100
#define ETHERTYPE_ARP		0x0806
#define ETHERTYPE_REVARP	0x8035
#define ETHERTYPE_LOOPBACK	0x9000	/* Loopback (configuration test protocol) */
#define ETHERTYPE_RESERVED	0xffff  /* "fake" frame added by dagsnap_lander to
					   record system time as first and last in
					   the trace */
		
#define UNUSED(x) 		do { (x) = (x); } while(0)
#define max(a, b)		((a)>(b)?(a):(b))
#define min(a, b)		((a)>(b)?(b):(a))

#define IS_UNASS_IP_PROTO(p)	(((p) >= 138 && (p) <= 253) || (p) == 255)

#define IS_IPV6OPT(proto)	((proto == IPPROTO_HOPOPTS)  || \
				 (proto == IPPROTO_ROUTING)  || \
				 (proto == IPPROTO_FRAGMENT) || \
				 (proto == IPPROTO_DSTOPTS)  || \
				 (proto == IPPROTO_NONE))

#define IS_ETHER_MCAST(p)	(((*(char*)(p)) & 0x01) == 0x01)
#define RESET_ETHER_MCAST(p)	(*(char*)(p) &= 0xfe)
typedef enum {
	PARSE_COND_SHORT, /* short packet */
	PARSE_COND_ERROR, /* malformed packet */
	PARSE_COND_SKIP,  /* ignore this packet */
	PARSE_COND_OK     /* parsed OK */
} parse_cond_t;

typedef struct flags {
	u_char		iface:2;
	u_char		vlen:1;
	u_char		trunc:1;
	u_char		rxerror:1;
	u_char		dserror:1;
	u_char		pad:2;
} flags_t;

#define PCAP_RECORD_TYPE_ETH	     1000

typedef struct dag_record_ {
	uint64_t	ts;
	uint8_t		type;
#define DAG_RECORD_TYPE_ETH          2
	flags_t		flags;
	uint16_t	rlen;
	uint16_t	lctr;
	uint16_t	wlen;
} dag_record_t;

typedef struct {
	int 		chop_tail;	/* length of the record's tail that can be scrapped */
	parse_cond_t	cond;
} parse_ret_t;

typedef enum {
	PARSE_IP4 = IPPROTO_IPIP,
	PARSE_IP6 = IPPROTO_IPV6
} parse_ip_t;

typedef parse_ret_t (*proto_parser_t)(void *p, int len, int proto);

typedef struct {
	proto_parser_t	parser;		/* handler */
	uint64_t	stats_cnt;	/* stats: totol count */
	uint64_t	stats_err;	/* stats: malformed count */
	char		name[MAX_PROTO_NAME+1]; /* pretty printing */
} ipprotosw_t;

typedef struct {
	u_char		dst[ETH_ADDR_LEN];
	u_char		src[ETH_ADDR_LEN];
	u_int16_t	etype;
	union {
		u_char		_eth_pload[1];
		struct {
			u_short	_vlan_tag;
			u_short _vlan_etype;
			u_char	_vlan_pload[1];
		}		_vlan;
	} vlan_un;
#define eth_pload	vlan_un._eth_pload
#define vlan_tag	vlan_un._vlan._vlan_tag
#define vlan_etype	vlan_un._vlan._vlan_etype
#define vlan_pload	vlan_un._vlan._vlan_pload
} eth_hdr_t;

typedef struct {
	u_char		offset;
	u_char		pad;
	/* followed by standard eth hdr */
	eth_hdr_t	eth_hdr;
} erf_eth_rec_t;


typedef struct {
	uint64_t	prev_ts;
	uint64_t	diff_max;
	uint64_t	min_ts;
	uint64_t	max_ts;
	int		not_increasing;
	int		first_record;
} ts_stats_t;

static void		init_filter	(const char *, struct bpf_program *, int, int); 
static int		run_filter	(const struct bpf_program *, const void *, int, int);

static void		parse_init	(void);
static void		parse_erf	(void);
static void		parse_pcap	(void);

static int		repair_erf	(int, int);

static parse_ret_t	parse_erf_eth	(void *, int, int, int);

static parse_ret_t	parse_link	(void *, int, int, int);
static parse_ret_t	parse_eth	(void *, int, int, int);

static parse_ret_t	parse_ipv4	(void *, int, int);
static parse_ret_t	parse_ipv4_opt	(struct iphdr * , int);
static parse_ret_t	parse_icmp4	(void *, int, int);

static parse_ret_t	parse_ipv6	(void *, int, int);
static parse_ret_t	parse_ipv6_opt	(void *, int, int);
static parse_ret_t	parse_icmp6	(void *, int, int);

static parse_ret_t	parse_tcp	(void *, int, int);
static parse_ret_t	parse_udp	(void *, int, int);
static parse_ret_t	parse_esp	(void *, int, int);
static parse_ret_t	parse_gre	(void *, int, int);
static parse_ret_t	parse_ah	(void *, int, int);

static parse_ret_t	parse_nofurther	(void *, int, int);
static parse_ret_t	parse_zerowhole	(void *, int, int);
static parse_ret_t	parse_unassigned(void *, int, int);
static parse_ret_t	parse_defaulterr(void *, int, int);

static void		usage		(void);
static void		panic		(char *, ...) __attribute__((noreturn, format (printf, 1, 2)));
static void		error		(char *, ...) __attribute__((unused, format (printf, 1, 2)));
static void		warn		(char *, ...) __attribute__((unused, format (printf, 1, 2)));
static inline uint16_t	cksum_adjust	(uint32_t, uint32_t, uint32_t);

#if 0
static u_char	bpdu_ether_bcast[] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00};
#define IS_BPDU_ADDR(p)		(memcmp((p), bpdu_ether_bcast, sizeof(bpdu_ether_bcast)) == 0)
#endif /* XXX not used */

static char 			*prog;
static char			*fi = NULL, *fo = NULL, *logdump = NULL;
static FILE			*f_input, *f_output, *f_logdump;
static int			do_pcap = 0;
static int			do_chopping = 0;
static int			do_warnings = 0;
static char			*scramble_crypto_fn = NULL;
static int			pass_bits4 = 0; /* # bits high-to-low to pass unscrambled (ipv4) */
static int			pass_bits6 = 0; /* # bits high-to-low to pass unscrambled (ipv6) */
static int			do_mac_scramble = 0;
static int			do_preserve_vlantags = 0; /* -v option */
static int			do_reverse_scramble = 0;  /* -r option */
static int			do_ts_increase = 0; 	  /* -M option */
static int			repair_damage = 0; 	  /* -R option */
static int			nocksum = 0; 		  /* --nocksum */
static int			keep_payload = 0;

/* point either to anon or unanon (-r) functions */
static uint32_t			(*_scrambler_ip4)(uint32_t, int) 	  = scramble_ip4;
static void			(*_scrambler_ip6)(struct in6_addr *, int) = scramble_ip6;

static char			*pass_filter_expr = NULL; /* pass filter expression, drop everything else */
static char			*paypass_filter_expr = NULL; /*pass payload filter expr */
static struct bpf_program 	pass_pf;
static struct bpf_program 	paypass_pf;
static int			pcap_dlt = DLT_RAW;     /* XXX give an option to set this */
static int			pcap_linktype = -10000; /* XXX neg number for non-existent link */
static pcap_dumper_t 		*pcap_dumper = NULL;
static pcap_dumper_t 		*pcap_logdumper = NULL;

/* I don't like these two globals, but they simplify passing
   checksum-relevant info between parsers */
static uint16_t			ip_hdr_cksum_adj	= 0;

static ipprotosw_t 		ipsw[MAX_IP_PROTO+1];

/* parsers can return these, if no chopping needed */
static parse_ret_t		PARSE_SHORT = { 0, PARSE_COND_SHORT };
static parse_ret_t		PARSE_ERROR = { 0, PARSE_COND_ERROR };
static parse_ret_t		PARSE_OK    = { 0, PARSE_COND_OK    };
static parse_ret_t		PARSE_SKIP  = { 0, PARSE_COND_SKIP  };

/* Two buffers are needed in case of repair.  The idea is to have to
   buffers to use alternately, this way, when we come to a corruption
   in the file, we have a previous record to work with */
static char 			parse_buf[2][ERF_REC_LEN_MAX];
static int			parse_buf_dex = 1; /* alternates between 0 and 1 */

/* stats */
static uint64_t 		rcount = 0, wcount = 0, fcount = 0, loss = 0;
static uint64_t 		rbytes = 0, wbytes = 0;

static inline void *cond_memset(void *s, int c, size_t n) {
	if (keep_payload) {
		/* if doing filtering and filter matched, do not scrub */
		return s;
	}
	return memset(s, c, n);
}

int
main(int argc, char *argv[])
{
	int  opt;
	int  do_stats = 0;
	int  do_debug = 0;
	char *logfn = NULL;

#define PASS4	256
#define PASS6	257
#define NOCKSUM 258
#define VERBOSE 259

	struct option long_options[] = {
		{"help",  0, NULL, 'h'},
		{"pass4", 1, NULL, PASS4},
		{"pass6", 1, NULL, PASS6},
		{"nocksum", 0, NULL, NOCKSUM},
		{"verbose", 0, NULL, VERBOSE},
	};
	prog = argv[0];
	while((opt = getopt_long(argc, argv, 
				 "DF:L:MPRSchl:mn:rs:v",
				 long_options, NULL)) != EOF) {
		switch(opt) {
			/* long options first: */
		case PASS4:
			pass_bits4 = atoi(optarg);
			if (pass_bits4 < 0 || pass_bits4 > 32)
				panic("--pass4 option argument must be within [0..32]\n");
			break;
		case PASS6:
			pass_bits6 = atoi(optarg);
			if (pass_bits6 < 0 || pass_bits6 > 128)
				panic("--pass4 option argument must be within [0..128]\n");
			break;
		case NOCKSUM:
			++nocksum;
			break;
		case VERBOSE:
			++do_warnings;
			break;

			/* short options: */
		case 'D':
			do_debug = 1; /* for now does nothing */
			break;
		case 'F':
			pass_filter_expr = optarg;
			break;
		case 'L':
			/* dump freaky packets in here */
			logdump = optarg;
			f_logdump = fopen(logdump, "a");
			if (f_logdump == NULL)
				error("cannot open the log dump file %s: %s\n",
				      logdump, strerror(errno));
			break;
		case 'M':
			++do_ts_increase; /* skip over records to maintain 
					     monotonously increasing TS */ 
			break;
		case 'P':
			++do_pcap;	 /* Use pcap for input/output */
			break;
		case 'R':
			++repair_damage; /* will try to repair damaged files by
					    skipping some bytes until what looks 
					    like a good record */
			break;
		case 'S':
			++do_stats;
			break;
		case 'c':
			++do_chopping;
			break;
       	       	case 'h':			    
			usage();		    
			/* never returns */
		case 'l':
			logfn = optarg;
			if (freopen(logfn, "a", stderr) == NULL) {
				error("cannot open the log file %s: %s\n",
				      logfn,
				      strerror(errno));
			}
			break;
		case 'm':
			++do_mac_scramble;
			break;
		case 'n':
			paypass_filter_expr = optarg;
			break;
		case 'r':
			++do_reverse_scramble;
			_scrambler_ip4 = &unscramble_ip4;
			_scrambler_ip6 = &unscramble_ip6;
			break;
		case 's':
			scramble_crypto_fn = optarg;
			break;
		case 'v':
			++do_preserve_vlantags;
			break;
		default:
			panic("unknown option, see -h for help on usage\n");
			/* never returns */
		}
	}

	argc -= optind;
	argv += optind;

	fi = (argc > 0 && argv[0]) ? argv[0] : NULL;
	fo = (argc > 1 && argv[1]) ? argv[1] : NULL;
	
	if (fi) {
		if ((f_input = fopen(fi, "r")) == NULL)
			panic("cannot open %s: %s\n", fi, strerror(errno));
	} else {
		fi = "(stdin)";
		f_input = stdin;
	}
	if (fo) {
		if ((f_output = fopen(fo, "r")) != NULL) {
			error("output %s exists, exiting without overwriting\n", fo);
			exit(1);
		}
		if ((f_output = fopen(fo, "w")) == NULL)
			panic("cannot open %s: %s\n", fo, strerror(errno));
	} else {
		fo = "(stdout)";
		f_output = stdout;
	}

	if (scramble_crypto_fn) {
		int do_mac = do_mac_scramble;
		if (scramble_init_from_file(scramble_crypto_fn, 
					    SCRAMBLE_BLOWFISH, SCRAMBLE_BLOWFISH, 
					    &do_mac) < 0) {
			error("Cannot init crypto scrambling");
			exit(1);
		}
		if (do_mac_scramble && !do_mac) {
			error("Cannot do MAC scrambling: disabled in keyfile\n");
			exit(1);
		}
		fprintf(stderr,
			"# Scrambling IP addresses, using crypto state from %s (ipv4:%s, ipv6:%s)\n",
			scramble_crypto_fn,
			scramble_type2name(scramble_crypto_ip4()),
			scramble_type2name(scramble_crypto_ip6()));
		if (pass_bits4)
			fprintf(stderr,
				"# Passing %d bits of ipv4 unchanged\n", pass_bits4);
		if (pass_bits6)
			fprintf(stderr,
				"# Passing %d bits of ipv6 unchanged\n", pass_bits6);
		fprintf(stderr,
			"# Scrambling MAC addresses/vlans is %s\n", (do_mac) ? "enabled" : "disabled");
	} else {
		if (pass_bits4 || pass_bits6) 
			fprintf(stderr, 
				"# Warning: --pass4 or --pass6 without -s makes no difference\n");
		if (do_reverse_scramble) {
			fprintf(stderr,
				"# Warning: -r makes no sense without -s\n");
		}
	}

	parse_init();

	if (pass_filter_expr)
		init_filter(pass_filter_expr,
			    &pass_pf,
			    1500,	/*XXX snaplen? does it matter? */
			    pcap_dlt);
	if (paypass_filter_expr)
		init_filter(paypass_filter_expr,
			    &paypass_pf,
			    1500,	/*XXX snaplen? does it matter? */
			    pcap_dlt);


	if (do_pcap)
		parse_pcap();
	else
		parse_erf();

	fprintf(stderr, 
		"**Read from %s: %"PRIu64"(%"PRIu64"B),"
		"  Written to %s %"PRIu64"(%"PRIu64"B), Freaky: %"PRIu64"\n", 
		fi, rcount, rbytes, 
		fo, wcount, wbytes, fcount);
	
	if (do_stats) {
		int i;
		fprintf(stderr, "**Extended Stats:\n");
		for (i = 0; i<= MAX_IP_PROTO; ++i) {
			if (ipsw[i].stats_cnt > 0) {
				fprintf(stderr, 
					"**  PROTO: 0x%02x, %*s, total: %20"PRIu64
					", errors: %20"PRIu64"\n",
					i, 
					MAX_PROTO_NAME, ipsw[i].name, 
					ipsw[i].stats_cnt, 
					ipsw[i].stats_err);
			}
		}
	}

	if (f_input)
		fclose(f_input);
	if (f_output)
		fclose(f_output);
	if (f_logdump)
		fclose(f_logdump);

	f_input = f_output = f_logdump = NULL;

	fclose(stderr);

	return 0;
}

#define REPAIR_ERF_LOOKAHEAD		4096 /* seek a good record 
						within so many bytes */
/* this (stronger version, works if corruption happened in the mid of the file */
#define ERF_REC_HEURISTIC_NEXT(oldrec, newrec)					\
	(((((oldrec)->ts ^ (newrec)->ts) >> 48) == 0) /* within 18hrs */	\
	 && ((oldrec)->type == (newrec)->type)					\
	 && ((int)ntohs((newrec)->rlen) < 1600)	     /* not too large */ \
	 && ((int)ntohs((newrec)->rlen) >= (int)sizeof(dag_record_t))/* nor too small */ \
	 && ((int)ntohs((newrec)->wlen) < 2000)				\
	 && ((int)ntohs((newrec)->wlen) >				\
	     (int)ntohs((newrec)->rlen) - (int)sizeof(dag_record_t)))
/* this (weaker) version will have to be used if it's the first record
   that's busted */
#define ERF_REC_HEURISTIC_FIRST(newrec)						\
	(((int)ntohs((newrec)->rlen) < 1600)		     /* not too large */ \
	 && ((int)ntohs((newrec)->rlen) >= (int)sizeof(dag_record_t))/* nor too small */ \
	 && ((int)ntohs((newrec)->wlen) < 2000)					\
	 && ((int)ntohs((newrec)->wlen) > 						\
	     (int)ntohs((newrec)->rlen) - (int)sizeof(dag_record_t)))
/* all in one */
#define ERF_REC_HEURISTIC(oldrec, newrec)		\
	((oldrec) == NULL				\
	 ? ERF_REC_HEURISTIC_FIRST(newrec)		\
	 : ERF_REC_HEURISTIC_NEXT(oldrec, newrec))

static int
repair_erf(int prev_buf_dex, int bad_read) {
	/* we have the last record */
	dag_record_t *prev_rec = NULL;
	dag_record_t rec;
	char buf[REPAIR_ERF_LOOKAHEAD];
	int buflen = 0;
	int off;
	int bufs = -1;
	int matches = 0;

	/* negative prev_buf_dex, means the buffer is not available */
	if (prev_buf_dex >= 0)
		prev_rec = (dag_record_t *)parse_buf[prev_buf_dex];

	if (fseek(f_input, (long)-bad_read, SEEK_CUR) != 0) {
		error("Cannot lseek to the end of previous record: %s\n", strerror(errno));
		return -1; /* something went wrong */
	}
	do {
		++bufs;
		errno = 0;
		buflen = fread(buf, 1, REPAIR_ERF_LOOKAHEAD, f_input);
		if (buflen == 0) {
			error("EOF while repairing (or error: %s), skipped last %d bytes\n",
			      strerror(errno), bufs*REPAIR_ERF_LOOKAHEAD);
			/* we did our best */			
			return 0;
		}
		/* all this memcpy's aren't very efficient, but hopefully we don't have to
		 * do it very often */
		for (off = 0; off + (int)sizeof(rec) < buflen; ++off) {
			memcpy(&rec, buf + off, sizeof(rec));
			/* heuristic to find a valid record */
			if (ERF_REC_HEURISTIC(prev_rec, &rec)) {
				/* this looks like a record */
				/* let's make sure a few following records look good too */
				dag_record_t r = rec;
				dag_record_t pr;
				int len = ntohs(rec.rlen);
				for (matches = 1; matches < 5; ++matches) {
					if (off + len + (int)sizeof(r) >= buflen)
						break;
					pr = r;
					memcpy(&r, buf + off + len, sizeof(r));
					if (!ERF_REC_HEURISTIC(&pr, &r)) {
						matches = 0;
						break;
					}
					len += ntohs(r.rlen);
				}
				if (matches)
					break;
			}
		}
	} while (matches == 0);
	{
		int64_t   dts  = 0;
		uint64_t  adts = 0;
		uint32_t  ds = 0;
		uint32_t  df = 0;
		if (prev_rec) {
			dts = TS(rec.ts) - TS(prev_rec->ts);
			adts = (dts < 0) ? -dts : dts;
			ds = adts >> 32;
			df = adts & 0xffffffff;
		}
		fprintf(stderr, 
			"Repair: found %d good(?) records, skipping %d bytes, "
			"timestamp diff: %c0x%"PRIx32".%08"PRIx32"\n",
			matches, bufs*REPAIR_ERF_LOOKAHEAD + off,
			dts < 0 ? '-' : '+',
			ds, df);
	}
	/* reposition the file to the beginning of a good record */
	if (fseek(f_input, (long)(off-buflen), SEEK_CUR) != 0) {
		error("Repair: cannot reposition file to the new good record: %s\n", 
		      strerror(errno));
		return -1;
	}
	return 0;
}

static void
pcap_callback(u_char *null, 
	      const struct pcap_pkthdr *h,
	      const u_char *bytes)
{
	struct pcap_pkthdr nh = *h;
	parse_ret_t ret;
	
	++rcount;
	rbytes += h->caplen;

	/* anonymizing */

	ret = parse_eth((u_char*)bytes, h->caplen, h->len, pcap_linktype);

	if (ret.cond == PARSE_COND_ERROR) {
		++fcount;
		if (f_logdump != NULL)
			pcap_dump((u_char*)pcap_logdumper, &nh, bytes);
	}
	if (ret.cond != PARSE_COND_SKIP) {
		/* write output */
		if (do_chopping && !keep_payload) {
			assert(ret.chop_tail >= 0 && ret.chop_tail <= h->caplen);
			nh.caplen -= ret.chop_tail;
		}
		if (nh.caplen > 0)
			pcap_dump((u_char*)pcap_dumper, &nh, bytes);

		++wcount;
		wbytes += nh.caplen;
	}
}

static void
parse_pcap(void)
{
	char errbuf[PCAP_ERRBUF_SIZE];
	int ret = 0;
	pcap_t *pcap = NULL;

	if ((pcap = pcap_fopen_offline(f_input, errbuf)) == NULL) {
		error("Error opening pcap input: %s\n", errbuf);
		return;
	}

	pcap_linktype = pcap_datalink(pcap); 

	if ((pcap_dumper = pcap_dump_fopen(pcap, f_output))
	    == NULL) {
		error("Error opening pcap output: %s\n", pcap_geterr(pcap));
		return;
	}

	if (f_logdump) {
		if ((pcap_logdumper = pcap_dump_open(pcap, logdump))
		    == NULL) {
			error("Error opening pcap freakdump: %s\n", pcap_geterr(pcap));
			return;
		}
	}
	

	ret = pcap_loop(pcap, -1, pcap_callback, NULL);
	
	if (ret == -1)
		error("Error processing pcap input: %s\n",
		      pcap_geterr(pcap));

	/* close dumpers */
	pcap_dump_close(pcap_dumper);
	if (f_logdump) 
		pcap_dump_close(pcap_logdumper);
	pcap_close(pcap);

	/* don't need to close these files again */
	f_input = f_output = f_logdump = NULL;
}

static void
parse_erf(void)
{
	dag_record_t	*rec; /*  = (dag_record_t *)parse_buf[parse_buf_dex ^= 1]; */
	ts_stats_t	ts_stats[ERF_FLAGS_IFACE_MAX+1];
	int		prev_buf_dex;
	int		lctr, loss_overflowed = 0;
	int		i, xlen = 0;
	int		first_record = 1;

	/* reset stats */
	for (i = 0; i<ERF_FLAGS_IFACE_MAX+1; ++i) {
		ts_stats[i].not_increasing = 0;
		ts_stats[i].first_record = 1;
		ts_stats[i].diff_max = 0;
		ts_stats[i].min_ts = ~(0ULL);
		ts_stats[i].max_ts = 0;
	}

	for (;;) {
		unsigned short rlen, wlen;
		int read_bytes;
		parse_ret_t ret;

		int l = fread((rec = (dag_record_t *)parse_buf[parse_buf_dex ^= 1]),
			  sizeof(dag_record_t), 1, f_input);
		if (l != 1) {
			if (l < 0)
				panic("problem reading input: %s\n", strerror(ferror(f_input)));
			break;
		}

		rlen = ntohs(rec->rlen);
		wlen = ntohs(rec->wlen);
		read_bytes = sizeof(dag_record_t);

		xlen = rlen - sizeof(dag_record_t);

		if (xlen < 0) {
			error("record too short on input\n");
			goto try_repair;
		}
		/* read the remainder */
		if ((l = fread(rec + 1, xlen, 1, f_input)) != 1) {
			error("unexpected end of file");
			if ((errno=ferror(f_input)) != 0)
				fprintf(stderr, ": %s", strerror(errno));
			fprintf(stderr, "\n");
			break;
		}

		read_bytes = rlen;

		/* early check for validity of record type to possibly repair */
		if (rec->type != DAG_RECORD_TYPE_ETH) {
			/* the only supported record type */
			error("unsupported link type %d\n", rec->type);
			goto try_repair;
		}
		/* check that lengths make sense */
		if (xlen > wlen) {
			/* It's OK, this is just pad */
		}

		/* assume first record is OK */
		first_record = 0;

		/* work with timestamps: already in little endian byte order */
		{
			int iface = rec->flags.iface;
			uint64_t ts = TS(rec->ts);

			assert(iface < ERF_FLAGS_IFACE_MAX+1);
			
			if (ts_stats[iface].first_record) {
				ts_stats[iface].first_record = 0;
			} else {
				if (ts_stats[iface].prev_ts > ts) {
					if (do_ts_increase) {
						uint64_t dts = ts_stats[iface].prev_ts - ts;
						fprintf(stderr, 
							"Monotonously increasing TS enforced, "
							"skipping over dTS=-0x%"PRIx32".%08"PRIx32", IF=%d\n",
							(uint32_t)(dts >> 32),
							(uint32_t)(dts & 0xffffffff),
							iface);
						continue;
					}
					ts_stats[iface].not_increasing = 1;
				} else
					ts_stats[iface].diff_max = 
						max(ts_stats[iface].diff_max, 
						    ts - ts_stats[iface].prev_ts);
			}
			ts_stats[iface].prev_ts = ts;
			if (ts < ts_stats[iface].min_ts)
				ts_stats[iface].min_ts = ts;
			if (ts > ts_stats[iface].max_ts)
				ts_stats[iface].max_ts = ts;
		}

		++rcount;
		rbytes += rlen;
		
		if (rec->lctr) {
			lctr = ntohs(rec->lctr);
			loss += lctr;
			if (lctr == 0xffff)
				loss_overflowed = 1;

		}

		ret = parse_link(rec + 1, xlen, wlen, -DAG_RECORD_TYPE_ETH);
		
		if (ret.cond == PARSE_COND_ERROR) {
			++fcount;
			if (f_logdump != NULL) {
				if (fwrite(rec, rlen, 1, f_logdump) != 1)
					error("cannot write freakdump - %s\n", 
					      strerror(ferror(f_logdump)));
			}
		}
		if (ret.cond != PARSE_COND_SKIP) {
			unsigned short w_len = rlen;	
			/* write output */
			if (do_chopping && !keep_payload) {
				assert(ret.chop_tail >= 0 && ret.chop_tail < xlen);
				if (w_len < (int)sizeof(dag_record_t) + ret.chop_tail) {
					continue; /* skip this, but == is OK */
				} else {
					w_len -= ret.chop_tail;
				}
				rec->rlen = htons(w_len);
			} /*xxx memset to zero here */
			
			if (fwrite(rec, w_len, 1, f_output) != 1)
				panic("cannot write output: %s\n", strerror(ferror(f_output)));
			++wcount;
			wbytes += w_len;
		}
		continue;
	try_repair:
		if (!repair_damage) {
			fprintf(stderr, "Panic: try running in repair mode [-R]\n");
			exit(1); /* this is fatal */
		}

		prev_buf_dex = (first_record) ? -1 : (parse_buf_dex ^ 1);
		if (repair_erf(prev_buf_dex,read_bytes) != 0)
			panic("Tried to repair, but wasn't successful\n");
		/* revert to the old value */
		parse_buf_dex ^= 1;
	}

	for (i = 0; i<ERF_FLAGS_IFACE_MAX+1; ++i) {
		if (ts_stats[i].first_record == 0) {
			uint64_t dts = ts_stats[i].max_ts - ts_stats[i].min_ts;
			uint32_t ds = (dts >> 32);	  /* seconds */
			uint32_t df = (dts & 0xffffffff); /* fractional seconds */
			fprintf(stderr, 
				"**IF=%d: Trace Time: 0x%"PRIx32".%08"PRIx32", Max timestamp diff: 0x%"
				PRIx32".%08"PRIx32", "
				"montonically increasing: %s\n",
				i,
				ds, df,
				(uint32_t)(ts_stats[i].diff_max >> 32), 
				(uint32_t)(ts_stats[i].diff_max & 0xffffffff),
				(ts_stats[i].not_increasing) ? "NO" : "YES");
		}
	}
	fprintf(stderr, "**Total losses within the file %s %"PRIu64"\n", 
		(loss_overflowed) ? ">=" : "=", loss);
}

static parse_ret_t
parse_link(void *p, int len, int wlen, int type)
{
	switch (type) {
	case -DAG_RECORD_TYPE_ETH:
		return parse_erf_eth(p, len, wlen, DAG_RECORD_TYPE_ETH);

	case DLT_EN10MB:
		return parse_eth(p, len, wlen, type);

	default:
		panic("unsupported link type %d\n", type);
	}
	/* notreached */
	error("parse_link() notreached\n");
	return PARSE_ERROR;
}

inline static
parse_ret_t call_parser(void *p, int len, int proto) {
	parse_ret_t ret;
	assert(proto >= 0 && proto <= MAX_IP_PROTO);

	if (len < 0)
		return PARSE_SHORT;
	else if (len == 0)
		return PARSE_OK;
	else {
		if (ipsw[proto].parser == NULL) {
			/* not supposed to happen: default handler is set for everyone */
			error("no default parser for proto %d\n", proto);
			abort();
		}
		++ipsw[proto].stats_cnt;
		
		ret = (ipsw[proto].parser)(p, len, proto);
		
		if (ret.cond == PARSE_COND_ERROR) {
			++ipsw[proto].stats_err;
		}
		
		return ret;
	}
}	

static parse_ret_t
parse_eth(void *p, int len, int wlen, int type)
{
	parse_ret_t ret;
	unsigned short etype;
	u_char *lh = p;
	u_char *nh = NULL; /*next hdr */
	eth_hdr_t *eth_hdr = p;
	int unicast_dst = ! IS_ETHER_MCAST(eth_hdr->dst);
	int silent = 0;

	if (len < (int)offsetof(eth_hdr_t, eth_pload)) {
		warn("eth header truncated: %d\n", len);
		cond_memset(p, 0, len);
		return PARSE_SHORT;
	}

	/* scramble eth_hdr->src, eth_hdr->dst addresses */
	SCRAMBLE_ETHER_ADDR(eth_hdr->src);
	if (unicast_dst) {
		/* scramble only if dst is unicast */
		SCRAMBLE_ETHER_ADDR(eth_hdr->dst);
	}

	etype = ntohs(eth_hdr->etype);
	nh = eth_hdr->eth_pload;
	/* skip VLAN tag if present */
	if (etype == ETHERTYPE_8021Q) {
		etype = ntohs(eth_hdr->vlan_etype);
		nh = eth_hdr->vlan_pload;

		if (!do_preserve_vlantags)
			SCRAMBLE_ETHER_VLAN(eth_hdr->vlan_tag);
 	}

	len -= (nh-lh);
	
	if(len < 0) return PARSE_SHORT; /* does not happen b/c of the above */

	if (pass_filter_expr &&
	    !run_filter(&pass_pf, nh, len, wlen)) {
		/* if doing filtering and filter didn't match, skip this packet */
		return PARSE_SKIP;
	}
	keep_payload = (paypass_filter_expr)?run_filter(&paypass_pf, nh, len, wlen):0;
	
	switch (etype) {
	case ETHERTYPE_IP:
		return call_parser(nh, len, PARSE_IP4);

	case ETHERTYPE_IP6:
		return call_parser(nh, len, PARSE_IP6);
		
	case ETHERTYPE_ARP:
	case ETHERTYPE_REVARP:
		cond_memset(nh, 0, len);
		ret.cond = PARSE_COND_OK;
		ret.chop_tail = len;
		return ret;
	case ETHERTYPE_RESERVED: /* pass through */
		ret.cond = PARSE_COND_OK;
		ret.chop_tail = 0;
		return ret;
	case ETHERTYPE_LOOPBACK:
		silent = 1;
		/* fallthrough */
	default:
		cond_memset(nh, 0, len);
		ret.chop_tail = len;
		ret.cond = PARSE_COND_OK;

		/* be silent if BPDU */
		if (unicast_dst && !silent) {
			error("unknown ether type: 0x%04x\n", etype);
			cond_memset(p, 0, len);
			ret.chop_tail = len;
			ret.cond = PARSE_COND_ERROR;
			return ret;
		}

		return ret;
	}
	/* not reached */
}

/* Ether frames are followed by FCS (checksum 4 byte field) */
#define ETHERNET_FCS_BYTES	4
#define ETHERNET_WLEN(wlen)	(wlen - ETHERNET_FCS_BYTES)

static parse_ret_t
parse_erf_eth(void *p, int len, int wlen, int type)
{
	parse_ret_t ret;
	erf_eth_rec_t *eth_rec = p;
	size_t overcapture = 0; /* this may include captured bytes of eth checksum:
				   between 0 and ETHERNET_FCS_BYTES followed by pad */
	int eth_wlen = ETHERNET_WLEN(wlen);

	len -= offsetof(erf_eth_rec_t, eth_hdr); /* -= 2 */

	if (len > eth_wlen) {
		overcapture = len - eth_wlen;
		assert(overcapture > 0);
		//assert(overcapture <= ETHERNET_FCS_BYTES);
		memset((u_char*)&eth_rec->eth_hdr + eth_wlen, 0, overcapture);
		len = eth_wlen;
	}


	ret = parse_eth(&eth_rec->eth_hdr, len, eth_wlen, type);
	/* if chopping, remove the ether cksum at the end */
	ret.chop_tail += overcapture;
	return ret;
}

static void
parse_init(void)
{
	int i;

	memset(ipsw, 0, sizeof(ipsw));

	for (i = 0; i <= MAX_IP_PROTO; ++i) {
		ipsw[i] = 
 			(IS_UNASS_IP_PROTO(i)) 
			? (ipprotosw_t){ parse_defaulterr, 0, 0, "UNASSIGNED" } 
			: (ipprotosw_t){ parse_defaulterr, 0, 0, "UNSUPP" };
	}

	ipsw[IPPROTO_IPIP] 	= (ipprotosw_t){ parse_ipv4, 0, 0, "IPv4" };
	ipsw[IPPROTO_IPV6] 	= (ipprotosw_t){ parse_ipv6, 0, 0, "IPv6" };

	ipsw[IPPROTO_ICMP] 	= (ipprotosw_t){ parse_icmp4, 0, 0, "ICMP"   };
	ipsw[IPPROTO_ICMPV6]	= (ipprotosw_t){ parse_icmp6, 0, 0, "ICMPv6" };

	ipsw[IPPROTO_TCP] 	= (ipprotosw_t){ parse_tcp, 0, 0, "TCP" };
	ipsw[IPPROTO_UDP] 	= (ipprotosw_t){ parse_udp, 0, 0, "UDP" };
	ipsw[IPPROTO_ESP]	= (ipprotosw_t){ parse_esp, 0, 0, "ESP" };
	ipsw[IPPROTO_GRE]	= (ipprotosw_t){ parse_gre, 0, 0, "GRE" };
	ipsw[IPPROTO_AH]	= (ipprotosw_t){ parse_ah,  0, 0, "AH" };

	/* ipv6 options */
	ipsw[IPPROTO_HOPOPTS]	= (ipprotosw_t){ parse_ipv6_opt, 0, 0, "IPv6-HOP"  };
	ipsw[IPPROTO_ROUTING]	= (ipprotosw_t){ parse_ipv6_opt, 0, 0, "IPv6_ROUT" };
	ipsw[IPPROTO_FRAGMENT]	= (ipprotosw_t){ parse_ipv6_opt, 0, 0, "IPv6_FRAG" };
	ipsw[IPPROTO_DSTOPTS]	= (ipprotosw_t){ parse_ipv6_opt, 0, 0, "IPv6_DEST" };

	/* remove everything silently for the following: */
	ipsw[IPPROTO_NONE]	= (ipprotosw_t){ parse_zerowhole, 0, 0, "IPv6_NONE" };
	ipsw[IPPROTO_EIGRP]	= (ipprotosw_t){ parse_zerowhole, 0, 0, "EIGRP" };
	ipsw[IPPROTO_IGP]	= (ipprotosw_t){ parse_zerowhole, 0, 0, "IGP"   };
	ipsw[IPPROTO_RSVP]	= (ipprotosw_t){ parse_zerowhole, 0, 0, "RSVP"  };
	ipsw[IPPROTO_OSPFIGP]	= (ipprotosw_t){ parse_zerowhole, 0, 0, "OSPFIGP"  };
#ifndef IPPROTO_VRRP
#define IPPROTO_VRRP 112
#else
#if IPPROTO_VRRP != 112
#error VRRP != 112
#endif
#endif
	ipsw[IPPROTO_VRRP]      = (ipprotosw_t){ parse_zerowhole, 0, 0, "VRRP"  };

	/* for now, just kill the payload; want to revisit for IGMP and PIM xxx */
	ipsw[IPPROTO_IGMP]	= (ipprotosw_t){ parse_zerowhole, 0, 0, "IGMP"   };
	ipsw[IPPROTO_PIM]	= (ipprotosw_t){ parse_zerowhole, 0, 0, "PIM"    };
	ipsw[IPPROTO_IPMP]	= (ipprotosw_t){ parse_zerowhole, 0, 0, "IPMP"};
	ipsw[IPPROTO_TESTING_253_RFC3692]
				= (ipprotosw_t){ parse_zerowhole, 0, 0, "TESTING_253"  };
}

/* Checksum adjustment function, optimized to some extent;
 * byte order is unimportant, as long as it is all the same;
 * see RFC 1624 and  RFC 1071 for details. 
 */
static inline uint16_t
cksum_adjust(uint32_t sum, uint32_t old, uint32_t new)
{
	if (nocksum) return sum;
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
	/* make use of ADDC */
	asm volatile ("\t not  %0"	: "+r" (sum));
	asm volatile ("\t not  %0"	: "+r" (old));
	asm volatile ("\t addl %1,%0"	: "+r" (sum) : "g" (old));
	asm volatile ("\t adcl %1,%0"	: "+r" (sum) : "g" (new));
	asm volatile ("\t adcl $0,%0"	: "+r" (sum));
#else
	sum ^= 0xffff;
	old = ~old;
	sum += (old & 0xffff) + (old >> 16);
	sum += (new & 0xffff) + (new >> 16);
#endif
	sum = (sum & 0xffff) + (sum >> 16);
	if (sum & 0xffff0000) 
		++sum;
	return ((~sum) & 0xffff); /* (~s) & 0xffff */
}

static parse_ret_t
parse_ipv4(void *p, int len, int proto) {
	struct iphdr *iph = p;
	parse_ret_t ret;
	int next_proto, iphlen, foff, iplen;
	int pad = 0;

	if (len < (int)sizeof(struct iphdr)) {
		warn("ipv4 header truncated1\n");
		/* there may be an address leak here.  Wipe it clean, 
		   because tcpdpriv won't handle it */
		memset(p, 0, len);
		ret.cond = PARSE_COND_SHORT;
		ret.chop_tail = len;
		return ret;
	}

	if (iph->version != 4) {
		error("expected ipv4, but got ipv%d\n", iph->version);
		return PARSE_ERROR; /* malformed packet, keep */
	}

	next_proto = iph->protocol;
	iphlen = iph->ihl << 2;

	iplen = ntohs(iph->tot_len);
	if (len >= iplen) {
		pad = len - iplen;
		len = iplen;
	}
	ip_hdr_cksum_adj = 0;
	if (scramble_crypto_fn) {
		uint32_t old_src = iph->saddr;
		uint32_t old_dst = iph->daddr;

		iph->saddr = (*_scrambler_ip4)(old_src, pass_bits4);
		iph->daddr = (*_scrambler_ip4)(old_dst, pass_bits4);
		/* checksum adjustment is needed only if we have whole header */
		if (iphlen <= len) {
			uint16_t ocsum = iph->check;
			iph->check = cksum_adjust(iph->check, old_src, iph->saddr);
			iph->check = cksum_adjust(iph->check, old_dst, iph->daddr);
			/* ip in ip is handled here too, we'll just overwrite
			   previous pseudo-value with new one */
			/* since IPPROTO_TCP is the prevalent case, we don't check
			 * for next_proto being TCP or UDP, and don't expect to
			 * pay much penalty for this; the absense of this check
			 * would also make the ICMP_UNREACH hack possible */

			ip_hdr_cksum_adj = cksum_adjust(0, iph->check, ocsum);
			/* XXX this would also work:
			 * ip_hdr_cksum_adj = cksum_adjust(0, 
			 *				   old_src, iph->saddr);
			 * ip_hdr_cksum_adj = cksum_adjust(ip_hdr_cksum_adj, 
			 *				   old_dst, iph->daddr);
			 */
		} else {
			iph->check = 0; /* xxx maybe adjust anyway? */
		}
	}

	if (iphlen < (int)sizeof(struct iphdr)) {
		error("ipv4 hdr len is too short: %d\n", iphlen);
		return PARSE_ERROR; /* malformed packet, keep */
	}

	if (iphlen > (int)sizeof(struct iphdr)) {
		/* we've got options, blah... */
		int optlen = min(iphlen, len) - sizeof(struct iphdr);
		if (optlen > 0) {
			parse_ret_t opt_res =
				parse_ipv4_opt(iph, optlen);
			if (opt_res.cond != PARSE_COND_OK)
				memset((u_char *)(iph+1), 0, optlen);
		}
	}
	p = (char *)p + iphlen;
	len -= iphlen;
	if (len < 0) {
		warn("ipv4 header truncated2\n");
		/* unlike "truncated1", tcpdpriv will handle this */
		return PARSE_SHORT;
	}

	if (ipsw[next_proto].parser == parse_ipv6_opt) {
		error("ipv6 option (0x%02x) in ipv4 pkt\n", next_proto);
		return PARSE_ERROR; /* malformed packet, keep */
	}

	foff = ntohs(iph->frag_off);
	if ((foff & IP_OFFMASK) != 0) {
		/* fragmented packet, erase payload */
		ret = parse_zerowhole(p, len, next_proto);
		ret.chop_tail += pad;
		return ret;
	}

	ret = call_parser(p, len, next_proto);
	ret.chop_tail += pad;
	return ret;
}

static parse_ret_t
parse_ipv4_opt(struct iphdr *iph, int len) {
	int i, l, t;
	u_char *opt = (u_char *)(iph+1);

	if (!scramble_crypto_fn)
		return PARSE_OK;

	while (len > 0) {
		switch (*opt) {
		case IPOPT_EOL: /* end of list */
			return PARSE_OK;
		case IPOPT_NOP:
			++opt;
			--len;
			continue;
		case IPOPT_RR:		/* record route */
		case IPOPT_LSRR: 	/* loose source route */
		case IPOPT_SSRR:	/* strict source route */
			l = opt[1]; /* length of list */
			t = opt[2]; /* pointer */
			
			if (l < 3) l = 3; /* otherwise an infinite loop may result */
			if (t > len)
				t = len;
			if (t > l)
				t = l;
			for (i = 3; i < t; i += sizeof(uint32_t)) {
				u_char *a = opt + i;
				uint32_t old, new;
				memcpy(&old, a, sizeof(uint32_t));
				new = (*_scrambler_ip4)(old, pass_bits4);
				memcpy(a, &old, sizeof(uint32_t));
				iph->check = cksum_adjust(iph->check, old, new);
			}
			len -= l;
			opt += l;
			continue;
		case IPOPT_SEC:		/* security */
			l = 11;
			len -= l;
			opt += l;
			continue;
		case IPOPT_TS:		/* timestamp */
			l = opt[1];
			if (l < 4) l = 4; /* otherwise an infinite loop may result */
			len -= l;
			opt += l;
			continue;
		case IPOPT_SATID:	/* stream id */
		case IPOPT_RA:		/* router alert */
			l = 4; /* otherwise an infinite loop may result */
			len -= l;
			opt += l;
			continue;
		default:
			error("unknown ipv4 option: 0x%x\n", *opt);
			return PARSE_ERROR; /* will be zeroed out in parse_ipv4() */
		}
	}
	if (len < 0) 
		return PARSE_SHORT;
	return PARSE_OK;
}

static parse_ret_t
parse_ipv6(void *p, int len, int proto) {
	struct ip6_hdr *ip6h = p;
	parse_ret_t ret;
	int vers, next_proto;

	if (len < (int)sizeof(struct ip6_hdr)) {
		warn("ipv6 header truncated1\n");
		/* there may be an address leak here.  Wipe it clean, 
		   because tcpdpriv won't handle it */
		memset(p, 0, len);
		ret.cond = PARSE_COND_SHORT;
		ret.chop_tail = len;
		return ret;
	}

	vers = (ip6h->ip6_vfc & 0xf0) >> 4;

	if (vers != 6) {
		error("expected ipv6, but got ipv%d\n", vers);
		return PARSE_ERROR; /* malformed packet */
	}

	if (scramble_crypto_fn) {
		(*_scrambler_ip6)(&ip6h->ip6_src, pass_bits6);
		(*_scrambler_ip6)(&ip6h->ip6_dst, pass_bits6);
		/* TODO: save the adjustment for TCP/UDP pseudo header checksum */
	}
	
	next_proto = ip6h->ip6_nxt;
	p = (char *)p + sizeof(struct ip6_hdr);
	len -= (int)sizeof(struct ip6_hdr);

	return call_parser(p, len, next_proto); //ok if len < 0
}

static parse_ret_t
parse_icmp4(void *p, int len, int proto) {
	struct icmphdr *icmph = p;

	if (!scramble_crypto_fn)
		return PARSE_OK; //not scrambling

	if (len <= offsetof(struct icmphdr, un))
		return PARSE_SHORT; /* nothing to scramble here */

	switch (icmph->type) {
	case ICMP_REDIRECT:
		if (len < offsetof(struct icmphdr, un) + sizeof(icmph->un.gateway)) {
			memset(&icmph->un.gateway, 0, len-offsetof(struct icmphdr, un));
			return PARSE_SHORT;
		}
		/* scramble gateway address */
		{
			uint32_t old = icmph->un.gateway;
			icmph->un.gateway = (*_scrambler_ip4)(old, pass_bits4);
			icmph->checksum = cksum_adjust(icmph->checksum, 
						       old, icmph->un.gateway);
		}
		/* FALLTHROUGH */
	case ICMP_UNREACH:
	case ICMP_SOURCE_QUENCH:		
	case ICMP_TIME_EXCEEDED:
	case ICMP_PARAMETERPROB:
		len -= (offsetof(struct icmphdr, un) 
			+ sizeof(icmph->un.gateway));
		p = (char *)p + offsetof(struct icmphdr, un) 
			+ sizeof(icmph->un.gateway);
		/* TODO: will need to update icmph->checksum to reflect ip scrambling */
		/* a hack: 	call_parser(p, len, IPPROTO_IPIP);
		 *		read ip_hdr_cksum_adj and update the checksum */
		icmph->checksum = 0; /* xxx for now, see above */
		return call_parser(p, len, IPPROTO_IPIP); /* OK if len < 0 */

	default:
		return PARSE_OK;
	}

	return PARSE_OK;
}

static parse_ret_t
parse_icmp6(void *p, int len, int proto) {
	struct icmp6_hdr *icmp6h = p;
	
	if (!scramble_crypto_fn)
		return PARSE_OK;

	if (len <= offsetof(struct icmp6_hdr, icmp6_dataun))
		return PARSE_SHORT; /* nothing to hide here */

	switch (icmp6h->icmp6_type) {
	case ICMP6_DST_UNREACH:
	case ICMP6_PACKET_TOO_BIG:
	case ICMP6_TIME_EXCEEDED:
	case ICMP6_PARAM_PROB:
		len -= offsetof(struct icmp6_hdr, icmp6_dataun);
		p = (char *)p + offsetof(struct icmp6_hdr, icmp6_dataun);
		icmp6h->icmp6_cksum = 0; /* xxx for now */
		return call_parser(p, len, IPPROTO_IPV6); /* TODO: will have to adjust icmp6h->icmp6_cksum */
#ifndef ND_NEIGHBOR_REDIRECT
#define ND_NEIGHBOR_REDIRECT	137
#endif
	case ND_NEIGHBOR_REDIRECT:
		len -= offsetof(struct icmp6_hdr, icmp6_dataun);
		assert(len >= 0); /* because of above */
		p = (char *)p + offsetof(struct icmp6_hdr, icmp6_dataun);
		if (len < (int)sizeof(struct in6_addr)) {
			memset(p, 0, len);
			return PARSE_SHORT;
		}
		(*_scrambler_ip6)(p, pass_bits6);
		/* TODO: adjust icmp6h->icmp6_cksum */
		len -= sizeof(struct in6_addr);
		p = (char *)p + sizeof(struct in6_addr);
		/* FALLTHROUGH */
	case ND_NEIGHBOR_SOLICIT:
	case ND_NEIGHBOR_ADVERT:
		if (len < (int)sizeof(struct in6_addr)) {
			memset(p, 0, len);
			return PARSE_SHORT;
		}
		(*_scrambler_ip6)(p, pass_bits6);
		/* TODO: adjust icmp6h->icmp6_cksum */
		return PARSE_OK;
	default:
		error("unknown icmp6 type (0x%x)\n", icmp6h->icmp6_type);
		return PARSE_OK;
	}
	return PARSE_OK;
}

static parse_ret_t
parse_ipv6_opt(void *p, int len, int proto) {
	struct ip6_ext *ext = p;
	int next_proto;
	int optlen;

	if (len < (int)sizeof(struct ip6_ext)) {
		warn("ipv6 option 0x%02x truncated1\n", proto);
		return PARSE_SHORT;
	}
	next_proto = ext->ip6e_nxt;
	optlen	   = (ext->ip6e_len + 1)*8; /* whole length */
	
	switch (proto) {
	case IPPROTO_HOPOPTS:
	case IPPROTO_DSTOPTS:
		{
			int zlen = min(len, optlen) - (int)sizeof(struct ip6_ext);
			if (zlen > 0)
				memset(ext+1, 0, zlen);
			/* it's OK if len < zlen */
		}
		break;
	case IPPROTO_ROUTING:
		{
			struct ip6_rthdr *rh = p;
			int zlen = min(len, optlen) - (int)sizeof(struct ip6_rthdr);
			if (zlen > 0)
				memset(rh+1, 0, zlen);
			/* it's OK if len < zlen */
		}
		break;
	case IPPROTO_FRAGMENT:
		optlen = sizeof(struct ip6_frag); /* fixed size */
		break;
	default:
		error("unknown ipv6 option: 0x%x\n", proto);
		memset(p, 0, len);
		return PARSE_ERROR;
	}
	len -= optlen;
	if (len < 0)
		return PARSE_SHORT;
	
	p = ext + 1;
	p = (char*)p + optlen;

	return call_parser(p, len, next_proto);
}

static parse_ret_t
parse_tcp(void *p, int len, int proto) {
	parse_ret_t ret;
	struct tcphdr *tcph = p;
	char *data;
	int datalen;
	int tcphlen;

	if (len < (int)sizeof(struct tcphdr)) {
		warn("tcp header truncated\n");
		return PARSE_SHORT;
	}

	tcphlen = max(sizeof(struct tcphdr), (tcph->doff << 2));
	if (len < tcphlen) {
		/* be quiet here */
		return PARSE_SHORT;
	}

	datalen = len - tcphlen;
	if (datalen > 0) {
		/* wipe-out user's data */
		data = (char *)p + tcphlen;
		cond_memset(data, 0, datalen);
		/* removal of payload invalidates checksum, so
		 * don't bother to adjust */
		tcph->check = 0;
	} else {
		/* OK, we rolled in all our adjustments into one
		 * static ip_hdr_cksum_adj.  Now we need to
		 * add its inversion (or subtract itself).
		 */
		tcph->check = cksum_adjust(tcph->check, 
					   ip_hdr_cksum_adj, 0);

		ip_hdr_cksum_adj = 0; /* not really needed */
	}
	ret.cond = PARSE_COND_OK;
	ret.chop_tail = datalen;

	return ret;
}

static parse_ret_t
parse_udp(void *p, int len, int proto) {
	parse_ret_t ret;
	struct udphdr *udph = p;
	char *data;
	int datalen;
	
	if (len < (int)sizeof(struct udphdr)) {
		warn("udp header truncated\n");
		return PARSE_SHORT;
	}

	datalen = len - (int)sizeof(struct udphdr);
	data = (char *)p + sizeof(struct udphdr);

	if (datalen > 0) {
		data = (char *)p + sizeof(struct udphdr);
		cond_memset(data, 0, datalen);
		/* removal of payload invalidates checksum, so
		 * don't bother to adjust */
		udph->check = 0;
	} else {
		/* same as TCP: adjust for pseudo-header renumber */
		if (udph->check	/* (checksum == 0) == no_check_sum */
		    && ip_hdr_cksum_adj) {
			udph->check = cksum_adjust(udph->check, 
						   ip_hdr_cksum_adj, 0);
			if (!udph->check)
				udph->check = ~udph->check;

			ip_hdr_cksum_adj = 0; /* not really needed */
		}
	}

	ret.cond = PARSE_COND_OK;
	ret.chop_tail = datalen;

	return ret;
}

static parse_ret_t
parse_esp(void *p, int len, int proto) {
	parse_ret_t ret;
	struct {
		uint32_t spi; /* only keep these fields */
		uint32_t seqno;
		/* variable length: payload data */
		/* variable length: padding */
		/* 8 bits: pad length */
		/* 8 bits: next header */
		/* variable length: auth data */
	} esp;
	len -= (int)sizeof(esp);
	if (len < 0) {
		warn("esp header truncated\n");
		return PARSE_SHORT;
	}
	p = (char *)p + sizeof(esp);

	cond_memset(p, 0, len);

	ret.cond = PARSE_COND_OK;
	ret.chop_tail = len;

	return ret;
}

static parse_ret_t
parse_gre(void *p, int len, int proto) {
	parse_ret_t ret;
	struct { /* only keep these fields */
		uint16_t flags_version;
		uint16_t protocol;
		uint16_t cksum;   /* or length in v1 */
		uint16_t offset;  /* or callid in v1 */
		uint32_t key;	  /* or seqno  in v1 */
		uint32_t seqno;   /* or ackno  in v1 */
		/* variable length: routing info */
		/* variable length: padding */
		/* 8 bits: pad length */
		/* 8 bits: next header */
		/* variable length: auth data */
	} gre;
	len -= (int)sizeof(gre);
	if (len < 0) {
		warn("gre header truncated\n");
		return PARSE_SHORT;
	}
	p = (char *)p + sizeof(gre);

	cond_memset(p, 0, len);

	ret.cond = PARSE_COND_OK;
	ret.chop_tail = len;

	return ret;
}

static parse_ret_t
parse_ah(void *p, int len, int proto) {
	parse_ret_t ret;
	struct { /* only keep these fields */
		uint8_t  nexthdr;
		uint8_t  length;
		uint16_t zeros;
		uint32_t spi;
		uint32_t seqno;
		/* variable length: auth data */
	} ah;

	len -= (int)sizeof(ah);

	if (len < 0) {
		warn("ah header truncated\n");
		return PARSE_SHORT;
	}

	p = (char *)p + sizeof(ah);

	cond_memset(p, 0, len);

	ret.cond = PARSE_COND_OK;
	ret.chop_tail = len;

	return ret;
}

static parse_ret_t
parse_zerowhole(void *p, int len, int proto) {
	parse_ret_t ret;
	if (len > 0)
		cond_memset(p, 0, len);
	else
		len = 0;

	ret.cond = PARSE_COND_OK;
	ret.chop_tail = len;

	return ret;
}

static parse_ret_t
parse_nofurther(void *p, int len, int proto) {
	/* silently return */
	return PARSE_OK;
}

static parse_ret_t
parse_unassigned(void *p, int len, int proto) {

	error("Unassigned proto %d\n", proto);

	/* don't scrub */
	return PARSE_OK;
}

static parse_ret_t
parse_defaulterr(void *p, int len, int proto) {
	parse_ret_t ret;

	error("don't know how to parse proto %d\n", proto);

	if (len > 0) {
		memset(p, 0, len);
	} else {
		len = 0;
	}
	ret.cond = PARSE_COND_ERROR;
	ret.chop_tail = len;

	return ret;
}

static void
init_filter(const char *filter_expr, struct bpf_program *fp, int snaplen, int pcap_dlt) 
{
	int error;
	if ((error = pcap_compile_nopcap(snaplen, pcap_dlt, fp, filter_expr, 
				1 /*optimize*/, 0 /*mask*/)) < 0) {
		panic("pcap compile error: %s\n", pcap_strerror(error));
		/* not reached */
	}
		
}
static int
run_filter(const struct bpf_program *fp, const void *pkt, int plen, int wlen)
{
	uint ret = bpf_filter(fp->bf_insns, pkt, wlen, plen);
	return (int) ret;
} 

static void
panic(char *fmt, ...)
{
	va_list	ap;

	(void)fprintf(stderr, "%s: panic: ", prog);
	va_start(ap, fmt);
	(void)vfprintf(stderr, fmt, ap);
	va_end(ap);
	exit(1);
}

static void
warn(char *fmt, ...)
{
	va_list	ap;
	if (!do_warnings) return;

	(void)fprintf(stderr, "%s: warning: ", prog);
	va_start(ap, fmt);
	(void)vfprintf(stderr, fmt, ap);
	va_end(ap);
}

static void
error(char *fmt, ...)
{
	va_list	ap;

	(void)fprintf(stderr, "%s: error: ", prog);
	va_start(ap, fmt);
	(void)vfprintf(stderr, fmt, ap);
	va_end(ap);
}

static char usgtxt[] = 
    "Usage: %s [-FLSachlmnps] [--pass4=<num>] [--pass6=<num>] [<input trace> [<output trace>]]\n"
    "    -F <filter>     process only packets matching <filter>-expression\n"
    "    -L <freakdump>  save freaky packets in the file <freakdump>\n"
    "    -M              force monotonously increasing timestamps (may skip over packets)\n"
    "    -P              use pcap for input/output\n"
    "    -R              try to repair busted files (will skip some bytes)\n"
    "    -S              report extended stats at the end\n"
    "    -a              parse ATM (not supported)\n"
    "    -c              chop off scrubbed packet tails (to save space)\n"
    "    -n <filter>     do not zero out payload for packets matching <filter>\n"
    "    -h              this page\n"
    "    -l <logfile>    send log to <logfile> (stderr by default)\n"
    "    -m              enable mac-layer scrambling (depends on the keyfile)\n"
    "    -r              reverse anonymization using key in -s\n"
    "    -s <keyfile>    enable ip address scrambling using <keyfile>; <keyfile>\n"
    "                    <keyfile> is created if doesn't exist\n"
    "    -v              preserve vlan tags (no scrambling)\n"
    "    --pass4=<num>   pass <num> higher bits of ipv4 unchanged\n"
    "    --pass6=<num>   pass <num> higher bits of ipv6 unchanged\n"
    "    --nocksum       do not attempt to adjust checksums\n"
    "    --verbose       print all kinds of warnings\n"
    "    <input trace>   defaults to stdin\n"
    "    <output trace>  defaults to stdout, if filename is given and exists, will be appended to\n";

static void
usage(void)
{
	(void)fprintf(stderr, usgtxt, prog);
	exit(0);
}
