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