From: Mike Stepanek (mstepane) Date: Mon, 4 Nov 2019 13:59:47 +0000 (-0500) Subject: Merge pull request #1807 in SNORT/snort3 from ~DAVMCPHE/snort3:stream_reload_memcap... X-Git-Tag: 3.0.0-264~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d126c051c33b4204cc162fdc87bc158769f8579d;p=thirdparty%2Fsnort3.git Merge pull request #1807 in SNORT/snort3 from ~DAVMCPHE/snort3:stream_reload_memcap to master Squashed commit of the following: commit b127a8a89a00336480bdf9cfb6c196c8db8d93ca Author: davis mcpherson Date: Tue Aug 20 11:40:34 2019 -0400 stream: implement reload resource tuner for stream to adjust the number of flow objects as needed when the stream 'max_flows' configuration option changes --- diff --git a/src/flow/expect_cache.cc b/src/flow/expect_cache.cc index 73e12bb69..c2c8831a9 100644 --- a/src/flow/expect_cache.cc +++ b/src/flow/expect_cache.cc @@ -140,7 +140,7 @@ void ExpectCache::prune() break; node->clear(free_list); - hash_table->remove(); + hash_table->release(); ++prunes; } } @@ -206,7 +206,7 @@ ExpectNode* ExpectCache::find_node_by_packet(Packet* p, FlowKey &key) { if (node->head) node->clear(free_list); - hash_table->remove(&key); + hash_table->release(&key); return nullptr; } /* Make sure the packet direction is correct */ @@ -257,7 +257,7 @@ bool ExpectCache::process_expected(ExpectNode* node, FlowKey& key, Packet* p, Fl lws->ssn_state.snort_protocol_id = node->snort_protocol_id; if (!node->count) - hash_table->remove(&key); + hash_table->release(&key); return ignoring; } diff --git a/src/flow/expect_cache.h b/src/flow/expect_cache.h index ca50478e4..ada51c69b 100644 --- a/src/flow/expect_cache.h +++ b/src/flow/expect_cache.h @@ -120,7 +120,8 @@ private: private: class ZHash* hash_table; ExpectNode* nodes; - snort::ExpectFlow* pool, * free_list; + snort::ExpectFlow* pool; + snort::ExpectFlow* free_list; unsigned long expects, realized; unsigned long prunes, overflows; diff --git a/src/flow/flow_cache.cc b/src/flow/flow_cache.cc index 785668c49..9ff56697b 100644 --- a/src/flow/flow_cache.cc +++ b/src/flow/flow_cache.cc @@ -42,6 +42,10 @@ using namespace snort; #define SESSION_CACHE_FLAG_PURGING 0x01 +static const unsigned ALLOWED_FLOWS_ONLY = 1; +static const unsigned OFFLOADED_FLOWS_TOO = 2; +static const unsigned ALL_FLOWS = 3; + //------------------------------------------------------------------------- // FlowCache stuff //------------------------------------------------------------------------- @@ -120,7 +124,7 @@ Flow* FlowCache::allocate(const FlowKey* key) { if ( flows_allocated < config.max_flows ) { - Flow* new_flow = new Flow; + Flow* new_flow = new Flow(); push(new_flow); } else if ( !prune_stale(timestamp, nullptr) ) @@ -148,35 +152,31 @@ Flow* FlowCache::allocate(const FlowKey* key) return flow; } -int FlowCache::release(Flow* flow, PruneReason reason, bool do_cleanup) -{ - flow->reset(do_cleanup); - prune_stats.update(reason); - return remove(flow); -} - -int FlowCache::remove(Flow* flow) +void FlowCache::remove(Flow* flow) { if ( flow->next ) unlink_uni(flow); - bool deleted = hash_table->remove(flow->key); - // FIXIT-M This check is added for offload case where both Flow::reset // and Flow::retire try remove the flow from hash. Flow::reset should // just mark the flow as pending instead of trying to remove it. - if ( deleted ) + if ( hash_table->release(flow->key) ) memory::MemoryCap::update_deallocations(config.proto[to_utype(flow->key->pkt_type)].cap_weight); +} - return deleted; +void FlowCache::release(Flow* flow, PruneReason reason, bool do_cleanup) +{ + flow->reset(do_cleanup); + prune_stats.update(reason); + remove(flow); } -int FlowCache::retire(Flow* flow) +void FlowCache::retire(Flow* flow) { flow->reset(true); flow->term(); prune_stats.update(PruneReason::NONE); - return remove(flow); + remove(flow); } unsigned FlowCache::prune_stale(uint32_t thetime, const Flow* save_me) @@ -201,11 +201,7 @@ unsigned FlowCache::prune_stale(uint32_t thetime, const Flow* save_me) #else // Reached the current flow. This *should* be the newest flow if ( flow == save_me ) - { - // assert( flow->last_data_seen + config.pruning_timeout >= thetime ); - // bool rv = hash_table->touch(); assert( !rv ); break; - } #endif if ( flow->is_suspended() ) break; @@ -307,7 +303,6 @@ unsigned FlowCache::prune_excess(const Flow* save_me) bool FlowCache::prune_one(PruneReason reason, bool do_cleanup) { - // so we don't prune the current flow (assume current == MRU) if ( hash_table->get_count() <= 1 ) return false; @@ -360,6 +355,70 @@ unsigned FlowCache::timeout(unsigned num_flows, time_t thetime) return retired; } +unsigned FlowCache::delete_active_flows(unsigned mode, unsigned num_to_delete, unsigned &deleted) +{ + unsigned flows_to_check = hash_table->get_count(); + while ( num_to_delete && flows_to_check-- ) + { + auto flow = static_cast(hash_table->first()); + assert(flow); + if ( (mode == ALLOWED_FLOWS_ONLY and (flow->was_blocked() || flow->is_suspended())) + or (mode == OFFLOADED_FLOWS_TOO and flow->was_blocked()) ) + { + if (!hash_table->touch()) + break; + + continue; + } + + // we have a winner... + hash_table->remove(flow->key); + if ( flow->next ) + unlink_uni(flow); + + if ( flow->was_blocked() ) + delete_stats.update(FlowDeleteState::BLOCKED); + else if ( flow->is_suspended() ) + delete_stats.update(FlowDeleteState::OFFLOADED); + else + delete_stats.update(FlowDeleteState::ALLOWED); + + delete flow; + --flows_allocated; + ++deleted; + --num_to_delete; + } + + return num_to_delete; +} + +unsigned FlowCache::delete_flows(unsigned num_to_delete) +{ + ActiveSuspendContext act_susp; + + unsigned deleted = 0; + + // delete from the free list first... + while ( num_to_delete ) + { + Flow* flow = (Flow*)hash_table->pop(); + if ( !flow ) + break; + + delete flow; + delete_stats.update(FlowDeleteState::FREELIST); + --flows_allocated; + ++deleted; + --num_to_delete; + } + + unsigned mode = ALLOWED_FLOWS_ONLY; + while ( num_to_delete && mode <= ALL_FLOWS ) + num_to_delete = delete_active_flows(mode++, num_to_delete, deleted); + + return deleted; +} + // Remove all flows from the hash table. unsigned FlowCache::purge() { @@ -375,7 +434,10 @@ unsigned FlowCache::purge() } while ( Flow* flow = (Flow*)hash_table->pop() ) + { delete flow; + --flows_allocated; + } return retired; } diff --git a/src/flow/flow_cache.h b/src/flow/flow_cache.h index 2a37d99e2..bbd6793cd 100644 --- a/src/flow/flow_cache.h +++ b/src/flow/flow_cache.h @@ -28,6 +28,8 @@ #include #include +#include "framework/counts.h" + #include "flow_config.h" #include "prune_stats.h" @@ -51,12 +53,15 @@ public: snort::Flow* find(const snort::FlowKey*); snort::Flow* allocate(const snort::FlowKey*); - int release(snort::Flow*, PruneReason = PruneReason::NONE, bool do_cleanup = true); + void release(snort::Flow*, PruneReason = PruneReason::NONE, bool do_cleanup = true); + unsigned prune_stale(uint32_t thetime, const snort::Flow* save_me); unsigned prune_excess(const snort::Flow* save_me); bool prune_one(PruneReason, bool do_cleanup); unsigned timeout(unsigned num_flows, time_t cur_time); + unsigned delete_flows(unsigned num_to_delete); + unsigned purge(); unsigned get_count(); @@ -69,21 +74,37 @@ public: PegCount get_prunes(PruneReason reason) const { return prune_stats.get(reason); } + PegCount get_total_deletes() const + { return delete_stats.get_total(); } + + PegCount get_deletes(FlowDeleteState state) const + { return delete_stats.get(state); } + void reset_stats() - { prune_stats = PruneStats(); } + { + prune_stats = PruneStats(); + delete_stats = FlowDeleteStats(); + } void unlink_uni(snort::Flow*); + void set_flow_cache_config(FlowCacheConfig& cfg) { config = cfg; } + + unsigned get_flows_allocated() const + { return flows_allocated; } + private: void push(snort::Flow*); void link_uni(snort::Flow*); - int remove(snort::Flow*); - int retire(snort::Flow*); + void remove(snort::Flow*); + void retire(snort::Flow*); unsigned prune_unis(PktType); + unsigned delete_active_flows + (unsigned mode, unsigned num_to_delete, unsigned &deleted); private: static const unsigned cleanup_flows = 1; - const FlowCacheConfig config; + FlowCacheConfig config; uint32_t flags; class ZHash* hash_table; @@ -92,6 +113,7 @@ private: FlowUniList* uni_ip_flows; PruneStats prune_stats; + FlowDeleteStats delete_stats; }; #endif diff --git a/src/flow/flow_control.cc b/src/flow/flow_control.cc index 9a4b4477f..74d57b8bb 100644 --- a/src/flow/flow_control.cc +++ b/src/flow/flow_control.cc @@ -63,14 +63,16 @@ FlowControl::~FlowControl() //------------------------------------------------------------------------- PegCount FlowControl::get_total_prunes() const -{ - return cache->get_total_prunes(); -} +{ return cache->get_total_prunes(); } PegCount FlowControl::get_prunes(PruneReason reason) const -{ - return cache->get_prunes(reason); -} +{ return cache->get_prunes(reason); } + +PegCount FlowControl::get_total_deletes() const +{ return cache->get_total_deletes(); } + +PegCount FlowControl::get_deletes(FlowDeleteState state) const +{ return cache->get_deletes(state); } void FlowControl::clear_counts() { @@ -82,6 +84,9 @@ void FlowControl::clear_counts() // cache foo //------------------------------------------------------------------------- +unsigned FlowControl::get_flows_allocated() const +{ return cache->get_flows_allocated(); } + Flow* FlowControl::find_flow(const FlowKey* key) { return cache->find(key); @@ -92,13 +97,13 @@ Flow* FlowControl::new_flow(const FlowKey* key) return cache->allocate(key); } -void FlowControl::delete_flow(const FlowKey* key) +void FlowControl::release_flow(const FlowKey* key) { if ( auto flow = cache->find(key) ) cache->release(flow, PruneReason::HA); } -void FlowControl::delete_flow(Flow* flow, PruneReason reason) +void FlowControl::release_flow(Flow* flow, PruneReason reason) { cache->release(flow, reason); } @@ -108,6 +113,11 @@ void FlowControl::purge_flows () cache->purge(); } +unsigned FlowControl::delete_flows(unsigned num_to_delete) +{ + return cache->delete_flows(num_to_delete); +} + // hole for memory manager/prune handler bool FlowControl::prune_one(PruneReason reason, bool do_cleanup) { @@ -487,6 +497,11 @@ bool FlowControl::expected_flow(Flow* flow, Packet* p) return ignore; } +void FlowControl::update_flow_cache_cfg(FlowCacheConfig& cfg) +{ + cache->set_flow_cache_config(cfg); +} + int FlowControl::add_expected( const Packet* ctrlPkt, PktType type, IpProtocol ip_proto, const SfIp *srcIP, uint16_t srcPort, diff --git a/src/flow/flow_control.h b/src/flow/flow_control.h index 0bc1fd92d..750c09a4a 100644 --- a/src/flow/flow_control.h +++ b/src/flow/flow_control.h @@ -44,6 +44,7 @@ struct SfIp; class FlowCache; enum class PruneReason : uint8_t; +enum class FlowDeleteState : uint8_t; class FlowControl { @@ -56,25 +57,27 @@ public: snort::Flow* find_flow(const snort::FlowKey*); snort::Flow* new_flow(const snort::FlowKey*); + unsigned get_flows_allocated() const; void init_proto(PktType, snort::InspectSsnFunc); void init_exp(uint32_t max); - void delete_flow(const snort::FlowKey*); - void delete_flow(snort::Flow*, PruneReason); + void release_flow(const snort::FlowKey*); + void release_flow(snort::Flow*, PruneReason); void purge_flows(); + unsigned delete_flows(unsigned num_to_delete); bool prune_one(PruneReason, bool do_cleanup); snort::Flow* stale_flow_cleanup(FlowCache*, snort::Flow*, snort::Packet*); + void update_flow_cache_cfg(FlowCacheConfig& cfg); void timeout_flows(time_t cur_time); - bool expected_flow(snort::Flow*, snort::Packet*); bool is_expected(snort::Packet*); int add_expected( const snort::Packet* ctrlPkt, PktType, IpProtocol, const snort::SfIp *srcIP, uint16_t srcPort, - const snort::SfIp *dstIP, uint16_t dstPort, + const snort::SfIp *dstIP, uint16_t dstPort, char direction, snort::FlowData*); int add_expected( @@ -91,7 +94,8 @@ public: PegCount get_total_prunes() const; PegCount get_prunes(PruneReason) const; - + PegCount get_total_deletes() const; + PegCount get_deletes(FlowDeleteState state) const; void clear_counts(); private: diff --git a/src/flow/prune_stats.h b/src/flow/prune_stats.h index d31584b93..1015f4695 100644 --- a/src/flow/prune_stats.h +++ b/src/flow/prune_stats.h @@ -45,7 +45,14 @@ struct PruneStats PegCount prunes[static_cast(PruneReason::MAX)] { }; - PegCount get_total() const; + PegCount get_total() const + { + PegCount total = 0; + for ( reason_t i = 0; i < static_cast(PruneReason::NONE); ++i ) + total += prunes[i]; + + return total; + } PegCount& get(PruneReason reason) { return prunes[static_cast(reason)]; } @@ -57,14 +64,38 @@ struct PruneStats { ++get(reason); } }; -inline PegCount PruneStats::get_total() const +enum class FlowDeleteState : uint8_t +{ + FREELIST, + ALLOWED, + OFFLOADED, + BLOCKED, + MAX +}; + +struct FlowDeleteStats { - PegCount total = 0; - for ( reason_t i = 0; i < static_cast(PruneReason::NONE); ++i ) - total += prunes[i]; + using state_t = std::underlying_type::type; - return total; -} + PegCount deletes[static_cast(FlowDeleteState::MAX)] { }; + + PegCount get_total() const + { + PegCount total = 0; + for ( state_t i = 0; i < static_cast(PruneReason::MAX); ++i ) + total += deletes[i]; + + return total; + } + PegCount& get(FlowDeleteState state) + { return deletes[static_cast(state)]; } + + const PegCount& get(FlowDeleteState state) const + { return deletes[static_cast(state)]; } + + void update(FlowDeleteState state) + { ++get(state); } +}; #endif diff --git a/src/flow/test/CMakeLists.txt b/src/flow/test/CMakeLists.txt index 7d0f20ffb..2cb88afba 100644 --- a/src/flow/test/CMakeLists.txt +++ b/src/flow/test/CMakeLists.txt @@ -8,6 +8,16 @@ add_cpputest( flow_control_test SOURCES ../flow_control.cc ) +add_cpputest( flow_cache_test + SOURCES + ../flow_cache.cc + ../flow_control.cc + ../flow_key.cc + ../../hash/zhash.cc + ../../hash/hashfcn.cc + ../../hash/primetable.cc +) + add_cpputest( session_test ) add_cpputest( flow_test diff --git a/src/flow/test/flow_cache_test.cc b/src/flow/test/flow_cache_test.cc new file mode 100644 index 000000000..5ce730288 --- /dev/null +++ b/src/flow/test/flow_cache_test.cc @@ -0,0 +1,267 @@ + +// Copyright (C) 2019-2019 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. +//-------------------------------------------------------------------------- + +// flow_control_test.cc author Shivakrishna Mulka + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "flow/flow_control.h" + +#include "detection/detection_engine.h" +#include "main/snort_config.h" +#include "managers/inspector_manager.h" +#include "memory/memory_cap.h" +#include "packet_io/active.h" +#include "packet_tracer/packet_tracer.h" +#include "protocols/icmp4.h" +#include "protocols/packet.h" +#include "protocols/tcp.h" +#include "protocols/udp.h" +#include "protocols/vlan.h" +#include "stream/stream.h" +#include "utils/util.h" +#include "flow/expect_cache.h" +#include "flow/flow_cache.h" +#include "flow/ha.h" +#include "flow/session.h" + +#include +#include + +using namespace snort; + +THREAD_LOCAL bool Active::s_suspend = false; + +THREAD_LOCAL PacketTracer* snort::s_pkt_trace = nullptr; + +PacketTracer::PacketTracer() { } +PacketTracer::~PacketTracer() { } +void PacketTracer::log(const char* format, ...) { } +void PacketTracer::open_file() { } +void PacketTracer::dump_to_daq(Packet* p) { } +void PacketTracer::reset() { } +Packet::Packet(bool) { } +Packet::~Packet() { } +Flow::Flow() { memset(this, 0, sizeof(*this)); } +Flow::~Flow() { } +DetectionEngine::DetectionEngine() { } +ExpectCache::~ExpectCache() { } +DetectionEngine::~DetectionEngine() { } +void Flow::init(PktType type) { } +void Flow::term() { } +void Flow::reset(bool) { } +void set_network_policy(SnortConfig* sc, unsigned i) { } +void DataBus::publish(const char* key, const uint8_t* buf, unsigned len, Flow* f) { } +void DataBus::publish(const char* key, Packet* p, Flow* f) { } +SnortConfig* SnortConfig::get_conf() { return nullptr; } +void Flow::set_direction(Packet* p) { } +void set_inspection_policy(SnortConfig* sc, unsigned i) { } +void set_ips_policy(SnortConfig* sc, unsigned i) { } +void Flow::set_mpls_layer_per_dir(Packet* p) { } +void DetectionEngine::disable_all(Packet* p) { } +void Stream::drop_traffic(const Packet* p, char dir) { } +bool Stream::blocked_flow(Packet* p) { return true; } +ExpectCache::ExpectCache(uint32_t max) { } +bool ExpectCache::check(Packet* p, Flow* lws) { return true; } +bool ExpectCache::is_expected(Packet* p) { return true; } +Flow* HighAvailabilityManager::import(Packet& p, FlowKey& key) { return nullptr; } +bool HighAvailabilityManager::in_standby(Flow* flow) { return true; } +SfIpRet SfIp::set(void const*, int) { return SfIpRet::SFIP_SUCCESS; } +namespace memory +{ +void MemoryCap::update_allocations(unsigned long m) { } +void MemoryCap::update_deallocations(unsigned long m) { } +bool MemoryCap::over_threshold() { return true; } +} + +namespace snort +{ +namespace layer +{ +const vlan::VlanTagHdr* get_vlan_layer(const Packet* const p) { return nullptr; } +} +time_t packet_time() { return 0; } +} + +namespace snort +{ +namespace ip +{ +uint32_t IpApi::id() const { return 0; } +} +} + +void Stream::stop_inspection( + Flow* flow, Packet* p, char dir, + int32_t /*bytes*/, int /*response*/) { } + + +int ExpectCache::add_flow(const Packet *ctrlPkt, + PktType type, IpProtocol ip_proto, + const SfIp* cliIP, uint16_t cliPort, + const SfIp* srvIP, uint16_t srvPort, + char direction, FlowData* fd, SnortProtocolId snort_protocol_id) +{ + return 1; +} + +TEST_GROUP(flow_prune) { }; + +// No flows in the flow cache, pruning should not happen +TEST(flow_prune, empty_cache_prune_flows) +{ + FlowCacheConfig fcg; + fcg.max_flows = 3; + FlowCache *cache = new FlowCache(fcg); + + CHECK(cache->get_count() == 0); + CHECK(cache->delete_flows(1) == 0); + CHECK(cache->get_count() == 0); + delete cache; +} + +// Do not delete blocked flow +TEST(flow_prune, blocked_flow_prune_flows) +{ + FlowCacheConfig fcg; + fcg.max_flows = 2; + FlowCache *cache = new FlowCache(fcg); + + Flow *list_flows[fcg.max_flows]; + int first_port = 1; + int second_port = 2; + + // Add two flows in the flow cache + FlowKey flow_key; + memset(&flow_key, 0, sizeof(FlowKey)); + flow_key.pkt_type = PktType::TCP; + + flow_key.port_l = first_port; + list_flows[0] = cache->allocate(&flow_key); + + flow_key.port_l = second_port; + list_flows[1] = cache->allocate(&flow_key); + + CHECK(cache->get_count() == fcg.max_flows); + + // block the second flow + list_flows[1]->block(); + + // Access the first flow + // This will move it to the MRU + flow_key.port_l = first_port; + CHECK(cache->find(&flow_key) != nullptr); + + // Prune one flow. This should delete the MRU flow, since + // LRU flow is blocked + CHECK(cache->delete_flows(1) == 1); + + // Blocked Flow should still be there + flow_key.port_l = second_port; + CHECK(cache->find(&flow_key) != nullptr); + cache->purge(); + CHECK(cache->get_flows_allocated() == 0); + delete cache; +} + +// Add 3 flows in flow cache and delete one +TEST(flow_prune, prune_flows) +{ + FlowCacheConfig fcg; + fcg.max_flows = 3; + FlowCache *cache = new FlowCache(fcg); + int port = 1; + + Flow *list_flows[fcg.max_flows]; + for ( int i = 0; i < fcg.max_flows; i++ ) + { + FlowKey flow_key; + flow_key.port_l = port++; + flow_key.pkt_type = PktType::TCP; + list_flows[i] = cache->allocate(&flow_key); + } + + CHECK(cache->get_count() == fcg.max_flows); + CHECK(cache->delete_flows(1) == 1); + CHECK(cache->get_count() == fcg.max_flows-1); + cache->purge(); + CHECK(cache->get_flows_allocated() == 0); + delete cache; +} + + +// Add 3 flows in flow cache, delete all +TEST(flow_prune, prune_all_flows) +{ + FlowCacheConfig fcg; + fcg.max_flows = 3; + FlowCache *cache = new FlowCache(fcg); + int port = 1; + + Flow *list_flows[fcg.max_flows]; + for ( int i = 0; i < fcg.max_flows; i++ ) + { + FlowKey flow_key; + flow_key.port_l = port++; + flow_key.pkt_type = PktType::TCP; + list_flows[i] = cache->allocate(&flow_key); + } + + CHECK(cache->get_count() == fcg.max_flows); + CHECK(cache->delete_flows(3) == 3); + CHECK(cache->get_count() == 0); + cache->purge(); + CHECK(cache->get_flows_allocated() == 0); + delete cache; +} + +// Add 3 flows, all blocked, in flow cache, delete all +TEST(flow_prune, prune_all_blocked_flows) +{ + FlowCacheConfig fcg; + fcg.max_flows = 3; + FlowCache *cache = new FlowCache(fcg); + int port = 1; + + Flow *list_flows[fcg.max_flows]; + for ( int i = 0; i < fcg.max_flows; i++ ) + { + FlowKey flow_key; + flow_key.port_l = port++; + flow_key.pkt_type = PktType::TCP; + list_flows[i] = cache->allocate(&flow_key); + list_flows[i]->block(); + } + + CHECK(cache->get_count() == fcg.max_flows); + CHECK(cache->delete_flows(3) == 3); + CHECK(cache->get_count() == 0); + + cache->purge(); + CHECK(cache->get_flows_allocated() == 0); + delete cache; +} + +int main(int argc, char** argv) +{ + return CommandLineTestRunner::RunAllTests(argc, argv); +} diff --git a/src/flow/test/flow_control_test.cc b/src/flow/test/flow_control_test.cc index 5d51af823..5db1996f1 100644 --- a/src/flow/test/flow_control_test.cc +++ b/src/flow/test/flow_control_test.cc @@ -69,15 +69,16 @@ DetectionEngine::DetectionEngine() = default; DetectionEngine::~DetectionEngine() = default; ExpectCache::~ExpectCache() = default; unsigned FlowCache::purge() { return 1; } -Flow* FlowCache::find(const FlowKey*) { return nullptr; } +Flow* FlowCache::find(const FlowKey* key) { return nullptr; } Flow* FlowCache::allocate(const FlowKey*) { return nullptr; } -void FlowCache::push(Flow*) { } +void FlowCache::push(Flow* flow) { } bool FlowCache::prune_one(PruneReason, bool) { return true; } -unsigned FlowCache::timeout(unsigned, time_t) { return 1; } -void Flow::init(PktType) { } -void set_network_policy(SnortConfig*, unsigned) { } -void DataBus::publish(const char*, const uint8_t*, unsigned, Flow*) { } -void DataBus::publish(const char*, Packet*, Flow*) { } +unsigned FlowCache::delete_flows(unsigned) { return 0; } +unsigned FlowCache::timeout(unsigned num_flows, time_t thetime) { return 1; } +void Flow::init(PktType type) { } +void set_network_policy(SnortConfig* sc, unsigned i) { } +void DataBus::publish(const char* key, const uint8_t* buf, unsigned len, Flow* f) { } +void DataBus::publish(const char* key, Packet* p, Flow* f) { } SnortConfig* SnortConfig::get_conf() { return nullptr; } void FlowCache::unlink_uni(Flow*) { } void Flow::set_direction(Packet*) { } @@ -133,7 +134,6 @@ bool FlowKey::init( void Stream::stop_inspection(Flow*, Packet*, char, int32_t, int) { } - int ExpectCache::add_flow(const Packet*, PktType, IpProtocol, const SfIp*, uint16_t, @@ -143,10 +143,7 @@ int ExpectCache::add_flow(const Packet*, return 1; } -int FlowCache::release(Flow*, PruneReason, bool) -{ - return 1; -} +void FlowCache::release(Flow*, PruneReason, bool) { } TEST_GROUP(stale_flow) { }; diff --git a/src/hash/xhash.h b/src/hash/xhash.h index 0283b9f44..977f2f5ea 100644 --- a/src/hash/xhash.h +++ b/src/hash/xhash.h @@ -152,6 +152,7 @@ SO_PUBLIC int xhash_free_node(XHash* t, XHashNode* node); typedef uint32_t (* hash_func)(HashFnc*, const unsigned char* d, int n); + // return 0 for ==, 1 for != ; FIXIT-L convert to bool typedef int (* keycmp_func)(const void* s1, const void* s2, size_t n); diff --git a/src/hash/zhash.cc b/src/hash/zhash.cc index b22baacdf..b1b141c6c 100644 --- a/src/hash/zhash.cc +++ b/src/hash/zhash.cc @@ -151,14 +151,14 @@ void ZHash::link_node(ZHashNode* node) if ( table[node->rindex] ) // UNINITUSE { node->prev = nullptr; - node->next=table[node->rindex]; + node->next = table[node->rindex]; table[node->rindex]->prev = node; table[node->rindex] = node; } else { - node->prev=nullptr; - node->next=nullptr; + node->prev = nullptr; + node->next = nullptr; table[node->rindex] = node; // UNINITUSE } } @@ -272,9 +272,9 @@ ZHash::~ZHash() if ( table ) { - for ( unsigned i=0; i < nrows; ++i ) + for ( unsigned i = 0; i < nrows; ++i ) { - for ( ZHashNode* node=table[i]; node; ) + for ( ZHashNode* node = table[i]; node; ) { ZHashNode* onode = node; node = node->next; @@ -386,32 +386,48 @@ bool ZHash::touch() return false; } -bool ZHash::remove(ZHashNode* node) +bool ZHash::move_to_free_list(ZHashNode* node) { if ( !node ) return false; unlink_node(node); gunlink_node(node); - count--; save_free_node(node); return true; } -bool ZHash::remove() +bool ZHash::release() { ZHashNode* node = cursor; cursor = nullptr; - return remove(node); + return move_to_free_list(node); } -bool ZHash::remove(const void* key) +bool ZHash::release(const void* key) { int row; ZHashNode* node = find_node_row(key, row); - return remove(node); + return move_to_free_list(node); +} + +void* ZHash::remove(const void* key) +{ + void* pv = nullptr; + int row; + ZHashNode* node = find_node_row(key, row); + if ( node ) + { + unlink_node(node); + gunlink_node(node); + count--; + pv = node->data; + s_node_free(node); + } + + return pv; } int ZHash::set_keyops( @@ -423,4 +439,3 @@ int ZHash::set_keyops( return -1; } - diff --git a/src/hash/zhash.h b/src/hash/zhash.h index b6e5e05ca..607421705 100644 --- a/src/hash/zhash.h +++ b/src/hash/zhash.h @@ -44,12 +44,11 @@ public: void* find(const void* key); void* get(const void* key, bool *new_node = nullptr); - - bool remove(const void* key); - bool remove(); + bool release(const void* key); + bool release(); + void* remove(const void* key); inline unsigned get_count() { return count; } - int set_keyops( unsigned (* hash_fcn)(HashFnc* p, const unsigned char* d, int n), int (* keycmp_fcn)(const void* s1, const void* s2, size_t n)); @@ -67,14 +66,13 @@ private: void delete_free_list(); void save_free_node(ZHashNode*); - bool remove(ZHashNode*); + bool move_to_free_list(ZHashNode*); void move_to_front(ZHashNode*); int nearest_powerof2(int nrows); private: HashFnc* hashfcn; int keysize; - unsigned nrows; unsigned count; @@ -82,7 +80,8 @@ private: unsigned find_success; ZHashNode** table; - ZHashNode* ghead, * gtail; + ZHashNode* ghead; + ZHashNode* gtail; ZHashNode* fhead; ZHashNode* cursor; }; diff --git a/src/main/analyzer.h b/src/main/analyzer.h index a77f4d569..b15f4ade7 100644 --- a/src/main/analyzer.h +++ b/src/main/analyzer.h @@ -57,8 +57,8 @@ public: UncompletedAnalyzerCommand(snort::AnalyzerCommand* ac, void* acs) : command(ac), state(acs) { } - snort::AnalyzerCommand* command = nullptr; - void* state = nullptr; + snort::AnalyzerCommand* command; + void* state; }; class Analyzer @@ -134,14 +134,11 @@ public: private: std::atomic state; - unsigned id; bool exit_requested = false; - uint64_t exit_after_cnt; uint64_t pause_after_cnt = 0; uint64_t skip_cnt = 0; - std::string source; snort::SFDAQInstance* daq_instance; RetryQueue* retry_queue = nullptr; diff --git a/src/main/analyzer_command.cc b/src/main/analyzer_command.cc index 40f1524df..bf358a0e5 100644 --- a/src/main/analyzer_command.cc +++ b/src/main/analyzer_command.cc @@ -114,6 +114,9 @@ bool ACSwap::execute(Analyzer& analyzer, void** ac_state) { reload_tuners = new std::list(sc->get_reload_resource_tuners()); *ac_state = reload_tuners; + for (auto const& rtt : *reload_tuners) + rtt->tinit(); + } else reload_tuners = (std::list*)*ac_state; @@ -121,7 +124,7 @@ bool ACSwap::execute(Analyzer& analyzer, void** ac_state) if ( !reload_tuners->empty() ) { auto rrt = reload_tuners->front(); - if (rrt->tune_resources()) + if ( rrt->tune_packet_context() ) reload_tuners->pop_front(); } diff --git a/src/main/snort_config.h b/src/main/snort_config.h index 4ab41711c..f38db4166 100644 --- a/src/main/snort_config.h +++ b/src/main/snort_config.h @@ -147,7 +147,6 @@ struct VarNode; namespace snort { class ProtocolReference; -class ReloadResourceTuner; struct ProfilerConfig; struct GHash; struct XHash; @@ -163,11 +162,13 @@ public: virtual ~ReloadResourceTuner() = default; - virtual bool tune_resources() = 0; - virtual bool tune_resources_idle() = 0; + virtual void tinit() { } + virtual bool tune_packet_context() = 0; + virtual bool tune_idle_context() = 0; protected: ReloadResourceTuner() = default; + virtual bool tune_resources(unsigned work_limit) = 0; unsigned max_work = RELOAD_MAX_WORK_PER_PACKET; unsigned max_work_idle = RELOAD_MAX_WORK_WHEN_IDLE; diff --git a/src/stream/base/stream_base.cc b/src/stream/base/stream_base.cc index dc61eb48e..2b1c08f50 100644 --- a/src/stream/base/stream_base.cc +++ b/src/stream/base/stream_base.cc @@ -65,6 +65,12 @@ const PegInfo base_pegs[] = { CountType::SUM, "expected_realized", "number of expected flows realized" }, { CountType::SUM, "expected_pruned", "number of expected flows pruned" }, { CountType::SUM, "expected_overflows", "number of expected cache overflows" }, + { CountType::SUM, "reload_total_adds", "number of flows added by config reloads" }, + { CountType::SUM, "reload_total_deletes", "number of flows deleted by config reloads" }, + { CountType::SUM, "reload_freelist_deletes", "number of flows deleted from the free list by config reloads" }, + { CountType::SUM, "reload_allowed_deletes", "number of allowed flows deleted by config reloads" }, + { CountType::SUM, "reload_blocked_deletes", "number of blocked flows deleted by config reloads" }, + { CountType::SUM, "reload_offloaded_deletes", "number of offloaded flows deleted by config reloads" }, { CountType::END, nullptr, nullptr } }; @@ -82,7 +88,10 @@ void base_sum() stream_base_stats.preemptive_prunes = flow_con->get_prunes(PruneReason::PREEMPTIVE); 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.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); + stream_base_stats.reload_blocked_flow_deletes= flow_con->get_deletes(FlowDeleteState::BLOCKED); ExpectCache* exp_cache = flow_con->get_exp_cache(); if ( exp_cache ) @@ -127,6 +136,7 @@ static inline bool is_eligible(Packet* p) return true; } + //------------------------------------------------------------------------- // inspector stuff //------------------------------------------------------------------------- @@ -135,7 +145,6 @@ class StreamBase : public Inspector { public: StreamBase(const StreamModuleConfig*); - void show(SnortConfig*) override; void tinit() override; @@ -180,7 +189,7 @@ void StreamBase::tinit() if ( config.flow_cache_cfg.max_flows > 0 ) flow_con->init_exp(config.flow_cache_cfg.max_flows); - + FlushBucket::set(config.footprint); } diff --git a/src/stream/base/stream_module.cc b/src/stream/base/stream_module.cc index 127adf531..8e88a97c3 100644 --- a/src/stream/base/stream_module.cc +++ b/src/stream/base/stream_module.cc @@ -115,9 +115,7 @@ const RuleMap* StreamModule::get_rules() const { return stream_rules; } const StreamModuleConfig* StreamModule::get_data() -{ - return &config; -} +{ return &config; } bool StreamModule::begin(const char* fqn, int, SnortConfig*) { @@ -177,50 +175,21 @@ bool StreamModule::set(const char* fqn, Value& v, SnortConfig* c) return true; } -static int check_stream_config(const FlowCacheConfig& new_cfg, const FlowCacheConfig& saved_cfg) -{ - int ret = 0; - - if ( saved_cfg.max_flows != new_cfg.max_flows - or saved_cfg.pruning_timeout != new_cfg.pruning_timeout ) - { - ReloadError("Change of stream flow cache options requires a restart\n"); - ret = 1; - } - - return ret; -} - -static int check_stream_proto_config(const FlowCacheConfig& new_cfg, const FlowCacheConfig& saved_cfg, PktType type) -{ - int ret = 0; - - if ( saved_cfg.proto[to_utype(type)].nominal_timeout != new_cfg.proto[to_utype(type)].nominal_timeout ) - { - ReloadError("Change of stream protocol configuration options requires a restart\n"); - ret = 1; - } - - return ret; -} - // FIXIT-L the detection of stream.xxx_cache changes below is a temporary workaround // remove this check when stream.xxx_cache params become reloadable -bool StreamModule::end(const char* fqn, int, SnortConfig*) +bool StreamModule::end(const char* fqn, int, SnortConfig* cfg) { static StreamModuleConfig saved_config = {}; - static int issue_found = 0; if ( saved_config.flow_cache_cfg.max_flows ) { - // FIXIT-H - stream reload story will change this to look for change to max_flows config option - issue_found += check_stream_config(config.flow_cache_cfg, saved_config.flow_cache_cfg); - issue_found += check_stream_proto_config(config.flow_cache_cfg, saved_config.flow_cache_cfg, PktType::IP); - issue_found += check_stream_proto_config(config.flow_cache_cfg, saved_config.flow_cache_cfg, PktType::UDP); - issue_found += check_stream_proto_config(config.flow_cache_cfg, saved_config.flow_cache_cfg, PktType::TCP); - issue_found += check_stream_proto_config(config.flow_cache_cfg, saved_config.flow_cache_cfg, PktType::ICMP); - issue_found += check_stream_proto_config(config.flow_cache_cfg, saved_config.flow_cache_cfg, PktType::PDU); - issue_found += check_stream_proto_config(config.flow_cache_cfg, saved_config.flow_cache_cfg, PktType::FILE); + int max_flows_change = config.flow_cache_cfg.max_flows - saved_config.flow_cache_cfg.max_flows; + if ( max_flows_change ) + { + // register handler + reload_resource_manager.initialize(config.flow_cache_cfg, max_flows_change); + cfg->register_reload_resource_tuner(reload_resource_manager); + } } if ( !strcmp(fqn, "stream") ) @@ -229,11 +198,9 @@ bool StreamModule::end(const char* fqn, int, SnortConfig*) and config.footprint != saved_config.footprint ) { ReloadError("Changing of stream.footprint requires a restart\n"); - issue_found++; } - if ( issue_found == 0 ) + else saved_config = config; - issue_found = 0; } return true; @@ -248,3 +215,44 @@ void StreamModule::show_stats() void StreamModule::reset_stats() { base_reset(); } +// Stream handler to adjust allocated resources as needed on a config reload +void StreamReloadResourceManager::initialize(FlowCacheConfig& config_, int max_flows_change_) +{ + config = config_; + max_flows_change = max_flows_change_; +} + +void StreamReloadResourceManager::tinit() +{ + flow_con->update_flow_cache_cfg(config); + if ( max_flows_change < 0 ) + stream_base_stats.reload_total_deletes += abs(max_flows_change); + else + stream_base_stats.reload_total_adds += max_flows_change; +} + +bool StreamReloadResourceManager::tune_packet_context() +{ + return tune_resources(max_work); +} + +bool StreamReloadResourceManager::tune_idle_context() +{ + return tune_resources(max_work_idle); +} + +bool StreamReloadResourceManager::tune_resources(unsigned work_limit) +{ + // we are done if new max is > currently allocated flow objects + if ( flow_con->get_flows_allocated() <= config.max_flows ) + return true; + + unsigned flows_to_delete = flow_con->get_flows_allocated() - config.max_flows; + if ( flows_to_delete > work_limit ) + flows_to_delete -= flow_con->delete_flows(work_limit); + else + flows_to_delete -= flow_con->delete_flows(flows_to_delete); + + return ( flows_to_delete ) ? false : true; +} + diff --git a/src/stream/base/stream_module.h b/src/stream/base/stream_module.h index a95916829..f2b6508b0 100644 --- a/src/stream/base/stream_module.h +++ b/src/stream/base/stream_module.h @@ -21,10 +21,14 @@ #ifndef STREAM_MODULE_H #define STREAM_MODULE_H +#include "main/analyzer.h" +#include "main/snort_config.h" #include "flow/flow_config.h" +#include "flow/flow_control.h" #include "framework/module.h" extern THREAD_LOCAL snort::ProfileStats s5PerfStats; +extern THREAD_LOCAL class FlowControl* flow_con; namespace snort { @@ -53,6 +57,12 @@ struct BaseStats PegCount expected_realized; PegCount expected_pruned; PegCount expected_overflows; + PegCount reload_total_adds; + PegCount reload_total_deletes; + PegCount reload_freelist_flow_deletes; + PegCount reload_allowed_flow_deletes; + PegCount reload_blocked_flow_deletes; + PegCount reload_offloaded_flow_deletes; }; extern const PegInfo base_pegs[]; @@ -65,6 +75,24 @@ struct StreamModuleConfig unsigned footprint; }; +class StreamReloadResourceManager : public snort::ReloadResourceTuner +{ +public: + StreamReloadResourceManager() {} + + void initialize(FlowCacheConfig&, int max_flows_change_); + void tinit() override; + bool tune_packet_context() override; + bool tune_idle_context() override; + +private: + bool tune_resources(unsigned work_limit) override; + +private: + FlowCacheConfig config; + int max_flows_change = 0; +}; + class StreamModule : public snort::Module { public: @@ -91,6 +119,7 @@ public: private: StreamModuleConfig config; + StreamReloadResourceManager reload_resource_manager; }; extern void base_sum(); @@ -98,4 +127,3 @@ extern void base_stats(); extern void base_reset(); #endif - diff --git a/src/stream/flush_bucket.cc b/src/stream/flush_bucket.cc index b19fd15a3..203a2dce2 100644 --- a/src/stream/flush_bucket.cc +++ b/src/stream/flush_bucket.cc @@ -43,10 +43,8 @@ void FlushBucket::set(unsigned sz) if ( sz ) s_flush_bucket = new ConstFlushBucket(sz); - else if ( SnortConfig::static_hash() ) s_flush_bucket = new StaticFlushBucket; - else s_flush_bucket = new RandomFlushBucket; diff --git a/src/stream/stream.cc b/src/stream/stream.cc index ce693410e..65abe6335 100644 --- a/src/stream/stream.cc +++ b/src/stream/stream.cc @@ -74,11 +74,11 @@ Flow* Stream::new_flow(const FlowKey* key) { return flow_con->new_flow(key); } void Stream::delete_flow(const FlowKey* key) -{ flow_con->delete_flow(key); } +{ flow_con->release_flow(key); } void Stream::delete_flow(Flow* flow) { - flow_con->delete_flow(flow, PruneReason::NONE); + flow_con->release_flow(flow, PruneReason::NONE); } //------------------------------------------------------------------------- @@ -167,7 +167,7 @@ void Stream::check_flow_closed(Packet* p) // this will get called on each onload // eventually all onloads will occur and delete will be called if ( not flow->is_suspended() ) - flow_con->delete_flow(flow, PruneReason::NONE); + flow_con->release_flow(flow, PruneReason::NONE); p->flow = nullptr; } @@ -335,32 +335,28 @@ void Stream::init_active_response(const Packet* p, Flow* flow) void Stream::purge_flows() { - if ( !flow_con ) - return; - - flow_con->purge_flows(); + if ( flow_con ) + flow_con->purge_flows(); } void Stream::timeout_flows(time_t cur_time) { - if ( !flow_con ) - return; - // FIXIT-M batch here or loop vs looping over idle? - flow_con->timeout_flows(cur_time); + if ( flow_con ) + flow_con->timeout_flows(cur_time); } void Stream::prune_flows() { - if ( !flow_con ) - return; - - flow_con->prune_one(PruneReason::MEMCAP, false); + if ( flow_con ) + flow_con->prune_one(PruneReason::MEMCAP, false); } bool Stream::expected_flow(Flow* f, Packet* p) { - return flow_con->expected_flow(f, p) != SSN_DIR_NONE; + if ( flow_con ) + return flow_con->expected_flow(f, p) != SSN_DIR_NONE; + return false; } //------------------------------------------------------------------------- diff --git a/src/stream/tcp/tcp_session.cc b/src/stream/tcp/tcp_session.cc index e2679aab7..474dea2ad 100644 --- a/src/stream/tcp/tcp_session.cc +++ b/src/stream/tcp/tcp_session.cc @@ -96,8 +96,8 @@ bool TcpSession::setup(Packet* p) TcpStreamSession::setup(p); splitter_init = false; - const TcpStreamConfig* pc = get_tcp_cfg(flow->ssn_server); - flow->set_default_session_timeout(pc->session_timeout, false); + const TcpStreamConfig* cfg = get_tcp_cfg(flow->ssn_server); + flow->set_default_session_timeout(cfg->session_timeout, false); SESSION_STATS_ADD(tcpStats); tcpStats.setups++; diff --git a/src/stream/tcp/tcp_state_syn_recv.cc b/src/stream/tcp/tcp_state_syn_recv.cc index 528c9807b..b5ed979b3 100644 --- a/src/stream/tcp/tcp_state_syn_recv.cc +++ b/src/stream/tcp/tcp_state_syn_recv.cc @@ -75,7 +75,7 @@ bool TcpStateSynRecv::syn_ack_sent(TcpSegmentDescriptor& tsd, TcpStreamTracker& bool TcpStateSynRecv::syn_ack_recv(TcpSegmentDescriptor& tsd, TcpStreamTracker& trk) { - if ( trk.is_ack_valid(tsd.get_seg_ack() ) ) + if ( trk.is_ack_valid(tsd.get_seg_ack()) ) { Flow* flow = tsd.get_flow(); @@ -102,7 +102,7 @@ bool TcpStateSynRecv::ack_sent(TcpSegmentDescriptor& tsd, TcpStreamTracker& trk) bool TcpStateSynRecv::ack_recv(TcpSegmentDescriptor& tsd, TcpStreamTracker& trk) { - if ( trk.is_ack_valid(tsd.get_seg_ack() ) ) + if ( trk.is_ack_valid(tsd.get_seg_ack()) ) { Flow* flow = tsd.get_flow(); @@ -123,7 +123,7 @@ bool TcpStateSynRecv::ack_recv(TcpSegmentDescriptor& tsd, TcpStreamTracker& trk) bool TcpStateSynRecv::data_seg_recv(TcpSegmentDescriptor& tsd, TcpStreamTracker& trk) { - if ( trk.is_ack_valid(tsd.get_seg_ack() ) ) + if ( trk.is_ack_valid(tsd.get_seg_ack()) ) { trk.update_tracker_ack_recv(tsd); tsd.get_pkt()->packet_flags |= PKT_STREAM_TWH;