1 /*
2  *  Copyright (c) 2009-2020, Peter Haag
3  *  Copyright (c) 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 #ifdef HAVE_STDINT_H
33 #include <stdint.h>
34 #endif
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <stdarg.h>
39 #include <string.h>
40 #include <errno.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <sys/socket.h>
44 #include <sys/param.h>
45 #include <netinet/in.h>
46 #include <arpa/inet.h>
47 #include <unistd.h>
48 #include <stdarg.h>
49 
50 #include <time.h>
51 
52 #ifdef HAVE_STDINT_H
53 #include <stdint.h>
54 #endif
55 
56 #include "util.h"
57 #include "nfdump.h"
58 #include "nffile.h"
59 #include "bookkeeper.h"
60 #include "collector.h"
61 #include "nfx.h"
62 
63 #include "nffile_inline.c"
64 
65 /* local variables */
66 static uint32_t	exporter_sysid = 0;
67 static char *DynamicSourcesDir = NULL;
68 
69 /* local prototypes */
70 static uint32_t AssignExporterID(void);
71 
72 /* local functions */
AssignExporterID(void)73 static uint32_t AssignExporterID(void) {
74 
75 	if ( exporter_sysid >= 0xFFFF ) {
76 		LogError("Too many exporters (id > 65535). Flow records collected but without reference to exporter");
77 		return 0;
78 	}
79 
80 	return ++exporter_sysid;
81 
82 } // End of AssignExporterID
83 
84 /* global functions */
85 
SetDynamicSourcesDir(FlowSource_t ** FlowSource,char * dir)86 int SetDynamicSourcesDir(FlowSource_t **FlowSource, char *dir) {
87 
88 	if ( *FlowSource )
89 		return 0;
90 
91 	DynamicSourcesDir = dir;
92 	return 1;
93 
94 } // End of SetDynamicSourcesDir
95 
AddFlowSource(FlowSource_t ** FlowSource,char * ident)96 int AddFlowSource(FlowSource_t **FlowSource, char *ident) {
97 FlowSource_t	**source;
98 struct 	stat 	fstat;
99 char *p, *q, s[MAXPATHLEN];
100 int	 has_any_source = 0;
101 int ok;
102 
103 	if ( DynamicSourcesDir )
104 		return 0;
105 
106 	source = FlowSource;
107 	while ( *source ) {
108 		has_any_source |= (*source)->any_source;
109 		source = &((*source)->next);
110 	}
111 	if ( has_any_source ) {
112 		fprintf(stderr, "Ambiguous idents not allowed\n");
113 		return 0;
114 	}
115 
116 	*source = (FlowSource_t *)calloc(1, sizeof(FlowSource_t));
117 	if ( !*source ) {
118 		LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
119 		return 0;
120 	}
121 	(*source)->next 	  	  	  = NULL;
122 	(*source)->bookkeeper 	  	  = NULL;
123 	(*source)->any_source 	  	  = 0;
124 	(*source)->exporter_data  	  = NULL;
125 	(*FlowSource)->exporter_count = 0;
126 
127 	// separate IP address from ident
128 	if ( ( p = strchr(ident, ',')) == NULL  ) {
129 		fprintf(stderr, "Syntax error for netflow source definition. Expect -n ident,IP,path\n");
130 		return 0;
131 	}
132 	*p++ = '\0';
133 
134 	// separate path from IP
135 	if ( ( q = strchr(p, ',')) == NULL  ) {
136 		fprintf(stderr, "Syntax error for netflow source definition. Expect -n ident,IP,path\n");
137 		return 0;
138 	}
139 	*q++ = '\0';
140 
141 	if ( strchr(p, ':') != NULL ) {
142 		uint64_t _ip[2];
143 		ok = inet_pton(PF_INET6, p, _ip);
144 		(*source)->sa_family = PF_INET6;
145 		(*source)->ip.V6[0] = ntohll(_ip[0]);
146 		(*source)->ip.V6[1] = ntohll(_ip[1]);
147 	} else {
148 		uint32_t _ip;
149 		ok = inet_pton(PF_INET, p, &_ip);
150 		(*source)->sa_family = PF_INET;
151 		(*source)->ip.V6[0] = 0;
152 		(*source)->ip.V6[1] = 0;
153 		(*source)->ip.V4 = ntohl(_ip);
154 	}
155 	switch (ok) {
156 		case 0:
157 			fprintf(stderr, "Unparsable IP address: %s\n", p);
158 			return 0;
159 		case 1:
160 			// success
161 			break;
162 		case -1:
163 			fprintf(stderr, "Error while parsing IP address: %s\n", strerror(errno));
164 			return 0;
165 			break;
166 	}
167 
168 	// fill in ident
169 	if ( strlen(ident) >= IDENTLEN ) {
170 		fprintf(stderr, "Source identifier too long: %s\n", ident);
171 		return 0;
172 	}
173 	if ( strchr(ident, ' ') ) {
174 		fprintf(stderr,"Illegal characters in ident %s\n", ident);
175 		exit(255);
176 	}
177 	strncpy((*source)->Ident, ident, IDENTLEN-1 );
178 	(*source)->Ident[IDENTLEN-1] = '\0';
179 
180 	if ( strlen(q) >= MAXPATHLEN ) {
181 		fprintf(stderr,"Path too long: %s\n", q);
182 		exit(255);
183 	}
184 
185 	char *path = realpath(q, NULL);
186 	if ( !path ) {
187 		fprintf(stderr, "realpath() error %s: %s\n", q, strerror(errno));
188 		return 0;
189 	}
190 
191 	// check for existing path
192 	if ( stat(path, &fstat) ) {
193 		fprintf(stderr, "stat() error %s: %s\n", path, strerror(errno));
194 		return 0;
195 	}
196 	if ( !(fstat.st_mode & S_IFDIR) ) {
197 		fprintf(stderr, "Not a directory: %s\n", path);
198 		return 0;
199 	}
200 
201 	// remember path
202 	(*source)->datadir = path;
203 
204 	// cache current collector file
205 	if ( snprintf(s, MAXPATHLEN-1, "%s/%s.%lu", (*source)->datadir , NF_DUMPFILE, (unsigned long)getpid() ) >= (MAXPATHLEN-1)) {
206 		fprintf(stderr, "Path too long: %s\n", q);
207 		return 0;
208 	}
209 	(*source)->current = strdup(s);
210 	if ( !(*source)->current ) {
211 		fprintf(stderr, "strdup() error: %s\n", strerror(errno));
212 		return 0;
213 	}
214 
215 	return 1;
216 
217 } // End of AddFlowSource
218 
AddDefaultFlowSource(FlowSource_t ** FlowSource,char * ident,char * path)219 int AddDefaultFlowSource(FlowSource_t **FlowSource, char *ident, char *path) {
220 struct 	stat 	fstat;
221 char s[MAXPATHLEN];
222 
223 	if ( DynamicSourcesDir )
224 		return 0;
225 
226 	*FlowSource = (FlowSource_t *)calloc(1,sizeof(FlowSource_t));
227 	if ( !*FlowSource ) {
228 		fprintf(stderr, "calloc() allocation error: %s\n", strerror(errno));
229 		return 0;
230 	}
231 	(*FlowSource)->next 	  	  = NULL;
232 	(*FlowSource)->bookkeeper 	  = NULL;
233 	(*FlowSource)->any_source 	  = 1;
234 	(*FlowSource)->exporter_data  = NULL;
235 	(*FlowSource)->exporter_count = 0;
236 
237 	// fill in ident
238 	if ( strlen(ident) >= IDENTLEN ) {
239 		fprintf(stderr, "Source identifier too long: %s\n", ident);
240 		return 0;
241 	}
242 	if ( strchr(ident, ' ') ) {
243 		fprintf(stderr,"Illegal characters in ident %s\n", ident);
244 		return 0;
245 	}
246 	strncpy((*FlowSource)->Ident, ident, IDENTLEN-1 );
247 	(*FlowSource)->Ident[IDENTLEN-1] = '\0';
248 
249 	if ( strlen(path) >= MAXPATHLEN ) {
250 		fprintf(stderr,"Path too long: %s\n",path);
251 		return 0;
252 	}
253 
254 	// check for existing path
255 	if ( stat(path, &fstat) ) {
256 		fprintf(stderr, "stat() error %s: %s\n", path, strerror(errno));
257 		return 0;
258 	}
259 	if ( !(fstat.st_mode & S_IFDIR) ) {
260 		fprintf(stderr, "No such directory: %s\n", path);
261 		return 0;
262 	}
263 
264 	// remember path
265 	(*FlowSource)->datadir = strdup(path);
266 	if ( !(*FlowSource)->datadir ) {
267 		fprintf(stderr, "strdup() error: %s\n", strerror(errno));
268 		return 0;
269 	}
270 
271 	// cache current collector file
272 	if ( snprintf(s, MAXPATHLEN-1, "%s/%s.%lu", (*FlowSource)->datadir , NF_DUMPFILE, (unsigned long)getpid() ) >= (MAXPATHLEN-1)) {
273 		fprintf(stderr, "Path too long: %s\n", path);
274 		return 0;
275 	}
276 	(*FlowSource)->current = strdup(s);
277 	if ( !(*FlowSource)->current ) {
278 		fprintf(stderr, "strdup() error: %s\n", strerror(errno));
279 		return 0;
280 	}
281 
282 	return 1;
283 
284 } // End of AddDefaultFlowSource
285 
AddDynamicSource(FlowSource_t ** FlowSource,struct sockaddr_storage * ss)286 FlowSource_t *AddDynamicSource(FlowSource_t **FlowSource, struct sockaddr_storage *ss) {
287 FlowSource_t	**source;
288 void			*ptr;
289 char			*s, ident[100], path[MAXPATHLEN];
290 int				err;
291 
292     union {
293         struct sockaddr_storage	*ss;
294         struct sockaddr			*sa;
295         struct sockaddr_in		*sa_in;
296         struct sockaddr_in6		*sa_in6;
297     } u;
298     u.ss = ss;
299 
300 	if ( !DynamicSourcesDir )
301 		return NULL;
302 
303 	source = FlowSource;
304 	while ( *source ) {
305 		source = &((*source)->next);
306 	}
307 
308 	*source = (FlowSource_t *)calloc(1, sizeof(FlowSource_t));
309 	if ( !*source ) {
310 		LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
311 		return NULL;
312 	}
313 	(*source)->next 	  	  	  = NULL;
314 	(*source)->bookkeeper 	  	  = NULL;
315 	(*source)->any_source 	  	  = 0;
316 	(*source)->exporter_data  	  = NULL;
317 	(*FlowSource)->exporter_count = 0;
318 
319 	switch (ss->ss_family) {
320 		case PF_INET: {
321 #ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
322 			if (ss->ss_len != sizeof(struct sockaddr_in) ) {
323 				// malformed struct
324 				LogError("Malformed IPv4 socket struct in '%s', line '%d'", __FILE__, __LINE__ );
325 				free(*source);
326 				*source = NULL;
327 				return NULL;
328 			}
329 #endif
330 			(*source)->sa_family = PF_INET;
331 			(*source)->ip.V6[0] = 0;
332 			(*source)->ip.V6[1] = 0;
333 			(*source)->ip.V4 = ntohl(u.sa_in->sin_addr.s_addr);
334 			ptr 	   = &u.sa_in->sin_addr;
335 			} break;
336 		case PF_INET6: {
337 			uint64_t *ip_ptr = (uint64_t *)u.sa_in6->sin6_addr.s6_addr;
338 
339 #ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
340 			if (ss->ss_len != sizeof(struct sockaddr_in6) ) {
341 				// malformed struct
342 				LogError("Malformed IPv6 socket struct in '%s', line '%d'", __FILE__, __LINE__ );
343 				free(*source);
344 				*source = NULL;
345 				return NULL;
346 			}
347 #endif
348 			// ptr = &((struct sockaddr_in6 *)sa)->sin6_addr;
349 			(*source)->sa_family = PF_INET6;
350 			(*source)->ip.V6[0] = ntohll(ip_ptr[0]);
351 			(*source)->ip.V6[1] = ntohll(ip_ptr[1]);
352 			ptr = &u.sa_in6->sin6_addr;
353 			} break;
354 		default:
355 			// keep compiler happy
356 			(*source)->ip.V6[0] = 0;
357 			(*source)->ip.V6[1] = 0;
358 			ptr   = NULL;
359 
360 			LogError("Unknown sa fanily: %d in '%s', line '%d'", ss->ss_family, __FILE__, __LINE__ );
361 			free(*source);
362 			*source = NULL;
363 			return NULL;
364 	}
365 
366 	if ( !ptr ) {
367 		free(*source);
368 		*source = NULL;
369 		return NULL;
370 	}
371 
372 	inet_ntop(ss->ss_family, ptr, ident, sizeof(ident));
373 	ident[99] = '\0';
374 	dbg_printf("Dynamic Flow Source IP: %s\n", ident);
375 
376 	s = ident;
377 	while ( *s != '\0' ) {
378 		if ( *s == '.' || *s == ':' )
379 			*s = '-';
380 		s++;
381 	}
382 	dbg_printf("Dynamic Flow Source ident: %s\n", ident);
383 
384 	strncpy((*source)->Ident, ident, IDENTLEN-1 );
385 	(*source)->Ident[IDENTLEN-1] = '\0';
386 
387 	snprintf(path, MAXPATHLEN-1, "%s/%s", DynamicSourcesDir, ident);
388 	path[MAXPATHLEN-1] = '\0';
389 
390 	err = mkdir(path, 0755);
391 	if ( err != 0 && errno != EEXIST ) {
392 		LogError("mkdir() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
393 		free(*source);
394 		*source = NULL;
395 		return NULL;
396 	}
397 	(*source)->datadir = strdup(path);
398 
399 	if ( snprintf(path, MAXPATHLEN-1, "%s/%s.%lu", (*source)->datadir, NF_DUMPFILE, (unsigned long)getpid() ) >= (MAXPATHLEN-1)) {
400 		fprintf(stderr, "Path too long: %s\n", path);
401 		free(*source);
402 		*source = NULL;
403 		return NULL;
404 	}
405 	(*source)->current = strdup(path);
406 
407 	LogInfo("Dynamically add source ident: %s in directory: %s", ident, path);
408 	return *source;
409 
410 } // End of AddDynamicSource
411 
InitExtensionMapList(FlowSource_t * fs)412 int InitExtensionMapList(FlowSource_t *fs) {
413 
414 	fs->extension_map_list.maps = (extension_map_t **)calloc(BLOCK_SIZE, sizeof(extension_map_t *));
415 	if ( !fs->extension_map_list.maps ) {
416 		LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
417 		return 0;
418 	}
419 	fs->extension_map_list.max_maps  = BLOCK_SIZE;
420 	fs->extension_map_list.next_free = 0;
421 	fs->extension_map_list.num_maps  = 0;
422 
423 	return 1;
424 
425 } // End of InitExtensionMapList
426 
ReInitExtensionMapList(FlowSource_t * fs)427 int ReInitExtensionMapList(FlowSource_t *fs) {
428 
429 	dbg_printf("Flush all extension maps!\n");
430 	free(fs->extension_map_list.maps);
431 	fs->extension_map_list.maps = NULL;
432 
433 	return InitExtensionMapList(fs);
434 
435 } // End of ReInitExtensionMapList
436 
RemoveExtensionMap(FlowSource_t * fs,extension_map_t * map)437 int RemoveExtensionMap(FlowSource_t *fs, extension_map_t *map) {
438 int slot = map->map_id;
439 
440 	dbg_printf("Remove extension map ID: %d\n", slot);
441 	if ( slot >= fs->extension_map_list.max_maps ) {
442 		// XXX hmm .. is simply return correct ///
443 		LogError("*** software error *** Try to remove extension map %d, while only %d slots are available\n", slot, fs->extension_map_list.max_maps);
444 		return 0;
445 	}
446 	fs->extension_map_list.maps[slot] = NULL;
447 
448 	return 1;
449 
450 } // End of RemoveExtensionMap
451 
AddExtensionMap(FlowSource_t * fs,extension_map_t * map)452 int AddExtensionMap(FlowSource_t *fs, extension_map_t *map) {
453 int next_slot = fs->extension_map_list.next_free;
454 
455 	dbg_printf("Add extension map\n");
456 	// is it a new map, we have not yet in the list
457 	if ( map->map_id == INIT_ID ) {
458 		if ( next_slot >= fs->extension_map_list.max_maps ) {
459 			// extend map list
460 			dbg_printf("List full - extend extension list to %d slots\n", fs->extension_map_list.max_maps + BLOCK_SIZE);
461 			extension_map_t **p = realloc((void *)fs->extension_map_list.maps,
462 				(fs->extension_map_list.max_maps + BLOCK_SIZE ) * sizeof(extension_map_t *));
463 			if ( !p ) {
464 				LogError("realloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
465 				return 0;
466 			}
467 			fs->extension_map_list.maps 	= p;
468 			fs->extension_map_list.max_maps += BLOCK_SIZE;
469 		}
470 
471 		dbg_printf("Add map to slot %d\n", next_slot);
472 		fs->extension_map_list.maps[next_slot] = map;
473 		map->map_id = next_slot;
474 		fs->extension_map_list.num_maps++;
475 
476 		if ( (next_slot + 1) == fs->extension_map_list.num_maps ) {
477 			// sequencially filled slots
478 			// next free is next slot
479 			fs->extension_map_list.next_free++;
480 			dbg_printf("Next slot is sequential: %d\n", fs->extension_map_list.next_free);
481 		} else {
482 			// fill gap in list - search for next free
483 			int i;
484 			dbg_printf("Search next slot ... \n");
485 			for ( i = (next_slot + 1); i < fs->extension_map_list.max_maps; i++ ) {
486 				if ( fs->extension_map_list.maps[i] == NULL ) {
487 					// empty slot found
488 					dbg_printf("Empty slot found at %d\n", i);
489 					break;
490 				}
491 			}
492 			// assign next_free - if none found up to max, the list will get extended
493 			// in the next round
494 			dbg_printf("Set next free to %d\n", i);
495 			fs->extension_map_list.next_free = i;
496 		}
497 
498 	}
499 
500 	AppendToBuffer(fs->nffile, (void *)map, map->size);
501 
502 	return 1;
503 
504 } // End of AddExtensionMap
505 
FlushInfoExporter(FlowSource_t * fs,exporter_info_record_t * exporter)506 int FlushInfoExporter(FlowSource_t *fs, exporter_info_record_t *exporter) {
507 
508 	exporter->sysid = AssignExporterID();
509 	fs->exporter_count++;
510 	AppendToBuffer(fs->nffile, (void *)exporter, exporter->header.size);
511 
512 #ifdef DEVEL
513 	{
514 		#define IP_STRING_LEN   40
515 		char ipstr[IP_STRING_LEN];
516 		printf("Flush Exporter: ");
517 		if ( exporter->sa_family == AF_INET ) {
518 			uint32_t _ip = htonl(exporter->ip.V4);
519 			inet_ntop(AF_INET, &_ip, ipstr, sizeof(ipstr));
520 			printf("SysID: %u, IP: %16s, version: %u, ID: %2u\n", exporter->sysid,
521 				ipstr, exporter->version, exporter->id);
522 		} else if ( exporter->sa_family == AF_INET6 ) {
523 			uint64_t _ip[2];
524 			_ip[0] = htonll(exporter->ip.V6[0]);
525 			_ip[1] = htonll(exporter->ip.V6[1]);
526 			inet_ntop(AF_INET6, &_ip, ipstr, sizeof(ipstr));
527 			printf("SysID: %u, IP: %40s, version: %u, ID: %2u\n", exporter->sysid,
528 				ipstr, exporter->version, exporter->id);
529 		} else {
530 			strncpy(ipstr, "<unknown>", IP_STRING_LEN);
531 			printf("**** Exporter IP version unknown ****\n");
532 		}
533 	}
534 #endif
535 
536 	return 1;
537 
538 } // End of FlushInfoExporter
539 
FlushInfoSampler(FlowSource_t * fs,sampler_info_record_t * sampler)540 int FlushInfoSampler(FlowSource_t *fs, sampler_info_record_t *sampler) {
541 
542 	AppendToBuffer(fs->nffile, (void *)sampler, sampler->header.size);
543 
544 #ifdef DEVEL
545 	{
546 		printf("Flush Sampler: ");
547 		if ( sampler->id < 0 ) {
548 			printf("Exporter SysID: %u,	Generic Sampler: mode: %u, interval: %u\n",
549 				sampler->exporter_sysid, sampler->mode, sampler->interval);
550 		} else {
551 			printf("Exporter SysID: %u, Sampler: id: %i, mode: %u, interval: %u\n",
552 				sampler->exporter_sysid, sampler->id, sampler->mode, sampler->interval);
553 		}
554 	}
555 #endif
556 
557 	return 1;
558 
559 } // End of FlushInfoSampler
560 
FlushStdRecords(FlowSource_t * fs)561 void FlushStdRecords(FlowSource_t *fs) {
562 exporter_t *e = fs->exporter_data;
563 int i;
564 
565 	while ( e ) {
566 		sampler_t *sampler = e->sampler;
567 		AppendToBuffer(fs->nffile, (void *)&(e->info), e->info.header.size);
568 		while ( sampler ) {
569 			AppendToBuffer(fs->nffile, (void *)&(sampler->info), sampler->info.header.size);
570 			sampler = sampler->next;
571 		}
572 		e = e->next;
573 	}
574 
575     for ( i=0; i<fs->extension_map_list.next_free; i++ ) {
576         extension_map_t *map = fs->extension_map_list.maps[i];
577 		if ( map )
578 			AppendToBuffer(fs->nffile, (void *)map, map->size);
579     }
580 
581 } // End of FlushStdRecords
582 
FlushExporterStats(FlowSource_t * fs)583 void FlushExporterStats(FlowSource_t *fs) {
584 exporter_t *e = fs->exporter_data;
585 exporter_stats_record_t	*exporter_stats;
586 uint32_t i, size;
587 
588 	// idle collector ..
589 	if ( !fs->exporter_count )
590 		return;
591 
592 	size = sizeof(exporter_stats_record_t) + (fs->exporter_count -1) * sizeof(struct exporter_stat_s);
593 	exporter_stats = ( exporter_stats_record_t *)malloc(size);
594 	if ( !exporter_stats ) {
595 		LogError("malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
596 		return;
597 	}
598 	exporter_stats->header.type = ExporterStatRecordType;
599 	exporter_stats->header.size = size;
600 	exporter_stats->stat_count	= fs->exporter_count;
601 
602 #ifdef DEVEL
603 	printf("Flush Exporter Stats: %u exporters, size: %u\n", fs->exporter_count, size);
604 #endif
605 	i = 0;
606 	while ( e ) {
607 		// prevent memory corruption - just in case .. should not happen anyway
608 		// continue loop for error reporting after while
609 		if ( i >= fs->exporter_count ) {
610 			i++;
611 			e = e->next;
612 			continue;
613 		}
614 		exporter_stats->stat[i].sysid 			 = e->info.sysid;
615 		exporter_stats->stat[i].sequence_failure = e->sequence_failure;
616 		exporter_stats->stat[i].packets 		 = e->packets;
617 		exporter_stats->stat[i].flows 			 = e->flows;
618 #ifdef DEVEL
619 		printf("Stat: SysID: %u, version: %u, ID: %2u, Packets: %llu, Flows: %llu, Sequence Failures: %u, Padding Errors: %u\n",
620 			e->info.sysid, e->info.version, e->info.id, e->packets, e->flows, e->sequence_failure, e->padding_errors);
621 
622 #endif
623 		// reset counters
624 		e->sequence_failure = 0;
625 		e->padding_errors   = 0;
626 		e->packets 			= 0;
627 		e->flows 			= 0;
628 
629 		i++;
630 		e = e->next;
631 	}
632 	AppendToBuffer(fs->nffile, (void *)exporter_stats, size);
633 	free(exporter_stats);
634 
635 	if ( i != fs->exporter_count ) {
636 		LogError("ERROR: exporter stats: Expected %u records, but found %u in %s line %d: %s\n",
637 			fs->exporter_count, i, __FILE__, __LINE__, strerror(errno) );
638 	}
639 
640 } // End of FlushExporterStats
641 
642