#!/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")
math.randomseed(os.time())
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 default_3_port_layout = { ports = { [1] = "lan2", [2] = "lan1", [3] = "wan" } }
local default_1_port_layout = { ports = { [1] = "eth0" } }
local layouts = {
["mikrotik,hap-ac2"] = default_5_port_layout,
["mikrotik,hap-ac3"] = default_5_port_layout,
["qemu-standard-pc-i440fx-piix-1996"] = default_1_port_layout,
["VMware, Inc. VMware Virtual Platform"] = default_1_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_3_port_config = {
{
name = "dtdlink",
vlan = 2,
ports = { lan2 = { tagged = true } },
tagged = true
},
{
name = "lan",
vlan = 3,
ports = { lan1 = { 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,
["glinet,gl-b1300"] = default_3_port_config,
["qemu-standard-pc-i440fx-piix-1996"] = nil,
}
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)
if config.vlan == 2 or config.vlan >= 4 then
config.tagged = true
else
config.tagged = false
end
end
m = line:match("list%s+ports%s+'(%S+):u'") or line:match("list%s+ports%s+'(%S+):t'")
if m then
config.ports[m] = {
tagged = config.tagged
}
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,
mac = ""
}
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+macaddr%s+'(%S+)'")
if m then
config.mac = m
end
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
f:write("\toption proto '<" .. network .. "_proto>'\n")
f:write("\toption ipaddr '<" .. network .. "_ip>'\n")
f:write("\toption netmask '<" .. network .. "_mask>'\n")
end
if network == "lan" then
f:write("\toption dns ''\n")
end
if network == "wan" 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")
if config.mac == "" then
config.mac = string.gsub("x2:xx:xx:xx:xx:xx", "x", function()
local i = math.random(1, 16)
return string.sub("0123456789ABCDEF", i, i)
end)
end
f:write("\toption macaddr '" .. config.mac .. "'\n")
f:write("\toption proto 'static'\n")
f:write("\toption ipaddr '" .. config.ipaddr .. "'\n")
f:write("\toption netmask '255.255.255.255'\n")
f:write("\toption weight '" .. config.weight .. "'\n")
if config.peer and config.peer ~= "" then
f:write("\toption peer '" .. config.peer .. "'\n")
f:write("\nconfig route\n")
f:write("\toption interface '" .. config.name .. "'\n")
f:write("\toption target '" .. config.peer .. "'\n")
end
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