aredn/files/usr/local/bin/node-setup

1295 lines
48 KiB
Lua
Executable File

#! /usr/bin/lua
--[[
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
Copyright (C) 2021-2023 Tim Wilkinson
Orignal 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.info')
require("aredn.hardware")
require("iwinfo")
require("aredn.services")
-- helpers start
function is_null(v)
if not v or v == "" or v == 0 or v == "0" then
return true
else
return false
end
end
local function h2s(hex)
local s = ""
if hex then
for i = 1,#hex,2
do
local p = hex:sub(i, i+1)
if p:match("[0-9a-f][0-9a-f]") then
s = s .. string.char(tonumber(p, 16))
else
s = s .. p
end
end
end
return s
end
local function tablesize(t)
local len = 0
for _ in pairs(t)
do
len = len + 1
end
return len
end
local function get_subtable(container, key)
local subtable = container[key]
if not subtable then
subtable = {}
container[key] = subtable
end
return subtable
end
-- helpers end
local FORCED = "force"
local UNFORCED = "onrequest"
local c = uci.cursor()
local cm = uci.cursor("/etc/config.mesh")
local ieee80211 = "/sys/class/ieee80211/"
local lanintf = aredn.hardware.get_board_network_ifname("lan")
local node = aredn.info.get_nvram("node")
local tactical = aredn.info.get_nvram("tactical")
local mac2 = aredn.info.get_nvram("mac2")
local dtdmac = aredn.info.get_nvram("dtdmac")
local wifi_mon_enable = false
local deleteme = {}
local cfg = {
lan_intf = lanintf,
wan_intf = aredn.hardware.get_board_network_ifname("wan"),
bridge_network_config = "",
lan_network_config = "",
wan_network_config = "",
dtdlink_network_config = "",
wifi_network_config = "",
olsrd_dtd_interface_mode = "ether",
tun_network_config = "",
wireguard_network_config = "",
olsrd_pollrate = "0.05",
tun_devices_config = "",
hello_interval = "4.0",
tc_interval = "10.0",
mid_interval = "10.0",
hna_interval = "10.0"
}
-- Track the changes so we can make better decissions about what to restart/reboot
local changes = {
reboot = false,
system = false,
manager = false,
network = false,
olsr = false,
dnsmasq = false,
firewall = false,
tunnels = false,
wireless = false,
localservices = false
}
function valid_config(config)
remove_all("/tmp/uci_validate")
nixio.fs.mkdir("/tmp/uci_validate")
write_all("/tmp/uci_validate/config", config)
local r = os.execute("/sbin/uci -c /tmp/uci_validate import dummy < /tmp/uci_validate/config")
remove_all("/tmp/uci_validate")
return r == 0 and true or false
end
function expand_vars(lines)
local nlines = {}
for line in lines:gmatch("([^\n]*\n?)")
do
local inc = line:match("^include%s+(%S+)%s*")
if inc then
if nixio.fs.stat(inc) then
line = expand_vars(read_all(inc))
if not valid_config(line) then
print("Invalid config fragment: " .. inc)
os.exit(1)
end
else
line = nil
end
elseif line:match("^[^#]") then
for parm in line:gmatch("<([^%s]*)>")
do
if deleteme[parm] then
line = nil
elseif parm == "NODE" then
line = line:gsub("<NODE>", node)
elseif parm == "MAC2" then
line = line:gsub("<MAC2>", mac2)
elseif parm == "DTDMAC" then
line = line:gsub("<DTDMAC>", dtdmac)
elseif cfg[parm] then
line = line:gsub("<" .. parm .. ">", cfg[parm])
else
line = nil
end
end
end
if line then
nlines[#nlines + 1] = line
end
end
return table.concat(nlines, "")
end
-- load the verify the selected configuration
for line in io.lines("/etc/config.mesh/_setup")
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
line = line:gsub("<NODE>", node):gsub("<MAC2>", mac2):gsub("<DTDMAC>", dtdmac)
local k, v = line:match("^([^%s]*)%s*=%s*(.*)%s*$")
cfg[k] = v
end
end
if cfg.wifi_enable == "1" then
if not cfg.wifi_intf or cfg.wifi_intf == "" then
cfg.wifi_intf = aredn.hardware.get_board_network_ifname("wifi"):match("^(%S+)")
end
local mtu = c:get("aredn", "@lqm[0]", "mtu")
mtu = tonumber(mtu)
if mtu and mtu >= 256 and mtu <= 1500 then
cfg.wifi_mtu = mtu
end
else
cfg.wifi_intf = "br-nomesh"
end
-- Supernode options
local is_supernode = (cm:get("aredn", "@supernode[0]", "enable") == "1")
if is_supernode then
cfg.olsrd_dtd_interface_mode = "isolated"
cfg.olsrd_pollrate = "0.01"
end
-- delete some config lines if necessary
if cfg.wan_proto == "dhcp" then
deleteme.wan_ip = true
deleteme.wan_gw = true
deleteme.wan_mask = true
end
if not is_null(cfg.dmz_mode) or cfg.wan_proto ~= "disabled" then
deleteme.lan_gw = true
end
-- lan_dhcp sense is inverted in the dhcp config file
-- and it is a checkbox so it may not be defined - this fixes that
if cfg.lan_dhcp == "1" then
cfg.lan_dhcp = 0
else
cfg.lan_dhcp = 1
end
-- handle possible remote syslog
local remote_log = cm:get("aredn", "@remotelog[0]", "url") or ""
local proto, ip, port = remote_log:match("^(.+)://(%d+%.%d+%.%d+%.%d+):(%d+)$")
port = tonumber(port)
if proto and (proto == "tcp" or proto == "udp") and (port > 0 and port < 65536) and validate_ip(ip) then
cfg.remote_log_ip = ip
cfg.remote_log_port = port
cfg.remote_log_proto = proto
else
deleteme.remote_log_ip = true
deleteme.remote_log_port = true
deleteme.remote_log_proto = true
end
-- verify that we have all the variables we need
for file in nixio.fs.glob("/etc/config.mesh/*")
do
for line in io.lines(file)
do
if line:match("^[^#]") then
for parm in line:gmatch("<([^%s]*)>")
do
if parm:upper() == parm then
-- nvram variable
if aredn.info.get_nvram(parm:lower()) == "" then
print ("nv parameter '" .. parm .. "' in file '" .. file .. "' does not exist")
return -1
end
else
if not cfg[parm] and not deleteme[parm] then
print ("parameter '" .. parm .. "' in file '" .. file .. "' does not exist")
return -1
end
end
end
end
end
end
-- sensible dmz_mode default
if is_null(cfg.dmz_mode) then
cfg.dmz_mode = "0"
end
-- switch to dmz values if needed
if not is_null(cfg.dmz_mode) then
cfg.lan_ip = cfg.dmz_lan_ip
cfg.lan_mask = cfg.dmz_lan_mask
cfg.dhcp_start = cfg.dmz_dhcp_start
cfg.dhcp_end = cfg.dmz_dhcp_end
cfg.dhcp_limit = cfg.dmz_dhcp_limit
end
-- select ports and dhcp files based on mode
local portfile = "/etc/config.mesh/_setup.ports"
local dhcpfile = "/etc/config.mesh/_setup.dhcp"
local dhcptagsfile = "/etc/config.mesh/_setup.dhcptags"
local dhcpoptionsfile = "/etc/config.mesh/_setup.dhcpoptions"
local aliasfile = "/etc/config.mesh/aliases"
local servfile = "/etc/config.mesh/_setup.services"
if is_null(cfg.dmz_mode) then
portfile = portfile .. ".nat"
dhcpfile = dhcpfile .. ".nat"
dhcptagsfile = dhcptagsfile .. ".nat"
dhcpoptionsfile = dhcpoptionsfile .. ".nat"
aliasfile = aliasfile .. ".nat"
servfile = servfile .. ".nat"
else
portfile = portfile .. ".dmz"
dhcpfile = dhcpfile .. ".dmz"
dhcptagsfile = dhcptagsfile .. ".dmz"
dhcpoptionsfile = dhcpoptionsfile .. ".dmz"
aliasfile = aliasfile .. ".dmz"
servfile = servfile .. ".dmz"
end
-- check for old aliases file, copy it to .dmz and create symlink
-- just in case anyone is already using the file for some script or something
if not nixio.fs.readlink("/etc/config.mesh/aliases") then
if nixio.fs.stat("/etc/config.mesh/aliases") then
filecopy("/etc/config.mesh/aliases", "/etc/config.mesh/aliases.dmz")
os.remove("/etc/config.mesh/aliases")
else
io.open("/etc/config.mesh/aliases.dmz", "a"):close()
end
nixio.fs.symlink("aliases.dmz", "/etc/config.mesh/aliases")
end
-- generate the new school bridge configuration
if nixio.fs.stat("/etc/aredn_include/bridge.network.user") then
cfg.bridge_network_config = expand_vars(read_all("/etc/aredn_include/bridge.network.user"))
if not valid_config(cfg.bridge_network_config) then
print("Invalid config fragment: /etc/aredn_include/bridge.network.user")
os.exit(1)
end
else
local list = {}
for _, net in ipairs({ "lan", "wan", "dtdlink" })
do
local ports = aredn.hardware.get_board_network_ifname(net):split(" ")
for _, port in ipairs(ports)
do
list[port:gsub("%..*$", "")] = true
end
end
local config = "config device\n option name 'br0'\n option type 'bridge'\n option vlan_filtering '1'\n"
for port, _ in pairs(list)
do
config = config .. " list ports '" .. port .. "'\n"
end
cfg.bridge_network_config = config
end
-- generate the network configurations
for _, net in ipairs({ "lan", "wan", "dtdlink", "wifi" })
do
local wireless = false
local config = ""
-- user override
if nixio.fs.stat("/etc/aredn_include/" .. net .. ".network.user") then
if nixio.fs.stat("/etc/aredn_include/fixedmac." .. net) then
for line in io.lines("/etc/aredn_include/fixedmac." .. net)
do
local m = line:match("option%s+macaddr%s+(%S+)")
if m then
cfg[net .. "_mac"] = m
end
end
end
config = expand_vars(read_all("/etc/aredn_include/" .. net .. ".network.user"))
if not valid_config(config) then
print("Invalid config fragment: /etc/aredn_include/" .. net .. ".network.user")
os.exit(1)
end
else
-- generate a complete config
local vlan = nil
local ports = aredn.hardware.get_board_network_ifname(net):split(" ")
if net == "lan" then
-- If the LAN has be given a vlan to use (usually by a switch) we use that here,
-- otherwise we just use '3'
local lvlan = ports[1]:match("%.(%d+)$")
if lvlan then
vlan = lvlan .. ":t"
else
vlan = "3:u"
end
elseif net == "wan" then
-- wifi can be used for the WAN (we become a wifi client)
if cfg.wifi3_enable == "1" then
-- WAN uses wifi
wireless = true
ports = { "wlan0" }
for devname in nixio.fs.dir(ieee80211)
do
local hwmode = "11g"
if iwinfo.nl80211.freqlist(devname)[1].mhz > 5000 then
hwmode="11a"
end
if hwmode == cfg.wifi3_hwmode then
ports = { "wlan" .. devname:match("^phy(%d+)$") }
break
end
end
else
-- If the WAN has been given a vlan to use (usually 1) we use that here.
local wvlan = ports[1]:match("%.(%d+)$")
if wvlan then
vlan = wvlan .. ":t"
else
vlan = "4:u"
end
-- handle wan vlan override
for line in io.lines("/etc/config.mesh/_setup")
do
local wport, wvlan = line:match("^wan_intf = (%w+%.)(%d+)")
if wvlan then
vlan = wvlan .. ":t"
ports = { wport .. wvlan }
break
end
end
end
elseif net == "dtdlink" then
-- Always vlan 2
vlan = "2:t"
cfg.dtdlink_proto = "static"
cfg.dtdlink_mask = "255.0.0.0"
elseif net == "wifi" then
wireless = true
ports = { cfg.wifi_intf }
end
if vlan then
-- new school vlan configuration
local v, t = vlan:match("(.*):(.*)")
config = config .. "\nconfig bridge-vlan\n option device 'br0'\n option vlan '" .. v .. "'\n"
for _, port in ipairs(ports)
do
config = config .. " list ports '" .. port:gsub("%..*$", "") .. ":" .. t .. "'\n"
end
config = config .. "\n"
ports = { "br0." .. v }
end
local proto = cfg[net .. "_proto"] or ""
local ipaddr = cfg[net .. "_ip"] or ""
local netmask = cfg[net .. "_mask"] or ""
local mtu = cfg[net .. "_mtu"] or ""
local dns1 = ""
local dns2 = ""
if net == "lan" then
dns1 = cfg.wan_dns1 or ""
dns2 = cfg.wan_dns2 or ""
end
local gateway = cfg[net .. "_gw"] or ""
config = config .. "config device\n"
if not wireless then
config = config .. " option name 'br-" .. net .. "'\n option type 'bridge'\n"
elseif cfg.wifi_enable ~= "1" then
netmask = "255.255.255.255"
end
if nixio.fs.stat("/etc/aredn_include/fixedmac." .. net) then
config = config .. read_all("/etc/aredn_include/fixedmac." .. net)
end
if not wireless then
if #ports == 0 then
config = config .. " option bridge_empty '1'\n"
else
for _, port in ipairs(ports)
do
config = config .. " list ports '" .. port .. "'\n"
end
end
else
config = config .. " option name '" .. ports[1] .. "'\n"
if not ports[1]:match("^wlan") then
config = config .. " option type 'bridge'\n option bridge_empty '1'\n"
end
end
config = config .. "\nconfig interface " .. net .. "\n"
if wireless then
config = config .. " option device '" .. ports[1] .. "'\n"
else
config = config .. " option device 'br-" .. net .. "'\n"
end
if proto ~= "" then
config = config .. " option proto '" .. proto .. "'\n"
end
if mtu ~= "" then
config = config .. " option mtu '" .. mtu .. "'\n"
end
if ipaddr ~= "" then
config = config .. " option ipaddr '" .. ipaddr .. "'\n"
end
if netmask ~= "" then
config = config .. " option netmask '" .. netmask .. "'\n"
end
if dns1 ~= "" or dns2 ~= "" then
config = config .. " option dns '" .. (dns1 or "") .. (dns1 and dns2 and " " or "") .. (dns2 or "") .. "'\n"
end
if gateway ~= "" then
config = config .. " option gateway '" .. gateway .. "'\n"
end
end
cfg[net .. "_network_config"] = config
end
-- Generate the tunnel configurations
local tun_port = cm:get("vtun", "@options[0]", "port")
if tun_port then
cfg.tun_network_config = cfg.tun_network_config .. "config options\n\toption port '" .. tun_port .. "'\n\n"
end
local tun_start = cm:get("vtun", "@network[0]", "start")
local tun_dns = cm:get("vtun", "@network[0]", "dns")
if tun_start or tun_dns then
cfg.tun_network_config = cfg.tun_network_config .. "config network\n"
if tun_start then
cfg.tun_network_config = cfg.tun_network_config .. "\toption start '" .. tun_start .. "'\n"
end
if tun_dns then
cfg.tun_network_config = cfg.tun_network_config .. "\toption dns '" .. tun_dns .. "'\n"
end
cfg.tun_network_config = cfg.tun_network_config .. "\n"
end
local vtunclients = 0
cm:foreach("vtun", "client",
function(s)
if s.enabled == "1" then
cfg.tun_network_config = cfg.tun_network_config .. string.format("config client\n\toption enabled '1'\n\toption node '%s'\n\toption passwd '%s'\n\toption clientip '%s'\n\toption serverip '%s'\n\toption netip '%s'\n\n",
s.node:upper(), s.passwd, s.clientip, s.serverip, s.netip)
vtunclients = vtunclients + 1
end
end
)
local wgclients = 0
cm:foreach("wireguard", "client",
function(s)
if s.enabled == "1" then
local server_priv, _, _, client_pub = s.key:match("^(.+=)(.+=)(.+=)(.+=)$")
local addr, port = s.clientip:match("^(%d+%.%d+%.%d+%.%d+):(%d+)$")
cfg.wireguard_network_config = cfg.wireguard_network_config ..
string.format("config interface 'wgc%d'\n\toption proto 'wireguard'\n\toption private_key '%s'\n\toption nohostroute '1'\n\toption listen_port '%s'\n\tlist addresses '%s'\n\n",
wgclients, server_priv, port, addr)
cfg.wireguard_network_config = cfg.wireguard_network_config ..
string.format("config wireguard_wgc%d\n\toption public_key '%s'\n\toption persistent_keepalive '25'\n\tlist allowed_ips '0.0.0.0/0'\n\n",
wgclients, client_pub)
wgclients = wgclients + 1
end
end
)
local vtunservers = 0
local wgservers = 0
cm:foreach("vtun", "server",
function(s)
if s.enabled == "1" then
if s.netip:match(":") then
local server_pub, client_priv, client_pub = s.passwd:match("^(.+=)(.+=)(.+=)$")
local abc, d, p = s.netip:match("^(%d+%.%d+%.%d+)%.(%d+):(%d+)")
d = tonumber(d) + 1
cfg.wireguard_network_config = cfg.wireguard_network_config ..
string.format("config interface 'wgs%d'\n\toption proto 'wireguard'\n\toption private_key '%s'\n\toption nohostroute '1'\n\tlist addresses '%s'\n\n",
wgservers, client_priv, (abc .. "." .. d))
cfg.wireguard_network_config = cfg.wireguard_network_config ..
string.format("config wireguard_wgs%d\n\toption public_key '%s'\n\toption endpoint_host '%s'\n\toption endpoint_port '%s'\n\toption persistent_keepalive '25'\n\tlist allowed_ips '0.0.0.0/0'\n\n",
wgservers, server_pub, s.host, p)
wgservers = wgservers + 1
else
cfg.tun_network_config = cfg.tun_network_config ..
string.format("config server\n\toption enabled '1'\n\toption host '%s'\n\toption node '%s'\n\toption passwd '%s'\n\toption clientip '%s'\n\toption serverip '%s'\n\toption netip '%s'\n\n",
s.host, s.node:upper(), s.passwd, s.clientip, s.serverip, s.netip)
vtunservers = vtunservers + 1
end
end
end
)
-- Create the tunnel devices we need
local maxclients = 0
local maxservers = 0
-- No tunnel devices without the vtund app
if nixio.fs.stat("/usr/sbin/vtund") then
maxclients = 10 * math.ceil(vtunclients / 10)
maxservers = 10 * math.ceil(vtunservers / 10)
end
for dev = 50, 50 + maxclients + maxservers - 1
do
cfg.tun_devices_config = cfg.tun_devices_config ..
string.format("config interface 'tun%d'\n\toption ifname 'tun%d'\n\toption proto 'none'\n\n", dev, dev)
end
-- Not wireguard devices without the wg app
if not nixio.fs.stat("/usr/bin/wg") then
wgclients = 0
wgservers = 0
end
remove_all("/tmp/new_config")
nixio.fs.mkdir("/tmp/new_config")
for file in nixio.fs.glob("/etc/config.mesh/*")
do
local bfile = nixio.fs.basename(file)
if bfile == "vtun" or bfile == "wireguard" or bfile == "xlink" or bfile == "olsrd" or bfile == "firewall.user" or bfile:match("^_setup") or bfile:match("^aliases") then
-- Dont copy these
else
local f = io.open("/tmp/new_config/" .. bfile, "w")
if f then
f:write(expand_vars(read_all(file)))
f:close()
end
end
end
-- Tunnels
write_all("/tmp/new_config/vtun", expand_vars("<tun_network_config>"))
local nc = uci.cursor("/tmp/new_config")
-- append to firewall
local add_masq = false
local fw = io.open("/tmp/new_config/firewall", "a")
if fw then
if not is_null(cfg.dmz_mode) then
fw:write("\nconfig forwarding\n option src wifi\n option dest lan\n")
fw:write("\nconfig forwarding\n option src dtdlink\n option dest lan\n")
add_masq = true
else
fw:write("\nconfig 'include'\n option 'path' '/etc/firewall.natmode'\n option 'reload' '1'\n")
end
if nc:get("aredn", "@wan[0]", "olsrd_gw") == "1" then
fw:write("\nconfig forwarding\n option src wifi\n option dest wan\n")
fw:write("\nconfig forwarding\n option src dtdlink\n option dest wan\n")
end
if nixio.fs.access(portfile) then
for line in io.lines(portfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local dip = line:match("dmz_ip = (%w+)")
if dip and cfg.dmz_mode ~= 0 then
fw:write("\nconfig redirect\n option src wifi\n option proto tcp\n option src_dip " .. cfg.wifi_ip .. "\n option dest_ip " .. dip .. "\n")
fw:write("\nconfig redirect\n option src wifi\n option proto udp\n option src_dip " .. cfg.wifi_ip .. "\n option dest_ip " .. dip .. "\n")
else
local intf, type, oport, host, iport, enable = line:match("(.*):(.*):(.*):(.*):(.*):(.*)")
if enable == "1" then
local match = " option src_dport " .. oport .. "\n"
if type == "tcp" then
match = match .. " option proto tcp\n"
elseif type == "udp" then
match = match .. " option proto udp\n"
end
-- uci the host and then
-- set the inside port unless the rule uses an outside port range
host = "option dest_ip " .. host .. "\n"
if not oport:match("-") then
host = host .. " option dest_port " .. iport .. "\n"
end
if not is_null(cfg.dmz_mode) and intf == "both" then
intf = "wan"
end
if intf == "both" then
fw:write("\nconfig redirect\n option src wifi\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
fw:write("\nconfig redirect\n option src dtdlink\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
fw:write("config redirect\n option src wan\n " .. match .. " " .. host .. "\n")
elseif intf == "wifi" and is_null(cfg.dmz_mode) then
fw:write("\nconfig redirect\n option src dtdlink\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
fw:write("\nconfig redirect\n option src wifi\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
elseif intf == "wan" then
fw:write("\nconfig redirect\n option src dtdlink\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
fw:write("config redirect\n option src wan\n " .. match .. " " .. host .. "\n")
end
end
end
end
end
end
fw:close();
end
if add_masq then
nc:set("firewall", "@zone[2]", "masq", "0")
nc:commit("firewall")
end
-- setup node lan dhcp
local function load_dhcp_tags(dhcptagsfile)
local dhcp_tags = {}
for line in io.lines(dhcptagsfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local name, condition, pattern = line:match("(%S+)%s+(%S+)%s+(.*)")
if pattern then
table.insert(get_subtable(dhcp_tags, condition),
{name = name, pattern = pattern})
end
end
end
return dhcp_tags
end
local function load_dhcp_options(dhcpoptionsfile)
local dhcp_options = {}
if nixio.fs.access(dhcpoptionsfile) then
for line in io.lines(dhcpoptionsfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local tag, force, opt_num, opt_val = line:match("(%S*)%s+(%w+)%s+(%d+)%s+(.*)")
if opt_val then
local by_tag = get_subtable(dhcp_options, tag)
if tag == "" and force == FORCED then
force = UNFORCED -- force is unsupported for untagged options
end
table.insert(get_subtable(by_tag, force),
{num = opt_num, val = opt_val})
end
end
end
end
return dhcp_options
end
local function option_item(tag, option)
local parts = {}
if tag ~= "" then
table.insert(parts, "tag:" .. tag)
end
table.insert(parts, option.num)
table.insert(parts, option.val)
return table.concat(parts, ",")
end
local function create_classifying_section(condition, cond_list)
for i, props in ipairs(cond_list)
do
local secname = condition
local pat = props.pattern
if (condition == "subscriberid") then
secname = "subscrid"
pat = '"' .. pat:gsub('"', '\\"') .. '"'
print(props.pattern, "->", pat)
end
nc:add("dhcp", secname)
local section_ref = string.format("@%s[%d]", secname, i-1)
nc:set("dhcp", section_ref, "networkid", props.name)
nc:set("dhcp", section_ref, condition, pat)
end
end
local function create_tag_section(tag, force, optlist)
if tag ~= "" then
nc:set("dhcp", tag, "tag")
if force == FORCED then
nc:set("dhcp", tag, "force", 1)
end
local options = {}
for _, option in ipairs(optlist) do
table.insert(options, option_item("", option)) -- tag is on section
end
nc:set("dhcp", tag, "dhcp_option", options)
end
end
do
local dhcp_option_list = {}
if nc:get("aredn", "@wan[0]", "lan_dhcp_route") == "1" or nc:get("aredn", "@wan[0]", "lan_dhcp_defaultroute") == "1" then
-- Provide stateless routes and default route
table.insert(dhcp_option_list, "121,10.0.0.0/8," .. cfg.lan_ip .. ",0.0.0.0/0," .. cfg.lan_ip)
table.insert(dhcp_option_list, "249,10.0.0.0/8," .. cfg.lan_ip .. ",0.0.0.0/0," .. cfg.lan_ip)
else
-- Provide stateless routes to the mesh, and a blank default route (option 3 has no values) to
-- suppress default route being sent
table.insert(dhcp_option_list, "121,10.0.0.0/8," .. cfg.lan_ip)
table.insert(dhcp_option_list, "249,10.0.0.0/8," .. cfg.lan_ip)
table.insert(dhcp_option_list, "3")
end
local advanced_options = load_dhcp_options(dhcpoptionsfile)
if nixio.fs.access(dhcptagsfile) then
for condition, cond_list in pairs(load_dhcp_tags(dhcptagsfile))
do
create_classifying_section(condition, cond_list)
end
end
for tag, forcelist in pairs(advanced_options)
do
if forcelist then
if tag == "" -- tag-section name cannot be empty
or tablesize(forcelist) > 1 -- section name must be unique
then -- place unforced options in the anonymous section
for _, option in ipairs(forcelist[UNFORCED]) do
table.insert(dhcp_option_list, option_item(tag, option))
end
forcelist[UNFORCED] = nil
end
for force, optlist in pairs(forcelist)
do
create_tag_section(tag, force, optlist)
end
end
end
if #dhcp_option_list > 0 then
nc:set("dhcp", "@dhcp[0]", "dhcp_option", dhcp_option_list)
end
end
nc:commit("dhcp")
-- generate the wireless config file
local config = ""
local ifacecount = aredn.hardware.get_radio_count()
local devpaths = {}
for dev = 0, ifacecount - 1
do
local devname = "phy" .. dev
local radio = "radio" .. dev
local wlan = "wlan" .. dev
local devpath = nixio.fs.realpath(ieee80211 .. nixio.fs.readlink(ieee80211 .. devname)):match("^/sys/devices/(.*)/ieee802.*$")
if devpath:match("^platform.*/pci.*") then
devpath = devpath:match("^platform/(.*)")
end
local devpathc = devpaths[devpath] or 0
devpaths[devpath] = devpathc + 1
if devpathc > 0 then
devpath = devpath .. "+" .. devpathc
end
local is_mesh_rf = false
local htmode = "HT20"
local disabled = "0"
local chanbw = nil
local country = nil
local channel = nil
local distance = nil
local hwmode = "11g"
if iwinfo.nl80211.freqlist(devname)[1].mhz > 5000 then
hwmode="11a"
end
local network = nil
local mode = nil
local ssid = nil
local encryption = nil
local key = nil
if wlan == cfg.wifi_intf then
-- mesh RF adhoc configuration
is_mesh_rf = true
channel = cfg.wifi_channel
chanbw = cfg.wifi_chanbw
country = "HX"
distance = cfg.wifi_distance
ssid = cfg.wifi_ssid .. "-" .. chanbw .. "-v3"
mode = "adhoc"
encryption = "none"
network = "wifi"
elseif cfg.wifi2_enable == "1" and (ifacecount == 1 or (ifacecount > 1 and hwmode == cfg.wifi2_hwmode)) then
-- lan AP interface
channel = cfg.wifi2_channel
ssid = h2s(cfg.wifi2_ssid)
mode = "ap"
encryption = cfg.wifi2_encryption
key = h2s(cfg.wifi2_key)
network = "lan"
elseif cfg.wifi3_enable == "1" and (ifacecount == 1 or (ifacecount > 1 and hwmode == cfg.wifi3_hwmode)) then
-- wan client
ssid = h2s(cfg.wifi3_ssid)
mode = "sta"
if cfg.wifi3_key and cfg.wifi3_key ~= "" then
encryption = "psk2"
key = h2s(cfg.wifi3_key)
else
encryption = "none"
end
network = "wan"
htmode = nil
else
disabled = "1"
end
config = config .. "config wifi-device '" .. radio .. "'\n option type 'mac80211'\n"
config = config .. " option disabled '" .. disabled .. "'\n"
if channel then
config = config .. " option channel '" .. channel .. "'\n"
end
if chanbw then
config = config .. " option chanbw '" .. chanbw .. "'\n"
end
if country then
config = config .. " option country '" .. country .. "'\n"
end
if distance then
config = config .. " option distance '" .. distance .. "'\n"
end
config = config .. " option hwmode '" .. hwmode .. "'\n"
if htmode then
config = config .. " option htmode '" .. htmode .. "'\n"
end
config = config .. " option path '" .. devpath .. "'\n\n"
config = config .. "config wifi-iface\n"
config = config .. " option ifname '" .. wlan .. "'\n"
config = config .. " option device '" .. radio .. "'\n"
if network then
config = config .. " option network '" .. network .. "'\n"
end
if mode then
config = config .. " option mode '" .. mode .. "'\n"
end
if ssid then
config = config .. " option ssid '" .. ssid .. "'\n"
end
if encryption then
config = config .. " option encryption '" .. encryption .. "'\n"
end
if key then
config = config .. " option key '" .. key .. "'\n"
end
config = config .. "\n"
if is_mesh_rf and wifi_mon_enable then
config = config .. "config wifi-iface\n"
config = config .. " option ifname '" .. wlan .. "-1'\n"
config = config .. " option device '" .. radio .. "'\n"
config = config .. " option network 'wifi_mon'\n option mode 'monitor'\n\n"
end
end
write_all("/tmp/new_config/wireless", config)
-- indicate whether lan is running in dmz mode
nc:set("aredn", "@dmz[0]", "mode", cfg.dmz_mode)
nc:commit("aredn")
-- generate the host and ethers files
local h = io.open("/etc/hosts", "w")
local e = io.open("/etc/ethers", "w")
if h and e then
h:write("# automatically generated file - do not edit\n")
h:write("# use /etc/hosts.user for custom entries\n")
h:write("127.0.0.1\tlocalhost\n")
if not is_null(cfg.wifi_ip) then
h:write(cfg.lan_ip .. "\tlocalnode\n")
h:write(cfg.wifi_ip .. "\t" .. node .. " " .. tactical .. "\n")
else
h:write(cfg.lan_ip .. "\tlocalnode " .. node .. " " .. tactical .. "\n")
end
if not is_null(cfg.dtdlink_ip) then
h:write(cfg.dtdlink_ip .. "\tdtdlink." .. node .. ".local.mesh dtdlink." .. node .."\n")
end
if is_null(cfg.dmz_mode) then
h:write(decimal_to_ip(ip_to_decimal(cfg.lan_ip) + 1) .. "\tlocalap\n")
end
e:write("# automatically generated file - do not edit\n")
e:write("# use /etc/ethers.user for custom entries\n")
local netaddr = nixio.bit.band(ip_to_decimal(cfg.lan_ip), ip_to_decimal(cfg.lan_mask))
if nixio.fs.access(dhcpfile) then
for line in io.lines(dhcpfile)
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local mac, ip, host, noprop = line:match("(%S+)%s+(%S+)%s+(%S+)%s*(%S*)")
if mac and ip and host and noprop then
ip = decimal_to_ip(netaddr + ip)
if validate_same_subnet(ip, cfg.lan_ip, cfg.lan_mask) and validate_ip_netmask(ip, cfg.lan_mask) then
h:write(ip .. "\t" .. host .. " " .. noprop .. "\n")
e:write(mac .. "\t" .. ip .. "\n")
end
end
end
end
end
-- aliases need to ba added to /etc/hosts or they will now show up on the localnode
-- nor will the services thehy offer
-- also add a comment to the hosts file so we can display the aliases differently if needed
local f = io.open(aliasfile, "r")
if f then
for line in f:lines()
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local ip, host = line:match("(%S+)%s+(%S+)")
if ip then
if host:match("%.") and not host:match("%.local%.mesh$") then
host = host .. ".local.mesh"
end
h:write(ip .. "\t" .. host .. " #ALIAS\n")
end
end
end
f:close()
end
h:write("\n")
if nixio.fs.access("/etc/hosts.user", "r") then
for line in io.lines("/etc/hosts.user")
do
h:write(line .. "\n")
end
end
if nixio.fs.access("/etc/ethers.user", "r") then
for line in io.lines("/etc/ethers.user")
do
e:write(line .. "\n")
end
end
h:close()
e:close()
end
-- generate olsrd.conf
if nixio.fs.access("/etc/config.mesh/olsrd", "r") then
local of = io.open("/tmp/new_config/olsrd", "w")
if of then
for line in io.lines("/etc/config.mesh/olsrd")
do
if line:match("<olsrd_bridge>") then
if is_null(cfg.olsrd_bridge) then
line = line:gsub("<olsrd_bridge>", '"wifi" "lan"')
else
line = line:gsub("<olsrd_bridge>", '"lan"')
end
elseif line:match("^[^#]") then
for parm in line:gmatch("<([^%s]*)>")
do
line = line:gsub("<" .. parm .. ">", cfg[parm])
end
end
of:write(line .. "\n")
end
if not is_null(cfg.dmz_mode) then
local a, b, c, d = cfg.dmz_lan_ip:match("(.*)%.(.*)%.(.*)%.(.*)")
of:write(string.format("\nconfig Hna4\n\toption netaddr %s.%s.%s.%d\n\toption netmask 255.255.255.%d\n\n", a, b, c, d - 1, nixio.bit.band(255 * 2 ^ cfg.dmz_mode, 255)))
end
if cfg.wifi_enable ~= "1" and not is_null(cfg.wifi_ip) then
of:write(string.format("config Hna4\n\toption netaddr %s\n\toption netmask 255.255.255.255\n\n", cfg.wifi_ip))
end
if is_supernode then
of:write("config Hna4\n\toption netaddr 10.0.0.0\n\toption netmask 255.0.0.0\n\n")
end
if nixio.fs.stat("/etc/config.mesh/xlink") then
uci.cursor("/etc/config.mesh"):foreach("xlink", "interface",
function(section)
if section.netmask ~= "255.255.255.255" then
local addr = decimal_to_ip(nixio.bit.band(ip_to_decimal(section.ipaddr), ip_to_decimal(section.netmask)))
of:write(string.format("config Hna4\n\toption netaddr %s\n\toption netmask %s\n\n", addr, section.netmask))
end
end
)
end
if nc:get("aredn", "@wan[0]", "olsrd_gw") == "1" then
of:write("config LoadPlugin\n\toption library 'olsrd_dyn_gw.so.0.5'\n\toption Interval '60'\n\tlist Ping '8.8.8.8'\n\tlist Ping '8.8.4.4'\n\n")
end
of:write("config LoadPlugin 'nameservice'\n\toption library 'olsrd_nameservice.so.0.4'\n\toption interval '30'\n\toption timeout '300'\n\toption sighup_pid_file '/var/run/dnsmasq/dnsmasq.pid'\n\toption name_change_script '/usr/local/bin/olsrd-namechange'\n")
aredn.services.reset_validation()
local names, hosts, services = aredn.services.get()
for _, name in ipairs(names)
do
of:write("\tlist name '" .. name .. "'\n")
end
for _, host in ipairs(hosts)
do
if host.host ~= "" then
of:write("\tlist hosts '" .. host.ip .. " " .. host.host .. "'\n")
end
end
for _, service in ipairs(services)
do
of:write("\tlist service '" .. service .. "'\n")
end
of:write("\n")
-- add all the tunnel interfaces
if vtunclients + vtunservers + wgclients + wgservers > 0 then
of:write("config Interface\n")
for dev = 50, 50 + vtunclients - 1
do
of:write("\tlist interface 'tun" .. dev .. "'\n")
end
for dev = 50 + maxclients, 50 + maxclients + vtunservers - 1
do
of:write("\tlist interface 'tun" .. dev .. "'\n")
end
if wgclients > 0 then
for dev = 0, wgclients - 1
do
of:write("\tlist interface 'wgc" .. dev .. "'\n")
end
end
if wgservers > 0 then
for dev = 0, wgservers - 1
do
of:write("\tlist interface 'wgs" .. dev .. "'\n")
end
end
of:write("\toption Ip4Broadcast '255.255.255.255'\n")
local tun_weight = tonumber(nc:get("aredn", "@tunnel[0]", "weight") or 1)
local is_supernode = nc:get("aredn", "@supernode[0]", "enable") == "1"
if not tun_weight or tun_weight < 1 or is_supernode then
of:write("\toption Mode 'ether'\n")
elseif tun_weight > 1 then
of:write("\toption LinkQualityMult 'default " .. (1 / tun_weight) .. "'\n")
end
of:write("\toption HelloInterval '" .. cfg.hello_interval .. "'\n")
of:write("\toption TcInterval '" .. cfg.tc_interval .. "'\n")
of:write("\toption MidInterval '" .. cfg.mid_interval .. "'\n")
of:write("\toption HnaInterval '" .. cfg.hna_interval .. "'\n")
end
nc:set("aredn", "@tunnel[0]", "maxclients", maxclients)
nc:set("aredn", "@tunnel[0]", "maxservers", maxservers)
nc:commit("aredn")
-- add xlink interfaces
if nixio.fs.stat("/etc/config.mesh/xlink") then
uci.cursor("/etc/config.mesh"):foreach("xlink", "interface",
function(section)
of:write("\nconfig Interface\n\tlist interface '" .. section[".name"] .. "'\n")
if section.peer then
of:write("\toption Ip4Broadcast '" .. section.peer .. "'\n")
else
of:write("\toption Ip4Broadcast '255.255.255.255'\n")
end
local weight = tonumber(section.weight or 0)
if weight then
if weight > 1 then
of:write("\toption LinkQualityMult 'default " .. (1 / weight) .. "'\n")
elseif weight < 1 then
of:write("\toption Mode 'ether'\n")
end
else
of:write("\toption Mode 'ether'\n")
end
of:write("\toption HelloInterval '" .. cfg.hello_interval .. "'\n")
of:write("\toption TcInterval '" .. cfg.tc_interval .. "'\n")
of:write("\toption MidInterval '" .. cfg.mid_interval .. "'\n")
of:write("\toption HnaInterval '" .. cfg.hna_interval .. "'\n")
end
)
end
of:close()
end
end
-- Update user firewall
local _, diff = filecopy("/etc/config.mesh/firewall.user", "/etc/firewall.user", true)
if diff then
changes.firewall = true
end
-- Update services script
local sf = io.open("/tmp/local_services", "w")
if sf then
sf:write("#!/bin/sh\n")
if cfg.wifi_proto ~= "disabled" then
local wifi_channel = tonumber(cfg.wifi_channel)
if is_null(cfg.wifi_txpower) or tonumber(cfg.wifi_txpower) > aredn.hardware.wifi_maxpower(cfg.wifi_intf, wifi_channel) then
cfg.wifi_txpower = aredn.hardware.wifi_maxpower(cfg.wifi_intf, wifi_channel)
elseif tonumber(cfg.wifi_txpower) < 1 then
cfg.wifi_txpower = 1
end
if cfg.wifi_enable == "1" then
sf:write("/usr/sbin/iw dev " .. cfg.wifi_intf .. " set txpower fixed " .. cfg.wifi_txpower .. "00\n")
end
if not is_null(cfg.aprs_lat) and not is_null(cfg.aprs_lon) then
nc:set("aredn", "@location[0]", "lat", cfg.aprs_lat)
nc:set("aredn", "@location[0]", "lon", cfg.aprs_lon)
nc:commit("aredn")
end
end
sf:close()
local _, diff = filecopy("/tmp/local_services", "/etc/local/services", true)
if diff then
changes.localservices = true
end
os.remove("/tmp/local_services")
nixio.fs.chmod("/etc/local/services", "777")
end
---
--- Make it official
---
-- Handle special cases
local config_special = {
lqm_enable = c:get("aredn", "@lqm[0]", "enable"),
tunnel_weight = c:get("aredn", "@tunnel[0]", "weight"),
supernode_enable = c:get("aredn", "@supernode[0]", "enable"),
watchdog_enable = c:get("aredn", "@watchdog[0]", "enable"),
watchdog_pings = c:get("aredn", "@watchdog[0]", "ping_addresses"),
watchdog_daily = c:get("aredn", "@watchdog[0]", "daily"),
web_access = c:get("aredn", "@wan[0]", "web_access"),
ssh_access = c:get("aredn", "@wan[0]", "ssh_access"),
telnet_access = c:get("aredn", "@wan[0]", "telnet_access"),
wifi_mode_0 = c:get("wireless", "@wifi-iface[0]", "mode"),
wifi_mode_1 = c:get("wireless", "@wifi-iface[1]", "mode")
}
local nfiles = {}
for file in nixio.fs.glob("/tmp/new_config/*")
do
nfiles[nixio.fs.basename(file)] = true
end
-- Remove files we no longer need
for file in nixio.fs.glob("/etc/config/*")
do
if not nfiles[nixio.fs.basename(file)] then
nixio.fs.remove(file)
changes.reboot = true
end
end
for file, _ in pairs(nfiles)
do
local ffile = "/tmp/new_config/" .. file
local _, diff = filecopy(ffile, "/etc/config/" .. file, true)
if diff then
if file == "system" then
changes.log = true
changes.system = true
elseif file == "aredn" then
local oc = uci:cursor()
if oc:get("aredn", "@lqm[0]", "enable") ~= config_special.lqm_enable then
changes.manager = true
end
if oc:get("aredn", "@tunnel[0]", "weight") ~= config_special.tunnel_weight then
changes.olsrd = true
end
if oc:get("aredn", "@supernode[0]", "enable") ~= config_special.supernode_enable then
changes.reboot = true
end
if oc:get("aredn", "@watchdog[0]", "enable") ~= config_special.watchdog_enable then
changes.reboot = true
end
if oc:get("aredn", "@watchdog[0]", "ping_addresses") ~= config_special.watchdog_pings then
changes.manager = true
end
if oc:get("aredn", "@watchdog[0]", "daily") ~= config_special.watchdog_daily then
changes.manager = true
end
if oc:get("aredn", "@wan[0]", "web_access") ~= config_special.web_access or oc:get("aredn", "@wan[0]", "ssh_access") ~= config_special.ssh_access or oc:get("aredn", "@wan[0]", "telnet_access") ~= config_special.telnet_access then
changes.firewall = true
end
elseif file == "network" then
changes.network = true
changes.tunnels = true -- restarting network devices requires tunnels to restart
elseif file == "dhcp" then
changes.dnsmasq = true
elseif file == "olsrd" then
changes.olsrd = true
elseif file == "firewall" then
changes.firewall = true
elseif file == "wireless" then
local oc = uci:cursor()
if oc:get("wireless", "@wifi-iface[0]", "mode") ~= config_special.wifi_mode_0 or oc:get("wireless", "@wifi-iface[1]", "mode") ~= config_special.wifi_mode_1 then
changes.reboot = true
else
changes.wireless = true
end
elseif file == "vtun" then
changes.tunnels = true
else
changes.reboot = true
end
end
nixio.fs.remove(ffile)
end
nixio.fs.rmdir("/tmp/new_config")
aredn.info.set_nvram("config", "mesh")
aredn.info.set_nvram("node", node)
aredn.info.set_nvram("tactical", tactical)
-- Set file flags for whichever parts of the system require a restart/reboot
for k, v in pairs(changes)
do
if v then
nixio.fs.mkdir("/tmp/reboot-required")
io.open("/tmp/reboot-required/" .. k, "w"):close()
end
end
return 0