Added extensive documentation about template update logic

This commit is contained in:
Pavel Odintsov 2023-07-03 17:55:27 +01:00
parent fa71ec7c59
commit b7aa639548

View File

@ -603,6 +603,20 @@ std::vector<system_counter_t> get_netflow_stats() {
return system_counter;
}
// Returns fancy name of protocol version
std::string get_netflow_protocol_version_as_string(const netflow_protocol_version_t& netflow_protocol_version) {
std::string protocol_name = "unknown";
if (netflow_protocol_version == netflow_protocol_version_t::netflow_v9) {
protocol_name = "Netflow v9";
} else if (netflow_protocol_version == netflow_protocol_version_t::ipfix) {
protocol_name = "IPFIX";
}
return protocol_name;
}
/* Prototypes */
void add_update_peer_template(const netflow_protocol_version_t& netflow_version,
std::map<std::string, std::map<uint32_t, template_t>>& table_for_add,
@ -727,84 +741,141 @@ void override_packet_fields_from_nested_packet(simple_packet_t& packet, const si
std::copy(std::begin(nested_packet.destination_mac), std::end(nested_packet.destination_mac), std::begin(packet.destination_mac));
}
// Wrapper functions
template_t* peer_nf9_find_template(uint32_t source_id, uint32_t template_id, std::string client_addres_in_string_format) {
return peer_find_template(global_netflow9_templates, source_id, template_id, client_addres_in_string_format);
}
template_t* peer_nf10_find_template(uint32_t source_id, uint32_t template_id, std::string client_addres_in_string_format) {
return peer_find_template(global_ipfix_templates, source_id, template_id, client_addres_in_string_format);
}
void add_update_peer_template(const netflow_protocol_version_t& netflow_version,
void add_update_peer_template(
const netflow_protocol_version_t& netflow_protocol_version,
std::map<std::string, std::map<uint32_t, template_t>>& table_for_add,
uint32_t source_id,
uint32_t template_id,
const std::string& client_addres_in_string_format,
const std::string& client_address_in_string_format,
const template_t& field_template,
bool& updated,
bool& updated_existing_template) {
std::string key = client_addres_in_string_format + "_" + std::to_string(source_id);
std::string key = client_address_in_string_format + "_" + std::to_string(source_id);
// logger<< log4cpp::Priority::INFO<<"It's new option template
// "<<template_id<<" for host:
// "<<client_addres_in_string_format
// <<" with source id: "<<source_id;
if (logger.getPriority() == log4cpp::Priority::DEBUG) {
logger << log4cpp::Priority::DEBUG << "Received " << get_netflow_protocol_version_as_string(netflow_protocol_version)
<< " template with id " << template_id << " from host " << client_address_in_string_format
<< " source id: " << source_id;
}
auto itr = table_for_add.find(key);
if (itr != table_for_add.end()) {
// We have information block about this agent
// Try to find actual template id here
if (itr->second.count(template_id) > 0) {
// logger<< log4cpp::Priority::INFO<<"We already have information about
// this template
// with id:"
// <<template_id<<" for host: "<<client_addres_in_string_format;
// Should I track timestamp here and drop old templates after some time?
if (itr->second[template_id] != field_template) {
itr->second[template_id] = field_template;
updated = true;
} else {
template_update_attempts_with_same_template_data++;
}
} else {
// logger<< log4cpp::Priority::INFO<<"It's new option template
// "<<template_id<<" for
// host: "<<client_addres_in_string_format;
itr->second[template_id] = field_template;
updated = true;
}
} else {
// We do not have any information about this Netflow agent
if (itr == table_for_add.end()) {
std::map<uint32_t, template_t> temp_template_storage;
temp_template_storage[template_id] = field_template;
table_for_add[key] = temp_template_storage;
updated = true;
if (logger.getPriority() == log4cpp::Priority::DEBUG) {
logger << log4cpp::Priority::DEBUG << "We had no "
<< get_netflow_protocol_version_as_string(netflow_protocol_version)
<< " templates for source " << key;
logger << log4cpp::Priority::DEBUG << "Added " << get_netflow_protocol_version_as_string(netflow_protocol_version)
<< " template with ID " << template_id << " for " << key;
}
return;
}
// We have information about this agent
// Try to find actual template id here
if (itr->second.count(template_id) == 0) {
if (logger.getPriority() == log4cpp::Priority::DEBUG) {
logger << log4cpp::Priority::DEBUG << "We had no information about "
<< get_netflow_protocol_version_as_string(netflow_protocol_version)
<< " template with ID " << template_id << " for " << key;
logger << log4cpp::Priority::DEBUG << "Added " << get_netflow_protocol_version_as_string(netflow_protocol_version)
<< " template with ID " << template_id << " for " << key;
}
itr->second[template_id] = field_template;
updated = true;
return;
}
// TODO: Should I track timestamp here and drop old templates after some time?
if (itr->second[template_id] != field_template) {
//
// We can see that template definition actually changed
//
// In case of IPFIX this is clear protocol violation:
// https://datatracker.ietf.org/doc/html/rfc7011#section-8.1
//
//
// If a Collecting Process receives a new Template Record or Options
// Template Record for an already-allocated Template ID, and that
// Template or Options Template is different from the already-received
// Template or Options Template, this indicates a malfunctioning or
// improperly implemented Exporting Process. The continued receipt and
// unambiguous interpretation of Data Records for this Template ID are
// no longer possible, and the Collecting Process SHOULD log the error.
// Further Collecting Process actions are out of scope for this
// specification.
//
//
// We cannot follow RFC recommendation for IPFIX as it will break our on disk template caching.
// I.e. we may have template with specific list of fields in cache
// Then after firmware upgrade vendor changes list of fields but does not change template id
// We have to accept new one and update to be able to decode data
//
//
// Netflow v9 explicitly prohibits template content updates: https://www.ietf.org/rfc/rfc3954.txt
//
// A newly created Template record is assigned an unused Template ID
// from the Exporter. If the template configuration is changed, the
// current Template ID is abandoned and SHOULD NOT be reused until the
// NetFlow process or Exporter restarts.
//
//
//
// But in same time Netflow v9 RFC allows template update for collector and that's exactly what we do:
//
// If a Collector should receive a new definition for an already existing Template ID, it MUST discard
// the previous template definition and use the new one.
//
// On debug level we have to print templates
if (logger.getPriority() == log4cpp::Priority::DEBUG) {
logger << log4cpp::Priority::DEBUG << "Old " << get_netflow_protocol_version_as_string(netflow_protocol_version)
<<" template: " << print_template(itr->second[template_id]);
logger << log4cpp::Priority::DEBUG << "New " << get_netflow_protocol_version_as_string(netflow_protocol_version)
<<" template: " << print_template(field_template);
}
// We use ERROR level as this behavior is definitely not a common and must be carefully investigated
logger << log4cpp::Priority::ERROR << get_netflow_protocol_version_as_string(netflow_protocol_version)
<< " template " << template_id << " was updated for " << key;
// Warn user that something bad going on
logger << log4cpp::Priority::ERROR << get_netflow_protocol_version_as_string(netflow_protocol_version)
<< " template update may be sign of RFC violation by vendor and if you observe this behaviour please reach support@fastnetmon.com and share information about your equipment and firmware versions";
itr->second[template_id] = field_template;
// We need to track this case as it's pretty unusual and in some cases it may be very destructive when router does it incorrectly
updated_existing_template = true;
updated = true;
} else {
template_update_attempts_with_same_template_data++;
}
return;
}
// Returns fancy name of protocol version
std::string get_netflow_protocol_version_as_string(const netflow_protocol_version_t& netflow_protocol_version) {
std::string protocol_name = "unknown";
if (netflow_protocol_version == netflow_protocol_version_t::netflow_v9) {
protocol_name = "Netflow v9";
} else if (netflow_protocol_version == netflow_protocol_version_t::ipfix) {
protocol_name = "IPFIX";
}
return protocol_name;
}
/* Copy an int (possibly shorter than the target) keeping their LSBs aligned */
#define BE_COPY(a) memcpy((u_char*)&a + (sizeof(a) - record_length), data, record_length);