From bfb90653c70a1f9c9573b4d0b4efc48702320f62 Mon Sep 17 00:00:00 2001 From: Jan-Tarek Butt <tarek@ring0.de> Date: Mon, 18 Sep 2017 18:20:28 +0200 Subject: [PATCH 5/5] add pkg gluon-hoodselector * add pkg Makefile * add respondd c file * add respondd Makefile * add etc config * add micron.d file * add upgrade script 540-hoodselector * add hoodselector lua script * rm gluon-hoods and gluon-mesh-vpn-fastd from DEPENDS * config rm static option * backport tested comunity version: - Hoodselector: is now able to handle Polygon hoods. !60 - Hoodselector: does not use `scan dump` anymore which saved airtime - Hoodselector: L2TP tunneldigger support #47 - Hoodselector: the upgrade script got a refactoring. Dead code is removed. desing failuer is fixed, the script use the hood BSSID instead the possible redundant hood name for hood identification. #107 - Hoodselector: New state, a router from hood A which is connected to an another router from hood B is now able to switch back in its own hood if a router from hood A viseble. #108 - Hoodselector: in state "radio less" is now ensured that mesh on LAN / WAN is enabled befor entering mode. #109 - Hoodselector: A bug in the function get_mesh_if() is fixed now. This function returns now a list of all mesh interfaces exzept the VPN interface #116 - Hoodselector: Old VPN configurations will be deleted now. If a hood without VPN peers got configured the old peers from the old hood was still presend. #117 - Hoodselector: many functions of the hoodselector are now placed in a lua libary. #118 --- package/gluon-hoodselector/Makefile | 43 + .../files/etc/config/hoodselector | 3 + .../files/usr/lib/micron.d/hoodselector | 1 + .../luasrc/lib/gluon/upgrade/540-hoodselector | 88 ++ .../luasrc/usr/lib/lua/hoodselector/util.lua | 898 +++++++++++++++++++++ .../luasrc/usr/sbin/hoodselector | 622 ++++++++++++++ package/gluon-hoodselector/src/Makefile | 6 + package/gluon-hoodselector/src/respondd.c | 139 ++++ 8 files changed, 1800 insertions(+) create mode 100644 package/gluon-hoodselector/Makefile create mode 100644 package/gluon-hoodselector/files/etc/config/hoodselector create mode 100644 package/gluon-hoodselector/files/usr/lib/micron.d/hoodselector create mode 100755 package/gluon-hoodselector/luasrc/lib/gluon/upgrade/540-hoodselector create mode 100644 package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua create mode 100755 package/gluon-hoodselector/luasrc/usr/sbin/hoodselector create mode 100644 package/gluon-hoodselector/src/Makefile create mode 100644 package/gluon-hoodselector/src/respondd.c diff --git a/package/gluon-hoodselector/Makefile b/package/gluon-hoodselector/Makefile new file mode 100644 index 00000000..210e5200 --- /dev/null +++ b/package/gluon-hoodselector/Makefile @@ -0,0 +1,43 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=gluon-hoodselector +PKG_VERSION:=1 +PKG_RELEASE:=1 + +PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) +PKG_BUILD_DEPENDS := respondd + +include ../gluon.mk + +define Package/gluon-hoodselector + SECTION:=network + CATEGORY:=Gluon + TITLE:=Automatic layer2 networksecmentation depending on geo coordinates + DEPENDS:=+luci-lib-jsonc +gluon-mesh-batman-adv-15 +respondd +iwinfo +endef + +define Package/gluon-hoodselector/description + Automatic layer2 networksecmentation depending on 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/gluon-hoodselector/install + $(CP) ./files/* $(1)/ + $(CP) $(PKG_BUILD_DIR)/luadest/* $(1)/ + $(INSTALL_DIR) $(1)/lib/gluon/respondd + $(CP) $(PKG_BUILD_DIR)/respondd.so $(1)/lib/gluon/respondd/hoodselector.so +endef + +$(eval $(call BuildPackage,gluon-hoodselector)) diff --git a/package/gluon-hoodselector/files/etc/config/hoodselector b/package/gluon-hoodselector/files/etc/config/hoodselector new file mode 100644 index 00000000..24b2800c --- /dev/null +++ b/package/gluon-hoodselector/files/etc/config/hoodselector @@ -0,0 +1,3 @@ +config settings 'hoodselector' + option hoodfile '/lib/gluon/hoods.json' + option enabled '1' diff --git a/package/gluon-hoodselector/files/usr/lib/micron.d/hoodselector b/package/gluon-hoodselector/files/usr/lib/micron.d/hoodselector new file mode 100644 index 00000000..a4c9916d --- /dev/null +++ b/package/gluon-hoodselector/files/usr/lib/micron.d/hoodselector @@ -0,0 +1 @@ +*/2 * * * * /usr/sbin/hoodselector 2>> /tmp/hoodselector_error diff --git a/package/gluon-hoodselector/luasrc/lib/gluon/upgrade/540-hoodselector b/package/gluon-hoodselector/luasrc/lib/gluon/upgrade/540-hoodselector new file mode 100755 index 00000000..a0fe8d65 --- /dev/null +++ b/package/gluon-hoodselector/luasrc/lib/gluon/upgrade/540-hoodselector @@ -0,0 +1,88 @@ +#!/usr/bin/lua + +local uci = require('simple-uci').cursor() +local hoodutil = require("hoodselector.util") + +-- Retun a table of current peers from /etc/config/fastd +local function getCurrentPeers() + local configPeers = {} + local err = uci:foreach('fastd', 'peer', + function(s) + hoodutil.extrac_fastd_peer(s,configPeers) + end + ) + if not err then + os.exit(1) + end + return configPeers +end + +-- START HERE +local hoodfile = uci:get('hoodselector', 'hoodselector', 'hoodfile') +local hoodbssid = uci:get('hoodselector', 'hoodselector', 'hood') + +if hoodfile == nil then + os.exit(1) +end + +local jhood = hoodutil.readHoodfile(hoodfile) +if jhood == nil then + uci:set('hoodselector', 'hoodselector', 'enabled', false) + uci:save('hoodselector') + os.exit(1) +end + +if hoodbssid == nil then + local defaulthood = hoodutil.getDefaultHood(jhood) + if defaulthood == nil then + os.exit(1) + end + if defaulthood.bssid == nil then + os.exit(1) + end + hoodbssid = defaulthood.bssid +end + +local hood = hoodutil.gethoodByBssid(jhood,hoodbssid) +if hood == nil then + os.exit(1) +end + +uci:section('hoodselector', 'settings', 'hoodselector', { + hood = hoodbssid, + hoodfile = hoodfile, + enabled = true +}) +uci:save('hoodselector') + +local radios = hoodutil.getWifiDevices() +-- pre check if fastd conf exsist +if uci:get('fastd', 'mesh_vpn_backbone', 'net') ~= nil then + hoodutil.fastd_reconfigure(hood["servers"],getCurrentPeers()) +end +-- per check if tunneldigger conf exsist +if uci:get('tunneldigger', 'mesh_vpn', 'interface') ~= nil then + hoodutil.tunneldigger_reconfigure(hood["servers"]) +end + +if next(radios) then + local ibss_exists = false + local mesh_exists = false + for _,radio in ipairs(radios) do + local ifname = uci:get('wireless', 'ibss_' .. radio, 'ifname') + if (ifname ~= nil) then + ibss_exists = true + end + ifname = uci:get('wireless', 'mesh_' .. radio, 'ifname') + if (ifname ~= nil) then + mesh_exists = true + end + end + if ibss_exists then + hoodutil.ibss_reconfigure(radios, hood["bssid"]) + end + if mesh_exists then + local mesh_prefix = hoodutil.get_mesh_prefix(radios) + hoodutil.mesh_reconfigure(radios, mesh_prefix..hood["bssid"]:lower()) + end +end diff --git a/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua b/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua new file mode 100644 index 00000000..05678809 --- /dev/null +++ b/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua @@ -0,0 +1,898 @@ +local json = require ("luci.jsonc") +local uci = require('simple-uci').cursor() + +local M = {} + +function M.split(s, delimiter) + local result = {}; + for match in (s..delimiter):gmatch("(.-)"..delimiter) do + table.insert(result, match); + end + return result; +end + +local PID = M.split(io.open("/proc/self/stat", 'r'):read('*a'), " ")[1] + +function M.log(msg) + if msg then + io.stdout:write(msg.."\n") + os.execute("logger hoodselector["..PID.."]: "..msg) + end +end + +-- Read the full hoodfile. Return nil for wrong format or no such file +function M.readHoodfile(file) + local jhood = io.open(file, 'r') + if not jhood then return nil end + local obj, _, err = json.parse (jhood:read('*a'), 1, nil) + if err then + return nil + else + return obj + end +end + +function M.mesh_on_wan_disable() + os.execute('ifdown mesh_wan') + io.stdout:write('Interface mesh_wan disabled.\n') +end + +function M.mesh_on_wan_enable() + os.execute('ifup mesh_wan') + io.stdout:write('Interface mesh_wan enabled.\n') +end + +function M.mesh_on_lan_disable() + os.execute('ifdown mesh_lan') + io.stdout:write('Interface mesh_lan disabled.\n') +end + +function M.mesh_on_lan_enable() + os.execute('ifup mesh_lan') + io.stdout:write('Interface mesh_lan enabled.\n') +end + +function M.fastd_installed() + if io.open("/usr/bin/fastd", 'r') ~= nil then + return true + end + return false +end + +function M.tunneldigger_installed() + if io.open("/usr/bin/tunneldigger", 'r') ~= nil then + return true + end + return false +end + +function M.filt_if(filt,str) + for _, c in ipairs(filt) do + -- check if - is contains because it is a magic character thus it musst be replace) + if c:gsub("%-", "0"):match(str:gsub("%-", "0")) then + return true + end + end + return false +end + +function M.filter_redundancy(list) + local flag = {} + local ret = {} + for _,element in ipairs(list) do + if not flag[element] then + ret[#ret+1] = element + flag[element] = true + end + end + return ret +end + +function M.trim(s) + -- from PiL2 19.4 + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + +function M.sleep(n) + os.execute("sleep " .. tonumber(n)) +end + +function M.brclient_restart() + os.execute('ifconfig br-client down') + os.execute('ifconfig br-client up') + io.stdout:write('Interface br-client restarted.\n') +end + +function M.vpn_stop() + -- check if fastd installed + if M.fastd_installed() then + if uci:get_bool('fastd','mesh_vpn','enabled') then + os.execute('/etc/init.d/fastd stop') + io.stdout:write('Fastd stoped.\n') + end + end + -- check if tunneldigger installed + if M.tunneldigger_installed() then + if uci:get_bool('tunneldigger','mesh_vpn','enabled') then + os.execute('/etc/init.d/tunneldigger stop') + io.stdout:write('Tunneldigger stoped.\n') + end + end +end + +function M.vpn_start() + if M.fastd_installed() then + if uci:get_bool('fastd','mesh_vpn','enabled') then + os.execute('/etc/init.d/fastd start') + io.stdout:write('Fastd started.\n') + end + end + if M.tunneldigger_installed() then + if uci:get_bool('tunneldigger','mesh_vpn','enabled') then + os.execute('/etc/init.d/tunneldigger start') + io.stdout:write('Tunneldigger started.\n') + end + end + M.brclient_restart() +end + +function M.vpn_disable() + if M.fastd_installed() then + if uci:get_bool('fastd','mesh_vpn','enabled') then + os.execute('/etc/init.d/fastd disable') + io.stdout:write('Fastd disabled.\n') + end + end + if M.tunneldigger_installed() then + if uci:get_bool('tunneldigger','mesh_vpn','enabled') then + os.execute('/etc/init.d/tunneldigger disable') + io.stdout:write('Tunneldigger disabled.\n') + end + end +end + +function M.vpn_enable() + if M.fastd_installed() then + if not uci:get_bool('fastd','mesh_vpn','enabled') then + os.execute('/etc/init.d/fastd enable') + io.stdout:write('Fastd enabled.\n') + end + end + if M.tunneldigger_installed() then + if not uci:get_bool('tunneldigger','mesh_vpn','enabled') then + os.execute('/etc/init.d/tunneldigger enable') + io.stdout:write('Tunneldigger enabled.\n') + end + end +end + +function M.wireless_restart() + os.execute('wifi') + io.stdout:write('Wireless restarted.\n') +end + +-- Get a list of wifi devices return an emty table for no divices +function M.getWifiDevices() + local radios = {} + uci:foreach('wireless', 'wifi-device', + function(s) + table.insert(radios, s['.name']) + end + ) + return radios +end + +-- get signal strength +function M.scan_filter_quality(scan_str, redirect) + local tmp_quality = redirect + if scan_str:match("signal:") then + tmp_quality = M.split(scan_str, " ") + tmp_quality = M.split(tmp_quality[2], "%.") + if tmp_quality[1]:match("-") then + tmp_quality = M.split(tmp_quality[1], "-") + end + tmp_quality = tonumber(tmp_quality[2]:match("(%d%d)")) + end + return tmp_quality +end + +-- get frequency +function M.scan_filter_frequency(scan_str, redirect) + local tmp_frequency = redirect + if scan_str:match("freq") then + tmp_frequency = M.split(scan_str, ":") + tmp_frequency = tmp_frequency[2] + if tmp_frequency ~= nil then + tmp_frequency = M.trim(tmp_frequency) + end + end + return tmp_frequency +end + +function M.ibss_scan(radio,ifname,ssid,networks) + 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(row["bssid"] ~= nil and row["quality"] ~= nil and row["ssid"] == ssid) then + table.insert(networks, row) + row = {} + row["radio"] = radio + end + + -- get ssid + if wifiscan:match("SSID:") then + row["ssid"] = M.split(wifiscan, ":") + row["ssid"] = row["ssid"][2] + if(row["ssid"] ~= nil) then + row["ssid"] = M.trim(row["ssid"]) + end + end + row["frequency"] = M.scan_filter_frequency(wifiscan, row["frequency"]) + + -- get bssid + if wifiscan:match("(%w+:%w+:%w+:%w+:%w+:%w+)") then + row["bssid"] = wifiscan:match("(%w+:%w+:%w+:%w+:%w+:%w+)") + end + row["quality"] = M.scan_filter_quality(wifiscan, row["quality"]) + end + return networks +end + +function M.mesh_scan(radio,ifname,meshid,networks) + 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(row["bssid"] ~= nil and row["quality"] ~= nil and row["ssid"] == meshid) then + table.insert(networks, row) + row = {} + row["radio"] = radio + end + row["frequency"] = M.scan_filter_frequency(wifiscan, row["frequency"]) + row["quality"] = M.scan_filter_quality(wifiscan, row["quality"]) + + -- get mesh-ID(ssid) and hoodbssid + if wifiscan:match("MESH ID:") then + local meshID = M.split(wifiscan, " ")[3] + if meshID ~= nil then + meshID = M.split(meshID, "_") + if meshID[1] ~= nil then + row["ssid"] = meshID[1].."_" + end + if meshID[2] ~= nil then + if meshID[2]:match("(%w+:%w+:%w+:%w+:%w+:%w+)") then + row["bssid"] = meshID[2]:match("(%w+:%w+:%w+:%w+:%w+:%w+)") + end + end + end + end + end + return networks +end + +-- Scans for wireless networks and returns a two dimensional array containing +-- wireless mesh neighbour 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) +function M.wlan_list_sorted(radios, mesh_prefix) + local networks = {} + for _, 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 + --do ibss scan + networks = M.ibss_scan(radio,ifname,ssid,networks) + end + ifname = uci:get('wireless', 'mesh_' .. radio, 'ifname') + if (ifname ~= nil and mesh_prefix ~= nil) then + --do mesh scan + networks = M.mesh_scan(radio,ifname,mesh_prefix,networks) + 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 +function M.filter_my_wlan_network(wlan_list) + for i=#wlan_list,1,-1 do + local bssid = uci:get('wireless', 'ibss_' .. wlan_list[i].radio, 'bssid') + if bssid ~= nil then + if string.lower(wlan_list[i].bssid) == string.lower(bssid) then + table.remove(wlan_list, i) + end + else + local mesh = uci:get('wireless', 'mesh_' .. wlan_list[i].radio, 'mesh_id') + if mesh ~= nil then + if string.lower(wlan_list[i].ssid..wlan_list[i].bssid) == string.lower(mesh) then + table.remove(wlan_list, i) + end + end + end + end + return wlan_list +end + +function M.filter_default_hood_wlan_networks(default_hood, wlan_list) + for i=#wlan_list,1,-1 do + if(string.lower(default_hood.bssid) == string.lower(wlan_list[i].bssid)) then + table.remove(wlan_list, i) + end + end + return wlan_list +end + +-- Get Geoposition. Return nil for no position +function M.getGeolocation() + return {["lat"] = tonumber(uci:get('gluon-node-info', uci:get_first('gluon-node-info', 'location'), 'latitude')), + ["lon"] = tonumber(uci:get('gluon-node-info', uci:get_first('gluon-node-info', 'location'), 'longitude')) } +end + +-- Source with pseudocode: https://de.wikipedia.org/wiki/Punkt-in-Polygon-Test_nach_Jordan +-- see also https://en.wikipedia.org/wiki/Point_in_polygon +-- parameters: points A = (x_A,y_A), B = (x_B,y_B), C = (x_C,y_C) +-- return value: −1 if the ray from A to the right bisects the edge [BC] (the lower vortex of [BC] +-- is not seen as part of [BC]); +-- 0 if A is on [BC]; +-- +1 else +function M.crossProdTest(x_A,y_A,x_B,y_B,x_C,y_C) + if y_A == y_B and y_B == y_C then + if (x_B <= x_A and x_A <= x_C) or (x_C <= x_A and x_A <= x_B) then + return 0 + end + return 1 + end + if not ((y_A == y_B) and (x_A == x_B)) then + if y_B > y_C then + -- swap B and C + local h = x_B + x_B = x_C + x_C = h + h = y_B + y_B = y_C + y_C = h + end + if (y_A <= y_B) or (y_A > y_C) then + return 1 + end + local Delta = (x_B-x_A) * (y_C-y_A) - (y_B-y_A) * (x_C-x_A) + if Delta > 0 then + return 1 + elseif Delta < 0 then + return -1 + end + end + return 0 +end + +-- Source with pseudocode: https://de.wikipedia.org/wiki/Punkt-in-Polygon-Test_nach_Jordan +-- see also: https://en.wikipedia.org/wiki/Point_in_polygon +-- let P be a 2D Polygon and Q a 2D Point +-- return value: +1 if Q within P; +-- −1 if Q outside of P; +-- 0 if Q on an edge of P +function M.pointInPolygon(poly, point) + local t = -1 + for i=1,#poly-1 do + t = t * M.crossProdTest(point.lon,point.lat,poly[i][2],poly[i][1],poly[i+1][2],poly[i+1][1]) + if t == 0 then break end + end + return t +end + +-- Return hood from the hood file based on geo position or nil if no real hood could be determined +-- First check if an area has > 2 points and is hence a polygon. Else assume it is a rectangular +-- box defined by two points (south-west and north-east) +function M.getHoodByGeo(jhood,geo) + for _, h in pairs(jhood) do + for _, area in pairs(h.boxes) do + if #area > 2 then + if (M.pointInPolygon(area,geo) == 1) then + return h + end + else + if ( geo.lat >= area[1][1] and geo.lat < area[2][1] and geo.lon >= area[1][2] and geo.lon < area[2][2] ) then + return h + end + end + end + end + return nil +end + +-- This method checks if the fastd 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 fastd configuration has a server that does not exist +-- in the hoodfile. +-- 2. Check if a server that does exist in the local fastd configuration AND +-- in the hoodfile has a configuration change. +-- 3. Check if the hoodfile contains a server that does not exist in the +-- local fastd configuration. +function M.fastd_reconfiguration_needed(hood_serverlist,local_serverlist) + + local tmp_hood_serverlist = {} + for _,hood_server in pairs(hood_serverlist) do + if (hood_server["type"] == "fastd") then + table.insert(tmp_hood_serverlist,hood_server) + end + end + + -- 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 in pairs(tmp_hood_serverlist) do + if (local_server_config_name == 'mesh_vpn_backbone_peer_'.. + M.split(hood_server["host"], '.')[1]:gsub("%-", "%_") .. "_" .. hood_server['port']) 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 in pairs(tmp_hood_serverlist) do + local hood_server_exists_locally = false + for local_server_config_name, _ in pairs(local_serverlist) do + if (local_server_config_name == 'mesh_vpn_backbone_peer_'.. + M.split(hood_server["host"], '.')[1]:gsub("%-", "%_") .. "_" .. hood_server['port']) then + hood_server_exists_locally = true + end + end + if not hood_server_exists_locally then return true end + end + return false +end + +function M.tunneldigger_reconfiguration_needed(hood_serverlist,local_serverlist) + + local tmp_hood_serverlist = {} + for _,hood_server in pairs(hood_serverlist) do + if (hood_server["type"] == "l2tp") then + table.insert(tmp_hood_serverlist,hood_server) + end + end + + -- Checks 1. and 2. + for _,local_peer in pairs(local_serverlist) do + local local_server_exists_in_hoodfile = false + for _,hood_server in pairs(tmp_hood_serverlist) do + if (local_peer == hood_server["host"] .. ":" .. hood_server["port"]) then + local_server_exists_in_hoodfile = true + end + end + if not local_server_exists_in_hoodfile then return true end + end + + -- Check 3. + for _,hood_server in pairs(tmp_hood_serverlist) do + local hood_server_exists_locally = false + for _,local_peer in pairs(local_serverlist) do + if (local_peer == hood_server["host"] .. ":" .. hood_server["port"]) then + hood_server_exists_locally = true + end + end + if not(hood_server_exists_locally) then return true end + end + return false +end + +-- Reconfigure fastd +function M.fastd_reconfigure(hood_serverlist,local_serverlist) + -- remove all servers + for config_index, _ in pairs(local_serverlist) do + uci:delete('fastd',config_index) + end + + -- add servers from hoodfile + local group = 'mesh_vpn_backbone' + for _,hood_server in pairs(hood_serverlist) do + if (hood_server["type"] == "fastd") then + uci:section('fastd', 'peer', group .. '_peer_' .. + M.split(hood_server.host, '.')[1]:gsub("%-", "%_") .. "_" .. hood_server.port, + { + enabled = 1, + net = 'mesh_vpn', + group = group, + key = hood_server.publickey, + remote = {'\"'..hood_server.host..'\"'..' port '..hood_server.port} + } + ) + end + end + + uci:save('fastd') + uci:commit('fastd') + io.stdout:write('Fastd needed reconfiguration. Stopped and applied new settings.\n') +end + +function M.tunneldigger_reconfigure(hood_serverlist) + -- remove all servers + uci:delete('tunneldigger', 'mesh_vpn', 'address') + + -- add servers from hoodfile + local addresslist = {} + for _,hood_server in pairs(hood_serverlist) do + if (hood_server["type"] == "l2tp") then + local tmpAdrr =hood_server["host"] .. ":" .. hood_server["port"] + table.insert(addresslist, tmpAdrr) + end + end + if next(addresslist) then + uci:set('tunneldigger', 'mesh_vpn', 'address', addresslist) + end + uci:save('tunneldigger') + uci:commit('tunneldigger') + io.stdout:write('tunneldigger 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. +function M.wireless_reconfiguration_needed(radios, prefix, hood_bssid) + local configure_ibss = false + local configure_mesh = false + for _, radio in ipairs(radios) do + if uci:get('wireless', 'ibss_' .. radio, 'ifname') ~= nil then + if ( uci:get('wireless', 'ibss_' .. radio, 'bssid') ~= hood_bssid ) then + configure_ibss = true + end + end + if uci:get('wireless', 'mesh_' .. radio, 'ifname') ~= nil then + if ( uci:get('wireless', 'mesh_' .. radio, 'mesh_id') ~= prefix..hood_bssid:lower() ) then + configure_mesh = true + end + end + end + if not configure_ibss and not configure_mesh then return 0 end --no changes + if configure_ibss and not configure_mesh then return 1 end --ibss changes + if not configure_ibss and configure_mesh then return 2 end --mesh changes + return 3 --bouth changes +end + +-- Reconfigure wireless +function M.ibss_reconfigure(radios, hood_bssid) + for _, 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 + +function M.mesh_reconfigure(radios, meshid) + for _, radio in ipairs(radios) do + if not ( uci:get('wireless', 'mesh_' .. radio, 'mesh_id') == meshid ) then + uci:section('wireless', 'wifi-iface', 'mesh_' .. radio, {mesh_id = meshid}) + end + end + uci:save('wireless') + uci:commit('wireless') +end + +-- Save selected hood to config to make a restore after a sysupgrade possible +function M.saveHoodToConfig(hoodbssid) + uci:set('hoodselector', 'hoodselector', 'hood', hoodbssid) + uci:save('hoodselector') + uci:commit('hoodselector') +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 +function M.getDefaultHood(jhood) + for _, h in pairs(jhood) do + if h.defaulthood then + return h + end + end + return nil +end + +-- boolean check if batman-adv has gateways +function M.batmanHasGateway() + local file = io.open("/sys/kernel/debug/batman_adv/bat0/gateways", 'r') + if file ~= nil then + for gw in file:lines() do + if gw:match("Bit") then + return true + end + end + end + return false +end + +-- Return hood from the hood file based on a given BSSID. nil if no matching hood could be found +function M.gethoodByBssid(jhood, scan_bssid) + for _, h in pairs(jhood) do + if scan_bssid:lower():match(h.bssid:lower()) then + return h + end + end + return nil +end + +-- Return hood from hood file based on a bssid address. nil if no matching hood could be found +function M.getCurrentHood(jhood) + local hoodbssid = uci:get('hoodselector', 'hoodselector', 'hood') + for _, h in pairs(jhood) do + if h.bssid == hoodbssid then + return h + end + end + return nil +end + +function M.test_batman_mesh_networks(sorted_wlan_list, meshprefix) + -- remove the ap network because we cannot change + -- the settings of the adhoc network if the ap network is still operating + for iface in io.popen(string.format("iw dev"),'r'):lines() do + if iface:match("Interface") then + iface = M.trim(M.split(iface, "Interface")[2]) + if not ( iface:match("ibss") or iface:match("mesh")) then + os.execute("iw dev "..iface.." del") + end + end + end + for _, wireless in pairs(sorted_wlan_list) do + local ibss = uci:get('wireless', 'ibss_' .. wireless["radio"], 'ifname') + local mesh = uci:get('wireless', 'mesh_' .. wireless["radio"], 'ifname') + if wireless["ssid"] == uci:get('wireless', 'ibss_' .. wireless["radio"], 'ssid') then + io.stdout:write("Testing IBSS "..wireless["bssid"].."...\n") + -- leave the current adhoc network + os.execute("iw dev "..ibss.." ibss leave 2> /dev/null") + if mesh ~= nil then + os.execute("iw dev "..mesh.." mesh leave 2> /dev/null") + end + -- setup the adhoc network we want to test + os.execute("iw dev "..ibss.." ibss join "..wireless["ssid"].." "..wireless["frequency"].." "..wireless["bssid"]) + end + if wireless["ssid"] == meshprefix then + io.stdout:write("Testing MESH "..wireless["bssid"].."...\n") + -- leave the current mesh network + os.execute("iw dev "..mesh.." mesh leave 2> /dev/null") + if ibss ~= nil then + os.execute("iw dev "..ibss.." ibss leave 2> /dev/null") + end + -- setup the mesh network we want to test + os.execute("iw dev "..mesh.." mesh join "..meshprefix..wireless["bssid"].." freq "..wireless["frequency"]) + end + -- sleep 30 seconds till the connection is fully setup + local c = 0; + while(not M.batmanHasGateway()) do + if(c >= 30) then break end + M.sleep(1) + c = c +1; + end + if c < 30 then + return wireless["bssid"] + end + end + return nil +end + +function M.get_batman_mesh_network(sorted_wlan_list, defaultHood, meshprefix) + io.stdout:write('Testing neighbouring adhoc networks for batman advanced gw connection.\n') + io.stdout:write('The following wireless networks have been found:\n') + for _, network in pairs(sorted_wlan_list) do + print(network["quality"].."\t"..network["frequency"].."\t"..network["bssid"].."\t"..network["ssid"]) + end + + -- 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 = M.filter_default_hood_wlan_networks(defaultHood, sorted_wlan_list) + + -- we dont want to test duplicated networks + sorted_wlan_list = M.filter_redundancy(sorted_wlan_list) + + -- we dont want to get tricked by our signal + sorted_wlan_list = M.filter_my_wlan_network(sorted_wlan_list) + + io.stdout:write('After filtering we will test the following wireless networks:\n') + for _, 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.stdout: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. + M.vpn_stop() + bssid = M.test_batman_mesh_networks(sorted_wlan_list, meshprefix) + M.vpn_start() + M.wireless_restart() + io.stdout:write("Finished testing wireless networks, restored previous configuration\n") + end + + return bssid +end + +function M.get_batman_GW_interface() + for gw in io.open("/sys/kernel/debug/batman_adv/bat0/gateways", 'r'):lines() do + if gw:match("=>") then + return M.trim(gw:match("%[.-%]"):gsub("%[", ""):gsub("%]", "")) + end + end + return nil +end + +function M.get_radio_to_bssid(radios,iface,jhood) + for _, radio in ipairs(radios) do + local ifname = uci:get('wireless', 'ibss_' .. radio, 'ifname') + if ifname == iface then + return M.gethoodByBssid(jhood, uci:get('wireless', 'ibss_' .. radio, 'bssid')) + end + ifname = uci:get('wireless', 'mesh_' .. radio, 'ifname') + if ifname == iface then + local meshid = uci:get('wireless', 'mesh_' .. radio, 'mesh_id') + meshid = M.split(meshid, '_') + if meshid[2] ~= nil then + if meshid[2]:match("(%w+:%w+:%w+:%w+:%w+:%w+)") then + return M.gethoodByBssid(jhood, meshid[2]:match("(%w+:%w+:%w+:%w+:%w+:%w+)")) + end + end + end + end + return nil +end + +function M.mesh_lan_wan(iface) + if uci:get('network', 'mesh_wan') then + local if_wan = io.popen(string.format("ifstatus mesh_wan"), 'r'):read('*a') + if if_wan ~= nil then + local wan, _, _ = json.parse(if_wan, 1, nil) + if wan["device"] == iface then + io.stdout:write("gw source is wan\n") + return true + end + end + end + if uci:get('network', 'mesh_lan') then + local if_lan = io.popen(string.format("ifstatus mesh_lan"), 'r'):read('*a') + if if_lan ~= nil then + local lan, _, _ = json.parse(if_lan, 1, nil) + if lan["device"] == iface then + io.stdout:write("gw source is lan\n") + return true + end + end + end + return false +end + +function M.conditional_increment(table,index) + if index["bssid"] ~= nil then + if not table[index["bssid"]] then + table[index["bssid"]] = 1 + else + table[index["bssid"]] = table[index["bssid"]] + 1 + end + end +end + +-- Get BSSID from neighbour hoods over respondd +function M.molw_get_bssid(iface) + -- differentiate between VPN and mesh only routers + local vpn_router_neighbour_bssids = {} + local mesh_router_neighbour_bssids = {} + local own_mac = uci:get('network', 'client', 'macaddr') + for _,respondd_if in ipairs(iface) do + local respondd = string.format("gluon-neighbour-info -i " .. + respondd_if .. " -p 1001 -d ff02::2 -r hoodselector -t 0.5") + print(respondd) + for line in io.popen(respondd, 'r'):lines() do + local obj, _, err = json.parse (line, 1, nil) + if err then + io.stdout:write("json parse error!\n") + else + if obj["mac"] ~= own_mac then + if obj["hoodinfo"] ~= nil then + if obj["hoodinfo"]["vpnrouter"] ~= nil then + if obj["hoodinfo"]["vpnrouter"]:match("true") then + if obj["mesh"] ~= nil then + M.conditional_increment(vpn_router_neighbour_bssids, obj["mesh"]) + end + else + if obj["mesh"] ~= nil then + M.conditional_increment(mesh_router_neighbour_bssids, obj["mesh"]) + end + end + end + end + end + end + end + end + local neighbour_bssids + if next(vpn_router_neighbour_bssids) then + -- molwm VPN router founds. + print("Found VPN routers over mesh on lan or wan") + neighbour_bssids = vpn_router_neighbour_bssids + else + -- molwm VPN router not founds. Get most presented bssid + print("No VPN routers over mesh on lan or wan") + neighbour_bssids = mesh_router_neighbour_bssids + end + + local bssid = nil + local value = 0 + for b,c in pairs(neighbour_bssids) do + io.stdout:write(b.."\t"..c.."\n") + if value < c then + bssid = b + value = c + end + end + local eq_cost_entrys = {} + for b,c in pairs(neighbour_bssids) do + if b == bssid then + table.insert(eq_cost_entrys,b) + end + if c == value and b ~= bssid then + table.insert(eq_cost_entrys,b) + end + end + if #eq_cost_entrys ~= 0 then + bssid = eq_cost_entrys[math.random(#eq_cost_entrys)] + end + return bssid +end + +function M.get_mesh_prefix(radios) + local mesh_prefix = nil + for _,radio in ipairs(radios) do + local mesh_id = uci:get('wireless', 'mesh_' .. radio, 'mesh_id') + if mesh_id ~= nil then + mesh_id = M.split(mesh_id, '_') + if mesh_id[1] ~= "" then + mesh_prefix = mesh_id[1].."_" + else + if mesh_id[2] ~= "" then + mesh_prefix = mesh_id[2].."_" + end + end + end + end + return mesh_prefix +end + +function M.extrac_fastd_peer(s,configPeers) + 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 = M.split(tmpRemote[1], " ") + 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 + +return M diff --git a/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector b/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector new file mode 100755 index 00000000..b74f0362 --- /dev/null +++ b/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector @@ -0,0 +1,622 @@ +#!/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 works with the folowing additional software: +-- * fastd (vpn configuration) see getCurrentFastdPeers() +-- * tunneldigger (vpn configuration) see getCurrentTunneldiggerPeers() +-- * iw (wireless network scanning) see get_batman_mesh_network() +-- * batman-adv (mesh protocol) see directVPN(), get_batman_GW_interface() +-- * respondd (mesh on LAN or WAN) see molwm() +-- +-- To detect the current hood the hoodselector knows 5 modes containing +-- * 1. VPN mode (VPN Router) +-- - set hood dependent on geo position. +-- * 2. Gateway mode +-- - set hood dependent on gateway source +-- * 3. Scan mode +-- - Set wifi conf on scanned BSSID +-- - Set vpn conf getting by BSSID (if no VPN conf exsist disable vpn) +-- * 4. Radio less mode +-- - Set hood via mesh on lan wan managemand +-- * 5. Default mode +-- - Set default hood if no other hood matchs + +-- 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 neighbour 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"] = "" +molwmtable["bssid"] = "" + +-- PID file to ensure the hoodselector isn't running parallel +local pidPath="/var/run/hoodselector.pid" + +local json = require ("luci.jsonc") +local uci = require('simple-uci').cursor() +local hash = require("hash") +local hoodutil = require("hoodselector.util") + +if not uci:get_bool('hoodselector', 'hoodselector', 'enabled') then + io.stdout:write("Hoodselector is disabled by UCI\n") + os.exit(0) +end + +if io.open(pidPath, "r") ~=nil then + hoodutil.log("The hoodselector is still running.") + os.exit(1) +else + if io.open(pidPath, "w") ==nil then + hoodutil.log("Can`t create pid file on "..pidPath) + os.exit(1) + end +end + +-- Program terminating function including removing of PID file +local function exit(exc) + if io.open(pidPath, "r") ~=nil then + os.remove(pidPath) + end + os.exit(tonumber(exc)) +end + +local FILE = uci:get('hoodselector', 'hoodselector', 'hoodfile') +if FILE == nil then + hoodutil.log("No hood file in config") + exit(1) +end + +-- initialization done + +local function get_mesh_vpn_interface() + local ret = {} + if hoodutil.fastd_installed() then + local vpnifac = uci:get('fastd', 'mesh_vpn_backbone', 'net') + if vpnifac ~= nil then + vpnifac = vpnifac:gsub("%_",'-') + table.insert(ret,vpnifac) + else + hoodutil.log("fastd uci config broken! abort...") + exit(1) + end + end + if hoodutil.tunneldigger_installed() then + local vpnifac = uci:get('tunneldigger', 'mesh_vpn', 'interface') + if vpnifac ~= nil then + table.insert(ret,vpnifac) + else + hoodutil.log("tunneldigger uci config broken! abort...") + exit(1) + end + end + return ret +end + +-- Give a list of interfaces where respondd is listening +local function get_mesh_if(radios) + -- get VPN interfaces + local filtert_if = get_mesh_vpn_interface() + + -- get Wifi interfaces + for _, radio in ipairs(radios) do + local ifname = uci:get('wireless', 'ibss_' .. radio, 'ifname') + if ifname ~= nil then + table.insert(filtert_if,ifname) + end + ifname = uci:get('wireless', 'mesh_' .. radio, 'ifname') + if ifname ~= nil then + table.insert(filtert_if,ifname) + end + end + + local respondd_if = {} + for line in io.popen(string.format("ubus call network.interface dump | jsonfilter -e \"@.interface[@.proto='gluon_mesh' && @.up=true].device\" -e \"@.interface[@.interface='$(cat /lib/gluon/respondd/client.dev 2>/dev/null)' && @.up=true].device\""), "r"):lines() do + -- filter vpn and wifi interfaces + if not hoodutil.filt_if(filtert_if,line) then + table.insert(respondd_if,line) + end + end + + return hoodutil.filter_redundancy(respondd_if) +end + +local function molwm(radios) + if uci:get_bool("network", "mesh_lan", "auto") or uci:get_bool("network", "mesh_wan", "auto") then + local mesh_en = true + for _,respondd_if in ipairs(get_mesh_if(radios)) do + local respondd = string.format("gluon-neighbour-info -i " .. respondd_if .. " -p 1001 -d ff02::2 -r hoodselector -t 0.5") + for line in io.popen(respondd, 'r'):lines() do + local obj, _, err = json.parse (line, 1, nil) + if err then + io.stdout:write("json parse error!\n") + mesh_en = false + break + else + if obj["hoodinfo"] ~= nil then + if obj["mesh"] ~= nil then + if ( obj["mesh"]["lan"] or obj["mesh"]["wan"] ) then + if not ( obj["hoodinfo"]["md5hash"] == molwmtable["md5hash"]:gsub('\"', '') ) then + io.stdout:write("hashes are not equal! Souce " .. respondd .. "\n") + mesh_en = false + break + end + end + end + end + end + end + end + if uci:get('network', 'mesh_wan') then + local ifstatus_wan = io.popen(string.format("ifstatus mesh_wan"), 'r'):read('*a') + if ifstatus_wan ~= nil then + local wan, _, _ = json.parse (ifstatus_wan, 1, nil) + if wan["up"] and not mesh_en then + hoodutil.mesh_on_wan_disable() + end + if not wan["up"] and mesh_en then + hoodutil.mesh_on_wan_enable() + end + end + end + if uci:get('network', 'mesh_lan') then + local ifstatus_lan = io.popen(string.format("ifstatus mesh_lan"), 'r'):read('*a') + if ifstatus_lan ~= nil then + local lan, _, _ = json.parse (ifstatus_lan, 1, nil) + if lan["up"] and not mesh_en then + hoodutil.mesh_on_lan_disable() + end + if not lan["up"] and mesh_en then + hoodutil.mesh_on_lan_enable() + end + end + end + end +end + +function table.val_to_str ( v ) + if "string" == type( v ) then + v = string.gsub( v, "\n", "\\n" ) + if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then + return "'" .. v .. "'" + end + return '"' .. string.gsub(v,'"', '\\"' ) .. '"' + else + return "table" == type( v ) and table.tostring( v ) or tostring( v ) + end +end + +function table.key_to_str ( k ) + if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then + return k + else + return "[" .. table.val_to_str( k ) .. "]" + end +end + +function table.tostring( tbl ) + local result, done = {}, {} + for k, v in ipairs( tbl ) do + table.insert( result, table.val_to_str( v ) ) + done[ k ] = true + end + for k, v in pairs( tbl ) do + if not done[ k ] then + table.insert( result, table.key_to_str( k ) .. "=" .. table.val_to_str( v ) ) + end + end + return "{" .. table.concat( result, "," ) .. "}" +end + +local function molwm_to_file() + local file = io.open(molwmFile, "w") + if not file then + hoodutil.log(molwmFile ..' not found or not createble!') + else + file:write("\"md5hash\": " .. molwmtable["md5hash"] .. "\n") + file:write("\"vpnrouter\": " .. molwmtable["vpnrouter"] .. "\n") + file:write("\"hoodname\": " .. molwmtable["hoodname"] .. "\n") + file:write("\"bssid\": " .. molwmtable["bssid"] .. "\n") + file:close() + end +end + +-- Write MOLWM content into file +local function write_molwm(hood,radios) + if hood ~= nil then + molwmtable["md5hash"] = "\"" .. string.format(hash.md5(table.tostring(hood))) .. "\"" + molwmtable["hoodname"] = "\"" .. hood["name"] .. "\"" + molwmtable["bssid"] = "\"" .. hood["bssid"] .. "\"" + end + molwm(radios) + molwm_to_file() +end + +-- bool if direct VPN. The detection is realaise by searching the fastd network interface inside the originator table +local function directVPN() + local vpnIfaceList = get_mesh_vpn_interface() + for _,vpnIface in ipairs(vpnIfaceList) do + local file = io.open("/sys/kernel/debug/batman_adv/bat0/originators", 'r') + if file ~= nil then + for outgoingIF in file:lines() do + -- escape special chars "[]-" + if outgoingIF:match(string.gsub("%[ " .. vpnIface .. "%]","%-", "%%-")) then + molwmtable["vpnrouter"] = "\"true\"" + return true + end + end + end + end + molwmtable["vpnrouter"] = "\"false\"" + return false +end + +-- Retun a table of current peers from /etc/config/fastd +local function getFastdCurrentPeers() + local configPeers = {} + local err = uci:foreach('fastd', 'peer', + function(s) + hoodutil.extrac_fastd_peer(s,configPeers) + end + ) + if not err then + hoodutil.log("fastd uci config broken! abort...") + exit(1) + end + return configPeers +end + +-- Retun a table of current peers from /etc/config/tunneldigger +local function getTunneldiggerCurrentPeers() + local configPeers = {} + local err = uci:foreach('tunneldigger', 'broker', + function(s) + if s["address"] then + for _, peer in pairs(s["address"]) do + table.insert(configPeers, peer) + end + end + end + ) + if not err then + hoodutil.log("tunneldigger uci config broken! abort...") + exit(1) + end + return configPeers +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 vpn needs reconfiguration and prepare reconfiguration +-- * If vpn needs reconfiguration, stop vpn and apply new settings but +-- dont restart it before wireless has been reconfigured +-- * If wireless needs reconfiguration apply new settings and restart wireless +-- * If vpn needed reconfiguration start fastd now +local function set_hoodconfig(hood, prefix, radios) + local fastd_serverlist = {} + local fastd_reconf_needed = false + local tunneldigger_reconf_needed = false + + -- pre check if fastd conf exsist + if uci:get('fastd', 'mesh_vpn_backbone', 'net') ~= nil then + fastd_serverlist = getFastdCurrentPeers() + -- Check if fastd needs reconfiguration because in case of reconfiguration we + -- need to stop fastd before we can reconfigure any other connection. + fastd_reconf_needed = hoodutil.fastd_reconfiguration_needed(hood["servers"], fastd_serverlist); + else + -- check if fastd uci conf broken + if hoodutil.fastd_installed() then + hoodutil.log("fastd uci config broken! abort...") + exit(1) + end + end + + -- per check if tunneldigger conf exsist + if uci:get('tunneldigger', 'mesh_vpn', 'interface') ~= nil then + local tunneldigger_serverlist = getTunneldiggerCurrentPeers() + tunneldigger_reconf_needed = hoodutil.tunneldigger_reconfiguration_needed(hood["servers"], tunneldigger_serverlist) + else + -- check if tunneldigger uci conf broken + if hoodutil.tunneldigger_installed() then + hoodutil.log("tunneldigger uci config broken! abort...") + exit(1) + end + end + + if fastd_reconf_needed or tunneldigger_reconf_needed then + hoodutil.vpn_stop() + end + + -- reconfigure wireless + local wifi_reconf = hoodutil.wireless_reconfiguration_needed(radios, prefix, hood["bssid"]) + if(wifi_reconf == 1 or wifi_reconf == 3) then + hoodutil.ibss_reconfigure(radios, hood["bssid"]) + hoodutil.wireless_restart() + io.stdout:write('IBSS needed reconfiguration. Applied new settings and restarted.\n') + end + if(wifi_reconf == 2 or wifi_reconf == 3) then + hoodutil.mesh_reconfigure(radios, prefix..hood["bssid"]:lower()) + hoodutil.wireless_restart() + io.stdout:write('MESH needed reconfiguration. Applied new settings and restarted.\n') + end + + -- reconfigure fastd + if fastd_reconf_needed then + hoodutil.fastd_reconfigure(hood["servers"],fastd_serverlist) + io.stdout:write('fastd needed reconfiguration. Applied new settings and restarted.\n') + end + + if tunneldigger_reconf_needed then + hoodutil.tunneldigger_reconfigure(hood["servers"]) + io.stdout:write('tunneldigger needed reconfiguration. Applied new settings and restarted.\n') + end + + if fastd_reconf_needed or tunneldigger_reconf_needed then + -- scan mode can disable VPN so we need to make shure that VPN is enabled + -- if the router selects a hood + hoodutil.vpn_enable() + hoodutil.vpn_start() + end + + io.stdout:write("Set hood \""..hood["name"].."\"\n") + molwmtable["hoodname"] = "\"" .. hood["name"] .. "\"" + + if(uci:get('hoodselector', 'hoodselector' , 'hood') ~= hood["bssid"]) then + hoodutil.saveHoodToConfig(hood["bssid"]) + end + return true +end + +-- INITIALIZE AND PREPARE DATA -- +-- read hoodfile, exit if reading the hoodfile fails +local jhood = hoodutil.readHoodfile(FILE) +if jhood == nil then + hoodutil.log('There seems to have gone something wrong while reading hoodfile from ' .. FILE) + exit(1) +end + +-- check if a default hood has been defined and exit if none has been defined +local defaultHood = hoodutil.getDefaultHood(jhood) +if defaultHood == nil then + hoodutil.log('No defaulthood defined.') + exit(1) +end + +-- Get list of wifi devices +local radios = hoodutil.getWifiDevices() + +local mesh_prefix = hoodutil.get_mesh_prefix(radios) + +-- 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.stdout:write('VPN connection found.\n') + local geo = hoodutil.getGeolocation() + if geo.lat ~= nil and geo.lon ~= nil then + io.stdout:write('Position found.\n') + local geoHood = hoodutil.getHoodByGeo(jhood, geo) + if geoHood ~= nil then + set_hoodconfig(geoHood, mesh_prefix, radios) + io.stdout:write('Hood set by VPN mode.\n') + write_molwm(geoHood,radios) + exit(0) + end + io.stdout:write('No hood has been defined for current position.\n') + else + io.stdout:write('No position found\n') + end + set_hoodconfig(defaultHood, mesh_prefix, radios) + io.stdout:write('Set defaulthood.\n') + write_molwm(defaultHood,radios) + exit(0) +else + io.stdout:write('No VPN connection found\n') +end + +if hoodutil.batmanHasGateway() then + io.stdout:write('Batman gateways found\n') + local gw_intf = {} + table.insert(gw_intf,hoodutil.get_batman_GW_interface()) + + if next(gw_intf) then + -- wifi interface + local bssidHood = hoodutil.get_radio_to_bssid(radios,gw_intf[1], jhood) + if bssidHood ~= nil then + --check if hood inside geo pos + local geo = hoodutil.getGeolocation() + if geo.lat ~= nil and geo.lon ~= nil then + io.stdout:write('Position found.\n') + local geoHood = hoodutil.getHoodByGeo(jhood, geo) + if geoHood ~= nil then + if string.format(hash.md5(table.tostring(bssidHood))) ~= string.format(hash.md5(table.tostring(geoHood))) then + io.stdout:write('Geo hood and bssid hood are not equal, do wifi scan...\n') + local sortedWlanList = hoodutil.wlan_list_sorted(radios, mesh_prefix) + for i=#sortedWlanList,1,-1 do + if(string.lower(geoHood.bssid) ~= string.lower(sortedWlanList[i].bssid)) then + table.remove(sortedWlanList, i) + end + end + if next(sortedWlanList) then + io.stdout:write('Try to switch back in our real hood!\n') + io.stdout:write('After filtering we will test the following wireless networks:\n') + for _, network in pairs(sortedWlanList) do + print(network["quality"].."\t"..network["frequency"].."\t"..network["bssid"].."\t"..network["ssid"]) + end + io.stdout:write("Prepare configuration for testing wireless networks...\n") + local bssid = hoodutil.test_batman_mesh_networks(sortedWlanList, mesh_prefix) + hoodutil.wireless_restart() + io.stdout:write("Finished testing wireless networks, restored previous configuration\n") + if bssid ~= nil then + set_hoodconfig(geoHood, mesh_prefix, radios) + hoodutil.vpn_enable() + hoodutil.vpn_start() + io.stdout:write('Set Geo Hood by Gateway mode\n') + write_molwm(geoHood, radios) + exit(0) + else + io.stdout:write('No neighboring freifunk batman advanced mesh found.\n') + end + else + io.stdout:write('No networks left after filtering!\n') + end --end next(sortedWlanList) + else + io.stdout:write('Geo hood are equal to bssid hood no wifi scan necessary.\n') + end --end bssidHood ~= geoHood + else + io.stdout:write('No hood has been defined for current position.\n') + end --end geoHood ~= nil + else + io.stdout:write('No position found.\n') + end --end geo.lat ~= nil and geo.lon ~= nil + set_hoodconfig(bssidHood, mesh_prefix, radios) + io.stdout:write('Hood set by batmanHasGateway mode, GW source is wifi\n') + write_molwm(bssidHood,radios) + exit(0) + end --end bssidHood ~= nil + + -- mesh lan or wan interface + if hoodutil.mesh_lan_wan(gw_intf[1]) then + -- if mesh_lan/wan try to get hood by selected bssid of neightbour vpnRouters + local neighbourBssid = hoodutil.molw_get_bssid(gw_intf) + if neighbourBssid ~= nil then + bssidHood = hoodutil.gethoodByBssid(jhood, neighbourBssid) + if bssidHood ~= nil then + set_hoodconfig(bssidHood, mesh_prefix, radios) + io.stdout:write('Hood set by batmanHasGateway mode, GW source is mesh on lan/wan\n') + molwmtable["md5hash"] = "\"" .. string.format(hash.md5(table.tostring(bssidHood))) .. "\"" + molwmtable["hoodname"] = "\"" .. bssidHood["name"] .. "\"" + molwmtable["bssid"] = "\"" .. bssidHood["bssid"] .. "\"" + molwm_to_file() + exit(0) + end + end + end + end --end next(gw_intf) + local currendHood = hoodutil.getCurrentHood(jhood) + if currendHood ~= nil then + write_molwm(currendHood,radios) + end +end + +-- SCAN MODE +if next(radios) then + -- check if there exist a neighbouring freifunk batman advanced mesh + -- network with an active connection to a batman advanced gateway + local sortedWlanList = hoodutil.wlan_list_sorted(radios, mesh_prefix) + local meshBSSID = hoodutil.get_batman_mesh_network(sortedWlanList, defaultHood, mesh_prefix) + if meshBSSID ~= nil then + io.stdout:write("Neighoring freifunk batman advanced mesh with BSSID "..meshBSSID.." found\n") + local bssidHood = hoodutil.gethoodByBssid(jhood, meshBSSID) + if bssidHood ~= nil then + set_hoodconfig(bssidHood, mesh_prefix, radios) + io.stdout:write('Hood set by scan mode\n') + write_molwm(bssidHood,radios) + exit(0) + 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 + hoodutil.vpn_stop() + hoodutil.vpn_disable() + local ibss_exists = false + local mesh_exists = false + for _,radio in ipairs(radios) do + local ifname = uci:get('wireless', 'ibss_' .. radio, 'ifname') + if (ifname ~= nil) then + ibss_exists = true + end + ifname = uci:get('wireless', 'mesh_' .. radio, 'ifname') + if (ifname ~= nil) then + mesh_exists = true + end + end + if ibss_exists then + hoodutil.ibss_reconfigure(radios, meshBSSID) + end + if mesh_exists then + hoodutil.mesh_reconfigure(radios, mesh_prefix..meshBSSID:lower()) + end + hoodutil.wireless_restart() + io.stdout:write('Could not select a hood but established a connection via wireless mesh.\n') + io.stdout:write('Disabled all connections except connections via wireless mesh.\n') + exit(0) + end + io.stdout:write('No neighbouring freifunk batman advanced mesh found.\n') +end + +--Radio less router have mesh lan/wan neighbours +local mesh_inf = get_mesh_if(radios); +if next(mesh_inf) then + local wait_for_mesh_lan_wan = false + if uci:get('network', 'mesh_wan') then + local ifstatus_wan = io.popen(string.format("ifstatus mesh_wan"), 'r'):read('*a') + if ifstatus_wan ~= nil then + local wan, _, _ = json.parse (ifstatus_wan, 1, nil) + if not wan["up"] and uci:get_bool("network", "mesh_wan", "auto") then + hoodutil.mesh_on_wan_enable() + wait_for_mesh_lan_wan = true + end + end + end + if uci:get('network', 'mesh_lan') then + local ifstatus_lan = io.popen(string.format("ifstatus mesh_lan"), 'r'):read('*a') + if ifstatus_lan ~= nil then + local lan, _, _ = json.parse (ifstatus_lan, 1, nil) + if not lan["up"] and uci:get_bool("network", "mesh_lan", "auto") then + hoodutil.mesh_on_lan_enable() + wait_for_mesh_lan_wan = true + end + end + end + -- wait for network delay + if wait_for_mesh_lan_wan then + hoodutil.sleep(5) + end + local neighbourBssid = hoodutil.molw_get_bssid(mesh_inf) + if neighbourBssid ~= nil then + local bssidHood = hoodutil.gethoodByBssid(jhood, neighbourBssid) + if bssidHood ~= nil then + set_hoodconfig(bssidHood, mesh_prefix, radios) + io.stdout:write('Hood set by "Radio less router have mesh lan/wan neighbours"\n') + molwmtable["md5hash"] = "\"" .. string.format(hash.md5(table.tostring(bssidHood) )) .. "\"" + molwmtable["hoodname"] = "\"" .. bssidHood["name"] .. "\"" + molwmtable["bssid"] = "\"" .. bssidHood["bssid"] .. "\"" + molwm_to_file() + exit(0) + end + end + io.stdout:write('No molwm neighbours 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 if no current hood set. +io.stdout:write("ENV does not give enough information set default hood\n") +set_hoodconfig(defaultHood, mesh_prefix, radios) +io.stdout:write('Set defaulthood.\n') +write_molwm(defaultHood,radios) +exit(0) diff --git a/package/gluon-hoodselector/src/Makefile b/package/gluon-hoodselector/src/Makefile new file mode 100644 index 00000000..3ddc8a58 --- /dev/null +++ b/package/gluon-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 diff --git a/package/gluon-hoodselector/src/respondd.c b/package/gluon-hoodselector/src/respondd.c new file mode 100644 index 00000000..cc864a1c --- /dev/null +++ b/package/gluon-hoodselector/src/respondd.c @@ -0,0 +1,139 @@ +#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 uci mesh on lan wan +static struct json_object * get_mesh_on_lan_wan(void){ + struct uci_context *ctx = uci_alloc_context(); + ctx->flags &= ~UCI_FLAG_STRICT; + struct uci_package *p; + + if (!uci_load(ctx, "wireless", &p)) { + 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((char*)bssid); + if(ret) { + uci_free_context(ctx); + return ret; + } + } + } + uci_free_context(ctx); + 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) { + //Get bssid from current selected hood. + if (strstw("\"bssid\": ",line)) { + if (!strrmbs(line, 10, 12)) + continue; + json_object_object_add(ret, "bssid", gluonutil_wrap_string(line)); + free(line); + fclose(f); + return ret; + } + } + free(line); + fclose(f); + 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 *mesh_on_lan_wan = get_mesh_on_lan_wan(); + if(mesh_on_lan_wan) + json_object_object_add(ret, "mesh", mesh_on_lan_wan); + + json_object_object_add(ret, "mac", gluonutil_wrap_and_free_string(gluonutil_get_sysconfig("primary_mac"))); + return ret; +} + +// related to respondd_provider_hoodselector +const struct respondd_provider_info respondd_providers[] = { + {"hoodselector", respondd_provider_hoodselector}, + {} +}; -- 2.15.1