summaryrefslogtreecommitdiffstats
path: root/dag_scrubber.c
diff options
context:
space:
mode:
Diffstat (limited to 'dag_scrubber.c')
-rw-r--r--dag_scrubber.c1996
1 files changed, 1996 insertions, 0 deletions
diff --git a/dag_scrubber.c b/dag_scrubber.c
new file mode 100644
index 0000000..a512850
--- /dev/null
+++ b/dag_scrubber.c
@@ -0,0 +1,1996 @@
+/* -*- Mode:C; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */
+/*
+ * Copyright (C) 2004-2021 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 "cryptopANT.h"
+#include "scramble_wlw.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;
+
+typedef struct pnat_ip4_table_ {
+ uint32_t ip4_from;
+ uint32_t ip4_to;
+ struct pnat_ip4_table_ *next;
+} pnat_ip4_table_t;
+
+typedef struct pnat_ip6_table_ {
+ struct in6_addr ip6_from;
+ struct in6_addr ip6_to;
+ struct pnat_ip6_table_ *next;
+} pnat_ip6_table_t;
+
+static pnat_ip4_table_t *pnat_ip4_table = NULL;
+static pnat_ip6_table_t *pnat_ip6_table = NULL;
+
+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_raw (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);
+#if 0
+static parse_ret_t parse_unassigned(void *, int, int);
+#endif
+static parse_ret_t parse_defaulterr(void *, int, int);
+
+static void build_pnat_tables (char *);
+static uint32_t pnat_ip4(uint32_t, int);
+static void pnat_ip6(struct in6_addr *, int);
+
+static void usage (void);
+static void panic (const char *, ...) __attribute__((noreturn, format (printf, 1, 2)));
+static void error (const char *, ...) __attribute__((unused, format (printf, 1, 2)));
+static void warn (const 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 const char *prog;
+static const char *fi = NULL, *fo = NULL, *logdump = NULL;
+static FILE *f_input, *f_output, *f_logdump;
+static int do_pcap = 1;
+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 char *do_pnat = NULL;
+static char *noscramble_prefixes = "";/* --no-scramble/--dont-scramble option */
+static int keep_payload = 0;
+static int do_scrambling = 0;
+
+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;
+
+ /* 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;
+
+
+#define PASS4 256
+#define PASS6 257
+#define NOCKSUM 258
+#define VERBOSE 259
+#define OPT_PNAT 260
+#define OPT_DAG 261
+#define OPT_PCAP 262
+#define OPT_NOSCRAMBLE 263
+
+ struct option long_options[] = {
+ {"help", 0, NULL, 'h'},
+ {"pass4", 1, NULL, PASS4},
+ {"pass6", 1, NULL, PASS6},
+ {"nocksum", 0, NULL, NOCKSUM},
+ {"verbose", 0, NULL, VERBOSE},
+ {"pnat", 1, NULL, OPT_PNAT},
+ {"no-scramble", 1, NULL, OPT_NOSCRAMBLE},
+ {"dont-scramble", 1, NULL, OPT_NOSCRAMBLE},
+ {"pcap", 0, NULL, OPT_PCAP},
+ {"dag", 0, NULL, OPT_DAG},
+ {"erf", 0, NULL, OPT_DAG},
+
+ };
+ prog = argv[0];
+ while((opt = getopt_long(argc, argv,
+ "DF:L:MPRSW:chl: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 OPT_PNAT:
+ do_pnat = optarg;
+ 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': /* fallthrough */
+ case OPT_PCAP:
+ do_pcap = 1; /* Use pcap for input/output */
+ break;
+ case OPT_DAG:
+ do_pcap = 0; /* Use erf 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;
+ case OPT_NOSCRAMBLE:
+ noscramble_prefixes = optarg;
+ 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 (do_pnat) {
+ if (scramble_crypto_fn) {
+ error("running in PNAT mode, scrambling is disabled\n");
+ scramble_crypto_fn = NULL;
+ }
+ do_scrambling = 1;
+ keep_payload = 1;
+ build_pnat_tables(do_pnat);
+ scrambler_ip4 = &pnat_ip4;
+ scrambler_ip6 = &pnat_ip6;
+ }
+
+ scramble_wlw_init(noscramble_prefixes, scrambler_ip4, scrambler_ip6);
+
+ 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");
+ do_scrambling = 1;
+ } 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 */
+
+ if (pcap_linktype == DLT_RAW)
+ ret = parse_raw((u_char*)bytes, h->caplen, h->len, pcap_linktype);
+ else
+ 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 = (u_char *)p;
+ u_char *nh = NULL; /*next hdr */
+ eth_hdr_t *eth_hdr = (eth_hdr_t *)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 = (erf_eth_rec_t *)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 oldip, uint32_t newip)
+{
+ if (nocksum || oldip == newip) 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" (oldip));
+ asm volatile ("\t addl %1,%0" : "+r" (sum) : "g" (oldip));
+ asm volatile ("\t adcl %1,%0" : "+r" (sum) : "g" (newip));
+ asm volatile ("\t adcl $0,%0" : "+r" (sum));
+#else
+ sum ^= 0xffff;
+ oldip = ~oldip;
+ sum += (old & 0xffff) + (oldip >> 16);
+ sum += (new & 0xffff) + (newip >> 16);
+#endif
+ sum = (sum & 0xffff) + (sum >> 16);
+ if (sum & 0xffff0000)
+ ++sum;
+ return ((~sum) & 0xffff); /* (~s) & 0xffff */
+}
+
+static parse_ret_t
+parse_raw(void *p, int len, int wlen, int type)
+{
+ parse_ret_t ret;
+
+ if (len < 0) return PARSE_SHORT; /* does not happen b/c of the above */
+
+ if (pass_filter_expr &&
+ !run_filter(&pass_pf, p, 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, p, len, wlen):0;
+
+ struct iphdr *iph = (struct iphdr *)p;
+ switch (iph->version) {
+ case 4:
+ return call_parser(p, len, PARSE_IP4);
+ case 6:
+ return call_parser(p, len, PARSE_IP6);
+ default:
+ cond_memset(p, 0, len);
+ ret.chop_tail = len;
+ ret.cond = PARSE_COND_OK;
+
+ error("unknown ip version: %d\n", iph->version);
+ cond_memset(p, 0, len);
+ ret.chop_tail = len;
+ ret.cond = PARSE_COND_ERROR;
+
+ return ret;
+ }
+ /* not reached */
+}
+
+
+static parse_ret_t
+parse_ipv4(void *p, int len, int proto) {
+ struct iphdr *iph = (struct iphdr *)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 (do_scrambling) {
+ uint32_t old_src = iph->saddr;
+ uint32_t old_dst = iph->daddr;
+
+ iph->saddr = scramble_wlw_v4(old_src, pass_bits4);
+ iph->daddr = scramble_wlw_v4(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 (!do_scrambling)
+ 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 oldip, newip;
+ memcpy(&oldip, a, sizeof(uint32_t));
+ newip = scramble_wlw_v4(oldip, pass_bits4);
+ memcpy(a, &oldip, sizeof(uint32_t));
+ iph->check = cksum_adjust(iph->check, oldip, newip);
+ }
+ 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 = (struct ip6_hdr *)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 (do_scrambling) {
+ scramble_wlw_v6(&ip6h->ip6_src, pass_bits6);
+ scramble_wlw_v6(&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 = (struct icmphdr *)p;
+
+ if (!do_scrambling)
+ 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 = scramble_wlw_v4(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 = (struct icmp6_hdr *)p;
+
+ if (!do_scrambling)
+ 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;
+ }
+ scramble_wlw_v6((struct in6_addr *)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;
+ }
+ scramble_wlw_v6((struct in6_addr *)p, pass_bits6);
+ /* TODO: adjust icmp6h->icmp6_cksum */
+ return PARSE_OK;
+#ifndef ICMP6_ECHO_REQUEST
+#define ICMP6_ECHO_REQUEST 128
+#endif
+#ifndef ICMP6_ECHO_REPLY
+#define ICMP6_ECHO_REPLY 129
+#endif
+ case ICMP6_ECHO_REQUEST:
+ case ICMP6_ECHO_REPLY:
+ if (len < offsetof(struct icmp6_hdr, icmp6_dataun))
+ return PARSE_SHORT;
+
+ /* TODO: adjust icmp6h->icmp6_cksum */
+ icmp6h->icmp6_cksum = 0; /* xxx for now */
+
+ len -= sizeof(struct icmp6_hdr);
+ if (len > 0) {
+ parse_ret_t ret = { .chop_tail = len, .cond = PARSE_COND_OK };
+ p = (char *)p + sizeof(struct icmp6_hdr);
+ memset(p, 0, len);
+ return ret;
+ }
+ return PARSE_SHORT;
+
+ 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 = (struct ip6_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 = (struct ip6_rthdr *)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 = (struct tcphdr *)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 */
+ if (!keep_payload) 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 = (struct udphdr *)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 */
+ if (!keep_payload) 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;
+}
+#if 0
+static parse_ret_t
+parse_unassigned(void *p, int len, int proto) {
+
+ error("Unassigned proto %d\n", proto);
+
+ /* don't scrub */
+ return PARSE_OK;
+}
+#endif
+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;
+ pcap_t *cap = pcap_open_dead(pcap_dlt, snaplen);
+ if (cap == NULL) {
+ panic("pcap_open_dead(%d, %d) failed\n", pcap_dlt, snaplen);
+ }
+ if ((error = pcap_compile(cap, fp, filter_expr, 1 /*optimize*/, 0 /*mask*/)) < 0) {
+ panic("pcap compile error: %s\n", pcap_strerror(error));
+ /* not reached */
+ }
+ pcap_close(cap);
+
+}
+
+static int
+run_filter(const struct bpf_program *fp, const void *pkt, int plen, int wlen)
+{
+ uint ret = bpf_filter(fp->bf_insns, (const u_char *)pkt, wlen, plen);
+ return (int) ret;
+}
+
+static void
+panic(const 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(const 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(const char *fmt, ...)
+{
+ va_list ap;
+
+ (void)fprintf(stderr, "%s: error: ", prog);
+ va_start(ap, fmt);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static void
+build_pnat_tables(char *pnat)
+{
+ char *saveptr = NULL;
+ char *tok;
+ pnat_ip4_table_t *e4 = NULL;
+ pnat_ip6_table_t *e6 = NULL;
+ for (tok = strtok_r(pnat, ",", &saveptr); tok; tok = strtok_r(NULL, ",", &saveptr)) {
+ struct in_addr ip4_from, ip4_to;
+ struct in6_addr ip6_from, ip6_to;
+ char *from_ip, *to_ip;
+ char *sep = index(tok, '-');
+
+ if (sep == NULL) {
+ error("unparseable pnat mapping: %s missing `-` separator\n", tok);
+ exit(1);
+ }
+ *sep = '\0';
+ from_ip = tok;
+ to_ip = sep+1;
+ if (inet_pton(AF_INET, from_ip, &ip4_from) == 1) {
+ //fprintf(stderr, "%s, %x\n", tok, ip4.s_addr);
+ if (inet_pton(AF_INET, to_ip, &ip4_to) != 1) {
+ error("cannot map ip4 (%s) to non-ip4 (%s)\n", from_ip, to_ip);
+ exit(1);
+ }
+ e4 = (pnat_ip4_table_t *)malloc(sizeof(*e4));
+ e4->ip4_from = ip4_from.s_addr;
+ e4->ip4_to = ip4_to.s_addr;
+ e4->next = pnat_ip4_table;
+ pnat_ip4_table = e4;
+ continue;
+ }
+ if (inet_pton(AF_INET6, from_ip, &ip6_from) == 1) {
+ //fprintf(stderr, "%s, %x\n", tok, ip4.s_addr);
+ if (inet_pton(AF_INET6, to_ip, &ip6_to) != 1) {
+ error("cannot map ip6 (%s) to non-ip6 (%s)\n", from_ip, to_ip);
+ exit(1);
+ }
+ e6 = (pnat_ip6_table_t *)malloc(sizeof(*e6));
+ e6->ip6_from = ip6_from;
+ e6->ip6_to = ip6_to;
+ e6->next = pnat_ip6_table;
+ pnat_ip6_table = e6;
+ continue;
+ }
+ }
+}
+
+uint32_t
+pnat_ip4(uint32_t ip, int unused)
+{
+ UNUSED(unused);
+ pnat_ip4_table_t *e4;
+ for (e4 = pnat_ip4_table; e4; e4=e4->next) {
+ if (e4->ip4_from == ip) {
+ return e4->ip4_to;
+ }
+ }
+ return ip;
+}
+void
+pnat_ip6(struct in6_addr *input, int unused)
+{
+ UNUSED(unused);
+ pnat_ip6_table_t *e6;
+ for (e6 = pnat_ip6_table; e6; e6=e6->next) {
+ if (memcmp(&e6->ip6_from, input, sizeof(*input)) == 0) {
+ *input = e6->ip6_to;
+ return;
+ }
+ }
+}
+
+
+static char usgtxt[] =
+ "Usage: %s [OPTIONS] [<input trace> [<output trace>]]\n\n"
+ "Options are:\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 (default)\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"
+ " --erf, --dag use ERF (DAG) format for input/output\n"
+ " --pass4=<num> pass <num> higher bits of ipv4 unchanged\n"
+ " --pass6=<num> pass <num> higher bits of ipv6 unchanged\n"
+ " --pnat=<expr> instead of scrambling use fixed substitutions, expr is comma separated list:\n"
+ " e.g. --pnat='1.2.3.4-2.3.4.5,1::-3::'\n"
+ " --no-scramble=<prefix_list>, --dont-scramble=<prefix_list>\n"
+ " do not scramble addresses that fall into the specified <prefix_list>\n"
+ " prefixes in <prefix_list> can be separated by commas, semicolons, or spaces\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);
+}