]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4884: flow: add new flow prune reason
authorRaza Shafiq (rshafiq) <rshafiq@cisco.com>
Mon, 27 Oct 2025 14:50:32 +0000 (14:50 +0000)
committerSteven Baigal (sbaigal) <sbaigal@cisco.com>
Mon, 27 Oct 2025 14:50:32 +0000 (14:50 +0000)
Merge in SNORT/snort3 from ~RSHAFIQ/snort3:flow_release to master

Squashed commit of the following:

commit c6c4c580d3aa46a09b9063b08347c6071de631f6
Author: rshafiq <rshafiq@cisco.com>
Date:   Tue Aug 26 16:51:20 2025 -0400

    flow: new pegs and packet tracer log for flow prune

15 files changed:
src/flow/CMakeLists.txt
src/flow/flow.cc
src/flow/flow_cache.cc
src/flow/flow_cache.h
src/flow/prune_stats.h
src/flow/test/flow_cache_test.cc
src/flow/test/flow_control_test.cc
src/flow/test/flow_stubs.h
src/packet_io/packet_tracer.cc
src/packet_io/packet_tracer.h
src/stream/base/stream_base.cc
src/stream/base/stream_module.h
src/stream/stream.cc
src/stream/stream.h
src/utils/util.h

index 5ea36dac145f925874621287687edfac582b17db..f54f2fcb2f03d35974395b5bc3bf4aa93a5896cb 100644 (file)
@@ -6,6 +6,7 @@ set (FLOW_INCLUDES
     flow_key.h
     flow_stash.h
     ha.h
+    prune_stats.h
     session.h
     stream_flow.h
 )
index 9221ea2ef0be2a17438554212f7f27927d353804..69a9aab39df6c374177e5f018fbe8209b0683091 100644 (file)
@@ -483,7 +483,8 @@ bool Flow::handle_allowlist()
     {
         if ( flow_con->move_to_allowlist(this) )
         {
-            PacketTracer::log("Flow: flow has been moved to allowlist cache\n");
+            if ( PacketTracer::is_active() )
+                PacketTracer::log("Flow: flow has been moved to allowlist cache\n");
             return true;
         }
     }
index 5464027019d06142f5404d78bb092918541a94bc..e61092bb86fb73b8c3eab720c20e942eb077d46a 100644 (file)
@@ -438,6 +438,9 @@ bool FlowCache::release(Flow* flow, PruneReason reason, bool do_cleanup)
         }
     }
 
+    if ( UNLIKELY(PacketTracer::is_active()) )
+        log_flow_release(flow, reason);
+
     uint8_t in_allowlist = flow->flags.in_allowlist;
     flow->reset(do_cleanup);
     prune_stats.update(reason, ( in_allowlist ? static_cast<PktType>(allowlist_lru_index) : flow->key->pkt_type ));
@@ -1017,7 +1020,8 @@ bool FlowCache::filter_flows(const Flow& flow, const FilterFlowCriteria& ffc) co
     return true;
 }
 
-void FlowCache::output_flow(std::fstream& stream, const Flow& flow, const struct timeval& now) const
+template<typename StreamType>
+void FlowCache::output_flow(StreamType& stream, const Flow& flow, const struct timeval& now) const
 {
     char src_ip[INET6_ADDRSTRLEN];
     src_ip[0] = 0;
@@ -1226,4 +1230,16 @@ size_t FlowCache::count_flows_in_lru(uint8_t lru_index) const
 }
 #endif
 
+inline void FlowCache::log_flow_release(const snort::Flow* flow, PruneReason reason) const
+{
+    PacketTracerUnsuspend pt_unsusp;
+
+    std::stringstream temp_stream;
+    struct timeval now;
 
+    packet_gettimeofday(&now);
+    output_flow(temp_stream, *flow, now);
+    std::string flow_info = temp_stream.str();
+
+    PacketTracer::log("Flow: Releasing flow due to %s: %s", prune_reason_to_string(reason), flow_info.c_str());
+}
index 572d808a99dcb7ffd1f76256292e5171b2d8f8af..f57755357c454054241728b854eab3918cb71bf0 100644 (file)
@@ -180,7 +180,8 @@ public:
     bool move_to_allowlist(snort::Flow* f);
 
     virtual bool filter_flows(const snort::Flow&, const FilterFlowCriteria&) const;
-    virtual void output_flow(std::fstream&, const snort::Flow&, const struct timeval&) const;
+    template<typename StreamType>
+    void output_flow(StreamType&, const snort::Flow&, const struct timeval&) const;
 
     unsigned get_flows_allocated() const;
 
@@ -229,6 +230,8 @@ private:
         empty_lru_masks |= lru_mask;
     }
 
+    inline void log_flow_release(const snort::Flow* flow, PruneReason reason) const;
+
 private:
     uint8_t timeout_idx;
     static const unsigned cleanup_flows = 1;
