mirror of https://github.com/aredn/aredn.git
663 lines
16 KiB
Lua
Executable File
663 lines
16 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.
|
|
|
|
--]]
|
|
|
|
|
|
local nxo = require("nixio")
|
|
local ipc = require("luci.ip")
|
|
local auci = require("aredn.uci")
|
|
require("uci")
|
|
|
|
function round2(num, idp)
|
|
return tonumber(string.format("%." .. (idp or 0) .. "f", num))
|
|
end
|
|
|
|
function adjust_rate(r,b)
|
|
local ar
|
|
if b==5 then
|
|
ar=round2(r/4,1)
|
|
elseif b==10 then
|
|
ar=round2(r/2,1)
|
|
else
|
|
ar=round2(r/1,1)
|
|
end
|
|
return ar
|
|
end
|
|
|
|
function starts_with(str, start)
|
|
return str:sub(1, #start) == start
|
|
end
|
|
|
|
function ends_with(str, ending)
|
|
return ending == "" or str:sub(-#ending) == ending
|
|
end
|
|
|
|
|
|
function string:split(delim)
|
|
local t = {}
|
|
local function helper(line) table.insert(t, line) return "" end
|
|
helper((self:gsub("(.-)"..delim, helper)))
|
|
return t
|
|
end
|
|
|
|
function parseQueryString(qs)
|
|
local qsa={}
|
|
if qs ~=nil then
|
|
for i,j in pairs(qs:split("&")) do
|
|
z=j:split("=")
|
|
qsa[z[1]]=z[2]
|
|
end
|
|
end
|
|
return qsa
|
|
end
|
|
|
|
function setContains(set, key)
|
|
return set[key] ~= nil
|
|
end
|
|
|
|
function sleep(n) -- seconds
|
|
nxo.nanosleep(n, 0)
|
|
end
|
|
|
|
function get_ip_type(ip)
|
|
local R = {ERROR = 0, IPV4 = 1, IPV6 = 2, STRING = 3}
|
|
if type(ip) ~= "string" then return R.ERROR end
|
|
|
|
-- check for format 1.11.111.111 for ipv4
|
|
local chunks = {ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")}
|
|
if #chunks == 4 then
|
|
for _,v in pairs(chunks) do
|
|
if tonumber(v) > 255 then return R.STRING end
|
|
end
|
|
return R.IPV4
|
|
end
|
|
|
|
--[[
|
|
-- TODO
|
|
-- check for ipv6 format, should be 8 'chunks' of numbers/letters
|
|
-- without trailing chars
|
|
local chunks = {ip:match(("([a-fA-F0-9]*):"):rep(8):gsub(":$","$"))}
|
|
if #chunks == 8 then
|
|
for _,v in pairs(chunks) do
|
|
if #v > 0 and tonumber(v, 16) > 65535 then return R.STRING end
|
|
end
|
|
return R.IPV6
|
|
end
|
|
--]]
|
|
return R.STRING
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Returns name of the radio (radio0 or radio1) for the selected wifi interface (wifi or lan)
|
|
-------------------------------------
|
|
function get_radio(ifn)
|
|
local interfaces=auci.getUciConfType("wireless", "wifi-iface")
|
|
for n, i in ipairs(interfaces) do
|
|
if i.network==ifn then
|
|
return i.device
|
|
end
|
|
end
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Returns PHY name of the radio (phy0 or phy1) for the selected wifi interface (wifi or lan)
|
|
-------------------------------------
|
|
function get_radiophy(ifn)
|
|
local rname=get_radio(ifn)
|
|
return string.format("phy%d",string.sub(rname,-1))
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Reset the auto-distance calculation for the radio
|
|
-------------------------------------
|
|
function reset_auto_distance()
|
|
local rc=0
|
|
local radio = get_radio("wifi") -- get radio number from /etc/config/wireless and convert to -> phy0 or phy1
|
|
local u=uci.cursor()
|
|
local distance=u:get("wireless",radio,"distance")
|
|
print("DISTANCE=" .. distance)
|
|
if distance=="0" then
|
|
local phyname = get_radiophy("wifi") -- get radio number from /etc/config/wireless and convert to -> phy0 or phy1
|
|
print("iw phy " .. phyname .. " set distance 60000")
|
|
print("iw phy " .. phyname .. " set distance auto")
|
|
os.execute("iw phy " .. phyname .. " set distance 60000")
|
|
rc=os.execute("iw phy " .. phyname .. " set distance auto")
|
|
else
|
|
rc=-1
|
|
end
|
|
return rc
|
|
end
|
|
|
|
function get_ifname(ifn)
|
|
local u=uci.cursor()
|
|
iface=u:get("network",ifn,"ifname")
|
|
return iface
|
|
end
|
|
|
|
-- Copyright 2009-2015 Jo-Philipp Wich <jow@openwrt.org>
|
|
-- Licensed to the public under the Apache License 2.0.
|
|
function get_interfaces()
|
|
_interfaces={}
|
|
local n, i
|
|
for n, i in ipairs(nxo.getifaddrs()) do
|
|
local name = i.name:match("[^:]+")
|
|
_interfaces[name] = _interfaces[name] or {
|
|
idx = i.ifindex or n,
|
|
name = name,
|
|
rawname = i.name,
|
|
ipaddrs = { },
|
|
}
|
|
|
|
if i.family == "packet" then
|
|
_interfaces[name].stats = i.data
|
|
_interfaces[name].macaddr = i.addr
|
|
elseif i.family == "inet" then
|
|
_interfaces[name].ipaddrs[#_interfaces[name].ipaddrs+1] = ipc.IPv4(i.addr, i.netmask)
|
|
end
|
|
end
|
|
return _interfaces
|
|
end
|
|
|
|
-- checks if a file exists and can be read
|
|
function file_exists(name)
|
|
local f=io.open(name,"r")
|
|
if f~=nil then io.close(f) return true else return false end
|
|
end
|
|
|
|
function dir_exists(name)
|
|
return (nixio.fs.stat(name, "type") == 'dir')
|
|
end
|
|
|
|
function hardware_boardid()
|
|
local bid=""
|
|
local ssdid="/sys/devices/pci0000:00/0000:00:00.0/subsystem_device"
|
|
if file_exists(ssdid) then
|
|
local bfile, err=io.open(ssdid,"r")
|
|
if bfile~=nil then
|
|
bid=bfile:read()
|
|
bfile:close()
|
|
end
|
|
else
|
|
bid=capture("/usr/local/bin/get_boardid")
|
|
end
|
|
return bid:chomp()
|
|
end
|
|
|
|
-- get IP from CIDR (strip network)
|
|
function ipFromCIDR(ip)
|
|
return string.match(ip,"(.*)/.-")
|
|
end
|
|
|
|
-- strips newline from a string
|
|
-- ex. mystr=mystr:chomp()
|
|
function string:chomp()
|
|
return(self:gsub("\n$", ""))
|
|
end
|
|
|
|
-- splits a string on newlines
|
|
-- ex. newtable=mystrings.splitNewLine()
|
|
function string:splitNewLine()
|
|
local t = {}
|
|
local function helper(line) table.insert(t, line) return "" end
|
|
helper((self:gsub("(.-)\r?\n", helper)))
|
|
return t
|
|
end
|
|
|
|
-- splits a string into words
|
|
function string:splitWhiteSpace()
|
|
local t = {}
|
|
local function helper(line) table.insert(t, line) return "" end
|
|
helper((self:gsub("%S+", helper)))
|
|
return t
|
|
end
|
|
|
|
function nslookup(ip)
|
|
local hostname=nil
|
|
if get_ip_type(ip)==1 then
|
|
o1, o2, o3, o4 = ip:match("([^%.]+)%.([^%.]+)%.([^%.]+)%.([^%.]+)")
|
|
rip = o4.."."..o3.."."..o2.."."..o1
|
|
nso = capture("nslookup "..ip)
|
|
hostname = nso:match(rip.."%.in%-addr%.arpa[%s]+name[%s]+=[%s]+(.*)")
|
|
if hostname ~= nil then
|
|
hostname=hostname:chomp()
|
|
hostname=hostname:chomp()
|
|
return hostname
|
|
end
|
|
end
|
|
end
|
|
|
|
-------------------------------------
|
|
-- Returns first IP of given host
|
|
-------------------------------------
|
|
function iplookup(host)
|
|
if host:find("dtd.*%.") or host:find("mid%d+%.") then
|
|
host=host:match("%.(.*)")
|
|
end
|
|
local nso=capture("nslookup "..host)
|
|
local ip=nso:match("Address 1: (.*)%c")
|
|
return ip
|
|
end
|
|
|
|
|
|
-------------------------------------
|
|
-- Returns traceroute
|
|
-------------------------------------
|
|
function getTraceroute(target)
|
|
local info={}
|
|
local routes={}
|
|
trall=capture('/bin/traceroute -q1 ' .. target )
|
|
local lines = trall:splitNewLine()
|
|
|
|
table.remove(lines, 1) -- remove heading
|
|
table.remove(lines, #lines) -- remove blank last line
|
|
|
|
data = {}
|
|
priortime = 0
|
|
for i,v in pairs(lines) do
|
|
data = v:splitWhiteSpace()
|
|
entry = {}
|
|
if data[2] ~= "*" then
|
|
node = data[2]:gsub("^mid[0-9]*%.","") -- strip midXX.
|
|
node = node:gsub("^dtdlink%.","") -- strip dtdlink.
|
|
node = node:gsub("%.local%.mesh$","") -- strip .local.mesh
|
|
entry['nodename'] = node
|
|
ip = data[3]:match("%((.*)%)")
|
|
entry['ip'] = ip
|
|
entry['timeto'] = round2(data[4])
|
|
entry['timedelta'] = math.abs(round2(data[4] - priortime))
|
|
priortime = round2(data[4])
|
|
table.insert(routes, entry)
|
|
end
|
|
end
|
|
return routes
|
|
end
|
|
|
|
|
|
function file_trim(filename, maxl)
|
|
local lines={}
|
|
local tmpfilename=filename..".tmp"
|
|
if file_exists(filename) then
|
|
for line in io.lines(filename) do table.insert(lines,line) end
|
|
if (#lines > maxl) then
|
|
local startline=(#lines-maxl)+1
|
|
nxo.fs.rename(filename,tmpfilename)
|
|
local f,err=io.open(filename,"w+")
|
|
|
|
for n, l in pairs(lines) do
|
|
if (n>=startline) then
|
|
f:write(l.."\n")
|
|
end
|
|
end
|
|
nxo.fs.remove(tmpfilename)
|
|
f:close()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- secondsToClock
|
|
function secondsToClock(seconds)
|
|
local seconds = tonumber(seconds)
|
|
if seconds <= 0 then
|
|
return "00:00:00";
|
|
else
|
|
days = string.format("%d", math.floor(seconds/86400));
|
|
hours = string.format("%d", math.floor(math.mod(seconds, 86400)/3600));
|
|
mins = string.format("%02d", math.floor(math.mod(seconds,3600)/60));
|
|
secs = string.format("%02d", math.floor(math.mod(seconds,60)));
|
|
return days.." days, "..hours..":"..mins..":"..secs
|
|
end
|
|
end
|
|
|
|
-- table.print = pretty prints a table
|
|
function print_r(t)
|
|
local print_r_cache={}
|
|
local function sub_print_r(t,indent)
|
|
if (print_r_cache[tostring(t)]) then
|
|
print(indent.."*"..tostring(t))
|
|
else
|
|
print_r_cache[tostring(t)]=true
|
|
if (type(t)=="table") then
|
|
for pos,val in pairs(t) do
|
|
if (type(val)=="table") then
|
|
print(indent.."["..pos.."] => "..tostring(t).." {")
|
|
sub_print_r(val,indent..string.rep(" ",string.len(pos)+8))
|
|
print(indent..string.rep(" ",string.len(pos)+6).."}")
|
|
elseif (type(val)=="string") then
|
|
print(indent.."["..pos..'] => "'..val..'"')
|
|
else
|
|
print(indent.."["..pos.."] => "..tostring(val))
|
|
end
|
|
end
|
|
else
|
|
print(indent..tostring(t))
|
|
end
|
|
end
|
|
end
|
|
if (type(t)=="table") then
|
|
print(tostring(t).." {")
|
|
sub_print_r(t," ")
|
|
print("}")
|
|
else
|
|
sub_print_r(t," ")
|
|
end
|
|
print()
|
|
end
|
|
|
|
-- os.capture = captures output from a shell command
|
|
function capture(cmd)
|
|
local handle= io.popen(cmd)
|
|
local result=handle:read("*a")
|
|
handle:close()
|
|
return(result)
|
|
end
|
|
|
|
-- copy a file
|
|
function filecopy(from, to)
|
|
local f = io.open(from, "r")
|
|
if not f then
|
|
return false
|
|
end
|
|
local t = io.open(to, "w")
|
|
if not t then
|
|
f:close()
|
|
return false
|
|
end
|
|
-- not great on memory usage
|
|
t:write(f:read("*a"))
|
|
t:close()
|
|
f:close()
|
|
return true
|
|
end
|
|
|
|
-- remove all files (including recursively into directories)
|
|
function remove_all(name)
|
|
local type = nixio.fs.stat(name, "type")
|
|
if type then
|
|
if type == "dir" then
|
|
for subname in nixio.fs.dir(name)
|
|
do
|
|
remove_all(name .. "/" .. subname)
|
|
end
|
|
nixio.fs.rmdir(name)
|
|
else
|
|
nixio.fs.remove(name)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- write all data to a file in one go
|
|
function write_all(filename, data)
|
|
local f = io.open(filename, "w")
|
|
if f then
|
|
f:write(data)
|
|
f:close()
|
|
end
|
|
end
|
|
|
|
-- read all data from file in one go
|
|
function read_all(filename)
|
|
local f = io.open(filename, "r")
|
|
local data
|
|
if f then
|
|
data = f:read("*a")
|
|
f:close()
|
|
end
|
|
return data
|
|
end
|
|
|
|
-- Return list of MAC to Hostname files
|
|
function mac2host(dir)
|
|
dir = dir or "/tmp/snrlog"
|
|
local i, list, popen = 0, {}, io.popen
|
|
local pfile = popen("ls -A " .. dir)
|
|
for filename in pfile:lines() do
|
|
i = i + 1
|
|
list[i] = filename
|
|
end
|
|
pfile:close()
|
|
return list
|
|
end
|
|
|
|
function mac_to_ip(mac, shift)
|
|
local a, b, c = mac:match("%w%w:%w%w:%w%w:(%w%w):(%w%w):(%w%w)")
|
|
if not a then
|
|
return "0.0.0"
|
|
end
|
|
return string.format("%d.%d.%d", tonumber(a, 16), tonumber(b, 16), tonumber(c, 16))
|
|
end
|
|
|
|
function decimal_to_ip(val)
|
|
return (math.floor(val / 16777216) % 256) .. "." .. (math.floor(val / 65536) % 256) .. "." .. (math.floor(val / 256) % 256) .. "." .. (val % 256)
|
|
end
|
|
|
|
function ip_to_decimal(ip)
|
|
local a, b, c, d = ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")
|
|
if a then
|
|
return ((a * 256 + b) * 256 + c) * 256 + d
|
|
end
|
|
return 0
|
|
end
|
|
|
|
function netmask_to_cidr(mask)
|
|
local v = ip_to_decimal(mask)
|
|
cidr = 0
|
|
while v ~= 0
|
|
do
|
|
cidr = cidr + 1
|
|
v = (v * 2) % 0x100000000
|
|
end
|
|
return cidr
|
|
end
|
|
|
|
function validate_same_subnet(ip1, ip2, mask)
|
|
ip1 = ip_to_decimal(ip1)
|
|
ip2 = ip_to_decimal(ip2)
|
|
mask = ip_to_decimal(mask)
|
|
if nixio.bit.band(ip1, mask) == nixio.bit.band(ip2, mask) then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
|
|
function validate_ip(ip)
|
|
ip = ip:gsub("%s", "")
|
|
if ip == "0.0.0.0" or ip == "255.255.255.255" then
|
|
return false
|
|
end
|
|
local a, b, c, d = ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
|
|
if not a then
|
|
return false
|
|
end
|
|
if tonumber(a) > 255 or tonumber(b) > 255 or tonumber(c) > 255 or tonumber(d) > 255 then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
function validate_netmask(mask)
|
|
mask = mask:gsub("%s", "")
|
|
if mask == "0.0.0.0" then
|
|
return false
|
|
end
|
|
local a, b, c, d = mask:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
|
|
if not a then
|
|
return false
|
|
end
|
|
if a == "255" then
|
|
if b == "255" then
|
|
if c == "255" then
|
|
a = d
|
|
elseif d ~= "0" then
|
|
return false
|
|
else
|
|
a = c
|
|
end
|
|
elseif not (c == "0" and d == "0") then
|
|
return false
|
|
else
|
|
a = b
|
|
end
|
|
elseif not (b == "0" and c == "0" and d == "0") then
|
|
return false
|
|
end
|
|
if a == "0" or a == "128" or a == "192" or a == "224" or a == "240" or a == "248" or a == "252" or a == "254" or a == "255" then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
function validate_ip_netmask(ip, mask)
|
|
if not (validate_ip(ip) and validate_netmask(mask)) then
|
|
return false
|
|
end
|
|
ip = ip_to_decimal(ip)
|
|
mask = ip_to_decimal(mask)
|
|
local notmask = 0xffffffff - mask
|
|
if nixio.bit.band(ip, notmask) == 0 or nixio.bit.band(ip, notmask) == notmask then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
function validate_fqdn(name)
|
|
return name and name:match('^[%d%a_.-]+$') ~= nil and name:sub(0, 1) ~= '.' and name:sub(-1) ~= '.' and name:find('%.%.') == nil
|
|
end
|
|
|
|
function validate_hostname(name)
|
|
if not name then
|
|
return false
|
|
end
|
|
name = name:gsub("^%s+", ""):gsub("%s+$", "")
|
|
if name:match("_") or not name:match("^[%w%-]+$") then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
function validate_port(port)
|
|
if not port then
|
|
return false
|
|
end
|
|
port = port:gsub("^%s+", ""):gsub("%s+$", "")
|
|
if port == "" or port:match("%D") then
|
|
return false
|
|
end
|
|
port = tonumber(port)
|
|
if port < 1 or port > 65535 then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
function validate_port_range(range)
|
|
if not range then
|
|
return false
|
|
end
|
|
local port1, port2 = range:match("^%s*(%d+)%s*-%s*(%d+)%s*$")
|
|
if not port2 then
|
|
return false
|
|
end
|
|
if not validate_port(port1) or not validate_port(port2) then
|
|
return false
|
|
end
|
|
if tonumber(port2) > tonumber(port1) then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
--[[
|
|
LuCI - System library
|
|
|
|
Description:
|
|
Utilities for interaction with the Linux system
|
|
|
|
FileId:
|
|
$Id: sys.lua 9662 2013-01-30 13:36:20Z soma $
|
|
|
|
License:
|
|
Copyright 2008 Steven Barth <steven@midlink.org>
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
]]--
|
|
--- Returns the current arp-table entries as two-dimensional table.
|
|
-- @return Table of table containing the current arp entries.
|
|
-- The following fields are defined for arp entry objects:
|
|
-- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
|
|
function arptable(callback)
|
|
local arp, e, r, v
|
|
if nixio.fs.access("/proc/net/arp") then
|
|
for e in io.lines("/proc/net/arp") do
|
|
local r = { }, v
|
|
for v in e:gmatch("%S+") do
|
|
r[#r+1] = v
|
|
end
|
|
|
|
if r[1] ~= "IP" then
|
|
local x = {
|
|
["IP address"] = r[1],
|
|
["HW type"] = r[2],
|
|
["Flags"] = r[3],
|
|
["HW address"] = r[4],
|
|
["Mask"] = r[5],
|
|
["Device"] = r[6]
|
|
}
|
|
|
|
if callback then
|
|
callback(x)
|
|
else
|
|
arp = arp or { }
|
|
arp[#arp+1] = x
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return arp
|
|
end
|