From 17be40f13cc3a08dc942ea6116958773305bd35d Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:15:07 +0200 Subject: [PATCH 01/56] CTT - Update packet structure --- include/ipfixprobe/packet.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/ipfixprobe/packet.hpp b/include/ipfixprobe/packet.hpp index c84baa534..aeb193ef2 100644 --- a/include/ipfixprobe/packet.hpp +++ b/include/ipfixprobe/packet.hpp @@ -37,6 +37,7 @@ #include #include #include +#include namespace ipxp { @@ -100,7 +101,10 @@ struct Packet : public Record { uint16_t buffer_size; /**< Size of buffer */ bool source_pkt; /**< Direction of packet from flow point of view */ - + + std::optional cttmeta; /**< Metadata from CTT */ + bool external_export; /**< True if packet payload is ctt export data */ + /** * \brief Constructor. */ @@ -142,6 +146,7 @@ struct Packet : public Record { , buffer(nullptr) , buffer_size(0) , source_pkt(true) + , external_export(false) { } }; From cccc2033893e45bc8124dcf1eb9ab38e1c77d4f0 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:18:50 +0200 Subject: [PATCH 02/56] CTT - Update NHTFlowCache class --- src/plugins/storage/cache/src/cache.cpp | 1007 ++++++++++------------- src/plugins/storage/cache/src/cache.hpp | 475 ++++------- 2 files changed, 605 insertions(+), 877 deletions(-) diff --git a/src/plugins/storage/cache/src/cache.cpp b/src/plugins/storage/cache/src/cache.cpp index bda8bb4ac..8e516073e 100644 --- a/src/plugins/storage/cache/src/cache.cpp +++ b/src/plugins/storage/cache/src/cache.cpp @@ -1,673 +1,546 @@ /** - * @file - * @brief "NewHashTable" flow cache - * @author Martin Zadnik - * @author Vaclav Bartos - * @author Jiri Havranek - * @author Pavel Siska - * @date 2025 + * \file cache.cpp + * \brief "NewHashTable" flow cache + * \author Martin Zadnik + * \author Vaclav Bartos + * \author Jiri Havranek + * \author Pavel Siska + * \author Damir Zainullin + * \date 2014 + * \date 2015 + * \date 2016 + * \date 2023 + * \date 2025 + */ +/* + * Copyright (C) 2023 CESNET * - * Copyright (c) 2025 CESNET + * LICENSE TERMS * - * SPDX-License-Identifier: BSD-3-Clause + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. */ - #include "cache.hpp" -#include "xxhash.h" - +#include #include -#include #include - +#include +#include +#include +#include +#include +#include #include #include -#include -#include + +#include "xxhash.h" +#include "fragmentationCache/timevalUtils.hpp" +#include "cacheRowSpan.hpp" +#include "flowKeyFactory.hpp" namespace ipxp { static const PluginManifest cachePluginManifest = { - .name = "cache", - .description = "Storage plugin implemented as a hash table.", - .pluginVersion = "1.0.0", - .apiVersion = "1.0.0", - .usage = - []() { - CacheOptParser parser; - parser.usage(std::cout); - }, + .name = "cache", + .description = "Storage plugin implemented as a hash table.", + .pluginVersion = "1.0.0", + .apiVersion = "1.0.0", + .usage = + []() { + CacheOptParser parser(cachePluginManifest.name, cachePluginManifest.description); + parser.usage(std::cout); + }, }; -FlowRecord::FlowRecord() +OptionsParser * NHTFlowCache::get_parser() const { - erase(); -}; + return new CacheOptParser("cache", "Storage plugin implemented as a hash table"); +} -FlowRecord::~FlowRecord() +std::string NHTFlowCache::get_name() const noexcept { - erase(); -}; + return "cache"; +} -void FlowRecord::erase() +void NHTFlowCache::finish() { - m_flow.remove_extensions(); - m_hash = 0; + std::for_each(m_flow_table.get(), m_flow_table.get() + m_cache_size, [&](FlowRecord*& flow_record) { + if (!flow_record->is_empty()) { + plugins_pre_export(flow_record->m_flow); + export_flow(&flow_record - m_flow_table.get(), FLOW_END_FORCED); + } + }); + print_report(); +} - memset(&m_flow.time_first, 0, sizeof(m_flow.time_first)); - memset(&m_flow.time_last, 0, sizeof(m_flow.time_last)); - m_flow.ip_version = 0; - m_flow.ip_proto = 0; - memset(&m_flow.src_ip, 0, sizeof(m_flow.src_ip)); - memset(&m_flow.dst_ip, 0, sizeof(m_flow.dst_ip)); - m_flow.src_port = 0; - m_flow.dst_port = 0; - m_flow.src_packets = 0; - m_flow.dst_packets = 0; - m_flow.src_bytes = 0; - m_flow.dst_bytes = 0; - m_flow.src_tcp_flags = 0; - m_flow.dst_tcp_flags = 0; +NHTFlowCache::NHTFlowCache(bool vlan_is_flow_key) +: m_vlan_is_flow_key(vlan_is_flow_key) +{ } -void FlowRecord::reuse() + +NHTFlowCache::NHTFlowCache(const std::string& params, ipx_ring_t* queue) { - m_flow.remove_extensions(); - m_flow.time_first = m_flow.time_last; - m_flow.src_packets = 0; - m_flow.dst_packets = 0; - m_flow.src_bytes = 0; - m_flow.dst_bytes = 0; - m_flow.src_tcp_flags = 0; - m_flow.dst_tcp_flags = 0; + set_queue(queue); + init(params.c_str()); } -inline __attribute__((always_inline)) bool FlowRecord::is_empty() const +NHTFlowCache::~NHTFlowCache() { - return m_hash == 0; + close(); } -inline __attribute__((always_inline)) bool FlowRecord::belongs(uint64_t hash) const +void NHTFlowCache::get_parser_options(CacheOptParser& parser) noexcept { - return hash == m_hash; + m_cache_size = parser.m_cache_size; + m_line_size = parser.m_line_size; + m_active_timeout = parser.m_active; + m_inactive_timeout = parser.m_inactive; + m_line_mask = (m_cache_size - 1) & ~(m_line_size - 1); + m_new_flow_insert_index = m_line_size / 2; + m_split_biflow = parser.m_split_biflow; + m_enable_fragmentation_cache = parser.m_enable_fragmentation_cache; } -void FlowRecord::create(const Packet& pkt, uint64_t hash) +void NHTFlowCache::allocate_table() { - m_flow.src_packets = 1; + try { + const size_t size = m_cache_size + m_queue_size; + m_flow_table = std::make_unique(size); + m_flows = std::make_unique(size); + std::for_each(m_flow_table.get(), m_flow_table.get() + size, [index = 0, this](FlowRecord*& flow) mutable { + flow = &m_flows[index++]; + }); + } catch (std::bad_alloc &e) { + throw PluginError("not enough memory for flow cache allocation"); + } +} - m_hash = hash; +void NHTFlowCache::init(const char *params) +{ + std::unique_ptr parser(static_cast(get_parser())); + try { + parser->parse(params); + } catch (ParserError &e) { + throw PluginError(e.what()); + } + + get_parser_options(*parser); + if (m_export_queue == nullptr) { + throw PluginError("output queue must be set before init"); + } + if (m_line_size > m_cache_size) { + throw PluginError("flow cache line size must be greater or equal to cache size"); + } + if (m_cache_size == 0) { + throw PluginError("flow cache won't properly work with 0 records"); + } + allocate_table(); + + if (m_enable_fragmentation_cache) { + try { + m_fragmentation_cache = FragmentationCache(parser->m_frag_cache_size, parser->m_frag_cache_timeout); + } catch (std::bad_alloc &e) { + throw PluginError("not enough memory for fragment cache allocation"); + } + } +} - m_flow.time_first = pkt.ts; - m_flow.time_last = pkt.ts; - m_flow.flow_hash = hash; +void NHTFlowCache::close() +{ + m_flows.reset(); + m_flow_table.reset(); +} - memcpy(m_flow.src_mac, pkt.src_mac, 6); - memcpy(m_flow.dst_mac, pkt.dst_mac, 6); +void NHTFlowCache::set_queue(ipx_ring_t *queue) +{ + m_export_queue = queue; + m_queue_size = ipx_ring_size(queue); +} - if (pkt.ip_version == IP::v4) { - m_flow.ip_version = pkt.ip_version; - m_flow.ip_proto = pkt.ip_proto; - m_flow.src_ip.v4 = pkt.src_ip.v4; - m_flow.dst_ip.v4 = pkt.dst_ip.v4; - m_flow.src_bytes = pkt.ip_len; - } else if (pkt.ip_version == IP::v6) { - m_flow.ip_version = pkt.ip_version; - m_flow.ip_proto = pkt.ip_proto; - memcpy(m_flow.src_ip.v6, pkt.src_ip.v6, 16); - memcpy(m_flow.dst_ip.v6, pkt.dst_ip.v6, 16); - m_flow.src_bytes = pkt.ip_len; - } +void NHTFlowCache::export_flow(size_t flow_index) +{ + export_flow(flow_index, get_export_reason(m_flow_table[flow_index]->m_flow)); +} - if (pkt.ip_proto == IPPROTO_TCP) { - m_flow.src_port = pkt.src_port; - m_flow.dst_port = pkt.dst_port; - m_flow.src_tcp_flags = pkt.tcp_flags; - } else if (pkt.ip_proto == IPPROTO_UDP) { - m_flow.src_port = pkt.src_port; - m_flow.dst_port = pkt.dst_port; - } else if (pkt.ip_proto == IPPROTO_ICMP || pkt.ip_proto == IPPROTO_ICMPV6) { - m_flow.src_port = pkt.src_port; - m_flow.dst_port = pkt.dst_port; - } +void NHTFlowCache::export_flow(FlowRecord** flow) +{ + export_flow(flow, get_export_reason((*flow)->m_flow)); } -void FlowRecord::update(const Packet& pkt, bool src) +void NHTFlowCache::export_flow(size_t flow_index, int reason) { - m_flow.time_last = pkt.ts; - if (src) { - m_flow.src_packets++; - m_flow.src_bytes += pkt.ip_len; + export_flow(&m_flow_table[flow_index], reason); +} - if (pkt.ip_proto == IPPROTO_TCP) { - m_flow.src_tcp_flags |= pkt.tcp_flags; - } - } else { - m_flow.dst_packets++; - m_flow.dst_bytes += pkt.ip_len; +void NHTFlowCache::export_flow(FlowRecord** flow_ptr, int reason) +{ + FlowRecord*& flow = *flow_ptr; + if (flow->is_empty()) { + return; + } + flow->m_flow.end_reason = reason; + update_flow_record_stats(flow->m_flow.src_packets + flow->m_flow.dst_packets); + update_flow_end_reason_stats(flow->m_flow.end_reason); + m_cache_stats.exported++; + push_to_export_queue(flow); + flow->erase(); + m_cache_stats.flows_in_cache--; +} - if (pkt.ip_proto == IPPROTO_TCP) { - m_flow.dst_tcp_flags |= pkt.tcp_flags; - } - } +void NHTFlowCache::push_to_export_queue(size_t flow_index) noexcept +{ + push_to_export_queue(m_flow_table[flow_index]); } -NHTFlowCache::NHTFlowCache(const std::string& params, ipx_ring_t* queue) - : m_cache_size(0) - , m_line_size(0) - , m_line_mask(0) - , m_line_new_idx(0) - , m_qsize(0) - , m_qidx(0) - , m_timeout_idx(0) - , m_active(0) - , m_inactive(0) - , m_split_biflow(false) - , m_enable_fragmentation_cache(true) - , m_keylen(0) - , m_key() - , m_key_inv() - , m_flow_table(nullptr) - , m_flow_records(nullptr) - , m_fragmentation_cache(0, 0) -{ - set_queue(queue); - init(params.c_str()); +void NHTFlowCache::push_to_export_queue(FlowRecord*& flow) noexcept +{ + ipx_ring_push(m_export_queue, &flow->m_flow); + std::swap(flow, m_flow_table[m_cache_size + m_queue_index]); + m_queue_index = (m_queue_index + 1) % m_queue_size; } -NHTFlowCache::~NHTFlowCache() +void NHTFlowCache::export_and_reuse_flow(size_t flow_index) noexcept { - close(); -} - -void NHTFlowCache::init(const char* params) -{ - CacheOptParser parser; - try { - parser.parse(params); - } catch (ParserError& e) { - throw PluginError(e.what()); - } - - m_cache_size = parser.m_cache_size; - m_line_size = parser.m_line_size; - m_active = parser.m_active; - m_inactive = parser.m_inactive; - m_qidx = 0; - m_timeout_idx = 0; - m_line_mask = (m_cache_size - 1) & ~(m_line_size - 1); - m_line_new_idx = m_line_size / 2; - - if (m_export_queue == nullptr) { - throw PluginError("output queue must be set before init"); - } - - if (m_line_size > m_cache_size) { - throw PluginError("flow cache line size must be greater or equal to cache size"); - } - if (m_cache_size == 0) { - throw PluginError("flow cache won't properly work with 0 records"); - } - - try { - m_flow_table = new FlowRecord*[m_cache_size + m_qsize]; - m_flow_records = new FlowRecord[m_cache_size + m_qsize]; - for (decltype(m_cache_size + m_qsize) i = 0; i < m_cache_size + m_qsize; i++) { - m_flow_table[i] = m_flow_records + i; - } - } catch (std::bad_alloc& e) { - throw PluginError("not enough memory for flow cache allocation"); - } - - m_split_biflow = parser.m_split_biflow; - m_enable_fragmentation_cache = parser.m_enable_fragmentation_cache; - - if (m_enable_fragmentation_cache) { - try { - m_fragmentation_cache - = FragmentationCache(parser.m_frag_cache_size, parser.m_frag_cache_timeout); - } catch (std::bad_alloc& e) { - throw PluginError("not enough memory for fragment cache allocation"); - } - } - -#ifdef FLOW_CACHE_STATS - m_empty = 0; - m_not_empty = 0; - m_hits = 0; - m_expired = 0; - m_flushed = 0; - m_lookups = 0; - m_lookups2 = 0; -#endif /* FLOW_CACHE_STATS */ + push_to_export_queue(flow_index); + m_flow_table[flow_index]->m_flow.remove_extensions(); + const size_t old_queue_index = m_queue_index == 0 ? m_queue_size - 1 : m_queue_index - 1; + *m_flow_table[flow_index] = *m_flow_table[m_cache_size + old_queue_index]; + m_flow_table[flow_index]->m_flow.m_exts = nullptr; + m_flow_table[flow_index]->reuse(); // Clean counters, set time first to last } -void NHTFlowCache::close() +void NHTFlowCache::flush(Packet &pkt, size_t flow_index, int return_flags) { - if (m_flow_records != nullptr) { - delete[] m_flow_records; - m_flow_records = nullptr; - } - if (m_flow_table != nullptr) { - delete[] m_flow_table; - m_flow_table = nullptr; - } + m_cache_stats.flushed++; + + if (return_flags == ProcessPlugin::FlowAction::FLUSH_WITH_REINSERT) { + export_and_reuse_flow(flow_index); + m_flow_table[flow_index]->update(pkt); // Set new counters from packet + const size_t post_create_return_flags = plugins_post_create(m_flow_table[flow_index]->m_flow, pkt); + if (post_create_return_flags & ProcessPlugin::FlowAction::FLUSH) { + flush(pkt, flow_index, post_create_return_flags); + } + return; + } + try_to_export(flow_index, false, FLOW_END_FORCED); } -void NHTFlowCache::set_queue(ipx_ring_t* queue) +NHTFlowCache::FlowSearch +NHTFlowCache::find_row(const FlowKey& key) noexcept { - m_export_queue = queue; - m_qsize = ipx_ring_size(queue); + const size_t hash_value = key.hash(); + const size_t first_flow_in_row = hash_value & m_line_mask; + const CacheRowSpan row(&m_flow_table[first_flow_in_row], m_line_size); + if (const std::optional flow_index = row.find_by_hash(hash_value); flow_index.has_value()) { + return {row, first_flow_in_row + *flow_index, hash_value}; + } + return {row, std::nullopt, hash_value}; } -void NHTFlowCache::export_flow(size_t index) +std::pair +NHTFlowCache::find_flow_index(const FlowKey& key, bool swapped) noexcept { - m_total_exported++; - update_flow_end_reason_stats(m_flow_table[index]->m_flow.end_reason); - update_flow_record_stats( - m_flow_table[index]->m_flow.src_packets + m_flow_table[index]->m_flow.dst_packets); - m_flows_in_cache--; + const FlowSearch search = find_row(key); + if (search.flow_found()) { + return {search, m_flow_table[*search.flow_index]->m_flow.swapped == swapped}; + } + return {search, swapped && !m_split_biflow}; +} - ipx_ring_push(m_export_queue, &m_flow_table[index]->m_flow); - std::swap(m_flow_table[index], m_flow_table[m_cache_size + m_qidx]); - m_flow_table[index]->erase(); - m_qidx = (m_qidx + 1) % m_qsize; +static bool is_tcp_connection_restart(const Packet& packet, const Flow& flow) noexcept +{ + constexpr uint8_t TCP_FIN = 0x01; + constexpr uint8_t TCP_RST = 0x04; + constexpr uint8_t TCP_SYN = 0x02; + const uint8_t flags = packet.source_pkt ? flow.src_tcp_flags : flow.dst_tcp_flags; + return (packet.tcp_flags & TCP_SYN) && (flags & (TCP_FIN | TCP_RST)); } -void NHTFlowCache::finish() +bool NHTFlowCache::try_to_export_on_inactive_timeout(size_t flow_index, const timeval& now) noexcept { - for (decltype(m_cache_size) i = 0; i < m_cache_size; i++) { - if (!m_flow_table[i]->is_empty()) { - plugins_pre_export(m_flow_table[i]->m_flow); - m_flow_table[i]->m_flow.end_reason = FLOW_END_FORCED; - export_flow(i); -#ifdef FLOW_CACHE_STATS - m_expired++; -#endif /* FLOW_CACHE_STATS */ - } - } -} - -void NHTFlowCache::flush(Packet& pkt, size_t flow_index, int ret, bool source_flow) -{ -#ifdef FLOW_CACHE_STATS - m_flushed++; -#endif /* FLOW_CACHE_STATS */ - - if (ret == FLOW_FLUSH_WITH_REINSERT) { - FlowRecord* flow = m_flow_table[flow_index]; - flow->m_flow.end_reason = FLOW_END_FORCED; - ipx_ring_push(m_export_queue, &flow->m_flow); - - std::swap(m_flow_table[flow_index], m_flow_table[m_cache_size + m_qidx]); - - flow = m_flow_table[flow_index]; - flow->m_flow.remove_extensions(); - *flow = *m_flow_table[m_cache_size + m_qidx]; - m_qidx = (m_qidx + 1) % m_qsize; - - flow->m_flow.m_exts = nullptr; - flow->reuse(); // Clean counters, set time first to last - flow->update(pkt, source_flow); // Set new counters from packet - - ret = plugins_post_create(flow->m_flow, pkt); - if (ret & FLOW_FLUSH) { - flush(pkt, flow_index, ret, source_flow); - } - } else { - m_flow_table[flow_index]->m_flow.end_reason = FLOW_END_FORCED; - export_flow(flow_index); - } -} - -int NHTFlowCache::put_pkt(Packet& pkt) -{ - int ret = plugins_pre_create(pkt); - - if (m_enable_fragmentation_cache) { - try_to_fill_ports_to_fragmented_packet(pkt); - } - - if (!create_hash_key(pkt)) { // saves key value and key length into attributes NHTFlowCache::key - // and NHTFlowCache::m_keylen - return 0; - } - - prefetch_export_expired(); - - uint64_t hashval - = XXH64(m_key, m_keylen, 0); /* Calculates hash value from key created before. */ - - FlowRecord* flow; /* Pointer to flow we will be working with. */ - bool found = false; - bool source_flow = true; - uint32_t line_index = hashval & m_line_mask; /* Get index of flow line. */ - uint32_t flow_index = 0; - uint32_t next_line = line_index + m_line_size; - - /* Find existing flow record in flow cache. */ - for (flow_index = line_index; flow_index < next_line; flow_index++) { - if (m_flow_table[flow_index]->belongs(hashval)) { - found = true; - break; - } - } - - /* Find inversed flow. */ - if (!found && !m_split_biflow) { - uint64_t hashval_inv = XXH64(m_key_inv, m_keylen, 0); - uint64_t line_index_inv = hashval_inv & m_line_mask; - uint64_t next_line_inv = line_index_inv + m_line_size; - for (flow_index = line_index_inv; flow_index < next_line_inv; flow_index++) { - if (m_flow_table[flow_index]->belongs(hashval_inv)) { - found = true; - source_flow = false; - hashval = hashval_inv; - line_index = line_index_inv; - break; - } - } - } - - if (found) { - /* Existing flow record was found, put flow record at the first index of flow line. */ -#ifdef FLOW_CACHE_STATS - m_lookups += (flow_index - line_index + 1); - m_lookups2 += (flow_index - line_index + 1) * (flow_index - line_index + 1); -#endif /* FLOW_CACHE_STATS */ - - flow = m_flow_table[flow_index]; - for (decltype(flow_index) j = flow_index; j > line_index; j--) { - m_flow_table[j] = m_flow_table[j - 1]; - } - - m_flow_table[line_index] = flow; - flow_index = line_index; -#ifdef FLOW_CACHE_STATS - m_hits++; -#endif /* FLOW_CACHE_STATS */ - } else { - /* Existing flow record was not found. Find free place in flow line. */ - for (flow_index = line_index; flow_index < next_line; flow_index++) { - if (m_flow_table[flow_index]->is_empty()) { - found = true; - break; - } - } - if (!found) { - /* If free place was not found (flow line is full), find - * record which will be replaced by new record. */ - flow_index = next_line - 1; - - // Export flow - plugins_pre_export(m_flow_table[flow_index]->m_flow); - m_flow_table[flow_index]->m_flow.end_reason = FLOW_END_NO_RES; - export_flow(flow_index); - -#ifdef FLOW_CACHE_STATS - m_expired++; -#endif /* FLOW_CACHE_STATS */ - uint32_t flow_new_index = line_index + m_line_new_idx; - flow = m_flow_table[flow_index]; - for (decltype(flow_index) j = flow_index; j > flow_new_index; j--) { - m_flow_table[j] = m_flow_table[j - 1]; - } - flow_index = flow_new_index; - m_flow_table[flow_new_index] = flow; -#ifdef FLOW_CACHE_STATS - m_not_empty++; - } else { - m_empty++; -#endif /* FLOW_CACHE_STATS */ - } - } - - pkt.source_pkt = source_flow; - flow = m_flow_table[flow_index]; - - uint8_t flw_flags = source_flow ? flow->m_flow.src_tcp_flags : flow->m_flow.dst_tcp_flags; - if ((pkt.tcp_flags & 0x02) && (flw_flags & (0x01 | 0x04))) { - // Flows with FIN or RST TCP flags are exported when new SYN packet arrives - m_flow_table[flow_index]->m_flow.end_reason = FLOW_END_EOF; - export_flow(flow_index); - put_pkt(pkt); - return 0; - } - - if (flow->is_empty()) { - m_flows_in_cache++; - flow->create(pkt, hashval); - ret = plugins_post_create(flow->m_flow, pkt); - - if (ret & FLOW_FLUSH) { - export_flow(flow_index); -#ifdef FLOW_CACHE_STATS - m_flushed++; -#endif /* FLOW_CACHE_STATS */ - } - } else { - /* Check if flow record is expired (inactive timeout). */ - if (pkt.ts.tv_sec - flow->m_flow.time_last.tv_sec >= m_inactive) { - m_flow_table[flow_index]->m_flow.end_reason = get_export_reason(flow->m_flow); - plugins_pre_export(flow->m_flow); - export_flow(flow_index); -#ifdef FLOW_CACHE_STATS - m_expired++; -#endif /* FLOW_CACHE_STATS */ - return put_pkt(pkt); - } - - /* Check if flow record is expired (active timeout). */ - if (pkt.ts.tv_sec - flow->m_flow.time_first.tv_sec >= m_active) { - m_flow_table[flow_index]->m_flow.end_reason = FLOW_END_ACTIVE; - plugins_pre_export(flow->m_flow); - export_flow(flow_index); -#ifdef FLOW_CACHE_STATS - m_expired++; -#endif /* FLOW_CACHE_STATS */ - return put_pkt(pkt); - } - - ret = plugins_pre_update(flow->m_flow, pkt); - if (ret & FLOW_FLUSH) { - flush(pkt, flow_index, ret, source_flow); - return 0; - } else { - flow->update(pkt, source_flow); - ret = plugins_post_update(flow->m_flow, pkt); - - if (ret & FLOW_FLUSH) { - flush(pkt, flow_index, ret, source_flow); - return 0; - } - } - } - - export_expired(pkt.ts.tv_sec); - return 0; + if (!m_flow_table[flow_index]->is_empty() && now.tv_sec - m_flow_table[flow_index]->m_flow.time_last.tv_sec >= m_inactive_timeout) { + try_to_export(flow_index, false); + return true; + } + return false; } -void NHTFlowCache::try_to_fill_ports_to_fragmented_packet(Packet& packet) +void NHTFlowCache::create_record(const Packet& packet, size_t flow_index, size_t hash_value) noexcept { - m_fragmentation_cache.process_packet(packet); + m_cache_stats.flows_in_cache++; + m_flow_table[flow_index]->create(packet, hash_value); + const size_t post_create_return_flags = plugins_post_create(m_flow_table[flow_index]->m_flow, packet); + if (post_create_return_flags & ProcessPlugin::FlowAction::FLUSH) { + try_to_export(flow_index, false, FLOW_END_FORCED); + m_cache_stats.flushed++; + return; + } } -uint8_t NHTFlowCache::get_export_reason(Flow& flow) +int NHTFlowCache::update_flow(Packet& packet, size_t flow_index) noexcept { - if ((flow.src_tcp_flags | flow.dst_tcp_flags) & (0x01 | 0x04)) { - // When FIN or RST is set, TCP connection ended naturally - return FLOW_END_EOF; - } else { - return FLOW_END_INACTIVE; - } + if (is_tcp_connection_restart(packet, m_flow_table[flow_index]->m_flow)) { + try_to_export(flow_index, false, FLOW_END_EOF); + put_pkt(packet); + return 0; + } + + /* Check if flow record is expired (inactive timeout). */ + if (try_to_export_on_inactive_timeout(flow_index, packet.ts)) { + return put_pkt(packet); + } + + if (try_to_export_on_active_timeout(flow_index, packet.ts)) { + return put_pkt(packet); + } + + const size_t pre_update_return_flags = plugins_pre_update(m_flow_table[flow_index]->m_flow, packet); + if ((pre_update_return_flags & ProcessPlugin::FlowAction::FLUSH)) { + flush(packet, flow_index, pre_update_return_flags); + return 0; + } + + m_flow_table[flow_index]->update(packet); + const size_t post_update_return_flags = plugins_post_update(m_flow_table[flow_index]->m_flow, packet); + if ((post_update_return_flags & ProcessPlugin::FlowAction::FLUSH)) { + flush(packet, flow_index, post_update_return_flags); + return 0; + } + + export_expired(packet.ts); + return 0; } -void NHTFlowCache::export_expired(time_t ts) +void NHTFlowCache::try_to_export(size_t flow_index, bool call_pre_export) noexcept { - for (decltype(m_timeout_idx) i = m_timeout_idx; i < m_timeout_idx + m_line_new_idx; i++) { - if (!m_flow_table[i]->is_empty() - && ts - m_flow_table[i]->m_flow.time_last.tv_sec >= m_inactive) { - m_flow_table[i]->m_flow.end_reason = get_export_reason(m_flow_table[i]->m_flow); - plugins_pre_export(m_flow_table[i]->m_flow); - export_flow(i); -#ifdef FLOW_CACHE_STATS - m_expired++; -#endif /* FLOW_CACHE_STATS */ - } - } + try_to_export(flow_index, call_pre_export, get_export_reason(m_flow_table[flow_index]->m_flow)); +} + +void NHTFlowCache::try_to_export(size_t flow_index, bool call_pre_export, int reason) noexcept +{ + if (call_pre_export) { + plugins_pre_export(m_flow_table[flow_index]->m_flow); + } + export_flow(flow_index, reason); +} - m_timeout_idx = (m_timeout_idx + m_line_new_idx) & (m_cache_size - 1); +static bool check_ip_version(const Packet& pkt) noexcept +{ + return pkt.ip_version == IP::v4 || pkt.ip_version == IP::v6; +} + +int NHTFlowCache::put_pkt(Packet& packet) +{ + plugins_pre_create(packet); + + if (m_enable_fragmentation_cache) { + try_to_fill_ports_to_fragmented_packet(packet); + } + + if (!check_ip_version(packet)) { + return 0; + } + + const auto [key, swapped] = m_split_biflow + ? std::make_pair(FlowKeyFactory::create_direct_key(&packet.src_ip, &packet.dst_ip, packet.src_port, packet.dst_port, + packet.ip_proto, static_cast(packet.ip_version), m_vlan_is_flow_key ? packet.vlan_id : FlowKeyFactory::EMPTY_VLAN), false) + : FlowKeyFactory::create_sorted_key(&packet.src_ip, &packet.dst_ip, packet.src_port, packet.dst_port, + packet.ip_proto, static_cast(packet.ip_version), m_vlan_is_flow_key ? packet.vlan_id : FlowKeyFactory::EMPTY_VLAN); + + prefetch_export_expired(); + + auto [flow_search, source_to_destination] = find_flow_index(key, swapped); + + packet.source_pkt = flow_search.flow_index.has_value() ? source_to_destination : true; + + if (!flow_search.flow_found()) { + const size_t empty_place = get_empty_place(flow_search.cache_row) + (flow_search.hash_value & m_line_mask); + create_record(packet, empty_place, flow_search.hash_value); + m_flow_table[empty_place]->m_flow.swapped = source_to_destination; + export_expired(packet.ts); + return 0; + } + + size_t flow_index = *flow_search.flow_index; + + /* Existing flow record was found, put flow record at the first index of flow line. */ + const size_t relative_flow_index = flow_index % m_line_size; + m_cache_stats.lookups += relative_flow_index + 1; + m_cache_stats.lookups2 += (relative_flow_index + 1) * (relative_flow_index + 1); + m_cache_stats.hits++; + + flow_search.cache_row.advance_flow(relative_flow_index); + return update_flow(packet, flow_index - relative_flow_index); } -bool NHTFlowCache::create_hash_key(Packet& pkt) +size_t NHTFlowCache::find_victim([[maybe_unused]] CacheRowSpan& row) const noexcept { - if (pkt.ip_version == IP::v4) { - struct flow_key_v4_t* key_v4 = reinterpret_cast(m_key); - struct flow_key_v4_t* key_v4_inv = reinterpret_cast(m_key_inv); + return m_line_size - 1; +} - key_v4->proto = pkt.ip_proto; - key_v4->ip_version = IP::v4; - key_v4->src_port = pkt.src_port; - key_v4->dst_port = pkt.dst_port; - key_v4->src_ip = pkt.src_ip.v4; - key_v4->dst_ip = pkt.dst_ip.v4; - key_v4->vlan_id = pkt.vlan_id; +size_t NHTFlowCache::get_empty_place(CacheRowSpan& row) noexcept +{ + if (const std::optional empty_index = row.find_empty(); empty_index.has_value()) { + m_cache_stats.empty++; + return empty_index.value(); + } + m_cache_stats.not_empty++; - key_v4_inv->proto = pkt.ip_proto; - key_v4_inv->ip_version = IP::v4; - key_v4_inv->src_port = pkt.dst_port; - key_v4_inv->dst_port = pkt.src_port; - key_v4_inv->src_ip = pkt.dst_ip.v4; - key_v4_inv->dst_ip = pkt.src_ip.v4; - key_v4_inv->vlan_id = pkt.vlan_id; + const size_t victim_index = m_line_size - 1; + row.advance_flow_to(victim_index, m_new_flow_insert_index); - m_keylen = sizeof(flow_key_v4_t); - return true; - } else if (pkt.ip_version == IP::v6) { - struct flow_key_v6_t* key_v6 = reinterpret_cast(m_key); - struct flow_key_v6_t* key_v6_inv = reinterpret_cast(m_key_inv); + try_to_export(&row[m_new_flow_insert_index] - m_flow_table.get(), true, FLOW_END_NO_RES); + return m_new_flow_insert_index; +} - key_v6->proto = pkt.ip_proto; - key_v6->ip_version = IP::v6; - key_v6->src_port = pkt.src_port; - key_v6->dst_port = pkt.dst_port; - memcpy(key_v6->src_ip, pkt.src_ip.v6, sizeof(pkt.src_ip.v6)); - memcpy(key_v6->dst_ip, pkt.dst_ip.v6, sizeof(pkt.dst_ip.v6)); - key_v6->vlan_id = pkt.vlan_id; +bool NHTFlowCache::try_to_export_on_active_timeout(size_t flow_index, const timeval& now) noexcept +{ + if (!m_flow_table[flow_index]->is_empty() && now.tv_sec - m_flow_table[flow_index]->m_flow.time_first.tv_sec >= m_active_timeout) { + try_to_export(flow_index, true, FLOW_END_ACTIVE); + return true; + } + return false; +} - key_v6_inv->proto = pkt.ip_proto; - key_v6_inv->ip_version = IP::v6; - key_v6_inv->src_port = pkt.dst_port; - key_v6_inv->dst_port = pkt.src_port; - memcpy(key_v6_inv->src_ip, pkt.dst_ip.v6, sizeof(pkt.dst_ip.v6)); - memcpy(key_v6_inv->dst_ip, pkt.src_ip.v6, sizeof(pkt.src_ip.v6)); - key_v6_inv->vlan_id = pkt.vlan_id; +void NHTFlowCache::try_to_fill_ports_to_fragmented_packet(Packet& packet) +{ + m_fragmentation_cache.process_packet(packet); +} - m_keylen = sizeof(flow_key_v6_t); - return true; - } +uint8_t NHTFlowCache::get_export_reason(const Flow& flow) +{ + constexpr uint8_t TCP_FIN = 0x01; + constexpr uint8_t TCP_RST = 0x04; + if ((flow.src_tcp_flags | flow.dst_tcp_flags) & (TCP_FIN | TCP_RST)) { + // When FIN or RST is set, TCP connection ended naturally + return FLOW_END_EOF; + } + return FLOW_END_INACTIVE; +} - return false; +void NHTFlowCache::export_expired(time_t now) +{ + export_expired({now, 0}); } -#ifdef FLOW_CACHE_STATS -void NHTFlowCache::print_report() +void NHTFlowCache::export_expired(const timeval& now) { - float tmp = float(m_lookups) / m_hits; + for (size_t i = m_last_exported_on_timeout_index; i < m_last_exported_on_timeout_index + m_new_flow_insert_index; i++) { + try_to_export_on_inactive_timeout(i, now); + } + m_last_exported_on_timeout_index = (m_last_exported_on_timeout_index + m_new_flow_insert_index) & (m_cache_size - 1); +} - cout << "Hits: " << m_hits << endl; - cout << "Empty: " << m_empty << endl; - cout << "Not empty: " << m_not_empty << endl; - cout << "Expired: " << m_expired << endl; - cout << "Flushed: " << m_flushed << endl; - cout << "Average Lookup: " << tmp << endl; - cout << "Variance Lookup: " << float(m_lookups2) / m_hits - tmp * tmp << endl; +void NHTFlowCache::print_report() const +{ + const float tmp = static_cast(m_cache_stats.lookups) / m_cache_stats.hits; + std::cout << "Hits: " << m_cache_stats.hits << "\n"; + std::cout << "Empty: " << m_cache_stats.empty << "\n"; + std::cout << "Not empty: " << m_cache_stats.not_empty << "\n"; + std::cout << "Expired: " << m_cache_stats.exported << "\n"; + std::cout << "Flushed: " << m_cache_stats.flushed << "\n"; + std::cout << "Average Lookup: " << tmp << "\n"; + std::cout << "Variance Lookup: " << static_cast(m_cache_stats.lookups2) / m_cache_stats.hits - tmp * tmp << "\n"; + std::cout << "Flow end stats: " << "\n"; + std::cout << "Flow end reason: active timeout: " << m_flow_end_reason_stats.active_timeout << "\n"; + std::cout << "Flow end reason: inactive timeout: " << m_flow_end_reason_stats.inactive_timeout << "\n"; + std::cout << "Flow end reason: end of flow: " << m_flow_end_reason_stats.end_of_flow << "\n"; + std::cout << "Flow end reason: collision: " << m_flow_end_reason_stats.collision << "\n"; + std::cout << "Flow end reason: forced: " << m_flow_end_reason_stats.forced << "\n"; } -#endif /* FLOW_CACHE_STATS */ void NHTFlowCache::set_telemetry_dir(std::shared_ptr dir) { - telemetry::FileOps statsOps = {[this]() { return get_cache_telemetry(); }, nullptr}; - register_file(dir, "cache-stats", statsOps); + telemetry::FileOps statsOps = {[this]() -> telemetry::Content { return get_cache_telemetry(); }, nullptr}; + register_file(dir, "cache-stats", statsOps); - if (m_enable_fragmentation_cache) { - m_fragmentation_cache.set_telemetry_dir(dir); - } + if (m_enable_fragmentation_cache) { + m_fragmentation_cache.set_telemetry_dir(dir); + } } void NHTFlowCache::update_flow_record_stats(uint64_t packets_count) { - if (packets_count == 1) { - m_flow_record_stats.packets_count_1++; - } else if (packets_count >= 2 && packets_count <= 5) { - m_flow_record_stats.packets_count_2_5++; - } else if (packets_count >= 6 && packets_count <= 10) { - m_flow_record_stats.packets_count_6_10++; - } else if (packets_count >= 11 && packets_count <= 20) { - m_flow_record_stats.packets_count_11_20++; - } else if (packets_count >= 21 && packets_count <= 50) { - m_flow_record_stats.packets_count_21_50++; - } else { - m_flow_record_stats.packets_count_51_plus++; - } + if (packets_count == 1) { + m_flow_record_stats.packets_count_1++; + } else if (packets_count >= 2 && packets_count <= 5) { + m_flow_record_stats.packets_count_2_5++; + } else if (packets_count >= 6 && packets_count <= 10) { + m_flow_record_stats.packets_count_6_10++; + } else if (packets_count >= 11 && packets_count <= 20) { + m_flow_record_stats.packets_count_11_20++; + } else if (packets_count >= 21 && packets_count <= 50) { + m_flow_record_stats.packets_count_21_50++; + } else { + m_flow_record_stats.packets_count_51_plus++; + } } void NHTFlowCache::update_flow_end_reason_stats(uint8_t reason) { - switch (reason) { - case FLOW_END_ACTIVE: - m_flow_end_reason_stats.active_timeout++; - break; - case FLOW_END_INACTIVE: - m_flow_end_reason_stats.inactive_timeout++; - break; - case FLOW_END_EOF: - m_flow_end_reason_stats.end_of_flow++; - break; - case FLOW_END_NO_RES: - m_flow_end_reason_stats.collision++; - break; - case FLOW_END_FORCED: - m_flow_end_reason_stats.forced++; - break; - default: - break; - } -} - -telemetry::Content NHTFlowCache::get_cache_telemetry() -{ - telemetry::Dict dict; - - dict["FlowEndReason:ActiveTimeout"] = m_flow_end_reason_stats.active_timeout; - dict["FlowEndReason:InactiveTimeout"] = m_flow_end_reason_stats.inactive_timeout; - dict["FlowEndReason:EndOfFlow"] = m_flow_end_reason_stats.end_of_flow; - dict["FlowEndReason:Collision"] = m_flow_end_reason_stats.collision; - dict["FlowEndReason:Forced"] = m_flow_end_reason_stats.forced; - - dict["FlowsInCache"] = m_flows_in_cache; - dict["FlowCacheUsage"] - = telemetry::ScalarWithUnit {double(m_flows_in_cache) / m_cache_size * 100, "%"}; - - dict["FlowRecordStats:1packet"] = m_flow_record_stats.packets_count_1; - dict["FlowRecordStats:2-5packets"] = m_flow_record_stats.packets_count_2_5; - dict["FlowRecordStats:6-10packets"] = m_flow_record_stats.packets_count_6_10; - dict["FlowRecordStats:11-20packets"] = m_flow_record_stats.packets_count_11_20; - dict["FlowRecordStats:21-50packets"] = m_flow_record_stats.packets_count_21_50; - dict["FlowRecordStats:51-plusPackets"] = m_flow_record_stats.packets_count_51_plus; - - dict["TotalExportedFlows"] = m_total_exported; - - return dict; + switch (reason) { + case FLOW_END_ACTIVE: + m_flow_end_reason_stats.active_timeout++; + break; + case FLOW_END_INACTIVE: + m_flow_end_reason_stats.inactive_timeout++; + break; + case FLOW_END_EOF: + m_flow_end_reason_stats.end_of_flow++; + break; + case FLOW_END_NO_RES: + m_flow_end_reason_stats.collision++; + break; + case FLOW_END_FORCED: + m_flow_end_reason_stats.forced++; + break; + default: + break; + } +} + +telemetry::Dict NHTFlowCache::get_cache_telemetry() +{ + telemetry::Dict dict; + + dict["FlowEndReason:ActiveTimeout"] = m_flow_end_reason_stats.active_timeout; + dict["FlowEndReason:InactiveTimeout"] = m_flow_end_reason_stats.inactive_timeout; + dict["FlowEndReason:EndOfFlow"] = m_flow_end_reason_stats.end_of_flow; + dict["FlowEndReason:Collision"] = m_flow_end_reason_stats.collision; + dict["FlowEndReason:Forced"] = m_flow_end_reason_stats.forced; + + dict["FlowsInCache"] = m_cache_stats.flows_in_cache; + dict["FlowCacheUsage"] = telemetry::ScalarWithUnit {double(m_cache_stats.flows_in_cache) / m_cache_size * 100, "%"}; + + dict["FlowRecordStats:1packet"] = m_flow_record_stats.packets_count_1; + dict["FlowRecordStats:2-5packets"] = m_flow_record_stats.packets_count_2_5; + dict["FlowRecordStats:6-10packets"] = m_flow_record_stats.packets_count_6_10; + dict["FlowRecordStats:11-20packets"] = m_flow_record_stats.packets_count_11_20; + dict["FlowRecordStats:21-50packets"] = m_flow_record_stats.packets_count_21_50; + dict["FlowRecordStats:51-plusPackets"] = m_flow_record_stats.packets_count_51_plus; + + dict["TotalExportedFlows"] = m_cache_stats.exported; + return dict; } void NHTFlowCache::prefetch_export_expired() const { - for (decltype(m_timeout_idx) i = m_timeout_idx; i < m_timeout_idx + m_line_new_idx; i++) { - __builtin_prefetch(m_flow_table[i], 0, 1); - } + for (decltype(m_last_exported_on_timeout_index) i = m_last_exported_on_timeout_index; i < m_last_exported_on_timeout_index + m_new_flow_insert_index; i++) { + __builtin_prefetch(m_flow_table[i], 0, 1); + } } static const PluginRegistrar cacheRegistrar(cachePluginManifest); - -} // namespace ipxp +} diff --git a/src/plugins/storage/cache/src/cache.hpp b/src/plugins/storage/cache/src/cache.hpp index 704d6e402..4874cdda9 100644 --- a/src/plugins/storage/cache/src/cache.hpp +++ b/src/plugins/storage/cache/src/cache.hpp @@ -1,328 +1,183 @@ /** - * @file - * @brief "NewHashTable" flow cache - * @author Martin Zadnik - * @author Vaclav Bartos - * @author Jiri Havranek - * @author Pavel Siska - * @date 2025 + * \file cache.hpp + * \brief "NewHashTable" flow cache + * \author Martin Zadnik + * \author Vaclav Bartos + * \author Jiri Havranek + * \author Pavel Siska + * \author Damir Zainullin + * \date 2014 + * \date 2015 + * \date 2016 + * \date 2023 + * \date 2025 + */ +/* + * Copyright (C) 2023 CESNET * - * Copyright (c) 2025 CESNET + * LICENSE TERMS * - * SPDX-License-Identifier: BSD-3-Clause + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. */ - #pragma once -#include "fragmentationCache/fragmentationCache.hpp" - +#include #include - -#include -#include #include +#include +#include #include -#include +#include +#include "fragmentationCache/fragmentationCache.hpp" +#include "cacheOptParser.hpp" +#include "cacheRowSpan.hpp" +#include "flowKey.hpp" +#include "flowRecord.hpp" +#include "cacheStats.hpp" +#include namespace ipxp { -struct __attribute__((packed)) flow_key_v4_t { - uint16_t src_port; - uint16_t dst_port; - uint8_t proto; - uint8_t ip_version; - uint32_t src_ip; - uint32_t dst_ip; - uint16_t vlan_id; -}; - -struct __attribute__((packed)) flow_key_v6_t { - uint16_t src_port; - uint16_t dst_port; - uint8_t proto; - uint8_t ip_version; - uint8_t src_ip[16]; - uint8_t dst_ip[16]; - uint16_t vlan_id; -}; - -#define MAX_KEY_LENGTH (max(sizeof(flow_key_v4_t), sizeof(flow_key_v6_t))) - -#ifdef IPXP_FLOW_CACHE_SIZE -static const uint32_t DEFAULT_FLOW_CACHE_SIZE = IPXP_FLOW_CACHE_SIZE; -#else -static const uint32_t DEFAULT_FLOW_CACHE_SIZE = 17; // 131072 records total -#endif /* IPXP_FLOW_CACHE_SIZE */ - -#ifdef IPXP_FLOW_LINE_SIZE -static const uint32_t DEFAULT_FLOW_LINE_SIZE = IPXP_FLOW_LINE_SIZE; -#else -static const uint32_t DEFAULT_FLOW_LINE_SIZE = 4; // 16 records per line -#endif /* IPXP_FLOW_LINE_SIZE */ - -static const uint32_t DEFAULT_INACTIVE_TIMEOUT = 30; -static const uint32_t DEFAULT_ACTIVE_TIMEOUT = 300; - -static_assert( - std::is_unsigned(), - "Static checks of default cache sizes won't properly work without unsigned type."); -static_assert( - bitcount(-1) > DEFAULT_FLOW_CACHE_SIZE, - "Flow cache size is too big to fit in variable!"); -static_assert( - bitcount(-1) > DEFAULT_FLOW_LINE_SIZE, - "Flow cache line size is too big to fit in variable!"); - -static_assert(DEFAULT_FLOW_LINE_SIZE >= 1, "Flow cache line size must be at least 1!"); -static_assert( - DEFAULT_FLOW_CACHE_SIZE >= DEFAULT_FLOW_LINE_SIZE, - "Flow cache size must be at least cache line size!"); - -class CacheOptParser : public OptionsParser { -public: - uint32_t m_cache_size; - uint32_t m_line_size; - uint32_t m_active; - uint32_t m_inactive; - bool m_split_biflow; - bool m_enable_fragmentation_cache; - std::size_t m_frag_cache_size; - time_t m_frag_cache_timeout; - - CacheOptParser() - : OptionsParser("cache", "Storage plugin implemented as a hash table") - , m_cache_size(1 << DEFAULT_FLOW_CACHE_SIZE) - , m_line_size(1 << DEFAULT_FLOW_LINE_SIZE) - , m_active(DEFAULT_ACTIVE_TIMEOUT) - , m_inactive(DEFAULT_INACTIVE_TIMEOUT) - , m_split_biflow(false) - , m_enable_fragmentation_cache(true) - , m_frag_cache_size(10007) - , // Prime for better distribution in hash table - m_frag_cache_timeout(3) - { - register_option( - "s", - "size", - "EXPONENT", - "Cache size exponent to the power of two", - [this](const char* arg) { - try { - unsigned exp = str2num(arg); - if (exp < 4 || exp > 30) { - throw PluginError("Flow cache size must be between 4 and 30"); - } - m_cache_size = static_cast(1) << exp; - } catch (std::invalid_argument& e) { - return false; - } - return true; - }, - OptionFlags::RequiredArgument); - register_option( - "l", - "line", - "EXPONENT", - "Cache line size exponent to the power of two", - [this](const char* arg) { - try { - m_line_size = static_cast(1) << str2num(arg); - if (m_line_size < 1) { - throw PluginError("Flow cache line size must be at least 1"); - } - } catch (std::invalid_argument& e) { - return false; - } - return true; - }, - OptionFlags::RequiredArgument); - register_option( - "a", - "active", - "TIME", - "Active timeout in seconds", - [this](const char* arg) { - try { - m_active = str2num(arg); - } catch (std::invalid_argument& e) { - return false; - } - return true; - }, - OptionFlags::RequiredArgument); - register_option( - "i", - "inactive", - "TIME", - "Inactive timeout in seconds", - [this](const char* arg) { - try { - m_inactive = str2num(arg); - } catch (std::invalid_argument& e) { - return false; - } - return true; - }, - OptionFlags::RequiredArgument); - register_option( - "S", - "split", - "", - "Split biflows into uniflows", - [this](const char* arg) { - (void) arg; - m_split_biflow = true; - return true; - }, - OptionFlags::NoArgument); - register_option( - "fe", - "frag-enable", - "true|false", - "Enable/disable fragmentation cache. Enabled (true) by default.", - [this](const char* arg) { - if (strcmp(arg, "true") == 0) { - m_enable_fragmentation_cache = true; - } else if (strcmp(arg, "false") == 0) { - m_enable_fragmentation_cache = false; - } else { - return false; - } - return true; - }, - OptionFlags::RequiredArgument); - register_option( - "fs", - "frag-size", - "size", - "Size of fragmentation cache, must be at least 1. Default value is 10007.", - [this](const char* arg) { - try { - m_frag_cache_size = str2num(arg); - } catch (std::invalid_argument& e) { - return false; - } - return m_frag_cache_size > 0; - }); - register_option( - "ft", - "frag-timeout", - "TIME", - "Timeout of fragments in fragmentation cache in seconds. Default value is 3.", - [this](const char* arg) { - try { - m_frag_cache_timeout = str2num(arg); - } catch (std::invalid_argument& e) { - return false; - } - return true; - }); - } -}; - -class alignas(64) FlowRecord { - uint64_t m_hash; - +class NHTFlowCache : protected TelemetryUtils, public StoragePlugin +{ public: - Flow m_flow; - - FlowRecord(); - ~FlowRecord(); - void erase(); - void reuse(); - - inline bool is_empty() const; - inline bool belongs(uint64_t pkt_hash) const; - void create(const Packet& pkt, uint64_t pkt_hash); - void update(const Packet& pkt, bool src); -}; - -struct FlowEndReasonStats { - uint64_t active_timeout; - uint64_t inactive_timeout; - uint64_t end_of_flow; - uint64_t collision; - uint64_t forced; -}; - -struct FlowRecordStats { - uint64_t packets_count_1; - uint64_t packets_count_2_5; - uint64_t packets_count_6_10; - uint64_t packets_count_11_20; - uint64_t packets_count_21_50; - uint64_t packets_count_51_plus; -}; - -class NHTFlowCache - : TelemetryUtils - , public StoragePlugin { -public: - NHTFlowCache(const std::string& params, ipx_ring_t* queue); - ~NHTFlowCache(); - void init(const char* params); - void close(); - void set_queue(ipx_ring_t* queue); - OptionsParser* get_parser() const { return new CacheOptParser(); } - std::string get_name() const { return "cache"; } - - int put_pkt(Packet& pkt); - void export_expired(time_t ts); - - /** - * @brief Set and configure the telemetry directory where cache stats will be stored. - */ - void set_telemetry_dir(std::shared_ptr dir) override; + /** + * @brief Constructor + * @param vlan_is_flow_key If true, VLAN ID is included in the flow key, not included otherwise. + */ + NHTFlowCache(bool vlan_is_flow_key = true); + + /** + * @brief Constructor + * @param params Parameters for the cache. + * @param queue Pointer to the ring buffer + */ + NHTFlowCache(const std::string& params, ipx_ring_t* queue); + + ~NHTFlowCache() override; + + /** + * @brief Get the options parser for the cache. + * @return Pointer to the options parser. + */ + OptionsParser * get_parser() const override; + + /** + * @brief Get the name of the cache. + * @return Name of the cache. + */ + std::string get_name() const noexcept override; + + /** + * @brief Insert a packet into the cache. + * @param packet The packet to be inserted. + * @return 0 on success, negative value on error. + */ + int put_pkt(Packet& packet) override; + + /** + * @brief Export expired flows. + * @param now Current time in seconds since the epoch. + */ + void export_expired(time_t now) override; + + /** + * @brief Set and configure the telemetry directory where cache stats will be stored. + */ + void set_telemetry_dir(std::shared_ptr dir) override; + + /** + * @brief Finish the cache, export all remaining flows. + */ + void finish() override; + +protected: + struct FlowSearch { + CacheRowSpan cache_row; // Cache row where the flow to which packet belongs must be stored + std::optional flow_index; // Index of the flow in the table, if found + size_t hash_value; // Hash value of the flow + + /** + * @brief Check if the flow was found in the cache. + * @return True if the flow was found, false otherwise. + */ + bool flow_found() const noexcept { + return flow_index.has_value(); + } + }; + + uint32_t m_cache_size{0}; + uint32_t m_line_size{0}; + uint32_t m_line_mask{0}; + uint32_t m_new_flow_insert_index{0}; + uint32_t m_queue_size{0}; + uint32_t m_queue_index{0}; + uint32_t m_last_exported_on_timeout_index{0}; + + uint32_t m_active_timeout{0}; + uint32_t m_inactive_timeout{0}; + bool m_split_biflow{false}; + bool m_enable_fragmentation_cache{true}; + std::unique_ptr m_flow_table; + std::unique_ptr m_flows; + + FragmentationCache m_fragmentation_cache{0,0}; + FlowEndReasonStats m_flow_end_reason_stats = {}; + FlowRecordStats m_flow_record_stats = {}; + FlowCacheStats m_cache_stats = {}; + + void init(const char* params) override; + void close() override; + void set_queue(ipx_ring_t* queue) override; + maybe_virtual void allocate_table(); + maybe_virtual telemetry::Dict get_cache_telemetry(); + maybe_virtual int update_flow(Packet& packet, size_t flow_index) noexcept; + maybe_virtual void try_to_export(size_t flow_index, bool call_pre_export, int reason) noexcept; + maybe_virtual void create_record(const Packet& packet, size_t flow_index, size_t hash_value) noexcept; + maybe_virtual void export_flow(FlowRecord** flow, int reason); + maybe_virtual size_t find_victim(CacheRowSpan& row) const noexcept; + maybe_virtual void export_expired(const timeval& now); + maybe_virtual void export_and_reuse_flow(size_t flow_index) noexcept; + virtual void print_report() const; + + void try_to_fill_ports_to_fragmented_packet(Packet& packet); + void flush(Packet &pkt, size_t flow_index, int return_flags); + void update_flow_end_reason_stats(uint8_t reason); + void update_flow_record_stats(uint64_t packets_count); + void prefetch_export_expired() const; + void get_parser_options(CacheOptParser& parser) noexcept; + void push_to_export_queue(size_t flow_index) noexcept; + void push_to_export_queue(FlowRecord*& flow) noexcept; + std::pair + find_flow_index(const FlowKey& key, bool swapped) noexcept; + FlowSearch find_row(const FlowKey& key) noexcept; + bool try_to_export_on_inactive_timeout(size_t flow_index, const timeval& now) noexcept; + bool try_to_export_on_active_timeout(size_t flow_index, const timeval& now) noexcept; + void export_flow(size_t flow_index, int reason); + void export_flow(FlowRecord** flow); + void export_flow(size_t flow_index); + bool try_to_export_delayed_flow(const Packet& packet, size_t flow_index) noexcept; + void try_to_export(size_t flow_index, bool call_pre_export) noexcept; + void send_export_request_to_ctt(size_t ctt_flow_hash) noexcept; + void try_to_add_flow_to_ctt(size_t flow_index) noexcept; + size_t get_empty_place(CacheRowSpan& row) noexcept; + + static uint8_t get_export_reason(const Flow &flow); private: - uint32_t m_cache_size; - uint32_t m_line_size; - uint32_t m_line_mask; - uint32_t m_line_new_idx; - uint32_t m_qsize; - uint32_t m_qidx; - uint32_t m_timeout_idx; - uint64_t m_flows_in_cache = 0; - uint64_t m_total_exported = 0; -#ifdef FLOW_CACHE_STATS - uint64_t m_empty; - uint64_t m_not_empty; - uint64_t m_hits; - uint64_t m_expired; - uint64_t m_flushed; - uint64_t m_lookups; - uint64_t m_lookups2; -#endif /* FLOW_CACHE_STATS */ - uint32_t m_active; - uint32_t m_inactive; - bool m_split_biflow; - bool m_enable_fragmentation_cache; - uint8_t m_keylen; - char m_key[MAX_KEY_LENGTH]; - char m_key_inv[MAX_KEY_LENGTH]; - FlowRecord** m_flow_table; - FlowRecord* m_flow_records; - - FragmentationCache m_fragmentation_cache; - FlowEndReasonStats m_flow_end_reason_stats = {}; - FlowRecordStats m_flow_record_stats = {}; - - void try_to_fill_ports_to_fragmented_packet(Packet& packet); - void flush(Packet& pkt, size_t flow_index, int ret, bool source_flow); - bool create_hash_key(Packet& pkt); - void export_flow(size_t index); - static uint8_t get_export_reason(Flow& flow); - void finish(); - - void update_flow_end_reason_stats(uint8_t reason); - void update_flow_record_stats(uint64_t packets_count); - telemetry::Content get_cache_telemetry(); - void prefetch_export_expired() const; - -#ifdef FLOW_CACHE_STATS - void print_report(); -#endif /* FLOW_CACHE_STATS */ + const bool m_vlan_is_flow_key{true}; }; -} // namespace ipxp +} \ No newline at end of file From 88583ece8b949f96e6372b384209511f62514d67 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:21:06 +0200 Subject: [PATCH 03/56] CTT - Update ipfixprobe core --- src/core/ipfixprobe.cpp | 4 ++++ src/core/workers.cpp | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/core/ipfixprobe.cpp b/src/core/ipfixprobe.cpp index aceb789b7..4aba56fa1 100644 --- a/src/core/ipfixprobe.cpp +++ b/src/core/ipfixprobe.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #include #include @@ -410,6 +411,9 @@ bool process_plugin_args(ipxp_conf_t& conf, IpfixprobeOptParser& parser) if (storagePlugin == nullptr) { throw IPXPError("invalid storage plugin " + storage_name); } + if (std::optional config = inputPlugin->get_ctt_config(); config.has_value()) { + storagePlugin->init_ctt(*config); + } storagePlugin->set_telemetry_dir(pipeline_queue_dir); conf.storagePlugins.emplace_back(storagePlugin); } catch (PluginError& e) { diff --git a/src/core/workers.cpp b/src/core/workers.cpp index c11775aff..63dc7214d 100644 --- a/src/core/workers.cpp +++ b/src/core/workers.cpp @@ -63,7 +63,10 @@ void input_storage_worker( const clockid_t clk_id = CLOCK_MONOTONIC; #endif - while (!terminate_input) { + while (storagePlugin->requires_input()) { + if (terminate_input) { + storagePlugin->terminate_input(); + } block.cnt = 0; block.bytes = 0; @@ -92,7 +95,9 @@ void input_storage_worker( diff.tv_sec--; } storagePlugin->export_expired(ts.tv_sec + diff.tv_sec); - usleep(1); + if (!terminate_input) { + usleep(1); + } continue; } else if (ret == InputPlugin::Result::PARSED) { stats.packets = inputPlugin->m_seen; From 8e563a8cfde57e0b328aa4184d885a2c429d15b9 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:21:47 +0200 Subject: [PATCH 04/56] CTT - Update Flow interface --- include/ipfixprobe/flowifc.hpp | 78 ++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/include/ipfixprobe/flowifc.hpp b/include/ipfixprobe/flowifc.hpp index 4919df27f..1d88158c9 100644 --- a/include/ipfixprobe/flowifc.hpp +++ b/include/ipfixprobe/flowifc.hpp @@ -37,6 +37,7 @@ #include #include #include +#include #ifdef WITH_NEMEA #include @@ -233,38 +234,61 @@ struct Record { virtual ~Record() { remove_extensions(); } }; -#define FLOW_END_INACTIVE 0x01 -#define FLOW_END_ACTIVE 0x02 -#define FLOW_END_EOF 0x03 -#define FLOW_END_FORCED 0x04 -#define FLOW_END_NO_RES 0x05 +enum FlowEndReason : int { + FLOW_END_INACTIVE = 0x1, /**< Flow ended due to inactivity timeout. */ + FLOW_END_ACTIVE = 0x2, /**< Flow ended due to active timeout. */ + FLOW_END_EOF = 0x3, /**< Flow ended due to end of flow (TCP FIN or RST). */ + FLOW_END_FORCED = 0x4, /**< Flow ended due to process plugin flushes */ + FLOW_END_NO_RES = 0x5 /**< Flow ended due to lack of resources (e.g. full cache line). */ +}; /** * \brief Flow record struct constaining basic flow record data and extension headers. */ struct Flow : public Record { - uint64_t flow_hash; - - struct timeval time_first; - struct timeval time_last; - uint64_t src_bytes; - uint64_t dst_bytes; - uint32_t src_packets; - uint32_t dst_packets; - uint8_t src_tcp_flags; - uint8_t dst_tcp_flags; - - uint8_t ip_version; - - uint8_t ip_proto; - uint16_t src_port; - uint16_t dst_port; - ipaddr_t src_ip; - ipaddr_t dst_ip; - - uint8_t src_mac[6]; - uint8_t dst_mac[6]; - uint8_t end_reason; + static inline const int MAXIMAL_PROCESS_PLUGIN_COUNT = 64; + /** + * \brief Plugins status struct describes flow information required by process plugins. + */ + struct PluginsStatus { + // get_no_data[i] == true -> i-th process plugin requires no flow data + // get_no_data[i] == false && get_all_data[i] == true -> i-th process plugin requires all + // available flow data + // get_no_data[i] == false && get_all_data[i] == false -> i-th process plugin requires + // only metadata + std::bitset get_all_data; + std::bitset get_no_data; + }; + + uint64_t flow_hash; + uint64_t flow_hash_ctt; /**< Flow hash for CTT. */ + + PluginsStatus plugins_status; /**< Statuses of the process plugins for this flow, used to check + if the flow process plugins requires all available data, only + metadata or nothing of this. */ + + struct timeval time_first; + struct timeval time_last; + uint64_t src_bytes; + uint64_t dst_bytes; + uint32_t src_packets; + uint32_t dst_packets; + uint8_t src_tcp_flags; + uint8_t dst_tcp_flags; + + uint8_t ip_version; + uint16_t vlan_id; + + uint8_t ip_proto; + uint16_t src_port; + uint16_t dst_port; + ipaddr_t src_ip; + ipaddr_t dst_ip; + + uint8_t src_mac[6]; + uint8_t dst_mac[6]; + uint8_t end_reason; + bool swapped; /**< Flow addresses and ports were swapped on creation. */ }; } // namespace ipxp From e9fc164ff3a638d251233432ebb7966f45d83840 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:24:03 +0200 Subject: [PATCH 05/56] Fix non-virtual base class destructor of OptionParser --- include/ipfixprobe/options.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ipfixprobe/options.hpp b/include/ipfixprobe/options.hpp index e7c14e566..542bcbddd 100644 --- a/include/ipfixprobe/options.hpp +++ b/include/ipfixprobe/options.hpp @@ -49,7 +49,7 @@ class IPXP_API OptionsParser { OptionsParser(); OptionsParser(const std::string& name, const std::string& info); - ~OptionsParser(); + virtual ~OptionsParser(); OptionsParser(OptionsParser& p) = delete; OptionsParser(OptionsParser&& p) = delete; void operator=(OptionsParser& p) = delete; From 2cb2f89b8c6dcb5b45352e053e3ce413898cb985 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:25:05 +0200 Subject: [PATCH 06/56] CTT - Refactor NDP plugin --- src/plugins/input/nfb/src/ndp.cpp | 160 +++++++++----------------- src/plugins/input/nfb/src/ndp.hpp | 111 ++++++------------ src/plugins/input/nfb/src/ndpCore.cpp | 115 ++++++++++++++++++ src/plugins/input/nfb/src/ndpCore.hpp | 122 ++++++++++++++++++++ 4 files changed, 328 insertions(+), 180 deletions(-) create mode 100644 src/plugins/input/nfb/src/ndpCore.cpp create mode 100644 src/plugins/input/nfb/src/ndpCore.hpp diff --git a/src/plugins/input/nfb/src/ndp.cpp b/src/plugins/input/nfb/src/ndp.cpp index cb11dfed7..85ac12315 100644 --- a/src/plugins/input/nfb/src/ndp.cpp +++ b/src/plugins/input/nfb/src/ndp.cpp @@ -1,46 +1,62 @@ /** - * @file - * @brief Packet reader using NDP library for high speed capture. - * @author Jiri Havranek - * @author Tomas Benes - * @author Pavel Siska + * \file ndp.cpp + * \brief Packet reader using NDP library for high speed capture. + * \author Tomas Benes + * \author Jiri Havranek + * \date 2021 + */ +/* + * Copyright (C) 2020-2021 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * * - * Copyright (c) 2025 CESNET * - * SPDX-License-Identifier: BSD-3-Clause */ #include "ndp.hpp" - -#include "parser.hpp" - + +#include +#include +#include #include #include #include - -#include -#include +#include +#include +#include +#include +#include + +#include "ipfixprobe/packet.hpp" +#include "ipfixprobe/plugin.hpp" +#include "parser.hpp" namespace ipxp { -telemetry::Content NdpPacketReader::get_queue_telemetry() -{ - telemetry::Dict dict; - dict["received_packets"] = m_stats.receivedPackets; - dict["received_bytes"] = m_stats.receivedBytes; - return dict; -} - static const PluginManifest ndpPluginManifest = { - .name = "ndp", - .description = "Ndp input plugin for reading packets from network interface or ndp file.", - .pluginVersion = "1.0.0", - .apiVersion = "1.0.0", - .usage = - []() { - NdpOptParser parser; - parser.usage(std::cout); - }, + .name = "ndp", + .description = "Ndp input plugin for reading packets from network interface or ndp file.", + .pluginVersion = "1.0.0", + .apiVersion = "1.0.0", + .usage = + []() { + NdpOptParser parser; + parser.usage(std::cout); + }, }; NdpPacketReader::NdpPacketReader(const std::string& params) @@ -48,87 +64,19 @@ NdpPacketReader::NdpPacketReader(const std::string& params) init(params.c_str()); } -NdpPacketReader::~NdpPacketReader() +std::optional NdpPacketReader::get_ctt_config() const { - close(); + return std::nullopt; } -void NdpPacketReader::init(const char* params) +InputPlugin::Result NdpPacketReader::get(PacketBlock &packets) { - NdpOptParser parser; - try { - parser.parse(params); - } catch (ParserError& e) { - throw PluginError(e.what()); - } - - if (parser.m_dev.empty()) { - throw PluginError("specify device path"); - } - init_ifc(parser.m_dev); -} - -void NdpPacketReader::close() -{ - ndpReader.close(); -} - -void NdpPacketReader::init_ifc(const std::string& dev) -{ - if (ndpReader.init_interface(dev) != 0) { - throw PluginError(ndpReader.error_msg); - } -} - -InputPlugin::Result NdpPacketReader::get(PacketBlock& packets) -{ - parser_opt_t opt = {&packets, false, false, 0}; - struct ndp_packet* ndp_packet; - struct timeval timestamp; - size_t read_pkts = 0; - int ret = -1; - - packets.cnt = 0; - for (unsigned i = 0; i < packets.size; i++) { - ret = ndpReader.get_pkt(&ndp_packet, ×tamp); - if (ret == 0) { - if (opt.pblock->cnt) { - break; - } - return Result::TIMEOUT; - } else if (ret < 0) { - // Error occured. - throw PluginError(ndpReader.error_msg); - } - read_pkts++; - parse_packet( - &opt, - m_parser_stats, - timestamp, - ndp_packet->data, - ndp_packet->data_length, - ndp_packet->data_length); - } - - m_seen += read_pkts; - m_parsed += opt.pblock->cnt; - - m_stats.receivedPackets += read_pkts; - m_stats.receivedBytes += packets.bytes; - - return opt.pblock->cnt ? Result::PARSED : Result::NOT_PARSED; -} - -void NdpPacketReader::configure_telemetry_dirs( - std::shared_ptr plugin_dir, - std::shared_ptr queues_dir) -{ - (void) plugin_dir; - - telemetry::FileOps statsOps = {[&]() { return get_queue_telemetry(); }, nullptr}; - register_file(queues_dir, "input-stats", statsOps); + constexpr auto parsing_callback = [](parser_opt_t *opt, ParserStats& stats, struct timeval ts, const ndp_packet* packet){ + parse_packet(opt, stats, ts, packet->data, packet->data_length, packet->data_length); + }; + return NdpPacketReaderCore::getBurst(packets, parsing_callback); } static const PluginRegistrar ndpRegistrar(ndpPluginManifest); -} // namespace ipxp +} diff --git a/src/plugins/input/nfb/src/ndp.hpp b/src/plugins/input/nfb/src/ndp.hpp index 878df3c75..e98e5cb6a 100644 --- a/src/plugins/input/nfb/src/ndp.hpp +++ b/src/plugins/input/nfb/src/ndp.hpp @@ -1,90 +1,53 @@ /** - * @file - * @brief Packet reader using NDP library for high speed capture. - * @author Jiri Havranek - * @author Tomas Benes - * @author Pavel Siska + * \file ndp.hpp + * \brief Packet reader using NDP library for high speed capture. + * \author Tomas Benes + * \author Jiri Havranek + * \date 2021 + */ +/* + * Copyright (C) 2020-2021 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * * - * Copyright (c) 2025 CESNET * - * SPDX-License-Identifier: BSD-3-Clause */ - #pragma once -#include "ndpReader.hpp" - -#include -#include +#include #include +#include #include +#include "ndpCore.hpp" namespace ipxp { -class NdpOptParser : public OptionsParser { +class NdpPacketReader : public NdpPacketReaderCore +{ public: - std::string m_dev; - uint64_t m_id; - - NdpOptParser() - : OptionsParser("ndp", "Input plugin for reading packets from a ndp device") - , m_dev("") - , m_id(0) - { - register_option( - "d", - "dev", - "PATH", - "Path to a device file", - [this](const char* arg) { - m_dev = arg; - return true; - }, - OptionFlags::RequiredArgument); - register_option( - "I", - "id", - "NUM", - "Link identifier number", - [this](const char* arg) { - try { - m_id = str2num(arg); - } catch (std::invalid_argument& e) { - return false; - } - return true; - }, - OptionFlags::RequiredArgument); - } -}; - -class NdpPacketReader : public InputPlugin { -public: - NdpPacketReader(const std::string& params); - ~NdpPacketReader(); - - void init(const char* params); - void close(); - OptionsParser* get_parser() const { return new NdpOptParser(); } - std::string get_name() const { return "ndp"; } - InputPlugin::Result get(PacketBlock& packets); - - void configure_telemetry_dirs( - std::shared_ptr plugin_dir, - std::shared_ptr queues_dir) override; + NdpPacketReader(const std::string& params); + OptionsParser *get_parser() const { return new NdpOptParser(); } + std::string get_name() const { return "ndp"; } + InputPlugin::Result get(PacketBlock &packets); + std::optional get_ctt_config() const override; private: - struct RxStats { - uint64_t receivedPackets; - uint64_t receivedBytes; - }; - - telemetry::Content get_queue_telemetry(); - - NdpReader ndpReader; - RxStats m_stats = {}; - - void init_ifc(const std::string& dev); + NdpReader ndpReader; + RxStats m_stats = {}; }; -} // namespace ipxp +} diff --git a/src/plugins/input/nfb/src/ndpCore.cpp b/src/plugins/input/nfb/src/ndpCore.cpp new file mode 100644 index 000000000..b1bf473e4 --- /dev/null +++ b/src/plugins/input/nfb/src/ndpCore.cpp @@ -0,0 +1,115 @@ +/** + * \file ndp.cpp + * \brief Packet reader using NDP library for high speed capture. + * \author Tomas Benes + * \author Jiri Havranek + * \date 2021 + */ +/* + * Copyright (C) 2020-2021 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ndpCore.hpp" +#include "ipfixprobe/packet.hpp" +#include "ipfixprobe/plugin.hpp" + +namespace ipxp { + +telemetry::Dict NdpPacketReaderCore::get_queue_telemetry() +{ + telemetry::Dict dict; + dict["received_packets"] = m_stats.receivedPackets; + dict["received_bytes"] = m_stats.receivedBytes; + return dict; +} + +NdpPacketReaderCore::NdpPacketReaderCore() +{ +} + +NdpPacketReaderCore::~NdpPacketReaderCore() +{ + close(); +} + +void NdpPacketReaderCore::init(const char *params) +{ + NdpOptParser parser; + try { + parser.parse(params); + } catch (ParserError &e) { + throw PluginError(e.what()); + } + + if (parser.m_dev.empty()) { + throw PluginError("specify device path"); + } + + init_ifc(parser.m_dev); + m_device = parser.m_dev; +} + +void NdpPacketReaderCore::close() +{ + ndpReader.close(); +} + + +void NdpPacketReaderCore::init_ifc(const std::string &dev) +{ + if (ndpReader.init_interface(dev) != 0) { + throw PluginError(ndpReader.error_msg); + } +} + + +std::optional NdpPacketReaderCore::get_ctt_config() const +{ + std::string dev = m_device; + int channel_id = 0; + std::size_t delimiter_found = m_device.find_last_of(":"); + if (delimiter_found != std::string::npos) { + std::string channel_str = m_device.substr(delimiter_found + 1); + dev = m_device.substr(0, delimiter_found); + channel_id = std::stoi(channel_str); + } + return CttConfig{dev, channel_id}; +} + +void NdpPacketReaderCore::configure_telemetry_dirs( + std::shared_ptr plugin_dir, + std::shared_ptr queues_dir) +{ + telemetry::FileOps statsOps = {[&]() -> telemetry::Content { return get_queue_telemetry(); }, nullptr}; + register_file(queues_dir, "input-stats", statsOps); +} + +} diff --git a/src/plugins/input/nfb/src/ndpCore.hpp b/src/plugins/input/nfb/src/ndpCore.hpp new file mode 100644 index 000000000..22a846d14 --- /dev/null +++ b/src/plugins/input/nfb/src/ndpCore.hpp @@ -0,0 +1,122 @@ +/** + * \file ndp.hpp + * \brief Packet reader using NDP library for high speed capture. + * \author Tomas Benes + * \author Jiri Havranek + * \date 2021 + */ +/* + * Copyright (C) 2020-2021 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * + * + */ + +#pragma once + +#include +#include "ndpReader.hpp" + +#include +#include +#include +#include +#include +#include +#include "parser.hpp" + +namespace ipxp { + +class NdpOptParser : public OptionsParser +{ +public: + std::string m_dev; + uint64_t m_id; + + NdpOptParser() : OptionsParser("ndp", "Input plugin for reading packets from a ndp device"), m_dev(""), m_id(0) + { + register_option("d", "dev", "PATH", "Path to a device file", [this](const char *arg){m_dev = arg; return true;}, OptionFlags::RequiredArgument); + register_option("I", "id", "NUM", "Link identifier number", + [this](const char *arg){try {m_id = str2num(arg);} catch(std::invalid_argument &e) {return false;} return true;}, OptionFlags::RequiredArgument); + } +}; + +class NdpPacketReaderCore : public InputPlugin +{ +public: + NdpPacketReaderCore(); + ~NdpPacketReaderCore(); + + void init(const char *params) override; + void close(); + + template + InputPlugin::Result getBurst(PacketBlock &packets, PacketParsingCallback& callback) + { + parser_opt_t opt = {&packets, false, false, 0}; + struct ndp_packet *ndp_packet; + struct timeval timestamp; + size_t read_pkts = 0; + int ret = -1; + + packets.cnt = 0; + for (unsigned i = 0; i < packets.size; i++) { + ret = ndpReader.get_pkt(&ndp_packet, ×tamp); + if (ret == 0) { + if (opt.pblock->cnt) { + break; + } + return Result::TIMEOUT; + } else if (ret < 0) { + // Error occured. + throw PluginError(ndpReader.error_msg); + } + read_pkts++; + callback(&opt, m_parser_stats, timestamp, ndp_packet); + } + + m_seen += read_pkts; + m_parsed += opt.pblock->cnt; + + m_stats.receivedPackets += read_pkts; + m_stats.receivedBytes += packets.bytes; + return opt.pblock->cnt ? Result::PARSED : Result::NOT_PARSED; + } + + void configure_telemetry_dirs( + std::shared_ptr plugin_dir, + std::shared_ptr queues_dir) override; + + virtual std::optional get_ctt_config() const; + +protected: + struct RxStats { + uint64_t receivedPackets; + uint64_t receivedBytes; + }; + + virtual telemetry::Dict get_queue_telemetry(); + void init_ifc(const std::string &dev); + + NdpReader ndpReader; + RxStats m_stats = {}; + std::string m_device; + +}; + +} From 19906a5c591c1ac6bc027c535f45f4ec9473a8ad Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:27:41 +0200 Subject: [PATCH 07/56] CTT - Introduce ndp-meta input plugin --- src/plugins/input/nfb-meta/CMakeLists.txt | 33 +++++ src/plugins/input/nfb-meta/src/ndpMeta.cpp | 137 +++++++++++++++++++++ src/plugins/input/nfb-meta/src/ndpMeta.hpp | 77 ++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 src/plugins/input/nfb-meta/CMakeLists.txt create mode 100644 src/plugins/input/nfb-meta/src/ndpMeta.cpp create mode 100644 src/plugins/input/nfb-meta/src/ndpMeta.hpp diff --git a/src/plugins/input/nfb-meta/CMakeLists.txt b/src/plugins/input/nfb-meta/CMakeLists.txt new file mode 100644 index 000000000..8b76b56fb --- /dev/null +++ b/src/plugins/input/nfb-meta/CMakeLists.txt @@ -0,0 +1,33 @@ +project(ipfixprobe-input-nfb-meta VERSION 1.0.0 DESCRIPTION "ipfixprobe-input-nfb-meta plugin ") + +add_library(ipfixprobe-input-nfb-meta MODULE + src/ndpMeta.hpp + src/ndpMeta.cpp + ../nfb/src/ndpCore.hpp + ../nfb/src/ndpCore.cpp + ../nfb/src/ndpReader.hpp + ../nfb/src/ndpReader.cpp + ../nfb/src/ndpHeader.hpp + ../parser/parser.cpp + ../parser/parser.hpp +) + +set_target_properties(ipfixprobe-input-nfb-meta PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES +) + +target_include_directories(ipfixprobe-input-nfb-meta PRIVATE + ${CMAKE_SOURCE_DIR}/include/ + ${CMAKE_SOURCE_DIR}/src/plugins/input/parser +) + +target_link_libraries(ipfixprobe-input-nfb-meta PRIVATE + nfb::nfb + numa::numa + telemetry::telemetry +) + +install(TARGETS ipfixprobe-input-nfb-meta + LIBRARY DESTINATION "${INSTALL_DIR_LIB}/ipfixprobe/input/" +) diff --git a/src/plugins/input/nfb-meta/src/ndpMeta.cpp b/src/plugins/input/nfb-meta/src/ndpMeta.cpp new file mode 100644 index 000000000..a57e5ec69 --- /dev/null +++ b/src/plugins/input/nfb-meta/src/ndpMeta.cpp @@ -0,0 +1,137 @@ +/** + * \file ndp.cpp + * \brief Packet reader using NDP library for high speed capture. + * \author Tomas Benes + * \author Jiri Havranek + * \date 2021 + */ +/* + * Copyright (C) 2020-2021 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * + * + */ + +#include "ndpMeta.hpp" + +#include +#include + +#include "ipfixprobe/packet.hpp" +#include "ipfixprobe/plugin.hpp" +#include "parser.hpp" +#include + +namespace ipxp { + +static const PluginManifest ndpMetadataPluginManifest = { + .name = "ndp-meta", + .description = "Ndp input plugin for reading packets from network interface with metadata.", + .pluginVersion = "1.0.0", + .apiVersion = "1.0.0", + .usage = + []() { + NdpMetaOptParser parser; + parser.usage(std::cout); + }, +}; + +telemetry::Dict NdpMetadataPacketReader::get_queue_telemetry() +{ + telemetry::Dict dict = NdpPacketReaderCore::get_queue_telemetry(); + dict["bad_metadata"] = m_ctt_stats.bad_metadata; + dict["ctt_unknown_packet_type"] = m_ctt_stats.ctt_unknown_packet_type; + return dict; +} + +NdpMetadataPacketReader::NdpMetadataPacketReader(const std::string& params) +{ + init(params.c_str()); +} + +void NdpMetadataPacketReader::init(const char *params) +{ + NdpMetaOptParser parser; + try { + parser.parse(params); + } catch (const std::exception &e) { + throw std::runtime_error("NDP metadata plugin: " + std::string(e.what())); + } + if (parser.m_dev.empty()) { + throw PluginError("specify device path"); + } + + init_ifc(parser.m_dev); + m_device = parser.m_dev; + if (parser.m_metadata != "ctt") { + throw PluginError("Only ctt metadata are supported"); + } +} + + +static bool try_to_add_external_export_packet(parser_opt_t& opt, const uint8_t* packet_data, size_t length) noexcept +{ + if (opt.pblock->cnt >= opt.pblock->size) { + return false; + } + opt.pblock->pkts[opt.pblock->cnt].packet = packet_data; + opt.pblock->pkts[opt.pblock->cnt].payload = packet_data; + opt.pblock->pkts[opt.pblock->cnt].packet_len = length; + opt.pblock->pkts[opt.pblock->cnt].packet_len_wire = length; + opt.pblock->pkts[opt.pblock->cnt].payload_len = length; + opt.pblock->pkts[opt.pblock->cnt].external_export = true; + opt.packet_valid = true; + opt.pblock->cnt++; + opt.pblock->bytes += length; + return true; +} + +InputPlugin::Result NdpMetadataPacketReader::get(PacketBlock &packets) +{ + const auto parsing_callback = [this](parser_opt_t *opt, ParserStats& stats, struct timeval ts, const ndp_packet* packet){ + switch (packet->flags) + { + case MessageType::FLOW_EXPORT:{ + try_to_add_external_export_packet(*opt, packet->data, packet->data_length); + break; + } + case MessageType::FRAME_AND_FULL_METADATA:{ + CttMetadata metadata = CttMetadata::parse(packet->header, packet->header_length); + if (metadata.flow_hash == 0) { + m_ctt_stats.bad_metadata++; + } + size_t count = opt->pblock->cnt; + parse_packet(opt, stats, ts, packet->data, packet->data_length, packet->data_length); + if (opt->pblock->cnt != count && metadata.flow_hash != 0) { + opt->pblock->pkts[opt->pblock->cnt - 1].cttmeta = metadata; + } + break; + } + default:{ + m_ctt_stats.ctt_unknown_packet_type++; + break; + } + } + }; + return NdpPacketReaderCore::getBurst(packets, parsing_callback); +} + +static const PluginRegistrar ndpRegistrar(ndpMetadataPluginManifest); + + +} // namespace ipxp diff --git a/src/plugins/input/nfb-meta/src/ndpMeta.hpp b/src/plugins/input/nfb-meta/src/ndpMeta.hpp new file mode 100644 index 000000000..535b3b50b --- /dev/null +++ b/src/plugins/input/nfb-meta/src/ndpMeta.hpp @@ -0,0 +1,77 @@ +/** + * \file ndp.hpp + * \brief Packet reader using NDP library for high speed capture. + * \author Tomas Benes + * \author Jiri Havranek + * \date 2021 + */ +/* + * Copyright (C) 2020-2021 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * + * + */ +#pragma once + +#include +#include +#include +#include +#include +#include "../../nfb/src/ndpCore.hpp" + +namespace ipxp { + +class NdpMetaOptParser : public OptionsParser +{ +public: + std::string m_dev; + uint64_t m_id; + std::string m_metadata; + + NdpMetaOptParser() : OptionsParser("ndp-meta", "Input plugin for reading packets from a ndp device using metadata"), m_dev(""), m_id(0), m_metadata("") + { + register_option("d", "dev", "PATH", "Path to a device file", [this](const char *arg){m_dev = arg; return true;}, OptionFlags::RequiredArgument); + register_option("I", "id", "NUM", "Link identifier number", + [this](const char *arg){try {m_id = str2num(arg);} catch(std::invalid_argument &e) {return false;} return true;}, OptionFlags::RequiredArgument); + register_option("M", "meta", "Metadata type", "Choose metadata type if any", [this](const char *arg){m_metadata = arg; return true;}, OptionFlags::RequiredArgument); + } +}; + +class NdpMetadataPacketReader : public NdpPacketReaderCore +{ +public: + NdpMetadataPacketReader(const std::string& params); + void init(const char *params) override; + OptionsParser *get_parser() const { return new NdpMetaOptParser(); } + std::string get_name() const { return "ndp-meta"; } + InputPlugin::Result get(PacketBlock &packets); + +private: + struct CttStats { + uint64_t bad_metadata{0}; + uint64_t ctt_unknown_packet_type{0}; + }; + + telemetry::Dict get_queue_telemetry() override; + + NdpReader ndpReader; + CttStats m_ctt_stats = {}; +}; + +} From 500c4a50f4393b6feafbc75e7ec0e7a4ce946c74 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:30:05 +0200 Subject: [PATCH 08/56] CTT - Introduce CTT auxilary header --- include/ipfixprobe/cttmeta.hpp | 126 +++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 include/ipfixprobe/cttmeta.hpp diff --git a/include/ipfixprobe/cttmeta.hpp b/include/ipfixprobe/cttmeta.hpp new file mode 100644 index 000000000..9c4da0eb9 --- /dev/null +++ b/include/ipfixprobe/cttmeta.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include + +namespace ipxp { + +static uint64_t extract(const uint8_t* bitvec, size_t start_bit, size_t bit_length) { + size_t start_byte = start_bit / 8; + size_t end_bit = start_bit + bit_length; + size_t end_byte = (end_bit + 7) / 8; + uint64_t value = 0; + for (size_t i = 0; i < end_byte - start_byte; ++i) { + value |= static_cast(bitvec[start_byte + i]) << (8 * i); + } + value >>= (start_bit % 8); + uint64_t mask = (bit_length == 64) ? ~0ULL : ((1ULL << bit_length) - 1); + return value & mask; +} + +enum MessageType : uint8_t +{ + FRAME_AND_FULL_METADATA = 0x0, ///< Frame and full metadata + FRAME_AND_HALF_METADATA = 0x1, ///< Frame and half metadata + FRAME_WITH_TIMESTAMP = 0x2, ///< Frame with timestamp + FRAME_WITH_NO_METADATA = 0x3, ///< Frame with no metadata + ONLY_FULL_METADATA = 0x4, ///< Only full metadata + FLOW_EXPORT = 0xF ///< Flow export +}; + +enum CsumStatus : uint8_t { + CSUM_UNKNOWN = 0x0, ///< No information about the checksum + CSUM_BAD = 0x1, ///< The checksum in the packet is wrong + CSUM_GOOD = 0x2, ///< The checksum in the packet is valid + CSUM_NONE = 0x3 ///< Checksum not correct but header integrity verified +}; + +enum ParserStatus : uint8_t { + PA_OK = 0x0, ///< Parsing completed successfully + PA_UNKNOWN = 0x1, ///< Parser stopped at an unknown protocol + PA_LIMIT = 0x2, ///< Parser stopped at its own limit (e.g., VLAN=4) + PA_ERROR = 0x3 ///< Error in protocol header or parsing overflow +}; + +enum L2PType : uint8_t { + L2_UNKNOWN = 0x0, ///< Unknown L2 protocol + L2_ETHER_IP = 0x1, ///< Ethernet with IP payload + L2_ETHER_TIMESYNC = 0x2, ///< Ethernet with TimeSync protocol + L2_ETHER_ARP = 0x3, ///< Ethernet with ARP protocol + L2_ETHER_LLDP = 0x4, ///< Ethernet with LLDP protocol + L2_ETHER_NSH = 0x5, ///< Ethernet with NSH protocol + L2_ETHER_VLAN = 0x6, ///< Ethernet with VLAN tagging + L2_ETHER_QINQ = 0x7, ///< Ethernet with QinQ tagging + L2_ETHER_PPPOE = 0x8, ///< Ethernet with PPPoE encapsulation + L2_ETHER_FCOE = 0x9, ///< Ethernet with FCoE protocol + L2_ETHER_MPLS = 0xA ///< Ethernet with MPLS +}; + +enum L3PType : uint8_t { + L3_UNKNOWN = 0x0, ///< Unknown L3 protocol + L3_IPV4 = 0x1, ///< IPv4 protocol + L3_IPV4_EXT = 0x3, ///< IPv4 with extensions + L3_IPV6 = 0x4, ///< IPv6 protocol + L3_IPV6_EXT = 0xC ///< IPv6 with extensions +}; + +enum L4PType : uint8_t { + L4_UNKNOWN = 0x0, ///< Unknown L4 protocol + L4_TCP = 0x1, ///< TCP protocol + L4_UDP = 0x2, ///< UDP protocol + L4_FRAG = 0x3, ///< Fragmented packet + L4_SCTP = 0x4, ///< SCTP protocol + L4_ICMP = 0x5, ///< ICMP protocol + L4_NONFRAG = 0x6, ///< Non-fragmented packet + L4_IGMP = 0x7 ///< IGMP protocol +}; + +struct CttMetadata { + constexpr static size_t SIZE = 32; + + static CttMetadata parse(const uint8_t* data, size_t length) noexcept + { + CttMetadata metadata; + if (length != CttMetadata::SIZE) { + metadata.flow_hash = 0; + return metadata; + } + + metadata.vlan_tci = *reinterpret_cast(data + 8); + metadata.vlan_vld = *reinterpret_cast(data + 10) & 0x01; + metadata.vlan_stripped = *reinterpret_cast(data + 10) & 0x02; + metadata.flow_hash = *reinterpret_cast(data + 16); + return metadata; + } + struct timeval ts; ///< Timestamp; invalid if all bits are 1 + uint16_t vlan_tci; ///< VLAN Tag Control Information from outer VLAN + bool vlan_vld : 1; ///< VLAN valid flag; indicates if VLAN TCI is valid + bool vlan_stripped : 1; ///< VLAN stripped flag; outer VLAN only + CsumStatus ip_csum_status : 2; ///< IP checksum status + CsumStatus l4_csum_status : 2; ///< Layer 4 checksum status + ParserStatus parser_status : 2;///< Final state of FPGA parser + uint8_t ifc; ///< Interface (IFC) number + uint16_t filter_bitmap; ///< Filter bitmap; each filter rule can have several mark bits + bool ctt_export_trig : 1; ///< CTT flag; packet triggered export in CTT + bool ctt_rec_matched : 1; ///< CTT flag; packet matched record in CTT + bool ctt_rec_created : 1; ///< CTT flag; packet created record in CTT + bool ctt_rec_deleted : 1; ///< CTT flag; packet deleted record in CTT + uint64_t flow_hash; ///< Flow hash; not the same as RSS hash + uint8_t l2_len : 7; ///< Length of the L2 layer, if known + uint16_t l3_len : 9; ///< Length of the L3 layer, if known + uint8_t l4_len : 8; ///< Length of the L4 layer, if known + L2PType l2_ptype : 4; ///< Type of the L2 layer + L3PType l3_ptype : 4; ///< Type of the L3 layer + L4PType l4_ptype : 4; ///< Type of the L4 layer +}; + +constexpr static timeval CTT_REQUEST_TIMEOUT = {10, 0}; ///< Timeout for CTT request + +static constexpr size_t KEY_SIZE = 8; +static constexpr size_t STATE_SIZE = sizeof(feta::CttRecord); +static constexpr size_t MASK_SIZE = 21; + + +} From 41c57b370d2bd17e50268bb2a635357f2362b37b Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:30:47 +0200 Subject: [PATCH 09/56] CTT - Update timeval utils functions --- .../src/fragmentationCache/timevalUtils.hpp | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/plugins/storage/cache/src/fragmentationCache/timevalUtils.hpp b/src/plugins/storage/cache/src/fragmentationCache/timevalUtils.hpp index 30256803d..ed14f543c 100644 --- a/src/plugins/storage/cache/src/fragmentationCache/timevalUtils.hpp +++ b/src/plugins/storage/cache/src/fragmentationCache/timevalUtils.hpp @@ -22,13 +22,13 @@ * software without specific prior written permission. */ -#include + #include #pragma once namespace ipxp { -struct timeval operator+(const struct timeval& a, const struct timeval& b) noexcept +inline struct timeval operator+(const struct timeval& a, const struct timeval& b) noexcept { constexpr time_t USEC_IN_SEC = 1000000; @@ -42,11 +42,33 @@ struct timeval operator+(const struct timeval& a, const struct timeval& b) noexc return result; } -bool operator>(const struct timeval& a, const struct timeval& b) noexcept +inline struct timeval operator-(const struct timeval& a, const struct timeval& b) noexcept +{ + struct timeval result; + result.tv_sec = a.tv_sec - b.tv_sec; + result.tv_usec = a.tv_usec - b.tv_usec; + if (result.tv_usec < 0) { + result.tv_sec--; + result.tv_usec += 1000000; + } + return result; +} + +inline bool operator>(const struct timeval& a, const struct timeval& b) noexcept { if (a.tv_sec == b.tv_sec) return a.tv_usec > b.tv_usec; return a.tv_sec > b.tv_sec; } +inline bool operator==(const struct timeval& a, const struct timeval& b) noexcept +{ + return a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec; +} + +inline bool operator>=(const struct timeval& a, const struct timeval& b) noexcept +{ + return a > b || a == b; +} + } // namespace ipxp From 2b507afed15096108dae53c0b7fb75da62e6e8ab Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:31:22 +0200 Subject: [PATCH 10/56] CTT - Update parser --- src/plugins/input/parser/parser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/input/parser/parser.cpp b/src/plugins/input/parser/parser.cpp index 543d4805e..e42a21f25 100644 --- a/src/plugins/input/parser/parser.cpp +++ b/src/plugins/input/parser/parser.cpp @@ -672,6 +672,7 @@ void parse_packet( return; } Packet* pkt = &opt->pblock->pkts[opt->pblock->cnt]; + pkt->external_export = false; uint16_t data_offset = 0; DEBUG_MSG("---------- packet parser #%u -------------\n", ++s_total_pkts); From 91061766c43ffba1d975c2fa7092eb9579d36456 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:32:44 +0200 Subject: [PATCH 11/56] CTT - Refactor NHTFlowCache options parser --- .../storage/cache/src/cacheOptParser.cpp | 115 ++++++++++++++++++ .../storage/cache/src/cacheOptParser.hpp | 49 ++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/plugins/storage/cache/src/cacheOptParser.cpp create mode 100644 src/plugins/storage/cache/src/cacheOptParser.hpp diff --git a/src/plugins/storage/cache/src/cacheOptParser.cpp b/src/plugins/storage/cache/src/cacheOptParser.cpp new file mode 100644 index 000000000..dc59d5c0e --- /dev/null +++ b/src/plugins/storage/cache/src/cacheOptParser.cpp @@ -0,0 +1,115 @@ +/** +* \file + * \author Damir Zainullin + * \brief CacheOptParser implementation. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#include "cacheOptParser.hpp" + +#include +#include +#include + +namespace ipxp { + +#ifdef IPXP_FLOW_CACHE_SIZE +static const uint32_t DEFAULT_FLOW_CACHE_SIZE = IPXP_FLOW_CACHE_SIZE; +#else +static const uint32_t DEFAULT_FLOW_CACHE_SIZE = 17; // 131072 records total +#endif /* IPXP_FLOW_CACHE_SIZE */ + +#ifdef IPXP_FLOW_LINE_SIZE +static const uint32_t DEFAULT_FLOW_LINE_SIZE = IPXP_FLOW_LINE_SIZE; +#else +static const uint32_t DEFAULT_FLOW_LINE_SIZE = 4; // 16 records per line +#endif /* IPXP_FLOW_LINE_SIZE */ + +static const uint32_t DEFAULT_INACTIVE_TIMEOUT = 30; +static const uint32_t DEFAULT_ACTIVE_TIMEOUT = 300; + +static_assert(std::is_unsigned(), "Static checks of default cache sizes won't properly work without unsigned type."); +static_assert(bitcount(-1) > DEFAULT_FLOW_CACHE_SIZE, "Flow cache size is too big to fit in variable!"); +static_assert(bitcount(-1) > DEFAULT_FLOW_LINE_SIZE, "Flow cache line size is too big to fit in variable!"); + +static_assert(DEFAULT_FLOW_LINE_SIZE >= 1, "Flow cache line size must be at least 1!"); +static_assert(DEFAULT_FLOW_CACHE_SIZE >= DEFAULT_FLOW_LINE_SIZE, "Flow cache size must be at least cache line size!"); + +CacheOptParser::CacheOptParser(const std::string &name, const std::string &description) + : OptionsParser(name, description), + m_cache_size(1 << DEFAULT_FLOW_CACHE_SIZE), m_line_size(1 << DEFAULT_FLOW_LINE_SIZE), + m_active(DEFAULT_ACTIVE_TIMEOUT), m_inactive(DEFAULT_INACTIVE_TIMEOUT), m_split_biflow(false), + m_enable_fragmentation_cache(true), m_frag_cache_size(10007), // Prime for better distribution in hash table + m_frag_cache_timeout(3) + { + register_option("s", "size", "EXPONENT", "Cache size exponent to the power of two", + [this](const char *arg){try {unsigned exp = str2num(arg); + if (exp < 4 || exp > 30) { + throw PluginError("Flow cache size must be between 4 and 30"); + } + m_cache_size = static_cast(1) << exp; + } catch(std::invalid_argument &e) {return false;} return true;}, + OptionFlags::RequiredArgument); + register_option("l", "line", "EXPONENT", "Cache line size exponent to the power of two", + [this](const char *arg){try {m_line_size = static_cast(1) << str2num(arg); + if (m_line_size < 1) { + throw PluginError("Flow cache line size must be at least 1"); + } + } catch(std::invalid_argument &e) {return false;} return true;}, + OptionFlags::RequiredArgument); + register_option("a", "active", "TIME", "Active timeout in seconds", + [this](const char *arg){try {m_active = str2num(arg);} catch(std::invalid_argument &e) {return false;} return true;}, + OptionFlags::RequiredArgument); + register_option("i", "inactive", "TIME", "Inactive timeout in seconds", + [this](const char *arg){try {m_inactive = str2num(arg);} catch(std::invalid_argument &e) {return false;} return true;}, + OptionFlags::RequiredArgument); + register_option("S", "split", "", "Split biflows into uniflows", + [this](const char *arg){ m_split_biflow = true; return true;}, OptionFlags::NoArgument); + register_option("fe", "frag-enable", "true|false", "Enable/disable fragmentation cache. Enabled (true) by default.", + [this](const char *arg){ + if (strcmp(arg, "true") == 0) { + m_enable_fragmentation_cache = true; + } else if (strcmp(arg, "false") == 0) { + m_enable_fragmentation_cache = false; + } else { + return false; + } + return true; + }, OptionFlags::RequiredArgument); + register_option("fs", "frag-size", "size", "Size of fragmentation cache, must be at least 1. Default value is 10007.", [this](const char *arg) { + try { + m_frag_cache_size = str2num(arg); + } catch(std::invalid_argument &e) { + return false; + } + return m_frag_cache_size > 0; + }); + register_option("ft", "frag-timeout", "TIME", "Timeout of fragments in fragmentation cache in seconds. Default value is 3.", [this](const char *arg) { + try { + m_frag_cache_timeout = str2num(arg); + } catch(std::invalid_argument &e) { + return false; + } + return true; + }); + } + + +} // ipxp diff --git a/src/plugins/storage/cache/src/cacheOptParser.hpp b/src/plugins/storage/cache/src/cacheOptParser.hpp new file mode 100644 index 000000000..ef25e1fcf --- /dev/null +++ b/src/plugins/storage/cache/src/cacheOptParser.hpp @@ -0,0 +1,49 @@ +/** +* \file + * \author Damir Zainullin + * \brief Contains the CacheOptParser class for parsing cache options. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#pragma once + +#include +#include + +namespace ipxp { + +class CacheOptParser : public OptionsParser +{ +public: + uint32_t m_cache_size; /**< Count of flows that cache can keep simultaneously. Calculated as 2^m_cache_size */ + uint32_t m_line_size; /**< Count of flows that can be stored in one line of the cache. Calculated as 2^m_line_size */ + uint32_t m_active; /**< Time in seconds after which the flow is considered active timeouted */ + uint32_t m_inactive; /**< Time in seconds after which the flow is considered inactive timeouted */ + bool m_split_biflow; /**< If true, the cache will split bi-directional flows into two unidirectional flows. */ + bool m_enable_fragmentation_cache; /**< If true, the cache will store fragmented packets and reassemble them. */ + std::size_t m_frag_cache_size; /**< Size of the fragmentation cache, used to store fragmented packets. */ + time_t m_frag_cache_timeout; /**< Timeout for the fragmentation cache, after which the fragmented packets are removed. */ + + ~CacheOptParser() override = default; + CacheOptParser(const std::string &name, const std::string &description); +}; + + +} // ipxp From daedc041cf911f2bc4cd70d9780dcb00efa449c7 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:33:49 +0200 Subject: [PATCH 12/56] CTT - Introduce cache row span class --- .../storage/cache/src/cacheRowSpan.hpp | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/plugins/storage/cache/src/cacheRowSpan.hpp diff --git a/src/plugins/storage/cache/src/cacheRowSpan.hpp b/src/plugins/storage/cache/src/cacheRowSpan.hpp new file mode 100644 index 000000000..0b2277ff2 --- /dev/null +++ b/src/plugins/storage/cache/src/cacheRowSpan.hpp @@ -0,0 +1,125 @@ +/** +* \file + * \author Damir Zainullin + * \brief CacheRowSpan implementation. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#pragma once + +#include +#include +#include + +#include "flowRecord.hpp" +#include "fragmentationCache/timevalUtils.hpp" +#include "flowKey.hpp" + +namespace ipxp { +/** + * \brief Class representing a non-owning view of a row span in a cache. + */ +class CacheRowSpan { +public: + /** + * \brief Construct a new CacheRowSpan object. + * \param begin Pointer to the first element in the row. + * \param count Number of elements in the row. + */ + CacheRowSpan(FlowRecord** begin, size_t count) noexcept + : m_begin(begin), m_count(count) + { + } + + /** + * \brief Find a flow record by hash. + * \param hash Hash value to search for. + * \return Index of the flow record relative to row begin if found, std::nullopt otherwise. + */ + __attribute__((always_inline)) std::optional find_by_hash(uint64_t hash) const noexcept + { + for (size_t i = 0; i < m_count; ++i) { + if (m_begin[i]->belongs(hash)) { + return i; + } + } + return std::nullopt; + } + + /** + * \brief Move a flow record to the beginning of the row. + * \param flow_index Index of the flow record to move. + */ + __attribute__((always_inline)) void advance_flow(size_t flow_index) noexcept + { + advance_flow_to(flow_index, 0); + } + + /** + * \brief Move a flow record to a specific position in the row. + * \param from Index of the flow record to move. + * \param to Index of the position to move the flow record to. + */ + __attribute__((always_inline)) void advance_flow_to(size_t from, size_t to) noexcept + { + if (from == to) return; + + FlowRecord* tmp = m_begin[from]; + + if (from < to) { + std::memmove(m_begin + from, m_begin + from + 1, (to - from) * sizeof(FlowRecord*)); + m_begin[to] = tmp; + } else { + std::memmove(m_begin + to + 1, m_begin + to, (from - to) * sizeof(FlowRecord*)); + m_begin[to] = tmp; + } + return; + } + + /** + * \brief Find an empty flow record in the row. + * \return Index of the empty flow record if found, std::nullopt otherwise. + */ + __attribute__((always_inline)) std::optional find_empty() const noexcept + { + for (size_t i = 0; i < m_count; ++i) { + if (m_begin[i]->is_empty()) { + return i; + } + } + return std::nullopt; + } + + /** + * \brief Access a flow record by index + * \param index Index of the flow record to access. + * \return Reference to the flow record at the specified index. + */ + __attribute__((always_inline)) FlowRecord*& operator[](const size_t index) const noexcept + { + return m_begin[index]; + } + +private: + FlowRecord** m_begin; + size_t m_count; +}; + +} // ipxp From 53fd697281ca54868ad9e8ba55dc8f4544d14335 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:34:36 +0200 Subject: [PATCH 13/56] CTT - Introduce FlowKey class --- src/plugins/storage/cache/src/flowKey.hpp | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/plugins/storage/cache/src/flowKey.hpp diff --git a/src/plugins/storage/cache/src/flowKey.hpp b/src/plugins/storage/cache/src/flowKey.hpp new file mode 100644 index 000000000..2a186cdae --- /dev/null +++ b/src/plugins/storage/cache/src/flowKey.hpp @@ -0,0 +1,61 @@ +/** + * \file + * \author Damir Zainullin + * \brief FlowKey declaration. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#pragma once + +#include +#include +#include + +namespace ipxp { + +/** + * @brief Unique identifier for each flow - packets with the same flow key belongs to the same flow + */ +class alignas(16) FlowKey { +public: + /** + * @brief Get hash value of the key + * @return Hash value of the key + */ + size_t hash() const noexcept + { + return XXH3_64bits(this, sizeof(*this)); + } + +private: + std::array src_ip; // IPv4 or IPv6 source address + std::array dst_ip; // IPv4 or IPv6 destination address + uint16_t src_port; // Source port (0 for non-TCP/UDP protocols) + uint16_t dst_port; // Destination port (0 for non-TCP/UDP protocols) + uint8_t proto; // IP protocol + uint8_t ip_version; // IP version (4 or 6) + uint16_t vlan_id; // VLAN ID if used, 0 otherwise + +friend class FlowKeyFactory; + +} __attribute__((packed)); + + +} // namespace ipxp \ No newline at end of file From eadfe2afba5ab376f062eb188619a5259a90824b Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:35:03 +0200 Subject: [PATCH 14/56] CTT - Introduce cache stats header --- src/plugins/storage/cache/src/cacheStats.hpp | 78 ++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/plugins/storage/cache/src/cacheStats.hpp diff --git a/src/plugins/storage/cache/src/cacheStats.hpp b/src/plugins/storage/cache/src/cacheStats.hpp new file mode 100644 index 000000000..e22805c09 --- /dev/null +++ b/src/plugins/storage/cache/src/cacheStats.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include + +namespace ipxp { + +struct FlowEndReasonStats { + uint64_t active_timeout{0}; ///< Flows ended due to active timeout + uint64_t inactive_timeout{0}; ///< Flows ended due to inactive timeout + uint64_t end_of_flow{0}; ///< Flows ended due to end of flow (e.g., TCP FIN) + uint64_t collision{0}; ///< Flows ended due to lack of space in the row + uint64_t forced{0}; ///< Flows ended due to process plugins +}; + +struct FlowRecordStats { + uint64_t packets_count_1{0}; + uint64_t packets_count_2_5{0}; + uint64_t packets_count_6_10{0}; + uint64_t packets_count_11_20{0}; + uint64_t packets_count_21_50{0}; + uint64_t packets_count_51_plus{0}; +}; + +struct FlowCacheStats{ + uint64_t empty{0}; ///< Empty place found on flow creation + uint64_t not_empty{0}; ///< Some victim was exported on flow creation + uint64_t hits{0}; ///< Number of successful lookups + uint64_t exported{0}; ///< Number of flows exported + uint64_t flushed{0}; ///< Number of flows flushed by process plugins + uint64_t lookups{0}; ///< Sum of all checked cells during all flows searches + uint64_t lookups2{0}; ///< Sum of all checked cells squared during all flows searches + uint64_t flows_in_cache{0}; ///< Number of flows currently in cache +}; + +struct CttStats { + uint64_t total_requests_count{0}; ///< Total number of requests sent to CTT + uint64_t lost_requests_count{0}; ///< Number of lost requests to CTT(no response during timeout) + uint64_t real_processed_packets{0}; ///< Number of packets processed by CTT (counting offloaded packets) + uint64_t flows_offloaded{0}; ///< Number of flows offloaded to CTT + uint64_t trim_packet_offloaded{0}; ///< Number of flows offloaded to CTT with trim offload + uint64_t drop_packet_offloaded{0}; ///< Number of flows offloaded to CTT with drop packet offload + uint64_t flows_removed{0}; ///< Number of flows removed from CTT after export packet + uint64_t export_packets{0}; ///< Number of export packets accepted from CTT(including pv0) + uint64_t export_packets_for_missing_flow{0}; ///< Number of export packets for which no corresponding flow in ipfixprobe cache was found + uint64_t export_packets_parsing_failed{0}; ///< Number of export packets that couldn't be parsed + uint64_t remove_queue_lost_requests{0}; ///< Number of requests lost in CTT remove queue + uint64_t flush_ctt_lost_requests{0}; ///< Number of requests lost on CTT flush + uint64_t wb_before_pv1[2]{0, 0}; ///< Count of writeback flags including invalid packets + uint64_t wb_after_pv1[2]{0, 0}; ///< Count of writeback flags excluding invalid packets + uint64_t pv_zero{0}; ///< Number of export packets with pv == 0 + + /** + * @brief Counters for all possible CTT export reasons + */ + struct ExportReasons{ + uint64_t counter_overflow{0}; ///< Count of packets in the offloaded flow exceeded counter maximum + uint64_t tcp_eof{0}; ///< TCP connection end + uint64_t active_timeout{0}; ///< Active timeout reached + uint64_t by_request{0}; ///< Export by request from ipfixprobe + uint64_t ctt_full{0}; ///< CTT hash collision + uint64_t hash_collision{0}; ///< Another kind of CTT hash collision + uint64_t reserved{0}; ///< Reserved for future use, must be 0 + }; + ExportReasons export_reasons_before_pv1; ///< Export reasons including pv0 packets + ExportReasons export_reasons_after_pv1; ///< Export reasons excluding pv0 packets + + struct { + uint64_t counter_overflow[2]{0, 0}; + uint64_t tcp_eof[2]{0, 0}; + uint64_t active_timeout[2]{0, 0}; + uint64_t by_request[2]{0, 0}; + uint64_t ctt_full[2]{0, 0}; + uint64_t hash_collision[2]{0, 0}; + uint64_t reserved[2]{0, 0}; + } advanced_export_reasons; ///< Export reasons of pv1 packets, split by writeback flag (0 or 1) +}; + +} // namespace ipxp From a2d11b021972e60a52a0697667a378cbd73b4a23 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:35:47 +0200 Subject: [PATCH 15/56] CTT - Introduce FlowKeyFactory class --- .../storage/cache/src/flowKeyFactory.hpp | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/plugins/storage/cache/src/flowKeyFactory.hpp diff --git a/src/plugins/storage/cache/src/flowKeyFactory.hpp b/src/plugins/storage/cache/src/flowKeyFactory.hpp new file mode 100644 index 000000000..131fa38c2 --- /dev/null +++ b/src/plugins/storage/cache/src/flowKeyFactory.hpp @@ -0,0 +1,131 @@ +/** + * \file + * \author Damir Zainullin + * \brief FlowKey factory. Create FlowKey objects from packet data + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "flowKey.hpp" + +namespace ipxp { + +/** + * \brief Factory class for creating FlowKey objects. + * + * This class provides static methods to create FlowKey objects based on + * source and destination IP addresses, ports, protocol, IP version, and VLAN ID. + */ +class FlowKeyFactory { +public: + static constexpr size_t EMPTY_VLAN = 0; + + /** + * \brief Create a direct FlowKey object with the given parameters. Keeps given ip addresses and ports directions + * + * @param src_ip Pointer to the source IP address (IPv4 or IPv6). + * @param dst_ip Pointer to the destination IP address (IPv4 or IPv6). + * @param src_port Source port (0 for non-TCP/UDP protocols). + * @param dst_port Destination port (0 for non-TCP/UDP protocols). + * @param proto IP protocol. + * @param ip_version IP version (IPv4 or IPv6). + * @param vlan_id VLAN ID (EMPTY_VLAN if not used). + * @return A FlowKey object initialized with the provided parameters. + */ + template + static FlowKey + create_direct_key(const Int* src_ip, const Int* dst_ip, + uint16_t src_port, uint16_t dst_port, uint8_t proto, IP ip_version, uint16_t vlan_id) noexcept + { + FlowKey res{}; + // IPv4 to IPv6 mapping + if (ip_version == IP::v4) { + *reinterpret_cast(&res.src_ip[0]) = 0; + *reinterpret_cast(&res.src_ip[8]) = htobe32(0x0000FFFF); + *reinterpret_cast(&res.src_ip[12]) = *reinterpret_cast(src_ip); + *reinterpret_cast(&res.dst_ip[0]) = 0; + *reinterpret_cast(&res.dst_ip[8]) = htobe32(0x0000FFFF); + *reinterpret_cast(&res.dst_ip[12]) = *reinterpret_cast(dst_ip); + } else if (ip_version == IP::v6) { + std::memcpy(res.src_ip.begin(), src_ip, 16); + std::memcpy(res.dst_ip.begin(), dst_ip, 16); + } + res.src_port = src_port; + res.dst_port = dst_port; + res.proto = proto; + res.ip_version = ip_version; + res.vlan_id = vlan_id; + return res; + } + + /** + * \brief Create a reversed FlowKey object with the given parameters. Reverses ip addresses and ports directions + * + * @param src_ip Pointer to the source IP address (IPv4 or IPv6). + * @param dst_ip Pointer to the destination IP address (IPv4 or IPv6). + * @param src_port Source port (0 for non-TCP/UDP protocols). + * @param dst_port Destination port (0 for non-TCP/UDP protocols). + * @param proto IP protocol. + * @param ip_version IP version (IPv4 or IPv6). + * @param vlan_id VLAN ID (EMPTY_VLAN if not used). + * @return A FlowKey object initialized with the provided parameters, with reversed IP addresses and ports. + */ + template + static FlowKey + create_reversed_key(const Int* src_ip, const Int* dst_ip, + uint16_t src_port, uint16_t dst_port, uint8_t proto, IP ip_version, uint16_t vlan_id) noexcept + { + return create_direct_key(dst_ip, src_ip, dst_port, src_port, proto, ip_version, vlan_id); + } + + /** + * \brief Create a sorted FlowKey object based on source and destination IP addresses and ports. + * + * Flow to which packet belongs can be found only with one search. + * + * @param src_ip Pointer to the source IP address (IPv4 or IPv6). + * @param dst_ip Pointer to the destination IP address (IPv4 or IPv6). + * @param src_port Source port (0 for non-TCP/UDP protocols). + * @param dst_port Destination port (0 for non-TCP/UDP protocols). + * @param proto IP protocol. + * @param ip_version IP version (IPv4 or IPv6). + * @param vlan_id VLAN ID (EMPTY_VLAN if not used). + * @return A pair containing the FlowKey and a boolean indicating if it was created in reversed order. + */ + template + static std::pair + create_sorted_key(const Int* src_ip, const Int* dst_ip, + uint16_t src_port, uint16_t dst_port, uint8_t proto, IP ip_version, uint16_t vlan_id) noexcept + { + if (src_port < dst_port || (src_port == dst_port && std::memcmp(src_ip, dst_ip, ip_version == IP::v4 ? 4 : 16) < 0)) { + return {create_direct_key(src_ip, dst_ip, src_port, dst_port, proto, ip_version, vlan_id), false}; + } + return {create_reversed_key(src_ip, dst_ip, src_port, dst_port, proto, ip_version, vlan_id), true}; + } +}; + +} // ipxp \ No newline at end of file From ba0aa2fa1497dab592eee37ea11b9b406f476701 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:46:24 +0200 Subject: [PATCH 16/56] CTT - Update FlowRecord class --- src/plugins/storage/cache/src/flowRecord.cpp | 136 +++++++++++++++++++ src/plugins/storage/cache/src/flowRecord.hpp | 100 ++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 src/plugins/storage/cache/src/flowRecord.cpp create mode 100644 src/plugins/storage/cache/src/flowRecord.hpp diff --git a/src/plugins/storage/cache/src/flowRecord.cpp b/src/plugins/storage/cache/src/flowRecord.cpp new file mode 100644 index 000000000..54dd9795a --- /dev/null +++ b/src/plugins/storage/cache/src/flowRecord.cpp @@ -0,0 +1,136 @@ +/** +* \file + * \author Damir Zainullin + * \brief FlowRecord implementation. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#include "flowRecord.hpp" + +#include +#include +#include + +namespace ipxp { + +FlowRecord::FlowRecord() +{ + erase(); +}; + +FlowRecord::~FlowRecord() +{ + erase(); +}; + +void FlowRecord::erase() +{ + m_flow.remove_extensions(); + m_hash = 0; + memset(&m_flow.time_first, 0, sizeof(m_flow.time_first)); + memset(&m_flow.time_last, 0, sizeof(m_flow.time_last)); + m_flow.ip_version = 0; + m_flow.ip_proto = 0; + memset(&m_flow.src_ip, 0, sizeof(m_flow.src_ip)); + memset(&m_flow.dst_ip, 0, sizeof(m_flow.dst_ip)); + m_flow.src_port = 0; + m_flow.dst_port = 0; + m_flow.src_packets = 0; + m_flow.dst_packets = 0; + m_flow.src_bytes = 0; + m_flow.dst_bytes = 0; + m_flow.src_tcp_flags = 0; + m_flow.dst_tcp_flags = 0; +} +void FlowRecord::reuse() +{ + m_flow.remove_extensions(); + m_flow.time_first = m_flow.time_last; + m_flow.src_packets = 0; + m_flow.dst_packets = 0; + m_flow.src_bytes = 0; + m_flow.dst_bytes = 0; + m_flow.src_tcp_flags = 0; + m_flow.dst_tcp_flags = 0; +} + +void FlowRecord::create(const Packet &pkt, uint64_t hash) +{ + m_flow.src_packets = 1; + + m_hash = hash; + + m_flow.time_first = pkt.ts; + m_flow.time_last = pkt.ts; + m_flow.flow_hash = hash; + m_flow.vlan_id = pkt.vlan_id; + + memcpy(m_flow.src_mac, pkt.src_mac, 6); + memcpy(m_flow.dst_mac, pkt.dst_mac, 6); + + if (pkt.ip_version == IP::v4) { + m_flow.ip_version = pkt.ip_version; + m_flow.ip_proto = pkt.ip_proto; + m_flow.src_ip.v4 = pkt.src_ip.v4; + m_flow.dst_ip.v4 = pkt.dst_ip.v4; + m_flow.src_bytes = pkt.ip_len; + } else if (pkt.ip_version == IP::v6) { + m_flow.ip_version = pkt.ip_version; + m_flow.ip_proto = pkt.ip_proto; + memcpy(m_flow.src_ip.v6, pkt.src_ip.v6, 16); + memcpy(m_flow.dst_ip.v6, pkt.dst_ip.v6, 16); + m_flow.src_bytes = pkt.ip_len; + } + + if (pkt.ip_proto == IPPROTO_TCP) { + m_flow.src_port = pkt.src_port; + m_flow.dst_port = pkt.dst_port; + m_flow.src_tcp_flags = pkt.tcp_flags; + } else if (pkt.ip_proto == IPPROTO_UDP) { + m_flow.src_port = pkt.src_port; + m_flow.dst_port = pkt.dst_port; + } else if (pkt.ip_proto == IPPROTO_ICMP || + pkt.ip_proto == IPPROTO_ICMPV6) { + m_flow.src_port = pkt.src_port; + m_flow.dst_port = pkt.dst_port; + } +} + +void FlowRecord::update(const Packet &pkt) +{ + m_flow.time_last = pkt.ts; + if (pkt.source_pkt) { + m_flow.src_packets++; + m_flow.src_bytes += pkt.ip_len; + + if (pkt.ip_proto == IPPROTO_TCP) { + m_flow.src_tcp_flags |= pkt.tcp_flags; + } + } else { + m_flow.dst_packets++; + m_flow.dst_bytes += pkt.ip_len; + + if (pkt.ip_proto == IPPROTO_TCP) { + m_flow.dst_tcp_flags |= pkt.tcp_flags; + } + } +} + +} // ipxp \ No newline at end of file diff --git a/src/plugins/storage/cache/src/flowRecord.hpp b/src/plugins/storage/cache/src/flowRecord.hpp new file mode 100644 index 000000000..62303996e --- /dev/null +++ b/src/plugins/storage/cache/src/flowRecord.hpp @@ -0,0 +1,100 @@ +/** +* \file + * \author Damir Zainullin + * \brief FlowRecord declaration. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#pragma once + +#include +#include +#include +#include + +namespace ipxp { + +/** + * \brief Class representing a flow record in the cache. + * + * This class contains the flow data and provides methods to create, update, and erase the flow record. + */ +class alignas(64) FlowRecord +{ + uint64_t m_hash; +public: + Flow m_flow; ///< Flow data + + FlowRecord(); + + ~FlowRecord(); + + /** + * \brief Erase the flow record data. + * + * This method resets all fields of the flow record to their initial state. + */ + maybe_virtual void erase(); + + /** + * \brief Reuse the flow record. + * + * This method only resets the flow counters not erasing flow key + */ + void reuse(); + + /** + * \brief Create a new flow record from a packet. + * + * This method initializes the flow record with data from the given packet. + * + * @param pkt The packet to create the flow record from. + * @param pkt_hash The hash of the FlowKey of the packet. + */ + maybe_virtual void create(const Packet &pkt, uint64_t pkt_hash); + + /** + * \brief Update the flow record with data from a packet. + * @param pkt The packet to update the flow record with. + */ + void update(const Packet &pkt); + + /** + * \brief Check if flow record does not contain any valid flow. + * @return True if the flow record is empty, false otherwise. + */ + __attribute__((always_inline)) bool is_empty() const noexcept + { + return m_hash == 0; + } + + /** + * \brief Check if the given hash belongs to this flow record. + * @param hash The hash to check. + * @return True if the hash belongs to this flow record, false otherwise. + */ + __attribute__((always_inline)) bool belongs(uint64_t hash) const noexcept + { + return hash == m_hash; + } + +}; + +} // ipxp From f4ae919fdbcbdb7f08778e4f107d7b19426d2c68 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:46:53 +0200 Subject: [PATCH 17/56] CTT - Update input plugin base class --- include/ipfixprobe/inputPlugin.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/ipfixprobe/inputPlugin.hpp b/include/ipfixprobe/inputPlugin.hpp index 456af4052..5edc289a7 100644 --- a/include/ipfixprobe/inputPlugin.hpp +++ b/include/ipfixprobe/inputPlugin.hpp @@ -20,10 +20,12 @@ #include "parser-stats.hpp" #include "plugin.hpp" #include "telemetry-utils.hpp" +#include "cttConfig.hpp" #include #include #include +#include #include @@ -66,6 +68,11 @@ class IPXP_API InputPlugin std::shared_ptr plugin_dir, std::shared_ptr queues_dir); + virtual std::optional get_ctt_config() const + { + return std::nullopt; + } + /// Number of packets seen by the plugin. uint64_t m_seen = 0; /// Number of packets successfully parsed. From fc3198a8e0daa34d8ec41204bfdc42cc330bd208 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:47:20 +0200 Subject: [PATCH 18/56] CTT - Update base storage plugin class --- include/ipfixprobe/storagePlugin.hpp | 254 ++++++++++++++++++++------- 1 file changed, 195 insertions(+), 59 deletions(-) diff --git a/include/ipfixprobe/storagePlugin.hpp b/include/ipfixprobe/storagePlugin.hpp index 5bf6f84e1..2b217e02f 100644 --- a/include/ipfixprobe/storagePlugin.hpp +++ b/include/ipfixprobe/storagePlugin.hpp @@ -19,10 +19,10 @@ #include "plugin.hpp" #include "processPlugin.hpp" #include "ring.h" +#include "cttConfig.hpp" #include #include - #include namespace ipxp { @@ -64,8 +64,25 @@ class IPXP_API StoragePlugin : public Plugin { const ipx_ring_t* get_queue() const { return m_export_queue; } virtual void export_expired(time_t ts) { (void) ts; } + virtual void finish() {} + /** + * \brief Send signal that no new flows should be created + */ + virtual void terminate_input() { m_input_terminted = true; } + + /** + * \brief Check if input is still required by the storage plugin + * \return True if input is required, false otherwise. + */ + virtual bool requires_input() const { return !m_input_terminted; } + + virtual void init_ctt([[maybe_unused]]const CttConfig& ctt_config) + { + throw PluginError("CTT is not supported in this storage plugin"); + } + /** * \brief set telemetry directory for the storage */ @@ -92,83 +109,202 @@ class IPXP_API StoragePlugin : public Plugin { m_plugins[m_plugin_cnt++] = plugin; } -protected: - // Every StoragePlugin implementation should call these functions at appropriate places - /** - * \brief Call pre_create function for each added plugin. - * \param [in] pkt Input parsed packet. - * \return Options for flow cache. - */ - int plugins_pre_create(Packet& pkt) + * \brief Checks if process plugins require all available data. + * \param [in] flow Stored flow record. + * \return True if all data required, false otherwise. + */ + bool all_data_required(const Flow& flow) const noexcept { - int ret = 0; - for (unsigned int i = 0; i < m_plugin_cnt; i++) { - ret |= m_plugins[i]->pre_create(pkt); - } - return ret; + return flow.plugins_status.get_all_data.any(); } - /** - * \brief Call post_create function for each added plugin. - * \param [in,out] rec Stored flow record. - * \param [in] pkt Input parsed packet. - * \return Options for flow cache. - */ - int plugins_post_create(Flow& rec, const Packet& pkt) + /** + * \brief Checks if process plugins don't require any data. + * \param [in] flow Stored flow record. + * \return True if no data required, false otherwise. + */ + bool no_data_required(const Flow& flow) const noexcept { - int ret = 0; - for (unsigned int i = 0; i < m_plugin_cnt; i++) { - ret |= m_plugins[i]->post_create(rec, pkt); - } - return ret; + return flow.plugins_status.get_no_data.all(); } - /** - * \brief Call pre_update function for each added plugin. - * \param [in,out] rec Stored flow record. - * \param [in] pkt Input parsed packet. - * \return Options for flow cache. - */ - int plugins_pre_update(Flow& rec, Packet& pkt) + /** + * \brief Checks if process plugins require only flow metadata. + * \param [in] rec Stored flow record. + * \return True if only metadata required, false otherwise. + */ + bool only_metadata_required(const Flow& flow) const noexcept { - int ret = 0; - for (unsigned int i = 0; i < m_plugin_cnt; i++) { - ret |= m_plugins[i]->pre_update(rec, pkt); - } - return ret; + return !all_data_required(flow); } +protected: + //Every StoragePlugin implementation should call these functions at appropriate places + + /** + * \brief Call pre_create function for each added plugin. + * \param [in] pkt Input parsed packet. + * \return Options for flow cache. + */ + int plugins_pre_create(Packet& pkt) + { + PluginStatusConverter plugin_status_converter(m_plugins_status); + plugin_status_converter.reset(m_plugin_cnt); + int ret = 0; + for (unsigned int i = 0; i < m_plugin_cnt; i++) { + auto flow_action = m_plugins[i]->pre_create(pkt); + plugin_status_converter.set_flow_status(i, flow_action); + ret |= flow_action; + } + return ret; + } + + /** + * \brief Call post_create function for each added plugin. + * \param [in,out] rec Stored flow record. + * \param [in] pkt Input parsed packet. + * \return Options for flow cache. + */ + int plugins_post_create(Flow& rec, const Packet& pkt) + { + PluginStatusConverter plugin_status_converter(m_plugins_status); + int ret = 0; + for (unsigned int i = 0; i < m_plugin_cnt; i++) { + if (plugin_status_converter.plugin_gets_no_data(i)) + continue; + + auto flow_action = m_plugins[i]->post_create(rec, pkt); + plugin_status_converter.set_flow_status(i, flow_action); + ret |= flow_action; + } + + PluginStatusConverter(rec.plugins_status) = plugin_status_converter; + return ret; + } + + /** + * \brief Call pre_update function for each added plugin. + * \param [in,out] rec Stored flow record. + * \param [in] pkt Input parsed packet. + * \return Options for flow cache. + */ + int plugins_pre_update(Flow& rec, Packet& pkt) + { + PluginStatusConverter plugin_status_converter(rec.plugins_status); + int ret = 0; + for (unsigned int i = 0; i < m_plugin_cnt; i++) { + if (plugin_status_converter.plugin_gets_no_data(i)) + continue; + + auto flow_action = m_plugins[i]->pre_update(rec, pkt); + plugin_status_converter.set_flow_status(i, flow_action); + ret |= flow_action; + } + return ret; + } + + /** + * \brief Call post_update function for each added plugin. + * \param [in,out] rec Stored flow record. + * \param [in] pkt Input parsed packet. + */ + int plugins_post_update(Flow& rec, const Packet& pkt) + { + PluginStatusConverter plugin_status_converter(rec.plugins_status); + int ret = 0; + for (unsigned int i = 0; i < m_plugin_cnt; i++) { + if (plugin_status_converter.plugin_gets_no_data(i)) + continue; + + auto flow_action = m_plugins[i]->post_update(rec, pkt); + plugin_status_converter.set_flow_status(i, flow_action); + ret |= flow_action; + } + return ret; + } + + /** + * \brief Call pre_export function for each added plugin. + * \param [in,out] rec Stored flow record. + */ + void plugins_pre_export(Flow& rec) + { + PluginStatusConverter plugin_status_converter(rec.plugins_status); + for (unsigned int i = 0; i < m_plugin_cnt; i++) { + //if (plugin_status_converter.plugin_gets_no_data(i)) + // continue; + m_plugins[i]->pre_export(rec); + } + } + /** - * \brief Call post_update function for each added plugin. - * \param [in,out] rec Stored flow record. - * \param [in] pkt Input parsed packet. - */ - int plugins_post_update(Flow& rec, const Packet& pkt) - { - int ret = 0; - for (unsigned int i = 0; i < m_plugin_cnt; i++) { - ret |= m_plugins[i]->post_update(rec, pkt); + * \brief Auxiliary class for manipulations plugins status. + */ + class PluginStatusConverter { + public: + PluginStatusConverter(Flow::PluginsStatus& plugins_status) noexcept + : m_plugins_status(plugins_status) + { } - return ret; - } - /** - * \brief Call pre_export function for each added plugin. - * \param [in,out] rec Stored flow record. - */ - void plugins_pre_export(Flow& rec) - { - for (unsigned int i = 0; i < m_plugin_cnt; i++) { - m_plugins[i]->pre_export(rec); + /** + * \brief Resets all kept plugins status to the initial state. + * \param [in] plugin_count Count of process plugins. + */ + void reset(size_t plugin_count) noexcept + { + m_plugins_status.get_all_data.reset(); + m_plugins_status.get_no_data = (uint64_t) -1 << plugin_count; } - } + + /** + * \brief Sets process plugin status at the given index. + * \param [in] index Index of the process plugin. + * \param [in] flow_action Given flow action to set. + */ + void set_flow_status(size_t index, ProcessPlugin::FlowAction flow_action) noexcept + { + if (flow_action == ProcessPlugin::FlowAction::GET_NO_DATA) { + m_plugins_status.get_all_data[index] = false; + m_plugins_status.get_no_data[index] = true; + } else if (flow_action == ProcessPlugin::FlowAction::GET_ONLY_METADATA) { + m_plugins_status.get_all_data[index] = false; + } else if (flow_action == ProcessPlugin::FlowAction::GET_ALL_DATA) { + m_plugins_status.get_all_data[index] = true; + } + } + + /** + * \brief Checks if the process plugin at the given index doesn't require any data. + * \param [in] index Index of the process plugin. + * \return True, if the process plugin doesn't require any data. + */ + bool plugin_gets_no_data(size_t index) noexcept + { + return m_plugins_status.get_no_data[index]; + } + + PluginStatusConverter& + operator=(const PluginStatusConverter& plugin_status_converter) noexcept + { + m_plugins_status.get_all_data = plugin_status_converter.m_plugins_status.get_all_data; + m_plugins_status.get_no_data = plugin_status_converter.m_plugins_status.get_no_data; + return *this; + } + + private: + Flow::PluginsStatus& m_plugins_status; + }; ipx_ring_t* m_export_queue; + bool m_input_terminted = false; private: - ProcessPlugin** m_plugins; /**< Array of plugins. */ + ProcessPlugin **m_plugins; /**< Array of plugins. */ uint32_t m_plugin_cnt; + Flow::PluginsStatus + m_plugins_status; /**< Keeps statuses of the process plugin before flow is created. */ }; /** From f401758e81670cf0c335c0841fcd101045c7fadf Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:49:29 +0200 Subject: [PATCH 19/56] CTT - Update base process plugin class --- include/ipfixprobe/processPlugin.hpp | 88 +++++++++++++++------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/include/ipfixprobe/processPlugin.hpp b/include/ipfixprobe/processPlugin.hpp index 8f6e92709..27189b2d5 100644 --- a/include/ipfixprobe/processPlugin.hpp +++ b/include/ipfixprobe/processPlugin.hpp @@ -21,26 +21,42 @@ namespace ipxp { -/** - * \brief Tell storage plugin to flush (immediately export) current flow. - * Behavior when called from post_create, pre_update and post_update: flush current Flow and erase - * FlowRecord. - */ -#define FLOW_FLUSH 0x1 - -/** - * \brief Tell storage plugin to flush (immediately export) current flow. - * Behavior when called from post_create: flush current Flow and erase FlowRecord. - * Behavior when called from pre_update and post_update: flush current Flow, erase FlowRecord and - * call post_create on packet. - */ -#define FLOW_FLUSH_WITH_REINSERT 0x3 - /** * \brief Class template for flow cache plugins. */ class IPXP_API ProcessPlugin : public Plugin { public: + enum FlowAction : int { + /** + * \brief Tell storage plugin that process plugin requires all incoming data for given flow. + */ + GET_ALL_DATA = 0, + /** + * \brief Tell storage plugin that process plugin requires only metadata. + * Used to offload the cache when all process plugin return GET_METADATA. + */ + GET_ONLY_METADATA = 0x2, + /** + * \brief Tell storage plugin that process plugin has ended up its work and doesn't require + * any new data. Used to offload the cache when all process plugin return NO_PROCESS. + */ + GET_NO_DATA = 0x4, + /** + * \brief Tell storage plugin to flush (immediately export) current flow. + * Behavior when called from post_create, pre_update and post_update: flush current Flow and + * erase FlowRecord. + */ + FLUSH = 0x1, + + /** + * \brief Tell storage plugin to flush (immediately export) current flow. + * Behavior when called from post_create: flush current Flow and erase FlowRecord. + * Behavior when called from pre_update and post_update: flush current Flow, erase + * FlowRecord and call post_create on packet. + */ + FLUSH_WITH_REINSERT = 0x7 + }; + ProcessPlugin(int pluginID) : m_pluginID(pluginID) { @@ -56,11 +72,10 @@ class IPXP_API ProcessPlugin : public Plugin { * \param [in] pkt Parsed packet. * \return 0 on success or FLOW_FLUSH option. */ - virtual int pre_create(Packet& pkt) - { - (void) pkt; - return 0; - } + virtual FlowAction pre_create([[maybe_unused]] Packet& pkt) + { + return FlowAction::GET_ALL_DATA; + } /** * \brief Called after a new flow record is created. @@ -68,25 +83,20 @@ class IPXP_API ProcessPlugin : public Plugin { * \param [in] pkt Parsed packet. * \return 0 on success or FLOW_FLUSH option. */ - virtual int post_create(Flow& rec, const Packet& pkt) - { - (void) rec; - (void) pkt; - return 0; - } - + virtual FlowAction post_create([[maybe_unused]] Flow& rec, [[maybe_unused]] const Packet& pkt) + { + return FlowAction::GET_ALL_DATA; + } /** * \brief Called before an existing record is update. * \param [in,out] rec Reference to flow record. * \param [in,out] pkt Parsed packet. * \return 0 on success or FLOW_FLUSH option. */ - virtual int pre_update(Flow& rec, Packet& pkt) - { - (void) rec; - (void) pkt; - return 0; - } + virtual FlowAction pre_update([[maybe_unused]] Flow& rec, [[maybe_unused]] Packet& pkt) + { + return FlowAction::GET_ALL_DATA; + } /** * \brief Called after an existing record is updated. @@ -94,18 +104,16 @@ class IPXP_API ProcessPlugin : public Plugin { * \param [in,out] pkt Parsed packet. * \return 0 on success or FLOW_FLUSH option. */ - virtual int post_update(Flow& rec, const Packet& pkt) - { - (void) rec; - (void) pkt; - return 0; - } + virtual FlowAction post_update([[maybe_unused]] Flow& rec, [[maybe_unused]] const Packet& pkt) + { + return FlowAction::GET_ALL_DATA; + } /** * \brief Called before a flow record is exported from the cache. * \param [in,out] rec Reference to flow record. */ - virtual void pre_export(Flow& rec) { (void) rec; } + virtual void pre_export([[maybe_unused]] Flow& rec) {} protected: int m_pluginID; From ae2a42b38c8bb3f4a33b8a6796bb63c40ed668e4 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:51:03 +0200 Subject: [PATCH 20/56] CTT - Update BasicPlus process plugin --- src/plugins/process/basicplus/src/basicplus.cpp | 8 ++++---- src/plugins/process/basicplus/src/basicplus.hpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/process/basicplus/src/basicplus.cpp b/src/plugins/process/basicplus/src/basicplus.cpp index 667c7180e..617f60781 100644 --- a/src/plugins/process/basicplus/src/basicplus.cpp +++ b/src/plugins/process/basicplus/src/basicplus.cpp @@ -56,7 +56,7 @@ ProcessPlugin* BASICPLUSPlugin::copy() return new BASICPLUSPlugin(*this); } -int BASICPLUSPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction BASICPLUSPlugin::post_create(Flow& rec, const Packet& pkt) { RecordExtBASICPLUS* p = new RecordExtBASICPLUS(m_pluginID); @@ -71,10 +71,10 @@ int BASICPLUSPlugin::post_create(Flow& rec, const Packet& pkt) p->tcp_syn_size = pkt.ip_len; } - return 0; + return ProcessPlugin::FlowAction::GET_ONLY_METADATA; } -int BASICPLUSPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction BASICPLUSPlugin::pre_update(Flow& rec, Packet& pkt) { RecordExtBASICPLUS* p = (RecordExtBASICPLUS*) rec.get_extension(m_pluginID); uint8_t dir = pkt.source_pkt ? 0 : 1; @@ -91,7 +91,7 @@ int BASICPLUSPlugin::pre_update(Flow& rec, Packet& pkt) } // update tcp options mask across the tcp flow p->tcp_opt[dir] |= pkt.tcp_options; - return 0; + return ProcessPlugin::FlowAction::GET_ONLY_METADATA; } static const PluginRegistrar diff --git a/src/plugins/process/basicplus/src/basicplus.hpp b/src/plugins/process/basicplus/src/basicplus.hpp index 9d890473d..3a26d3fd5 100644 --- a/src/plugins/process/basicplus/src/basicplus.hpp +++ b/src/plugins/process/basicplus/src/basicplus.hpp @@ -152,8 +152,8 @@ class BASICPLUSPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtBASICPLUS(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt); }; } // namespace ipxp From b66a384dc234ecc5b93b20f938a17e105a40ae4d Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:51:36 +0200 Subject: [PATCH 21/56] CTT - Update BSTATS process plugin --- src/plugins/process/bstats/src/bstats.cpp | 23 ++++++----------------- src/plugins/process/bstats/src/bstats.hpp | 6 ++---- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/plugins/process/bstats/src/bstats.cpp b/src/plugins/process/bstats/src/bstats.cpp index 47285e419..4ae547bb8 100644 --- a/src/plugins/process/bstats/src/bstats.cpp +++ b/src/plugins/process/bstats/src/bstats.cpp @@ -57,12 +57,6 @@ ProcessPlugin* BSTATSPlugin::copy() return new BSTATSPlugin(*this); } -int BSTATSPlugin::pre_create(Packet& pkt) -{ - (void) pkt; - return 0; -} - #define BCOUNT burst_count[direction] void BSTATSPlugin::initialize_new_burst( RecordExtBSTATS* bstats_record, @@ -133,28 +127,23 @@ void BSTATSPlugin::update_record(RecordExtBSTATS* bstats_record, const Packet& p } } -int BSTATSPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction BSTATSPlugin::post_create(Flow& rec, const Packet& pkt) { RecordExtBSTATS* bstats_record = new RecordExtBSTATS(m_pluginID); rec.add_extension(bstats_record); update_record(bstats_record, pkt); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } -int BSTATSPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction BSTATSPlugin::pre_update(Flow& rec, Packet& pkt) { RecordExtBSTATS* bstats_record = static_cast(rec.get_extension(m_pluginID)); update_record(bstats_record, pkt); - return 0; -} - -int BSTATSPlugin::post_update(Flow& rec, const Packet& pkt) -{ - (void) rec; - (void) pkt; - return 0; + return bstats_record->burst_count[0] + bstats_record->burst_count[1] >= BSTATS_MAXELENCOUNT ? + ProcessPlugin::FlowAction::GET_NO_DATA : + ProcessPlugin::FlowAction::GET_ALL_DATA; } void BSTATSPlugin::pre_export(Flow& rec) diff --git a/src/plugins/process/bstats/src/bstats.hpp b/src/plugins/process/bstats/src/bstats.hpp index f9a7d22b4..e27c7ae45 100644 --- a/src/plugins/process/bstats/src/bstats.hpp +++ b/src/plugins/process/bstats/src/bstats.hpp @@ -253,10 +253,8 @@ class BSTATSPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtBSTATS(m_pluginID); } ProcessPlugin* copy(); - int pre_create(Packet& pkt); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt); void pre_export(Flow& rec); static const struct timeval min_packet_in_burst; From 4880cb9dfcc39d9597c2675e9fd1ab8aa56ab80f Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:52:00 +0200 Subject: [PATCH 22/56] CTT - Update DNS process plugin --- src/plugins/process/dns/src/dns.cpp | 21 +++++++++++---------- src/plugins/process/dns/src/dns.hpp | 8 +++++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/plugins/process/dns/src/dns.cpp b/src/plugins/process/dns/src/dns.cpp index 08aa71f92..93e90e8d3 100644 --- a/src/plugins/process/dns/src/dns.cpp +++ b/src/plugins/process/dns/src/dns.cpp @@ -94,7 +94,7 @@ ProcessPlugin* DNSPlugin::copy() return new DNSPlugin(*this); } -int DNSPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction DNSPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.dst_port == 53 || pkt.src_port == 53) { return add_ext_dns( @@ -104,13 +104,13 @@ int DNSPlugin::post_create(Flow& rec, const Packet& pkt) rec); } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int DNSPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction DNSPlugin::post_update(Flow& rec, const Packet& pkt) { if (pkt.dst_port == 53 || pkt.src_port == 53) { - RecordExt* ext = rec.get_extension(m_pluginID); + auto* ext = static_cast(rec.get_extension(m_pluginID)); if (ext == nullptr) { return add_ext_dns( reinterpret_cast(pkt.payload), @@ -122,12 +122,13 @@ int DNSPlugin::post_update(Flow& rec, const Packet& pkt) reinterpret_cast(pkt.payload), pkt.payload_len, pkt.ip_proto == IPPROTO_TCP, - static_cast(ext)); + ext); } - return FLOW_FLUSH; + + return ProcessPlugin::FlowAction::FLUSH; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } void DNSPlugin::finish(bool print_stats) @@ -670,16 +671,16 @@ bool DNSPlugin::parse_dns(const char* data, unsigned int payload_len, bool tcp, * \param [in] tcp DNS over tcp. * \param [out] rec Destination Flow. */ -int DNSPlugin::add_ext_dns(const char* data, unsigned int payload_len, bool tcp, Flow& rec) +ProcessPlugin::FlowAction DNSPlugin::add_ext_dns(const char* data, unsigned int payload_len, bool tcp, Flow& rec) { RecordExtDNS* ext = new RecordExtDNS(m_pluginID); if (!parse_dns(data, payload_len, tcp, ext)) { delete ext; - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } else { rec.add_extension(ext); } - return FLOW_FLUSH; + return ProcessPlugin::FlowAction::FLUSH; } static const PluginRegistrar dnsRegistrar(dnsPluginManifest); diff --git a/src/plugins/process/dns/src/dns.hpp b/src/plugins/process/dns/src/dns.hpp index 44b08b02a..07dc570a5 100644 --- a/src/plugins/process/dns/src/dns.hpp +++ b/src/plugins/process/dns/src/dns.hpp @@ -61,7 +61,9 @@ struct RecordExtDNS : public RecordExt { char data[160]; uint16_t psize; uint8_t dns_do; + uint16_t parsing_tries; + static constexpr size_t MAX_PARSING_TRIES = 30; /** * \brief Constructor. */ @@ -157,8 +159,8 @@ class DNSPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtDNS(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); void finish(bool print_stats); private: @@ -170,7 +172,7 @@ class DNSPlugin : public ProcessPlugin { uint32_t data_len; /**< Length of packet payload. */ bool parse_dns(const char* data, unsigned int payload_len, bool tcp, RecordExtDNS* rec); - int add_ext_dns(const char* data, unsigned int payload_len, bool tcp, Flow& rec); + ProcessPlugin::FlowAction add_ext_dns(const char* data, unsigned int payload_len, bool tcp, Flow& rec); void process_srv(std::string& str) const; void process_rdata( const char* record_begin, From 14dde4bf3995d95e5ff2eed27cbd6497310b5ff4 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:52:27 +0200 Subject: [PATCH 23/56] CTT - Update DNSSD process plugin --- src/plugins/process/dnssd/src/dnssd.cpp | 16 ++++++++-------- src/plugins/process/dnssd/src/dnssd.hpp | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/plugins/process/dnssd/src/dnssd.cpp b/src/plugins/process/dnssd/src/dnssd.cpp index 140f45e64..ff0d28603 100644 --- a/src/plugins/process/dnssd/src/dnssd.cpp +++ b/src/plugins/process/dnssd/src/dnssd.cpp @@ -107,7 +107,7 @@ ProcessPlugin* DNSSDPlugin::copy() return new DNSSDPlugin(*this); } -int DNSSDPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction DNSSDPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.dst_port == 5353 || pkt.src_port == 5353) { return add_ext_dnssd( @@ -117,10 +117,10 @@ int DNSSDPlugin::post_create(Flow& rec, const Packet& pkt) rec); } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int DNSSDPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction DNSSDPlugin::post_update(Flow& rec, const Packet& pkt) { if (pkt.dst_port == 5353 || pkt.src_port == 5353) { RecordExt* ext = rec.get_extension(m_pluginID); @@ -138,10 +138,10 @@ int DNSSDPlugin::post_update(Flow& rec, const Packet& pkt) pkt.ip_proto == IPPROTO_TCP, static_cast(ext)); } - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } void DNSSDPlugin::finish(bool print_stats) @@ -710,18 +710,18 @@ void DNSSDPlugin::filtered_append( * \param [in] tcp DNS over tcp. * \param [out] rec Destination Flow. */ -int DNSSDPlugin::add_ext_dnssd(const char* data, unsigned int payload_len, bool tcp, Flow& rec) +ProcessPlugin::FlowAction DNSSDPlugin::add_ext_dnssd(const char* data, unsigned int payload_len, bool tcp, Flow& rec) { RecordExtDNSSD* ext = new RecordExtDNSSD(m_pluginID); if (!parse_dns(data, payload_len, tcp, ext)) { delete ext; - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } else { rec.add_extension(ext); } - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } static const PluginRegistrar dnssdRegistrar(dnssdPluginManifest); diff --git a/src/plugins/process/dnssd/src/dnssd.hpp b/src/plugins/process/dnssd/src/dnssd.hpp index a927a642d..53c963ca3 100644 --- a/src/plugins/process/dnssd/src/dnssd.hpp +++ b/src/plugins/process/dnssd/src/dnssd.hpp @@ -249,8 +249,8 @@ class DNSSDPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtDNSSD(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); void finish(bool print_stats); private: @@ -263,7 +263,7 @@ class DNSSDPlugin : public ProcessPlugin { uint32_t data_len; /**< Length of packet payload. */ bool parse_dns(const char* data, unsigned int payload_len, bool tcp, RecordExtDNSSD* rec); - int add_ext_dnssd(const char* data, unsigned int payload_len, bool tcp, Flow& rec); + ProcessPlugin::FlowAction add_ext_dnssd(const char* data, unsigned int payload_len, bool tcp, Flow& rec); void process_rdata( const char* record_begin, const char* data, From 7c5fc092b0af5c8cd03a60d2b7e8222306f48a2f Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:53:03 +0200 Subject: [PATCH 24/56] CTT - Update FlowHash process plugin --- src/plugins/process/flowHash/src/flow_hash.cpp | 4 ++-- src/plugins/process/flowHash/src/flow_hash.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/process/flowHash/src/flow_hash.cpp b/src/plugins/process/flowHash/src/flow_hash.cpp index 73d0299b7..7cd735955 100644 --- a/src/plugins/process/flowHash/src/flow_hash.cpp +++ b/src/plugins/process/flowHash/src/flow_hash.cpp @@ -51,7 +51,7 @@ ProcessPlugin* FLOW_HASHPlugin::copy() return new FLOW_HASHPlugin(*this); } -int FLOW_HASHPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction FLOW_HASHPlugin::post_create(Flow& rec, const Packet& pkt) { (void) pkt; auto ext = new RecordExtFLOW_HASH(m_pluginID); @@ -60,7 +60,7 @@ int FLOW_HASHPlugin::post_create(Flow& rec, const Packet& pkt) rec.add_extension(ext); - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } static const PluginRegistrar diff --git a/src/plugins/process/flowHash/src/flow_hash.hpp b/src/plugins/process/flowHash/src/flow_hash.hpp index ab981de5f..1e01b521d 100644 --- a/src/plugins/process/flowHash/src/flow_hash.hpp +++ b/src/plugins/process/flowHash/src/flow_hash.hpp @@ -101,7 +101,7 @@ class FLOW_HASHPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtFLOW_HASH(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); }; } // namespace ipxp From debbb575272eb7bbea896369d4da82af216ca2cd Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:53:21 +0200 Subject: [PATCH 25/56] CTT - Update HTTP process plugin --- src/plugins/process/http/src/http.cpp | 42 +++++++++++++++++---------- src/plugins/process/http/src/http.hpp | 8 +++-- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/plugins/process/http/src/http.cpp b/src/plugins/process/http/src/http.cpp index 8b14f8be6..70b0b1879 100644 --- a/src/plugins/process/http/src/http.cpp +++ b/src/plugins/process/http/src/http.cpp @@ -94,49 +94,61 @@ ProcessPlugin* HTTPPlugin::copy() return new HTTPPlugin(*this); } -int HTTPPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction HTTPPlugin::post_create(Flow& rec, const Packet& pkt) { const char* payload = reinterpret_cast(pkt.payload); if (is_request(payload, pkt.payload_len)) { add_ext_http_request(payload, pkt.payload_len, rec); } else if (is_response(payload, pkt.payload_len)) { add_ext_http_response(payload, pkt.payload_len, rec); - } + } - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } -int HTTPPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction HTTPPlugin::pre_update(Flow& rec, Packet& pkt) { - RecordExt* ext = nullptr; + auto* ext = static_cast(rec.get_extension(m_pluginID)); const char* payload = reinterpret_cast(pkt.payload); - if (is_request(payload, pkt.payload_len)) { - ext = rec.get_extension(m_pluginID); + + if (is_request(payload, pkt.payload_len)) { if (ext == nullptr) { /* Check if header is present in flow. */ add_ext_http_request(payload, pkt.payload_len, rec); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } + ext->parsing_failed = 0; - parse_http_request(payload, pkt.payload_len, static_cast(ext)); + parse_http_request(payload, pkt.payload_len, ext); if (flow_flush) { flow_flush = false; - return FLOW_FLUSH_WITH_REINSERT; + return ProcessPlugin::FlowAction::FLUSH_WITH_REINSERT; } } else if (is_response(payload, pkt.payload_len)) { - ext = rec.get_extension(m_pluginID); if (ext == nullptr) { /* Check if header is present in flow. */ add_ext_http_response(payload, pkt.payload_len, rec); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } + ext->parsing_failed = 0; - parse_http_response(payload, pkt.payload_len, static_cast(ext)); + parse_http_response(payload, pkt.payload_len, ext); if (flow_flush) { flow_flush = false; - return FLOW_FLUSH_WITH_REINSERT; + return ProcessPlugin::FlowAction::FLUSH_WITH_REINSERT; } + } else if (ext != nullptr) { + ext->parsing_failed++; } - return 0; + if (ext != nullptr) { + return ext->parsing_failed >= RecordExtHTTP::MAX_PARSING_FAILED_COUNT ? + ProcessPlugin::FlowAction::GET_NO_DATA : + ProcessPlugin::FlowAction::GET_ALL_DATA; + } + + return rec.src_packets + rec.dst_packets >= RecordExtHTTP::MAX_PARSING_FAILED_COUNT ? + ProcessPlugin::FlowAction::GET_NO_DATA : + ProcessPlugin::FlowAction::GET_ALL_DATA; + } void HTTPPlugin::finish(bool print_stats) diff --git a/src/plugins/process/http/src/http.hpp b/src/plugins/process/http/src/http.hpp index df37699f3..641fd12ee 100644 --- a/src/plugins/process/http/src/http.hpp +++ b/src/plugins/process/http/src/http.hpp @@ -67,6 +67,9 @@ struct RecordExtHTTP : public RecordExt { char server[128]; char set_cookie[512]; + uint16_t parsing_failed; + static constexpr size_t MAX_PARSING_FAILED_COUNT = 30; + /** * \brief Constructor. */ @@ -84,6 +87,7 @@ struct RecordExtHTTP : public RecordExt { content_type[0] = 0; server[0] = 0; set_cookie[0] = 0; + parsing_failed = 0; } #ifdef WITH_NEMEA @@ -198,8 +202,8 @@ class HTTPPlugin : public ProcessPlugin { std::string get_name() const { return "http"; } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt) override; + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt) override; void finish(bool print_stats); private: From 5e2b1ff5b5bc7fac49a4ddd2ce6bb817da2194a9 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:53:43 +0200 Subject: [PATCH 26/56] CTT - Update ICMP process plugin --- src/plugins/process/icmp/src/icmp.cpp | 7 ++++--- src/plugins/process/icmp/src/icmp.hpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/plugins/process/icmp/src/icmp.cpp b/src/plugins/process/icmp/src/icmp.cpp index 426ae9b85..e1b98e3ee 100644 --- a/src/plugins/process/icmp/src/icmp.cpp +++ b/src/plugins/process/icmp/src/icmp.cpp @@ -42,11 +42,11 @@ ProcessPlugin* ICMPPlugin::copy() return new ICMPPlugin(*this); } -int ICMPPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction ICMPPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.ip_proto == IPPROTO_ICMP || pkt.ip_proto == IPPROTO_ICMPV6) { if (pkt.payload_len < sizeof(RecordExtICMP::type_code)) - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; auto ext = new RecordExtICMP(m_pluginID); @@ -55,8 +55,9 @@ int ICMPPlugin::post_create(Flow& rec, const Packet& pkt) ext->type_code = *reinterpret_cast(pkt.payload); rec.add_extension(ext); + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } static const PluginRegistrar icmpRegistrar(icmpPluginManifest); diff --git a/src/plugins/process/icmp/src/icmp.hpp b/src/plugins/process/icmp/src/icmp.hpp index 85ca955f1..18917a3da 100644 --- a/src/plugins/process/icmp/src/icmp.hpp +++ b/src/plugins/process/icmp/src/icmp.hpp @@ -97,7 +97,7 @@ class ICMPPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtICMP(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); }; } // namespace ipxp From af73d56838a2ec4ac1b4c0817d688592c5d0502b Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:54:06 +0200 Subject: [PATCH 27/56] CTT - Update MPLS process plugin --- src/plugins/process/mpls/src/mpls.cpp | 6 +++--- src/plugins/process/mpls/src/mpls.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/process/mpls/src/mpls.cpp b/src/plugins/process/mpls/src/mpls.cpp index 7f4e77b21..d0ef3ad3e 100644 --- a/src/plugins/process/mpls/src/mpls.cpp +++ b/src/plugins/process/mpls/src/mpls.cpp @@ -42,17 +42,17 @@ ProcessPlugin* MPLSPlugin::copy() return new MPLSPlugin(*this); } -int MPLSPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction MPLSPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.mplsTop == 0) { - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } auto ext = new RecordExtMPLS(m_pluginID); ext->mpls = pkt.mplsTop; rec.add_extension(ext); - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } static const PluginRegistrar mplsRegistrar(mplsPluginManifest); diff --git a/src/plugins/process/mpls/src/mpls.hpp b/src/plugins/process/mpls/src/mpls.hpp index cd5614e04..e44ba02e8 100644 --- a/src/plugins/process/mpls/src/mpls.hpp +++ b/src/plugins/process/mpls/src/mpls.hpp @@ -100,7 +100,7 @@ class MPLSPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtMPLS(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); }; } // namespace ipxp From b25587a7b1060e717dfea16f44159933112ca3f2 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:54:27 +0200 Subject: [PATCH 28/56] CTT - Update MQTT process plugin --- src/plugins/process/mqtt/src/mqtt.cpp | 23 +++++++++++------------ src/plugins/process/mqtt/src/mqtt.hpp | 6 +++--- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/plugins/process/mqtt/src/mqtt.cpp b/src/plugins/process/mqtt/src/mqtt.cpp index 269b03617..8d06434c6 100644 --- a/src/plugins/process/mqtt/src/mqtt.cpp +++ b/src/plugins/process/mqtt/src/mqtt.cpp @@ -42,23 +42,25 @@ MQTTPlugin::MQTTPlugin(const std::string& params, int pluginID) init(params.c_str()); } -int MQTTPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction MQTTPlugin::post_create(Flow& rec, const Packet& pkt) { - if (has_mqtt_protocol_name(reinterpret_cast(pkt.payload), pkt.payload_len)) + if (has_mqtt_protocol_name(reinterpret_cast(pkt.payload), pkt.payload_len)) { add_ext_mqtt(reinterpret_cast(pkt.payload), pkt.payload_len, rec); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; + } + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int MQTTPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction MQTTPlugin::pre_update(Flow& rec, Packet& pkt) { const char* payload = reinterpret_cast(pkt.payload); RecordExt* ext = rec.get_extension(m_pluginID); if (ext == nullptr) { - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } else { parse_mqtt(payload, pkt.payload_len, static_cast(ext)); } - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } /** @@ -179,16 +181,13 @@ bool MQTTPlugin::parse_mqtt(const char* data, int payload_len, RecordExtMQTT* re return true; } -int MQTTPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction MQTTPlugin::post_update([[maybe_unused]] Flow& rec, [[maybe_unused]] const Packet& pkt) { - (void) pkt; - (void) rec; - if (flow_flush) { flow_flush = false; - return FLOW_FLUSH; + return ProcessPlugin::FlowAction::FLUSH; } - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } /** diff --git a/src/plugins/process/mqtt/src/mqtt.hpp b/src/plugins/process/mqtt/src/mqtt.hpp index 74b6ac7ac..9d5d420dd 100644 --- a/src/plugins/process/mqtt/src/mqtt.hpp +++ b/src/plugins/process/mqtt/src/mqtt.hpp @@ -157,9 +157,9 @@ struct RecordExtMQTT : public RecordExt { class MQTTPlugin : public ProcessPlugin { public: MQTTPlugin(const std::string& params, int pluginID); - int post_create(Flow& rec, const Packet& pkt) override; - int pre_update(Flow& rec, Packet& pkt) override; - int post_update(Flow& rec, const Packet& pkt) override; + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt) override; + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt) override; + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt) override; RecordExt* get_ext() const { return new RecordExtMQTT(m_pluginID); } OptionsParser* get_parser() const { return new MQTTOptionsParser(); } std::string get_name() const { return "mqtt"; } From 6d00cc7178de25846ffec12b433a015b14c5d842 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:54:45 +0200 Subject: [PATCH 29/56] CTT - Update IDPContnent process plugin --- src/plugins/process/idpContent/src/idpcontent.cpp | 8 ++++---- src/plugins/process/idpContent/src/idpcontent.hpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/process/idpContent/src/idpcontent.cpp b/src/plugins/process/idpContent/src/idpcontent.cpp index 3957cfdb4..64b262de6 100644 --- a/src/plugins/process/idpContent/src/idpcontent.cpp +++ b/src/plugins/process/idpContent/src/idpcontent.cpp @@ -72,22 +72,22 @@ void IDPCONTENTPlugin::update_record(RecordExtIDPCONTENT* idpcontent_data, const } } -int IDPCONTENTPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction IDPCONTENTPlugin::post_create(Flow& rec, const Packet& pkt) { RecordExtIDPCONTENT* idpcontent_data = new RecordExtIDPCONTENT(m_pluginID); memset(idpcontent_data->pkt_export_flg, 0, 2 * sizeof(uint8_t)); rec.add_extension(idpcontent_data); update_record(idpcontent_data, pkt); - return 0; + return ProcessPlugin::FlowAction::GET_ONLY_METADATA; } -int IDPCONTENTPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction IDPCONTENTPlugin::post_update(Flow& rec, const Packet& pkt) { RecordExtIDPCONTENT* idpcontent_data = static_cast(rec.get_extension(m_pluginID)); update_record(idpcontent_data, pkt); - return 0; + return ProcessPlugin::FlowAction::GET_ONLY_METADATA; } static const PluginRegistrar diff --git a/src/plugins/process/idpContent/src/idpcontent.hpp b/src/plugins/process/idpContent/src/idpcontent.hpp index a23e36f1e..9d8a1a7ef 100644 --- a/src/plugins/process/idpContent/src/idpcontent.hpp +++ b/src/plugins/process/idpContent/src/idpcontent.hpp @@ -133,8 +133,8 @@ class IDPCONTENTPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtIDPCONTENT(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); void update_record(RecordExtIDPCONTENT* pstats_data, const Packet& pkt); }; From 88ac66f3e3c47f9b7e466aeb111e489c0d2eef09 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:55:05 +0200 Subject: [PATCH 30/56] CTT - Update NetBIOS process plugin --- src/plugins/process/netbios/src/netbios.cpp | 17 ++++++++--------- src/plugins/process/netbios/src/netbios.hpp | 6 +++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/plugins/process/netbios/src/netbios.cpp b/src/plugins/process/netbios/src/netbios.cpp index d0e38808c..762c41967 100644 --- a/src/plugins/process/netbios/src/netbios.cpp +++ b/src/plugins/process/netbios/src/netbios.cpp @@ -58,35 +58,34 @@ ProcessPlugin* NETBIOSPlugin::copy() return new NETBIOSPlugin(*this); } -int NETBIOSPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction NETBIOSPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.dst_port == 137 || pkt.src_port == 137) { return add_netbios_ext(rec, pkt); } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int NETBIOSPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction NETBIOSPlugin::post_update(Flow& rec, const Packet& pkt) { if (pkt.dst_port == 137 || pkt.src_port == 137) { return add_netbios_ext(rec, pkt); } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int NETBIOSPlugin::add_netbios_ext(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction NETBIOSPlugin::add_netbios_ext(Flow& rec, const Packet& pkt) { RecordExtNETBIOS* ext = new RecordExtNETBIOS(m_pluginID); if (parse_nbns(ext, pkt)) { total_netbios_packets++; rec.add_extension(ext); - } else { - delete ext; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - - return 0; + delete ext; + return ProcessPlugin::FlowAction::GET_NO_DATA; } bool NETBIOSPlugin::parse_nbns(RecordExtNETBIOS* rec, const Packet& pkt) diff --git a/src/plugins/process/netbios/src/netbios.hpp b/src/plugins/process/netbios/src/netbios.hpp index fd6b8adb9..760b3db61 100644 --- a/src/plugins/process/netbios/src/netbios.hpp +++ b/src/plugins/process/netbios/src/netbios.hpp @@ -102,14 +102,14 @@ class NETBIOSPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtNETBIOS(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); void finish(bool print_stats); private: int total_netbios_packets; - int add_netbios_ext(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction add_netbios_ext(Flow& rec, const Packet& pkt); bool parse_nbns(RecordExtNETBIOS* rec, const Packet& pkt); int get_query_count(const char* payload, uint16_t payload_length); bool store_first_query(const char* payload, RecordExtNETBIOS* rec); From 662406e67d6a4c78f5341e5e7c1083572d2deb7b Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:55:32 +0200 Subject: [PATCH 31/56] CTT - Update SSADetector process plugin --- src/plugins/process/ssaDetector/src/ssadetector.cpp | 6 +++--- src/plugins/process/ssaDetector/src/ssadetector.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/process/ssaDetector/src/ssadetector.cpp b/src/plugins/process/ssaDetector/src/ssadetector.cpp index 048ddc4ed..33087205e 100644 --- a/src/plugins/process/ssaDetector/src/ssadetector.cpp +++ b/src/plugins/process/ssaDetector/src/ssadetector.cpp @@ -117,11 +117,11 @@ void SSADetectorPlugin::update_record(RecordExtSSADetector* record, const Packet transition_from_init(record, len, ts, dir); } -int SSADetectorPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction SSADetectorPlugin::post_update(Flow& rec, const Packet& pkt) { RecordExtSSADetector* record = nullptr; if (rec.src_packets + rec.dst_packets < MIN_PKT_IN_FLOW) { - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } record = (RecordExtSSADetector*) rec.get_extension(m_pluginID); @@ -131,7 +131,7 @@ int SSADetectorPlugin::post_update(Flow& rec, const Packet& pkt) } update_record(record, pkt); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } double classes_ratio(uint8_t* syn_pkts, uint8_t size) diff --git a/src/plugins/process/ssaDetector/src/ssadetector.hpp b/src/plugins/process/ssaDetector/src/ssadetector.hpp index 3ab8fab73..965999ac9 100644 --- a/src/plugins/process/ssaDetector/src/ssadetector.hpp +++ b/src/plugins/process/ssaDetector/src/ssadetector.hpp @@ -155,7 +155,7 @@ class SSADetectorPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtSSADetector(m_pluginID); } ProcessPlugin* copy(); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); void pre_export(Flow& rec); void update_record(RecordExtSSADetector* record, const Packet& pkt); static inline void transition_from_init( From 730ab7c09f3d03589d3a1ddc63b5b15a65f30346 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:55:48 +0200 Subject: [PATCH 32/56] CTT - Update SMTP process plugin --- src/plugins/process/smtp/src/smtp.cpp | 12 +++++++----- src/plugins/process/smtp/src/smtp.hpp | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/plugins/process/smtp/src/smtp.cpp b/src/plugins/process/smtp/src/smtp.cpp index f8a2cf1f3..01c6c0779 100644 --- a/src/plugins/process/smtp/src/smtp.cpp +++ b/src/plugins/process/smtp/src/smtp.cpp @@ -62,27 +62,29 @@ ProcessPlugin* SMTPPlugin::copy() return new SMTPPlugin(*this); } -int SMTPPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction SMTPPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.src_port == 25 || pkt.dst_port == 25) { create_smtp_record(rec, pkt); + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int SMTPPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction SMTPPlugin::pre_update(Flow& rec, Packet& pkt) { if (pkt.src_port == 25 || pkt.dst_port == 25) { RecordExt* ext = rec.get_extension(m_pluginID); if (ext == nullptr) { create_smtp_record(rec, pkt); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } update_smtp_record(static_cast(ext), pkt); + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } char* strncasestr(const char* str, size_t n, const char* substr) diff --git a/src/plugins/process/smtp/src/smtp.hpp b/src/plugins/process/smtp/src/smtp.hpp index 21de8299c..e166d7d34 100644 --- a/src/plugins/process/smtp/src/smtp.hpp +++ b/src/plugins/process/smtp/src/smtp.hpp @@ -213,8 +213,8 @@ class SMTPPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtSMTP(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt); void finish(bool print_stats); bool smtp_keyword(const char* data); From 4e57642068b11d20d3f1c977cd170fb2c7d03d75 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:56:12 +0200 Subject: [PATCH 33/56] CTT - Update VLAN process plugin --- src/plugins/process/vlan/src/vlan.cpp | 4 ++-- src/plugins/process/vlan/src/vlan.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/process/vlan/src/vlan.cpp b/src/plugins/process/vlan/src/vlan.cpp index 6b8dedd50..71e843c00 100644 --- a/src/plugins/process/vlan/src/vlan.cpp +++ b/src/plugins/process/vlan/src/vlan.cpp @@ -42,12 +42,12 @@ ProcessPlugin* VLANPlugin::copy() return new VLANPlugin(*this); } -int VLANPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction VLANPlugin::post_create(Flow& rec, const Packet& pkt) { auto ext = new RecordExtVLAN(m_pluginID); ext->vlan_id = pkt.vlan_id; rec.add_extension(ext); - return 0; + return ProcessPlugin::FlowAction::GET_ONLY_METADATA; } static const PluginRegistrar vlanRegistrar(vlanPluginManifest); diff --git a/src/plugins/process/vlan/src/vlan.hpp b/src/plugins/process/vlan/src/vlan.hpp index f12abfd14..4c00f1d5f 100644 --- a/src/plugins/process/vlan/src/vlan.hpp +++ b/src/plugins/process/vlan/src/vlan.hpp @@ -92,7 +92,7 @@ class VLANPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtVLAN(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); }; } // namespace ipxp From 7a51cae665ccf603e07dea148f426072f6183a50 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:56:33 +0200 Subject: [PATCH 34/56] CTT - Update SIP process plugin --- src/plugins/process/sip/src/sip.cpp | 12 ++++++------ src/plugins/process/sip/src/sip.hpp | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/plugins/process/sip/src/sip.cpp b/src/plugins/process/sip/src/sip.cpp index 3904fe942..178e01525 100644 --- a/src/plugins/process/sip/src/sip.cpp +++ b/src/plugins/process/sip/src/sip.cpp @@ -63,14 +63,14 @@ ProcessPlugin* SIPPlugin::copy() return new SIPPlugin(*this); } -int SIPPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction SIPPlugin::post_create(Flow& rec, const Packet& pkt) { (void) rec; uint16_t msg_type; msg_type = parse_msg_type(pkt); if (msg_type == SIP_MSG_TYPE_INVALID) { - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } RecordExtSIP* sip_data = new RecordExtSIP(m_pluginID); @@ -78,20 +78,20 @@ int SIPPlugin::post_create(Flow& rec, const Packet& pkt) rec.add_extension(sip_data); parser_process_sip(pkt, sip_data); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } -int SIPPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction SIPPlugin::pre_update(Flow& rec, Packet& pkt) { (void) rec; uint16_t msg_type; msg_type = parse_msg_type(pkt); if (msg_type != SIP_MSG_TYPE_INVALID) { - return FLOW_FLUSH_WITH_REINSERT; + return ProcessPlugin::FlowAction::FLUSH_WITH_REINSERT; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } void SIPPlugin::finish(bool print_stats) diff --git a/src/plugins/process/sip/src/sip.hpp b/src/plugins/process/sip/src/sip.hpp index f4211f842..680d08dd8 100644 --- a/src/plugins/process/sip/src/sip.hpp +++ b/src/plugins/process/sip/src/sip.hpp @@ -471,8 +471,8 @@ class SIPPlugin : public ProcessPlugin { std::string get_name() const { return "sip"; } RecordExt* get_ext() const { return new RecordExtSIP(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt); void finish(bool print_stats); private: From 277c108ee1b359be2a14aedbb9561d6b16dec989 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:56:51 +0200 Subject: [PATCH 35/56] CTT - Update RTSP process plugin --- src/plugins/process/rtsp/src/rtsp.cpp | 16 +++++++++------- src/plugins/process/rtsp/src/rtsp.hpp | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/plugins/process/rtsp/src/rtsp.cpp b/src/plugins/process/rtsp/src/rtsp.cpp index abe4bcd9c..2db1236c9 100644 --- a/src/plugins/process/rtsp/src/rtsp.cpp +++ b/src/plugins/process/rtsp/src/rtsp.cpp @@ -92,19 +92,21 @@ ProcessPlugin* RTSPPlugin::copy() return new RTSPPlugin(*this); } -int RTSPPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction RTSPPlugin::post_create(Flow& rec, const Packet& pkt) { const char* payload = reinterpret_cast(pkt.payload); if (is_request(payload, pkt.payload_len)) { add_ext_rtsp_request(payload, pkt.payload_len, rec); + return ProcessPlugin::FlowAction::GET_ALL_DATA; } else if (is_response(payload, pkt.payload_len)) { add_ext_rtsp_response(payload, pkt.payload_len, rec); + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int RTSPPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction RTSPPlugin::pre_update(Flow& rec, Packet& pkt) { RecordExt* ext = nullptr; const char* payload = reinterpret_cast(pkt.payload); @@ -112,25 +114,25 @@ int RTSPPlugin::pre_update(Flow& rec, Packet& pkt) ext = rec.get_extension(m_pluginID); if (ext == nullptr) { /* Check if header is present in flow. */ add_ext_rtsp_request(payload, pkt.payload_len, rec); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } parse_rtsp_request(payload, pkt.payload_len, static_cast(ext)); if (flow_flush) { flow_flush = false; - return FLOW_FLUSH_WITH_REINSERT; + return ProcessPlugin::FlowAction::FLUSH_WITH_REINSERT; } } else if (is_response(payload, pkt.payload_len)) { ext = rec.get_extension(m_pluginID); if (ext == nullptr) { /* Check if header is present in flow. */ add_ext_rtsp_response(payload, pkt.payload_len, rec); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } parse_rtsp_response(payload, pkt.payload_len, static_cast(ext)); if (flow_flush) { flow_flush = false; - return FLOW_FLUSH_WITH_REINSERT; + return ProcessPlugin::FlowAction::FLUSH_WITH_REINSERT; } } diff --git a/src/plugins/process/rtsp/src/rtsp.hpp b/src/plugins/process/rtsp/src/rtsp.hpp index 6b05aeacd..bdb7127ce 100644 --- a/src/plugins/process/rtsp/src/rtsp.hpp +++ b/src/plugins/process/rtsp/src/rtsp.hpp @@ -177,8 +177,8 @@ class RTSPPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtRTSP(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt); void finish(bool print_stats); private: From 573f08c061e28296b07d135dded849e094354f82 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:57:08 +0200 Subject: [PATCH 36/56] CTT - Update WG process plugin --- src/plugins/process/wg/src/wg.cpp | 18 ++++++++---------- src/plugins/process/wg/src/wg.hpp | 5 ++--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/plugins/process/wg/src/wg.cpp b/src/plugins/process/wg/src/wg.cpp index 7c18001bf..082de2f98 100644 --- a/src/plugins/process/wg/src/wg.cpp +++ b/src/plugins/process/wg/src/wg.cpp @@ -66,7 +66,7 @@ ProcessPlugin* WGPlugin::copy() return new WGPlugin(*this); } -int WGPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction WGPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.ip_proto == IPPROTO_UDP) { add_ext_wg( @@ -74,12 +74,13 @@ int WGPlugin::post_create(Flow& rec, const Packet& pkt) pkt.payload_len, pkt.source_pkt, rec); + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int WGPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction WGPlugin::pre_update(Flow& rec, Packet& pkt) { RecordExtWG* vpn_data = (RecordExtWG*) rec.get_extension(m_pluginID); if (vpn_data != nullptr && vpn_data->possible_wg) { @@ -91,20 +92,17 @@ int WGPlugin::pre_update(Flow& rec, Packet& pkt) // In case of new flow, flush if (flow_flush) { flow_flush = false; - return FLOW_FLUSH_WITH_REINSERT; + return ProcessPlugin::FlowAction::FLUSH_WITH_REINSERT; } // In other cases, when WG was not detected if (!res) { vpn_data->possible_wg = 0; + } else { + return ProcessPlugin::FlowAction::GET_ALL_DATA; } } - return 0; -} - -void WGPlugin::pre_export(Flow& rec) -{ - (void) rec; + return ProcessPlugin::FlowAction::GET_NO_DATA; } void WGPlugin::finish(bool print_stats) diff --git a/src/plugins/process/wg/src/wg.hpp b/src/plugins/process/wg/src/wg.hpp index 223c910a9..568178e1a 100644 --- a/src/plugins/process/wg/src/wg.hpp +++ b/src/plugins/process/wg/src/wg.hpp @@ -130,9 +130,8 @@ class WGPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtWG(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); - void pre_export(Flow& rec); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt); void finish(bool print_stats); private: From e8974850e2bb0424e966ee72cc8ba929f71c6e34 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:57:34 +0200 Subject: [PATCH 37/56] CTT - Update QUIC process plugin --- src/plugins/process/quic/src/quic.cpp | 37 +++++++++------------------ src/plugins/process/quic/src/quic.hpp | 10 +++----- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/plugins/process/quic/src/quic.cpp b/src/plugins/process/quic/src/quic.cpp index 83ee02084..9e0928f2a 100644 --- a/src/plugins/process/quic/src/quic.cpp +++ b/src/plugins/process/quic/src/quic.cpp @@ -347,7 +347,7 @@ void QUICPlugin::set_packet_type(RecordExtQUIC* quic_data, Flow& rec, uint8_t pa } } -int QUICPlugin::process_quic( +ProcessPlugin::FlowAction QUICPlugin::process_quic( RecordExtQUIC* quic_data, Flow& rec, const Packet& pkt, @@ -399,7 +399,7 @@ int QUICPlugin::process_quic( if (version == QUICParser::QUIC_VERSION::version_negotiation) { set_cid_fields(quic_data, rec, &process_quic, toServer, new_quic_flow, pkt); - return FLOW_FLUSH; + return ProcessPlugin::FlowAction::FLUSH; } // export if parsed CH @@ -490,40 +490,27 @@ int QUICPlugin::process_quic( break; } - return QUIC_DETECTED; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } else { // Even if no QUIC detected store packets, which will only include the QUIC bit. uint8_t packets = 0; process_quic.quic_get_packets(packets); set_packet_type(quic_data, rec, packets); } - return QUIC_NOT_DETECTED; + return ProcessPlugin::FlowAction::GET_NO_DATA; } // QUICPlugin::process_quic -int QUICPlugin::pre_create(Packet& pkt) -{ - (void) pkt; - return 0; -} - -int QUICPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction QUICPlugin::post_create(Flow& rec, const Packet& pkt) { return add_quic(rec, pkt); } -int QUICPlugin::pre_update(Flow& rec, Packet& pkt) -{ - (void) rec; - (void) pkt; - return 0; -} - -int QUICPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction QUICPlugin::post_update(Flow& rec, const Packet& pkt) { return add_quic(rec, pkt); } -int QUICPlugin::add_quic(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction QUICPlugin::add_quic(Flow& rec, const Packet& pkt) { RecordExtQUIC* q_ptr = (RecordExtQUIC*) rec.get_extension(m_pluginID); bool new_qptr = false; @@ -532,18 +519,18 @@ int QUICPlugin::add_quic(Flow& rec, const Packet& pkt) q_ptr = new RecordExtQUIC(m_pluginID); } - int ret = process_quic(q_ptr, rec, pkt, new_qptr); + ProcessPlugin::FlowAction ret = process_quic(q_ptr, rec, pkt, new_qptr); // Test if QUIC extension is not set - if (new_qptr && ((ret == QUIC_DETECTED) || (ret == FLOW_FLUSH))) { + if (new_qptr && ((ret == ProcessPlugin::FlowAction::GET_ALL_DATA) || (ret == ProcessPlugin::FlowAction::FLUSH))) { rec.add_extension(q_ptr); } - if (new_qptr && (ret == QUIC_NOT_DETECTED)) { + if (new_qptr && (ret == ProcessPlugin::FlowAction::GET_NO_DATA)) { // If still no record delete q_ptr delete q_ptr; } // Correct if QUIC has already been detected - if (!new_qptr && (ret == QUIC_NOT_DETECTED)) { - return QUIC_DETECTED; + if (!new_qptr && (ret == ProcessPlugin::FlowAction::GET_NO_DATA)) { + return ProcessPlugin::FlowAction::GET_ALL_DATA; } return ret; } diff --git a/src/plugins/process/quic/src/quic.hpp b/src/plugins/process/quic/src/quic.hpp index 620569159..c6a3695ac 100644 --- a/src/plugins/process/quic/src/quic.hpp +++ b/src/plugins/process/quic/src/quic.hpp @@ -374,16 +374,14 @@ class QUICPlugin : public ProcessPlugin { ProcessPlugin* copy(); - int pre_create(Packet& pkt); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); - int add_quic(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction add_quic(Flow& rec, const Packet& pkt); void finish(bool print_stats); void set_packet_type(RecordExtQUIC* quic_data, Flow& rec, uint8_t packets); private: - int process_quic(RecordExtQUIC*, Flow& rec, const Packet&, bool new_quic_flow); + ProcessPlugin::FlowAction process_quic(RecordExtQUIC*, Flow& rec, const Packet&, bool new_quic_flow); void set_stored_cid_fields(RecordExtQUIC* quic_data, bool new_quic_flow); void set_cid_fields( RecordExtQUIC* quic_data, From 5f17d88e24cb056649679bfe409e64b475c8116f Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:57:51 +0200 Subject: [PATCH 38/56] CTT - Update PHISTS process plugin --- src/plugins/process/phists/src/phists.cpp | 8 ++++---- src/plugins/process/phists/src/phists.hpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/process/phists/src/phists.cpp b/src/plugins/process/phists/src/phists.cpp index be2312d08..faa52b0e1 100644 --- a/src/plugins/process/phists/src/phists.cpp +++ b/src/plugins/process/phists/src/phists.cpp @@ -148,22 +148,22 @@ void PHISTSPlugin::pre_export(Flow& rec) } } -int PHISTSPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction PHISTSPlugin::post_create(Flow& rec, const Packet& pkt) { RecordExtPHISTS* phists_data = new RecordExtPHISTS(m_pluginID); rec.add_extension(phists_data); update_record(phists_data, pkt); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } -int PHISTSPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction PHISTSPlugin::post_update(Flow& rec, const Packet& pkt) { RecordExtPHISTS* phists_data = (RecordExtPHISTS*) rec.get_extension(m_pluginID); update_record(phists_data, pkt); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } static const PluginRegistrar diff --git a/src/plugins/process/phists/src/phists.hpp b/src/plugins/process/phists/src/phists.hpp index a44cf40ea..803ed6a08 100644 --- a/src/plugins/process/phists/src/phists.hpp +++ b/src/plugins/process/phists/src/phists.hpp @@ -191,8 +191,8 @@ class PHISTSPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtPHISTS(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); private: bool use_zeros; From 6f2a61d28b0b8eb18532f59b6de5abb9a172067b Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:58:25 +0200 Subject: [PATCH 39/56] CTT - Update NETTISA process plugin --- src/plugins/process/nettisa/src/nettisa.cpp | 8 ++++---- src/plugins/process/nettisa/src/nettisa.hpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/process/nettisa/src/nettisa.cpp b/src/plugins/process/nettisa/src/nettisa.cpp index 8a97273dc..9f57347e6 100644 --- a/src/plugins/process/nettisa/src/nettisa.cpp +++ b/src/plugins/process/nettisa/src/nettisa.cpp @@ -85,7 +85,7 @@ void NETTISAPlugin::update_record( } } -int NETTISAPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction NETTISAPlugin::post_create(Flow& rec, const Packet& pkt) { RecordExtNETTISA* nettisa_data = new RecordExtNETTISA(m_pluginID); rec.add_extension(nettisa_data); @@ -93,15 +93,15 @@ int NETTISAPlugin::post_create(Flow& rec, const Packet& pkt) nettisa_data->prev_time = timeval2usec(pkt.ts); update_record(nettisa_data, pkt, rec); - return 0; + return ProcessPlugin::FlowAction::GET_ONLY_METADATA; } -int NETTISAPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction NETTISAPlugin::post_update(Flow& rec, const Packet& pkt) { RecordExtNETTISA* nettisa_data = (RecordExtNETTISA*) rec.get_extension(m_pluginID); update_record(nettisa_data, pkt, rec); - return 0; + return ProcessPlugin::FlowAction::GET_ONLY_METADATA; } void NETTISAPlugin::pre_export(Flow& rec) diff --git a/src/plugins/process/nettisa/src/nettisa.hpp b/src/plugins/process/nettisa/src/nettisa.hpp index ae014f4d0..f4673b828 100644 --- a/src/plugins/process/nettisa/src/nettisa.hpp +++ b/src/plugins/process/nettisa/src/nettisa.hpp @@ -177,8 +177,8 @@ class NETTISAPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtNETTISA(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); void update_record(RecordExtNETTISA* nettisa_data, const Packet& pkt, const Flow& rec); void pre_export(Flow& rec); }; From 64443129a9ed91d08ee54caf945434eed7b683e2 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:58:40 +0200 Subject: [PATCH 40/56] CTT - Update PassiveDNS process plugin --- src/plugins/process/passiveDns/src/passivedns.cpp | 15 +++++++++------ src/plugins/process/passiveDns/src/passivedns.hpp | 6 +++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/plugins/process/passiveDns/src/passivedns.cpp b/src/plugins/process/passiveDns/src/passivedns.cpp index 3c57163f7..0992c6fec 100644 --- a/src/plugins/process/passiveDns/src/passivedns.cpp +++ b/src/plugins/process/passiveDns/src/passivedns.cpp @@ -102,7 +102,7 @@ ProcessPlugin* PassiveDNSPlugin::copy() return new PassiveDNSPlugin(*this); } -int PassiveDNSPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction PassiveDNSPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.src_port == 53) { return add_ext_dns( @@ -111,11 +111,14 @@ int PassiveDNSPlugin::post_create(Flow& rec, const Packet& pkt) pkt.ip_proto == IPPROTO_TCP, rec); } + if (pkt.dst_port == 53) { + return ProcessPlugin::FlowAction::GET_ALL_DATA; + } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int PassiveDNSPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction PassiveDNSPlugin::post_update(Flow& rec, const Packet& pkt) { if (pkt.src_port == 53) { return add_ext_dns( @@ -125,7 +128,7 @@ int PassiveDNSPlugin::post_update(Flow& rec, const Packet& pkt) rec); } - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } void PassiveDNSPlugin::finish(bool print_stats) @@ -507,14 +510,14 @@ bool PassiveDNSPlugin::process_ptr_record(std::string name, RecordExtPassiveDNS* * \param [in] tcp DNS over tcp. * \param [out] rec Destination Flow. */ -int PassiveDNSPlugin::add_ext_dns(const char* data, unsigned int payload_len, bool tcp, Flow& rec) +ProcessPlugin::FlowAction PassiveDNSPlugin::add_ext_dns(const char* data, unsigned int payload_len, bool tcp, Flow& rec) { RecordExt* tmp = parse_dns(data, payload_len, tcp); if (tmp != nullptr) { rec.add_extension(tmp); } - return FLOW_FLUSH; + return ProcessPlugin::FlowAction::FLUSH; } static const PluginRegistrar diff --git a/src/plugins/process/passiveDns/src/passivedns.hpp b/src/plugins/process/passiveDns/src/passivedns.hpp index 8337e4cb0..2b200ec6f 100644 --- a/src/plugins/process/passiveDns/src/passivedns.hpp +++ b/src/plugins/process/passiveDns/src/passivedns.hpp @@ -138,8 +138,8 @@ class PassiveDNSPlugin : public ProcessPlugin { std::string get_name() const { return "passivedns"; } RecordExt* get_ext() const { return new RecordExtPassiveDNS(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); void finish(bool print_stats); private: @@ -152,7 +152,7 @@ class PassiveDNSPlugin : public ProcessPlugin { uint32_t data_len; /**< Length of packet payload. */ RecordExtPassiveDNS* parse_dns(const char* data, unsigned int payload_len, bool tcp); - int add_ext_dns(const char* data, unsigned int payload_len, bool tcp, Flow& rec); + ProcessPlugin::FlowAction add_ext_dns(const char* data, unsigned int payload_len, bool tcp, Flow& rec); std::string get_name(const char* data) const; size_t get_name_length(const char* data) const; From 402b760d02170c9360ef9c0b621a15d13c909905 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:58:57 +0200 Subject: [PATCH 41/56] CTT - Update OSQuery process plugin --- src/plugins/process/osquery/src/osquery.cpp | 4 ++-- src/plugins/process/osquery/src/osquery.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/process/osquery/src/osquery.cpp b/src/plugins/process/osquery/src/osquery.cpp index cc90fb3ff..8f7153220 100644 --- a/src/plugins/process/osquery/src/osquery.cpp +++ b/src/plugins/process/osquery/src/osquery.cpp @@ -79,7 +79,7 @@ ProcessPlugin* OSQUERYPlugin::copy() return new OSQUERYPlugin(*this); } -int OSQUERYPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction OSQUERYPlugin::post_create(Flow& rec, const Packet& pkt) { (void) pkt; ConvertedFlowData flowDataIPv4(rec.src_ip.v4, rec.dst_ip.v4, rec.src_port, rec.dst_port); @@ -91,7 +91,7 @@ int OSQUERYPlugin::post_create(Flow& rec, const Packet& pkt) numberOfSuccessfullyRequests++; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } void OSQUERYPlugin::finish(bool print_stats) diff --git a/src/plugins/process/osquery/src/osquery.hpp b/src/plugins/process/osquery/src/osquery.hpp index 8fb2e9b05..d7a0a9baf 100644 --- a/src/plugins/process/osquery/src/osquery.hpp +++ b/src/plugins/process/osquery/src/osquery.hpp @@ -526,7 +526,7 @@ class OSQUERYPlugin : public ProcessPlugin { std::string get_name() const { return "osquery"; } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); void finish(bool print_stats); private: From 674b4ee237a38c1d1aaaeba3327d7600a7cde5f8 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:59:34 +0200 Subject: [PATCH 42/56] CTT - Update TLS process plugin --- src/plugins/process/tls/src/tls.cpp | 24 ++++++++++++++---------- src/plugins/process/tls/src/tls.hpp | 6 +++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/plugins/process/tls/src/tls.cpp b/src/plugins/process/tls/src/tls.cpp index 23c2f7b0f..f9b08edde 100644 --- a/src/plugins/process/tls/src/tls.cpp +++ b/src/plugins/process/tls/src/tls.cpp @@ -98,26 +98,26 @@ ProcessPlugin* TLSPlugin::copy() return new TLSPlugin(*this); } -int TLSPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction TLSPlugin::post_create(Flow& rec, const Packet& pkt) { - add_tls_record(rec, pkt); - return 0; + return add_tls_record(rec, pkt); } -int TLSPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction TLSPlugin::pre_update(Flow& rec, Packet& pkt) { auto* ext = static_cast(rec.get_extension(m_pluginID)); if (ext != nullptr) { if (!ext->server_hello_parsed) { // Add ALPN from server packet - parse_tls(pkt.payload, pkt.payload_len, ext, rec.ip_proto); + if (parse_tls(pkt.payload, pkt.payload_len, ext, rec.ip_proto)) { + return ProcessPlugin::FlowAction::GET_NO_DATA; + } } - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - add_tls_record(rec, pkt); - - return 0; + + return add_tls_record(rec, pkt); } static std::string concatenate_vector_to_string(const std::vector& vector) @@ -390,6 +390,8 @@ bool TLSPlugin::parse_tls( rec->extensions_buffer_size = count_to_copy; } rec->version = parser.get_handshake()->version.version; + //rec->version = parser.get_supported_versions().empty() ? parser.get_handshake()->version.version + // : parser.get_supported_versions()[0]; parser.save_server_names(rec->sni, sizeof(rec->sni)); md5_get_bin(get_ja3_string(parser), rec->ja3); auto ja4 = get_ja4_string(parser, ip_proto); @@ -407,7 +409,7 @@ bool TLSPlugin::parse_tls( return false; } -void TLSPlugin::add_tls_record(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction TLSPlugin::add_tls_record(Flow& rec, const Packet& pkt) { if (ext_ptr == nullptr) { ext_ptr = new RecordExtTLS(m_pluginID); @@ -421,6 +423,8 @@ void TLSPlugin::add_tls_record(Flow& rec, const Packet& pkt) rec.add_extension(ext_ptr); ext_ptr = nullptr; } + + return ProcessPlugin::FlowAction::GET_ALL_DATA; } void TLSPlugin::finish(bool print_stats) diff --git a/src/plugins/process/tls/src/tls.hpp b/src/plugins/process/tls/src/tls.hpp index 9a844330e..75b706d24 100644 --- a/src/plugins/process/tls/src/tls.hpp +++ b/src/plugins/process/tls/src/tls.hpp @@ -200,12 +200,12 @@ class TLSPlugin : public ProcessPlugin { ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt) override; - int pre_update(Flow& rec, Packet& pkt) override; + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt) override; + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt) override; void finish(bool print_stats); private: - void add_tls_record(Flow&, const Packet&); + ProcessPlugin::FlowAction add_tls_record(Flow&, const Packet&); bool parse_tls(const uint8_t* data, uint16_t payload_len, RecordExtTLS* rec, uint8_t ip_proto); RecordExtTLS* ext_ptr {nullptr}; From 86b432763577a640a36442535985abcc87e0ae16 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 10:59:49 +0200 Subject: [PATCH 43/56] CTT - Update PSTATS process plugin --- src/plugins/process/pstats/src/pstats.cpp | 12 ++++++++---- src/plugins/process/pstats/src/pstats.hpp | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/plugins/process/pstats/src/pstats.cpp b/src/plugins/process/pstats/src/pstats.cpp index 6000c81b0..7be421cdb 100644 --- a/src/plugins/process/pstats/src/pstats.cpp +++ b/src/plugins/process/pstats/src/pstats.cpp @@ -142,13 +142,15 @@ void PSTATSPlugin::update_record(RecordExtPSTATS* pstats_data, const Packet& pkt } } -int PSTATSPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction PSTATSPlugin::post_create(Flow& rec, const Packet& pkt) { RecordExtPSTATS* pstats_data = new RecordExtPSTATS(m_pluginID); rec.add_extension(pstats_data); update_record(pstats_data, pkt); - return 0; + return pstats_data->pkt_count < PSTATS_MAXELEMCOUNT + ? ProcessPlugin::FlowAction::GET_ALL_DATA + : ProcessPlugin::FlowAction::GET_NO_DATA; } void PSTATSPlugin::pre_export(Flow& rec) @@ -161,11 +163,13 @@ void PSTATSPlugin::pre_export(Flow& rec) } } -int PSTATSPlugin::post_update(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction PSTATSPlugin::post_update(Flow& rec, const Packet& pkt) { RecordExtPSTATS* pstats_data = (RecordExtPSTATS*) rec.get_extension(m_pluginID); update_record(pstats_data, pkt); - return 0; + return pstats_data->pkt_count < PSTATS_MAXELEMCOUNT + ? ProcessPlugin::FlowAction::GET_ALL_DATA + : ProcessPlugin::FlowAction::GET_NO_DATA; } static const PluginRegistrar diff --git a/src/plugins/process/pstats/src/pstats.hpp b/src/plugins/process/pstats/src/pstats.hpp index 1ddad9477..8da506308 100644 --- a/src/plugins/process/pstats/src/pstats.hpp +++ b/src/plugins/process/pstats/src/pstats.hpp @@ -221,8 +221,8 @@ class PSTATSPlugin : public ProcessPlugin { std::string get_name() const { return "pstats"; } RecordExt* get_ext() const { return new RecordExtPSTATS(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int post_update(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_update(Flow& rec, const Packet& pkt); void update_record(RecordExtPSTATS* pstats_data, const Packet& pkt); void pre_export(Flow& rec); From 38688506d02eec0bfff6565c45c312d30e464997 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:00:16 +0200 Subject: [PATCH 44/56] CTT - Update OVPN process plugin --- src/plugins/process/ovpn/src/ovpn.cpp | 14 ++++++++++---- src/plugins/process/ovpn/src/ovpn.hpp | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/plugins/process/ovpn/src/ovpn.cpp b/src/plugins/process/ovpn/src/ovpn.cpp index 4b97188d9..ec4bdf17e 100644 --- a/src/plugins/process/ovpn/src/ovpn.cpp +++ b/src/plugins/process/ovpn/src/ovpn.cpp @@ -203,20 +203,26 @@ void OVPNPlugin::update_record(RecordExtOVPN* vpn_data, const Packet& pkt) return; } -int OVPNPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction OVPNPlugin::post_create(Flow& rec, const Packet& pkt) { RecordExtOVPN* vpn_data = new RecordExtOVPN(m_pluginID); rec.add_extension(vpn_data); update_record(vpn_data, pkt); - return 0; + return ProcessPlugin::FlowAction::GET_ALL_DATA; } -int OVPNPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction OVPNPlugin::pre_update(Flow& rec, Packet& pkt) { RecordExtOVPN* vpn_data = (RecordExtOVPN*) rec.get_extension(m_pluginID); update_record(vpn_data, pkt); - return 0; + if (rec.src_packets + rec.dst_packets <= min_pckt_export_treshold) { + return ProcessPlugin::FlowAction::GET_ALL_DATA; + } + + return vpn_data->status == status_null ? + ProcessPlugin::FlowAction::GET_NO_DATA : + ProcessPlugin::FlowAction::GET_ALL_DATA; } void OVPNPlugin::pre_export(Flow& rec) diff --git a/src/plugins/process/ovpn/src/ovpn.hpp b/src/plugins/process/ovpn/src/ovpn.hpp index 8d26abd88..0e4834a63 100644 --- a/src/plugins/process/ovpn/src/ovpn.hpp +++ b/src/plugins/process/ovpn/src/ovpn.hpp @@ -102,8 +102,8 @@ class OVPNPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtOVPN(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt); void update_record(RecordExtOVPN* vpn_data, const Packet& pkt); void pre_export(Flow& rec); From 5c34163a3b73601740da97b9277291edc8bee1e6 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:00:33 +0200 Subject: [PATCH 45/56] CTT - Update NTP process plugin --- src/plugins/process/ntp/src/ntp.cpp | 6 +++--- src/plugins/process/ntp/src/ntp.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/process/ntp/src/ntp.cpp b/src/plugins/process/ntp/src/ntp.cpp index c67603ea7..baf77676f 100644 --- a/src/plugins/process/ntp/src/ntp.cpp +++ b/src/plugins/process/ntp/src/ntp.cpp @@ -79,14 +79,14 @@ ProcessPlugin* NTPPlugin::copy() *\param [in] pkt Parsed packet. *\return 0 on success or FLOW_FLUSH option. */ -int NTPPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction NTPPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.dst_port == 123 || pkt.src_port == 123) { add_ext_ntp(rec, pkt); - return FLOW_FLUSH; + return ProcessPlugin::FlowAction::FLUSH; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } /** diff --git a/src/plugins/process/ntp/src/ntp.hpp b/src/plugins/process/ntp/src/ntp.hpp index 5c634020a..82454b6cb 100644 --- a/src/plugins/process/ntp/src/ntp.hpp +++ b/src/plugins/process/ntp/src/ntp.hpp @@ -216,7 +216,7 @@ class NTPPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtNTP(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); void finish(bool print_stats); private: From eba3e223fd08aa215c8135b863a595a05e2429e4 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:00:54 +0200 Subject: [PATCH 46/56] CTT - Update SSDP process plugin --- src/plugins/process/ssdp/src/ssdp.cpp | 10 ++++++---- src/plugins/process/ssdp/src/ssdp.hpp | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/plugins/process/ssdp/src/ssdp.cpp b/src/plugins/process/ssdp/src/ssdp.cpp index 515654e11..b9dadec79 100644 --- a/src/plugins/process/ssdp/src/ssdp.cpp +++ b/src/plugins/process/ssdp/src/ssdp.cpp @@ -71,7 +71,7 @@ ProcessPlugin* SSDPPlugin::copy() return new SSDPPlugin(*this); } -int SSDPPlugin::post_create(Flow& rec, const Packet& pkt) +ProcessPlugin::FlowAction SSDPPlugin::post_create(Flow& rec, const Packet& pkt) { if (pkt.dst_port == 1900) { record = new RecordExtSSDP(m_pluginID); @@ -79,16 +79,18 @@ int SSDPPlugin::post_create(Flow& rec, const Packet& pkt) record = nullptr; parse_ssdp_message(rec, pkt); + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } -int SSDPPlugin::pre_update(Flow& rec, Packet& pkt) +ProcessPlugin::FlowAction SSDPPlugin::pre_update(Flow& rec, Packet& pkt) { if (pkt.dst_port == 1900) { parse_ssdp_message(rec, pkt); + return ProcessPlugin::FlowAction::GET_ALL_DATA; } - return 0; + return ProcessPlugin::FlowAction::GET_NO_DATA; } void SSDPPlugin::finish(bool print_stats) diff --git a/src/plugins/process/ssdp/src/ssdp.hpp b/src/plugins/process/ssdp/src/ssdp.hpp index a340b67c3..c2ce19850 100644 --- a/src/plugins/process/ssdp/src/ssdp.hpp +++ b/src/plugins/process/ssdp/src/ssdp.hpp @@ -154,8 +154,8 @@ class SSDPPlugin : public ProcessPlugin { RecordExt* get_ext() const { return new RecordExtSSDP(m_pluginID); } ProcessPlugin* copy(); - int post_create(Flow& rec, const Packet& pkt); - int pre_update(Flow& rec, Packet& pkt); + ProcessPlugin::FlowAction post_create(Flow& rec, const Packet& pkt); + ProcessPlugin::FlowAction pre_update(Flow& rec, Packet& pkt); void finish(bool print_stats); /** From aa79562d322bbedbbf570fec1a0e5fe434751a1a Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:01:25 +0200 Subject: [PATCH 47/56] CTT - Introduce ctt configuration structure --- include/ipfixprobe/cttConfig.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 include/ipfixprobe/cttConfig.hpp diff --git a/include/ipfixprobe/cttConfig.hpp b/include/ipfixprobe/cttConfig.hpp new file mode 100644 index 000000000..24743505a --- /dev/null +++ b/include/ipfixprobe/cttConfig.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace ipxp { + +struct CttConfig { + std::string nfb_device; + unsigned dma_channel; +}; + +} // namespace ipxp \ No newline at end of file From 36282a0a8cb24bf3dac01479f428091e32beca45 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:04:10 +0200 Subject: [PATCH 48/56] CTT - Introduce NHTFlowCacheCtt class --- .../storage/cache-ctt/src/cacheCtt.cpp | 692 ++++++++++++++++++ .../storage/cache-ctt/src/cacheCtt.hpp | 124 ++++ 2 files changed, 816 insertions(+) create mode 100644 src/plugins/storage/cache-ctt/src/cacheCtt.cpp create mode 100644 src/plugins/storage/cache-ctt/src/cacheCtt.hpp diff --git a/src/plugins/storage/cache-ctt/src/cacheCtt.cpp b/src/plugins/storage/cache-ctt/src/cacheCtt.cpp new file mode 100644 index 000000000..040fe5f8a --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/cacheCtt.cpp @@ -0,0 +1,692 @@ +/** + * \file cache.cpp + * \brief NHTFlowCache extension with CTT support + * \author Zainullin Damir + * \date 2025 + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#include "cacheCtt.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "cacheOptParserCtt.hpp" +#include "../../cache/src/flowKeyFactory.hpp" + +namespace ipxp { + +static const PluginManifest cachePluginManifest = { + .name = "cache-ctt", + .description = "Storage plugin implemented as a hash table with ctt support.", + .pluginVersion = "1.0.0", + .apiVersion = "1.0.0", + .usage = + []() { + CacheOptParserCtt parser; + parser.usage(std::cout); + }, +}; + +OptionsParser * NHTFlowCacheCtt::get_parser() const +{ + return new CacheOptParserCtt(); +} + +std::string NHTFlowCacheCtt::get_name() const noexcept +{ + return "cache-ctt"; +} + +NHTFlowCacheCtt::NHTFlowCacheCtt(const std::string& params, ipx_ring_t* queue) +: NHTFlowCache(false) +{ + set_queue(queue); + init(params.c_str()); +} + +void NHTFlowCacheCtt::init(const char* params) +{ + std::unique_ptr parser(static_cast(get_parser())); + try { + parser->parse(params); + } catch (ParserError &e) { + throw PluginError(e.what()); + } + m_offload_mode = parser->m_offload_mode; + m_offload_threshold = parser->m_offload_threshold; + m_ctt_remove_queue_size = parser->m_ctt_remove_queue_size; + if (parser->m_split_biflow) { + throw PluginError("CTT does not support uniflows"); + } + NHTFlowCache::init(params); +} + +void NHTFlowCacheCtt::allocate_table() +{ + try { + const size_t size = m_cache_size + m_queue_size; + NHTFlowCache::m_flow_table = std::make_unique(size); + m_flows = std::make_unique(size + m_ctt_remove_queue_size); + m_ctt_remove_queue.set_buffer(m_flows.get() + size, m_ctt_remove_queue_size); + m_flow_table = reinterpret_cast(NHTFlowCache::m_flow_table.get()); + std::for_each(m_flow_table, m_flow_table + size, [index = 0, this](FlowRecordCtt*& flow) mutable { + flow = &m_flows[index++]; + }); + } catch (std::bad_alloc &e) { + throw PluginError("not enough memory for flow cache allocation"); + } + m_flow_table = reinterpret_cast(NHTFlowCache::m_flow_table.get()); +} + +void NHTFlowCacheCtt::export_flow(FlowRecord** flow, int reason) +{ + m_ctt_stats.real_processed_packets += (*flow)->m_flow.src_packets + (*flow)->m_flow.dst_packets; + NHTFlowCache::export_flow(flow, reason); +} + +void NHTFlowCacheCtt::finish() +{ + std::for_each(m_flow_table, m_flow_table + m_cache_size, [&](FlowRecordCtt*& flow_record) { + if (!flow_record->is_empty()) { + if (flow_record->is_in_ctt()) { + throw PluginError("Flow record is in CTT, but it was not exported before cache termination"); + m_ctt_stats.total_requests_count++; + m_ctt_controller->remove_record_without_notification(flow_record->m_flow.flow_hash_ctt); + } + plugins_pre_export(flow_record->m_flow); + export_flow(reinterpret_cast(&flow_record), FLOW_END_FORCED); + } + }); + print_report(); +} + +void NHTFlowCacheCtt::close() +{ + NHTFlowCache::close(); + m_flows.reset(); +} + +NHTFlowCacheCtt::~NHTFlowCacheCtt() +{ + close(); +} + +void NHTFlowCacheCtt::flush_ctt(const timeval now) noexcept +{ + constexpr size_t BLOCK_SIZE = 8; + for(size_t current_index = m_prefinish_index; current_index < m_prefinish_index + BLOCK_SIZE; current_index++) { + FlowRecordCtt* flow_record = m_flow_table[current_index]; + if (flow_record->is_empty() || !flow_record->is_in_ctt()) { + continue; + } + + m_ctt_flow_seen = true; + if (flow_record->is_waiting_ctt_response() && *flow_record->last_request_time + CTT_REQUEST_TIMEOUT > now) { + continue; + } else if (flow_record->is_waiting_ctt_response()) { + m_ctt_stats.lost_requests_count++; + m_ctt_stats.flush_ctt_lost_requests++; + } + m_ctt_flows_flushed++; + m_ctt_stats.total_requests_count++; + m_ctt_controller->export_record(flow_record->m_flow.flow_hash_ctt); + flow_record->last_request_time = now; + + }; + + m_prefinish_index = (m_prefinish_index + BLOCK_SIZE) % m_cache_size; + if (m_prefinish_index == 0) { + m_table_flushed = !m_ctt_flow_seen; + m_ctt_flow_seen = false; + } + + if (m_ctt_flows_flushed >= 16) { + m_ctt_flows_flushed = 0; + usleep(400); + } +} + +std::optional NHTFlowCacheCtt::get_offload_mode(size_t flow_index) noexcept +{ + if (!m_offload_mode.has_value() || !m_flow_table[flow_index]->can_be_offloaded) { + return std::nullopt; + } + + if (no_data_required(m_flow_table[flow_index]->m_flow) + && *m_offload_mode == feta::OffloadMode::DROP_PACKET_DROP_META + && m_flow_table[flow_index]->m_flow.src_packets + m_flow_table[flow_index]->m_flow.dst_packets >= m_offload_threshold + && !m_ctt_remove_queue.find(m_flow_table[flow_index]->m_flow.flow_hash)) { + m_ctt_stats.drop_packet_offloaded++; + return feta::OffloadMode::DROP_PACKET_DROP_META; + } + + if (only_metadata_required(m_flow_table[flow_index]->m_flow) + && *m_offload_mode == feta::OffloadMode::TRIM_PACKET_META + && m_flow_table[flow_index]->m_flow.src_packets + m_flow_table[flow_index]->m_flow.dst_packets >= m_offload_threshold + && !m_ctt_remove_queue.find(m_flow_table[flow_index]->m_flow.flow_hash)) { + m_ctt_stats.trim_packet_offloaded++; + return feta::OffloadMode::TRIM_PACKET_META; + } + + return std::nullopt; +} + +void NHTFlowCacheCtt::export_and_reuse_flow(size_t flow_index) noexcept +{ + if (!m_flow_table[flow_index]->is_in_ctt()) { + return NHTFlowCache::export_and_reuse_flow(flow_index); + } + auto& flow_record = *m_flow_table[flow_index]; + m_flow_table[flow_index] = m_ctt_remove_queue.add(m_flow_table[flow_index]); + *m_flow_table[flow_index] = flow_record; + m_flow_table[flow_index]->reuse(); +} + +void NHTFlowCacheCtt::create_record(const Packet& packet, size_t flow_index, size_t hash_value) noexcept +{ + m_cache_stats.flows_in_cache++; + m_flow_table[flow_index]->create(packet, hash_value); + const size_t post_create_return_flags = plugins_post_create(m_flow_table[flow_index]->m_flow, packet); + if (post_create_return_flags & ProcessPlugin::FlowAction::FLUSH) { + NHTFlowCache::export_flow(flow_index); + m_cache_stats.flushed++; + return; + } + // if metadata are valid, add flow hash ctt to the flow record + if (!packet.cttmeta.has_value()) { + return; + } + m_flow_table[flow_index]->m_flow.flow_hash_ctt = packet.cttmeta->flow_hash; + if (const std::optional offload_mode = get_offload_mode(flow_index); offload_mode.has_value()) { + offload_flow_to_ctt(flow_index, *offload_mode); + } +} + +void NHTFlowCacheCtt::offload_flow_to_ctt(size_t flow_index, feta::OffloadMode offload_mode) noexcept +{ + m_ctt_stats.total_requests_count++; + m_ctt_controller->create_record(m_flow_table[flow_index]->m_flow, m_dma_channel, offload_mode); + m_ctt_stats.flows_offloaded++; + m_flow_table[flow_index]->offload_mode = offload_mode; +} + +void NHTFlowCacheCtt::try_to_add_flow_to_ctt(size_t flow_index) noexcept +{ + if (m_flow_table[flow_index]->is_in_ctt() || m_flow_table[flow_index]->m_flow.flow_hash_ctt == 0) { + return; + } + if (const std::optional offload_mode = get_offload_mode(flow_index); offload_mode.has_value()) { + offload_flow_to_ctt(flow_index, *offload_mode); + } +} + +int NHTFlowCacheCtt::update_flow(Packet& packet, size_t flow_index) noexcept +{ + int res = NHTFlowCache::update_flow(packet, flow_index); + if (!m_flow_table[flow_index]->is_empty()) { + try_to_add_flow_to_ctt(flow_index); + } + return res; +} + +void NHTFlowCacheCtt::send_export_request_to_ctt(size_t ctt_flow_hash) noexcept +{ + m_ctt_stats.total_requests_count++; + m_ctt_controller->export_record(ctt_flow_hash); +} + +void NHTFlowCacheCtt::try_to_export(size_t flow_index, bool call_pre_export, int reason) noexcept +{ + if (m_flow_table[flow_index]->is_in_ctt()) { + m_flow_table[flow_index] = m_ctt_remove_queue.add(m_flow_table[flow_index]); + return; + } + if (call_pre_export) { + plugins_pre_export(m_flow_table[flow_index]->m_flow); + } + NHTFlowCache::export_flow(flow_index, reason); +} + +int convert_ctt_export_reason_to_ipfxiprobe(feta::ExportReason ctt_reason, feta::MuExportReason mu_reason) noexcept +{ + switch (ctt_reason) { + case feta::ExportReason::EXPORT_BY_SW: + return FLOW_END_FORCED; + case feta::ExportReason::FULL_CTT: + return FLOW_END_FORCED; + case feta::ExportReason::EXPORT_BY_MU: + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::COUNTER_OVERFLOW)) { + return FLOW_END_FORCED; + } + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::TCP_CONN_END)) { + return FLOW_END_EOF; + } + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::ACTIVE_TIMEOUT)) { + return FLOW_END_ACTIVE; + } + [[fallthrough]]; + default: + return FLOW_END_NO_RES; + } +} + +void update_ctt_export_stats(feta::ExportReason ctt_reason, feta::MuExportReason mu_reason, CttStats::ExportReasons& reasons) noexcept +{ + switch (ctt_reason) { + case feta::ExportReason::EXPORT_BY_SW: + reasons.by_request++; + break; + case feta::ExportReason::FULL_CTT: + reasons.ctt_full++; + break; + case feta::ExportReason::RESERVED: + reasons.reserved++; + break; + case feta::ExportReason::EXPORT_BY_MU: + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::COUNTER_OVERFLOW)) { + reasons.counter_overflow++; + } + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::TCP_CONN_END)) { + reasons.tcp_eof++; + } + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::ACTIVE_TIMEOUT)) { + reasons.active_timeout++; + } + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::FLOW_COLLISION)) { + reasons.hash_collision++; + } + break; + } +} + +void NHTFlowCacheCtt::update_advanced_ctt_export_stats(const feta::CttExportPkt& export_data) noexcept +{ + feta::ExportReason ctt_reason = export_data.fields.rsn; + feta::MuExportReason mu_reason = export_data.fields.ursn; + + switch (ctt_reason) { + case feta::ExportReason::EXPORT_BY_SW: + m_ctt_stats.advanced_export_reasons.by_request[export_data.fields.wb]++; + break; + case feta::ExportReason::FULL_CTT: + m_ctt_stats.advanced_export_reasons.ctt_full[export_data.fields.wb]++; + break; + case feta::ExportReason::RESERVED: + m_ctt_stats.advanced_export_reasons.reserved[export_data.fields.wb]++; + break; + case feta::ExportReason::EXPORT_BY_MU: + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::COUNTER_OVERFLOW)) { + m_ctt_stats.advanced_export_reasons.counter_overflow[export_data.fields.wb]++; + } + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::TCP_CONN_END)) { + m_ctt_stats.advanced_export_reasons.tcp_eof[export_data.fields.wb]++; + } + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::ACTIVE_TIMEOUT)) { + m_ctt_stats.advanced_export_reasons.active_timeout[export_data.fields.wb]++; + } + if (static_cast(mu_reason) & static_cast(feta::MuExportReason::FLOW_COLLISION)) { + m_ctt_stats.advanced_export_reasons.hash_collision[export_data.fields.wb]++; + } + break; + } +} + +static bool is_counter_overflow(feta::ExportReason ctt_reason, feta::MuExportReason mu_reason) noexcept +{ + return ctt_reason == feta::ExportReason::EXPORT_BY_MU && (static_cast(mu_reason) & static_cast(feta::MuExportReason::COUNTER_OVERFLOW)); +} + +static void update_packet_counters_from_external_export(Flow& flow, const feta::CttRecord& state) noexcept +{ + flow.src_packets += state.pkts; + flow.dst_packets += state.pkts_rev; + flow.src_bytes += state.bytes; + flow.dst_bytes += state.bytes_rev; + flow.time_last.tv_sec = state.ts_last.time_sec; + flow.time_last.tv_usec = state.ts_last.time_ns / 1000; + if (flow.ip_proto == 6) { + flow.src_tcp_flags |= state.tcp_flags; + flow.dst_tcp_flags |= state.tcp_flags_rev; + } +} + +static bool is_hash_collision(feta::ExportReason ctt_reason, feta::MuExportReason mu_reason) noexcept +{ + return ctt_reason == feta::ExportReason::EXPORT_BY_MU && (mu_reason & feta::MuExportReason::FLOW_COLLISION); +} + +static bool is_tcp_restart(feta::ExportReason ctt_reason, feta::MuExportReason mu_reason) noexcept +{ + return ctt_reason == feta::ExportReason::EXPORT_BY_MU && (mu_reason & feta::MuExportReason::TCP_CONN_END); +} + +static bool is_active_timeout(feta::ExportReason ctt_reason, feta::MuExportReason mu_reason) noexcept +{ + return ctt_reason == feta::ExportReason::EXPORT_BY_MU && (mu_reason & feta::MuExportReason::ACTIVE_TIMEOUT); +} + +static bool is_ctt_full(feta::ExportReason ctt_reason) noexcept +{ + return ctt_reason == feta::ExportReason::FULL_CTT; +} + +std::optional NHTFlowCacheCtt::find_flow_from_ctt_export(const feta::CttExportPkt& export_data) noexcept +{ + const IP ip_version = export_data.record.ip_ver == feta::IpVersion::IPV4 ? IP::v4 : IP::v6; + + const auto [key, swapped] = FlowKeyFactory::create_sorted_key(export_data.record.ip_src.data(), export_data.record.ip_dst.data(), + export_data.record.port_src, export_data.record.port_dst, export_data.record.l4_proto, ip_version, FlowKeyFactory::EMPTY_VLAN); + + const auto [search, source_to_destination] = find_flow_index(key, swapped); + const auto [row, flow_index, hash_value] = search; + + FlowRecordCtt** flow_record_ptr = m_ctt_remove_queue.find(hash_value); + + bool is_from_remove_queue = true; + if (flow_record_ptr == nullptr && flow_index.has_value()) { + flow_record_ptr = &m_flow_table[*flow_index]; + is_from_remove_queue = false; + } + if (flow_record_ptr == nullptr + || !(*flow_record_ptr)->is_in_ctt() + || !(*flow_record_ptr)->offload_mode.has_value()) { + return std::nullopt; + } + return CttFlowSearch{ + .flow_record = flow_record_ptr, + .is_from_remove_queue = is_from_remove_queue + }; +} + +void NHTFlowCacheCtt::process_external_export(const Packet& pkt) noexcept +{ + m_ctt_stats.export_packets++; + if (pkt.packet_len != sizeof(feta::CttExportPkt)) { + m_ctt_stats.export_packets_parsing_failed++; + return; + } + feta::CttExportPkt export_data = feta::CttExportPkt::deserialize(reinterpret_cast(const_cast(pkt.packet))); + + m_ctt_stats.wb_before_pv1[export_data.fields.wb]++; + update_ctt_export_stats(export_data.fields.rsn, export_data.fields.ursn, m_ctt_stats.export_reasons_before_pv1); + + if (export_data.fields.pv != 1) { + m_ctt_stats.pv_zero++; + return; // Drop invalid packet + } + + m_ctt_stats.wb_after_pv1[export_data.fields.wb]++; + update_ctt_export_stats(export_data.fields.rsn, export_data.fields.ursn, m_ctt_stats.export_reasons_after_pv1); + update_advanced_ctt_export_stats(export_data); + + std::optional flow_search = find_flow_from_ctt_export(export_data); + if (!flow_search.has_value()) { + m_ctt_stats.export_packets_for_missing_flow++; + return; + } + + auto [flow_record_ptr, is_from_remove_queue] = *flow_search; + FlowRecordCtt*& flow_record = *flow_record_ptr; + + // Two flows with different ipfixprobe keys but same CTT flow hash tried to create flow at the same time + if (export_data.fields.wb && export_data.fields.rsn == feta::ExportReason::EXPORT_BY_SW) { + flow_record->can_be_offloaded = false; + flow_record->offload_mode.reset(); + m_ctt_stats.flows_removed++; + if (is_from_remove_queue) { + NHTFlowCache::export_flow(reinterpret_cast(&flow_record)); + } + return; + } + + if (!export_data.fields.wb && flow_record->offload_mode == feta::OffloadMode::TRIM_PACKET_META) { + flow_record->last_request_time.reset(); + } + + if (flow_record->offload_mode == feta::OffloadMode::DROP_PACKET_DROP_META) { + flow_record->last_request_time.reset(); + update_packet_counters_from_external_export(flow_record->m_flow, export_data.record); + } + + if (!export_data.fields.wb && flow_record->offload_mode == feta::OffloadMode::DROP_PACKET_DROP_META + && (is_tcp_restart(export_data.fields.rsn, export_data.fields.ursn) + || export_data.fields.rsn == feta::ExportReason::EXPORT_BY_SW)) { + NHTFlowCache::export_flow(reinterpret_cast(&flow_record)); + m_ctt_stats.flows_removed++; + return; + } + + //Counter overflow is not a flow end reason, but it is used to update ipfixprobe flow counters + if (is_counter_overflow(export_data.fields.rsn, export_data.fields.ursn) || is_active_timeout(export_data.fields.rsn, export_data.fields.ursn)) { + return; + } + + // Mark flow as not offloadable to avoid ping-pong with CTT + if (is_hash_collision(export_data.fields.rsn, export_data.fields.ursn) || is_ctt_full(export_data.fields.rsn)) { + flow_record->can_be_offloaded = false; + } + + // Flow is not in the CTT anymore + if (!export_data.fields.wb) { + flow_record->offload_mode.reset(); + m_ctt_stats.flows_removed++; + if (is_from_remove_queue) { + NHTFlowCache::export_flow(reinterpret_cast(&flow_record)); + } + return; + } +} + +int NHTFlowCacheCtt::put_pkt(Packet& packet) +{ + if (packet.external_export) { + process_external_export(packet); + } + if (m_input_terminted) { + flush_ctt(packet.ts); + } + if (packet.external_export || m_input_terminted) { + return 0; + } + return NHTFlowCache::put_pkt(packet); +} + +size_t NHTFlowCacheCtt::find_victim(CacheRowSpan& row) const noexcept +{ + auto begin = reinterpret_cast(&row[0]); + for (size_t i = m_line_size; i > 0; i-- ) { + if (!begin[i - 1]->is_in_ctt() || + (begin[i - 1]->offload_mode.has_value() + && begin[i - 1]->offload_mode.value() == feta::OffloadMode::TRIM_PACKET_META)) { + return i - 1; + } + } + return m_line_size - 1; +} + +bool NHTFlowCacheCtt::requires_input() const +{ + return !m_table_flushed; +} + +void NHTFlowCacheCtt::export_expired(const timeval& now) +{ + auto [sent_request, lost_request] = m_ctt_remove_queue.resend_lost_requests(now); + m_ctt_stats.remove_queue_lost_requests += lost_request; + m_ctt_stats.lost_requests_count += lost_request; + m_ctt_stats.total_requests_count += sent_request; + if (m_input_terminted) { + flush_ctt(now); + return; + } + NHTFlowCache::export_expired(now); +} + +void NHTFlowCacheCtt::print_report() const +{ + const float tmp = static_cast(m_cache_stats.lookups) / m_cache_stats.hits; + std::cout << "Hits: " << m_cache_stats.hits << "\n"; + std::cout << "Empty: " << m_cache_stats.empty << "\n"; + std::cout << "Not empty: " << m_cache_stats.not_empty << "\n"; + std::cout << "Expired: " << m_cache_stats.exported << "\n"; + std::cout << "Flushed: " << m_cache_stats.flushed << "\n"; + std::cout << "Average Lookup: " << tmp << "\n"; + std::cout << "Variance Lookup: " << static_cast(m_cache_stats.lookups2) / m_cache_stats.hits - tmp * tmp << "\n"; + std::cout << "Flow end stats: " << "\n"; + std::cout << "Flow end reason: active timeout: " << m_flow_end_reason_stats.active_timeout << "\n"; + std::cout << "Flow end reason: inactive timeout: " << m_flow_end_reason_stats.inactive_timeout << "\n"; + std::cout << "Flow end reason: end of flow: " << m_flow_end_reason_stats.end_of_flow << "\n"; + std::cout << "Flow end reason: collision: " << m_flow_end_reason_stats.collision << "\n"; + std::cout << "Flow end reason: forced: " << m_flow_end_reason_stats.forced << "\n"; + std::cout << "Really processed: " << m_ctt_stats.real_processed_packets << "\n"; + std::cout << "CTT offloaded: " << m_ctt_stats.flows_offloaded << "\n"; + std::cout << "CTT trim packet offloaded: " << m_ctt_stats.trim_packet_offloaded << "\n"; + std::cout << "CTT drop packet offloaded: " << m_ctt_stats.drop_packet_offloaded << "\n"; + std::cout << "CTT flows removed after export packet: " << m_ctt_stats.flows_removed << "\n"; + std::cout << "CTT sent export packets:" << m_ctt_stats.export_packets << "\n"; + std::cout << "CTT export packets parsing failed:" << m_ctt_stats.export_packets_parsing_failed << "\n"; + std::cout << "CTT export packet failed to find corresponding flow:" << m_ctt_stats.export_packets_for_missing_flow << "\n"; +} + +telemetry::Dict get_export_reasons_telemetry(CttStats::ExportReasons& reasons) noexcept +{ + telemetry::Dict dict; + dict["BySW"] = reasons.by_request; + dict["CttFull"] = reasons.ctt_full; + dict["Reserved"] = reasons.reserved; + dict["CounterOverflow"] = reasons.counter_overflow; + dict["TcpEof"] = reasons.tcp_eof; + dict["ActiveTimeout"] = reasons.active_timeout; + dict["HashCollision"] = reasons.hash_collision; + dict["Total"] = reasons.by_request + reasons.ctt_full + reasons.reserved + reasons.counter_overflow + + reasons.tcp_eof + reasons.active_timeout + reasons.hash_collision; + return dict; + +} + +telemetry::Dict NHTFlowCacheCtt::get_ctt_telemetry() noexcept +{ + telemetry::Dict dict; + dict["CttRequests"] = m_ctt_stats.total_requests_count; + dict["CttRemoveQueueSize"] = m_ctt_remove_queue.size(); + dict["CttLostRequests"] = m_ctt_stats.lost_requests_count; + dict["FlowsInCtt"] = m_ctt_stats.flows_offloaded - m_ctt_stats.flows_removed; + dict["ExportPacketsForMissingFlow"] = m_ctt_stats.export_packets_for_missing_flow; + dict["CttHashCollision"] = std::to_string(m_ctt_stats.advanced_export_reasons.hash_collision[0]) + + " (WB0), " + std::to_string(m_ctt_stats.advanced_export_reasons.hash_collision[1]) + " (WB1)"; + dict["CttExportPackets"] = m_ctt_stats.export_packets; + dict["CttFull"] = std::to_string(m_ctt_stats.advanced_export_reasons.ctt_full[0]) + + " (WB0), " + std::to_string(m_ctt_stats.advanced_export_reasons.ctt_full[1]) + " (WB1)"; + dict["CttEof"] = std::to_string(m_ctt_stats.advanced_export_reasons.tcp_eof[0]) + + " (WB0), " + std::to_string(m_ctt_stats.advanced_export_reasons.tcp_eof[1]) + " (WB1)"; + dict["CttActiveTimeout"] = std::to_string(m_ctt_stats.advanced_export_reasons.active_timeout[0]) + + " (WB0), " + std::to_string(m_ctt_stats.advanced_export_reasons.active_timeout[1]) + " (WB1)"; + dict["CttCounterOverflow"] = std::to_string(m_ctt_stats.advanced_export_reasons.counter_overflow[0]) + + " (WB0), " + std::to_string(m_ctt_stats.advanced_export_reasons.counter_overflow[1]) + " (WB1)"; + dict["CttSwExport"] = std::to_string(m_ctt_stats.advanced_export_reasons.by_request[0]) + + " (WB0), " + std::to_string(m_ctt_stats.advanced_export_reasons.by_request[1]) + " (WB1)"; + dict["CttRemoveQueueLostRequests"] = m_ctt_stats.remove_queue_lost_requests; + dict["CttFlushCttLostRequests"] = m_ctt_stats.flush_ctt_lost_requests; + dict["CttPvZero"] = m_ctt_stats.pv_zero; + dict["ControllerCreateRequests"] = m_ctt_controller->get_request_stats().create_record_requests; + dict["ControllerExportAndDeleteRequests"] = m_ctt_controller->get_request_stats().export_and_delete_requests; + dict["CttFlowsOffloaded"] = m_ctt_stats.flows_offloaded; + dict["CttFlowsRemoved"] = m_ctt_stats.flows_removed; + dict["CttParsingFailed"] = m_ctt_stats.export_packets_parsing_failed; + dict["WbBeforePv1"] = std::to_string(m_ctt_stats.wb_before_pv1[0]) + " = 0, " + + std::to_string(m_ctt_stats.wb_before_pv1[1]) + " = 1"; + dict["WbAfterPv1"] = std::to_string(m_ctt_stats.wb_after_pv1[0]) + " = 0, " + + std::to_string(m_ctt_stats.wb_after_pv1[1]) + " = 1"; + dict["LibcttQueueSize(DMA shared)"] = m_ctt_controller->get_approximate_queue_size(); + return dict; +} + +telemetry::Dict NHTFlowCacheCtt::get_libctt_telemetry() noexcept +{ + telemetry::Dict dict; + auto [ctt_stats_succ, ctt_stats_err] = m_ctt_controller->get_queue_stats(); + dict["CommandsOffloaded"] = std::to_string(ctt_stats_succ.commands_offloaded) + " (success), " + + std::to_string(ctt_stats_err.commands_offloaded) + " (error)"; + dict["ReadOffloaded"] = std::to_string(ctt_stats_succ.read_offloaded) + " (success), " + + std::to_string(ctt_stats_err.read_offloaded) + " (error)"; + dict["WriteOffloaded"] = std::to_string(ctt_stats_succ.write_offloaded) + " (success), " + + std::to_string(ctt_stats_err.write_offloaded) + " (error)"; + dict["DeleteOffloaded"] = std::to_string(ctt_stats_succ.delete_offloaded) + " (success), " + + std::to_string(ctt_stats_err.delete_offloaded) + " (error)"; + dict["ExportOffloaded"] = std::to_string(ctt_stats_succ.export_offloaded) + " (success), " + + std::to_string(ctt_stats_err.export_offloaded) + " (error)"; + dict["ExportAndWriteOffloaded"] = std::to_string(ctt_stats_succ.export_and_write_offloaded) + " (success), " + + std::to_string(ctt_stats_err.export_and_write_offloaded) + " (error)"; + dict["ExportAndDeleteOffloaded"] = std::to_string(ctt_stats_succ.export_and_delete_offloaded) + " (success), " + + std::to_string(ctt_stats_err.export_and_delete_offloaded) + " (error)"; + dict["RmwOffloaded"] = std::to_string(ctt_stats_succ.rmw_offloaded) + " (success), " + + std::to_string(ctt_stats_err.rmw_offloaded) + " (error)"; + return dict; +} + +void NHTFlowCacheCtt::set_telemetry_dir(std::shared_ptr dir) +{ + telemetry::FileOps cacheStats = {[this]() -> telemetry::Content { return get_cache_telemetry(); }, nullptr}; + register_file(dir, "cache-stats", cacheStats); + + telemetry::FileOps cttStats = {[this]() -> telemetry::Content { return get_ctt_telemetry(); }, nullptr}; + register_file(dir, "ctt-stats", cttStats); + + if (m_enable_fragmentation_cache) { + m_fragmentation_cache.set_telemetry_dir(dir); + } + + telemetry::FileOps reasonsBeforePv1 = {[this]() -> telemetry::Content { + return get_export_reasons_telemetry(m_ctt_stats.export_reasons_before_pv1); + }, nullptr}; + register_file(dir, "ctt-export-reasons-before-pv1", reasonsBeforePv1); + + telemetry::FileOps reasonsAfterPv1 = {[this]() -> telemetry::Content { + return get_export_reasons_telemetry(m_ctt_stats.export_reasons_after_pv1); + }, nullptr}; + register_file(dir, "ctt-export-reasons-after-pv1", reasonsAfterPv1); + + telemetry::FileOps libcttStats = {[this]() -> telemetry::Content { return get_libctt_telemetry(); }, nullptr}; + register_file(dir, "libctt-stats", libcttStats); +} + + +void NHTFlowCacheCtt::init_ctt(const CttConfig& ctt_config) +{ + m_dma_channel = ctt_config.dma_channel; + m_ctt_controller.emplace(ctt_config.nfb_device, ctt_config.dma_channel/16); + m_ctt_remove_queue.set_ctt_controller(&*m_ctt_controller); +} + +static const PluginRegistrar + cacheRegistrar(cachePluginManifest); +} diff --git a/src/plugins/storage/cache-ctt/src/cacheCtt.hpp b/src/plugins/storage/cache-ctt/src/cacheCtt.hpp new file mode 100644 index 000000000..fb218c612 --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/cacheCtt.hpp @@ -0,0 +1,124 @@ +/** + * \file cache.hpp + * \brief NHTFlowCache extension with CTT support + * \author Zainullin Damir + * \date 2025 + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ +#pragma once + +#include "../../cache/src/cache.hpp" + +#include +#include + +#include "cttController.hpp" +#include "flowRecordCtt.hpp" +#include "cttRemoveQueue.hpp" + +namespace ipxp { + +/** + * \brief Extension of the NHTFlowCache class with CTT support + */ +class NHTFlowCacheCtt : public NHTFlowCache { +public: + + /** + * \brief Constructor + * \param params Parameters for the cache + * \param queue Pointer to the ring buffer + */ + NHTFlowCacheCtt(const std::string& params, ipx_ring_t* queue); + + ~NHTFlowCacheCtt() override; + + /** + * \brief Get the options parser for the cache + * \return Pointer to the options parser + */ + OptionsParser * get_parser() const override; + + /** + * \brief Get the name of the cache + * \return Name of the cache + */ + std::string get_name() const noexcept override; + + /** + * \brief Insert a packet into the cache + * \param packet Packet to be inserted + * \return 0 on success, -1 on error + */ + int put_pkt(Packet& packet) override; + +private: + /** + * \brief Structure to search ipfixprobe flow records based on CTT export data. + */ + struct CttFlowSearch{ + FlowRecordCtt** flow_record; /**< Pointer to the flow record */ + bool is_from_remove_queue; /**< True if the flow record is from the remove queue, false if from the main ipfixprobe memory */ + }; + + void init(const char* params) override; + void export_expired(const timeval& now) override; + void process_external_export(const Packet& pkt) noexcept; + void flush_ctt(const timeval now) noexcept; + void export_flow(FlowRecord** flow, int reason) override; + + void finish() override; + void create_record(const Packet& packet, size_t flow_index, size_t hash_value) noexcept override; + int update_flow(Packet& packet, size_t flow_index) noexcept override; + void try_to_export(size_t flow_index, bool call_pre_export, int reason) noexcept override; + bool requires_input() const override; + void init_ctt(const CttConfig& ctt_config) override; + void allocate_table() override; + void print_report() const override; + void close() override; + size_t find_victim(CacheRowSpan& row) const noexcept override; + void print_flush_progress(size_t current_pos) const noexcept; + void export_and_reuse_flow(size_t flow_index) noexcept override; + void set_telemetry_dir(std::shared_ptr dir) override; + + std::optional get_offload_mode(size_t flow_index) noexcept; + std::optional find_flow_from_ctt_export(const feta::CttExportPkt& export_data) noexcept; + void offload_flow_to_ctt(size_t flow_index, feta::OffloadMode offload_mode) noexcept; + void try_to_add_flow_to_ctt(size_t flow_index) noexcept; + void send_export_request_to_ctt(size_t ctt_flow_hash) noexcept; + void update_advanced_ctt_export_stats(const feta::CttExportPkt& export_data) noexcept; + telemetry::Dict get_libctt_telemetry() noexcept; + telemetry::Dict get_ctt_telemetry() noexcept; + + CttStats m_ctt_stats = {}; + uint8_t m_dma_channel{0}; + std::optional m_ctt_controller; + size_t m_prefinish_index{0}; + bool m_ctt_flow_seen{false}; + size_t m_ctt_flows_flushed{0}; + bool m_table_flushed{false}; + std::optional m_offload_mode; + FlowRecordCtt** m_flow_table{nullptr}; + std::unique_ptr m_flows; + CttRemoveQueue m_ctt_remove_queue; + size_t m_ctt_remove_queue_size{0}; + size_t m_offload_threshold{std::numeric_limits::max()}; +}; +} \ No newline at end of file From 32b3e13ba33942d9234cb4d090a75b487cf90f50 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:04:46 +0200 Subject: [PATCH 49/56] CTT - Introduce CttController class --- .../storage/cache-ctt/src/cttController.cpp | 174 ++++++++++++++++++ .../storage/cache-ctt/src/cttController.hpp | 113 ++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 src/plugins/storage/cache-ctt/src/cttController.cpp create mode 100644 src/plugins/storage/cache-ctt/src/cttController.hpp diff --git a/src/plugins/storage/cache-ctt/src/cttController.cpp b/src/plugins/storage/cache-ctt/src/cttController.cpp new file mode 100644 index 000000000..e805aac8f --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/cttController.cpp @@ -0,0 +1,174 @@ +/** +* \file + * \author Damir Zainullin + * \brief CttController implementation. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#include "cttController.hpp" +#include +#include +#include +#include + +namespace ipxp { + +CttController::CttController(const std::string& nfb_dev, unsigned ctt_comp_index) { + ctt::Card card(nfb_dev); + m_commander = card.get_async_commander(ctt_comp_index, std::nullopt, CttController::LIBCTT_QUEUE_SIZE); + try { + // Get UserInfo to determine key, state, and state_mask sizes + ctt::UserInfo user_info = m_commander->get_user_info(); + m_key_size_bytes = (user_info.key_bit_width + 7) / 8; + m_state_size_bytes = (user_info.state_bit_width + 7) / 8; + if (m_state_size_bytes != sizeof(feta::CttRecord)) { + throw std::runtime_error("Size of CTT state does not match the expected size."); + } + m_state_mask_size_bytes = (user_info.state_mask_bit_width + 7) / 8; + + // Enable the CTT + std::future enable_future = m_commander->enable(true); + enable_future.wait(); + } + catch (const std::exception& e) { + throw; + } +} + +size_t CttController::get_approximate_queue_size() +{ + return m_commander->get_queue_size_approx(); +} + +const CttController::RequestStats& CttController::get_request_stats() const noexcept +{ + return m_stats; +}; + +template +void try_with_sleep(Callable&& callable) noexcept +{ + bool success = false; + while (!success) { + try { + callable(); + success = true; + } catch (const ctt::CttException& e) { + sleep(1); + } + } +} + +void CttController::create_record(const Flow& flow, uint8_t dma_channel, feta::OffloadMode offload_mode) +{ + std::array key = assemble_key(flow.flow_hash_ctt); + std::array state = assemble_state( + offload_mode, + feta::MetaType::FULL_META, + flow, dma_channel); + try_with_sleep([&]() { + m_stats.create_record_requests++; + m_commander->export_and_write_record(std::move(key), std::move(state)); + }); +} + +void CttController::get_state(uint64_t flow_hash_ctt) +{ + std::array key = assemble_key(flow_hash_ctt); + try_with_sleep([&]() { + m_commander->export_record(std::move(key)); + }); +} + +void CttController::remove_record_without_notification(uint64_t flow_hash_ctt) +{ + std::array key = assemble_key(flow_hash_ctt); + try_with_sleep([&]() { + m_commander->delete_record(std::move(key)); + }); +} + +void CttController::export_record(uint64_t flow_hash_ctt) +{ + std::array key = assemble_key(flow_hash_ctt); + try_with_sleep([&]() { + m_stats.export_and_delete_requests++; + m_commander->export_and_delete_record(std::move(key)); + }); +} + +std::array CttController::assemble_key(uint64_t flow_hash_ctt) +{ + std::array key; + std::memcpy(key.data(), &flow_hash_ctt, KEY_SIZE); + return key; +} + +std::array CttController::assemble_state( + feta::OffloadMode offload_mode, feta::MetaType meta_type, const Flow& flow, uint8_t dma_channel) +{ + std::array state; + std::memset(state.data(), 0, sizeof(feta::CttRecord)); + feta::CttRecord record; + record.ts_first.time_sec = flow.time_first.tv_sec; + record.ts_first.time_ns = flow.time_first.tv_usec * 1000; + record.ts_last.time_sec = flow.time_last.tv_sec; + record.ts_last.time_ns = flow.time_last.tv_usec * 1000; + const size_t ip_length = flow.ip_version == IP::v4 ? 4 : 16; + std::memset(record.ip_src.data(), 0, 16); + std::memset(record.ip_dst.data(), 0, 16); + std::memcpy(record.ip_src.data(), &flow.src_ip, ip_length); + std::memcpy(record.ip_dst.data(), &flow.dst_ip, ip_length); + record.port_src = flow.src_port; + record.port_dst = flow.dst_port; + record.vlan_tci = flow.vlan_id; + record.l4_proto = flow.ip_proto; + record.ip_ver = flow.ip_version == IP::v4 ? feta::IpVersion::IPV4 : feta::IpVersion::IPV6; + record.vlan_vld = flow.vlan_id ? 1 : 0; + record.offload_mode = offload_mode; + record.meta_type = meta_type; + record.limit_size = 0; + record.dma_chan = dma_channel; + record.bytes = 0; + record.bytes_rev = 0; + record.pkts = 0; + record.pkts_rev = 0; + record.tcp_flags = 0; + record.tcp_flags_rev = 0; + feta::CttRecord::serialize(record, state.data()); + return state; +} + +ctt::CommanderStats CttController::get_queue_stats() const noexcept +{ + return m_commander->get_stats_global(); +} + +CttController::~CttController() noexcept +{ + /*if (!m_commander) { + return; + } + std::future enable_future = m_commander->enable(false); + enable_future.wait(); + m_commander.reset();*/ +} + +} // ipxp diff --git a/src/plugins/storage/cache-ctt/src/cttController.hpp b/src/plugins/storage/cache-ctt/src/cttController.hpp new file mode 100644 index 000000000..feac02718 --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/cttController.hpp @@ -0,0 +1,113 @@ +/** +* \file + * \author Damir Zainullin + * \brief CttController declaration. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ipxp { + +class CttController { +public: + /** + * @brief init the CTT. + * + * @param nfb_dev The NFB device file (e.g., "/dev/nfb0"). + * @param ctt_comp_index The index of the CTT component. + */ + CttController(const std::string& nfb_dev, unsigned ctt_comp_index); + + /** + * @brief Command: mark a flow for offload. + * + * @param flow_hash_ctt The flow hash to be offloaded. + */ + void create_record(const Flow& flow, uint8_t dma_channel, feta::OffloadMode offload_mode); + + /** + * @brief Command: export a flow from the CTT. + * + * @param flow_hash_ctt The flow hash to be exported. + */ + void export_record(uint64_t flow_hash_ctt); + + ~CttController() noexcept; + + void remove_record_without_notification(uint64_t flow_hash_ctt); + + void get_state(uint64_t flow_hash_ctt); + + struct RequestStats{ + size_t create_record_requests{0}; + size_t export_and_delete_requests{0}; + }; + + ctt::CommanderStats get_queue_stats() const noexcept; + + const RequestStats& get_request_stats() const noexcept; + + size_t get_approximate_queue_size(); + +private: + + constexpr static size_t LIBCTT_QUEUE_SIZE = 10'000'000; + + std::shared_ptr> m_commander; + size_t m_key_size_bytes; + size_t m_state_size_bytes; + size_t m_state_mask_size_bytes; + + RequestStats m_stats; + + /** + * @brief Assembles the state vector from the given values. + * + * @param offload_mode The offload mode. + * @param meta_type The metadata type. + * @param timestamp_first The first timestamp of the flow. + * @return A byte vector representing the assembled state vector. + */ + std::array + assemble_state(feta::OffloadMode offload_mode, feta::MetaType meta_type, const Flow& flow, uint8_t dma_channel); + + /** + * @brief Assembles the key vector from the given flow hash. + * + * @param flow_hash_ctt The flow hash. + * @return A byte vector representing the assembled key vector. + */ + std::array assemble_key(uint64_t flow_hash_ctt); +}; + +} // ipxp From 9aef78fa34c8bc8bb03efd942ba8eaf6b70c7efb Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:05:39 +0200 Subject: [PATCH 50/56] CTT - Introduce Ctt cache options parser class --- .../cache-ctt/src/cacheOptParserCtt.cpp | 67 +++++++++++++++++++ .../cache-ctt/src/cacheOptParserCtt.hpp | 45 +++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 src/plugins/storage/cache-ctt/src/cacheOptParserCtt.cpp create mode 100644 src/plugins/storage/cache-ctt/src/cacheOptParserCtt.hpp diff --git a/src/plugins/storage/cache-ctt/src/cacheOptParserCtt.cpp b/src/plugins/storage/cache-ctt/src/cacheOptParserCtt.cpp new file mode 100644 index 000000000..6490a7b59 --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/cacheOptParserCtt.cpp @@ -0,0 +1,67 @@ +/** +* \file + * \author Damir Zainullin + * \brief CacheOptParser implementation. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#include "cacheOptParserCtt.hpp" + +#include +#include +#include + +namespace ipxp { + +CacheOptParserCtt::CacheOptParserCtt() + : CacheOptParser("cache-ctt", "Storage plugin implemented as a hash table with support of ctt-offload") + { + register_option("m", "mode", "MODE", "none/drop/trim", + [this](const char *arg){ + if (strcmp(arg, "none") == 0) { + m_offload_mode = std::nullopt; + } else if (strcmp(arg, "drop") == 0) { + m_offload_mode = feta::OffloadMode::DROP_PACKET_DROP_META; + } else if (strcmp(arg, "trim") == 0) { + m_offload_mode = feta::OffloadMode::TRIM_PACKET_META; + } else { + return false; + } + return true; + }, + OptionFlags::RequiredArgument); + register_option("ot", "offload-threshold", "count", "Flow is ctt offloaded if count of packets is more than threshold. Must be at least 0. Default is 1000.", [this](const char *arg) { + try { + m_offload_threshold = str2num(arg); + } catch(std::invalid_argument &e) { + return false; + } + return true; + }); + register_option("rqs", "remove-queue-size", "size", "Maximal count of flows that are simultanously waiting for export packet from CTT. Default is 1024. At least 512.", [this](const char *arg) { + try { + m_ctt_remove_queue_size = str2num(arg); + } catch(std::invalid_argument &e) { + return false; + } + return m_ctt_remove_queue_size >= 512; + }); + } +} // ipxp diff --git a/src/plugins/storage/cache-ctt/src/cacheOptParserCtt.hpp b/src/plugins/storage/cache-ctt/src/cacheOptParserCtt.hpp new file mode 100644 index 000000000..188ca8fd7 --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/cacheOptParserCtt.hpp @@ -0,0 +1,45 @@ +/** +* \file + * \author Damir Zainullin + * \brief Contains the CacheOptParser class for parsing cache options. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#pragma once + +#include "../../cache/src/cacheOptParser.hpp" + +#include +#include + +namespace ipxp { + +class CacheOptParserCtt : public CacheOptParser { +public: + std::optional m_offload_mode; + size_t m_offload_threshold{1000}; + size_t m_ctt_remove_queue_size{1024}; + + ~CacheOptParserCtt() override = default; + CacheOptParserCtt(); +}; + + +} // ipxp From 183884e863129c7d4931128986ba6405cba97886 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:06:00 +0200 Subject: [PATCH 51/56] CTT - Introduce CttRemoveQueue class --- .../storage/cache-ctt/src/cttRemoveQueue.cpp | 103 ++++++++++++++++++ .../storage/cache-ctt/src/cttRemoveQueue.hpp | 50 +++++++++ 2 files changed, 153 insertions(+) create mode 100644 src/plugins/storage/cache-ctt/src/cttRemoveQueue.cpp create mode 100644 src/plugins/storage/cache-ctt/src/cttRemoveQueue.hpp diff --git a/src/plugins/storage/cache-ctt/src/cttRemoveQueue.cpp b/src/plugins/storage/cache-ctt/src/cttRemoveQueue.cpp new file mode 100644 index 000000000..1bdd2fbd7 --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/cttRemoveQueue.cpp @@ -0,0 +1,103 @@ +#include "cttRemoveQueue.hpp" +#include "../../cache/src/fragmentationCache/timevalUtils.hpp" + +#include + +namespace ipxp +{ + +CttRemoveQueue::CttRemoveQueue() noexcept + : m_last_index(0) +{ +} + +void CttRemoveQueue::set_buffer(FlowRecordCtt* buffer, size_t size) noexcept +{ + m_flows = buffer; + m_flows_capacity = size; + m_flow_table = std::make_unique(size); + std::for_each(m_flow_table.get(), m_flow_table.get() + size, [index = 0, this](FlowRecordCtt*& flow) mutable { + flow = &m_flows[index++]; + }); +} + +void CttRemoveQueue::set_ctt_controller(CttController* ctt_controller) noexcept +{ + m_ctt_controller = ctt_controller; +} + +FlowRecordCtt** CttRemoveQueue::find(size_t hash) noexcept +{ + for (size_t index = 0; index < m_last_index; index++) { + if (!m_flow_table[index]->is_empty() && m_flow_table[index]->belongs(hash)) { + return &m_flow_table[index]; + } + } + return nullptr; +} + +FlowRecordCtt** CttRemoveQueue::find_by_flowhash(size_t hash) noexcept +{ + for (size_t index = 0; index < m_last_index; index++) { + if (!m_flow_table[index]->is_empty() && m_flow_table[index]->m_flow.flow_hash_ctt == hash) { + return &m_flow_table[index]; + } + } + return nullptr; +} + +FlowRecordCtt* CttRemoveQueue::add(FlowRecordCtt* flow) +{ + if (m_last_index == m_flows_capacity) { + throw std::runtime_error("CttRemoveQueue is full"); + } + + for (size_t index = 0; index < m_last_index; index++) { + if (m_flow_table[index]->is_empty()) { + std::swap(m_flow_table[index], flow); + shrink(); + return flow; + } + } + std::swap(m_flow_table[m_last_index], flow); + m_last_index++; + shrink(); + return flow; +} + +void CttRemoveQueue::shrink() noexcept +{ + for (; m_last_index > 0 && m_flow_table[m_last_index - 1]->is_empty(); m_last_index--); +} + +size_t CttRemoveQueue::size() const noexcept +{ + return m_last_index; +} + +CttRemoveQueue::RequestCounts CttRemoveQueue::resend_lost_requests(const timeval now) noexcept +{ + constexpr size_t BLOCK_SIZE = 16; + size_t sent_requests = 0; + size_t lost_requests = 0; + + for (size_t index = m_export_index; index < m_export_index + BLOCK_SIZE && index < m_last_index; index++) { + if (m_flow_table[index]->is_empty() || !m_flow_table[index]->is_in_ctt()) { + continue; + } + + if (!m_flow_table[index]->is_waiting_ctt_response() || now > *m_flow_table[index]->last_request_time + CTT_REQUEST_TIMEOUT) { + if (m_flow_table[index]->is_waiting_ctt_response()) { + lost_requests++; + } + sent_requests++; + m_ctt_controller->export_record(m_flow_table[index]->m_flow.flow_hash_ctt); + m_flow_table[index]->last_request_time = now; + } + } + m_export_index = m_export_index + BLOCK_SIZE; + m_export_index = m_export_index > m_last_index ? 0 : m_export_index; + return {sent_requests, lost_requests}; +} + +} \ No newline at end of file diff --git a/src/plugins/storage/cache-ctt/src/cttRemoveQueue.hpp b/src/plugins/storage/cache-ctt/src/cttRemoveQueue.hpp new file mode 100644 index 000000000..e48b59df5 --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/cttRemoveQueue.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "flowRecordCtt.hpp" +#include "cttController.hpp" + +#include +#include + +namespace ipxp { + +class CttRemoveQueue +{ +public: + + CttRemoveQueue() noexcept; + + void set_buffer(FlowRecordCtt* buffer, size_t size) noexcept; + + void set_ctt_controller(CttController* ctt_controller) noexcept; + + FlowRecordCtt** find(size_t hash) noexcept; + + FlowRecordCtt** find_by_flowhash(size_t hash) noexcept; + + FlowRecordCtt* add(FlowRecordCtt* flow); + + size_t size() const noexcept; + + struct RequestCounts { + size_t sent_requests{0}; + size_t lost_requests{0}; + }; + + RequestCounts resend_lost_requests(const timeval now) noexcept; + +private: + + void shrink() noexcept; + + FlowRecordCtt* m_flows; + size_t m_flows_capacity; + std::unique_ptr m_flow_table; + size_t m_last_index; + size_t m_export_index{0}; + CttController* m_ctt_controller; +}; + + + +} // namespace ipxp \ No newline at end of file From 9120bc77c9501344de5f95ad631890c2b4d09100 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:07:19 +0200 Subject: [PATCH 52/56] CTT - Inroduce FlowRecordCtt structure --- .../storage/cache-ctt/src/flowRecordCtt.cpp | 48 +++++++++++++++ .../storage/cache-ctt/src/flowRecordCtt.hpp | 59 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/plugins/storage/cache-ctt/src/flowRecordCtt.cpp create mode 100644 src/plugins/storage/cache-ctt/src/flowRecordCtt.hpp diff --git a/src/plugins/storage/cache-ctt/src/flowRecordCtt.cpp b/src/plugins/storage/cache-ctt/src/flowRecordCtt.cpp new file mode 100644 index 000000000..5058540de --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/flowRecordCtt.cpp @@ -0,0 +1,48 @@ +/** +* \file + * \author Damir Zainullin + * \brief FlowRecord implementation. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#include "flowRecordCtt.hpp" + +#include +#include +#include + +namespace ipxp { + +void FlowRecordCtt::erase() +{ + FlowRecord::erase(); + last_request_time.reset(); + can_be_offloaded = false; + offload_mode.reset(); +} + +void FlowRecordCtt::create(const Packet &pkt, uint64_t hash) +{ + FlowRecord::create(pkt, hash); + can_be_offloaded = true; + last_request_time.reset(); +} + +} // ipxp \ No newline at end of file diff --git a/src/plugins/storage/cache-ctt/src/flowRecordCtt.hpp b/src/plugins/storage/cache-ctt/src/flowRecordCtt.hpp new file mode 100644 index 000000000..85e8d5cf7 --- /dev/null +++ b/src/plugins/storage/cache-ctt/src/flowRecordCtt.hpp @@ -0,0 +1,59 @@ +/** +* \file + * \author Damir Zainullin + * \brief FlowRecord declaration. + */ +/* + * Copyright (C) 2023 CESNET + * + * LICENSE TERMS + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "../../cache/src/flowRecord.hpp" + +namespace ipxp { + +struct alignas(64) FlowRecordCtt : public FlowRecord +{ + bool can_be_offloaded; /**< No flow collision in CTT */ + std::optional last_request_time; /**< Time point when the last not processed request was sent to CTT. */ + std::optional offload_mode; /**< Offload mode of the flow. Nullopt if not offloaded*/ + + void erase() override; + + void create(const Packet &pkt, uint64_t pkt_hash) override; + + __attribute__((always_inline)) bool is_in_ctt() const noexcept + { + return offload_mode.has_value(); + } + + __attribute__((always_inline)) bool is_waiting_ctt_response() const noexcept + { + return is_in_ctt() && last_request_time.has_value(); + } + +}; + +} // ipxp From 6bd8fb93257882866d50ae996b44b2ee1a4a688c Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:08:06 +0200 Subject: [PATCH 53/56] CTT - Update RPM build --- pkg/rpm/CMakeLists.txt | 4 ++++ pkg/rpm/ipfixprobe-nemea.spec.in | 2 +- pkg/rpm/ipfixprobe.spec.in | 29 ++++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pkg/rpm/CMakeLists.txt b/pkg/rpm/CMakeLists.txt index cbdd25f48..f5f4a7fce 100644 --- a/pkg/rpm/CMakeLists.txt +++ b/pkg/rpm/CMakeLists.txt @@ -30,6 +30,10 @@ if (ENABLE_INPUT_NFB) list(APPEND RPMBUILD_ARGS "--with" "input_nfb") endif() +if (ENABLE_CTT) + list(APPEND RPMBUILD_ARGS "--with" "ctt") +endif() + if (ENABLE_PROCESS_EXPERIMENTAL) list(APPEND RPMBUILD_ARGS "--with" "process_experimental") endif() diff --git a/pkg/rpm/ipfixprobe-nemea.spec.in b/pkg/rpm/ipfixprobe-nemea.spec.in index c93f92606..79149bf39 100644 --- a/pkg/rpm/ipfixprobe-nemea.spec.in +++ b/pkg/rpm/ipfixprobe-nemea.spec.in @@ -120,7 +120,7 @@ source /opt/rh/gcc-toolset-14/enable %{_libdir}/ipfixprobe/process/libipfixprobe-process-ssadetector.so %{_libdir}/ipfixprobe/process/libipfixprobe-process-ssdp.so -%{_libdir}/ipfixprobe/storage/libipfixprobe-storage-cache.so +%{_libdir}/ipfixprobe/storage/libipfixprobe-storage-cache-ctt.so %{_libdir}/ipfixprobe/schema.json %{_libdir}/ipfixprobe/config2args.py diff --git a/pkg/rpm/ipfixprobe.spec.in b/pkg/rpm/ipfixprobe.spec.in index 09e12b534..d27b68a50 100644 --- a/pkg/rpm/ipfixprobe.spec.in +++ b/pkg/rpm/ipfixprobe.spec.in @@ -2,6 +2,7 @@ %bcond_with input_dpdk %bcond_with input_nfb %bcond_with process_experimental +%bcond_with ctt %global _unitdir %{_prefix}/lib/systemd/system @@ -84,6 +85,24 @@ Requires: numactl Input plugin for nfb cards. %endif +%if %{with ctt} +%package ctt +Summary: Ctt. +BuildRequires: nfb-framework +BuildRequires: numactl-devel +BuildRequires: libctt-devel +BuildRequires: libfeta-devel +Requires: nfb-framework +Requires: numactl +Requires: libctt +Requires: libfeta +Requires: cttctl +Requires: ndk-nic-ctl + +%description ctt +Ctt package. +%endif + %if %{with process_experimental} %package process-experimental Summary: Experimental process plugins. @@ -102,7 +121,7 @@ Experimental process plugins. %if 0%{?rhel} == 8 source /opt/rh/gcc-toolset-14/enable %endif -%cmake -DCMAKE_BUILD_TYPE=Release %{?with_input_pcap:-DENABLE_INPUT_PCAP=ON} %{?with_input_dpdk:-DENABLE_INPUT_DPDK=ON} %{?with_input_nfb:-DENABLE_INPUT_NFB=ON} %{?with_process_experimental: -DENABLE_PROCESS_EXPERIMENTAL=ON} +%cmake -DCMAKE_BUILD_TYPE=Release %{?with_ctt:-DENABLE_CTT=ON} %{?with_input_pcap:-DENABLE_INPUT_PCAP=ON} %{?with_input_dpdk:-DENABLE_INPUT_DPDK=ON} %{?with_input_nfb:-DENABLE_INPUT_NFB=ON} %{?with_process_experimental: -DENABLE_PROCESS_EXPERIMENTAL=ON} %cmake_build %install @@ -141,7 +160,9 @@ source /opt/rh/gcc-toolset-14/enable %{_libdir}/ipfixprobe/process/libipfixprobe-process-ssadetector.so %{_libdir}/ipfixprobe/process/libipfixprobe-process-ssdp.so +%if %{without ctt} %{_libdir}/ipfixprobe/storage/libipfixprobe-storage-cache.so +%endif %{_libdir}/ipfixprobe/schema.json %{_libdir}/ipfixprobe/config2args.py @@ -160,6 +181,12 @@ source /opt/rh/gcc-toolset-14/enable %{_libdir}/ipfixprobe/input/libipfixprobe-input-nfb.so %endif +%if %{with ctt} +%files ctt +%{_libdir}/ipfixprobe/input/libipfixprobe-input-nfb-meta.so +%{_libdir}/ipfixprobe/storage/libipfixprobe-storage-cache-ctt.so +%endif + %if %{with input_dpdk} %files input-dpdk %{_libdir}/ipfixprobe/input/libipfixprobe-input-dpdk.so From ac4f97657bc4c3c89b191aa0e620ad826630a93f Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:08:24 +0200 Subject: [PATCH 54/56] CTT - add empty ctt cache readme --- src/plugins/storage/cache-ctt/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/plugins/storage/cache-ctt/README.md diff --git a/src/plugins/storage/cache-ctt/README.md b/src/plugins/storage/cache-ctt/README.md new file mode 100644 index 000000000..e69de29bb From a6351445bae8d725769ff6e800449ebd2eb1b6d5 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Thu, 26 Jun 2025 11:09:35 +0200 Subject: [PATCH 55/56] CTT - Update CMakeLists --- CMakeLists.txt | 3 + cmake/dependencies.cmake | 2 +- src/plugins/input/CMakeLists.txt | 4 ++ src/plugins/input/nfb/CMakeLists.txt | 2 + src/plugins/process/CMakeLists.txt | 28 ++++---- src/plugins/storage/CMakeLists.txt | 8 ++- src/plugins/storage/cache-ctt/CMakeLists.txt | 74 ++++++++++++++++++++ src/plugins/storage/cache/CMakeLists.txt | 15 ++++ 8 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 src/plugins/storage/cache-ctt/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 784bb56ec..070191c45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ option(ENABLE_OUTPUT_UNIREC "Enable build of output UNIREC plugin" option(ENABLE_PROCESS_EXPERIMENTAL "Enable build of experimental process plugins" OFF) option(ENABLE_MILLISECONDS_TIMESTAMP "Compile ipfixprobe with miliseconds timestamp precesion" OFF) option(ENABLE_NEMEA "Enable build of NEMEA plugins" OFF) +option(ENABLE_CTT "Enable support of conaction tracking table" OFF) option(ENABLE_RPMBUILD "Enable build of RPM package" ON) option(ENABLE_TESTS "Build tests (make test)" OFF) @@ -49,6 +50,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -ggdb3") +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + include(cmake/dependencies.cmake) add_subdirectory(external) diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 2459ed45c..b2d9b224c 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -15,7 +15,7 @@ if (ENABLE_INPUT_DPDK) pkg_check_modules(DPDK REQUIRED libdpdk) endif() -if (ENABLE_INPUT_NFB) +if (ENABLE_INPUT_NFB OR ENABLE_CTT) find_package(NFB REQUIRED) find_package(NUMA REQUIRED) endif() diff --git a/src/plugins/input/CMakeLists.txt b/src/plugins/input/CMakeLists.txt index de07ed619..40154dc12 100644 --- a/src/plugins/input/CMakeLists.txt +++ b/src/plugins/input/CMakeLists.txt @@ -11,3 +11,7 @@ endif() if (ENABLE_INPUT_NFB) add_subdirectory(nfb) endif() + +if (ENABLE_CTT) + add_subdirectory(nfb-meta) +endif() diff --git a/src/plugins/input/nfb/CMakeLists.txt b/src/plugins/input/nfb/CMakeLists.txt index 03086c023..81a3923cd 100644 --- a/src/plugins/input/nfb/CMakeLists.txt +++ b/src/plugins/input/nfb/CMakeLists.txt @@ -6,6 +6,8 @@ add_library(ipfixprobe-input-nfb MODULE src/ndpHeader.hpp src/ndpReader.cpp src/ndpReader.hpp + src/ndpCore.hpp + src/ndpCore.cpp ../parser/parser.cpp ../parser/parser.hpp ) diff --git a/src/plugins/process/CMakeLists.txt b/src/plugins/process/CMakeLists.txt index a47322075..dc74b94e6 100644 --- a/src/plugins/process/CMakeLists.txt +++ b/src/plugins/process/CMakeLists.txt @@ -1,26 +1,26 @@ -add_subdirectory(common) add_subdirectory(basicplus) add_subdirectory(bstats) -add_subdirectory(icmp) -add_subdirectory(vlan) +add_subdirectory(common) +add_subdirectory(dns) +add_subdirectory(dnssd) add_subdirectory(flowHash) -add_subdirectory(osquery) +add_subdirectory(http) +add_subdirectory(icmp) add_subdirectory(idpContent) -add_subdirectory(phists) -add_subdirectory(pstats) -add_subdirectory(ovpn) -add_subdirectory(wg) -add_subdirectory(ssdp) -add_subdirectory(ssaDetector) add_subdirectory(mqtt) -add_subdirectory(dns) -add_subdirectory(dnssd) add_subdirectory(netbios) +add_subdirectory(ovpn) +add_subdirectory(osquery) add_subdirectory(passiveDns) -add_subdirectory(smtp) +add_subdirectory(phists) +add_subdirectory(pstats) add_subdirectory(quic) +add_subdirectory(smtp) +add_subdirectory(ssaDetector) +add_subdirectory(ssdp) add_subdirectory(tls) -add_subdirectory(http) +add_subdirectory(vlan) +add_subdirectory(wg) if (ENABLE_PROCESS_EXPERIMENTAL) add_subdirectory(sip) diff --git a/src/plugins/storage/CMakeLists.txt b/src/plugins/storage/CMakeLists.txt index 9dddffe96..020389af5 100644 --- a/src/plugins/storage/CMakeLists.txt +++ b/src/plugins/storage/CMakeLists.txt @@ -1 +1,7 @@ -add_subdirectory(cache) +if (!ENABLE_CTT) + add_subdirectory(cache) +endif() + +if (ENABLE_CTT) + add_subdirectory(cache-ctt) +endif() \ No newline at end of file diff --git a/src/plugins/storage/cache-ctt/CMakeLists.txt b/src/plugins/storage/cache-ctt/CMakeLists.txt new file mode 100644 index 000000000..376fe2c26 --- /dev/null +++ b/src/plugins/storage/cache-ctt/CMakeLists.txt @@ -0,0 +1,74 @@ +project(ipfixprobe-storage-cache-ctt VERSION 1.0.0 DESCRIPTION "ipfixprobe-storage-cache-ctt plugin") + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/) + +add_library(ipfixprobe-storage-cache-ctt MODULE + src/cacheCtt.hpp + src/cacheCtt.cpp + src/cacheOptParserCtt.hpp + src/cacheOptParserCtt.cpp + src/cttController.hpp + src/cttController.cpp + src/flowRecordCtt.hpp + src/flowRecordCtt.cpp + src/cttRemoveQueue.hpp + src/cttRemoveQueue.cpp + ../cache/src/cache.hpp + ../cache/src/cache.cpp + ../cache/src/cacheOptParser.hpp + ../cache/src/cacheOptParser.cpp + ../cache/src/cacheRowSpan.hpp + ../cache/src/cacheStats.hpp + ../cache/src/flowKey.hpp + ../cache/src/flowKeyFactory.hpp + ../cache/src/flowRecord.hpp + ../cache/src/flowRecord.cpp + ../cache/src/fragmentationCache/fragmentationCache.cpp + ../cache/src/fragmentationCache/fragmentationCache.hpp + ../cache/src/fragmentationCache/fragmentationKeyData.hpp + ../cache/src/fragmentationCache/fragmentationTable.cpp + ../cache/src/fragmentationCache/fragmentationTable.hpp + ../cache/src/fragmentationCache/ringBuffer.hpp + ../cache/src/fragmentationCache/timevalUtils.hpp + ../cache/src/xxhash.c + ../cache/src/xxhash.h +) + +add_compile_definitions(maybe_virtual=virtual) + +set_target_properties(ipfixprobe-storage-cache-ctt PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES +) + +target_compile_options(ipfixprobe-storage-cache-ctt PRIVATE + $<$: -O4 -flto=auto -fwhole-program> +) + +target_include_directories(ipfixprobe-storage-cache-ctt PRIVATE + ${CMAKE_SOURCE_DIR}/include/ +) + +target_link_libraries(ipfixprobe-storage-cache-ctt PRIVATE + telemetry::telemetry + telemetry::appFs + ctt + feta +) + +install(TARGETS ipfixprobe-storage-cache-ctt + LIBRARY DESTINATION "${INSTALL_DIR_LIB}/ipfixprobe/storage/" +) + +set(LIB_CTT_KEY_SIZE "0" CACHE STRING "Set the custom size value for cttctl") +set(LIB_CTT_STATE_SIZE "0" CACHE STRING "Set the custom size value for cttctl") +set(LIB_CTT_STATE_MASK_SIZE "0" CACHE STRING "Set the custom size value for cttctl") + +add_definitions( + -DLIB_CTT_VERSION_MAJOR=${VERSION_MAJOR} + -DLIB_CTT_VERSION_MINOR=${VERSION_MINOR} + -DLIB_CTT_KEY_SIZE=${LIB_CTT_KEY_SIZE} + -DLIB_CTT_STATE_SIZE=${LIB_CTT_STATE_SIZE} + -DLIB_CTT_STATE_MASK_SIZE=${LIB_CTT_STATE_MASK_SIZE} +) + diff --git a/src/plugins/storage/cache/CMakeLists.txt b/src/plugins/storage/cache/CMakeLists.txt index 5a8623619..f9b9abb26 100644 --- a/src/plugins/storage/cache/CMakeLists.txt +++ b/src/plugins/storage/cache/CMakeLists.txt @@ -5,6 +5,14 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/) add_library(ipfixprobe-storage-cache MODULE src/cache.hpp src/cache.cpp + src/cacheOptParser.hpp + src/cacheOptParser.cpp + src/cacheRowSpan.hpp + src/cacheStats.hpp + src/flowKey.hpp + src/flowKeyFactory.hpp + src/flowRecord.hpp + src/flowRecord.cpp src/fragmentationCache/fragmentationCache.cpp src/fragmentationCache/fragmentationCache.hpp src/fragmentationCache/fragmentationKeyData.hpp @@ -16,17 +24,24 @@ add_library(ipfixprobe-storage-cache MODULE src/xxhash.h ) +add_compile_definitions(maybe_virtual=) # No need for virtual functions if ctt cache is not being compiled + set_target_properties(ipfixprobe-storage-cache PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES ) +target_compile_options(ipfixprobe-storage-cache PRIVATE + $<$: -O4 -flto=auto -fopt-info-optimized -fopt-info-inline> +) + target_include_directories(ipfixprobe-storage-cache PRIVATE ${CMAKE_SOURCE_DIR}/include/ ) target_link_libraries(ipfixprobe-storage-cache PRIVATE telemetry::telemetry + telemetry::appFs ) install(TARGETS ipfixprobe-storage-cache From 99ae409e397c77c083846dd9d67e6b6538040719 Mon Sep 17 00:00:00 2001 From: Damir Zainullin Date: Sun, 20 Jul 2025 20:04:21 +0200 Subject: [PATCH 56/56] CTT - Terminate all worker threads at once --- src/core/ipfixprobe.cpp | 4 ++- src/core/ipfixprobe.hpp | 1 + src/core/workers.cpp | 10 ++++-- src/core/workers.hpp | 35 ++++++++++++++++++- .../storage/cache-ctt/src/cacheCtt.cpp | 5 --- 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/core/ipfixprobe.cpp b/src/core/ipfixprobe.cpp index 4aba56fa1..f7a7417b4 100644 --- a/src/core/ipfixprobe.cpp +++ b/src/core/ipfixprobe.cpp @@ -448,7 +448,9 @@ bool process_plugin_args(ipxp_conf_t& conf, IpfixprobeOptParser& parser) conf.iqueue_size, conf.max_pkts, input_res, - input_stats), + input_stats, + pipeline_idx, + &conf.finished_workers), input_res, input_stats}, {storagePlugin, storage_process_plugins}}; diff --git a/src/core/ipfixprobe.hpp b/src/core/ipfixprobe.hpp index e949cb592..ec6e839c1 100644 --- a/src/core/ipfixprobe.hpp +++ b/src/core/ipfixprobe.hpp @@ -325,6 +325,7 @@ struct ipxp_conf_t { } active; std::vector pipelines; + FinishedWorkers finished_workers; std::vector outputs; std::vector*> input_stats; diff --git a/src/core/workers.cpp b/src/core/workers.cpp index 63dc7214d..f986a8177 100644 --- a/src/core/workers.cpp +++ b/src/core/workers.cpp @@ -43,7 +43,9 @@ void input_storage_worker( size_t queue_size, uint64_t pkt_limit, std::promise* out, - std::atomic* out_stats) + std::atomic* out_stats, + size_t worker_id, + FinishedWorkers* finished_workers) { struct timespec start_cache; struct timespec end_cache; @@ -54,6 +56,7 @@ void input_storage_worker( InputPlugin::Result ret; InputStats stats = {0, 0, 0, 0, 0}; WorkerResult res = {false, ""}; + finished_workers->mark_in_progress(worker_id); PacketBlock block(queue_size); @@ -63,10 +66,13 @@ void input_storage_worker( const clockid_t clk_id = CLOCK_MONOTONIC; #endif - while (storagePlugin->requires_input()) { + while (!finished_workers->all_finished()) { if (terminate_input) { storagePlugin->terminate_input(); } + if (!storagePlugin->requires_input()) { + finished_workers->mark_finished(worker_id); + } block.cnt = 0; block.bytes = 0; diff --git a/src/core/workers.hpp b/src/core/workers.hpp index fa6f1ac23..8cf8af4fc 100644 --- a/src/core/workers.hpp +++ b/src/core/workers.hpp @@ -71,13 +71,46 @@ struct OutputWorker { ipx_ring_t* queue; }; +class FinishedWorkers { + constexpr static size_t MAX_WORKERS = 64; + enum class WorkerStatus : uint8_t { + IN_PROGRESS = 0, + FINISHED = 1 + }; + std::array workers_status; + +public: + FinishedWorkers() noexcept + { + workers_status.fill(WorkerStatus::FINISHED); + } + + void mark_in_progress(size_t worker_id) noexcept + { + workers_status[worker_id] = WorkerStatus::IN_PROGRESS; + } + + void mark_finished(size_t worker_id) noexcept + { + workers_status[worker_id] = WorkerStatus::FINISHED; + } + + bool all_finished() const noexcept + { + return std::all_of(workers_status.begin(), workers_status.end(), + [](WorkerStatus status) { return status == WorkerStatus::FINISHED; }); + } +}; + void input_storage_worker( std::shared_ptr inputPlugin, std::shared_ptr storagePlugin, size_t queue_size, uint64_t pkt_limit, std::promise* out, - std::atomic* out_stats); + std::atomic* out_stats, + size_t worker_id, + FinishedWorkers* finished_workers); void output_worker( std::shared_ptr outputPlugin, ipx_ring_t* queue, diff --git a/src/plugins/storage/cache-ctt/src/cacheCtt.cpp b/src/plugins/storage/cache-ctt/src/cacheCtt.cpp index 040fe5f8a..b5b76a0e9 100644 --- a/src/plugins/storage/cache-ctt/src/cacheCtt.cpp +++ b/src/plugins/storage/cache-ctt/src/cacheCtt.cpp @@ -168,11 +168,6 @@ void NHTFlowCacheCtt::flush_ctt(const timeval now) noexcept m_table_flushed = !m_ctt_flow_seen; m_ctt_flow_seen = false; } - - if (m_ctt_flows_flushed >= 16) { - m_ctt_flows_flushed = 0; - usleep(400); - } } std::optional NHTFlowCacheCtt::get_offload_mode(size_t flow_index) noexcept