From 57be36071e3adcb8c7d59f096cfb82d2f47ce8c5 Mon Sep 17 00:00:00 2001 From: Ludovic Pouzenc Date: Mon, 16 May 2016 20:32:13 +0200 Subject: Inspector: more of them. Discovery: guess from mac address and some unification. Equipment : lowercase for iface --- haircontrol/data.py | 16 ++++--- haircontrol/discovery.py | 119 +++++++++++++++++++++++++++++++++++++++------- haircontrol/inspectors.py | 24 ++++++++++ tests/test_discovery.py | 9 ++-- 4 files changed, 139 insertions(+), 29 deletions(-) diff --git a/haircontrol/data.py b/haircontrol/data.py index 463e38a..2ab8268 100644 --- a/haircontrol/data.py +++ b/haircontrol/data.py @@ -5,7 +5,7 @@ class EtherDomain: self.mac2ip = {} def __repr__(self): - return '([\n%s\n],\n%s\n)'%(',\n'.join(' %s'%repr(e) for e in get_equipment_list_sorted()), repr(self.ip2mac)) + return '([\n%s\n],\n%s\n)'%(',\n'.join(' %s'%repr(e) for e in self.get_equipment_list_sorted()), repr(self.ip2mac)) def get_equipment_list_sorted(self): e_list = list(self.equipments.values()) @@ -40,18 +40,20 @@ class Equipment: def add_iface(self, ifname, mac): iface = self.ifaces.get(ifname) + mac_lower = mac.lower() if iface: - iface.mac = mac + iface.mac = mac_lower else: - self.ifaces[ifname] = Interface(ifname, mac) + self.ifaces[ifname] = Interface(ifname, mac_lower) def add_seen_mac(self, ifname, mac): iface = self.ifaces.get(ifname) + mac_lower = mac.lower() if not iface: - print("Warn : add_seen_mac(%s, %s) auto-create iface on %s"%(ifname, mac, self.name)) + print("Warn : add_seen_mac(%s, %s) auto-create iface on %s"%(ifname, mac_lower, self.name)) iface = Interface(ifname) self.ifaces[ifname] = iface - iface.mac_seen.append(mac) + iface.mac_seen.append(mac_lower) class Interface: def __init__(self, name=None, mac=None): @@ -60,6 +62,6 @@ class Interface: self.mac_seen = [] def __repr__(self): - #return repr( ( self.mac, self.name, self.mac_seen ) ) - return repr( ( self.mac, self.name, '[ %i mac_seen ]'%len(self.mac_seen) ) ) + return repr( ( self.mac, self.name, self.mac_seen ) ) + #return repr( ( self.mac, self.name, '[ %i mac_seen ]'%len(self.mac_seen) ) ) diff --git a/haircontrol/discovery.py b/haircontrol/discovery.py index 7c3c9f0..d6839b2 100644 --- a/haircontrol/discovery.py +++ b/haircontrol/discovery.py @@ -5,16 +5,77 @@ from haircontrol.inspectors import * class Discovery: def __init__(self): #, inspector): - # XXX Use only one Inspector + self.net = EtherDomain() + self.dummy_inspector = DummyInspector() self.linux_inspector = LinuxInspector() self.ubnt_inspector = UbntInspector() self.toughswitch_inspector = ToughSwitchInspector() - self.net = EtherDomain() + self.edgemax_inspector = EdgeMaxInspector() + self.netonix_inspector = NetonixInspector() + self.mikrotik_inspector = MikrotikInspector() + self.openwrt_inspector = OpenWRTInspector() + + self.inspector_by_mac = { + # Mikrotik + 'd4:ca:6d': self.mikrotik_inspector, + 'e4:8d:8c': self.mikrotik_inspector, + # Netonix + 'ec:13:b2': self.netonix_inspector, + 'ec:13:b3': self.netonix_inspector, + # PC + '0c:c4:7a': self.linux_inspector, # SuperMicro + '52:54:00': self.linux_inspector, # VM + # TP-Link + '10:fe:ed': self.openwrt_inspector, + '14:cc:20': self.openwrt_inspector, + '30:b5:c2': self.openwrt_inspector, + '54:e6:fc': self.openwrt_inspector, + '60:e3:27': self.openwrt_inspector, + '64:66:b3': self.openwrt_inspector, + '64:70:02': self.openwrt_inspector, + '90:f6:52': self.openwrt_inspector, + 'a0:f3:c1': self.openwrt_inspector, + 'b0:48:7a': self.openwrt_inspector, + 'c0:4a:00': self.openwrt_inspector, + 'c4:6e:1f': self.openwrt_inspector, + 'c4:e9:84': self.openwrt_inspector, + 'f4:ec:38': self.openwrt_inspector, + 'f8:1a:67': self.openwrt_inspector, + 'f8:d1:11': self.openwrt_inspector, + # Ubnt + '00:15:6d': self.ubnt_inspector, + '00:27:22': self.ubnt_inspector, + '04:18:d6:07': self.toughswitch_inspector, + '04:18:d6': self.ubnt_inspector, + '06:18:d6': self.ubnt_inspector, # Non globaly unique ?! + '24:a4:3c:05': self.toughswitch_inspector, + '24:a4:3c:06': self.toughswitch_inspector, + '24:a4:3c:07': self.toughswitch_inspector, + '24:a4:3c:3c': self.toughswitch_inspector, + '24:a4:3c:3d': self.toughswitch_inspector, + '24:a4:3c:b3': self.toughswitch_inspector, + '24:a4:3c': self.ubnt_inspector, + '44:d9:e7': self.edgemax_inspector, + 'dc:9f:db:80': self.toughswitch_inspector, + 'dc:9f:db:81': self.toughswitch_inspector, + 'dc:9f:db': self.ubnt_inspector, + } + + + def inspector(self, mac): + if mac: + return self.inspector_by_mac.get(mac[:11]) \ + or self.inspector_by_mac.get(mac[:8]) \ + or self.dummy_inspector + else: + return self.dummy_inspector + def discover_static_hinting(self, name_ip_tuples): for name, ip in name_ip_tuples: self.net.add_equipment(Equipment(name, ip)) + def discover_lldp_hinting(self, e_lldp): self.net.add_equipment(e_lldp) self.linux_inspector.connect(e_lldp) @@ -37,6 +98,7 @@ class Discovery: self.linux_inspector.disconnect() + def discover_from_root(self, e_root): self.net.add_equipment(e_root) self.linux_inspector.connect(e_root) @@ -49,7 +111,8 @@ class Discovery: self.linux_inspector.disconnect() - # Create Equipment object for all neighbours (if not already previously done by hinting) + # Create/Update Equipment object for all neighbours + # (could be already created by hinting) for iface in e_root.ifaces.values(): local_ifname = iface.name local_mac = iface.mac @@ -57,10 +120,13 @@ class Discovery: remote_ip = self.net.mac2ip.get(remote_mac) if remote_ip: e = self.net.equipments.get(remote_ip) - if not e: + if e and not e.ifaces: + e.add_iface(None, remote_mac) + elif not e: e = Equipment('?', remote_ip) e.add_iface('?', remote_mac) self.net.add_equipment(e) + # Inspect all non-already inspected equipement done = False @@ -69,28 +135,47 @@ class Discovery: for ip,e in self.net.equipments.items(): if not e.inspected: done = False - # Inspect antennas bridge tables - if ip.startswith('172.16.1'): # XXX Filter with OUI - self.ubnt_inspector.connect(e) + + # Find the right inspector from equipment's first iface mac address + if e.ifaces: + e_first_mac = next(iter(e.ifaces.values())).mac + i = self.inspector(e_first_mac) + else: + # XXX Custom hack + if e.mgmtip.startswith('172.16.1'): + i = self.ubnt_inspector + elif e.mgmtip == '172.16.30.23': + i = self.edgemax_inspector + elif e.mgmtip.startswith('172.16.3'): + i = self.toughswitch_inspector + else: + i = self.dummy_inspector + + i.connect(e) + + # Inspect antennas + if isinstance(i, UbntInspector): # Learn local interfaces - result = self.ubnt_inspector.command('status.cgi') + result = i.command('status.cgi') for (ifname, mac) in result: if ifname not in [ 'lo', 'wifi0', 'br0' ]: # XXX configurable filter e.add_iface(ifname, mac) # Learn bridge tables - result = self.ubnt_inspector.command('brmacs.cgi') + result = i.command('brmacs.cgi') for (ifname, mac) in result: e.add_seen_mac(ifname, mac) - self.ubnt_inspector.disconnect() + # Inspect switches - elif ip.startswith('172.16.3'): # XXX Filter with OUI - self.toughswitch_inspector.connect(e) - result = self.toughswitch_inspector.command('mactable_data.cgi') + elif isinstance(i, ToughSwitchInspector): + result = i.command('mactable_data.cgi') for (ifname, mac) in result: e.add_seen_mac(ifname, mac) - self.toughswitch_inspector.disconnect() - # Flag unknowns as inspected (and warn) + + elif isinstance(i, EdgeMaxInspector): + result = i.command('mac-addr-table') + for (mac, ifname) in result: + e.add_seen_mac(ifname, mac) else: - e.inspected = 'cannot' - print("Notice: Unimplemented inspector for %s"%e) + print("Notice: Nothing inspected on %s"%e) + i.disconnect() diff --git a/haircontrol/inspectors.py b/haircontrol/inspectors.py index 3d7b766..04b05c1 100644 --- a/haircontrol/inspectors.py +++ b/haircontrol/inspectors.py @@ -62,6 +62,8 @@ class Inspector(): fd.close() return js +class DummyInspector(Inspector): + pass class LinuxInspector(Inspector): @@ -180,3 +182,25 @@ class ToughSwitchInspector(Inspector): }, } +class EdgeMaxInspector(Inspector): + + cmds = { + 'mac-addr-table': { + 'cmd':'show mac-addr-table', #XXX needs "enable" mode + 're': re.compile("\d+\s+(?P[A-F0-9:]+)\s+(?P[0-9/]+)\s") + # VLAN ID MAC Address Interface IfIndex Status + # 1 00:15:6D:8E:22:46 0/15 15 Learned + # 1 00:27:22:0E:67:F9 0/17 17 Learned + }, + } + +#TODO Implement them +class NetonixInspector(Inspector): + pass + +class MikrotikInspector(Inspector): + pass + +class OpenWRTInspector(Inspector): + pass + diff --git a/tests/test_discovery.py b/tests/test_discovery.py index ba30dab..2e7b308 100755 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -8,12 +8,11 @@ from haircontrol import discovery, data #, inspectors class TestDiscovery(unittest.TestCase): def setUp(self): - self.discovery = discovery.Discovery() #MockInspector('../test-data/input')) self.maxDiff=None + self.discovery = discovery.Discovery() #MockInspector('../test-data/input')) + self.ref_net = data.EtherDomain() #json.load('../test-data/ref-output/equipments.json') def test_wire_graph(self): - ref_net = data.EtherDomain() #json.load('../test-data/ref-output/equipments.json') - self.discovery.discover_lldp_hinting(data.Equipment('stg2', '172.16.0.253')) self.discovery.discover_static_hinting([ ('SW_SergeGOUSSE', '172.16.30.23'), @@ -21,8 +20,8 @@ class TestDiscovery(unittest.TestCase): ('SW_Eglise_ESTANCARBON', '172.16.30.38'), ]) self.discovery.discover_from_root(data.Equipment('stg', '172.16.0.254')) - - self.assertEqual(ref_net.get_equipment_list_sorted(), list(self.discovery.net.get_equipment_list_sorted())) +# self.assertEqual(self.ref_net.get_equipment_list_sorted(), list(self.discovery.net.get_equipment_list_sorted())) + print(self.discovery.net) if __name__ == '__main__': unittest.main() -- cgit v1.1