From 4e7878c22819dc9ffbb7b592b073daf9ec8f4c30 Mon Sep 17 00:00:00 2001 From: Jan-Tarek Butt Date: Sun, 25 Sep 2016 02:26:25 +0200 Subject: [PATCH 1/7] add exsample hoodfile #789 --- .../files/lib/ffnw/hoods/hoods.json | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 package/ffnw-hoods/files/lib/ffnw/hoods/hoods.json diff --git a/package/ffnw-hoods/files/lib/ffnw/hoods/hoods.json b/package/ffnw-hoods/files/lib/ffnw/hoods/hoods.json new file mode 100644 index 00000000..86a0b905 --- /dev/null +++ b/package/ffnw-hoods/files/lib/ffnw/hoods/hoods.json @@ -0,0 +1,64 @@ +[ + { + "name": "default", + "bssid": "02:CA:FF:AA:BA:BF", + "defaulthood": true, + "servers": [ + { + "host": "default02.entenhausen.de", + "port": "11111", + "publickey": "32a02dd5e6b9454734tt3r3ad30bcf269564546ewtrew5442533tgr258cb" + }, + { + "host": "default03.entenhausen.de", + "port": "11111", + "publickey": "973732543523frea17cfa53d2b9e025202trwrtefacf9503737tf5a23204" + }, + { + "host": "default04.entenhausen.de", + "port": "11111", + "publickey": "432534t2ewe9a10620d09402ca2frewfwdf364e6fwqfer4re669a2eb5e6f" + }, + { + "host": "default05.entenhausen.de", + "port": "11111", + "publickey": "34rfewf4754tg3f54538236bae815ef2r4245f20c63aeregt452b183045d" + }, + { + "host": "default06.entenhausen.de", + "port": "11111", + "publickey": "2245t42gvre0ff93a0rewferewfrewb00dd215arefewf4355e5eb16f0b69" + } + ], + "boxes": [] + }, + { + "name": "quackenbruek", + "bssid": "02:AA:0A:12:80:43", + "defaulthood": false, + "servers": [ + { + "host": "quackenbruek01.sn.entenhausen.de", + "port": "10000", + "publickey": "c6485dsaqdwe33dbebdbweqrfqca4aewfc3453453224532434fr4f3d629d" + }, + { + "host": "quackenbruek02.sn.entenhausen.de", + "port": "10001", + "publickey": "887c6c4e1ff435t4t42gt4aac03cd9494bd13d1221ea6cewqdq3431r4194" + } + ], + "boxes": [ + [ + [ + 52.18, + 7.41 + ], + [ + 52.35, + 7.9 + ] + ] + ] + } +] From 16d7eafdf3c3ed4a3b35840bbb585a87a97e93ef Mon Sep 17 00:00:00 2001 From: Jan-Tarek Butt Date: Sun, 25 Sep 2016 02:26:26 +0200 Subject: [PATCH 2/7] add PKG Makefile for hoodfile #789 --- package/ffnw-hoods/Makefile | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 package/ffnw-hoods/Makefile diff --git a/package/ffnw-hoods/Makefile b/package/ffnw-hoods/Makefile new file mode 100644 index 00000000..f7ac9192 --- /dev/null +++ b/package/ffnw-hoods/Makefile @@ -0,0 +1,38 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=ffnw-hoods +PKG_VERSION:=1 +PKG_RELEASE:=1 + +PKG_BUILD_DEPENDS := luci-base/host lua-cjson/host + +PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) + +include $(GLUONDIR)/include/package.mk + +define Package/ffnw-hoods + SECTION:=networke + CATEGORY:=Freifunk Nordwest + TITLE:=Hoodjson file +endef + +define Package/ffnw-hoods/description + Hoodjson file for defined hoods +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) +endef + +define Build/Configure +endef + +define Build/Compile +endef + +define Package/ffnw-hoods/install + $(INSTALL_DIR) $(1)/lib/ffnw/hoods + lua -e 'local cjson = require("cjson"); print(cjson.encode(cjson.decode(assert(io.open("./files/lib/ffnw/hoods/hoods.json"):read("*a")))))' > $(1)/lib/ffnw/hoods/hoods.json +endef + +$(eval $(call BuildPackage,ffnw-hoods)) From 9a32ecb0d7165af998dae24add587b32952d151d Mon Sep 17 00:00:00 2001 From: Jan-Tarek Butt Date: Sun, 25 Sep 2016 02:26:27 +0200 Subject: [PATCH 3/7] add hoodselector respondd c file #789 --- package/ffnw-hoodselector/src/respondd.c | 119 +++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 package/ffnw-hoodselector/src/respondd.c diff --git a/package/ffnw-hoodselector/src/respondd.c b/package/ffnw-hoodselector/src/respondd.c new file mode 100644 index 00000000..fc43c0c2 --- /dev/null +++ b/package/ffnw-hoodselector/src/respondd.c @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include + +#define _STRINGIFY(s) #s +#define STRINGIFY(s) _STRINGIFY(s) + +bool strstw(const char *pre, const char *str) { + size_t lenpre = strlen(pre); + return strlen(str) < lenpre ? false : strncmp(pre, str, lenpre) == 0; +} + +bool strrmbs(char *line, int begin, int end) { // <- ist es hier sinvoller pointer auf die ints zu setzen?? + size_t len = strlen(line); + if (len < begin) + return false; + + memmove(line, line+begin, len - begin + 1); + if (len < end) + return false; + + line[len-end] = 0; //remove val of end characters on the end + return true; +} + +// extract hood informations +static struct json_object * get_hoodselector(void) { + FILE *f = fopen("/tmp/.hoodselector", "r"); + if (!f) + return NULL; + + struct json_object *ret = json_object_new_object(); + char *line = NULL; + size_t len = 0; + while (getline(&line, &len, f) >= 0) { + //1. Get md5 hash from current selected hood. + if (strstw("\"md5hash\": ",line)) { + if (!strrmbs(line, 12, 14)) + continue; + + json_object_object_add(ret, "md5hash", gluonutil_wrap_string(line)); + } + //2. Get true or false string for VPN Router. + if (strstw("\"vpnrouter\": ",line)) { + if (!strrmbs(line, 14, 16)) + continue; + + json_object_object_add(ret, "vpnrouter", gluonutil_wrap_string(line)); + } + //3. Get hoodname + if (strstw("\"hoodname\": ",line)) { + if (!strrmbs(line, 13, 15)) + continue; + + json_object_object_add(ret, "hoodname", gluonutil_wrap_string(line)); + } + } + free(line); + fclose(f); + return ret; +} + +//Get currend selected BSSID +static struct json_object * get_current_selected_bssid(void){ + 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; + + if (strncmp(e->name, "ibss_", 5)) + continue; + + const char *bssid = uci_lookup_option_string(ctx, s, "bssid"); + if (!bssid) + continue; + + struct json_object *ret = json_object_new_object(); + json_object_object_add(ret, "bssid", gluonutil_wrap_string(bssid)); + free(bssid); + uci_free_context(ctx); + return ret; + } +end: + uci_free_context(ctx); + return NULL; +} + + +// create final obj with logical structure +static struct json_object * respondd_provider_hoodselector(void) { + struct json_object *ret = json_object_new_object(); + + struct json_object *hoodinfo = get_hoodselector(); + if(hoodinfo) + json_object_object_add(ret, "hoodinfo", hoodinfo); + + struct json_object *selectedbssid = get_current_selected_bssid(); + if(selectedbssid) + json_object_object_add(ret, "selectedbssid", selectedbssid); + + return ret; +} + +// related to respondd_provider_hoodselector +const struct respondd_provider_info respondd_providers[] = { + {"hoodselector", respondd_provider_hoodselector}, + {} +}; From 71541ac88de32650fceb40106dc084852bfe1dc7 Mon Sep 17 00:00:00 2001 From: Jan-Tarek Butt Date: Sun, 25 Sep 2016 02:26:28 +0200 Subject: [PATCH 4/7] add hoodselector respondd Makefile #789 --- package/ffnw-hoodselector/src/Makefile | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 package/ffnw-hoodselector/src/Makefile diff --git a/package/ffnw-hoodselector/src/Makefile b/package/ffnw-hoodselector/src/Makefile new file mode 100644 index 00000000..3ddc8a58 --- /dev/null +++ b/package/ffnw-hoodselector/src/Makefile @@ -0,0 +1,6 @@ +all: respondd.so + +CFLAGS += -Wall + +respondd.so: respondd.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -fPIC -D_GNU_SOURCE -o $@ $^ $(LDLIBS) -lgluonutil -luci From 1334cf00aedb25b49ba333f230af39d4a035d029 Mon Sep 17 00:00:00 2001 From: Jan-Tarek Butt Date: Sun, 25 Sep 2016 02:26:29 +0200 Subject: [PATCH 5/7] add hoodselector #789 --- package/ffnw-hoodselector/luasrc/hoodselector | 722 ++++++++++++++++++ 1 file changed, 722 insertions(+) create mode 100755 package/ffnw-hoodselector/luasrc/hoodselector diff --git a/package/ffnw-hoodselector/luasrc/hoodselector b/package/ffnw-hoodselector/luasrc/hoodselector new file mode 100755 index 00000000..5147dc09 --- /dev/null +++ b/package/ffnw-hoodselector/luasrc/hoodselector @@ -0,0 +1,722 @@ +#!/usr/bin/lua + +-- This is the hoodselector. The hoodselector is one of the main components for +-- splitting a layer 2 mesh network into seperated network segments (hoods). +-- The job of the hoodselector is to automatically detect in which hood +-- the router is located based on geo settings or by scanning its environment. +-- Based on these informations the hoodselector should select a hood from a +-- list of known hoods (hoodlist) and adjust vpn, wireless and mesh on lan +-- configuration based on the settings given for the selected hood. +-- +-- The hoodlist containing all hood settings is located in a seperate hoodfile +-- in the hoods package. +-- +-- The hoodselector depends on the folowing additional software: +-- * fastd (vpn configuration) see getCurrentPeers(), setHoodVPN() +-- * iw (wireless network scanning) see getNeigbourBssid() +-- * batman-adv (mesh protocol) see directVPN(), getGwRange() +-- * respondd (molwm) see molwm() +-- +-- To detect the current hood the hoodselector knows 2 modes containing +-- * 1. Default mode (VPN Router) +-- - set real hood dependent on geo position. +-- - set default hood dependent on geo position. +-- * 2. Scan modes +-- - Set wifi conf on scanned BSSID +-- - Set vpn conf getting by BSSID (if no VPN conf exsist disable fastd) +-- When selecting a hood, the hoodselector has the following priorities: +-- 1. Selecting a hood by geo position depending on direct VPN connection. +-- 2. force creating one mesh cloud with neigbour mesh routers +-- 3. if routers had only mesh setting vpn config depends on the BSSID +-- +-- Resources +-- * https://wireless.wiki.kernel.org/en/users/documentation/iw + +-- MOLWM respondd file +local molwmFile="/tmp/.hoodselector" + +local molwmtable = {} +molwmtable["md5hash"] = "" +molwmtable["vpnrouter"] = "" +molwmtable["hoodname"] = "" + +-- PID file to ensure the hoodselector isn't running parallel +local pidPath="/var/run/hoodselector.pid" + +if io.open(pidPath, "r") ~=nil then + io.stderr:write("The hoodselector is still running.\n") + os.exit(1) +else + io.close(io.open(pidPath, "w")) +end + +local json = require ("luci.jsonc") +local uci = require('luci.model.uci').cursor() +local file = '/lib/ffnw/hoods/hoods.json' +-- initialization done + +-- Read the full hoodfile. Return nil for wrong format or no such file +local function readHoodfile(file) + local jhood = io.open(file, 'r') + if not jhood then return nil end + local obj, pos, err = json.parse (jhood:read('*a'), 1, nil) + if err then + return nil + else + return obj + end +end + +local function mesh_on_wan_disable() + os.execute('ifdown mesh_wan') + io.stderr:write('Interface mesh_wan disabled.\n') +end + +local function mesh_on_wan_enable() + os.execute('ifup mesh_wan') + io.stderr:write('Interface mesh_wan enabled.\n') +end + +local function mesh_on_lan_disable() + os.execute('ifdown mesh_lan') + io.stderr:write('Interface mesh_lan disabled.\n') +end + +local function mesh_on_lan_enable() + os.execute('ifup mesh_lan') + io.stderr:write('Interface mesh_lan enabled.\n') +end + +local function molwm() + local mesh_en = true + local respondd = string.format("gluon-neighbour-info -i bat0 -p 1001 -d ff02::2 -r hoodselector -t 0.5") + for line in io.popen(respondd, 'r'):lines() do + local obj, pos, err = json.parse (line, 1, nil) + if err then + io.stderr:write("json parse error!\n") + mesh_en = false + break + else + if obj["hoodinfo"] ~= nil then + if not ( obj["hoodinfo"]["md5hash"] == molwmtable["md5hash"]:gsub('\"', '') ) then + io.stderr:write("hashes are not equals!\n") + mesh_en = false + break + end + end + end + end + if uci:get('network', 'mesh_wan') and not mesh_en then + mesh_on_wan_disable() + end + if uci:get('network', 'mesh_lan') and not mesh_en then + mesh_on_lan_disable() + end + if uci:get('network', 'mesh_wan') and mesh_en then + mesh_on_wan_enable() + end + if uci:get('network', 'mesh_lan') and mesh_en then + mesh_on_lan_enable() + end +end + +-- Create md5 hash from currend hood +local function molwm_md5hash(hood) + local file = io.open("/tmp/.hoodhash", "w") + if not file then + io.stderr:write('\"/tmp/.hoodhash\" can not created\n') + else + file:write(json.stringify(hood, { indent = true })) + file:close() + --part to create md5 hash of this file + for line in io.popen(string.format( "md5sum /tmp/.hoodhash")):lines() do + for i in string.gmatch(line, "%S+") do + if (string.len(i) == 32) then + molwmtable["md5hash"] = "\"" .. string.format(i) .. "\"" + break + end + end + end + os.remove("/tmp/.hoodhash") + end +end + +-- Write MOLWM content into file +local function write_molwm(hood) + if hood ~= nil then + molwm_md5hash(hood) + molwmtable["hoodname"] = "\"" .. hood["name"] .. "\"" + end + molwm() + local file = io.open(molwmFile, "w") + if not file then + io.stderr:write(molwmFile ..' not found or not createble!\n') + else + file:write("\"md5hash\": " .. molwmtable["md5hash"] .. "\n") + file:write("\"vpnrouter\": " .. molwmtable["vpnrouter"] .. "\n") + file:write("\"hoodname\": " .. molwmtable["hoodname"] .. "\n") + file:close() + end +end + +-- Program terminating function including removing of PID file +local function exit() + if io.open(pidPath, "r") ~=nil then + os.remove(pidPath) + end + os.exit(0) +end + +local function trim(s) + -- from PiL2 20.4 + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + +local function sleep(n) + os.execute("sleep " .. tonumber(n)) +end + +local function brclient_restart() + os.execute('ifconfig br-client down') + os.execute('ifconfig br-client up') + io.stderr:write('Interface br-client restarted.\n') +end + +local function vpn_stop() + os.execute('/etc/init.d/fastd stop') + io.stderr:write('VPN stopped.\n') +end + +local function vpn_start() + os.execute('/etc/init.d/fastd start') + io.stderr:write('VPN started.\n') + brclient_restart() +end + +local function vpn_disable() + -- disable VPN if not already disabled + os.execute('/etc/init.d/fastd disable') + io.stderr:write('VPN disabled.\n') +end + +local function vpn_enable() + -- enable VPN if not already enabled + os.execute('/etc/init.d/fastd enable') + io.stderr:write('VPN enable.\n') +end + +local function wireless_restart() + os.execute('wifi') + io.stderr:write('Wireless restarted.\n') +end + +-- Get a list of wifi devices return an emty table for no divices +local function getWifiDevices() + local radios = {} + uci:foreach('wireless', 'wifi-device', + function(s) + table.insert(radios, s['.name']) + end + ) + return radios +end + +-- Scans for wireless networks and returns a two dimensional array containing +-- wireless mesh neigbour networks and their properties. +-- The array is sorted descending by signal strength (strongest signal +-- first, usually the local signal of the wireless chip of the router) +local function wlan_list_sorted(radios) + local networks = {} + for index, radio in ipairs(radios) do + local ifname = uci:get('wireless', 'ibss_' .. radio, 'ifname') + local ssid = uci:get('wireless', 'ibss_' .. radio, 'ssid') + if (ifname ~= nil and ssid ~= nil) then + local wireless_scan = string.format( "iw %s scan", ifname) + local row = {} + row["radio"] = radio + -- loop through each line in the output of iw + for wifiscan in io.popen(wireless_scan, 'r'):lines() do + -- the following line matches a new network in the output of iw + if wifiscan:match("BSS (%w+:%w+:%w+:%w+:%w+:%w+)") then + if(row["bssid"] ~= nil and row["quality"] ~= nil + and row["ssid"] == ssid) then + table.insert(networks, row) + row = {} + row["radio"] = radio + end + end + + -- get ssid + if wifiscan:match("SSID:") then + row["ssid"] = wifiscan:split(":") + row["ssid"] = row["ssid"][2] + if(row["ssid"] ~= nil) then + row["ssid"] = trim(row["ssid"]) + end + end + + -- get frequency + if wifiscan:match("freq:") then + row["frequency"] = wifiscan:split(":") + row["frequency"] = row["frequency"][2] + if(row["frequency"] ~= nil) then + row["frequency"] = trim(row["frequency"]) + end + end + + -- get bssid + if wifiscan:match("(%w+:%w+:%w+:%w+:%w+:%w+)") then + row["bssid"] = wifiscan:match("(%w+:%w+:%w+:%w+:%w+:%w+)"):upper() + end + + -- get signal strength + if wifiscan:match("signal:") then + row["quality"] = wifiscan:split(" ") + row["quality"] = row["quality"][2]:split(".") + if row["quality"][1]:match("-") then + row["quality"] = row["quality"][1]:split("-") + end + row["quality"] = tonumber(row["quality"][2]:match("(%d%d)")) + end + end + else + io.stderr:write("wireless uci config broken! abort...\n") + exit(); + end + end + + table.sort(networks, function(a,b) return a["quality"] < b["quality"] end) + return networks +end + +-- this method removes the wireless network of the router itself +-- from the wlan_list +local function filter_my_wlan_network(wlan_list) + local filtered_wlan_list = {} + + for n,wlan in pairs(wlan_list) do + if(wlan.quality ~= 0) then + table.insert(filtered_wlan_list, wlan) + end + end + + return filtered_wlan_list +end + +local function filter_default_hood_wlan_networks(default_hood, wlan_list) + local filtered_wlan_list = {} + + for n,wlan in pairs(wlan_list) do + if(default_hood.bssid ~= wlan.bssid) then + table.insert(filtered_wlan_list, wlan) + end + end + + return filtered_wlan_list +end + +-- bool if direct VPN. The detection is realaise by searching the fastd network interface inside the originator table +local function directVPN() + -- escape special chars "[]-" + for outgoingIF in io.open("/sys/kernel/debug/batman_adv/bat0/originators", 'r'):lines() do + local vpnIface = uci:get('fastd', 'mesh_vpn_backbone', 'net') + if not vpnIface then + io.stderr:write("fastd uci config broken! abort...\n") + exit() + end + if outgoingIF:match(string.gsub("%[ " .. vpnIface .. "%]","%_",'-'):gsub("%-", "%%-")) then + molwmtable["vpnrouter"] = "\"true\"" + return true + end + end + molwmtable["vpnrouter"] = "\"false\"" + return false +end + +-- Retun a table of current peers from /etc/config/fastd +local function getCurrentPeers() + local configPeers = {} + local err = uci:foreach('fastd', 'peer', + function(s) + if s['.name'] then + for prefix,peer in pairs(s) do + local tmpPeer = {} + if prefix:match(".name") then + if peer:match("mesh_vpn_backbone_peer_") then + -- val tmpRemote does not need uci exception check because its already include by "uci:foreach" + local tmpRemote = uci:get('fastd', peer, 'remote') + tmpRemote = tmpRemote[1]:split(" ") + local remote = {} + remote['host'] = tmpRemote[1] + remote[tmpRemote[2]] = tmpRemote[3] + -- uci:get does not need uci exception check because its already include by "uci:foreach" + tmpPeer['key'] = tostring(uci:get('fastd', peer, 'key')) + tmpPeer['remote'] = remote + configPeers[peer] = tmpPeer + end + end + end + end + end + ) + if not err then + io.stderr:write("fastd uci config broken! abort...\n") + exit() + end + return configPeers +end + + +-- Get Geoposition. Return nil for no position +local function getGeolocation() + local ret = {} + table.insert(ret, tonumber(uci:get('gluon-node-info', uci:get_first('gluon-node-info', 'location'), 'latitude'))) + table.insert(ret, tonumber(uci:get('gluon-node-info', uci:get_first('gluon-node-info', 'location'), 'longitude'))) + return ret +end + +-- Return hood from the hood file based on geo position or nil, no real hood could be determined +local function getHoodByGeo(jhood,geo) + for n, h in pairs(jhood) do + for n, box in pairs(h.boxes) do + if ( geo[1] >= box[1][1] and geo[1] < box[2][1] and geo[2] >= box[1][2] and geo[2] < box[2][2] ) then + return h + end + end + end + return nil +end + +-- This method checks if the VPN configuration needs to be rewritten from the +-- hoodfile. Therefore the method performs 3 checks and returns false if all +-- checks fail. If one of the checks results to true the method returns true: +-- 1. Check if the local VPN configuratin has a server that does not exist +-- in the hoodfile. +-- 2. Check if a server that does exist in the local VPN configuration AND +-- in the hoodfile has a configuration change. +-- 3. Check if the hoodfile contains a server that does not exist in the +-- local VPN configuration. +local function vpn_reconfiguration_needed(hood_serverlist,local_serverlist) + -- Checks 1. and 2. + for local_server_config_name, local_server in pairs(local_serverlist) do + local local_server_exists_in_hoodfile = false + for hood_server_index,hood_server in pairs(hood_serverlist) do + if (local_server_config_name == 'mesh_vpn_backbone_peer_'.. hood_server["host"]:split('.')[1]:gsub("%-", "%_")) then + local_server_exists_in_hoodfile = true + if ( local_server.key ~= hood_server['publickey'] ) then + return true + end + if ( local_server.remote.host ~= '\"'..hood_server["host"]..'\"' ) then + return true + end + if ( local_server.remote.port ~= hood_server['port'] ) then + return true + end + end + end + if not(local_server_exists_in_hoodfile) then return true end + end + + -- Check 3. + for hood_server_index,hood_server in pairs(hood_serverlist) do + local hood_server_exists_locally = false + for local_server_config_name, local_server in pairs(local_serverlist) do + if (local_server_config_name == 'mesh_vpn_backbone_peer_'.. hood_server["host"]:split('.')[1]:gsub("%-", "%_")) then + hood_server_exists_locally = true + end + end + if not(hood_server_exists_locally) then return true end + end + + return false +end + +-- Reconfigure fastd +local function vpn_reconfigure(hood_serverlist,local_serverlist) + -- remove all servers + for config_index, local_server in pairs(local_serverlist) do + uci:delete('fastd',config_index) + end + + -- add servers from hoodfile + local group = 'mesh_vpn_backbone' + for i,hood_server in pairs(hood_serverlist) do + uci:section('fastd', 'peer', group .. '_peer_' .. hood_server.host:split('.')[1]:gsub("%-", "%_"), + { + enabled = 1, + net = 'mesh_vpn', + group = group, + key = hood_server.publickey, + remote = {'\"'..hood_server.host..'\"'..' port '..hood_server.port} + } + ) + end + + uci:save('fastd') + uci:commit('fastd') + io.stderr:write('Fastd needed reconfiguration. Stopped and applied new settings.\n') +end + +-- Checks if wireless needs a reconfiguration. Returns true if any of the checks +-- passes. Otherwise the method returns false. +local function wireless_reconfiguration_needed(radios, hood_bssid) + for index, radio in ipairs(radios) do + if ( uci:get('wireless', 'ibss_' .. radio, 'bssid') ~= hood_bssid ) then + return true + end + end + return false +end + +-- Reconfigure wireless +local function wireless_reconfigure(radios, hood_bssid) + for index, radio in ipairs(radios) do + if not ( uci:get('wireless', 'ibss_' .. radio, 'bssid') == hood_bssid ) then + uci:section('wireless', 'wifi-iface', 'ibss_' .. radio, { + bssid = hood_bssid + }) + end + end + uci:save('wireless') + uci:commit('wireless') +end + +-- This method sets a new hoodconfig and takes care that services are only +-- stopped or restarted if reconfiguration is needed. +-- Process: +-- * Check if wireless needs reconfiguration and prepare reconfiguration +-- * Check if fastd needs reconfiguration and prepare reconfiguration +-- * If fastd needs reconfiguration, stop fastd and apply new settings but +-- dont restart it before wireless has been reconfigured +-- * If wireless needs reconfiguration apply new settings and restart wireless +-- * If fastd needed reconfiguration start fastd now +local function set_hoodconfig(hood, radios) + local local_serverlist = getCurrentPeers() + -- Check if VPN needs reconfiguration because in case of reconfiguration we + -- need to stop VPN before we can reconfigure any other connection. + local vpn_reconfiguration_needed = vpn_reconfiguration_needed(hood["servers"],local_serverlist); + if(vpn_reconfiguration_needed) then + vpn_stop() + end + + -- reconfigure wireless + if(wireless_reconfiguration_needed(radios, hood["bssid"])) then + wireless_reconfigure(radios, hood["bssid"]) + wireless_restart() + io.stderr:write('Wireless needed reconfiguration. Applied new settings and restarted.\n') + end + + -- reconfigure fastd + if (vpn_reconfiguration_needed) then + vpn_reconfigure(hood["servers"],local_serverlist) + -- scan mode can disable VPN so we need to make shure that VPN is enabled + -- if the router selects a hood + vpn_enable() + vpn_start() + io.stderr:write('VPN needed reconfiguration. Applied new settings and restarted.\n') + end + io.stderr:write("Set hood \""..hood["name"].."\"\n") + molwmtable["hoodname"] = "\"" .. hood["name"] .. "\"" + + return true +end + +-- Return the default hood in the hood list. +-- This method can return the following data: +-- * default hood +-- * nil if no default hood has been defined +local function getDefaultHood(jhood) + for n, h in pairs(jhood) do + if h.defaulthood then + return h + end + end + return nil +end + +-- boolean check if batman-adv has gateways +local function batmanHasGateway() + for gw in io.open("/sys/kernel/debug/batman_adv/bat0/gateways", 'r'):lines() do + if gw:match("Bit") then + return true + end + end + return false +end + +-- Return hood from the hood file based on a given BSSID. nil if no matching hood could be found +local function gethoodByBssid(jhood, scan_bssid) + for n, h in pairs(jhood) do + if scan_bssid:match(h.bssid) then + return h + end + end + return nil +end + +-- Return hood from hood file based on a peer address. nil if no matching hood could be found +local function getCurrentHood(jhood) + for local_server_config_name, local_server in pairs(getCurrentPeers()) do + for n, h in pairs(jhood) do + for n, peer in pairs(h.servers) do + if ( peer["host"] == local_server.remote.host:gsub("\"", "") ) then + return h + end + end + end + end + return nil +end + +local function get_batman_mesh_network(sorted_wlan_list, defaultHood) + io.stderr:write('Testing neighboring adhoc networks for batman advanced gw connection.\n') + io.stderr:write('The following wireless networks have been found:\n') + for n, network in pairs(sorted_wlan_list) do + print(network["quality"].."\t"..network["frequency"].."\t"..network["bssid"].."\t"..network["ssid"]) + end + + -- we dont want to get tricked by our signal + sorted_wlan_list = filter_my_wlan_network(sorted_wlan_list) + -- we dont want to test the default hood because if there is no other + -- hood present we will connect to the default hood anyway + sorted_wlan_list = filter_default_hood_wlan_networks(defaultHood, sorted_wlan_list) + + io.stderr:write('After filtering we will test the following wireless networks:\n') + for n, network in pairs(sorted_wlan_list) do + print(network["quality"].."\t"..network["frequency"].."\t"..network["bssid"].."\t"..network["ssid"]) + end + + local bssid = nil + if(next(sorted_wlan_list)) then + io.stderr:write("Prepare configuration for testing wireless networks...\n") + -- Notice: + -- we will use iw for testing the wireless networks because using iw does + -- not need any changes inside the uci config. This approach allows the + -- router to automatically reset to previous configuration in case + -- someone disconnects the router from power during test. + + -- stop vpn to prevent two hoods from beeing connected in case + -- the router gets internet unexpectedly during test. + vpn_stop() + -- remove the ap network because we cannot change + -- the settings of the adhoc network if the ap network is still operating + os.execute("iw dev client0 del") + for n, wireless in pairs(sorted_wlan_list) do + io.stderr:write("Testing "..wireless["bssid"].."...") + -- leave the current adhoc network + os.execute("iw dev ibss0 ibss leave") + -- setup the adhoc network we want to test + os.execute("iw dev ibss0 ibss join "..wireless["ssid"].." "..wireless["frequency"].." "..wireless["bssid"]) + -- sleep 30 seconds till the connection is fully setup + sleep(30) + + if batmanHasGateway() then + bssid = wireless["bssid"] + break; + end + end + vpn_start() + wireless_restart() + io.stderr:write("Finished testing wireless networks, restored previous configuration\n") + end + + return bssid +end + +-- INITIALIZE AND PREPARE DATA -- +-- read hoodfile, exit if reading the hoodfile fails +local jhood = readHoodfile(file) +if jhood == nil then + io.stderr:write('There seems to have gone something wrong while reading hoodfile from ' .. file .. '\n') + exit() +end + +-- check if a default hood has been defined and exit if none has been defined +local defaultHood = getDefaultHood(jhood) +if defaultHood == nil then + io.stderr:write('No defaulthood defined.\n') + exit() +end + +-- Get list of wifi devices +local radios = getWifiDevices() + +-- VPN MODE +-- If we have a VPN connection then we will try to get the routers location and +-- select the hood coresponding to our location. +-- If no hood for the location has been defined, we will select +-- the default hood. +-- If we can not get our routers location, we will fallback to scan mode. +if directVPN() then + io.stderr:write('VPN connection found.\n') + local geo = getGeolocation() + if geo[1] ~= nil and geo[2] ~= nil then + io.stderr:write('Position found.\n') + local geoHood = getHoodByGeo(jhood, geo) + if geoHood ~= nil then + set_hoodconfig(geoHood, radios) + io.stderr:write('Hood set by VPN mode.\n') + write_molwm(geoHood) + exit() + end + io.stderr:write('No hood has been defined for current position.\n') + set_hoodconfig(defaultHood, radios) + io.stderr:write('Defaulthood set.\n') + write_molwm(defaultHood) + exit() + end + io.stderr:write('No position found\n') +else + io.stderr:write('No VPN connection found\n') +end + +if batmanHasGateway() then + io.stderr:write('Batman gateways found, everything seems to be ok - doing nothing\n') + local currendHood = getCurrentHood(jhood) + if currendHood ~= nil then + write_molwm(currendHood) + end + exit() +end + +-- SCAN MODE +if next(radios) then + -- check if there exist a neighboring freifunk batman advanced mesh + -- network with an active connection to a batman advanced gateway + local sortedWlanList = wlan_list_sorted(radios) + local meshBSSID = get_batman_mesh_network(sortedWlanList, defaultHood) + if meshBSSID ~= nil then + io.stderr:write("Neighoring freifunk batman advanced mesh with BSSID "..meshBSSID.." found\n") + local bssidHood = gethoodByBssid(jhood, meshBSSID) + if bssidHood ~= nil then + set_hoodconfig(bssidHood, radios) + io.stderr:write('Hood set by scan mode\n') + write_molwm(bssidHood) + exit() + end + + -- if the bssid does not corespond to any hood, we disable vpn and + -- just establish a wireless connection to the mesh without any vpn or + -- mesh on lan (TODO) connectivity + vpn_stop() + vpn_disable() + wireless_reconfigure(radios, meshBSSID) + wireless_restart() + io.stderr:write('Could not select a hood but established a connection via wireless mesh.\n') + io.stderr:write('Disabled all connections except connections via wireless mesh.\n') + local currendHood = getCurrentHood(jhood) + if currendHood ~= nil then + write_molwm(currendHood) + end + exit() + end + io.stderr:write('No neighboring freifunk batman advanced mesh found.\n') +end + +-- DEFAULT-HOOD MODE +-- If we do NOT have a VPN connection AND found no freifunk mesh network while +-- scanning then we set the default hood +set_hoodconfig(defaultHood, radios) +io.stderr:write('Set defaulthood.\n') +write_molwm(defaultHood) +exit() From 1f3ba481d1ca7cfafd69b27a3fb0e47d05e2c87a Mon Sep 17 00:00:00 2001 From: Jan-Tarek Butt Date: Sun, 25 Sep 2016 02:26:30 +0200 Subject: [PATCH 6/7] add hoodselector cron file #789 --- package/ffnw-hoodselector/files/usr/lib/micron.d/hoodselector | 1 + 1 file changed, 1 insertion(+) create mode 100644 package/ffnw-hoodselector/files/usr/lib/micron.d/hoodselector diff --git a/package/ffnw-hoodselector/files/usr/lib/micron.d/hoodselector b/package/ffnw-hoodselector/files/usr/lib/micron.d/hoodselector new file mode 100644 index 00000000..6e793151 --- /dev/null +++ b/package/ffnw-hoodselector/files/usr/lib/micron.d/hoodselector @@ -0,0 +1 @@ +*/2 * * * * /usr/sbin/hoodselector From 7e6b0fda732e1631199ecaf88e8154b77ddf4b63 Mon Sep 17 00:00:00 2001 From: Jan-Tarek Butt Date: Sun, 25 Sep 2016 02:26:31 +0200 Subject: [PATCH 7/7] add hoodselector PKG Makefile #789 --- package/ffnw-hoodselector/Makefile | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 package/ffnw-hoodselector/Makefile diff --git a/package/ffnw-hoodselector/Makefile b/package/ffnw-hoodselector/Makefile new file mode 100644 index 00000000..3682a3e7 --- /dev/null +++ b/package/ffnw-hoodselector/Makefile @@ -0,0 +1,45 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=ffnw-hoodselector +PKG_VERSION:=1 +PKG_RELEASE:=1 + +PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) +PKG_BUILD_DEPENDS := lua/host luci-base/host respondd + +include $(GLUONDIR)/include/package.mk + + +define Package/ffnw-hoodselector + SECTION:=networke + CATEGORY:=Freifunk Nordwest + TITLE:=Select the hoods depending on the geo coordinate + DEPENDS:=+ffnw-hoods +luci-lib-jsonc gluon-mesh-batman-adv-15 +gluon-mesh-vpn-fastd +respondd +endef + +define Package/ffnw-hoodselector/description + Select the hoods depending on the geo coordinates +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Configure +endef + +define Build/Compile + $(call Build/Compile/Default) + $(call GluonSrcDiet,./luasrc,$(PKG_BUILD_DIR)/luadest/) +endef + +define Package/ffnw-hoodselector/install + $(CP) ./files/* $(1)/ + $(INSTALL_DIR) $(1)/usr/sbin + $(CP) $(PKG_BUILD_DIR)/luadest/hoodselector $(1)/usr/sbin/hoodselector + $(INSTALL_DIR) $(1)/lib/gluon/respondd + $(CP) $(PKG_BUILD_DIR)/respondd.so $(1)/lib/gluon/respondd/hoodselector.so +endef + +$(eval $(call BuildPackage,ffnw-hoodselector))