From 9e5775865c329114b10bc14730c52cfa04a5e1c9 Mon Sep 17 00:00:00 2001 From: CodeFetch Date: Tue, 29 Dec 2020 02:24:22 +0100 Subject: [PATCH] gluon-check-connection: initial commit This commit adds a new package which can be used for scheduled connectivity checks. --- docs/index.rst | 1 + docs/package/gluon-check-connection.rst | 69 ++++++++ docs/user/site.rst | 1 - package/gluon-check-connection/Makefile | 21 +++ package/gluon-check-connection/check_site.lua | 5 + .../files/etc/config/gluon-check-connection | 0 .../usr/lib/micron.d/gluon-check-connection | 1 + .../gluon/upgrade/500-gluon-check-connection | 14 ++ .../luasrc/usr/sbin/gluon-check-connection | 158 ++++++++++++++++++ 9 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 docs/package/gluon-check-connection.rst create mode 100644 package/gluon-check-connection/Makefile create mode 100644 package/gluon-check-connection/check_site.lua create mode 100644 package/gluon-check-connection/files/etc/config/gluon-check-connection create mode 100644 package/gluon-check-connection/files/usr/lib/micron.d/gluon-check-connection create mode 100755 package/gluon-check-connection/luasrc/lib/gluon/upgrade/500-gluon-check-connection create mode 100755 package/gluon-check-connection/luasrc/usr/sbin/gluon-check-connection diff --git a/docs/index.rst b/docs/index.rst index 775e3bcf..1b73618a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,6 +59,7 @@ Several Freifunk communities in Germany use Gluon as the foundation of their Fre :caption: Packages :maxdepth: 1 + package/gluon-check-connection package/gluon-client-bridge package/gluon-config-mode-domain-select package/gluon-ebtables-filter-multicast diff --git a/docs/package/gluon-check-connection.rst b/docs/package/gluon-check-connection.rst new file mode 100644 index 00000000..2bb3442f --- /dev/null +++ b/docs/package/gluon-check-connection.rst @@ -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 + diff --git a/docs/user/site.rst b/docs/user/site.rst index 408cf455..c037f195 100644 --- a/docs/user/site.rst +++ b/docs/user/site.rst @@ -516,7 +516,6 @@ config_mode \: optional }, }, - roles \: optional Optional role definitions. Nodes will announce their role inside the mesh. This will allow in the backend to distinguish between normal, backbone and diff --git a/package/gluon-check-connection/Makefile b/package/gluon-check-connection/Makefile new file mode 100644 index 00000000..cdaf4930 --- /dev/null +++ b/package/gluon-check-connection/Makefile @@ -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))) diff --git a/package/gluon-check-connection/check_site.lua b/package/gluon-check-connection/check_site.lua new file mode 100644 index 00000000..02c767b0 --- /dev/null +++ b/package/gluon-check-connection/check_site.lua @@ -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) diff --git a/package/gluon-check-connection/files/etc/config/gluon-check-connection b/package/gluon-check-connection/files/etc/config/gluon-check-connection new file mode 100644 index 00000000..e69de29b diff --git a/package/gluon-check-connection/files/usr/lib/micron.d/gluon-check-connection b/package/gluon-check-connection/files/usr/lib/micron.d/gluon-check-connection new file mode 100644 index 00000000..481acaae --- /dev/null +++ b/package/gluon-check-connection/files/usr/lib/micron.d/gluon-check-connection @@ -0,0 +1 @@ +* * * * * /usr/sbin/gluon-check-connection diff --git a/package/gluon-check-connection/luasrc/lib/gluon/upgrade/500-gluon-check-connection b/package/gluon-check-connection/luasrc/lib/gluon/upgrade/500-gluon-check-connection new file mode 100755 index 00000000..e876d4fd --- /dev/null +++ b/package/gluon-check-connection/luasrc/lib/gluon/upgrade/500-gluon-check-connection @@ -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') + diff --git a/package/gluon-check-connection/luasrc/usr/sbin/gluon-check-connection b/package/gluon-check-connection/luasrc/usr/sbin/gluon-check-connection new file mode 100755 index 00000000..8c20dac6 --- /dev/null +++ b/package/gluon-check-connection/luasrc/usr/sbin/gluon-check-connection @@ -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)))