/* ** 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@ */ /* ** rwstatsutils.c ** ** utility functions for the rwstats application. See rwstats.c for ** a full explanation. ** */ #include "silk.h" RCSIDENT("$SiLK: rwstatsutils.c 8269 2007-08-03 18:54:48Z mthomas $"); #include "rwstats.h" /* LOCAL FUNCTIONS */ static void appUsageLong(void); static void setColumnWidth(int width); /* LOCAL VARIABLES */ /* Whether options handler handled a key switch, value switch, top-bottom */ static int got_key = 0; static int got_val = 0; static int got_bt = 0; /* Did user ask for legacy help? */ static int legacy_help_requested = 0; /* Paging program to use. */ static char *g_pager = NULL; /* Whether stdout is being used */ static int g_stdout_used = 0; /* OPTIONS SETUP */ /* appOptionsEnum defined in rwstats.h */ static struct option appOptions[] = { {"overall-stats", NO_ARG, 0, OPT_OVERALL_STATS}, {"detail-proto-stats", REQUIRED_ARG, 0, OPT_DETAIL_PROTO_STATS}, {"sip", OPTIONAL_ARG, 0, OPT_SIP}, {"dip", OPTIONAL_ARG, 0, OPT_DIP}, {"sport", NO_ARG, 0, OPT_SPORT}, {"dport", NO_ARG, 0, OPT_DPORT}, {"protocol", NO_ARG, 0, OPT_PROTOCOL}, {"icmp", NO_ARG, 0, OPT_ICMP}, {"flows", NO_ARG, 0, OPT_FLOWS}, {"packets", NO_ARG, 0, OPT_PACKETS}, {"bytes", NO_ARG, 0, OPT_BYTES}, {"count", REQUIRED_ARG, 0, OPT_COUNT}, {"threshold", REQUIRED_ARG, 0, OPT_THRESHOLD}, {"percentage", REQUIRED_ARG, 0, OPT_PERCENTAGE}, {"top", NO_ARG, 0, OPT_TOP}, {"bottom", NO_ARG, 0, OPT_BOTTOM}, {"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}, {"integer-ips", NO_ARG, 0, OPT_INTEGER_IPS}, {"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-help", NO_ARG, 0, OPT_LEGACY_HELP}, {0,0,0,0} /* sentinel entry */ }; static const char *appHelp[] = { ("Print minima, maxima, quartiles, and interval-count\n" "\tstatistics for bytes, pkts, bytes/pkt across all flows. Def. No"), ("Print above statistics for each of the specified\n" "\tprotocols. List protocols or ranges separated by commas. Def. No\n" "\n" "Key fields to use; must specify one or two (all pairs not supported):"), ("Use the source address as the key.\n" "\tOptional argument is CIDR block---number of bits to consider"), ("Use the destination address as the key.\n" "\tOptional argument is CIDR block---number of bits to consider"), "Use the source port as the key", "Use the destination port as the key", "Use the protocol as the key", ("Use the ICMP type and code as the key; assumes all input is ICMP\n" "\n" "Value field to use; may specify one (flow is default if none given):"), "Use the flow count as the value. Def. Yes", "Use the packet count as the value. Def. No", ("Use the byte count as the value. Def. No\n" "\n" "How to determine the N for Top-/Bottom-N; must specify one:"), "Print the specified number of key/value pairs", ("Print key/value pairs where the value is\n" "\tgreater-/less-than this threshold"), ("Print key/value pairs where the value is\n" "\tgreater-/less-than this percentage of the total value\n" "\n" "Whether to compute Top- or Bottom-N; may specify one (top is default):"), "Print the top N keys and their values. Def. Yes", ("Print the bottom N keys and their values. Def. No\n" "\n" "Miscellaneous switches:"), "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 ips as integers. Def. dotted decimal", "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", "Print help, including legacy switches", (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 \ (" [FILES]\n" \ "\tSummarize SiLK Flow records by one of a limited number of\n" \ "\tkey/value pairs and display the results as a Top-N or Bottom-N\n" \ "\tlist. The N can be a fixed value, a certain percentage of the\n" \ "\tinnput, or a threshold value.\n" \ "\tAlternatively, provide statistics for each of bytes, packets, and\n" \ "\tbytes-per-packet giving minima, maxima, quartile, and interval\n" \ "\tflow-counts across all flows or across user-specified protocols.\n" \ "\tWhen no files are given on command line, flows are read from STDIN.\n") FILE *fh = USAGE_FH; skAppStandardUsage(fh, USAGE_MSG, appOptions, appHelp); sksiteOptionsUsage(fh); if (legacy_help_requested) { legAppOptionsUsage(fh); } } /* * appTeardown() * * Teardown all modules, close all files, and tidy up all * application state. * * This function is idempotent. */ void appTeardown(void) { static int teardownFlag = 0; int rv; if (0 != teardownFlag) { return; } teardownFlag = 1; if (g_proto_stats) { teardownProtoStats(); } else { teardownTopn(); } rv = skStreamDestroy(&outstream); if (rv) { skStreamPrintLastErr(outstream, rv, &skAppPrintErr); } rv = rwioDestroy(&g_copy_input); if (rv) { rwioPrintLastErr(g_copy_input, rv, &skAppPrintErr); } 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. */ void appSetup(int argc, char **argv) { int rv = 0; /* 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 variables */ if (skStreamCreate(&outstream, SK_IO_WRITE, SK_CONTENT_TEXT)) { skAppPrintErr("unable to create output stream"); exit(EXIT_FAILURE); } /* Initialize the output field widths to their defaults */ setColumnWidth(-1); /* register the options */ if ((optionsRegister(appOptions, (optHandler)appOptionsHandler, NULL)) || sksiteOptionsRegister(SK_SITE_FLAG_CONFIG_FILE)) { skAppPrintErr("unable to register options"); exit(EXIT_FAILURE); } /* register legacy options */ if (legAppOptionsSetup()) { exit(EXIT_FAILURE); } /* parse options */ arg_index = optionsParse(argc, argv); assert(arg_index <= argc); if (arg_index < 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); /* configure input: make certain we have input. */ if (arg_index == argc) { /* No files on the command line; we should have some from stdin. */ if (FILEIsATty(stdin)) { /* stdin is a terminal. no files */ skAppPrintErr("No input: No records to read from stdin\n" "\t and no files listed on the command line."); skAppUsage(); } } /* make certain there is something to compute and that conflicting * requests were not given */ if (got_key || got_val || got_bt || g_limit) { if (g_proto_stats) { skAppPrintErr(("%s no longer supports detailed protocol\n" "\tanalysis and computing top-N list in one run."), skAppName()); skAppUsage(); } /* Whether top-N or bottom-N; top-N is default */ if (!got_bt) { top_or_btm = BT_TOP; } /* What value to count: bytes, packets, flows. Flows is default. */ if (!got_val) { val_type = VAL_FLOWS; } /* Both a key and a limit are required */ if (!got_key) { skAppPrintErr(("No key was selected. Choose among --%s, --%s\n" "\t--%s, --%s, --%s, or --%s"), appOptions[OPT_SIP].name, appOptions[OPT_DIP].name, appOptions[OPT_SPORT].name, appOptions[OPT_DPORT].name, appOptions[OPT_PROTOCOL].name, appOptions[OPT_ICMP].name); skAppUsage(); } if (!g_limit) { skAppPrintErr(("No stopping condition was entered.\n" "\tChoose one of --%s, --%s, or --%s"), appOptions[OPT_COUNT].name, appOptions[OPT_THRESHOLD].name, appOptions[OPT_PERCENTAGE].name); skAppUsage(); } if (setupTopn()) { exit(EXIT_FAILURE); } } else if (g_proto_stats) { if (setupProtoStats()) { exit(EXIT_FAILURE); } } else { skAppPrintErr("No computional output was requested."); skAppUsage(); } /* Set output-path to stdout if none selected */ if (NULL == skStreamGetPathname(outstream)) { if (g_stdout_used) { skAppPrintErr("Multiple outputs are trying to use stdout"); exit(EXIT_FAILURE); } rv = skStreamBind(outstream, "stdout"); if (rv) { skStreamPrintLastErr(outstream, rv, &skAppPrintErr); exit(EXIT_FAILURE); } } if (atexit(appTeardown) < 0) { skAppPrintErr("unable to register appTeardown() with atexit()"); appTeardown(); exit(EXIT_FAILURE); } /* Open the stream and invoke the pager */ if ((rv = skStreamPageOutput(outstream, g_pager)) || (rv = skStreamOpen(outstream))) { skStreamPrintLastErr(outstream, rv, &skAppPrintErr); exit(EXIT_FAILURE); } /* Open the stream for the input copy */ if (g_copy_input) { if ((rv = rwioOpen(g_copy_input)) || (rv = rwioWriteHeader(g_copy_input))) { rwioPrintLastErr(g_copy_input, rv, &skAppPrintErr); 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. */ int appOptionsHandler( clientData UNUSED(cData), int opt_index, char *opt_arg) { uint32_t val; int old_id; int rv; #define KEY_COMBO_ERR(a, b) \ skAppPrintErr(("Key combination --%s and --%s is not supported\n" \ "\tMay only pair %s with %s or %s with %s"), \ appOptions[(a)].name, appOptions[(b)].name, \ appOptions[OPT_SIP].name, \ appOptions[OPT_DIP].name, \ appOptions[OPT_SPORT].name, \ appOptions[OPT_DPORT].name) switch ((appOptionsEnum)opt_index) { case OPT_TOP: case OPT_BOTTOM: if (got_bt) { skAppPrintErr("May only specify one of --%s or --%s.", appOptions[OPT_TOP].name, appOptions[OPT_BOTTOM].name); return 1; } got_bt = 1; top_or_btm = (opt_index - OPT_TOP); break; case OPT_COUNT: case OPT_THRESHOLD: case OPT_PERCENTAGE: if (g_limit != 0) { skAppPrintErr("May only specify one of --%s, --%s, or --%s.", appOptions[OPT_COUNT].name, appOptions[OPT_THRESHOLD].name, appOptions[OPT_PERCENTAGE].name); return 1; } if (opt_index == OPT_PERCENTAGE) { g_limit = 100; } if (skStringParseUint64(&g_limit, opt_arg, 1, g_limit)) { skAppPrintErr("Illegal %s value: '%s'", appOptions[opt_index].name, opt_arg); return 1; } stats_type = (opt_index - OPT_COUNT); break; case OPT_FLOWS: case OPT_PACKETS: case OPT_BYTES: if (got_val) { skAppPrintErr("May only specify one of --%s, --%s or --%s.", appOptions[OPT_FLOWS].name, appOptions[OPT_PACKETS].name, appOptions[OPT_BYTES].name); return 1; } got_val = 1; val_type = (opt_index - OPT_FLOWS); break; case OPT_SIP: if (!got_key) { got_key = 1; key_type = (opt_index - OPT_SIP); } else { old_id = (key_type + OPT_SIP); if (key_type == KEY_DIP) { key_type = KEY_IP_PAIR; } else if (key_type == KEY_IP_PAIR) { /* ignore second --sip */ } else if (key_type == KEY_PORT_PAIR) { skAppPrintErr("A maximum of two keys may be specified."); return 1; } else if (opt_index != old_id) { KEY_COMBO_ERR(opt_index, old_id); return 1; } } if (opt_arg) { if (skStringParseUint32(&val, opt_arg, 1, 31)) { skAppPrintErr(("Bad value for %s CIDR length: '%s'\n" "\tUse a value between 1 and 31."), appOptions[opt_index].name, opt_arg); return 1; } g_cidr_src = ~0 << (32 - val); } break; case OPT_DIP: if (!got_key) { got_key = 1; key_type = (opt_index - OPT_SIP); } else { old_id = (key_type + OPT_SIP); if (key_type == KEY_SIP) { key_type = KEY_IP_PAIR; } else if (key_type == KEY_IP_PAIR) { /* ignore second --dip */ } else if (key_type == KEY_PORT_PAIR) { skAppPrintErr("A maximum of two keys may be specified."); return 1; } else if (opt_index != old_id) { KEY_COMBO_ERR(opt_index, old_id); return 1; } } if (opt_arg) { if (skStringParseUint32(&val, opt_arg, 1, 31)) { skAppPrintErr(("Bad value for %s CIDR length: '%s'\n" "\tUse a value between 1 and 31."), appOptions[opt_index].name, opt_arg); return 1; } g_cidr_dest = ~0 << (32 - val); } break; case OPT_SPORT: if (!got_key) { got_key = 1; key_type = (opt_index - OPT_SIP); } else { old_id = (key_type + OPT_SIP); if (key_type == KEY_DPORT) { key_type = KEY_PORT_PAIR; } else if (key_type == KEY_PORT_PAIR) { /* ignore second --sport */ } else if (key_type == KEY_IP_PAIR) { skAppPrintErr("A maximum of two keys may be specified."); return 1; } else if (opt_index != old_id) { KEY_COMBO_ERR(opt_index, old_id); return 1; } } break; case OPT_DPORT: if (!got_key) { got_key = 1; key_type = (opt_index - OPT_SIP); } else { old_id = (key_type + OPT_SIP); if (key_type == KEY_SPORT) { key_type = KEY_PORT_PAIR; } else if (key_type == KEY_PORT_PAIR) { /* ignore second --dport */ } else if (key_type == KEY_IP_PAIR) { skAppPrintErr("A maximum of two keys may be specified."); return 1; } else if (opt_index != old_id) { KEY_COMBO_ERR(opt_index, old_id); return 1; } } break; case OPT_PROTOCOL: if (!got_key) { got_key = 1; key_type = (opt_index - OPT_SIP); } else { old_id = (key_type + OPT_SIP); if (opt_index != old_id) { KEY_COMBO_ERR(opt_index, old_id); return 1; } break; } break; case OPT_ICMP: if (!got_key) { got_key = 1; key_type = (opt_index - OPT_SIP); } else { old_id = (key_type + OPT_SIP); if (opt_index != old_id) { KEY_COMBO_ERR(opt_index, old_id); return 1; } break; } break; case OPT_OVERALL_STATS: /* combined stats for all protocols */ g_proto_stats = 1; break; case OPT_DETAIL_PROTO_STATS: /* detailed stats for specific proto */ if (0 != parseProtos(opt_arg)) { return 1; } g_proto_stats = 1; break; case OPT_NO_TITLES: /* whether to print column titles */ g_print_titles = 0; break; case OPT_NO_COLUMNS: setColumnWidth(1); break; case OPT_COLUMN_SEPARATOR: g_delim = opt_arg[0]; break; case OPT_DELIMITED: /* delimiter string & fixed width output columns */ if (opt_arg) { g_delim = opt_arg[0]; } setColumnWidth(1); break; case OPT_INTEGER_IPS: /* whether to print integer ips */ g_integer_ips = 1; break; case OPT_PRINT_FILENAMES: /* whether to print input filenames */ g_print_filenames = 1; break; case OPT_OUTPUT_PATH: if (skStreamGetPathname(outstream)) { skAppPrintErr("The --%s switch was given multiple times", appOptions[opt_index].name); return 1; } if (0 == strcmp(opt_arg, "stdout")) { if (g_stdout_used) { skAppPrintErr("Multiple outputs are trying to use stdout"); return 1; } g_stdout_used = 1; } rv = skStreamBind(outstream, opt_arg); if (rv) { skStreamPrintLastErr(outstream, rv, &skAppPrintErr); return 1; } break; case OPT_COPY_INPUT: if (g_copy_input) { skAppPrintErr("The --%s switch was given multiple times", appOptions[opt_index].name); return 1; } if (0 == strcmp(opt_arg, "stdout")) { if (g_stdout_used) { skAppPrintErr("Multiple outputs are trying to use stdout"); return 1; } g_stdout_used = 1; } rv = rwioCreate(&g_copy_input, opt_arg, SK_RWIO_WRITE); if (rv) { rwioPrintLastErr(g_copy_input, rv, &skAppPrintErr); return 1; } break; case OPT_PAGER: /* output paging program to invoke */ g_pager = opt_arg; break; case OPT_LEGACY_HELP: legacy_help_requested = 1; appUsageLong(); exit(EXIT_SUCCESS); } return 0; /* OK */ } /* * statsSetColumnWidth(width); * * Sets the width of the output columns to 'width' if width is * greater than zero; otherwise set columns' widths to their * default values. */ static void setColumnWidth(int width) { unsigned int i; int default_width[] = { 15, /* WIDTH_KEY: key */ 20, /* WIDTH_VAL: count */ 10, /* WIDTH_INTVL: interval maximum */ 10, /* WIDTH_PCT: percentage value */ }; if (width > 0) { for (i = 0; i < sizeof(g_width)/sizeof(g_width[0]); ++i) { g_width[i] = width; } } else { for (i = 0; i < sizeof(g_width)/sizeof(g_width[0]); ++i) { g_width[i] = default_width[i]; } } } /* ** Local Variables: ** mode:c ** indent-tabs-mode:nil ** c-basic-offset:4 ** End: */