Browse Source

style rework / fix bug / default-config rework

Marcus Scharf 6 years ago
parent
commit
63adccf644
9 changed files with 313 additions and 288 deletions
  1. 0 1
      alias.json.example
  2. 12 6
      ext-respondd.py
  3. 13 7
      lib/helper.py
  4. 55 66
      lib/neighbours.py
  5. 79 76
      lib/nodeinfo.py
  6. 1 1
      lib/ratelimit.py
  7. 18 17
      lib/respondd.py
  8. 27 37
      lib/respondd_client.py
  9. 108 77
      lib/statistics.py

+ 0 - 1
alias.json.example

@@ -16,7 +16,6 @@
       "latitude": 89.10727755551,
       "longitude": 5.00932562351
     },
-    "vpn": true,
     "pages": [
       "http://start.ffggrz/",
       "http://start.ffggrz.de/"

+ 12 - 6
ext-respondd.py

@@ -4,8 +4,8 @@ import json
 import argparse
 import sys
 
-
 from lib.respondd_client import ResponddClient
+import lib.helper
 
 parser = argparse.ArgumentParser()
 
@@ -16,12 +16,18 @@ parser.add_argument('-t', '--dry-run', action='store_true', help='Dry Run', requ
 args = parser.parse_args()
 options = vars(args)
 
-config = {}
+config = {
+  'bridge': 'br-client',
+  'batman': 'bat0',
+  'port': 1001,
+  'addr': 'ff02::2:1001'
+}
+
 try:
   with open("config.json", 'r') as fh:
-    config = json.load(fh)
+    config = lib.helper.merge(config, json.load(fh))
 except IOError:
-  raise
+  print('no config.json, use defaults')
 
 if options["test"]:
   from lib.nodeinfo import Nodeinfo
@@ -32,8 +38,8 @@ if options["test"]:
   print(json.dumps(Neighbours(config).getStruct(), sort_keys=True, indent=4))
   sys.exit(1)
 
-config["verbose"] = options["verbose"]
-config["dry_run"] = options["dry_run"]
+config['verbose'] = options['verbose']
+config['dry_run'] = options['dry_run']
 
 extResponddClient = ResponddClient(config)
 extResponddClient.start()

+ 13 - 7
lib/helper.py

@@ -2,15 +2,20 @@
 
 import netifaces as netif
 import subprocess
-
-def toUTF8(line):
-    return line.decode("utf-8")
+import sys
 
 def call(cmdnargs):
-    output = subprocess.check_output(cmdnargs)
+  try:
+    output = subprocess.check_output(cmdnargs, stderr=subprocess.STDOUT)
     lines = output.splitlines()
-    lines = [toUTF8(line) for line in lines]
+    lines = [line.decode('utf-8') for line in lines]
     return lines
+  except subprocess.CalledProcessError as err:
+    print(err)
+    return []
+  except:
+    print(str(sys.exc_info()[0]))
+    return []
 
 def merge(a, b):
   if isinstance(a, dict) and isinstance(b, dict):
@@ -23,10 +28,11 @@ def merge(a, b):
 
   return a if b is None else b
 
-def getDevice_MAC(dev):
+def getInterfaceMAC(interface):
   try:
-    interface = netif.ifaddresses(dev)
+    interface = netif.ifaddresses(interface)
     mac = interface[netif.AF_LINK]
     return mac[0]['addr']
   except:
     return None
+

+ 55 - 66
lib/neighbours.py

@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 
-import subprocess
 import re
 
 from lib.respondd import Respondd
@@ -11,92 +10,82 @@ class Neighbours(Respondd):
   def __init__(self, config):
     Respondd.__init__(self, config)
 
-  def getStationDump(self, devList):
-    j = {}
-
-    for dev in devList:
-      try:
-        output = subprocess.check_output(["iw", "dev", dev, "station", "dump"])
-        output_utf8 = output.decode("utf-8")
-        lines = output_utf8.splitlines()
-
-        mac = ""
-        for line in lines:
-          # Station 32:b8:c3:86:3e:e8 (on ibss3)
-          ml = re.match(r"^Station ([0-9a-f:]+) \(on ([\w\d]+)\)", line, re.I)
-          if ml:
-            mac = ml.group(1)
-            j[mac] = {}
-          else:
-            ml = re.match(r"^[\t ]+([^:]+):[\t ]+([^ ]+)", line, re.I)
-            if ml:
-              j[mac][ ml.group(1) ] = ml.group(2)
-      except:
-          pass
-    return j
-
-  def getMeshInterfaces(self, batmanDev):
-    j = {}
-
-    output = subprocess.check_output(["batctl", "-m", batmanDev, "if"])
-    output_utf8 = output.decode("utf-8")
-    lines = output_utf8.splitlines()
-
+  @staticmethod
+  def getStationDump(interfaceList):
+    ret = {}
+
+    for interface in interfaceList:
+      mac = ''
+      lines = lib.helper.call(['iw', 'dev', interface, 'station', 'dump'])
+      for line in lines:
+        # Station 32:b8:c3:86:3e:e8 (on ibss3)
+        lineMatch = re.match(r'^Station ([0-9a-f:]+) \(on ([\w\d]+)\)', line, re.I)
+        if lineMatch:
+          mac = lineMatch.group(1)
+          ret[mac] = {}
+        else:
+          lineMatch = re.match(r'^[\t ]+([^:]+):[\t ]+([^ ]+)', line, re.I)
+          if lineMatch:
+            ret[mac][lineMatch.group(1)] = lineMatch.group(2)
+    return ret
+
+  @staticmethod
+  def getMeshInterfaces(batmanInterface):
+    ret = {}
+
+    lines = lib.helper.call(['batctl', '-m', batmanInterface, 'if'])
     for line in lines:
-      ml = re.match(r"^([^:]*)", line)
-      dev = ml.group(1)
-      j[dev] = lib.helper.getDevice_MAC(dev)
+      lineMatch = re.match(r'^([^:]*)', line)
+      interface = lineMatch.group(1)
+      ret[interface] = lib.helper.getInterfaceMAC(interface)
 
-    return j
+    return ret
 
   def _get(self):
-    j = {"batadv": {}}
+    ret = {'batadv': {}}
 
     stationDump = None
 
     if 'mesh-wlan' in self._config:
-      j["wifi"] = {}
-      stationDump = self.getStationDump(self._config["mesh-wlan"])
+      ret['wifi'] = {}
+      stationDump = self.getStationDump(self._config['mesh-wlan'])
 
     meshInterfaces = self.getMeshInterfaces(self._config['batman'])
 
-    output = subprocess.check_output(["batctl", "-m", self._config['batman'], "o", "-n"])
-    output_utf8 = output.decode("utf-8")
-    lines = output_utf8.splitlines()
-
+    lines = lib.helper.call(['batctl', '-m', self._config['batman'], 'o', '-n'])
     for line in lines:
       # * e2:ad:db:b7:66:63    2.712s   (175) be:b7:25:4f:8f:96 [mesh-vpn-l2tp-1]
-      ml = re.match(r"^[ \*\t]*([0-9a-f:]+)[ ]*([\d\.]*)s[ ]*\(([ ]*\d*)\)[ ]*([0-9a-f:]+)[ ]*\[[ ]*(.*)\]", line, re.I)
+      lineMatch = re.match(r'^[ \*\t]*([0-9a-f:]+)[ ]*([\d\.]*)s[ ]*\(([ ]*\d*)\)[ ]*([0-9a-f:]+)[ ]*\[[ ]*(.*)\]', line, re.I)
 
-      if ml:
-        dev = ml.group(5)
-        macOrigin = ml.group(1)
-        macNexthop = ml.group(4)
-        tq = ml.group(3)
-        lastseen = ml.group(2)
+      if lineMatch:
+        interface = lineMatch.group(5)
+        macOrigin = lineMatch.group(1)
+        macNexthop = lineMatch.group(4)
+        tq = lineMatch.group(3)
+        lastseen = lineMatch.group(2)
 
         if macOrigin == macNexthop:
-          if 'mesh-wlan' in self._config and dev in self._config["mesh-wlan"] and not stationDump is None:
-            if not meshInterfaces[dev] in j["wifi"]:
-              j["wifi"][ meshInterfaces[dev] ] = {}
-              j["wifi"][ meshInterfaces[dev] ]["neighbours"] = {}
+          if 'mesh-wlan' in self._config and interface in self._config['mesh-wlan'] and stationDump is not None:
+            if meshInterfaces[interface] not in ret['wifi']:
+              ret['wifi'][meshInterfaces[interface]] = {}
+              ret['wifi'][meshInterfaces[interface]]['neighbours'] = {}
 
             if macOrigin in stationDump:
-              j["wifi"][ meshInterfaces[dev] ]["neighbours"][macOrigin] = {
-                "signal": stationDump[macOrigin]["signal"],
-                "noise": 0, # TODO: fehlt noch
-                "inactive": stationDump[macOrigin]["inactive time"]
+              ret['wifi'][meshInterfaces[interface]]['neighbours'][macOrigin] = {
+                'signal': stationDump[macOrigin]['signal'],
+                'noise': 0, # TODO: fehlt noch
+                'inactive': stationDump[macOrigin]['inactive time']
               }
 
-          if dev in meshInterfaces:
-            if not meshInterfaces[dev] in j["batadv"]:
-              j["batadv"][ meshInterfaces[dev] ] = {}
-              j["batadv"][ meshInterfaces[dev] ]["neighbours"] = {}
+          if interface in meshInterfaces:
+            if meshInterfaces[interface] not in ret['batadv']:
+              ret['batadv'][meshInterfaces[interface]] = {}
+              ret['batadv'][meshInterfaces[interface]]['neighbours'] = {}
 
-            j["batadv"][ meshInterfaces[dev] ]["neighbours"][macOrigin] = {
-              "tq": int(tq),
-              "lastseen": float(lastseen)
+            ret['batadv'][meshInterfaces[interface]]['neighbours'][macOrigin] = {
+              'tq': int(tq),
+              'lastseen': float(lastseen)
             }
 
-    return j
+    return ret
 

+ 79 - 76
lib/nodeinfo.py

@@ -1,9 +1,8 @@
 #!/usr/bin/env python3
 
 import socket
-import netifaces as netif
-import subprocess
 import re
+import netifaces as netif
 
 from lib.respondd import Respondd
 import lib.helper
@@ -13,116 +12,120 @@ class Nodeinfo(Respondd):
   def __init__(self, config):
     Respondd.__init__(self, config)
 
-  def getDeviceAddresses(self, dev):
-    l = []
+  @staticmethod
+  def getInterfaceAddresses(interface):
+    addresses = []
 
     try:
-      for ip6 in netif.ifaddresses(dev)[netif.AF_INET6]:
-        raw6 = ip6['addr'].split('%')
-        l.append(raw6[0])
+      for ip6 in netif.ifaddresses(interface)[netif.AF_INET6]:
+        addresses.append(ip6['addr'].split('%')[0])
 
-      for ip in netif.ifaddresses(dev)[netif.AF_INET]:
-        raw = ip['addr'].split('%')
-        l.append(raw[0])
+      for ip in netif.ifaddresses(interface)[netif.AF_INET]:
+        addresses.append(ip['addr'].split('%')[0])
     except:
       pass
 
-    return l
+    return addresses
 
-  def getBatmanInterfaces(self, dev):
-    j = {}
-
-    output = subprocess.check_output(["batctl", "-m", dev, "if"])
-    output_utf8 = output.decode("utf-8")
-    lines = output_utf8.splitlines()
+  def getBatmanInterfaces(self, batmanInterface):
+    ret = {}
 
+    lines = lib.helper.call(['batctl', '-m', batmanInterface, 'if'])
     for line in lines:
-      dev_line = re.match(r"^([^:]*)", line)
-      nif = dev_line.group(0)
-
-      if_group = ""
-      if "fastd" in self._config and nif == self._config["fastd"]: # keep for compatibility
-        if_group = "tunnel"
-      elif nif.find("l2tp") != -1:
-        if_group = "l2tp"
-      elif "mesh-vpn" in self._config and nif in self._config["mesh-vpn"]:
-        if_group = "tunnel"
-      elif "mesh-wlan" in self._config and nif in self._config["mesh-wlan"]:
-        if_group = "wireless"
+      lineMatch = re.match(r'^([^:]*)', line)
+      interface = lineMatch.group(0)
+
+      interfaceType = ''
+      if 'fastd' in self._config and interface == self._config['fastd']: # keep for compatibility
+        interfaceType = 'tunnel'
+      elif interface.find('l2tp') != -1:
+        interfaceType = 'l2tp'
+      elif 'mesh-vpn' in self._config and interface in self._config['mesh-vpn']:
+        interfaceType = 'tunnel'
+      elif 'mesh-wlan' in self._config and interface in self._config['mesh-wlan']:
+        interfaceType = 'wireless'
       else:
-        if_group = "other"
+        interfaceType = 'other'
 
-      if not if_group in j:
-        j[if_group] = []
+      if interfaceType not in ret:
+        ret[interfaceType] = []
 
-      j[if_group].append(lib.helper.getDevice_MAC(nif))
+      ret[interfaceType].append(lib.helper.getInterfaceMAC(interface))
 
-    if "l2tp" in j:
-      if "tunnel" in j:
-        j["tunnel"] = j["tunnel"] + j["l2tp"]
+    if 'l2tp' in ret:
+      if 'tunnel' in ret:
+        ret['tunnel'] += ret['l2tp']
       else:
-        j["tunnel"] = j["l2tp"]
+        ret['tunnel'] = ret['l2tp']
 
-    return j
+    return ret
 
-  def getCPUInfo(self):
-    j = {}
+  @staticmethod
+  def getCPUInfo():
+    ret = {}
 
-    with open("/proc/cpuinfo", 'r') as fh:
+    with open('/proc/cpuinfo', 'r') as fh:
       for line in fh:
-        ml = re.match(r"^(.+?)[\t ]+:[\t ]+(.*)$", line, re.I)
+        lineMatch = re.match(r'^(.+?)[\t ]+:[\t ]+(.*)$', line, re.I)
+        if lineMatch:
+          ret[lineMatch.group(1)] = lineMatch.group(2)
 
-        if ml:
-          j[ml.group(1)] = ml.group(2)
+    return ret
 
-    return j
+  @staticmethod
+  def getVPN(batmanInterface):
+    lines = lib.helper.call(['batctl', '-m', batmanInterface, 'gw_mode'])
+    if re.match(r'^server', lines[0]):
+      return True
+    else:
+      return False
 
   def _get(self):
-    j = {
-      "hostname": socket.gethostname(),
-      "network": {
-        "addresses": self.getDeviceAddresses(self._config['bridge']),
-        "mesh": {
-          "bat0": {
-            "interfaces": self.getBatmanInterfaces(self._config['batman'])
+    ret = {
+      'hostname': socket.gethostname(),
+      'network': {
+        'addresses': self.getInterfaceAddresses(self._config['bridge']),
+        'mesh': {
+          'bat0': {
+            'interfaces': self.getBatmanInterfaces(self._config['batman'])
           }
         },
-        "mac": lib.helper.getDevice_MAC(self._config["batman"])
+        'mac': lib.helper.getInterfaceMAC(self._config['batman'])
       },
-      "software": {
-        "firmware": {
-          "base": lib.helper.call(['lsb_release', '-is'])[0],
-          "release": lib.helper.call(['lsb_release', '-ds'])[0]
+      'software': {
+        'firmware': {
+          'base': lib.helper.call(['lsb_release', '-is'])[0],
+          'release': lib.helper.call(['lsb_release', '-ds'])[0]
         },
-        "batman-adv": {
-          "version": open('/sys/module/batman_adv/version').read().strip(),
-#                "compat": # /lib/gluon/mesh-batman-adv-core/compat
+        'batman-adv': {
+          'version': open('/sys/module/batman_adv/version').read().strip(),
+#                'compat': # /lib/gluon/mesh-batman-adv-core/compat
         },
-        "status-page": {
-          "api": 0
+        'status-page': {
+          'api': 0
         },
-        "autoupdater": {
-          "enabled": False
+        'autoupdater': {
+          'enabled': False
         }
       },
-      "hardware": {
-        "model": self.getCPUInfo()["model name"],
-        "nproc": int(lib.helper.call(['nproc'])[0])
+      'hardware': {
+        'model': self.getCPUInfo()['model name'],
+        'nproc': int(lib.helper.call(['nproc'])[0])
       },
-      "owner": {},
-      "system": {},
-      "location": {}
+      'owner': {},
+      'system': {},
+      'location': {},
+      'vpn': self.getVPN(self._config['batman'])
     }
 
-    if 'mesh-vpn' in self._config and len(self._config["mesh-vpn"]) > 0:
+    if 'mesh-vpn' in self._config and len(self._config['mesh-vpn']) > 0:
       try:
-        j["software"]["fastd"] = {
-          "version": lib.helper.call(['fastd', '-v'])[0].split(' ')[1],
-          "enabled": True
+        ret['software']['fastd'] = {
+          'version': lib.helper.call(['fastd', '-v'])[0].split(' ')[1],
+          'enabled': True
         }
       except:
         pass
 
-    return lib.helper.merge(j, self._aliasOverlay["nodeinfo"])
-
+    return lib.helper.merge(ret, self._aliasOverlay['nodeinfo'])
 

+ 1 - 1
lib/ratelimit.py

@@ -18,7 +18,7 @@ class rateLimit: # rate limit like iptables limit (per minutes)
     tDiff = tNow - self.tLast
     self.tLast = tNow
 
-    self.bucket+= (tDiff / (60 / self.rate_limit))
+    self.bucket += (tDiff / (60 / self.rate_limit))
     if self.bucket > self.rate_burst:
       self.bucket = self.rate_burst
 

+ 18 - 17
lib/respondd.py

@@ -10,26 +10,25 @@ class Respondd:
     self._config = config
     self._aliasOverlay = {}
     try:
-      with open("alias.json", 'r') as fh:
+      with open('alias.json', 'r') as fh:
         self._aliasOverlay = json.load(fh)
     except IOError:
       raise
-      pass
 
-  def getNode_ID(self):
-    if 'node_id' in self._aliasOverlay["nodeinfo"]:
-      return self._aliasOverlay["nodeinfo"]["node_id"]
+  def getNodeID(self):
+    if 'node_id' in self._aliasOverlay['nodeinfo']:
+      return self._aliasOverlay['nodeinfo']['node_id']
     else:
-      return lib.helper.getDevice_MAC(self._config["batman"]).replace(':', '')
+      return lib.helper.getInterfaceMAC(self._config['batman']).replace(':', '')
 
   def getStruct(self, rootName=None):
-    j = self._get()
-    j['node_id'] = self.getNode_ID()
-    if not rootName is None:
-      j_tmp = j
-      j = {}
-      j[rootName] = j_tmp
-    return j
+    ret = self._get()
+    ret['node_id'] = self.getNodeID()
+    if rootName is not None:
+      ret_tmp = ret
+      ret = {}
+      ret[rootName] = ret_tmp
+    return ret
 
   def getJSON(self, rootName=None):
     return bytes(json.dumps(self.getStruct(rootName), separators=(',', ':')), 'UTF-8')
@@ -37,12 +36,14 @@ class Respondd:
   def getJSONCompressed(self, rootName=None):
     return self.compress(self.getJSON(rootName))
 
-  def compress(self, data):
+  @staticmethod
+  def compress(data):
     encoder = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) # The data may be decompressed using zlib and many zlib bindings using -15 as the window size parameter.
     dataGzip = encoder.compress(data)
-    dataGzip+= encoder.flush()
+    dataGzip += encoder.flush()
     return dataGzip
 
-  def _get(self):
+  @staticmethod
+  def _get():
     return {}
-  pass
+

+ 27 - 37
lib/respondd_client.py

@@ -1,38 +1,21 @@
 #!/usr/bin/env python3
 
-from lib.ratelimit import rateLimit
-from lib.nodeinfo import Nodeinfo
-from lib.neighbours import Neighbours
-from lib.statistics import Statistics
-
 import socket
 import select
 import struct
 import json
 
+from lib.ratelimit import rateLimit
+from lib.nodeinfo import Nodeinfo
+from lib.neighbours import Neighbours
+from lib.statistics import Statistics
+
 class ResponddClient:
   def __init__(self, config):
     self._config = config
 
-    if 'addr' in self._config:
-        addr = self._config['addr']
-    else:
-        addr = 'ff02::2:1001'
-
-    if 'addr' in self._config:
-        port = self._config['port']
-    else:
-        port = 1001
-
-    if_idx = socket.if_nametoindex(self._config["bridge"])
-    group = socket.inet_pton(socket.AF_INET6, addr) + struct.pack("I", if_idx)
-
-    self._sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
-    self._sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group)
-    self._sock.bind(('::', port))
-
     if 'rate_limit' in self._config:
-      if not 'rate_limit_burst' in self._config:
+      if 'rate_limit_burst' not in self._config:
         self._config['rate_limit_burst'] = 10
       self.__RateLimit = rateLimit(self._config['rate_limit'], self._config['rate_limit_burst'])
     else:
@@ -42,22 +25,29 @@ class ResponddClient:
     self._neighbours = Neighbours(self._config)
     self._statistics = Statistics(self._config)
 
+    self._sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+
   def start(self):
+    if_idx = socket.if_nametoindex(self._config['bridge'])
+    group = socket.inet_pton(socket.AF_INET6, self._config['addr']) + struct.pack('I', if_idx)
+    self._sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group)
+
+    self._sock.bind(('::', self._config['port']))
+
     while True:
-      if select.select([self._sock], [], [], 1)[0]:
-        msg, sourceAddress = self._sock.recvfrom(2048)
+      msg, sourceAddress = self._sock.recvfrom(2048)
 
-        msgSplit = str(msg, 'UTF-8').split(" ")
+      msgSplit = str(msg, 'UTF-8').split(' ')
 
-        if msgSplit[0] == 'GET': # multi_request
-          for request in msgSplit[1:]:
-            self.sendResponse(sourceAddress, request, True)
-        else: # single_request
-          self.sendResponse(sourceAddress, msgSplit[0], False)
+      if msgSplit[0] == 'GET': # multi_request
+        for request in msgSplit[1:]:
+          self.sendResponse(sourceAddress, request, True)
+      else: # single_request
+        self.sendResponse(sourceAddress, msgSplit[0], False)
 
   def sendResponse(self, destAddress, responseType, withCompression):
-    if not self.__RateLimit is None and not self.__RateLimit.limit():
-      print("rate limit reached!")
+    if self.__RateLimit is not None and not self.__RateLimit.limit():
+      print('rate limit reached!')
       return
 
     responseClass = None
@@ -68,16 +58,16 @@ class ResponddClient:
     elif responseType == 'neighbours':
       responseClass = self._neighbours
     else:
-      print("unknown command: " + responseType)
+      print('unknown command: ' + responseType)
       return
 
-    if not self._config["dry_run"]:
+    if not self._config['dry_run']:
       if withCompression:
         self._sock.sendto(responseClass.getJSONCompressed(responseType), destAddress)
       else:
         self._sock.sendto(responseClass.getJSON(responseType), destAddress)
 
-    if self._config["verbose"] or self._config["dry_run"]:
-      print("%35s %5d %13s: " % (destAddress[0], destAddress[1], responseType), end='')
+    if self._config['verbose'] or self._config['dry_run']:
+      print('%35s %5d %13s: ' % (destAddress[0], destAddress[1], responseType), end='')
       print(json.dumps(responseClass.getStruct(responseType), sort_keys=True, indent=4))
 

+ 108 - 77
lib/statistics.py

@@ -1,9 +1,9 @@
 #!/usr/bin/env python3
 
 import socket
-#import netifaces as netif
-import subprocess
 import re
+import sys
+import json
 
 from lib.respondd import Respondd
 import lib.helper
@@ -14,14 +14,11 @@ class Statistics(Respondd):
     Respondd.__init__(self, config)
 
   def getClients(self):
-    j = {"total": 0, "wifi": 0}
+    ret = {'total': 0, 'wifi': 0}
 
-    batmanMAC = lib.helper.getDevice_MAC(self._config['batman'])
-
-    output = subprocess.check_output(["batctl", "-m", self._config['batman'], "tl", "-n"])
-    output_utf8 = output.decode("utf-8")
-    lines = output_utf8.splitlines()
+    macBridge = lib.helper.getInterfaceMAC(self._config['bridge'])
 
+    lines = lib.helper.call(['batctl', '-m', self._config['batman'], 'tl', '-n'])
     for line in lines:
       # batman-adv -> translation-table.c -> batadv_tt_local_seq_print_text
       # R = BATADV_TT_CLIENT_ROAM
@@ -32,47 +29,84 @@ class Statistics(Respondd):
       # I = BATADV_TT_CLIENT_ISOLA
       # . = unset
       # * c0:11:73:b2:8f:dd   -1 [.P..W.]   1.710   (0xe680a836)
-      ml = re.match(r"^\s\*\s([0-9a-f:]+)\s+-\d\s\[([RPNXWI\.]+)\]", line, re.I)
-      if ml:
-        if not batmanMAC == ml.group(1): # Filter bat0
-          if not ml.group(1).startswith('33:33:') and not ml.group(1).startswith('01:00:5e:'): # Filter Multicast
-            j["total"] += 1
-            if ml.group(2)[4] == 'W':
-              j["wifi"] += 1
-
-    return j
-
-  def getTraffic(self): # TODO: design rework needed!
-    return (lambda fields: dict(
-      (key, dict(
-        (type_, int(value_))
-        for key_, type_, value_ in fields
-        if key_ == key))
-      for key in ['rx', 'tx', 'forward', 'mgmt_rx', 'mgmt_tx']
-  ))(list(
-      (
-        key.replace('_bytes', '').replace('_dropped', ''),
-        'bytes' if key.endswith('_bytes') else 'dropped' if key.endswith('_dropped') else 'packets',
-        value
-      )
-      for key, value in map(lambda s: list(map(str.strip, s.split(': ', 1))), lib.helper.call(['ethtool', '-S', self._config['batman']])[1:])
-    ))
-
-  def getMemory(self): # TODO: design rework needed!
-    return dict(
-      (key.replace('Mem', '').lower(), int(value.split(' ')[0]))
-      for key, value in map(lambda s: map(str.strip, s.split(': ', 1)), open('/proc/meminfo').readlines())
-      if key in ('MemTotal', 'MemFree', 'Buffers', 'Cached')
-    )
+      #d4:3d:7e:34:5c:b1   -1 [.P....]   0.000   (0x12a02817)
+      lineMatch = re.match(r'^[\s*]*([0-9a-f:]+)\s+-\d\s\[([RPNXWI\.]+)\]', line, re.I)
+      if lineMatch:
+        mac = lineMatch.group(1)
+        flags = lineMatch.group(2)
+        if macBridge != mac and flags[0] != 'R': # Filter bridge and roaming clients
+          if not mac.startswith('33:33:') and not mac.startswith('01:00:5e:'): # Filter Multicast
+            ret['total'] += 1
+            if flags[4] == 'W':
+              ret['wifi'] += 1
+
+    return ret
+
+  def getTraffic(self):
+    traffic = {}
+    lines = lib.helper.call(['ethtool', '-S', self._config['batman']])
+    if len(lines) == 0:
+      return {}
+    for line in lines[1:]:
+      lineSplit = line.strip().split(':', 1)
+      name = lineSplit[0]
+      value = lineSplit[1].strip()
+      traffic[name] = value
+
+    ret = {
+      'tx': {
+        'packets': traffic['tx'],
+        'bytes': traffic['tx_bytes'],
+        'dropped': traffic['tx_dropped'],
+      },
+      'rx': {
+        'packets': traffic['rx'],
+        'bytes': traffic['rx_bytes'],
+      },
+      'forward': {
+        'packets': traffic['forward'],
+        'bytes': traffic['forward_bytes'],
+      },
+      'mgmt_rx': {
+        'packets': traffic['mgmt_rx'],
+        'bytes': traffic['mgmt_rx_bytes'],
+      },
+      'mgmt_tx': {
+        'packets': traffic['mgmt_tx'],
+        'bytes': traffic['mgmt_tx_bytes'],
+      },
+    }
+
+    return ret
+
+  @staticmethod
+  def getMemory():
+    ret = {}
+    lines = open('/proc/meminfo').readlines()
+    for line in lines:
+      lineSplit = line.split(' ', 1)
+      name = lineSplit[0][:-1]
+      value = lineSplit[1].strip().split(' ', 1)[0]
+
+      if name == 'MemTotal':
+        ret['total'] = value
+      elif name == 'MemFree':
+        ret['free'] = value
+      elif name == 'Buffers':
+        ret['buffers'] = value
+      elif name == 'Cached':
+        ret['cached'] = value
+
+    return ret
 
   def getFastd(self):
-    dataFastd = b""
+    dataFastd = b''
 
     try:
       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-      sock.connect(config["fastd_socket"])
+      sock.connect(self._config['fastd_socket'])
     except socket.error as err:
-      print("socket error: ", sys.stderr, err)
+      print('socket error: ', sys.stderr, err)
       return None
 
     while True:
@@ -82,54 +116,51 @@ class Statistics(Respondd):
       dataFastd += data
 
     sock.close()
-    return json.loads(dataFastd.decode("utf-8"))
+    return json.loads(dataFastd.decode('utf-8'))
 
   def getMeshVPNPeers(self):
-    j = {}
+    ret = {}
 
-    if "fastd_socket" in self._config:
+    if 'fastd_socket' in self._config:
       fastd = self.getFastd()
-      for peer in fastd["peers"].values():
-        if peer["connection"]:
-          j[peer["name"]] = {
-            "established": peer["connection"]["established"]
+      for peer in fastd['peers'].values():
+        if peer['connection']:
+          ret[peer['name']] = {
+            'established': peer['connection']['established']
           }
         else:
-          j[peer["name"]] = None
+          ret[peer['name']] = None
 
-      return j
+      return ret
     else:
       return None
 
   def getGateway(self):
-    j = None
-
-    output = subprocess.check_output(["batctl", "-m", self._config['batman'], "gwl", "-n"])
-    output_utf8 = output.decode("utf-8")
-    lines = output_utf8.splitlines()
+    ret = None
 
+    lines = lib.helper.call(['batctl', '-m', self._config['batman'], 'gwl', '-n'])
     for line in lines:
-      gw_line = re.match(r"^(\*|=>) +([0-9a-f:]+) \([\d ]+\) ([0-9a-f:]+)", line)
-      if gw_line:
-        j = {}
-        j["gateway"] = gw_line.group(2)
-        j["gateway_nexthop"] = gw_line.group(3)
+      lineMatch = re.match(r'^(\*|=>) +([0-9a-f:]+) \([\d ]+\) ([0-9a-f:]+)', line)
+      if lineMatch:
+        ret = {}
+        ret['gateway'] = lineMatch.group(2)
+        ret['gateway_nexthop'] = lineMatch.group(3)
 
-    return j
+    return ret
 
   def _get(self):
-    j = {
-      "clients":  self.getClients(),
-      "traffic": self.getTraffic(),
-      "idletime": float(open('/proc/uptime').read().split(' ')[1]),
-      "loadavg": float(open('/proc/loadavg').read().split(' ')[0]),
-      "memory": self.getMemory(),
-      "processes": dict(zip(('running', 'total'), map(int, open('/proc/loadavg').read().split(' ')[3].split('/')))),
-      "uptime": float(open('/proc/uptime').read().split(' ')[0]),
-      "mesh_vpn" : { # HopGlass-Server: node.flags.uplink = parsePeerGroup(_.get(n, 'statistics.mesh_vpn'))
-        "groups": {
-          "backbone": {
-            "peers": self.getMeshVPNPeers()
+    ret = {
+      'clients': self.getClients(),
+      'traffic': self.getTraffic(),
+      'idletime': float(open('/proc/uptime').read().split(' ')[1]),
+      'loadavg': float(open('/proc/loadavg').read().split(' ')[0]),
+      'memory': self.getMemory(),
+      'processes': dict(zip(('running', 'total'), map(int, open('/proc/loadavg').read().split(' ')[3].split('/')))),
+      'uptime': float(open('/proc/uptime').read().split(' ')[0]),
+      'mesh_vpn' : { # HopGlass-Server: node.flags.uplink = parsePeerGroup(_.get(n, 'statistics.mesh_vpn'))
+        'groups': {
+          'backbone': {
+            'peers': self.getMeshVPNPeers()
           }
         }
       }
@@ -137,7 +168,7 @@ class Statistics(Respondd):
 
     gateway = self.getGateway()
     if gateway != None:
-      j = lib.helper.merge(j, gateway)
+      ret = lib.helper.merge(ret, gateway)
 
-    return j
+    return ret