]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2808 in SNORT/snort3 from ~SBAIGAL/snort3:netflow_cfg to master
authorSteve Chew (stechew) <stechew@cisco.com>
Wed, 31 Mar 2021 22:38:58 +0000 (22:38 +0000)
committerSteve Chew (stechew) <stechew@cisco.com>
Wed, 31 Mar 2021 22:38:58 +0000 (22:38 +0000)
Squashed commit of the following:

commit d895eb631410232976bd389e90a2cd3b2c6650b0
Author: Steven Baigal (sbaigal) <sbaigal@cisco.com>
Date:   Tue Mar 23 11:23:12 2021 -0400

    netflow: add device list configuration

    netflow: add filter matching for v5 decoder

src/service_inspectors/netflow/netflow.cc
src/service_inspectors/netflow/netflow_module.cc
src/service_inspectors/netflow/netflow_module.h

index 136a25237f52bcbb290d060b4f884ea160ec927f..599737ebde851bb33efce1a138fa6c38070a4d54 100644 (file)
@@ -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<uint64_t>() (ip64[0]) ^
-               std::hash<uint64_t>() (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 <snort::SfCidr>& 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 <int>& 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<snort::SfIp> 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;
 }
 
index 095735488fdad38237c6a6b6fdf24d3694875fa9..8f8c2316b52c54e1173d6b8f82de3c56ce3444e5 100644 (file)
@@ -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; }
-
index 2240de149f23945902552696178c066bd494b36c..3b5ba008da7eeb3fe073a44e033ea11dbaec195d 100644 (file)
 #ifndef NETFLOW_MODULE_H
 #define NETFLOW_MODULE_H
 
+#include <unordered_map>
+
 #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<uint64_t>() (ip64[0]) ^
+               std::hash<uint64_t>() (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 <snort::SfCidr> networks;
+    std::vector <int> zones;
+    bool create_host = false;
+    bool create_service = false;
+};
+
+using NetflowRuleList = std::vector<NetflowRule>;
+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 <snort::SfIp, NetflowRules, NetflowHash> 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