From f362446a524efb4bd412cbb608d8704efd47d720 Mon Sep 17 00:00:00 2001 From: Ludovic Pouzenc Date: Sat, 14 May 2016 00:24:05 +0200 Subject: WIP : First try at getting some data from lldp and ip neigh. --- .gitignore | 1 + haircontrol/data.py | 27 +++++++++++++++++ haircontrol/discovery.py | 68 ++++++++++++++++++++++++++++++++++++++++++ test-data/ref-outpout/tree.txt | 21 +++++++++++++ tests/context.py | 5 ++++ tests/test_discovery.py | 43 ++++++++++++++++++++++++++ 6 files changed, 165 insertions(+) create mode 100644 .gitignore create mode 100644 haircontrol/data.py create mode 100644 haircontrol/discovery.py create mode 100644 test-data/ref-outpout/tree.txt create mode 100644 tests/context.py create mode 100755 tests/test_discovery.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/haircontrol/data.py b/haircontrol/data.py new file mode 100644 index 0000000..599d322 --- /dev/null +++ b/haircontrol/data.py @@ -0,0 +1,27 @@ +class Equipment: + def __init__(self, name=None, mgmtip=None): + self.name = name + self.mgmtip = mgmtip + self.ifaces = {} + + def __repr__(self): + return repr( (self.name, self.mgmtip, self.ifaces.values()) ) + + def add_seen_mac(self, ifname, mac): + iface = self.ifaces.get(ifname) + if not iface: + iface = Interface() + iface.name = ifname + self.ifaces[ifname] = iface + iface.mac_seen.append(mac) + +class Interface: + def __init__(self, name=None, mac=None): + self.name = name + self.mac = mac + self.mac_seen = [] + self.remote = [] + + def __repr__(self): + return repr( (self.mac, self.name) ) + diff --git a/haircontrol/discovery.py b/haircontrol/discovery.py new file mode 100644 index 0000000..d454283 --- /dev/null +++ b/haircontrol/discovery.py @@ -0,0 +1,68 @@ + +import re +import xml.etree.ElementTree +from haircontrol.data import Equipment + +class Discovery: + def __init__(self, inspector): + self.inspector = inspector + self.equipments = {} + self.ip2mac = {} + self.mac2ip = {} + + def add_equipment(self,e): + old = self.equipments.get(e.mgmtip) + if old: + print("Warn : %s replaced by %s"%(old,e)) + self.equipments[e.mgmtip] = e + + def index_mac_ip(self,mac, ip): + oldmac = self.ip2mac.get(ip) + if oldmac: + print("Warn : %s replaced by %s for %s"%(oldmac, mac, ip)) + oldip = self.mac2ip.get(mac) + if oldip: + print("Warn : %s replaced by %s for %s"%(oldip, ip, mac)) + self.ip2mac[ip] = mac + self.mac2ip[mac] = ip + + def discover_hinting_from_lldp(self, lldpnode): + self.inspector.connect(lldpnode) + fd = self.inspector.command('lldp') + root = xml.etree.ElementTree.parse(fd).getroot() + for iface in root.iter('interface'): + ifname = iface.get('name') + chassis = iface.find('chassis') + e = Equipment() + e.name = chassis.find('name').text + e.mgmtip = chassis.find('mgmt-ip').text + self.add_equipment(e) + for port in iface.findall('port'): + #remote_ifname = port.find('id').text + e.add_seen_mac(ifname, 'lldp') #XXX + fd.close() + self.inspector.disconnect() + + def discover_from_root(self, rootnode): + self.inspector.connect(rootnode) + fd = self.inspector.command('ip-neigh') + IPNEIGH = re.compile("^(?P[a-f0-9:.]+) dev (?P.*) lladdr (?P[a-f0-9:]*)") + for line in fd: + matches = IPNEIGH.search(line) + if matches: + ip, ifname, mac = [ matches.group(k) for k in ['ip','ifname','mac'] ] + self.index_mac_ip(mac, ip) + rootnode.add_seen_mac(ifname, mac) + fd.close() + self.inspector.disconnect() + +# fe80::8300 dev eth1 lladdr 10:fe:ed:f1:e1:f3 router STALE +# 172.16.11.46 dev eth1 lladdr 24:a4:3c:ee:89:ca STALE +# 172.16.20.3 dev eth1 lladdr 00:27:22:0e:74:15 STALE +# 172.16.20.210 dev eth1 lladdr c0:4a:00:fe:1f:87 REACHABLE +# 172.16.21.69 dev eth1 lladdr e8:de:27:b5:f2:b1 DELAY +# 172.16.20.216 dev eth1 lladdr c0:4a:00:fe:09:bd PERMANENT +# 172.16.11.41 dev eth1 lladdr 04:18:d6:0e:37:d4 STALE +# 172.16.11.104 dev eth1 lladdr 00:15:6d:8e:22:46 STALE +# 172.16.10.8 dev eth1 lladdr 00:27:22:0e:67:f9 STALE +# diff --git a/test-data/ref-outpout/tree.txt b/test-data/ref-outpout/tree.txt new file mode 100644 index 0000000..99523d0 --- /dev/null +++ b/test-data/ref-outpout/tree.txt @@ -0,0 +1,21 @@ +stg.chd.sx + SW_SergeGOUSSE + TTN_CG_EST + Joel_PINCE + rt-pince + TTN_JP_TX_NE + Denis_MAUBE + VINSONNEAU_RX_CG_EST + rt-vinsonneau + TTN_CG_POINTIS + TTN_PI_EGL_RX_CG + SW_PI_EGL + TTN_PI_EGL_NORD_EST + Daniel_POUZENC + rt-pouzenc + TTN_PI_ESTAN + Eglise_ESTANCARBON + SW_Eglise_ESTANCARBON + TTN_ESTAN_SUD + Samuel_BOURILLON + rt-bourillon diff --git a/tests/context.py b/tests/context.py new file mode 100644 index 0000000..57d10fb --- /dev/null +++ b/tests/context.py @@ -0,0 +1,5 @@ +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +import haircontrol diff --git a/tests/test_discovery.py b/tests/test_discovery.py new file mode 100755 index 0000000..96d262e --- /dev/null +++ b/tests/test_discovery.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import unittest +import pprint + +from context import haircontrol +from haircontrol import discovery, data + +class MockInspector(object): + def __init__(self, testDataPath): + self.testDataPath = testDataPath + + def connect(self, e): + self.name = e.name + + def disconnect(self): + self.name = None + + def command(self, command): + if not self.name: + return None + mockfile = self.testDataPath + '/' + self.name + '-' + command + '.out' + return open(mockfile) + + +class TestDiscovery(unittest.TestCase): + + # http://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable + def setUp(self): + self.discovery = discovery.Discovery(MockInspector('../test-data/input')) + self.maxDiff=None + self.ref_equipments = [] #json.load('../test-data/ref-output/equipments.json') + + def test_wire_graph(self): + self.discovery.discover_hinting_from_lldp(data.Equipment('stg2')) + self.discovery.discover_from_root(data.Equipment('stg')) + for e in self.discovery.equipments: + print(e) + for i in e.ifaces: + print(i) + self.assertEqual(self.discovery.equipments, self.ref_equipments) + +if __name__ == '__main__': + unittest.main() -- cgit v1.1