add gluon-hoodselector: Integrate VPN Mode

This MR includs only the VPN MODE of the hoodselector whitch simply set
hoods base on their geopositions.

Signed-off-by: Jan-Tarek Butt <tarek@ring0.de>
This commit is contained in:
Jan-Tarek Butt 2018-05-12 17:48:32 +02:00
parent e81d1a390f
commit 7f42ea3a72
5 changed files with 355 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,192 @@
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
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
-- 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.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
-- 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
-- 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].lon,poly[i].lat,poly[i+1].lon,poly[i+1].lat)
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].lat and geo.lat < area[2].lat and geo.lon >= area[1].lon
and geo.lon < area[2].lon ) 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
function M.restart_services()
local procTBL = {
"fastd",
"tunneldigger",
"network",
"gluon-respondd",
}
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,95 @@
#!/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 continure to next 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
-- The hoodselector should continure with next states because thier can be other
-- VPN routers in the local mesh network which provides a possition and therfore
-- have set a geo base hood.
io.stdout:write('No position found\n')
end
else
io.stdout:write('No VPN connection found\n')
end
exit(0)