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
{
// -----------------------------------------------------------------------------
// 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)
{
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;
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;
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;
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;
if( version == 5 )
{
- retval = decode_netflow_v5(data, size);
+ retval = decode_netflow_v5(data, size, p, cfg);
if ( retval )
{
++netflow_stats.packets;
void tterm() override;
void eval(snort::Packet*) override;
+ void show(const snort::SnortConfig*) const override;
private:
const NetflowConfig *config;
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;
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;
}
// -----------------------------------------------------------------------------
// 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 }
};
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;
ProfileStats* NetflowModule::get_profile() const
{ return &netflow_perf_stats; }
-
#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"
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
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;
{ return true; }
private:
- NetflowConfig* conf;
-
+ NetflowConfig* conf = nullptr;
+ NetflowRule rule_cfg = {};
+ snort::SfIp device_ip_cfg = {};
+ bool is_exclude_rule = false;
};
#endif