From 0f9ab5e3068826005583e6894793b1272f16ac2a Mon Sep 17 00:00:00 2001
From: Christof Schulze <christof.schulze@gmx.net>
Date: Wed, 16 May 2018 16:58:24 +0200
Subject: [PATCH] gluon-mesh-babel: add new package

---
 docs/user/site.rst                            |  19 +
 package/gluon-mesh-babel/Makefile             |  27 +
 package/gluon-mesh-babel/check_site.lua       |   7 +
 ...90-gluon-babel-brclient-configure-snooping |   9 +
 .../files/etc/init.d/gluon-mesh-babel         |  78 ++
 .../files/lib/gluon/respondd/client.dev       |   1 +
 .../luasrc/lib/gluon/radvd/arguments          |   7 +
 .../upgrade/140-gluon-mesh-babel-firewall     | 103 +++
 .../gluon/upgrade/300-gluon-mesh-babel-ip6    | 102 +++
 .../upgrade/300-gluon-mesh-babel-mkconfig     |  20 +
 .../430-gluon-mesh-babel-add-mmfd-interface   |  11 +
 package/gluon-mesh-babel/src/Makefile         |  31 +
 package/gluon-mesh-babel/src/babel-respondd.c | 713 ++++++++++++++++++
 .../gluon-mesh-babel/src/handle_neighbour.c   |  28 +
 .../gluon-mesh-babel/src/handle_neighbour.h   |   4 +
 .../gluon-mesh-babel/src/neighbours-babel.c   |  31 +
 16 files changed, 1191 insertions(+)
 create mode 100644 package/gluon-mesh-babel/Makefile
 create mode 100644 package/gluon-mesh-babel/check_site.lua
 create mode 100644 package/gluon-mesh-babel/files/etc/hotplug.d/iface/90-gluon-babel-brclient-configure-snooping
 create mode 100755 package/gluon-mesh-babel/files/etc/init.d/gluon-mesh-babel
 create mode 100644 package/gluon-mesh-babel/files/lib/gluon/respondd/client.dev
 create mode 100755 package/gluon-mesh-babel/luasrc/lib/gluon/radvd/arguments
 create mode 100755 package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/140-gluon-mesh-babel-firewall
 create mode 100755 package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/300-gluon-mesh-babel-ip6
 create mode 100755 package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/300-gluon-mesh-babel-mkconfig
 create mode 100755 package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/430-gluon-mesh-babel-add-mmfd-interface
 create mode 100644 package/gluon-mesh-babel/src/Makefile
 create mode 100644 package/gluon-mesh-babel/src/babel-respondd.c
 create mode 100644 package/gluon-mesh-babel/src/handle_neighbour.c
 create mode 100644 package/gluon-mesh-babel/src/handle_neighbour.h
 create mode 100644 package/gluon-mesh-babel/src/neighbours-babel.c

diff --git a/docs/user/site.rst b/docs/user/site.rst
index 96644350a..0e763ef83 100644
--- a/docs/user/site.rst
+++ b/docs/user/site.rst
@@ -46,6 +46,22 @@ prefix6
 
        prefix6 = 'fdca::ffee:babe:1::/64'
 
+node_prefix6
+    The ipv6 prefix from which the unique IP-addresses for nodes are selected
+    in babel-based networks. This may overlap with prefix6. e.g.
+    ::
+
+       node_prefix6 = 'fdca::ffee:babe:2::/64'
+
+node_client_prefix6
+    The ipv6 prefix from which the client-specific IP-address is calculated that
+    is assigned to each node by l3roamd to allow efficient communication when 
+    roaming. This is exclusively useful when running a routing mesh protocol
+    like babel. e.g.
+    ::
+
+       node_client_prefix6 = 'fdca::ffee:babe:3::/64'
+
 timezone
     The timezone of your community live in, e.g.
     ::
@@ -260,6 +276,9 @@ mesh_vpn
     and *gluon-mesh-vpn-tunneldigger* should be installed with the current
     implementation.
 
