From: Masud Hasan (mashasan) Date: Thu, 21 Apr 2022 19:20:11 +0000 (+0000) Subject: Pull request #3385: netflow: Framework for netflow V5 and V9 events X-Git-Tag: 3.1.28.0~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=62d6e47053e540523ca54ed05590adc452b802c1;p=thirdparty%2Fsnort3.git Pull request #3385: netflow: Framework for netflow V5 and V9 events Merge in SNORT/snort3 from ~MASHASAN/snort3:netflow_event2 to master Squashed commit of the following: commit 9320cdc01f9ace6dec235274b38e3115381e5a19 Author: Masud Hasan Date: Mon Apr 18 08:22:53 2022 -0400 netflow: Framework for netflow V5 and V9 events --- diff --git a/src/network_inspectors/rna/rna_event_handler.cc b/src/network_inspectors/rna/rna_event_handler.cc index dd47d5e41..cc7768ea1 100644 --- a/src/network_inspectors/rna/rna_event_handler.cc +++ b/src/network_inspectors/rna/rna_event_handler.cc @@ -133,3 +133,10 @@ void RnaCPEOSInfoEventHandler::handle(DataEvent& event, Flow*) ++rna_stats.cpe_os; pnd.analyze_cpe_os_info(event); } + +void RnaNetflowEventHandler::handle(DataEvent& event, Flow*) +{ + Profile profile(rna_perf_stats); + ++rna_stats.netflow_record; + pnd.analyze_netflow(event); +} diff --git a/src/network_inspectors/rna/rna_event_handler.h b/src/network_inspectors/rna/rna_event_handler.h index 621abdd69..b0ce76d50 100644 --- a/src/network_inspectors/rna/rna_event_handler.h +++ b/src/network_inspectors/rna/rna_event_handler.h @@ -162,4 +162,13 @@ private: RnaPnd& pnd; }; +class RnaNetflowEventHandler : public snort::DataHandler +{ +public: + RnaNetflowEventHandler(RnaPnd& nd) : DataHandler(RNA_NAME), pnd(nd) { } + void handle(snort::DataEvent&, snort::Flow*) override; +private: + RnaPnd& pnd; +}; + #endif diff --git a/src/network_inspectors/rna/rna_inspector.cc b/src/network_inspectors/rna/rna_inspector.cc index 15fb65cd8..ac15390b7 100644 --- a/src/network_inspectors/rna/rna_inspector.cc +++ b/src/network_inspectors/rna/rna_inspector.cc @@ -105,7 +105,9 @@ bool RnaInspector::configure(SnortConfig*) DataBus::subscribe_network( STREAM_TCP_SYN_EVENT, new RnaTcpSynEventHandler(*pnd) ); DataBus::subscribe_network( STREAM_TCP_SYN_ACK_EVENT, new RnaTcpSynAckEventHandler(*pnd) ); DataBus::subscribe_network( STREAM_TCP_MIDSTREAM_EVENT, new RnaTcpMidstreamEventHandler(*pnd) ); + DataBus::subscribe_network( CPE_OS_INFO_EVENT, new RnaCPEOSInfoEventHandler(*pnd) ); + DataBus::subscribe_network( NETFLOW_EVENT, new RnaNetflowEventHandler(*pnd) ); if (rna_conf && rna_conf->log_when_idle) DataBus::subscribe_network( THREAD_IDLE_EVENT, new RnaIdleEventHandler(*pnd) ); diff --git a/src/network_inspectors/rna/rna_module.cc b/src/network_inspectors/rna/rna_module.cc index 9561d0392..78e863df5 100644 --- a/src/network_inspectors/rna/rna_module.cc +++ b/src/network_inspectors/rna/rna_module.cc @@ -387,6 +387,7 @@ static const PegInfo rna_pegs[] = { CountType::SUM, "dhcp_data", "count of DHCP data events received" }, { CountType::SUM, "dhcp_info", "count of new DHCP lease events received" }, { CountType::SUM, "smb", "count of new SMB events received" }, + { CountType::SUM, "netflow_record", "count of netflow record events received" }, { CountType::END, nullptr, nullptr}, }; diff --git a/src/network_inspectors/rna/rna_module.h b/src/network_inspectors/rna/rna_module.h index d5c98d80e..8d327711d 100644 --- a/src/network_inspectors/rna/rna_module.h +++ b/src/network_inspectors/rna/rna_module.h @@ -54,6 +54,7 @@ struct RnaStats PegCount dhcp_data; PegCount dhcp_info; PegCount smb; + PegCount netflow_record; }; extern THREAD_LOCAL RnaStats rna_stats; diff --git a/src/network_inspectors/rna/rna_pnd.cc b/src/network_inspectors/rna/rna_pnd.cc index edf078170..3fe45744d 100644 --- a/src/network_inspectors/rna/rna_pnd.cc +++ b/src/network_inspectors/rna/rna_pnd.cc @@ -176,6 +176,26 @@ bool RnaPnd::analyze_cpe_os_info(snort::DataEvent& event) return true; } +bool RnaPnd::analyze_netflow(snort::DataEvent& event) +{ + const Packet* p = event.get_packet(); + if ( !p ) + return false; + + const auto& src_ip = p->ptrs.ip_api.get_src(); + const auto& src_ip_ptr = (const struct in6_addr*) src_ip->get_ip6_ptr(); + const auto& src_mac = layer::get_eth_layer(p)->ether_src; + NetflowEvent* nfe = static_cast(&event); + const NetflowSessionRecord* nf_record = nfe->get_record(); + + // process host and service log events + UNUSED(src_ip_ptr); + UNUSED(src_mac); + UNUSED(nf_record); + + return true; +} + void RnaPnd::discover_network_icmp(const Packet* p) { discover_network(p, 0); diff --git a/src/network_inspectors/rna/rna_pnd.h b/src/network_inspectors/rna/rna_pnd.h index b810cc467..326eadf6d 100644 --- a/src/network_inspectors/rna/rna_pnd.h +++ b/src/network_inspectors/rna/rna_pnd.h @@ -31,6 +31,7 @@ #include "protocols/vlan.h" #include "pub_sub/appid_events.h" #include "pub_sub/dhcp_events.h" +#include "pub_sub/netflow_event.h" #include "pub_sub/smb_events.h" #include "sfip/sf_ip.h" @@ -131,6 +132,7 @@ public: void add_dhcp_info(snort::DataEvent&); void analyze_smb_fingerprint(snort::DataEvent&); bool analyze_cpe_os_info(snort::DataEvent&); + bool analyze_netflow(snort::DataEvent&); // generate change event for all hosts in the ip cache void generate_change_host_update(); diff --git a/src/pub_sub/CMakeLists.txt b/src/pub_sub/CMakeLists.txt index 648d736c2..d00049a66 100644 --- a/src/pub_sub/CMakeLists.txt +++ b/src/pub_sub/CMakeLists.txt @@ -12,6 +12,7 @@ set (PUB_SUB_INCLUDES finalize_packet_event.h http_events.h http_request_body_event.h + netflow_event.h opportunistic_tls_event.h sip_events.h smb_events.h diff --git a/src/pub_sub/netflow_event.h b/src/pub_sub/netflow_event.h new file mode 100644 index 000000000..dd44ebe6f --- /dev/null +++ b/src/pub_sub/netflow_event.h @@ -0,0 +1,50 @@ +//-------------------------------------------------------------------------- +// 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_event.h author Masud Hasan + +#ifndef NETFLOW_EVENT_H +#define NETFLOW_EVENT_H + +#include "framework/data_bus.h" +#include "service_inspectors/netflow/netflow_headers.h" + +#define NETFLOW_EVENT "service_inspector.netflow" + +namespace snort +{ + +class NetflowEvent : public DataEvent +{ +public: + NetflowEvent(const snort::Packet* p, const NetflowSessionRecord* rec) + : pkt(p), record(rec) { } + + const Packet* get_packet() override + { return pkt; } + + const NetflowSessionRecord* get_record() + { return record; } + +private: + const Packet* pkt; + const NetflowSessionRecord* record; +}; + +} + +#endif diff --git a/src/service_inspectors/netflow/CMakeLists.txt b/src/service_inspectors/netflow/CMakeLists.txt index 1017d4057..11e6c5fb2 100644 --- a/src/service_inspectors/netflow/CMakeLists.txt +++ b/src/service_inspectors/netflow/CMakeLists.txt @@ -12,4 +12,4 @@ if (STATIC_INSPECTORS) else (STATIC_INSPECTORS) add_dynamic_module(netflow inspectors ${FILE_LIST}) -endif (STATIC_INSPECTORS) +endif (STATIC_INSPECTORS) \ No newline at end of file diff --git a/src/service_inspectors/netflow/netflow.cc b/src/service_inspectors/netflow/netflow.cc index fa23b689e..de7e1cfb1 100644 --- a/src/service_inspectors/netflow/netflow.cc +++ b/src/service_inspectors/netflow/netflow.cc @@ -35,6 +35,7 @@ #include "log/messages.h" #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" @@ -51,6 +52,19 @@ struct IpCompare { return a.less_than(b); } }; +// Used to ensure we fully populate the record; can't rely on the actual values being zero +struct RecordStatus +{ + bool src = false; + bool dst = false; + bool first = false; + bool last = false; + bool src_tos = false; + bool dst_tos = false; + bool bytes_sent = false; + bool packets_sent = false; +}; + // ----------------------------------------------------------------------------- // static variables // ----------------------------------------------------------------------------- @@ -81,9 +95,7 @@ static bool filter_record(const NetflowRules* rules, const int zone, for( auto const& rule : rules->exclude ) { if ( rule.filter_match(address, zone) ) - { return false; - } } } @@ -92,18 +104,15 @@ static bool filter_record(const NetflowRules* rules, const int zone, for( auto const& rule : rules->include ) { if ( rule.filter_match(address, zone) ) - { - // check i.create_host i.create_service - // and publish events return true; - } } } return false; } static bool version_9_record_update(const unsigned char* data, uint32_t unix_secs, - std::vector::iterator field, NetflowSessionRecord &record) + std::vector::iterator field, NetflowSessionRecord &record, + RecordStatus& record_status) { switch ( field->field_type ) @@ -144,6 +153,8 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec // Invalid source IP address provided if ( record.initiator_ip.set((const uint32_t *)data, AF_INET) != SFIP_SUCCESS ) return false; + + record_status.src = true; break; case NETFLOW_SRC_IPV6: @@ -151,6 +162,8 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec // Invalid source IP address provided if ( record.initiator_ip.set((const uint32_t *)data, AF_INET6) != SFIP_SUCCESS ) return false; + + record_status.src = true; break; case NETFLOW_DST_PORT: @@ -171,6 +184,8 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec // Invalid destination IP address if ( record.responder_ip.set((const uint32_t *)data, AF_INET) != SFIP_SUCCESS ) return false; + + record_status.dst = true; break; case NETFLOW_DST_IPV6: @@ -178,6 +193,8 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec // Invalid destination IP address if ( record.responder_ip.set((const uint32_t *)data, AF_INET6) != SFIP_SUCCESS ) return false; + + record_status.dst = true; break; case NETFLOW_IPV4_NEXT_HOP: @@ -202,6 +219,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec if( record.last_pkt_second > MAX_TIME ) return false; + record_status.last = true; break; case NETFLOW_FIRST_PKT: @@ -215,6 +233,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec if( record.first_pkt_second > MAX_TIME ) return 0; + record_status.first = true; break; case NETFLOW_IN_BYTES: @@ -228,6 +247,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec else return false; + record_status.bytes_sent = true; break; case NETFLOW_IN_PKTS: @@ -241,6 +261,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec else return false; + record_status.packets_sent = true; break; case NETFLOW_SRC_TOS: @@ -249,6 +270,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec return false; record.nf_src_tos = (uint8_t)*data; + record_status.src_tos = true; break; case NETFLOW_DST_TOS: @@ -257,6 +279,7 @@ static bool version_9_record_update(const unsigned char* data, uint32_t unix_sec return false; record.nf_dst_tos = (uint8_t)*data; + record_status.dst_tos = true; break; case NETFLOW_SNMP_IN: @@ -397,6 +420,7 @@ static bool decode_netflow_v9(const unsigned char* data, uint16_t size, { NetflowSessionRecord record = {}; + RecordStatus record_status; bool bad_field = false; for ( auto t_field = tf.begin(); t_field != tf.end(); ++t_field ) @@ -407,7 +431,8 @@ 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); + bool status = version_9_record_update(data, header.unix_secs, + t_field, record, record_status); if ( !status ) bad_field = true; @@ -430,7 +455,24 @@ static bool decode_netflow_v9(const unsigned char* data, uint16_t size, continue; } - // create flow event here + if ( record_status.bytes_sent and record_status.packets_sent and + record_status.src and record_status.dst and record_status.first and + record_status.last and record.first_pkt_second <= record.last_pkt_second ) + { + if ( record_status.src_tos ) + { + if ( !record_status.dst_tos ) + record.nf_dst_tos = record.nf_src_tos; + } + else if ( record_status.dst_tos ) + { + if ( !record_status.src_tos ) + record.nf_src_tos = record.nf_dst_tos; + } + // send create_host and create_service flags too so that rna event handler can log those + NetflowEvent event(p, &record); + DataBus::publish(NETFLOW_EVENT, event); + } // check if record exists auto result = netflow_cache->find(record.initiator_ip); @@ -625,6 +667,9 @@ static bool decode_netflow_v5(const unsigned char* data, uint16_t size, if ( result.second ) ++netflow_stats.unique_flows; + // send create_host and create_service flags too so that rna event handler can log those + NetflowEvent event(p, &record); + DataBus::publish(NETFLOW_EVENT, event); } return true; } @@ -896,9 +941,8 @@ NetflowInspector::~NetflowInspector() void NetflowInspector::eval(Packet* p) { - // precondition - what we registered for - assert((p->is_udp() and p->dsize and p->data)); - assert(netflow_cache); + if ( !p->is_udp() or !p->dsize or !p->data or !netflow_cache ) + return; auto d = config->device_rule_map.find(*p->ptrs.ip_api.get_src());