/* * * (C) 2013-19 - ntop.org * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "ntop_includes.h" /* *************************************** */ Mac::Mac(NetworkInterface *_iface, u_int8_t _mac[6]) : GenericHashEntry(_iface) { memcpy(mac, _mac, 6); special_mac = Utils::isSpecialMac(mac); source_mac = false, fingerprint = NULL; bridge_seen_iface_id = 0, lockDeviceTypeChanges = false; memset(&names, 0, sizeof(names)); device_type = device_unknown; host_pool_id = NO_HOST_POOL_ID; #ifdef NTOPNG_PRO captive_portal_notified = 0; #endif model = NULL, ssid = NULL; stats_reset_requested = data_delete_requested = false; stats = new MacStats(_iface); stats_shadow = NULL; last_stats_reset = ntop->getLastStatsReset(); /* assume fresh stats, may be changed by deserialize */ char redis_key[64], buf1[64], rsp[8]; char *mac_ptr = Utils::formatMac(mac, buf1, sizeof(buf1)); if(ntop->getMacManufacturers()) { manuf = ntop->getMacManufacturers()->getManufacturer(mac); if(manuf) checkDeviceTypeFromManufacturer(); } else manuf = NULL; #ifdef MANUF_DEBUG ntop->getTrace()->traceEvent(TRACE_NORMAL, "Assigned manufacturer [mac: %02x:%02x:%02x:%02x:%02x:%02x] [manufacturer: %s]", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], manuf ? manuf : "- not available -"); #endif #ifdef DEBUG char buf[32]; ntop->getTrace()->traceEvent(TRACE_NORMAL, "Created %s [total %u]", Utils::formatMac(mac, buf, sizeof(buf)), iface->getNumL2Devices()); #endif if(!special_mac && ntop->getPrefs()->is_idle_local_host_cache_enabled()) { deserializeFromRedis(); // Load the user defined device type, if available snprintf(redis_key, sizeof(redis_key), MAC_CUSTOM_DEVICE_TYPE, mac_ptr); if((ntop->getRedis()->get(redis_key, rsp, sizeof(rsp)) == 0) && rsp[0]) forceDeviceType((DeviceType)atoi(rsp)); readDHCPCache(); } updateHostPool(true /* inline with packet processing */, true /* first inc */); } /* *************************************** */ Mac::~Mac() { if(model) free(model); if(ssid) free(ssid); if(fingerprint) free(fingerprint); freeMacData(); if(stats) delete(stats); if(stats_shadow) delete(stats_shadow); #ifdef DEBUG ntop->getTrace()->traceEvent(TRACE_NORMAL, "Deleted %s [total %u][%s]", Utils::formatMac(mac, buf, sizeof(buf)), iface->getNumL2Devices(), source_mac ? "Host" : "Special"); #endif } /* *************************************** */ void Mac::set_hash_entry_state_idle() { if(source_mac) iface->decNumL2Devices(); if(!special_mac) { if(data_delete_requested) deleteRedisSerialization(); else if(ntop->getPrefs()->is_idle_local_host_cache_enabled()) serializeToRedis(); } /* Pool counters are updated both in and outside the datapath. So decPoolNumHosts must stay in the destructor to preserve counters consistency (no thread outside the datapath will change the last pool id) */ #ifdef HOST_POOLS_DEBUG char buf[32]; ntop->getTrace()->traceEvent(TRACE_NORMAL, "Going to decrease the number of pool l2 devices for %s " "[num pool l2 devices: %u]...", Utils::formatMac(mac, buf, sizeof(buf)), iface->getHostPools()->getNumPoolL2Devices(get_host_pool())); #endif iface->decPoolNumL2Devices(get_host_pool(), true /* Mac is deleted inline */); #ifdef HOST_POOLS_DEBUG ntop->getTrace()->traceEvent(TRACE_NORMAL, "Number of pool l2 devices decreased." "[num pool l2 devices: %u]", iface->getHostPools()->getNumPoolL2Devices(get_host_pool())); #endif GenericHashEntry::set_hash_entry_state_idle(); } /* *************************************** */ bool Mac::is_hash_entry_state_idle_transition_ready() { bool rc; if(getUses() > 0 || !iface->is_purge_idle_interface()) return(false); rc = isIdle(MAX_LOCAL_HOST_IDLE); #ifdef DEBUG if(true) { char buf[32]; ntop->getTrace()->traceEvent(TRACE_NORMAL, "Is idle %s [uses %u][%s][last: %u][diff: %d]", Utils::formatMac(mac, buf, sizeof(buf)), getUses(), rc ? "Idle" : "Not Idle", last_seen, iface->getTimeLastPktRcvd() - (last_seen+MAX_LOCAL_HOST_IDLE)); } #endif return(rc); } /* *************************************** */ static const char* location2str(MacLocation location) { switch(location) { case located_on_lan_interface: return "lan"; case located_on_wan_interface: return "wan"; default: return "unknown"; } } /* *************************************** */ void Mac::lua(lua_State* vm, bool show_details, bool asListElement) { char buf[32], *m; lua_newtable(vm); lua_push_str_table_entry(vm, "mac", m = Utils::formatMac(mac, buf, sizeof(buf))); lua_push_uint64_table_entry(vm, "bridge_seen_iface_id", bridge_seen_iface_id); if(show_details) { if(manuf) lua_push_str_table_entry(vm, "manufacturer", (char*)manuf); lua_push_bool_table_entry(vm, "source_mac", source_mac); lua_push_bool_table_entry(vm, "special_mac", special_mac); lua_push_str_table_entry(vm, "location", (char *) location2str(locate())); lua_push_uint64_table_entry(vm, "devtype", device_type); if(model) lua_push_str_table_entry(vm, "model", (char*)model); if(ssid) lua_push_str_table_entry(vm, "ssid", (char*)ssid); } stats->lua(vm, show_details); lua_push_str_table_entry(vm, "fingerprint", fingerprint ? fingerprint : (char*)""); lua_push_uint64_table_entry(vm, "seen.first", first_seen); lua_push_uint64_table_entry(vm, "seen.last", last_seen); lua_push_uint64_table_entry(vm, "duration", get_duration()); lua_push_uint64_table_entry(vm, "num_hosts", getNumHosts()); lua_push_uint64_table_entry(vm, "pool", get_host_pool()); if(asListElement) { lua_pushstring(vm, m); lua_insert(vm, -2); lua_settable(vm, -3); } } /* *************************************** */ bool Mac::isNull() const { u_int8_t zero_mac[6] = {0}; return !memcmp(mac, zero_mac, sizeof(mac)); } /* *************************************** */ bool Mac::equal(const u_int8_t _mac[6]) { if(!_mac) return(false); if(memcmp(mac, _mac, 6) == 0) return(true); else return(false); } /* *************************************** */ char* Mac::getSerializationKey(char *buf, uint bufsize) { char buf1[32]; char *mac_ptr = Utils::formatMac(mac, buf1, sizeof(buf1)); snprintf(buf, bufsize, MAC_SERIALIZED_KEY, iface->get_id(), mac_ptr); return(buf); } /* *************************************** */ void Mac::deserialize(json_object *o) { json_object *obj; if(json_object_object_get_ex(o, "seen.first", &obj)) first_seen = json_object_get_int64(obj); if(json_object_object_get_ex(o, "seen.last", &obj)) last_seen = json_object_get_int64(obj); if(json_object_object_get_ex(o, "last_stats_reset", &obj)) last_stats_reset = json_object_get_int64(obj); if(json_object_object_get_ex(o, "devtype", &obj)) device_type = (DeviceType)json_object_get_int(obj); if(json_object_object_get_ex(o, "model", &obj)) inlineSetModel((char*)json_object_get_string(obj)); if(json_object_object_get_ex(o, "ssid", &obj)) inlineSetSSID((char*)json_object_get_string(obj)); if(json_object_object_get_ex(o, "fingerprint", &obj)) inlineSetFingerprint((char*)json_object_get_string(obj)); stats->deserialize(o); checkStatsReset(); } /* *************************************** */ bool Mac::statsResetRequested() { return(stats_reset_requested || (last_stats_reset < ntop->getLastStatsReset())); } /* *************************************** */ void Mac::serialize(json_object *my_object, DetailsLevel details_level) { char buf[32]; json_object_object_add(my_object, "mac", json_object_new_string(Utils::formatMac(get_mac(), buf, sizeof(buf)))); json_object_object_add(my_object, "seen.first", json_object_new_int64(first_seen)); json_object_object_add(my_object, "seen.last", json_object_new_int64(last_seen)); json_object_object_add(my_object, "last_stats_reset", json_object_new_int64(last_stats_reset)); json_object_object_add(my_object, "devtype", json_object_new_int(device_type)); if(model) json_object_object_add(my_object, "model", json_object_new_string(model)); if(ssid) json_object_object_add(my_object, "ssid", json_object_new_string(ssid)); if(fingerprint) json_object_object_add(my_object, "fingerprint", json_object_new_string(fingerprint)); if(!statsResetRequested()) stats->getJSONObject(my_object); } /* *************************************** */ MacLocation Mac::locate() { if(iface->is_bridge_interface()) { if(bridge_seen_iface_id == iface->getBridgeLanInterfaceId()) return(located_on_lan_interface); else if(bridge_seen_iface_id == iface->getBridgeWanInterfaceId()) return(located_on_wan_interface); } else { if(bridge_seen_iface_id == DUMMY_BRIDGE_INTERFACE_ID) return(located_on_lan_interface); } return(located_on_unknown_interface); } /* *************************************** */ void Mac::updateHostPool(bool isInlineCall, bool firstUpdate) { if(!iface) return; #ifdef HOST_POOLS_DEBUG char buf[24]; u_int16_t cur_pool_id = get_host_pool(); ntop->getTrace()->traceEvent(TRACE_NORMAL, "Going to refresh pool for %s " "[pool id: %u]" "[pool num devices: %u]...", Utils::formatMac(get_mac(), buf, sizeof(buf)), cur_pool_id, iface->getHostPools()->getNumPoolL2Devices(get_host_pool())); #endif if(!firstUpdate) iface->decPoolNumL2Devices(get_host_pool(), isInlineCall); host_pool_id = iface->getHostPool(this); iface->incPoolNumL2Devices(get_host_pool(), isInlineCall); #ifdef HOST_POOLS_DEBUG ntop->getTrace()->traceEvent(TRACE_NORMAL, "Refresh done. " "[old pool id: %u]" "[new pool id: %u]" "[old pool num devices: %u]" "[new pool num devices: %u]", cur_pool_id, get_host_pool(), iface->getHostPools()->getNumPoolL2Devices(cur_pool_id), iface->getHostPools()->getNumPoolL2Devices(get_host_pool())); #endif } /* *************************************** */ char * Mac::getDHCPName(char * const buf, ssize_t buf_size) { if(buf && buf_size) { m.lock(__FILE__, __LINE__); snprintf(buf, buf_size, "%s", names.dhcp ? names.dhcp : ""); m.unlock(__FILE__, __LINE__); } return buf; } /* *************************************** */ void Mac::checkDeviceTypeFromManufacturer() { if(isNull()) return; if(strstr(manuf, "Networks") /* Arista, Juniper... */ || strstr(manuf, "Brocade") || strstr(manuf, "Routerboard") || strstr(manuf, "Alcatel-Lucent") || strstr(manuf, "AVM") ) setDeviceType(device_networking); else if(strstr(manuf, "Xerox") ) setDeviceType(device_printer); else if(strstr(manuf, "Raspberry Pi") || strstr(manuf, "PCS Computer Systems") /* VirtualBox */ ) setDeviceType(device_workstation); else { /* https://www.techrepublic.com/blog/data-center/mac-address-scorecard-for-common-virtual-machine-platforms/ */ if((!memcmp(mac, "\x00\x50\x56", 3)) || (!memcmp(mac, "\x00\x0C\x29", 3)) || (!memcmp(mac, "\x00\x05\x69", 3)) || (!memcmp(mac, "\x00\x03\xFF", 3)) || (!memcmp(mac, "\x00\x1C\x42", 3)) || (!memcmp(mac, "\x00\x0F\x4B", 3)) || (!memcmp(mac, "\x00\x16\x3E", 3)) || (!memcmp(mac, "\x08\x00\x27", 3)) ) setDeviceType(device_workstation); /* VM */ } } /* *************************************** */ void Mac::inlineSetModel(const char * const the_model) { if(!model && the_model && (model = strdup(the_model))) { if(strstr(model, "AppleTV") != NULL) setDeviceType(device_multimedia); else if(strstr(model, "MacBook") != NULL) setDeviceType(device_laptop); else if(strstr(model, "AirPort") != NULL) setDeviceType(device_wifi); else if(strstr(model, "Mac") != NULL) setDeviceType(device_workstation); else if(strstr(model, "TimeCapsule") != NULL) setDeviceType(device_nas); } } /* *************************************** */ bool Mac::inlineSetFingerprint(const char * const f) { if(!fingerprint && f) { fingerprint = strdup(f); return(true); } return(false); } /* *************************************** */ void Mac::inlineSetSSID(const char * const s) { if(!ssid && s && (ssid = strdup(s))) setDeviceType(device_wifi); } /* *************************************** */ void Mac::inlineSetDHCPName(const char * const dhcp_name) { if(!names.dhcp && dhcp_name && (names.dhcp = strdup(dhcp_name))) ; } /* *************************************** */ void Mac::checkDataReset() { if(data_delete_requested) { deleteMacData(); data_delete_requested = false; } } /* *************************************** */ void Mac::checkStatsReset() { if(statsResetRequested()) { MacStats *new_stats = new MacStats(iface); stats_shadow = stats; stats = new_stats; last_stats_reset = ntop->getLastStatsReset(); stats_reset_requested = false; } } /* *************************************** */ void Mac::updateStats(struct timeval *tv) { if(get_state() == hash_entry_state_idle) set_hash_entry_state_ready_to_be_purged(); checkDataReset(); checkStatsReset(); stats->updateStats(tv); } /* *************************************** */ void Mac::readDHCPCache() { /* Check DHCP cache */ char mac_str[24], buf[64], key[CONST_MAX_LEN_REDIS_KEY]; if(!names.dhcp && !isNull()) { Utils::formatMac(get_mac(), mac_str, sizeof(mac_str)); snprintf(key, sizeof(key), DHCP_CACHE, iface->get_id()); if(ntop->getRedis()->hashGet(key, mac_str, buf, sizeof(buf)) == 0) { names.dhcp = strdup(buf); } } } /* *************************************** */ void Mac::freeMacData() { // TODO: allow fingerprint, ssid, and model to be resettable if(names.dhcp) { free(names.dhcp); names.dhcp = NULL; } } /* *************************************** */ void Mac::deleteMacData() { m.lock(__FILE__, __LINE__); freeMacData(); m.unlock(__FILE__, __LINE__); source_mac = false; device_type = device_unknown; #ifdef NTOPNG_PRO captive_portal_notified = false; #endif first_seen = last_seen; }