return nrows;
}
+static inline uint64_t hash_mac(const uint8_t* a)
+{
+ uint64_t hash = a[0];
+ hash = (hash << 8) | a[1];
+ hash = (hash << 8) | a[2];
+ hash = (hash << 8) | a[3];
+ hash = (hash << 8) | a[4];
+ hash = (hash << 8) | a[5];
+ return hash;
+}
+
class HashKeyOperations
{
public:
return is_monitored(p, DF_APP, *flag, DF_APP_CHECKED, DF_APP_MONITORED);
}
-bool DiscoveryFilter::is_host_monitored(const Packet* p, uint8_t* flag)
+bool DiscoveryFilter::is_host_monitored(const Packet* p, uint8_t* flag, const SfIp* ip)
{
if ( flag == nullptr )
- return is_monitored(p, DF_HOST);
- return is_monitored(p, DF_HOST, *flag, DF_HOST_CHECKED, DF_HOST_MONITORED);
+ return is_monitored(p, DF_HOST, ip);
+ return is_monitored(p, DF_HOST, *flag, DF_HOST_CHECKED, DF_HOST_MONITORED, ip);
}
bool DiscoveryFilter::is_user_monitored(const Packet* p, uint8_t* flag)
}
bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type, uint8_t& flag,
- uint8_t checked, uint8_t monitored)
+ uint8_t checked, uint8_t monitored, const SfIp* ip)
{
if ( flag & checked )
return flag & monitored;
flag |= checked;
- if ( is_monitored(p, type) )
+ if ( is_monitored(p, type, ip) )
{
flag |= monitored;
return true;
return false;
}
-bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type)
+bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type, const SfIp* ip)
{
if ( !vartable )
return true; // when not configured, 'any' ip/port/zone are monitored by default
if (!varip and zone != DF_ANY_ZONE)
varip = get_list(type, DF_ANY_ZONE, true);
- return sfvar_ip_in(varip, p->ptrs.ip_api.get_src()); // source ip only
+ if (!p->ptrs.ip_api.get_src() and !ip)
+ return true; // Don't check for non-IP, non ARP
+
+ const SfIp* host_ip = (ip) ? ip : p->ptrs.ip_api.get_src();
+ return sfvar_ip_in(varip, host_ip);
}
bool DiscoveryFilter::is_port_excluded(const Packet* p)
// If flag is provided (preferable), results are stored in flag to avoid future lookups
bool is_app_monitored(const snort::Packet* p, uint8_t* flag = nullptr);
- bool is_host_monitored(const snort::Packet* p, uint8_t* flag = nullptr);
+ bool is_host_monitored(const snort::Packet* p, uint8_t* flag = nullptr,
+ const snort::SfIp* ip = nullptr);
bool is_user_monitored(const snort::Packet* p, uint8_t* flag = nullptr);
private:
enum Direction { CLIENT, SERVER, NUM_DIRECTIONS };
bool is_monitored(const snort::Packet* p, FilterType type, uint8_t& flag,
- uint8_t checked, uint8_t monitored);
- bool is_monitored(const snort::Packet* p, FilterType type);
+ uint8_t checked, uint8_t monitored, const snort::SfIp* ip = nullptr);
+ bool is_monitored(const snort::Packet* p, FilterType type, const snort::SfIp* ip = nullptr);
void add_ip(FilterType type, ZoneType zone, std::string& ip);
sfip_var_t* get_list(FilterType type, ZoneType zone, bool exclude_empty = false);
if ( !memcmp(mac, hm.mac, MAC_SIZE) )
return false;
- if ( primary )
+ macs.emplace_back(ttl, mac, primary, last_seen);
+ return true;
+}
+
+const HostMac* HostTracker::get_hostmac(const uint8_t* mac)
+{
+ if ( !mac or !memcmp(mac, zero_mac, MAC_SIZE) )
+ return nullptr;
+
+ std::lock_guard<std::mutex> lck(host_tracker_lock);
+
+ for ( const auto& hm : macs )
+ if ( !memcmp(mac, hm.mac, MAC_SIZE) )
+ return &hm;
+
+ return nullptr;
+}
+
+bool HostTracker::update_mac_ttl(const uint8_t* mac, uint8_t new_ttl)
+{
+ if ( !mac or !memcmp(mac, zero_mac, MAC_SIZE) )
+ return false;
+
+ std::lock_guard<std::mutex> lck(host_tracker_lock);
+
+ for ( auto& hm : macs )
+ if ( !memcmp(mac, hm.mac, MAC_SIZE) )
+ {
+ if (hm.ttl < new_ttl)
+ {
+ hm.ttl = new_ttl;
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+bool HostTracker::make_primary(const uint8_t* mac)
+{
+ if ( !mac or !memcmp(mac, zero_mac, MAC_SIZE) )
+ return false;
+
+ HostMac* hm = nullptr;
+
+ std::lock_guard<std::mutex> lck(host_tracker_lock);
+
+ for ( auto& hm_iter : macs )
+ if ( !memcmp(mac, hm_iter.mac, MAC_SIZE) )
+ {
+ hm = &hm_iter;
+ break;
+ }
+
+ if ( !hm )
+ return false;
+
+ if (!hm->primary)
{
- // only one primary mac (e.g., from ARP) is maintained at the front
- if ( !macs.empty() )
- macs.front().primary = 0;
- macs.emplace_front(ttl, mac, primary, last_seen);
+ hm->primary = true;
+ return true;
}
- else
- macs.emplace_back(ttl, mac, primary, last_seen);
- return true;
+
+ return false;
+}
+
+HostMac* HostTracker::get_max_ttl_hostmac()
+{
+ std::lock_guard<std::mutex> lck(host_tracker_lock);
+
+ HostMac* max_ttl_hm = nullptr;
+ uint8_t max_ttl = 0;
+
+ for ( auto& hm : macs )
+ {
+ if (hm.primary)
+ return &hm;
+
+ if (hm.ttl > max_ttl)
+ {
+ max_ttl = hm.ttl;
+ max_ttl_hm = &hm;
+ }
+ }
+
+ return max_ttl_hm;
+}
+
+void HostTracker::update_vlan(uint16_t vth_pri_cfi_vlan, uint16_t vth_proto)
+{
+ vlan_tag_present = true;
+ vlan_tag.vth_pri_cfi_vlan = vth_pri_cfi_vlan;
+ vlan_tag.vth_proto = vth_proto;
+}
+
+bool HostTracker::has_vlan()
+{
+ return vlan_tag_present;
+}
+
+uint16_t HostTracker::get_vlan()
+{
+ return vlan_tag.vth_pri_cfi_vlan;
+}
+
+void HostTracker::get_vlan_details(uint8_t& cfi, uint8_t& priority, uint16_t& vid)
+{
+ cfi = vlan_tag.cfi();
+ priority = vlan_tag.priority();
+ vid = vlan_tag.vid();
}
void HostTracker::copy_data(uint8_t& p_hops, uint32_t& p_last_seen, list<HostMac>*& p_macs)
#include "main/thread.h"
#include "network_inspectors/appid/application_ids.h"
#include "protocols/protocol_ids.h"
+#include "protocols/vlan.h"
#include "time/packet_time.h"
struct HostTrackerStats
bool inferred_appid;
};
+enum HostType
+{
+ HOST_TYPE_HOST=0,
+ HOST_TYPE_ROUTER,
+ HOST_TYPE_BRIDGE,
+ HOST_TYPE_NAT,
+ HOST_TYPE_LB
+};
+
typedef HostCacheAllocIp<HostMac> HostMacAllocator;
typedef HostCacheAllocIp<HostApplication> HostAppAllocator;
return last_event;
}
+ void set_host_type(HostType rht)
+ { host_type = rht; }
+
+ uint8_t get_hops() { return hops; }
+ void update_hops(uint8_t h) { hops = h; }
+
// Returns true if a new mac entry is added, false otherwise
bool add_mac(const uint8_t* mac, uint8_t ttl, uint8_t primary);
+ // Returns true if a mac entry TTL is updated and decreased, false otherwise
+ bool update_mac_ttl(const uint8_t* mac, uint8_t new_ttl);
+
+ // Returns true if we changed primary (false->true), false otherwise
+ bool make_primary(const uint8_t* mac);
+
+ // Returns the hostmac pointer with the highest TTL
+ HostMac* get_max_ttl_hostmac();
+
+ // Returns the matching host_mac
+ const HostMac* get_hostmac(const uint8_t* mac);
+
+ void update_vlan(uint16_t vth_pri_cfi_vlan, uint16_t vth_proto);
+ bool has_vlan();
+ uint16_t get_vlan();
+ void get_vlan_details(uint8_t& cfi, uint8_t& priority, uint16_t& vid);
+
// The caller owns and deletes the copied list of mac addresses
void copy_data(uint8_t& p_hops, uint32_t& p_last_seen, std::list<HostMac>*& p_macs);
uint32_t last_event; // the last time an event was generated
std::list<HostMac, HostMacAllocator> macs;
std::vector<HostApplication, HostAppAllocator> services;
+ bool vlan_tag_present = false;
+ vlan::VlanTagHdr vlan_tag;
+ HostType host_type;
// Hide / delete the constructor from the outside world. We don't want to
// have zombie host trackers, i.e. host tracker objects that live outside
ht.add_mac(mac1, 9, 0);
test_time = 1562198407; // this time should be the time of the second mac address
ht.update_last_seen();
- ht.add_mac(mac2, 3, 1); // this primary mac should go to the front of the list
+ ht.add_mac(mac2, 3, 1); // this primary mac should go to the back of the list
ht.add_service(80, IpProtocol::TCP, 676, true);
test_time = 1562198409; // this time should be the last seen time of the host
STRCMP_EQUAL(host_tracker_string.c_str(),
"\n hops: 255, time: 2019-07-04 00:00:09"
"\nmacs size: 2"
- "\n mac: CA:FE:C0:FF:EE:00, ttl: 3, primary: 1, time: 2019-07-04 00:00:07"
"\n mac: FE:ED:DE:AD:BE:EF, ttl: 9, primary: 0, time: 2019-07-04 00:00:04"
+ "\n mac: CA:FE:C0:FF:EE:00, ttl: 3, primary: 1, time: 2019-07-04 00:00:07"
"\nservices size: 2"
"\n port: 80, proto: 6, appid: 676, inferred"
"\n port: 443, proto: 6, appid: 1122");
rna_inspector.h
rna_logger.cc
rna_logger_common.h
+ rna_mac_cache.cc
+ rna_mac_cache.h
rna_module.cc
rna_module.h
rna_pnd.cc
to log the discovered hosts into a file, one can
1) issue socket command: host_cache.dump('file.out'), or
2) add lua config: host_cache = { dump_file = 'file.out'}.
+
+RNA Uses HostCacheMac, derived from LruCacheSharedMemcap, to track MAC addresses. The implementation
+of the MAC cache is as follows:
+
+ HostCacheMac (LruCacheSharedMemcap<uint8_t*, LruMac, HashMac>)
+ |- - - - Key = uint8_t[6] (mac address)
+ |- - - - Data = LruMac
+ | | - - - - Valuetype = HostMacIp (derived from snort::HostMac)
+ | | - - - - Data = std::vector<ValueType, MacAllocator<ValueType>>
+ |
+ |- - - - Hash = HashMac
+ | |
+ | - - - - Takes uint8_t[6], returns 64-bit hash of MAC. This allows us to
+ | hash a 48 bit value (MAC) to a 64 bit key with relatively
+ few collisions. AA:BB:CC:DD:EE:FF becomes 0xAABBCCDDEEFF
+
+In RNAPnd discover_network_ethernet, in some scenarios, we are required to create a host tracker to
+be used for exclusively for logging. The call chain is the following:
+ -> discover_network_ethernet
+ | discover_network_arp
+ | discover_network_bpdu
+ | discover_switch
+
+We pass a host tracker pointer by reference (HostTracker**) from discover_network_ethernet down to
+the lower-level calls. These functions (discover[network_arp|network_bpdu|switch]) are responsible
+for setting the top level pointer to point at their own instantiated host tracker, as it
+needs to be preserved until discover_network_ethernet calls generate_change_vlan_update with this
+newly-created host tracker as an argument. This host tracker is deleted at the top level, and we must
+not return prior to that to avoid leaking any host trackers.
assert( !(BIT((unsigned)p->type()) & PROTO_BIT__ANY_SSN) );
// Handling untracked sessions, e.g., non-IP packets
- // pnd->analyze_flow_non_ip(p);
- UNUSED(p);
+ pnd->analyze_flow_non_ip(p);
}
void RnaInspector::show(const SnortConfig*) const
bool RnaLogger::log(uint16_t type, uint16_t subtype, const Packet* p, RnaTracker* ht,
const struct in6_addr* src_ip, const uint8_t* src_mac, uint32_t event_time,
- void* cond_var)
+ void* cond_var, const HostMac* hm)
{
if ( !enabled )
return false;
rle.cond_var = cond_var;
}
+ if (hm)
+ rle.hm = hm;
+
EventManager::call_loggers(nullptr, const_cast<Packet*>(p), "RNA", &rle);
return true;
}
#include "events/event.h"
#include "host_tracker/host_cache.h"
+#include "host_tracker/host_tracker.h"
namespace snort
{
const uint8_t* mac;
const struct in6_addr* ip = nullptr;
void* cond_var = nullptr;
+ const snort::HostMac* hm;
};
class RnaLogger
RnaLogger(const bool enable) : enabled(enable) { }
bool log(uint16_t type, uint16_t subtype, const snort::Packet* p, RnaTracker* ht,
const struct in6_addr* src_ip, const uint8_t* src_mac,
- uint32_t event_time = 0, void* cond_var = nullptr);
+ uint32_t event_time = 0, void* cond_var = nullptr, const snort::HostMac* hm = nullptr);
private:
const bool enabled;
#define NEW_HOST 1
#define RNA_EVENT_CHANGE 1001
+ #define CHANGE_HOPS 5
+ #define CHANGE_MAC_INFO 13
+ #define CHANGE_MAC_ADD 14
#define CHANGE_HOST_UPDATE 15
+ #define CHANGE_VLAN_TAG 18
#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 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.
+//--------------------------------------------------------------------------
+
+// rna_mac_cache.cc author Michael Matirko <mmatirko@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "rna_mac_cache.h"
+
+#include <cassert>
+#include <vector>
+
+#ifdef UNIT_TEST
+#include "catch/snort_catch.h"
+#endif
+
+HostCacheMac host_cache_mac(MAC_CACHE_INITIAL_SIZE);
+
+void HostMacIp::update_vlan(uint16_t vth_pri_cfi_vlan, uint16_t vth_proto)
+{
+ vlan_tag_present = true;
+ vlan_tag.vth_pri_cfi_vlan = vth_pri_cfi_vlan;
+ vlan_tag.vth_proto = vth_proto;
+}
+
+bool HostMacIp::has_vlan()
+{
+ return vlan_tag_present;
+}
+
+uint16_t HostMacIp::get_vlan()
+{
+ return vlan_tag.vth_pri_cfi_vlan;
+}
+
+void HostMacIp::get_vlan_details(uint8_t& cfi, uint8_t& priority, uint16_t& vid)
+{
+ cfi = vlan_tag.cfi();
+ priority = vlan_tag.priority();
+ vid = vlan_tag.vid();
+}
+
+#ifdef UNIT_TEST
+TEST_CASE("RNA Mac Cache", "[rna_mac_cache]")
+{
+ SECTION("HostCacheMac: store, retrieve")
+ {
+ uint8_t a_mac[6] = {0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6};
+ uint8_t b_mac[6] = {0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6};
+ uint8_t c_mac[6] = {0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6};
+
+ uint8_t test_mac[6] = {0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6};
+
+ MacKey a(a_mac);
+ MacKey b(b_mac);
+ MacKey c(c_mac);
+ MacKey test(test_mac);
+
+ HostMacIp hm_b(b_mac, 1, 0);
+
+ bool new_host = false;
+ bool new_host_test = false;
+
+ auto a_ptr = host_cache_mac.find_else_create(a, &new_host);
+ assert(new_host);
+ assert(a_ptr);
+
+ auto b_ptr = host_cache_mac.find_else_create(b, &new_host);
+ assert(new_host);
+ b_ptr->insert(&hm_b);
+
+ auto c_ptr = host_cache_mac.find_else_create(c, &new_host);
+ assert(new_host);
+ assert(c_ptr);
+
+ // Try to add one of the previous macs again
+ auto test_ptr = host_cache_mac.find_else_create(test, &new_host_test);
+ assert(test_ptr);
+ assert(!new_host_test);
+
+ // Verify that test_ptr contains the host_mac we're looking for
+ assert(!memcmp(test_ptr->data.front().mac, b_mac, MAC_SIZE));
+ }
+
+ SECTION("HostCacheMac: MAC Hashing")
+ {
+ uint8_t a_mac[6] = {0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6};
+ uint64_t hash = snort::hash_mac(a_mac);
+ assert(hash == 0xA1A2A3A4A5A6);
+ }
+}
+#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 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.
+//--------------------------------------------------------------------------
+
+// rna_mac_cache.h author Michael Matirko <mmatirko@cisco.com>
+
+#ifndef RNA_MAC_CACHE
+#define RNA_MAC_CACHE
+
+#include "hash/hash_key_operations.h"
+// Non-standard, required to include template definition across compiler translation units
+#include "host_tracker/host_cache_allocator.cc"
+#include "host_tracker/host_cache_allocator.h"
+#include "host_tracker/host_cache.h"
+#include "host_tracker/host_tracker.h"
+#include "sfip/sf_ip.h"
+
+using snort::SfIp;
+
+#define MAC_CACHE_INITIAL_SIZE 1024 * 1024 // Default to 1 MB
+
+class HostMacIp : public snort::HostMac
+{
+public:
+ HostMacIp (const uint8_t* p_mac, uint8_t p_primary, uint32_t p_last_seen)
+ : snort::HostMac(0, p_mac, p_primary, p_last_seen), src_ip(nullptr), dst_ip(nullptr)
+ { }
+
+ HostMacIp (const uint8_t* p_mac, uint8_t p_primary, uint32_t p_last_seen,
+ SfIp* src, SfIp* dst) : snort::HostMac(0, p_mac, p_primary, p_last_seen),
+ src_ip(src), dst_ip(dst)
+ { }
+
+ SfIp* src_ip;
+ SfIp* dst_ip;
+
+ snort::HostType host_type;
+
+ void update_vlan(uint16_t vth_pri_cfi_vlan, uint16_t vth_proto);
+ bool has_vlan();
+ uint16_t get_vlan();
+ void get_vlan_details(uint8_t& cfi, uint8_t& priority, uint16_t& vid);
+
+private:
+ bool vlan_tag_present = false;
+ snort::vlan::VlanTagHdr vlan_tag;
+};
+
+class MacKey
+{
+public:
+ MacKey(const uint8_t *ma)
+ { memcpy(mac_addr, ma, MAC_SIZE); }
+
+ bool operator==(const MacKey& mk) const
+ {
+ return !(memcmp(mac_addr, mk.mac_addr, MAC_SIZE));
+ }
+
+ uint8_t mac_addr[MAC_SIZE];
+};
+
+struct HashMac
+{
+ uint64_t operator()(const MacKey& mk) const
+ { return snort::hash_mac(mk.mac_addr); }
+};
+
+template <class T>
+class HostCacheAllocMac : public HostCacheAlloc<T>
+{
+public:
+ template <class U>
+ struct rebind
+ {
+ typedef HostCacheAllocMac<U> other;
+ };
+
+ using HostCacheAlloc<T>::lru;
+
+ HostCacheAllocMac();
+};
+
+class LruMac
+{
+public:
+ typedef HostMacIp ValueType;
+ std::vector<ValueType, HostCacheAllocMac<ValueType>> data;
+
+ bool isempty()
+ { return data.size() == 0; }
+
+ void insert(HostMacIp *hmi)
+ { data.emplace_back(*hmi); }
+
+};
+
+typedef LruCacheSharedMemcap<MacKey, LruMac, HashMac> HostCacheMac;
+extern HostCacheMac host_cache_mac;
+
+template <class T>
+HostCacheAllocMac<T>::HostCacheAllocMac()
+{
+ lru = &host_cache_mac;
+}
+#endif
#include "rna_module.h"
#include <cassert>
+#include <fstream>
+#include <iomanip>
+#include <sstream>
+#include <sys/stat.h>
#include "log/messages.h"
+#include "lua/lua.h"
#include "main/snort_config.h"
#include "main/swapper.h"
#include "managers/inspector_manager.h"
+#include "managers/module_manager.h"
#include "src/main.h"
+#include "utils/util.h"
+
+#include "rna_mac_cache.h"
#ifdef UNIT_TEST
#include "catch/snort_catch.h"
//-------------------------------------------------------------------------
// rna commands, params, and pegs
//-------------------------------------------------------------------------
+static int dump_mac_cache(lua_State* L)
+{
+ RnaModule* mod = (RnaModule*) ModuleManager::get_module(RNA_NAME);
+ if ( mod )
+ mod->log_mac_cache( luaL_optstring(L, 1, nullptr) );
+ return 0;
+}
static int reload_fingerprint(lua_State*)
{
return 0;
}
+std::string format_dump_mac(uint8_t mac[MAC_SIZE])
+{
+ std::stringstream ss;
+ ss << std::hex;
+
+ for(int i=0; i < MAC_SIZE; i++)
+ {
+ ss << std::setfill('0') << std::setw(2) << static_cast<int>(mac[i]);
+ if (i != MAC_SIZE - 1)
+ ss << ":";
+ }
+
+ return ss.str();
+}
+
static const Command rna_cmds[] =
{
{ "reload_fingerprint", reload_fingerprint, nullptr,
"reload rna database of fingerprint patterns/signatures" },
+ { "dump_macs", dump_mac_cache, nullptr,
+ "dump rna's internal MAC trackers" },
{ nullptr, nullptr, nullptr, nullptr }
};
{ "log_when_idle", Parameter::PT_BOOL, nullptr, "false",
"enable host update logging when snort is idle" },
+ { "dump_file", Parameter::PT_STRING, nullptr, nullptr,
+ "file name to dump RNA mac cache on shutdown; won't dump by default" },
+
{ nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
};
RnaModule::~RnaModule()
{
+ if ( dump_file )
+ {
+ log_mac_cache(dump_file);
+ snort_free((void*)dump_file);
+ }
+
delete mod_conf;
}
mod_conf->enable_logger = v.get_bool();
else if (v.is("log_when_idle"))
mod_conf->log_when_idle = v.get_bool();
+ else if ( v.is("dump_file") )
+ {
+ if ( dump_file )
+ snort_free((void*)dump_file);
+ dump_file = snort_strdup(v.get_string());
+ }
else
return false;
ProfileStats* RnaModule::get_profile() const
{ return &rna_perf_stats; }
+bool RnaModule::log_mac_cache(const char* outfile)
+{
+ if ( !outfile )
+ {
+ LogMessage("File name is needed!\n");
+ return 0;
+ }
+
+ struct stat file_stat;
+ if ( stat(outfile, &file_stat) == 0 )
+ {
+ LogMessage("File %s already exists!\n", outfile);
+ return 0;
+ }
+
+ std::ofstream out_stream(outfile);
+ if ( !out_stream )
+ {
+ snort::LogMessage("Error opening %s for dumping MAC cache", outfile);
+ }
+
+ std::string str;
+ const auto&& lru_data = host_cache_mac.get_all_data();
+ out_stream << "Current mac cache size: " << host_cache_mac.mem_size() << " bytes, "
+ << lru_data.size() << " trackers" << std::endl << std::endl;
+ for ( const auto& elem : lru_data )
+ {
+ str = "MAC: ";
+
+ if (elem.second->data.size() > 0)
+ str += format_dump_mac(elem.second->data.front().mac);
+ else
+ str += "No MacHostTracker found for entry ";
+
+ str += "\n Key: " + std::to_string(hash_mac(elem.second->data.front().mac));
+ out_stream << str << std::endl << std::endl;
+ }
+ out_stream.close();
+
+ return 0;
+}
+
#ifdef UNIT_TEST
TEST_CASE("RNA module", "[rna_module]")
{
#define RNA_NAME "rna"
#define RNA_HELP "Real-time network awareness and OS fingerprinting (experimental)"
+std::string format_dump_mac(uint8_t mac[6]);
+
struct RnaStats
{
PegCount icmp_bidirectional;
bool begin(const char*, int, snort::SnortConfig*) override;
bool set(const char*, snort::Value&, snort::SnortConfig*) override;
bool end(const char*, int, snort::SnortConfig*) override;
+ bool log_mac_cache(const char* outfile);
const snort::Command* get_commands() const override;
RnaModuleConfig* get_config();
private:
RnaModuleConfig* mod_conf = nullptr;
+ const char* dump_file = nullptr;
+
};
#endif
#include "rna_pnd.h"
-#include "protocols/eth.h"
+#include <algorithm>
+
+#include "protocols/arp.h"
+#include "protocols/bpdu.h"
#include "protocols/icmp4.h"
#include "protocols/packet.h"
+#include "protocols/protocol_ids.h"
#include "protocols/tcp.h"
#include "rna_logger_common.h"
#endif
using namespace snort;
+using namespace bpdu;
static inline bool is_eligible_packet(const Packet* p)
{
void RnaPnd::discover_network_non_ip(const Packet* p)
{
// process rna discovery for non-ip in mac cache
- UNUSED(p);
+ discover_network_ethernet(p);
}
void RnaPnd::discover_network_tcp(const Packet* p)
void RnaPnd::discover_network(const Packet* p, uint8_t ttl)
{
bool new_host = false;
+ bool new_mac = false;
const auto& src_ip = p->ptrs.ip_api.get_src();
auto ht = host_cache.find_else_create(*src_ip, &new_host);
+
if ( !new_host )
ht->update_last_seen(); // this should be done always and foremost
const auto& src_mac = layer::get_eth_layer(p)->ether_src;
- ht->add_mac(src_mac, ttl, 0);
+
+ new_mac = ht->add_mac(src_mac, ttl, 0);
if ( new_host )
{
logger.log(RNA_EVENT_NEW, NEW_HOST, p, &ht,
(const struct in6_addr*) src_ip->get_ip6_ptr(), src_mac);
}
- else if ( update_timeout )
+
+ if ( new_mac and !new_host )
+ logger.log(RNA_EVENT_CHANGE, CHANGE_MAC_ADD, p, &ht,
+ (const struct in6_addr*) src_ip->get_ip6_ptr(), src_mac,
+ 0, nullptr, ht->get_hostmac(src_mac));
+
+ if ( ht->update_mac_ttl(src_mac, ttl) )
+ {
+ logger.log(RNA_EVENT_CHANGE, CHANGE_MAC_INFO, p, &ht,
+ (const struct in6_addr*) src_ip->get_ip6_ptr(), src_mac,
+ 0, nullptr, ht->get_hostmac(src_mac));
+
+ HostMac* hm = ht->get_max_ttl_hostmac();
+ if (hm and hm->primary and ht->get_hops())
+ {
+ ht->update_hops(0);
+ logger.log(RNA_EVENT_CHANGE, CHANGE_HOPS, p, &ht,
+ (const struct in6_addr*) src_ip->get_ip6_ptr(), src_mac);
+ }
+ }
+
+ if ( !new_host )
+ {
generate_change_host_update(&ht, p, src_ip, src_mac, packet_time());
+ }
+}
+
+inline void RnaPnd::update_vlan(const Packet* p, HostMacIp& hm)
+{
+ if (!(p->proto_bits & PROTO_BIT__VLAN))
+ return;
+ const vlan::VlanTagHdr* vh = layer::get_vlan_layer(p);
+
+ if (vh)
+ hm.update_vlan(vh->vth_pri_cfi_vlan, vh->vth_proto);
+}
+
+void RnaPnd::generate_change_vlan_update(RnaTracker *rt, const Packet* p,
+ const uint8_t* src_mac, HostMacIp& hm, bool isnew)
+{
+ if (!(p->proto_bits & PROTO_BIT__VLAN))
+ return;
+
+ const vlan::VlanTagHdr* vh = layer::get_vlan_layer(p);
+
+ if (!vh)
+ return;
+
+ if (isnew or !hm.has_vlan() or hm.get_vlan() != vh->vth_pri_cfi_vlan)
+ {
+ time_t timestamp = packet_time() - update_timeout;
+
+ if (!isnew)
+ update_vlan(p, hm);
+
+ rt->get()->update_vlan(vh->vth_pri_cfi_vlan, vh->vth_proto);
+ logger.log(RNA_EVENT_CHANGE, CHANGE_VLAN_TAG, p, rt, nullptr,
+ src_mac, rt->get()->get_last_seen(), (void*) ×tamp);
+ }
+}
+
+void RnaPnd::generate_change_vlan_update(RnaTracker *rt, const Packet* p,
+ const uint8_t* src_mac, const SfIp* src_ip, bool isnew)
+{
+ if (!(p->proto_bits & PROTO_BIT__VLAN))
+ return;
+
+ const vlan::VlanTagHdr* vh = layer::get_vlan_layer(p);
+
+ if (!vh)
+ return;
+
+ if (isnew or !rt->get()->has_vlan() or rt->get()->get_vlan() != vh->vth_pri_cfi_vlan)
+ {
+ time_t timestamp = packet_time() - update_timeout;
+
+ rt->get()->update_vlan(vh->vth_pri_cfi_vlan, vh->vth_proto);
+ logger.log(RNA_EVENT_CHANGE, CHANGE_VLAN_TAG, p, rt,
+ (const struct in6_addr*) src_ip->get_ip6_ptr(),
+ src_mac, rt->get()->get_last_seen(), (void*) ×tamp);
+ }
}
void RnaPnd::generate_change_host_update(RnaTracker* ht, const Packet* p,
generate_change_host_update(&h.second, nullptr, &h.first, nullptr, sec);
}
+void RnaPnd::generate_new_host_mac(const Packet* p, RnaTracker ht)
+{
+ // In general, this is the default case for mac eventing.
+ // Ex. if BPDU dsap, ssap checks fail, we fallback here to
+ // generate a new_host event
+
+ bool new_host_mac = false;
+ MacKey mk(layer::get_eth_layer(p)->ether_src);
+
+ auto hm_ptr = host_cache_mac.find_else_create(mk, &new_host_mac);
+
+ if (new_host_mac)
+ {
+ HostMacIp hm(mk.mac_addr, 0, p->pkth->ts.tv_sec);
+
+ update_vlan(p, hm);
+ hm_ptr->insert(&hm);
+
+ ht.get()->update_last_seen();
+ ht.get()->add_mac(mk.mac_addr, 0, 0);
+
+ logger.log(RNA_EVENT_NEW, NEW_HOST, p, &ht,
+ (const struct in6_addr*) nullptr, mk.mac_addr);
+
+ generate_change_vlan_update(&ht, p, mk.mac_addr, hm, true);
+ }
+ else
+ {
+ if (hm_ptr and !hm_ptr->isempty())
+ {
+ generate_change_vlan_update(&ht, p, mk.mac_addr, hm_ptr->data.front(), false);
+ }
+ }
+}
+
+// RNA Flow Tracking for non-IP connections (ARP, BPDU, CDP)
+void RnaPnd::discover_network_ethernet(const Packet* p)
+{
+ #define BPDU_ID 0x42
+ #define SNAP_ID 0xAA
+ int retval = 1;
+ HostTracker* ht = new HostTracker();
+ RnaTracker rt = std::shared_ptr<snort::HostTracker>(ht);
+
+ if (!p->is_eth())
+ return;
+
+ if (layer::get_arp_layer(p))
+ {
+ retval = discover_network_arp(p, &rt);
+ }
+
+ // If we have an inner LLC layer, grab it
+ Layer lyr = p->layers[p->num_layers-1];
+ if (p->layers[p->num_layers-1].prot_id == ProtocolId::ETHERNET_LLC)
+ {
+ uint16_t etherType = rna_get_eth(p);
+
+ if (!etherType || etherType > static_cast<uint16_t>(ProtocolId::ETHERTYPE_MINIMUM))
+ {
+ generate_new_host_mac(p, rt);
+ return;
+ }
+
+ const RNA_LLC* llc = (const RNA_LLC*) lyr.start;
+
+ if (llc->s.s.DSAP != llc->s.s.SSAP)
+ {
+ generate_new_host_mac(p, rt);
+ return;
+ }
+
+ switch (llc->s.s.DSAP)
+ {
+ case BPDU_ID:
+ {
+ retval = discover_network_bpdu(p, ((const uint8_t*)llc + sizeof(RNA_LLC)), rt);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ if (retval)
+ generate_new_host_mac(p, rt);
+
+ return;
+}
+
+int RnaPnd::discover_network_arp(const Packet* p, RnaTracker* ht_ref)
+{
+ bool new_host_mac = false;
+ bool new_host = false;
+
+ MacKey mk(layer::get_eth_layer(p)->ether_src);
+ const auto& src_mac = mk.mac_addr;
+ HostMacIp hm(mk.mac_addr, 0, p->pkth->ts.tv_sec);
+
+ const snort::arp::EtherARP *ah = layer::get_arp_layer(p);
+
+ if (ntohs(ah->ea_hdr.ar_hrd) != 0x0001)
+ return 1;
+ if (ntohs(ah->ea_hdr.ar_pro) != 0x0800)
+ return 1;
+ if (ah->ea_hdr.ar_hln != 6 || ah->ea_hdr.ar_pln != 4)
+ return 1;
+ if ((ntohs(ah->ea_hdr.ar_op) != 0x0002))
+ return 1;
+ if (memcmp(src_mac, ah->arp_sha, MAC_SIZE))
+ return 1;
+ if (!ah->arp_spa32)
+ return 1;
+
+ SfIp spa(ah->arp_spa, AF_INET);
+
+ // In the case where SPA is not monitored, log as a generic "NEW MAC"
+ if ( !(filter.is_host_monitored(p, nullptr, &spa) ))
+ return 1;
+
+ auto ht = host_cache.find_else_create(spa, &new_host);
+
+ std::shared_ptr<LruMac> hm_ptr = host_cache_mac.find_else_create(mk, &new_host_mac);
+
+ if (new_host_mac)
+ hm_ptr->insert(&hm);
+
+ *ht_ref = ht;
+
+ if( new_host )
+ {
+ ht->update_hops(255);
+ ht->add_mac(src_mac, 0, 0);
+ logger.log(RNA_EVENT_NEW, NEW_HOST, p, &ht,
+ (const struct in6_addr*) spa.get_ip6_ptr(), src_mac);
+ }
+
+ if ( ht->add_mac(src_mac, 0, 0) )
+ {
+ logger.log(RNA_EVENT_CHANGE, CHANGE_MAC_ADD, p, ht_ref,
+ (const struct in6_addr*) spa.get_ip6_ptr(), src_mac,
+ 0, nullptr, ht->get_hostmac(src_mac));
+ }
+ else if (ht->make_primary(src_mac))
+ {
+ logger.log(RNA_EVENT_CHANGE, CHANGE_MAC_INFO, p, ht_ref,
+ (const struct in6_addr*) spa.get_ip6_ptr(), src_mac,
+ 0, nullptr, ht->get_hostmac(src_mac));
+ }
+
+ generate_change_vlan_update(&ht, p, src_mac, &spa, true);
+
+ if ( ht->get_hops() )
+ {
+ ht->update_hops(0);
+ logger.log(RNA_EVENT_CHANGE, CHANGE_HOPS, p, ht_ref,
+ (const struct in6_addr*) spa.get_ip6_ptr(), src_mac, 0, nullptr,
+ ht->get_hostmac(src_mac));
+ }
+
+ return 0;
+}
+
+int RnaPnd::discover_network_bpdu(const Packet* p, const uint8_t* data,
+ RnaTracker ht_ref)
+{
+ const uint8_t* dst_mac = layer::get_eth_layer(p)->ether_dst;
+
+ const BPDUData* stp;
+
+ if (!isBPDU(dst_mac))
+ return 1;
+ stp = reinterpret_cast<const BPDUData*>(data);
+ if (stp->id || stp->version)
+ return 1;
+ if (stp->type != BPDU_TYPE_TOPCHANGE)
+ return 1;
+
+ return discover_switch(p, ht_ref);
+}
+
+int RnaPnd::discover_switch(const Packet* p, RnaTracker ht_ref)
+{
+ bool new_host = false;
+ MacKey mk(layer::get_eth_layer(p)->ether_src);
+
+ std::shared_ptr<LruMac> hm_ptr = host_cache_mac.find_else_create(mk, &new_host);
+
+ if (new_host)
+ {
+ HostMacIp hm(mk.mac_addr, 0, p->pkth->ts.tv_sec);
+
+ hm.host_type = HOST_TYPE_BRIDGE;
+
+ update_vlan(p, hm);
+ hm_ptr->insert(&hm);
+
+ ht_ref.get()->update_last_seen();
+ ht_ref.get()->add_mac(mk.mac_addr, 0, 1);
+
+ logger.log(RNA_EVENT_NEW, NEW_HOST, p, &ht_ref,
+ (const struct in6_addr*) nullptr, mk.mac_addr);
+
+ generate_change_vlan_update(&ht_ref, p, mk.mac_addr, hm, true);
+ }
+ else
+ {
+ generate_change_vlan_update(&ht_ref, p, mk.mac_addr, hm_ptr->data.front(), false);
+ }
+
+ return 0;
+}
+
#ifdef UNIT_TEST
TEST_CASE("RNA pnd", "[non-ip]")
{
#ifndef RNA_PND_H
#define RNA_PND_H
+#include <limits>
+
#include "helpers/discovery_filter.h"
+#include "host_tracker/host_tracker.h"
+#include "protocols/eth.h"
+#include "protocols/layer.h"
+#include "protocols/vlan.h"
+#include "sfip/sf_ip.h"
+#include "rna_mac_cache.h"
#include "rna_logger.h"
-#include "sfip/sf_ip.h"
+
+#define USHRT_MAX std::numeric_limits<unsigned short>::max()
namespace snort
{
SYN, SYN_ACK, MIDSTREAM
};
+#pragma pack(1)
+struct RNA_LLC
+{
+ union
+ {
+ struct
+ {
+ uint8_t DSAP;
+ uint8_t SSAP;
+ } s;
+ uint16_t proto;
+ } s;
+ uint8_t flags;
+};
+#pragma pack()
+
+static inline unsigned short rna_get_eth(const snort::Packet* p)
+{
+ const snort::vlan::VlanTagHdr* vh = nullptr;
+ const snort::eth::EtherHdr* eh = nullptr;
+
+ if (p->proto_bits & PROTO_BIT__VLAN)
+ vh = snort::layer::get_vlan_layer(p);
+
+ if (vh)
+ return ntohs(vh->vth_proto);
+ else if ((eh = snort::layer::get_eth_layer(p)))
+ return ntohs(eh->ether_type);
+ return USHRT_MAX;
+}
+
class RnaPnd
{
public:
// generate change event for all hosts in the ip cache
void generate_change_host_update();
+ // Change vlan event related utilities
+ inline void update_vlan(const snort::Packet* p, HostMacIp& hm);
+ void generate_change_vlan_update(RnaTracker *rt, const snort::Packet* p,
+ const uint8_t* src_mac, HostMacIp& hm, bool isnew);
+ void generate_change_vlan_update(RnaTracker *rt, const snort::Packet* p,
+ const uint8_t* src_mac, const SfIp* src_ip, bool isnew);
+
+ void generate_new_host_mac(const snort::Packet* p, RnaTracker ht);
+
private:
// General rna utilities not associated with flow
void discover_network_icmp(const snort::Packet* p);
void discover_network_udp(const snort::Packet* p);
void discover_network(const snort::Packet* p, uint8_t ttl);
+ // RNA utilities for non-IP packets
+ void discover_network_ethernet(const snort::Packet* p);
+ int discover_network_arp(const snort::Packet* p, RnaTracker* ht_ref);
+ int discover_network_bpdu(const snort::Packet* p, const uint8_t* data, RnaTracker ht_ref);
+ int discover_switch(const snort::Packet* p, RnaTracker ht_ref);
+
RnaLogger logger;
DiscoveryFilter filter;
time_t update_timeout;
#ifndef RNA_MODULE_MOCK_H
#define RNA_MODULE_MOCK_H
+#include "../rna_mac_cache.cc"
+
bool Swapper::reload_in_progress = false;
THREAD_LOCAL RnaStats rna_stats;
THREAD_LOCAL ProfileStats rna_perf_stats;
static std::string message;
static Request mock_request;
+const char* luaL_optlstring(lua_State*, int, const char*, size_t*) { return nullptr; }
+
void Request::respond(const char* msg, bool, bool)
{
message = msg;
{
Inspector* InspectorManager::get_inspector(const char*, bool, const SnortConfig*)
{ return nullptr; }
+
+Module* ModuleManager::get_module(const char*)
+{ return nullptr; }
+
+char* snort_strdup(const char* s)
+{ return strdup(s); }
+
Module::Module(const char*, const char*, const Parameter*, bool) {}
void Module::sum_stats(bool) {}
void Module::show_stats() {}
set (PROTOCOL_HEADERS
arp.h
+ bpdu.h
cisco_meta_data.h
eapol.h
eth.h
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 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.
+//--------------------------------------------------------------------------
+// bpdu.h author Michael Matirko <mmatirko@cisco.com>
+
+// Represents the BPDU (bridge protocol data unit) payload used for Spanning Tree Protocol
+
+#ifndef PROTOCOLS_BPDU_H
+#define PROTOCOLS_BPDU_H
+
+namespace snort
+{
+namespace bpdu
+{
+
+#define BPDU_TYPE_TOPCHANGE 0x80 // Topology change type BPDU
+
+static const uint8_t BPDU_DEST[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x00};
+
+bool isBPDU(const uint8_t mac[6]);
+
+struct BPDUData
+{
+ uint16_t id;
+ uint8_t version;
+ uint8_t type;
+} __attribute__((__packed__));
+
+
+bool isBPDU(const uint8_t mac[6])
+{
+ return (memcmp(mac, BPDU_DEST, sizeof(BPDU_DEST)) == 0);
+}
+
+} // namespace bpdu
+} // namespace snort
+
+#endif