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