From 610cde651ca6a821bedb2d5604532e35b51344c7 Mon Sep 17 00:00:00 2001 From: Tim Wilkinson Date: Wed, 19 Jan 2022 05:42:31 -0800 Subject: [PATCH] Lua userpages (#190) * Lua Services * Lua versions of user page (non-admin) * Support multiple antenna chains * Improved led detection * Fix logging * Add manager.log files to support tool * Lua versions of user page (non-admin) * Dont count default route * Fix board id for older nodes * Handle missing node.history * Check /tmp/snrlog exists * Per discussion, make the Lua pages the defaults and Perl the backups --- files/usr/lib/lua/aredn/hardware.lua | 2 +- files/www/cgi-bin/mesh | 1377 ++++++++++++++------------ files/www/cgi-bin/mesh.pl | 680 +++++++++++++ files/www/cgi-bin/scan | 316 ++++-- files/www/cgi-bin/scan.pl | 98 ++ files/www/cgi-bin/signal | 558 ++++++----- files/www/cgi-bin/signal.pl | 307 ++++++ files/www/cgi-bin/status | 608 +++++++----- files/www/cgi-bin/status.pl | 298 ++++++ 9 files changed, 3007 insertions(+), 1237 deletions(-) create mode 100755 files/www/cgi-bin/mesh.pl create mode 100755 files/www/cgi-bin/scan.pl create mode 100755 files/www/cgi-bin/signal.pl create mode 100755 files/www/cgi-bin/status.pl diff --git a/files/usr/lib/lua/aredn/hardware.lua b/files/usr/lib/lua/aredn/hardware.lua index 5d5bae15..9296eece 100644 --- a/files/usr/lib/lua/aredn/hardware.lua +++ b/files/usr/lib/lua/aredn/hardware.lua @@ -95,7 +95,7 @@ function hardware.get_board_id() if not name or name == "" or name == "0x0000" then name = hardware.get_board().model.name end - return name + return name:chomp() end function hardware.get_board_type() diff --git a/files/www/cgi-bin/mesh b/files/www/cgi-bin/mesh index c8eb246b..b8c5aae0 100755 --- a/files/www/cgi-bin/mesh +++ b/files/www/cgi-bin/mesh @@ -1,680 +1,791 @@ -#!/usr/bin/perl -=for comment +#!/usr/bin/lua +--[[ - Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks - Copyright (C) 2015 Conrad Lara - See Contributors file for additional contributors + 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 - Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet + 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 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. - 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 . - You should have received a copy of the GNU General Public License - along with this program. If not, see . + Additional Terms: - Additional Terms: + Additional use restrictions exist on the AREDN(TM) trademark and logo. + See AREDNLicense.txt for more info. - 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. - 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. - 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 - Modified versions must be modified to attribute to the original source - and be marked in reasonable ways as differentiate it from the original - version. +--]] -=cut +require("nixio") +require("aredn.hardware") +require("aredn.http") +require("aredn.utils") +require("aredn.html") +require("uci") +aredn.info = require("aredn.info") +aredn.olsr = require("aredn.olsr") +require("iwinfo") -$debug = 0; +local html = aredn.html -BEGIN {push @INC, '/www/cgi-bin'}; -use perlfunc; -use ucifunc; - -%rateL = ( - '1.0M' => '1', # CCP LP - '2.0M' => '2', # CCP LP - '5.5M' => '5.5', # CCP LP - 'MCS0' => '6.5', # HT20 LGI - '11.0M'=> '11', # CCP LP - 'MCS1' => '13', # HT20 LGI... - 'MCS2' => '19.5', - 'MCS3' => '26', - 'MCS4' => '39', - 'MCS5' => '52', - 'MCS6' => '58.5', - 'MCS7' => '65', - 'MCS8' => '13', - 'MCS9' => '26', - 'MCS10' => '39', - 'MCS11' => '52', - 'MCS12' => '78', - 'MCS13' => '104', - 'MCS14' => '117', - 'MCS15' => '130', -); - -%rateS = ( - 'MCS0' => '7.2', - 'MCS1' => '14.4', - 'MCS2' => '21.7', - 'MCS3' => '28.9', - 'MCS4' => '43.3', - 'MCS5' => '57.8', - 'MCS6' => '65', - 'MCS7' => '72.2', - 'MCS8' => '14.4', - 'MCS9' => '28.9', - 'MCS10' => '43.3', - 'MCS11' => '57.8', - 'MCS12' => '86.7', - 'MCS13' => '115.6', - 'MCS14' => '130', - 'MCS15' => '144.4', - ); - -# Limit displayed nodes and services to the most reachable routes if memory on the node is small -%lowMemoryLimits = ( - memory => `/sbin/uci -q get aredn.\@meshstatus[0].lowmem` || 10000, - routes => `/sbin/uci -q get aredn.\@meshstatus[0].lowroutes` || 1000, -); - -sub get_available_mem -{ - foreach(`free`) - { - next unless /^Mem[:]/; - my @tmp = split /\s+/, $_; - return $tmp[6]; - } - return "N/A"; +local rateL = { + ["1.0M"] = 1, -- CCP LP + ["2.0M"] = 2, -- CCP LP + ["5.5M"] = 5.5, -- CCP LP + MCS0 = 6.5, -- HT20 LGI + ["11.0M"] = 11, -- CCP LP + MCS1 = 13, -- HT20 LGI... + MCS2 = 19.5, + MCS3 = 26, + MCS4 = 39, + MCS5 = 52, + MCS6 = 58.5, + MCS7 = 65, + MCS8 = 13, + MCS9 = 26, + MCS10 = 39, + MCS11 = 52, + MCS12 = 78, + MCS13 = 104, + MCS14 = 117, + MCS15 = 130 +} +local rateS = { + MCS0 = 7.2, + MCS1 = 14.4, + MCS2 = 21.7, + MCS3 = 28.9, + MCS4 = 43.3, + MCS5 = 57.8, + MCS6 = 65, + MCS7 = 72.2, + MCS8 = 14.4, + MCS9 = 28.9, + MCS10 = 43.3, + MCS11 = 57.8, + MCS12 = 86.7, + MCS13 = 115.6, + MCS14 = 130, + MCS15 = 144.4 } -# collect some variables -$node = nvram_get("node"); -$node = "NOCALL" if $node eq ""; -$tactical = nvram_get("tactical"); -$config = nvram_get("config"); -$config = "not set" if $config eq "" || not -d "/etc/config.mesh"; -($my_ip) = get_ip4_network(get_interface("wifi")); -${wifiif} = `uci -q get 'network.wifi.ifname'`; -chomp ${wifiif}; -$phy = get_wlan2phy("${wifiif}"); +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 wifiif = aredn.hardware.get_iface_name("wifi") +local my_ip = aredn.hardware.get_interface_ip4(wifiif) +if not my_ip then + my_ip = "none" +end +local phy = iwinfo.nl80211.phyname(wifiif) +if not phy then + phy = 0 +end -chomp ($chanbw = `cat /sys/kernel/debug/ieee80211/${phy}/ath9k/chanbw`); -if ($chanbw eq "0x00000005") {$chanbw = 4;} -elsif ($chanbw eq "0x0000000a") {$chanbw = 2;} - else {$chanbw = 1;} +local chanbw = 1 +local cb = "/sys/kernel/debug/ieee80211/" .. phy .. "/ath9k/chanbw" +if nixio.fs.stat(cb) then + for line in io.lines(cb) + do + if line == "0x00000005" then + chanbw = 4 + elseif line == "0x0000000a" then + chanbw = 2 + end + break + end +end -read_postdata(); +if not nixio.fs.stat("/tmp/web") then + nixio.fs.mkdir("/tmp/web") +end -system "mkdir -p /tmp/web"; -system "touch /tmp/web/automesh" if $parms{auto}; -system "rm -f /tmp/web/automesh" if $parms{stop}; +-- post data -#get location info if available -$lat=&uci_get_indexed_option("aredn","location",0,"lat"); -$lon=&uci_get_indexed_option("aredn","location",0,"lon"); -if($lat ne "" and $lon ne "") -{ - $lat_lon = "
Location: $lat $lon
"; -} +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 + ) + if request:formvalue("auto") then + io.open("/tmp/web/automesh", "w"):close() + end + if request:formvalue("stop") then + os.remove("/tmp/web/automesh") + end +end + +local cursor = uci.cursor() +local node_desc = cursor:get("system", "@system[0]", "description") +local lat_lon = "Location Not Available" +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("
Location: %s %s
", lat, lon) +end +-- low memory mitigation +local lowmemory = cursor:get("aredn", "@meshstatus[0]", "lowmem") +if not lowmemory then + lowmemory = 10000 else -{ - $lat_lon = "Location Not Available"; -} -@route30 = `/sbin/ip route list table 30`; -$olsrTotal = scalar @route30; -$olsrNodes = scalar grep /\//, @route30; -$node_desc = `/sbin/uci -q get system.\@system[0].description`; #pull the node description from uci -undef @route30; + lowmemory = tonumber(lowmemory) +end +local lowroutes = cursor:get("aredn", "@meshstatus[0]", "lowroutes") +if not lowroutes then + lowroutes = 1000 +else + lowroutes = tonumber(lowroutes) +end -# parse the txtinfo output -chomp($tmperr = `mktemp /tmp/web/nc.XXXXXX`); +local routes = {} +local links = {} +local neighbor = {} +local wangateway = {} +local ipalias = {} +local localhosts = {} +local dtd = {} +local midcount = {} +local hosts = {} +local services = {} +local history = {} -foreach(`echo /rou | nc 127.0.0.1 2006 2>>$tmperr`) -{ - next if /^\D/; - my ($ip, $junk, $junk, $etx) = split /\s+/, $_; - my ($net, $cidr) = split /\//, $ip; - if ( $etx <= 50 ) { $routes{$net}{etx} = $etx; } -} -if ($olsrTotal > $lowMemoryLimits{routes} && get_available_mem() < $lowMemoryLimits{memory}) -{ - my @oroutes = sort { $routes{$a}{etx} <=> $routes{$b}{etx} } keys %routes; - foreach ( @oroutes[$lowMemoryLimits{routes} .. $#oroutes] ) - { - delete $routes{$_}; - } -} -@arps = {}; -open my $fh, '<', '/proc/net/arp'; -foreach (<$fh>) { push @arps, $_ if (/$wifiif/ && !/00:00:00:00:00:00/) } -close $fh; -foreach(`echo /lin | nc 127.0.0.1 2006 2>>$tmperr`) -{ - next if /^\D/; - my ($junk, $ip, $junk, $lq, $nlq) = split /\s+/, $_; - $links{$ip} = { - lq => $lq, - nlq => $nlq, - mbps => "" - }; - $neighbor{$ip} = 1; +local olsr_total = 0 +local olsr_nodes = 0 +local olsr_routes = 0 +for i, node in ipairs(aredn.olsr.getOLSRRoutes()) +do + olsr_total = olsr_total + 1 + if node.genmask ~= 32 then + olsr_nodes = olsr_nodes + 1 + end + if node.etx <= 50 then + routes[node.destination] = { etx = node.etx } + olsr_routes = olsr_routes + 1 + end +end +-- low memory route reduction +if olsr_routes > lowroutes and nixio.sysinfo().freeram < lowmemory then + local list = {} + for k,v in pairs(routes) + do + list[#list + 1] = { key = k, etx = v.etx } + end + table.sort(list, function (a, b) return a.etx < b.etx end) + for i = lowroutes,olsr_routes + do + table.remove(routes, list[i].key) + end +end - my ($mac) = grep /^$ip/, @arps; - $mac =~ s/^.*(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w).*$/$1/; - chomp $mac; +-- load up arpcache +local arpcache = {} +arptable(function(a) + arpcache[a["IP address"]] = a +end) - open my $fh, '<', "/sys/kernel/debug/ieee80211/${phy}/netdev:${wifiif}/stations/$mac/rc_stats_csv"; - if ( $mac && $fh ) - { - my @csv = <$fh>; - close $fh; - #802.11b/n - my ($mbps) = grep /^([^,]*,){3}A/, @csv; - if ($mbps) - { - my ($gi, $dummy, $rate, $dummy, $ewma) = $mbps =~ /^[^,]*,([^,]*),([^,]*,){2}([^,]*),([^,]*,){4}([^,]*).*$/ ; - $rate =~ s/[ \t]//g; - $mbps = $gi eq "SGI" ? $rateS{$rate}*$ewma/100 : $rateL{$rate}*$ewma/100 ; - } +local prefix = "/sys/kernel/debug/ieee80211/" .. phy .. "/netdev:" .. wifiif .. "/stations/" +for i, node in ipairs(aredn.olsr.getOLSRLinks()) +do + links[node.remoteIP] = { lq = node.linkQuality, nlq = node.neighborLinkQuality, mbps = "" } + neighbor[node.remoteIP] = true + local mac = arpcache[node.remoteIP] + if mac then + local f = io.open(prefix .. mac["HW address"] .. "/rc_stats_csv", "r") + if f then + for line in f:lines() + do + local gi, rate, ewma = line:match("^[^,]*,([^,]*),[^,]*,A[^,]*,([^,%s]*)%s*,[^,]*,[^,]*,[^,]*,[^,]*,([^,]*),") + if gi then + -- 802.11b/n + if gi == "SGI" then + links[node.remoteIP].mbps = string.format("%.1f", rateS[rate] * tonumber(ewma) / 100 / chanbw) + else + links[node.remoteIP].mbps = string.format("%.1f", rateL[rate] * tonumber(ewma) / 100 / chanbw) + end + else + rate, ewma = line:match("^A[^,]*,([^,]*),[^,]*,[^,]*,([^,]*,)") + if rate then + -- 802.11a/b/g + links[node.remoteIP].mbps = string.format("%.1f", tonumber(rate) * tonumber(ewma) / 100 / chanbw) + end + end + end + f:close() + end + end +end + +-- discard +arpcache = nil + +for i, node in ipairs(aredn.olsr.getOLSRHNA()) +do + if node.destination == "0.0.0.0" then + wangateway[node.gateway] = true + end +end + +for i, node in ipairs(aredn.olsr.getOLSRMid()) +do + local ip = node.main.ipAddress + for _, alias in ipairs(node.aliases) + do + local aip = alias.ipAddress + ipalias[aip] = ip + neighbor[aip] = true + if links[aip] then + neighbor[ip] = true + end + end +end + +-- load the local hosts file +for line in io.lines("/etc/hosts") +do + if line:match("^10%.") then + local ip, name = line:match("([%d%.]+)%s+(%S+)") + if name then + local name9 = name:sub(1, 9) + if name9 ~= "localhost" and name9 ~= "localnode" then + local name7 = name:sub(1, 7) + if name7 ~= "localap" and name7 ~= "dtdlink" then + if not name:match("%.") then + name = name .. ".local.mesh" + end + local tac = line:match("[%d%.]+%s+%S+%s+(%S)") + if not tac then + tac = "" + end + if not localhosts[my_ip] then + localhosts[my_ip] = { hosts = {}, noprops = {}, aliases = {}, name = name, tactical = tac } + end + local host = localhosts[my_ip] + if ip == my_ip then + host.tactical = tac + host.name = name + else + host.hosts[#host.hosts + 1] = name + end + if tac == "#NOPROP" then + host.noprops[#host.noprops + 1] = name + end + if tac == "#ALIAS" then + host.aliases[#host.aliases + 1] = name + end + end + end + end + end +end + +-- load the olsr hosts file +for line in io.lines("/var/run/hosts_olsr") +do + local ip, name, originator = line:match("^([%d%.]+)%s+(%S+)%s+%S+%s+(%S+)") + if ip and originator and originator ~= "myself" and (routes[ip] or routes[originator]) then + local etx = routes[ip] + if not etx then + etx = routes[originator] + end + etx = etx.etx + if not name:match("%.") or name:match("^mid%.[^%.]*$") then + name = name .. ".local.mesh" + end + if ip == originator then + if not hosts[originator] then + hosts[originator] = { hosts = {} } + end + local host = hosts[originator] + if host.name then + host.tactical = name + else + host.name = name + host.etx = etx + end + elseif name:match("^dtdlink%.") then + dtd[originator] = true + if links[ip] then + links[ip].dtd = true + end + elseif name:match("^mid%d+%.") then + if not midcount[originator] then + midcount[originator] = 1 + else + midcount[originator] = midcount[originator] + 1 + end + if links[ip] then + links[ip].tun = true + end else - { - #802.11a/b/g - ($mbps) = grep /^A/, @csv; - if ($mbps) - { - $mbps =~ /^[^,]*,([^,]*),([^,]*,){4}([^,]*).*$/; - $mbps = $1*$3/100; - } - } - $links{$ip}{mbps} = $mbps ? sprintf "%.1f", $mbps / $chanbw : "0.0"; - } -} -undef @arps; -foreach(`echo /hna | nc 127.0.0.1 2006 2>>$tmperr`) -{ - next if /^\D/; - my ($iproute, $ip) = split /\s+/, $_; - my ($net, $cidr) = split /\//, $iproute; - if ( $net eq "0.0.0.0" ) { $wangateway{$ip} = 1; } -} -foreach(`echo /mid | nc 127.0.0.1 2006 2>>$tmperr`) -{ - next if /^\D/; - my ($ip, $junk) = $_ =~ /^(\S+)\s+(.*)$/; - foreach $aip ( split /\s+/, $junk ) - { - $ipalias{$aip} = $ip; - $neighbor{$aip} = 1; - if ( $links{$aip} ) { $neighbor{$ip} = 1 } - } -} + if not hosts[originator] then + hosts[originator] = { hosts = {} } + end + local host = hosts[originator] + host.hosts[#host.hosts + 1] = name + end + end +end -# stat and -s do not work in microperl -@parts = split /\s+/, `ls -l $tmperr`; -$txtinfo_err = $parts[4]; -unlink $tmperr; +-- discard +routes = nil -# load the local hosts file +for line in io.lines("/var/run/services_olsr") +do + if line:match("^%w") then + local url, name = line:match("^(.*)|.*|(.*)$") + if name then + local protocol, host, port, path = url:match("^([%w][%w%+%-%.]+)%://(.+):(%d+)/(.*)") + if path then + local name, originator = name:match("(.*%S)%s*#(.*)") + if originator == " my own service" or (hosts[originator] and hosts[originator].name) then + if not host:match("%.") then + host = host .. ".local.mesh" + end + if not services[host] then + services[host] = {} + end + if not services[host][name] then + if port ~= "0" then + services[host][name] = "" .. name .. "" + else + services[host][name] = name + end + end + end + end + end + end +end -open my $fh, '<', '/etc/hosts'; -foreach (<$fh>) -{ - next unless /^10[.]/; - chomp; - my ($ip, $name, $tactical) = split /\s+/, $_; - next if $name =~ /^(localhost|localnode|localap|dtdlink\..*)$/; - if ( $name !~ /\./ ) { $name="${name}.local.mesh"; } - if($ip eq $my_ip) - { - $tactical = "" unless $tactical; - $localhosts{$ip}{tactical} = $tactical; - $localhosts{$ip}{name} = $name; - } - else { push @{$localhosts{$my_ip}{hosts}}, $name; } - if($tactical eq "#NOPROP") { push @{$localhosts{$my_ip}{noprops}}, $name; } - if($tactical eq "#ALIAS") { push @{$localhosts{$my_ip}{aliases}}, $name; } -} -close $fh; +-- load the node history +if nixio.fs.stat("/tmp/node.history") then + for line in io.lines("/tmp/node.history") + do + local ip, age, host = line:match("^(.*) (.*) (.*)") + if age then + history[ip] = { age = age, host = host:gsub("/", " / ") } + end + end +end -# load the olsr hosts file -open my $fh, '<', '/var/run/hosts_olsr'; -foreach (<$fh>) -{ - next unless /^\d/; - chomp; - my ($ip, $name, undef, $originator, undef, undef) = split /\s+/, $_; - next unless $originator; - next if $originator eq "myself"; - # Filter hosts which are unreachable - next unless $routes{$ip} || $routes{$originator}; +http_header() +html.header(node .. " mesh status", false) +local automesh = nixio.fs.stat("/tmp/web/automesh"); +if automesh then + html.print("") +end +html.print("") - my $etx = $routes{$ip}{etx} ? $routes{$ip}{etx} : $routes{$originator}{etx}; +html.print("
") +html.print("") +html.print("
") - if (( $name !~ /\./ ) || ( $name =~ /^mid\.[^\.]*$/ )) { $name="${name}.local.mesh"; } +html.alert_banner() - if ( $ip eq $originator ) - { - if ($hosts{$ip}{name}) - { - $hosts{$ip}{tactical} = $name; - } +html.print("

" .. node .. " mesh status

") + +html.print(lat_lon) +if node_desc then + html.print("
" .. node_desc .. "
") +end +html.print("
") + +if nixio.fs.stat("/tmp/web/automesh") then + html.print("") +else + html.print("") + html.print("  ") + html.print("") +end + +html.print("  ") +html.print("") + +html.print("

") + +if not next(localhosts) and not next(links) then + html.print("No other nodes are available.") + html.print("
") + html.footer() + html.print("") + os.exit(0) +end + +html.print("") +end -print "   "; -print "\n"; +-- discard +wangateway = nil +services = nil -if($txtinfo_err) -{ - print "

Whoops! OLSR is not running, try again later.\n"; - print ""; - page_footer(); - print "\n"; - exit; -} +-- show previous neighbors -print "

\n"; +html.print("") +html.print("") +html.print("") -unless(keys %localhosts || keys %links) -{ - print "No other nodes are available.\n"; - print ""; - page_footer(); - print "
") + +-- show local hosts + +html.print("") +html.print("") + +if next(localhosts) then + local rows = {} + for ip, host in pairs(localhosts) + do + local localpart = host.name:match("([^.]*)%.") + local tactical = "" + if host.tactical ~= "" then + tactical = " / " .. host.tactical + end + local row = "" + -- add locally advertised dmz hosts + for i, dmzhost in ipairs(host.hosts) + do + local nopropd = false + local aliased = false + for _, v in ipairs(host.noprops) + do + if v == dmzhost then + nopropd = true; + break + end + end + for _, v in ipairs(host.aliases) + do + if v == dmzhost then + aliased = true; + break + end + end + local localpart = dmzhost:match("(.*)%.local%.mesh") + if not nopropd and not aliased then + row = row .. "" + elseif aliased then + row = row .. "" + else + row = row .. "" + end + if services[dmzhost] then + for n, v in pairs(services[dmzhost]) + do + row = row .. "" .. v .. "
" + end + end + row = row .. "" + end + rows[#rows + 1] = { key = host.name, row = row } + end + table.sort(rows, function(a,b) return a.key < b.key end) + for _, row in ipairs(rows) + do + html.print(row.row) + end + -- discard + rows = nil +else + html.print("") +end + +-- discard +localhosts = nil + +-- show remote nodes + +html.print("") +html.print("") +html.print("") + +local rows = {} +for ip, host in pairs(hosts) +do + if not neighbor[ip] and host.name then + local localpart = host.name:match("(.*)%.local%.mesh") + local tactical = "" + if host.tactical then + tactical = " / " .. host.tactical + end + local etx = string.format("%.2f", host.etx) + local row = "" + -- add locally advertised dmz hosts + for _, dmzhost in ipairs(host.hosts) + do + local localpart = dmzhost:match("(.*)%.local%.mesh") + row = row .. "" + row = row .. "" + end + rows[#rows + 1] = { key = host.etx, row = row } + end +end + +if #rows > 0 then + table.sort(rows, function(a,b) return a.key < b.key end) + for _, row in ipairs(rows) + do + html.print(row.row) + end + -- discard + rows = nil +else + html.print("") +end + +-- discard +neighbor = nil +dtd = nil +midcount = nil + +html.print("
Local HostsServices

" .. localpart .. tactical .. "" + if wangateway[ip] then + row = row .. "   (wan)" + end + row = row .. " " + if services[host.name] then + for n, v in pairs(services[host.name]) + do + row = row .. "" .. v .. "
" + end + end + row = row .. "
 " .. localpart .. "
 " .. localpart .. "
 " .. localpart .."
none
 
Remote Nodes  ETX  Services

" .. localpart .. tactical .. "" + local nodeiface + local mycount = 0 + if midcount[ip] then + mycount = midcount[ip] + end + if dtd[ip] then + mycount = mycount - 1 + end + if hosts[ip].tactical then + mycount = mycount - 1 + end + if mycount > 0 then + nodeiface = "tun*" .. mycount + end + if wangateway[ip] then + if nodeiface then + nodeiface = nodeiface .. ",wan" + else + nodeiface = "wan" + end + end + if nodeiface then + row = row .. "   (" .. nodeiface .. ")" + end + row = row .. "" .. etx .. "" + if services[host.name] then + for _, v in pairs(services[host.name]) + do + row = row .. "" .. v .. "
" + end + end + row = row .. "
 " .. localpart .. "" + if services[dmzhost] then + for _, v in pairs(services[dmzhost]) + do + row = row .. "" .. v .. "
" + end + end + row = row .. "
none
 ") + +-- show current neighbors + +html.print("") +html.print("") + +local rows = {} +local neighservices = {} +for ip, link in pairs(links) +do + local ipmain = ipalias[ip] + if not ipmain then + ipmain = ip + end + local name = ipmain + local localpart = ipmain + local tactical = "" + local host = hosts[ipmain] + if host then + if host.name then + name = host.name + localpart = name:match("(.*)%.local%.mesh") + if not localpart then + localpart = name + end + end + if host.tactical then + tactical = " / " .. host.tactical + end + end + if rows[name] then + name = name .. " " -- avoid collision 2 links to same host {rf, dtd} + end + local no_space_host = name:match("(.*%S)%s*$") + local row = "" + -- add advertised dmz hosts + if host then + for _, dmzhost in ipairs(host.hosts) + do + local localpart = dmzhost:match("(.*)%.local%.mesh") + row = row .. "" + row = row .. "" + end + end + end -# load the olsr services file -open my $fh, '<', '/var/run/services_olsr'; -foreach (<$fh>) -{ - next unless /^\w/; - chomp; - my ($url, $junk, $name) = split /\|/, $_; - next unless defined $name; - my ($protocol, $host, $port, $path) = $url =~ /^(\w+):\/\/([\w\-\.]+):(\d+)\/(.*)/; - next unless defined $path; - my ($name, $originator) = split /\#/, $name; - - # Filter services for unreachable hosts - next unless $hosts{$originator}{name} || $originator eq " my own service"; - - $name =~ s/\s+$//; - - if ( $host !~ /\./ ) { $host="${host}.local.mesh"; } - - # attempt to work around olsr never forgetting defunct services - # assume that the first entry in the file by this name is the most recent, ignore the rest - next if $services{$host}{$name}; - - $services{$host}{$name} = $port ? "$name" : $name; -} -close $fh; - -# load the node history -open my $fh, '<', '/tmp/node.history'; -foreach (<$fh>) -{ - chomp; - ($ip, $age, $host) = split / /, $_; - next unless $age; - $host = "" unless $host; - $host =~ s|/| / |; - $history{$ip}{age} = $age; - $history{$ip}{host} = $host; -} -close $fh; - -#delete $hosts{"127.0.0.1"}; - -# compress the output if we can -if ( $ENV{HTTP_ACCEPT_ENCODING} =~ /gzip/ ) -{ - print "Content-type: text/html\r\nCache-Control: no-store\r\nContent-Encoding: gzip\r\n\r\n"; - open my $zout, "|gzip"; - select $zout; -} -else -{ - print "Content-type: text/html\r\nCache-Control: no-store\r\n\r\n"; -} - -# generate the page -html_header("$node mesh status", 0); -print "\n" if -f "/tmp/web/automesh"; -print "\n"; -print "\n"; -print "\n"; -print "
\n"; - -alert_banner(); - -# page header -print "

$node mesh status

"; - -print "$lat_lon"; #display lat lon info -print "
Current Neighbors  LQNLQTxMbps  Services

" .. localpart .. tactical .. "" + local nodeiface + if ipmain ~= ip then + if links[ip].dtd then + nodeiface = "dtd" + elseif links[ip].tun then + nodeiface = "tun" else - { - $hosts{$ip}{name} = $name; - $hosts{$ip}{etx} = $etx; - } - } - elsif ( $name =~ /^dtdlink\..*$/ ) - { - $dtd{$originator} = 1; - if ( $links{$ip} ) { $links{$ip}{dtd} = 1 } - } - elsif ( $name =~ /^mid\d+\..*$/ ) - { - $midcount{$originator} = $midcount{$originator} ? $midcount{$originator}+1: 1 ; - if ( $links{$ip} ) { $links{$ip}{tun} = 1 } - } - else - { - push @{$hosts{$originator}{hosts}}, $name; - } -} -close $fh; + nodeiface = "?" + end + end + if wangateway[ip] or wangateway[ipmain] then + if nodeiface then + nodeiface = nodeiface .. ",wan" + else + nodeiface = "wan" + end + end + if nodeiface then + row = row .. "   (" .. nodeiface .. ")" + end + row = row .. string.format("%.0f%%%.0f%%%s\n", 100 * link.lq, 100 * link.nlq, link.mbps) -# Discard -undef %routes; + if not neighservices[name] then + neighservices[name] = true + if services[name] then + for _, v in pairs(services[name]) + do + row = row .. "" .. v .. "
" + end + end + row = row .. "
 " .. localpart .. "" + if services[dmzhost] then + for _, v in pairs(services[dmzhost]) + do + row = row .. v .. "
" + end + end + row = row .. "
$node_desc
" if $node_desc; #display node description -print "
"; - -# nav buttons -if(-f "/tmp/web/automesh") -{ - print "\n"; -} + rows[#rows + 1] = { key = name, row = row } +end +if #rows > 0 then + table.sort(rows, function(a,b) return a.key < b.key end) + for _, row in ipairs(rows) + do + html.print(row.row) + end + -- discard + rows = nil else -{ - print "\n"; - print "   \n"; - print "\n"; -} + html.print("
none
 
Previous NeighborsWhen

\n"; - - -# show local hosts - -print "\n"; -print "\n"; - -if(keys %localhosts) -{ - my %rows = (); - - foreach $ip (keys %localhosts) - { - my $host = $localhosts{$ip}{name}; - my $localpart = $host =~ s/.local.mesh//r; - my $tactical = $localhosts{$ip}{tactical} ? " / " . $localhosts{$ip}{tactical} : ""; - $rows{$host} = sprintf "\n"; - - # add locally advertised dmz hosts - foreach $dmzhost (@{$localhosts{$ip}{hosts}}) - { - #find non-propagated and aliased hosts and change color - my $nopropd = 0; - my $aliased = 0; - if(grep { /$dmzhost/ } @{$localhosts{$ip}{noprops}}) { $nopropd = 1; } - if(grep { /$dmzhost/ } @{$localhosts{$ip}{aliases}}) { $aliased = 1; } - $localpart = $dmzhost =~ s/.local.mesh//r; - if(!$nopropd && !$aliased) { $rows{$host} .= ""; } - elsif($aliased) { $rows{$host} .= ""; } - else { $rows{$host} .= ""; } - $rows{$host} .= "\n"; - } - } - - foreach(sort keys %rows) { print $rows{$_} } -} +local rows = {} +local uptime = nixio.sysinfo().uptime +for ip, node in pairs(history) +do + if not (links[ip] or links[ipalias[ip]]) then + local age = uptime - tonumber(node.age) + local host = node.host + if not host then + host = ip + else + host = host:gsub("^mid%d+%.", ""):gsub("^dtdlink%.", "") + end + local row = "" + end + rows[#rows + 1] = { key = age, row = row } + end +end +if #rows > 0 then + table.sort(rows, function(a,b) return a.key < b.key end) + for _, row in ipairs(rows) + do + html.print(row.row) + end + -- discard + rows = nil else -{ - print "\n"; -} + html.print("") +end +-- discard +links = nil +ipalias = nil +hosts = nil +history = nil -# show remote nodes +-- footer -print "\n"; -print "\n"; -print "\n"; +html.print("") +html.print("") +html.print("") +html.print("") +html.print("") +html.print("
Local HostsServices

%s", $localpart . $tactical; - - if ( $wangateway{$ip} ) { $nodeiface = "wan" ; } - if ( $nodeiface ) { $rows{$host} .= "   ($nodeiface)"; } - - $rows{$host} .= " \n" ; - - foreach(sort keys %{$services{$host}}) - { - $rows{$host} .= "" . $services{$host}{$_} . "
\n"; - } - $rows{$host} .= "
 $localpart
 $localpart
 $localpart\n"; - foreach(sort keys %{$services{$dmzhost}}) - { - $rows{$host} .= "" . $services{$dmzhost}{$_} . "
\n"; - } - $rows{$host} .= "
" .. host .. "" + if hosts[ip] and hosts[ip].hosts then + for _, v in ipairs(hosts[ip].hosts) + do + row = row .. "
" .. v .. "" + end + end + row = row .. "
" + if age < 3600 then + local val = math.floor(age / 60) + if val == 1 then + row = row .. "1 minute ago" + else + row = row .. val .. " minutes ago" + end + else + val = string.format("%.1f", age / 3600) + if val == "1.0" then + row = row .. "1 hour ago" + else + row = row .. val .. " hours ago" + end + row = row .. "
none
none
 
Remote Nodes  ETX  Services

 
OLSR Entries

Total " .. olsr_total .. "
Nodes " .. olsr_nodes .. "
") -my $row; -foreach $ip (sort { $hosts{$a}{etx} <=> $hosts{$b}{etx} } keys %hosts) -{ - next if $neighbor{$ip}; - my $host = $hosts{$ip}{name}; - next unless $host; - my $localpart = $host =~ s/.local.mesh//r; - my $tactical = $hosts{$ip}{tactical} ? " / " . $hosts{$ip}{tactical} : ""; - my $etx = sprintf "%.2f", $hosts{$ip}{etx}; - - $row = sprintf "%s", $host, $localpart . $tactical; - - my $nodeiface; - my $mcount = 0 + $midcount{$ip}; - if ( $dtd{$ip} ) { $mcount -= 1; } # extra mid entry matching and with dtdlink in hosts_olsrd - if ( $hosts{$ip}{tactical} ) { $mcount -= 1; } # extra mid entry if tactical name defined - if ( $mcount > 0 ) { $nodeiface = "tun*$mcount" ; } - if ( $wangateway{$ip} ) { $nodeiface = $nodeiface ? $nodeiface . ",wan" : "wan" ; } - - if ( $nodeiface ) { $row .= "   ($nodeiface)"; } - - $row .= sprintf "%s\n", $etx; - foreach(sort keys %{$services{$host}}) - { - $row .= "" . $services{$host}{$_} . "
\n"; - } - $row .= "\n"; - - # add advertised dmz hosts - foreach $dmzhost (@{$hosts{$ip}{hosts}}) - { - my $localpart = $dmzhost =~ s/.local.mesh//r; - $row .= " $localpart"; - $row .= "\n"; - foreach(sort keys %{$services{$dmzhost}}) - { - $row .= "" . $services{$dmzhost}{$_} . "
\n"; - } - $row .= "\n"; - } - print $row; -} - -undef %neighbor; - -if(!$row) -{ - print "none\n"; -} - - -print " \n"; - -# show current neighbors - -print "\n"; -print "\n"; - -if(keys %links) -{ - my %rows = (); - - foreach $ip (keys %links) - { - my $ipmain = exists $ipalias{$ip} ? $ipalias{$ip} : $ip ; - my $host = $hosts{$ipmain}{name} ? $hosts{$ipmain}{name} : $ipmain; - my $localpart = $host =~ s/.local.mesh//r; - my $tactical = $hosts{$ipmain}{tactical} ? " / " . $hosts{$ipmain}{tactical} : ""; - if ( $rows{$host} ) { $host .= " " ; } # avoid collision 2 links to same host {rf, dtd} - - my $no_space_host=$host; - $no_space_host =~ s/\s+$//; - my $row = sprintf "\n"; - - # add advertised dmz hosts - foreach $dmzhost (@{$hosts{$ipmain}{hosts}}) - { - my $localpart = $dmzhost =~ s/.local.mesh//r; - $row .= "\n"; - } - $neighservices{$host}=1; - } - - $rows{$host}=$row; - } - - foreach(sort keys %rows) { print $rows{$_} } -} -else -{ - print "\n"; -} - -undef %services; - -# show previous neighbors - -print "\n"; -print "\n"; -print "\n"; -%rows = (); -($uptime) = `cat /proc/uptime` =~ /^(\d+)/; - -foreach $ip (keys %history) -{ - next if $links{$ip}; - next if $links{$ipalias{$ip}}; - $age = sprintf "%010d", $uptime - $history{$ip}{age}; - $host = $history{$ip}{host} ? $history{$ip}{host} : $ip; - $host =~ s/^mid\d+\.// ; - $host =~ s/^dtdlink\.// ; - $rows{$age} .= sprintf "\n"; -} - -if(keys %rows) -{ - foreach(sort keys %rows) { print $rows{$_} } -} -else -{ - print "\n"; -} - -print "\n"; -print "\n"; -print "\n"; -print "\n"; -print "\n"; -print "
Current Neighbors  LQNLQTxMbps  Services

%s", $no_space_host, $localpart . $tactical; - - my $nodeiface; - if ( $ipmain ne $ip ) # indicate if dtd or tunnel interface to neighbor - { - if ( $links{$ip}{dtd} ){ $nodeiface="dtd" ; } - elsif ( $links{$ip}{tun} ){ $nodeiface="tun" ; } - else { $nodeiface="?" ; } - } - - if ( $wangateway{$ip} || $wangateway{$ipmain} ) { $nodeiface = $nodeiface ? $nodeiface . ",wan" : "wan" ; } - if ( $nodeiface ) { $row .= "   ($nodeiface)"; } - - $row .= sprintf ("%.0f%%%.0f%%%s\n", 100*$links{$ip}{lq}, 100*$links{$ip}{nlq},$links{$ip}{mbps}); - - if ( ! exists $neighservices{$host} ) - { - foreach(sort keys %{$services{$host}}) { $row .= "" . $services{$host}{$_} . "
\n" } - - $row .= "
 $localpart\n"; - foreach(sort keys %{$services{$dmzhost}}) { $row .= $services{$dmzhost}{$_} . "
\n" } - $row .= "
none
 
Previous NeighborsWhen

%s", $host; - foreach(@{$hosts{$ip}{hosts}}) { $rows{$age} .= "
$_" } - $rows{$age} .= "
"; - if($age < 3600) - { - $val = int($age/60); - $rows{$age} .= $val == 1 ? "1 minute ago" : "$val minutes ago"; - } - else - { - $val = sprintf "%.1f", $age/3660; - $rows{$age} .= $val eq "1.0" ? "1 hour ago" : "$val hours ago"; - } - $rows{$age} .= "
none
 
OLSR Entries

Total $olsrTotal
Nodes $olsrNodes
\n"; - -# end -print "\n"; -print "\n"; - -if($debug) -{ - print "
\n";
-    print "localhosts\n";
-    foreach $ip (sort keys %localhosts)
-    {
-	printf "%s %s", $ip, $localhosts{$ip}{name};
-	printf "/%s", $localhosts{$ip}{tactical} if $localhosts{$ip}{tactical};
-	foreach(@{$localhosts{$ip}{hosts}}) { print ":$_" }
-	print "\n";
-    }
-
-    print "\nhosts\n";
-    foreach $ip (sort keys %hosts)
-    {
-	$hosts{$ip}{name} = "" unless $hosts{$ip}{name};
-	printf "%s %s", $ip, $hosts{$ip}{name};
-	printf "/%s", $hosts{$ip}{tactical} if $hosts{$ip}{tactical};
-	foreach(@{$hosts{$ip}{hosts}}) { print ":$_" }
-	printf(" %d", $hosts{$ip}{mid}) if $hosts{$ip}{mid};
-    }
-
-    print "\nlinks\n";
-    foreach(sort keys %links)
-    {
-	print "$_\n";
-    }
-
-    print "
\n"; -} - -show_debug_info(); -show_parse_errors(); - -page_footer(); -print "\n"; -print "\n"; +-- end +html.print("") +html.print("") +html.footer(); +html.print("") +html.print("") diff --git a/files/www/cgi-bin/mesh.pl b/files/www/cgi-bin/mesh.pl new file mode 100755 index 00000000..c8eb246b --- /dev/null +++ b/files/www/cgi-bin/mesh.pl @@ -0,0 +1,680 @@ +#!/usr/bin/perl +=for comment + + Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks + Copyright (C) 2015 Conrad Lara + See Contributors file for additional contributors + + Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet + + 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. + +=cut + +$debug = 0; + +BEGIN {push @INC, '/www/cgi-bin'}; +use perlfunc; +use ucifunc; + +%rateL = ( + '1.0M' => '1', # CCP LP + '2.0M' => '2', # CCP LP + '5.5M' => '5.5', # CCP LP + 'MCS0' => '6.5', # HT20 LGI + '11.0M'=> '11', # CCP LP + 'MCS1' => '13', # HT20 LGI... + 'MCS2' => '19.5', + 'MCS3' => '26', + 'MCS4' => '39', + 'MCS5' => '52', + 'MCS6' => '58.5', + 'MCS7' => '65', + 'MCS8' => '13', + 'MCS9' => '26', + 'MCS10' => '39', + 'MCS11' => '52', + 'MCS12' => '78', + 'MCS13' => '104', + 'MCS14' => '117', + 'MCS15' => '130', +); + +%rateS = ( + 'MCS0' => '7.2', + 'MCS1' => '14.4', + 'MCS2' => '21.7', + 'MCS3' => '28.9', + 'MCS4' => '43.3', + 'MCS5' => '57.8', + 'MCS6' => '65', + 'MCS7' => '72.2', + 'MCS8' => '14.4', + 'MCS9' => '28.9', + 'MCS10' => '43.3', + 'MCS11' => '57.8', + 'MCS12' => '86.7', + 'MCS13' => '115.6', + 'MCS14' => '130', + 'MCS15' => '144.4', + ); + +# Limit displayed nodes and services to the most reachable routes if memory on the node is small +%lowMemoryLimits = ( + memory => `/sbin/uci -q get aredn.\@meshstatus[0].lowmem` || 10000, + routes => `/sbin/uci -q get aredn.\@meshstatus[0].lowroutes` || 1000, +); + +sub get_available_mem +{ + foreach(`free`) + { + next unless /^Mem[:]/; + my @tmp = split /\s+/, $_; + return $tmp[6]; + } + return "N/A"; +} + +# collect some variables +$node = nvram_get("node"); +$node = "NOCALL" if $node eq ""; +$tactical = nvram_get("tactical"); +$config = nvram_get("config"); +$config = "not set" if $config eq "" || not -d "/etc/config.mesh"; +($my_ip) = get_ip4_network(get_interface("wifi")); +${wifiif} = `uci -q get 'network.wifi.ifname'`; +chomp ${wifiif}; +$phy = get_wlan2phy("${wifiif}"); + +chomp ($chanbw = `cat /sys/kernel/debug/ieee80211/${phy}/ath9k/chanbw`); +if ($chanbw eq "0x00000005") {$chanbw = 4;} +elsif ($chanbw eq "0x0000000a") {$chanbw = 2;} + else {$chanbw = 1;} + +read_postdata(); + +system "mkdir -p /tmp/web"; +system "touch /tmp/web/automesh" if $parms{auto}; +system "rm -f /tmp/web/automesh" if $parms{stop}; + +#get location info if available +$lat=&uci_get_indexed_option("aredn","location",0,"lat"); +$lon=&uci_get_indexed_option("aredn","location",0,"lon"); +if($lat ne "" and $lon ne "") +{ + $lat_lon = "
Location: $lat $lon
"; +} +else +{ + $lat_lon = "Location Not Available"; +} +@route30 = `/sbin/ip route list table 30`; +$olsrTotal = scalar @route30; +$olsrNodes = scalar grep /\//, @route30; +$node_desc = `/sbin/uci -q get system.\@system[0].description`; #pull the node description from uci +undef @route30; + +# parse the txtinfo output + +chomp($tmperr = `mktemp /tmp/web/nc.XXXXXX`); + +foreach(`echo /rou | nc 127.0.0.1 2006 2>>$tmperr`) +{ + next if /^\D/; + my ($ip, $junk, $junk, $etx) = split /\s+/, $_; + my ($net, $cidr) = split /\//, $ip; + if ( $etx <= 50 ) { $routes{$net}{etx} = $etx; } +} +if ($olsrTotal > $lowMemoryLimits{routes} && get_available_mem() < $lowMemoryLimits{memory}) +{ + my @oroutes = sort { $routes{$a}{etx} <=> $routes{$b}{etx} } keys %routes; + foreach ( @oroutes[$lowMemoryLimits{routes} .. $#oroutes] ) + { + delete $routes{$_}; + } +} +@arps = {}; +open my $fh, '<', '/proc/net/arp'; +foreach (<$fh>) { push @arps, $_ if (/$wifiif/ && !/00:00:00:00:00:00/) } +close $fh; +foreach(`echo /lin | nc 127.0.0.1 2006 2>>$tmperr`) +{ + next if /^\D/; + my ($junk, $ip, $junk, $lq, $nlq) = split /\s+/, $_; + $links{$ip} = { + lq => $lq, + nlq => $nlq, + mbps => "" + }; + $neighbor{$ip} = 1; + + my ($mac) = grep /^$ip/, @arps; + $mac =~ s/^.*(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w).*$/$1/; + chomp $mac; + + open my $fh, '<', "/sys/kernel/debug/ieee80211/${phy}/netdev:${wifiif}/stations/$mac/rc_stats_csv"; + if ( $mac && $fh ) + { + my @csv = <$fh>; + close $fh; + #802.11b/n + my ($mbps) = grep /^([^,]*,){3}A/, @csv; + if ($mbps) + { + my ($gi, $dummy, $rate, $dummy, $ewma) = $mbps =~ /^[^,]*,([^,]*),([^,]*,){2}([^,]*),([^,]*,){4}([^,]*).*$/ ; + $rate =~ s/[ \t]//g; + $mbps = $gi eq "SGI" ? $rateS{$rate}*$ewma/100 : $rateL{$rate}*$ewma/100 ; + } + else + { + #802.11a/b/g + ($mbps) = grep /^A/, @csv; + if ($mbps) + { + $mbps =~ /^[^,]*,([^,]*),([^,]*,){4}([^,]*).*$/; + $mbps = $1*$3/100; + } + } + $links{$ip}{mbps} = $mbps ? sprintf "%.1f", $mbps / $chanbw : "0.0"; + } +} +undef @arps; +foreach(`echo /hna | nc 127.0.0.1 2006 2>>$tmperr`) +{ + next if /^\D/; + my ($iproute, $ip) = split /\s+/, $_; + my ($net, $cidr) = split /\//, $iproute; + if ( $net eq "0.0.0.0" ) { $wangateway{$ip} = 1; } +} +foreach(`echo /mid | nc 127.0.0.1 2006 2>>$tmperr`) +{ + next if /^\D/; + my ($ip, $junk) = $_ =~ /^(\S+)\s+(.*)$/; + foreach $aip ( split /\s+/, $junk ) + { + $ipalias{$aip} = $ip; + $neighbor{$aip} = 1; + if ( $links{$aip} ) { $neighbor{$ip} = 1 } + } +} + +# stat and -s do not work in microperl +@parts = split /\s+/, `ls -l $tmperr`; +$txtinfo_err = $parts[4]; +unlink $tmperr; + +# load the local hosts file + +open my $fh, '<', '/etc/hosts'; +foreach (<$fh>) +{ + next unless /^10[.]/; + chomp; + my ($ip, $name, $tactical) = split /\s+/, $_; + next if $name =~ /^(localhost|localnode|localap|dtdlink\..*)$/; + if ( $name !~ /\./ ) { $name="${name}.local.mesh"; } + if($ip eq $my_ip) + { + $tactical = "" unless $tactical; + $localhosts{$ip}{tactical} = $tactical; + $localhosts{$ip}{name} = $name; + } + else { push @{$localhosts{$my_ip}{hosts}}, $name; } + if($tactical eq "#NOPROP") { push @{$localhosts{$my_ip}{noprops}}, $name; } + if($tactical eq "#ALIAS") { push @{$localhosts{$my_ip}{aliases}}, $name; } +} +close $fh; + +# load the olsr hosts file +open my $fh, '<', '/var/run/hosts_olsr'; +foreach (<$fh>) +{ + next unless /^\d/; + chomp; + my ($ip, $name, undef, $originator, undef, undef) = split /\s+/, $_; + next unless $originator; + next if $originator eq "myself"; + # Filter hosts which are unreachable + next unless $routes{$ip} || $routes{$originator}; + + my $etx = $routes{$ip}{etx} ? $routes{$ip}{etx} : $routes{$originator}{etx}; + + if (( $name !~ /\./ ) || ( $name =~ /^mid\.[^\.]*$/ )) { $name="${name}.local.mesh"; } + + if ( $ip eq $originator ) + { + if ($hosts{$ip}{name}) + { + $hosts{$ip}{tactical} = $name; + } + else + { + $hosts{$ip}{name} = $name; + $hosts{$ip}{etx} = $etx; + } + } + elsif ( $name =~ /^dtdlink\..*$/ ) + { + $dtd{$originator} = 1; + if ( $links{$ip} ) { $links{$ip}{dtd} = 1 } + } + elsif ( $name =~ /^mid\d+\..*$/ ) + { + $midcount{$originator} = $midcount{$originator} ? $midcount{$originator}+1: 1 ; + if ( $links{$ip} ) { $links{$ip}{tun} = 1 } + } + else + { + push @{$hosts{$originator}{hosts}}, $name; + } +} +close $fh; + +# Discard +undef %routes; + +# load the olsr services file +open my $fh, '<', '/var/run/services_olsr'; +foreach (<$fh>) +{ + next unless /^\w/; + chomp; + my ($url, $junk, $name) = split /\|/, $_; + next unless defined $name; + my ($protocol, $host, $port, $path) = $url =~ /^(\w+):\/\/([\w\-\.]+):(\d+)\/(.*)/; + next unless defined $path; + my ($name, $originator) = split /\#/, $name; + + # Filter services for unreachable hosts + next unless $hosts{$originator}{name} || $originator eq " my own service"; + + $name =~ s/\s+$//; + + if ( $host !~ /\./ ) { $host="${host}.local.mesh"; } + + # attempt to work around olsr never forgetting defunct services + # assume that the first entry in the file by this name is the most recent, ignore the rest + next if $services{$host}{$name}; + + $services{$host}{$name} = $port ? "$name" : $name; +} +close $fh; + +# load the node history +open my $fh, '<', '/tmp/node.history'; +foreach (<$fh>) +{ + chomp; + ($ip, $age, $host) = split / /, $_; + next unless $age; + $host = "" unless $host; + $host =~ s|/| / |; + $history{$ip}{age} = $age; + $history{$ip}{host} = $host; +} +close $fh; + +#delete $hosts{"127.0.0.1"}; + +# compress the output if we can +if ( $ENV{HTTP_ACCEPT_ENCODING} =~ /gzip/ ) +{ + print "Content-type: text/html\r\nCache-Control: no-store\r\nContent-Encoding: gzip\r\n\r\n"; + open my $zout, "|gzip"; + select $zout; +} +else +{ + print "Content-type: text/html\r\nCache-Control: no-store\r\n\r\n"; +} + +# generate the page +html_header("$node mesh status", 0); +print "\n" if -f "/tmp/web/automesh"; +print "\n"; +print "
\n"; +print "\n"; +print "
\n"; + +alert_banner(); + +# page header +print "

$node mesh status

"; + +print "$lat_lon"; #display lat lon info +print "
$node_desc
" if $node_desc; #display node description +print "
"; + +# nav buttons +if(-f "/tmp/web/automesh") +{ + print "\n"; +} +else +{ + print "\n"; + print "   \n"; + print "\n"; +} + +print "   "; +print "\n"; + +if($txtinfo_err) +{ + print "

Whoops! OLSR is not running, try again later.\n"; + print "
"; + page_footer(); + print "\n"; + exit; +} + +print "

\n"; + +unless(keys %localhosts || keys %links) +{ + print "No other nodes are available.\n"; + print ""; + page_footer(); + print "\n"; + + +# show local hosts + +print "\n"; +print "\n"; + +if(keys %localhosts) +{ + my %rows = (); + + foreach $ip (keys %localhosts) + { + my $host = $localhosts{$ip}{name}; + my $localpart = $host =~ s/.local.mesh//r; + my $tactical = $localhosts{$ip}{tactical} ? " / " . $localhosts{$ip}{tactical} : ""; + $rows{$host} = sprintf "\n"; + + # add locally advertised dmz hosts + foreach $dmzhost (@{$localhosts{$ip}{hosts}}) + { + #find non-propagated and aliased hosts and change color + my $nopropd = 0; + my $aliased = 0; + if(grep { /$dmzhost/ } @{$localhosts{$ip}{noprops}}) { $nopropd = 1; } + if(grep { /$dmzhost/ } @{$localhosts{$ip}{aliases}}) { $aliased = 1; } + $localpart = $dmzhost =~ s/.local.mesh//r; + if(!$nopropd && !$aliased) { $rows{$host} .= ""; } + elsif($aliased) { $rows{$host} .= ""; } + else { $rows{$host} .= ""; } + $rows{$host} .= "\n"; + } + } + + foreach(sort keys %rows) { print $rows{$_} } +} +else +{ + print "\n"; +} + + +# show remote nodes + +print "\n"; +print "\n"; +print "\n"; + +my $row; +foreach $ip (sort { $hosts{$a}{etx} <=> $hosts{$b}{etx} } keys %hosts) +{ + next if $neighbor{$ip}; + my $host = $hosts{$ip}{name}; + next unless $host; + my $localpart = $host =~ s/.local.mesh//r; + my $tactical = $hosts{$ip}{tactical} ? " / " . $hosts{$ip}{tactical} : ""; + my $etx = sprintf "%.2f", $hosts{$ip}{etx}; + + $row = sprintf "\n"; + + # add advertised dmz hosts + foreach $dmzhost (@{$hosts{$ip}{hosts}}) + { + my $localpart = $dmzhost =~ s/.local.mesh//r; + $row .= ""; + $row .= "\n"; + } + print $row; +} + +undef %neighbor; + +if(!$row) +{ + print "\n"; +} + + +print "
Local HostsServices

%s", $localpart . $tactical; + + if ( $wangateway{$ip} ) { $nodeiface = "wan" ; } + if ( $nodeiface ) { $rows{$host} .= "   ($nodeiface)"; } + + $rows{$host} .= " \n" ; + + foreach(sort keys %{$services{$host}}) + { + $rows{$host} .= "" . $services{$host}{$_} . "
\n"; + } + $rows{$host} .= "
 $localpart
 $localpart
 $localpart\n"; + foreach(sort keys %{$services{$dmzhost}}) + { + $rows{$host} .= "" . $services{$dmzhost}{$_} . "
\n"; + } + $rows{$host} .= "
none
 
Remote Nodes  ETX  Services

%s", $host, $localpart . $tactical; + + my $nodeiface; + my $mcount = 0 + $midcount{$ip}; + if ( $dtd{$ip} ) { $mcount -= 1; } # extra mid entry matching and with dtdlink in hosts_olsrd + if ( $hosts{$ip}{tactical} ) { $mcount -= 1; } # extra mid entry if tactical name defined + if ( $mcount > 0 ) { $nodeiface = "tun*$mcount" ; } + if ( $wangateway{$ip} ) { $nodeiface = $nodeiface ? $nodeiface . ",wan" : "wan" ; } + + if ( $nodeiface ) { $row .= "   ($nodeiface)"; } + + $row .= sprintf "%s\n", $etx; + foreach(sort keys %{$services{$host}}) + { + $row .= "" . $services{$host}{$_} . "
\n"; + } + $row .= "
 $localpart\n"; + foreach(sort keys %{$services{$dmzhost}}) + { + $row .= "" . $services{$dmzhost}{$_} . "
\n"; + } + $row .= "
none
 \n"; + +# show current neighbors + +print "\n"; +print "\n"; + +if(keys %links) +{ + my %rows = (); + + foreach $ip (keys %links) + { + my $ipmain = exists $ipalias{$ip} ? $ipalias{$ip} : $ip ; + my $host = $hosts{$ipmain}{name} ? $hosts{$ipmain}{name} : $ipmain; + my $localpart = $host =~ s/.local.mesh//r; + my $tactical = $hosts{$ipmain}{tactical} ? " / " . $hosts{$ipmain}{tactical} : ""; + if ( $rows{$host} ) { $host .= " " ; } # avoid collision 2 links to same host {rf, dtd} + + my $no_space_host=$host; + $no_space_host =~ s/\s+$//; + my $row = sprintf "\n"; + + # add advertised dmz hosts + foreach $dmzhost (@{$hosts{$ipmain}{hosts}}) + { + my $localpart = $dmzhost =~ s/.local.mesh//r; + $row .= "\n"; + } + $neighservices{$host}=1; + } + + $rows{$host}=$row; + } + + foreach(sort keys %rows) { print $rows{$_} } +} +else +{ + print "\n"; +} + +undef %services; + +# show previous neighbors + +print "\n"; +print "\n"; +print "\n"; +%rows = (); +($uptime) = `cat /proc/uptime` =~ /^(\d+)/; + +foreach $ip (keys %history) +{ + next if $links{$ip}; + next if $links{$ipalias{$ip}}; + $age = sprintf "%010d", $uptime - $history{$ip}{age}; + $host = $history{$ip}{host} ? $history{$ip}{host} : $ip; + $host =~ s/^mid\d+\.// ; + $host =~ s/^dtdlink\.// ; + $rows{$age} .= sprintf "\n"; +} + +if(keys %rows) +{ + foreach(sort keys %rows) { print $rows{$_} } +} +else +{ + print "\n"; +} + +print "\n"; +print "\n"; +print "\n"; +print "\n"; +print "\n"; +print "
Current Neighbors  LQNLQTxMbps  Services

%s", $no_space_host, $localpart . $tactical; + + my $nodeiface; + if ( $ipmain ne $ip ) # indicate if dtd or tunnel interface to neighbor + { + if ( $links{$ip}{dtd} ){ $nodeiface="dtd" ; } + elsif ( $links{$ip}{tun} ){ $nodeiface="tun" ; } + else { $nodeiface="?" ; } + } + + if ( $wangateway{$ip} || $wangateway{$ipmain} ) { $nodeiface = $nodeiface ? $nodeiface . ",wan" : "wan" ; } + if ( $nodeiface ) { $row .= "   ($nodeiface)"; } + + $row .= sprintf ("%.0f%%%.0f%%%s\n", 100*$links{$ip}{lq}, 100*$links{$ip}{nlq},$links{$ip}{mbps}); + + if ( ! exists $neighservices{$host} ) + { + foreach(sort keys %{$services{$host}}) { $row .= "" . $services{$host}{$_} . "
\n" } + + $row .= "
 $localpart\n"; + foreach(sort keys %{$services{$dmzhost}}) { $row .= $services{$dmzhost}{$_} . "
\n" } + $row .= "
none
 
Previous NeighborsWhen

%s", $host; + foreach(@{$hosts{$ip}{hosts}}) { $rows{$age} .= "
$_" } + $rows{$age} .= "
"; + if($age < 3600) + { + $val = int($age/60); + $rows{$age} .= $val == 1 ? "1 minute ago" : "$val minutes ago"; + } + else + { + $val = sprintf "%.1f", $age/3660; + $rows{$age} .= $val eq "1.0" ? "1 hour ago" : "$val hours ago"; + } + $rows{$age} .= "
none
 
OLSR Entries

Total $olsrTotal
Nodes $olsrNodes
\n"; + +# end +print "\n"; +print "\n"; + +if($debug) +{ + print "
\n";
+    print "localhosts\n";
+    foreach $ip (sort keys %localhosts)
+    {
+	printf "%s %s", $ip, $localhosts{$ip}{name};
+	printf "/%s", $localhosts{$ip}{tactical} if $localhosts{$ip}{tactical};
+	foreach(@{$localhosts{$ip}{hosts}}) { print ":$_" }
+	print "\n";
+    }
+
+    print "\nhosts\n";
+    foreach $ip (sort keys %hosts)
+    {
+	$hosts{$ip}{name} = "" unless $hosts{$ip}{name};
+	printf "%s %s", $ip, $hosts{$ip}{name};
+	printf "/%s", $hosts{$ip}{tactical} if $hosts{$ip}{tactical};
+	foreach(@{$hosts{$ip}{hosts}}) { print ":$_" }
+	printf(" %d", $hosts{$ip}{mid}) if $hosts{$ip}{mid};
+    }
+
+    print "\nlinks\n";
+    foreach(sort keys %links)
+    {
+	print "$_\n";
+    }
+
+    print "
\n"; +} + +show_debug_info(); +show_parse_errors(); + +page_footer(); +print "\n"; +print "\n"; + diff --git a/files/www/cgi-bin/scan b/files/www/cgi-bin/scan index aa332068..dba10cf7 100755 --- a/files/www/cgi-bin/scan +++ b/files/www/cgi-bin/scan @@ -1,98 +1,262 @@ -#!/usr/bin/perl -=for comment +#!/usr/bin/lua +--[[ - Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks - Copyright (C) 2015 Conrad Lara - See Contributors file for additional contributors + 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 - Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet + 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 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. - 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 . - You should have received a copy of the GNU General Public License - along with this program. If not, see . + Additional Terms: - Additional Terms: + Additional use restrictions exist on the AREDN(TM) trademark and logo. + See AREDNLicense.txt for more info. - 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. - 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. - 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 - Modified versions must be modified to attribute to the original source - and be marked in reasonable ways as differentiate it from the original - version. +--]] -=cut +require("aredn.http") +require("aredn.hardware") +local html = require("aredn.html") +local aredn_info = require("aredn.info") -$debug = 0; +local node = aredn_info.get_nvram("node") +if not node then + node = "NOCALL" +end +local wifiiface = aredn.hardware.get_iface_name("wifi") -BEGIN {push @INC, '/www/cgi-bin'}; -use perlfunc; +if not nixio.fs.stat("/tmp/web") then + nixio.fs.mkdir("/tmp/web") +end -# collect some variables -$node = nvram_get("node"); -$node = "NOCALL" if $node eq ""; +-- scan start -read_postdata(); +local scanned = {} +local f = io.popen("iw dev " .. wifiiface .. " scan passive") +if f then + local scan + for line in f:lines() + do + local m = line:match("^BSS ([%da-fA-F:]+)") + if m then + scan = { + mac = m, + mode = "AP", + ssid = "", + signal = 0, + freq = 0, + key = "" + } + scanned[m] = scan + if line:match("joined") then + scan.mode = "My Ad-Hoc Network" + end + end + m = line:match("freq: (%d+)") + if m then + scan.freq = tonumber(m) + end + m = line:match("SSID: (.+)") + if m then + scan.ssid = m + end + m = line:match("signal: ([%d-]+)") + if m then + scan.signal = tonumber(m) + end + m = line:match("Group cipher: (.+)") + if m then + scan.key = m + end + if line:match("capability: IBSS") and scan.mode == "AP" then + scan.mode = "Foreign Ad-Hoc Network" + end + end + if scan then + scanned[#scanned + 1] = scan + end + f:close() +end +local f = io.popen("iw dev " .. wifiiface .. " station dump") +if f then + local scan + local myssid = aredn_info.getSSID() + local myfreq = tonumber(aredn_info.getFreq()) + for line in f:lines() + do + local m = line:match("^Station ([%da-fA-F:]+) %(on " .. wifiiface .. "%)") + if m then + scan = scanned[m] + if not scan then + scan = { + mac = m, + mode = "Connected Ad-Hoc Station", + ssid = myssid, + signal = 0, + freq = myfreq, + key = "" + } + scanned[m] = scan + else + scan.mode = "Connected Ad-Hoc Station" + scan.ssid = myssid + scan.key = "" + scan.freq = myfreq + end + end + m = line:match("signal avg:%s+([%d-]+)") + if m then + scan.signal = tonumber(m) + end + end + f:close() +end -system "mkdir -p /tmp/web"; -system "/usr/local/bin/wscan -w > /tmp/web/wscan.next" unless $parms{stop}; -system "touch /tmp/web/autoscan" if $parms{auto}; -system "rm -f /tmp/web/autoscan" if $parms{stop}; -system "mv /tmp/web/wscan.next /tmp/web/wscan" unless $parms{stop}; +-- scan end -# generate the page -http_header(); -html_header("$node WiFi scan", 0); -print "\n" if -f "/tmp/web/autoscan"; -print ""; -print ""; -print "\n"; -print "
\n"; -print "
\n"; -alert_banner(); -print "

$node WiFi scan


\n"; +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 + ) + if request:formvalue("auto") then + io.open("/tmp/web/autoscan", "w"):close() + end + if request:formvalue("stop") then + os.remove("/tmp/web/autoscan") + end +end -if(-f "/tmp/web/autoscan") -{ - print "\n"; -} +-- generate page +http_header() +html.header(node .. " WiFi scan", false) +local autoscan = nixio.fs.stat("/tmp/web/autoscan"); +if autoscan then + html.print("") +end +html.print([[ + + + + +
+]]) + +html.alert_banner() +html.print("

" .. node .. " WiFi scan


") + +if autoscan then + html.print("") else -{ - print "\n"; - print "   \n"; - print "\n"; -} + html.print("") + html.print("   ") + html.print("") +end -print "   \n"; -print "

\n"; -system "cat /tmp/web/wscan"; -print "
"; -print "
\n"; -print "\n"; +html.print("   ") +html.print("

") -show_debug_info(); -show_parse_errors(); +-- display scan +html.print("") +html.print("") -page_footer(); -print "\n"; -print "\n"; +-- load arp cache +local arpcache = {} +arptable(function(a) + arpcache[a["HW address"]] = a["IP address"] +end) + +local scanlist = {} +for _, v in pairs(scanned) +do + if v.signal ~= 0 then + scanlist[#scanlist + 1] = v + end +end +table.sort(scanlist, function(a, b) return a.signal > b.signal end) +for _, scan in ipairs(scanlist) +do + -- freq to chan + local chan = scan.freq + if chan < 256 then + elseif chan == 2484 then + chan = 14 + elseif chan == 2407 then + chan = 0 + elseif chan < 2484 then + chan = (chan - 2407) / 5 + elseif chan < 5000 then + elseif chan < 5380 then + chan = (chan - 5000) / 5 + elseif chan < 5500 then + chan = chan - 2000 + elseif chan < 6000 then + chan = (chan - 5000) / 5 + end + -- ip lookup then host lookup + local ip = arpcache[scan.mac] + if ip then + hostname = ip + local f = io.popen("nslookup " .. ip) + if f then + for line in f:lines() + do + local m = line:match("name = (.*)%.local%.mesh") + if m then + hostname = m + break + end + end + f:close() + end + else + hostname = "N/A" + end + if scan.ssid:match("^AREDN-") then + html.print("") + else + html.print("") + end + html.print("") + html.print("") +end + +html.print("
SigChanEncSSIDHostnameMAC/BSSID802.11 Mode
" .. scan.signal .. "" .. chan .. "" .. scan.key .. "" .. scan.ssid .. "" .. hostname .. "" .. scan.mac:upper() .. "" .. scan.mode .. "

") + +html.footer() +html.print("") diff --git a/files/www/cgi-bin/scan.pl b/files/www/cgi-bin/scan.pl new file mode 100755 index 00000000..aa332068 --- /dev/null +++ b/files/www/cgi-bin/scan.pl @@ -0,0 +1,98 @@ +#!/usr/bin/perl +=for comment + + Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks + Copyright (C) 2015 Conrad Lara + See Contributors file for additional contributors + + Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet + + 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. + +=cut + +$debug = 0; + +BEGIN {push @INC, '/www/cgi-bin'}; +use perlfunc; + +# collect some variables +$node = nvram_get("node"); +$node = "NOCALL" if $node eq ""; + +read_postdata(); + +system "mkdir -p /tmp/web"; +system "/usr/local/bin/wscan -w > /tmp/web/wscan.next" unless $parms{stop}; +system "touch /tmp/web/autoscan" if $parms{auto}; +system "rm -f /tmp/web/autoscan" if $parms{stop}; +system "mv /tmp/web/wscan.next /tmp/web/wscan" unless $parms{stop}; + +# generate the page +http_header(); +html_header("$node WiFi scan", 0); +print "\n" if -f "/tmp/web/autoscan"; +print ""; +print ""; +print "\n"; +print "
\n"; +print "
\n"; +alert_banner(); +print "

$node WiFi scan


\n"; + +if(-f "/tmp/web/autoscan") +{ + print "\n"; +} +else +{ + print "\n"; + print "   \n"; + print "\n"; +} + +print "   \n"; +print "

\n"; +system "cat /tmp/web/wscan"; +print "
"; +print "
\n"; +print "
\n"; + +show_debug_info(); +show_parse_errors(); + +page_footer(); +print "\n"; +print "\n"; diff --git a/files/www/cgi-bin/signal b/files/www/cgi-bin/signal index 54c48108..142cb0cc 100755 --- a/files/www/cgi-bin/signal +++ b/files/www/cgi-bin/signal @@ -1,307 +1,305 @@ -#!/usr/bin/perl -=for comment +#!/usr/bin/lua +--[[ - Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks - Copyright (C) 2015 Darryl Quinn - See Contributors file for additional contributors + Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks + Copyright (C) 2021 Tim Wilkinson + Original Perl Copyright (C) 2015 Darryl Quinn + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . - Additional Terms: + Additional Terms: - Additional use restrictions exist on the AREDN(TM) trademark and logo. - See AREDNLicense.txt for more info. + 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. + 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. + 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. -=cut + Modified versions must be modified to attribute to the original source + and be marked in reasonable ways as differentiate it from the original + version -$debug = 0; +--]] -BEGIN {push @INC, '/www/cgi-bin'}; -use perlfunc; +require("nixio") +require("aredn.http") +require("aredn.utils") +local html = require("aredn.html") +local aredn_info = require("aredn.info") -chomp ($tzone=`date +%Z`); -read_query_string(); +local node = aredn_info.get_nvram("node") +if not node then + node = "NOCALL" +end +local device = "" +local tzone = capture("date +%Z"):gsub("\n","") -# collect some variables -$node = nvram_get("node"); -$node = "NOCALL" unless $node; +local dmode = "Realtime" +-- query string +local query = os.getenv("QUERY_STRING") +if query then + require('luci.http') + local params = luci.http.urldecode_params(query) + if params.realtime then + dmode = "Realtime" + else + dmode = "Archived" + end + device = params.device +end -if($parms{"realtime"} ) -{ - $dmode="Realtime"; -} else { - $dmode="Archived"; -} +-- get list of files from /tmp/snrlog +local snrfiles = {} +local devfound = false +if nixio.fs.stat("/tmp/snrlog") then + for filename in nixio.fs.dir("/tmp/snrlog") + do + snrfiles[#snrfiles + 1] = filename + if device == filename then + devfound = true + end + end +end +if not devfound then + device = "" +end -http_header(); -html_header("$node $dmode signal strength", 0); +http_header() +html.header(node .. " " .. dmode .. " signal strength", false) -$header = < - - - + + - -EOF - -$page = < -
-
-
-
-
- Loading . . . + gainNode.gain.value = 1; + + function toneFreq(snr) { + var p = document.getElementById("tonePitch").value; + oscillator.frequency.value = snr * p; + var v = document.getElementById("toneVol").value; + gainNode.gain.value = v; + } + + function toneOn() { + oscillator = audioCtx.createOscillator(); + gainNode = audioCtx.createGain(); + oscillator.connect(gainNode); + oscillator.type = 'sine'; + gainNode.connect(audioCtx.destination); + gainNode.gain.value = .5; + oscillator.start(); + document.getElementById("toneOff").disabled = false; + document.getElementById("toneOn").disabled = true; + }; + + function toneOff() { + document.getElementById("toneOff").disabled = true; + document.getElementById("toneOn").disabled = false; + oscillator.stop(); + }; + + + + +
+
+
+
+
+ Loading . . . +
-
-
-
-
-
-

$node


- - - - -
-
-
- Selected Device:  +]] +html.print(prelude:gsub("$dmode", dmode):gsub("$tzone", tzone):gsub("$parms{device}", device):gsub("$node", node)) -# get a list of files from /tmp/snrlog -my @files = `ls -1A /tmp/snrlog`; -$parms{device}="" if(!/$parms{device}/ ~~ @files); +if dmode == "Realtime" and device == "" then + html.print("") +end -# default to "Strongest Signal" for Realtime -if(! $parms{device} and $parms{realtime}) { - $page = $page . ""; -} +local first_sel = true +for _, logfile in ipairs(snrfiles) +do + local dmac, dname = logfile:match("^([%da-f:]+)-(.*)$") + if dname == "" then + dname = dmac + end + if device == logfile or (dmode ~= "Realtime" and first_sel) then + html.print("") + first_sel = false + else + html.print("") + end +end -$firstSel=1; -# iterate over each file -foreach $logfile (@files) -{ - chomp($logfile); - my ($dmac, $dname) = $logfile =~ /^(.*?)\-(.*)/; - $dname=$dmac if($dname eq ''); - if($parms{device} eq $logfile or (!$parms{realtime} and $firstSel)) { - $page = $page . "\n"; - $firstSel=0; - } else { - $page = $page . "\n"; - } -} - -$page = $page . "\n"; -$page = $page . "\n" if($parms{realtime} eq "1"); -$page = $page . < -
-
-EOF -$page = $page . "

