gluon-hoodselector: add VPN-state and checksite

Signed-off-by: Jan-Tarek Butt <tarek@ring0.de>
This commit is contained in:
Jan-Tarek Butt 2018-04-23 12:19:35 +02:00
parent 5bf160040e
commit 95566316cb
5 changed files with 441 additions and 0 deletions

View File

@ -0,0 +1,30 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=gluon-hoodselector
GLUON_VERSION = 3
PKG_VERSION:=2
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include ../gluon.mk
define Package/gluon-hoodselector
SECTION:=gluon
CATEGORY:=Gluon
TITLE:=Automatically sorte routers into network hoods.
DEPENDS:=+gluon-site +gluon-mesh-batman-adv-15 @GLUON_MULTIDOMAIN
CONFLICTS:=+gluon-config-mode-domain-select
endef
define Package/gluon-hoodselector/description
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.
endef
$(eval $(call BuildPackageGluon,gluon-hoodselector))

View File

@ -0,0 +1,37 @@
need_string_match(in_domain({'domain_seed'}), '^' .. ('%x'):rep(64) .. '$')
function need_nil(path)
need(path, function(val)
if val == nil then
return true
end
return false
end, true, "default hood should not contain shapes")
return nil
end
--Need to check if not default hood and does the default not contain shapes
if this_domain() ~= need_string(in_site({'default_domain'})) then
local no_shapes = true
for _,shape in ipairs(need_table(in_domain({'hoodselector', 'shapes'}))) do
no_shapes = false
if #shape >= 2 then
for _, pos in ipairs(shape) do
if pos.lat == nil or not ( pos.lat < 90.0 and pos.lat > -90.0 ) then
need(in_domain({'hoodselector', 'shapes'}), function(err) return false end, true, "lat muss match a rage +/-90.0")
end
if pos.lon == nil or not ( pos.lon < 180.0 and pos.lon > -180.0 ) then
need(in_domain({'hoodselector', 'shapes'}), function(err) return false end, true, "lon muss match a rage +/-180.0")
end
end
end
if #shape < 2 then
need(in_domain({'hoodselector', 'shapes'}), function(err) return false end, true, "needs to have at lease 2 coordiantes for rectangular shapes.")
end
end
if no_shapes then
need(in_domain({'hoodselector', 'shapes'}), function(err) return false end, true, "no shapes are defined in hoods")
end
else -- ente by default hood
need_nil(in_domain({'hoodselector', 'shapes'}))
end

View File

@ -0,0 +1 @@
*/2 * * * * /usr/sbin/hoodselector

View File

@ -0,0 +1,191 @@
local fs = require 'nixio.fs'
local json = require 'jsonc'
local uci = require('simple-uci').cursor()
local site = require 'gluon.site'
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
-- bool if direct VPN. The detection is realaise by searching the fastd network interface inside the originator table
function M.directVPN(vpnIfaceList)
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
return true
end
end
end
end
return false
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
-- 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
function M.get_domains()
local list = {}
for domain_path in fs.glob('/lib/gluon/domains/*.json') do
table.insert(list, {
domain_code = domain_path:match('([^/]+)%.json$'),
domain = assert(json.load(domain_path)),
})
end
return list
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 _, hood in pairs(jhood) do
if hood.domain_code ~= site.default_domain() then
for _, area in pairs(hood.domain.hoodselector.shapes) do
if #area > 2 then
if (M.pointInPolygon(area,geo) == 1) then
return hood
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 hood
end
end
end
end
end
return nil
end
function M.set_hoodconfig(geoHood)
if uci:get('gluon', 'core', 'domain') ~= geoHood.domain_code then
uci:set('gluon', 'core', 'domain', geoHood.domain_code)
uci:save('gluon')
uci:commit('gluon') -- necessary?
os.execute('gluon-reconfigure')
io.stdout:write("Set hood \""..geoHood.domain.domain_names[geoHood.domain_code].."\"\n")
return true
end
return false
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.domain_code == site.default_domain() then
return h
end
end
return nil
end
function M.restart_services()
local procTBL = {
"fastd",
"tunneldigger",
"network",
}
for proc in ipairs(procTBL) do
if io.open("/etc/init.d/"..proc, 'r') ~= nil then
print(proc.." restarting ...")
os.execute("/etc/init.d/"..proc.." restart")
end
end
if io.open("/etc/config/wireless", 'r') then
print("wifi restarting ...")
os.execute("wifi")
end
end
return M

View File