index 544d6d7d93a2f428379a16972a77368275b8455b..ba722004386deabf07392e03a67dacef77ce69e6 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <cstdint>
 #include <type_traits>
+#include <array>
 
 #include "framework/counts.h"
 
@@ -33,12 +34,26 @@ enum class PruneReason : uint8_t
     MEMCAP,
     HA,
     STALE,
-       IDLE_MAX_FLOWS,
-       IDLE_PROTOCOL_TIMEOUT,
+    IDLE_MAX_FLOWS,
+    IDLE_PROTOCOL_TIMEOUT,
+    STREAM_CLOSED,
+    END_OF_FLOW,
     NONE,
     MAX
 };
 
+inline const char* prune_reason_to_string(PruneReason reason)
+{
+    static constexpr const char* names[] = {
+        "EXCESS", "UNI", "MEMCAP", "HA", "STALE",
+        "IDLE_MAX_FLOWS", "IDLE_PROTOCOL_TIMEOUT", 
+        "STREAM_CLOSED", "EOF", "NONE"
+    };
+    
+    auto idx = static_cast<uint8_t>(reason);
+    return (idx < static_cast<uint8_t>(PruneReason::MAX)) ? names[idx] : "UNKNOWN";
+}
+
 struct LRUPruneStats
 {
     using lru_t = std::underlying_type_t<LRUType>;
index 679f7ab6e5a199380ef44d548ce68a59e62bd3d1..9ecd8edf1adfdb7d2ad5c139631966a5d8c9c63b 100644 (file)
@@ -151,7 +151,6 @@ class DummyCache : public FlowCache
     public:
         DummyCache(const FlowCacheConfig& cfg) : FlowCache(cfg) {}
         ~DummyCache() = default;
-        void output_flow(std::fstream& stream, const Flow& flow, const struct timeval& now) const override { (void)stream, (void)flow, (void)now; };
         bool filter_flows(const Flow& flow, const FilterFlowCriteria& ffc) const override { (void)flow; (void)ffc; return true; };
 };
 
@@ -160,7 +159,6 @@ class DummyCacheWithFilter : public FlowCache
     public:
         DummyCacheWithFilter(const FlowCacheConfig& cfg) : FlowCache(cfg) {}
         ~DummyCacheWithFilter() = default;
-        void output_flow(std::fstream& stream, const Flow& flow, const struct timeval& now) const override { (void)stream, (void)flow, (void)now; };
 };
 
 TEST_GROUP(flow_prune) { };
index 4a88e0fc3ecdbadee53ba4f0518e3483138979ee..c05301357114cf8865a7489a791d2f0538551560 100644 (file)
@@ -77,7 +77,6 @@ const SnortConfig* SnortConfig::get_conf() { return nullptr; }
 void FlowCache::unlink_uni(Flow*) { }
 bool FlowCache::dump_flows(std::fstream&, unsigned, const FilterFlowCriteria&, bool, uint8_t) const { return false; }
 bool FlowCache::dump_flows_summary(FlowsSummary&, const FilterFlowCriteria&) const { return false; }
-void FlowCache::output_flow(std::fstream&, const Flow&, const struct timeval& ) const { }
 bool FlowCache::filter_flows(const Flow&, const FilterFlowCriteria&) const { return true; };
 void Flow::set_client_initiate(Packet*) { }
 void Flow::set_direction(Packet*) { }
index d929409c3beee2cdccad84034c423e2e77e7b89d..46d42877c019bad6957ef7b049b81cdc818f56cd 100644 (file)
@@ -57,6 +57,7 @@ void PacketTracer::dump_to_daq(Packet*) { }
 void PacketTracer::reset(bool) { }
 void PacketTracer::pause() { }
 void PacketTracer::unpause() { }
+bool PacketTracer::is_paused() { return true; }
 bool PacketTracer::is_active() { return false; }
 
 namespace layer
index 2db50f59b4642da9c5d038449583e8a863b4be2b..f67905fe15a06de2c7491f77fac5de142dad8399 100644 (file)
@@ -132,6 +132,26 @@ void PacketTracer::thread_init()
 
 void PacketTracer::thread_term()
 {
+    if ( s_pkt_trace and s_pkt_trace->buff_len > 0 
+        and ( s_pkt_trace->user_enabled or s_pkt_trace->shell_enabled ) )
+    {
+        if ( !snort::SnortConfig::get_conf()->use_log_buffered() ) 
+            LogMessage(s_pkt_trace->log_fh, "%s\n", s_pkt_trace->buffer);
+        else
+        {
+            if ( s_pkt_trace->log_fh and s_pkt_trace->log_fh != stdout )
+            {
+                fprintf(s_pkt_trace->log_fh, "%.*s\n", s_pkt_trace->buff_len, s_pkt_trace->buffer);
+                fflush(s_pkt_trace->log_fh);
+            }
+            else
+            {
+                BatchedLogger::BatchedLogManager::log(s_pkt_trace->log_fh, SnortConfig::log_syslog(),
+                    s_pkt_trace->buffer, s_pkt_trace->buff_len);
+            }
+        }
+    }
+
     BatchedLogger::BatchedLogManager::flush_thread_buffers();
     delete s_pkt_trace;
     s_pkt_trace = nullptr;
index eb1422aa42e91169233bb7277cb5aa4844a35f00..961039c4eca8be2ae5dc55464fa96ef90ffdc9b0 100644 (file)
@@ -135,5 +135,25 @@ struct PacketTracerSuspend
     { PacketTracer::unpause(); }
 };
 
