From: Steve Chew (stechew) Date: Wed, 31 Mar 2021 22:38:58 +0000 (+0000) Subject: Merge pull request #2808 in SNORT/snort3 from ~SBAIGAL/snort3:netflow_cfg to master X-Git-Tag: 3.1.4.0~30 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1f90d2706ffe20305420b959df439b3bc17a2205;p=thirdparty%2Fsnort3.git Merge pull request #2808 in SNORT/snort3 from ~SBAIGAL/snort3:netflow_cfg to master Squashed commit of the following: commit d895eb631410232976bd389e90a2cd3b2c6650b0 Author: Steven Baigal (sbaigal) Date: Tue Mar 23 11:23:12 2021 -0400 netflow: add device list configuration netflow: add filter matching for v5 decoder --- diff --git a/src/service_inspectors/netflow/netflow.cc b/src/service_inspectors/netflow/netflow.cc index 136a25237..599737ebd 100644 --- a/src/service_inspectors/netflow/netflow.cc +++ b/src/service_inspectors/netflow/netflow.cc @@ -43,17 +43,6 @@ using namespace snort; THREAD_LOCAL NetflowStats netflow_stats; THREAD_LOCAL ProfileStats netflow_perf_stats; -// Used to create hash of key for indexing into cache. -struct NetflowHash -{ - size_t operator()(const snort::SfIp& ip) const - { - const uint64_t* ip64 = (const uint64_t*) ip.get_ip6_ptr(); - return std::hash() (ip64[0]) ^ - std::hash() (ip64[1]); - } -}; - // compare struct to use with ip sort struct IpCompare { @@ -78,6 +67,37 @@ static NetflowCache* dump_cache = nullptr; // ----------------------------------------------------------------------------- // static functions // ----------------------------------------------------------------------------- +static bool filter_record(const NetflowRules* rules, const int zone, + const SfIp* src, const SfIp* dst) +{ + const SfIp* addr[2] = {src, dst}; + + for( auto const & address : addr ) + { + for( auto const& rule : rules->exclude ) + { + if ( rule.filter_match(address, zone) ) + { + return false; + } + } + } + + for( auto const & address : addr ) + { + 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; +} + // FIXIT-M - keeping only few checks right now static bool decode_netflow_v9(const unsigned char* data, uint16_t size) { @@ -97,7 +117,8 @@ static bool decode_netflow_v9(const unsigned char* data, uint16_t size) return true; } -static bool decode_netflow_v5(const unsigned char* data, uint16_t size) +static bool decode_netflow_v5(const unsigned char* data, uint16_t size, + const Packet* p, const NetflowConfig* cfg) { Netflow5Hdr header; const Netflow5Hdr *pheader; @@ -113,6 +134,15 @@ static bool decode_netflow_v5(const unsigned char* data, uint16_t size) if( header.flow_count < NETFLOW_MIN_COUNT or header.flow_count > NETFLOW_MAX_COUNT ) return false; + const NetflowRules* p_rules = nullptr; + auto d = cfg->device_rule_map.find(*p->ptrs.ip_api.get_src()); + if ( d != cfg->device_rule_map.end() ) + p_rules = &(d->second); + + if ( p_rules == nullptr ) + return false; + const int zone = p->pkth->ingress_group; + data += sizeof(Netflow5Hdr); precord = (const Netflow5RecordHdr *)data; @@ -150,6 +180,9 @@ static bool decode_netflow_v5(const unsigned char* data, uint16_t size) if ( record.next_hop_ip.set(&precord->next_hop_addr, AF_INET) != SFIP_SUCCESS ) return false; + if ( !filter_record(p_rules, zone, &record.initiator_ip, &record.responder_ip) ) + continue; + record.initiator_port = ntohs(precord->src_port); record.responder_port = ntohs(precord->dst_port); record.proto = precord->flow_protocol; @@ -180,7 +213,7 @@ static bool decode_netflow_v5(const unsigned char* data, uint16_t size) return true; } -static bool validate_netflow(const Packet* p) +static bool validate_netflow(const Packet* p, const NetflowConfig* cfg) { uint16_t size = p->dsize; const unsigned char* data = p->data; @@ -195,7 +228,7 @@ static bool validate_netflow(const Packet* p) if( version == 5 ) { - retval = decode_netflow_v5(data, size); + retval = decode_netflow_v5(data, size, p, cfg); if ( retval ) { ++netflow_stats.packets; @@ -229,6 +262,7 @@ public: void tterm() override; void eval(snort::Packet*) override; + void show(const snort::SnortConfig*) const override; private: const NetflowConfig *config; @@ -237,6 +271,84 @@ private: void stringify(std::ofstream&); }; +static std::string to_string(const std::vector & networks) +{ + std::string nets; + if ( networks.empty() ) + { + nets = "any"; + } + else + { + for( auto const& n : networks ) + { + SfIpString s; + n.ntop(s); + nets += s; + auto bits = n.get_bits(); + bits -= (n.get_family() == AF_INET and bits) ? 96 : 0; + nets += "/" + std::to_string(bits); + nets += " "; + } + } + return nets; +} + +static std::string to_string(const std::vector & zones) +{ + std::string zs; + if ( zones.empty() ) + { + zs = "any"; + } + else + { + for( auto const& z : zones ) + { + if ( z == NETFLOW_ANY_ZONE ) + { + zs = "any"; + break; + } + else + zs += std::to_string(z) + " "; + } + } + return zs; +} + +static void show_device(const NetflowRule& d, bool is_exclude) +{ + ConfigLogger::log_flag("exclude", is_exclude, true); + ConfigLogger::log_flag("create_host", d.create_host, true); + ConfigLogger::log_flag("create_service", d.create_service, true); + ConfigLogger::log_value("networks", to_string(d.networks).c_str(), true); + ConfigLogger::log_value("zones", to_string(d.zones).c_str(), true); +} + +void NetflowInspector::show(const SnortConfig*) const +{ + ConfigLogger::log_value("dump_file", config->dump_file); + ConfigLogger::log_value("update_timeout", config->update_timeout); + std::once_flag d_once; + for ( auto const& d : config->device_rule_map ) + { + std::call_once(d_once, []{ ConfigLogger::log_option("rules"); }); + SfIpString addr_str; + d.first.ntop(addr_str); + for( auto const& r : d.second.exclude ) + { + ConfigLogger::log_value("device_ip", addr_str); + show_device(r, true); + } + for( auto const& r : d.second.include ) + { + ConfigLogger::log_value("device_ip", addr_str); + show_device(r, false); + } + } +} + void NetflowInspector::stringify(std::ofstream& file_stream) { std::vector keys; @@ -367,7 +479,7 @@ void NetflowInspector::eval(Packet* p) assert((p->is_udp() and p->dsize and p->data)); assert(netflow_cache); - if ( ! validate_netflow(p) ) + if ( ! validate_netflow(p, config) ) ++netflow_stats.invalid_netflow_pkts; } diff --git a/src/service_inspectors/netflow/netflow_module.cc b/src/service_inspectors/netflow/netflow_module.cc index 095735488..8f8c2316b 100644 --- a/src/service_inspectors/netflow/netflow_module.cc +++ b/src/service_inspectors/netflow/netflow_module.cc @@ -31,11 +31,40 @@ using namespace snort; // ----------------------------------------------------------------------------- // static variables // ----------------------------------------------------------------------------- +static const Parameter device_rule_params[] = +{ + { "device_ip", Parameter::PT_ADDR, nullptr, nullptr, + "restrict the NetFlow devices from which Snort will analyze packets" }, + + { "exclude", Parameter::PT_BOOL, nullptr, "false", + "exclude the NetFlow records that match this rule" }, + + { "zones", Parameter::PT_STRING, nullptr, nullptr, + "generate events only for NetFlow packets that originate from these zones" }, + + { "networks", Parameter::PT_STRING, nullptr, nullptr, + "generate events for NetFlow records that contain an initiator or responder IP from these networks" }, + + { "create_host", Parameter::PT_BOOL, nullptr, "false", + "generate a new host event" }, + + { "create_service", Parameter::PT_BOOL, nullptr, "false", + "generate a new or changed service event" }, + + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } +}; static const Parameter netflow_params[] = { { "dump_file", Parameter::PT_STRING, nullptr, nullptr, "file name to dump netflow cache on shutdown; won't dump by default" }, + + { "update_timeout", Parameter::PT_INT, "0:max32", "3600", + "the interval at which the system updates host cache information" }, + + { "rules", Parameter::PT_LIST, device_rule_params, nullptr, + "list of NetFlow device rules" }, + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } }; @@ -71,22 +100,96 @@ NetflowConfig* NetflowModule::get_data() return tmp; } -bool NetflowModule::begin(const char*, int, SnortConfig*) +bool NetflowModule::begin(const char* fqn, int idx, SnortConfig*) { - assert(!conf); - conf = new NetflowConfig(); + if ( !conf ) + { + conf = new NetflowConfig(); + } + + if ( idx && !strcmp(fqn, "netflow.rules") ) + { + rule_cfg.reset(); + device_ip_cfg.clear(); + is_exclude_rule = false; + } return true; } -bool NetflowModule::set(const char*, Value& v, SnortConfig*) +bool NetflowModule::end(const char* fqn, int idx, SnortConfig*) { + if ( idx && !strcmp(fqn, "netflow.rules") ) + { + if ( device_ip_cfg.is_set() ) + { + auto& d = conf->device_rule_map[device_ip_cfg]; + if ( is_exclude_rule ) + d.exclude.emplace_back(rule_cfg); + else + d.include.emplace_back(rule_cfg); + } + } + return true; +} +bool NetflowModule::set(const char*, Value& v, SnortConfig*) +{ if ( v.is("dump_file") ) { if ( conf->dump_file ) snort_free((void*)conf->dump_file); conf->dump_file = snort_strdup(v.get_string()); } + else if ( v.is("update_timeout") ) + { + conf->update_timeout = v.get_uint32(); + } + else if ( v.is("device_ip") ) + { + v.get_addr(device_ip_cfg); + } + else if ( v.is("networks") ) + { + std::string net; + v.set_first_token(); + for ( int i = 0; v.get_next_token(net); i++ ) + { + SfCidr n; + if ( n.set(net.c_str()) != SFIP_SUCCESS ) + return false; + rule_cfg.networks.emplace_back(n); + } + } + else if ( v.is("zones") ) + { + std::string zone; + v.set_first_token(); + for ( int i = 0; v.get_next_token(zone); i++ ) + { + if ( zone == "any" ) + { + rule_cfg.zones.clear(); + rule_cfg.zones.emplace_back(NETFLOW_ANY_ZONE); + break; + } + int z = std::stoi(zone); + if ( z < 0 or z >= NETFLOW_MAX_ZONES ) + return false; + rule_cfg.zones.emplace_back(z); + } + } + else if ( v.is("exclude") ) + { + is_exclude_rule = v.get_bool(); + } + else if ( v.is("create_host") ) + { + rule_cfg.create_host = v.get_bool(); + } + else if ( v.is("create_service") ) + { + rule_cfg.create_service = v.get_bool(); + } else return false; @@ -101,4 +204,3 @@ const PegInfo* NetflowModule::get_pegs() const ProfileStats* NetflowModule::get_profile() const { return &netflow_perf_stats; } - diff --git a/src/service_inspectors/netflow/netflow_module.h b/src/service_inspectors/netflow/netflow_module.h index 2240de149..3b5ba008d 100644 --- a/src/service_inspectors/netflow/netflow_module.h +++ b/src/service_inspectors/netflow/netflow_module.h @@ -22,7 +22,10 @@ #ifndef NETFLOW_MODULE_H #define NETFLOW_MODULE_H +#include + #include "framework/module.h" +#include "sfip/sf_cidr.h" #include "utils/util.h" #define NETFLOW_NAME "netflow" @@ -33,10 +36,77 @@ namespace snort struct SnortConfig; } +#define NETFLOW_MAX_ZONES 1024 +#define NETFLOW_ANY_ZONE (-1) + +// Used to create hash of key for indexing into cache. +struct NetflowHash +{ + size_t operator()(const snort::SfIp& ip) const + { + const uint64_t* ip64 = (const uint64_t*) ip.get_ip6_ptr(); + return std::hash() (ip64[0]) ^ + std::hash() (ip64[1]); + } +}; + +struct NetflowRule +{ + NetflowRule() { reset(); } + void reset() + { + networks.clear(); + zones.clear(); + create_host = create_service = false; + } + + bool filter_match(const snort::SfIp* ip, const int zone) const + { + bool zone_match = false; + if ( zones.empty() ) + zone_match = true; + else + { + for( int z : zones) + { + if ( z == NETFLOW_ANY_ZONE or z == zone ) + { + zone_match = true; + break; + } + } + } + if ( !zone_match ) + return false; + + if ( networks.empty() ) + return true; + for( auto const &net : networks ) + if ( net.contains(ip) == SFIP_CONTAINS ) + return true; + + return false; + } + + std::vector networks; + std::vector zones; + bool create_host = false; + bool create_service = false; +}; + +using NetflowRuleList = std::vector; +struct NetflowRules +{ + NetflowRuleList exclude; + NetflowRuleList include; +}; + struct NetflowConfig { - NetflowConfig() { dump_file = nullptr; } - const char* dump_file; + NetflowConfig() { } + const char* dump_file = nullptr; + std::unordered_map device_rule_map; + uint32_t update_timeout = 0; }; struct NetflowStats @@ -60,6 +130,7 @@ public: bool set(const char*, snort::Value&, snort::SnortConfig*) override; bool begin(const char*, int, snort::SnortConfig*) override; + bool end(const char*, int, snort::SnortConfig*) override; const PegInfo* get_pegs() const override; PegCount* get_counts() const override; @@ -73,8 +144,10 @@ public: { return true; } private: - NetflowConfig* conf; - + NetflowConfig* conf = nullptr; + NetflowRule rule_cfg = {}; + snort::SfIp device_ip_cfg = {}; + bool is_exclude_rule = false; }; #endif