Hi,
This patch set contains the hoodselector and a hoodfile as exsample. The hoodselector is a software that creates decentralized, semi automated ISO OSI layer 2 network segmentation for batman-adv layer 2 routing networks. This program reads the geobased sub-networks called hoods from the above mentioned hoodfile. The decision of choosing the right hood is made on following points: first, the hoodselector checks, if the router has a VPN connection. If it has, the hoodselector then checks, if a geoposition was set on the router. Knowing the position of the router the hoodselector can find the right hood, because each hood is defined with geocoordinates. If the Router doesn't have a VPN connection e.g. as a mesh only router, the hoodselector triggers a WIFI scan and searches for neighboured mesh routers in other hoods. If there is an other router with a different BSSID but with the same mesh SSID, the router chooses it’s hood based on the neighboured BSSID. Open issues can be found here[0].
cheers Tarek
[0] https://git.nordwest.freifunk.net/ffnw-firmware/packages/issues?label_name%5...
Jan-Tarek Butt (7): add exsample hoodfile #789 add PKG Makefile for hoodfile #789 add hoodselector respondd c file #789 add hoodselector respondd Makefile #789 add hoodselector #789 add hoodselector cron file #789 add hoodselector PKG Makefile #789
package/ffnw-hoods/Makefile | 38 ++ package/ffnw-hoods/files/lib/ffnw/hoods/hoods.json | 64 ++ package/ffnw-hoodselector/Makefile | 45 ++ .../files/usr/lib/micron.d/hoodselector | 1 + package/ffnw-hoodselector/luasrc/hoodselector | 722 +++++++++++++++++++++ package/ffnw-hoodselector/src/Makefile | 6 + package/ffnw-hoodselector/src/respondd.c | 119 ++++ 7 files changed, 995 insertions(+) create mode 100644 package/ffnw-hoods/Makefile create mode 100644 package/ffnw-hoods/files/lib/ffnw/hoods/hoods.json create mode 100644 package/ffnw-hoodselector/Makefile create mode 100644 package/ffnw-hoodselector/files/usr/lib/micron.d/hoodselector create mode 100755 package/ffnw-hoodselector/luasrc/hoodselector create mode 100644 package/ffnw-hoodselector/src/Makefile create mode 100644 package/ffnw-hoodselector/src/respondd.c
-- 2.10.0
--- package/ffnw-hoods/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 0000000..86a0b90 --- /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 + ] + ] + ] + } +] -- 2.10.0
--- 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 0000000..f7ac919 --- /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)) -- 2.10.0
--- 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 0000000..fc43c0c --- /dev/null +++ b/package/ffnw-hoodselector/src/respondd.c @@ -0,0 +1,119 @@ +#include <respondd.h> +#include <json-c/json.h> +#include <libgluonutil.h> +#include <uci.h> +#include <string.h> +#include <net/if.h> + +#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}, + {} +}; -- 2.10.0
--- 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 0000000..3ddc8a5 --- /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 -- 2.10.0
--- 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 0000000..5147dc0 --- /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() -- 2.10.0
--- 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 0000000..6e79315 --- /dev/null +++ b/package/ffnw-hoodselector/files/usr/lib/micron.d/hoodselector @@ -0,0 +1 @@ +*/2 * * * * /usr/sbin/hoodselector -- 2.10.0
--- 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 0000000..3682a3e --- /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)) -- 2.10.0
On Sun, Sep 25, 2016 at 02:26:24AM +0200, Jan-Tarek Butt wrote:
Hi,
This patch set contains the hoodselector and a hoodfile as exsample. The hoodselector is a software that creates decentralized, semi automated ISO OSI layer 2 network segmentation for batman-adv layer 2 routing networks. This program reads the geobased sub-networks called hoods from the above mentioned hoodfile. The decision of choosing the right hood is made on following points: first, the hoodselector checks, if the router has a VPN connection. If it has, the hoodselector then checks, if a geoposition was set on the router. Knowing the position of the router the hoodselector can find the right hood, because each hood is defined with geocoordinates. If the Router doesn't have a VPN connection e.g. as a mesh only router, the hoodselector triggers a WIFI scan and searches for neighboured mesh routers in other hoods. If there is an other router with a different BSSID but with the same mesh SSID, the router chooses it’s hood based on the neighboured BSSID. Open issues can be found here[0].
His Jan-Tarek,
Nice work so far! Is there some additional information regarding the hoodselector available somewhere? Ideally with some pretty flow chart(s) :-).
Next to the implementation specific issues you listed in [0], are there any known, conceptual limitations documented somewhere?
I'm having the feeling that conceptually a hoodselector might run into issues similar to the ones that prevented DFS (dynamic frequency selection) for 802.11s/IBSS so far. Especially when geocordinates are not reliablly available (e.g. one of the node owners configured bogus geocordinates, a router with geocordinates not in reliable range, timing issues?).
I would be very interested in hearing about your discussions concerning potential merging/converging issues and the solutions you came up with.
Regards, Linus
On Sat, Dec 24, 2016 at 04:21:13AM +0100, Linus Lüssing wrote:
Nice work so far! Is there some additional information regarding the hoodselector available somewhere? Ideally with some pretty flow chart(s) :-).
A flow chart or something similar would also be nice to understand why there is a dependency to batman-adv, for instance.
(Or could help in finding a protocol independent solution?)