/* * * Copyright (c) 2007-2016 The University of Waikato, Hamilton, New Zealand. * All rights reserved. * * This file is part of libtrace. * * This code has been developed by the University of Waikato WAND * research group. For further information please see http://www.wand.net.nz/ * * libtrace is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * libtrace 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * */ #include "config.h" #include "Anon.h" #include "libtrace_parallel.h" #include #include #include #include #include #include #include #include #include #include enum enc_type_t { ENC_NONE, ENC_CRYPTOPAN, ENC_PREFIX_SUBSTITUTION }; bool enc_source_opt = false; bool enc_dest_opt = false; enum enc_type_t enc_type = ENC_NONE; char *enc_key = NULL; int level = -1; trace_option_compresstype_t compress_type = TRACE_OPTION_COMPRESSTYPE_NONE; struct libtrace_t *inptrace = NULL; static void cleanup_signal(int signal) { (void)signal; // trace_pstop isn't really signal safe because its got lots of locks in it trace_pstop(inptrace); } static void usage(char *argv0) { fprintf(stderr,"Usage:\n" "%s flags inputfile outputfile\n" "-s --encrypt-source Encrypt the source addresses\n" "-d --encrypt-dest Encrypt the destination addresses\n" "-c --cryptopan=key Encrypt the addresses with the cryptopan\n" " prefix preserving\n" "-F --keyfile=file A file containing the cryptopan key\n" "-p --prefix=C.I.D.R/bits Substitute the prefix of the address\n" "-h --help Print this usage information\n" "-z --compress-level Compress the output trace at the specified level\n" "-Z --compress-type Compress the output trace using the specified" " compression algorithm\n" "-t --threads=max Use this number of threads for packet processing\n" "-f --filter=expr Discard all packets that do not match the\n" " provided BPF expression\n" ,argv0); exit(1); } /* Incrementally update a checksum */ static void update_in_cksum(uint16_t *csum, uint16_t old, uint16_t newval) { uint32_t sum = (~htons(*csum) & 0xFFFF) + (~htons(old) & 0xFFFF) + htons(newval); sum = (sum & 0xFFFF) + (sum >> 16); *csum = htons(~(sum + (sum >> 16))); } UNUSED static void update_in_cksum32(uint16_t *csum, uint32_t old, uint32_t newval) { update_in_cksum(csum,(uint16_t)(old>>16),(uint16_t)(newval>>16)); update_in_cksum(csum,(uint16_t)(old&0xFFFF),(uint16_t)(newval&0xFFFF)); } /* Ok this is remarkably complicated * * We want to change one, or the other IP address, while preserving * the checksum. TCP and UDP both include the faux header in their * checksum calculations, so you have to update them too. ICMP is * even worse -- it can include the original IP packet that caused the * error! So anonymise that too, but remember that it's travelling in * the opposite direction so we need to encrypt the destination and * source instead of the source and destination! */ static void encrypt_ips(Anonymiser *anon, struct libtrace_ip *ip, bool enc_source,bool enc_dest) { libtrace_icmp_t *icmp=trace_get_icmp_from_ip(ip,NULL); if (enc_source) { uint32_t new_ip=htonl(anon->anonIPv4(ntohl(ip->ip_src.s_addr))); ip->ip_src.s_addr = new_ip; } if (enc_dest) { uint32_t new_ip=htonl(anon->anonIPv4(ntohl(ip->ip_dst.s_addr))); ip->ip_dst.s_addr = new_ip; } if (icmp) { /* These are error codes that return the IP packet * internally */ if (icmp->type == 3 || icmp->type == 5 || icmp->type == 11) { char *ptr = (char *)icmp; encrypt_ips(anon, (struct libtrace_ip*)(ptr+ sizeof(struct libtrace_icmp)), enc_dest, enc_source); } if (enc_source || enc_dest) icmp->checksum = 0; } } static void encrypt_ipv6(Anonymiser *anon, libtrace_ip6_t *ip6, bool enc_source, bool enc_dest) { uint8_t previp[16]; if (enc_source) { memcpy(previp, &(ip6->ip_src.s6_addr), 16); anon->anonIPv6(previp, (uint8_t *)&(ip6->ip_src.s6_addr)); } if (enc_dest) { memcpy(previp, &(ip6->ip_dst.s6_addr), 16); anon->anonIPv6(previp, (uint8_t *)&(ip6->ip_dst.s6_addr)); } } static libtrace_packet_t *per_packet(libtrace_t *trace, libtrace_thread_t *t, void *global, void *tls, libtrace_packet_t *packet) { struct libtrace_ip *ipptr; libtrace_ip6_t *ip6; libtrace_udp_t *udp = NULL; libtrace_tcp_t *tcp = NULL; libtrace_icmp6_t *icmp6 = NULL; Anonymiser *anon = (Anonymiser *)tls; libtrace_generic_t result; if (IS_LIBTRACE_META_PACKET(packet)) return packet; ipptr = trace_get_ip(packet); ip6 = trace_get_ip6(packet); if (ipptr && (enc_source_opt || enc_dest_opt)) { encrypt_ips(anon, ipptr,enc_source_opt,enc_dest_opt); ipptr->ip_sum = 0; } else if (ip6 && (enc_source_opt || enc_dest_opt)) { encrypt_ipv6(anon, ip6, enc_source_opt, enc_dest_opt); } /* Replace checksums so that IP encryption cannot be * reversed -- TODO allow checksums to be updated and remain valid * for the new addresses */ /* XXX replace with nice use of trace_get_transport() */ udp = trace_get_udp(packet); if (udp && (enc_source_opt || enc_dest_opt)) { udp->check = 0; } tcp = trace_get_tcp(packet); if (tcp && (enc_source_opt || enc_dest_opt)) { tcp->check = 0; } icmp6 = trace_get_icmp6(packet); if (icmp6 && (enc_source_opt || enc_dest_opt)) { icmp6->checksum = 0; } /* TODO: Encrypt IP's in ARP packets */ result.pkt = packet; trace_publish_result(trace, t, trace_packet_get_order(packet), result, RESULT_PACKET); return NULL; } static void *start_anon(libtrace_t *trace, libtrace_thread_t *t, void *global) { if (enc_type == ENC_PREFIX_SUBSTITUTION) { PrefixSub *sub = new PrefixSub(enc_key, NULL); return sub; } if (enc_type == ENC_CRYPTOPAN) { if (strlen(enc_key) < 32) { fprintf(stderr, "ERROR: Key must be at least 32 " "characters long for CryptoPan anonymisation.\n"); exit(1); } #ifdef HAVE_LIBCRYPTO CryptoAnon *anon = new CryptoAnon((uint8_t *)enc_key, (uint8_t)strlen(enc_key), 20); return anon; #else /* TODO nicer way of exiting? */ fprintf(stderr, "Error: requested CryptoPan anonymisation but " "libtrace was built without libcrypto support!\n"); exit(1); #endif } return NULL; } static void end_anon(libtrace_t *trace, libtrace_thread_t *t, void *global, void *tls) { Anonymiser *anon = (Anonymiser *)tls; delete(anon); } static void *init_output(libtrace_t *trace, libtrace_thread_t *t, void *global) { libtrace_out_t *writer = NULL; char *outputname = (char *)global; writer = trace_create_output(outputname); if (trace_is_err_output(writer)) { trace_perror_output(writer,"trace_create_output"); trace_destroy_output(writer); return NULL; } /* Hopefully this will deal nicely with people who want to crank the * compression level up to 11 :) */ if (level > 9) { fprintf(stderr, "WARNING: Compression level > 9 specified, setting to 9 instead\n"); level = 9; } if (level >= 0 && trace_config_output(writer, TRACE_OPTION_OUTPUT_COMPRESS, &level) == -1) { trace_perror_output(writer, "Configuring compression level"); trace_destroy_output(writer); return NULL; } if (trace_config_output(writer, TRACE_OPTION_OUTPUT_COMPRESSTYPE, &compress_type) == -1) { trace_perror_output(writer, "Configuring compression type"); trace_destroy_output(writer); return NULL; } if (trace_start_output(writer)==-1) { trace_perror_output(writer,"trace_start_output"); trace_destroy_output(writer); return NULL; } return writer; } static void write_packet(libtrace_t *trace, libtrace_thread_t *sender, void *global, void *tls, libtrace_result_t *result) { libtrace_packet_t *packet = (libtrace_packet_t*) result->value.pkt; libtrace_out_t *writer = (libtrace_out_t *)tls; if (writer != NULL && trace_write_packet(writer,packet)==-1) { trace_perror_output(writer,"writer"); trace_interrupt(); } trace_free_packet(trace, packet); } static void end_output(libtrace_t *trace, libtrace_thread_t *t, void *global, void *tls) { libtrace_out_t *writer = (libtrace_out_t *)tls; trace_destroy_output(writer); } int main(int argc, char *argv[]) { //struct libtrace_t *trace = 0; struct sigaction sigact; char *output = 0; char *compress_type_str=NULL; int maxthreads = 4; libtrace_callback_set_t *pktcbs = NULL; libtrace_callback_set_t *repcbs = NULL; int exitcode = 0; char *filterstring = NULL; libtrace_filter_t *filter = NULL; if (argc<2) usage(argv[0]); while (1) { int option_index; struct option long_options[] = { { "encrypt-source", 0, 0, 's' }, { "encrypt-dest", 0, 0, 'd' }, { "cryptopan", 1, 0, 'c' }, { "cryptopan-file", 1, 0, 'F' }, { "prefix", 1, 0, 'p' }, { "threads", 1, 0, 't' }, { "filter", 1, 0, 'f' }, { "compress-level", 1, 0, 'z' }, { "compress-type", 1, 0, 'Z' }, { "help", 0, 0, 'h' }, { NULL, 0, 0, 0 }, }; int c=getopt_long(argc, argv, "Z:z:sc:f:dp:ht:f:", long_options, &option_index); if (c==-1) break; switch (c) { case 'Z': compress_type_str=optarg; break; case 'z': level = atoi(optarg); break; case 's': enc_source_opt=true; break; case 'd': enc_dest_opt =true; break; case 'c': if (enc_key!=NULL) { fprintf(stderr,"You can only have one encryption type and one key\n"); usage(argv[0]); } enc_key=strdup(optarg); enc_type = ENC_CRYPTOPAN; break; case 'F': { if(enc_key != NULL) { fprintf(stderr,"You can only have one encryption type and one key\n"); usage(argv[0]); } FILE * infile = fopen(optarg,"rb"); if(infile == NULL) { perror("Failed to open cryptopan keyfile"); return 1; } enc_key = (char *) malloc(sizeof(char *) * 32); if(fread(enc_key,1,32,infile) != 32) { if(ferror(infile)) { perror("Failed while reading cryptopan keyfile"); } } fclose(infile); enc_type = ENC_CRYPTOPAN; break; } case 'f': filterstring = optarg; break; case 'p': if (enc_key!=NULL) { fprintf(stderr,"You can only have one encryption type and one key\n"); usage(argv[0]); } enc_key=strdup(optarg); enc_type = ENC_PREFIX_SUBSTITUTION; break; case 'h': usage(argv[0]); case 't': maxthreads=atoi(optarg); if (maxthreads <= 0) maxthreads = 1; break; default: fprintf(stderr,"unknown option: %c\n",c); usage(argv[0]); } } if (compress_type_str == NULL && level >= 0) { fprintf(stderr, "Compression level set, but no compression type was defined, setting to gzip\n"); compress_type = TRACE_OPTION_COMPRESSTYPE_ZLIB; } else if (compress_type_str == NULL) { /* If a level or type is not specified, use the "none" * compression module */ compress_type = TRACE_OPTION_COMPRESSTYPE_NONE; } /* I decided to be fairly generous in what I accept for the * compression type string */ else if (strncmp(compress_type_str, "gz", 2) == 0 || strncmp(compress_type_str, "zlib", 4) == 0) { compress_type = TRACE_OPTION_COMPRESSTYPE_ZLIB; } else if (strncmp(compress_type_str, "bz", 2) == 0) { compress_type = TRACE_OPTION_COMPRESSTYPE_BZ2; } else if (strncmp(compress_type_str, "lzo", 3) == 0) { compress_type = TRACE_OPTION_COMPRESSTYPE_LZO; } else if (strncmp(compress_type_str, "no", 2) == 0) { compress_type = TRACE_OPTION_COMPRESSTYPE_NONE; } else { fprintf(stderr, "Unknown compression type: %s\n", compress_type_str); return 1; } /* open input uri */ inptrace = trace_create(argv[optind]); if (trace_is_err(inptrace)) { trace_perror(inptrace,"trace_create"); exitcode = 1; goto exitanon; } if (optind +1>= argc) { /* no output specified, output in same format to * stdout */ output = strdup("erf:-"); } else { output = argv[optind +1]; } // OK parallel changes start here /* Set a special mode flag that means the output is timestamped * and ordered before its read into reduce. Seems like a good * special case to have. */ trace_set_combiner(inptrace, &combiner_ordered, (libtrace_generic_t){0}); pktcbs = trace_create_callback_set(); trace_set_packet_cb(pktcbs, per_packet); trace_set_stopping_cb(pktcbs, end_anon); trace_set_starting_cb(pktcbs, start_anon); repcbs = trace_create_callback_set(); trace_set_result_cb(repcbs, write_packet); trace_set_stopping_cb(repcbs, end_output); trace_set_starting_cb(repcbs, init_output); trace_set_perpkt_threads(inptrace, maxthreads); if (filterstring) { filter = trace_create_filter(filterstring); } if (filter && trace_config(inptrace, TRACE_OPTION_FILTER, filter) == -1) { trace_perror(inptrace, "Configuring input filter"); exitcode = 1; goto exitanon; } if (trace_pstart(inptrace, output, pktcbs, repcbs)==-1) { trace_perror(inptrace,"trace_start"); exitcode = 1; goto exitanon; } sigact.sa_handler = cleanup_signal; sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; sigaction(SIGINT, &sigact, NULL); sigaction(SIGTERM, &sigact, NULL); // Wait for the trace to finish trace_join(inptrace); exitanon: if (pktcbs) trace_destroy_callback_set(pktcbs); if (repcbs) trace_destroy_callback_set(repcbs); if (inptrace) trace_destroy(inptrace); return exitcode; }