+struct PacketTracerUnsuspend
+{
+    unsigned saved_pause_count = 0;
+
+    PacketTracerUnsuspend()
+    {
+        while (PacketTracer::is_paused())
+        {
+            PacketTracer::unpause();
+            saved_pause_count++;
+        }
+    }
+
+    ~PacketTracerUnsuspend() noexcept
+    {
+        for (unsigned i = 0; i < saved_pause_count; i++)
+            PacketTracer::pause();
+    }
+};
+
 }
 #endif
index eb27ffb1a585198f2ebd56593152d07089cab3c9..df706971ff3f4869d5e46916dfcc4804b2537d84 100644 (file)
@@ -70,6 +70,7 @@ const PegInfo base_pegs[] =
     { CountType::SUM, "memcap_prunes", "sessions pruned due to memcap" },
     { CountType::SUM, "ha_prunes", "sessions pruned by high availability sync" },
     { CountType::SUM, "stale_prunes", "sessions pruned due to stale connection" },
+    { CountType::SUM, "closed_prunes", "sessions pruned due to stream closed" },
     { CountType::SUM, "expected_flows", "total expected flows created within snort" },
     { CountType::SUM, "expected_realized", "number of expected flows realized" },
     { CountType::SUM, "expected_pruned", "number of expected flows pruned" },
@@ -97,6 +98,14 @@ const PegInfo base_pegs[] =
     { CountType::SUM, "file_memcap_prunes", "number of FILE flows pruned due to memcap" },
     { CountType::SUM, "pdu_memcap_prunes", "number of PDU flows pruned due to memcap" },
     { CountType::SUM, "allowlist_memcap_prunes", "number of allowlist flows pruned due to memcap" },
+    { CountType::SUM, "ip_eof_prunes", "number of IP flows pruned due to EOF" },
+    { CountType::SUM, "tcp_eof_prunes", "number of TCP flows pruned due to EOF" },
+    { CountType::SUM, "udp_eof_prunes", "number of UDP flows pruned due to EOF" },
+    { CountType::SUM, "icmp_eof_prunes", "number of ICMP flows pruned due to EOF" },
+    { CountType::SUM, "user_eof_prunes", "number of USER flows pruned due to EOF" },
+    { CountType::SUM, "file_eof_prunes", "number of FILE flows pruned due to EOF" },
+    { CountType::SUM, "pdu_eof_prunes", "number of PDU flows pruned due to EOF" },
+    { CountType::SUM, "allowlist_eof_prunes", "number of allowlist flows pruned due to EOF" },
     { CountType::SUM, "excess_to_allowlist", "number of flows moved to the allowlist due to excess" },
 
     // Keep the NOW stats at the bottom as it requires special sum_stats logic
@@ -124,6 +133,7 @@ void base_prep()
     stream_base_stats.memcap_prunes = flow_con->get_prunes(PruneReason::MEMCAP);
     stream_base_stats.ha_prunes = flow_con->get_prunes(PruneReason::HA);
     stream_base_stats.stale_prunes = flow_con->get_prunes(PruneReason::STALE);
+    stream_base_stats.closed_prunes = flow_con->get_prunes(PruneReason::STREAM_CLOSED);
     stream_base_stats.reload_freelist_flow_deletes = flow_con->get_deletes(FlowDeleteState::FREELIST);
     stream_base_stats.reload_allowed_flow_deletes = flow_con->get_deletes(FlowDeleteState::ALLOWED);
     stream_base_stats.reload_offloaded_flow_deletes= flow_con->get_deletes(FlowDeleteState::OFFLOADED);
@@ -143,6 +153,14 @@ void base_prep()
     stream_base_stats.file_memcap_prunes = flow_con->get_proto_prune_count(PruneReason::MEMCAP, PktType::FILE);
     stream_base_stats.pdu_memcap_prunes = flow_con->get_proto_prune_count(PruneReason::MEMCAP, PktType::PDU);
     stream_base_stats.allowlist_memcap_prunes = flow_con->get_proto_prune_count(PruneReason::MEMCAP, static_cast<PktType>(allowlist_lru_index));
