/* ** 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@ */ /* ** rwptoflow.c ** ** Generates a flow for every IP packet. Since IP packets can arrive ** out of order, though, some fragments are collapsed into a single ** flow. (In particular, all fragments before the "zero" fragment are ** lumped into the "zero" fragment's flow. Later fragments are output ** as their own flows. We do this so that we can add OSI layer 4 ** information to the flows we generate, like source and destination ** ports.) ** ** Future development: ** ** Add mark-as-deleted call to hashlib so that we can purge the hash ** table occasionally. Currently, the hash table will grow until ** system memory is exhausted. ** ** In the event that the zero fragment is too small to contain TCP ** flags, attempt to get them from the next fragment. This will ** require more sophisticated fragment reassembly. */ #include "silk.h" RCSIDENT("$SiLK: rwptoflow.c 6081 2007-01-22 19:25:22Z mthomas $"); #include #include "hashlib.h" #include "utils.h" #include "rwpack.h" #include "sksite.h" #include "dynlib.h" #include "rwppacketheaders.h" /* LOCAL DEFINES AND TYPEDEFS */ /* where to write --help output */ #define USAGE_FH stdout /* where to print the statistics */ #define STATS_STREAM stderr /* maximum number of dynamic libraries that this app can open */ #define APP_MAX_DYNLIBS 8 /* the signature of the function that the dynlib should provide */ typedef int (*ptoflow_fn_t)(rwRec *rec, void *pktsrc); /* LOCAL FUNCTIONS */ static void appSetup(int argc, char **argv); static void appTeardown(void); static void appUsageLong(void); static int appOptionsHandler(clientData cData, int opt_index, char *opt_arg); /* Functions to handle plug-ins */ static int appAddDynlib(const char *dlpath, int warn_on_error); /* LOCAL VARIABLES */ /* the packet file to read */ static const char *packet_input_path = NULL; static pcap_t *packet_input = NULL; /* the flow file to write */ static rwIOStruct *flow_output = NULL; /* the compression method to use when writing the flow_output file. * sksiteCompmethodOptionsRegister() will set this to the default or * to the value the user specifies. */ static sk_compmethod_t comp_method; /* the optional packet file to write for packets that pass */ static const char *packet_pass_path = NULL; static pcap_dumper_t *packet_pass = NULL; /* the optional packet file to write for packets that reject */ static const char *packet_reject_path = NULL; static pcap_dumper_t *packet_reject = NULL; /* time window over which to process data */ static struct _time_window { struct timeval tw_begin; struct timeval tw_end; } time_window; /* default values to insert into each SiLK Flow */ static rwRec default_flow_values; /* whether to ignore all fragmented packets; whether to ignore * fragmented packets other than the initial one */ static int reject_frags_all = 0; static int reject_frags_subsequent = 0; /* whether to ignore packets where either the fragment or the capture * size is too small to gather the port information for TCP, UPD, * ICMP---and the flags information for TCP. */ static int reject_incomplete = 0; /* statistics counters and whether to print them */ static struct _statistics { /* total number of packets read */ uint64_t s_total; /* packets that were too short to get any information from */ uint64_t s_short; /* packets that were not Ethernet_IP or non-IPv4 packets */ uint64_t s_nonipv4; /* packets that occurred outside the time window */ uint64_t s_prewindow; uint64_t s_postwindow; /* packets that were fragmented */ uint64_t s_fragmented; /* packets that were the initial packet of a fragment */ uint64_t s_zerofrag; /* packets that the user's plug-in ignored and rejected */ uint64_t s_plugin_ign; uint64_t s_plugin_rej; /* packets that were long enough to get most info but too short to * get the ports---and/or flags for TCP */ uint64_t s_incomplete; } statistics; static int print_statistics = 0; /* number of shared object plug-ins loaded */ static int app_dynlib_count = 0; /* pointer to each plug-in */ static dynlibInfoStruct *app_dynlib_list[APP_MAX_DYNLIBS]; /* pointer to each plug-in's processing function */ static ptoflow_fn_t app_dynlib_proc[APP_MAX_DYNLIBS]; /* value passed to pcap_open for stdin/stdout */ static const char *pcap_stdio = "-"; /* buffer for pcap error messages */ static char errbuf[PCAP_ERRBUF_SIZE]; /* OPTION SETUP */ typedef enum { OPT_DYNAMIC_LIB_VALUE, OPT_ACTIVE_TIME, OPT_FLOW_OUTPUT, OPT_PACKET_PASS_OUTPUT, OPT_PACKET_REJECT_OUTPUT, OPT_REJECT_ALL_FRAGMENTS, OPT_REJECT_NONZERO_FRAGMENTS, OPT_REJECT_INCOMPLETE, OPT_SET_SENSORID, OPT_SET_INPUTINDEX, OPT_SET_OUTPUTINDEX, OPT_SET_NEXTHOPIP, OPT_PRINT_STATISTICS } appOptionsEnum; static struct option appOptions[] = { {OPT_DYNAMIC_LIBRARY, REQUIRED_ARG, 0, OPT_DYNAMIC_LIB_VALUE}, {"active-time", REQUIRED_ARG, 0, OPT_ACTIVE_TIME}, {"flow-output", REQUIRED_ARG, 0, OPT_FLOW_OUTPUT}, {"packet-pass-output", REQUIRED_ARG, 0, OPT_PACKET_PASS_OUTPUT}, {"packet-reject-output", REQUIRED_ARG, 0, OPT_PACKET_REJECT_OUTPUT}, {"reject-all-fragments", NO_ARG, 0, OPT_REJECT_ALL_FRAGMENTS}, {"reject-nonzero-fragments",NO_ARG, 0, OPT_REJECT_NONZERO_FRAGMENTS}, {"reject-incomplete", NO_ARG, 0, OPT_REJECT_INCOMPLETE}, {"set-sensorid", REQUIRED_ARG, 0, OPT_SET_SENSORID}, {"set-inputindex", REQUIRED_ARG, 0, OPT_SET_INPUTINDEX}, {"set-outputindex", REQUIRED_ARG, 0, OPT_SET_OUTPUTINDEX}, {"set-nexthopip", REQUIRED_ARG, 0, OPT_SET_NEXTHOPIP}, {"print-statistics", NO_ARG, 0, OPT_PRINT_STATISTICS}, {0,0,0,0} /* sentinel entry */ }; static const char *appHelp[] = { "Use given dynamic library. Def. None", ("Only generate flows for packets whose time falls within\n" "\tthe specified range. Def. Generate flows for all packets\n" "\tYYYY/MM/DD:hh:dd:mm:ss.uuuuuu-YYYY/MM/DD:hh:dd:mm:ss.uuuuuu"), ("Write the generated SiLK Flow records to the specified\n" "\tpath. Def. stdout"), ("For each generated flow, write its corresponding\n" "\tpacket to the specified path. Def. No"), ("Write each packet that occurs within the\n" "\tactive-time window but for which a SiLK Flow is NOT generated to\n" "\tthe specified path. Def. No"), ("Do not generate a SiLK Flow when the packet is\n" "\tfragmented. Def. All packets"), ("Do not generate SiLK Flows for packets where\n" "\tthe fragment-offset is non-zero. Def. All packets"), ("Do not generate SiLK Flows for zero-fragment or\n" "\tunfragmented packets when the flow cannot be completely filled\n" "\t(missing ICMP type&code, TCP/UDP ports, TCP flags). Def. All packets"), "Set sensor ID for all flows, 0-65534. Def. 0", "Set SNMP input index for all flows, 0-65535. Def. 0", "Set SNMP output index for all flows, 0-65535. Def. 0", "Set next hop IP address for all flows. Def. 0.0.0.0", ("Print the count of packets read, packets processed,\n" "\tand bad packets to the standard error"), (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 \ ("[SWITCHES] TCPDUMP_FILE\n" \ "\tRead packet capture data from TCPDUMP_FILE and attempt to generate\n" \ "\ta SiLK Flow record for every packet; use \"stdin\" to read the\n" \ "\tpackets from the standard input. Write the SiLK Flows to the\n" \ "\tnamed flow-output path or to the standard output if it is not\n" \ "\tconnected to a terminal.\n") FILE *fh = USAGE_FH; int i; fprintf(fh, "%s %s", skAppName(), USAGE_MSG); fprintf(fh, "\nSWITCHES:\n"); skOptionsDefaultUsage(fh); for (i = 0; appOptions[i].name; ++i) { fprintf(fh, "--%s %s. %s\n", appOptions[i].name, SK_OPTION_HAS_ARG(appOptions[i]), appHelp[i]); } sksiteCompmethodOptionsUsage(fh); for (i = 0; i < app_dynlib_count; ++i) { dynlibOptionsUsage(app_dynlib_list[i], fh); } } /* * 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; int i; int rv; if (teardownFlag) { return; } teardownFlag = 1; for (i = 0; i < app_dynlib_count; ++i) { dynlibTeardown(app_dynlib_list[i]); app_dynlib_list[i] = NULL; } app_dynlib_count = 0; /* * Close all files */ /* flow output */ if (flow_output) { rv = rwioClose(flow_output); if (rv) { rwioPrintLastErr(flow_output, rv, &skAppPrintErr); } rwioDestroy(&flow_output); } /* packet output */ if (packet_pass) { if (-1 == pcap_dump_flush(packet_pass)) { skAppPrintErr("Error finalizing %s file '%s'", appOptions[OPT_PACKET_PASS_OUTPUT].name, packet_pass_path); } pcap_dump_close(packet_pass); packet_pass = NULL; } if (packet_reject) { if (-1 == pcap_dump_flush(packet_reject)) { skAppPrintErr("Error finalizing %s file '%s'", appOptions[OPT_PACKET_REJECT_OUTPUT].name, packet_reject_path); } pcap_dump_close(packet_reject); packet_reject = NULL; } /* packet input */ if (packet_input) { pcap_close(packet_input); packet_input = NULL; } 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) { int rv; int arg_index; int stdout_used = 0; int i; /* 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); /* initialize globals */ memset(&statistics, 0, sizeof(statistics)); memset(&time_window, 0, sizeof(time_window)); memset(&default_flow_values, 0, sizeof(default_flow_values)); default_flow_values.pkts = 1; default_flow_values.sID = SK_INVALID_SENSOR; /* register the options */ if (optionsRegister(appOptions, (optHandler)appOptionsHandler, NULL) || sksiteCompmethodOptionsRegister(&comp_method)) { skAppPrintErr("unable to register options"); exit(EXIT_FAILURE); } /* parse options */ arg_index = optionsParse(argc, argv); if (arg_index < 0) { /* options parsing should print error */ skAppUsage(); /* never returns */ } /* verify one and only one 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); } packet_input_path = pcap_stdio; } else { packet_input_path = argv[arg_index]; } /* verify that multiple outputs are not using stdout; do not * overwrite exiting files */ if (flow_output == NULL) { ++stdout_used; } if (packet_pass_path && (0 == strcmp(packet_pass_path, "-"))) { ++stdout_used; } if (packet_reject_path && (0 == strcmp(packet_reject_path, "-"))) { ++stdout_used; } if (stdout_used > 1) { skAppPrintErr("Multiple binary outputs are using standard output"); exit(EXIT_FAILURE); } if (stdout_used && FILEIsATty(stdout)) { skAppPrintErr("Will not write binary data to stdout\n" "\twhen it is connected to a terminal"); exit(EXIT_FAILURE); } /* if the user didn't give a flow-output value; use stdout */ if (flow_output == NULL) { rv = rwioCreate(&flow_output, "stdout", SK_RWIO_WRITE); if (rv) { rwioPrintLastErr(flow_output, rv, &skAppPrintErr); exit(EXIT_FAILURE); } } /* initialize each dynamic library */ for (i = 0; i < app_dynlib_count; ++i) { rv = dynlibInitialize(app_dynlib_list[i]); if (rv != 0) { skAppPrintErr("unable to initialize plugin %s", dynlibGetPath(app_dynlib_list[i])); exit(EXIT_FAILURE); } } /* install our exit handler */ 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("error opening input %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 the packet output file(s), if any */ if (packet_pass_path) { packet_pass = pcap_dump_open(packet_input, packet_pass_path); if (packet_pass == NULL) { skAppPrintErr("error opening %s file '%s': %s", appOptions[OPT_PACKET_PASS_OUTPUT].name, packet_pass_path, pcap_geterr(packet_input)); exit(EXIT_FAILURE); } } if (packet_reject_path) { packet_reject = pcap_dump_open(packet_input, packet_reject_path); if (packet_reject == NULL) { skAppPrintErr("error opening %s file '%s': %s", appOptions[OPT_PACKET_REJECT_OUTPUT].name, packet_reject_path, pcap_geterr(packet_input)); exit(EXIT_FAILURE); } } /* open the flow-output file */ if ((rv = rwioSetFileType(flow_output, FT_RWFILTER)) || (rv = rwioSetCompression(flow_output, comp_method)) || (rv = rwioOpen(flow_output)) || (rv = rwioAppendToHistoryArgv(flow_output, argc, argv)) || (rv = rwioWriteHeader(flow_output))) { rwioPrintLastErr(flow_output, rv, &skAppPrintErr); rwioDestroy(&flow_output); 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) { struct timeval end_time; uint32_t temp; int precision; int rv; switch ((appOptionsEnum)opt_index) { case OPT_DYNAMIC_LIB_VALUE: if (appAddDynlib(opt_arg, 1)) { return 1; } break; case OPT_ACTIVE_TIME: /* parse the time */ if (skStringParseDatetimeRange(&time_window.tw_begin, &end_time, opt_arg, NULL, &precision)) { return 1; } /* adjust the maximum if required */ if (end_time.tv_sec != LONG_MAX && precision < 6) { /* the max date precision is less than (courser than) second * resolution, so "round" the date up */ if (skDatetimeCeiling(&time_window.tw_end, &end_time, precision)) { return 1; } } else { /* use what we were given */ memcpy(&time_window.tw_end, &end_time, sizeof(struct timeval)); } break; case OPT_FLOW_OUTPUT: if ((0 == strcmp(opt_arg, "stdout")) || (0 == strcmp(opt_arg, "-"))) { /* do nothing, since this is the default */ break; } if (flow_output) { skAppPrintErr("The --%s switch was given multiple times", appOptions[opt_index].name); return 1; } if (fileExists(opt_arg)) { skAppPrintErr("The %s '%s' exists. Will not overwrite it.", appOptions[opt_index].name, opt_arg); exit(EXIT_FAILURE); } rv = rwioCreate(&flow_output, opt_arg, SK_RWIO_WRITE); if (rv) { rwioPrintLastErr(flow_output, rv, &skAppPrintErr); exit(EXIT_FAILURE); } break; case OPT_PACKET_PASS_OUTPUT: if (packet_pass_path) { skAppPrintErr("The --%s switch was given multiple times", appOptions[opt_index].name); return 1; } if ((0 == strcmp(opt_arg, "stdout")) || (0 == strcmp(opt_arg, "-"))) { packet_pass_path = pcap_stdio; } else if (fileExists(opt_arg)) { skAppPrintErr("The %s '%s' exists. Will not overwrite it.", appOptions[opt_index].name, opt_arg); exit(EXIT_FAILURE); } else { packet_pass_path = opt_arg; } break; case OPT_PACKET_REJECT_OUTPUT: if (packet_reject_path) { skAppPrintErr("The --%s switch was given multiple times", appOptions[opt_index].name); return 1; } if ((0 == strcmp(opt_arg, "stdout")) || (0 == strcmp(opt_arg, "-"))) { packet_reject_path = pcap_stdio; } else if (fileExists(opt_arg)) { skAppPrintErr("The %s '%s' exists. Will not overwrite it.", appOptions[opt_index].name, opt_arg); exit(EXIT_FAILURE); } else { packet_reject_path = opt_arg; } break; case OPT_REJECT_ALL_FRAGMENTS: reject_frags_all = 1; break; case OPT_REJECT_NONZERO_FRAGMENTS: reject_frags_subsequent = 1; break; case OPT_REJECT_INCOMPLETE: reject_incomplete = 1; break; case OPT_SET_SENSORID: rv = skStringParseUint32(&temp, opt_arg, 0, (SK_INVALID_SENSOR-1)); if (rv) { if (rv < 10) { skAppPrintErr("Argument to --%s switch is greater than %u: %s", appOptions[opt_index].name, UINT16_MAX, opt_arg); } else { skAppPrintErr("Invalid argument to --%s switch: %s", appOptions[opt_index].name, opt_arg); } return 1; } default_flow_values.sID = (sensorID_t)temp; break; case OPT_SET_INPUTINDEX: rv = skStringParseUint32(&temp, opt_arg, 0, UINT16_MAX); if (rv) { if (rv < 10) { skAppPrintErr("Argument to --%s switch is greater than %u: %s", appOptions[opt_index].name, UINT16_MAX, opt_arg); } else { skAppPrintErr("Invalid argument to --%s switch: %s", appOptions[opt_index].name, opt_arg); } return 1; } default_flow_values.input = (uint16_t)temp; break; case OPT_SET_OUTPUTINDEX: rv = skStringParseUint32(&temp, opt_arg, 0, UINT16_MAX); if (rv) { if (rv < 10) { skAppPrintErr("Argument to --%s switch is greater than %u: %s", appOptions[opt_index].name, UINT16_MAX, opt_arg); } else { skAppPrintErr("Invalid argument to --%s switch: %s", appOptions[opt_index].name, opt_arg); } return 1; } default_flow_values.output = (uint16_t)temp; break; case OPT_SET_NEXTHOPIP: if (skStringParseIP(&temp, opt_arg)) { skAppPrintErr("Invalid argument to --%s switch: %s", appOptions[opt_index].name, opt_arg); return 1; } default_flow_values.nhIP.ipnum = temp; break; case OPT_PRINT_STATISTICS: print_statistics = 1; break; } return 0; /* OK */ } /* * appAddDynlib(dlpath, warnOnError); * * Load the dynamic library at 'dlpath', store its information in * the global 'app_dynlib_list', and increment the global * 'app_dynlib_count'. Return 0 on success. Return 1 if the * library cannot be loaded or if too many dynamic libraries have * been loaded. */ static int appAddDynlib(const char *dlpath, int warnOnError) { dynlibInfoStruct *dl; if (app_dynlib_count == APP_MAX_DYNLIBS) { skAppPrintErr("Too many dynlibs. Only %u allowed", APP_MAX_DYNLIBS); return 1; } dl = dynlibCreate(DYNLIB_PTOFLOW); if (dynlibLoad(dl, dlpath)) { /* failed to find library, missing symbols, or setup failed */ if (warnOnError) { skAppPrintErr("Error loading dynamic library '%s': %s", dlpath, dynlibLastError(dl)); } dynlibTeardown(dl); return 1; } app_dynlib_list[app_dynlib_count] = dl; app_dynlib_proc[app_dynlib_count] = dynlibGetRWProcessor(dl); ++app_dynlib_count; return 0; } /* * status = packetsToFlows(); * * For every packet in the global 'packet_input' file, try to * produce a SiLK flow record, and write that record to the * 'flow_output' rwioStream. In addition, print the packets to the * 'packet_pass' and/or 'packet_fail' dump files if requested. * Update the global 'statistics' struct. Return 0 on success, or * -1 if writing a flow to the 'flow_output' stream fails. */ static int packetsToFlows(void) { #define DUMP_REJECT_PACKET \ if ( !packet_reject) { /* no-op */} \ else {pcap_dump((u_char*)packet_reject, &pcaph, data);} sk_pktsrc_t pktsrc; struct pcap_pkthdr pcaph; const u_char *data; rwRec flow; /* pointer to the ethernet header inside of 'data' */ eth_header_t *ethh; /* pointer to the IP header inside of 'data' */ ip_header_t *iph; /* pointer to the protocol-specific header in 'data' */ u_char *protoh; /* the advertised length of the IP header */ uint32_t iph_len; uint32_t len; int i; int rv; /* set up the sk_pktsrc_t for communicating with the plugins */ pktsrc.pcap_src = packet_input; pktsrc.pcap_hdr = &pcaph; while (NULL != (data = pcap_next(packet_input, &pcaph))) { ++statistics.s_total; /* see if the packet's time is within our time window */ if (time_window.tw_end.tv_sec) { if (pcaph.ts.tv_sec < time_window.tw_begin.tv_sec || (pcaph.ts.tv_sec == time_window.tw_begin.tv_sec && pcaph.ts.tv_usec < time_window.tw_begin.tv_usec)) { /* packet's time is before window */ ++statistics.s_prewindow; continue; } if (pcaph.ts.tv_sec > time_window.tw_end.tv_sec || (pcaph.ts.tv_sec == time_window.tw_end.tv_sec && pcaph.ts.tv_usec > time_window.tw_end.tv_usec)) { /* packet's time is after window */ ++statistics.s_postwindow; continue; } } /* make certain we captured the ethernet header */ len = pcaph.caplen; if (len < sizeof(eth_header_t)) { /* short packet */ ++statistics.s_short; DUMP_REJECT_PACKET; continue; } /* get the ethernet header; goto next packet if not Ethernet. */ ethh = (eth_header_t*)data; if (ntohs(ethh->ether_type) != ETHERTYPE_IP) { /* ignoring non IP packet */ ++statistics.s_nonipv4; DUMP_REJECT_PACKET; continue; } /* get the IP header; verify that we have the entire IP header * that the version is 4. */ iph = (ip_header_t*)(data + sizeof(eth_header_t)); len -= sizeof(eth_header_t); if (len < sizeof(ip_header_t)) { ++statistics.s_short; DUMP_REJECT_PACKET; continue; } if ((iph->ver_ihl >> 4) != 4) { /* ignoring non IPv4 packet */ ++statistics.s_nonipv4; DUMP_REJECT_PACKET; continue; } /* the protocol-specific header begins after the advertised * length of the IP header */ iph_len = (iph->ver_ihl & 0x0F) << 2; if (len > iph_len) { protoh = (u_char*)(((u_char*)iph) + iph_len); len -= iph_len; } else { protoh = NULL; } /* check for fragmentation */ if (ntohs(iph->flags_fo) & (IP_MF | IPHEADER_FO_MASK)) { ++statistics.s_fragmented; if (reject_frags_all) { DUMP_REJECT_PACKET; continue; } if ((ntohs(iph->flags_fo) & IPHEADER_FO_MASK) == 0) { ++statistics.s_zerofrag; } else if (reject_frags_subsequent) { DUMP_REJECT_PACKET; continue; } } /* we have enough data to generate a flow; fill it in with * what we know so far. */ memcpy(&flow, &default_flow_values, sizeof(rwRec)); flow.sIP.ipnum = ntohl(iph->saddr.ipnum); flow.dIP.ipnum = ntohl(iph->daddr.ipnum); flow.proto = iph->proto; flow.bytes = iph->tlen; flow.sTime = pcaph.ts.tv_sec; flow.sTime_msec = pcaph.ts.tv_usec / 1000; /* Get the port information from unfragmented datagrams or * from the zero-packet of fragmented datagrams. */ if (protoh && ((ntohs(iph->flags_fo) & IPHEADER_FO_MASK) == 0)) { /* Set ports and flags based on the IP protocol */ switch (iph->proto) { case 1: /* ICMP */ /* did we capture enough to get ICMP data? */ if (len < 2) { ++statistics.s_incomplete; if (reject_incomplete) { DUMP_REJECT_PACKET; continue; } } else { icmp_header_t *icmphdr = (icmp_header_t*)protoh; flow.dPort = ((icmphdr->type << 8) | icmphdr->code); } break; case 6: /* TCP */ /* did we capture enough to get the TCP flags? */ if (len < 14) { ++statistics.s_incomplete; if (reject_incomplete) { DUMP_REJECT_PACKET; continue; } /* can we at least get the ports? */ if (len >= 4) { tcp_header_t *tcphdr = (tcp_header_t*)protoh; flow.sPort = ntohs(tcphdr->sport); flow.dPort = ntohs(tcphdr->dport); } } else { tcp_header_t *tcphdr = (tcp_header_t*)protoh; flow.sPort = ntohs(tcphdr->sport); flow.dPort = ntohs(tcphdr->dport); flow.flags = tcphdr->flags; } break; case 17: /* UDP */ /* did we capture enough to get UDP sport and dport? */ if (len < 4) { ++statistics.s_incomplete; if (reject_incomplete) { DUMP_REJECT_PACKET; continue; } } else { udp_header_t *udphdr = (udp_header_t*)protoh; flow.sPort = ntohs(udphdr->sport); flow.dPort = ntohs(udphdr->dport); } break; } } /* If the user provided dynamic library(s), call it(them) */ pktsrc.pcap_data = data; for (i = 0; i < app_dynlib_count; ++i) { rv = (*(app_dynlib_proc[i]))(&flow, &pktsrc); switch (rv) { case 0: /* success, but no opinion; try next dynlib */ break; case 1: /* success, immediately write flow */ goto WRITE_FLOW; case 2: /* success, but immediately reject the flow */ ++statistics.s_plugin_rej; DUMP_REJECT_PACKET; goto NEXT_PACKET; case 3: /* success, immediately ignore the flow */ ++statistics.s_plugin_ign; goto NEXT_PACKET; default: /* an error */ skAppPrintErr("Quitting on error code %d from plug-in", rv); return -1; } } WRITE_FLOW: /* FINALLY, write the record to the SiLK Flow file and write * the packet to the packet-pass-output file */ rv = rwWrite(flow_output, &flow); if (rv) { rwioPrintLastErr(flow_output, rv, &skAppPrintErr); if (LIBRW_ERROR_IS_FATAL(rv)) { return -1; } } if (packet_pass) { pcap_dump((u_char*)packet_pass, &pcaph, data); } NEXT_PACKET: ; /* empty */ } return 0; } /* * printStatistics(fh); * * Print statistics about the number of packets read, ignored, * rejected, and written to the specified file handle. */ static void printStatistics(FILE *fh) { uint64_t count = statistics.s_total; fprintf(fh, ("Packet count statistics for %s\n" "\t%20" PRIu64 " read\n"), packet_input_path, statistics.s_total); if (time_window.tw_end.tv_sec) { fprintf(fh, ("\t%20" PRIu64 " ignored: before active-time\n" "\t%20" PRIu64 " ignored: after active-time\n"), statistics.s_prewindow, statistics.s_postwindow); count -= (statistics.s_prewindow + statistics.s_postwindow); } fprintf(fh, ("\t%20" PRIu64 " rejected: too short to get information\n" "\t%20" PRIu64 " rejected: not IPv4\n"), statistics.s_short, statistics.s_nonipv4); count -= (statistics.s_short + statistics.s_nonipv4); if (reject_frags_all) { fprintf(fh, ("\t%20" PRIu64 " rejected: fragmented\n"), statistics.s_fragmented); count -= statistics.s_fragmented; } if (reject_incomplete) { fprintf(fh, ("\t%20" PRIu64 " rejected: incomplete\n"), statistics.s_fragmented); count -= statistics.s_fragmented; } if (reject_frags_subsequent) { fprintf(fh, ("\t%20" PRIu64 " rejected: non-zero fragment\n"), (statistics.s_fragmented - statistics.s_zerofrag)); count -= (statistics.s_fragmented - statistics.s_zerofrag); } if (app_dynlib_count) { fprintf(fh, ("\t%20" PRIu64 " ignored: by plug-in\n" "\t%20" PRIu64 " rejected: by plug-in\n"), statistics.s_plugin_ign, statistics.s_plugin_rej); count -= (statistics.s_plugin_ign + statistics.s_plugin_rej); } fprintf(fh, ("\n\t%20" PRIu64 " total written\n"), count); if ( !reject_frags_all) { if ( !reject_frags_subsequent) { fprintf(fh, ("\t%20" PRIu64 " total fragmented packets\n"), statistics.s_fragmented); } fprintf(fh, ("\t%20" PRIu64 " zero-packet of a fragment\n"), statistics.s_zerofrag); } if ( !reject_incomplete) { fprintf(fh, ("\t%20" PRIu64 " incomplete (no ports and/or flags)\n"), statistics.s_incomplete); } } int main(int argc, char **argv) { appSetup(argc, argv); if (packetsToFlows()) { exit(EXIT_FAILURE); } if (print_statistics) { printStatistics(STATS_STREAM); } appTeardown(); return 0; } /* ** Local variables: ** mode:c ** indent-tabs-mode:nil ** c-basic-offset:4 ** End: */