123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- #!/usr/bin/python
- # -*- coding: utf-8 -*-
- from __future__ import print_function
- import io
- import json
- import re
- import string
- import subprocess
- import time
- import ffstatus
- from .exceptions import SanityCheckError
- class BatmanParser:
- batadv_vis = 'batadv-vis'
- batctl = 'batctl'
- mactranslation = string.maketrans('2367abef', '014589cd')
- def __str__(self):
- return 'BatmanParser using \'{0}\' and \'{1}\''.format(
- self.batadv_vis, self.batctl)
- def sanitycheck(self):
- """Checks that programs are executable and give sane output."""
- testdata = None
- try:
- cmd = [self.batadv_vis, '-f', 'jsondoc']
- testdata = subprocess.check_output(cmd)
- except Exception as err:
- raise SanityCheckError(
- self, "batadv-vis not found or incompatible", err)
- try:
- json.loads(testdata)
- except Exception as err:
- raise SanityCheckError(
- self, "batadv-vis does not return valid JSON data", err)
- try:
- cmd = [self.batctl, 'vd', 'json', '-n']
- testdata = subprocess.check_output(cmd)
- except Exception as err:
- raise SanityCheckError(
- self, "batctl not found or incompatible", err)
- lineno = 0
- try:
- for line in testdata.splitlines():
- lineno += 1
- json.loads(line)
- except Exception as err:
- raise SanityCheckError(
- self,
- "batctl does not return valid JSON data (line " + lineno + ")",
- err)
- return True
- def fetch(self):
- """Fetches the current data from batadv-vis."""
- data = {}
- timestamp = int(time.time())
- # call 'batctl vd json -n' and parse output line-wise as JSON
- rawvisdata = subprocess.check_output([self.batctl, 'vd', 'json', '-n'])
- visdata = [json.loads(line) for line in rawvisdata.splitlines()]
- # call 'batctl gwl' and parse output lines
- rawgwdata = subprocess.check_output([self.batctl, 'gwl'])
- gateways = []
- for l in rawgwdata.splitlines()[1:]:
- m = re.search("^\s*(([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2})\s+", l)
- if m is not None:
- gateways.append(ffstatus.mac2id(m.group(1)))
- else:
- print('Failed to parse gateway from batctl-gwl line:', l)
- # call batadv-vis and parse output as unicode JSON
- rawdata = subprocess.check_output([self.batadv_vis, '-f', 'jsondoc'])
- rawdata = unicode(rawdata, encoding='latin1', errors='replace')
- batmandata = json.loads(rawdata)
- # parse vis data
- for item in visdata:
- itemmac = item.get('primary', item.get('router'))
- itemid = ffstatus.mac2id(itemmac)
- content = {
- 'aliases': [],
- 'neighbours': {},
- '__UPDATED__': {'batctl': timestamp},
- '__RAW__': {'batctl': [item]},
- }
- neigh_id = ffstatus.mac2id(item.get('neighbor'))
- if neigh_id is not None:
- neigh_q = item.get('label', '0')
- if neigh_q != 'TT':
- neigh_q = float(neigh_q)
- else:
- neigh_q = None
- content['neighbours'][neigh_id] = neigh_q
- if itemid is None:
- # assumption: this is a secondary
- secid = ffstatus.mac2id(item.get('secondary'))
- ofid = ffstatus.mac2id(item.get('of'))
- if secid is None or ofid is None:
- print('BATCTL-VD threw garbage: ' + json.dumps(item))
- continue
- itemid = ofid
- content['aliases'].append(secid)
- else:
- if not 'mac' in content:
- content['mac'] = itemmac
- if itemid in gateways:
- content['type'] = 'gateway'
- if itemid in data:
- data[itemid] = ffstatus.dict_merge(
- data[itemid], content, overwrite_lists=False)
- else:
- data[itemid] = content
- # parse raw data, convert all MAC into nodeid
- for item in batmandata['vis']:
- itemid = ffstatus.mac2id(item['primary'])
- aliases = []
- if 'secondary' in item:
- for mac in item['secondary']:
- aliases.append(ffstatus.mac2id(mac))
- neighbours = {}
- if 'neighbors' in item:
- for neighbour in item['neighbors']:
- #if neighbour['router'] != item['primary']:
- # print('node {0}\'s neighbor {1} has unexpected router {2}'.format(itemid, neighbour['neighbor'], neighbour['router']))
- metric = float(neighbour['metric'])
- neighbours[neighbour['neighbor']] = metric
- # construct dict entry as expected by BATCAVE
- content = {
- 'aliases': aliases,
- 'neighbours': neighbours,
- 'clients': [x for x in item.get('clients', [])],
- '__UPDATED__': {'batadv': timestamp},
- '__RAW__': {'batadv': {itemid: item}},
- }
- if itemid in data:
- data[itemid] = ffstatus.dict_merge(
- data[itemid], content, overwrite_lists=False)
- else:
- data[itemid] = content
- return data
- # standalone test mode
- if __name__ == "__main__":
- parser = BatmanParser()
- try:
- parser.sanitycheck()
- except SanityCheckError as err:
- print('SANITY-CHECK failed:', str(err))
- import sys
- sys.exit(1)
- bdata = parser.fetch()
- print(json.dumps(bdata))
|