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