+    stream_base_stats.ip_eof_prunes = flow_con->get_proto_prune_count(PruneReason::END_OF_FLOW, PktType::IP);
+    stream_base_stats.tcp_eof_prunes = flow_con->get_proto_prune_count(PruneReason::END_OF_FLOW, PktType::TCP);
+    stream_base_stats.udp_eof_prunes = flow_con->get_proto_prune_count(PruneReason::END_OF_FLOW, PktType::UDP);
+    stream_base_stats.icmp_eof_prunes = flow_con->get_proto_prune_count(PruneReason::END_OF_FLOW, PktType::ICMP);
+    stream_base_stats.user_eof_prunes = flow_con->get_proto_prune_count(PruneReason::END_OF_FLOW, PktType::USER);
+    stream_base_stats.file_eof_prunes = flow_con->get_proto_prune_count(PruneReason::END_OF_FLOW, PktType::FILE);
+    stream_base_stats.pdu_eof_prunes = flow_con->get_proto_prune_count(PruneReason::END_OF_FLOW, PktType::PDU);
+    stream_base_stats.allowlist_eof_prunes = flow_con->get_proto_prune_count(PruneReason::END_OF_FLOW, static_cast<PktType>(allowlist_lru_index));
     stream_base_stats.excess_to_allowlist = flow_con->get_excess_to_allowlist_count();
 
     stream_base_stats.allowlist_flows = flow_con->get_allowlist_flow_count();
index 208eda2d4190cd38dc13a36da911773596384783..89dd2552e94bd582c42587f035fdde2599f80290 100644 (file)
@@ -77,6 +77,7 @@ struct BaseStats
      PegCount memcap_prunes;
      PegCount ha_prunes;
      PegCount stale_prunes;
+     PegCount closed_prunes;
      PegCount expected_flows;
      PegCount expected_realized;
      PegCount expected_pruned;
@@ -104,6 +105,14 @@ struct BaseStats
      PegCount file_memcap_prunes;
      PegCount pdu_memcap_prunes;
      PegCount allowlist_memcap_prunes;
+     PegCount ip_eof_prunes;
+     PegCount tcp_eof_prunes;
+     PegCount udp_eof_prunes;
+     PegCount icmp_eof_prunes;
+     PegCount user_eof_prunes;
+     PegCount file_eof_prunes;
+     PegCount pdu_eof_prunes;
+     PegCount allowlist_eof_prunes;
      PegCount excess_to_allowlist;
 
      // Keep the NOW stats at the bottom as it requires special sum_stats logic
index 625e2c60fa290efa657d2568d276bde568a69607..3d88004a77b9d34c561d676ef3db304ac5936b95 100644 (file)
@@ -81,8 +81,8 @@ Flow* Stream::new_flow(const FlowKey* key)
 void Stream::delete_flow(const FlowKey* key)
 { flow_con->release_flow(key); }
 
-void Stream::delete_flow(Flow* flow)
-{ flow_con->release_flow(flow, PruneReason::NONE); }
+void Stream::delete_flow(Flow* flow, PruneReason reason)
+{ flow_con->release_flow(flow, reason); }
 
 //-------------------------------------------------------------------------
 // key foo
@@ -194,7 +194,7 @@ void Stream::check_flow_closed(Packet* p)
         // eventually all onloads will occur and delete will be called
         if ( not flow->is_suspended() )
         {
-            flow_con->release_flow(flow, PruneReason::NONE);
+            flow_con->release_flow(flow, PruneReason::STREAM_CLOSED);
             return;
         }
     }
index f8c40eb471e02bc2d568541ebdabdd7250fa5cad..77e714e3d11648c19e24ee102ecbb7c6b8a385b5 100644 (file)
@@ -29,6 +29,7 @@
 #include <daq_common.h>
 
 #include "flow/flow.h"
+#include "flow/prune_stats.h"
 #include "main/policy.h"
 #include "protocols/packet.h"
 #include "time/packet_time.h"
@@ -113,6 +114,7 @@ public:
     // the resources allocated to that flow to the free list.
     static void delete_flow(const FlowKey*);
     static void delete_flow(Flow*);
+    static void delete_flow(Flow*, PruneReason reason = PruneReason::NONE);
 
     // Examines the source and destination ip addresses and ports to determine if the
     // packet is from the client or server side of the flow and sets bits in the
index f3302b512554249260b919055cbbd2d1161c2038..b17431f3d8956fcab6d525544f2edd6fd405b9ff 100644 (file)
 
 #endif
 
+#ifdef __GNUC__
+#define UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+#define UNLIKELY(x) (x)
+#endif
+
+
 #define TIMEBUF_SIZE 27
 
 #define SECONDS_PER_DAY  86400  /* number of seconds in a day  */