1 /*
2 * Copyright (c) 2009-2020, Peter Haag
3 * Copyright (c) 2004-2008, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * * Neither the name of the author nor the names of its contributors may be
15 * used to endorse or promote products derived from this software without
16 * specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 *
30 */
31
32 #include <stdio.h>
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <stdarg.h>
36 #include <errno.h>
37 #include <time.h>
38 #include <string.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/param.h>
42 #include <signal.h>
43 #include <fcntl.h>
44 #include <netinet/in.h>
45 #include <arpa/inet.h>
46 #include <syslog.h>
47
48 #include "config.h"
49
50 #ifdef HAVE_STDINT_H
51 #include <stdint.h>
52 #endif
53
54 #include "util.h"
55 #include "nfdump.h"
56 #include "nffile.h"
57 #include "nfx.h"
58 #include "exporter.h"
59 #include "flist.h"
60 #include "nftree.h"
61
62 #include "nftrack_stat.h"
63 #include "nftrack_rrd.h"
64
65 // We have 288 slot ( 1 day ) for stat record
66 #define AVG_STAT 1
67
68 /* Global Variables */
69 FilterEngine_t *Engine;
70 int byte_mode, packet_mode;
71 uint32_t byte_limit, packet_limit; // needed for linking purpose only
72
73 extension_map_list_t *extension_map_list;
74
75 /* Local Variables */
76 static const char *nfdump_version = VERSION;
77
78 /* Function Prototypes */
79 static void usage(char *name);
80
81 static int CheckRunningOnce(char *pidfile);
82
83 static data_row *process(char *filter);
84
85 /* Functions */
86
87 #include "nffile_inline.c"
88
usage(char * name)89 static void usage(char *name) {
90 printf("usage %s [options] [\"filter\"]\n"
91 "-h\t\tthis text you see right here\n"
92 "-l\t\tLast update of Ports DB\n"
93 "-V\t\tPrint version and exit.\n"
94 "-I\t\tInitialize Ports DB files.\n"
95 "-d <db_dir>\tPorts DB directory.\n"
96 "-r <input>\tread from file. default: stdin\n"
97 "-p\t\tOnline output mode.\n"
98 "-s\t\tCreate port statistics for timeslot -t\n"
99 "-t <time>\tTimeslot for statistics\n"
100 "-S\t\tCreate port statistics for last day\n"
101 "-w <file>\twrite output to file\n"
102 "-f <filter>\tfilter syntaxfile\n"
103 , name);
104 } /* usage */
105
CheckRunningOnce(char * pidfile)106 static int CheckRunningOnce(char *pidfile) {
107 int pidf;
108 pid_t pid;
109 char pidstr[32];
110
111 pidf = open(pidfile, O_RDONLY, 0);
112 if ( pidf > 0 ) {
113 // pid file exists
114 char s[32];
115 ssize_t len;
116 len = read(pidf, (void *)s, 31);
117 close(pidf);
118 s[31] = '\0';
119 if ( len < 0 ) {
120 LogError("read() error existing pid file: %s\n", strerror(errno));
121 return 0;
122 } else {
123 unsigned long pid = atol(s);
124 if ( pid == 0 ) {
125 // garbage - use this file
126 unlink(pidfile);
127 } else {
128 if ( kill(pid, 0) == 0 ) {
129 // process exists
130 LogError("An nftrack process with pid %lu is already running!\n", pid);
131 return 0;
132 } else {
133 // no such process - use this file
134 LogError("The nftrack process with pid %lu died unexpectedly!\n", pid);
135 unlink(pidfile);
136 }
137 }
138 }
139 }
140
141 pid = getpid();
142 pidf = open(pidfile, O_RDWR|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
143 if ( pidf == -1 ) {
144 LogError("Error opening nftrack pid file: '%s' %s", pidfile, strerror(errno));
145 return 0;
146 }
147 snprintf(pidstr,31,"%lu\n", (unsigned long)pid);
148 if ( write(pidf, pidstr, strlen(pidstr)) <= 0 ) {
149 LogError("Error write nftrack pid file: '%s' %s", pidfile, strerror(errno));
150 }
151 close(pidf);
152
153 return 1;
154
155 } // End of CheckRunningOnce
156
process(char * filter)157 static data_row *process(char *filter) {
158 master_record_t master_record;
159 common_record_t *flow_record;
160 nffile_t *nffile;
161 int i, done, ret;
162 data_row * port_table;
163 uint64_t total_bytes;
164
165 nffile = GetNextFile(NULL, 0, 0);
166 if ( !nffile ) {
167 LogError("GetNextFile() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
168 return NULL;
169 }
170 if ( nffile == EMPTY_LIST ) {
171 LogError("Empty file list. No files to process\n");
172 return NULL;
173 }
174
175 port_table = (data_row *)calloc(65536, sizeof(data_row));
176 if ( !port_table) {
177 LogError("malloc() allocation error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
178 return NULL;
179 }
180
181 memset((void *)port_table, 0, 65536 * sizeof(data_row));
182
183 // setup Filter Engine to point to master_record, as any record read from file
184 // is expanded into this record
185 Engine->nfrecord = (uint64_t *)&master_record;
186
187 total_bytes = 0;
188 done = 0;
189 while ( !done ) {
190
191 // get next data block from file
192 ret = ReadBlock(nffile);
193
194 switch (ret) {
195 case NF_CORRUPT:
196 case NF_ERROR:
197 if ( ret == NF_CORRUPT )
198 LogError("Skip corrupt data file '%s'\n",GetCurrentFilename());
199 else
200 LogError("Read error in file '%s': %s\n",GetCurrentFilename(), strerror(errno) );
201 // fall through - get next file in chain
202 case NF_EOF: {
203 nffile_t *next = GetNextFile(nffile, 0, 0);
204 if ( next == EMPTY_LIST ) {
205 done = 1;
206 }
207 if ( next == NULL ) {
208 done = 1;
209 LogError("Unexpected end of file list\n");
210 }
211 // else continue with next file
212 continue;
213
214 } break; // not really needed
215 default:
216 // successfully read block
217 total_bytes += ret;
218 }
219
220 if ( nffile->block_header->id != DATA_BLOCK_TYPE_2 ) {
221 LogError("Can't process block type %u\n", nffile->block_header->id);
222 continue;
223 }
224
225 flow_record = nffile->buff_ptr;
226
227 for ( i=0; i < nffile->block_header->NumRecords; i++ ) {
228 int ret;
229
230 switch ( flow_record->type ) {
231 case CommonRecordV0Type:
232 case CommonRecordType: {
233 if ( extension_map_list->slot[flow_record->ext_map] == NULL ) {
234 LogError("Corrupt data file! No such extension map id: %u. Skip record", flow_record->ext_map );
235 } else {
236 ExpandRecord_v2( flow_record, extension_map_list->slot[flow_record->ext_map], NULL, &master_record);
237
238 ret = (*Engine->FilterEngine)(Engine);
239
240 if ( ret == 0 ) { // record failed to pass the filter
241 // increment pointer by number of bytes for netflow record
242 flow_record = (common_record_t *)((pointer_addr_t)flow_record + flow_record->size);
243 // go to next record
244 continue;
245 }
246
247
248 // Add to stat record
249 if ( master_record.prot == 6 ) {
250 port_table[master_record.dstport].proto[tcp].type[flows]++;
251 port_table[master_record.dstport].proto[tcp].type[packets] += master_record.dPkts;
252 port_table[master_record.dstport].proto[tcp].type[bytes] += master_record.dOctets;
253 } else if ( master_record.prot == 17 ) {
254 port_table[master_record.dstport].proto[udp].type[flows]++;
255 port_table[master_record.dstport].proto[udp].type[packets] += master_record.dPkts;
256 port_table[master_record.dstport].proto[udp].type[bytes] += master_record.dOctets;
257 }
258 }
259 } break;
260 case ExtensionMapType: {
261 extension_map_t *map = (extension_map_t *)flow_record;
262
263 if ( Insert_Extension_Map(extension_map_list, map) ) {
264 // flush new map
265 } // else map already known and flushed
266 } break;
267 case ExporterInfoRecordType:
268 case ExporterStatRecordType:
269 case SamplerInfoRecordype:
270 // Silently skip exporter records
271 break;
272 default: {
273 LogError("Skip unknown record type %i\n", flow_record->type);
274 }
275 }
276
277 // Advance pointer by number of bytes for netflow record
278 flow_record = (common_record_t *)((pointer_addr_t)flow_record + flow_record->size);
279 }
280 } // while
281
282 CloseFile(nffile);
283 DisposeFile(nffile);
284
285 PackExtensionMapList(extension_map_list);
286
287 return port_table;
288
289 } // End of process
290
291
main(int argc,char ** argv)292 int main( int argc, char **argv ) {
293 struct stat stat_buff;
294 char *wfile, *rfile, *Rfile, *Mdirs, *ffile, *filter, *timeslot, *DBdir;
295 char datestr[64];
296 char pidfile[MAXPATHLEN];
297 int c, ffd, ret, DBinit, AddDB, GenStat, AvStat, output_mode, topN;
298 unsigned int lastupdate;
299 data_row *port_table;
300 time_t when;
301 struct tm * t1;
302
303 wfile = rfile = Rfile = Mdirs = ffile = filter = DBdir = timeslot = NULL;
304 DBinit = AddDB = GenStat = AvStat = 0;
305 lastupdate = output_mode = 0;
306 topN = 10;
307 while ((c = getopt(argc, argv, "d:hln:pr:st:w:AIM:L:R:SV")) != EOF) {
308 switch (c) {
309 case 'h':
310 usage(argv[0]);
311 exit(0);
312 break;
313 case 'I':
314 DBinit = 1;
315 break;
316 case 'M':
317 Mdirs = strdup(optarg);
318 break;
319 case 'R':
320 Rfile = strdup(optarg);
321 break;
322 case 'd':
323 DBdir = strdup(optarg);
324 ret = stat(DBdir, &stat_buff);
325 if ( !(stat_buff.st_mode & S_IFDIR) ) {
326 fprintf(stderr, "No such directory: %s\n", DBdir);
327 exit(255);
328 }
329 break;
330 case 'l':
331 lastupdate = 1;
332 break;
333 case 'n':
334 topN = atoi(optarg);
335 if ( topN < 0 ) {
336 fprintf(stderr, "TopnN number %i out of range\n", topN);
337 exit(255);
338 }
339 break;
340 case 'p':
341 output_mode = 1;
342 break;
343 case 'r':
344 rfile = strdup(optarg);
345 break;
346 case 'w':
347 wfile = strdup(optarg);
348 break;
349 case 's':
350 GenStat = 1;
351 break;
352 case 't':
353 timeslot = optarg;
354 if ( !ISO2UNIX(timeslot) ) {
355 exit(255);
356 }
357 break;
358 case 'A':
359 AddDB = 1;
360 break;
361 case 'L':
362 if ( !InitLog(0, "nftrack", optarg, 0) )
363 exit(255);
364 break;
365 case 'S':
366 AvStat = 1;
367 break;
368 case 'V':
369 printf("%s: Version: %s\n",argv[0], nfdump_version);
370 exit(0);
371 break;
372 default:
373 usage(argv[0]);
374 exit(0);
375 }
376 }
377
378 if (argc - optind > 1) {
379 usage(argv[0]);
380 exit(255);
381 } else {
382 /* user specified a pcap filter */
383 filter = argv[optind];
384 }
385
386 if ( !filter && ffile ) {
387 if ( stat(ffile, &stat_buff) ) {
388 LogError("stat() filter file: '%s' %s", ffile, strerror(errno));
389 exit(255);
390 }
391 filter = (char *)malloc(stat_buff.st_size);
392 if ( !filter ) {
393 LogError("malloc() allocation error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
394 exit(255);
395 }
396 ffd = open(ffile, O_RDONLY);
397 if ( ffd < 0 ) {
398 LogError("open() filter file: '%s' %s", ffile, strerror(errno));
399 exit(255);
400 }
401 ret = read(ffd, (void *)filter, stat_buff.st_size);
402 if ( ret < 0 ) {
403 LogError("read() filter file: '%s' %s", ffile, strerror(errno));
404 close(ffd);
405 exit(255);
406 }
407 close(ffd);
408 }
409
410 if ( !DBdir ) {
411 LogError("DB directory required\n");
412 exit(255);
413 }
414
415 InitStat(DBdir);
416
417 // check if pid file exists and if so, if a process with registered pid is running
418 snprintf(pidfile, MAXPATHLEN-1, "%s/nftrack.pid", DBdir);
419 pidfile[MAXPATHLEN-1]= '\0';
420 if ( !CheckRunningOnce(pidfile) ) {
421 LogError("Run once check failed.\n");
422 exit(255);
423 }
424
425 if ( !filter )
426 filter = "any";
427
428 Engine = CompileFilter(filter);
429 if ( !Engine ) {
430 unlink(pidfile);
431 exit(254);
432 }
433
434 if ( DBinit ) {
435 when = time(NULL);
436 when -= ((when % 300) + 300);
437 InitStatFile();
438 if ( !CreateRRDBs(DBdir, when) ) {
439 LogError("Init DBs failed\n");
440 unlink(pidfile);
441 exit(255);
442 }
443 LogInfo("Port DBs initialized.\n");
444 unlink(pidfile);
445 exit(0);
446 }
447
448 extension_map_list = InitExtensionMaps(NEEDS_EXTENSION_LIST);
449
450 if ( lastupdate ) {
451 when = RRD_LastUpdate(DBdir);
452 if ( !when ) {
453 unlink(pidfile);
454 exit(255);
455 }
456 t1 = localtime(&when);
457 strftime(datestr, 63, "%b %d %Y %T", t1);
458 LogInfo("Last Update: %i, %s\n", (int)when, datestr);
459 unlink(pidfile);
460 exit(0);
461 }
462
463 port_table = NULL;
464 if ( Mdirs || Rfile || rfile ) {
465 SetupInputFileSequence(Mdirs, rfile, Rfile);
466 port_table = process(filter);
467 // Lister(port_table);
468 if ( !port_table ) {
469 unlink(pidfile);
470 exit(255);
471 }
472 if ( AddDB ) {
473 if ( !timeslot ) {
474 LogError("Timeslot required!\n");
475 unlink(pidfile);
476 exit(255);
477 }
478 UpdateStat(port_table, ISO2UNIX(timeslot));
479 RRD_StoreDataRow(DBdir, timeslot, port_table);
480 }
481 }
482
483 if ( AvStat ) {
484 port_table = GetStat();
485 if ( !port_table ) {
486 LogError("Unable to get port table!\n");
487 unlink(pidfile);
488 exit(255);
489 }
490 // DoStat
491 Generate_TopN(port_table, topN, AVG_STAT, 0, output_mode, wfile);
492
493 }
494
495
496 if ( GenStat ) {
497 when = ISO2UNIX(timeslot);
498 if ( !port_table ) {
499 if ( !timeslot ) {
500 LogError("Timeslot required!\n");
501 unlink(pidfile);
502 exit(255);
503 }
504 port_table = RRD_GetDataRow(DBdir, when);
505 }
506 if ( !port_table ) {
507 LogError("Unable to get port table!\n");
508 unlink(pidfile);
509 exit(255);
510 }
511 // DoStat
512 Generate_TopN(port_table, topN, 0, when, output_mode, wfile);
513 }
514
515 CloseStat();
516 unlink(pidfile);
517
518 return 0;
519 }
520