From: Masud Hasan (mashasan) Date: Fri, 7 Aug 2020 22:23:25 +0000 (+0000) Subject: Merge pull request #2339 in SNORT/snort3 from ~MMATIRKO/snort3:rna_mac to master X-Git-Tag: 3.0.2-5~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e9480f10b4e7808691666cb56fca2bb8b231adf0;p=thirdparty%2Fsnort3.git Merge pull request #2339 in SNORT/snort3 from ~MMATIRKO/snort3:rna_mac to master Squashed commit of the following: commit 10b80bae582fe4fc391b26f06cd57f8e90fc5a7d Author: Michael Matirko Date: Wed Jun 24 16:13:31 2020 -0400 Add RNA MAC-based discovery logic --- diff --git a/src/hash/hash_key_operations.h b/src/hash/hash_key_operations.h index b48ab11fb..5b4d6ae3d 100644 --- a/src/hash/hash_key_operations.h +++ b/src/hash/hash_key_operations.h @@ -65,6 +65,17 @@ static inline int hash_nearest_power_of_2(int nrows) 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: diff --git a/src/helpers/discovery_filter.cc b/src/helpers/discovery_filter.cc index 02ce41736..d408530f1 100644 --- a/src/helpers/discovery_filter.cc +++ b/src/helpers/discovery_filter.cc @@ -203,11 +203,11 @@ bool DiscoveryFilter::is_app_monitored(const Packet* p, uint8_t* flag) 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) @@ -218,14 +218,14 @@ 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; @@ -235,7 +235,7 @@ bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type, uint8_t& fl 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 @@ -255,7 +255,11 @@ bool DiscoveryFilter::is_monitored(const Packet* p, FilterType type) 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) diff --git a/src/helpers/discovery_filter.h b/src/helpers/discovery_filter.h index cd67bab27..a9616ac13 100644 --- a/src/helpers/discovery_filter.h +++ b/src/helpers/discovery_filter.h @@ -41,7 +41,8 @@ public: // 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: @@ -49,8 +50,8 @@ 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); diff --git a/src/host_tracker/host_tracker.cc b/src/host_tracker/host_tracker.cc index 2b15ff49b..0e1f35427 100644 --- a/src/host_tracker/host_tracker.cc +++ b/src/host_tracker/host_tracker.cc @@ -57,16 +57,118 @@ bool HostTracker::add_mac(const uint8_t* mac, uint8_t ttl, uint8_t primary) 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 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 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 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 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*& p_macs) diff --git a/src/host_tracker/host_tracker.h b/src/host_tracker/host_tracker.h index 639481e34..9f42611b6 100644 --- a/src/host_tracker/host_tracker.h +++ b/src/host_tracker/host_tracker.h @@ -36,6 +36,7 @@ #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 @@ -71,6 +72,15 @@ struct HostApplication bool inferred_appid; }; +enum HostType +{ + HOST_TYPE_HOST=0, + HOST_TYPE_ROUTER, + HOST_TYPE_BRIDGE, + HOST_TYPE_NAT, + HOST_TYPE_LB +}; + typedef HostCacheAllocIp HostMacAllocator; typedef HostCacheAllocIp HostAppAllocator; @@ -97,9 +107,32 @@ public: 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*& p_macs); @@ -122,6 +155,9 @@ private: uint32_t last_event; // the last time an event was generated std::list macs; std::vector 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 diff --git a/src/host_tracker/test/host_tracker_test.cc b/src/host_tracker/test/host_tracker_test.cc index 3e521094a..50116f87e 100644 --- a/src/host_tracker/test/host_tracker_test.cc +++ b/src/host_tracker/test/host_tracker_test.cc @@ -114,7 +114,7 @@ TEST(host_tracker, stringify) 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 @@ -127,8 +127,8 @@ TEST(host_tracker, stringify) 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"); diff --git a/src/network_inspectors/rna/CMakeLists.txt b/src/network_inspectors/rna/CMakeLists.txt index 3684996d5..3ab0b7297 100644 --- a/src/network_inspectors/rna/CMakeLists.txt +++ b/src/network_inspectors/rna/CMakeLists.txt @@ -18,6 +18,8 @@ set ( RNA_SOURCES 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 diff --git a/src/network_inspectors/rna/dev_notes.txt b/src/network_inspectors/rna/dev_notes.txt index 3fa1052fa..d90c9aaa5 100644 --- a/src/network_inspectors/rna/dev_notes.txt +++ b/src/network_inspectors/rna/dev_notes.txt @@ -50,3 +50,32 @@ or reader is not implemented yet. However, since RNA stores host information int 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) + |- - - - Key = uint8_t[6] (mac address) + |- - - - Data = LruMac + | | - - - - Valuetype = HostMacIp (derived from snort::HostMac) + | | - - - - Data = std::vector> + | + |- - - - 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. diff --git a/src/network_inspectors/rna/rna_inspector.cc b/src/network_inspectors/rna/rna_inspector.cc index 3c66c9bb7..e1ddfe4c0 100644 --- a/src/network_inspectors/rna/rna_inspector.cc +++ b/src/network_inspectors/rna/rna_inspector.cc @@ -96,8 +96,7 @@ void RnaInspector::eval(Packet* p) 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 diff --git a/src/network_inspectors/rna/rna_logger.cc b/src/network_inspectors/rna/rna_logger.cc index 8110d8e59..01e3b5686 100644 --- a/src/network_inspectors/rna/rna_logger.cc +++ b/src/network_inspectors/rna/rna_logger.cc @@ -42,7 +42,7 @@ using namespace snort; 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; @@ -58,6 +58,9 @@ bool RnaLogger::log(uint16_t type, uint16_t subtype, const Packet* p, RnaTracker rle.cond_var = cond_var; } + if (hm) + rle.hm = hm; + EventManager::call_loggers(nullptr, const_cast(p), "RNA", &rle); return true; } diff --git a/src/network_inspectors/rna/rna_logger.h b/src/network_inspectors/rna/rna_logger.h index ddb44e795..7bfc487c3 100644 --- a/src/network_inspectors/rna/rna_logger.h +++ b/src/network_inspectors/rna/rna_logger.h @@ -22,6 +22,7 @@ #include "events/event.h" #include "host_tracker/host_cache.h" +#include "host_tracker/host_tracker.h" namespace snort { @@ -41,6 +42,7 @@ struct RnaLoggerEvent : public Event const uint8_t* mac; const struct in6_addr* ip = nullptr; void* cond_var = nullptr; + const snort::HostMac* hm; }; class RnaLogger @@ -49,7 +51,7 @@ public: 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; diff --git a/src/network_inspectors/rna/rna_logger_common.h b/src/network_inspectors/rna/rna_logger_common.h index caf505994..ec06c93f4 100644 --- a/src/network_inspectors/rna/rna_logger_common.h +++ b/src/network_inspectors/rna/rna_logger_common.h @@ -25,6 +25,10 @@ #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 diff --git a/src/network_inspectors/rna/rna_mac_cache.cc b/src/network_inspectors/rna/rna_mac_cache.cc new file mode 100644 index 000000000..db7cdd1ca --- /dev/null +++ b/src/network_inspectors/rna/rna_mac_cache.cc @@ -0,0 +1,108 @@ +//-------------------------------------------------------------------------- +// 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 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "rna_mac_cache.h" + +#include +#include + +#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 diff --git a/src/network_inspectors/rna/rna_mac_cache.h b/src/network_inspectors/rna/rna_mac_cache.h new file mode 100644 index 000000000..f916ea5e5 --- /dev/null +++ b/src/network_inspectors/rna/rna_mac_cache.h @@ -0,0 +1,120 @@ +//-------------------------------------------------------------------------- +// 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 + +#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 HostCacheAllocMac : public HostCacheAlloc +{ +public: + template + struct rebind + { + typedef HostCacheAllocMac other; + }; + + using HostCacheAlloc::lru; + + HostCacheAllocMac(); +}; + +class LruMac +{ +public: + typedef HostMacIp ValueType; + std::vector> data; + + bool isempty() + { return data.size() == 0; } + + void insert(HostMacIp *hmi) + { data.emplace_back(*hmi); } + +}; + +typedef LruCacheSharedMemcap HostCacheMac; +extern HostCacheMac host_cache_mac; + +template +HostCacheAllocMac::HostCacheAllocMac() +{ + lru = &host_cache_mac; +} +#endif diff --git a/src/network_inspectors/rna/rna_module.cc b/src/network_inspectors/rna/rna_module.cc index 47bcdb999..462ca62ac 100644 --- a/src/network_inspectors/rna/rna_module.cc +++ b/src/network_inspectors/rna/rna_module.cc @@ -25,12 +25,21 @@ #include "rna_module.h" #include +#include +#include +#include +#include #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" @@ -41,6 +50,13 @@ using namespace snort; //------------------------------------------------------------------------- // 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*) { @@ -73,10 +89,27 @@ 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(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 } }; @@ -94,6 +127,9 @@ static const Parameter rna_params[] = { "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 } }; @@ -122,6 +158,12 @@ RnaModule::RnaModule() : Module(RNA_NAME, RNA_HELP, rna_params) RnaModule::~RnaModule() { + if ( dump_file ) + { + log_mac_cache(dump_file); + snort_free((void*)dump_file); + } + delete mod_conf; } @@ -144,6 +186,12 @@ bool RnaModule::set(const char*, Value& v, SnortConfig*) 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; @@ -187,6 +235,48 @@ const PegInfo* RnaModule::get_pegs() const 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]") { diff --git a/src/network_inspectors/rna/rna_module.h b/src/network_inspectors/rna/rna_module.h index 9fc20dd03..ece385d07 100644 --- a/src/network_inspectors/rna/rna_module.h +++ b/src/network_inspectors/rna/rna_module.h @@ -29,6 +29,8 @@ #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; @@ -56,6 +58,7 @@ public: 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(); @@ -68,6 +71,8 @@ public: private: RnaModuleConfig* mod_conf = nullptr; + const char* dump_file = nullptr; + }; #endif diff --git a/src/network_inspectors/rna/rna_pnd.cc b/src/network_inspectors/rna/rna_pnd.cc index 991f4069c..fe217a47c 100644 --- a/src/network_inspectors/rna/rna_pnd.cc +++ b/src/network_inspectors/rna/rna_pnd.cc @@ -26,9 +26,13 @@ #include "rna_pnd.h" -#include "protocols/eth.h" +#include + +#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" @@ -38,6 +42,7 @@ #endif using namespace snort; +using namespace bpdu; static inline bool is_eligible_packet(const Packet* p) { @@ -125,7 +130,7 @@ void RnaPnd::discover_network_ip(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) @@ -147,22 +152,104 @@ void RnaPnd::discover_network_udp(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, @@ -191,6 +278,220 @@ void RnaPnd::generate_change_host_update() 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(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(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 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(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 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]") { diff --git a/src/network_inspectors/rna/rna_pnd.h b/src/network_inspectors/rna/rna_pnd.h index 1fbbe3bce..47833ad43 100644 --- a/src/network_inspectors/rna/rna_pnd.h +++ b/src/network_inspectors/rna/rna_pnd.h @@ -20,10 +20,19 @@ #ifndef RNA_PND_H #define RNA_PND_H +#include + #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::max() namespace snort { @@ -35,6 +44,37 @@ enum class TcpPacketType 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: @@ -55,6 +95,15 @@ 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); @@ -64,6 +113,12 @@ private: 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; diff --git a/src/network_inspectors/rna/test/rna_module_mock.h b/src/network_inspectors/rna/test/rna_module_mock.h index 0724eb009..d7baad34e 100644 --- a/src/network_inspectors/rna/test/rna_module_mock.h +++ b/src/network_inspectors/rna/test/rna_module_mock.h @@ -21,12 +21,16 @@ #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; @@ -38,6 +42,13 @@ namespace snort { 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() {} diff --git a/src/protocols/CMakeLists.txt b/src/protocols/CMakeLists.txt index 20390609d..82e0d01a3 100644 --- a/src/protocols/CMakeLists.txt +++ b/src/protocols/CMakeLists.txt @@ -1,6 +1,7 @@ set (PROTOCOL_HEADERS arp.h + bpdu.h cisco_meta_data.h eapol.h eth.h diff --git a/src/protocols/bpdu.h b/src/protocols/bpdu.h new file mode 100644 index 000000000..d6ed773a1 --- /dev/null +++ b/src/protocols/bpdu.h @@ -0,0 +1,52 @@ +//-------------------------------------------------------------------------- +// 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 + +// 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