#! /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 . 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 -- helpers end 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) elseif parm == "MAC2" then line = line:gsub("", mac2) elseif parm == "DTDMAC" then line = line:gsub("", 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):gsub("", mac2):gsub("", 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("")) 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 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 local cond_table = dhcp_tags[condition] if not cond_table then cond_table = {} dhcp_tags[condition] = cond_table end table.insert(cond_table, {name = name, pattern = pattern}) end end end return dhcp_tags end function load_dhcp_options(dhcpoptionsfile) local dhcp_options = {} 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 table.insert(dhcp_options, { tag = tag, force = force == "force", num = opt_num, val = opt_val}) end end end return dhcp_options end if nixio.fs.access(dhcptagsfile) then for condition, cond_table in pairs(load_dhcp_tags(dhcptagsfile)) do for i, props in ipairs(cond_table) do nc:add("dhcp", condition) nc:set("dhcp", string.format("@%s[%d]", condition, i-1), "networkid", props.name) nc:set("dhcp", string.format("@%s[%d]", condition, i-1), condition, props.pattern) end 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 if nixio.fs.access(dhcpoptionsfile) then local forced_advanced_options = {} for _, option in ipairs(load_dhcp_options(dhcpoptionsfile)) do local parts = {} if option.tag ~= "" then table.insert(parts, "tag:" .. option.tag) end table.insert(parts, option.num) table.insert(parts, option.val) table.insert(option.force and forced_advanced_options or dhcp_option_list, table.concat(parts, ",")) end if #forced_advanced_options > 0 then nc:set("dhcp", "@dhcp[0]", "dhcp_option_force", forced_advanced_options) 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("") then if is_null(cfg.olsrd_bridge) then line = line:gsub("", '"wifi" "lan"') else line = line:gsub("", '"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"), 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 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