From 928aa3ef114ebf9246c90f9eae80da9a20172530 Mon Sep 17 00:00:00 2001 From: Ludovic Pouzenc Date: Sun, 15 May 2016 13:05:33 +0200 Subject: Add ip link support to mac local_macs, improove test result readability --- haircontrol/data.py | 9 +++++++-- haircontrol/discovery.py | 31 +++++++++++++++++++++---------- haircontrol/inspectors.py | 21 +++++++++++++++++++++ test-data/input/stg2-ip-link.out | 2 ++ tests/test_discovery.py | 14 +++++++------- 5 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 haircontrol/inspectors.py create mode 100644 test-data/input/stg2-ip-link.out diff --git a/haircontrol/data.py b/haircontrol/data.py index d8195c7..6f5733d 100644 --- a/haircontrol/data.py +++ b/haircontrol/data.py @@ -5,9 +5,12 @@ 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)) + + def get_equipment_list_sorted(self): e_list = list(self.equipments.values()) e_list.sort(key=lambda x: x.name) - return '([\n%s\n],\n%s\n)'%(',\n'.join(' %s'%repr(e) for e in e_list), repr(self.ip2mac)) + return e_list def add_equipment(self,e): old = self.equipments.get(e.mgmtip) @@ -37,6 +40,7 @@ class Equipment: def add_seen_mac(self, ifname, mac): iface = self.ifaces.get(ifname) if not iface: + print("Warn : add_seen_mac(%s, %s) auto-create iface on %s"%(ifname, mac, self.name)) iface = Interface(ifname) self.ifaces[ifname] = iface iface.mac_seen.append(mac) @@ -48,5 +52,6 @@ class Interface: self.mac_seen = [] def __repr__(self): - 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 2b7ad83..680eb4c 100644 --- a/haircontrol/discovery.py +++ b/haircontrol/discovery.py @@ -4,9 +4,8 @@ import xml.etree.ElementTree from haircontrol.data import * class Discovery: - IPNEIGH = re.compile("^(?P[a-f0-9:.]+) dev (?P.*) lladdr (?P[a-f0-9:]*)") - # fe80::8300 dev eth1 lladdr 10:fe:ed:f1:e1:f3 router STALE - # 172.16.20.210 dev eth1 lladdr c0:4a:00:fe:1f:87 REACHABLE + IPNEIGH = re.compile("(?P[a-f0-9:.]+) dev (?P.*) lladdr (?P[a-f0-9:]*)") + IPLINKSHOW = re.compile("(?P\d+):\s+(?P[^:]*):.+\s+(?:link/ether\s+(?P[a-f0-9:]*)\s+brd|link/none)") def __init__(self, inspector): self.inspector = inspector @@ -15,18 +14,30 @@ class Discovery: def discover_hinting_from_lldp(self, e_lldp): self.net.add_equipment(e_lldp) self.inspector.connect(e_lldp) + + # Learn local interfaces of e_lldp + fd = self.inspector.command('ip-link') + for line in fd: + matches = Discovery.IPLINKSHOW.match(line) + if matches: + ifname, mac = [ matches.group(k) for k in ['ifname','mac'] ] + e_lldp.ifaces[ifname] = Interface(ifname, mac) + fd.close() + + # Create equipments and ifaces from LLDP neighbour discovery fd = self.inspector.command('lldp') root = xml.etree.ElementTree.parse(fd).getroot() for iface in root.iter('interface'): - ifname = iface.get('name') + local_ifname = iface.get('name') + local_mac = e_lldp.ifaces[local_ifname].mac chassis = iface.find('chassis') - e = Equipment() - e.name = chassis.find('name').text - e.mgmtip = chassis.find('mgmt-ip').text + remote_name = chassis.find('name').text + remote_ipmgmt = chassis.find('mgmt-ip').text + e = Equipment(remote_name, remote_ipmgmt) self.net.add_equipment(e) for port in iface.findall('port'): - #remote_ifname = port.find('id').text - e.add_seen_mac(ifname, 'lldp') #XXX + remote_ifname = port.find('id').text + e.add_seen_mac(remote_ifname, local_mac) fd.close() self.inspector.disconnect() @@ -35,7 +46,7 @@ class Discovery: self.inspector.connect(e_root) fd = self.inspector.command('ip-neigh') for line in fd: - matches = Discovery.IPNEIGH.search(line) + matches = Discovery.IPNEIGH.match(line) if matches: ip, ifname, mac = [ matches.group(k) for k in ['ip','ifname','mac'] ] self.net.index_mac_ip(mac, ip) diff --git a/haircontrol/inspectors.py b/haircontrol/inspectors.py new file mode 100644 index 0000000..9b797fe --- /dev/null +++ b/haircontrol/inspectors.py @@ -0,0 +1,21 @@ +import re + +class Inspector(): + cmds = {} + fd = None + + def parse(self): + return None #XXX Implement + +class LinuxInspector(Inspector): + cmds = { + 'ip-neigh': { + 'cmd': 'ip neigh', + # fe80::8300 dev eth1 lladdr 10:fe:ed:f1:e1:f3 router STALE + # 172.16.20.210 dev eth1 lladdr c0:4a:00:fe:1f:87 REACHABLE + 'kind': 'text', + 'fields': ['ip','ifname','mac'], + 're': re.compile("(?P[a-f0-9:.]+) dev (?P.*) lladdr (?P[a-f0-9:]*)") + } + } + diff --git a/test-data/input/stg2-ip-link.out b/test-data/input/stg2-ip-link.out new file mode 100644 index 0000000..58c4fb4 --- /dev/null +++ b/test-data/input/stg2-ip-link.out @@ -0,0 +1,2 @@ +1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default \ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 +2: eth0: mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000\ link/ether 52:54:00:75:c6:1d brd ff:ff:ff:ff:ff:ff diff --git a/tests/test_discovery.py b/tests/test_discovery.py index b0de19b..7fb0bd2 100755 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -3,9 +3,9 @@ import unittest #import pudb; pudb.set_trace() from context import haircontrol -from haircontrol import discovery, data +from haircontrol import discovery, data, inspectors -class MockInspector(object): +class MockInspector(inspectors.Inspector): def __init__(self, testDataPath): self.testDataPath = testDataPath self.e = None @@ -20,7 +20,7 @@ class MockInspector(object): if not self.e: return None mockfile = self.testDataPath + '/' + self.e.name + '-' + command + '.out' - return open(mockfile) + return open(mockfile) #XXX use Inspector.parse() class TestDiscovery(unittest.TestCase): @@ -30,11 +30,11 @@ class TestDiscovery(unittest.TestCase): self.maxDiff=None def test_wire_graph(self): - ref_equipments = { 'todo': data.Equipment() } #json.load('../test-data/ref-output/equipments.json') + ref_net = data.EtherDomain() #json.load('../test-data/ref-output/equipments.json') - self.discovery.discover_hinting_from_lldp(data.Equipment('stg2', '172.16.0.254')) - self.discovery.discover_from_root(data.Equipment('stg', '172.16.0.253')) - self.assertDictEqual(self.discovery.net.equipments, ref_equipments) + self.discovery.discover_hinting_from_lldp(data.Equipment('stg2', '172.16.0.253')) + 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())) if __name__ == '__main__': unittest.main() -- cgit v1.1