mirror of https://github.com/aredn/aredn.git
418 lines
15 KiB
Lua
Executable File
418 lines
15 KiB
Lua
Executable File
#!/usr/bin/lua
|
|
--[[
|
|
|
|
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
|
|
Copyright (C) 2021 Tim Wilkinson
|
|
Original 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.hardware")
|
|
require("aredn.http")
|
|
require("aredn.utils")
|
|
local html = require("aredn.html")
|
|
require("uci")
|
|
local aredn_info = require("aredn.info")
|
|
local olsr = require("aredn.olsr")
|
|
|
|
-- helpers start
|
|
|
|
function mesh_ip_to_hostnames(ip)
|
|
if not ip or ip == "" or ip == "none" then
|
|
return ""
|
|
end
|
|
local pattern = "^" .. ip .. "%s+([%w%-]+)"
|
|
for line in io.lines("/etc/hosts")
|
|
do
|
|
local host = line:match(pattern)
|
|
if host then
|
|
return host.gsub("%s+", " / ")
|
|
end
|
|
end
|
|
local hosts = ""
|
|
for line in io.lines("/var/run/hosts_olsr")
|
|
do
|
|
local host = line:match(pattern)
|
|
if host then
|
|
hosts = hosts .. " / " .. host
|
|
end
|
|
end
|
|
return hosts:sub(4, #hosts)
|
|
end
|
|
|
|
function get_default_gw()
|
|
-- a node with a wired default gw will route via this
|
|
local p = io.popen("ip route list table 254")
|
|
if p then
|
|
for line in p:lines()
|
|
do
|
|
local gw = line:match("^default%svia%s([%d%.]+)")
|
|
if gw then
|
|
p:close()
|
|
return gw
|
|
end
|
|
end
|
|
p:close()
|
|
end
|
|
-- table 31 is populated by OLSR
|
|
p = io.popen("ip route list table 31")
|
|
if p then
|
|
for line in p:lines()
|
|
do
|
|
local gw = line:match("^default%svia%s([%d%.]+)")
|
|
if gw then
|
|
p:close()
|
|
return gw
|
|
end
|
|
end
|
|
p:close()
|
|
end
|
|
return "none"
|
|
end
|
|
|
|
function get_wifi_signal(wifiif)
|
|
local signal = -1000
|
|
local noise = -1000
|
|
for mac, station in pairs(iwinfo.nl80211.assoclist(wifiif))
|
|
do
|
|
if station.signal ~= 0 and station.signal > signal then
|
|
signal = station.signal
|
|
end
|
|
if station.noise ~= 0 and station.noise > noise then
|
|
noise = station.noise
|
|
end
|
|
end
|
|
if signal == -1000 or noise == -1000 then
|
|
return "N/A", "N/A"
|
|
else
|
|
return signal, noise
|
|
end
|
|
end
|
|
|
|
function css_options()
|
|
html.print("<option>Select a theme</option>")
|
|
for file in nixio.fs.glob("/www/*.css")
|
|
do
|
|
if file ~= "/www/style.css" then
|
|
file = file:match("/www/(.*).css")
|
|
html.print("<option value=\"" .. file .. ".css\">" .. file .. "</option>")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- helpers end
|
|
|
|
local node = aredn_info.get_nvram("node")
|
|
if node == "" then
|
|
node = "NOCALL"
|
|
end
|
|
local tactical = aredn_info.get_nvram("tactical")
|
|
local config = aredn_info.get_nvram("config")
|
|
if config == "" or nixio.fs.stat("/etc/config.mesh", "type") ~= "dir" then
|
|
config = "not set"
|
|
end
|
|
local wifi_iface = aredn.hardware.get_iface_name("wifi")
|
|
local wifi_nr = wifi_iface:match("wlan(%d+)")
|
|
local wifi_disabled = true
|
|
local radio = "radio0"
|
|
if wifi_nr then
|
|
wifi_disabled = false
|
|
radio = "radio" .. wifi_nr
|
|
end
|
|
|
|
local cursor = uci.cursor()
|
|
|
|
local wifi_channel
|
|
local wifi_chanbw
|
|
local wifi_ssid
|
|
if not wifi_disabled then
|
|
wifi_channel = tonumber(cursor:get("wireless", radio, "channel"))
|
|
if wifi_channel >= 76 and wifi_channel <= 99 then
|
|
wifi_channel = wifi_channel * 5 + 3000
|
|
end
|
|
wifi_chanbw = cursor:get("wireless", radio, "chanbw")
|
|
wifi_ssid = "N/A"
|
|
cursor:foreach("wireless", "wifi-iface",
|
|
function (section)
|
|
if section.network == "wifi" then
|
|
wifi_ssid = section.ssid
|
|
return false
|
|
end
|
|
end
|
|
)
|
|
end
|
|
|
|
local node_desc = cursor:get("system", "@system[0]", "description")
|
|
local lat_lon = "<strong>Location Not Available</strong>"
|
|
local lat = cursor:get("aredn", "@location[0]", "lat")
|
|
local lon = cursor:get("aredn", "@location[0]", "lon")
|
|
if lat and lon then
|
|
lat_lon = string.format("<center><strong>Location: </strong> %s %s</center>", lat, lon)
|
|
end
|
|
|
|
local olsr_routes = olsr.getOLSRRoutes()
|
|
local olsr_total = 0
|
|
local olsr_nodes = 0
|
|
for i, node in ipairs(olsr_routes)
|
|
do
|
|
if node.genmask ~= 0 then -- don't count default route
|
|
olsr_total = olsr_total + 1
|
|
if node.genmask ~= 32 then
|
|
olsr_nodes = olsr_nodes + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
-- post data
|
|
|
|
if os.getenv("REQUEST_METHOD") == "POST" then
|
|
require('luci.http')
|
|
require('luci.sys')
|
|
local request = luci.http.Request(luci.sys.getenv(),
|
|
function()
|
|
local v = io.read(1024)
|
|
if not v then
|
|
io.close()
|
|
end
|
|
return v
|
|
end
|
|
)
|
|
local css = request:formvalue("css")
|
|
if css and css:match("%.css$") and nixio.fs.stat("/www/" .. css) then
|
|
nixio.fs.unlink("/tmp/web/style.css")
|
|
nixio.fs.symlink("/www/" .. css, "/tmp/web/style.css")
|
|
end
|
|
end
|
|
|
|
-- generate page
|
|
|
|
http_header()
|
|
html.header(node .. " status", true)
|
|
html.print("<body><form method='post' action='/cgi-bin/status' enctype='multipart/form-data'>")
|
|
html.print("<center>")
|
|
|
|
html.alert_banner()
|
|
html.msg_banner()
|
|
|
|
-- page header
|
|
html.print("<h1><big>" .. node)
|
|
if tactical ~= "" then
|
|
html.print(" / " .. tactical)
|
|
end
|
|
html.print("</big></h1>")
|
|
html.print("<center>" .. lat_lon .. "</center>")
|
|
if node_desc then
|
|
html.print("<table id='node_description_display'><tr><td>" .. node_desc .. "</td></tr></table>")
|
|
end
|
|
html.print("<hr>")
|
|
|
|
-- nav buttons
|
|
html.print("<nobr>")
|
|
html.print("<a href='/help.html' target='_blank'>Help</a>")
|
|
html.print(" ")
|
|
html.print("<input type=submit name=refresh value=Refresh title='Refresh this page'>")
|
|
if config == "mesh" then
|
|
html.print(" ")
|
|
html.print("<button type=button onClick='window.location=\"mesh\"' title='See what is on the mesh'>Mesh Status</button>")
|
|
if cursor:get("aredn", "@lqm[0]", "enable") == "1" then
|
|
html.print(" ")
|
|
html.print("<button type=button onClick='window.location=\"lqm\"' title='See the link status to our neighbors'>Neighbor Status</button>")
|
|
end
|
|
if not wifi_disabled then
|
|
html.print(" ")
|
|
html.print("<button type=button onClick='window.location=\"scan\"' title='See what wireless networks are nearby'>WiFi Scan</button>")
|
|
end
|
|
end
|
|
html.print("  ")
|
|
html.print("<button type=button onClick='window.location=\"setup\"' title='Configure this node'>Setup</button>")
|
|
html.print(" ")
|
|
html.print("<select name=\"css\" size=\"1\" onChange=\"form.submit()\" >")
|
|
css_options()
|
|
html.print("</select>")
|
|
html.print("</nobr")
|
|
|
|
html.print("<input type=hidden name=reload value=reload>")
|
|
|
|
if config == "not set" then
|
|
html.print("<b><br><br>This node is not yet configured.<br>")
|
|
html.print("Go to the setup page and set your node name and password.<br>")
|
|
html.print("Click Save Changes, <u>even if you didn't make any changes</u>, then the node will reboot.</b>")
|
|
html.print("<br><br>")
|
|
html.print("<div style=\"max-width: 540px\; text-align: left\">")
|
|
html.print("<p>This device can be configured to either permit or prohibit known encrypted traffic on its RF link. It is up to the user to decide which is appropriate based on how it will be used and the license under which it will be operated. These rules vary by country, frequency, and intended use. You are encouraged to read and understand these rules before going further.</p>")
|
|
html.print("<p>This device is pre-configured with no restrictions as to the type of data being passed.</p>")
|
|
html.print("<p>Follow these steps if <span style=\"text-decoration: underline\">you wish to prohibit</span> known encrypted traffic on the RF link. These instructions will disappear, so copy them for your reference:</p>")
|
|
html.print("<p><ol>")
|
|
html.print("<li>Setup your node name and password as instructed at the top of this page</li>")
|
|
html.print("<li>After you Save Changes allow your node to reboot</li>")
|
|
html.print("<li>Return to the Node Status page and navigate to Setup > Administration</li>")
|
|
html.print("<li>Obtain the blockknownencryption package from the AREDN™ website OR refresh the Package list (node must be connected to the internet)</li>")
|
|
html.print("<li>Install the blockknownencryption package by uploading it or choosing it from the package drop-down list</li>")
|
|
html.print("<li>Wait until the package installs and then reboot your node</li>")
|
|
html.print("</ol></p>")
|
|
html.print("</div>")
|
|
end
|
|
|
|
-- status display
|
|
|
|
local col1 = {}
|
|
local col2 = {}
|
|
|
|
-- left column - network interface info
|
|
|
|
local ip = cursor:get("network", "wifi", "ipaddr")
|
|
local cidr = netmask_to_cidr(cursor:get("network", "wifi", "netmask"))
|
|
if wifi_disabled then
|
|
col1[#col1 + 1] = "<th align=right><nobr>Primary address</nobr></th><td>" .. ip .. " <small>/ " .. cidr .. "</small><br>"
|
|
else
|
|
col1[#col1 + 1] = "<th align=right><nobr>Wifi address</nobr></th><td>" .. ip .. " <small>/ " .. cidr .. "</small><br>"
|
|
end
|
|
|
|
ip = cursor:get("network", "lan", "ipaddr")
|
|
mask = cursor:get("network", "lan", "netmask")
|
|
local browser_ip
|
|
local remote_ip = os.getenv("REMOTE_ADDRESS")
|
|
if remote_ip then
|
|
remote_ip = remote_ip.match("::ffff:([%d%.]+)")
|
|
end
|
|
local hide_local = false
|
|
if remote_ip then
|
|
browser_ip = remote_ip
|
|
if not validate_same_subnet(remote_ip, ip, mask) then
|
|
hide_local = true
|
|
end
|
|
end
|
|
|
|
if ip:match("^10%.") or not hide_local then
|
|
cidr = netmask_to_cidr(mask)
|
|
col1[#col1 + 1] = "<th align=right><nobr>LAN address</nobr></th><td>" .. ip .. " <small>/ " .. cidr .. "</small><br>"
|
|
end
|
|
|
|
local wan_iface = aredn.hardware.get_iface_name("wan")
|
|
if not hide_local and wan_iface then
|
|
local ip, bcast, mask = aredn.hardware.get_interface_ip4(wan_iface)
|
|
if ip then
|
|
cidr = netmask_to_cidr(mask)
|
|
col1[#col1 + 1] = "<th align=right><nobr>WAN address</nobr></th><td>" .. ip .. " <small>/ " .. cidr .. "</small><br>"
|
|
else
|
|
col1[#col1 + 1] = "<th align=right><nobr>WAN address</nobr></th><td>none</small><br>"
|
|
end
|
|
end
|
|
|
|
ip = get_default_gw()
|
|
if ip:match("^10%.") or not hide_local then
|
|
col1[#col1 + 1] = "<th align=right><nobr>default gateway</nobr></th><td>" .. ip
|
|
if ip:match("^10%.") then
|
|
col1[#col1] = col1[#col1] .. "<br><nobr>" .. mesh_ip_to_hostnames(ip) .. "</nobr>"
|
|
end
|
|
col1[#col1] = col1[#col1] .. "</td>"
|
|
end
|
|
|
|
if browser_ip then
|
|
col1[#col1 + 1] = "<th align=right><nobr>your address</nobr></th><td>" .. browser_ip .. "<br><nobr>" .. mesh_ip_to_hostnames(browser_ip) .. "</nobr></td>"
|
|
end
|
|
|
|
if not wifi_disabled then
|
|
col1[#col1 + 1] = "<th align=right><nobr>SSID</nobr></th><td>" .. wifi_ssid .. "</td>"
|
|
col1[#col1 + 1] = "<th align=right><nobr>Channel</nobr></th><td>" .. wifi_channel .. "</td>"
|
|
col1[#col1 + 1] = "<th align=right><nobr>Bandwidth</nobr></th><td>" .. wifi_chanbw .. " MHz</td>"
|
|
end
|
|
|
|
-- right column - system info
|
|
|
|
if config == "mesh" and not wifi_disabled then
|
|
col2[#col2 + 1] = "<th align=right valign=middle><nobr>Signal/Noise/Ratio</nobr></th><td valign=middle><nobr>"
|
|
local s, n = get_wifi_signal(wifi_iface)
|
|
if s == "N/A" then
|
|
col2[#col2] = col2[#col2] .. "N/A"
|
|
else
|
|
col2[#col2] = col2[#col2] .. "<big><b>" .. s .. " / " .. n .. " / " .. math.abs(s - n) .. " dB</b></big>"
|
|
end
|
|
col2[#col2] = col2[#col2] .. " <button type=button onClick='window.location=\"signal?realtime=1\"' title='Display continuous or archived signal strength on a chart'>Charts</button></nobr></td>"
|
|
end
|
|
|
|
col2[#col2 + 1] = "<th align=right><nobr>firmware version</nobr><br><nobr>model</nobr></th><td>" .. read_all("/etc/mesh-release") .. "<br>" .. (aredn.hardware.get_radio() or { name = "unknown" }).name .. "</td>";
|
|
|
|
local sysinfo = nixio.sysinfo()
|
|
local uptime = string.format("%d:%02d", math.floor(sysinfo.uptime / 3600) % 24, math.floor(sysinfo.uptime / 60) % 60)
|
|
if sysinfo.uptime >= 172800 then
|
|
uptime = math.floor(sysinfo.uptime / 86400) .. " days, " .. uptime
|
|
elseif sysinfo.uptime >= 86400 then
|
|
uptime = "1 day, " .. uptime
|
|
end
|
|
col2[#col2 + 1] = "<th align=right><nobr>system time</nobr><br>uptime</th><td>" .. os.date("%a %b %e %Y") .. " " .. os.date("%T %Z") .. "<br>" .. uptime .. "</td>";
|
|
|
|
local vfs = nixio.fs.statvfs("/overlay")
|
|
local fspace = vfs.bfree * vfs.bsize / 1024
|
|
if fspace < 100 then
|
|
fspace = "<blink><b>" .. fspace .. " KB</b></blink>"
|
|
else
|
|
fspace = fspace .. " KB"
|
|
end
|
|
vfs = nixio.fs.statvfs("/tmp")
|
|
local tspace = vfs.bfree * vfs.bsize / 1024
|
|
if tspace < 3000 then
|
|
tspace = "<blink><b>" .. tspace .. " KB</b></blink>"
|
|
else
|
|
tspace = tspace .. " KB"
|
|
end
|
|
local rspace = (sysinfo.freeram + sysinfo.bufferram) / 1024
|
|
if rspace < 500 then
|
|
rspace = "<blink><b>" .. rspace .. " KB</b></blink>"
|
|
else
|
|
rspace = rspace .. " KB"
|
|
end
|
|
|
|
col2[#col2 + 1] = "<th align=right valign=top><nobr>load average</nobr><br><nobr>free space</nobr></th><td>" .. string.format("%.2f, %.2f, %.2f", sysinfo.loads[1], sysinfo.loads[2], sysinfo.loads[3]) .. "<br><nobr>flash = " .. fspace .. "</nobr><br><nobr>/tmp = " .. tspace .. "</nobr><br><nobr>memory = " .. rspace .. "</nobr></td>";
|
|
col2[#col2 + 1] = "<th align=right valign=top>OLSR Entries</th><td><nobr>Total = " .. olsr_total .. "<nobr><br><nobr>Nodes = " .. olsr_nodes .. "<nobr></td>"
|
|
|
|
-- now print the tables
|
|
|
|
html.print("<br><br><table>")
|
|
html.print("<tr><td valign=top><table cellpadding=4>")
|
|
for i, line in ipairs(col1)
|
|
do
|
|
html.print("<tr>" .. line .. "</tr>")
|
|
end
|
|
html.print("</table></td><td valign=top><table cellpadding=4>")
|
|
for i, line in ipairs(col2)
|
|
do
|
|
html.print("<tr>" .. line .. "</tr>")
|
|
end
|
|
html.print("</table></td></tr></table>")
|
|
|
|
-- end
|
|
html.print("</center></form>")
|
|
|
|
html.footer()
|
|
html.print("</body></html>")
|