526 lines
19 KiB
C++
526 lines
19 KiB
C++
#include "libsflow.h"
|
|
|
|
#include <sstream>
|
|
|
|
// log4cpp logging facility
|
|
#include "log4cpp/Appender.hh"
|
|
#include "log4cpp/BasicLayout.hh"
|
|
#include "log4cpp/Category.hh"
|
|
#include "log4cpp/FileAppender.hh"
|
|
#include "log4cpp/Layout.hh"
|
|
#include "log4cpp/OstreamAppender.hh"
|
|
#include "log4cpp/PatternLayout.hh"
|
|
#include "log4cpp/Priority.hh"
|
|
|
|
extern log4cpp::Category& logger;
|
|
|
|
std::string sflow_parser_log_prefix = "sflow_parser ";
|
|
|
|
// start copy and paste from fast_library
|
|
|
|
#if defined(__APPLE__)
|
|
#include <libkern/OSByteOrder.h>
|
|
// Source: https://gist.github.com/pavel-odintsov/d13684600423d1c5e64e
|
|
#define be64toh(x) OSSwapBigToHostInt64(x)
|
|
#define htobe64(x) OSSwapHostToBigInt64(x)
|
|
#endif
|
|
|
|
// For be64toh and htobe64
|
|
#if defined(__FreeBSD__) || defined(__DragonFly__)
|
|
#include <sys/endian.h>
|
|
#endif
|
|
|
|
// Type safe versions of ntohl, ntohs with type control
|
|
uint16_t strict_ntoh(uint16_t value) {
|
|
return ntohs(value);
|
|
}
|
|
|
|
uint32_t strict_ntoh(uint32_t value) {
|
|
return ntohl(value);
|
|
}
|
|
|
|
int32_t strict_ntoh(int32_t value) {
|
|
return ntohl(value);
|
|
}
|
|
|
|
// network (big endian) byte order to host byte order
|
|
uint64_t strict_ntoh(uint64_t value) {
|
|
return be64toh(value);
|
|
}
|
|
|
|
// Little endian (host) to network byte order conversion
|
|
uint16_t strict_hton(uint16_t value) {
|
|
return htons(value);
|
|
}
|
|
|
|
uint32_t strict_hton(uint32_t value) {
|
|
return htonl(value);
|
|
}
|
|
|
|
int32_t strict_hton(int32_t value) {
|
|
return htonl(value);
|
|
}
|
|
|
|
// end copy and paste
|
|
|
|
// Convert scoped enum to internal integer representation
|
|
unsigned int get_flow_enum_type_as_number(const sflow_sample_type_t& value) {
|
|
return static_cast<std::underlying_type<sflow_sample_type_t>::type>(value);
|
|
}
|
|
|
|
std::string build_ipv4_address_from_array(std::array<uint8_t, 4> ipv4_array_address) {
|
|
std::stringstream buffer;
|
|
|
|
for (int index = 0; index < 4; index++) {
|
|
buffer << int(ipv4_array_address[index]);
|
|
|
|
if (index + 1 != 4) {
|
|
buffer << ".";
|
|
}
|
|
}
|
|
|
|
return buffer.str();
|
|
}
|
|
|
|
std::string build_ipv6_address_from_array(std::array<uint8_t, 16> ipv6_array_address) {
|
|
std::stringstream buffer;
|
|
|
|
for (int index = 0; index < 16; index++) {
|
|
buffer << std::ios_base::hex << int(ipv6_array_address[index]);
|
|
|
|
if (index + 1 != 16) {
|
|
buffer << ":";
|
|
}
|
|
}
|
|
|
|
return buffer.str();
|
|
}
|
|
|
|
std::tuple<int32_t, int32_t> split_mixed_enterprise_and_format(int32_t enterprise_and_format) {
|
|
// Get first 20 bits as enterprise
|
|
int32_t enterprise = enterprise_and_format >> 12;
|
|
|
|
// Get last 12 bits
|
|
int32_t integer_format = enterprise_and_format & 0b00000000000000000000111111111111;
|
|
|
|
return std::make_tuple(enterprise, integer_format);
|
|
}
|
|
|
|
// Convert arbitrary flow record structure with record samples to well formed
|
|
// data
|
|
bool get_records(vector_tuple_t& vector_tuple,
|
|
uint8_t* flow_record_zone_start,
|
|
uint32_t number_of_flow_records,
|
|
uint8_t* current_packet_end,
|
|
bool& padding_found) {
|
|
uint8_t* flow_record_start = flow_record_zone_start;
|
|
|
|
for (int i = 0; i < number_of_flow_records; i++) {
|
|
// Check that we have at least 2 4 byte integers here
|
|
if (current_packet_end - flow_record_start < 8) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix
|
|
<< "do not have enough space in packet to read flow type and length";
|
|
return false;
|
|
}
|
|
|
|
int32_t element_type = get_int_value_by_32bit_shift(flow_record_start, 0);
|
|
int32_t element_length = get_int_value_by_32bit_shift(flow_record_start, 1);
|
|
|
|
// sFlow v5 standard does not constrain size of each sample but
|
|
// we need to apply some reasonable limits on this value to avoid possible integer overflows in boundary checks code below
|
|
// and I've decided to limit sample size by maximum UDP packet size
|
|
if (element_length > max_udp_packet_size) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "Element length " << element_length
|
|
<< " exceeds maximum allowed size: " << max_udp_packet_size;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
uint8_t* flow_record_data_ptr = flow_record_start + sizeof(element_type) + sizeof(element_length);
|
|
uint8_t* flow_record_end = flow_record_data_ptr + element_length;
|
|
|
|
if (flow_record_end > current_packet_end) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "flow record payload is outside packet bounds";
|
|
return false;
|
|
}
|
|
|
|
vector_tuple.push_back(std::make_tuple(element_type, flow_record_data_ptr, element_length));
|
|
|
|
flow_record_start = flow_record_end;
|
|
}
|
|
|
|
// Well, I do not think that we need this kind of check because it should be blocked in previous section but let's keep it
|
|
int64_t packet_padding = current_packet_end - flow_record_start;
|
|
|
|
if (packet_padding < 0) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "negative padding is not possible";
|
|
return false;
|
|
}
|
|
|
|
// Return information that we found padding. Just for information purposes
|
|
if (packet_padding != 0) {
|
|
padding_found = true;
|
|
}
|
|
|
|
/*
|
|
* I just discovered that Brocade devices (Brocade ICX6610) could add 4 byte pagging at the end of packet.
|
|
* So I see no reasons to return error here.
|
|
*/
|
|
|
|
return true;
|
|
}
|
|
|
|
// Convert arbitrary data structure with samples to vector with meta data and
|
|
// pointers to real data
|
|
bool get_all_samples(vector_sample_tuple_t& vector_sample,
|
|
uint8_t* samples_block_start,
|
|
uint8_t* total_packet_end,
|
|
int32_t samples_count,
|
|
bool& discovered_padding) {
|
|
uint8_t* sample_start = samples_block_start;
|
|
|
|
for (int i = 0; i < samples_count; i++) {
|
|
if (total_packet_end - sample_start < 8) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we haven't sample format and length information here";
|
|
return false;
|
|
}
|
|
|
|
int32_t enterprise_with_format = get_int_value_by_32bit_shift(sample_start, 0);
|
|
int32_t sample_length = get_int_value_by_32bit_shift(sample_start, 1);
|
|
|
|
// sFlow v5 standard does not constrain size of each sample but
|
|
// we need to apply some reasonable limits on this value to avoid possible integer overflows in boundary checks code below
|
|
// and I've decided to limit sample size by maximum UDP packet size
|
|
if (sample_length > max_udp_packet_size) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "Sample length " << sample_length
|
|
<< " exceeds maximum allowed size: " << max_udp_packet_size;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Get first 20 bits as enterprise
|
|
int32_t enterprise = enterprise_with_format >> 12;
|
|
|
|
// Get last 12 bits as format, zeroify first 20 bits
|
|
int32_t integer_format = enterprise_with_format & 0b00000000000000000000111111111111;
|
|
|
|
uint8_t* data_block_start = sample_start + sizeof(enterprise_with_format) + sizeof(sample_length);
|
|
// Skip format,length and data
|
|
uint8_t* this_sample_end = data_block_start + sample_length;
|
|
|
|
// Check sample bounds inside packet
|
|
if (this_sample_end > total_packet_end) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we have tried to read outside the packet";
|
|
|
|
return false;
|
|
}
|
|
|
|
vector_sample.push_back(std::make_tuple(enterprise, integer_format, data_block_start, sample_length));
|
|
|
|
// This sample end become next sample start
|
|
sample_start = this_sample_end;
|
|
}
|
|
|
|
// Sanity check! We should achieve end of whole packet in any case
|
|
// We discovered that Brocade MLXe-4 adds 20 bytes at the end of sflow packet and this check prevent FNM from
|
|
// correct work
|
|
// And I do not think that this change could harm other customers
|
|
if (sample_start != total_packet_end) {
|
|
// logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix
|
|
// << "We haven't acheived end of whole packed due to some reasons! "
|
|
// "Some samples skipped";
|
|
|
|
discovered_padding = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int32_t get_int_value_by_32bit_shift(uint8_t* payload_ptr, unsigned int shift) {
|
|
return strict_ntoh(*(int32_t*)(payload_ptr + shift * 4));
|
|
}
|
|
|
|
bool get_all_counter_records(counter_record_sample_vector_t& counter_record_sample_vector,
|
|
uint8_t* data_block_start,
|
|
uint8_t* data_block_end,
|
|
uint32_t number_of_records) {
|
|
|
|
uint8_t* record_start = data_block_start;
|
|
|
|
for (int i = 0; i < number_of_records; i++) {
|
|
uint8_t* payload_ptr = record_start + sizeof(uint32_t) + sizeof(uint32_t);
|
|
|
|
if (payload_ptr >= data_block_end) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we could not read flow counter record, too short packet";
|
|
return false;
|
|
}
|
|
|
|
int32_t enterprise_and_format = get_int_value_by_32bit_shift(record_start, 0);
|
|
uint32_t record_length = get_int_value_by_32bit_shift(record_start, 1);
|
|
|
|
// sFlow v5 standard does not constrain size of each sample but
|
|
// we need to apply some reasonable limits on this value to avoid possible integer overflows in boundary checks code below
|
|
// and I've decided to limit sample size by maximum UDP packet size
|
|
if (record_length > max_udp_packet_size) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "Record length " << record_length
|
|
<< " exceeds maximum allowed size: " << max_udp_packet_size;
|
|
|
|
return false;
|
|
}
|
|
|
|
uint8_t* current_record_end = payload_ptr + record_length;
|
|
|
|
if (current_record_end > data_block_end) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "record payload is outside of record border";
|
|
return false;
|
|
}
|
|
|
|
int32_t enterprise = 0;
|
|
int32_t integer_format = 0;
|
|
|
|
std::tie(enterprise, integer_format) = split_mixed_enterprise_and_format(enterprise_and_format);
|
|
|
|
// std::cout << "enterprise: " << enterprise << " integer_format: " <<
|
|
// integer_format <<
|
|
// std::endl;
|
|
|
|
counter_record_sample_vector.push_back(std::make_tuple(enterprise, integer_format, record_length, payload_ptr));
|
|
|
|
record_start = current_record_end;
|
|
}
|
|
|
|
if (record_start != data_block_end) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix
|
|
<< "we haven't read whole packet in counter record: " << record_start - data_block_end;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
sflow_sample_type_t sflow_sample_type_from_integer(int32_t format_as_integer) {
|
|
if (format_as_integer < get_flow_enum_type_as_number(sflow_sample_type_t::FLOW_SAMPLE) ||
|
|
format_as_integer > get_flow_enum_type_as_number(sflow_sample_type_t::EXPANDED_COUNTER_SAMPLE)) {
|
|
|
|
return sflow_sample_type_t::BROKEN_TYPE;
|
|
}
|
|
|
|
return static_cast<sflow_sample_type_t>(format_as_integer);
|
|
}
|
|
|
|
bool read_sflow_header(uint8_t* payload_ptr, unsigned int payload_length, sflow_packet_header_unified_accessor& sflow_header_accessor) {
|
|
// zero sized packet
|
|
if (payload_ptr == NULL || payload_length == 0) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "zero sized packet could not be parsed";
|
|
return false;
|
|
}
|
|
|
|
// Calculate packet end, it's too useful for sanity checks
|
|
uint8_t* total_packet_end = payload_ptr + payload_length;
|
|
|
|
// if received packet is smaller than smallest possible header size
|
|
if (payload_length < sizeof(sflow_packet_header_v4_t)) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "received packet too small. It shorter than sFlow header";
|
|
return false;
|
|
}
|
|
|
|
int32_t sflow_version = get_int_value_by_32bit_shift(payload_ptr, 0);
|
|
|
|
if (sflow_version != 5) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "We do not support sFLOW version " << sflow_version;
|
|
return false;
|
|
}
|
|
|
|
int32_t ip_protocol_version = get_int_value_by_32bit_shift(payload_ptr, 1);
|
|
|
|
// Reject broken protocol numbers
|
|
if (!(ip_protocol_version == 1 or ip_protocol_version == 2)) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "Unknown ip protocol version for sflow: " << ip_protocol_version;
|
|
return false;
|
|
}
|
|
|
|
if (ip_protocol_version == 1) {
|
|
// IPv4
|
|
sflow_packet_header_v4_t sflow_v4_header_struct;
|
|
memcpy(&sflow_v4_header_struct, payload_ptr, sizeof(sflow_v4_header_struct));
|
|
|
|
// Convert all 32 bit values from network byte order to host byte order
|
|
sflow_v4_header_struct.network_to_host_byte_order();
|
|
|
|
// sflow_v4_header_struct.print();
|
|
|
|
sflow_header_accessor = sflow_v4_header_struct;
|
|
} else if (ip_protocol_version == 2) {
|
|
// IPv6
|
|
|
|
// Check for packet length
|
|
if (payload_length < sizeof(sflow_packet_header_v6_t)) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "received packet too small for IPv6 sflow packet.";
|
|
return false;
|
|
}
|
|
|
|
sflow_packet_header_v6_t sflow_v6_header_struct;
|
|
memcpy(&sflow_v6_header_struct, payload_ptr, sizeof(sflow_v6_header_struct));
|
|
|
|
sflow_v6_header_struct.network_to_host_byte_order();
|
|
|
|
// Create unified accessor format
|
|
sflow_header_accessor = sflow_v6_header_struct;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string print_counter_record_sample_vector(counter_record_sample_vector_t counter_record_sample_vector) {
|
|
std::stringstream buffer;
|
|
|
|
int index = 0;
|
|
for (auto counter_record_sample : counter_record_sample_vector) {
|
|
buffer << "index: " << index << " enterprise: " << std::get<0>(counter_record_sample)
|
|
<< " format: " << std::get<1>(counter_record_sample) << " length: "
|
|
<< std::get<2>(counter_record_sample)
|
|
//<< " pointer: " << (void*)std::get<3>(counter_record_sample);
|
|
<< " pointer: "
|
|
<< "XXX";
|
|
|
|
index++;
|
|
|
|
if (counter_record_sample_vector.size() != index) {
|
|
buffer << ",";
|
|
}
|
|
}
|
|
|
|
return buffer.str();
|
|
}
|
|
|
|
std::string print_vector_sample_tuple(vector_sample_tuple_t vector_sample_tuple) {
|
|
std::stringstream buffer;
|
|
|
|
int index = 0;
|
|
for (auto sample_tuple : vector_sample_tuple) {
|
|
buffer << "index: " << index << " enterprise: " << std::get<0>(sample_tuple) << " format: "
|
|
<< std::get<1>(sample_tuple)
|
|
//<< " pointer: " << (void*)std::get<2>(sample_tuple)
|
|
<< " pointer: "
|
|
<< "XXX"
|
|
<< " length: " << std::get<3>(sample_tuple);
|
|
|
|
index++;
|
|
|
|
if (vector_sample_tuple.size() != index) {
|
|
buffer << ",";
|
|
}
|
|
}
|
|
|
|
return buffer.str();
|
|
}
|
|
|
|
bool read_sflow_counter_header(uint8_t* data_pointer,
|
|
size_t data_length,
|
|
bool expanded,
|
|
sflow_counter_header_unified_accessor_t& sflow_counter_header_unified_accessor) {
|
|
|
|
if (expanded) {
|
|
// Expanded format
|
|
|
|
if (data_length < sizeof(sflow_counter_expanded_header_t)) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "could not read counter_sample reader, too short packet";
|
|
return false;
|
|
}
|
|
|
|
sflow_counter_expanded_header_t sflow_counter_expanded_header;
|
|
memcpy(&sflow_counter_expanded_header, data_pointer, sizeof(sflow_counter_expanded_header_t));
|
|
|
|
sflow_counter_expanded_header.network_to_host_byte_order();
|
|
// sflow_counter_expanded_header.print();
|
|
|
|
sflow_counter_header_unified_accessor = sflow_counter_expanded_header;
|
|
} else {
|
|
// Not expanded format
|
|
|
|
if (data_length < sizeof(sflow_counter_header_t)) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "could not read counter_sample reader, too short packet";
|
|
return false;
|
|
}
|
|
|
|
sflow_counter_header_t sflow_counter_header;
|
|
memcpy(&sflow_counter_header, data_pointer, sizeof(sflow_counter_header_t));
|
|
|
|
sflow_counter_header.network_to_host_byte_order();
|
|
// sflow_counter_header.print();
|
|
|
|
sflow_counter_header_unified_accessor = sflow_counter_header;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::tuple<uint32_t, uint32_t> split_32bit_integer_by_8_and_24_bits(uint32_t original_data) {
|
|
uint32_t extracted_8bit_data = original_data >> 24;
|
|
uint32_t extracted_24_bit_data = original_data & 0x0fffffff;
|
|
|
|
return std::make_tuple(extracted_8bit_data, extracted_24_bit_data);
|
|
}
|
|
|
|
std::tuple<uint32_t, uint32_t> split_32bit_integer_by_2_and_30_bits(uint32_t original_data) {
|
|
uint32_t extracted_2bit_data = original_data >> 30;
|
|
uint32_t extracted_30bit_data = original_data & 0b00111111111111111111111111111111;
|
|
|
|
return std::make_tuple(extracted_2bit_data, extracted_30bit_data);
|
|
}
|
|
|
|
bool read_sflow_sample_header_unified(sflow_sample_header_unified_accessor_t& sflow_sample_header_unified_accessor,
|
|
uint8_t* data_pointer,
|
|
size_t data_length,
|
|
bool expanded) {
|
|
|
|
if (expanded) {
|
|
if (data_length < sizeof(sflow_sample_expanded_header_t)) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we have so short packet for FLOW_SAMPLE";
|
|
return false;
|
|
}
|
|
|
|
sflow_sample_expanded_header_t sflow_sample_expanded_header;
|
|
memcpy(&sflow_sample_expanded_header, data_pointer, sizeof(sflow_sample_expanded_header_t));
|
|
sflow_sample_expanded_header.network_to_host_byte_order();
|
|
|
|
sflow_sample_header_unified_accessor = sflow_sample_expanded_header;
|
|
} else {
|
|
// So short data block length
|
|
if (data_length < sizeof(sflow_sample_header_t)) {
|
|
logger << log4cpp::Priority::ERROR << sflow_parser_log_prefix << "we have so short packet for FLOW_SAMPLE";
|
|
return false;
|
|
}
|
|
|
|
sflow_sample_header_t flow_sample_header;
|
|
memcpy(&flow_sample_header, data_pointer, sizeof(flow_sample_header));
|
|
flow_sample_header.network_to_host_byte_order();
|
|
|
|
sflow_sample_header_unified_accessor = flow_sample_header;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string print_vector_tuple(vector_tuple_t vector_tuple) {
|
|
std::stringstream buffer;
|
|
|
|
int index = 0;
|
|
|
|
for (record_tuple_t record_tuple : vector_tuple) {
|
|
|
|
buffer << "index: " << index << " "
|
|
<< "type: " << std::get<0>(record_tuple) << " "
|
|
<< "length: " << std::get<2>(record_tuple);
|
|
|
|
index++;
|
|
|
|
if (vector_tuple.size() != index) {
|
|
buffer << ",";
|
|
}
|
|
}
|
|
|
|
return buffer.str();
|
|
}
|