nfdump/bin/ipfix.c
2015-10-03 14:06:34 +02:00

1743 lines
63 KiB
C

/*
* Copyright (c) 2014, Peter Haag
* Copyright (c) 2012, Peter Haag
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the author nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $Author:$
*
* $Id:$
*
* $LastChangedRevision:$
*
*/
#include "config.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include "nffile.h"
#include "nfx.h"
#include "nfnet.h"
#include "nf_common.h"
#include "util.h"
#include "bookkeeper.h"
#include "nfxstat.h"
#include "collector.h"
#include "exporter.h"
#include "ipfix.h"
#ifndef DEVEL
# define dbg_printf(...) /* printf(__VA_ARGS__) */
#else
# define dbg_printf(...) printf(__VA_ARGS__)
#endif
// a few handy macros
#define GET_FLOWSET_ID(p) (Get_val16(p))
#define GET_FLOWSET_LENGTH(p) (Get_val16((void *)((p) + 2)))
#define GET_TEMPLATE_ID(p) (Get_val16(p))
#define GET_TEMPLATE_COUNT(p) (Get_val16((void *)((p) + 2)))
#define GET_OPTION_TEMPLATE_ID(p) (Get_val16(p))
#define GET_OPTION_TEMPLATE_FIELD_COUNT(p) (Get_val16((void *)((p) + 2)))
#define GET_OPTION_TEMPLATE_SCOPE_FIELD_COUNT(p) (Get_val16((void *)((p) + 4)))
/* module limited globals */
/*
* sequence element to move data from data input to output
* a sequence exists for each IPFIX element
*/
typedef struct sequence_map_s {
/* sequence definition:
just move a certain number of bytes -> moveXX
set a certain number of output bytes to zero -> zeroXX
process input data into appropriate output -> AnyName
*/
#define nop 0
#define move8 1
#define move16 2
#define move32 3
#define move40 4
#define move48 5
#define move56 6
#define move64 7
#define move128 8
#define move32_sampling 9
#define move64_sampling 10
#define move_mac 11
#define move_mpls 12
#define Time64Mili 13
#define saveICMP 14
#define zero8 15
#define zero16 16
#define zero32 17
#define zero64 18
#define zero128 19
uint32_t id; // sequence ID as defined above
uint16_t input_offset; // copy/process data at this input offset
uint16_t output_offset; // copy final data to this output offset
void *stack; // optionally copy data onto this stack
} sequence_map_t;
/*
* the IPFIX template records are processed and
* for each template we create a a translation table, which contains
* all information required, to transform the data records from
* the exporter into nfdump internal data structurs.
* All templates are chained in a linked list
*/
typedef struct input_translation_s {
struct input_translation_s *next; // linked list
uint32_t flags; // flags for output record
time_t updated; // timestamp of last update/refresh
uint32_t id; // template ID of exporter domains
uint32_t input_record_size; // size of the input record
uint32_t output_record_size; // required size in nfdump format
// tmp vars needed while processing the data record
uint64_t flow_start; // start time in msec
uint64_t flow_end; // end time in msec
uint32_t ICMP_offset; // offset of ICMP type/code in data stream
uint64_t packets; // total (in)packets - sampling corrected
uint64_t bytes; // total (in)bytes - sampling corrected
uint64_t out_packets; // total out packets - sampling corrected
uint64_t out_bytes; // total out bytes - sampling corrected
// uint32_t src_as_offset;
// uint32_t dst_as_offset;
// uint32_t sampler_offset;
// uint32_t sampler_size;
// uint32_t engine_offset;
uint32_t router_ip_offset;
uint32_t received_offset;
// etension map infos
uint32_t extension_map_changed; // map changed while refreshing?
extension_info_t extension_info; // the extension map reflecting this template
// sequence map information
uint32_t number_of_sequences; // number of sequences for the translate
sequence_map_t *sequence; // sequence map
} input_translation_t;
/*
* All Obervation Domains from all exporter are stored in a linked list
* which uniquely can identify each exporter/Observation Domain
*/
typedef struct exporter_ipfix_domain_s {
struct exporter_ipfix_domain_s *next; // linkes list to next exporter
// generic exporter information
exporter_info_record_t info;
uint64_t packets; // number of packets sent by this exporter
uint64_t flows; // number of flow records sent by this exporter
uint32_t sequence_failure; // number of sequence failues
// generic sampler
generic_sampler_t *sampler;
// exporter parameters
uint32_t ExportTime;
// Current sequence number
uint32_t PacketSequence;
// statistics
uint64_t TemplateRecords; // stat counter
uint64_t DataRecords; // stat counter
// linked list of all templates sent by this exporter
input_translation_t *input_translation_table;
// in order to prevent search through all lists keep
// the last template we processed as a cache
input_translation_t *current_table;
} exporter_ipfix_domain_t;
static struct ipfix_element_map_s {
uint16_t id; // IPFIX element id
uint16_t length; // type of this element ( input length )
uint16_t out_length; // type of this element ( output length )
uint32_t sequence; //
uint32_t zero_sequence; //
uint16_t extension; // maps into nfdump extension ID
} ipfix_element_map[] = {
{0, 0, 0},
{ IPFIX_octetDeltaCount, _8bytes, _8bytes, move64_sampling, zero64, COMMON_BLOCK },
{ IPFIX_octetDeltaCount, _4bytes, _8bytes, move32_sampling, zero64, COMMON_BLOCK },
{ IPFIX_packetDeltaCount, _8bytes, _8bytes, move64_sampling, zero64, COMMON_BLOCK },
{ IPFIX_packetDeltaCount, _4bytes, _8bytes, move32_sampling, zero64, COMMON_BLOCK },
{ IPFIX_octetTotalCount, _8bytes, _8bytes, move64_sampling, zero64, COMMON_BLOCK },
{ IPFIX_octetTotalCount, _4bytes, _8bytes, move32_sampling, zero64, COMMON_BLOCK },
{ IPFIX_packetTotalCount, _8bytes, _8bytes, move64_sampling, zero64, COMMON_BLOCK },
{ IPFIX_packetTotalCount, _4bytes, _8bytes, move32_sampling, zero64, COMMON_BLOCK },
{ IPFIX_protocolIdentifier, _1byte, _1byte, move8, zero8, COMMON_BLOCK },
{ IPFIX_ipClassOfService, _1byte, _1byte, move8, zero8, COMMON_BLOCK },
{ IPFIX_tcpControlBits, _1byte, _1byte, move8, zero8, COMMON_BLOCK },
{ IPFIX_SourceTransportPort, _2bytes, _2bytes, move16, zero16, COMMON_BLOCK },
{ IPFIX_SourceIPv4Address, _4bytes, _4bytes, move32, zero32, COMMON_BLOCK },
{ IPFIX_SourceIPv4PrefixLength, _1byte, _1byte, move8, zero8, EX_MULIPLE },
{ IPFIX_ingressInterface, _4bytes, _4bytes, move32, zero32, EX_IO_SNMP_4 },
{ IPFIX_ingressInterface, _2bytes, _2bytes, move16, zero16, EX_IO_SNMP_2 },
{ IPFIX_DestinationTransportPort, _2bytes, _2bytes, move16, zero16, COMMON_BLOCK },
{ IPFIX_DestinationIPv4Address, _4bytes, _4bytes, move32, zero32, COMMON_BLOCK },
{ IPFIX_DestinationIPv4PrefixLength, _1byte, _1byte, move8, zero8, EX_MULIPLE },
{ IPFIX_egressInterface, _4bytes, _4bytes, move32, zero32, EX_IO_SNMP_4 },
{ IPFIX_egressInterface, _2bytes, _2bytes, move16, zero16, EX_IO_SNMP_2 },
{ IPFIX_ipNextHopIPv4Address, _4bytes, _4bytes, move32, zero32, EX_NEXT_HOP_v4 },
{ IPFIX_bgpSourceAsNumber, _4bytes, _4bytes, move32, zero32, EX_AS_4 },
{ IPFIX_bgpSourceAsNumber, _2bytes, _2bytes, move16, zero16, EX_AS_2 },
{ IPFIX_bgpDestinationAsNumber, _4bytes, _4bytes, move32, zero32, EX_AS_4 },
{ IPFIX_bgpDestinationAsNumber, _2bytes, _2bytes, move16, zero16, EX_AS_2 },
{ IPFIX_bgpNextHopIPv4Address, _4bytes, _4bytes, move32, zero32, EX_NEXT_HOP_BGP_v4},
{ IPFIX_flowEndSysUpTime, _4bytes, _4bytes, nop, nop, COMMON_BLOCK },
{ IPFIX_flowStartSysUpTime, _4bytes, _4bytes, nop, nop, COMMON_BLOCK },
{ IPFIX_postOctetDeltaCount, _8bytes, _8bytes, move64, zero64, EX_OUT_BYTES_8 },
{ IPFIX_postOctetDeltaCount, _4bytes, _4bytes, move32, zero32, EX_OUT_BYTES_4 },
{ IPFIX_postPacketDeltaCount, _8bytes, _8bytes, move64, zero64, EX_OUT_PKG_8 },
{ IPFIX_postPacketDeltaCount, _4bytes, _4bytes, move32, zero32, EX_OUT_PKG_4 },
{ IPFIX_SourceIPv6Address, _16bytes, _16bytes, move128, zero128, COMMON_BLOCK },
{ IPFIX_DestinationIPv6Address, _16bytes, _16bytes, move128, zero128, COMMON_BLOCK },
{ IPFIX_SourceIPv6PrefixLength, _1byte, _1byte, move8, zero8, EX_MULIPLE },
{ IPFIX_DestinationIPv6PrefixLength, _1byte, _1byte, move8, zero8, EX_MULIPLE },
{ IPFIX_flowLabelIPv6, _4bytes, _4bytes, nop, nop, COMMON_BLOCK },
{ IPFIX_icmpTypeCodeIPv4, _2bytes, _2bytes, nop, nop, COMMON_BLOCK },
{ IPFIX_postIpClassOfService, _1byte, _1byte, move8, zero8, EX_MULIPLE },
{ IPFIX_SourceMacAddress, _6bytes, _8bytes, move_mac, zero64, EX_MAC_1},
{ IPFIX_postDestinationMacAddress, _6bytes, _8bytes, move_mac, zero64, EX_MAC_1},
{ IPFIX_vlanId, _2bytes, _2bytes, move16, zero16, EX_VLAN},
{ IPFIX_postVlanId, _2bytes, _2bytes, move16, zero16, EX_VLAN},
{ IPFIX_flowDirection, _1byte, _1byte, move8, zero8, EX_MULIPLE },
{ IPFIX_ipNextHopIPv6Address, _16bytes, _16bytes, move128, zero128, EX_NEXT_HOP_v6},
{ IPFIX_bgpNextHopIPv6Address, _16bytes, _16bytes, move128, zero128, EX_NEXT_HOP_BGP_v6},
{ IPFIX_mplsTopLabelStackSection, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_mplsLabelStackSection2, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_mplsLabelStackSection3, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_mplsLabelStackSection4, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_mplsLabelStackSection5, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_mplsLabelStackSection6, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_mplsLabelStackSection7, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_mplsLabelStackSection8, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_mplsLabelStackSection9, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_mplsLabelStackSection10, _3bytes, _4bytes, move_mpls, zero32, EX_MPLS},
{ IPFIX_DestinationMacAddress, _6bytes, _8bytes, move_mac, zero64, EX_MAC_2},
{ IPFIX_postSourceMacAddress, _6bytes, _8bytes, move_mac, zero64, EX_MAC_2},
{ IPFIX_flowStartMilliseconds, _8bytes, _8bytes, Time64Mili, zero32, COMMON_BLOCK},
{ IPFIX_flowEndMilliseconds, _8bytes, _8bytes, Time64Mili, zero32, COMMON_BLOCK},
{0, 0, 0}
};
// cache to be used while parsing a template
static struct cache_s {
struct element_param_s {
uint16_t index;
uint16_t found;
uint16_t offset;
uint16_t length;
} *lookup_info;
uint32_t max_ipfix_elements;
uint32_t *common_extensions;
} cache;
// module limited globals
static uint32_t processed_records;
// externals
extern int verbose;
extern uint32_t Max_num_extensions;
extern extension_descriptor_t extension_descriptor[];
extern uint32_t overwrite_sampling;
extern uint32_t exporter_sysid;
// prototypes
static input_translation_t *add_translation_table(exporter_ipfix_domain_t *exporter, uint16_t id);
static void remove_translation_table(FlowSource_t *fs, exporter_ipfix_domain_t *exporter, uint16_t id);
static void remove_all_translation_tables(exporter_ipfix_domain_t *exporter);
static inline exporter_ipfix_domain_t *GetExporter(FlowSource_t *fs, ipfix_header_t *ipfix_header);
static inline uint32_t MapElement(uint16_t Type, uint16_t Length, uint16_t Offset);
static inline void PushSequence(input_translation_t *table, uint16_t Type, uint32_t *offset, void *stack);
static inline void Process_ipfix_templates(exporter_ipfix_domain_t *exporter, void *flowset_header, uint32_t size_left, FlowSource_t *fs);
static inline void Process_ipfix_template_add(exporter_ipfix_domain_t *exporter, void *DataPtr, uint32_t size_left, FlowSource_t *fs);
static inline void Process_ipfix_template_withdraw(exporter_ipfix_domain_t *exporter, void *DataPtr, uint32_t size_left, FlowSource_t *fs);
#include "inline.c"
#include "nffile_inline.c"
int Init_IPFIX(void) {
int i;
cache.lookup_info = (struct element_param_s *)calloc(65536, sizeof(struct element_param_s));
cache.common_extensions = (uint32_t *)malloc((Max_num_extensions+1)*sizeof(uint32_t));
if ( !cache.common_extensions || !cache.lookup_info ) {
syslog(LOG_ERR, "Process_ipfix: Panic! malloc(): %s line %d: %s", __FILE__, __LINE__, strerror (errno));
return 0;
}
// init the helper element table
for (i=1; ipfix_element_map[i].id != 0; i++ ) {
uint32_t Type = ipfix_element_map[i].id;
// multiple same type - save first index only
// iterate through same Types afterwards
if ( cache.lookup_info[Type].index == 0 )
cache.lookup_info[Type].index = i;
}
cache.max_ipfix_elements = i;
syslog(LOG_DEBUG,"Init IPFIX: Max number of IPFIX tags: %u", cache.max_ipfix_elements);
return 1;
} // End of Init_IPFIX
static inline exporter_ipfix_domain_t *GetExporter(FlowSource_t *fs, ipfix_header_t *ipfix_header) {
#define IP_STRING_LEN 40
char ipstr[IP_STRING_LEN];
exporter_ipfix_domain_t **e = (exporter_ipfix_domain_t **)&(fs->exporter_data);
uint32_t ObservationDomain = ntohl(ipfix_header->ObservationDomain);
while ( *e ) {
if ( (*e)->info.id == ObservationDomain && (*e)->info.version == 10 &&
(*e)->info.ip.v6[0] == fs->ip.v6[0] && (*e)->info.ip.v6[1] == fs->ip.v6[1])
return *e;
e = &((*e)->next);
}
if ( fs->sa_family == AF_INET ) {
uint32_t _ip = htonl(fs->ip.v4);
inet_ntop(AF_INET, &_ip, ipstr, sizeof(ipstr));
} else if ( fs->sa_family == AF_INET6 ) {
uint64_t _ip[2];
_ip[0] = htonll(fs->ip.v6[0]);
_ip[1] = htonll(fs->ip.v6[1]);
inet_ntop(AF_INET6, &_ip, ipstr, sizeof(ipstr));
} else {
strncpy(ipstr, "<unknown>", IP_STRING_LEN);
}
// nothing found
*e = (exporter_ipfix_domain_t *)malloc(sizeof(exporter_ipfix_domain_t));
if ( !(*e)) {
syslog(LOG_ERR, "Process_ipfix: Panic! malloc() %s line %d: %s", __FILE__, __LINE__, strerror (errno));
return NULL;
}
memset((void *)(*e), 0, sizeof(exporter_ipfix_domain_t));
(*e)->info.header.type = ExporterInfoRecordType;
(*e)->info.header.size = sizeof(exporter_info_record_t);
(*e)->info.id = ObservationDomain;
(*e)->info.ip = fs->ip;
(*e)->info.sa_family = fs->sa_family;
(*e)->info.version = 10;
(*e)->info.sysid = 0;
(*e)->TemplateRecords = 0;
(*e)->DataRecords = 0;
(*e)->sequence_failure = 0;
(*e)->next = NULL;
(*e)->sampler = NULL;
FlushInfoExporter(fs, &((*e)->info));
dbg_printf("[%u] New exporter: SysID: %u, Observation domain %u from: %s\n",
ObservationDomain, (*e)->info.sysid, ObservationDomain, ipstr);
syslog(LOG_INFO, "Process_ipfix: New exporter: SysID: %u, Observation domain %u from: %s\n",
(*e)->info.sysid, ObservationDomain, ipstr);
return (*e);
} // End of GetExporter
static inline uint32_t MapElement(uint16_t Type, uint16_t Length, uint16_t Offset) {
int index;
index = cache.lookup_info[Type].index;
if ( index ) {
while ( index && ipfix_element_map[index].id == Type ) {
if ( Length == ipfix_element_map[index].length ) {
cache.lookup_info[Type].found = 1;
cache.lookup_info[Type].offset = Offset;
cache.lookup_info[Type].length = Length;
cache.lookup_info[Type].index = index;
dbg_printf("found extension %u for type: %u, input length: %u output length: %u Extension: %u\n",
ipfix_element_map[index].extension, ipfix_element_map[index].id,
ipfix_element_map[index].length, ipfix_element_map[index].out_length, ipfix_element_map[index].extension);
return ipfix_element_map[index].extension;
}
index++;
}
}
dbg_printf("Skip unknown element type: %u, Length: %u\n", Type, Length);
return 0;
} // End of MapElement
static inline input_translation_t *GetTranslationTable(exporter_ipfix_domain_t *exporter, uint16_t id) {
input_translation_t *table;
if ( exporter->current_table && ( exporter->current_table->id == id ) )
return exporter->current_table;
table = exporter->input_translation_table;
while ( table ) {
if ( table->id == id ) {
exporter->current_table = table;
return table;
}
table = table->next;
}
dbg_printf("[%u] Get translation table %u: %s\n", exporter->info.id, id, table == NULL ? "not found" : "found");
exporter->current_table = table;
return table;
} // End of GetTranslationTable
static input_translation_t *add_translation_table(exporter_ipfix_domain_t *exporter, uint16_t id) {
input_translation_t **table;
table = &(exporter->input_translation_table);
while ( *table ) {
table = &((*table)->next);
}
// Allocate enough space for all potential ipfix tags, which we support
// so template refreshing may change the table size without danger of overflowing
*table = calloc(1, sizeof(input_translation_t));
if ( !(*table) ) {
syslog(LOG_ERR, "Process_ipfix: Panic! calloc() %s line %d: %s", __FILE__, __LINE__, strerror (errno));
return NULL;
}
(*table)->sequence = calloc(cache.max_ipfix_elements, sizeof(sequence_map_t));
if ( !(*table)->sequence ) {
syslog(LOG_ERR, "Process_ipfix: Panic! malloc() %s line %d: %s", __FILE__, __LINE__, strerror (errno));
return NULL;
}
(*table)->id = id;
(*table)->next = NULL;
dbg_printf("[%u] Get new translation table %u\n", exporter->info.id, id);
return *table;
} // End of add_translation_table
static void remove_translation_table(FlowSource_t *fs, exporter_ipfix_domain_t *exporter, uint16_t id) {
input_translation_t *table, *parent;
syslog(LOG_INFO, "Process_ipfix: [%u] Withdraw template id: %i",
exporter->info.id, id);
parent = NULL;
table = exporter->input_translation_table;
while ( table && ( table->id != id ) ) {
parent = table;
table = table->next;
}
if ( table == NULL ) {
syslog(LOG_ERR, "Process_ipfix: [%u] Withdraw template id: %i. translation table not found",
exporter->info.id, id);
return;
}
dbg_printf("\n[%u] Withdraw template ID: %u\n", exporter->info.id, table->id);
// clear table cache, if this is the table to delete
if (exporter->current_table == table)
exporter->current_table = NULL;
if ( parent ) {
// remove table from list
parent->next = table->next;
} else {
// last table removed
exporter->input_translation_table = NULL;
}
RemoveExtensionMap(fs, table->extension_info.map);
free(table->sequence);
free(table->extension_info.map);
free(table);
} // End of remove_translation_table
static void remove_all_translation_tables(exporter_ipfix_domain_t *exporter) {
input_translation_t *table, *next;
syslog(LOG_INFO, "Process_ipfix: Withdraw all templates from observation domain %u\n",
exporter->info.id);
table = exporter->input_translation_table;
while ( table ) {
next = table->next;
dbg_printf("\n[%u] Withdraw template ID: %u\n", exporter->info.id, table->id);
free(table->sequence);
free(table->extension_info.map);
free(table);
table = next;
}
// clear references
exporter->input_translation_table = NULL;
exporter->current_table = NULL;
} // End of remove_all_translation_tables
static inline void PushSequence(input_translation_t *table, uint16_t Type, uint32_t *offset, void *stack) {
uint32_t i = table->number_of_sequences;
uint32_t index = cache.lookup_info[Type].index;
if ( table->number_of_sequences >= cache.max_ipfix_elements ) {
syslog(LOG_ERR, "Process_ipfix: Software bug! Sequence table full. at %s line %d",
__FILE__, __LINE__);
dbg_printf("Software bug! Sequence table full. at %s line %d",
__FILE__, __LINE__);
return;
}
if ( cache.lookup_info[Type].found ) {
table->sequence[i].id = ipfix_element_map[index].sequence;
table->sequence[i].input_offset = cache.lookup_info[Type].offset;
table->sequence[i].output_offset = *offset;
table->sequence[i].stack = stack;
} else {
table->sequence[i].id = ipfix_element_map[index].zero_sequence;
table->sequence[i].input_offset = 0;
table->sequence[i].output_offset = *offset;
table->sequence[i].stack = NULL;
}
dbg_printf("Push: sequence: %u, Type: %u, length: %u, out length: %u, id: %u, in offset: %u, out offset: %u\n",
i, Type, ipfix_element_map[index].length, ipfix_element_map[index].out_length, table->sequence[i].id,
table->sequence[i].input_offset, table->sequence[i].output_offset);
table->number_of_sequences++;
(*offset) += ipfix_element_map[index].out_length;
} // End of PushSequence
static input_translation_t *setup_translation_table (exporter_ipfix_domain_t *exporter, uint16_t id, uint16_t input_record_size) {
input_translation_t *table;
extension_map_t *extension_map;
uint32_t i, ipv6, offset, next_extension;
size_t size_required;
ipv6 = 0;
table = GetTranslationTable(exporter, id);
if ( !table ) {
syslog(LOG_INFO, "Process_ipfix: [%u] Add template %u", exporter->info.id, id);
table = add_translation_table(exporter, id);
if ( !table ) {
return NULL;
}
// Add an extension map
// The number of extensions for this template is currently unknown
// Allocate enough space for all configured extensions - some may be unused later
// make sure memory is 4byte alligned
size_required = Max_num_extensions * sizeof(uint16_t) + sizeof(extension_map_t);
size_required = (size_required + 3) &~(size_t)3;
extension_map = malloc(size_required);
if ( !extension_map ) {
syslog(LOG_ERR, "Process_ipfix: Panic! malloc() error in %s line %d: %s", __FILE__, __LINE__, strerror (errno));
return NULL;
}
extension_map->type = ExtensionMapType;
// Set size to an empty table - will be adapted later
extension_map->size = sizeof(extension_map_t);
extension_map->map_id = INIT_ID;
// packed record size still unknown at this point - will be added later
extension_map->extension_size = 0;
table->extension_info.map = extension_map;
table->extension_map_changed = 1;
table->number_of_sequences = 0;
} else {
extension_map = table->extension_info.map;
// reset size/extension size - it's refreshed automatically
extension_map->size = sizeof(extension_map_t);
extension_map->extension_size = 0;
dbg_printf("[%u] Refresh template %u\n", exporter->info.id, id);
// very noisy with somee exporters
dbg_printf("[%u] Refresh template %u\n", exporter->info.id, id);
}
// clear current table
memset((void *)table->sequence, 0, cache.max_ipfix_elements * sizeof(sequence_map_t));
table->number_of_sequences = 0;
table->updated = time(NULL);
// IPFIX only has 64bit counters
table->flags = 0;
SetFlag(table->flags, FLAG_PKG_64);
SetFlag(table->flags, FLAG_BYTES_64);
table->ICMP_offset = 0;
// table->sampler_offset = 0;
// table->sampler_size = 0;
// table->engine_offset = 0;
table->router_ip_offset = 0;
table->received_offset = 0;
dbg_printf("[%u] Build sequence table %u\n", exporter->info.id, id);
// fill table
table->id = id;
/*
* common data block: The common record is expected in the output stream. If not available
* in the template, fill values with 0
*/
// All required extensions
// The order we Push all ipfix elements, must corresponde to the structure of the common record
// followed by all available extension in the extension map
offset = BYTE_OFFSET_first;
PushSequence( table, IPFIX_flowStartMilliseconds, &offset, &table->flow_start);
offset = BYTE_OFFSET_first + 4;
PushSequence( table, IPFIX_flowEndMilliseconds, &offset, &table->flow_end);
offset = BYTE_OFFSET_first + 8;
offset +=1; // Skip netflow v9 fwd status
PushSequence( table, IPFIX_tcpControlBits, &offset, NULL);
PushSequence( table, IPFIX_protocolIdentifier, &offset, NULL);
PushSequence( table, IPFIX_ipClassOfService, &offset, NULL);
PushSequence( table, IPFIX_SourceTransportPort, &offset, NULL);
PushSequence( table, IPFIX_DestinationTransportPort, &offset, NULL);
// skip exporter_sysid and reserved
offset += 4;
/* IP addresss record
* This record is expected in the output stream. If not available
* in the template, assume empty v4 address.
*/
if ( cache.lookup_info[IPFIX_SourceIPv4Address].found ) {
// IPv4 addresses
PushSequence( table, IPFIX_SourceIPv4Address, &offset, NULL);
PushSequence( table, IPFIX_DestinationIPv4Address, &offset, NULL);
} else if ( cache.lookup_info[IPFIX_SourceIPv6Address].found ) {
// IPv6 addresses
PushSequence( table, IPFIX_SourceIPv6Address, &offset, NULL);
PushSequence( table, IPFIX_DestinationIPv6Address, &offset, NULL);
// mark IPv6
SetFlag(table->flags, FLAG_IPV6_ADDR);
ipv6 = 1;
} else {
// should not happen, assume empty IPv4 addresses, zero
PushSequence( table, IPFIX_SourceIPv4Address, &offset, NULL);
PushSequence( table, IPFIX_DestinationIPv4Address, &offset, NULL);
}
// decide between Delta or Total counters - prefer Total if available
if ( cache.lookup_info[IPFIX_packetTotalCount].found )
PushSequence( table, IPFIX_packetTotalCount, &offset, &table->packets);
else
PushSequence( table, IPFIX_packetDeltaCount, &offset, &table->packets);
SetFlag(table->flags, FLAG_PKG_64);
if ( cache.lookup_info[IPFIX_octetTotalCount].found )
PushSequence( table, IPFIX_octetTotalCount, &offset, &table->bytes);
else
PushSequence( table, IPFIX_octetDeltaCount, &offset, &table->bytes);
SetFlag(table->flags, FLAG_BYTES_64);
// Optional extensions
next_extension = 0;
for (i=4; extension_descriptor[i].id; i++ ) {
uint32_t map_index = i;
if ( cache.common_extensions[i] == 0 )
continue;
switch(i) {
case EX_IO_SNMP_2:
PushSequence( table, IPFIX_ingressInterface, &offset, NULL);
PushSequence( table, IPFIX_egressInterface, &offset, NULL);
break;
case EX_IO_SNMP_4:
PushSequence( table, IPFIX_ingressInterface, &offset, NULL);
PushSequence( table, IPFIX_egressInterface, &offset, NULL);
break;
case EX_AS_2:
PushSequence( table, IPFIX_bgpSourceAsNumber, &offset, NULL);
PushSequence( table, IPFIX_bgpDestinationAsNumber, &offset, NULL);
break;
case EX_AS_4:
PushSequence( table, IPFIX_bgpSourceAsNumber, &offset, NULL);
PushSequence( table, IPFIX_bgpDestinationAsNumber, &offset, NULL);
break;
case EX_MULIPLE:
PushSequence( table, IPFIX_postIpClassOfService, &offset, NULL);
PushSequence( table, IPFIX_flowDirection, &offset, NULL);
if ( ipv6 ) {
// IPv6
PushSequence( table, IPFIX_SourceIPv6PrefixLength, &offset, NULL);
PushSequence( table, IPFIX_DestinationIPv6PrefixLength, &offset, NULL);
} else {
// IPv4
PushSequence( table, IPFIX_SourceIPv4PrefixLength, &offset, NULL);
PushSequence( table, IPFIX_DestinationIPv4PrefixLength, &offset, NULL);
}
break;
case EX_NEXT_HOP_v4:
PushSequence( table, IPFIX_ipNextHopIPv4Address, &offset, NULL);
break;
case EX_NEXT_HOP_v6:
PushSequence( table, IPFIX_ipNextHopIPv6Address, &offset, NULL);
SetFlag(table->flags, FLAG_IPV6_NH);
break;
case EX_NEXT_HOP_BGP_v4:
PushSequence( table, IPFIX_bgpNextHopIPv4Address, &offset, NULL);
break;
case EX_NEXT_HOP_BGP_v6:
PushSequence( table, IPFIX_bgpNextHopIPv6Address, &offset, NULL);
SetFlag(table->flags, FLAG_IPV6_NHB);
break;
case EX_VLAN:
PushSequence( table, IPFIX_vlanId, &offset, NULL);
PushSequence( table, IPFIX_postVlanId, &offset, NULL);
break;
case EX_OUT_PKG_4:
PushSequence( table, IPFIX_postPacketDeltaCount, &offset, NULL);
break;
case EX_OUT_PKG_8:
PushSequence( table, IPFIX_postPacketDeltaCount, &offset, NULL);
break;
case EX_OUT_BYTES_4:
PushSequence( table, IPFIX_postOctetDeltaCount, &offset, NULL);
break;
case EX_OUT_BYTES_8:
PushSequence( table, IPFIX_postOctetDeltaCount, &offset, NULL);
break;
case EX_AGGR_FLOWS_8:
break;
case EX_MAC_1:
PushSequence( table, IPFIX_SourceMacAddress, &offset, NULL);
PushSequence( table, IPFIX_postDestinationMacAddress, &offset, NULL);
break;
case EX_MAC_2:
PushSequence( table, IPFIX_DestinationMacAddress, &offset, NULL);
PushSequence( table, IPFIX_postSourceMacAddress, &offset, NULL);
break;
case EX_MPLS:
PushSequence( table, IPFIX_mplsTopLabelStackSection, &offset, NULL);
PushSequence( table, IPFIX_mplsLabelStackSection2, &offset, NULL);
PushSequence( table, IPFIX_mplsLabelStackSection3, &offset, NULL);
PushSequence( table, IPFIX_mplsLabelStackSection4, &offset, NULL);
PushSequence( table, IPFIX_mplsLabelStackSection5, &offset, NULL);
PushSequence( table, IPFIX_mplsLabelStackSection6, &offset, NULL);
PushSequence( table, IPFIX_mplsLabelStackSection7, &offset, NULL);
PushSequence( table, IPFIX_mplsLabelStackSection8, &offset, NULL);
PushSequence( table, IPFIX_mplsLabelStackSection9, &offset, NULL);
PushSequence( table, IPFIX_mplsLabelStackSection10, &offset, NULL);
break;
case EX_ROUTER_IP_v4:
case EX_ROUTER_IP_v6:
if ( exporter->info.sa_family == PF_INET6 ) {
table->router_ip_offset = offset;
dbg_printf("Router IPv6: offset: %u, olen: %u\n", offset, 16 );
// not an entry for the translateion table.
// but reserve space in the output record for IPv6
offset += 16;
SetFlag(table->flags, FLAG_IPV6_EXP);
map_index = EX_ROUTER_IP_v6;
} else {
table->router_ip_offset = offset;
dbg_printf("Router IPv4: offset: %u, olen: %u\n", offset, 4 );
// not an entry for the translateion table.
// but reserve space in the output record for IPv4
offset += 4;
ClearFlag(table->flags, FLAG_IPV6_EXP);
map_index = EX_ROUTER_IP_v4;
}
break;
case EX_ROUTER_ID:
// no value in ipfix
break;
case EX_RECEIVED:
table->received_offset = offset;
dbg_printf("Received offset: %u\n", offset);
offset += 8;
break;
}
extension_map->size += sizeof(uint16_t);
extension_map->extension_size += extension_descriptor[map_index].size;
// found extension in map_index must be the same as in map - otherwise map is dirty
if ( extension_map->ex_id[next_extension] != map_index ) {
// dirty map - needs to be refreshed in output stream
extension_map->ex_id[next_extension] = map_index;
table->extension_map_changed = 1;
}
next_extension++;
}
extension_map->ex_id[next_extension++] = 0;
// make sure map is aligned
if ( extension_map->size & 0x3 ) {
extension_map->ex_id[next_extension] = 0;
extension_map->size = ( extension_map->size + 3 ) &~ 0x3;
}
table->output_record_size = offset;
table->input_record_size = input_record_size;
// for netflow historical reason, ICMP type/code goes into dst port field
// remember offset, for decoding
if ( cache.lookup_info[IPFIX_icmpTypeCodeIPv4].found && cache.lookup_info[IPFIX_icmpTypeCodeIPv4].length == 2 ) {
table->ICMP_offset = cache.lookup_info[IPFIX_icmpTypeCodeIPv4].offset;
}
#ifdef DEVEL
if ( table->extension_map_changed ) {
printf("Extension Map id=%u changed!\n", extension_map->map_id);
} else {
printf("[%u] template %u unchanged\n", exporter->info.id, id);
}
printf("Process_ipfix: Check extension map: id: %d, size: %u, extension_size: %u\n",
extension_map->map_id, extension_map->size, extension_map->extension_size);
{ int i;
for (i=0; i<table->number_of_sequences; i++ ) {
printf("Sequence %i: id: %u, in offset: %u, out offset: %u, stack: %llu\n",
i, table->sequence[i].id, table->sequence[i].input_offset, table->sequence[i].output_offset,
(unsigned long long)table->sequence[i].stack);
}
printf("Flags: 0x%x\n", table->flags);
printf("Input record size: %u, output record size: %u\n",
table->input_record_size, table->output_record_size);
}
PrintExtensionMap(extension_map);
#endif
return table;
} // End of setup_translation_table
static inline void Process_ipfix_templates(exporter_ipfix_domain_t *exporter, void *flowset_header, uint32_t size_left, FlowSource_t *fs) {
ipfix_template_record_t *ipfix_template_record;
void *DataPtr;
uint32_t id, count;
size_left -= 4; // subtract message header
DataPtr = flowset_header + 4;
ipfix_template_record = (ipfix_template_record_t *)DataPtr;
id = ntohs(ipfix_template_record->TemplateID);
count = ntohs(ipfix_template_record->FieldCount);
if ( count == 0 ) {
// withdraw template
Process_ipfix_template_withdraw(exporter, DataPtr, size_left, fs);
} else {
// refresh/add templates
Process_ipfix_template_add(exporter, DataPtr, size_left, fs);
}
} // End of Process_ipfix_templates
static inline void Process_ipfix_template_add(exporter_ipfix_domain_t *exporter, void *DataPtr, uint32_t size_left, FlowSource_t *fs) {
input_translation_t *translation_table;
ipfix_template_record_t *ipfix_template_record;
ipfix_template_elements_std_t *NextElement;
int i;
uint16_t Offset = 0;
// a template flowset can contain multiple records ( templates )
while ( size_left ) {
// clear helper tables
memset((void *)cache.common_extensions, 0, (Max_num_extensions+1)*sizeof(uint32_t));
memset((void *)cache.lookup_info, 0, 65536 * sizeof(struct element_param_s));
for (i=1; ipfix_element_map[i].id != 0; i++ ) {
uint32_t Type = ipfix_element_map[i].id;
if ( ipfix_element_map[i].id == ipfix_element_map[i-1].id )
continue;
cache.lookup_info[Type].index = i;
// other elements cleard be memset
}
uint32_t id, count, size_required;
uint32_t num_extensions = 0;
// map next record.
ipfix_template_record = (ipfix_template_record_t *)DataPtr;
size_left -= 4;
id = ntohs(ipfix_template_record->TemplateID);
count = ntohs(ipfix_template_record->FieldCount);
dbg_printf("\n[%u] Template ID: %u\n", exporter->info.id, id);
dbg_printf("FieldCount: %u buffersize: %u\n", count, size_left);
// assume all elements in template are std elements. correct this value, if we find an enterprise element
size_required = 4*count;
if ( size_left < size_required ) {
// if we fail this check, this flowset must be skipped.
syslog(LOG_ERR, "Process_ipfix: [%u] Not enough data for template elements! required: %i, left: %u",
exporter->info.id, size_required, size_left);
dbg_printf("ERROR: Not enough data for template elements! required: %i, left: %u", size_required, size_left);
return;
}
Offset = 0;
// process all elements in this record
NextElement = (ipfix_template_elements_std_t *)ipfix_template_record->elements;
for ( i=0; i<count; i++ ) {
uint16_t Type, Length;
uint32_t ext_id;
int Enterprise;
Type = ntohs(NextElement->Type);
Length = ntohs(NextElement->Length);
Enterprise = Type & 0x8000 ? 1 : 0;
ext_id = MapElement(Type, Length, Offset);
// do we store this extension? enabled != 0
// more than 1 v9 tag may map to an extension - so count this extension once only
if ( ext_id && extension_descriptor[ext_id].enabled ) {
if ( cache.common_extensions[ext_id] == 0 ) {
cache.common_extensions[ext_id] = 1;
num_extensions++;
}
}
Offset += Length;
if ( Enterprise ) {
ipfix_template_elements_e_t *e = (ipfix_template_elements_e_t *)NextElement;
size_required += 4; // ad 4 for enterprise value
if ( size_left < size_required ) {
syslog(LOG_ERR, "Process_ipfix: [%u] Not enough data for template elements! required: %i, left: %u",
exporter->info.id, size_required, size_left);
dbg_printf("ERROR: Not enough data for template elements! required: %i, left: %u", size_required, size_left);
return;
}
dbg_printf(" [%i] Enterprise: 1, Type: %u, Length %u EnterpriseNumber: %u\n", i, Type, Length, ntohl(e->EnterpriseNumber));
e++;
NextElement = (ipfix_template_elements_std_t *)e;
} else {
dbg_printf(" [%i] Enterprise: 0, Type: %u, Length %u\n", i, Type, Length);
NextElement++;
}
}
dbg_printf("Processed: %u\n", size_required);
// as the router IP address extension is not part announced in a template, we need to deal with it here
if ( extension_descriptor[EX_ROUTER_IP_v4].enabled ) {
if ( cache.common_extensions[EX_ROUTER_IP_v4] == 0 ) {
cache.common_extensions[EX_ROUTER_IP_v4] = 1;
num_extensions++;
}
dbg_printf("Add sending router IP address (%s) => Extension: %u\n",
fs->sa_family == PF_INET6 ? "ipv6" : "ipv4", EX_ROUTER_IP_v4);
}
// XXX for now, we do not stre router ID in IPFIX
extension_descriptor[EX_ROUTER_ID].enabled = 0;
/*
// as the router IP address extension is not part announced in a template, we need to deal with it here
if ( extension_descriptor[EX_ROUTER_ID].enabled ) {
if ( cache.common_extensions[EX_ROUTER_ID] == 0 ) {
cache.common_extensions[EX_ROUTER_ID] = 1;
num_extensions++;
}
dbg_printf("Force add router ID (engine type/ID), Extension: %u\n", EX_ROUTER_ID);
}
*/
// as the received time is not announced in a template, we need to deal with it here
if ( extension_descriptor[EX_RECEIVED].enabled ) {
if ( cache.common_extensions[EX_RECEIVED] == 0 ) {
cache.common_extensions[EX_RECEIVED] = 1;
num_extensions++;
}
dbg_printf("Force add packet received time, Extension: %u\n", EX_RECEIVED);
}
#ifdef DEVEL
{
int i;
for (i=4; extension_descriptor[i].id; i++ ) {
if ( cache.common_extensions[i] ) {
printf("Enabled extension: %i\n", i);
}
}
}
#endif
translation_table = setup_translation_table(exporter, id, Offset);
if (translation_table->extension_map_changed ) {
translation_table->extension_map_changed = 0;
// refresh he map in the ouput buffer
dbg_printf("Translation Table changed! Add extension map ID: %i\n", translation_table->extension_info.map->map_id);
AddExtensionMap(fs, translation_table->extension_info.map);
dbg_printf("Translation Table added! map ID: %i\n", translation_table->extension_info.map->map_id);
}
// update size left of this flowset
size_left -= size_required;
DataPtr = DataPtr + size_required+4; // +4 for header
if ( size_left < 4 ) {
// pading
dbg_printf("Skip %u bytes padding\n", size_left);
size_left = 0;
}
}
} // End of Process_ipfix_template_add
static inline void Process_ipfix_template_withdraw(exporter_ipfix_domain_t *exporter, void *DataPtr, uint32_t size_left, FlowSource_t *fs) {
ipfix_template_record_t *ipfix_template_record;
// a template flowset can contain multiple records ( templates )
while ( size_left ) {
uint32_t id, count;
// map next record.
ipfix_template_record = (ipfix_template_record_t *)DataPtr;
size_left -= 4;
id = ntohs(ipfix_template_record->TemplateID);
count = ntohs(ipfix_template_record->FieldCount);
if ( id == IPFIX_TEMPLATE_FLOWSET_ID ) {
// withdraw all templates
remove_all_translation_tables(exporter);
ReInitExtensionMapList(fs);
} else {
remove_translation_table(fs, exporter, id);
}
DataPtr = DataPtr + 4;
if ( size_left < 4 ) {
// pading
dbg_printf("Skip %u bytes padding\n", size_left);
size_left = 0;
}
}
} // End of Process_ipfix_template_withdraw
static inline void Process_ipfix_option_templates(exporter_ipfix_domain_t *exporter, void *option_template_flowset, FlowSource_t *fs) {
void *DataPtr;
uint32_t size_left, size_required, i;
// uint32_t nr_scopes, nr_options;
uint16_t id, field_count, scope_field_count, offset, sampler_id_length;
uint16_t offset_sampler_id, offset_sampler_mode, offset_sampler_interval, found_sampler;
uint16_t offset_std_sampler_interval, offset_std_sampler_algorithm, found_std_sampling;
i = 0; // keep compiler happy
size_left = GET_FLOWSET_LENGTH(option_template_flowset) - 4; // -4 for flowset header -> id and length
if ( size_left < 6 ) {
syslog(LOG_ERR, "Process_ipfix: [%u] option template length error: size left %u too small for an options template",
exporter->info.id, size_left);
return;
}
DataPtr = option_template_flowset + 4;
id = GET_OPTION_TEMPLATE_ID(DataPtr);
field_count = GET_OPTION_TEMPLATE_FIELD_COUNT(DataPtr);
scope_field_count = GET_OPTION_TEMPLATE_SCOPE_FIELD_COUNT(DataPtr);
DataPtr += 6;
size_left -= 6;
if ( scope_field_count == 0 ) {
syslog(LOG_ERR, "Process_ipfx: [%u] scope field count error: length must not be zero",
exporter->info.id);
dbg_printf("scope field count error: length must not be zero\n");
return;
}
size_required = field_count * 2 * sizeof(uint16_t);
dbg_printf("Size left: %u, size required: %u\n", size_left, size_required);
if ( size_left < size_required ) {
syslog(LOG_ERR, "Process_ipfix: [%u] option template length error: size left %u too small for %u scopes length and %u options length",
exporter->info.id, size_left, field_count, scope_field_count);
dbg_printf("option template length error: size left %u too small for field_count %u\n",
size_left, field_count);
return;
}
dbg_printf("Decode Option Template. id: %u, field count: %u, scope field count: %u\n",
id, field_count, scope_field_count);
if ( scope_field_count == 0 ) {
syslog(LOG_ERR, "Process_ipfxi: [%u] scope field count error: length must not be zero",
exporter->info.id);
return;
}
for ( i=0; i<scope_field_count; i++ ) {
uint32_t enterprise_value;
uint16_t id, length;
int Enterprise;
id = Get_val16(DataPtr); DataPtr += 2;
length = Get_val16(DataPtr); DataPtr += 2;
Enterprise = id & 0x8000 ? 1 : 0;
if ( Enterprise ) {
size_required += 4;
dbg_printf("Adjusted: Size left: %u, size required: %u\n", size_left, size_required);
if ( size_left < size_required ) {
syslog(LOG_ERR, "Process_ipfix: [%u] option template length error: size left %u too small for %u scopes length and %u options length",
exporter->info.id, size_left, field_count, scope_field_count);
dbg_printf("option template length error: size left %u too small for field_count %u\n",
size_left, field_count);
return;
}
enterprise_value = Get_val32(DataPtr);
DataPtr += 4;
dbg_printf(" [%i] Enterprise: 1, scope id: %u, scope length %u enterprise value: %u\n",
i, id, length, enterprise_value);
} else {
dbg_printf(" [%i] Enterprise: 0, scope id: %u, scope length %u\n", i, id, length);
}
}
for ( ;i<field_count; i++ ) {
uint32_t enterprise_value;
uint16_t id, length;
int Enterprise;
id = Get_val16(DataPtr); DataPtr += 2;
length = Get_val16(DataPtr); DataPtr += 2;
Enterprise = id & 0x8000 ? 1 : 0;
if ( Enterprise ) {
size_required += 4;
dbg_printf("Adjusted: Size left: %u, size required: %u\n", size_left, size_required);
if ( size_left < size_required ) {
syslog(LOG_ERR, "Process_ipfix: [%u] option template length error: size left %u too small for %u scopes length and %u options length",
exporter->info.id, size_left, field_count, scope_field_count);
dbg_printf("option template length error: size left %u too small for field_count %u\n",
size_left, field_count);
return;
}
enterprise_value = Get_val32(DataPtr);
DataPtr += 4;
dbg_printf(" [%i] Enterprise: 1, option id: %u, option length %u enterprise value: %u\n",
i, id, length, enterprise_value);
} else {
dbg_printf(" [%i] Enterprise: 0, option id: %u, option length %u\n", i, id, length);
}
}
sampler_id_length = 0;
offset_sampler_id = 0;
offset_sampler_mode = 0;
offset_sampler_interval = 0;
offset_std_sampler_interval = 0;
offset_std_sampler_algorithm = 0;
found_sampler = 0;
found_std_sampling = 0;
offset = 0;
/* XXX
XXX Sampling for IPFIX not yet implemented due to lack of data and information
switch (type) {
// general sampling
case NF9_SAMPLING_INTERVAL:
offset_std_sampler_interval = offset;
found_std_sampling++;
break;
case NF9_SAMPLING_ALGORITHM:
offset_std_sampler_algorithm = offset;
found_std_sampling++;
break;
// individual samplers
case NF9_FLOW_SAMPLER_ID:
offset_sampler_id = offset;
sampler_id_length = length;
found_sampler++;
break;
case FLOW_SAMPLER_MODE:
offset_sampler_mode = offset;
found_sampler++;
break;
case NF9_FLOW_SAMPLER_RANDOM_INTERVAL:
offset_sampler_interval = offset;
found_sampler++;
break;
}
offset += length;
if ( found_sampler == 3 ) { // need all three tags
dbg_printf("[%u] Sampling information found\n", exporter->info.id);
InsertSamplerOffset(fs, id, offset_sampler_id, sampler_id_length, offset_sampler_mode, offset_sampler_interval);
} else if ( found_std_sampling == 2 ) { // need all two tags
dbg_printf("[%u] Std sampling information found\n", exporter->info.id);
InsertStdSamplerOffset(fs, id, offset_std_sampler_interval, offset_std_sampler_algorithm);
} else {
dbg_printf("[%u] No Sampling information found\n", exporter->info.id);
}
*/
dbg_printf("\n");
processed_records++;
} // End of Process_ipfix_option_templates
static inline void Process_ipfix_data(exporter_ipfix_domain_t *exporter, void *data_flowset, FlowSource_t *fs, input_translation_t *table ){
uint64_t sampling_rate;
uint32_t size_left;
uint8_t *in, *out;
int i;
char *string;
size_left = GET_FLOWSET_LENGTH(data_flowset) - 4; // -4 for data flowset header -> id and length
// map input buffer as a byte array
in = (uint8_t *)(data_flowset + 4); // skip flowset header
dbg_printf("[%u] Process data flowset size: %u\n", exporter->info.id, size_left);
// Check if sampling is announced
sampling_rate = 1;
/* ###
if ( table->sampler_offset && fs->sampler ) {
uint32_t sampler_id;
if ( table->sampler_size == 2 ) {
sampler_id = Get_val16((void *)&in[table->sampler_offset]);
} else {
sampler_id = in[table->sampler_offset];
}
if ( fs->sampler[sampler_id] ) {
sampling_rate = fs->sampler[sampler_id]->interval;
dbg_printf("[%u] Sampling ID %u available\n", exporter->info.id, sampler_id);
dbg_printf("[%u] Sampler_offset : %u\n", exporter->info.id, table->sampler_offset);
dbg_printf("[%u] Sampler Data : %s\n", exporter->info.id, fs->sampler == NULL ? "not available" : "available");
dbg_printf("[%u] Sampling rate: %llu\n", exporter->info.id, (long long unsigned)sampling_rate);
} else {
sampling_rate = default_sampling;
dbg_printf("[%u] Sampling ID %u not (yet) available\n", exporter->info.id, sampler_id);
}
} else if ( fs->std_sampling.interval > 0 ) {
sampling_rate = fs->std_sampling.interval;
dbg_printf("[%u] Std sampling available for this flow source: Rate: %llu\n", exporter->info.id, (long long unsigned)sampling_rate);
} else {
sampling_rate = default_sampling;
dbg_printf("[%u] No Sampling record found\n", exporter->info.id);
}
### */
if ( overwrite_sampling > 0 ) {
sampling_rate = overwrite_sampling;
dbg_printf("[%u] Hard overwrite sampling rate: %llu\n", exporter->info.id, (long long unsigned)sampling_rate);
}
if ( sampling_rate != 1 )
SetFlag(table->flags, FLAG_SAMPLED);
while (size_left) {
common_record_t *data_record;
if ( (size_left < table->input_record_size) ) {
if ( size_left > 3 ) {
syslog(LOG_WARNING,"Process_ipfix: Corrupt data flowset? Pad bytes: %u", size_left);
dbg_printf("Process_ipfix: Corrupt data flowset? Pad bytes: %u, table record_size: %u\n",
size_left, table->input_record_size);
}
size_left = 0;
continue;
}
// check for enough space in output buffer
if ( !CheckBufferSpace(fs->nffile, table->output_record_size) ) {
// this should really never occur, because the buffer gets flushed ealier
syslog(LOG_ERR,"Process_ipfix: output buffer size error. Abort ipfix record processing");
dbg_printf("Process_ipfix: output buffer size error. Abort ipfix record processing");
return;
}
processed_records++;
exporter->PacketSequence++;
// map file record to output buffer
data_record = (common_record_t *)fs->nffile->buff_ptr;
// map output buffer as a byte array
out = (uint8_t *)data_record;
dbg_printf("[%u] Process data record: %u addr: %llu, in record size: %u, buffer size_left: %u\n",
exporter->info.id, processed_records, (long long unsigned)((ptrdiff_t)in - (ptrdiff_t)data_flowset),
table->input_record_size, size_left);
// fill the data record
data_record->flags = table->flags;
data_record->size = table->output_record_size;
data_record->type = CommonRecordType;
data_record->ext_map = table->extension_info.map->map_id;
data_record->exporter_sysid = exporter->info.sysid;
data_record->reserved = 0;
table->flow_start = 0;
table->flow_end = 0;
table->packets = 0;
table->bytes = 0;
table->out_packets = 0;
table->out_bytes = 0;
// apply copy and processing sequence
for ( i=0; i<table->number_of_sequences; i++ ) {
int input_offset = table->sequence[i].input_offset;
int output_offset = table->sequence[i].output_offset;
void *stack = table->sequence[i].stack;
switch (table->sequence[i].id) {
case nop:
break;
case move8:
out[output_offset] = in[input_offset];
break;
case move16:
*((uint16_t *)&out[output_offset]) = Get_val16((void *)&in[input_offset]);
break;
case move32:
*((uint32_t *)&out[output_offset]) = Get_val32((void *)&in[input_offset]);
break;
case move40:
/* 64bit access to potentially unaligned output buffer. use 2 x 32bit for _LP64 CPUs */
{ type_mask_t t;
t.val.val64 = Get_val40((void *)&in[input_offset]);
*((uint32_t *)&out[output_offset]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+4]) = t.val.val32[1];
}
break;
case move48:
/* 64bit access to potentially unaligned output buffer. use 2 x 32bit for _LP64 CPUs */
{ type_mask_t t;
t.val.val64 = Get_val48((void *)&in[input_offset]);
*((uint32_t *)&out[output_offset]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+4]) = t.val.val32[1];
}
break;
case move56:
/* 64bit access to potentially unaligned output buffer. use 2 x 32bit for _LP64 CPUs */
{ type_mask_t t;
t.val.val64 = Get_val56((void *)&in[input_offset]);
*((uint32_t *)&out[output_offset]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+4]) = t.val.val32[1];
}
break;
case move64:
{ type_mask_t t;
t.val.val64 = Get_val64((void *)&in[input_offset]);
*((uint32_t *)&out[output_offset]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+4]) = t.val.val32[1];
} break;
case move128:
/* 64bit access to potentially unaligned output buffer. use 2 x 32bit for _LP64 CPUs */
{ type_mask_t t;
t.val.val64 = Get_val64((void *)&in[input_offset]);
*((uint32_t *)&out[output_offset]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+4]) = t.val.val32[1];
t.val.val64 = Get_val64((void *)&in[input_offset+8]);
*((uint32_t *)&out[output_offset+8]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+12]) = t.val.val32[1];
} break;
case move32_sampling:
/* 64bit access to potentially unaligned output buffer. use 2 x 32bit for _LP64 CPUs */
{ type_mask_t t;
t.val.val64 = Get_val32((void *)&in[input_offset]);
t.val.val64 *= sampling_rate;
*((uint32_t *)&out[output_offset]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+4]) = t.val.val32[1];
*(uint64_t *)stack = t.val.val64;
} break;
case move64_sampling:
/* 64bit access to potentially unaligned output buffer. use 2 x 32bit for _LP64 CPUs */
{ type_mask_t t;
t.val.val64 = Get_val64((void *)&in[input_offset]);
t.val.val64 *= sampling_rate;
*((uint32_t *)&out[output_offset]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+4]) = t.val.val32[1];
*(uint64_t *)stack = t.val.val64;
} break;
case Time64Mili:
{ uint64_t DateMiliseconds = Get_val64((void *)&in[input_offset]);
*(uint64_t *)stack = DateMiliseconds;
} break;
case move_mac:
/* 64bit access to potentially unaligned output buffer. use 2 x 32bit for _LP64 CPUs */
{ type_mask_t t;
t.val.val64 = Get_val48((void *)&in[input_offset]);
*((uint32_t *)&out[output_offset]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+4]) = t.val.val32[1];
}
break;
case zero8:
out[output_offset] = 0;
break;
case zero16:
*((uint16_t *)&out[output_offset]) = 0;
break;
case zero32:
*((uint32_t *)&out[output_offset]) = 0;
break;
case zero64:
*((uint64_t *)&out[output_offset]) = 0;
break;
case zero128:
*((uint64_t *)&out[output_offset]) = 0;
*((uint64_t *)&out[output_offset+8]) = 0;
break;
default:
syslog(LOG_ERR, "Process_ipfix: Software bug! Unknown Sequence: %u. at %s line %d",
table->sequence[i].id, __FILE__, __LINE__);
dbg_printf("Software bug! Unknown Sequence: %u. at %s line %d\n",
table->sequence[i].id, __FILE__, __LINE__);
}
}
// for netflow historical reason, ICMP type/code goes into dst port field
if ( data_record->prot == IPPROTO_ICMP || data_record->prot == IPPROTO_ICMPV6 ) {
if ( table->ICMP_offset ) {
data_record->srcport = 0;
data_record->dstport = Get_val16((void *)&in[table->ICMP_offset]);
}
}
// check, if we need to store the packet received time
if ( table->received_offset ) {
type_mask_t t;
t.val.val64 = (uint64_t)((uint64_t)fs->received.tv_sec * 1000LL) + (uint64_t)((uint64_t)fs->received.tv_usec / 1000LL);
*((uint32_t *)&out[table->received_offset]) = t.val.val32[0];
*((uint32_t *)&out[table->received_offset+4]) = t.val.val32[1];
}
// split first/last time into epoch/msec values
data_record->first = table->flow_start / 1000;
data_record->msec_first = table->flow_start % 1000;
data_record->last = table->flow_end / 1000;
data_record->msec_last = table->flow_end % 1000;
// update first_seen, last_seen
if ( table->flow_start < fs->first_seen )
fs->first_seen = table->flow_start;
if ( table->flow_end > fs->last_seen )
fs->last_seen = table->flow_end;
// check if we need to record the router IP address
if ( table->router_ip_offset ) {
int output_offset = table->router_ip_offset;
if ( exporter->info.sa_family == PF_INET6 ) {
// 64bit access to potentially unaligned output buffer. use 2 x 32bit for _LP64 CPUs
type_mask_t t;
t.val.val64 = exporter->info.ip.v6[0];
*((uint32_t *)&out[output_offset]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+4]) = t.val.val32[1];
t.val.val64 = exporter->info.ip.v6[1];
*((uint32_t *)&out[output_offset+8]) = t.val.val32[0];
*((uint32_t *)&out[output_offset+12]) = t.val.val32[1];
} else {
*((uint32_t *)&out[output_offset]) = exporter->info.ip.v4;
}
}
switch (data_record->prot ) { // switch protocol of
case IPPROTO_ICMP:
fs->nffile->stat_record->numflows_icmp++;
fs->nffile->stat_record->numpackets_icmp += table->packets;
fs->nffile->stat_record->numbytes_icmp += table->bytes;
fs->nffile->stat_record->numpackets_icmp += table->out_packets;
fs->nffile->stat_record->numbytes_icmp += table->out_bytes;
break;
case IPPROTO_TCP:
fs->nffile->stat_record->numflows_tcp++;
fs->nffile->stat_record->numpackets_tcp += table->packets;
fs->nffile->stat_record->numbytes_tcp += table->bytes;
fs->nffile->stat_record->numpackets_tcp += table->out_packets;
fs->nffile->stat_record->numbytes_tcp += table->out_bytes;
break;
case IPPROTO_UDP:
fs->nffile->stat_record->numflows_udp++;
fs->nffile->stat_record->numpackets_udp += table->packets;
fs->nffile->stat_record->numbytes_udp += table->bytes;
fs->nffile->stat_record->numpackets_udp += table->out_packets;
fs->nffile->stat_record->numbytes_udp += table->out_bytes;
break;
default:
fs->nffile->stat_record->numflows_other++;
fs->nffile->stat_record->numpackets_other += table->packets;
fs->nffile->stat_record->numbytes_other += table->bytes;
fs->nffile->stat_record->numpackets_other += table->out_packets;
fs->nffile->stat_record->numbytes_other += table->out_bytes;
}
exporter->flows++;
fs->nffile->stat_record->numflows++;
fs->nffile->stat_record->numpackets += table->packets;
fs->nffile->stat_record->numbytes += table->bytes;
fs->nffile->stat_record->numpackets += table->out_packets;
fs->nffile->stat_record->numbytes += table->out_bytes;
if ( fs->xstat ) {
uint32_t bpp = table->packets ? table->bytes/table->packets : 0;
if ( bpp > MAX_BPP )
bpp = MAX_BPP;
if ( data_record->prot == IPPROTO_TCP ) {
fs->xstat->bpp_histogram->tcp.bpp[bpp]++;
fs->xstat->bpp_histogram->tcp.count++;
fs->xstat->port_histogram->src_tcp.port[data_record->srcport]++;
fs->xstat->port_histogram->dst_tcp.port[data_record->dstport]++;
fs->xstat->port_histogram->src_tcp.count++;
fs->xstat->port_histogram->dst_tcp.count++;
} else if ( data_record->prot == IPPROTO_UDP ) {
fs->xstat->bpp_histogram->udp.bpp[bpp]++;
fs->xstat->bpp_histogram->udp.count++;
fs->xstat->port_histogram->src_udp.port[data_record->srcport]++;
fs->xstat->port_histogram->dst_udp.port[data_record->dstport]++;
fs->xstat->port_histogram->src_udp.count++;
fs->xstat->port_histogram->dst_udp.count++;
}
}
if ( verbose ) {
master_record_t master_record;
ExpandRecord_v2((common_record_t *)data_record, &(table->extension_info), &(exporter->info), &master_record);
format_file_block_record(&master_record, &string, 0);
printf("%s\n", string);
}
fs->nffile->block_header->size += data_record->size;
fs->nffile->block_header->NumRecords++;
fs->nffile->buff_ptr = (common_record_t *)((pointer_addr_t)data_record + data_record->size);
// advance input
size_left -= table->input_record_size;
in += table->input_record_size;
// buffer size sanity check
if ( fs->nffile->block_header->size > BUFFSIZE ) {
// should never happen
syslog(LOG_ERR,"### Software error ###: %s line %d", __FILE__, __LINE__);
syslog(LOG_ERR,"Process ipfix: Output buffer overflow! Flush buffer and skip records.");
syslog(LOG_ERR,"Buffer size: %u > %u", fs->nffile->block_header->size, BUFFSIZE);
// reset buffer
fs->nffile->block_header->size = 0;
fs->nffile->block_header->NumRecords = 0;
fs->nffile->buff_ptr = (void *)((pointer_addr_t)fs->nffile->block_header + sizeof(data_block_header_t) );
return;
}
}
} // End of Process_ipfix_data
void Process_IPFIX(void *in_buff, ssize_t in_buff_cnt, FlowSource_t *fs) {
exporter_ipfix_domain_t *exporter;
ssize_t size_left;
uint32_t ExportTime, ObservationDomain, Sequence, flowset_length;
ipfix_header_t *ipfix_header;
void *flowset_header;
#ifdef DEVEL
static uint32_t packet_cntr = 0;
#endif
size_left = in_buff_cnt;
if ( size_left < IPFIX_HEADER_LENGTH ) {
syslog(LOG_ERR, "Process_ipfix: Too little data for ipfix packet: '%lli'", (long long)size_left);
return;
}
ipfix_header = (ipfix_header_t *)in_buff;
ObservationDomain = ntohl(ipfix_header->ObservationDomain);
ExportTime = ntohl(ipfix_header->ExportTime);
Sequence = ntohl(ipfix_header->LastSequence);
exporter = GetExporter(fs, ipfix_header);
if ( !exporter ) {
syslog(LOG_ERR,"Process_ipfix: Exporter NULL: Abort ipfix record processing");
return;
}
exporter->packets++;
//exporter->PacketSequence = Sequence;
flowset_header = (void *)ipfix_header + IPFIX_HEADER_LENGTH;
size_left -= IPFIX_HEADER_LENGTH;
dbg_printf("\n[%u] Next packet: %u, exported: %s, TemplateRecords: %llu, DataRecords: %llu, buffer: %li \n",
ObservationDomain, packet_cntr++, UNIX2ISO(ExportTime), (long long unsigned)exporter->TemplateRecords,
(long long unsigned)exporter->DataRecords, size_left);
dbg_printf("[%u] Sequence: %u\n", ObservationDomain, Sequence);
// sequence check
// 2^32 wrap is handled automatically as both counters overflow
if ( Sequence != exporter->PacketSequence ) {
if ( exporter->DataRecords != 0 ) {
// sync sequence on first data record without error report
fs->nffile->stat_record->sequence_failure++;
exporter->sequence_failure++;
dbg_printf("[%u] Sequence check failed: last seq: %u, seq %u\n",
exporter->info.id, Sequence, exporter->PacketSequence);
/* maybee to noise onbuggy exporters
syslog(LOG_ERR, "Process_ipfix [%u] Sequence error: last seq: %u, seq %u\n",
info.id, exporter->LastSequence, Sequence);
*/
} else {
dbg_printf("[%u] Sync Sequence: %u\n", exporter->info.id, Sequence);
}
exporter->PacketSequence = Sequence;
} else {
dbg_printf("[%u] Sequence check ok\n", exporter->info.id);
}
// iterate over all set
flowset_length = 0;
while (size_left) {
uint16_t flowset_id;
flowset_header = flowset_header + flowset_length;
flowset_id = GET_FLOWSET_ID(flowset_header);
flowset_length = GET_FLOWSET_LENGTH(flowset_header);
dbg_printf("Process_ipfix: Next flowset %u, length %u.\n", flowset_id, flowset_length);
if ( flowset_length == 0 ) {
/* this should never happen, as 4 is an empty flowset
and smaller is an illegal flowset anyway ...
if it happends, we can't determine the next flowset, so skip the entire export packet
*/
syslog(LOG_ERR,"Process_ipfix: flowset zero length error.");
dbg_printf("Process_ipfix: flowset zero length error.\n");
return;
}
// possible padding
if ( flowset_length <= 4 ) {
size_left = 0;
continue;
}
if ( flowset_length > size_left ) {
syslog(LOG_ERR,"Process_ipfix: flowset length error. Expected bytes: %u > buffersize: %lli",
flowset_length, (long long)size_left);
size_left = 0;
continue;
}
switch (flowset_id) {
case IPFIX_TEMPLATE_FLOWSET_ID:
// Process_ipfix_templates(exporter, flowset_header, fs);
exporter->TemplateRecords++;
dbg_printf("Process template flowset, length: %u\n", flowset_length);
Process_ipfix_templates(exporter, flowset_header, flowset_length, fs);
break;
case IPFIX_OPTIONS_FLOWSET_ID:
// option_flowset = (option_template_flowset_t *)flowset_header;
exporter->TemplateRecords++;
dbg_printf("Process option template flowset, length: %u\n", flowset_length);
Process_ipfix_option_templates(exporter, flowset_header, fs);
break;
default: {
if ( flowset_id < IPFIX_MIN_RECORD_FLOWSET_ID ) {
dbg_printf("Invalid flowset id: %u. Skip flowset\n", flowset_id);
syslog(LOG_ERR,"Process_ipfix: Invalid flowset id: %u. Skip flowset", flowset_id);
} else {
input_translation_t *table;
dbg_printf("Process data flowset, length: %u\n", flowset_length);
table = GetTranslationTable(exporter, flowset_id);
if ( table ) {
Process_ipfix_data(exporter, flowset_header, fs, table);
exporter->DataRecords++;
} else if ( HasOptionTable(fs, flowset_id) ) {
// Process_ipfix_option_data(exporter, flowset_header, fs);
} else {
// maybe a flowset with option data
dbg_printf("Process ipfix: [%u] No table for id %u -> Skip record\n",
exporter->info.id, flowset_id);
}
}
}
} // End of switch
// next record
size_left -= flowset_length;
} // End of while
} // End of Process_IPFIX