/* ** Copyright (C) 2001-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@ */ /* * addrcount.c * * this is the last major rwset tool I want to write, it takes in two streams * of data, an rwset and an rwfilter stream. From this data, it then generates * a result - one of three outputs: * totals (default) - outputs to screen a table containing the * ip address, bytes, packets, flows * print-ips - outputs to screen the ip addresses * set-file - outputs to screen the set data. * * So the reason for the second two is because I'm including three thresholds * here - bytes, packets & records. * * 12/2 notes. Often, the best is the enemy of the good, I am implementing * a simple version of this application for now with the long-term plan being * that I am going to write a better, faster, set-friendly version later. */ #include "silk.h" RCSIDENT("$SiLK: rwaddrcount.c 8269 2007-08-03 18:54:48Z mthomas $"); #include "iptree.h" #include "rwpack.h" #include "utils.h" #include "iochecks.h" #include "rwaddrcount.h" #include "sksite.h" /* LOCAL DEFINES AND TYPEDEFS */ /* where to write output from --help */ #define USAGE_FH stdout /* Where to write filenames if --print-file specified */ #define PRINT_FILENAMES_FH stderr /* I'm #define the hash and comparison functions to avoid stack overhead */ #define HASHFUNC(value) \ ((value ^ (value >> 7) ^ (value << 23)) % RWAC_ARRAYSIZE) /* IPHASH: * rwrec: rwRecord to use. * useSrc: use Source IP address */ #define IPHASH(rwrec, useDst) \ ((useDst) ? HASHFUNC((rwrec).dIP.ipnum) : HASHFUNC((rwrec).sIP.ipnum)) #define CMPFNC(rwrec, crPtr, useDst) \ ((useDst) ? ((rwrec).dIP.ipnum == (crPtr)->value) : \ ((rwrec).sIP.ipnum == (crPtr)->value)) /* * when generating ouput, this macro will evaluate to TRUE if the * record is within the limits given by the user and should be * printed/counted/used-to-genrate-output. This macro uses the global * limit variables. */ #define IS_RECORD_WITHIN_LIMITS(count_rec) \ ((count_rec)->bytes >= byteMin && \ (count_rec)->packets >= packetMin && \ (count_rec)->flows >= recMin && \ (!byteMax || (count_rec)->bytes <= byteMax) && \ (!packetMax || (count_rec)->packets <= packetMax) && \ (!recMax || (count_rec)->flows <= recMax)) /* LOCAL VARIABLES */ static iochecksInfoStruct_t *ioISP; static rwRec currentRecord; /* Record we're currently reading*/ /* output mode */ static rwac_print_mode_t printMode = RWAC_PMODE_NONE; static uint32_t byteMin, recMin, packetMin, byteMax, recMax, packetMax; static countRecord_t *countArray[RWAC_ARRAYSIZE]; static uint32_t totalHashRecords = 0; static char *outputSetFN = NULL; /* whether to use the source(==0) or destination(==1) IPs */ static uint8_t useDestFlag = 0; /* output mode for IPs */ static uint8_t integer_ips_flag = 0; static uint8_t zero_pad_ips_flag = 0; static uint8_t sort_ips_flag = 0; /* whether to suppress column titles; default no (i.e. print titles) */ static uint8_t no_titles = 0; /* whether to suppress columnar output; default no (i.e. columnar) */ static uint8_t no_columns = 0; /* column separator */ static char delimiter = '|'; /* format strings to print records; indexed by the no_columns value */ static const char * const format_records[] = { ("%15s%c%20" PRIu64 "%c%10u%c%10u%c%20s%c%20s%c\n"), /* columnar */ ("%s%c%" PRIu64 "%c%u%c%u%c%s%c%s%c\n") /* no columns */ }; static const char * const format_records_title[] = { "%15s%c%20s%c%10s%c%10s%c%20s%c%20s%c\n", /* columnar */ "%s%c%s%c%s%c%s%c%s%c%s%c\n" /* no columns */ }; /* whether to print the names of files as they are opened */ static int print_filenames = 0; /* name of program to run to page output */ static char *pager = NULL; /* whether to use legacy (1), new-style (0), or default (-1) timestamps */ static int legacy_timestamps = -1; /* OPTIONS */ /* Names of options; keep the order in sync with appOptions[] */ typedef enum { OPT_PRINT_RECS, OPT_PRINT_STAT, OPT_PRINT_IPS, OPT_USE_DEST, OPT_BYTE_MIN, OPT_PKT_MIN, OPT_REC_MIN, OPT_BYTE_MAX, OPT_PKT_MAX, OPT_REC_MAX, OPT_SET_FILE, OPT_INTEGER_IPS, OPT_ZERO_PAD_IPS, OPT_SORT_IPS, OPT_NO_TITLES, OPT_NO_COLUMNS, OPT_COLUMN_SEPARATOR, OPT_DELIMITED, OPT_PRINT_FILENAMES, OPT_COPY_INPUT, OPT_OUTPUT_PATH, OPT_PAGER, OPT_LEGACY_TIMESTAMPS } appOptionsEnum; static struct option appOptions[] = { {"print-recs", NO_ARG, 0, OPT_PRINT_RECS}, {"print-stat", NO_ARG, 0, OPT_PRINT_STAT}, {"print-ips", NO_ARG, 0, OPT_PRINT_IPS}, {"use-dest", NO_ARG, 0, OPT_USE_DEST}, {"byte-min", REQUIRED_ARG, 0, OPT_BYTE_MIN}, {"packet-min", REQUIRED_ARG, 0, OPT_PKT_MIN}, {"rec-min", REQUIRED_ARG, 0, OPT_REC_MIN}, {"byte-max", REQUIRED_ARG, 0, OPT_BYTE_MAX}, {"packet-max", REQUIRED_ARG, 0, OPT_PKT_MAX}, {"rec-max", REQUIRED_ARG, 0, OPT_REC_MAX}, {"set-file", REQUIRED_ARG, 0, OPT_SET_FILE}, {"integer-ips", NO_ARG, 0, OPT_INTEGER_IPS}, {"zero-pad-ips", NO_ARG, 0, OPT_ZERO_PAD_IPS}, {"sort-ips", NO_ARG, 0, OPT_SORT_IPS}, {"no-titles", NO_ARG, 0, OPT_NO_TITLES}, {"no-columns", NO_ARG, 0, OPT_NO_COLUMNS}, {"column-separator", REQUIRED_ARG, 0, OPT_COLUMN_SEPARATOR}, {"delimited", OPTIONAL_ARG, 0, OPT_DELIMITED}, {"print-filenames", NO_ARG, 0, OPT_PRINT_FILENAMES}, {"copy-input", REQUIRED_ARG, 0, OPT_COPY_INPUT}, {"output-path", REQUIRED_ARG, 0, OPT_OUTPUT_PATH}, {"pager", REQUIRED_ARG, 0, OPT_PAGER}, {"legacy-timestamps", OPTIONAL_ARG, 0, OPT_LEGACY_TIMESTAMPS}, {0,0,0,0} /* sentinel entry */ }; static const char *appHelp[] = { "Print summary byte, packet, flow counts per IP record", "Print statistics (total bytes, packets, flows, unique IPs)", "Print IP addresses only to stdout", "Use destination IP address as key. Def. Source address", "Min byte count in an IP record for output. Def. 1", "Min packet count in an IP record for output. Def. 1", "Min record count in an IP record for output. Def. 1", "Max byte count in an IP record for output. Def. None", "Max packet count in an IP record for output. Def. None", "Max record count in an IP record for output. Def. None", "Write IPs to specified binary IPset file. Def. No", "Print IP numbers as integers. Def. No", "Print IP numbers as zero-padded dotted-decimal. Def. No", "When printing results, sort by IP address. Def. No", "Do not print column titles. Def. Print titles", "Disable fixed-width columnar output. Def. Columnar", "Use specified character between columns. Def. '|'", "Shortcut for --no-columns --column-sep=CHAR", "Print names of input files as they are opened. Def. No", "Copy all input SiLK Flows to given pipe or file. Def. No", "Send output to given file path. Def. stdout", "Program to invoke to page output. Def. $SILK_PAGER or $PAGER", ("Timestamp format. Choices:\n" "\t0==(new)\"YYYY/MM/DDThh:mm:ss\"; 1==(legacy)\"MM/DD/YYYY hh:mm:ss\".\n" "\tDef. " #ifdef SK_ENABLE_LEGACY_TIMESTAMP "1" #else "0" #endif " when switch not provided; 1 when switch provided with no value"), (char *)NULL }; /* LOCAL FUNCTION DECLARATIONS */ static void appUsageLong(void); static void appTeardown(void); static void appSetup(int argc, char **argv); /* never returns */ static int appOptionsHandler(clientData cData, int opt_index, char *opt_arg); /* 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. */ void appUsageLong(void) { #define USAGE_MSG \ ("{--print-recs|--print-stat|--print-ips} [SWITCHES] [FILES]\n" \ "\tSummarize SiLK Flow records by source or destination IP; with\n" \ "\tthe --print-recs option will produce textual ouput with counts of\n" \ "\tbytes, packets, and flow records for each IP, and the time range\n" \ "\twhen the IP was active. When no files are given on command line,\n" \ "\tflows are read from STDIN.\n") FILE *fh = USAGE_FH; skAppStandardUsage(fh, USAGE_MSG, appOptions, appHelp); sksiteOptionsUsage(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; countRecord_t *currentRec; countRecord_t *nextRec; uint32_t i; if (teardownFlag) { return; } teardownFlag = 1; /* free our memory */ for (i = 0; i < RWAC_ARRAYSIZE; ++i) { if (countArray[i] != NULL) { currentRec = countArray[i]; do { nextRec = currentRec->nextRecord; free(currentRec); currentRec = nextRec; } while (currentRec != countArray[i]); } } /* close the outputs */ if (ioISP->passCount && ioISP->passFD[0] && ioISP->passFD[0] != stdout) { if (ioISP->passIsPipe[0]) { if (-1 == pclose(ioISP->passFD[0])) { skAppPrintErr("Error closing output pipe %s", ioISP->passFPath[0]); } } else { if (EOF == fclose(ioISP->passFD[0])) { skAppPrintSyserror("Error closing output file %s", ioISP->passFPath[0]); } } ioISP->passFD[0] = NULL; } iochecksTeardown(ioISP); 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) { /* 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 */ useDestFlag = 0; printMode = RWAC_PMODE_NONE; byteMin = 0; recMin = 0; packetMin = 0; byteMax = 0; recMax = 0; packetMax = 0; /* create the input/output checker */ ioISP = iochecksSetup(1, 0, argc, argv); /* register the options */ if (optionsRegister(appOptions, (optHandler)appOptionsHandler, NULL) || sksiteOptionsRegister(SK_SITE_FLAG_CONFIG_FILE)) { skAppPrintErr("unable to register options"); exit(EXIT_FAILURE); } /* parse options */ ioISP->firstFile = optionsParse(argc, argv); if (ioISP->firstFile < 0) { skAppUsage();/* never returns */ } /* try to load site config file; if it fails, we will not be able * to resolve flowtype and sensor from input file names */ sksiteConfigure(0); /* Use a particular timestamp if requested */ if (legacy_timestamps == 0) { sktimestampSetMode(SK_TIMESTAMP_YYYYMMDD, SK_TIME_FRACSEC_OFF); } else if (legacy_timestamps == 1) { sktimestampSetMode(SK_TIMESTAMP_MMDDYYYY, SK_TIME_FRACSEC_OFF); } else { sktimestampSetMode(SK_TIMESTAMP_DEFAULT, SK_TIME_FRACSEC_OFF); } /* Use STDIN as an input stream if it is not a TTY; make certain * we have some input and we are either reading from STDIN or * using files listed the command line, but not both. */ if (iochecksAcceptFromStdin(ioISP) || iochecksInputs(ioISP, 0)) { skAppUsage(); } if (printMode == RWAC_PMODE_NONE) { skAppPrintErr("Specify --print-stat, --print-recs or --print-ips"); skAppUsage(); } /* Do they want the IPs in sorted order? */ if (sort_ips_flag) { switch (printMode) { case RWAC_PMODE_IPS: printMode = RWAC_PMODE_SORTED_IPS; break; case RWAC_PMODE_RECS: printMode = RWAC_PMODE_SORTED_RECS; break; default: /* skAppPrintErr("--sort-ips switch ignored"); */ break; } } /* Using both the integer-ips and zero-pad-ips options is * inconsistent. Let integer-ips override zero-pad-ips and print * warning */ if (integer_ips_flag && zero_pad_ips_flag) { skAppPrintErr("--integer-ips option overrides --zero-pad-ips\n" "\t Will print IPs as integers."); zero_pad_ips_flag = 0; } /* if no destination was specified, use stdout */ if ((0 == ioISP->passCount) && iochecksPassDestinations(ioISP, "stdout", 1)) { exit(EXIT_FAILURE); } /* looks good, open the --copy-input destination */ if (iochecksOpenCopyDest(ioISP)) { exit(EXIT_FAILURE); } if (atexit(appTeardown) < 0) { skAppPrintErr("unable to register appTeardown() with atexit()"); appTeardown(); exit(EXIT_FAILURE); } return; /* OK */ } /* * status = appOptionsHandler(cData, opt_index, opt_arg); * * Called by optionsParse(), this handles a user-specified switch * that the application has registered, typically by setting global * variables. Returns 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_USE_DEST: useDestFlag = 1; break; case OPT_BYTE_MIN: if (skStringParseUint32(&byteMin, opt_arg, 0, 0) != 0) { skAppPrintErr("Bad argument to --%s switch: '%s'", appOptions[opt_index].name, opt_arg); return 1; } break; case OPT_PKT_MIN: if (skStringParseUint32(&packetMin, opt_arg, 0, 0) != 0) { skAppPrintErr("Bad argument to --%s switch: '%s'", appOptions[opt_index].name, opt_arg); return 1; } break; case OPT_REC_MIN: if (skStringParseUint32(&recMin, opt_arg, 0, 0) != 0) { skAppPrintErr("Bad argument to --%s switch: '%s'", appOptions[opt_index].name, opt_arg); return 1; } break; case OPT_BYTE_MAX: if (skStringParseUint32(&byteMax, opt_arg, 0, 0) != 0) { skAppPrintErr("Bad argument to --%s switch: '%s'", appOptions[opt_index].name, opt_arg); return 1; } break; case OPT_PKT_MAX: if (skStringParseUint32(&packetMax, opt_arg, 0, 0) != 0) { skAppPrintErr("Bad argument to --%s switch: '%s'", appOptions[opt_index].name, opt_arg); return 1; } break; case OPT_REC_MAX: if (skStringParseUint32(&recMax, opt_arg, 0, 0) != 0) { skAppPrintErr("Bad argument to --%s switch: '%s'", appOptions[opt_index].name, opt_arg); return 1; } break; case OPT_PRINT_STAT: printMode = RWAC_PMODE_STAT; break; case OPT_PRINT_IPS: printMode = RWAC_PMODE_IPS; break; case OPT_PRINT_RECS: printMode = RWAC_PMODE_RECS; break; case OPT_SET_FILE: printMode = RWAC_PMODE_IPSETFILE; outputSetFN = opt_arg; break; case OPT_INTEGER_IPS: integer_ips_flag = 1; break; case OPT_ZERO_PAD_IPS: zero_pad_ips_flag = 1; break; case OPT_SORT_IPS: sort_ips_flag = 1; break; case OPT_NO_TITLES: no_titles = 1; break; case OPT_NO_COLUMNS: no_columns = 1; break; case OPT_COLUMN_SEPARATOR: delimiter = opt_arg[0]; break; case OPT_DELIMITED: no_columns = 1; if (opt_arg) { delimiter = opt_arg[0]; } break; case OPT_PRINT_FILENAMES: print_filenames = 1; break; case OPT_COPY_INPUT: if (iochecksAllDestinations(ioISP, opt_arg)) { return 1; } break; case OPT_OUTPUT_PATH: if (iochecksPassDestinations(ioISP, opt_arg, 1)) { return 1; } break; case OPT_PAGER: pager = opt_arg; break; case OPT_LEGACY_TIMESTAMPS: if (opt_arg == NULL) { legacy_timestamps = 1; } else if (opt_arg[0] == '1') { legacy_timestamps = 1; } else if (opt_arg[0] == '0') { legacy_timestamps = 0; } else { skAppPrintErr("Invalid value for %s: '%s'.", appOptions[opt_index].name, opt_arg); return 1; } break; } return 0; /* OK */ } /* * int addRecord(rwRec *Location, rwRec *currentRecord) * * Adds the contents of a record to the values stored in currentRecord. * * This function is actually designed for new records or records that * already exist. The "not existence" flag is the epoch start time * which, if set to zero is assumed to be a recently calloc'd record. */ static int addRecord(countRecord_t *tgt, rwRec *newRecord) { tgt->bytes += newRecord->bytes; tgt->packets += newRecord->pkts; tgt->flows ++; if (!tgt->start) { tgt->value = useDestFlag ? newRecord->dIP.ipnum : newRecord->sIP.ipnum; tgt->start = newRecord->sTime; tgt->end = newRecord->sTime + newRecord->elapsed; totalHashRecords++; } else { tgt->start = (newRecord->sTime <= tgt->start) ? newRecord->sTime : tgt->start; tgt->end = (newRecord->sTime + newRecord->elapsed) > tgt->end ? (newRecord->sTime + newRecord->elapsed) : tgt->end; } return 0; } /* * SECTION: Dumping * * All the output routines are in this section of the text. */ /* * countArrayToIPTree(&iptree, count_array); * * Fills in the 'iptree' with all the IPs in the 'count_array'. */ static void countArrayToIPTree( skIPTree_t **ip_tree, countRecord_t **count_array) { countRecord_t *currentRec; uint32_t i; skIPTreeCreate(ip_tree); for (i = 0; i < RWAC_ARRAYSIZE; i++) { currentRec = count_array[i]; while (currentRec != NULL) { if (IS_RECORD_WITHIN_LIMITS(currentRec)) { skIPTreeAddAddress((currentRec->value), (*ip_tree)); }; if (currentRec->nextRecord == count_array[i]) break; currentRec = currentRec->nextRecord; } } } /* * int dumpRecords(outfp) * * Dumps the addrcount contents as a record of bytes, packets, * times &c to 'outfp' * * This is the typical text output from addrcount. * */ static int dumpRecords(FILE *outfp) { uint32_t i; countRecord_t *currentRec; char ip_st[SK_NUM2DOT_STRLEN]; char start_st[SK_TIMESTAMP_STRLEN]; char end_st[SK_TIMESTAMP_STRLEN]; if ( !no_titles) { fprintf(outfp, format_records_title[no_columns], (useDestFlag ? "dIP" : "sIP"), delimiter, "Bytes", delimiter, "Packets", delimiter, "Records", delimiter, "Start_Time", delimiter, "End_Time", delimiter); } for (i = 0; i < RWAC_ARRAYSIZE; i++) { currentRec = countArray[i]; while (currentRec != NULL) { if (IS_RECORD_WITHIN_LIMITS(currentRec)) { /* print IP in appropriate form */ if (integer_ips_flag) { snprintf(ip_st, sizeof(ip_st), "%lu", (unsigned long)currentRec->value); } else if (zero_pad_ips_flag) { num2dot0_r(currentRec->value, ip_st); } else { num2dot_r(currentRec->value, ip_st); } /* print "volumes" and time */ fprintf(outfp, format_records[no_columns], ip_st, delimiter, currentRec->bytes, delimiter, currentRec->packets, delimiter, currentRec->flows, delimiter, timestamp_r(currentRec->start, start_st), delimiter, timestamp_r(currentRec->end, end_st), delimiter); } if (currentRec->nextRecord == countArray[i]) break; currentRec = currentRec->nextRecord; } } return 0; } /* * int dumpRecordsSorted(outfp) * * Dumps the addrcount contents as a record of bytes, packets, * times &c to 'outfp', sorted by the IP address. * */ static int dumpRecordsSorted(FILE *outfp) { uint32_t ip, key; countRecord_t *currentRec; skIPTree_t *resultTree; skIPTreeIterator_t iter; char ip_st[SK_NUM2DOT_STRLEN]; char start_st[SK_TIMESTAMP_STRLEN]; char end_st[SK_TIMESTAMP_STRLEN]; countArrayToIPTree(&resultTree, countArray); if (skIPTreeIteratorBind(&iter, resultTree)) { skAppPrintErr("Unable to bind IPTree iterator"); return 1; } if ( !no_titles) { fprintf(outfp, format_records_title[no_columns], (useDestFlag ? "dIP" : "sIP"), delimiter, "Bytes", delimiter, "Packets", delimiter, "Records", delimiter, "Start_Time", delimiter, "End_Time", delimiter); } while (skIPTreeIteratorNext(&ip, &iter) == SK_ITERATOR_OK) { /* find the ip's entry in the hash table */ key = HASHFUNC(ip); /* loop through the list of records at this hash table entry * until we find the one that has the IP we want */ currentRec = countArray[key]; while (currentRec != NULL) { if (ip != currentRec->value) { /* not the ip we wanted; goto next */ currentRec = currentRec->nextRecord; /* if we've looped all the around, we missed the * IP we wanted, and things are horked */ assert(currentRec != countArray[key]); continue; } /* print IP in appropriate form */ if (integer_ips_flag) { sprintf(ip_st, "%lu", (unsigned long)currentRec->value); } else if (zero_pad_ips_flag) { num2dot0_r(currentRec->value, ip_st); } else { num2dot_r(currentRec->value, ip_st); } /* print "volumes" and time */ fprintf(outfp, format_records[no_columns], ip_st, delimiter, currentRec->bytes, delimiter, currentRec->packets, delimiter, currentRec->flows, delimiter, timestamp_r(currentRec->start, start_st), delimiter, timestamp_r(currentRec->end, end_st), delimiter); break; } } return 0; } /* * int dumpIPs(outfp) * * writes IP addresses to the stream 'outfp' in hash-table order * (unsorted). * * Returns 0 on success. */ static int dumpIPs(FILE *outfp) { uint32_t i; countRecord_t *currentRec; if ( !no_titles) { fprintf(outfp, "%15s\n", (useDestFlag ? "dIP" : "sIP")); } for (i = 0; i < RWAC_ARRAYSIZE; i++) { currentRec = countArray[i]; while (currentRec != NULL) { if (IS_RECORD_WITHIN_LIMITS(currentRec)) { if (integer_ips_flag) { fprintf(outfp, "%15lu\n", (unsigned long)currentRec->value); } else if (zero_pad_ips_flag) { fprintf(outfp, "%15s\n", num2dot0(currentRec->value)); } else { fprintf(outfp, "%15s\n", num2dot(currentRec->value)); } } if (currentRec->nextRecord == countArray[i]) break; currentRec = currentRec->nextRecord; } } return 0; } /* * int dumpIPsSorted(outfp) * * Writes the IPs to the stream 'outfp' in sorted order. * */ static int dumpIPsSorted(FILE *outfp) { uint32_t ip; skIPTree_t *resultTree; skIPTreeIterator_t iter; countArrayToIPTree(&resultTree, countArray); if (skIPTreeIteratorBind(&iter, resultTree)) { skAppPrintErr("Unable to create IPTree iterator"); return 1; } if ( !no_titles) { fprintf(outfp, "%15s\n", (useDestFlag ? "dIP" : "sIP")); } while (skIPTreeIteratorNext(&ip, &iter) == SK_ITERATOR_OK) { if (integer_ips_flag) { fprintf(outfp, "%15lu\n", (unsigned long)ip); } else if (zero_pad_ips_flag) { fprintf(outfp, "%15s\n", num2dot0(ip)); } else { fprintf(outfp, "%15s\n", num2dot(ip)); } } return 0; } /* * int dumpStats(outfp) * * dumps a text string describing rwaddrcont results to the stream * 'outfp'. */ static int dumpStats(FILE *outfp) { uint32_t i; uint32_t qual_ips; uint64_t qual_bytes, qual_packets, qual_flows; uint64_t tot_bytes, tot_packets, tot_flows; countRecord_t *currentRec; const char * const fmt_stats[] = { ("%10s%c%10u%c%20" PRIu64 "%c%15" PRIu64 "%c%15" PRIu64 "%c\n"), ("%s%c%u%c%" PRIu64 "%c%" PRIu64 "%c%" PRIu64 "%c\n") }; const char * const fmt_stats_title[] = { "%10s%c%10s%c%20s%c%15s%c%15s%c\n", "%s%c%s%c%s%c%s%c%s%c\n" }; qual_ips = 0; qual_bytes = qual_packets = qual_flows = 0; tot_bytes = tot_packets = tot_flows = 0; for (i = 0; i < RWAC_ARRAYSIZE; i++) { currentRec = countArray[i]; while (currentRec != NULL) { tot_bytes += currentRec->bytes; tot_packets += currentRec->packets; tot_flows += currentRec->flows; if (IS_RECORD_WITHIN_LIMITS(currentRec)) { ++qual_ips; qual_bytes += currentRec->bytes; qual_packets += currentRec->packets; qual_flows += currentRec->flows; } if (currentRec->nextRecord == countArray[i]) break; currentRec = currentRec->nextRecord; } } /* title */ if ( !no_titles) { fprintf(outfp, fmt_stats_title[no_columns], "", delimiter, (useDestFlag ? "dIP_Uniq" : "sIP_Uniq"), delimiter, "Bytes", delimiter, "Packets", delimiter, "Records", delimiter); } fprintf(outfp, fmt_stats[no_columns], "Total", delimiter, totalHashRecords, delimiter, tot_bytes, delimiter, tot_packets, delimiter, tot_flows, delimiter); if (byteMin || byteMax || recMin || recMax || packetMin || packetMax) { fprintf(outfp, fmt_stats[no_columns], "Qualifying", delimiter, qual_ips, delimiter, qual_bytes, delimiter, qual_packets, delimiter, qual_flows, delimiter); } return 0; } /* * int dumpIPSet (char *setFileName) * * Dumps the ip addresses counted during normal operation to disk * in ip tree format. */ static int dumpIPSet(char *setFileName) { skIPTree_t *resultTree; /* Create the ip tree */ countArrayToIPTree(&resultTree, countArray); /* * Okay, now we write to disk. */ if (skIPTreeSave(setFileName, resultTree)) { exit(EXIT_FAILURE); }; return 0; } /* * main invocation */ int main(int argc, char **argv) { int counter; uint32_t hashIndex, recCount; countRecord_t *hashRecord; countRecord_t *rootRecord; rwIOStruct_t *rwIOS; int using_pager = 0; FILE *stream_out; counter = hashIndex = recCount = 0; appSetup(argc, argv); /* never returns on error */ stream_out = ioISP->passFD[0]; /* Read in records */ for (counter = ioISP->firstFile; counter < ioISP->fileCount ; counter++) { rwIOS = rwOpenFile(ioISP->fnArray[counter], ioISP->inputCopyFD); if (NULL == rwIOS) { exit(EXIT_FAILURE); } if (print_filenames) { fprintf(PRINT_FILENAMES_FH, "%s\n", rwGetFileName(rwIOS)); } while (rwRead(rwIOS, ¤tRecord)) { recCount++; hashIndex = IPHASH(currentRecord, useDestFlag); /* * standard hash foo - check to see if we've got a value. * If not, create and stuff. If so, check to see if they * match - if so, stuff. If not, move down until you do - * if you find nothing, stuff. */ if (countArray[hashIndex] == NULL) { countArray[hashIndex] = calloc(1, sizeof(countRecord_t)); if (countArray[hashIndex] == NULL) { skAppPrintErr("Error allocating memory for record"); exit(EXIT_FAILURE); } /* * No error on callocing, continue */ if (addRecord(countArray[hashIndex], ¤tRecord)) { skAppPrintErr("Error creating record, terminating"); exit(EXIT_FAILURE); } /* * Alright, I'm modifying the record set so that it's circular, * the explicit rationale for this is that a loop (rather than * an array) will allow me to effectively rotate the "contact * point of the loop" so that the last used is always * at the countArray[hashIndex] position. */ countArray[hashIndex]->nextRecord = countArray[hashIndex]; } else { hashRecord = countArray[hashIndex]; rootRecord = hashRecord; while (!CMPFNC(currentRecord, hashRecord, useDestFlag) && (hashRecord->nextRecord != rootRecord)) { hashRecord = hashRecord->nextRecord; } /* * Alright, we've either hit the end of the linked * list or we've found the value. We check and handle * the end of list case first. */ if (!CMPFNC(currentRecord, hashRecord, useDestFlag)) { if ((hashRecord->nextRecord = (countRecord_t *) calloc(1, sizeof(countRecord_t))) == NULL) { skAppPrintErr("Error allocating memory for record"); exit(EXIT_FAILURE); } hashRecord = hashRecord->nextRecord; hashRecord->nextRecord = rootRecord; /* Restore the loop */ /* We've now implicitly created an empty * record and dropped it at currentRecord. */ } countArray[hashIndex] = hashRecord; if (addRecord(countArray[hashIndex], ¤tRecord)) { skAppPrintErr("Error creating record, terminating"); exit(EXIT_FAILURE); } } } rwCloseFile(rwIOS); } /* Invoke the pager when appropriate. */ switch (printMode) { case RWAC_PMODE_STAT: case RWAC_PMODE_IPSETFILE: break; case RWAC_PMODE_RECS: case RWAC_PMODE_IPS: case RWAC_PMODE_SORTED_RECS: case RWAC_PMODE_SORTED_IPS: using_pager = skOpenPagerWhenStdoutTty(&stream_out, &pager); if (using_pager < 0) { exit(EXIT_FAILURE); } break; case RWAC_PMODE_NONE: case RWAC_PMODE_RWACFILE: abort(); } /* Produce the output */ switch (printMode) { case RWAC_PMODE_STAT: dumpStats(stream_out); break; case RWAC_PMODE_IPSETFILE: dumpIPSet(outputSetFN); break; case RWAC_PMODE_RECS: dumpRecords(stream_out); break; case RWAC_PMODE_IPS: dumpIPs(stream_out); break; case RWAC_PMODE_SORTED_RECS: dumpRecordsSorted(stream_out); break; case RWAC_PMODE_SORTED_IPS: dumpIPsSorted(stream_out); break; case RWAC_PMODE_NONE: case RWAC_PMODE_RWACFILE: abort(); }; /* close the pager */ if (using_pager) { skClosePager(stream_out, pager); } /* output */ appTeardown(); return (0); } /* ** Local Variables: ** mode:c ** indent-tabs-mode:nil ** c-basic-offset:4 ** End: */