1 /*
2  *  Copyright (c) 2014, Peter Haag
3  *  Copyright (c) 2009, Peter Haag
4  *  Copyright (c) 2004-2008, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
5  *  All rights reserved.
6  *
7  *  Redistribution and use in source and binary forms, with or without
8  *  modification, are permitted provided that the following conditions are met:
9  *
10  *   * Redistributions of source code must retain the above copyright notice,
11  *     this list of conditions and the following disclaimer.
12  *   * Redistributions in binary form must reproduce the above copyright notice,
13  *     this list of conditions and the following disclaimer in the documentation
14  *     and/or other materials provided with the distribution.
15  *   * Neither the name of the author nor the names of its contributors may be
16  *     used to endorse or promote products derived from this software without
17  *     specific prior written permission.
18  *
19  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  *  POSSIBILITY OF SUCH DAMAGE.
30  *
31  *  $Author: peter $
32  *
33  *  $Id: nftrack_rrd.c 224 2014-02-16 12:59:29Z peter $
34  *
35  *  $LastChangedRevision: 224 $
36  *
37  *
38  */
39 
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <limits.h>
43 #include <time.h>
44 #include <unistd.h>
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <fcntl.h>
48 #include <string.h>
49 #include <errno.h>
50 
51 #include "config.h"
52 
53 #ifdef HAVE_STDINT_H
54 #include <stdint.h>
55 #endif
56 
57 #include "rrd.h"
58 #include "nftrack_stat.h"
59 #include "nftrack_rrd.h"
60 
61 #define BUFF_CHECK(num,buffsize) if ( (num) >= (buffsize) ) { \
62  fprintf(stderr, "No enough space to create RRD arg\n");	\
63  exit(0);	\
64 }
65 
66 // temporary RRD file
67 #define TMPRRD	"ports.rrd"
68 
69 #define MAXBUFF 15 * 1024;
70 
71 /* global const */
72 static const char *proto[] = { "tcp", "udp" };
73 static const char *type[]  = { "flows", "packets", "bytes" };
74 
75 /* Local prototypes */
76 
77 static void CreateRRDB (char *filename, time_t when);
78 
79 /* Functions */
80 
CreateRRDB(char * filename,time_t when)81 static void CreateRRDB (char *filename, time_t when) {
82 char *buff, *s, *rrd_arg[1100];
83 long i, num, buffsize, argc;
84 
85 	optind = 0; opterr = 0;
86 	argc   = 0;
87 	/*
88 		Create bufferspace for create args:
89 		1024 DS records: each ~ 23 bytes in average +
90 		3 RRA records + filename + start time => 512 bytes should be more than enough
91 	 */
92 	buffsize = 23 * 1024 + 512;
93 	buff = (char *)malloc(buffsize);
94 	if ( !buff ) {
95 		perror("Memory error!");
96 		exit(0);
97 	}
98 
99 	s = buff;
100 
101 	unlink(filename);
102 
103 	rrd_arg[argc++] = "create";
104 
105 	// add DB name
106 	rrd_arg[argc++] = filename;
107 
108 	// Add start time
109 	num = snprintf(s, buffsize, "--start=%lld", (long long)when);
110 	num++;	// include '\0'
111 	BUFF_CHECK(num,buffsize);
112 	rrd_arg[argc++] = s;
113 
114 	buffsize -= num;
115 	s += num;
116 
117 	/* Add the DS strings */
118 	for ( i=0; i<1024; i++) {
119 		num = snprintf(s, buffsize, "DS:p%ld:GAUGE:600:0:U", i);
120 		num++;	// include '\0'
121 		// printf("I: %ld ", i);
122 		BUFF_CHECK(num,buffsize);
123 		rrd_arg[argc++] = s;
124 
125 		buffsize -= num;
126 		s += num;
127 	}
128 
129 	/*
130 		RRD DB layout:
131 	  	  1 x 5min =  5 min samples	 7 * 288 ( per day ) = 2016 => 7 days
132 	 	 24 x 5min =  2 hour samples   60 *  12 ( per day ) = 720  => 60 days
133 		288 x 5min =  1 day samples   180 *   1 ( per day ) = 180  => 180 days
134 	*/
135 
136 	num = snprintf(s, buffsize, "RRA:AVERAGE:0.5:1:2016");
137 	num++;	// include '\0'
138 	BUFF_CHECK(num,buffsize);
139 	rrd_arg[argc++] = s;
140 
141 	buffsize -= num;
142 	s += num;
143 
144 	num = snprintf(s, buffsize, "RRA:AVERAGE:0.5:24:720");
145 	num++;	// include '\0'
146 	BUFF_CHECK(num,buffsize);
147 	rrd_arg[argc++] = s;
148 
149 	buffsize -= num;
150 	s += num;
151 
152 	num = snprintf(s, buffsize, "RRA:AVERAGE:0.5:288:180");
153 	num++;	// include '\0'
154 	BUFF_CHECK(num,buffsize);
155 	rrd_arg[argc] = s;
156 
157 /*
158 	for ( i=0; i<=argc; i++ ) {
159 		printf("I:%ld %s\n", i, rrd_arg[i]);
160 	}
161 */
162 
163 	rrd_clear_error();
164 	if ( ( i=rrd_create(argc, rrd_arg))) {
165 		fprintf(stderr, "Create DB Error: %ld %s\n", i, rrd_get_error());
166 	}
167 
168 } // End of CreateRRDB
169 
CreateRRDBs(char * path,time_t when)170 int CreateRRDBs (char *path, time_t when) {
171 const char progress[]	= { '|', '/', '-', '|', '\\', '-' };
172 char rrd_filename[1024];
173 int fd, i, p, t, len, total;
174 struct stat statbuf;
175 void	*buff;
176 
177 	// Check if path exists
178 	if ( (stat(path, &statbuf) < 0 ) || !(statbuf.st_mode & S_IFDIR) ) {
179 		fprintf(stderr, "No such directory: '%s'\n", path);
180 		return 0;
181 	}
182 
183 	// make stdout unbuffered for progress pointer
184 	setvbuf(stdout, (char *)NULL, _IONBF, 0);
185 
186 	printf("Create DBs ... ");
187 
188 	/*
189 	 * we create an RRD DB file and will copy this file
190 	 * that many time as required - so every RRD file looks the
191 	 * same. They only distinguish by their name
192 	 */
193 	len = snprintf(rrd_filename, 1024, "%s/%s", path, TMPRRD);
194 	if ( len >= 1024 ) {
195 		fprintf(stderr, "Failed to concat RRD filename: string overflow");
196 		return 0;
197 	}
198 
199 	CreateRRDB(rrd_filename, when);
200 	if ( (i = stat(rrd_filename, &statbuf) < 0 )) {
201 		fprintf(stderr, "Can't create RRD file '%s': %s\n", rrd_filename, strerror(errno));
202 		return 0;
203 	}
204 	buff = malloc(statbuf.st_size);
205 	if ( !buff ) {
206 		perror("Buffer allocation failed");
207 		unlink(rrd_filename);
208 		return 0;
209 	}
210 	fd = open(rrd_filename, O_RDONLY, 0);
211 	if ( fd < 0 ) {
212 		perror("Failed to open RRD file");
213 		unlink(rrd_filename);
214 		return 0;
215 	}
216 	if ( read(fd, buff, statbuf.st_size) != statbuf.st_size ) {
217 		perror("Failed to read data from RRD file");
218 		close(fd);
219 		unlink(rrd_filename);
220 		return 0;
221 	}
222 	close(fd);
223 	unlink(rrd_filename);
224 	printf("\n");
225 
226 	// we are now ready to multiplicate the DB files
227 	total = 384;	// 2 * 3 * 64 files total
228 	for (p=tcp; p<=udp; p++) {	// for TCP and UDP
229 		for (t=flows; t<=bytes; t++) {	// for flows, packets and bytes
230 			for (i=0; i<64; i++) {	// Create 64 times an RRD DB - each for 1024 ports
231 				printf("Creating %s:%s %c Left: %d files	   \r", proto[p], type[t], progress[i % 6], total );
232 				len = snprintf(rrd_filename, 1024, "%s/%s-%s-%d.rrd", path, proto[p], type[t], i);
233 				if ( len >= 1024 ) {
234 					fprintf(stderr, "Failed to concat RRD filename: string overflow");
235 					free(buff);
236 					return 0;
237 				}
238 				fd = open(rrd_filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
239 				if ( fd < 0 ) {
240 					fprintf(stderr, "Failed to create RRD file '%s': %s\n", rrd_filename, strerror(errno));
241 					free(buff);
242 					return 0;
243 				}
244 				if ( write(fd, buff, statbuf.st_size) != statbuf.st_size ) {
245 					fprintf(stderr, "Failed to write RRD file '%s': %s\n", rrd_filename, strerror(errno));
246 					free(buff);
247 					return 0;
248 				}
249 				close(fd);
250 				total--;
251 			}
252 		}
253 	}
254 
255 	printf("\n");
256 	return 1;
257 
258 } // End of CreateRRDBs
259 
RRD_StoreDataRow(char * path,char * iso_time,data_row * row)260 int RRD_StoreDataRow(char *path, char *iso_time, data_row *row) {
261 char 	rrd_filename[1024], *buff, *s;
262 char	*rrd_arg[10];
263 time_t	when, frag;
264 int 	i, j, len, p, t, buffsize, argc;
265 uint32_t	pnum;
266 struct stat statbuf;
267 
268 	buffsize = MAXBUFF;
269 	buff = (char *)malloc(buffsize);
270 	if ( !buff ) {
271 		perror("Memory error!");
272 		return 0;
273 	}
274 
275 	when = ISO2UNIX(iso_time);
276 	if ( !when )
277 		return 0;
278 
279 	// make sure, we are at a 5min boundary
280 	frag = when % 300;
281 	if ( frag ) {
282 		fprintf(stderr, "Round to next timeslot: offset %lld\n", (long long)frag);
283 		when -= frag;
284 	}
285 
286 	for ( p=tcp; p<=udp; p++ ) {
287 		// for every protocol TCP - UDP
288 		for ( t=flows; t<=bytes; t++ ) {
289 			// for every type flows - packets - bytes
290 			for (j=0; j<64; j++) {
291 				// for all 64 RRD files in proto - type
292 				len = snprintf(rrd_filename, 1024, "%s/%s-%s-%d.rrd", path, proto[p], type[t], j);
293 				if ( len >= 1024 ) {
294 					fprintf(stderr, "Failed to concat RRD filename: string overflow");
295 					return 0;
296 				}
297 
298 				// Check if RRD file exists
299 				if ( (stat(rrd_filename, &statbuf) < 0 ) || !(statbuf.st_mode & S_IFREG) ) {
300 					fprintf(stderr, "No such RRD file: '%s'\n", rrd_filename);
301 					return 0;
302 				}
303 
304 				buffsize = MAXBUFF;
305 				s = buff;
306 
307 				/* add time to RRD arg string */
308 				len = snprintf(s, buffsize, "%lld:", (long long)when);
309 				buffsize -= len;
310 				s += len;
311 
312 				/* add port data to RRD arg string */
313 				for ( i=0; i<1024; i++) {
314 					pnum = ( j << 10 ) + i;
315 /*
316 if ( row[pnum].proto[p].type[t] ) {
317 	fprintf(stderr, "%d %d %d\n", pnum, p, t);
318 }
319 */
320 					len = snprintf(s, buffsize, "%llu:", (long long unsigned)row[pnum].proto[p].type[t]);
321 					if ( len >= buffsize ) {
322 						fprintf(stderr, "No enough space to create RRD arg\n");
323 						return 0;
324 					}
325 					buffsize -= len;
326 					s += len;
327 				}
328 				s--;
329 				*s = '\0';
330 
331 				// Create arg vector
332 				argc = 0;
333 				rrd_arg[argc++] = "update";
334 				rrd_arg[argc++] = rrd_filename;
335 				rrd_arg[argc++] = buff;
336 				rrd_arg[argc]   = NULL;
337 
338 				optind = 0; opterr = 0;
339 				rrd_clear_error();
340 				if ( ( i=rrd_update(argc, rrd_arg))) {
341 					fprintf(stderr, "RRD: %s Insert Error: %d %s\n", rrd_filename, i, rrd_get_error());
342 				}
343 			} // for all 64 rrd files
344 		} // for every type flows - packets - bytes
345 	} // for every protocol TCP - UDP
346 
347 	return 1;
348 } // End of RRD_StoreDataRow
349 
RRD_GetDataRow(char * path,time_t when)350 data_row *RRD_GetDataRow(char *path, time_t when) {
351 time_t	last, frag;
352 struct tm * t1, *t2;
353 struct stat statbuf;
354 char 	datestr1[64] , datestr2[64], rrd_filename[1024];
355 char	*rrd_arg[10];
356 char 	**ds_namv;
357 int 	ret, i, j, p, t, len, argc;
358 unsigned long step, ds_cnt, pnum;
359 data_row	*row;
360 rrd_value_t   *data;
361 uint64_t	dummy;
362 
363 	data = NULL;
364 	frag = when % 300;
365 	if ( frag ) {
366 		fprintf(stderr, "Round to next timeslot: offset %lld\n", (long long)frag);
367 		when -= frag;
368 	}
369 
370 	last = RRD_LastUpdate(path);
371 	if ( when > last ) {
372 		t1 = localtime(&when);
373 		strftime(datestr1, 63, "%b %d %Y %T", t1);
374 
375 		t2 = localtime(&last);
376 		strftime(datestr2, 63, "%b %d %Y %T", t2);
377 
378 		fprintf(stderr, "Error get data: Requested time slot '%s' later then last available time slot '%s'\n",
379 			datestr1, datestr2);
380 
381 		return NULL;
382 	}
383 
384 	row = (data_row *)calloc(65536, sizeof(data_row));
385 	if ( !row ) {
386 		perror("Memory allocation error");
387 		return NULL;
388 	}
389 
390 	len = snprintf(datestr1, 64, "--start=%lld", (long long)when);
391 	if ( len >= 64 ) {
392 		fprintf(stderr, "String overflow --start\n");
393 		free(row);
394 		return NULL;
395 	}
396 	len = snprintf(datestr2, 64, "--end=%lld", (long long)when);
397 	if ( len >= 64 ) {
398 		fprintf(stderr, "String overflow --end\n");
399 		free(row);
400 		return NULL;
401 	}
402 
403 	for ( p=tcp; p<=udp; p++ ) {
404 		// for every protocol TCP - UDP
405 		for ( t=flows; t<=bytes; t++ ) {
406 			// for every type flows - packets - bytes
407 			for (j=0; j<64; j++) {
408 				// for all 64 RRD files in proto - type
409 				len = snprintf(rrd_filename, 1024, "%s/%s-%s-%d.rrd", path, proto[p], type[t], j);
410 				if ( len >= 1024 ) {
411 					fprintf(stderr, "Failed to concat RRD filename: string overflow");
412 					free(row);
413 					return NULL;
414 				}
415 
416 				// Check if RRD file exists
417 				if ( (stat(rrd_filename, &statbuf) < 0 ) || !(statbuf.st_mode & S_IFREG) ) {
418 					fprintf(stderr, "No such RRD file: '%s'\n", rrd_filename);
419 					free(row);
420 					return NULL;
421 				}
422 
423 
424 				// Create arg vector
425 				argc = 0;
426 				rrd_arg[argc++] = "fetch";
427 				rrd_arg[argc++] = rrd_filename;
428 				rrd_arg[argc++] = "AVERAGE";
429 				rrd_arg[argc++] = datestr1;
430 				rrd_arg[argc++] = datestr2;
431 				rrd_arg[argc]   = NULL;
432 
433 				optind = 0; opterr = 0;
434 				rrd_clear_error();
435 				if ( ( ret=rrd_fetch(argc, rrd_arg, &when, &when, &step, &ds_cnt, &ds_namv, &data))) {
436 					fprintf(stderr, "RRD: %s Fetch Error: %d %s\n", rrd_filename, ret, rrd_get_error());
437 				}
438 				if ( ds_cnt != 1024 ) {
439 					fprintf(stderr, "RRD: %s Fetch Error: Short read: Expected 1024 records got %lu\n",
440 						rrd_filename, ds_cnt);
441 					free(row);
442 					return NULL;
443 				}
444 
445 				for ( i=0; i<1024; i++) {
446 					pnum = ( j << 10 ) + i;
447 					dummy = data[0];
448 					row[pnum].proto[p].type[t] = dummy;
449 				}
450 
451 				free(ds_namv);
452 				free(data);
453 
454 			} // for all 64 rrd files
455 		} // for every type flows - packets - bytes
456 	} // for every protocol TCP - UDP
457 
458 	return row;
459 
460 } // End of RRD_GetDataRow
461 
RRD_LastUpdate(char * path)462 time_t	RRD_LastUpdate(char *path) {
463 struct stat statbuf;
464 char 	rrd_filename[1024];
465 char	*rrd_arg[10];
466 time_t	when;
467 int 	len, argc;
468 
469 	// Get timestamp from the first file
470 	len = snprintf(rrd_filename, 1024, "%s/%s-%s-%d.rrd", path, "tcp", "flows", 0);
471 	if ( len >= 1024 ) {
472 		fprintf(stderr, "Failed to concat RRD filename: string overflow");
473 		return 0;
474 	}
475 
476 	// Check if RRD file exists
477 	if ( (stat(rrd_filename, &statbuf) < 0 ) || !(statbuf.st_mode & S_IFREG) ) {
478 		fprintf(stderr, "RRD files not found in '%s'\n", path);
479 		return 0;
480 	}
481 
482 	argc = 0;
483 	rrd_arg[argc++] = "last";
484 	rrd_arg[argc++] = rrd_filename;
485 	rrd_arg[argc]   = NULL;
486 
487 	when = rrd_last(argc, rrd_arg);
488 
489 	return when;
490 
491 } // End of RRD_LastUpdate
492 
493 /*
494 int main () {
495 	char *buff, *s, *rrd_arg[10];
496 	long i, num, buffsize, argc;
497 	time_t	now;
498 
499 	CreateRRDBs("/data/rrd-db");
500 	exit(0);
501 
502 	buffsize = 15 * 1024;
503 	buff = (char *)malloc(buffsize);
504 	if ( !buff ) {
505 		perror("Memory error!");
506 		exit(0);
507 	}
508 
509 	s = buff;
510 	now = time(NULL);
511 	now -= now % 300;
512 
513 	num = snprintf(s, buffsize, "%ld:", now);
514 	// num = snprintf(s, buffsize, "N:");
515 	buffsize -= num;
516 	s += num;
517 
518 	for ( i=0; i<1024; i++) {
519 		num = snprintf(s, buffsize, "%ld:", i);
520 		if ( num >= buffsize ) {
521 			fprintf(stderr, "No enough space to create RRD arg\n");
522 			exit(0);
523 		}
524 		buffsize -= num;
525 		s += num;
526 	}
527 	s--;
528 	*s = '\0';
529 	printf("String: %s\n", buff);
530 
531 	argc = 0;
532 	rrd_arg[argc++] = "update";
533 	rrd_arg[argc++] = "ports.rrd";
534 	rrd_arg[argc++] = buff;
535 	rrd_arg[argc]   = NULL;
536 
537 	rrd_clear_error();
538 	if ( ( i=rrd_update(argc, rrd_arg))) {
539 		fprintf(stderr, "Insert Error: %ld %s\n", i, rrd_get_error());
540 	}
541 
542 	return 0;
543 }
544 
545 */
546