diff --git a/docs/user/site.rst b/docs/user/site.rst index 96644350..0e763ef8 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 00000000..14577c82 --- /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 00000000..5a7bf95a --- /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 00000000..9970a875 --- /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 00000000..e902f8cb --- /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 00000000..202a2374 --- /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 00000000..a07f2a25 --- /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 00000000..87433127 --- /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 00000000..7ab3f5ec --- /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 + (c) 2008 Steven Barth + + 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 00000000..7cf52cdb --- /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 00000000..47807d95 --- /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 00000000..e307d6ea --- /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 00000000..f7c870c1 --- /dev/null +++ b/package/gluon-mesh-babel/src/babel-respondd.c @@ -0,0 +1,713 @@ +/* + Copyright (c) 2016, Matthias Schiffer + 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 + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "errno.h" +#include + +#include +#include "libubus.h" + +#define _STRINGIFY(s) #s +#define STRINGIFY(s) _STRINGIFY(s) +#include + +#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 00000000..950bfe45 --- /dev/null +++ b/package/gluon-mesh-babel/src/handle_neighbour.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include "handle_neighbour.h" +#include + +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 00000000..85e0dcfc --- /dev/null +++ b/package/gluon-mesh-babel/src/handle_neighbour.h @@ -0,0 +1,4 @@ +#include +#include + +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 00000000..8d9ae76c --- /dev/null +++ b/package/gluon-mesh-babel/src/neighbours-babel.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include +#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; +}