/* ** Copyright (C) 2005-2007 by Carnegie Mellon University. ** ** @OPENSOURCE_HEADER_START@ ** ** Use of the SILK system and related source code is subject to the terms ** of the following licenses: ** ** GNU Public License (GPL) Rights pursuant to Version 2, June 1991 ** Government Purpose License Rights (GPLR) pursuant to DFARS 252.225-7013 ** ** NO WARRANTY ** ** ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER ** PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY ** PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN ** "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY ** KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT ** LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE, ** MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE ** OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT, ** SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY ** TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF ** WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES. ** LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF ** CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON ** CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE ** DELIVERABLES UNDER THIS LICENSE. ** ** Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie ** Mellon University, its trustees, officers, employees, and agents from ** all claims or demands made against them (and any related losses, ** expenses, or attorney's fees) arising out of, or relating to Licensee's ** and/or its sub licensees' negligent use or willful misuse of or ** negligent conduct or willful misconduct regarding the Software, ** facilities, or other rights or assistance granted by Carnegie Mellon ** University under this License, including, but not limited to, any ** claims of product liability, personal injury, death, damage to ** property, or violation of any laws or regulations. ** ** Carnegie Mellon University Software Engineering Institute authored ** documents are sponsored by the U.S. Department of Defense under ** Contract F19628-00-C-0003. Carnegie Mellon University retains ** copyrights in all material produced under this contract. The U.S. ** Government retains a non-exclusive, royalty-free license to publish or ** reproduce these documents, or allow others to do so, for U.S. ** Government purposes only pursuant to the copyright license under the ** contract clause at 252.227.7013. ** ** @OPENSOURCE_HEADER_END@ */ /* ** rwpmatch.c ** ** Filter a tcpdump file by outputting only packets whose 5-tuple and ** timestamp match corresponding flows in a rw-file. Outputs the ** filtered tcpdump file to stdout. ** */ #include "silk.h" RCSIDENT("$SiLK: rwpmatch.c 6904 2007-04-14 13:00:08Z mthomas $"); #include #include "rwpack.h" #include "utils.h" #include "rwppacketheaders.h" /* LOCAL DEFINES AND TYPEDEFS */ #define USAGE_FH stdout /* LOCAL FUNCTIONS */ static void appUsageLong(void); static void appTeardown(void); static void appSetup(int argc, char **argv); static int appOptionsHandler(clientData cData, int opt_index, char *opt_arg); static int timecmp_pkt_flow( struct timeval *pkt, const rwRec *flow); static int tuples_match( struct pcap_pkthdr *hdr, const u_char *pkt, const rwRec *flow); /* LOCAL VARIABLES */ static pcap_t *packet_input = NULL; static rwIOStruct_t *flow_input = NULL; static pcap_dumper_t *packet_match = NULL; /* Whether to use millisecond precision */ static int use_msec = 0; /* Whether to compare the port fields */ static int use_ports = 0; /* OPTION SETUP */ typedef enum { OPT_FLOW_FILE, OPT_USE_MSEC, OPT_USE_PORTS } appOptionsEnum; static struct option appOptions[] = { {"flow-file", REQUIRED_ARG, 0, OPT_FLOW_FILE}, {"msec-compare", NO_ARG, 0, OPT_USE_MSEC}, {"ports-compare", NO_ARG, 0, OPT_USE_PORTS}, {0, 0, 0, 0} /* sentinel entry */ }; static const char *appHelp[] = { "Flow file to use to filter the tcpdump data", "Compare flows to the millisecond (instead of second)", "Do not consider port data when comparing flows", (char *)NULL }; /* FUNCTION DEFINITIONS */ /* * appUsageLong(); * * Print complete usage information to USAGE_FH. Pass this * function to skOptionsSetUsageCallback(); optionsParse() will * call this funciton and then exit the program when the --help * option is given. */ static void appUsageLong(void) { #define USAGE_MSG \ ("--flow-file=FILE [SWITCHES]\n" \ "\tFilter a tcpdump file by writing, to the standard output, the\n" \ "\tpackets whose source and destination IP, protocol, timestamp\n" \ "\t(and optionally source and destination ports) match SiLK Flow\n" \ "\trecords read from the specified file.\n") FILE *fh = USAGE_FH; skAppStandardUsage(fh, USAGE_MSG, appOptions, appHelp); } /* * appTeardown() * * Teardown all modules, close all files, and tidy up all * application state. * * This function is idempotent. */ static void appTeardown(void) { static int teardownFlag = 0; if (teardownFlag) { return; } teardownFlag = 1; /* Close all files */ if (packet_match) { if (-1 == pcap_dump_flush(packet_match)) { skAppPrintErr("Error finalizing output file"); } pcap_dump_close(packet_match); packet_match = NULL; } if (packet_input) { pcap_close(packet_input); packet_input = NULL; } if (flow_input) { rwioDestroy(&flow_input); } skAppUnregister(); } /* * appSetup(argc, argv); * * Perform all the setup for this application include setting up * required modules, parsing options, etc. This function should be * passed the same arguments that were passed into main(). * * Returns to the caller if all setup succeeds. If anything fails, * this function will cause the application to exit with a FAILURE * exit status. */ static void appSetup(int argc, char **argv) { char errbuf[PCAP_ERRBUF_SIZE]; int arg_index; const char *packet_input_path; /* verify same number of options and help strings */ assert((sizeof(appHelp)/sizeof(char*)) == (sizeof(appOptions)/sizeof(struct option))); /* register the application */ skAppRegister(argv[0]); skOptionsSetUsageCallback(&appUsageLong); /* register the options */ if (optionsRegister(appOptions, (optHandler)appOptionsHandler, NULL)) { skAppPrintErr("unable to register options"); exit(EXIT_FAILURE); } /* parse options */ arg_index = optionsParse(argc, argv); if (arg_index < 0) { skAppUsage(); /* never returns */ } /* must have a flow file */ if (flow_input == NULL) { skAppPrintErr("The --%s switch is required", appOptions[OPT_FLOW_FILE].name); exit(EXIT_FAILURE); } /* Will not write binary data to a terminal */ if (FILEIsATty(stdout)) { skAppPrintErr("Will not write binary data to stdout\n" "\twhen it is connected to a terminal"); exit(EXIT_FAILURE); } /* verify one and only one PCAP input file; allow "stdin" to have * pcap read from the standard input */ if (argc - arg_index != 1) { skAppPrintErr("Must have one and only one input file"); skAppUsage(); /* never returns */ } if ((0 == strcmp(argv[arg_index], "stdin")) || (0 == strcmp(argv[arg_index], "-"))) { if (FILEIsATty(stdin)) { skAppPrintErr("Will not read binary data from stdin\n" "\twhen it is connected to a terminal"); exit(EXIT_FAILURE); } if (strcmp(rwGetFileName(flow_input), "stdin") == 0) { skAppPrintErr("Cannot read both pcap and flow data from stdin"); exit(EXIT_FAILURE); } packet_input_path = "-"; } else { packet_input_path = argv[arg_index]; } if (atexit(appTeardown) < 0) { skAppPrintErr("unable to register appTeardown() with atexit()"); appTeardown(); exit(EXIT_FAILURE); } /* open packet-input file; verify it contains ethernet data */ packet_input = pcap_open_offline(packet_input_path, errbuf); if (packet_input == NULL) { skAppPrintErr("unable to open input file %s: %s", packet_input_path, errbuf); exit(EXIT_FAILURE); } if (DLT_EN10MB != pcap_datalink(packet_input)) { skAppPrintErr("input file %s does not contain Ethernet data", packet_input_path); exit(EXIT_FAILURE); } /* open output */ packet_match = pcap_dump_open(packet_input, "-"); if (packet_match == NULL) { skAppPrintErr("error opening stdout for pcap data: %s", pcap_geterr(packet_input)); exit(EXIT_FAILURE); } return; /* OK */ } /* * status = appOptionsHandler(cData, opt_index, opt_arg); * * This function is passed to optionsRegister(); it will be called * by optionsParse() for each user-specified switch that the * application has registered; it should handle the switch as * required---typically by setting global variables---and return 1 * if the switch processing failed or 0 if it succeeded. Returning * a non-zero from from the handler causes optionsParse() to return * a negative value. * * The clientData in 'cData' is typically ignored; 'opt_index' is * the index number that was specified as the last value for each * struct option in appOptions[]; 'opt_arg' is the user's argument * to the switch for options that have a REQUIRED_ARG or an * OPTIONAL_ARG. */ static int appOptionsHandler( clientData UNUSED(cData), int opt_index, char *opt_arg) { switch ((appOptionsEnum)opt_index) { case OPT_FLOW_FILE: /* the input-pipe must be 'stdin' or it must be an existing * FIFO. If 'stdin', stdin must not be a TTY, since we expect * binary. */ if (strcmp(opt_arg, "stdin") == 0) { if (FILEIsATty(stdin)) { skAppPrintErr("stdin is connected to a terminal."); return -1; } } flow_input = rwOpenFile(opt_arg, NULL); if (flow_input == NULL) { exit(EXIT_FAILURE); } break; case OPT_USE_MSEC: use_msec = 1; break; case OPT_USE_PORTS: use_ports = 1; break; } return 0; /* OK */ } /* * returns: * -1 if packet is earlier than flow * 0 if packet and flow occurred at the same time * 1 if packet is later than flow */ static int timecmp_pkt_flow( struct timeval *pkt_time, const rwRec *flow) { uint32_t pkt_sec = pkt_time->tv_sec; /* should never get negative entries in timeval, but better safe than sorry */ assert(pkt_time->tv_sec >= 0); assert(pkt_time->tv_usec >= 0); /* compare seconds */ if (pkt_sec < flow->sTime) { return -1; } if (pkt_sec > flow->sTime) { return 1; } if (use_msec) { uint16_t pkt_msec = (uint16_t)(pkt_time->tv_usec / 1000); /* compare msec */ if (pkt_msec < flow->sTime_msec) { return -1; } if(pkt_msec > flow->sTime_msec) { return 1; } } return 0; } /* * Determines if a packet was aggregated into a flow using * rwptoflow. Returns a true value if they match, a false value * otherwise. * * This function assumes that the times have already been found to * be equal. */ static int tuples_match( struct pcap_pkthdr *pcaph, const uint8_t *pkt, const rwRec *flow) { /* pointer to the ethernet header inside of 'pkt' */ eth_header_t *ethh; /* pointer to the IP header inside of 'pkt' */ ip_header_t *iph; /* pointer to the tcp-specific header in 'pkt' */ tcp_header_t *tcph; /* the advertised length of the IP header */ uint32_t iph_len; uint32_t len; /* Calling code should have checked timestamp */ assert(timecmp_pkt_flow(&pcaph->ts, flow) == 0); /* verify packet is Ethernet */ len = pcaph->caplen; if (len < sizeof(eth_header_t)) { return 0; } ethh = (eth_header_t*)pkt; if (ntohs(ethh->ether_type) != ETHERTYPE_IP) { return 0; } /* * FUTURE: we don't store the ethernet identification field in the * flow record, so we can't compare it when going backwards. we * might want to consider storing it in the future. */ /* get the IP header; verify that we have the entire IP header * that the version is 4. */ iph = (ip_header_t*)(pkt + sizeof(eth_header_t)); len -= sizeof(eth_header_t); if (len < sizeof(ip_header_t)) { /* short packet */ return 0; } if ((iph->ver_ihl >> 4) != 4) { /* ignoring non IPv4 packet */ return 0; } /* compare saddr, daddr, proto */ if ((iph->saddr.ipnum != htonl(flow->sIP.ipnum)) || (iph->daddr.ipnum != htonl(flow->dIP.ipnum)) || (iph->proto != flow->proto)) { return 0; } /* compare the ports for TCP and UDP if requested on unfragmented * datagrams or on the zero-packet of fragmented datagrams */ if (use_ports && (iph->proto == 6 || iph->proto == 17) && ((ntohs(iph->flags_fo) & IPHEADER_FO_MASK) == 0)) { /* the protocol-specific header begins after the advertised * length of the IP header */ iph_len = (iph->ver_ihl & 0x0F) << 2; if (len > iph_len) { tcph = (tcp_header_t*)(((u_char*)iph) + iph_len); len -= iph_len; if (len >= 4) { if ((tcph->sport != htons(flow->sPort)) || (tcph->dport != htons(flow->dPort))) { return 0; } } } } return 1; } int main(int argc, char **argv) { /* * Flag to tell whether or not we should continue processing * files. 0 if processing should continue, 1 if packet flow * reached EOF, 2 if flow file reached EOF. -1 on an error * condition. */ int done = 0; /* * Flag to determine if the latest packet data has been consumed, * and a new packet must be read from file. */ int load_next_pkt = 1; /* * Flag to determine if the latest flow data has been consumed, * and a new flow must be read from file. */ int load_next_flow = 1; /* * Latest packet read from file. */ struct pcap_pkthdr pkt_hdr; const u_char *pkt_data = NULL; /* * Latest flow read from file. */ rwRec flow; /* * -1 if pkt < flow, 0 if pkt == flow, 1 if pkt > flow */ int ts_compare; appSetup(argc, argv); while (!done) { /* If the current packet data is stale, load the next packet */ if (load_next_pkt) { load_next_pkt = 0; pkt_data = pcap_next(packet_input, &pkt_hdr); if (pkt_data == NULL) { done = 1; } } /* If the current flow data is stale, load the next flow */ if (load_next_flow) { load_next_flow = 0; if (!rwRead(flow_input, &flow)) { done = 2; continue; /* drops out of while loop */ } } /* * At this point, we are guaranteed that both the packet and * flow data is fresh, so compare the records. */ /* * If the current packet matches the flow characteristics * (5-tuple and timestamp), output the packet. */ ts_compare = timecmp_pkt_flow(&pkt_hdr.ts, &flow); if ((ts_compare == 0) && tuples_match(&pkt_hdr, pkt_data, &flow)) { pcap_dump((u_char*)packet_match, &pkt_hdr, pkt_data); load_next_pkt = 1; load_next_flow = 1; continue; } /* * The packet and flow didn't match. */ if (ts_compare <= 0) { /* * If the packet data has an earlier timestamp (or equal, * but the 5-tuple didn't match), then the current packet * does not exist in the flow file, so it can be skipped. * Keep the same flow record, and try the next packet. */ load_next_pkt = 1; } else { /* * If the packet data has a later timestamp, then the flow * contains an entry which was not derived from this * packet data. Report an error, and exit. */ skAppPrintErr("found a flow which does not have " "corresponding packet data. Exitting."); exit(EXIT_FAILURE); } } return 0; } /* ** Local Variables: ** mode:c ** indent-tabs-mode:nil ** c-basic-offset:4 ** End: */