@ -0,0 +1,182 @@
#!/usr/bin/lua
-- PID file to ensure the hoodselector isn't running parallel
local pidPath="/var/run/hoodselector.pid"
local uci = require('simple-uci').cursor()
local hoodutil = require("hoodselector.util")
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
-- 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
-- INITIALIZE AND PREPARE DATA --
-- read hoodfile...
local jhood = hoodutil.get_domains()
-- get defaul hood
local defaultHood = hoodutil.getDefaultHood(jhood)
-- 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 hoodutil.directVPN(get_mesh_vpn_interface()) 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
if hoodutil.set_hoodconfig(geoHood) then
hoodutil.restart_services() -- TMP solution
io.stdout:write('Hood set by VPN mode.\n')
end
exit(0)
end
io.stdout:write('No hood has been defined for current position.\n')
if hoodutil.set_hoodconfig(defaultHood) then
hoodutil.restart_services() -- TMP solution
io.stdout:write('Hood set by VPN mode.\n')
end
exit(0)
else
io.stdout:write('No position found\n')
end
else
io.stdout:write('No VPN connection found\n')
end
--[[
if hoodutil.batmanHasGateway() then
441 io.stdout:write('Batman gateways found\n')
442 local gw_intf = {}
443 table.insert(gw_intf,hoodutil.get_batman_GW_interface())
444
445 if next(gw_intf) then
446 -- wifi interface
447 local bssidHood = hoodutil.get_radio_to_bssid(radios,gw_intf[1], jhood)
448 if bssidHood ~= nil then
449 --check if hood inside geo pos
450 local geo = hoodutil.getGeolocation()
451 if geo.lat ~= nil and geo.lon ~= nil then
452 io.stdout:write('Position found.\n')
453 local geoHood = hoodutil.getHoodByGeo(jhood, geo)
454 if geoHood ~= nil then
455 if string.format(hash.md5(table.tostring(bssidHood))) ~=
string.format(hash.md5(table.tostring(geoHood))) then
456 io.stdout:write('Geo hood and bssid hood are not equal, do wifi scan...\n')
457 local sortedWlanList = hoodutil.wlan_list_sorted(radios, mesh_prefix)
458 for i=#sortedWlanList,1,-1 do
459 if(string.lower(geoHood.bssid) ~= string.lower(sortedWlanList[i].bssid)) then
460 table.remove(sortedWlanList, i)
461 end
462 end
463 if next(sortedWlanList) then
464 io.stdout:write('Try to switch back in our real hood!\n')
465 io.stdout:write('After filtering we will test the following wireless networks:\n')
466 for _, network in pairs(sortedWlanList) do
467 print(network["quality"].."\t"..network["frequency"].."\t"..network["bssid"].."\t"..network["ssid"])
468 end
469 io.stdout:write("Prepare configuration for testing wireless networks...\n")
470 local bssid = hoodutil.test_batman_mesh_networks(sortedWlanList, mesh_prefix)
471 hoodutil.wireless_restart()
472 io.stdout:write("Finished testing wireless networks, restored previous configuration\n")
473 if bssid ~= nil then
474 set_hoodconfig(geoHood, mesh_prefix, radios)
475 hoodutil.vpn_enable()
476 hoodutil.vpn_start()
477 io.stdout:write('Set Geo Hood by Gateway mode\n')
478 write_molwm(geoHood, radios)
479 exit(0)
480 else
481 io.stdout:write('No neighboring freifunk batman advanced mesh found.\n')
482 end
483 else
484 io.stdout:write('No networks left after filtering!\n')
485 end --end next(sortedWlanList)
486 else
487 io.stdout:write('Geo hood are equal to bssid hood no wifi scan necessary.\n')
488 end --end bssidHood ~= geoHood
489 else
490 io.stdout:write('No hood has been defined for current position.\n')
491 end --end geoHood ~= nil
492 else
493 io.stdout:write('No position found.\n')
494 end --end geo.lat ~= nil and geo.lon ~= nil
495 set_hoodconfig(bssidHood, mesh_prefix, radios)
496 io.stdout:write('Hood set by batmanHasGateway mode, GW source is wifi\n')
497 write_molwm(bssidHood,radios)
498 exit(0)
499 end --end bssidHood ~= nil
500
501 -- mesh lan or wan interface
502 if hoodutil.mesh_lan_wan(gw_intf[1]) then
503 -- if mesh_lan/wan try to get hood by selected bssid of neightbour vpnRouters
504 local neighbourBssid = hoodutil.molw_get_bssid(gw_intf)
505 if neighbourBssid ~= nil then
506 bssidHood = hoodutil.gethoodByBssid(jhood, neighbourBssid)
507 if bssidHood ~= nil then
508 set_hoodconfig(bssidHood, mesh_prefix, radios)
509 io.stdout:write('Hood set by batmanHasGateway mode, GW source is mesh on lan/wan\n')
510 molwmtable["md5hash"] = "\"" .. string.format(hash.md5(table.tostring(bssidHood))) .. "\""
511 molwmtable["hoodname"] = "\"" .. bssidHood["name"] .. "\""
512 molwmtable["bssid"] = "\"" .. bssidHood["bssid"] .. "\""
513 molwm_to_file()
514 exit(0)
515 end
516 end
517 end
518 end --end next(gw_intf)
519 local currendHood = hoodutil.getCurrentHood(jhood)
520 if currendHood ~= nil then
521 write_molwm(currendHood,radios)
522 end
523 end
--]]
exit(0) -- Debug