gluon-check-connection: initial commit

This commit adds a new package which can be used for scheduled
connectivity checks.
This commit is contained in:
CodeFetch 2020-12-29 02:24:22 +01:00
parent c97be1e18a
commit 9e5775865c
9 changed files with 269 additions and 1 deletions

View File

@ -59,6 +59,7 @@ Several Freifunk communities in Germany use Gluon as the foundation of their Fre
:caption: Packages :caption: Packages
:maxdepth: 1 :maxdepth: 1
package/gluon-check-connection
package/gluon-client-bridge package/gluon-client-bridge
package/gluon-config-mode-domain-select package/gluon-config-mode-domain-select
package/gluon-ebtables-filter-multicast package/gluon-ebtables-filter-multicast

View File

@ -0,0 +1,69 @@
gluon-check-connection
======================
This package adds a script that checks if at least one connection to IPv6 hosts
defined as *target groups* is working using the ping command.
The script is called once every minute by ``micrond``.
For example one can define a group of *local* targets to check if a connection
to hosts in the mesh network is possible (e.g. time or update servers) and
*global* targets for checking if a connection to the global internet is possible.
Currently only IPv6 addresses are supported.
This is e.g. used by the *gluon-scheduled-domain-switch* package
site.conf
---------
Target groups can be pre-defined in the domain config.
::
check_connection = {
targets = {
targets_local = {
'fe80::dead:c0de:1',
'fe80::bad:c0de:1',
'fe80::dead:c0de:2',
'fe80::bad:c0de:2',
},
targets_global = {
'2620:0:ccc::2', -- OpenDNS
'2001:4860:4860::8888', -- Google DNS
'2600::1', -- Sprint DNS
'2620:0:ccd::2', -- OpenDNS
'2001:4860:4860::8844', -- Google DNS
'2600::2', -- Sprint DNS
},
},
},
Defining target groups in the site.conf will overwrite existing ones with the same
name when performing a *sysupgrade* or by triggering *gluon-reconfigure*.
Configuration via UCI
---------------------
Packages can use gluon-check-connection to be triggered after connection checks.
For this they can define the following *script* attributes:
script : an entry for defining the ping target
enabled :
- a boolean defining whether the target will be considered
interval :
- the interval to execute the trigger script (in minutes - defaults to 1)
command :
- the command to execute
groups :
- the target groups array on which the ping test will be performed on
onchange :
- if set true the command is only being executed on a state change or always otherwise
trigger :
- on which the command is being executed (``offline``, ``online`` or unset for both)
*target* groups can be defined with the following attributes:
target : an entry for defining the IPv6 adress to ping
hosts :
- array containing the IPv6 addresses to perform the ping test on

View File

@ -516,7 +516,6 @@ config_mode \: optional
}, },
}, },
roles \: optional roles \: optional
Optional role definitions. Nodes will announce their role inside the mesh. Optional role definitions. Nodes will announce their role inside the mesh.
This will allow in the backend to distinguish between normal, backbone and This will allow in the backend to distinguish between normal, backbone and

View File

@ -0,0 +1,21 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=gluon-check-connection
PKG_VERSION:=1
include ../gluon.mk
define Package/$(PKG_NAME)
TITLE:=Checks if a node can ping definable targets
DEPENDS:=+gluon-core +micrond @GLUON_MULTIDOMAIN
endef
define Package/$(PKG_NAME)/description
Script to check if there is a connection to any gateway and also if there is
a connection to the global internet. This script is called once every minute
by ``micrond``. It will trigger scripts which will be executed if a
connection state changes or after a definable interval after which
connections checks have been performed.
endef
$(eval $(call BuildPackageGluon,$(PKG_NAME)))

View File

@ -0,0 +1,5 @@
local function check_target(t)
need_string_array_match(t, '^[%x:]+$', false)
end
need_table(in_domain({'check_connection', 'targets'}), check_target, false)

View File

@ -0,0 +1 @@
* * * * * /usr/sbin/gluon-check-connection

View File

