1
0
mirror of https://github.com/pavel-odintsov/fastnetmon synced 2024-11-23 09:12:14 +01:00

Added new BGP Flow Spec native logic

This commit is contained in:
Pavel Odintsov 2024-06-05 01:53:22 +03:00
parent b590031f5a
commit 1b716fbbf8
3 changed files with 1659 additions and 1 deletions

@ -191,6 +191,7 @@ set(FASTNETMON_NOTIFY_SCRIPT_PATH_DEFAULT "/usr/local/bin/notify_about_attack.sh
set(FASTNETMON_NETWORK_WHITELIST_PATH "/etc/networks_whitelist")
set(FASTNETMON_NETWORKS_LIST_PATH "/etc/networks_list")
set(FASTNETMON_BACKTRACE_PATH "/var/log/fastnetmon_backtrace.dump")
set(FASTNETMON_WHITELIST_RULES_PATH "/etc/whitelist_rules")
# For FreeBSD based platforms we need to adjust them
if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly")
@ -347,6 +348,13 @@ add_library(ipfix_rfc STATIC ipfix_fields/ipfix_rfc.cpp)
add_library(bgp_protocol STATIC bgp_protocol.cpp)
# Here we store some service code for getting IP protocol name by number
add_library(iana_ip_protocols STATIC iana_ip_protocols.cpp)
# BGP Flow Spec
add_library(bgp_protocol_flow_spec STATIC bgp_protocol_flow_spec.cpp)
target_link_libraries(bgp_protocol_flow_spec iana_ip_protocols bgp_protocol)
# Our logic library
add_library(fastnetmon_logic STATIC fastnetmon_logic.cpp)
@ -944,7 +952,7 @@ target_link_libraries(fastnetmon fastnetmon_pcap_format)
target_link_libraries(fastnetmon ipfix_rfc)
target_link_libraries(fastnetmon_logic bgp_protocol exabgp_action)
target_link_libraries(fastnetmon_logic bgp_protocol bgp_protocol_flow_spec exabgp_action)
target_link_libraries(fastnetmon_logic protobuf_traffic_format)

@ -0,0 +1,909 @@
#include "bgp_protocol.hpp"
#include <iostream>
#include "fast_library.hpp"
// inet_ntoa
#include "network_data_structures.hpp"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <cmath>
#include "nlohmann/json.hpp"
#include "bgp_protocol_flow_spec.hpp"
bool read_flow_spec_fragmentation_types_from_string(const std::string& string_form, flow_spec_fragmentation_types_t& fragment_flag) {
// Unify case for better experience with this function
std::string string_form_lowercase = boost::algorithm::to_lower_copy(string_form);
if (string_form_lowercase == "dont-fragment") {
fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_DONT_FRAGMENT;
} else if (string_form_lowercase == "is-fragment") {
fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_IS_A_FRAGMENT;
} else if (string_form_lowercase == "first-fragment") {
fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_FIRST_FRAGMENT;
} else if (string_form_lowercase == "last-fragment") {
fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_LAST_FRAGMENT;
} else if (string_form_lowercase == "not-a-fragment") {
fragment_flag = flow_spec_fragmentation_types_t::FLOW_SPEC_NOT_A_FRAGMENT;
} else {
return false;
}
return true;
}
std::string flow_spec_fragmentation_flags_to_string(flow_spec_fragmentation_types_t const& fragment_flag) {
// https://github.com/Exa-Networks/exabgp/blob/71157d560096ec20084cf96cfe0f60203721e93b/lib/exabgp/protocol/ip/fragment.py
if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_DONT_FRAGMENT) {
return "dont-fragment";
} else if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_IS_A_FRAGMENT) {
return "is-fragment";
} else if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_FIRST_FRAGMENT) {
return "first-fragment";
} else if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_LAST_FRAGMENT) {
return "last-fragment";
} else if (fragment_flag == flow_spec_fragmentation_types_t::FLOW_SPEC_NOT_A_FRAGMENT) {
return "not-a-fragment";
} else {
return "";
}
}
bool read_flow_spec_action_type_from_string(const std::string& string_form, bgp_flow_spec_action_types_t& action_type) {
if (string_form == "accept") {
action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_ACCEPT;
} else if (string_form == "discard") {
action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_DISCARD;
} else if (string_form == "rate-limit") {
action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT;
} else if (string_form == "redirect") {
action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT;
} else if (string_form == "mark") {
action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_MARK;
} else {
return false;
}
return true;
}
std::string serialize_action_type(const bgp_flow_spec_action_types_t& action_type) {
if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_ACCEPT) {
return "accept";
} else if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_DISCARD) {
return "discard";
} else if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT) {
return "rate-limit";
} else if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT) {
return "redirect";
} else if (action_type == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_MARK) {
return "mark";
} else {
// TODO: add return code for notifying about this case
return std::string("");
}
}
bool read_flow_spec_tcp_flags_from_strig(const std::string& string_form, flow_spec_tcp_flagset_t& flagset) {
// Unify case for better experience with this function
std::string string_form_lowercase = boost::algorithm::to_lower_copy(string_form);
std::vector<std::string> tcp_flags;
// Split line by "|"
boost::split(tcp_flags, string_form_lowercase, boost::is_any_of("|"), boost::token_compress_on);
for (auto tcp_flag_string : tcp_flags) {
if (tcp_flag_string == "syn") {
flagset.syn_flag = true;
} else if (tcp_flag_string == "ack") {
flagset.ack_flag = true;
} else if (tcp_flag_string == "fin") {
flagset.fin_flag = true;
} else if (tcp_flag_string == "urgent") {
flagset.urg_flag = true;
} else if (tcp_flag_string == "push") {
flagset.psh_flag = true;
} else if (tcp_flag_string == "rst") {
flagset.rst_flag = true;
} else {
return false;
}
}
return true;
}
std::string flow_spec_tcp_flagset_to_string(flow_spec_tcp_flagset_t const& tcp_flagset) {
std::vector<std::string> output;
if (tcp_flagset.syn_flag) {
output.push_back("syn");
}
if (tcp_flagset.ack_flag) {
output.push_back("ack");
}
if (tcp_flagset.fin_flag) {
output.push_back("fin");
}
if (tcp_flagset.rst_flag) {
output.push_back("rst");
}
if (tcp_flagset.urg_flag) {
output.push_back("urgent");
}
if (tcp_flagset.psh_flag) {
output.push_back("push");
}
return boost::algorithm::join(output, "|");
}
bool operator==(const bgp_flow_spec_action_t& lhs, const bgp_flow_spec_action_t& rhs) {
if (lhs.get_type() != rhs.get_type()) {
return false;
}
// Action types are equal
if (lhs.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT) {
return lhs.get_rate_limit() == rhs.get_rate_limit();
} else {
return true;
}
}
bool operator!=(const bgp_flow_spec_action_t& lhs, const bgp_flow_spec_action_t& rhs) {
return !(lhs == rhs);
}
// It does not check UUID
bool operator==(const flow_spec_rule_t& lhs, const flow_spec_rule_t& rhs) {
// Compare source subnets
// IPv4
if (lhs.source_subnet_ipv4_used != rhs.source_subnet_ipv4_used) {
return false;
} else {
if (lhs.source_subnet_ipv4_used) {
// If they have values
if (lhs.source_subnet_ipv4 != rhs.source_subnet_ipv4) {
return false;
}
}
}
// IPv6
if (lhs.source_subnet_ipv6_used != rhs.source_subnet_ipv6_used) {
return false;
} else {
if (lhs.source_subnet_ipv6_used) {
// If they have values
if (lhs.source_subnet_ipv6 != rhs.source_subnet_ipv6) {
return false;
}
}
}
// Compare destination subnets
// IPv4
if (lhs.destination_subnet_ipv4_used != rhs.destination_subnet_ipv4_used) {
return false;
} else {
if (lhs.destination_subnet_ipv4_used) {
if (lhs.destination_subnet_ipv4 != rhs.destination_subnet_ipv4) {
return false;
}
}
}
// IPv6
if (lhs.destination_subnet_ipv6_used != rhs.destination_subnet_ipv6_used) {
return false;
} else {
if (lhs.destination_subnet_ipv6_used) {
if (lhs.destination_subnet_ipv6 != rhs.destination_subnet_ipv6) {
return false;
}
}
}
// Compare actions
if (lhs.action != rhs.action) {
return false;
}
if (lhs.source_ports != rhs.source_ports) {
return false;
}
if (lhs.destination_ports != rhs.destination_ports) {
return false;
}
if (lhs.packet_lengths != rhs.packet_lengths) {
return false;
}
// This one is non standard compliant field and it cannot be used for BGP flow spec announces
if (lhs.vlans != rhs.vlans) {
return false;
}
// This one is non standard compliant field and it cannot be used for BGP flow spec announces
if (lhs.ttls != rhs.ttls) {
return false;
}
if (lhs.ipv4_nexthops != rhs.ipv4_nexthops) {
return false;
}
if (lhs.protocols != rhs.protocols) {
return false;
}
if (lhs.tcp_flags != rhs.tcp_flags) {
return false;
}
if (lhs.fragmentation_flags != rhs.fragmentation_flags) {
return false;
}
return true;
}
bool operator!=(const flow_spec_rule_t& lhs, const flow_spec_rule_t& rhs) {
return !(lhs == rhs);
}
bool operator!=(const flow_spec_tcp_flagset_t& lhs, const flow_spec_tcp_flagset_t& rhs) {
return !(lhs == rhs);
}
bool operator==(const flow_spec_tcp_flagset_t& lhs, const flow_spec_tcp_flagset_t& rhs) {
if (lhs.syn_flag == rhs.syn_flag && lhs.ack_flag == rhs.ack_flag && lhs.rst_flag == rhs.rst_flag &&
lhs.psh_flag == rhs.psh_flag && lhs.urg_flag == rhs.urg_flag && lhs.fin_flag == rhs.fin_flag) {
return true;
} else {
return false;
}
}
/*
{
"source_prefix": "4.0.0.0\/24",
"destination_prefix": "127.0.0.0\/24",
"destination_ports": [ 80 ],
"source_ports": [ 53, 5353 ],
"packet_lengths": [ 777, 1122 ],
"protocols": [ "tcp" ],
"fragmentation_flags":[ "is-fragment", "dont-fragment" ],
"tcp_flags": [ "syn" ],
"action_type": "rate-limit",
"action": { "rate": 1024 }
}
*/
bool read_flow_spec_from_json_to_native_format(const std::string& json_encoded_flow_spec, flow_spec_rule_t& flow_spec_rule, bool require_action) {
using json = nlohmann::json;
// We explicitly disable exceptions
auto json_doc = json::parse(json_encoded_flow_spec, nullptr, false);
if (json_doc.is_discarded()) {
logger << log4cpp::Priority::ERROR << "Cannot decode Flow Spec rule from JSON: '" << json_encoded_flow_spec << "'";
return false;
}
if (json_doc.contains("source_prefix")) {
std::string source_prefix_string;
try {
source_prefix_string = json_doc["source_prefix"].get<std::string>();
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded source_prefix";
return false;
}
if (source_prefix_string.find(":") != std::string::npos) {
subnet_ipv6_cidr_mask_t subnet_cidr_mask;
bool conversion_result = read_ipv6_subnet_from_string(subnet_cidr_mask, source_prefix_string);
if (!conversion_result) {
logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded IPv6 source_prefix";
return false;
}
flow_spec_rule.set_source_subnet_ipv6(subnet_cidr_mask);
} else {
subnet_cidr_mask_t subnet_cidr_mask;
bool conversion_result =
convert_subnet_from_string_to_binary_with_cidr_format_safe(source_prefix_string, subnet_cidr_mask);
if (!conversion_result) {
logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded source_prefix";
return false;
}
flow_spec_rule.set_source_subnet_ipv4(subnet_cidr_mask);
}
}
if (json_doc.contains("destination_prefix")) {
std::string destination_prefix_string;
try {
destination_prefix_string = json_doc["destination_prefix"].get<std::string>();
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded destination_prefix";
return false;
}
if (destination_prefix_string.find(":") != std::string::npos) {
subnet_ipv6_cidr_mask_t subnet_cidr_mask;
bool conversion_result = read_ipv6_subnet_from_string(subnet_cidr_mask, destination_prefix_string);
if (!conversion_result) {
logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded IPv6 destination_prefix";
return false;
}
flow_spec_rule.set_destination_subnet_ipv6(subnet_cidr_mask);
} else {
subnet_cidr_mask_t subnet_cidr_mask;
bool conversion_result =
convert_subnet_from_string_to_binary_with_cidr_format_safe(destination_prefix_string, subnet_cidr_mask);
if (!conversion_result) {
logger << log4cpp::Priority::ERROR << "Could not parse json encoded destination_prefix";
return false;
}
flow_spec_rule.set_destination_subnet_ipv4(subnet_cidr_mask);
}
}
if (json_doc.contains("destination_ports")) {
std::vector<int32_t> ports_vector_as_ints;
try {
ports_vector_as_ints = json_doc["destination_ports"].get<std::vector<int32_t>>();
} catch (nlohmann::json::exception& e) {
logger << log4cpp::Priority::ERROR << "Could not decode destination_ports " << e.what();
return false;
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not decode destination_ports";
return false;
}
for (auto port : ports_vector_as_ints) {
if (!valid_port(port)) {
logger << log4cpp::Priority::ERROR << "Could not parse destination_ports element: bad range " << port;
return false;
}
flow_spec_rule.add_destination_port(port);
}
}
if (json_doc.contains("source_ports")) {
std::vector<int32_t> ports_vector_as_ints;
try {
ports_vector_as_ints = json_doc["source_ports"].get<std::vector<int32_t>>();
} catch (nlohmann::json::exception& e) {
logger << log4cpp::Priority::ERROR << "Could not decode source_ports " << e.what();
return false;
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not decode source_ports";
return false;
}
for (auto port : ports_vector_as_ints) {
if (!valid_port(port)) {
logger << log4cpp::Priority::ERROR << "Could not parse source_ports element: bad range " << port;
return false;
}
flow_spec_rule.add_source_port(port);
}
}
if (json_doc.contains("packet_lengths")) {
std::vector<int32_t> packet_lengths_vector_as_ints;
try {
packet_lengths_vector_as_ints = json_doc["packet_lengths"].get<std::vector<int32_t>>();
} catch (nlohmann::json::exception& e) {
logger << log4cpp::Priority::ERROR << "Could not decode packet_lengths " << e.what();
return false;
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not decode packet_lengths";
return false;
}
for (auto packet_length : packet_lengths_vector_as_ints) {
if (packet_length < 0) {
logger << log4cpp::Priority::ERROR << "Could not parse packet_lengths element, it must be positive: " << packet_length;
return false;
}
// Should we drop it?
if (packet_length > 1500) {
logger << log4cpp::Priority::ERROR
<< "Could not parse packet_lengths element, it must not exceed 1500: " << packet_length;
return false;
}
flow_spec_rule.add_packet_length(packet_length);
}
}
// TODO: this logic is not covered by tests
if (json_doc.contains("vlans")) {
std::vector<int32_t> vlans_vector_as_ints;
try {
vlans_vector_as_ints = json_doc["vlans"].get<std::vector<int32_t>>();
} catch (nlohmann::json::exception& e) {
logger << log4cpp::Priority::ERROR << "Could not decode vlans " << e.what();
return false;
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not decode vlans";
return false;
}
for (auto vlan : vlans_vector_as_ints) {
if (vlan < 0) {
logger << log4cpp::Priority::ERROR << "Could not parse vlan element, bad range: " << vlan;
return false;
}
flow_spec_rule.add_vlan(vlan);
}
}
// TODO: this logic is not covered by tests
if (json_doc.contains("ttls")) {
// TODO: I'm not sure that it can handle such small unsigned well
std::vector<uint8_t> ttls_vector_as_ints;
try {
ttls_vector_as_ints = json_doc["ttls"].get<std::vector<uint8_t>>();
} catch (nlohmann::json::exception& e) {
logger << log4cpp::Priority::ERROR << "Could not decode TTLs " << e.what();
return false;
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not decode TTLs";
return false;
}
for (auto ttl : ttls_vector_as_ints) {
flow_spec_rule.add_ttl(ttl);
}
}
if (json_doc.contains("protocols")) {
std::vector<std::string> protocols_vector_as_strings;
try {
protocols_vector_as_strings = json_doc["protocols"].get<std::vector<std::string>>();
} catch (nlohmann::json::exception& e) {
logger << log4cpp::Priority::ERROR << "Could not decode protocols " << e.what();
return false;
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not decode protocols";
return false;
}
for (const auto& protocol_as_string : protocols_vector_as_strings) {
ip_protocol_t protocol;
bool result = read_protocol_from_string(protocol_as_string, protocol);
if (!result) {
logger << log4cpp::Priority::ERROR << "Could not parse this " << protocol_as_string << " as protocol";
return false;
}
flow_spec_rule.add_protocol(protocol);
}
}
// TODO: this logic is not covered by tests
if (json_doc.contains("ipv4_nexthops")) {
std::vector<std::string> next_hops_vector_as_strings;
try {
next_hops_vector_as_strings = json_doc["ipv4_nexthops"].get<std::vector<std::string>>();
} catch (nlohmann::json::exception& e) {
logger << log4cpp::Priority::ERROR << "Could not decode ipv4_nexthops " << e.what();
return false;
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not decode ipv4_nexthops";
return false;
}
for (const auto& next_hop_as_string : next_hops_vector_as_strings) {
uint32_t next_hop_ipv4 = 0;
auto ip_parser_result = convert_ip_as_string_to_uint_safe(next_hop_as_string, next_hop_ipv4);
if (!ip_parser_result) {
logger << log4cpp::Priority::ERROR << "Could not parse this " << next_hop_as_string << " as IPv4 address";
return false;
}
flow_spec_rule.add_ipv4_nexthop(next_hop_ipv4);
}
}
if (json_doc.contains("fragmentation_flags")) {
std::vector<std::string> fragmentation_flags_vector_as_strings;
try {
fragmentation_flags_vector_as_strings = json_doc["fragmentation_flags"].get<std::vector<std::string>>();
} catch (nlohmann::json::exception& e) {
logger << log4cpp::Priority::ERROR << "Could not decode fragmentation_flags " << e.what();
return false;
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not decode fragmentation_flags";
return false;
}
for (const auto& fragmentation_flag_as_string : fragmentation_flags_vector_as_strings) {
flow_spec_fragmentation_types_t fragment_flag;
bool result = read_flow_spec_fragmentation_types_from_string(fragmentation_flag_as_string, fragment_flag);
if (!result) {
logger << log4cpp::Priority::ERROR << "Could not parse this " << fragmentation_flag_as_string
<< " as flow spec fragmentation flag";
return false;
}
flow_spec_rule.add_fragmentation_flag(fragment_flag);
}
}
if (json_doc.contains("tcp_flags")) {
std::vector<std::string> tcp_flags_vector_as_strings;
try {
tcp_flags_vector_as_strings = json_doc["tcp_flags"].get<std::vector<std::string>>();
} catch (nlohmann::json::exception& e) {
logger << log4cpp::Priority::ERROR << "Could not decode tcp_flags " << e.what();
return false;
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not decode tcp_flags";
return false;
}
for (const auto& tcp_flag_as_string : tcp_flags_vector_as_strings) {
flow_spec_tcp_flagset_t flagset;
bool result = read_flow_spec_tcp_flags_from_strig(tcp_flag_as_string, flagset);
if (!result) {
logger << log4cpp::Priority::ERROR << "Could not parse this " << tcp_flag_as_string << " as flow spec tcp option flag";
return false;
}
flow_spec_rule.add_tcp_flagset(flagset);
}
}
// Skip action section when we do not need it
if (!require_action) {
return true;
}
bgp_flow_spec_action_t bgp_flow_spec_action;
if (!json_doc.contains("action_type")) {
logger << log4cpp::Priority::ERROR << "We have no action_type in JSON and it's mandatory";
return false;
}
std::string action_as_string;
try {
action_as_string = json_doc["action_type"].get<std::string>();
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not parse JSON encoded action_type";
return false;
}
bgp_flow_spec_action_types_t action_type;
bool result = read_flow_spec_action_type_from_string(action_as_string, action_type);
if (!result) {
logger << log4cpp::Priority::ERROR << "Could not parse action type: " << action_as_string;
return false;
}
bgp_flow_spec_action.set_type(action_type);
// And in this case we should extract rate_limit number
if (bgp_flow_spec_action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT) {
if (json_doc.contains("action")) {
auto json_action_doc = json_doc["action"];
if (!json_action_doc.contains("rate")) {
logger << log4cpp::Priority::ERROR << "Absent rate argument for rate limit action";
return false;
}
int32_t rate = 0;
try {
rate = json_action_doc["rate"].get<int32_t>();
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not parse JSON document for rate";
return false;
}
if (rate < 0) {
logger << log4cpp::Priority::ERROR << "Rate validation failed, it must be positive: " << rate;
return false;
}
bgp_flow_spec_action.set_rate_limit(rate);
} else {
// We assume zero rate in this case
}
} else if (bgp_flow_spec_action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT) {
if (!json_doc.contains("action")) {
logger << log4cpp::Priority::ERROR << "Action need to be provided for redirect";
return false;
}
auto json_action_doc = json_doc["action"];
if (!json_action_doc.contains("redirect_target_as")) {
logger << log4cpp::Priority::ERROR << "Absent redirect_target_as argument for redirect action";
return false;
}
uint16_t redirect_target_as = 0;
try {
redirect_target_as = json_action_doc["redirect_target_as"].get<uint16_t>();
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not parse JSON document for redirect_target_as";
return false;
}
bgp_flow_spec_action.set_redirect_as(redirect_target_as);
uint32_t redirect_target_value = 0;
try {
redirect_target_value = json_action_doc["redirect_target_value"].get<uint32_t>();
} catch (...) {
logger << log4cpp::Priority::ERROR << "Could not parse JSON document for redirect_target_value";
return false;
}
bgp_flow_spec_action.set_redirect_value(redirect_target_value);
}
flow_spec_rule.set_action(bgp_flow_spec_action);
return true;
}
// Encode flow spec announce into JSON representation
bool encode_flow_spec_to_json(const flow_spec_rule_t& flow_spec_rule, std::string& json_encoded_flow_spec, bool add_uuid) {
nlohmann::json flow_json;
bool encoding_result = encode_flow_spec_to_json_raw(flow_spec_rule, add_uuid, flow_json);
if (!encoding_result) {
logger << log4cpp::Priority::ERROR << "Cannot encode Flow Spec into JSON";
return false;
}
std::string json_as_text = flow_json.dump();
// Remove ugly useless escaping for flow spec destination and source subnets
// I.e. 127.0.0.1\/32
boost::replace_all(json_as_text, "\\", "");
json_encoded_flow_spec = json_as_text;
return true;
}
// Encode flow spec in JSON object representation
bool encode_flow_spec_to_json_raw(const flow_spec_rule_t& flow_spec_rule, bool add_uuid, nlohmann::json& flow_json) {
// UUID is quite important for us, let's add it
if (add_uuid) {
flow_json["uuid"] = flow_spec_rule.get_announce_uuid_as_string();
}
if (flow_spec_rule.source_subnet_ipv4_used) {
flow_json["source_prefix"] = convert_ipv4_subnet_to_string(flow_spec_rule.source_subnet_ipv4);
} else if (flow_spec_rule.source_subnet_ipv6_used) {
flow_json["source_prefix"] = convert_ipv6_subnet_to_string(flow_spec_rule.source_subnet_ipv6);
}
if (flow_spec_rule.destination_subnet_ipv4_used) {
flow_json["destination_prefix"] = convert_ipv4_subnet_to_string(flow_spec_rule.destination_subnet_ipv4);
} else if (flow_spec_rule.destination_subnet_ipv6_used) {
flow_json["destination_prefix"] = convert_ipv6_subnet_to_string(flow_spec_rule.destination_subnet_ipv6);
}
if (!flow_spec_rule.destination_ports.empty()) {
flow_json["destination_ports"] = flow_spec_rule.destination_ports;
}
if (!flow_spec_rule.source_ports.empty()) {
flow_json["source_ports"] = flow_spec_rule.source_ports;
}
if (!flow_spec_rule.packet_lengths.empty()) {
flow_json["packet_lengths"] = flow_spec_rule.packet_lengths;
}
if (!flow_spec_rule.vlans.empty()) {
flow_json["vlans"] = flow_spec_rule.vlans;
}
if (!flow_spec_rule.ttls.empty()) {
flow_json["ttls"] = flow_spec_rule.ttls;
}
if (!flow_spec_rule.protocols.empty()) {
flow_json["protocols"] = nlohmann::json::array();
for (auto protocol : flow_spec_rule.protocols) {
std::string protocol_name = get_ip_protocol_name(protocol);
// We use lowercase format
boost::algorithm::to_lower(protocol_name);
flow_json["protocols"].push_back(protocol_name);
}
}
if (!flow_spec_rule.fragmentation_flags.empty()) {
flow_json["fragmentation_flags"] = nlohmann::json::array();
for (auto fragment_flag : flow_spec_rule.fragmentation_flags) {
std::string fragmentation_flag_as_string = flow_spec_fragmentation_flags_to_string(fragment_flag);
// For some reasons we cannot convert it to string
if (fragmentation_flag_as_string == "") {
continue;
}
flow_json["fragmentation_flags"].push_back(fragmentation_flag_as_string);
}
}
// If we have TCP in protocols list explicitly, we add flags
bool we_have_tcp_protocol_in_list = find(flow_spec_rule.protocols.begin(), flow_spec_rule.protocols.end(),
ip_protocol_t::TCP) != flow_spec_rule.protocols.end();
if (!flow_spec_rule.tcp_flags.empty() && we_have_tcp_protocol_in_list) {
flow_json["tcp_flags"] = nlohmann::json::array();
for (auto tcp_flag : flow_spec_rule.tcp_flags) {
std::string tcp_flags_as_string = flow_spec_tcp_flagset_to_string(tcp_flag);
// For some reasons we cannot encode it, skip iteration
if (tcp_flags_as_string == "") {
continue;
}
flow_json["tcp_flags"].push_back(tcp_flags_as_string);
}
}
// Encode action structure
flow_json["action_type"] = serialize_action_type(flow_spec_rule.action.get_type());
// We add sub document action when arguments needed
if (flow_spec_rule.action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_RATE_LIMIT) {
nlohmann::json action_json;
action_json["rate"] = flow_spec_rule.action.get_rate_limit();
flow_json["action"] = action_json;
} else if (flow_spec_rule.action.get_type() == bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_REDIRECT) {
nlohmann::json action_json;
action_json["redirect_target_as"] = flow_spec_rule.action.get_redirect_as();
action_json["redirect_target_value"] = flow_spec_rule.action.get_redirect_value();
flow_json["action"] = action_json;
}
return true;
}
bgp_flowspec_one_byte_byte_encoded_tcp_flags_t return_in_one_byte_encoding(const flow_spec_tcp_flagset_t& flagset) {
bgp_flowspec_one_byte_byte_encoded_tcp_flags_t one_byte_flags{};
if (flagset.syn_flag) {
one_byte_flags.syn = 1;
}
if (flagset.fin_flag) {
one_byte_flags.fin = 1;
}
if (flagset.urg_flag) {
one_byte_flags.urg = 1;
}
if (flagset.ack_flag) {
one_byte_flags.ack = 1;
}
if (flagset.psh_flag) {
one_byte_flags.psh = 1;
}
if (flagset.rst_flag) {
one_byte_flags.rst = 1;
}
return one_byte_flags;
}
flow_spec_tcp_flagset_t convert_one_byte_encoding_to_flowset(const bgp_flowspec_one_byte_byte_encoded_tcp_flags_t& one_byte_flags) {
flow_spec_tcp_flagset_t flagset;
if (one_byte_flags.syn == 1) {
flagset.syn_flag = true;
}
if (one_byte_flags.fin == 1) {
flagset.fin_flag = true;
}
if (one_byte_flags.urg == 1) {
flagset.urg_flag = true;
}
if (one_byte_flags.ack == 1) {
flagset.ack_flag = true;
}
if (one_byte_flags.psh == 1) {
flagset.psh_flag = true;
}
if (one_byte_flags.rst == 1) {
flagset.rst_flag = true;
}
return flagset;
}
// Is it range valid for port?
bool valid_port(int32_t port) {
return port >= 0 && port <= 65535;
}

