mirror of https://github.com/aredn/aredn.git
442 lines
11 KiB
Lua
Executable File
442 lines
11 KiB
Lua
Executable File
#!/usr/bin/lua
|
|
--[[
|
|
|
|
Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
|
|
Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
|
|
|
|
Additional Terms:
|
|
|
|
Additional use restrictions exist on the AREDN(TM) trademark and logo.
|
|
See AREDNLicense.txt for more info.
|
|
|
|
Attributions to the AREDN Project must be retained in the source code.
|
|
If importing this code into a new or existing project attribution
|
|
to the AREDN project must be added to the source code.
|
|
|
|
You must not misrepresent the origin of the material contained within.
|
|
|
|
Modified versions must be modified to attribute to the original source
|
|
and be marked in reasonable ways as differentiate it from the original
|
|
version
|
|
|
|
--]]
|
|
|
|
require("aredn.utils")
|
|
local aredn_info=require("aredn.info")
|
|
require("uci")
|
|
require("aredn.uci")
|
|
local nxo=require("nixio")
|
|
require("iwinfo")
|
|
require("luci.sys")
|
|
|
|
|
|
-- setup extensions
|
|
os.capture=capture
|
|
string.print=print_r
|
|
|
|
|
|
-- delay just after rssi_monitor has a chance to run noise floor calibration
|
|
local MAXLINES=2880 -- 2 days worth
|
|
local AGETIME=43200
|
|
local INACTIVETIMEOUT=10000
|
|
local tmpdir="/tmp/snrlog"
|
|
local lastdat="/tmp/snr.dat"
|
|
local autolog="/tmp/AutoDistReset.log"
|
|
local lasttime={}
|
|
local lines={}
|
|
local arpcache={}
|
|
local nscache={}
|
|
local defnoise=-95
|
|
local tmpdirlist={}
|
|
local neighbors={}
|
|
local stations={}
|
|
local wifiiface=""
|
|
local bandwidth=""
|
|
local nulledout={}
|
|
local pidfile="/tmp/snrlog.pid"
|
|
local rssifile="/tmp/rssi_monitor.pid"
|
|
|
|
-- Neighbor Class
|
|
Neighbor={}
|
|
Neighbor.__index=Neighbor
|
|
function Neighbor.create(macaddress)
|
|
local n={}
|
|
setmetatable(n,Neighbor)
|
|
n.mac=macaddress:lower()
|
|
n.hostname=nil
|
|
n.sigfile=nil
|
|
n.datafile=nil
|
|
n.ip=nil
|
|
n.lastseen=nil
|
|
n.level=nil
|
|
|
|
n.ip=n:findIp()
|
|
n.hostname=n:findHostname()
|
|
n.signal=n:findSignal() or ""
|
|
n.noise=n:findNoise() or ""
|
|
n.tx_mcs=n:findTxMcs() or ""
|
|
n.tx_rate=n:findTxRate() or ""
|
|
n.rx_mcs=n:findRxMcs() or ""
|
|
n.rx_rate=n:findRxRate() or ""
|
|
n.lastseen=n:findLastTime() or now
|
|
-- check if auto-distance reset is required (new node)
|
|
local efn=n:getExistingDataFileName()
|
|
if efn==nil or ((now-n.lastseen) > 100) then -- NEW or not recently seen node
|
|
reset_auto_distance()
|
|
f, err=assert(io.open(autolog, "a"),"Cannot open file (autolog) to write!")
|
|
if (f) then
|
|
f:write(now .. "\n")
|
|
f:close()
|
|
end
|
|
file_trim(autolog,MAXLINES)
|
|
end
|
|
|
|
n.datafile=n:generateDataFileName()
|
|
return n
|
|
end
|
|
|
|
function Neighbor:hasIP()
|
|
return not (self.ip==nil)
|
|
end
|
|
|
|
function Neighbor:hasHostName()
|
|
return not (self.hostname==nil)
|
|
end
|
|
|
|
function Neighbor:getExistingDataFileName()
|
|
local efn=nil
|
|
for fn in nxo.fs.glob(tmpdir.."/"..self.mac.."-*") do
|
|
efn=nxo.fs.basename(fn)
|
|
end
|
|
return efn
|
|
end
|
|
|
|
function Neighbor:generateDataFileName()
|
|
local tmpfn=nil
|
|
local efn=self:getExistingDataFileName()
|
|
if efn~=nil then
|
|
-- existing file found, improve or use
|
|
tmpfn=self.mac.."-"
|
|
m,x=string.match(efn,"^([%x2:]*)%-(.*)")
|
|
if x==nil or x=="" then -- "00:11:22:33:44:55-"
|
|
if self:hasHostName() then
|
|
tmpfn=tmpfn..self.hostname
|
|
nxo.fs.rename(tmpdir.."/"..efn,tmpdir.."/"..tmpfn)
|
|
elseif self:hasIP() then
|
|
tmpfn=tmpfn..self.ip
|
|
nxo.fs.rename(tmpdir.."/"..efn,tmpdir.."/"..tmpfn)
|
|
end
|
|
elseif get_ip_type(x)==1 then -- "00:11:22:33:44:55-10.11.22.33"
|
|
if self:hasHostName() then
|
|
tmpfn=tmpfn..self.hostname
|
|
nxo.fs.rename(tmpdir.."/"..efn,tmpdir.."/"..tmpfn)
|
|
else
|
|
tmpfn=efn
|
|
end
|
|
else -- "00:11:22:33:44:55-MAYBE-GOOD-HOST"
|
|
if x==self.hostname then -- "00:11:22:33:44:55-SAME-HOST"
|
|
tmpfn=efn
|
|
else -- "00:11:22:33:44:55-NEW-HOST-NAME"
|
|
if self.hostname~=nil then
|
|
tmpfn=tmpfn..self.hostname
|
|
nxo.fs.rename(tmpdir.."/"..efn,tmpdir.."/"..tmpfn)
|
|
else
|
|
tmpfn=efn
|
|
end
|
|
end
|
|
end
|
|
else
|
|
-- no prior file, let's generate one
|
|
tmpfn=self.mac.."-"
|
|
if self:hasHostName() then
|
|
tmpfn=tmpfn..self.hostname
|
|
elseif self:hasIP() then
|
|
tmpfn=tmpfn..self.ip
|
|
end
|
|
end
|
|
return tmpfn
|
|
end
|
|
|
|
function Neighbor:findLastTime()
|
|
self.lastseen=lasttime[mac]
|
|
return self.lastseen
|
|
end
|
|
|
|
function Neighbor:findHostname()
|
|
if self:hasIP() then
|
|
self.hostname=nslookup(self.ip)
|
|
end
|
|
return self.hostname
|
|
end
|
|
|
|
function Neighbor:findIp()
|
|
-- lookup IP from arpcache
|
|
local myarp=arpcache[self.mac]
|
|
if myarp~=nil then
|
|
self.ip=myarp["IP address"]
|
|
end
|
|
return self.ip
|
|
end
|
|
|
|
function Neighbor:findSignal()
|
|
-- lookup from iwinfo
|
|
self.signal=stations[self.mac:upper()].signal
|
|
return self.signal
|
|
end
|
|
|
|
function Neighbor:findNoise()
|
|
-- lookup from iwinfo
|
|
self.noise=stations[self.mac:upper()].noise
|
|
return self.noise
|
|
end
|
|
|
|
function Neighbor:findTxMcs()
|
|
self.tx_mcs=stations[self.mac:upper()].tx_mcs or -1
|
|
return self.tx_mcs
|
|
end
|
|
|
|
function Neighbor:findTxRate()
|
|
local r=(stations[self.mac:upper()].tx_rate)/1000
|
|
self.tx_rate=adjust_rate(r,bandwidth)
|
|
return self.tx_rate
|
|
end
|
|
|
|
function Neighbor:findRxMcs()
|
|
self.rx_mcs=stations[self.mac:upper()].rx_mcs or -1
|
|
return self.rx_mcs
|
|
end
|
|
|
|
function Neighbor:findRxRate()
|
|
local r=(stations[self.mac:upper()].rx_rate)/1000
|
|
self.rx_rate=adjust_rate(r,bandwidth)
|
|
return self.rx_rate
|
|
end
|
|
|
|
function Neighbor:log()
|
|
local oktolog=true
|
|
if self.lastseen ~= nil then
|
|
if stations[self.mac:upper()].inactive >= INACTIVETIMEOUT then
|
|
-- beacons expired
|
|
self.signal="null"
|
|
if nulledout[self.mac] == "true" then
|
|
-- No need to double log inactive null's
|
|
oktolog=false
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.signal==0 then
|
|
oktolog=false
|
|
if nulledout[self.mac] == nil then
|
|
-- First time we have seen this show up
|
|
-- but it is at 0 wont be logged but will
|
|
-- end up in snrcache
|
|
nulledout[self.mac]="true"
|
|
end
|
|
end
|
|
|
|
if oktolog then
|
|
nulledout[self.mac]="false"
|
|
-- log neighbor data to datafile
|
|
local f, err=assert(io.open(tmpdir.."/"..self.datafile, "a"),"Cannot open file ("..tmpdir.."/"..self.datafile..") for appending!")
|
|
if f~=nil then
|
|
local now=os.date("%m/%d/%Y %H:%M:%S",os.time())
|
|
local outline=string.format("%s,%s,%s,%s,%s,%s,%s\n",now,self.signal,self.noise,self.tx_mcs,self.tx_rate,self.rx_mcs,self.rx_rate)
|
|
f:write(outline)
|
|
f:close()
|
|
if self.signal == "null" then
|
|
nulledout[self.mac]="true"
|
|
end
|
|
else
|
|
print(err)
|
|
end
|
|
end
|
|
return oktolog
|
|
end
|
|
-- Neighbor Class END
|
|
|
|
-- MAIN() -------------------------------------------------------------------------------------
|
|
-- get wifi interface name
|
|
wifiiface=get_ifname("wifi")
|
|
|
|
-- if Mesh RF is turned off do nothing
|
|
if ( wifiiface == string.match(wifiiface,'eth.*')) then
|
|
return
|
|
end
|
|
|
|
-- check to make sure a prior instance is not still running
|
|
local f = io.open(pidfile,"r")
|
|
if (f) then
|
|
local oldpid = f:read("*number")
|
|
f:close()
|
|
if (oldpid ~= nil and dir_exists("/proc/" .. oldpid)) then
|
|
return
|
|
end
|
|
end
|
|
|
|
--- create pid file to communicate I'm running
|
|
f, err=assert(io.open(pidfile, "w"),"Cannot open file (pidfile) to write!")
|
|
if (f) then
|
|
local mypid = posix.unistd.getpid()
|
|
f:write(mypid)
|
|
f:close()
|
|
end
|
|
|
|
--- Do not run if prior period rssi_monitor is still running
|
|
local f = io.open(rssifile,"r")
|
|
if (f) then
|
|
local oldpid = f:read("*number")
|
|
f:close()
|
|
if (oldpid ~= nil and dir_exists("/proc/" .. oldpid)) then
|
|
os.remove(pidfile)
|
|
return
|
|
end
|
|
end
|
|
|
|
-- load the lasttime table
|
|
if file_exists(lastdat) then
|
|
local f,err=io.open(lastdat,"r")
|
|
if f~=nil then
|
|
for line in f:lines() do
|
|
mac,last,nulled=string.match(line, "(.*)|(.*)|(.*)")
|
|
lasttime[mac]=last
|
|
nulledout[mac]=nulled
|
|
end
|
|
end
|
|
f:close()
|
|
end
|
|
|
|
-- get radio noise floor
|
|
local nf=iwinfo["nl80211"].noise(wifiiface)
|
|
if (nf < -101 or nf > -50) then
|
|
nf=defnoise
|
|
end
|
|
|
|
-- create tmp dir if needed
|
|
if not dir_exists(tmpdir) then
|
|
nxo.fs.mkdir(tmpdir)
|
|
end
|
|
|
|
-- get system uptime
|
|
--now=os.time()
|
|
now=luci.sys.uptime()
|
|
|
|
-- get all stations
|
|
stations=iwinfo["nl80211"].assoclist(wifiiface)
|
|
|
|
|
|
-- load up arpcache
|
|
arptable(
|
|
function(a)
|
|
arpcache[a["HW address"]:lower()]=a
|
|
end
|
|
)
|
|
|
|
-- get the current bandwidth setting
|
|
local radio=aredn_info.getMeshRadioDevice()
|
|
bandwidth=aredn_info.getChannelBW(radio)
|
|
|
|
-- iterate over all the stations
|
|
for mstation in pairs(stations) do
|
|
local n=Neighbor.create(mstation)
|
|
table.insert(neighbors,n)
|
|
end
|
|
|
|
-- get all the existing files in tmpdir
|
|
local tmpdirlist={}
|
|
for maclist in nxo.fs.dir(tmpdir) do
|
|
maclistbase=nxo.fs.basename(maclist)
|
|
table.insert(tmpdirlist,maclistbase)
|
|
end
|
|
|
|
-- neighbors loop START
|
|
for _,n in pairs(neighbors) do
|
|
-- trim datafile
|
|
file_trim(tmpdir.."/"..n.datafile,MAXLINES)
|
|
if n:log()==true then
|
|
lasttime[n.mac]=now
|
|
end
|
|
end
|
|
-- neighbors loop END
|
|
|
|
-- update snr.dat
|
|
local snrdatcache={}
|
|
if not file_exists(lastdat) then
|
|
-- create file
|
|
local f,err=assert(io.open(lastdat,"w+"),"Cannot create "..lastdat)
|
|
f:close()
|
|
end
|
|
for line in io.lines(lastdat) do
|
|
local mac,lastt,nulledoutprev=string.match(line,"(.*)|(.*)|(.*)")
|
|
if ((now-lastt) < AGETIME) then
|
|
-- keep it
|
|
snrdatcache[mac]=lastt
|
|
|
|
-- check if in neighbors table
|
|
local foundneighbor=false
|
|
for _,n in pairs(neighbors) do
|
|
if n.mac == mac then
|
|
foundneighbor=true
|
|
end
|
|
end
|
|
|
|
-- If wasn't previously nulled out and we don't have it in the neigbors table write a null
|
|
if nulledoutprev[mac] == "false" and foundneigbor == false then
|
|
|
|
-- find the log file name
|
|
for maclist in nxo.fs.glob(tmpdir.."/"..mac.."*") do
|
|
logdatafile=macclist
|
|
end
|
|
|
|
-- Write a null to the log file
|
|
local f, err=assert(io.open(logdatafile, "a"),"Cannot open file ("..logdatafile..") for appending!")
|
|
if f~=nil then
|
|
local now=os.date("%m/%d %H:%M:%S",os.time())
|
|
local outline=string.format("%s,%s,%s,%s,%s,%s,%s\n",now,'null',nf,'0','0','0','0')
|
|
f:write(outline)
|
|
f:close()
|
|
nulledout[mac]="true"
|
|
else
|
|
-- Don't log the null into SNRLog cause we were not successful
|
|
-- Though the assert() above should cause this too.
|
|
nulledout[mac]="false"
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
-- find the file and purge it
|
|
for maclist in nxo.fs.glob(tmpdir.."/"..mac.."*") do
|
|
nxo.fs.remove(maclist)
|
|
end
|
|
end
|
|
end
|
|
|
|
for _,n in pairs(neighbors) do
|
|
snrdatcache[n.mac]=now
|
|
end
|
|
|
|
-- re-write snr.dat file
|
|
local f,err=assert(io.open(lastdat,"w+"),"Cannot overwrite "..lastdat)
|
|
for k,v in pairs(snrdatcache) do
|
|
f:write(string.format("%s|%s|%s\n",k,v,nulledout[k]))
|
|
end
|
|
f:close()
|
|
|
|
os.remove(pidfile)
|
|
|
|
-- END MAIN
|