From 284f435116767fdd2355e560adb6d64e9cff4384 Mon Sep 17 00:00:00 2001 From: CodeFetch Date: Tue, 8 Jun 2021 15:37:47 +0200 Subject: [PATCH] ffh-check-connection: initial commit This commit adds a new package which can be used for scheduled connectivity checks. --- ffh-check-connection/LICENSE | 23 +++ ffh-check-connection/Makefile | 21 +++ ffh-check-connection/README.md | 69 +++++++ ffh-check-connection/check_site.lua | 5 + .../files/etc/config/ffh-check-connection | 0 .../usr/lib/micron.d/ffh-check-connection | 1 + .../gluon/upgrade/500-ffh-check-connection | 13 ++ .../luasrc/usr/sbin/ffh-check-connection | 176 ++++++++++++++++++ 8 files changed, 308 insertions(+) create mode 100644 ffh-check-connection/LICENSE create mode 100644 ffh-check-connection/Makefile create mode 100644 ffh-check-connection/README.md create mode 100644 ffh-check-connection/check_site.lua create mode 100644 ffh-check-connection/files/etc/config/ffh-check-connection create mode 100644 ffh-check-connection/files/usr/lib/micron.d/ffh-check-connection create mode 100755 ffh-check-connection/luasrc/lib/gluon/upgrade/500-ffh-check-connection create mode 100755 ffh-check-connection/luasrc/usr/sbin/ffh-check-connection diff --git a/ffh-check-connection/LICENSE b/ffh-check-connection/LICENSE new file mode 100644 index 00000000..7126f014 --- /dev/null +++ b/ffh-check-connection/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2021, Freifunk Hannover +Copyright (c) 2013-2021, Project Gluon +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ffh-check-connection/Makefile b/ffh-check-connection/Makefile new file mode 100644 index 00000000..5c02beea --- /dev/null +++ b/ffh-check-connection/Makefile @@ -0,0 +1,21 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=ffh-check-connection +PKG_VERSION:=1 +PKG_RELEASE:=1 + +include $(TOPDIR)/../package/gluon.mk + +define Package/$(PKG_NAME) + TITLE:=Helper script to do ping checks with configurable IPv6 targets + DEPENDS:=+gluon-core +libgluonutil +gluon-site +luaposix +luabitop +micrond @GLUON_MULTIDOMAIN +endef + +define Package/$(PKG_NAME)/description + Script to check when there is connectivity to configurable targets + possible using ICMP IPv6 pings. This script will be executed every + minute by ``micrond``. If e.g. the connection state changes it + executes corresponding commands in a configurable manner. +endef + +$(eval $(call BuildPackageGluon,$(PKG_NAME))) diff --git a/ffh-check-connection/README.md b/ffh-check-connection/README.md new file mode 100644 index 00000000..d08ea03f --- /dev/null +++ b/ffh-check-connection/README.md @@ -0,0 +1,69 @@ +ffh-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 packages is e.g. being used by *ffh-wifi-offline-ssid*. + +domain.conf +----------- + +Target groups can be pre-defined in the domain config. + +:: + + ffh_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 domain.conf will overwrite existing ones with the same +name when performing a *sysupgrade* or by triggering *gluon-reconfigure*. + +Configuration via UCI +--------------------- + +Packages can use ffh-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 script will be considered + interval : + - the interval to execute the trigger script (in minutes - defaults to 1) + command : + - the command to execute + groups : + - the array of target groups 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 address to ping + hosts : + - array containing the IPv6 addresses to perform the ping test on + diff --git a/ffh-check-connection/check_site.lua b/ffh-check-connection/check_site.lua new file mode 100644 index 00000000..407b0e69 --- /dev/null +++ b/ffh-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({'ffh_check_connection', 'targets'}), check_target, false) diff --git a/ffh-check-connection/files/etc/config/ffh-check-connection b/ffh-check-connection/files/etc/config/ffh-check-connection new file mode 100644 index 00000000..e69de29b diff --git a/ffh-check-connection/files/usr/lib/micron.d/ffh-check-connection b/ffh-check-connection/files/usr/lib/micron.d/ffh-check-connection new file mode 100644 index 00000000..db21d004 --- /dev/null +++ b/ffh-check-connection/files/usr/lib/micron.d/ffh-check-connection @@ -0,0 +1 @@ +* * * * * /usr/sbin/ffh-check-connection diff --git a/ffh-check-connection/luasrc/lib/gluon/upgrade/500-ffh-check-connection b/ffh-check-connection/luasrc/lib/gluon/upgrade/500-ffh-check-connection new file mode 100755 index 00000000..f2a2235b --- /dev/null +++ b/ffh-check-connection/luasrc/lib/gluon/upgrade/500-ffh-check-connection @@ -0,0 +1,13 @@ +#!/usr/bin/lua + +local uci = require('simple-uci').cursor() +local site = require 'gluon.site' + +for group, hosts in pairs(site.ffh_check_connection.targets()) do + uci:delete('ffh-check-connection', 'target', group) + uci:section('ffh-check-connection', 'target', group, { + hosts = hosts + }) +end + +uci:save('ffh-check-connection') diff --git a/ffh-check-connection/luasrc/usr/sbin/ffh-check-connection b/ffh-check-connection/luasrc/usr/sbin/ffh-check-connection new file mode 100755 index 00000000..403617ce --- /dev/null +++ b/ffh-check-connection/luasrc/usr/sbin/ffh-check-connection @@ -0,0 +1,176 @@ +#!/usr/bin/lua + +local bit = require('bit') +local fcntl = require('posix.fcntl') +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_file_prefix = '/tmp/ffh-offline-' +local firstrun_file = '/tmp/ffh-check-connection-firstrun' +local lastrun_file = '/tmp/ffh-check-connection-lastrun' +local lockfile = '/var/lock/ffh-check-connection.lock' + +local function readnumber(file) + return tonumber(util.readfile(file)) +end + +local function writenumber(file, num) + io.open(file, "w"):write(tostring(math.floor(num))) +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_file = offline_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_file) + end + + return true + end + + if old_state then + util.log(group .. ' connectivity lost') + writenumber(offline_file, (util.get_uptime() / 60)) + end + + return false +end + +local lockfd, err = fcntl.open(lockfile, bit.bor(fcntl.O_WRONLY, fcntl.O_CREAT), 384) -- mode 0600 + +if not lockfd then + util.log(err, true) + os.exit(1) +end + +local ok, _ = fcntl.fcntl(lockfd, fcntl.F_SETLK, { + l_start = 0, + l_len = 0, + l_type = fcntl.F_WRLCK, + l_whence = unistd.SEEK_SET, +}) + +if not ok then + io.stderr:write(string.format( + "Unable to lock file %s. Make sure there is no other instance running.\n", + lockfile + )) + os.exit(1) +end + +local uptime = math.floor(util.get_uptime() / 60) + +if uptime < min_uptime then + 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 = readnumber(firstrun_file) + lastrun = readnumber(lastrun_file) +else + writenumber(firstrun_file, uptime) +end + +local runtime = lastrun - firstrun + +local scripts = {} +uci:foreach('ffh-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('ffh-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_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 + +writenumber(lastrun_file, (util.get_uptime() / 60))