#!/usr/bin/lua --[[ 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 . 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.hardware") require("aredn.http") local html = require("aredn.html") local aredn_info = require("aredn.info") local base = "/etc/aredn_include/" local xlink_file = "/etc/config.mesh/xlink" local default_5_port_layout = { ports = { [1] = "wan", [2] = "lan1", [3] = "lan2", [4] = "lan3", [5] = "lan4" } } local layouts = { ["mikrotik,hap-ac2"] = default_5_port_layout, ["mikrotik,hap-ac3"] = default_5_port_layout } local default_5_port_config = { { name = "dtdlink", vlan = 2, ports = { lan4 = { tagged = true } }, tagged = true }, { name = "lan", vlan = 3, ports = { lan1 = { tagged = false }, lan2 = { tagged = false }, lan3 = { tagged = false } }, tagged = false }, { name = "wan", vlan = 1, ports = { wan = { tagged = false } }, tagged = false } } local default_configs = { ["mikrotik,hap-ac2"] = default_5_port_config, ["mikrotik,hap-ac3"] = default_5_port_config } function read_user_config(network) local file = base .. network .. ".network.user" if not nixio.fs.stat(file) then return nil end local config = { name = network, vlan = nil, ports = {}, tagged = false } local invlan = false for line in io.lines(file) do if line:match("^config%s+bridge%-vlan") then invlan = true elseif line:match("^config") then invlan = false elseif invlan then local m m = line:match("option%s+vlan%s+'(%d+)'") if m then config.vlan = tonumber(m) end m = line:match("list%s+ports%s+'(%S+):u'") if m then config.ports[m] = { tagged = false } end m = line:match("list%s+ports%s+'(%S+):t'") if m then config.ports[m] = { tagged = true } config.tagged = true end end end return config end function read_xlink_config() if not nixio.fs.stat(xlink_file) then return {} end local configs = {} local config = {} local type = "none" for line in io.lines(xlink_file) do if line:match("^config%s+bridge%-vlan") then type = "vlan" config = { name = nil, vlan = nil, ipaddr = nil, peer = nil, weight = 0, port = nil } configs[#configs + 1] = config elseif line:match("^config%s+interface") then type = "interface" config.name = line:match("^config%s+interface%s+'(%S+)'") elseif type == "vlan" then local m m = line:match("option%s+vlan%s+'(%d+)'") if m then config.vlan = tonumber(m) end m = line:match("list%s+ports%s+'(%S+):t'") if m then config.port = m end elseif type == "interface" then local m m = line:match("option%s+ipaddr%s+'([%d%.]+)'") if m then config.ipaddr = m end m = line:match("option%s+peer%s+'([%d%.]+)'") if m then config.peer = m end m = line:match("option%s+weight%s+'([%d]+)'") if m then config.weight = tonumber(m) end end end return configs end function write_user_config(config, variables) local network = config.name local f = io.open(base .. network .. ".network.user", "w") f:write("# Generated by advancednetwork\n") f:write("\nconfig bridge-vlan\n") f:write("\toption device 'br0'\n") f:write("\toption vlan '" .. config.vlan .. "'\n") for name, port in pairs(config.ports) do f:write("\tlist ports '" .. name .. (port.tagged and ":t" or ":u") .. "'\n") end f:write("\nconfig device\n") f:write("\toption name 'br-" .. network .. "'\n") f:write("\toption type 'bridge'\n") f:write("\toption macaddr '<" .. network .. "_mac>'\n") f:write("\tlist ports 'br0." .. config.vlan .. "'\n") f:write("\nconfig interface " .. network .. "\n") f:write("\toption device 'br-" .. network .. "'\n") if network == "dtdlink" then f:write("\toption proto 'static'\n") f:write("\toption ipaddr '<" .. network .. "_ip>'\n") f:write("\toption netmask '255.0.0.0'\n") else if variables[network .. "_proto"] and variables[network .. "_proto"] ~= "" then f:write("\toption proto '<" .. network .. "_proto>'\n") end if variables[network .. "_ip"] and variables[network .. "_ip"] ~= "" then f:write("\toption ipaddr '<" .. network .. "_ip>'\n") end if variables[network .. "_mask"] and variables[network .. "_mask"] ~= "" then f:write("\toption netmask '<" .. network .. "_mask>'\n") end end if network == "lan" then f:write("\toption dns ' '\n") end if network == "wan" and variables.wan_gw and variables.wan_gw ~= "" then f:write("\toption gateway ''\n") end f:close() end function update_legacy_wan_vlan(config) local lines = {} for line in io.lines("/etc/config.mesh/_setup") do if not line:match("^wan_intf = ") then lines[#lines + 1] = line end end for name, port in pairs(config.ports) do if port.tagged then local wan_intf = "" for dev in aredn.hardware.get_board_network_ifname("wan"):gmatch("%S+") do wan_intf = wan_intf .. " " .. dev:match("^([^%.]+)") .. "." .. config.vlan end if wan_intf ~= "" then lines[#lines + 1] = "wan_intf =" .. wan_intf end break end end local f = io.open("/etc/config.mesh/_setup", "w") if f then for _, line in ipairs(lines) do f:write(line .. "\n") end f:close() end end function write_xlink_config(configs) local f = io.open(xlink_file, "w") f:write("# Generated by advancednetwork\n") for _, config in ipairs(configs) do f:write("\nconfig bridge-vlan\n") f:write("\toption device 'br0'\n") f:write("\toption vlan '" .. config.vlan .. "'\n") f:write("\tlist ports '" .. config.port .. ":t'\n") f:write("\nconfig interface '" .. config.name .. "'\n") f:write("\toption ifname 'br0." .. config.vlan .. "'\n") f:write("\toption proto 'static'\n") f:write("\toption ipaddr '" .. config.ipaddr .. "'\n") f:write("\toption netmask '255.255.255.252'\n") f:write("\toption peer '" .. config.peer .. "'\n") f:write("\toption weight '" .. config.weight .. "'\n") end f:close() end function reboot() local node = aredn_info.get_nvram("node") if node == "" then node = "Node" end http_header() html.header(node .. " rebooting", false) html.print("") html.print("
") html.print("

