diff --git a/src/netflow_plugin/netflow_collector.cpp b/src/netflow_plugin/netflow_collector.cpp index 2bc59df..bd5da62 100644 --- a/src/netflow_plugin/netflow_collector.cpp +++ b/src/netflow_plugin/netflow_collector.cpp @@ -987,8 +987,10 @@ int nf9_rec_to_flow(uint32_t record_type, uint32_t record_length, uint8_t* data, full_packet_length = flow_meta.data_link_frame_size; } + bool unpack_gre = false; + auto result = parse_raw_packet_to_simple_packet_full_ng((u_char*)(data), full_packet_length, record_length, - flow_meta.nested_packet, read_packet_length_from_ip_header); + flow_meta.nested_packet, unpack_gre, read_packet_length_from_ip_header); if (result != network_data_stuctures::parser_code_t::success) { // Cannot decode data diff --git a/src/sflow_plugin/sflow_collector.cpp b/src/sflow_plugin/sflow_collector.cpp index 83f74bc..e9e971b 100644 --- a/src/sflow_plugin/sflow_collector.cpp +++ b/src/sflow_plugin/sflow_collector.cpp @@ -354,10 +354,13 @@ bool process_sflow_flow_sample(uint8_t* data_pointer, if (sflow_raw_protocol_header.header_protocol == SFLOW_HEADER_PROTOCOL_ETHERNET) { + bool unpack_gre = false; + // We could enable this new parser for testing purpose auto result = parse_raw_packet_to_simple_packet_full_ng(header_payload_pointer, sflow_raw_protocol_header.frame_length_before_sampling, sflow_raw_protocol_header.header_size, packet, + unpack_gre, sflow_read_packet_length_from_ip_header); if (result != network_data_stuctures::parser_code_t::success) { diff --git a/src/simple_packet_parser_ng.cpp b/src/simple_packet_parser_ng.cpp index 8d23e0b..b43dcff 100644 --- a/src/simple_packet_parser_ng.cpp +++ b/src/simple_packet_parser_ng.cpp @@ -6,13 +6,17 @@ using namespace network_data_stuctures; +// By default, we do not touch MPLS +// TODO: it's not working code yet +bool decode_mpls = false; + // Our own native function to convert wire packet into simple_packet_t // TODO: development is going here, we still need to add number of options here -// It based on code from parse_ipv4_or_ipv6_packet_up_to_l3 parser_code_t parse_raw_packet_to_simple_packet_full_ng(uint8_t* pointer, int length_before_sampling, int captured_length, simple_packet_t& packet, + bool unpack_gre, bool read_packet_length_from_ip_header) { // We are using pointer copy because we are changing it uint8_t* local_pointer = pointer; @@ -30,9 +34,6 @@ parser_code_t parse_raw_packet_to_simple_packet_full_ng(uint8_t* pointer, local_pointer += sizeof(ethernet_header_t); - // Here we store IPv4 or IPv6 l4 protocol numbers - uint8_t protocol = 0; - if (ethernet_header->ethertype == IanaEthertypeVLAN) { // Return error if it shorter then vlan header if (local_pointer + sizeof(ethernet_vlan_header_t) > end_pointer) { @@ -50,6 +51,30 @@ parser_code_t parse_raw_packet_to_simple_packet_full_ng(uint8_t* pointer, ethernet_header->ethertype = ethernet_vlan_header->ethertype; } + if (decode_mpls) { + if (ethernet_header->ethertype == IanaEthertypeMPLS_unicast) { + REPEAT_MPLS_STRIP: + if (local_pointer + sizeof(mpls_label_t) > end_pointer) { + return parser_code_t::memory_violation; + } + + mpls_label_t* mpls_label_header = (mpls_label_t*)local_pointer; + + std::cout << "MPLS header: " << mpls_label_header->print() << std::endl; + + // Strip this MPLS label + local_pointer += sizeof(mpls_label_t); + + // If it's not bottom of stack, repeat operation + if (mpls_label_header->bottom_of_stack == 0) { + goto REPEAT_MPLS_STRIP; + } + } + } + + // Here we store IPv4 or IPv6 l4 protocol numbers + uint8_t protocol = 0; + if (ethernet_header->ethertype == IanaEthertypeIPv4) { // Return error if pointer is shorter then IP header if (local_pointer + sizeof(ipv4_header_t) > end_pointer) { @@ -69,11 +94,16 @@ parser_code_t parse_raw_packet_to_simple_packet_full_ng(uint8_t* pointer, ipv4_header->convert(); packet.ttl = ipv4_header->ttl; + + // We need this specific field for Flow Spec mitigation mode packet.ip_length = ipv4_header->total_length; + packet.ip_dont_fragment = ipv4_header->dont_fragment_flag; packet.ip_fragmented = ipv4_header->is_fragmented(); + packet.ip_more_fragments = ipv4_header->more_fragments_flag; + // We keep these variables to maintain backward compatibility with parse_raw_packet_to_simple_packet_full() packet.packet_payload_length = length_before_sampling; packet.packet_payload_full_length = length_before_sampling; @@ -92,9 +122,88 @@ parser_code_t parse_raw_packet_to_simple_packet_full_ng(uint8_t* pointer, // Ignore all IP options and shift pointer to L3 payload local_pointer += 4 * ipv4_header->ihl; + } else if (ethernet_header->ethertype == IanaEthertypeIPv6) { + // Return error if pointer is shorter then IP header + if (local_pointer + sizeof(ipv6_header_t) > end_pointer) { + return parser_code_t::memory_violation; + } + + ipv6_header_t* ipv6_header = (ipv6_header_t*)local_pointer; + + // Convert all integers in IP header to little endian + ipv6_header->convert(); + + memcpy(&packet.src_ipv6, ipv6_header->source_address, sizeof(packet.src_ipv6)); + memcpy(&packet.dst_ipv6, ipv6_header->destination_address, sizeof(packet.dst_ipv6)); + + packet.ip_protocol_version = 6; + + packet.ttl = ipv6_header->hop_limit; + + // We need this specific field for Flow Spec mitigation mode + packet.ip_length = ipv6_header->payload_length; + + // We keep these variables to maintain backward compatibility with parse_raw_packet_to_simple_packet_full() + packet.packet_payload_length = length_before_sampling; + packet.packet_payload_full_length = length_before_sampling; + + // Pointer to payload + packet.packet_payload_pointer = (void*)pointer; + + protocol = ipv6_header->next_header; + packet.protocol = protocol; + + if (read_packet_length_from_ip_header) { + packet.length = ipv6_header->payload_length; + } else { + packet.length = length_before_sampling; + } + + // Just skip our simple IPv6 header and then code below will try to decode specific protocol + local_pointer += sizeof(ipv6_header_t); + + // According to https://datatracker.ietf.org/doc/html/rfc8200#page-8 + // these 6 options are mandatory for complete IPv6 implementations + // + // IpProtocolNumberHOPOPT = 0 + // IpProtocolNumberIPV6_ROUTE = 43 + // IpProtocolNumberIPV6_FRAG = 44 + // IpProtocolNumberESP = 50 + // IpProtocolNumberAH = 51 + // IpProtocolNumberIPV6_OPTS = 60 + // + // https://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml + // + // We do not support all IPv6 options in current version of parser + // Some options are extremely rare in The Wild Internet: https://stats.labs.apnic.net/cgi-bin/v6frag_worldmap?w=7&d=f + if (protocol == IpProtocolNumberHOPOPT || protocol == IpProtocolNumberIPV6_ROUTE || protocol == IpProtocolNumberIPV6_FRAG || + protocol == IpProtocolNumberIPV6_OPTS || protocol == IpProtocolNumberAH || protocol == IpProtocolNumberESP) { + + // We decided to parse only fragmentation header option as only this field may be found in the Wild + if (protocol == IpProtocolNumberIPV6_FRAG) { + ipv6_extension_header_fragment_t* ipv6_extension_header_fragment = (ipv6_extension_header_fragment_t*)local_pointer; + + ipv6_extension_header_fragment->convert(); + + // If we received this header then we assume that packet was fragmented + packet.ip_fragmented = true; + + packet.ip_more_fragments = ipv6_extension_header_fragment->more_fragments; + + // We stop processing here as I believe that it's enough to know that this traffic was fragmented + // We do not parse nested protocol in this case at all + // If we observe first fragment of UDP datagram we may see header but for consequent packets we cannot do it + // I think that's it's safer to avoid parsing such traffic deeper until we collect packet examples for all cases + return parser_code_t::success; + } + + return parser_code_t::no_ipv6_options_support; + } + } else if (ethernet_header->ethertype == IanaEthertypeARP) { + // it's not parser error of course but we need to have visibility about this case + return parser_code_t::arp; } else { - // TODO: we do not support IPv6 yet - return parser_code_t::not_ipv4; + return parser_code_t::unknown_ethertype; } if (protocol == IpProtocolNumberTCP) { @@ -122,9 +231,63 @@ parser_code_t parse_raw_packet_to_simple_packet_full_ng(uint8_t* pointer, packet.source_port = udp_header->source_port; packet.destination_port = udp_header->destination_port; + } else if (protocol == IpProtocolNumberGRE) { + if (!unpack_gre) { + // We do not decode it automatically but we can report source and destination IPs for it to FNM processing + return parser_code_t::success; + } + + if (local_pointer + sizeof(gre_packet_t) > end_pointer) { + return parser_code_t::memory_violation; + } + + gre_packet_t* gre_header = (gre_packet_t*)local_pointer; + gre_header->convert(); + + // Current version of parser does not handle these special codes and we just fail parsing process + // These flags may extend length of GRE header and current logic is not ready to decode any of them + if (gre_header->checksum != 0 || gre_header->reserved != 0 || gre_header->version != 0) { + return parser_code_t::broken_gre; + } + + // We will try parsing IPv4 only for now + if (gre_header->protocol_type == IanaEthertypeIPv4) { + local_pointer += sizeof(gre_packet_t); + + // This function will override all fields in original packet structure by new fields + bool read_length_from_ip_header = true; + + // We need to calculate how much data we have after all parsed fields until end of packet to pass it to function below + int remaining_packet_length = end_pointer - local_pointer; + + parser_code_t nested_packet_parse_result = + parse_raw_ipv4_packet_to_simple_packet_full_ng(local_pointer, remaining_packet_length, + remaining_packet_length, packet, read_length_from_ip_header); + + return nested_packet_parse_result; + } else if (gre_header->protocol_type == IanaEthertypeERSPAN) { + local_pointer += sizeof(gre_packet_t); + + // We need to calculate how much data we have after all parsed fields until end of packet to pass it to function below + int remaining_packet_length = end_pointer - local_pointer; + + bool read_length_from_ip_header_erspan = true; + + // We do not decode it second time + bool decode_nested_gre = false; + + // We need to call same function because we have normal wire format encoded data with ethernet header here + parser_code_t nested_packet_parse_result = + parse_raw_packet_to_simple_packet_full_ng(local_pointer, remaining_packet_length, remaining_packet_length, + packet, decode_nested_gre, read_length_from_ip_header_erspan); + + return nested_packet_parse_result; + } else { + return parser_code_t::broken_gre; + } } else { - // We're not interested in other protocol types - return parser_code_t::not_ipv4; + // That's fine, it's not some known protocol but we can export basic information retrieved from IP packet + return parser_code_t::success; } return parser_code_t::success; @@ -221,4 +384,3 @@ parser_code_t parse_raw_ipv4_packet_to_simple_packet_full_ng(uint8_t* pointer, return parser_code_t::success; } - diff --git a/src/simple_packet_parser_ng.hpp b/src/simple_packet_parser_ng.hpp index c034e94..f80cb85 100644 --- a/src/simple_packet_parser_ng.hpp +++ b/src/simple_packet_parser_ng.hpp @@ -5,10 +5,12 @@ network_data_stuctures::parser_code_t parse_raw_packet_to_simple_packet_full_ng( int length_before_sampling, int captured_length, simple_packet_t& packet, + bool unpack_gre, bool use_packet_length_from_wire); + network_data_stuctures::parser_code_t parse_raw_ipv4_packet_to_simple_packet_full_ng(uint8_t* pointer, - int length_before_sampling, - int captured_length, - simple_packet_t& packet, - bool read_packet_length_from_ip_header); + int length_before_sampling, + int captured_length, + simple_packet_t& packet, + bool read_packet_length_from_ip_header);