import re import json import xml.etree.ElementTree class Inspector(): cmds = {} def __init__(self): #XXX in a mockup class ? self.testDataPath = '../test-data/input' self.e = None def connect(self, e): self.e = e self.e.inspected = 'in-progress' def disconnect(self): self.e.inspected = True self.e = None def command(self, cmdname): cmddef = self.cmds.get(cmdname) if not cmddef: return None cmd = cmddef['cmd'] result = [] fd = None try: fd = self._exec(cmdname, cmd) re = cmddef.get('re') func = cmddef.get('func') if re: for line in fd: matches = re.match(line) if matches: result.append(matches.groups()) elif func: result = func(self, fd) except Exception: print("Error : can't exec/read %s (%s) on %s (%s)"%(cmdname, cmd, self.e.name, self.e.mgmtip)) finally: if fd: fd.close() return result def _exec(self, cmdname, cmd): #XXX in a mockup class ? mockfile = self.testDataPath + '/' + self.e.name + '-' + cmdname + '.out' return open(mockfile) def _parse_cgi_json(self, fd): for cgi_headers in fd: if cgi_headers == '\n': break js = {} try: js = json.load(fd) except ValueError: print("Warn : unparsable json for %s (%s)"%(self.e.name, self.e.mgmtip)) finally: fd.close() return js class DummyInspector(Inspector): pass class LinuxInspector(Inspector): def parse_lldpctl_xml(self, fd): result = [] root = xml.etree.ElementTree.parse(fd).getroot() for iface in root.iter('interface'): local_ifname = iface.get('name') local_mac = self.e.ifaces[local_ifname].mac chassis = iface.find('chassis') remote_name = chassis.find('name').text remote_ipmgmt = chassis.find('mgmt-ip').text ports = [] for port in iface.findall('port'): remote_ifname = port.find('id').text ports.append(remote_ifname) result.append( (local_ifname, local_mac, remote_name, remote_ipmgmt, ports) ) return result cmds = { 'ip-neigh': { 'cmd': 'ip neigh', 're': 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 }, 'ip-link': { 'cmd': 'ip -o link', 'kind': 'text', 're': re.compile("\d+:\s+(?P[^:]*):.+\s+(?:link/ether\s+(?P[a-f0-9:]*)\s+brd|link/none)") # 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 8c:89:a5:c7:be:88 brd ff:ff:ff:ff:ff:ff }, 'lldpctl': { 'cmd': 'lldpctl -f xml', 'func': parse_lldpctl_xml # # # # # Robert_VILO # Robert_VILO # LM5 running on #XM.v5.5.10 # 172.16.10.26 # 169.254.227.212 # # # # br0 # br0 # # # # ... # # }, } class UbntInspector(Inspector): def parse_status_json(self, fd): result = [] interfaces = self._parse_cgi_json(fd).get('interfaces') # XXX dup parse_brmacs_json(fd) and many other cool info to get if interfaces: for line in interfaces: if isinstance(line, dict): ifname = line.get('ifname') mac = line.get('hwaddr') result.append( (ifname, mac) ) return result def parse_brmacs_json(self, fd): result = [] brmacs = self._parse_cgi_json(fd).get('brmacs') if brmacs: for line in brmacs: if isinstance(line, dict): ifname = line.get('port') mac = line.get('hwaddr') result.append( (ifname, mac) ) return result cmds = { 'status.cgi': { 'cmd':'/usr/www/status.cgi', 'func': parse_status_json }, 'brmacs.cgi': { 'cmd':'QUERY_STRING=brmacs=y /usr/www/brmacs.cgi', 'func': parse_brmacs_json } } class ToughSwitchInspector(Inspector): def parse_mactable_data_cgi(self, fd): result = [] macs = self._parse_cgi_json(fd).get('macs') if macs: for line in macs: if isinstance(line, dict): ifname = "p%i"%line.get('port') mac = line.get('mac') result.append( (ifname, mac) ) return result cmds = { 'mactable_data.cgi': { 'cmd':'/usr/www/mactable_data.cgi', 'func': parse_mactable_data_cgi }, 'ip-link': { 'cmd': 'ip -o link', 'kind': 'text', 're': re.compile("\d+:\s+(?P[^:]*):.+\s+(?:link/ether\s+(?P[a-f0-9:]*)\s+brd|link/none)") # 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 8c:89:a5:c7:be:88 brd ff:ff:ff:ff:ff:ff }, } 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 }, 'show-version': { 'cmd':'show version', #XXX needs "enable" mode 're': re.compile("(?P[^\.]+)\.+ (?P.*)") # Burned In MAC Address.......................... 44:D9:E7:51:BF:A7 }, } #TODO Implement them class NetonixInspector(Inspector): pass class MikrotikInspector(Inspector): pass class OpenWRTInspector(Inspector): pass