From f9b032aca1796d93149d7e6edd427aa4c44461bc Mon Sep 17 00:00:00 2001 From: Tim Wilkinson Date: Tue, 16 May 2023 19:29:20 -0700 Subject: [PATCH] Advanced networking tab (#834) * Advanced networking tab * Add page protection * Disable WAN VLAN option in advanced config when advanced networking available --- files/etc/httpd.conf | 2 +- .../lua/aredn/nav/admin/65advancednetwork.lua | 4 + files/www/cgi-bin/advancedconfig | 5 + files/www/cgi-bin/advancednetwork | 680 ++++++++++++++++++ 4 files changed, 690 insertions(+), 1 deletion(-) create mode 100644 files/usr/lib/lua/aredn/nav/admin/65advancednetwork.lua create mode 100755 files/www/cgi-bin/advancednetwork diff --git a/files/etc/httpd.conf b/files/etc/httpd.conf index 8f4655cd..a66f5158 100644 --- a/files/etc/httpd.conf +++ b/files/etc/httpd.conf @@ -6,4 +6,4 @@ /cgi-bin/supporttool:root:$p$root /cgi-bin/advancedconfig:root:$p$root /cgi-bin/apiprotected:root:$p$root - +/cgi-bin/advancednetwork:root:$p$root diff --git a/files/usr/lib/lua/aredn/nav/admin/65advancednetwork.lua b/files/usr/lib/lua/aredn/nav/admin/65advancednetwork.lua new file mode 100644 index 00000000..175ff0fc --- /dev/null +++ b/files/usr/lib/lua/aredn/nav/admin/65advancednetwork.lua @@ -0,0 +1,4 @@ +local board = aredn.hardware.get_board_type() +if board == "mikrotik,hap-ac2" or board == "mikrotik,hap-ac3" then + return { href = "advancednetwork", display = "Advanced Network" } +end diff --git a/files/www/cgi-bin/advancedconfig b/files/www/cgi-bin/advancedconfig index c9b2ed8b..25cb656a 100755 --- a/files/www/cgi-bin/advancedconfig +++ b/files/www/cgi-bin/advancedconfig @@ -507,6 +507,11 @@ function hasUSB() end function supportsVLANChange() + -- If we support advanced networking, we dont provide this option here + local board = aredn.hardware.get_board_type() + if board == "mikrotik,hap-ac2" or board == "mikrotik,hap-ac3" then + return false + end local stat = nixio.fs.stat("/etc/aredn_include/swconfig") -- We always support VLAN changing on devices without switches if not (stat and stat.size > 0) then diff --git a/files/www/cgi-bin/advancednetwork b/files/www/cgi-bin/advancednetwork new file mode 100755 index 00000000..6cfd21ae --- /dev/null +++ b/files/www/cgi-bin/advancednetwork @@ -0,0 +1,680 @@ +#!/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 = { "wan", "lan1", "lan2", "lan3", "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 _, 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
" .. port .. "
") + 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()