" .. node .. " is rebooting


") html.print("

Your browser should return to this node in 60 seconds.

") html.print("If something goes astray you can try to connect with

") html.print("http://localnode.local.mesh:8080/
") if node ~= "Node" then html.print("or
http://" .. node .. ".local.mesh:8080/

") end html.print("
") http_footer() os.execute("reboot >/dev/null 2>&1") os.exit() end local get_board_type = aredn.hardware.get_board_type() local layout = layouts[get_board_type] local configs = {} if os.getenv("REQUEST_METHOD") == "POST" then require('luci.http') local request = luci.http.Request(nixio.getenv(), function() local v = io.read(1024) if not v then io.close() end return v end ) local params = request:formvalue() if params.op == "save" then if params.configs then local variables = {} for line in io.lines("/etc/config.mesh/_setup") do if not (line:match("^%s*#") or line:match("^%s*$")) then local k, v = line:match("^([^%s]*)%s*=%s*(.*)%s*$") variables[k] = v end end local configs = luci.jsonc.parse(params.configs) for _, config in ipairs(configs) do write_user_config(config, variables) if config.name == "wan" then update_legacy_wan_vlan(config) 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() end elseif params.op == "defaults" then for _, network in ipairs({ "dtdlink", "lan", "wan" }) do 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() elseif params.op == "reboot" then reboot() end end for _, network in ipairs({ "dtdlink", "lan", "wan" }) do local config = read_user_config(network) if not config then for _, default_config in ipairs(default_configs[get_board_type]) do if default_config.name == network then config = default_config break end end end configs[#configs + 1] = config end local xlinks = read_xlink_config() http_header() html.header("Advanced Network Configuration") html.print("
") html.alert_banner() html.print([[ ]]) html.print([[ ]]) html.print("
") -- navbar html.navbar_admin("advancednetwork") html.print([[
]]) if nixio.fs.stat("/tmp/reboot-required") then html.print("
Reboot is required for changes to take effect
") end html.print([[]]) html.print("") html.print("") for pos, port in ipairs(layout.ports) do html.print("") end html.print("") for _, config in ipairs(configs) do html.print("") html.print("") for _, port in ipairs(layout.ports) do local checked = config.ports[port] and "checked" or "" html.print("") end html.print("") end html.print([[
Ports
" .. pos .. "
") html.print("
" .. config.name .. "
") if config.name == "wan" then local value = not config.vlan or config.vlan < 4 and "" or config.vlan html.print("
vlan:
") elseif config.name == "dtdlink" then html.print("
vlan: 2
") else html.print("
vlan: Untagged
") end html.print("
]]) html.print([[]]) html.print([[]]) for _, xlink in ipairs(xlinks) do html.print("") html.print("") html.print("") html.print("") html.print("") end html.print([[]]) html.print([[]]) print("
") html.footer() html.print("") http_footer()