+    **Note:** It may be interesting to include the package *gluon-iptables-clamp-mss-to-pmtu*
+    in the build when using *gluon-mesh-babel* to work around icmp blackholes on the internet.
+
     ::
 
       mesh_vpn = {
diff --git a/package/gluon-mesh-babel/Makefile b/package/gluon-mesh-babel/Makefile
new file mode 100644
index 000000000..14577c82e
--- /dev/null
+++ b/package/gluon-mesh-babel/Makefile
@@ -0,0 +1,27 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=gluon-mesh-babel
+PKG_VERSION:=1
+
+PKG_BUILD_DEPENDS := libbabelhelper
+PKG_BUILD_DEPENDS += libjson-c
+
+include ../gluon.mk
+
+define Package/gluon-mesh-babel
+  TITLE:=Babel mesh
+  DEPENDS:=+gluon-core +babeld +mmfd +libiwinfo +libgluonutil +firewall +libjson-c +libnl-tiny +libubus +libubox +libblobmsg-json +libbabelhelper +luabitop
+  PROVIDES:=gluon-mesh-provider
+endef
+
+define Package/gluon-mesh-babel/install
+	$(Gluon/Build/Install)
+
+	$(INSTALL_DIR) $(1)/usr/bin
+	$(INSTALL_DIR) $(1)/usr/lib/respondd
+	$(CP) $(PKG_BUILD_DIR)/babel-respondd.so $(1)/usr/lib/respondd/mesh-babel.so
+	$(INSTALL_DIR) $(1)/lib/gluon/status-page/providers
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/neighbours-babel $(1)/lib/gluon/status-page/providers/
+endef
+
+$(eval $(call BuildPackageGluon,gluon-mesh-babel))
diff --git a/package/gluon-mesh-babel/check_site.lua b/package/gluon-mesh-babel/check_site.lua
new file mode 100644
index 000000000..5a7bf95a3
--- /dev/null
+++ b/package/gluon-mesh-babel/check_site.lua
@@ -0,0 +1,7 @@
+need_string_match(in_domain({'node_prefix6'}), '^[%x:]+/64$')
+need_string_match(in_domain({'node_client_prefix6'}), '^[%x:]+/64$')
+
+need_string_match(in_domain({'next_node', 'ip6'}), '^[%x:]+$', false)
+need_string_match(in_domain({'next_node', 'ip4'}), '^%d+.%d+.%d+.%d+$', false)
+
+need_string_match(in_domain({'next_node', 'mac'}), '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$', false)
diff --git a/package/gluon-mesh-babel/files/etc/hotplug.d/iface/90-gluon-babel-brclient-configure-snooping b/package/gluon-mesh-babel/files/etc/hotplug.d/iface/90-gluon-babel-brclient-configure-snooping
new file mode 100644
index 000000000..9970a8755
--- /dev/null
+++ b/package/gluon-mesh-babel/files/etc/hotplug.d/iface/90-gluon-babel-brclient-configure-snooping
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+case "$ACTION"
+in
+	ifup)
+	echo 1 > /sys/devices/virtual/net/br-client/bridge/multicast_snooping
+	echo 2 > /sys/devices/virtual/net/br-client/bridge/multicast_router
+	;;
+esac
diff --git a/package/gluon-mesh-babel/files/etc/init.d/gluon-mesh-babel b/package/gluon-mesh-babel/files/etc/init.d/gluon-mesh-babel
new file mode 100755
index 000000000..e902f8cb4
--- /dev/null
+++ b/package/gluon-mesh-babel/files/etc/init.d/gluon-mesh-babel
@@ -0,0 +1,78 @@
+#!/bin/sh /etc/rc.common
+USE_PROCD=1
+START=45
+PORT=33123
+
+DAEMON=/usr/sbin/babeld
+pidfile=/var/run/gluon-babeld.pid
+CONFIGFILE=/etc/gluon-babeld.conf
+BABELOPTSFILE=/tmp/addn-babelopts
+touch $BABELOPTSFILE
+
+start_service() {
+	procd_open_instance
+	procd_set_param command $DAEMON
+	procd_append_param command -D -I "$pidfile" -G "$PORT" -c "$CONFIGFILE" $(cat $BABELOPTSFILE) babeldummydoesnotexist
+	procd_set_param respawn ${respawn_threshold:-60} ${respawn_timeout:-5} ${respawn_retry:-0}
+	procd_set_param stderr 1
+	procd_set_param stdout 1
+	procd_close_instance
+}
+
+echotobabel() {
+	local count=0
+	local line="$1"
+	while ! (echo -e "$line" | nc ::1 "$PORT" >/dev/null 2>&1)
+	do
+		sleep 1
+		echo retrying to connect to babeld in PID $$, waited ${count}s >&2
+		count=$((count+1))
+	done
+	return 0
+}
+
+waitforsocket() {
+	echotobabel "dump"
+	[ $? -gt 0 ] && { echo "Failed to connect to babeld socket on port $PORT, assuming the service was not started properly"; exit 43; }
+}
+
+reload_service() {
+	waitforsocket
+
+	for i in $(ubus call network.interface dump | jsonfilter -e "@.interface[@.proto='gluon_mesh' && @.up=true].device")
+	do
+	if ! echotobabel dump|grep "add interface"|grep -q $i
+	then
+		echotobabel "interface $i update-interval 300"
+	fi
+	done
+
+	for i in $(echotobabel "dump"|grep "add interface"|cut -d" " -f3)
+	do
+		if ! ubus call network.interface dump | jsonfilter -e "@.interface[@.proto='gluon_mesh' && @.up=true].device"|grep -q $i
+		then
+			echotobabel "flush interface $i"
+		fi
+	done
+}
+
+service_triggers() {
+	local script=$(readlink "$initscript")
+	local name=$(basename "${script:-$initscript}")
+
+	procd_open_trigger
+	procd_add_raw_trigger "interface.*" 0 "/etc/init.d/$name" reload
+	procd_close_trigger
+}
+
+service_started() {
+# make sure the init script does not finish until babeld is actually up.
+# unfortunately procd will still start multiple instances of the same script which is why waitforsocket is also run on reload
+	waitforsocket
+}
+stop_service(){
+	kill $(pgrep -P 1 babeld)
+}
+status() {
+	kill -USR1 $(pgrep -P 1 babeld)
+}
diff --git a/package/gluon-mesh-babel/files/lib/gluon/respondd/client.dev b/package/gluon-mesh-babel/files/lib/gluon/respondd/client.dev
new file mode 100644
index 000000000..202a2374a
--- /dev/null
+++ b/package/gluon-mesh-babel/files/lib/gluon/respondd/client.dev
@@ -0,0 +1 @@
+mmfd
diff --git a/package/gluon-mesh-babel/luasrc/lib/gluon/radvd/arguments b/package/gluon-mesh-babel/luasrc/lib/gluon/radvd/arguments
new file mode 100755
index 000000000..a07f2a257
--- /dev/null
+++ b/package/gluon-mesh-babel/luasrc/lib/gluon/radvd/arguments
@@ -0,0 +1,7 @@
+#!/usr/bin/lua
+local site = require "gluon.site"
+
+io.write("-i local-node --default-lifetime 900 -a " .. site.prefix6())
+if site.dns() and site.dns.servers() then
+	io.write(" --rdnss " .. site.next_node.ip6())
+end
diff --git a/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/140-gluon-mesh-babel-firewall b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/140-gluon-mesh-babel-firewall
new file mode 100755
index 000000000..874331271
--- /dev/null
+++ b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/140-gluon-mesh-babel-firewall
@@ -0,0 +1,103 @@
+#!/usr/bin/lua
+
+local uci = require('simple-uci').cursor()
+local site = require "gluon.site"
+
+uci:section('firewall', 'zone', 'l3roamd', {
+	name = 'l3roamd',
+	input = 'ACCEPT',
+	output = 'ACCEPT',
+	forward = 'REJECT',
+	device = 'l3roam+',
+	log = '1',
+})
+
+uci:section('firewall', 'zone', 'mmfd', {
+	name = 'mmfd',
+	input = 'REJECT',
+	output = 'accept',
+	forward = 'REJECT',
+	device = 'mmfd+',
+	log = '1',
+})
+
+-- forwardings and respective rules
+uci:section('firewall', 'forwarding', 'fcc', {
+	src = 'local_client',
+	dest = 'local_client',
+})
+
+uci:section('firewall', 'forwarding', 'fcm', {
+	src = 'local_client',
+	dest = 'mesh',
+})
+
+uci:section('firewall', 'forwarding', 'fmc', {
+	src = 'mesh',
+	dest = 'local_client',
+})
+
+uci:section('firewall', 'forwarding', 'fmm', {
+	src = 'mesh',
+	dest = 'mesh',
+})
+
+uci:section('firewall', 'forwarding', 'flc', {
+	src = 'l3roamd',
+	dest = 'local_client',
+})
+
+uci:section('firewall', 'forwarding', 'fcl', {
+	src = 'local_client',
+	dest = 'l3roamd',
+})
+
+uci:section('firewall', 'rule',  'mesh_respondd_mcast_ll', {
+	src = 'mesh',
+	src_ip = 'fe80::/64' ,
+	dest_port = '1001',
+	proto = 'udp',
+	target = 'ACCEPT',
+})
+
+uci:section('firewall', 'rule',  'mesh_respondd_mcast2', {
+	src = 'mesh',
+	src_ip = site.node_prefix6(),
+	dest_port = '1001',
+	proto = 'udp',
+	target = 'ACCEPT',
+})
+
+uci:section('firewall', 'rule',  'mmfd_respondd_ll', {
+	src = 'mmfd',
+	src_ip = 'fe80::/64',
+	dest_port = '1001',
+	proto = 'udp',
+	target = 'ACCEPT',
+})
+
+uci:section('firewall', 'rule',  'mmfd_respondd_mesh', {
+	src = 'mmfd',
+	src_ip = site.node_prefix6(),
+	dest_port = '1001',
+	proto = 'udp',
+	target = 'ACCEPT',
+})
+
+uci:section('firewall', 'rule',  'mesh_mmfd', {
+	src = 'mesh',
+	src_ip = 'fe80::/64',
+	dest_port = '27275',
+	proto = 'udp',
+	target = 'ACCEPT',
+})
+
+uci:section('firewall', 'rule', 'mesh_babel', {
+	src = 'mesh',
+	src_ip = 'fe80::/64',
+	dest_port = '6696',
+	proto = 'udp',
+	target = 'ACCEPT',
+})
+
+uci:save('firewall')
diff --git a/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/300-gluon-mesh-babel-ip6 b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/300-gluon-mesh-babel-ip6
new file mode 100755
index 000000000..7ab3f5ec2
--- /dev/null
+++ b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/300-gluon-mesh-babel-ip6
@@ -0,0 +1,102 @@
+#!/usr/bin/lua
+
+local bit = require 'bit'
+local sysconfig = require 'gluon.sysconfig'
+local uci = require('simple-uci').cursor()
+local site = require 'gluon.site'
+
+
+function IPv6(address)
+	--[[
+	(c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
+	(c) 2008 Steven Barth <steven@midlink.org>
+
+	Licensed under the Apache License, Version 2.0 (the "License").
+	You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+	]]--
+	local data = {}
+
+	local borderl = address:sub(1, 1) == ":" and 2 or 1
+	local borderh, zeroh, chunk, block
+
+	if #address > 45 then return nil end
+
+	repeat
+		borderh = address:find(":", borderl, true)
+		if not borderh then break end
+
+		block = tonumber(address:sub(borderl, borderh - 1), 16)
+		if block and block <= 0xFFFF then
+			data[#data+1] = block
+		else
+			if zeroh or borderh - borderl > 1 then return nil end
+			zeroh = #data + 1
+		end
+
+		borderl = borderh + 1
+	until #data == 7
+
+	chunk = address:sub(borderl)
+	if #chunk > 0 and #chunk <= 4 then
+		block = tonumber(chunk, 16)
+		if not block or block > 0xFFFF then return nil end
+
+		data[#data+1] = block
+	elseif #chunk > 4 then
+		if #data == 7 or #chunk > 15 then return nil end
+		borderl = 1
+		for i=1, 4 do
+			borderh = chunk:find(".", borderl, true)
+			if not borderh and i < 4 then return nil end
+			borderh = borderh and borderh - 1
+
+			block = tonumber(chunk:sub(borderl, borderh))
+			if not block or block > 255 then return nil end
+
+			if i == 1 or i == 3 then
+				data[#data+1] = block * 256
+			else
+				data[#data] = data[#data] + block
+			end
+
+			borderl = borderh and borderh + 2
+		end
+	end
+
+	if zeroh then
+		if #data == 8 then return nil end
+		while #data < 8 do
+			table.insert(data, zeroh, 0)
+		end
+	end
+
+	if #data == 8 then
+		return data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]
+	end
+end
+
+function mac_to_ip(prefix, mac)
+	local m1, m2, m3, m6, m7, m8 = string.match(mac, '(%x%x):(%x%x):(%x%x):(%x%x):(%x%x):(%x%x)')
+	local m4 = 0xff
+	local m5 = 0xfe
+	m1 = bit.bxor(tonumber(m1, 16), 0x02)
+
+	local h1 = 0x100 * m1 + tonumber(m2, 16)
+	local h2 = 0x100 * tonumber(m3, 16) + m4
+	local h3 = 0x100 * m5 + tonumber(m6, 16)
+	local h4 = 0x100 * tonumber(m7, 16) + tonumber(m8, 16)
+
+	local prefix, plen = string.match(prefix, '(.*)/(%d+)')
+	plen = tonumber(plen, 10)
+
+	local p1, p2, p3, p4, p5, p6, p7, p8 = IPv6(prefix)
+
+	return string.format("%x:%x:%x:%x:%x:%x:%x:%x/%d", p1, p2, p3, p4, h1, h2, h3, h4, 128)
+end
+
+local ip = mac_to_ip(site.node_prefix6(), sysconfig.primary_mac)
+
+uci:set('network', 'loopback', 'ip6addr', ip)
+uci:save('network')
diff --git a/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/300-gluon-mesh-babel-mkconfig b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/300-gluon-mesh-babel-mkconfig
new file mode 100755
index 000000000..7cf52cdb9
--- /dev/null
+++ b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/300-gluon-mesh-babel-mkconfig
@@ -0,0 +1,20 @@
+#!/usr/bin/lua
+
+local site = require 'gluon.site'
+local babelconf='/etc/gluon-babeld.conf'
+
+file = io.open(babelconf, "w")
+file:write("ipv6-subtrees true\n")
+file:write("reflect-kernel-metric true\n")
+file:write("export-table 254\n")
+file:write("log-file /dev/stderr\n")
+file:write("import-table 254\n")
+
+file:write("out ip " .. site.next_node.ip6() .. "/128 deny\n")
+file:write("redistribute ip " .. site.next_node.ip6() .. "/128 deny\n")
+file:write("redistribute ip " .. site.prefix6() .. " eq 128  allow\n")
+file:write("redistribute ip " .. site.node_client_prefix6() .. " eq 128  allow\n")
+file:write("redistribute ip " .. site.node_prefix6() .. " eq 128  allow\n")
+file:write("redistribute local if br-wan deny\n")
+file:write("redistribute local ip 0.0.0.0/0 deny\n")
+file:close()
diff --git a/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/430-gluon-mesh-babel-add-mmfd-interface b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/430-gluon-mesh-babel-add-mmfd-interface
new file mode 100755
index 000000000..47807d954
--- /dev/null
+++ b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/430-gluon-mesh-babel-add-mmfd-interface
@@ -0,0 +1,11 @@
+#!/usr/bin/lua
+
+local uci = require('simple-uci').cursor()
+
+uci:delete('network', 'mmfd')
+uci:section('network', 'interface', 'mmfd', {
+	proto = 'static',
+	ifname = 'mmfd0',
+	ip6addr = 'fe80::1/64'
+})
+uci:save('network')
diff --git a/package/gluon-mesh-babel/src/Makefile b/package/gluon-mesh-babel/src/Makefile
new file mode 100644
index 000000000..e307d6ea0
--- /dev/null
+++ b/package/gluon-mesh-babel/src/Makefile
@@ -0,0 +1,31 @@
+all: babel-respondd.so neighbours-babel
+
+CFLAGS += -Wall -g -fPIC -D_GNU_SOURCE
+
+ifeq ($(origin PKG_CONFIG), undefined)
+	PKG_CONFIG = pkg-config
+	ifeq ($(shell which $(PKG_CONFIG) 2>/dev/null),)
+		$(error $(PKG_CONFIG) not found)
+	endif
+endif
+
+ifeq ($(origin LIBBABEL_CFLAGS) $(origin LIBBABEL_LDLIBS), undefined undefined)
+	LIBBABEL_NAME ?= libbabelhelper
+	ifeq ($(shell $(PKG_CONFIG) --modversion $(LIBBABEL_NAME) 2>/dev/null),)
+		$(error No $(LIBBABEL_NAME) development libraries found!)
+	endif
+	LIBBABEL_CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBBABEL_NAME))
+	LIBBABEL_LDLIBS +=  $(shell $(PKG_CONFIG) --libs $(LIBBABEL_NAME))
+endif
+CFLAGS += $(LIBBABEL_CFLAGS)
+LDLIBS += $(LIBBABEL_LDLIBS)
+
+CFLAGS_JSONC = $(shell pkg-config --cflags json-c)
+LDFLAGS_JSONC = $(shell pkg-config --libs json-c)
+
+
+babel-respondd.so: babel-respondd.c handle_neighbour.c
+	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -shared $(LDFLAGS_JSONC) -o $@ $^ -lgluonutil -lblobmsg_json -lubox -lubus -liwinfo -luci
+
+neighbours-babel: neighbours-babel.c handle_neighbour.c
+	$(CC) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_JSONC) $(LDFLAGS) $(LDLIBS) $(LDFLAGS_JSONC) -o $@ $^
diff --git a/package/gluon-mesh-babel/src/babel-respondd.c b/package/gluon-mesh-babel/src/babel-respondd.c
new file mode 100644
index 000000000..f7c870c14
--- /dev/null
+++ b/package/gluon-mesh-babel/src/babel-respondd.c
@@ -0,0 +1,713 @@
+/*
+   Copyright (c) 2016, Matthias Schiffer <mschiffer@universe-factory.net>
+   All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+   */
+
+
+#include <respondd.h>
+
+#include <iwinfo.h>
+#include <json-c/json.h>
+#include <libgluonutil.h>
+#include <uci.h>
+
+#include <alloca.h>
+#include <glob.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <linux/ethtool.h>
+#include <linux/if_addr.h>
+#include <linux/sockios.h>
+
+#include <netdb.h>
+#include "errno.h"
+#include <libbabelhelper/babelhelper.h>
+
+#include <libubox/blobmsg_json.h>
+#include "libubus.h"
+
+#define _STRINGIFY(s) #s
+#define STRINGIFY(s) _STRINGIFY(s)
+#include <stdlib.h>
+
+#define SOCKET_INPUT_BUFFER_SIZE 255
+#define BABEL_PORT 33123
+#define VPN_INTERFACE "mesh-vpn"
+#define l3rdctl "/var/run/l3roamd.sock"
+
+#define IFNAMELEN 32
+#define PROTOLEN 32
+
+#define UBUS_TIMEOUT 30
+#define UBUS_SOCKET "/var/run/ubus.sock"
+
+static struct babelhelper_ctx bhelper_ctx = {};
+
+static int obtain_ifmac(unsigned char *ifmac, const char *ifname) {
+	struct ifreq ifr = {};
+	int sock;
+
+	sock=socket(PF_INET, SOCK_STREAM, 0);
+	if (-1==sock) {
+		perror("socket() ");
+		return 1;
+	}
+
+	strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1);
+
+	printf("obtaining hw address for nic: %s %s\n", ifname, ifr.ifr_name);
+	if (-1==ioctl(sock, SIOCGIFHWADDR, &ifr)) {
+		perror("ioctl(SIOCGIFHWADDR) ");
+		close(sock);
+		return 1;
+	}
+	close(sock);
+
+	memcpy(ifmac, ifr.ifr_hwaddr.sa_data, 6);
+	return 0;
+}
+
+static char*  get_line_from_run(const char* command) {
+	FILE *fp;
+	char *line = NULL;
+	size_t len = 0;
+
+	fp = popen(command, "r");
+
+	if (fp != NULL) {
+		ssize_t r = getline(&line, &len, fp);
+		if (r >= 0) {
+			len = strlen(line);
+
+			if (len && line[len-1] == '\n')
+				line[len-1] = 0;
+		}
+		else {
+			free(line);
+			line = NULL;
+		}
+
+		pclose(fp);
+	}
+	return line;
+}
+
+static struct json_object * get_addresses(void) {
+	char *primarymac = gluonutil_get_sysconfig("primary_mac");
+	char *address = malloc(INET6_ADDRSTRLEN+1);
+	char node_prefix_str[INET6_ADDRSTRLEN+1];
+	struct in6_addr node_prefix = {};
+	struct json_object *retval = json_object_new_array();
+
+	if (!gluonutil_get_node_prefix6(&node_prefix)) {
+		fprintf(stderr, "get_addresses: could not obtain mesh-prefix from site.conf. Not adding addresses to json data\n");
+		goto free;
+	}
+
+	if (inet_ntop(AF_INET6, &(node_prefix.s6_addr), node_prefix_str, INET6_ADDRSTRLEN) == NULL) {
+		fprintf(stderr, "get_addresses: could not convert mesh-prefix from site.conf to string\n");
+		goto free;
+	}
+
+	char *prefix_addresspart = strndup(node_prefix_str, INET6_ADDRSTRLEN);
+	if (! babelhelper_generateip_str(address, primarymac, prefix_addresspart) ) {
+		fprintf(stderr, "IP-address could not be generated by babelhelper");
+	}
+	free(prefix_addresspart);
+
+	json_object_array_add(retval, json_object_new_string(address));
+
+free:
+	free(address);
+	free(primarymac);
+
+	return retval;
+}
+
+static bool interface_file_exists(const char *ifname, const char *name) {
+	const char *format = "/sys/class/net/%s/%s";
+	char path[strlen(format) + strlen(ifname) + strlen(name)+1];
+	snprintf(path, sizeof(path), format, ifname, name);
+
+	return !access(path, F_OK);
+}
+
+struct in6_addr mac2ipv6(uint8_t mac[6], char * prefix) {
+	struct in6_addr address = {};
+	inet_pton(AF_INET6, prefix, &address);
+
+	address.s6_addr[8] = mac[0] ^ 0x02;
+	address.s6_addr[9] = mac[1];
+	address.s6_addr[10] = mac[2];
+	address.s6_addr[11] = 0xff;
+	address.s6_addr[12] = 0xfe;
+	address.s6_addr[13] = mac[3];
+	address.s6_addr[14] = mac[4];
+	address.s6_addr[15] = mac[5];
+
+	return address;
+}
+
+static void mesh_add_if(const char *ifname, struct json_object *wireless,
+		struct json_object *tunnel, struct json_object *other) {
+	char str_ip[INET6_ADDRSTRLEN] = {};
+	unsigned char mac[6] = {};
+
+	if (obtain_ifmac(mac, ifname)) {
+		printf("could not obtain mac for device: %s", ifname);
+		return;
+	}
+
+	struct in6_addr lladdr = mac2ipv6(mac, "fe80::");
+	inet_ntop(AF_INET6, &lladdr.s6_addr, str_ip, INET6_ADDRSTRLEN);
+
+	struct json_object *address = json_object_new_string(str_ip);
+
+	if (interface_file_exists(ifname, "wireless"))
+		json_object_array_add(wireless, address);
+	else if (interface_file_exists(ifname, "tun_flags"))
+		json_object_array_add(tunnel, address);
+	else
+		json_object_array_add(other, address);
+
+}
+static bool handle_neighbour(char **data, void *obj) {
+	if (data[NEIGHBOUR]) {
+		struct json_object *neigh = json_object_new_object();
+
+		if (data[RXCOST])
+			json_object_object_add(neigh, "rxcost", json_object_new_int(atoi(data[RXCOST])));
+		if (data[TXCOST])
+			json_object_object_add(neigh, "txcost", json_object_new_int(atoi(data[TXCOST])));
+		if (data[COST])
+			json_object_object_add(neigh, "cost", json_object_new_int(atoi(data[COST])));
+		if (data[REACH])
+			json_object_object_add(neigh, "reachability", json_object_new_double(strtod(data[REACH], NULL)));
+
+		struct json_object *nif = 0;
+		if (data[IF] && !json_object_object_get_ex(obj, data[IF], &nif)) {
+			nif = json_object_new_object();
+
+			unsigned char ifmac[6] = {};
+			char str_ip[INET6_ADDRSTRLEN] = {};
+
+			if (obtain_ifmac(ifmac, (const char*)data[IF])) {
+				printf("could not obtain mac for device: %s", data[IF]);
+				return false;
+			}
+			struct in6_addr lladdr = mac2ipv6(ifmac, "fe80::");
+			inet_ntop(AF_INET6, &lladdr.s6_addr, str_ip, INET6_ADDRSTRLEN);
+
+			json_object_object_add(nif, "ll-addr", json_object_new_string(str_ip));
+			json_object_object_add(nif, "protocol", json_object_new_string("babel"));
+			json_object_object_add(obj, data[IF], nif);
+
+		}
+		struct json_object *neighborcollector = 0;
+		if (!json_object_object_get_ex(nif, "neighbours", &neighborcollector)) {
+			neighborcollector = json_object_new_object();
+			json_object_object_add(nif, "neighbours", neighborcollector);
+		}
+
+		json_object_object_add(neighborcollector, data[ADDRESS], neigh);
+
+	}
+	return true;
+}
+
+static struct json_object * get_babel_neighbours(void) {
+
+	struct json_object *neighbours;
+	neighbours  = json_object_new_object();
+	if (!neighbours)
+		return NULL;
+
+	babelhelper_readbabeldata(&bhelper_ctx, (void*)neighbours, handle_neighbour);
+
+	return(neighbours);
+}
+
+static void blobmsg_handle_list(struct blob_attr *attr, int len, bool array, struct json_object *wireless, struct json_object *tunnel, struct json_object *other);
+
+static void blobmsg_handle_element(struct blob_attr *attr, bool head, char **ifname, char **proto, struct json_object *wireless, struct json_object *tunnel, struct json_object *other) {
+	void *data;
+
+	if (!blobmsg_check_attr(attr, false))
+		return;
+
+	data = blobmsg_data(attr);
+
+	switch (blob_id(attr)) {
+		case  BLOBMSG_TYPE_STRING:
+			if (!strncmp(blobmsg_name(attr),"device", 6)) {
+				free(*ifname);
+				*ifname = strndup(data, IFNAMELEN);
+			} else if (!strncmp(blobmsg_name(attr), "proto", 5)) {
+				free(*proto);
+				*proto = strndup(data, PROTOLEN);
+			}
+			return;
+		case BLOBMSG_TYPE_ARRAY:
+			blobmsg_handle_list(data, blobmsg_data_len(attr), true, wireless, tunnel, other);
+			return;
+		case BLOBMSG_TYPE_TABLE:
+			blobmsg_handle_list(data, blobmsg_data_len(attr), false, wireless, tunnel, other);
+	}
+}
+
+static void blobmsg_handle_list(struct blob_attr *attr, int len, bool array, struct json_object *wireless, struct json_object *tunnel, struct json_object *other) {
+	struct blob_attr *pos;
+	int rem = len;
+
+	char *ifname = NULL;
+	char *proto = NULL;
+
+	__blob_for_each_attr(pos, attr, rem) {
+		blobmsg_handle_element(pos, array, &ifname, &proto, wireless, tunnel, other);
+	}
+
+	if (ifname && proto) {
+		if (!strncmp(proto, "gluon_mesh", 10)) {
+			mesh_add_if(ifname, wireless, tunnel, other);
+		}
+	}
+	free(ifname);
+	free(proto);
+}
+
+static void receive_call_result_data(struct ubus_request *req, int type, struct blob_attr *msg) {
+	struct json_object *ret = json_object_new_object();
+	struct json_object *wireless = json_object_new_array();
+	struct json_object *tunnel = json_object_new_array();
+	struct json_object *other = json_object_new_array();
+
+	if (!ret || !wireless || !tunnel || !other) {
+		json_object_put(wireless);
+		json_object_put(tunnel);
+		json_object_put(other);
+		json_object_put(ret);
+		return;
+	}
+
+	if (!msg) {
+		printf("empty message\n");
+		return;
+	}
+
+	blobmsg_handle_list(blobmsg_data(msg), blobmsg_data_len(msg), false, wireless, tunnel, other);
+
+	json_object_object_add(ret, "wireless", wireless);
+	json_object_object_add(ret, "tunnel", tunnel);
+	json_object_object_add(ret, "other", other);
+
+	*((struct json_object**)(req->priv)) = ret;
+}
+
+
+static struct json_object * get_mesh_ifs() {
+	struct ubus_context *ubus_ctx;
+	struct json_object *ret = NULL;
+	struct blob_buf b = {};
+
+	unsigned int id=8;
+
+	ubus_ctx = ubus_connect(UBUS_SOCKET);
+	if (!ubus_ctx) {
+		fprintf(stderr,"could not connect to ubus, not providing mesh-data\n");
+		goto end;
+	}
+
+	int uret = -2;
+	blob_buf_init(&b, 0);
+	ubus_lookup_id(ubus_ctx, "network.interface", &id);
+	uret = ubus_invoke(ubus_ctx, id, "dump", b.head, receive_call_result_data, &ret, UBUS_TIMEOUT * 1000);
+
+	if (uret > 0)
+		fprintf(stderr, "ubus command failed: %s\n", ubus_strerror(uret));
+	else if (uret == -2)
+		fprintf(stderr, "invalid call, exiting\n");
+
+	blob_buf_free(&b);
+
+end:
+	ubus_free(ubus_ctx);
+	return ret;
+}
+
+static struct json_object * get_mesh(void) {
+	struct json_object *ret = json_object_new_object();
+	struct json_object *interfaces = NULL;
+	interfaces = json_object_new_object();
+	json_object_object_add(interfaces, "interfaces", get_mesh_ifs());
+	json_object_object_add(ret, "babel", interfaces);
+	return ret;
+}
+
+static struct json_object * get_babeld_version(void) {
+	char *version = get_line_from_run("exec babeld -V 2>&1");
+	struct json_object *ret = gluonutil_wrap_string(version);
+	free(version);
+	return ret;
+}
+
+static struct json_object * respondd_provider_nodeinfo(void) {
+	bhelper_ctx.debug=false;
+	struct json_object *ret = json_object_new_object();
+
+	struct json_object *network = json_object_new_object();
+	json_object_object_add(network, "addresses", get_addresses());
+	json_object_object_add(network, "mesh", get_mesh());
+	json_object_object_add(ret, "network", network);
+
+	struct json_object *software = json_object_new_object();
+	struct json_object *software_babeld = json_object_new_object();
+	json_object_object_add(software_babeld, "version", get_babeld_version());
+	json_object_object_add(software, "babeld", software_babeld);
+	json_object_object_add(ret, "software", software);
+
+	return ret;
+}
+
+static uint64_t getnumber(const char *ifname, const char *stat) {
+	const char *format = "/sys/class/net/%s/statistics/%s";
+	char path[strlen(format) + strlen(ifname) + strlen(stat)];
+	snprintf(path, sizeof(path), format, ifname, stat);
+	if (! access(path, F_OK))
+	{
+		char *line=gluonutil_read_line(path);
+		long long i = atoll(line);
+		free(line);
+		return(i);
+	}
+	return 0;
+}
+
+static struct json_object * get_traffic(void) {
+	char ifname[16];
+
+	strncpy(ifname, "br-client", 16);
+
+	struct json_object *ret = NULL;
+	struct json_object *rx = json_object_new_object();
+	struct json_object *tx = json_object_new_object();
+
+	json_object_object_add(rx, "packets", json_object_new_int64(getnumber(ifname, "rx_packets")));
+	json_object_object_add(rx, "bytes", json_object_new_int64(getnumber(ifname, "rx_bytes")));
+	json_object_object_add(rx, "dropped", json_object_new_int64(getnumber(ifname, "rx_dropped")));
+	json_object_object_add(tx, "packets", json_object_new_int64(getnumber(ifname, "tx_packets")));
+	json_object_object_add(tx, "dropped", json_object_new_int64(getnumber(ifname, "tx_dropped")));
+	json_object_object_add(tx, "bytes", json_object_new_int64(getnumber(ifname, "tx_bytes")));
+
+	ret = json_object_new_object();
+	json_object_object_add(ret, "rx", rx);
+	json_object_object_add(ret, "tx", tx);
+
+	return ret;
+}
+
+static void count_iface_stations(size_t *wifi24, size_t *wifi5, const char *ifname) {
+	const struct iwinfo_ops *iw = iwinfo_backend(ifname);
+	if (!iw)
+		return;
+
+	int freq;
+	if (iw->frequency(ifname, &freq) < 0)
+		return;
+
+	size_t *wifi;
+	if (freq >= 2400 && freq < 2500)
+		wifi = wifi24;
+	else if (freq >= 5000 && freq < 6000)
+		wifi = wifi5;
+	else
+		return;
+
+	int len;
+	char buf[IWINFO_BUFSIZE];
+	if (iw->assoclist(ifname, buf, &len) < 0)
+		return;
+
+	struct iwinfo_assoclist_entry *entry;
+	for (entry = (struct iwinfo_assoclist_entry *)buf; (char*)(entry+1) <= buf + len; entry++)
+		(*wifi)++;
+}
+
+static void count_stations(size_t *wifi24, size_t *wifi5) {
+	struct uci_context *ctx = uci_alloc_context();
+	ctx->flags &= ~UCI_FLAG_STRICT;
+
+
+	struct uci_package *p;
+	if (uci_load(ctx, "wireless", &p))
+		goto end;
+
+
+	struct uci_element *e;
+	uci_foreach_element(&p->sections, e) {
+		struct uci_section *s = uci_to_section(e);
+		if (strcmp(s->type, "wifi-iface"))
+			continue;
+
+		const char *network = uci_lookup_option_string(ctx, s, "network");
+		if (!network || strcmp(network, "client"))
+			continue;
+
+		const char *mode = uci_lookup_option_string(ctx, s, "mode");
+		if (!mode || strcmp(mode, "ap"))
+			continue;
+
+		const char *ifname = uci_lookup_option_string(ctx, s, "ifname");
+		if (!ifname)
+			continue;
+
+		count_iface_stations(wifi24, wifi5, ifname);
+	}
+
+end:
+	uci_free_context(ctx);
+}
+
+static bool handle_route_addgw_nexthop(char **data, void *arg) {
+	struct json_object *obj = (struct json_object*) arg;
+	if (data[PREFIX] && data[FROM] && data[VIA] && data[IF]) {
+		if ( (! strncmp(data[PREFIX], "::/0", 4) ) && ( ! strncmp(data[FROM], "::/0", 4) ) ) {
+			int gw_nexthoplen=strlen(data[VIA]) + strlen(data[IF])+2;
+			char gw_nexthop[gw_nexthoplen];
+			snprintf(gw_nexthop, gw_nexthoplen , "%s%%%s", data[VIA], data[IF]);
+			json_object_object_add(obj, "gateway_nexthop", json_object_new_string(gw_nexthop));
+		}
+	}
+	return true;
+}
+
+static int json_parse_get_clients(json_object * object) {
+	if (object) {
+		json_object_object_foreach(object, key, val) {
+			if (! strncmp("clients", key, 7)) {
+				return(json_object_get_int(val));
+			}
+		}
+	}
+	return(-1);
+}
+
+static int ask_l3roamd_for_client_count() {
+	struct sockaddr_un addr;
+	const char *socket_path = "/var/run/l3roamd.sock";
+	int fd;
+	int clients = -1;
+	char *buf = NULL;
+	int already_read = 0;
+
+	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+		fprintf(stderr, "could not setup l3roamd-control-socket\n");
+		return(-1);
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sun_family = AF_UNIX;
+	strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);
+
+	if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
+		fprintf(stderr, "connect error\n");
+		return(-1);
+	}
+
+	if (write(fd,"get_clients\n",12) != 12) {
+		perror("could not send command to l3roamd socket: get_clients");
+		goto end;
+	}
+
+	int rc = 0;
+	do {
+		buf = realloc(buf, already_read + SOCKET_INPUT_BUFFER_SIZE + 1);
+		if (buf == NULL) {
+			fprintf(stderr, "could not allocate memory for buffer\n");
+			goto end;
+		}
+
+		rc = read(fd, &buf[already_read], SOCKET_INPUT_BUFFER_SIZE);
+		already_read+=rc;
+		if (rc < 0) {
+			perror("error on read in ask_l3roamd_for_client_count():");
+			goto end;
+		}
+		buf[already_read]='\0';
+	} while (rc == SOCKET_INPUT_BUFFER_SIZE);
+
+	json_object * jobj = json_tokener_parse(buf);
+	clients = json_parse_get_clients(jobj);
+	json_object_put(jobj);
+
+end:
+	free(buf);
+	close(fd);
+
+	return clients;
+}
+
+static struct json_object * get_clients(void) {
+	size_t wifi24 = 0, wifi5 = 0;
+
+	count_stations(&wifi24, &wifi5);
+
+	size_t total = ask_l3roamd_for_client_count();
+
+	size_t wifi = wifi24 + wifi5;
+	struct json_object *ret = json_object_new_object();
+
+	if (total >= 0)
+		json_object_object_add(ret, "total", json_object_new_int(total));
+
+	json_object_object_add(ret, "wifi", json_object_new_int(wifi));
+	json_object_object_add(ret, "wifi24", json_object_new_int(wifi24));
+	json_object_object_add(ret, "wifi5", json_object_new_int(wifi5));
+	return ret;
+}
+
+static struct json_object * respondd_provider_statistics(void) {
+	struct json_object *ret = json_object_new_object();
+
+	json_object_object_add(ret, "clients", get_clients());
+	json_object_object_add(ret, "traffic", get_traffic());
+
+	babelhelper_readbabeldata(&bhelper_ctx, (void*)ret, handle_route_addgw_nexthop );
+
+	return ret;
+}
+
+static struct json_object * get_wifi_neighbours(const char *ifname) {
+	const struct iwinfo_ops *iw = iwinfo_backend(ifname);
+	if (!iw)
+		return NULL;
+
+	int len;
+	char buf[IWINFO_BUFSIZE];
+	if (iw->assoclist(ifname, buf, &len) < 0)
+		return NULL;
+
+	struct json_object *neighbours = json_object_new_object();
+
+	struct iwinfo_assoclist_entry *entry;
+	for (entry = (struct iwinfo_assoclist_entry *)buf; (char*)(entry+1) <= buf + len; entry++) {
+		struct json_object *obj = json_object_new_object();
+
+		json_object_object_add(obj, "signal", json_object_new_int(entry->signal));
+		json_object_object_add(obj, "noise", json_object_new_int(entry->noise));
+		json_object_object_add(obj, "inactive", json_object_new_int(entry->inactive));
+
+		char mac[18];
+		snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x",
+				entry->mac[0], entry->mac[1], entry->mac[2],
+				entry->mac[3], entry->mac[4], entry->mac[5]);
+
+		json_object_object_add(neighbours, mac, obj);
+	}
+
+	struct json_object *ret = json_object_new_object();
+
+	if (json_object_object_length(neighbours))
+		json_object_object_add(ret, "neighbours", neighbours);
+	else
+		json_object_put(neighbours);
+
+	return ret;
+}
+
+static struct json_object * get_wifi(void) {
+
+	struct uci_context *ctx = uci_alloc_context();
+	ctx->flags &= ~UCI_FLAG_STRICT;
+
+	struct json_object *ret = json_object_new_object();
+
+	struct uci_package *p;
+	if (uci_load(ctx, "network", &p))
+		goto end;
+
+
+	struct uci_element *e;
+	uci_foreach_element(&p->sections, e) {
+		struct uci_section *s = uci_to_section(e);
+		if (strcmp(s->type, "interface"))
+			continue;
+
+		const char *proto = uci_lookup_option_string(ctx, s, "proto");
+		if (!proto || strcmp(proto, "gluon_mesh"))
+			continue;
+
+		const char *ifname = uci_lookup_option_string(ctx, s, "ifname");
+		if (!ifname)
+			continue;
+
+		char *ifaddr = gluonutil_get_interface_address(ifname);
+		if (!ifaddr)
+			continue;
+
+		struct json_object *neighbours = get_wifi_neighbours(ifname);
+		if (neighbours)
+			json_object_object_add(ret, ifaddr, neighbours);
+
+		free(ifaddr);
+	}
+
+end:
+	uci_free_context(ctx);
+	return ret;
+}
+
+static struct json_object * respondd_provider_neighbours(void) {
+	struct json_object *ret = json_object_new_object();
+
+	struct json_object *babel = get_babel_neighbours();
+	if (babel)
+		json_object_object_add(ret, "babel", babel);
+
+
+	struct json_object *wifi = get_wifi();
+	if (wifi)
+		json_object_object_add(ret, "wifi", wifi);
+
+	return ret;
+}
+
+
+const struct respondd_provider_info respondd_providers[] = {
+	{"nodeinfo", respondd_provider_nodeinfo},
+	{"statistics", respondd_provider_statistics},
+	{"neighbours", respondd_provider_neighbours},
+	{}
+};
diff --git a/package/gluon-mesh-babel/src/handle_neighbour.c b/package/gluon-mesh-babel/src/handle_neighbour.c
new file mode 100644
index 000000000..950bfe458
--- /dev/null
+++ b/package/gluon-mesh-babel/src/handle_neighbour.c
@@ -0,0 +1,28 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "handle_neighbour.h"
+#include <libbabelhelper/babelhelper.h>
+
+bool handle_neighbour(char **data, void *arg) {
+	struct json_object *obj = (struct json_object*)arg;
+
+	if (data[NEIGHBOUR]) {
+		struct json_object *neigh = json_object_new_object();
+
+		if (data[RXCOST])
+			json_object_object_add(neigh, "rxcost", json_object_new_int(atoi(data[RXCOST])));
+		if (data[TXCOST])
+			json_object_object_add(neigh, "txcost", json_object_new_int(atoi(data[TXCOST])));
+		if (data[COST])
+			json_object_object_add(neigh, "cost", json_object_new_int(atoi(data[COST])));
+		if (data[REACH])
+			json_object_object_add(neigh, "reachability", json_object_new_double(strtod(data[REACH], NULL)));
+		if (data[IF])
+			json_object_object_add(neigh, "ifname", json_object_new_string(data[IF]));
+		if (data[ADDRESS])
+			json_object_object_add(obj, data[ADDRESS] , neigh);
+	}
+	return true;
+}
diff --git a/package/gluon-mesh-babel/src/handle_neighbour.h b/package/gluon-mesh-babel/src/handle_neighbour.h
new file mode 100644
index 000000000..85e0dcfc0
--- /dev/null
+++ b/package/gluon-mesh-babel/src/handle_neighbour.h
@@ -0,0 +1,4 @@
+#include <json-c/json.h>
+#include <stdbool.h>
+
+bool handle_neighbour(char **line, void *arg);
diff --git a/package/gluon-mesh-babel/src/neighbours-babel.c b/package/gluon-mesh-babel/src/neighbours-babel.c
new file mode 100644
index 000000000..8d9ae76c1
--- /dev/null
+++ b/package/gluon-mesh-babel/src/neighbours-babel.c
@@ -0,0 +1,31 @@
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <json-c/json.h>
+#include <libbabelhelper/babelhelper.h>
+#include "handle_neighbour.h"
+
+int main(void) {
+	struct json_object *neighbours;
+
+	printf("Content-type: text/event-stream\n\n");
+	fflush(stdout);
+
+	struct babelhelper_ctx bhelper_ctx = {};
+	while (1) {
+		neighbours = json_object_new_object();
+		if (!neighbours)
+			continue;
+
+		bhelper_ctx.debug = false;
+		babelhelper_readbabeldata(&bhelper_ctx, (void*)neighbours, handle_neighbour);
+
+		printf("data: %s\n\n", json_object_to_json_string(neighbours));
+		fflush(stdout);
+		json_object_put(neighbours);
+		neighbours = NULL;
+		sleep(10);
+	}
+
+	return 0;
+}
-- 
GitLab