SNR: 0dB

" if($parms{"realtime"} eq "1"); -if($parms{"realtime"} eq "1") { - $page = $page . "
Sound:  "; - $page = $page . "

"; - $page = $page . "Pitch: 

"; - $page = $page . "Volume: 

"; - $page = $page . "
"; -} - -$page = $page . < -
-
-EOF - -print $header; -print $page; -show_debug_info(); -print ""; - -sub DEBUGEXIT() -{ - my ($text) = @_; - http_header(); - html_header("$node setup", 1); - print "DEBUG-"; - print $text; - print ""; - exit; -} +html.print("") +if dmode == "Realtime" then + html.print("") + html.print("
") + html.print("

SNR: 0dB

") + html.print("
Sound:  ") + html.print("

") + html.print("Pitch: 

") + html.print("Volume: 

") + html.print("
") +else + html.print("
") +end +html.print("
") +html.print("") diff --git a/files/www/cgi-bin/signal.pl b/files/www/cgi-bin/signal.pl new file mode 100755 index 00000000..54c48108 --- /dev/null +++ b/files/www/cgi-bin/signal.pl @@ -0,0 +1,307 @@ +#!/usr/bin/perl +=for comment + + Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks + Copyright (C) 2015 Darryl Quinn + 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. +=cut + +$debug = 0; + +BEGIN {push @INC, '/www/cgi-bin'}; +use perlfunc; + +chomp ($tzone=`date +%Z`); +read_query_string(); + +# collect some variables +$node = nvram_get("node"); +$node = "NOCALL" unless $node; + +if($parms{"realtime"} ) +{ + $dmode="Realtime"; +} else { + $dmode="Archived"; +} + +http_header(); +html_header("$node $dmode signal strength", 0); + +$header = < + + + + +EOF + +$page = < +
+
+
+
+
+ Loading . . . +
+
+
+
+
+
+
+

$node


+
+ + + +
+
+
+ Selected Device: \n"; +$page = $page . "\n" if($parms{realtime} eq "1"); +$page = $page . < +
+
+EOF +$page = $page . "

SNR: 0dB

" if($parms{"realtime"} eq "1"); +if($parms{"realtime"} eq "1") { + $page = $page . "
Sound:  "; + $page = $page . "

"; + $page = $page . "Pitch: 

"; + $page = $page . "Volume: 

"; + $page = $page . "
"; +} + +$page = $page . < +
+
+EOF + +print $header; +print $page; +show_debug_info(); +print ""; + +sub DEBUGEXIT() +{ + my ($text) = @_; + http_header(); + html_header("$node setup", 1); + print "DEBUG-"; + print $text; + print ""; + exit; +} diff --git a/files/www/cgi-bin/status b/files/www/cgi-bin/status index 2ad5f12d..4cca8f14 100755 --- a/files/www/cgi-bin/status +++ b/files/www/cgi-bin/status @@ -1,298 +1,412 @@ -#!/usr/bin/perl -=for comment +#!/usr/bin/lua +--[[ - Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks - Copyright (C) 2015 Conrad Lara - See Contributors file for additional contributors + 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 - Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet + 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 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. - 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 . - You should have received a copy of the GNU General Public License - along with this program. If not, see . + Additional Terms: - Additional Terms: + Additional use restrictions exist on the AREDN(TM) trademark and logo. + See AREDNLicense.txt for more info. - 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. - 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. - 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 - Modified versions must be modified to attribute to the original source - and be marked in reasonable ways as differentiate it from the original - version. +--]] -=cut +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") -$debug = 0; -BEGIN {push @INC, '/www/cgi-bin'}; -use perlfunc; -use ucifunc; +-- helpers start -# collect some variables -$node = nvram_get("node"); -$node = "NOCALL" if $node eq ""; -$tactical = nvram_get("tactical"); -$config = nvram_get("config"); -$config = "not set" if $config eq "" or not -d "/etc/config.mesh"; -$wifi_iface = get_interface("wifi"); -$wifi_iface =~ /wlan(\d+)/; -$radio = ( defined $1 )? "radio$1" : "radio0"; -$wifi_disable = ( $wifi_iface =~ /eth.*$/ )? 1 : 0; +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 -if ( ! $wifi_disable ) -{ - ($junk, $wifi_channel) = &uci_get_named_option("wireless", "$radio", "channel"); - if ($wifi_channel >= 76 and $wifi_channel <= 99) - { - $wifi_channel = ($wifi_channel*5+3000); - } - ($junk, $wifi_chanbw) = &uci_get_named_option("wireless", "$radio", "chanbw"); +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 - $wifi_ssid = "N/A"; - @wisections = &uci_get_all_indexed_by_sectiontype("wireless", "wifi-iface"); - foreach(@wisections) { - if ($_->{network} eq "wifi") { - $wifi_ssid = $_->{ssid}; - } - } -} +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 -$node_desc = `/sbin/uci -q get system.\@system[0].description`; #pull the node description from uci -#get location info if available -$lat_lon = "Location Not Available"; -$lat=&uci_get_indexed_option("aredn","location",0,"lat"); -$lon=&uci_get_indexed_option("aredn","location",0,"lon"); +function css_options() + html.print("") + for file in nixio.fs.glob("/www/*.css") + do + if file ~= "/www/style.css" then + file = file:match("/www/(.*).css") + html.print("") + end + end +end -if($lat ne "" and $lon ne "") { - $lat_lon = "
Location: $lat $lon
"; -} -$olsrTotal = `/sbin/ip route list table 30 | wc -l`; #num hosts olsr is keeping track of -$olsrNodes = `/sbin/ip route list table 30 | egrep "/" | wc -l`; #num *nodes* on the network (minus other hosts) +-- helpers end -read_postdata(); +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 -if($parms{css} and -f "/www/$parms{css}" and $parms{css} =~ /\.css$/i) { - unlink "/tmp/web/style.css"; - symlink "/www/$parms{css}","/tmp/web/style.css"; -} +local cursor = uci.cursor() -# generate the page -http_header(); -html_header("$node status", 1); -print "\n"; -print "
\n"; +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 -alert_banner(); +local node_desc = cursor:get("system", "@system[0]", "description") +local lat_lon = "Location Not Available" +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("
Location: %s %s
", lat, lon) +end -# page header -print "

$node"; -print " / $tactical" if $tactical; -print "

"; -print "
$lat_lon
"; #display location info -print "
$node_desc
" if $node_desc; -print "
\n"; +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 -# nav buttons -print "
\n"; +-- post data -#print qq(\n); -print "Help\n"; +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 -print "   "; -print "\n"; +-- generate page -if($config eq "mesh") -{ - print "   "; - print "\n"; - if ( ! $wifi_disable ) - { - print "   "; - print "\n"; - } -} +http_header() +html.header(node .. " status", true) +html.print("") +html.print("
") -print "   "; -print "\n"; +html.alert_banner() -print "   "; +-- page header +html.print("

" .. node) +if tactical ~= "" then + html.print(" / " .. tactical) +end +html.print("

") +html.print("
" .. lat_lon .. "
") +if node_desc then + html.print("
" .. node_desc .. "
") +end +html.print("
") -print ""; +-- nav buttons +html.print("
") +html.print("Help") +html.print("  ") +html.print("") +if config == "mesh" then + html.print("  ") + html.print("") + if not wifi_disabled then + html.print("  ") + html.print("") + end +end +html.print("  ") +html.print("") +html.print("  ") +html.print("") +html.print(""; +html.print("") -print "\n"; +if config == "not set" then + html.print("

This node is not yet configured.
") + html.print("Go to the setup page and set your node name and password.
") + html.print("Click Save Changes, even if you didn't make any changes, then the node will reboot.
") + html.print("

") + html.print("
") + html.print("

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.

") + html.print("

This device is pre-configured with no restrictions as to the type of data being passed.

") + html.print("

Follow these steps if you wish to prohibit known encrypted traffic on the RF link. These instructions will disappear, so copy them for your reference:

") + html.print("

    ") + html.print("
  1. Setup your node name and password as instructed at the top of this page
  2. ") + html.print("
  3. After you Save Changes allow your node to reboot
  4. ") + html.print("
  5. Return to the Node Status page and navigate to Setup > Administration
  6. ") + html.print("
  7. Obtain the blockknownencryption package from the AREDN™ website OR refresh the Package list (node must be connected to the internet)
  8. ") + html.print("
  9. Install the blockknownencryption package by uploading it or choosing it from the package drop-down list
  10. ") + html.print("
  11. Wait until the package installs and then reboot your node
  12. ") + html.print("

") + html.print("
") +end +-- status display -if($config eq "not set") -{ - print "

This node is not yet configured.
"; - print "Go to the setup page and set your node name and password.
\n"; - print "Click Save Changes, even if you didn't make any changes, then the node will reboot.
\n"; - print "

\n"; - print "
\n"; - print "

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.

"; - print "

This device is pre-configured with no restrictions as to the type of data being passed.

\n"; - print "

Follow these steps if you wish to prohibit known encrypted traffic on the RF link. These instructions will disappear, so copy them for your reference:

"; - print "

    \n"; - print "
  1. Setup your node name and password as instructed at the top of this page
  2. "; - print "
  3. After you Save Changes allow your node to reboot
  4. "; - print "
  5. Return to the Node Status page and navigate to Setup > Administration
  6. "; - print "
  7. Obtain the blockknownencryption package from the AREDN™ website OR refresh the Package list (node must be connected to the internet)
  8. "; - print "
  9. Install the blockknownencryption package by uploading it or choosing it from the package drop-down list
  10. "; - print "
  11. Wait until the package installs and then reboot your node
  12. "; - print "

\n"; - print "
"; -} +local col1 = {} +local col2 = {} -# status display +-- left column - network interface info -@col1 = @col2 = (); -$hide_local = 0; -$browser_ip = ""; - -# left column - network interface info - -# show the Primary/Wifi address -($ip, $mask, $bcast, $net, $cidr) = get_ip4_network($wifi_iface); -$cidr = "/ $cidr" if $cidr; -if (! $wifi_disable ) -{ - $str = "
Wifi address$ip $cidr
"; -} +local ip = cursor:get("network", "wifi", "ipaddr") +local cidr = netmask_to_cidr(cursor:get("network", "wifi", "netmask")) +if wifi_disabled then + col1[#col1 + 1] = "Primary address" .. ip .. " / " .. cidr .. "
" else -{ - $str = "Primary address$ip $cidr
"; -} + col1[#col1 + 1] = "Wifi address" .. ip .. " / " .. cidr .. "
" +end -# $str .= "" . get_ip6_addr($wifi_iface) . ""; -push @col1, $str; +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 -# find out if the browser is on this node's lan -# if not, hide the local network details -($ip, $mask, $bcast, $net, $cidr) = get_ip4_network(get_interface("lan")); -if($ENV{REMOTE_ADDR} =~ /::ffff:([\d\.]+)/) -{ - $browser_ip = $1; - $hide_local = 1 unless validate_same_subnet($browser_ip, $ip, $mask); -} +if ip:match("^10%.") or not hide_local then + cidr = netmask_to_cidr(mask) + col1[#col1 + 1] = "LAN address" .. ip .. " / " .. cidr .. "
" +end -if($ip =~ /^10\./ or not $hide_local) -{ - $cidr = "/ $cidr" if $cidr; - $str = "LAN address$ip $cidr
"; - # $str .= "" . get_ip6_addr(get_interface("lan")) . ""; - push @col1, $str; -} +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] = "WAN address" .. ip .. " / " .. cidr .. "
" + else + col1[#col1 + 1] = "WAN addressnone
" + end +end -{ - my $wanintf = get_interface("wan"); - if(not $hide_local and not system "ifconfig $wanintf >/dev/null 2>&1") - { - ($ip, $mask, $bcast, $net, $cidr) = get_ip4_network("$wanintf"); - $cidr = "/ $cidr" if $cidr; - $cidr = "" unless $cidr; - $str = "WAN address$ip $cidr
"; - # $str .= "" . get_ip6_addr("$wanintf") . ""; - push @col1, $str; - } -} +ip = get_default_gw() +if ip:match("^10%.") or not hide_local then + col1[#col1 + 1] = "default gateway" .. ip + if ip:match("^10%.") then + col1[#col1] = col1[#col1] .. "
" .. mesh_ip_to_hostnames(ip) .. "" + end + col1[#col1] = col1[#col1] .. "" +end -$ip = get_default_gw(); +if browser_ip then + col1[#col1 + 1] = "your address" .. browser_ip .. "
" .. mesh_ip_to_hostnames(browser_ip) .. "" +end -if($ip =~ /^10\./ or not $hide_local) -{ - $str = "default gateway$ip"; - $str .= "
" . mesh_ip2hostname($ip) . "" if $ip =~ /^10\./; - push @col1, $str . ""; -} +if not wifi_disabled then + col1[#col1 + 1] = "SSID" .. wifi_ssid .. "" + col1[#col1 + 1] = "Channel" .. wifi_channel .. "" + col1[#col1 + 1] = "Bandwidth" .. wifi_chanbw .. " MHz" +end -if($browser_ip) -{ - $str = "your address$browser_ip"; - $str .= "
" . mesh_ip2hostname($browser_ip) . "";# if $ip =~ /^10\./; - push @col1, $str . ""; -} +-- right column - system info -if ( ! $wifi_disable ) -{ - $str = "SSID$wifi_ssid"; - push @col1, $str . ""; +if config == "mesh" and not wifi_disabled then + col2[#col2 + 1] = "Signal/Noise/Ratio" + local s, n = get_wifi_signal(wifi_iface) + if s == "N/A" then + col2[#col2] = col2[#col2] .. "N/A" + else + col2[#col2] = col2[#col2] .. "" .. s .. " / " .. n .. " / " .. (s - n) .. " dB" + end + col2[#col2] = col2[#col2] .. "   " +end - $str = "Channel$wifi_channel"; - push @col1, $str . ""; +col2[#col2 + 1] = "firmware version" .. read_all("/etc/mesh-release") .. ""; +col2[#col2 + 1] = "system time" .. os.date("%a %b %e %Y") .. "
" .. os.date("%T %Z") .. ""; - $str = "Bandwidth$wifi_chanbw MHz"; - push @col1, $str . ""; -} +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 -# right column - system info +col2[#col2 + 1] = "uptime
load average" .. uptime .. "
" .. string.format("%.2f, %.2f, %.2f", sysinfo.loads[1], sysinfo.loads[2], sysinfo.loads[3]) .. ""; +local vfs = nixio.fs.statvfs("/overlay") +local fspace = vfs.bfree * vfs.bsize / 1024 +if fspace < 100 then + fspace = "" .. fspace .. " KB" +else + fspace = fspace .. " KB" +end +vfs = nixio.fs.statvfs("/tmp") +local tspace = vfs.bfree * vfs.bsize / 1024 +if tspace < 3000 then + tspace = "" .. tspace .. " KB" +else + tspace = tspace .. " KB" +end +local rspace = (sysinfo.freeram + sysinfo.bufferram) / 1024 +if rspace < 500 then + rspace = "" .. rspace .. " KB" +else + rspace = rspace .. " KB" +end +col2[#col2 + 1] = "free spaceflash = " .. fspace .. "
/tmp = " .. tspace .. "
memory = " .. rspace .. "" +col2[#col2 + 1] = "OLSR EntriesTotal = " .. olsr_total .. "
Nodes = " .. olsr_nodes .. "" -if($config eq "mesh" and ! $wifi_disable ) -{ - $str = "Signal/Noise/Ratio"; - ($s, $n) = get_wifi_signal($wifi_iface); - if($s eq "N/A") { $str .= "N/A" } - else { $str .= sprintf "%d / %d / %d dB", $s, $n, $s - $n } - $str .= "   "; - $str .= "\n"; - $str .= ""; - push @col2, $str; -} +-- now print the tables -push @col2, "firmware version" . `cat /etc/mesh-release`. ""; -push @col2, "system time" . `date +'%a %b %e %Y
%T %Z'` . ""; +html.print("

") +html.print("
") +for i, line in ipairs(col1) +do + html.print("" .. line .. "") +end +html.print("
") +for i, line in ipairs(col2) +do + html.print("" .. line .. "") +end +html.print("
") -$uptime = `uptime`; -$uptime =~ s/^ ..:..:.. up //; -($uptime, $load) = $uptime =~ /(.*), load average: (.*)/; -push @col2, "uptime
load average$uptime
$load"; +-- end +html.print("
") -$str = "free spaceflash = "; -$space = get_free_space("/overlay"); -$str .= $space < 100 ? "$space KB" : "$space KB"; -$str .= "
/tmp = "; -$space = get_free_space("/tmp"); -$str .= $space < 3000 ? "$space KB" : "$space KB"; -$str .= "
memory = "; -$space = get_free_mem(); -$str .= $space < 500 ? "$space KB" : "$space KB"; -$str .= ""; - -push @col2, $str; -push @col2, "OLSR EntriesTotal = $olsrTotal
Nodes = $olsrNodes"; #display OLSR numbers - -# now print the tables - -print "

\n"; -print "
\n"; -foreach(@col1) { print "$_\n" } -print "
\n"; -foreach(@col2) { print "$_\n" } -print "
\n"; - -# end -print "
\n"; -print "\n"; - -show_debug_info(); -show_parse_errors(); - -page_footer(); -print "\n"; -print "\n"; +html.footer() +html.print("") diff --git a/files/www/cgi-bin/status.pl b/files/www/cgi-bin/status.pl new file mode 100755 index 00000000..2ad5f12d --- /dev/null +++ b/files/www/cgi-bin/status.pl @@ -0,0 +1,298 @@ +#!/usr/bin/perl +=for comment + + Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks + Copyright (C) 2015 Conrad Lara + See Contributors file for additional contributors + + Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet + + 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. + +=cut + +$debug = 0; +BEGIN {push @INC, '/www/cgi-bin'}; +use perlfunc; +use ucifunc; + +# collect some variables +$node = nvram_get("node"); +$node = "NOCALL" if $node eq ""; +$tactical = nvram_get("tactical"); +$config = nvram_get("config"); +$config = "not set" if $config eq "" or not -d "/etc/config.mesh"; +$wifi_iface = get_interface("wifi"); +$wifi_iface =~ /wlan(\d+)/; +$radio = ( defined $1 )? "radio$1" : "radio0"; +$wifi_disable = ( $wifi_iface =~ /eth.*$/ )? 1 : 0; + +if ( ! $wifi_disable ) +{ + ($junk, $wifi_channel) = &uci_get_named_option("wireless", "$radio", "channel"); + if ($wifi_channel >= 76 and $wifi_channel <= 99) + { + $wifi_channel = ($wifi_channel*5+3000); + } + ($junk, $wifi_chanbw) = &uci_get_named_option("wireless", "$radio", "chanbw"); + + $wifi_ssid = "N/A"; + @wisections = &uci_get_all_indexed_by_sectiontype("wireless", "wifi-iface"); + foreach(@wisections) { + if ($_->{network} eq "wifi") { + $wifi_ssid = $_->{ssid}; + } + } +} + +$node_desc = `/sbin/uci -q get system.\@system[0].description`; #pull the node description from uci +#get location info if available +$lat_lon = "Location Not Available"; +$lat=&uci_get_indexed_option("aredn","location",0,"lat"); +$lon=&uci_get_indexed_option("aredn","location",0,"lon"); + +if($lat ne "" and $lon ne "") { + $lat_lon = "
Location: $lat $lon
"; +} +$olsrTotal = `/sbin/ip route list table 30 | wc -l`; #num hosts olsr is keeping track of +$olsrNodes = `/sbin/ip route list table 30 | egrep "/" | wc -l`; #num *nodes* on the network (minus other hosts) + +read_postdata(); + +if($parms{css} and -f "/www/$parms{css}" and $parms{css} =~ /\.css$/i) { + unlink "/tmp/web/style.css"; + symlink "/www/$parms{css}","/tmp/web/style.css"; +} + +# generate the page +http_header(); +html_header("$node status", 1); +print "
\n"; +print "
\n"; + +alert_banner(); + +# page header +print "

$node"; +print " / $tactical" if $tactical; +print "

"; +print "
$lat_lon
"; #display location info +print "
$node_desc
" if $node_desc; +print "
\n"; + +# nav buttons +print "
\n"; + +#print qq(\n); +print "Help\n"; + +print "   "; +print "\n"; + +if($config eq "mesh") +{ + print "   "; + print "\n"; + if ( ! $wifi_disable ) + { + print "   "; + print "\n"; + } +} + +print "   "; +print "\n"; + +print "   "; + +print ""; + +print ""; + +print "\n"; + + +if($config eq "not set") +{ + print "

This node is not yet configured.
"; + print "Go to the setup page and set your node name and password.
\n"; + print "Click Save Changes, even if you didn't make any changes, then the node will reboot.
\n"; + print "

\n"; + print "
\n"; + print "

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.

"; + print "

This device is pre-configured with no restrictions as to the type of data being passed.

\n"; + print "

Follow these steps if you wish to prohibit known encrypted traffic on the RF link. These instructions will disappear, so copy them for your reference:

"; + print "

    \n"; + print "
  1. Setup your node name and password as instructed at the top of this page
  2. "; + print "
  3. After you Save Changes allow your node to reboot
  4. "; + print "
  5. Return to the Node Status page and navigate to Setup > Administration
  6. "; + print "
  7. Obtain the blockknownencryption package from the AREDN™ website OR refresh the Package list (node must be connected to the internet)
  8. "; + print "
  9. Install the blockknownencryption package by uploading it or choosing it from the package drop-down list
  10. "; + print "
  11. Wait until the package installs and then reboot your node
  12. "; + print "

\n"; + print "
"; +} + +# status display + +@col1 = @col2 = (); +$hide_local = 0; +$browser_ip = ""; + +# left column - network interface info + +# show the Primary/Wifi address +($ip, $mask, $bcast, $net, $cidr) = get_ip4_network($wifi_iface); +$cidr = "/ $cidr" if $cidr; +if (! $wifi_disable ) +{ + $str = "Wifi address$ip $cidr
"; +} +else +{ + $str = "Primary address$ip $cidr
"; +} + +# $str .= "" . get_ip6_addr($wifi_iface) . ""; +push @col1, $str; + +# find out if the browser is on this node's lan +# if not, hide the local network details +($ip, $mask, $bcast, $net, $cidr) = get_ip4_network(get_interface("lan")); +if($ENV{REMOTE_ADDR} =~ /::ffff:([\d\.]+)/) +{ + $browser_ip = $1; + $hide_local = 1 unless validate_same_subnet($browser_ip, $ip, $mask); +} + +if($ip =~ /^10\./ or not $hide_local) +{ + $cidr = "/ $cidr" if $cidr; + $str = "LAN address$ip $cidr
"; + # $str .= "" . get_ip6_addr(get_interface("lan")) . ""; + push @col1, $str; +} + +{ + my $wanintf = get_interface("wan"); + if(not $hide_local and not system "ifconfig $wanintf >/dev/null 2>&1") + { + ($ip, $mask, $bcast, $net, $cidr) = get_ip4_network("$wanintf"); + $cidr = "/ $cidr" if $cidr; + $cidr = "" unless $cidr; + $str = "WAN address$ip $cidr
"; + # $str .= "" . get_ip6_addr("$wanintf") . ""; + push @col1, $str; + } +} + +$ip = get_default_gw(); + +if($ip =~ /^10\./ or not $hide_local) +{ + $str = "default gateway$ip"; + $str .= "
" . mesh_ip2hostname($ip) . "" if $ip =~ /^10\./; + push @col1, $str . ""; +} + +if($browser_ip) +{ + $str = "your address$browser_ip"; + $str .= "
" . mesh_ip2hostname($browser_ip) . "";# if $ip =~ /^10\./; + push @col1, $str . ""; +} + +if ( ! $wifi_disable ) +{ + $str = "SSID$wifi_ssid"; + push @col1, $str . ""; + + $str = "Channel$wifi_channel"; + push @col1, $str . ""; + + $str = "Bandwidth$wifi_chanbw MHz"; + push @col1, $str . ""; +} + +# right column - system info + +if($config eq "mesh" and ! $wifi_disable ) +{ + $str = "Signal/Noise/Ratio"; + ($s, $n) = get_wifi_signal($wifi_iface); + if($s eq "N/A") { $str .= "N/A" } + else { $str .= sprintf "%d / %d / %d dB", $s, $n, $s - $n } + $str .= "   "; + $str .= "\n"; + $str .= ""; + push @col2, $str; +} + +push @col2, "firmware version" . `cat /etc/mesh-release`. ""; +push @col2, "system time" . `date +'%a %b %e %Y
%T %Z'` . ""; + +$uptime = `uptime`; +$uptime =~ s/^ ..:..:.. up //; +($uptime, $load) = $uptime =~ /(.*), load average: (.*)/; +push @col2, "uptime
load average$uptime
$load"; + +$str = "free spaceflash = "; +$space = get_free_space("/overlay"); +$str .= $space < 100 ? "$space KB" : "$space KB"; +$str .= "
/tmp = "; +$space = get_free_space("/tmp"); +$str .= $space < 3000 ? "$space KB" : "$space KB"; +$str .= "
memory = "; +$space = get_free_mem(); +$str .= $space < 500 ? "$space KB" : "$space KB"; +$str .= ""; + +push @col2, $str; +push @col2, "OLSR EntriesTotal = $olsrTotal
Nodes = $olsrNodes"; #display OLSR numbers + +# now print the tables + +print "

\n"; +print "
\n"; +foreach(@col1) { print "$_\n" } +print "
\n"; +foreach(@col2) { print "$_\n" } +print "
\n"; + +# end +print "
\n"; +print "\n"; + +show_debug_info(); +show_parse_errors(); + +page_footer(); +print "\n"; +print "\n";