/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; irlen); 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> 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] [ []]\n\n" "Options are:\n" " -F process only packets matching -expression\n" " -L save freaky packets in the file \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 do not zero out payload for packets matching \n" " -h this page\n" " -l send log to (stderr by default)\n" " -m enable mac-layer scrambling (depends on the keyfile)\n" " -r reverse anonymization using key in -s\n" " -s enable ip address scrambling using ; \n" " 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= pass higher bits of ipv4 unchanged\n" " --pass6= pass higher bits of ipv6 unchanged\n" " --pnat= 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=, --dont-scramble=\n" " do not scramble addresses that fall into the specified \n" " prefixes in can be separated by commas, semicolons, or spaces\n" " --nocksum do not attempt to adjust checksums\n" " --verbose print all kinds of warnings\n" " defaults to stdin\n" " 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); }