Improve node setup (#996)

* Reworking the node-setup system - less reboots when changing configuration

* Add restart-services to advnet
This commit is contained in:
Tim Wilkinson 2023-12-12 20:01:23 -08:00 committed by GitHub
parent 1e5d933c0d
commit 4120914a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1174 additions and 1203 deletions

View File

@ -11,7 +11,6 @@
/etc/config.mesh/aliases.nat
/etc/config.mesh/vtun
/etc/config.mesh/wireguard
/etc/config.mesh/network_tun
/etc/config.mesh/aredn
/etc/config.mesh/xlink
/etc/config.mesh/metrics

View File

@ -2,10 +2,10 @@ config downloads
option firmwarepath 'http://downloads.arednmesh.org/firmware'
config poe
option passthrough '0'
option passthrough '0'
config usb
option passthrough '1'
option passthrough '1'
config map
option leafletjs 'http://unpkg.com/leaflet@0.7.7/dist/leaflet.js'

View File

@ -1,4 +1,3 @@
# This file is interpreted as shell script.
# Put your custom nft rules here, they will
# be executed with each firewall (re-)start.

View File

@ -41,7 +41,7 @@ include /etc/aredn_include/static_routes
include /etc/config.mesh/xlink
### Tunnels devices
include /etc/config.mesh/network_tun
<tun_devices_config>
### Wireguard
<wireguard_network_config>

View File

@ -1,79 +0,0 @@
config interface 'tun50'
option ifname 'tun50'
option proto 'none'
config interface 'tun51'
option ifname 'tun51'
option proto 'none'
config interface 'tun52'
option ifname 'tun52'
option proto 'none'
config interface 'tun53'
option ifname 'tun53'
option proto 'none'
config interface 'tun54'
option ifname 'tun54'
option proto 'none'
config interface 'tun55'
option ifname 'tun55'
option proto 'none'
config interface 'tun56'
option ifname 'tun56'
option proto 'none'
config interface 'tun57'
option ifname 'tun57'
option proto 'none'
config interface 'tun58'
option ifname 'tun58'
option proto 'none'
config interface 'tun59'
option ifname 'tun59'
option proto 'none'
config interface 'tun60'
option ifname 'tun60'
option proto 'none'
config interface 'tun61'
option ifname 'tun61'
option proto 'none'
config interface 'tun62'
option ifname 'tun62'
option proto 'none'
config interface 'tun63'
option ifname 'tun63'
option proto 'none'
config interface 'tun64'
option ifname 'tun64'
option proto 'none'
config interface 'tun65'
option ifname 'tun65'
option proto 'none'
config interface 'tun66'
option ifname 'tun66'
option proto 'none'
config interface 'tun67'
option ifname 'tun67'
option proto 'none'
config interface 'tun68'
option ifname 'tun68'
option proto 'none'
config interface 'tun69'
option ifname 'tun69'
option proto 'none'

View File

@ -1,13 +1,11 @@
# Server configuration
config uhttpd main
# HTTP listen addresses, multiple allowed
list listen_http 0.0.0.0:8080
list listen_http 0.0.0.0:80
option home /www
option rfc1918_filter 1
option cgi_prefix /cgi-bin
option script_timeout 240
option network_timeout 30
option tcp_keepalive 5
# option config /etc/httpd.conf
list listen_http '0.0.0.0:8080'
list listen_http '0.0.0.0:80'
option home '/www'
option rfc1918_filter '1'
option cgi_prefix '/cgi-bin'
option script_timeout '240'
option network_timeout '30'
option tcp_keepalive '5'
option http_keepalive '0'

View File

View File

@ -6,10 +6,13 @@
# Example:
#
# config interface 'xlink0'
# option ifname "eth0.500"
# option ifname "br0.500"
# option proto static
# option ipaddr 44.1.2.3
# option netmask 255.255.255.0
# option netmask 255.255.255.255
# option peer 44.1.2.1
# option weight 2
#
# config route
# option interface 'xlink0'
# option target '44.1.2.1'

View File

@ -2,7 +2,7 @@
--[[
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2022 Tim Wilkinson
Copyright (C) 2022-2023 Tim Wilkinson
See Contributors file for additional contributors
This program is free software: you can redistribute it and/or modify
@ -34,76 +34,101 @@
--]]
local olsrd_conf = "/tmp/etc/olsrd.conf"
require("uci")
require("aredn.services")
-- Load olsrd.conf
local lines = {}
for line in io.lines(olsrd_conf)
local cursor = uci.cursor()
-- Current nameserver state
local ns = cursor:get_all("olsrd", "nameservice")
if not ns then
print("Missing nameservice")
return
end
local cnames = {}
for _, name in ipairs(ns.name)
do
lines[#lines + 1] = line
cnames[name] = true
end
local chosts = {}
for k, v in pairs(ns)
do
if k:match("^%d+_%d+_%d+_%d+$") then
chosts[k:gsub("_", ".")] = v
end
end
local cservices = {}
for _, service in ipairs(ns.service or {})
do
cservices[service] = true
end
-- Generate validated name services
local ns = {}
for line in io.popen("/usr/local/bin/olsrd-config olsrd validate"):lines()
-- Work out the differences between the validated state and the current state
local names, hosts, services = aredn.services.get(true)
local nnames = false
for _, name in ipairs(names)
do
ns[#ns + 1] = line
if cnames[name] then
cnames[name] = nil
else
nnames = true
end
end
local nhosts = {}
for _, host in ipairs(hosts)
do
if chosts[host.ip] then
chosts[host.ip] = nil
else
nhosts[host.ip] = host.host
end
end
local nservices = false
for _, service in ipairs(services)
do
if cservices[service] then
cservices[service] = nil
else
nservices = true
end
end
function not_empty(h)
for _, _ in pairs(h)
do
return true
end
return false
end
table.remove(ns, 1) -- strip first line which is blank
-- Locate merge point, then merge new validated services
local change = false
local i = 1
while true
do
if lines[i] == 'LoadPlugin "olsrd_nameservice.so.0.4"' then
local n = 1
while i <= #lines and n <= #ns
do
if lines[i] ~= ns[n] then
lines[i] = ns[n]
change = true
end
i = i + 1
n = n + 1
end
if i <= #lines then
-- truncated
while i <= #lines
do
table.remove(lines, #lines)
end
change = true
end
if n <= #ns then
-- append
while n <= #ns
do
lines[#lines + 1] = ns[n]
n = n + 1
end
change = true
end
break
-- Apply the changes
if nnames or not_empty(cnames) then
cursor:set("olsrd", "nameserver", "name", names)
change = true
end
if not_empty(chosts) or not_empty(nhosts) then
for k, _ in pairs(chosts)
do
cursor:delete("olsrd", "nameserver", k)
end
i = i + 1
for k, v in pairs(nhosts)
do
cursor:set("olsrd", "nameserver", k, v)
end
change = true
end
if nservices or not_empty(cservices) then
cursor:set("olsrd", "nameserver", "service", services)
change = true
end
-- If services have changed we need to update and restart olsrd
-- If services have changed we need to restart olsrd
if change then
cursor:commit("olsrd")
print("Change")
local f = io.open(olsrd_conf, "w")
if f then
for _, line in ipairs(lines)
do
f:write(line .. "\n")
end
f:close()
-- We use 'killall' to restart olsrd to stop if from regenerating the olsrd.conf file we
-- just changed. We dont want to modify the standard process so these changes wont persist
-- across restarts/reconfigurations but will only be applied daily when poor services are detected
os.execute("/usr/bin/killall olsrd > /dev/null 2>&1")
end
os.execute("/etc/init.d/olsrd restart > /dev/null 2>&1")
else
print("Unchange")
end

View File

@ -6,23 +6,6 @@ if [ "$(/sbin/uci -c /etc/config.mesh -q get aredn.@tunnel[0])" != "tunnel" ]; t
/sbin/uci -c /etc/config.mesh -q set aredn.@tunnel[0].weight=1
/sbin/uci -c /etc/config.mesh -q commit aredn
fi
clients=$(/sbin/uci -c /etc/config.mesh -q get aredn.@tunnel[0].maxclients)
servers=$(/sbin/uci -c /etc/config.mesh -q get aredn.@tunnel[0].maxservers)
maxtun=$((49 + $clients + $servers))
# Check we have sufficient tunnels and regenerate network_tun if not
if [ "$(/sbin/uci -c /etc/config.mesh -q get network_tun.tun$maxtun)" != "interface" -o "$(/sbin/uci -c /etc/config.mesh -q get network_tun.tun$((1 + $maxtun)))" != "" ]; then
cp /dev/null /etc/config.mesh/network_tun
for tun in $(seq 50 $maxtun)
do
cat >> /etc/config.mesh/network_tun <<__EOT__
config interface 'tun$tun'
option ifname 'tun$tun'
option proto 'none'
__EOT__
done
fi
# Default tunnel weight to 1 (perfect RF)
if [ "$(/sbin/uci -c /etc/config.mesh -q get aredn.@tunnel[0].weight)" = "" ]; then

View File

@ -670,4 +670,8 @@ function model.set_nvram(var, val)
c:commit("hsmmmesh")
end
if not aredn then
aredn = {}
end
aredn.info = model
return model

View File

@ -0,0 +1,314 @@
#! /usr/bin/lua
--[[
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2021-2023 Tim Wilkinson
Original Perl Copyright (C) 2015 Conrad Lara
See Contributors file for additional contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Additional Terms:
Additional use restrictions exist on the AREDN(TM) trademark and logo.
See AREDNLicense.txt for more info.
Attributions to the AREDN Project must be retained in the source code.
If importing this code into a new or existing project attribution
to the AREDN project must be added to the source code.
You must not misrepresent the origin of the material contained within.
Modified versions must be modified to attribute to the original source
and be marked in reasonable ways as differentiate it from the original
version
--]]
require("nixio")
require("aredn.utils")
require("aredn.hardware")
require('aredn.info')
require("uci")
-- Whether to validate hosts and services before publishing
local validation_timeout = 150 * 60 -- 2.5 hours (so must fail 3 times in a row)
local validation_state = "/tmp/service-validation-state"
local function ip_to_hostname(ip)
if ip and ip ~= "" and ip ~= "none" then
local a, b, c, d = ip:match("(.*)%.(.*)%.(.*)%.(.*)")
local revip = d .. "." .. c .. "." .. b .. "." .. a
local f = io.popen("nslookup " .. ip)
if f then
local pattern = "^" .. revip .. "%.in%-addr%.arpa%s+name%s+=%s+(%S+)%.local%.mesh"
for line in f:lines()
do
local host = line:match(pattern)
if host then
f:close()
return host
end
end
f:close()
end
end
return ""
end
--
-- Get all the known node names, host and services
-- with optional validation
--
local function get(validate)
local names = {}
local hosts = {}
local services = {}
-- canonical names for this node
-- (they should up in reverse order, make the official name last)
local name = aredn.info.get_nvram("tactical")
if name ~= "" then
names[#names + 1] = name
end
name = aredn.info.get_nvram("node")
if name ~= "" then
names[#names + 1] = name
end
local dmz_mode = uci.cursor("/etc/config.mesh"):get("aredn", "@dmz[0]", "mode")
if dmz_mode ~= "0" then
if nixio.fs.stat("/etc/config.mesh/aliases.dmz") then
for line in io.lines("/etc/config.mesh/aliases.dmz")
do
local ip, host = line:match("(.*) (.*)")
if host then
hosts[#hosts + 1] = { ip = ip, host = host }
end
end
end
if nixio.fs.stat("/etc/ethers") then
local noprop_ip = {}
if nixio.fs.stat("/etc/hosts") then
for line in io.lines("/etc/hosts")
do
local ip = line:match("^(%S+)%s.*#NOPROP$")
if ip then
noprop_ip[ip] = true
end
end
end
for line in io.lines("/etc/ethers")
do
local ip = line:match("[0-9a-fA-F:]+%s+([%d%.]+)")
if ip and not noprop_ip[ip] then
local host = ip_to_hostname(ip)
if host then
hosts[#hosts + 1] = { ip = ip, host = host }
end
end
end
end
end
-- add a name for the dtdlink and xlink interfaces
if name then
if nixio.fs.stat("/etc/hosts") then
for line in io.lines("/etc/hosts")
do
local dtdip = line:match("^(%d+%.%d+%.%d+%.%d+)%s+dtdlink%.")
if dtdip then
hosts[#hosts + 1] = { ip = dtdip, host = "dtdlink." .. name .. ".local.mesh" }
break
end
end
end
if nixio.fs.stat("/etc/config.mesh/xlink") then
local count = 0
uci.cursor("/etc/config.mesh"):foreach("xlink", "interface",
function(section)
if section.ipaddr then
hosts[#hosts + 1] = { ip = section.ipaddr, host = "xlink" .. count .. "." .. name .. ".local.mesh" }
count = count + 1
end
end
)
end
end
-- load the services
local servfile = "/etc/config.mesh/_setup.services.nat"
if dmz_mode ~= "0" then
servfile = "/etc/config.mesh/_setup.services.dmz"
end
if nixio.fs.access(servfile) then
for line in io.lines(servfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local name, link, proto, host, port, sffx = line:match("(.*)|(.*)|(.*)|(.*)|(.*)|(.*)")
if name and name ~= "" and host ~= "" then
if proto == "" then
proto = "http"
end
if link == "0" then
port = "0"
end
services[#services + 1] = string.format("%s://%s:%s/%s|tcp|%s\n", proto, host, port, sffx, name)
end
end
end
end
--
-- validation
if validate then
-- Load previous state
local vstate = {}
if nixio.fs.stat(validation_state) then
for line in io.lines(validation_state)
do
local last, key = line:match("^(%d+) (.*)$")
if last then
vstate[key] = tonumber(last)
end
end
end
local now = os.time()
local last = now + validation_timeout
local laniface = aredn.hardware.get_iface_name("lan")
-- Add in local names so services checks pass
for _, name in ipairs(names)
do
vstate[name:lower()] = last
end
-- Check we can reach all the IP addresses
for _, host in ipairs(hosts)
do
if os.execute("/bin/ping -q -c 1 -W 1 " .. host.ip .. " > /dev/null 2>&1") == 0 then
vstate[host.host:lower()] = last
elseif os.execute("/usr/sbin/arping -q -f -c 1 -w 1 -I " .. laniface .. " " .. host.ip .. " > /dev/null 2>&1") == 0 then
vstate[host.host:lower()] = last
end
end
-- Check all the services have a valid host
for _, service in ipairs(services)
do
local proto, hostname, port, path = service:match("^(%w+)://([%w%-%.]+):(%d+)(.*)|...|[^|]+$")
local vs = vstate[hostname:lower()]
if not vs or vs > now or dmz_mode == "0" then
if port == "0" then
-- no port so not a link - we can only check the hostname so have to assume the service is good
vstate[service] = last
elseif proto == "http" then
-- http so looks like a link. http check it
if not hostname:match("%.local%.mesh$") then
hostname = hostname .. ".local.mesh"
end
local status, effective_url = io.popen("/usr/bin/curl --max-time 10 --retry 0 --connect-timeout 2 --speed-time 5 --speed-limit 1000 --silent --output /dev/null --location --write-out '%{http_code} %{url_effective}' " .. "http://" .. hostname .. ":" .. port .. path):read("*a"):match("^(%d+) (.*)")
if status == "200" or status == "401" then
vstate[service] = last
elseif status == "301" or status == "302" or status == "303" or status == "307" or status == "308" then
-- Ended at a redirect rather than an actual page.
if effective_url:match("^https:") then
-- We cannot validate https: links so we just assume they're okay
vstate[service] = last
end
end
else
-- valid port, but we dont know the protocol (we cannot trust the one defined in the services file because the UI wont set
-- anything but 'tcp'). Check both tcp and udp and assume valid it either is okay
-- tcp
local s = nixio.socket("inet", "stream")
s:setopt("socket", "sndtimeo", 2)
local r = s:connect(hostname, tonumber(port))
s:close()
if r == true then
-- tcp connection succeeded
vstate[service] = last
else
-- udp
s = nixio.socket("inet", "dgram")
s:setopt("socket", "rcvtimeo", 2)
s:connect(hostname, tonumber(port))
s:send("")
r = s:recv(0)
s:close()
if r ~= nil then
-- A nil response is an explicity rejection of the udp request. Otherwise we have
-- to assume the service is valid
vstate[service] = last
end
end
end
end
end
-- Generate new hosts and services as long as they're valid
local old_hosts = hosts
hosts = {}
for _, host in ipairs(old_hosts)
do
local lname = host.host:lower()
local vs = vstate[lname]
if not vs then
hosts[#hosts + 1] = host
vstate[lname] = last
elseif vs > now then
hosts[#hosts + 1] = host
end
end
local old_services = services
services = {}
for _, service in ipairs(old_services)
do
local vs = vstate[service]
if not vs then
-- New services will be valid for a while, even if they're not there yet
services[#services + 1] = service
vstate[service] = last
elseif vs > now then
services[#services + 1] = service
end
end
-- Store state for next time
local f = io.open(validation_state, "w")
if f then
for key, last in pairs(vstate)
do
f:write(last .. " " .. key .. "\n")
end
f:close()
end
end
-- end validation
return names, hosts, services
end
--
-- Reset validation
--
local function reset_validation()
os.remove(validation_state)
end
if not aredn then
aredn = {}
end
aredn.services = {
get = get,
reset_validation = reset_validation
}
return aredn.services

View File

@ -311,21 +311,31 @@ function capture(cmd)
end
-- copy a file
function filecopy(from, to)
function filecopy(from, to, ifchanged)
local f = io.open(from, "r")
if not f then
return false
end
local r = f:read("*a")
f:close()
if ifchanged then
local t = io.open(to, "r")
if t then
if r == t:read("*a") then
t:close()
return true, false
end
t:close()
end
end
local t = io.open(to, "w")
if not t then
f:close()
return false
end
-- not great on memory usage
t:write(f:read("*a"))
t:write(r)
t:close()
f:close()
return true
return true, true
end
-- remove all files (including recursively into directories)
@ -345,12 +355,24 @@ function remove_all(name)
end
-- write all data to a file in one go
function write_all(filename, data)
function write_all(filename, data, ifchanged)
if ifchanged then
local t = io.open(filename, "r")
if t then
if data == t:read("*a") then
t:close()
return true, false
end
t:close()
end
end
local f = io.open(filename, "w")
if f then
f:write(data)
f:close()
return true, true
end
return false
end
-- read all data from file in one go

View File

@ -122,7 +122,7 @@ tmp:close()
filecopy("/tmp/.mesh_setup", "/etc/config.mesh/_setup")
os.remove("/tmp/.mesh_setup")
os.execute("/usr/local/bin/node-setup -a mesh")
os.execute("/usr/local/bin/node-setup")
aredn_info.set_nvram("nodeupgraded", "0")
print "Rebooting node"

File diff suppressed because it is too large Load Diff

View File

@ -1,397 +0,0 @@
#! /usr/bin/lua
--[[
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2021 Tim Wilkinson
Original Perl Copyright (C) 2015 Conrad Lara
See Contributors file for additional contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Additional Terms:
Additional use restrictions exist on the AREDN(TM) trademark and logo.
See AREDNLicense.txt for more info.
Attributions to the AREDN Project must be retained in the source code.
If importing this code into a new or existing project attribution
to the AREDN project must be added to the source code.
You must not misrepresent the origin of the material contained within.
Modified versions must be modified to attribute to the original source
and be marked in reasonable ways as differentiate it from the original
version
--]]
require("nixio")
require("aredn.utils")
require("aredn.hardware")
aredn.info = require('aredn.info')
require("uci")
-- Whether to validate hosts and services before publishing
local validate = false
local validation_timeout = 150 * 60 -- 2.5 hours (so must fail 3 times in a row)
local validation_state = "/tmp/service-validation-state"
-- check what config gile we are building for
local uci_conf_file
if #arg == 0 then
uci_conf_file = "olsrd"
else
uci_conf_file = arg[1]
if #arg == 2 and arg[2] == "validate" then
validate = true
end
end
if uci_conf_file == "olsrd6" then
-- we only generate entries for IPv4 at the moment
os.exit(0)
end
local cursor = uci.cursor()
local cursorm = uci.cursor("/etc/config.mesh")
local names = {}
local hosts = {}
local services = {}
local tunnels = {}
local wgtunnels = {}
function ip_to_hostname(ip)
if ip and ip ~= "" and ip ~= "none" then
local a, b, c, d = ip:match("(.*)%.(.*)%.(.*)%.(.*)")
local revip = d .. "." .. c .. "." .. b .. "." .. a
local f = io.popen("nslookup " .. ip)
if f then
local pattern = "^" .. revip .. "%.in%-addr%.arpa%s+name%s+=%s+(%S+)%.local%.mesh"
for line in f:lines()
do
local host = line:match(pattern)
if host then
f:close()
return host
end
end
f:close()
end
end
return ""
end
-- canonical names for this node
-- (they should up in reverse order, make the official name last)
local name = aredn.info.get_nvram("tactical")
if name ~= "" then
names[#names + 1] = name
end
name = aredn.info.get_nvram("node")
if name ~= "" then
names[#names + 1] = name
end
local dmz_mode = cursor:get("aredn", "@dmz[0]", "mode")
if dmz_mode ~= "0" then
if nixio.fs.stat("/etc/config.mesh/aliases.dmz") then
for line in io.lines("/etc/config.mesh/aliases.dmz")
do
local ip, host = line:match("(.*) (.*)")
if host then
hosts[#hosts + 1] = { ip = ip, host = host }
end
end
end
if nixio.fs.stat("/etc/ethers") then
local noprop_ip = {}
if nixio.fs.stat("/etc/hosts") then
for line in io.lines("/etc/hosts")
do
local ip = line:match("^(%S+)%s.*#NOPROP$")
if ip then
noprop_ip[ip] = true
end
end
end
for line in io.lines("/etc/ethers")
do
local ip = line:match("[0-9a-fA-F:]+%s+([%d%.]+)")
if ip and not noprop_ip[ip] then
local host = ip_to_hostname(ip)
if host then
hosts[#hosts + 1] = { ip = ip, host = host }
end
end
end
end
end
-- add a name for the dtdlink and xlink interfaces
if name then
local dtdip = aredn.hardware.get_interface_ip4(aredn.hardware.get_iface_name("dtdlink"))
if dtdip then
hosts[#hosts + 1] = { ip = dtdip, host = "dtdlink." .. name .. ".local.mesh" }
if nixio.fs.stat("/etc/config.mesh/xlink") then
local count = 0
uci.cursor("/etc/config.mesh"):foreach("xlink", "interface",
function(section)
if section.ipaddr then
hosts[#hosts + 1] = { ip = section.ipaddr, host = "xlink" .. count .. "." .. name .. ".local.mesh" }
count = count + 1
end
end
)
end
end
end
-- load the services
if nixio.fs.stat("/etc/config/services") then
for line in io.lines("/etc/config/services")
do
if line:match("^%w+://[%w%-%.]+:%d+.*|tcp|[^|]+$") or line:match("^%w+://[%w%-%.]+:%d+.*|udp|[^|]+$") then
services[#services + 1] = line
end
end
end
-- validation
if validate then
-- Load previous state
local vstate = {}
if nixio.fs.stat(validation_state) then
for line in io.lines(validation_state)
do
local last, key = line:match("^(%d+) (.*)$")
if last then
vstate[key] = tonumber(last)
end
end
end
local now = os.time()
local last = now + validation_timeout
local laniface = aredn.hardware.get_iface_name("lan")
-- Add in local names so services checks pass
for _, name in ipairs(names)
do
vstate[name:lower()] = last
end
-- Check we can reach all the IP addresses
for _, host in ipairs(hosts)
do
if os.execute("/bin/ping -q -c 1 -W 1 " .. host.ip .. " > /dev/null 2>&1") == 0 then
vstate[host.host:lower()] = last
elseif os.execute("/usr/sbin/arping -q -f -c 1 -w 1 -I " .. laniface .. " " .. host.ip .. " > /dev/null 2>&1") == 0 then
vstate[host.host:lower()] = last
end
end
-- Check all the services have a valid host
for _, service in ipairs(services)
do
local proto, hostname, port, path = service:match("^(%w+)://([%w%-%.]+):(%d+)(.*)|...|[^|]+$")
local vs = vstate[hostname:lower()]
if not vs or vs > now or dmz_mode == "0" then
if port == "0" then
-- no port so not a link - we can only check the hostname so have to assume the service is good
vstate[service] = last
elseif proto == "http" then
-- http so looks like a link. http check it
if not hostname:match("%.local%.mesh$") then
hostname = hostname .. ".local.mesh"
end
local status, effective_url = io.popen("/usr/bin/curl --max-time 10 --retry 0 --connect-timeout 2 --speed-time 5 --speed-limit 1000 --silent --output /dev/null --location --write-out '%{http_code} %{url_effective}' " .. "http://" .. hostname .. ":" .. port .. path):read("*a"):match("^(%d+) (.*)")
if status == "200" or status == "401" then
vstate[service] = last
elseif status == "301" or status == "302" or status == "303" or status == "307" or status == "308" then
-- Ended at a redirect rather than an actual page.
if effective_url:match("^https:") then
-- We cannot validate https: links so we just assume they're okay
vstate[service] = last
end
end
else
-- valid port, but we dont know the protocol (we cannot trust the one defined in the services file because the UI wont set
-- anything but 'tcp'). Check both tcp and udp and assume valid it either is okay
-- tcp
local s = nixio.socket("inet", "stream")
s:setopt("socket", "sndtimeo", 2)
local r = s:connect(hostname, tonumber(port))
s:close()
if r == true then
-- tcp connection succeeded
vstate[service] = last
else
-- udp
s = nixio.socket("inet", "dgram")
s:setopt("socket", "rcvtimeo", 2)
s:connect(hostname, tonumber(port))
s:send("")
r = s:recv(0)
s:close()
if r ~= nil then
-- A nil response is an explicity rejection of the udp request. Otherwise we have
-- to assume the service is valid
vstate[service] = last
end
end
end
end
end
-- Generate new hosts and services as long as they're valid
local old_hosts = hosts
hosts = {}
for _, host in ipairs(old_hosts)
do
local lname = host.host:lower()
local vs = vstate[lname]
if not vs then
hosts[#hosts + 1] = host
vstate[lname] = last
elseif vs > now then
hosts[#hosts + 1] = host
end
end
local old_services = services
services = {}
for _, service in ipairs(old_services)
do
local vs = vstate[service]
if not vs then
-- New services will be valid for a while, even if they're not there yet
services[#services + 1] = service
vstate[service] = last
elseif vs > now then
services[#services + 1] = service
end
end
-- Store state for next time
local f = io.open(validation_state, "w")
if f then
for key, last in pairs(vstate)
do
f:write(last .. " " .. key .. "\n")
end
f:close()
end
end
-- end validation
-- load the tunnels
if nixio.fs.stat("/etc/local/mesh-firewall/02-vtund") then
local tunnum = 50
cursor:foreach("vtun", "client",
function(section)
if section.enabled == "1" then
tunnels[#tunnels + 1] = "tun" .. tunnum
tunnum = tunnum + 1
end
end
)
local maxclients = cursor:get("aredn", "@tunnel[0]", "maxclients")
if not maxclients then
maxclients = 10
end
tunnum = 50 + maxclients
local wgtunnum = 0
if cursor:get("wireguard", "@client[0]", "name") then
tunnels[#tunnels + 1] = "wgc"
end
cursorm:foreach("vtun", "server",
function(section)
if section.enabled == "1" then
if section.netip:match("/") then
tunnels[#tunnels + 1] = "wgs" .. wgtunnum
wgtunnum = wgtunnum + 1
else
tunnels[#tunnels + 1] = "tun" .. tunnum
tunnum = tunnum + 1
end
end
end
)
end
-- add the nameservice plugin
print()
print([[LoadPlugin "olsrd_nameservice.so.0.4"]])
print([[{]])
print([[ PlParam "sighup-pid-file" "/var/run/dnsmasq/dnsmasq.pid"]])
print([[ PlParam "interval" "30"]])
print([[ PlParam "timeout" "300"]])
print([[ PlParam "name-change-script" "/usr/local/bin/olsrd-namechange"]])
for _, name in ipairs(names)
do
print([[ PlParam "name" "]] .. name .. [["]])
end
for _, host in ipairs(hosts)
do
print([[ PlParam "]] .. host.ip .. [[" "]] .. host.host .. [["]])
end
for _, service in ipairs(services)
do
print([[ PlParam "service" "]] .. service .. [["]])
end
print([[}]])
-- add the ACTIVE tunnel interfaces
if #tunnels > 0 then
local tun_weight = cursor:get("aredn", "@tunnel[0]", "weight")
tun_weight = tonumber(tun_weight)
local tuns = ""
for _, tunnel in ipairs(tunnels)
do
tuns = tuns .. " \"" .. tunnel .. "\""
end
print()
print([[Interface]] .. tuns)
print([[{]])
print([[ Ip4Broadcast 255.255.255.255]])
local is_supernode = cursor:get("aredn", "@supernode[0]", "enable") == "1"
if not tun_weight or tun_weight < 1 or is_supernode then
print([[ Mode "ether"]])
elseif tun_weight > 1 then
print([[ LinkQualityMult default ]] .. (1 / tun_weight))
end
print([[}]])
end
-- add the XLINK interfaces
if nixio.fs.stat("/etc/config.mesh/xlink") then
uci.cursor("/etc/config.mesh"):foreach("xlink", "interface",
function(section)
print()
print([[Interface "]] .. section.ifname .. [["]])
print([[{]])
if section.peer then
print([[ Ip4Broadcast ]] .. section.peer)
else
print([[ Ip4Broadcast 255.255.255.255]])
end
local weight = tonumber(section.weight)
if weight then
if weight > 1 then
print([[ LinkQualityMult default ]] .. (1 / weight))
elseif weight < 1 then
print([[ Mode "ether"]])
end
else
print([[ Mode "ether"]])
end
print([[}]])
end
)
end

View File

@ -0,0 +1,70 @@
#! /bin/sh
true <<'LICENSE'
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2023 Tim Wilkinson
See Contributors file for additional contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Additional Terms:
Additional use restrictions exist on the AREDN(TM) trademark and logo.
See AREDNLicense.txt for more info.
Attributions to the AREDN Project must be retained in the source code.
If importing this code into a new or existing project attribution
to the AREDN project must be added to the source code.
You must not misrepresent the origin of the material contained within.
Modified versions must be modified to attribute to the original source
and be marked in reasonable ways as differentiate it from the original
version.
LICENSE
ROOT="/tmp/reboot-required"
SERVICES="system firewall network wireless dnsmasq tunnels manager olsrd"
# Anything to do?
if [ ! -d $ROOT ]; then
exit 0
fi
# Override services to restart
if [ "$*" != "" ]; then
SERVICES="$*"
fi
for srv in $SERVICES
do
if [ -f $ROOT/$srv ]; then
echo "Restarting $srv"
if [ $srv = "tunnels" ]; then
/etc/init.d/vtund restart > /dev/null 2>&1
/etc/init.d/vtundsrv restart > /dev/null 2>&1
elif [ $srv = "wireless" ]; then
/sbin/wifi reload > /dev/null 2>&1
elif [ -x /etc/init.d/$srv ]; then
/etc/init.d/$srv restart > /dev/null 2>&1
fi
rm $ROOT/$srv
fi
done
rmdir --ignore-fail-on-non-empty $ROOT
if [ -d $ROOT ]; then
exit 1
fi
exit 0

View File

@ -89,8 +89,7 @@ local settings = {
type = "boolean",
desc = "Enable <b>Link Quality Management</b><br><br><small>aredn.@lqm[0].enable</small>",
default = "1",
postcallback = "lqm_defaults()",
needreboot = true
postcallback = "lqm_defaults()"
},
{
category = "Link Quality Settings",
@ -145,9 +144,7 @@ local settings = {
key = "aredn.@lqm[0].mtu",
type = "string",
desc = "<b>Maximum packet size</b> in bytes sent over WiFi (256 to 1500)<br><br><small>aredn.@lqm[0].mtu</small>",
default = "1500",
needreboot = true,
nodesetup = true
default = "1500"
},
{
category = "Link Quality Settings",
@ -170,27 +167,21 @@ local settings = {
key = "aredn.@wan[0].olsrd_gw",
type = "boolean",
desc = "<b>Allow other MESH nodes to use my WAN</b> - not recommended and OFF by default<br><br><small>aredn.@wan[0].olsrd_gw</small>",
default = "0",
needreboot = true,
nodesetup = true
default = "0"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].lan_dhcp_route",
type = "boolean",
desc = "<b>Allow my LAN devices to access my WAN</b> - ON by default<br><br><small>aredn.@wan[0].lan_dhcp_route</small>",
default = "1",
needreboot = true,
nodesetup = true
default = "1"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].lan_dhcp_defaultroute",
type = "boolean",
desc = "<b>Provide default route to LAN devices</b> even when WAN access is disabled<br><br><small>aredn.@wan[0].lan_dhcp_defaultroute</small>",
default = "0",
needreboot = true,
nodesetup = true
default = "0"
},
{
category = "WAN Settings",
@ -200,33 +191,28 @@ local settings = {
default = "",
condition = "supportsVLANChange()",
current = "currentWANVLAN()",
postcallback = "changeWANVLAN()",
needreboot = true,
nodesetup = true
postcallback = "changeWANVLAN()"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].web_access",
type = "boolean",
desc = "<b>Enable web access</b> to the node from the WAN interface<br><br><small>aredn.@wan[0].web_access</small>",
default = "1",
needreboot = true
default = "1"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].ssh_access",
type = "boolean",
desc = "<b>Enable SSH access</b> to the node from the WAN interface<br><br><small>aredn.@wan[0].ssh_access</small>",
default = "1",
needreboot = true
default = "1"
},
{
category = "WAN Settings",
key = "aredn.@wan[0].telnet_access",
type = "boolean",
desc = "<b>Enable TELNET access</b> to the node from the WAN interface<br><br><small>aredn.@wan[0].telnet_access</small>",
default = "1",
needreboot = true
default = "1"
},
{
category = "Power Options",
@ -252,8 +238,7 @@ local settings = {
type = "string",
desc = "<b>Tunnel Maxclients</b> specifies the maximum number of tunnel clients this node can serve; must be an integer in the range [0,100].<br><br><small>aredn.@tunnel[0].maxclients</small>",
default = "10",
precallback = "restrictTunnelLimitToValidRange()",
postcallback = "adjustTunnelInterfaceCount()"
precallback = "restrictTunnelLimitToValidRange()"
},
{
category = "Tunnel Options",
@ -261,8 +246,7 @@ local settings = {
type = "string",
desc = "<b>Tunnel Maxservers</b> specifies the maximum number of tunnel servers to which this node can connect; must be an integer in the range [0,100].<br><br><small>aredn.@tunnel[0].maxservers</small>",
default = "10",
precallback = "restrictTunnelLimitToValidRange()",
postcallback = "adjustTunnelInterfaceCount()"
precallback = "restrictTunnelLimitToValidRange()"
},
{
category = "Tunnel Options",
@ -270,7 +254,6 @@ local settings = {
type = "string",
desc = "<b>Tunnel Weight</b> specifies the cost of using a tunnel. The higher the number, the less likely a tunnel is used.<br><br><small>aredn.@tunnel[0].weight</small>",
default = "1",
needreboot= true,
condition = "not isSupernode()"
},
{
@ -278,8 +261,7 @@ local settings = {
key = "aredn.@tunnel[0].wanonly",
type = "boolean",
desc = "<b>WAN-Only Tunnel</b> prevents tunnel traffic from being routed over the Mesh network itself<br><br><small>aredn.@tunnel[0].wanonly</small>",
default = "1",
needreboot= true
default = "1"
},
{
category = "Memory Settings",
@ -323,8 +305,6 @@ local settings = {
type = "string",
desc = "<b>Remote logging URL</b> for the remote syslog machine. Must be formatted as <i>protocol://ipaddress:port</i><br><br><small>aredn.@remotelog[0].url</small>",
default = "",
needreboot = true,
nodesetup = true,
precallback = "validate_rsyslog()"
},
{
@ -455,8 +435,7 @@ local settings = {
key = "aredn.@alerts[0].pollrate",
type = "string",
desc = "<b>Alert Message Pollrate</b> - how many hours to wait between polling for new AREDN Alerts<br><br><small>aredn.@alerts[0].pollrate</small>",
default = "1",
needreboot = true
default = "1"
},
{
category = "AREDN Alert Settings",
@ -478,37 +457,18 @@ function msg(m)
end
-- uci cursor
local cursora = uci.cursor()
local cursorb = uci.cursor("/etc/config.mesh")
local cursor = uci.cursor("/etc/config.mesh")
function cursor_set(a, b, c, d)
if not cursora:get(a, b) and b:match("@(.+)%[0%]") then
cursora:add(a, b:match("@(.+)%[0%]"))
if not cursor:get(a, b) and b:match("@(.+)%[0%]") then
cursor:add(a, b:match("@(.+)%[0%]"))
end
cursora:set(a, b, c, d)
if not cursorb:get(a, b) and b:match("@(.+)%[0%]") then
cursorb:add(a, b:match("@(.+)%[0%]"))
end
cursorb:set(a, b, c, d)
cursora:commit(a)
cursorb:commit(a)
end
function cursor_add(a, b, c)
cursora:set(a, b, c)
cursorb:set(a, b, c)
cursora:commit(a)
cursorb:commit(a)
end
function cursor_delete(a, b)
cursora:delete(a, b)
cursorb:delete(a, b)
cursora:commit(a)
cursorb:commit(a)
cursor:set(a, b, c, d)
cursor:commit(a)
end
function cursor_get(a, b, c)
return cursora:get(a, b, c)
return cursor:get(a, b, c)
end
function reboot()
@ -679,48 +639,6 @@ function restrictTunnelLimitToValidRange()
end
end
function addTunnelInterface(file, tunnum)
local section = "tun" .. tunnum
cursor_add(file, section, "interface")
cursor_set(file, section, "ifname", section)
cursor_set(file, section, "proto", "none")
end
function deleteTunnelInterface(file, tunnum)
local section = "tun" .. tunnum
cursor_delete(file, section)
end
function adjustTunnelInterfaceCount()
local tunnel_if_count = 0
cursora:foreach('network_tun', 'interface', function(s) tunnel_if_count = tunnel_if_count + 1 end)
local maxclients = cursor_get("aredn", "@tunnel[0]", "maxclients")
if not maxclients then
maxclients = 10
end
local maxservers = cursor_get("aredn", "@tunnel[0]", "maxservers")
if not maxservers then
maxservers = 10
end
local needed_if_count = maxclients + maxservers
if tunnel_if_count ~= needed_if_count then
for i = tunnel_if_count,needed_if_count-1
do
local tunnum = 50 + i
addTunnelInterface("network_tun", tunnum)
addTunnelInterface("network", tunnum)
end
for i = tunnel_if_count-1,needed_if_count,-1
do
local tunnum = 50 + i
deleteTunnelInterface("network_tun", tunnum)
deleteTunnelInterface("network", tunnum)
end
-- can't clone network because it contains macros; re-edit it instead
os.execute("sed -i -e '$r /etc/config.mesh/network_tun' -e '/interface.*tun',$d' /etc/config.mesh/network")
end
end
function currentWANVLAN()
for line in io.lines("/etc/config.mesh/_setup")
do
@ -824,12 +742,8 @@ do
if setting.postcallback then
loadstring(setting.postcallback)()
end
if setting.nodesetup then
os.execute("/usr/local/bin/node-setup -a mesh")
end
if setting.needreboot then
io.open("/tmp/reboot-required", "w"):close()
end
os.execute("/usr/local/bin/node-setup > /dev/null 2>&1")
os.execute("/usr/local/bin/restart-services.sh > /dev/null 2>&1")
break
end
end

View File

@ -358,8 +358,8 @@ if os.getenv("REQUEST_METHOD") == "POST" then
end
end
write_xlink_config(luci.jsonc.parse(params.xlinks))
os.execute("/usr/local/bin/node-setup -a mesh > /dev/null 2>&1")
io.open("/tmp/reboot-required", "w"):close()
os.execute("/usr/local/bin/node-setup > /dev/null 2>&1")
os.execute("/usr/local/bin/restart-services.sh > /dev/null 2>&1")
end
elseif params.op == "defaults" then
for _, network in ipairs({ "dtdlink", "lan", "wan" })
@ -367,8 +367,8 @@ if os.getenv("REQUEST_METHOD") == "POST" then
nixio.fs.remove(base .. network .. ".network.user")
end
write_xlink_config({})
os.execute("/usr/local/bin/node-setup -a mesh > /dev/null 2>&1")
io.open("/tmp/reboot-required", "w"):close()
os.execute("/usr/local/bin/node-setup > /dev/null 2>&1")
os.execute("/usr/local/bin/restart-services.sh > /dev/null 2>&1")
elseif params.op == "reboot" then
reboot()
end

View File

@ -131,7 +131,7 @@ local hosts = {}
local addrs = {}
local macs = {}
if config ~= "mesh" or nixio.fs.stat("/tmp/reboot-required") then
if config == "" or nixio.fs.stat("/tmp/reboot-required") then
http_header()
html.header(node .. " setup", true)
html.print("<body><center>")
@ -814,17 +814,10 @@ if parms.button_save and not (#port_err > 0 or #dhcp_err > 0 or #dmz_err > 0 or
os.remove("/tmp/service-validation-state")
if os.execute("/usr/local/bin/node-setup -a -p mesh > /dev/null 2>&1") ~= 0 then
if os.execute("/usr/local/bin/node-setup > /dev/null 2>&1") ~= 0 then
err("problem with configuration")
end
if os.execute("/etc/init.d/dnsmasq restart > /dev/null 2>&1") ~= 0 then
err("problem with dnsmasq")
end
if os.execute("/etc/init.d/firewall restart > /dev/null 2>&1") ~= 0 then
err("problem with port setup")
end
if os.execute("/etc/init.d/olsrd restart > /dev/null 2>&1") ~= 0 then
err("problem with olsr setup")
else
os.execute("/usr/local/bin/restart-services.sh dnsmasq firewall olsrd > /dev/null 2>&1")
end
end

View File

@ -816,7 +816,7 @@ if parms.button_save then
if not nixio.fs.stat("/tmp/web/save") then
nixio.fs.mkdir("/tmp/web/save")
end
os.execute("/usr/local/bin/node-setup -a " .. parms.config .. " > /tmp/web/save/node-setup.out 2>&1")
os.execute("/usr/local/bin/node-setup > /tmp/web/save/node-setup.out 2>&1")
if nixio.fs.stat("/tmp/web/save/node-setup.out", "size") > 0 then
err(read_all("/tmp/web/save/node-setup.out"))
else
@ -828,7 +828,9 @@ if parms.button_save then
f:read("*a")
f:close()
end
io.open("/tmp/reboot-required", "w"):close()
if nixio.fs.stat("/tmp/reboot-required") and not nixio.fs.stat("/tmp/reboot-required/reboot") and not nixio.fs.stat("/tmp/unconfigured") then
os.execute("/usr/local/bin/restart-services.sh > /dev/null 2>&1")
end
end
if nixio.fs.stat("/tmp/unconfigured") and #errors == 0 then
reboot()

View File

@ -511,10 +511,8 @@ end
if parms.button_save and #cli_err == 0 then
cursor:commit("vtun")
cursor:commit("wireguard")
os.execute("/usr/local/bin/node-setup -a mesh > /dev/null 2>&1")
os.execute("/etc/init.d/olsrd restart > /dev/null 2>&1")
os.execute("/etc/init.d/vtundsrv restart > /dev/null 2>&1")
os.execute("/etc/init.d/network restart > /dev/null 2>&1")
os.execute("/usr/local/bin/node-setup > /dev/null 2>&1")
os.execute("/usr/local/bin/restart-services.sh olsrd tunnels network > /dev/null 2>&1")
end
local active_tun = get_active_tun()

View File

@ -358,10 +358,8 @@ end
-- save the connections the uci vtun file
if parms.button_save and #conn_err == 0 then
cursor:commit("vtun")
os.execute("/usr/local/bin/node-setup -a mesh > /dev/null 2>&1")
os.execute("/etc/init.d/olsrd restart > /dev/null 2>&1")
os.execute("/etc/init.d/vtund restart > /dev/null 2>&1")
os.execute("/etc/init.d/network restart > /dev/null 2>&1")
os.execute("/usr/local/bin/node-setup > /dev/null 2>&1")
os.execute("/usr/local/bin/restart-services.sh olsrd tunnels network > /dev/null 2>&1")
end
local active_tun = get_active_tun()