@ -0,0 +1,741 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "dynamic_binary_buffer.hpp"
#include "fast_library.hpp"
#include "fastnetmon_networks.hpp"
#include <boost/serialization/nvp.hpp>
#include "iana_ip_protocols.hpp"
#include "bgp_protocol.hpp"
class bgp_flow_spec_action_t;
// This structure stores TCP flags in very human friendly way
// It could store multiple enabled flags in same time
class flow_spec_tcp_flagset_t {
public:
bool syn_flag = false;
bool ack_flag = false;
bool fin_flag = false;
bool psh_flag = false;
bool rst_flag = false;
bool urg_flag = false;
// Do we have least one flag enabled?
bool we_have_least_one_flag_enabled() const {
return syn_flag || fin_flag || urg_flag || ack_flag || psh_flag || rst_flag;
}
std::string print() const {
std::stringstream buffer;
buffer << "syn: " << syn_flag << " "
<< "ack: " << ack_flag << " "
<< "fin: " << fin_flag << " "
<< "psh: " << psh_flag << " "
<< "rst: " << rst_flag << " "
<< "urg: " << urg_flag;
return buffer.str();
}
template <class Archive> void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) {
ar& BOOST_SERIALIZATION_NVP(syn_flag);
ar& BOOST_SERIALIZATION_NVP(ack_flag);
ar& BOOST_SERIALIZATION_NVP(fin_flag);
ar& BOOST_SERIALIZATION_NVP(psh_flag);
ar& BOOST_SERIALIZATION_NVP(rst_flag);
ar& BOOST_SERIALIZATION_NVP(urg_flag);
}
};
bool operator==(const flow_spec_tcp_flagset_t& lhs, const flow_spec_tcp_flagset_t& rhs);
bool operator!=(const flow_spec_tcp_flagset_t& lhs, const flow_spec_tcp_flagset_t& rhs);
// All possible values for BGP Flow Spec fragmentation field
enum class flow_spec_fragmentation_types_t {
FLOW_SPEC_DONT_FRAGMENT,
FLOW_SPEC_IS_A_FRAGMENT,
FLOW_SPEC_FIRST_FRAGMENT,
FLOW_SPEC_LAST_FRAGMENT,
// Well, this entity does not exist in RFC at all. It was addition from ExaBGP
FLOW_SPEC_NOT_A_FRAGMENT,
};
// Flow spec actions
enum class bgp_flow_spec_action_types_t {
FLOW_SPEC_ACTION_DISCARD,
FLOW_SPEC_ACTION_ACCEPT,
FLOW_SPEC_ACTION_RATE_LIMIT,
FLOW_SPEC_ACTION_REDIRECT,
FLOW_SPEC_ACTION_MARK
};
bool read_flow_spec_action_type_from_string(const std::string& string_form, bgp_flow_spec_action_types_t& action_type);
std::string serialize_action_type(const bgp_flow_spec_action_types_t& action_type);
class bgp_flow_spec_action_t {
public:
void set_type(bgp_flow_spec_action_types_t action_type) {
this->action_type = action_type;
}
bgp_flow_spec_action_types_t get_type() const {
return this->action_type;
}
void set_rate_limit(unsigned int rate_limit) {
this->rate_limit = rate_limit;
}
unsigned int get_rate_limit() const {
return this->rate_limit;
}
uint16_t get_redirect_as() const {
return redirect_as;
}
uint32_t get_redirect_value() const {
return redirect_value;
}
void set_redirect_as(uint16_t value) {
redirect_as = value;
}
void set_redirect_value(uint32_t value) {
redirect_value = value;
}
template <class Archive> void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) {
ar& BOOST_SERIALIZATION_NVP(action_type);
ar& BOOST_SERIALIZATION_NVP(rate_limit);
ar& BOOST_SERIALIZATION_NVP(redirect_as);
ar& BOOST_SERIALIZATION_NVP(redirect_value);
}
private:
bgp_flow_spec_action_types_t action_type = bgp_flow_spec_action_types_t::FLOW_SPEC_ACTION_ACCEPT;
unsigned int rate_limit = 0;
// Values for redirect
uint16_t redirect_as = 0;
uint32_t redirect_value = 0;
};
bool operator==(const bgp_flow_spec_action_t& lhs, const bgp_flow_spec_action_t& rhs);
bool operator!=(const bgp_flow_spec_action_t& lhs, const bgp_flow_spec_action_t& rhs);
// We do not use < and > operators at all, sorry
class flow_spec_rule_t {
public:
// This operation is very heavy, it may crash in case of entropy shortage and it actually happened to our customer
// And we must not do them in constructors as it causes lots of side effects and slows down all things
bool generate_uuid() {
boost::uuids::random_generator gen;
try {
announce_uuid = gen();
} catch (...) {
return false;
}
return true;
}
void set_source_subnet_ipv4(const subnet_cidr_mask_t& source_subnet) {
this->source_subnet_ipv4 = source_subnet;
this->source_subnet_ipv4_used = true;
}
void set_source_subnet_ipv6(const subnet_ipv6_cidr_mask_t& source_subnet) {
this->source_subnet_ipv6 = source_subnet;
this->source_subnet_ipv6_used = true;
}
void set_destination_subnet_ipv4(const subnet_cidr_mask_t& destination_subnet) {
this->destination_subnet_ipv4 = destination_subnet;
this->destination_subnet_ipv4_used = true;
}
void set_destination_subnet_ipv6(const subnet_ipv6_cidr_mask_t& destination_subnet) {
this->destination_subnet_ipv6 = destination_subnet;
this->destination_subnet_ipv6_used = true;
}
void set_agent_subnet(subnet_cidr_mask_t subnet_param) {
this->agent_subnet = subnet_param;
this->agent_subnet_used = true;
}
void add_source_port(uint16_t source_port) {
this->source_ports.push_back(source_port);
}
void add_destination_port(uint16_t destination_port) {
this->destination_ports.push_back(destination_port);
}
void add_packet_length(uint16_t packet_length) {
this->packet_lengths.push_back(packet_length);
}
void add_vlan(uint16_t vlan) {
this->vlans.push_back(vlan);
}
void add_ipv4_nexthop(uint32_t ip) {
this->ipv4_nexthops.push_back(ip);
}
void add_protocol(ip_protocol_t protocol) {
this->protocols.push_back(protocol);
}
void add_ttl(uint8_t ttl) {
this->ttls.push_back(ttl);
}
void add_fragmentation_flag(flow_spec_fragmentation_types_t flag) {
this->fragmentation_flags.push_back(flag);
}
void add_tcp_flagset(flow_spec_tcp_flagset_t flag) {
this->tcp_flags.push_back(flag);
}
void set_action(bgp_flow_spec_action_t action) {
this->action = action;
}
bgp_flow_spec_action_t get_action() const {
return this->action;
}
std::string get_announce_uuid_as_string() const {
return boost::uuids::to_string(announce_uuid);
}
template <class Archive> void serialize(Archive& ar, [[maybe_unused]] const unsigned int version) {
ar& BOOST_SERIALIZATION_NVP(source_subnet_ipv4);
ar& BOOST_SERIALIZATION_NVP(source_subnet_ipv4_used);
ar& BOOST_SERIALIZATION_NVP(source_subnet_ipv6);
ar& BOOST_SERIALIZATION_NVP(source_subnet_ipv6_used);
ar& BOOST_SERIALIZATION_NVP(destination_subnet_ipv4);
ar& BOOST_SERIALIZATION_NVP(destination_subnet_ipv4_used);
ar& BOOST_SERIALIZATION_NVP(destination_subnet_ipv6);
ar& BOOST_SERIALIZATION_NVP(destination_subnet_ipv6_used);
ar& BOOST_SERIALIZATION_NVP(agent_subnet);
ar& BOOST_SERIALIZATION_NVP(agent_subnet_used);
ar& BOOST_SERIALIZATION_NVP(source_ports);
ar& BOOST_SERIALIZATION_NVP(destination_ports);
ar& BOOST_SERIALIZATION_NVP(packet_lengths);
ar& BOOST_SERIALIZATION_NVP(vlans);
ar& BOOST_SERIALIZATION_NVP(ttls);
ar& BOOST_SERIALIZATION_NVP(ipv4_nexthops);
ar& BOOST_SERIALIZATION_NVP(protocols);
ar& BOOST_SERIALIZATION_NVP(fragmentation_flags);
ar& BOOST_SERIALIZATION_NVP(tcp_flags);
ar& BOOST_SERIALIZATION_NVP(set_match_bit_for_tcp_flags);
ar& BOOST_SERIALIZATION_NVP(set_match_bit_for_fragmentation_flags);
ar& BOOST_SERIALIZATION_NVP(action);
ar& BOOST_SERIALIZATION_NVP(announce_uuid);
}
// Source prefix
subnet_cidr_mask_t source_subnet_ipv4;
bool source_subnet_ipv4_used = false;
subnet_ipv6_cidr_mask_t source_subnet_ipv6;
bool source_subnet_ipv6_used = false;
// Destination prefix
subnet_cidr_mask_t destination_subnet_ipv4;
bool destination_subnet_ipv4_used = false;
subnet_ipv6_cidr_mask_t destination_subnet_ipv6;
bool destination_subnet_ipv6_used = false;
// Agent subnet
subnet_cidr_mask_t agent_subnet;
bool agent_subnet_used = false;
std::vector<uint16_t> source_ports;
std::vector<uint16_t> destination_ports;
// It's total IP packet length (excluding Layer 2 but including IP header)
// https://datatracker.ietf.org/doc/html/rfc5575#section-4
std::vector<uint16_t> packet_lengths;
// This one is an non standard extension for our own purposes
std::vector<uint16_t> vlans;
// This one is an non standard extension for our own purposes
std::vector<uint8_t> ttls;
// IPv4 next hops for https://datatracker.ietf.org/doc/html/draft-ietf-idr-flowspec-redirect-ip-01
std::vector<uint32_t> ipv4_nexthops ;
std::vector<ip_protocol_t> protocols;
std::vector<flow_spec_fragmentation_types_t> fragmentation_flags;
std::vector<flow_spec_tcp_flagset_t> tcp_flags;
// By default we do not use match bit for TCP flags when encode them to Flow Spec NLRI
// But in some cases it could be really useful
bool set_match_bit_for_tcp_flags = false;
// By default we do not use match bit for fragmentation flags when encode them to Flow Spec NLRI
// But in some cases (Huawei) it could be useful
bool set_match_bit_for_fragmentation_flags = false;
bgp_flow_spec_action_t action;
boost::uuids::uuid announce_uuid{};
};
bool operator==(const flow_spec_rule_t& lhs, const flow_spec_rule_t& rhs);
bool operator!=(const flow_spec_rule_t& lhs, const flow_spec_rule_t& rhs);
bool read_flow_spec_from_json_to_native_format(const std::string& json_encoded_flow_spec, flow_spec_rule_t& flow_spec_rule, bool require_action);
bool encode_flow_spec_to_json(const flow_spec_rule_t& flow_spec_rule, std::string& json_encoded_flow_spec, bool add_uuid);
bool decode_native_flow_spec_announce_from_binary_encoded_atributes(std::vector<dynamic_binary_buffer_t> binary_attributes,
flow_spec_rule_t& flow_spec_rule);
bool encode_bgp_flow_spec_action_as_extended_attribute(const bgp_flow_spec_action_t& bgp_flow_spec_action,
dynamic_binary_buffer_t& extended_attributes_as_binary_array);
// It's format of redirect target. So called route target community. Official spec RFC5575 is pretty vague about it:
// https://datatracker.ietf.org/doc/html/rfc4360#section-4
// But new BGP Flow Spec clarifies it as https://datatracker.ietf.org/doc/html/rfc8955#name-rt-redirect-rt-redirect-sub
class __attribute__((__packed__)) redirect_2_octet_as_4_octet_value_t {
// We must not access these fields directly as it requires explicit byte order conversion
private:
uint16_t as = 0;
uint32_t value = 0;
public:
uint16_t get_as_host_byte_order() const {
return fast_ntoh(as);
}
uint32_t get_value_host_byte_order() const {
return fast_ntoh(value);
}
std::string print() const {
std::stringstream buffer;
buffer << "as: " << get_as_host_byte_order() << " "
<< "value: " << get_value_host_byte_order() << " ";
return buffer.str();
}
};
static_assert(sizeof(redirect_2_octet_as_4_octet_value_t) == 6,
"Bad size for redirect_2_octet_as_4_octet_value_t");
// More details at https://tools.ietf.org/html/rfc5575 page 6
class __attribute__((__packed__)) bgp_flow_spec_operator_byte_t {
public:
uint8_t equal : 1 = 0, greater_than : 1 = 0, less_than : 1 = 0, reserved : 1 = 0, bit_shift_len : 2 = 0,
and_bit : 1 = 0, end_of_list : 1 = 0;
void set_equal_bit() {
equal = 1;
}
void set_greater_than_bit() {
greater_than = 1;
}
void set_less_than_bit() {
less_than = 1;
}
void set_and_bit() {
and_bit = 1;
}
void set_end_of_list_bit() {
end_of_list = 1;
}
bool set_length_in_bytes(uint32_t byte_length) {
// We could set only for numbers which are pow of 2
if (byte_length == 1) {
bit_shift_len = 0;
} else if (byte_length == 2) {
bit_shift_len = 1;
} else if (byte_length == 4) {
bit_shift_len = 2;
} else {
logger << log4cpp::Priority::ERROR << "Could not calculate log2 for " << byte_length;
return false;
}
return true;
}
std::string print() const {
std::stringstream buffer;
buffer << "end of list: " << uint32_t(end_of_list) << " "
<< "and_bit: " << uint32_t(and_bit) << " "
<< "bit_shift_len: " << uint32_t(bit_shift_len) << " "
<< "reserved: " << uint32_t(reserved) << " "
<< "less_than: " << uint32_t(less_than) << " "
<< "greater_than: " << uint32_t(greater_than) << " "
<< "equal: " << uint32_t(equal);
return buffer.str();
}
// Real value evaluated as 1 << bit_shift_len
uint32_t get_value_length() {
return 1 << bit_shift_len;
}
};
// Here we store multiple enumerable values for flow spec protocol (ports,
// protocols and other)
class flow_spec_enumerable_lement {
public:
uint8_t one_byte_value = 0;
uint16_t two_byte_value = 0;
// Could be only 1 or 2 bytes
uint32_t value_length = 0;
bgp_flow_spec_operator_byte_t operator_byte{};
};
typedef std::vector<flow_spec_enumerable_lement> multiple_flow_spec_enumerable_items_t;
bool read_one_or_more_values_encoded_with_operator_byte(uint8_t* start,
uint8_t* global_end,
uint32_t& readed_bytes,
multiple_flow_spec_enumerable_items_t& multiple_flow_spec_enumerable_items);
std::string get_flow_spec_type_name_by_number(uint8_t flow_spec_type);
std::string get_bgp_attribute_name_by_number(uint8_t bgp_attribute_type);
bool flow_spec_decode_nlri_value(uint8_t* data_ptr, uint32_t data_length, flow_spec_rule_t& flow_spec_rule);
class __attribute__((__packed__)) bgp_flow_spec_fragmentation_entity_t {
public:
uint8_t dont_fragment : 1 = 0, is_fragment : 1 = 0, first_fragment : 1 = 0, last_fragment : 1 = 0, reserved : 4 = 0;
std::string print() const {
std::stringstream buffer;
buffer << "reserved: " << uint32_t(reserved) << " "
<< "last_fragment: " << uint32_t(last_fragment) << " "
<< "first_fragment: " << uint32_t(first_fragment) << " "
<< "is_fragment: " << uint32_t(is_fragment) << " "
<< "dont_fragment: " << uint32_t(dont_fragment);
return buffer.str();
}
};
static_assert(sizeof(bgp_flow_spec_fragmentation_entity_t) == 1, "Broken size for bgp_flow_spec_fragmentation_entity_t");
// More details at https://tools.ietf.org/html/rfc5575#page-9
// We use this version of operator byte for TCP flags and for fragmentation flags
class __attribute__((__packed__)) bgp_flow_spec_bitmask_operator_byte_t {
public:
uint8_t match_bit : 1 = 0, not_bit : 1 = 0, reserved2 : 1 = 0, reserved1 : 1 = 0, bit_shift_len : 2 = 0,
and_bit : 1 = 0, end_of_list : 1 = 0;
bgp_flow_spec_bitmask_operator_byte_t() {
memset(this, 0, sizeof(*this));
}
std::string print() const {
std::stringstream buffer;
buffer << "end of list: " << uint32_t(end_of_list) << " "
<< "and_bit: " << uint32_t(and_bit) << " "
<< "bit_shift_len: " << uint32_t(bit_shift_len) << " "
<< "reserved1: " << uint32_t(reserved1) << " "
<< "reserved2: " << uint32_t(reserved2) << " "
<< "not_bit: " << uint32_t(not_bit) << " "
<< "match_bit: " << uint32_t(match_bit);
return buffer.str();
}
void set_not_bit() {
not_bit = 1;
}
void set_and_bit() {
and_bit = 1;
}
void set_match_bit() {
match_bit = 1;
}
void set_end_of_list_bit() {
end_of_list = 1;
}
bool set_length_in_bytes(uint32_t byte_length) {
// We could set only for numbers which are pow of 2
if (byte_length == 1) {
bit_shift_len = 0;
} else if (byte_length == 2) {
bit_shift_len = 1;
} else if (byte_length == 4) {
bit_shift_len = 2;
} else {
logger << log4cpp::Priority::WARN << "Could not calculate log2 for " << byte_length;
return false;
}
return true;
}
// Real value evaluated as 1 << bit_shift_len
uint32_t get_value_length() {
return 1 << bit_shift_len;
}
};
// We have two ways to encode TCP flags - one byte and two byte
// This is extracted some piece of code from: tcp_header_t /
// network_data_structures
class __attribute__((__packed__)) bgp_flowspec_two_byte_encoded_tcp_flags_t {
public:
uint16_t fin : 1 = 0, syn : 1 = 0, rst : 1 = 0, psh : 1 = 0, ack : 1 = 0, urg : 1 = 0, ece : 1 = 0, cwr : 1 = 0,
ns : 1 = 0, reserved : 3 = 0, data_offset : 4 = 0;
};
static_assert(sizeof(bgp_flowspec_two_byte_encoded_tcp_flags_t) == 2, "Bad size for bgp_flowspec_two_byte_encoded_tcp_flags_t");
class __attribute__((__packed__)) bgp_flowspec_one_byte_byte_encoded_tcp_flags_t {
public:
// Just drop 8 bytes from bgp_flowspec_two_byte_encoded_tcp_flags
uint8_t fin : 1 = 0, syn : 1 = 0, rst : 1 = 0, psh : 1 = 0, ack : 1 = 0, urg : 1 = 0, ece : 1 = 0, cwr : 1 = 0;
std::string print() const {
std::stringstream buffer;
buffer << "cwr: " << uint32_t(cwr) << " "
<< "ece: " << uint32_t(ece) << " "
<< "urg: " << uint32_t(urg) << " "
<< "ack: " << uint32_t(ack) << " "
<< "psh: " << uint32_t(psh) << " "
<< "rst: " << uint32_t(rst) << " "
<< "syn: " << uint32_t(syn) << " "
<< "fin: " << uint32_t(fin);
return buffer.str();
}
};
static_assert(sizeof(bgp_flowspec_one_byte_byte_encoded_tcp_flags_t) == 1, "Bad size for ");
// BGP flow spec entity numbers
enum FLOW_SPEC_ENTITY_TYPES : uint8_t {
FLOW_SPEC_ENTITY_DESTINATION_PREFIX = 1,
FLOW_SPEC_ENTITY_SOURCE_PREFIX = 2,
FLOW_SPEC_ENTITY_IP_PROTOCOL = 3,
FLOW_SPEC_ENTITY_PORT = 4,
FLOW_SPEC_ENTITY_DESTINATION_PORT = 5,
FLOW_SPEC_ENTITY_SOURCE_PORT = 6,
FLOW_SPEC_ENTITY_ICMP_TYPE = 7,
FLOW_SPEC_ENTITY_ICMP_CODE = 8,
FLOW_SPEC_ENTITY_TCP_FLAGS = 9,
FLOW_SPEC_ENTITY_PACKET_LENGTH = 10,
FLOW_SPEC_ENTITY_DSCP = 11,
FLOW_SPEC_ENTITY_FRAGMENT = 12,
};
/*
Here we have custom NLRI encoding
(https://tools.ietf.org/html/rfc4760#section-5.1.3):
+---------------------------------------------------------+
| Address Family Identifier (2 octets) |
+---------------------------------------------------------+
| Subsequent Address Family Identifier (1 octet) |
+---------------------------------------------------------+
| Length of Next Hop Network Address (1 octet) |
+---------------------------------------------------------+
| Network Address of Next Hop (variable) |
+---------------------------------------------------------+
| Reserved (1 octet) |
+---------------------------------------------------------+
| Network Layer Reachability Information (variable) |
+---------------------------------------------------------+
*/
class __attribute__((__packed__)) bgp_mp_ext_flow_spec_header_t {
public:
uint16_t afi_identifier = AFI_IP;
uint8_t safi_identifier = SAFI_FLOW_SPEC_UNICAST;
// For BGP Flow spec we are using blank next hop because it's useless for us
// now
uint8_t length_of_next_hop = 0;
// Here we have blank next hop. Or haven't ... :)
uint8_t reserved = 0;
// Here we have NLRI information
void network_to_host_byte_order() {
afi_identifier = ntohs(afi_identifier);
}
void host_byte_order_to_network_byte_order() {
afi_identifier = htons(afi_identifier);
}
std::string print() const {
std::stringstream buffer;
buffer << "afi_identifier: " << uint32_t(afi_identifier) << " "
<< "safi_identifier: " << uint32_t(safi_identifier) << " "
<< "length_of_next_hop: " << uint32_t(length_of_next_hop) << " "
<< "reserved: " << uint32_t(reserved);
return buffer.str();
}
};
class __attribute__((__packed__)) bgp_extended_community_element_flow_spec_rate_t {
public:
uint8_t type_hight = EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL;
uint8_t type_low = FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_TRAFFIC_RATE;
// This bytes are meaningless and should not processed at all by receiver side
uint8_t value[2] = { 0, 0 };
float rate_limit = 0;
void host_byte_order_to_network_byte_order() {
// Have you ever do little endian to big endian conversion for float? We do!
float rate_limit_copy = rate_limit;
logger << log4cpp::Priority::DEBUG << "Original rate: " << rate_limit;
// We do not use pointer to field structure here because it may cause alignment issues and gcc yells on it:
// warning: taking address of packed member of ... may result in an unaligned pointer value [-Waddress-of-packed-member]
uint32_t* integer_pointer = (uint32_t*)&rate_limit_copy;
logger << log4cpp::Priority::DEBUG << "Integer part of rate: " << *integer_pointer;
*integer_pointer = htonl(*integer_pointer);
// Overwrite original value
this->rate_limit = rate_limit_copy;
logger << log4cpp::Priority::DEBUG << "Network byte order encoded rate limit: " << rate_limit;
}
std::string print() const {
std::stringstream buffer;
buffer << "type hight: " << uint32_t(type_hight) << " "
<< "type low: " << uint32_t(type_low) << " "
<< "value raw: " << print_binary_string_as_hex_with_leading_0x(value, sizeof(value));
return buffer.str();
}
};
static_assert(sizeof(bgp_extended_community_element_flow_spec_rate_t) == 8,
"Bad size for bgp_extended_community_element_flow_spec_rate_t");
class __attribute__((__packed__)) bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value_t_t {
public:
uint8_t type_hight = EXTENDED_COMMUNITY_TRANSITIVE_EXPEREMENTAL;
uint8_t type_low = FLOW_SPEC_EXTENDED_COMMUNITY_SUBTYPE_REDIRECT_AS_TWO_BYTE;
// 6 octet value
uint16_t redirect_as = 0;
uint32_t redirect_value = 0;
void set_redirect_as(uint16_t value) {
redirect_as = fast_hton(value);
}
void set_redirect_value(uint32_t value) {
redirect_value = fast_hton(value);
}
std::string print() const {
std::stringstream buffer;
buffer << "type hight: " << uint32_t(type_hight) << " "
<< "type low: " << uint32_t(type_low) << " "
<< "redirect_as: " << redirect_as << " "
<< "redirect_value: " << redirect_value;
return buffer.str();
}
};
static_assert(sizeof(bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value_t_t) == 8,
"Bad size for bgp_extended_community_element_flow_spec_redirect_2_octet_as_4_octet_value_t_t");
// This structure encodes Flow Spec next hop IPv4
class __attribute__((__packed__)) bgp_extended_community_element_flow_spec_ipv4_next_hop_t {
public:
uint8_t type_hight = EXTENDED_COMMUNITY_TRANSITIVE_IPV4_ADDRESS_SPECIFIC;
uint8_t type_low = BGP_IPV4_EXTENDED_COMMUNITY_SUBTYPE_FLOW_SPEC_REDIRECT_IPv4;
// Actual value of IPv4 next hop
uint32_t next_hop_ipv4 = 0;
// In this field we can set mirror flag to make packet copies
uint16_t local_administrator = 0;
void host_byte_order_to_network_byte_order() {
}
std::string print() const {
std::stringstream buffer;
buffer << "type hight: " << uint32_t(type_hight) << " "
<< "type low: " << uint32_t(type_low) << " "
<< "nexthop: " << next_hop_ipv4 << " "
<< "local administrator: " << local_administrator;
return buffer.str();
}
};
static_assert(sizeof(bgp_extended_community_element_flow_spec_ipv4_next_hop_t) == 8,
"Bad size for bgp_extended_community_element_flow_spec_ipv4_next_hop_t");
static_assert(sizeof(bgp_flow_spec_bitmask_operator_byte_t) == 1, "Bad size for bgp_flow_spec_bitmask_operator_byte_t");
static_assert(sizeof(bgp_flow_spec_operator_byte_t) == 1, "Bad size for bgp_flow_spec_operator_byte_t");
bool read_flow_spec_tcp_flags_from_strig(const std::string& string_form, flow_spec_tcp_flagset_t& tcp_flagset);
bool read_flow_spec_fragmentation_types_from_string(const std::string& string_form, flow_spec_fragmentation_types_t& fragment_flag);
bool valid_port(int32_t port);
bool encode_flow_spec_to_json_raw(const flow_spec_rule_t& flow_spec_rule, bool add_uuid, nlohmann::json& flow_json);
std::string flow_spec_fragmentation_flags_to_string(flow_spec_fragmentation_types_t const& fragment_flag);
std::string flow_spec_tcp_flagset_to_string(flow_spec_tcp_flagset_t const& tcp_flagset);