diff --git a/CHANGES b/CHANGES index f2f1786452..99a4ad21f9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,21 @@ +8.0.0-dev.615 | 2025-07-08 17:53:56 +0200 + + * Add NEWS entry for generic packet thresholds (Jan Grashoefer, Corelight) + + * Allow for multiple generic packet thresholds (Jan Grashoefer, Corelight) + + Co-authored-by: Arne Welzel + + * Add btest for conn_generic_packet_threshold_crossed event (Jan Grashoefer, Corelight) + + * Update dump-events btest baseline (Jan Grashoefer, Corelight) + + Changes in endpoint stats are a side-effect caused by the ConnSize + analyzer updating the conn record triggering the threshold event. The + phenomenon is described in https://github.com/zeek/zeek/issues/4214. + + * Add conn_generic_packet_threshold_crossed event (Jan Grashoefer, Corelight) + 8.0.0-dev.609 | 2025-07-08 11:42:05 +0100 * PPPoE: don't forward more bytes than header indicates (Johanna Amann, Corelight) diff --git a/NEWS b/NEWS index d80fb54bf9..fc4a8c98b9 100644 --- a/NEWS +++ b/NEWS @@ -218,6 +218,17 @@ New Functionality get_tags_by_category("ANALYZER"); +- A new ``conn_generic_packet_threshold_crossed`` event was introduced. The event triggers + for any IP-based session that reaches a given threshold. Multiple packet thresholds can + be defined in ``ConnThreshold::generic_packet_thresholds``. The generic thresholds refer + to the total number of packets on a connection without taking direction into account + (i.e. the event also triggers on one-sided connections). + + The event is intended as an alternative to the ``new_connection`` event that allows for + ignoring short-lived connections like DNS or scans. For example, it can be used to set + up traditional connection monitoring without introducing overhead for connections that + would never reach a larger threshold anyway. + Changed Functionality --------------------- diff --git a/VERSION b/VERSION index 3adee24a00..7e88c867bd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.0.0-dev.609 +8.0.0-dev.615 diff --git a/scripts/base/init-bare.zeek b/scripts/base/init-bare.zeek index 96e3d58017..bcaa9928bd 100644 --- a/scripts/base/init-bare.zeek +++ b/scripts/base/init-bare.zeek @@ -6379,6 +6379,18 @@ export { }; } +module ConnThreshold; + +export { + ## Number of packets required to be observed on any IP-based session to + ## trigger :zeek:id:`conn_generic_packet_threshold_crossed`. Note that the + ## thresholds refers to the total number of packets transferred in both + ## directions. + ## + ## .. zeek:see:: conn_generic_packet_threshold_crossed + const generic_packet_thresholds: set[count] = {} &redef; +} + module GLOBAL; @load base/bif/event.bif diff --git a/src/analyzer/protocol/conn-size/ConnSize.cc b/src/analyzer/protocol/conn-size/ConnSize.cc index eeed6ce531..ae1f03315b 100644 --- a/src/analyzer/protocol/conn-size/ConnSize.cc +++ b/src/analyzer/protocol/conn-size/ConnSize.cc @@ -11,6 +11,8 @@ namespace zeek::analyzer::conn_size { +std::vector ConnSize_Analyzer::generic_pkt_thresholds; + ConnSize_Analyzer::ConnSize_Analyzer(Connection* c) : Analyzer("CONNSIZE", c) { start_time = c->StartTime(); } void ConnSize_Analyzer::Init() { @@ -25,6 +27,11 @@ void ConnSize_Analyzer::Init() { orig_pkts_thresh = 0; resp_bytes_thresh = 0; resp_pkts_thresh = 0; + + generic_pkt_thresh = 0; + generic_pkt_thresh_next_idx = 0; + if ( conn_generic_packet_threshold_crossed ) + NextGenericPacketThreshold(); } void ConnSize_Analyzer::Done() { Analyzer::Done(); } @@ -36,7 +43,21 @@ void ConnSize_Analyzer::ThresholdEvent(EventHandlerPtr f, uint64_t threshold, bo EnqueueConnEvent(f, ConnVal(), val_mgr->Count(threshold), val_mgr->Bool(is_orig)); } +void ConnSize_Analyzer::NextGenericPacketThreshold() { + if ( generic_pkt_thresh_next_idx >= generic_pkt_thresholds.size() ) { + generic_pkt_thresh = 0; + return; + } + + generic_pkt_thresh = generic_pkt_thresholds[generic_pkt_thresh_next_idx++]; +} + void ConnSize_Analyzer::CheckThresholds(bool is_orig) { + if ( generic_pkt_thresh && (orig_pkts + resp_pkts) == generic_pkt_thresh ) { + EnqueueConnEvent(conn_generic_packet_threshold_crossed, ConnVal(), val_mgr->Count(generic_pkt_thresh)); + NextGenericPacketThreshold(); + } + if ( is_orig ) { if ( orig_bytes_thresh && orig_bytes >= orig_bytes_thresh ) { ThresholdEvent(conn_bytes_threshold_crossed, orig_bytes_thresh, is_orig); diff --git a/src/analyzer/protocol/conn-size/ConnSize.h b/src/analyzer/protocol/conn-size/ConnSize.h index 268cba8d66..babfe262e9 100644 --- a/src/analyzer/protocol/conn-size/ConnSize.h +++ b/src/analyzer/protocol/conn-size/ConnSize.h @@ -26,9 +26,19 @@ public: static analyzer::Analyzer* Instantiate(Connection* conn) { return new ConnSize_Analyzer(conn); } + /** + * Update the generic packet thresholds. + * + * @param thresholds The generic packet thresholds to set. + */ + static void SetGenericPacketThresholds(std::vector thresholds) { + generic_pkt_thresholds = std::move(thresholds); + }; + protected: void DeliverPacket(int len, const u_char* data, bool is_orig, uint64_t seq, const IP_Hdr* ip, int caplen) override; void CheckThresholds(bool is_orig); + void NextGenericPacketThreshold(); void ThresholdEvent(EventHandlerPtr f, uint64_t threshold, bool is_orig); @@ -42,8 +52,13 @@ protected: uint64_t orig_pkts_thresh = 0; uint64_t resp_pkts_thresh = 0; + uint64_t generic_pkt_thresh = 0; + size_t generic_pkt_thresh_next_idx = 0; + double start_time = 0.0; double duration_thresh = 0.0; + + static std::vector generic_pkt_thresholds; }; // Exposed to make it available to script optimization. diff --git a/src/analyzer/protocol/conn-size/Plugin.cc b/src/analyzer/protocol/conn-size/Plugin.cc index 512fa71330..8375aa829d 100644 --- a/src/analyzer/protocol/conn-size/Plugin.cc +++ b/src/analyzer/protocol/conn-size/Plugin.cc @@ -2,6 +2,7 @@ #include "zeek/plugin/Plugin.h" +#include "zeek/Val.h" #include "zeek/analyzer/Component.h" #include "zeek/analyzer/protocol/conn-size/ConnSize.h" @@ -18,6 +19,20 @@ public: config.description = "Connection size analyzer"; return config; } + + void InitPostScript() override { + // Load generic_packet_thresholds at InitPostScript() time. + auto t = id::find_const("ConnThreshold::generic_packet_thresholds"); + std::vector thresholds; + thresholds.reserve(t->Size()); + + auto lv = t->ToPureListVal(); + for ( auto i = 0; i < lv->Length(); i++ ) + thresholds.emplace_back(lv->Idx(i)->AsCount()); + std::sort(thresholds.begin(), thresholds.end()); + + zeek::analyzer::conn_size::ConnSize_Analyzer::SetGenericPacketThresholds(thresholds); + } } plugin; } // namespace zeek::plugin::detail::Zeek_ConnSize diff --git a/src/analyzer/protocol/conn-size/events.bif b/src/analyzer/protocol/conn-size/events.bif index 7d22133d65..329660f7d6 100644 --- a/src/analyzer/protocol/conn-size/events.bif +++ b/src/analyzer/protocol/conn-size/events.bif @@ -46,3 +46,12 @@ event conn_packets_threshold_crossed%(c: connection, threshold: count, is_orig: ## get_current_conn_bytes_threshold get_current_conn_packets_threshold ## set_current_conn_duration_threshold get_current_conn_duration_threshold event conn_duration_threshold_crossed%(c: connection, threshold: interval, is_orig: bool%); + +## Generated for any IP-based session once :zeek:id:`ConnThreshold::generic_packet_thresholds` packets have been +## observed. Only one endpoint sending traffic is sufficient to trigger the event. This allows to handle new +## connections, while short interactions, like scans consisting of only a few packets, are ignored. +## +## c: the connection. +## +## threshold: the threshold that was set +event conn_generic_packet_threshold_crossed%(c: connection, threshold: count%); \ No newline at end of file diff --git a/src/event.bif b/src/event.bif index f89b099fa1..ea4cb56a0f 100644 --- a/src/event.bif +++ b/src/event.bif @@ -106,7 +106,8 @@ event network_time_init%(%); ## ## Handling this event is potentially expensive. For example, during a SYN ## flooding attack, every spoofed SYN packet will lead to a new -## event. +## event. Consider to use events like :zeek:id:`connection_established` or +## :zeek:id:`conn_generic_packet_threshold_crossed` instead. event new_connection%(c: connection%); ## Generated for a connection whose tunneling has changed. This could diff --git a/testing/btest/Baseline/core.conn-generic-packet-threshold/out b/testing/btest/Baseline/core.conn-generic-packet-threshold/out new file mode 100644 index 0000000000..a6b72cb9a6 --- /dev/null +++ b/testing/btest/Baseline/core.conn-generic-packet-threshold/out @@ -0,0 +1,17 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +=== Generic threshold crossed === +new_connection: [orig_h=141.142.228.5, orig_p=59856/tcp, resp_h=192.150.187.43, resp_p=80/tcp, proto=6, ctx=[]] +conn_generic_packet_threshold_crossed: [orig_h=141.142.228.5, orig_p=59856/tcp, resp_h=192.150.187.43, resp_p=80/tcp, proto=6, ctx=[]] at 5 +new_connection: [orig_h=192.168.170.8, orig_p=32795/udp, resp_h=192.168.170.20, resp_p=53/udp, proto=17, ctx=[]] +conn_generic_packet_threshold_crossed: [orig_h=192.168.170.8, orig_p=32795/udp, resp_h=192.168.170.20, resp_p=53/udp, proto=17, ctx=[]] at 5 +new_connection: [orig_h=192.168.170.8, orig_p=32795/udp, resp_h=192.168.170.20, resp_p=53/udp, proto=17, ctx=[]] +conn_generic_packet_threshold_crossed: [orig_h=192.168.170.8, orig_p=32795/udp, resp_h=192.168.170.20, resp_p=53/udp, proto=17, ctx=[]] at 5 +new_connection: [orig_h=192.168.170.8, orig_p=0/unknown, resp_h=192.168.170.56, resp_p=0/unknown, proto=132, ctx=[]] +conn_generic_packet_threshold_crossed: [orig_h=192.168.170.8, orig_p=0/unknown, resp_h=192.168.170.56, resp_p=0/unknown, proto=132, ctx=[]] at 5 +new_connection: [orig_h=141.142.228.5, orig_p=59856/tcp, resp_h=192.150.187.43, resp_p=80/tcp, proto=6, ctx=[]] +conn_generic_packet_threshold_crossed: [orig_h=141.142.228.5, orig_p=59856/tcp, resp_h=192.150.187.43, resp_p=80/tcp, proto=6, ctx=[]] at 5 +conn_generic_packet_threshold_crossed: [orig_h=141.142.228.5, orig_p=59856/tcp, resp_h=192.150.187.43, resp_p=80/tcp, proto=6, ctx=[]] at 10 +=== Generic threshold not crossed === +new_connection: [orig_h=141.142.228.5, orig_p=59856/tcp, resp_h=192.150.187.43, resp_p=80/tcp, proto=6, ctx=[]] +new_connection: [orig_h=10.87.3.74, orig_p=51871/udp, resp_h=10.87.1.10, resp_p=53/udp, proto=17, ctx=[]] +new_connection: [orig_h=141.142.228.5, orig_p=59856/tcp, resp_h=192.150.187.43, resp_p=80/tcp, proto=6, ctx=[]] diff --git a/testing/btest/core/conn-generic-packet-threshold.zeek b/testing/btest/core/conn-generic-packet-threshold.zeek new file mode 100644 index 0000000000..8873cc9742 --- /dev/null +++ b/testing/btest/core/conn-generic-packet-threshold.zeek @@ -0,0 +1,19 @@ +# @TEST-EXEC: echo "=== Generic threshold crossed ===" > out +# @TEST-EXEC: zeek -b -C -r $TRACES/http/get.trace %INPUT >> out +# @TEST-EXEC: zeek -b -C -r $TRACES/dns/long-connection.pcap %INPUT >> out +# @TEST-EXEC: zeek -b -C -r $TRACES/communityid/sctp.pcap %INPUT >> out +# @TEST-EXEC: zeek -b -C -r $TRACES/http/get.trace %INPUT ConnThreshold::generic_packet_thresholds+={10} >> out +# @TEST-EXEC: echo "=== Generic threshold not crossed ===" >> out +# @TEST-EXEC: zeek -b -C -r $TRACES/tcp/syn.pcap %INPUT >> out +# @TEST-EXEC: zeek -b -C -r $TRACES/dns/dns-binds.pcap %INPUT >> out +# @TEST-EXEC: zeek -b -C -r $TRACES/http/get.trace %INPUT ConnThreshold::generic_packet_thresholds={15} >> out + +# @TEST-EXEC: btest-diff out + +redef ConnThreshold::generic_packet_thresholds = {5}; + +event new_connection(c: connection) + { print fmt("new_connection: %s", c$id); } + +event conn_generic_packet_threshold_crossed(c: connection, threshold: count) + { print fmt("conn_generic_packet_threshold_crossed: %s at %d", c$id, threshold); }