diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c0afc2e..762fb30 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/bgp_protocol_flow_spec.cpp b/src/bgp_protocol_flow_spec.cpp new file mode 100644 index 0000000..31152e0 --- /dev/null +++ b/src/bgp_protocol_flow_spec.cpp @@ -0,0 +1,909 @@ +#include "bgp_protocol.hpp" + +#include + +#include "fast_library.hpp" + +// inet_ntoa +#include "network_data_structures.hpp" +#include +#include +#include + +#include +#include +#include +#include + +#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 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 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(); + } 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(); + } 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 ports_vector_as_ints; + + try { + ports_vector_as_ints = json_doc["destination_ports"].get>(); + } 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 ports_vector_as_ints; + + try { + ports_vector_as_ints = json_doc["source_ports"].get>(); + } 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 packet_lengths_vector_as_ints; + + try { + packet_lengths_vector_as_ints = json_doc["packet_lengths"].get>(); + } 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 vlans_vector_as_ints; + + try { + vlans_vector_as_ints = json_doc["vlans"].get>(); + } 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 ttls_vector_as_ints; + + try { + ttls_vector_as_ints = json_doc["ttls"].get>(); + } 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 protocols_vector_as_strings; + + try { + protocols_vector_as_strings = json_doc["protocols"].get>(); + } 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 next_hops_vector_as_strings; + + try { + next_hops_vector_as_strings = json_doc["ipv4_nexthops"].get>(); + } 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 fragmentation_flags_vector_as_strings; + + try { + fragmentation_flags_vector_as_strings = json_doc["fragmentation_flags"].get>(); + } 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 tcp_flags_vector_as_strings; + + try { + tcp_flags_vector_as_strings = json_doc["tcp_flags"].get>(); + } 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(); + } 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(); + } 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(); + } 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(); + } 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; +} diff --git a/src/bgp_protocol_flow_spec.hpp b/src/bgp_protocol_flow_spec.hpp new file mode 100644 index 0000000..eeea6f1 --- /dev/null +++ b/src/bgp_protocol_flow_spec.hpp @@ -0,0 +1,741 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "dynamic_binary_buffer.hpp" +#include "fast_library.hpp" +#include "fastnetmon_networks.hpp" + +#include + +#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 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 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 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 source_ports; + std::vector 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 packet_lengths; + + // This one is an non standard extension for our own purposes + std::vector vlans; + + // This one is an non standard extension for our own purposes + std::vector ttls; + + // IPv4 next hops for https://datatracker.ietf.org/doc/html/draft-ietf-idr-flowspec-redirect-ip-01 + std::vector ipv4_nexthops ; + + std::vector protocols; + std::vector fragmentation_flags; + + std::vector 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 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 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);