Bladeren bron

add sourcecode
first running version

ffggrz.de 8 jaren geleden
bovenliggende
commit
0d90535786
1 gewijzigde bestanden met toevoegingen van 391 en 0 verwijderingen
  1. 391 0
      ext-respondd.py

+ 391 - 0
ext-respondd.py

@@ -0,0 +1,391 @@
+#!/usr/bin/env python3
+
+# Code-Base: https://github.com/ffggrz/ffnord-alfred-announce
+#          + https://github.com/freifunk-mwu/ffnord-alfred-announce
+#          + https://github.com/FreifunkBremen/respondd
+
+import json
+import socket
+import subprocess
+import re
+import netifaces as netif
+from cpuinfo import cpuinfo
+
+# Force encoding to UTF-8
+import locale                                         # Ensures that subsequent open()s
+locale.getpreferredencoding = lambda _=None: 'UTF-8'  # are UTF-8 encoded.
+
+import sys
+
+import struct
+import select
+import zlib
+
+def toUTF8(line):
+    return line.decode("utf-8")
+
+def call(cmdnargs):
+    output = subprocess.check_output(cmdnargs)
+    lines = output.splitlines()
+    lines = [toUTF8(line) for line in lines]
+    return lines
+
+def merge(a, b):
+    if isinstance(a, dict) and isinstance(b, dict):
+        d = dict(a)
+        d.update({k: merge(a.get(k, None), b[k]) for k in b})
+        return d
+
+    if isinstance(a, list) and isinstance(b, list):
+        return [merge(x, y) for x, y in itertools.izip_longest(a, b)]
+
+    return a if b is None else b
+
+def getGateway(batadv_dev):
+#/sys/kernel/debug/batman_adv/bat0/gateways
+    output = subprocess.check_output(["batctl","-m",batadv_dev,"gwl","-n"])
+    output_utf8 = output.decode("utf-8")
+    lines = output_utf8.splitlines()
+    gw = None
+
+    for line in lines:
+        gw_line = re.match(r"^=> +([0-9a-f:]+) ", line)
+        if gw_line:
+            gw = gw_line.group(1)
+
+    return gw
+
+def getClients(batadv_dev):
+#/sys/kernel/debug/batman_adv/bat0/transtable_local
+    output = subprocess.check_output(["batctl","-m",batadv_dev,"tl","-n"])
+    output_utf8 = output.decode("utf-8")
+    lines = output_utf8.splitlines()
+
+    j = {"total": 0, "wifi": 0}
+
+    for line in lines:
+        # batman-adv -> translation-table.c -> batadv_tt_local_seq_print_text
+        # R = BATADV_TT_CLIENT_ROAM
+        # P = BATADV_TT_CLIENT_NOPURGE
+        # N = BATADV_TT_CLIENT_NEW
+        # X = BATADV_TT_CLIENT_PENDING
+        # W = BATADV_TT_CLIENT_WIFI
+        # 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:
+            j["total"] += 1
+            if ml.group(2)[4] == 'W':
+                j["wifi"] += 1
+
+    return j
+
+def getAddresses(bridge_dev):
+    ip_addrs = netif.ifaddresses(bridge_dev)
+    ip_list = []
+
+    try:
+        for ip6 in netif.ifaddresses(bridge_dev)[netif.AF_INET6]:
+            raw6 = ip6['addr'].split('%')
+            ip_list.append(raw6[0])
+    except:
+        pass
+
+    return ip_list
+
+def getMac_mesh(fastd_dev,meshmode=False):
+    interface = netif.ifaddresses(fastd_dev)
+    mesh = []
+    mac = None
+
+    try:
+        mac = interface[netif.AF_LINK]
+        mesh.append(mac[0]['addr'])
+    except:
+        KeyError
+
+    if meshmode:
+        return mesh
+    else:
+        return mac[0]['addr']
+
+def getMesh_interfaces(batadv_dev):
+    output = subprocess.check_output(["batctl","-m",batadv_dev,"if"])
+    output_utf8 = output.decode("utf-8")
+    lines = output_utf8.splitlines()
+    mesh = []
+
+    for line in lines:
+        dev_line = re.match(r"^([^:]*)", line)
+        interface = netif.ifaddresses(dev_line.group(0))
+        mac = interface[netif.AF_LINK]
+        mesh.append(mac[0]['addr'])
+
+    return mesh
+
+def getBat0_mesh(batadv_dev):
+    output = subprocess.check_output(["batctl","-m",batadv_dev,"if"])
+    output_utf8 = output.decode("utf-8")
+    lines = output_utf8.splitlines()
+    j = {"tunnel" : []}
+
+    for line in lines:
+        dev_line = re.match(r"^([^:]*)", line)
+        nif = dev_line.group(0)
+        interface = netif.ifaddresses(nif)
+        mac = interface[netif.AF_LINK]
+        j["tunnel"].append(mac[0]['addr'])
+
+    return j
+
+def getTraffic(batadv_dev):
+    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))), call(['ethtool', '-S', batadv_dev])[1:])
+    ))
+
+def getMemory():
+    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')
+    )
+
+def getFastd(fastd_socket): # Unused
+    fastd_data = b""
+    try:
+        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        sock.connect(fastd_socket)
+    except socket.error as err:
+        print("socket error: ", sys.stderr, err)
+        sys.exit(1)
+
+    while True:
+        data = sock.recv(1024)
+        if not data: break
+        fastd_data+= data
+
+    sock.close()
+    return json.loads(fastd_data.decode("utf-8"))
+
+def getNode_id(dev):
+    if 'node_id' in aliases["nodeinfo"]:
+        return aliases["nodeinfo"]["node_id"]
+    else:
+        return mac_mesh(dev).replace(':','')
+
+def getNeighbours():
+# https://github.com/freifunk-gluon/packages/blob/master/net/respondd/src/respondd.c
+# wenn Originators.mac == next_hop.mac dann
+    j = {}
+    with open("/sys/kernel/debug/batman_adv/" + batadv_dev + "/originators", 'r') as fh:
+        for line in fh:
+            #62:e7:27:cd:57:78 0.496s   (192) de:ad:be:ef:01:01 [  mesh-vpn]: de:ad:be:ef:01:01 (192) de:ad:be:ef:02:01 (148) de:ad:be:ef:03:01 (148)
+            ml = re.match(r"^([0-9a-f:]+)[ ]*([\d\.]*)s[ ]*\((\d*)\)[ ]*([0-9a-f:]+)[ ]*\[[ ]*(.*)\]", line, re.I)
+            if ml:
+                if ml.group(1) == ml.group(4):
+                    j[ml.group(1)] = {
+                        "tq": int(ml.group(3)),
+                        "lastseen": float(ml.group(2)),
+                    }
+    return j
+
+# ======================== Output =========================
+# =========================================================
+
+def createNodeinfo():
+    j = {
+        "node_id": getNode_id(fastd_dev),
+        "hostname": socket.gethostname(),
+        "network": {
+            "addresses": getAddresses(bridge_dev),
+            "mesh": {
+                "bat0": {
+                    "interfaces": getBat0_mesh(batadv_dev),
+                },
+            },
+            "mac": getMac_mesh(fastd_dev),
+            "mesh_interfaces": getMesh_interfaces(batadv_dev),
+        },
+        "software": {
+            "firmware": {
+                "base": call(['lsb_release','-is'])[0],
+                "release": call(['lsb_release','-ds'])[0],
+            },
+            "batman-adv": {
+                "version": open('/sys/module/batman_adv/version').read().strip(),
+#                "compat": # /lib/gluon/mesh-batman-adv-core/compat
+            },
+            "fastd": {
+                "version": call(['fastd','-v'])[0].split(' ')[1],
+                "enabled": True,
+            },
+            "status-page": {
+                "api": 0,
+            },
+            "autoupdater": {
+#                "branch": "stable",
+                "enabled": False,
+            },
+        },
+        "hardware": {
+            "model": cpuinfo.get_cpu_info()["brand"],
+            "nproc": int(call(['nproc'])[0]),
+        },
+#        "vpn": True,
+        "owner": {},
+        "system": {},
+        "location": {},
+    }
+    return merge(j, aliases["nodeinfo"])
+
+
+def createStatistics():
+    j = {
+        "node_id": getNode_id(fastd_dev),
+        "gateway" : getGateway(batadv_dev), # BUG: wenn man ein Gateway ist, was soll man dann hier senden?
+        "clients":  getClients(batadv_dev),
+        "traffic": getTraffic(batadv_dev),
+        "idletime": float(open('/proc/uptime').read().split(' ')[1]),
+        "loadavg": float(open('/proc/loadavg').read().split(' ')[0]),
+        "memory": 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": { # getFastd
+#            "groups": {
+#                "backbone": {
+#                    "peers": {
+#                        "vpn1": None,
+#                        "vpn2": {
+#                            "established": 1000,
+#                        },
+#                        "vpn3": None,
+#                    },
+#                },
+#            },
+#        },
+    }
+    return j
+
+
+def createNeighbours():
+#/sys/kernel/debug/batman_adv/bat0/originators
+
+    j = {
+        "node_id": getNode_id(fastd_dev),
+        "batadv": { # Testing
+            getMac_mesh(fastd_dev): {
+                "neighbours": getNeighbours(),
+            },
+            #"wifi": {},
+        },
+    }
+    return j
+
+def sendResponse(request, compress):
+    json_data = {}
+
+#https://github.com/freifunk-gluon/packages/blob/master/net/respondd/src/respondd.c
+    if request == 'statistics':
+        json_data[request] = createStatistics()
+    elif request == 'nodeinfo':
+        json_data[request] = createNodeinfo()
+    elif request == 'neighbours':
+        json_data[request] = createNeighbours()
+    else:
+        print("unknown command: " + request)
+        return
+
+    json_str = bytes(json.dumps(json_data, separators=(',', ':')), 'UTF-8')
+
+    if compress:
+        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.
+        gzip_data = encoder.compress(json_str)
+        gzip_data = gzip_data + encoder.flush()
+        sock.sendto(gzip_data, sender)
+    else:
+        sock.sendto(json_str, sender)
+
+    print(json.dumps(json_data, sort_keys=True, indent=4))
+
+# ===================== Mainfunction ======================
+# =========================================================
+
+config = {}
+try:
+    with open("config.json", 'r') as cfg_handle:
+        config = json.load(cfg_handle)
+except IOError:
+    raise
+
+aliases = {}
+try:
+    with open("alias.json", 'r') as cfg_handle:
+        aliases = json.load(cfg_handle)
+except IOError:
+    raise
+
+
+batadv_dev = config['batman']
+fastd_dev  = config['fastd']
+bridge_dev = config['bridge']
+
+#print(json.dumps(getFastd(config["fastd_socket"]), sort_keys=True, indent=4))
+#print(json.dumps(createNodeinfo(), sort_keys=True, indent=4))
+#print(json.dumps(createStatistics(), sort_keys=True, indent=4))
+#print(json.dumps(createNeighbours(), sort_keys=True, indent=4))
+#print(merge(createNodeinfo(), aliases["nodeinfo"]))
+#print(createStatistics())
+#sys.exit(1)
+
+
+if 'addr' in config:
+    addr = config['addr']
+else:
+    addr = 'ff02::2'
+
+if 'addr' in config:
+    port = config['port']
+else:
+    port = 1001
+
+if_idx = socket.if_nametoindex(config["bridge"])
+group = socket.inet_pton(socket.AF_INET6, addr) + struct.pack("I", if_idx)
+
+sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group)
+sock.bind(('::', port))
+
+# =========================================================
+
+while True:
+    if select.select([sock],[],[],1)[0]:
+        msg, sender = sock.recvfrom(2048) # buffer > mtu !?!? -> egal, da eh nur ein GET kommt welches kleiner ist als 1024 ist
+#        try:
+#            msg = zlib.decompress(msg, -15) # The data may be decompressed using zlib and many zlib bindings using -15 as the window size parameter.
+#        except zlib.error:
+#            pass
+        print(msg)
+
+        msg_spl = str(msg, 'UTF-8').split(" ")
+
+        # BUG: Es koennen auch Anfragen wie "GET statistics nodeinfo" existieren (laut gluon doku)
+        if msg_spl[0] == 'GET': # multi_request
+            for request in msg_spl[1:]:
+                sendResponse(request, True)
+        else: # single_request
+            sendResponse(msg_spl[0], False)
+