diff --git a/inv/models/macblacklist.py b/inv/models/macblacklist.py new file mode 100644 index 0000000000000000000000000000000000000000..958d2b5da5e67ff3e0bc73d171f786e02df9a7b8 --- /dev/null +++ b/inv/models/macblacklist.py @@ -0,0 +1,132 @@ +# ---------------------------------------------------------------------- +# MACBlacklist model +# ---------------------------------------------------------------------- +# Copyright (C) 2007-2020 The NOC Project +# See LICENSE for details +# ---------------------------------------------------------------------- + +# Python modules +import operator +from threading import Lock +from collections import namedtuple +from typing import List + +# Third-party modules +from mongoengine.document import Document, EmbeddedDocument +from mongoengine.fields import ( + StringField, + BooleanField, + UUIDField, + ListField, + EmbeddedDocumentField, +) +import cachetools + +# NOC modules +from noc.inv.models.vendor import Vendor +from noc.inv.models.platform import Platform +from noc.core.mongo.fields import PlainReferenceField +from noc.core.prettyjson import to_json +from noc.core.mac import MAC + +_list_lock = Lock() +ListItem = namedtuple("ListItem", ["from_mac", "to_mac", "is_duplicated"]) + + +class MACBlacklistAffected(EmbeddedDocument): + vendor = PlainReferenceField(Vendor) + platform = PlainReferenceField(Platform) + + def __str__(self): + if self.platform: + return "%s:%s" % (self.vendor.name, self.platform.name) + return self.vendor.name + + def to_json(self): + r = {"vendor__code": self.vendor.code} + if self.platform: + r["platform__name"] = self.platform.name + return r + + +class MACBlacklist(Document): + meta = { + "collection": "macblacklist", + "strict": False, + "auto_create_index": False, + "json_collection": "inv.macblacklist", + "json_depends_on": ["inv.vendor", "inv.platform"], + } + + name = StringField(unique=True) + # Unique id + uuid = UUIDField(binary=True) + # + from_mac = StringField() + to_mac = StringField() + description = StringField() + affected = ListField(EmbeddedDocumentField(MACBlacklistAffected)) + is_duplicated = BooleanField(default=False) + + _list_cache = cachetools.TTLCache(100, ttl=60) + + def __str__(self): + return self.name + + def clean(self): + super().clean() + self.from_mac = str(MAC(self.from_mac)) + self.to_mac = str(MAC(self.to_mac)) + if self.from_mac > self.to_mac: + self.from_mac, self.to_mac = self.to_mac, self.from_mac + + def to_json(self): + r = { + "name": self.name, + "$collection": self._meta["json_collection"], + "uuid": self.uuid, + "from_mac": self.from_mac, + "to_mac": self.to_mac, + "affected": [a.to_json() for a in self.affected], + "is_duplicated": self.is_duplicated, + } + if self.description: + r["description"] = self.description + return to_json( + r, + order=[ + "name", + "uuid", + "from_mac", + "to_mac", + "description", + "is_duplicated", + "affected", + ], + ) + + def get_json_path(self): + return "%s.json" % self.name + + @classmethod + @cachetools.cachedmethod(operator.attrgetter("_list_cache"), lock=lambda _: _list_lock) + def _get_blacklist(cls) -> List[ListItem]: + return [ + ListItem(from_mac=MAC(d.from_mac), to_mac=MAC(d.to_mac), is_duplicated=d.is_duplicated) + for d in cls.objects.all() + ] + + @classmethod + def is_banned_mac(cls, mac: str, is_duplicated: bool = False) -> bool: + """ + Check if mac is banned for specified reason + :param mac: + :param is_duplicated: + :return: + """ + m = MAC(mac) + for item in cls._get_blacklist(): + if item.from_mac <= m <= item.to_mac: + if is_duplicated and item.is_duplicated: + return True + return False diff --git a/models.py b/models.py index 3cbdd526f2ae1b2bb738666cce1c12b4f426411e..0e36116b5d8a95efee86bbcdf9c9c61bb9a6eec0 100644 --- a/models.py +++ b/models.py @@ -155,6 +155,7 @@ _MODELS = { "inv.InterfaceClassificationRule": "noc.inv.models.interfaceclassificationrule.InterfaceClassificationRule", "inv.InterfaceProfile": "noc.inv.models.interfaceprofile.InterfaceProfile", "inv.Link": "noc.inv.models.link.Link", + "inv.MACBlacklist": "noc.inv.models.macblacklist.MACBlacklist", "inv.MACDB": "noc.inv.models.macdb.MACDB", "inv.MACLog": "noc.inv.models.maclog.MACLog", "inv.MapSettings": "noc.inv.models.mapsettings.MapSettings", @@ -335,6 +336,7 @@ COLLECTIONS = [ "inv.Vendor", "inv.Platform", "inv.Firmware", + "inv.MACBlacklist", "fm.MIBAlias", "gis.Layer", "fm.OIDAlias", diff --git a/services/discovery/jobs/base.py b/services/discovery/jobs/base.py index 4c409c686df75ec35a455f48de3138ed0bff599d..399bed9e899405528442f058c4a73c9db1ead3b6 100644 --- a/services/discovery/jobs/base.py +++ b/services/discovery/jobs/base.py @@ -909,9 +909,10 @@ class TopologyDiscoveryCheck(DiscoveryCheck): Resolve neighbor by hostname """ if hostname not in self.neighbor_hostname_cache: - n = DiscoveryID.objects.filter(hostname=hostname).first() - if n: - n = n.object + hosts = DiscoveryID.objects.filter(hostname=hostname)[:2] + n = None + if len(hosts) == 1: + n = hosts[0].object elif "." not in hostname: # Sometimes, domain part is truncated. # Try to resolve anyway diff --git a/services/discovery/jobs/box/lldp.py b/services/discovery/jobs/box/lldp.py index 16db4af12aba8d76ab68d912962bdfc975fa84aa..4f9090c9b91a7f3f9a96adc653c676453540a8cb 100644 --- a/services/discovery/jobs/box/lldp.py +++ b/services/discovery/jobs/box/lldp.py @@ -1,7 +1,7 @@ # --------------------------------------------------------------------- # LLDP check # --------------------------------------------------------------------- -# Copyright (C) 2007-2019 The NOC Project +# Copyright (C) 2007-2020 The NOC Project # See LICENSE for details # --------------------------------------------------------------------- @@ -21,6 +21,7 @@ from noc.core.lldp import ( LLDP_PORT_SUBTYPE_LOCAL, LLDP_PORT_SUBTYPE_UNSPECIFIED, ) +from noc.inv.models.macblacklist import MACBlacklist class LLDPCheck(TopologyDiscoveryCheck): @@ -47,7 +48,15 @@ class LLDPCheck(TopologyDiscoveryCheck): chassis_subtype = neighbor_id["remote_chassis_id_subtype"] chassis_id = neighbor_id["remote_chassis_id"] if chassis_subtype == LLDP_CHASSIS_SUBTYPE_MAC: - return self.get_neighbor_by_mac(chassis_id) + if MACBlacklist.is_banned_mac(chassis_id, is_duplicated=True): + self.logger.info("Banned MAC %s found. Trying to negotiate via hostname") + if "remote_system_name" in neighbor_id: + return self.get_neighbor_by_hostname(neighbor_id["remote_system_name"]) + else: + self.logger.info("No remote hostname for %s. Giving up.") + return None + else: + return self.get_neighbor_by_mac(chassis_id) elif chassis_subtype == LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS: return self.get_neighbor_by_ip(chassis_id) elif chassis_subtype == LLDP_CHASSIS_SUBTYPE_LOCAL: diff --git a/services/web/apps/inv/macblacklist/__init__.py b/services/web/apps/inv/macblacklist/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/services/web/apps/inv/macblacklist/views.py b/services/web/apps/inv/macblacklist/views.py new file mode 100644 index 0000000000000000000000000000000000000000..7100c8ae0c74263b9ca52140b4a925b8872b63b6 --- /dev/null +++ b/services/web/apps/inv/macblacklist/views.py @@ -0,0 +1,21 @@ +# ---------------------------------------------------------------------- +# inv.macblacklist application +# ---------------------------------------------------------------------- +# Copyright (C) 2007-2020 The NOC Project +# See LICENSE for details +# ---------------------------------------------------------------------- + +# NOC modules +from noc.lib.app.extdocapplication import ExtDocApplication +from noc.inv.models.macblacklist import MACBlacklist +from noc.core.translation import ugettext as _ + + +class MACBlacklistApplication(ExtDocApplication): + """ + MACBlacklist application + """ + + title = "MAC Blacklist" + menu = [_("Setup"), _("MAC Blacklist")] + model = MACBlacklist diff --git a/ui/web/inv/macblacklist/Application.js b/ui/web/inv/macblacklist/Application.js new file mode 100644 index 0000000000000000000000000000000000000000..060a3316b31dfee3c9602862cf173c46441252a4 --- /dev/null +++ b/ui/web/inv/macblacklist/Application.js @@ -0,0 +1,153 @@ +//--------------------------------------------------------------------- +// inv.macblacklist application +//--------------------------------------------------------------------- +// Copyright (C) 2007-2020 The NOC Project +// See LICENSE for details +//--------------------------------------------------------------------- +console.debug("Defining NOC.inv.macblacklist.Application"); + +Ext.define("NOC.inv.macblacklist.Application", { + extend: "NOC.core.ModelApplication", + requires: [ + "NOC.inv.macblacklist.Model", + "NOC.inv.vendor.LookupField", + "NOC.inv.platform.LookupField" + ], + model: "NOC.inv.macblacklist.Model", + search: true, + initComponent: function() { + var me = this; + + me.jsonPanel = Ext.create("NOC.core.JSONPreview", { + app: me, + restUrl: new Ext.XTemplate('/inv/macblacklist/{id}/json/'), + previewName: new Ext.XTemplate('MAC Blacklist: {name}') + }); + + me.ITEM_JSON = me.registerItem(me.jsonPanel); + + Ext.apply(me, { + columns: [ + { + text: __("Name"), + dataIndex: "name", + width: 150 + }, + { + text: __("From"), + dataIndex: "from_mac", + width: 150 + }, + { + text: __("To"), + dataIndex: "from_mac", + width: 150 + }, + { + text: __("Duplicate"), + dataIndex: "is_duplicated", + width: 50, + renderer: NOC.render.Bool + } + ], + + fields: [ + { + name: "name", + xtype: "textfield", + fieldLabel: __("Name"), + allowBlank: false, + uiStyle: "medium" + }, + { + name: "uuid", + xtype: "displayfield", + fieldLabel: __("UUID"), + allowBlank: false + }, + { + name: "description", + xtype: "textarea", + fieldLabel: __("Description"), + allowBlank: true + }, + { + xtype: "container", + layout: "hbox", + defaults: { + padding: 4 + }, + items: [ + { + name: "from_mac", + xtype: "textfield", + fieldLabel: __("From"), + uiStyle: "medium", + allowBlank: false + }, + { + name: "to_mac", + xtype: "textfield", + fieldLabel: __("To"), + uiStyle: "medium", + allowBlank: false + } + ] + }, + { + xtype: "fieldset", + title: __("Reason"), + defaults: { + padding: 4 + }, + items: [ + { + name: "is_duplicated", + xtype: "checkbox", + boxLabel: __("Duplicated") + } + ] + }, + { + name: "affected", + xtype: "gridfield", + fieldLabel: __("Affected"), + columns: [ + { + text: __("Vendor"), + dataIndex: "vendor", + editor: "inv.vendor.LookupField", + width: 200, + renderer: NOC.render.Lookup("vendor") + }, + { + text: __("Platform"), + dataIndex: "platform", + editor: "inv.platform.LookupField", + width: 200, + renderer: NOC.render.Lookup("platform") + } + ] + } + ], + formToolbar: [ + { + text: __("JSON"), + glyph: NOC.glyph.file, + tooltip: __("Show JSON"), + hasAccess: NOC.hasPermission("read"), + scope: me, + handler: me.onJSON + } + ] + }); + me.callParent(); + }, + + // + onJSON: function() { + var me = this; + me.showItem(me.ITEM_JSON); + me.jsonPanel.preview(me.currentRecord); + } +}); \ No newline at end of file diff --git a/ui/web/inv/macblacklist/LookupField.js b/ui/web/inv/macblacklist/LookupField.js new file mode 100644 index 0000000000000000000000000000000000000000..0483272b002d2b0f86b3a77334f0f69eea0d3cbf --- /dev/null +++ b/ui/web/inv/macblacklist/LookupField.js @@ -0,0 +1,12 @@ +//--------------------------------------------------------------------- +// NOC.inv.macblacklist.Lookup +//--------------------------------------------------------------------- +// Copyright (C) 2007-2020 The NOC Project +// See LICENSE for details +//--------------------------------------------------------------------- +console.debug("Defining NOC.inv.macblacklist.LookupField"); + +Ext.define("NOC.inv.macblacklist.LookupField", { + extend: "NOC.core.LookupField", + alias: "widget.inv.macblacklist.LookupField" +}); \ No newline at end of file diff --git a/ui/web/inv/macblacklist/Model.js b/ui/web/inv/macblacklist/Model.js new file mode 100644 index 0000000000000000000000000000000000000000..c5c9053135d556fbce33845428c5f7ad78a4d087 --- /dev/null +++ b/ui/web/inv/macblacklist/Model.js @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// inv.macblacklist Model +//--------------------------------------------------------------------- +// Copyright (C) 2007-2020 The NOC Project +// See LICENSE for details +//--------------------------------------------------------------------- +console.debug("Defining NOC.inv.macblacklist.Model"); + +Ext.define("NOC.inv.macblacklist.Model", { + extend: "Ext.data.Model", + rest_url: "/inv/macblacklist/", + + fields: [ + { + name: "id", + type: "string" + }, + { + name: "name", + type: "string" + }, + { + name: "uuid", + type: "string" + }, + { + name: "from_mac", + type: "string" + }, + { + name: "to_mac", + type: "string" + }, + { + name: "description", + type: "string" + }, + { + name: "affected", + type: "auto" + }, + { + name: "is_duplicated", + type: "boolean" + } + ] +}); \ No newline at end of file