diff options
Diffstat (limited to 'dag_scrubber.c')
-rw-r--r-- | dag_scrubber.c | 1996 |
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*)ð_rec->eth_hdr + eth_wlen, 0, overcapture); + len = eth_wlen; + } + + + ret = parse_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); +} |