#!/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))