From f09a0ca8ca855ba8e19e48625f549bf93e6cea82 Mon Sep 17 00:00:00 2001 From: "Masud Hasan (mashasan)" Date: Fri, 27 May 2022 19:36:44 +0000 Subject: [PATCH] Pull request #3439: netflow: Enforcing memcap for record and template LRU caches Merge in SNORT/snort3 from ~MASHASAN/snort3:netflow_memcap to master Squashed commit of the following: commit bc2f0391d2011a359c8c1b238e222b305cd60db3 Author: Masud Hasan Date: Thu May 26 23:51:59 2022 -0400 host_tracker: Renaming generic files and classes commit bf7c31fd580de06f7c8311cd7e1fc3c91b7c5f4e Author: Masud Hasan Date: Wed May 18 14:50:13 2022 -0400 netflow: Enforcing memcap for session record and template LRU caches --- src/hash/CMakeLists.txt | 1 + src/hash/lru_cache_local.h | 169 ++++++++++++++++++ src/hash/test/CMakeLists.txt | 4 + src/hash/test/lru_cache_local_test.cc | 78 ++++++++ src/host_tracker/CMakeLists.txt | 6 +- ..._cache_allocator.cc => cache_allocator.cc} | 6 +- ...st_cache_allocator.h => cache_allocator.h} | 26 +-- ...st_cache_interface.h => cache_interface.h} | 8 +- src/host_tracker/dev_notes.txt | 4 +- src/host_tracker/host_cache.h | 7 +- src/host_tracker/host_tracker.cc | 2 +- src/host_tracker/host_tracker.h | 3 +- src/host_tracker/host_tracker_module.cc | 3 +- src/host_tracker/host_tracker_module.h | 2 +- src/host_tracker/test/CMakeLists.txt | 2 +- ...ocator_test.cc => cache_allocator_test.cc} | 12 +- .../test/host_cache_allocator_ht_test.cc | 2 +- src/host_tracker/test/host_tracker_test.cc | 2 +- .../appid/lua_detector_api.cc | 4 +- src/network_inspectors/rna/rna_mac_cache.h | 8 +- src/service_inspectors/netflow/CMakeLists.txt | 1 + src/service_inspectors/netflow/netflow.cc | 165 ++++++----------- .../netflow/netflow_cache.cc | 46 +++++ .../netflow/netflow_cache.h | 147 +++++++++++++++ .../netflow/netflow_module.cc | 13 +- .../netflow/netflow_module.h | 5 +- 26 files changed, 571 insertions(+), 155 deletions(-) create mode 100644 src/hash/lru_cache_local.h create mode 100644 src/hash/test/lru_cache_local_test.cc rename src/host_tracker/{host_cache_allocator.cc => cache_allocator.cc} (90%) rename src/host_tracker/{host_cache_allocator.h => cache_allocator.h} (75%) rename src/host_tracker/{host_cache_interface.h => cache_interface.h} (93%) rename src/host_tracker/test/{host_cache_allocator_test.cc => cache_allocator_test.cc} (91%) create mode 100644 src/service_inspectors/netflow/netflow_cache.cc create mode 100644 src/service_inspectors/netflow/netflow_cache.h diff --git a/src/hash/CMakeLists.txt b/src/hash/CMakeLists.txt index 029a77ef9..72605f005 100644 --- a/src/hash/CMakeLists.txt +++ b/src/hash/CMakeLists.txt @@ -4,6 +4,7 @@ set (HASH_INCLUDES hashes.h hash_defs.h hash_key_operations.h + lru_cache_local.h lru_cache_shared.h xhash.h ) diff --git a/src/hash/lru_cache_local.h b/src/hash/lru_cache_local.h new file mode 100644 index 000000000..52efbdf39 --- /dev/null +++ b/src/hash/lru_cache_local.h @@ -0,0 +1,169 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2022-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// lru_cache_local.h author Masud Hasan + +#ifndef LRU_CACHE_LOCAL_H +#define LRU_CACHE_LOCAL_H + +// LruCacheLocal - A simple thread-unsafe memcap-enforced least-recently-used cache. + +#include +#include +#include + +#include "framework/counts.h" + +#define LRU_CACHE_LOCAL_PEGS(module) \ + { CountType::SUM, "cache_adds", module " cache added new entry" }, \ + { CountType::SUM, "cache_hits", module " cache found existing entry" }, \ + { CountType::SUM, "cache_misses", module " cache did not find entry" }, \ + { CountType::SUM, "cache_replaces", module " cache found entry and replaced its value" }, \ + { CountType::SUM, "cache_max", module " cache's maximum byte usage"}, \ + { CountType::SUM, "cache_prunes", module " cache pruned entry to make space for new entry" } + +struct LruCacheLocalStats +{ + PegCount cache_adds; + PegCount cache_hits; + PegCount cache_misses; + PegCount cache_replaces; + PegCount cache_max; + PegCount cache_prunes; +}; + +template +class LruCacheLocal +{ +public: + LruCacheLocal(const size_t sz, struct LruCacheLocalStats& st) + : max_size(sz), current_size(0), stats(st) { } + + virtual ~LruCacheLocal() = default; + + // Return 1 if an entry associated with the key exists, else return 0 + int count(const Key&); + + // Return the entry associated with the key; insert new entry if absent + Value& find_else_create(const Key&, bool* is_new = nullptr); + + // If key does not exist, insert the key-value pair and return true; + // else return false replacing the existing value if asked + bool add(const Key&, const Value&, bool replace = false); + + // Copy all key-value pairs from the cache + void get_all_values(std::vector>&); + +protected: + using LruList = std::list>; + using LruListIter = typename LruList::iterator; + using LruMap = std::unordered_map; + using LruMapIter = typename LruMap::iterator; + + void prune(); + void add_entry(const Key&, const Value&); + + static constexpr size_t entry_size = 2 * sizeof(Key) + sizeof(Value) + sizeof(LruListIter); + const size_t max_size; + size_t current_size; + LruList list; + LruMap map; + struct LruCacheLocalStats& stats; +}; + +template +void LruCacheLocal::prune() +{ + if ( !max_size ) + return; + + while ( current_size > max_size and !list.empty() ) + { + auto it = --list.end(); + map.erase(it->first); + list.erase(it); + current_size -= entry_size; + ++stats.cache_prunes; + } +} + +template +int LruCacheLocal::count(const Key& key) +{ + return map.count(key); +} + +template +void LruCacheLocal::add_entry(const Key& key, const Value& value) +{ + stats.cache_adds++; + list.emplace_front(std::make_pair(key, value)); + map[key] = list.begin(); + current_size += entry_size; + prune(); + if ( stats.cache_max < current_size ) + stats.cache_max = current_size; +} + +template +Value& LruCacheLocal::find_else_create(const Key& key, bool* is_new) +{ + LruMapIter it = map.find(key); + if (it == map.end()) + { + stats.cache_misses++; + add_entry(key, Value()); + if ( is_new ) + *is_new = true; + return list.begin()->second; + } + + stats.cache_hits++; + list.splice(list.begin(), list, it->second); + return list.begin()->second; +} + +template +bool LruCacheLocal::add(const Key& key, const Value& value, bool replace) +{ + LruMapIter it = map.find(key); + if (it == map.end()) + { + stats.cache_misses++; + add_entry(key, value); + return true; + } + + stats.cache_hits++; + list.splice(list.begin(), list, it->second); + if ( replace ) + { + it->second->second = value; + stats.cache_replaces++; + } + return false; +} + +template +void LruCacheLocal::get_all_values(std::vector>& kv) +{ + for (auto& entry : list ) + kv.emplace_back(entry); +} + +#endif diff --git a/src/hash/test/CMakeLists.txt b/src/hash/test/CMakeLists.txt index 20e176d8b..d140fc3d9 100644 --- a/src/hash/test/CMakeLists.txt +++ b/src/hash/test/CMakeLists.txt @@ -1,3 +1,7 @@ +add_cpputest( lru_cache_local_test + SOURCES ../lru_cache_local.h +) + add_cpputest( lru_cache_shared_test SOURCES ../lru_cache_shared.cc ) diff --git a/src/hash/test/lru_cache_local_test.cc b/src/hash/test/lru_cache_local_test.cc new file mode 100644 index 000000000..b66ed9cd4 --- /dev/null +++ b/src/hash/test/lru_cache_local_test.cc @@ -0,0 +1,78 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2022-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// lru_cache_local.h author Masud Hasan + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "hash/lru_cache_local.h" + +#include +#include + +TEST_GROUP(lru_cache_local) +{ +}; + +TEST(lru_cache_local, basic) +{ + LruCacheLocalStats stats; + LruCacheLocal> lru_cache(40, stats); + + // Check pruning in LRU order; memcap = 40 bytes would hold 2 entries + int key = 1; + int val = 100; + lru_cache.add(key, val); + + key = 2; + val = 200; + lru_cache.add(key, val); + + key = 3; + val = 300; + lru_cache.add(key, val); + + // Check entries are stored in LRU order indeed + std::vector> vec; + lru_cache.get_all_values(vec); + CHECK(vec.size() == 2); + CHECK(vec[0].first == 3 and vec[0].second == 300); + CHECK(vec[1].first == 2 and vec[1].second == 200); + + // Check non-existent entry; find_else_create() would return a new entry + auto& entry = lru_cache.find_else_create(4); + entry = 400; + // Subsequent calls would return the same one + CHECK(lru_cache.find_else_create(4) == 400); + entry = 4000; + CHECK(lru_cache.find_else_create(4) == 4000); + + // The cache is changed after the above calls + vec.clear(); + lru_cache.get_all_values(vec); + CHECK(vec.size() == 2); + CHECK(vec[0].first == 4 and vec[0].second == 4000); + CHECK(vec[1].first == 3 and vec[1].second == 300); +} + +int main(int argc, char** argv) +{ + return CommandLineTestRunner::RunAllTests(argc, argv); +} diff --git a/src/host_tracker/CMakeLists.txt b/src/host_tracker/CMakeLists.txt index 94fe83580..56dfa6806 100644 --- a/src/host_tracker/CMakeLists.txt +++ b/src/host_tracker/CMakeLists.txt @@ -1,14 +1,14 @@ set (HOST_TRACKER_INCLUDES + cache_allocator.h + cache_interface.h host_cache.h - host_cache_allocator.h - host_cache_interface.h host_tracker.h ) add_library( host_tracker OBJECT ${HOST_TRACKER_INCLUDES} + cache_allocator.cc host_cache.cc - host_cache_allocator.cc host_cache_module.cc host_cache_module.h host_tracker_module.cc diff --git a/src/host_tracker/host_cache_allocator.cc b/src/host_tracker/cache_allocator.cc similarity index 90% rename from src/host_tracker/host_cache_allocator.cc rename to src/host_tracker/cache_allocator.cc index 6f752981a..ca50c5c1b 100644 --- a/src/host_tracker/host_cache_allocator.cc +++ b/src/host_tracker/cache_allocator.cc @@ -16,14 +16,14 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// host_cache_allocator.cc author Silviu Minut +// cache_allocator.cc author Silviu Minut #ifdef HAVE_CONFIG_H #include "config.h" #endif -#ifndef HOST_CACHE_ALLOCATOR_CC -#define HOST_CACHE_ALLOCATOR_CC +#ifndef CACHE_ALLOCATOR_CC +#define CACHE_ALLOCATOR_CC #include "host_cache.h" diff --git a/src/host_tracker/host_cache_allocator.h b/src/host_tracker/cache_allocator.h similarity index 75% rename from src/host_tracker/host_cache_allocator.h rename to src/host_tracker/cache_allocator.h index a14219c3c..d1f5be4c0 100644 --- a/src/host_tracker/host_cache_allocator.h +++ b/src/host_tracker/cache_allocator.h @@ -16,24 +16,24 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// host_cache_allocator.h author Silviu Minut +// cache_allocator.h author Silviu Minut -#ifndef HOST_CACHE_ALLOCATOR_H -#define HOST_CACHE_ALLOCATOR_H +#ifndef CACHE_ALLOCATOR_H +#define CACHE_ALLOCATOR_H #include -#include "host_cache_interface.h" +#include "cache_interface.h" template -class HostCacheAlloc : public std::allocator +class CacheAlloc : public std::allocator { public: template struct rebind { - typedef HostCacheAlloc other; + typedef CacheAlloc other; }; T* allocate(std::size_t n); @@ -41,11 +41,11 @@ public: protected: - HostCacheInterface* lru = nullptr; + CacheInterface* lru = nullptr; }; template -T* HostCacheAlloc::allocate(std::size_t n) +T* CacheAlloc::allocate(std::size_t n) { size_t sz = n * sizeof(T); T* out = std::allocator::allocate(n); @@ -54,7 +54,7 @@ T* HostCacheAlloc::allocate(std::size_t n) } template -void HostCacheAlloc::deallocate(T* p, std::size_t n) noexcept +void CacheAlloc::deallocate(T* p, std::size_t n) noexcept { size_t sz = n * sizeof(T); std::allocator::deallocate(p, n); @@ -63,13 +63,13 @@ void HostCacheAlloc::deallocate(T* p, std::size_t n) noexcept // Trivial derived allocator, pointing to their own host cache. -// HostCacheAllocIp has a HostCacheInterface* pointing to an lru cache +// HostCacheAllocIp has a CacheInterface* pointing to an lru cache // instantiated using snort::SfIp as the key. See host_cache.h. // We can create different cache types by instantiating the lru cache using -// different keys and derive here allocators with HostCacheInterface* +// different keys and derive here allocators with CacheInterface* // pointing to the appropriate lru cache object. template -class HostCacheAllocIp : public HostCacheAlloc +class HostCacheAllocIp : public CacheAlloc { public: @@ -80,7 +80,7 @@ public: typedef HostCacheAllocIp other; }; - using HostCacheAlloc::lru; + using CacheAlloc::lru; HostCacheAllocIp(); diff --git a/src/host_tracker/host_cache_interface.h b/src/host_tracker/cache_interface.h similarity index 93% rename from src/host_tracker/host_cache_interface.h rename to src/host_tracker/cache_interface.h index b57136efb..43df92770 100644 --- a/src/host_tracker/host_cache_interface.h +++ b/src/host_tracker/cache_interface.h @@ -16,12 +16,12 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// host_cache_interface.h author Silviu Minut +// cache_interface.h author Silviu Minut -#ifndef HOST_CACHE_INTERFACE_H -#define HOST_CACHE_INTERFACE_H +#ifndef CACHE_INTERFACE_H +#define CACHE_INTERFACE_H -class HostCacheInterface +class CacheInterface { public: diff --git a/src/host_tracker/dev_notes.txt b/src/host_tracker/dev_notes.txt index 3e438462b..1a9a85361 100644 --- a/src/host_tracker/dev_notes.txt +++ b/src/host_tracker/dev_notes.txt @@ -125,9 +125,9 @@ a single .h file. However, to break this dependency, we have to split the Allocator into a .h and a .cc file. We include the .h file in HostTracker and declare HostCache extern only in the .cc file. Then, we have to include the .cc file also in the HostTracker implementation file because Allocator -is templated. See host_cache.h, host_cache_allocator.h, host_cache_allocator.cc, +is templated. See host_cache.h, cache_allocator.h, cache_allocator.cc, host_tracker.h and host_tracker.cc. -Illustrative examples are test/host_cache_allocator_test.cc (standalone +Illustrative examples are test/cache_allocator_test.cc (standalone host cache / allocator example) and test/host_cache_allocator_ht_test.cc (host_cache / allocator with host tracker example). diff --git a/src/host_tracker/host_cache.h b/src/host_tracker/host_cache.h index 0d3b51a8c..2ecc8ac70 100644 --- a/src/host_tracker/host_cache.h +++ b/src/host_tracker/host_cache.h @@ -27,14 +27,15 @@ #include #include "hash/lru_cache_shared.h" -#include "host_cache_interface.h" -#include "host_cache_allocator.h" #include "host_tracker.h" #include "log/messages.h" #include "main/snort_config.h" #include "sfip/sf_ip.h" #include "utils/stats.h" +#include "cache_allocator.h" +#include "cache_interface.h" + // Used to create hash of key for indexing into cache. // // Note that both HashIp and IpEqualTo below ignore the IP family. @@ -61,7 +62,7 @@ struct IpEqualTo template, typename Purgatory = std::vector>> class LruCacheSharedMemcap : public LruCacheShared, - public HostCacheInterface + public CacheInterface { public: using LruBase = LruCacheShared; diff --git a/src/host_tracker/host_tracker.cc b/src/host_tracker/host_tracker.cc index cc48bf6cf..4bd4ac6fe 100644 --- a/src/host_tracker/host_tracker.cc +++ b/src/host_tracker/host_tracker.cc @@ -28,8 +28,8 @@ #include "network_inspectors/rna/rna_flow.h" #include "utils/util.h" +#include "cache_allocator.cc" #include "host_cache.h" -#include "host_cache_allocator.cc" #include "host_tracker.h" using namespace snort; diff --git a/src/host_tracker/host_tracker.h b/src/host_tracker/host_tracker.h index 9c7f22d68..b58e9fccf 100644 --- a/src/host_tracker/host_tracker.h +++ b/src/host_tracker/host_tracker.h @@ -33,7 +33,6 @@ #include #include "framework/counts.h" -#include "host_cache_allocator.h" #include "main/snort_types.h" #include "main/thread.h" #include "network_inspectors/appid/application_ids.h" @@ -41,6 +40,8 @@ #include "protocols/vlan.h" #include "time/packet_time.h" +#include "cache_allocator.h" + struct HostTrackerStats { PegCount service_adds; diff --git a/src/host_tracker/host_tracker_module.cc b/src/host_tracker/host_tracker_module.cc index 7a1bbef6f..1de162e3c 100644 --- a/src/host_tracker/host_tracker_module.cc +++ b/src/host_tracker/host_tracker_module.cc @@ -23,11 +23,12 @@ #endif #include "host_tracker_module.h" -#include "host_cache_allocator.cc" #include "log/messages.h" #include "main/snort_config.h" +#include "cache_allocator.cc" + using namespace snort; const PegInfo host_tracker_pegs[] = diff --git a/src/host_tracker/host_tracker_module.h b/src/host_tracker/host_tracker_module.h index 992eea50b..9e338d6a4 100644 --- a/src/host_tracker/host_tracker_module.h +++ b/src/host_tracker/host_tracker_module.h @@ -30,8 +30,8 @@ #include #include "framework/module.h" +#include "host_tracker/cache_allocator.cc" #include "host_tracker/host_cache.h" -#include "host_tracker/host_cache_allocator.cc" #define host_tracker_help \ "configure hosts" diff --git a/src/host_tracker/test/CMakeLists.txt b/src/host_tracker/test/CMakeLists.txt index 8e91de0b4..41bc7a93d 100644 --- a/src/host_tracker/test/CMakeLists.txt +++ b/src/host_tracker/test/CMakeLists.txt @@ -52,7 +52,7 @@ add_cpputest( host_cache_allocator_ht_test ../../sfip/sf_ip.cc ) -add_cpputest( host_cache_allocator_test +add_cpputest( cache_allocator_test SOURCES ../host_tracker.cc ../../network_inspectors/rna/test/rna_flow_stubs.cc diff --git a/src/host_tracker/test/host_cache_allocator_test.cc b/src/host_tracker/test/cache_allocator_test.cc similarity index 91% rename from src/host_tracker/test/host_cache_allocator_test.cc rename to src/host_tracker/test/cache_allocator_test.cc index 312f73891..9ad6872a1 100644 --- a/src/host_tracker/test/host_cache_allocator_test.cc +++ b/src/host_tracker/test/cache_allocator_test.cc @@ -16,14 +16,14 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// host_cache_allocator_test.cc author Silviu Minut +// cache_allocator_test.cc author Silviu Minut #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "host_tracker/host_cache.h" -#include "host_tracker/host_cache_allocator.cc" +#include "host_tracker/cache_allocator.cc" #include "network_inspectors/rna/rna_flow.h" #include @@ -38,9 +38,9 @@ HostCacheIp host_cache(100); using namespace std; using namespace snort; -// Derive an allocator from HostCacheAlloc: +// Derive an allocator from CacheAlloc: template -class Allocator : public HostCacheAlloc +class Allocator : public CacheAlloc { public: @@ -51,7 +51,7 @@ public: typedef Allocator other; }; - using HostCacheAlloc::lru; + using CacheAlloc::lru; Allocator(); }; @@ -70,7 +70,7 @@ typedef LruCacheSharedMemcap> CacheType; CacheType cache(100); // Implement the allocator constructor AFTER we have a cache object -// to point to and the implementation of our base HostCacheAlloc: +// to point to and the implementation of our base CacheAlloc: template Allocator::Allocator() { diff --git a/src/host_tracker/test/host_cache_allocator_ht_test.cc b/src/host_tracker/test/host_cache_allocator_ht_test.cc index 846283169..f9df18b88 100644 --- a/src/host_tracker/test/host_cache_allocator_ht_test.cc +++ b/src/host_tracker/test/host_cache_allocator_ht_test.cc @@ -23,8 +23,8 @@ #include "config.h" #endif +#include "host_tracker/cache_allocator.cc" #include "host_tracker/host_cache.h" -#include "host_tracker/host_cache_allocator.cc" #include "network_inspectors/rna/rna_flow.h" #include diff --git a/src/host_tracker/test/host_tracker_test.cc b/src/host_tracker/test/host_tracker_test.cc index 7afe66229..2a6f31b5a 100644 --- a/src/host_tracker/test/host_tracker_test.cc +++ b/src/host_tracker/test/host_tracker_test.cc @@ -25,8 +25,8 @@ #include +#include "host_tracker/cache_allocator.cc" #include "host_tracker/host_cache.h" -#include "host_tracker/host_cache_allocator.cc" #include "network_inspectors/rna/rna_flow.h" #include diff --git a/src/network_inspectors/appid/lua_detector_api.cc b/src/network_inspectors/appid/lua_detector_api.cc index 7780d7136..d8dd5d685 100644 --- a/src/network_inspectors/appid/lua_detector_api.cc +++ b/src/network_inspectors/appid/lua_detector_api.cc @@ -28,6 +28,8 @@ #include #include +#include "host_tracker/cache_allocator.cc" +#include "host_tracker/host_cache.h" #include "log/messages.h" #include "main/snort_debug.h" #include "main/snort_types.h" @@ -50,8 +52,6 @@ #include "lua_detector_util.h" #include "service_plugins/service_discovery.h" #include "service_plugins/service_ssl.h" -#include "host_tracker/host_cache.h" -#include "host_tracker/host_cache_allocator.cc" using namespace snort; using namespace std; diff --git a/src/network_inspectors/rna/rna_mac_cache.h b/src/network_inspectors/rna/rna_mac_cache.h index 55ae54eb2..ef14720c3 100644 --- a/src/network_inspectors/rna/rna_mac_cache.h +++ b/src/network_inspectors/rna/rna_mac_cache.h @@ -23,13 +23,13 @@ #include "hash/hash_key_operations.h" // Non-standard, required to include template definition across compiler translation units -#include "host_tracker/host_cache_allocator.cc" -#include "host_tracker/host_cache_allocator.h" +#include "host_tracker/cache_allocator.cc" +#include "host_tracker/cache_allocator.h" #define MAC_CACHE_INITIAL_SIZE 1024 * 1024 // Default to 1 MB template -class HostCacheAllocMac : public HostCacheAlloc +class HostCacheAllocMac : public CacheAlloc { public: template @@ -38,7 +38,7 @@ public: typedef HostCacheAllocMac other; }; - using HostCacheAlloc::lru; + using CacheAlloc::lru; HostCacheAllocMac(); }; diff --git a/src/service_inspectors/netflow/CMakeLists.txt b/src/service_inspectors/netflow/CMakeLists.txt index 11e6c5fb2..a4e0531f3 100644 --- a/src/service_inspectors/netflow/CMakeLists.txt +++ b/src/service_inspectors/netflow/CMakeLists.txt @@ -1,5 +1,6 @@ set ( FILE_LIST + netflow_cache.h netflow_headers.h netflow_module.cc netflow_module.h diff --git a/src/service_inspectors/netflow/netflow.cc b/src/service_inspectors/netflow/netflow.cc index de7e1cfb1..7ab33dd41 100644 --- a/src/service_inspectors/netflow/netflow.cc +++ b/src/service_inspectors/netflow/netflow.cc @@ -23,9 +23,6 @@ #include "config.h" #endif -#include "netflow_headers.h" -#include "netflow_module.h" - #include #include #include @@ -36,22 +33,16 @@ #include "profiler/profiler.h" #include "protocols/packet.h" #include "pub_sub/netflow_event.h" -#include "sfip/sf_ip.h" #include "src/utils/endian.h" #include "utils/util.h" +#include "netflow_cache.cc" + using namespace snort; THREAD_LOCAL NetflowStats netflow_stats; THREAD_LOCAL ProfileStats netflow_perf_stats; -// compare struct to use with ip sort -struct IpCompare -{ - bool operator()(const snort::SfIp& a, const snort::SfIp& b) - { return a.less_than(b); } -}; - // Used to ensure we fully populate the record; can't rely on the actual values being zero struct RecordStatus { @@ -69,22 +60,22 @@ struct RecordStatus // static variables // ----------------------------------------------------------------------------- -// Used to avoid creating multiple events for the same initiator IP. -// Cache can be thread local since Netflow packets coming from a Netflow -// device will go to the same thread. -typedef std::unordered_map NetflowCache; -static THREAD_LOCAL NetflowCache* netflow_cache = nullptr; +// temporary cache required to dump the output +typedef std::pair IpRecord; +typedef std::vector DumpCache; +static DumpCache* dump_cache = nullptr; -// cache required to dump the output -static NetflowCache* dump_cache = nullptr; - -// Netflow version 9 Template fields cache. -typedef std::unordered_map, std::vector, TemplateIpHash> TemplateFieldCache; -static THREAD_LOCAL TemplateFieldCache* template_cache = nullptr; +// compare struct to use with ip sort +struct IpCompare +{ + bool operator()(const IpRecord& a, const IpRecord& b) + { return a.first.less_than(b.first); } +}; // ----------------------------------------------------------------------------- // static functions // ----------------------------------------------------------------------------- + static bool filter_record(const NetflowRules* rules, const int zone, const SfIp* src, const SfIp* dst) { @@ -111,16 +102,16 @@ static bool filter_record(const NetflowRules* rules, const int zone, } static bool version_9_record_update(const unsigned char* data, uint32_t unix_secs, - std::vector::iterator field, NetflowSessionRecord &record, + uint16_t field_type, uint16_t field_length, NetflowSessionRecord &record, RecordStatus& record_status) { - switch ( field->field_type ) + switch ( field_type ) { case NETFLOW_PROTOCOL: // invalid protocol - if( field->field_length != sizeof(record.proto) ) + if( field_length != sizeof(record.proto) ) return false; record.proto = (uint8_t)*data; @@ -129,7 +120,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_TCP_FLAGS: // invalid tcp flags - if( field->field_length != sizeof(record.tcp_flags ) ) + if( field_length != sizeof(record.tcp_flags ) ) return false; record.tcp_flags = (uint8_t)*data; @@ -138,7 +129,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_SRC_PORT: // invalid src port - if( field->field_length != sizeof(record.initiator_port) ) + if( field_length != sizeof(record.initiator_port) ) return false; record.initiator_port = ntohs(*(const uint16_t*) data); @@ -147,7 +138,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_SRC_IP: // invalid source ip - if( field->field_length != sizeof(uint32_t) ) + if( field_length != sizeof(uint32_t) ) return false; // Invalid source IP address provided @@ -169,7 +160,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_DST_PORT: // invalid destination port - if( field->field_length != sizeof(record.responder_port) ) + if( field_length != sizeof(record.responder_port) ) return false; record.responder_port = ntohs(*(const uint16_t*) data); @@ -178,7 +169,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_DST_IP: // invalid length - if( field->field_length != sizeof(uint32_t) ) + if( field_length != sizeof(uint32_t) ) return false; // Invalid destination IP address @@ -200,7 +191,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_IPV4_NEXT_HOP: // invalid length - if( field->field_length != sizeof(uint32_t) ) + if( field_length != sizeof(uint32_t) ) return false; // Invalid next-hop IP address @@ -210,7 +201,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_LAST_PKT: - if( field->field_length != sizeof(record.last_pkt_second) ) + if( field_length != sizeof(record.last_pkt_second) ) return false; record.last_pkt_second = unix_secs + ntohl(*(const time_t*)data)/1000; @@ -224,7 +215,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_FIRST_PKT: - if( field->field_length != sizeof(record.first_pkt_second) ) + if( field_length != sizeof(record.first_pkt_second) ) return false; record.first_pkt_second = unix_secs + ntohl(*(const time_t*)data)/1000; @@ -238,11 +229,11 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_IN_BYTES: - if ( field->field_length == sizeof(uint64_t) ) + if ( field_length == sizeof(uint64_t) ) record.initiator_bytes = ntohll(*(const uint64_t*)data); - else if ( field->field_length == sizeof(uint32_t) ) + else if ( field_length == sizeof(uint32_t) ) record.initiator_bytes = (uint64_t)ntohl(*(const uint32_t*)data); - else if ( field->field_length == sizeof(uint16_t) ) + else if ( field_length == sizeof(uint16_t) ) record.initiator_bytes = (uint64_t)ntohs(*(const uint16_t*) data); else return false; @@ -252,11 +243,11 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_IN_PKTS: - if ( field->field_length == sizeof(uint64_t) ) + if ( field_length == sizeof(uint64_t) ) record.initiator_pkts = ntohll(*(const uint64_t*)data); - else if ( field->field_length == sizeof(uint32_t) ) + else if ( field_length == sizeof(uint32_t) ) record.initiator_pkts = (uint64_t)ntohl(*(const uint32_t*)data); - else if ( field->field_length == sizeof(uint16_t) ) + else if ( field_length == sizeof(uint16_t) ) record.initiator_pkts = (uint64_t)ntohs(*(const uint16_t*) data); else return false; @@ -266,7 +257,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_SRC_TOS: - if( field->field_length != sizeof(record.nf_src_tos) ) + if( field_length != sizeof(record.nf_src_tos) ) return false; record.nf_src_tos = (uint8_t)*data; @@ -275,7 +266,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_DST_TOS: - if( field->field_length != sizeof(record.nf_dst_tos)) + if( field_length != sizeof(record.nf_dst_tos)) return false; record.nf_dst_tos = (uint8_t)*data; @@ -284,9 +275,9 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_SNMP_IN: - if ( field->field_length == sizeof(uint32_t) ) + if ( field_length == sizeof(uint32_t) ) record.nf_snmp_in = ntohl(*(const uint32_t*)data); - else if ( field->field_length == sizeof(uint16_t) ) + else if ( field_length == sizeof(uint16_t) ) record.nf_snmp_in = (uint32_t)ntohs(*(const uint16_t*) data); else return false; @@ -295,9 +286,9 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_SNMP_OUT: - if ( field->field_length == sizeof(uint32_t) ) + if ( field_length == sizeof(uint32_t) ) record.nf_snmp_out = ntohl(*(const uint32_t*)data); - else if ( field->field_length == sizeof(uint16_t) ) + else if ( field_length == sizeof(uint16_t) ) record.nf_snmp_out = (uint32_t)ntohs(*(const uint16_t*) data); else return false; @@ -306,9 +297,9 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_SRC_AS: - if( field->field_length == sizeof(uint16_t) ) + if( field_length == sizeof(uint16_t) ) record.nf_src_as = (uint32_t)ntohs(*(const uint16_t*) data); - else if( field->field_length == sizeof(uint32_t) ) + else if( field_length == sizeof(uint32_t) ) record.nf_src_as = ntohl(*(const uint32_t*)data); else return false; @@ -316,9 +307,9 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_DST_AS: - if( field->field_length == sizeof(uint16_t) ) + if( field_length == sizeof(uint16_t) ) record.nf_dst_as = (uint32_t)ntohs(*(const uint16_t*) data); - else if( field->field_length == sizeof(uint32_t) ) + else if( field_length == sizeof(uint32_t) ) record.nf_dst_as = ntohl(*(const uint32_t*)data); else return false; @@ -327,7 +318,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_SRC_MASK: case NETFLOW_SRC_MASK_IPV6: - if( field->field_length != sizeof(record.nf_src_mask) ) + if( field_length != sizeof(record.nf_src_mask) ) return false; record.nf_src_mask = (uint8_t)*data; @@ -336,7 +327,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec case NETFLOW_DST_MASK: case NETFLOW_DST_MASK_IPV6: - if( field->field_length != sizeof(record.nf_dst_mask) ) + if( field_length != sizeof(record.nf_dst_mask) ) return false; record.nf_dst_mask = (uint8_t)*data; @@ -413,8 +404,7 @@ static bool decode_netflow_v9(const unsigned char* data, uint16_t size, // It's a data flowset if ( f_id > 255 && template_cache->count(ti_key) > 0 ) { - std::vector tf; - tf = template_cache->at(ti_key); + auto& tf = template_cache->find_else_create(ti_key); while( data < flowset_end && records ) { @@ -432,7 +422,7 @@ static bool decode_netflow_v9(const unsigned char* data, uint16_t size, if ( !bad_field ) { bool status = version_9_record_update(data, header.unix_secs, - t_field, record, record_status); + t_field->field_type, t_field->field_length, record, record_status); if ( !status ) bad_field = true; @@ -474,19 +464,8 @@ static bool decode_netflow_v9(const unsigned char* data, uint16_t size, DataBus::publish(NETFLOW_EVENT, event); } - // check if record exists - auto result = netflow_cache->find(record.initiator_ip); - - if ( result != netflow_cache->end() ) - { - // record exists and hence first remove the element - netflow_cache->erase(record.initiator_ip); - --netflow_stats.unique_flows; - } - - // emplace doesn't replace element if exist, hence removing it first - netflow_cache->emplace(record.initiator_ip, record); - ++netflow_stats.unique_flows; + if ( netflow_cache->add(record.initiator_ip, record, true) ) + ++netflow_stats.unique_flows; records--; } @@ -527,20 +506,8 @@ static bool decode_netflow_v9(const unsigned char* data, uint16_t size, if ( field_count > 0 ) { - auto t_key = std::make_pair(t_id, device_ip); - - // remove if there any entry exists for this template - auto is_erased = template_cache->erase(t_key); - - // count only unique templates - if ( is_erased == 1 ) - --netflow_stats.v9_templates; - - // add template to cache - template_cache->emplace(t_key, tf); - - // update the total templates count - ++netflow_stats.v9_templates; + if ( template_cache->insert(std::make_pair(t_id, device_ip), tf) ) + ++netflow_stats.v9_templates; // don't count template as record netflow_stats.records--; @@ -660,11 +627,7 @@ static bool decode_netflow_v5(const unsigned char* data, uint16_t size, record.nf_src_mask = precord->src_mask; record.nf_dst_mask = precord->dst_mask; - // insert record - auto result = netflow_cache->emplace(record.initiator_ip, record); - - // new unique record - if ( result.second ) + if ( netflow_cache->add(record.initiator_ip, record, false) ) ++netflow_stats.unique_flows; // send create_host and create_service flags too so that rna event handler can log those @@ -789,6 +752,8 @@ static void show_device(const NetflowRule& d, bool is_exclude) void NetflowInspector::show(const SnortConfig*) const { + ConfigLogger::log_value("flow_memcap", config->flow_memcap); + ConfigLogger::log_value("template_memcap", config->template_memcap); ConfigLogger::log_value("dump_file", config->dump_file); ConfigLogger::log_value("update_timeout", config->update_timeout); bool log_header = true; @@ -816,29 +781,21 @@ void NetflowInspector::show(const SnortConfig*) const void NetflowInspector::stringify(std::ofstream& file_stream) { - std::vector keys; - keys.reserve(dump_cache->size()); - - for (const auto& elem : *dump_cache) - keys.push_back(elem.first); - - std::sort(keys.begin(),keys.end(), IpCompare()); + std::sort(dump_cache->begin(), dump_cache->end(), IpCompare()); std::string str; SfIpString ip_str; uint32_t i = 0; - auto& cache = *dump_cache; - - for (auto elem : keys) + for (auto& elem : *dump_cache) { - NetflowSessionRecord& record = cache[elem]; + NetflowSessionRecord& record = elem.second; str = "Netflow Record #"; str += std::to_string(++i); str += "\n"; str += " Initiator IP (Port): "; - str += elem.ntop(ip_str); + str += elem.first.ntop(ip_str); str += " (" + std::to_string(record.initiator_port) + ")"; str += " -> Responder IP (Port): "; @@ -911,7 +868,7 @@ NetflowInspector::NetflowInspector(const NetflowConfig* pc) { // create dump cache if ( ! dump_cache ) - dump_cache = new NetflowCache; + dump_cache = new DumpCache; } } @@ -958,11 +915,10 @@ void NetflowInspector::eval(Packet* p) void NetflowInspector::tinit() { if ( !netflow_cache ) - netflow_cache = new NetflowCache; + netflow_cache = new NetflowCache(config->flow_memcap, netflow_stats); if ( !template_cache ) - template_cache = new TemplateFieldCache; - + template_cache = new TemplateFieldCache(config->template_memcap, netflow_stats); } void NetflowInspector::tterm() @@ -971,10 +927,7 @@ void NetflowInspector::tterm() { static std::mutex stats_mutex; std::lock_guard lock(stats_mutex); - { - // insert each cache - dump_cache->insert(netflow_cache->begin(), netflow_cache->end()); - } + netflow_cache->get_all_values(*dump_cache); } delete netflow_cache; delete template_cache; diff --git a/src/service_inspectors/netflow/netflow_cache.cc b/src/service_inspectors/netflow/netflow_cache.cc new file mode 100644 index 000000000..3a21fc068 --- /dev/null +++ b/src/service_inspectors/netflow/netflow_cache.cc @@ -0,0 +1,46 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2022-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// netflow_cache.cc author Masud Hasan + +#ifndef NETFLOW_CACHE_CC +#define NETFLOW_CACHE_CC + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "netflow_cache.h" + +THREAD_LOCAL NetflowCache* netflow_cache = nullptr; + +template +LruCacheAllocNetflow::LruCacheAllocNetflow() +{ + lru = netflow_cache; +} + +THREAD_LOCAL TemplateFieldCache* template_cache = nullptr; + +template +LruCacheAllocTemplate::LruCacheAllocTemplate() +{ + lru = template_cache; +} + +#endif diff --git a/src/service_inspectors/netflow/netflow_cache.h b/src/service_inspectors/netflow/netflow_cache.h new file mode 100644 index 000000000..02f3ebae6 --- /dev/null +++ b/src/service_inspectors/netflow/netflow_cache.h @@ -0,0 +1,147 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2022-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// netflow_cache.h author Masud Hasan + +#ifndef NETFLOW_CACHE_H +#define NETFLOW_CACHE_H + +#include + +#include "hash/lru_cache_local.h" +#include "host_tracker/cache_allocator.h" +#include "sfip/sf_ip.h" + +#include "netflow_headers.h" +#include "netflow_module.h" + +// Trivial derived allocator, pointing to their own cache. LruCacheAllocNetflow has a +// CacheInterface* pointing to an lru cache. We can create different cache types by +// instantiating the lru cache using different keys and derive here allocators with +// CacheInterface* pointing to the appropriate lru cache object. +template +class LruCacheAllocNetflow : public CacheAlloc +{ +public: + // This needs to be in every derived class: + template + struct rebind + { + typedef LruCacheAllocNetflow other; + }; + + using CacheAlloc::lru; + LruCacheAllocNetflow(); +}; + +template +class LruCacheLocalNetflow : public LruCacheLocal, public CacheInterface +{ +public: + using LruLocal = LruCacheLocal; + using LruLocal::current_size; + using LruLocal::max_size; + using LruLocal::list; + + LruCacheLocalNetflow(const size_t sz, struct LruCacheLocalStats& st) : LruLocal(sz, st) {} + + template + friend class LruCacheAllocNetflow; + +private: + // Only the allocator calls this + void update(int size) override + { + if ( size < 0 ) + assert( current_size >= (size_t) -size); + + // Checking 1+ size prevents crash if max_size is too low to hold even a single entry + if ( (current_size += size) > max_size and list.size() > 1 ) + LruLocal::prune(); + } +}; + +template +class LruCacheAllocTemplate : public CacheAlloc +{ +public: + template + struct rebind + { + typedef LruCacheAllocTemplate other; + }; + + using CacheAlloc::lru; + LruCacheAllocTemplate(); +}; + +template +class LruCacheLocalTemplate : public LruCacheLocal, public CacheInterface +{ +public: + using LruLocal = LruCacheLocal; + using LruLocal::current_size; + using LruLocal::max_size; + using LruLocal::stats; + using LruLocal::list; + + LruCacheLocalTemplate(const size_t sz, struct LruCacheLocalStats& st) : LruLocal(sz, st) + {} + + bool insert(const Key& key, std::vector& tf) + { + bool is_new = false; + Value& entry = LruLocal::find_else_create(key, &is_new); + + if ( !is_new ) + { + stats.cache_replaces++; + entry.clear(); + } + + for ( auto& elem : tf ) + entry.emplace_back(elem.field_type, elem.field_length); + + return is_new; + } + + template + friend class LruCacheAllocTemplate; + +private: + void update(int size) override + { + if ( size < 0 ) + assert( current_size >= (size_t) -size); + + if ( (current_size += size) > max_size and list.size() > 1 ) + LruLocal::prune(); + } +}; + +// Used to track record for unique IP; we assume Netflow packets coming from +// a given Netflow device will go to the same thread +typedef LruCacheLocalNetflow NetflowCache; + +// Used to track Netflow version 9 Template fields +typedef std::pair TemplateFieldKey; +typedef LruCacheAllocTemplate TemplateAllocator; +typedef std::vector TemplateFieldValue; +typedef LruCacheLocalTemplate TemplateFieldCache; + +#endif diff --git a/src/service_inspectors/netflow/netflow_module.cc b/src/service_inspectors/netflow/netflow_module.cc index e01412dfd..06908db3b 100644 --- a/src/service_inspectors/netflow/netflow_module.cc +++ b/src/service_inspectors/netflow/netflow_module.cc @@ -65,11 +65,18 @@ static const Parameter netflow_params[] = { "rules", Parameter::PT_LIST, device_rule_params, nullptr, "list of NetFlow device rules" }, + { "flow_memcap", Parameter::PT_INT, "0:maxSZ", "0", + "maximum memory for flow record cache in bytes, 0 = unlimited" }, + + { "template_memcap", Parameter::PT_INT, "0:maxSZ", "0", + "maximum memory for template cache in bytes, 0 = unlimited" }, + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } }; static const PegInfo netflow_pegs[] = { + LRU_CACHE_LOCAL_PEGS("netflow"), { CountType::SUM, "invalid_netflow_record", "count of invalid netflow records" }, { CountType::SUM, "packets", "total packets processed" }, { CountType::SUM, "records", "total records found in netflow data" }, @@ -137,7 +144,11 @@ bool NetflowModule::end(const char* fqn, int idx, SnortConfig*) } bool NetflowModule::set(const char*, Value& v, SnortConfig*) { - if ( v.is("dump_file") ) + if ( v.is("flow_memcap") ) + conf->flow_memcap = v.get_size(); + else if ( v.is("template_memcap") ) + conf->template_memcap = v.get_size(); + else if ( v.is("dump_file") ) { if ( conf->dump_file ) snort_free((void*)conf->dump_file); diff --git a/src/service_inspectors/netflow/netflow_module.h b/src/service_inspectors/netflow/netflow_module.h index e8aa38662..15011d65e 100644 --- a/src/service_inspectors/netflow/netflow_module.h +++ b/src/service_inspectors/netflow/netflow_module.h @@ -25,6 +25,7 @@ #include #include "framework/module.h" +#include "hash/lru_cache_local.h" #include "sfip/sf_cidr.h" #include "utils/util.h" @@ -120,9 +121,11 @@ struct NetflowConfig const char* dump_file = nullptr; std::unordered_map device_rule_map; uint32_t update_timeout = 0; + size_t flow_memcap = 0; + size_t template_memcap = 0; }; -struct NetflowStats +struct NetflowStats : public LruCacheLocalStats { PegCount invalid_netflow_record; PegCount packets; -- 2.47.3