@ -0,0 +1,14 @@
#!/usr/bin/lua
local site = require 'gluon.site'
local uci = require('simple-uci').cursor()
for group, hosts in pairs(site.check_connection.targets()) do
uci:delete('gluon-check-connection', 'target', group)
uci:section('gluon-check-connection', 'target', group, {
hosts = hosts
})
end
uci:save('gluon-check-connection')

View File

@ -0,0 +1,158 @@
#!/usr/bin/lua
local unistd = require 'posix.unistd'
local util = require 'gluon.util'
local uci = require('simple-uci').cursor()
-- Minimal uptime (in minutes) before the checks start
local min_uptime = 5
local offline_flag_file_prefix = '/tmp/gluon-offline-'
local firstrun_file = '/tmp/gluon-check-connection-firstrun'
local lastrun_file = '/tmp/gluon-check-connection-lastrun'
local function lock(file)
exec('lock', file)
end
local function unlock(file)
exec('lock', '-u', file)
end
local function shuffle(tbl)
new_tbl = {}
for i, ele in ipairs(tbl) do
table.insert(new_tbl, math.random(1, #new_tbl+1), ele)
end
return new_tbl
end
local function ping_hosts(hosts)
for _, host in ipairs(hosts) do
if 0 == os.execute("ping -c 1 -w 10 " .. host) then
return true
end
end
return false
end
local function check_connection(group, old_state, hosts)
local offline_flag_file = offline_flag_file_prefix .. group
local targets = shuffle(hosts)
if ping_hosts(targets) then
if not old_state then
util.log(group .. 'connectivity available again')
os.remove(offline_flag_file)
end
return true
end
if old_state then
util.log(group .. ' connectivity lost')
io.open(offline_flag_file, "w"):write(tostring(util.get_uptime()))
end
return false
end
local uptime = math.floor(util.get_uptime() / 60)
if uptime < min_uptime then
os.exit(0)
end
if not lock('/var/lock/gluon-check-connection.lock') then
util.log('Unable to set lock. Is an old instance still running?')
os.exit(0)
end
math.randomseed(uptime)
local firstrun = uptime
local lastrun = uptime
if unistd.access(firstrun_file) and unistd.access(lastrun_file) then
firstrun = tonumber(util.readfile(firstrun_file))
lastrun = tonumber(util.readfile(lastrun_file))
else
io.open(firstrun_file, "w"):write(tostring(uptime))
end
local runtime = lastrun - firstrun
local scripts = {}
uci:foreach('gluon-check-connection', 'script', function(script)
if not script['enabled'] then return end
if not runtime or uptime - lastrun >= (tonumber(script['interval']) or 1) then
table.insert(scripts, script)
end
end)
local groups = {}
uci:foreach('gluon-check-connection', 'target', function(group)
-- do not perform connection checks for groups which are not in use
for _, script in ipairs(scripts) do
if util.contains(script['groups'], group['.name']) then
groups[group['.name']] = group['hosts']
break
end
end
end)
local old_states = {}
for group, _ in pairs(groups) do
if unistd.access(offline_flag_file_prefix .. group) then
old_states[group] = false
else
old_states[group] = true
end
end
local states = {}
for group, hosts in pairs(groups) do
states[group] = check_connection(group, old_states[group], hosts)
end
for _, script in ipairs(scripts) do
local state_changed = false
local state_offline = false
local state_online = false
for _, group in ipairs(script['groups']) do
if nil ~= states[group] then
if states[group] ~= old_states[group] then
state_changed = true
end
if states[group] then
state_online = true
else
state_offline = true
end
end
end
if not runtime or state_changed or not script['onchange'] then
local do_run = not script['trigger']
if script['trigger'] == 'online' and state_online then
do_run = true
end
if script['trigger'] == 'offline' and state_offline then
do_run = true
end
if do_run then
util.exec(script['command'])
end
end
end
unlock('/var/lock/gluon-check-connection.lock')
io.open(lastrun_file, "w"):write(tostring(math.floor(util.get_uptime() / 60)))