1 /*
2 ** Copyright (C) 2005-2020 by Carnegie Mellon University.
3 **
4 ** @OPENSOURCE_LICENSE_START@
5 ** See license information in ../../LICENSE.txt
6 ** @OPENSOURCE_LICENSE_END@
7 */
8
9 /*
10 ** rwptoflow.c
11 **
12 ** Generates a flow for every IP packet. Since IP packets can
13 ** arrive out of order, though, some fragments are collapsed into a
14 ** single flow. (In particular, all fragments before the "zero"
15 ** fragment are lumped into the "zero" fragment's flow. Later
16 ** fragments are output as their own flows. We do this so that we
17 ** can add OSI layer 4 information to the flows we generate, like
18 ** source and destination ports.)
19 **
20 ** Future development:
21 **
22 ** In the event that the zero fragment is too small to contain TCP
23 ** flags, attempt to get them from the next fragment. This will
24 ** require more sophisticated fragment reassembly.
25 */
26
27 #include <silk/silk.h>
28
29 RCSIDENT("$SiLK: rwptoflow.c ef14e54179be 2020-04-14 21:57:45Z mthomas $");
30
31 #include <silk/rwrec.h>
32 #include <silk/skipaddr.h>
33 #include <silk/skplugin.h>
34 #include <silk/sksite.h>
35 #include <silk/skstream.h>
36 #include <silk/utils.h>
37 #include "rwppacketheaders.h"
38
39
40 /* LOCAL DEFINES AND TYPEDEFS */
41
42 /* where to write --help output */
43 #define USAGE_FH stdout
44
45 /* where to print the statistics */
46 #define STATS_STREAM stderr
47
48
49 /* LOCAL VARIABLES */
50
51 /*
52 * rwptoflow hands the packet to the plugin as an "extra argument".
53 * rwptoflow and its plugins must agree on the name of this argument.
54 * The extra argument is specified in a NULL-terminated array of
55 * argument names defined in rwppacketheaders.h.
56 */
57 static const char *plugin_extra_args[] = RWP2F_EXTRA_ARGUMENTS;
58
59 /* the packet file to read */
60 static const char *packet_input_path = NULL;
61 static pcap_t *packet_input = NULL;
62
63 /* the flow file to write */
64 static skstream_t *flow_output = NULL;
65
66 /* the compression method to use when writing the flow_output file.
67 * skCompMethodOptionsRegister() will set this to the default or
68 * to the value the user specifies. */
69 static sk_compmethod_t comp_method;
70
71 /* the optional packet file to write for packets that pass */
72 static const char *packet_pass_path = NULL;
73 static pcap_dumper_t *packet_pass = NULL;
74
75 /* the optional packet file to write for packets that reject */
76 static const char *packet_reject_path = NULL;
77 static pcap_dumper_t *packet_reject = NULL;
78
79 /* time window over which to process data */
80 static struct time_window_st {
81 struct timeval tw_begin;
82 struct timeval tw_end;
83 } time_window;
84
85 /* default values to insert into each SiLK Flow */
86 static rwRec default_flow_values;
87
88 /* whether to ignore all fragmented packets; whether to ignore
89 * fragmented packets other than the initial one */
90 static int reject_frags_all = 0;
91 static int reject_frags_subsequent = 0;
92
93 /* whether to ignore packets where either the fragment or the capture
94 * size is too small to gather the port information for TCP, UPD,
95 * ICMP---and the flags information for TCP. */
96 static int reject_incomplete = 0;
97
98 /* statistics counters and whether to print them */
99 static struct statistics_st {
100 /* total number of packets read */
101 uint64_t s_total;
102 /* packets that were too short to get any information from */
103 uint64_t s_short;
104 /* packets that were not Ethernet_IP or non-IPv4 packets */
105 uint64_t s_nonipv4;
106 /* packets that occurred outside the time window */
107 uint64_t s_prewindow;
108 uint64_t s_postwindow;
109 /* packets that were fragmented */
110 uint64_t s_fragmented;
111 /* packets that were the initial packet of a fragment */
112 uint64_t s_zerofrag;
113 /* packets that the user's plug-in ignored and rejected */
114 uint64_t s_plugin_ign;
115 uint64_t s_plugin_rej;
116 /* packets that were long enough to get most info but too short to
117 * get the ports---and/or flags for TCP */
118 uint64_t s_incomplete;
119 } statistics;
120
121 static int print_statistics = 0;
122
123 /* value passed to pcap_open for stdin/stdout */
124 static const char *pcap_stdio = "-";
125
126 /* buffer for pcap error messages */
127 static char errbuf[PCAP_ERRBUF_SIZE];
128
129
130 /* OPTION SETUP */
131
132 typedef enum {
133 OPT_PLUGIN,
134 OPT_ACTIVE_TIME,
135 OPT_FLOW_OUTPUT,
136 OPT_PACKET_PASS_OUTPUT,
137 OPT_PACKET_REJECT_OUTPUT,
138 OPT_REJECT_ALL_FRAGMENTS,
139 OPT_REJECT_NONZERO_FRAGMENTS,
140 OPT_REJECT_INCOMPLETE,
141 OPT_SET_SENSORID,
142 OPT_SET_INPUTINDEX,
143 OPT_SET_OUTPUTINDEX,
144 OPT_SET_NEXTHOPIP,
145 OPT_PRINT_STATISTICS
146 } appOptionsEnum;
147
148
149 static struct option appOptions[] = {
150 {"plugin", REQUIRED_ARG, 0, OPT_PLUGIN},
151 {"active-time", REQUIRED_ARG, 0, OPT_ACTIVE_TIME},
152 {"flow-output", REQUIRED_ARG, 0, OPT_FLOW_OUTPUT},
153 {"packet-pass-output", REQUIRED_ARG, 0, OPT_PACKET_PASS_OUTPUT},
154 {"packet-reject-output", REQUIRED_ARG, 0, OPT_PACKET_REJECT_OUTPUT},
155 {"reject-all-fragments", NO_ARG, 0, OPT_REJECT_ALL_FRAGMENTS},
156 {"reject-nonzero-fragments",NO_ARG, 0, OPT_REJECT_NONZERO_FRAGMENTS},
157 {"reject-incomplete", NO_ARG, 0, OPT_REJECT_INCOMPLETE},
158 {"set-sensorid", REQUIRED_ARG, 0, OPT_SET_SENSORID},
159 {"set-inputindex", REQUIRED_ARG, 0, OPT_SET_INPUTINDEX},
160 {"set-outputindex", REQUIRED_ARG, 0, OPT_SET_OUTPUTINDEX},
161 {"set-nexthopip", REQUIRED_ARG, 0, OPT_SET_NEXTHOPIP},
162 {"print-statistics", NO_ARG, 0, OPT_PRINT_STATISTICS},
163 {0,0,0,0} /* sentinel entry */
164 };
165
166
167 static const char *appHelp[] = {
168 "Use given plug-in. Def. None",
169 ("Only generate flows for packets whose time falls within\n"
170 "\tthe specified range. Def. Generate flows for all packets\n"
171 "\tYYYY/MM/DD:hh:dd:mm:ss.uuuuuu-YYYY/MM/DD:hh:dd:mm:ss.uuuuuu"),
172 ("Write the generated SiLK Flow records to the specified\n"
173 "\tstream or file path. Def. stdout"),
174 ("For each generated flow, write its corresponding\n"
175 "\tpacket to the specified path. Def. No"),
176 ("Write each packet that occurs within the\n"
177 "\tactive-time window but for which a SiLK Flow is NOT generated to\n"
178 "\tthe specified path. Def. No"),
179 ("Do not generate a SiLK Flow when the packet is\n"
180 "\tfragmented. Def. All packets"),
181 ("Do not generate SiLK Flows for packets where\n"
182 "\tthe fragment-offset is non-zero. Def. All packets"),
183 ("Do not generate SiLK Flows for zero-fragment or\n"
184 "\tunfragmented packets when the flow cannot be completely filled\n"
185 "\t(missing ICMP type&code, TCP/UDP ports, TCP flags). Def. All packets"),
186 "Set sensor ID for all flows, 0-65534. Def. 0",
187 "Set SNMP input index for all flows, 0-65535. Def. 0",
188 "Set SNMP output index for all flows, 0-65535. Def. 0",
189 "Set next hop IP address for all flows. Def. 0.0.0.0",
190 ("Print the count of packets read, packets processed,\n"
191 "\tand bad packets to the standard error"),
192 (char*)NULL
193 };
194
195
196 /* LOCAL FUNCTION PROTOTYPES */
197
198 static int appOptionsHandler(clientData cData, int opt_index, char *opt_arg);
199
200
201 /* FUNCTION DEFINITIONS */
202
203 /*
204 * appUsageLong();
205 *
206 * Print complete usage information to USAGE_FH. Pass this
207 * function to skOptionsSetUsageCallback(); skOptionsParse() will
208 * call this funciton and then exit the program when the --help
209 * option is given.
210 */
211 static void
appUsageLong(void)212 appUsageLong(
213 void)
214 {
215 #define USAGE_MSG \
216 ("[SWITCHES] TCPDUMP_FILE\n" \
217 "\tRead packet capture data from TCPDUMP_FILE and attempt to generate\n" \
218 "\ta SiLK Flow record for every packet; use \"stdin\" to read the\n" \
219 "\tpackets from the standard input. Write the SiLK Flows to the\n" \
220 "\tnamed flow-output path or to the standard output if it is not\n" \
221 "\tconnected to a terminal.\n")
222
223 FILE *fh = USAGE_FH;
224 int i;
225
226 fprintf(fh, "%s %s", skAppName(), USAGE_MSG);
227 fprintf(fh, "\nSWITCHES:\n");
228 skOptionsDefaultUsage(fh);
229 for (i = 0; appOptions[i].name; ++i) {
230 fprintf(fh, "--%s %s. %s\n", appOptions[i].name,
231 SK_OPTION_HAS_ARG(appOptions[i]), appHelp[i]);
232 }
233 skOptionsNotesUsage(fh);
234 skCompMethodOptionsUsage(fh);
235
236 skPluginOptionsUsage(fh);
237 }
238
239
240 /*
241 * appTeardown()
242 *
243 * Teardown all modules, close all files, and tidy up all
244 * application state.
245 *
246 * This function is idempotent.
247 */
248 static void
appTeardown(void)249 appTeardown(
250 void)
251 {
252 static int teardownFlag = 0;
253 int rv;
254
255 if (teardownFlag) {
256 return;
257 }
258 teardownFlag = 1;
259
260 skPluginRunCleanup(SKPLUGIN_APP_TRANSFORM);
261 skPluginTeardown();
262
263 /*
264 * Close all files
265 */
266
267 /* flow output */
268 if (flow_output) {
269 rv = skStreamClose(flow_output);
270 if (rv && rv != SKSTREAM_ERR_NOT_OPEN) {
271 skStreamPrintLastErr(flow_output, rv, &skAppPrintErr);
272 }
273 skStreamDestroy(&flow_output);
274 }
275
276 /* packet output */
277 if (packet_pass) {
278 if (-1 == pcap_dump_flush(packet_pass)) {
279 skAppPrintErr("Error finalizing %s file '%s'",
280 appOptions[OPT_PACKET_PASS_OUTPUT].name,
281 packet_pass_path);
282 }
283 pcap_dump_close(packet_pass);
284 packet_pass = NULL;
285 }
286 if (packet_reject) {
287 if (-1 == pcap_dump_flush(packet_reject)) {
288 skAppPrintErr("Error finalizing %s file '%s'",
289 appOptions[OPT_PACKET_REJECT_OUTPUT].name,
290 packet_reject_path);
291 }
292 pcap_dump_close(packet_reject);
293 packet_reject = NULL;
294 }
295
296 /* packet input */
297 if (packet_input) {
298 pcap_close(packet_input);
299 packet_input = NULL;
300 }
301
302 skOptionsNotesTeardown();
303 skAppUnregister();
304 }
305
306
307 /*
308 * appSetup(argc, argv);
309 *
310 * Perform all the setup for this application include setting up
311 * required modules, parsing options, etc. This function should be
312 * passed the same arguments that were passed into main().
313 *
314 * Returns to the caller if all setup succeeds. If anything fails,
315 * this function will cause the application to exit with a FAILURE
316 * exit status.
317 */
318 static void
appSetup(int argc,char ** argv)319 appSetup(
320 int argc,
321 char **argv)
322 {
323 SILK_FEATURES_DEFINE_STRUCT(features);
324 int rv;
325 int arg_index;
326 int stdout_used = 0;
327 sk_file_header_t *hdr;
328 #ifdef SILK_CLOBBER_ENVAR
329 const char *clobber_env = getenv(SILK_CLOBBER_ENVAR);
330 #endif
331
332 /* verify same number of options and help strings */
333 assert((sizeof(appHelp)/sizeof(char *)) ==
334 (sizeof(appOptions)/sizeof(struct option)));
335
336 /* register the application */
337 skAppRegister(argv[0]);
338 skAppVerifyFeatures(&features, NULL);
339 skOptionsSetUsageCallback(&appUsageLong);
340
341 /* initialize globals */
342 memset(&statistics, 0, sizeof(statistics));
343 memset(&time_window, 0, sizeof(time_window));
344 memset(&default_flow_values, 0, sizeof(default_flow_values));
345 rwRecSetPkts(&default_flow_values, 1);
346 rwRecSetSensor(&default_flow_values, SK_INVALID_SENSOR);
347
348 skPluginSetup(1, SKPLUGIN_APP_TRANSFORM);
349 skPluginSetAppExtraArgs(plugin_extra_args);
350
351 /* register the options */
352 if (skOptionsRegister(appOptions, &appOptionsHandler, NULL)
353 || skOptionsNotesRegister(NULL)
354 || skCompMethodOptionsRegister(&comp_method))
355 {
356 skAppPrintErr("Unable to register options");
357 exit(EXIT_FAILURE);
358 }
359
360 /* register the teardown handler */
361 if (atexit(appTeardown) < 0) {
362 skAppPrintErr("Unable to register appTeardown() with atexit()");
363 appTeardown();
364 exit(EXIT_FAILURE);
365 }
366
367 /* parse options */
368 arg_index = skOptionsParse(argc, argv);
369 if (arg_index < 0) {
370 skAppUsage(); /* never returns */
371 }
372
373 /* verify one and only one input file; allow "stdin" to have pcap
374 * read from the standard input */
375 if ((argc - arg_index) != 1) {
376 skAppPrintErr("Must have one and only one input file");
377 skAppUsage(); /* never returns */
378 }
379 packet_input_path = argv[arg_index];
380 if ((0 == strcmp(packet_input_path, "stdin"))
381 || (0 == strcmp(packet_input_path, "-")))
382 {
383 if (FILEIsATty(stdin)) {
384 skAppPrintErr("Will not read binary data from stdin\n"
385 "\twhen it is connected to a terminal");
386 exit(EXIT_FAILURE);
387 }
388 packet_input_path = pcap_stdio;
389 }
390
391 /* verify that multiple outputs are not using stdout */
392 if (flow_output == NULL) {
393 ++stdout_used;
394 if ((rv = skStreamCreate(&flow_output, SK_IO_WRITE,
395 SK_CONTENT_SILK_FLOW))
396 || (rv = skStreamBind(flow_output, "stdout")))
397 {
398 skStreamPrintLastErr(flow_output, rv, &skAppPrintErr);
399 exit(EXIT_FAILURE);
400 }
401 }
402 if (packet_pass_path) {
403 if ((0 == strcmp(packet_pass_path, "stdout"))
404 || (0 == strcmp(packet_pass_path, "-")))
405 {
406 ++stdout_used;
407 packet_pass_path = pcap_stdio;
408 #ifdef SILK_CLOBBER_ENVAR
409 } else if (clobber_env != NULL && *clobber_env && *clobber_env != '0'){
410 /* overwrite existing files */
411 #endif
412 } else if (skFileExists(packet_pass_path)) {
413 skAppPrintErr("The %s '%s' exists. Will not overwrite it.",
414 appOptions[OPT_PACKET_PASS_OUTPUT].name,
415 packet_pass_path);
416 exit(EXIT_FAILURE);
417 }
418 }
419 if (packet_reject_path) {
420 if ((0 == strcmp(packet_reject_path, "stdout"))
421 || (0 == strcmp(packet_reject_path, "-")))
422 {
423 ++stdout_used;
424 packet_reject_path = pcap_stdio;
425 #ifdef SILK_CLOBBER_ENVAR
426 } else if (clobber_env != NULL && *clobber_env && *clobber_env != '0'){
427 /* overwrite existing files */
428 #endif
429 } else if (skFileExists(packet_reject_path)) {
430 skAppPrintErr("The %s '%s' exists. Will not overwrite it.",
431 appOptions[OPT_PACKET_REJECT_OUTPUT].name,
432 packet_reject_path);
433 exit(EXIT_FAILURE);
434 }
435 }
436 if (stdout_used > 1) {
437 skAppPrintErr("Multiple binary outputs are using standard output");
438 exit(EXIT_FAILURE);
439 }
440 if (stdout_used && FILEIsATty(stdout)) {
441 skAppPrintErr("Will not write binary data to stdout\n"
442 "\twhen it is connected to a terminal");
443 exit(EXIT_FAILURE);
444 }
445
446 if (skPluginRunInititialize(SKPLUGIN_APP_TRANSFORM) != SKPLUGIN_OK) {
447 skAppPrintErr("Unable to initialize plugins");
448 exit(EXIT_FAILURE);
449 }
450
451 /* open packet-input file; verify it contains ethernet data */
452 packet_input = pcap_open_offline(packet_input_path, errbuf);
453 if (packet_input == NULL) {
454 skAppPrintErr("Error opening input %s: %s",
455 packet_input_path, errbuf);
456 exit(EXIT_FAILURE);
457 }
458 if (DLT_EN10MB != pcap_datalink(packet_input)) {
459 skAppPrintErr("Input file %s does not contain Ethernet data",
460 packet_input_path);
461 exit(EXIT_FAILURE);
462 }
463
464 /* open the packet output file(s), if any */
465 if (packet_pass_path) {
466 packet_pass = pcap_dump_open(packet_input, packet_pass_path);
467 if (packet_pass == NULL) {
468 skAppPrintErr("Error opening %s file '%s': %s",
469 appOptions[OPT_PACKET_PASS_OUTPUT].name,
470 packet_pass_path,
471 pcap_geterr(packet_input));
472 exit(EXIT_FAILURE);
473 }
474 }
475 if (packet_reject_path) {
476 packet_reject = pcap_dump_open(packet_input, packet_reject_path);
477 if (packet_reject == NULL) {
478 skAppPrintErr("Error opening %s file '%s': %s",
479 appOptions[OPT_PACKET_REJECT_OUTPUT].name,
480 packet_reject_path,
481 pcap_geterr(packet_input));
482 exit(EXIT_FAILURE);
483 }
484 }
485
486 /* open the flow-output file */
487 hdr = skStreamGetSilkHeader(flow_output);
488 rv = skHeaderSetCompressionMethod(hdr, comp_method);
489 if (rv == SKSTREAM_OK) {
490 rv = skOptionsNotesAddToStream(flow_output);
491 }
492 if (rv == SKSTREAM_OK) {
493 rv = skHeaderAddInvocation(hdr, 1, argc, argv);
494 }
495 if (rv == SKSTREAM_OK) {
496 rv = skStreamOpen(flow_output);
497 }
498 if (rv == SKSTREAM_OK) {
499 rv = skStreamWriteSilkHeader(flow_output);
500 }
501 if (rv != SKSTREAM_OK) {
502 skStreamPrintLastErr(flow_output, rv, &skAppPrintErr);
503 skStreamDestroy(&flow_output);
504 exit(EXIT_FAILURE);
505 }
506
507 return; /* OK */
508 }
509
510
511 /*
512 * status = appOptionsHandler(cData, opt_index, opt_arg);
513 *
514 * This function is passed to skOptionsRegister(); it will be called
515 * by skOptionsParse() for each user-specified switch that the
516 * application has registered; it should handle the switch as
517 * required---typically by setting global variables---and return 1
518 * if the switch processing failed or 0 if it succeeded. Returning
519 * a non-zero from from the handler causes skOptionsParse() to return
520 * a negative value.
521 *
522 * The clientData in 'cData' is typically ignored; 'opt_index' is
523 * the index number that was specified as the last value for each
524 * struct option in appOptions[]; 'opt_arg' is the user's argument
525 * to the switch for options that have a REQUIRED_ARG or an
526 * OPTIONAL_ARG.
527 */
528 static int
appOptionsHandler(clientData UNUSED (cData),int opt_index,char * opt_arg)529 appOptionsHandler(
530 clientData UNUSED(cData),
531 int opt_index,
532 char *opt_arg)
533 {
534 sktime_t begin_time;
535 sktime_t end_time;
536 skipaddr_t ip;
537 imaxdiv_t t_div;
538 uint32_t temp;
539 unsigned int precision;
540 int rv;
541
542 switch ((appOptionsEnum)opt_index) {
543 case OPT_PLUGIN:
544 if (skPluginLoadPlugin(opt_arg, 1)) {
545 skAppPrintErr("Fatal error loading plug-in '%s'", opt_arg);
546 return 1;
547 }
548 break;
549
550 case OPT_ACTIVE_TIME:
551 /* parse the time */
552 rv = skStringParseDatetimeRange(&begin_time, &end_time, opt_arg,
553 NULL, &precision);
554 if (rv) {
555 goto PARSE_ERROR;
556 }
557 /* set the begin time */
558 t_div = imaxdiv(begin_time, 1000);
559 time_window.tw_begin.tv_sec = t_div.quot;
560 time_window.tw_begin.tv_usec = t_div.rem * 1000;
561
562 /* adjust the maximum if required */
563 if (end_time != INT64_MAX
564 && (0 == (SK_PARSED_DATETIME_EPOCH & precision))
565 && (SK_PARSED_DATETIME_GET_PRECISION(precision)
566 < SK_PARSED_DATETIME_SECOND))
567 {
568 /* the max date precision is less than (courser than) second
569 * resolution, so "round" the date up */
570 if (skDatetimeCeiling(&end_time, &end_time, precision)) {
571 return 1;
572 }
573 }
574
575 /* set the end time */
576 t_div = imaxdiv(end_time, 1000);
577 time_window.tw_end.tv_sec = t_div.quot;
578 time_window.tw_end.tv_usec = t_div.rem * 1000;
579 break;
580
581 case OPT_FLOW_OUTPUT:
582 if (flow_output) {
583 skAppPrintErr("Invalid %s: Switch used multiple times",
584 appOptions[opt_index].name);
585 return 1;
586 }
587 if ((rv = skStreamCreate(&flow_output, SK_IO_WRITE,
588 SK_CONTENT_SILK_FLOW))
589 || (rv = skStreamBind(flow_output, opt_arg)))
590 {
591 skStreamPrintLastErr(flow_output, rv, &skAppPrintErr);
592 exit(EXIT_FAILURE);
593 }
594 break;
595
596 case OPT_PACKET_PASS_OUTPUT:
597 if (packet_pass_path) {
598 skAppPrintErr("Invalid %s: Switch used multiple times",
599 appOptions[opt_index].name);
600 return 1;
601 }
602 packet_pass_path = opt_arg;
603 break;
604
605 case OPT_PACKET_REJECT_OUTPUT:
606 if (packet_reject_path) {
607 skAppPrintErr("Invalid %s: Switch used multiple times",
608 appOptions[opt_index].name);
609 return 1;
610 }
611 packet_reject_path = opt_arg;
612 break;
613
614 case OPT_REJECT_ALL_FRAGMENTS:
615 reject_frags_all = 1;
616 break;
617
618 case OPT_REJECT_NONZERO_FRAGMENTS:
619 reject_frags_subsequent = 1;
620 break;
621
622 case OPT_REJECT_INCOMPLETE:
623 reject_incomplete = 1;
624 break;
625
626 case OPT_SET_SENSORID:
627 rv = skStringParseUint32(&temp, opt_arg, 0, (SK_INVALID_SENSOR-1));
628 if (rv) {
629 goto PARSE_ERROR;
630 }
631 rwRecSetSensor(&default_flow_values, (sk_sensor_id_t)temp);
632 break;
633
634 case OPT_SET_INPUTINDEX:
635 rv = skStringParseUint32(&temp, opt_arg, 0, UINT16_MAX);
636 if (rv) {
637 goto PARSE_ERROR;
638 }
639 rwRecSetInput(&default_flow_values, (uint16_t)temp);
640 break;
641
642 case OPT_SET_OUTPUTINDEX:
643 rv = skStringParseUint32(&temp, opt_arg, 0, UINT16_MAX);
644 if (rv) {
645 goto PARSE_ERROR;
646 }
647 rwRecSetOutput(&default_flow_values, (uint16_t)temp);
648 break;
649
650 case OPT_SET_NEXTHOPIP:
651 rv = skStringParseIP(&ip, opt_arg);
652 if (rv) {
653 goto PARSE_ERROR;
654 }
655 #if SK_ENABLE_IPV6
656 if (skipaddrIsV6(&ip)) {
657 skAppPrintErr("Invalid %s '%s': IPv6 addresses not supported",
658 appOptions[opt_index].name, opt_arg);
659 return 1;
660 }
661 #endif /* SK_ENABLE_IPV6 */
662 rwRecSetNhIPv4(&default_flow_values, skipaddrGetV4(&ip));
663 break;
664
665 case OPT_PRINT_STATISTICS:
666 print_statistics = 1;
667 break;
668 }
669
670 return 0; /* OK */
671
672 PARSE_ERROR:
673 skAppPrintErr("Invalid %s '%s': %s",
674 appOptions[opt_index].name, opt_arg,
675 skStringParseStrerror(rv));
676 return 1;
677
678 }
679
680
681
682
683 /*
684 * status = packetsToFlows();
685 *
686 * For every packet in the global 'packet_input' file, try to
687 * produce a SiLK flow record, and write that record to the
688 * 'flow_output' stream. In addition, print the packets to
689 * the 'packet_pass' and/or 'packet_fail' dump files if requested.
690 * Update the global 'statistics' struct. Return 0 on success, or
691 * -1 if writing a flow to the 'flow_output' stream fails.
692 */
693 static int
packetsToFlows(void)694 packetsToFlows(
695 void)
696 {
697 #define DUMP_REJECT_PACKET \
698 if ( !packet_reject) { /* no-op */} \
699 else {pcap_dump((u_char*)packet_reject, &pcaph, data);}
700
701 sk_pktsrc_t pktsrc;
702 struct pcap_pkthdr pcaph;
703 const u_char *data;
704 rwRec flow;
705 /* pointer to the ethernet header inside of 'data' */
706 eth_header_t *ethh;
707 /* pointer to the IP header inside of 'data' */
708 ip_header_t *iph;
709 /* pointer to the protocol-specific header in 'data' */
710 u_char *protoh;
711 /* the advertised length of the IP header */
712 uint32_t iph_len;
713 uint32_t len;
714 int rv;
715 void *pktptr;
716 skplugin_err_t err;
717
718 /* set up the sk_pktsrc_t for communicating with the plugins */
719 pktsrc.pcap_src = packet_input;
720 pktsrc.pcap_hdr = &pcaph;
721
722 while (NULL != (data = pcap_next(packet_input, &pcaph))) {
723 ++statistics.s_total;
724
725 /* see if the packet's time is within our time window */
726 if (time_window.tw_end.tv_sec) {
727 if (pcaph.ts.tv_sec < time_window.tw_begin.tv_sec
728 || (pcaph.ts.tv_sec == time_window.tw_begin.tv_sec
729 && pcaph.ts.tv_usec < time_window.tw_begin.tv_usec))
730 {
731 /* packet's time is before window */
732 ++statistics.s_prewindow;
733 continue;
734 }
735 if (pcaph.ts.tv_sec > time_window.tw_end.tv_sec
736 || (pcaph.ts.tv_sec == time_window.tw_end.tv_sec
737 && pcaph.ts.tv_usec > time_window.tw_end.tv_usec))
738 {
739 /* packet's time is after window */
740 ++statistics.s_postwindow;
741 continue;
742 }
743 }
744
745 /* make certain we captured the ethernet header */
746 len = pcaph.caplen;
747 if (len < sizeof(eth_header_t)) {
748 /* short packet */
749 ++statistics.s_short;
750 DUMP_REJECT_PACKET;
751 continue;
752 }
753
754 /* get the ethernet header; goto next packet if not Ethernet. */
755 ethh = (eth_header_t*)data;
756 if (ntohs(ethh->ether_type) != ETHERTYPE_IP) {
757 /* ignoring non IP packet */
758 ++statistics.s_nonipv4;
759 DUMP_REJECT_PACKET;
760 continue;
761 }
762
763 /* get the IP header; verify that we have the entire IP header
764 * that the version is 4. */
765 iph = (ip_header_t*)(data + sizeof(eth_header_t));
766 len -= sizeof(eth_header_t);
767 if (len < sizeof(ip_header_t)) {
768 ++statistics.s_short;
769 DUMP_REJECT_PACKET;
770 continue;
771 }
772 if ((iph->ver_ihl >> 4) != 4) {
773 /* ignoring non IPv4 packet */
774 ++statistics.s_nonipv4;
775 DUMP_REJECT_PACKET;
776 continue;
777 }
778
779 /* the protocol-specific header begins after the advertised
780 * length of the IP header */
781 iph_len = (iph->ver_ihl & 0x0F) << 2;
782 if (len > iph_len) {
783 protoh = (u_char*)(((u_char*)iph) + iph_len);
784 len -= iph_len;
785 } else {
786 protoh = NULL;
787 }
788
789 /* check for fragmentation */
790 if (ntohs(iph->flags_fo) & (IP_MF | IPHEADER_FO_MASK)) {
791 ++statistics.s_fragmented;
792
793 if (reject_frags_all) {
794 DUMP_REJECT_PACKET;
795 continue;
796 }
797 if ((ntohs(iph->flags_fo) & IPHEADER_FO_MASK) == 0) {
798 ++statistics.s_zerofrag;
799 } else if (reject_frags_subsequent) {
800 DUMP_REJECT_PACKET;
801 continue;
802 }
803 }
804
805 /* we have enough data to generate a flow; fill it in with
806 * what we know so far. */
807 memcpy(&flow, &default_flow_values, sizeof(rwRec));
808
809 rwRecSetSIPv4(&flow, ntohl(iph->saddr));
810 rwRecSetDIPv4(&flow, ntohl(iph->daddr));
811 rwRecSetProto(&flow, iph->proto);
812 rwRecSetBytes(&flow, ntohs(iph->tlen));
813 rwRecSetStartTime(&flow, sktimeCreateFromTimeval(&pcaph.ts));
814
815 /* Get the port information from unfragmented datagrams or
816 * from the zero-packet of fragmented datagrams. */
817 if (protoh && ((ntohs(iph->flags_fo) & IPHEADER_FO_MASK) == 0)) {
818
819 /* Set ports and flags based on the IP protocol */
820 switch (iph->proto) {
821 case 1: /* ICMP */
822 /* did we capture enough to get ICMP data? */
823 if (len < 2) {
824 ++statistics.s_incomplete;
825 if (reject_incomplete) {
826 DUMP_REJECT_PACKET;
827 continue;
828 }
829 } else {
830 icmp_header_t *icmphdr = (icmp_header_t*)protoh;
831 rwRecSetDPort(&flow,
832 ((icmphdr->type << 8) | icmphdr->code));
833 }
834 break;
835
836 case 6: /* TCP */
837 /* did we capture enough to get the TCP flags? */
838 if (len < 14) {
839 ++statistics.s_incomplete;
840 if (reject_incomplete) {
841 DUMP_REJECT_PACKET;
842 continue;
843 }
844 /* can we at least get the ports? */
845 if (len >= 4) {
846 tcp_header_t *tcphdr = (tcp_header_t*)protoh;
847 rwRecSetSPort(&flow, ntohs(tcphdr->sport));
848 rwRecSetDPort(&flow, ntohs(tcphdr->dport));
849 }
850 } else {
851 tcp_header_t *tcphdr = (tcp_header_t*)protoh;
852 rwRecSetSPort(&flow, ntohs(tcphdr->sport));
853 rwRecSetDPort(&flow, ntohs(tcphdr->dport));
854 rwRecSetFlags(&flow, tcphdr->flags);
855 }
856 break;
857
858 case 17: /* UDP */
859 /* did we capture enough to get UDP sport and dport? */
860 if (len < 4) {
861 ++statistics.s_incomplete;
862 if (reject_incomplete) {
863 DUMP_REJECT_PACKET;
864 continue;
865 }
866 } else {
867 udp_header_t *udphdr = (udp_header_t*)protoh;
868 rwRecSetSPort(&flow, ntohs(udphdr->sport));
869 rwRecSetDPort(&flow, ntohs(udphdr->dport));
870 }
871 break;
872 }
873 }
874
875 /* If the user provided plug-in(s), call it(them) */
876 pktsrc.pcap_data = data;
877 pktptr = &pktsrc;
878 err = skPluginRunTransformFn(&flow, &pktptr);
879 switch (err) {
880 case SKPLUGIN_FILTER_PASS:
881 /* success, but no opinion; try next plug-in */
882 break;
883
884 case SKPLUGIN_FILTER_PASS_NOW:
885 /* success, immediately write flow */
886 goto WRITE_FLOW;
887
888 case SKPLUGIN_FILTER_FAIL:
889 /* success, but immediately reject the flow */
890 ++statistics.s_plugin_rej;
891 DUMP_REJECT_PACKET;
892 goto NEXT_PACKET;
893
894 case SKPLUGIN_FILTER_IGNORE:
895 /* success, immediately ignore the flow */
896 ++statistics.s_plugin_ign;
897 goto NEXT_PACKET;
898
899 default:
900 /* an error */
901 skAppPrintErr("Quitting on error code %d from plug-in",
902 err);
903 return -1;
904 }
905
906 WRITE_FLOW:
907 /* FINALLY, write the record to the SiLK Flow file and write
908 * the packet to the packet-pass-output file */
909 rv = skStreamWriteRecord(flow_output, &flow);
910 if (rv) {
911 skStreamPrintLastErr(flow_output, rv, &skAppPrintErr);
912 if (SKSTREAM_ERROR_IS_FATAL(rv)) {
913 return -1;
914 }
915 }
916 if (packet_pass) {
917 pcap_dump((u_char*)packet_pass, &pcaph, data);
918 }
919
920 NEXT_PACKET:
921 ; /* empty */
922 }
923
924 return 0;
925 }
926
927
928 /*
929 * printStatistics(fh);
930 *
931 * Print statistics about the number of packets read, ignored,
932 * rejected, and written to the specified file handle.
933 */
934 static void
printStatistics(FILE * fh)935 printStatistics(
936 FILE *fh)
937 {
938 uint64_t count = statistics.s_total;
939
940 fprintf(fh,
941 ("Packet count statistics for %s\n"
942 "\t%20" PRIu64 " read\n"),
943 packet_input_path, statistics.s_total);
944
945 if (time_window.tw_end.tv_sec) {
946 fprintf(fh,
947 ("\t%20" PRIu64 " ignored: before active-time\n"
948 "\t%20" PRIu64 " ignored: after active-time\n"),
949 statistics.s_prewindow, statistics.s_postwindow);
950 count -= (statistics.s_prewindow + statistics.s_postwindow);
951 }
952
953 fprintf(fh,
954 ("\t%20" PRIu64 " rejected: too short to get information\n"
955 "\t%20" PRIu64 " rejected: not IPv4\n"),
956 statistics.s_short, statistics.s_nonipv4);
957 count -= (statistics.s_short + statistics.s_nonipv4);
958
959 if (reject_frags_all) {
960 fprintf(fh, ("\t%20" PRIu64 " rejected: fragmented\n"),
961 statistics.s_fragmented);
962 count -= statistics.s_fragmented;
963 }
964
965 if (reject_incomplete) {
966 fprintf(fh, ("\t%20" PRIu64 " rejected: incomplete\n"),
967 statistics.s_fragmented);
968 count -= statistics.s_fragmented;
969 }
970
971 if (reject_frags_subsequent) {
972 fprintf(fh, ("\t%20" PRIu64 " rejected: non-zero fragment\n"),
973 (statistics.s_fragmented - statistics.s_zerofrag));
974 count -= (statistics.s_fragmented - statistics.s_zerofrag);
975 }
976
977 if (statistics.s_plugin_ign || statistics.s_plugin_rej) {
978 fprintf(fh,
979 ("\t%20" PRIu64 " ignored: by plug-in\n"
980 "\t%20" PRIu64 " rejected: by plug-in\n"),
981 statistics.s_plugin_ign, statistics.s_plugin_rej);
982 count -= (statistics.s_plugin_ign + statistics.s_plugin_rej);
983 }
984
985 fprintf(fh, ("\n\t%20" PRIu64 " total written\n"),
986 count);
987
988 if ( !reject_frags_all) {
989 if ( !reject_frags_subsequent) {
990 fprintf(fh, ("\t%20" PRIu64 " total fragmented packets\n"),
991 statistics.s_fragmented);
992 }
993 fprintf(fh, ("\t%20" PRIu64 " zero-packet of a fragment\n"),
994 statistics.s_zerofrag);
995 }
996
997 if ( !reject_incomplete) {
998 fprintf(fh, ("\t%20" PRIu64 " incomplete (no ports and/or flags)\n"),
999 statistics.s_incomplete);
1000 }
1001 }
1002
1003
main(int argc,char ** argv)1004 int main(int argc, char **argv)
1005 {
1006 appSetup(argc, argv);
1007
1008 if (packetsToFlows()) {
1009 exit(EXIT_FAILURE);
1010 }
1011
1012 if (print_statistics) {
1013 printStatistics(STATS_STREAM);
1014 }
1015
1016 appTeardown();
1017
1018 return 0;
1019 }
1020
1021
1022 /*
1023 ** Local Variables:
1024 ** mode:c
1025 ** indent-tabs-mode:nil
1026 ** c-basic-offset:4
1027 ** End:
1028 */
1029