2022-05-18 11:49:00 -06:00
--[[
2024-03-29 00:01:48 -06:00
Copyright ( C ) 2022 - 2024 Tim Wilkinson
2022-05-18 11:49:00 -06:00
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 :
2024-05-29 01:45:25 -06:00
Additional use restrictions exist on the AREDN ® trademark and logo .
2022-05-18 11:49:00 -06:00
See AREDNLicense.txt for more info .
2024-05-29 01:45:25 -06:00
Attributions to the AREDN ® Project must be retained in the source code .
2022-05-18 11:49:00 -06:00
If importing this code into a new or existing project attribution
2024-05-29 01:45:25 -06:00
to the AREDN ® project must be added to the source code .
2022-05-18 11:49:00 -06:00
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 ip = require ( " luci.ip " )
2024-04-01 23:15:45 -06:00
require ( " aredn.info " )
2023-01-29 20:21:58 -07:00
local socket = require ( " socket " )
2022-05-18 11:49:00 -06:00
2024-08-19 10:13:12 -06:00
local refresh_timeout_base = 12 * 60 -- refresh high cost data every 12 minutes
local refresh_timeout_limit = 17 * 60 -- to 17 minutes
2024-05-12 00:03:29 -06:00
local refresh_retry_timeout = 5 * 60
2022-05-18 11:49:00 -06:00
local pending_timeout = 5 * 60 -- pending node wait 5 minutes before they are included
local lastseen_timeout = 60 * 60 -- age out nodes we've not seen for 1 hour
local snr_run_avg = 0.8 -- snr running average
local quality_min_packets = 100 -- minimum number of tx packets before we can safely calculate the link quality
2022-05-24 09:35:36 -06:00
local tx_quality_run_avg = 0.8 -- tx quality running average
2022-05-18 11:49:00 -06:00
local ping_timeout = 1.0 -- timeout before ping gives a qualtiy penalty
2023-01-29 20:21:58 -07:00
local ping_time_run_avg = 0.8 -- ping time runnng average
local bitrate_run_avg = 0.8 -- rx/tx running average
2022-05-26 23:28:37 -06:00
local dtd_distance = 50 -- distance (meters) after which nodes connected with DtD links are considered different sites
2023-01-04 23:06:08 -07:00
local connect_timeout = 5 -- timeout (seconds) when fetching information from other nodes
local speed_time = 10 --
local speed_limit = 1000 -- close connection if it's too slow (< 1kB/s for 10 seconds)
2024-03-27 18:01:14 -06:00
local default_short_retries = 20 -- More link-level retries helps overall tcp performance (factory default is 7)
local default_long_retries = 20 -- (factory default is 4)
2024-05-21 22:02:58 -06:00
local default_min_routes = 8 -- Minimum number of routes (nodes x2 usually) on a link which *must* be left active. Avoids cutting off poorly behaving leaf nodes.
2024-05-29 23:49:38 -06:00
local wireguard_alive_time = 300 -- 5 minutes
2022-05-18 11:49:00 -06:00
2022-12-22 13:22:49 -07:00
local NFT = " /usr/sbin/nft "
2022-06-07 22:15:38 -06:00
local IW = " /usr/sbin/iw "
2024-03-29 00:01:48 -06:00
local ARPING = " /usr/sbin/arping "
local CURL = " /usr/bin/curl "
2024-05-15 19:39:55 -06:00
local IPCMD = " /sbin/ip "
2022-06-07 22:15:38 -06:00
2022-05-18 11:49:00 -06:00
local now = 0
2024-05-12 00:03:29 -06:00
local config = { }
2022-05-18 11:49:00 -06:00
2024-06-01 18:32:38 -06:00
local total_node_route_count = nil
2024-05-21 22:02:58 -06:00
2024-05-12 00:03:29 -06:00
function update_config ( )
2022-05-18 11:49:00 -06:00
local c = uci.cursor ( ) -- each time as /etc/config/aredn may have changed
2024-05-12 00:03:29 -06:00
config = {
enable = c : get ( " aredn " , " @lqm[0] " , " enable " ) == " 1 " ,
2022-05-18 11:49:00 -06:00
margin = tonumber ( c : get ( " aredn " , " @lqm[0] " , " margin_snr " ) ) ,
low = tonumber ( c : get ( " aredn " , " @lqm[0] " , " min_snr " ) ) ,
2023-02-11 12:44:10 -07:00
rts_threshold = tonumber ( c : get ( " aredn " , " @lqm[0] " , " rts_threshold " ) or " 1 " ) ,
2022-05-18 11:49:00 -06:00
min_distance = tonumber ( c : get ( " aredn " , " @lqm[0] " , " min_distance " ) ) ,
max_distance = tonumber ( c : get ( " aredn " , " @lqm[0] " , " max_distance " ) ) ,
2022-06-27 15:31:43 -06:00
auto_distance = tonumber ( c : get ( " aredn " , " @lqm[0] " , " auto_distance " ) or " 0 " ) ,
2022-05-18 11:49:00 -06:00
min_quality = tonumber ( c : get ( " aredn " , " @lqm[0] " , " min_quality " ) ) ,
margin_quality = tonumber ( c : get ( " aredn " , " @lqm[0] " , " margin_quality " ) ) ,
ping_penalty = tonumber ( c : get ( " aredn " , " @lqm[0] " , " ping_penalty " ) ) ,
2024-05-21 22:02:58 -06:00
min_routes = tonumber ( c : get ( " aredn " , " @lqm[0] " , " min_routes " ) or default_min_routes ) ,
2022-06-12 22:01:12 -06:00
user_blocks = c : get ( " aredn " , " @lqm[0] " , " user_blocks " ) or " " ,
user_allows = c : get ( " aredn " , " @lqm[0] " , " user_allows " ) or " "
2022-05-18 11:49:00 -06:00
}
end
2022-05-22 20:06:02 -06:00
-- Connected if we have tracked this link recently
2022-05-20 20:23:57 -06:00
function is_connected ( track )
if track.lastseen >= now then
return true
else
return false
end
end
2022-05-22 20:06:02 -06:00
-- Pending if this link is too new
function is_pending ( track )
if track.pending > now then
return true
2022-05-18 11:49:00 -06:00
else
2022-05-22 20:06:02 -06:00
return false
end
end
2024-05-12 00:03:29 -06:00
function is_user_blocked ( track )
if not track.user_allow and track.blocks . user then
return true
end
return false
end
2024-05-22 20:46:41 -06:00
function is_leaf ( track )
return ( track.leaf == " minor " and track.rev_leaf == " major " ) or ( track.leaf == " major " and track.rev_leaf == " minor " )
end
2022-05-22 20:06:02 -06:00
function should_block ( track )
2022-06-12 22:01:12 -06:00
if track.user_allow then
return false
2024-05-12 00:03:29 -06:00
elseif not config.enable then
return track.blocks . user
2022-06-12 22:01:12 -06:00
elseif is_pending ( track ) then
2022-05-18 11:49:00 -06:00
return track.blocks . dtd or track.blocks . user
2024-05-22 20:46:41 -06:00
elseif is_leaf ( track ) then
2024-05-21 22:02:58 -06:00
return false
2022-05-22 20:06:02 -06:00
else
return track.blocks . dtd or track.blocks . signal or track.blocks . distance or track.blocks . user or track.blocks . dup or track.blocks . quality
2022-05-18 11:49:00 -06:00
end
end
function should_nonpair_block ( track )
2022-06-07 22:15:38 -06:00
return track.blocks . dtd or track.blocks . signal or track.blocks . distance or track.blocks . user or track.blocks . quality or track.type ~= " RF "
2022-05-18 11:49:00 -06:00
end
2022-05-20 20:23:57 -06:00
function should_ping ( track )
2024-05-27 00:31:54 -06:00
if not track.ip or is_user_blocked ( track ) or track.lastseen < now then
2024-05-12 00:03:29 -06:00
return false
end
if track.type == " Tunnel " or track.type == " Wireguard " then
-- Tunnels use L3 pings, so we can only ping if we're not blocked
if track.blocked then
return false
end
else
-- Non-tunnels use L2 pings, so we can still ping even when blocked
-- but we dont ping if the node is too distance, the signal is too low, or we dont use this RF because
-- we have a DTD connection instead
if track.blocks . distance or track.blocks . signal or track.blocks . dtd then
return false
2024-03-29 00:01:48 -06:00
end
2022-05-20 20:23:57 -06:00
end
2024-05-12 00:03:29 -06:00
return true
end
function nft ( cmd )
os.execute ( NFT .. " " .. cmd )
2022-05-20 20:23:57 -06:00
end
2024-05-12 00:03:29 -06:00
function nft_insert ( chain , cmd )
os.execute ( NFT .. " insert rule ip fw4 " .. chain .. " " .. cmd )
end
function nft_delete ( chain , handle )
os.execute ( NFT .. " delete rule ip fw4 " .. chain .. " handle " .. handle )
end
function nft_handle ( chain , query )
for line in io.popen ( NFT .. " -a list chain ip fw4 " .. chain ) : lines ( )
2022-12-22 13:22:49 -07:00
do
2023-09-18 00:12:33 -06:00
local handle = line : match ( query .. " .*# handle (%d+)$ " )
2022-12-22 13:22:49 -07:00
if handle then
return handle
end
end
return nil
end
2022-05-18 11:49:00 -06:00
function update_block ( track )
if should_block ( track ) then
2022-05-31 20:54:02 -06:00
track.blocked = true
2024-01-17 15:02:20 -07:00
if track.type == " Tunnel " or track.type == " Wireguard " then
2023-02-17 22:07:39 -07:00
if not nft_handle ( " input_lqm " , " iifname \\ \" " .. track.device .. " \\ \" udp dport 698 drop " ) then
2024-05-12 00:03:29 -06:00
nft_insert ( " input_lqm " , " iifname \\ \" " .. track.device .. " \\ \" udp dport 698 drop 2> /dev/null " )
2022-06-07 22:15:38 -06:00
return " blocked "
end
else
2024-05-15 19:39:55 -06:00
if not nft_handle ( " input_lqm " , " udp dport 698 ether saddr " .. track.mac .. " drop " ) then
2024-05-12 00:03:29 -06:00
nft_insert ( " input_lqm " , " udp dport 698 ether saddr " .. track.mac .. " drop 2> /dev/null " )
2022-06-07 22:15:38 -06:00
return " blocked "
end
2022-05-18 11:49:00 -06:00
end
else
2022-05-31 20:54:02 -06:00
track.blocked = false
2024-01-17 15:02:20 -07:00
if track.type == " Tunnel " or track.type == " Wireguard " then
2023-02-17 22:07:39 -07:00
local handle = nft_handle ( " input_lqm " , " iifname \\ \" " .. track.device .. " \\ \" udp dport 698 drop " )
2022-12-22 13:22:49 -07:00
if handle then
2024-05-12 00:03:29 -06:00
nft_delete ( " input_lqm " , handle )
2022-12-22 13:22:49 -07:00
return " unblocked "
2022-06-07 22:15:38 -06:00
end
else
2024-05-15 19:39:55 -06:00
local handle = nft_handle ( " input_lqm " , " udp dport 698 ether saddr " .. track.mac .. " drop " )
2022-12-22 13:22:49 -07:00
if handle then
2024-05-12 00:03:29 -06:00
nft_delete ( " input_lqm " , handle )
2022-06-07 22:15:38 -06:00
return " unblocked "
end
2022-05-18 11:49:00 -06:00
end
end
return " unchanged "
end
2022-05-31 20:54:02 -06:00
function force_remove_block ( track )
track.blocked = false
2024-05-15 19:39:55 -06:00
local handle = nft_handle ( " input_lqm " , " udp dport 698 ether saddr " .. track.mac .. " drop " )
2022-12-22 13:22:49 -07:00
if handle then
2024-05-12 00:03:29 -06:00
nft_delete ( " input_lqm " , handle )
2022-12-22 13:22:49 -07:00
end
2023-02-17 22:07:39 -07:00
handle = nft_handle ( " input_lqm " , " iifname \\ \" " .. track.device .. " \\ \" udp dport 698 drop " )
2022-12-22 13:22:49 -07:00
if handle then
2024-05-12 00:03:29 -06:00
nft_delete ( " input_lqm " , handle )
2022-12-22 13:22:49 -07:00
end
2022-05-31 20:54:02 -06:00
end
2024-08-19 10:13:12 -06:00
function refresh_timeout ( )
return math.random ( refresh_timeout_base , refresh_timeout_limit )
end
2022-05-22 20:06:02 -06:00
-- Distance in meters between two points
function calc_distance ( lat1 , lon1 , lat2 , lon2 )
2022-05-18 11:49:00 -06:00
local r2 = 12742000 -- diameter earth (meters)
local p = 0.017453292519943295 -- Math.PI / 180
local v = 0.5 - math.cos ( ( lat2 - lat1 ) * p ) / 2 + math.cos ( lat1 * p ) * math.cos ( lat2 * p ) * ( 1 - math.cos ( ( lon2 - lon1 ) * p ) ) / 2
return math.floor ( r2 * math.asin ( math.sqrt ( v ) ) )
end
2024-05-12 00:03:29 -06:00
function av ( c , f , n , o )
if c and o and n then
return f * c + ( 1 - f ) * ( n - o )
else
return n
end
end
2024-05-12 23:10:53 -06:00
function round ( v )
return math.floor ( v + 0.5 )
end
2023-01-12 11:31:28 -07:00
-- Canonical hostname
function canonical_hostname ( hostname )
2024-05-12 00:03:29 -06:00
return hostname and hostname : lower ( ) : gsub ( " ^dtdlink%. " , " " ) : gsub ( " ^mid%d+%. " , " " ) : gsub ( " ^xlink%d+%. " , " " ) : gsub ( " %.local%.mesh$ " , " " )
2023-01-12 11:31:28 -07:00
end
2024-04-01 23:15:45 -06:00
local myhostname = canonical_hostname ( aredn.info . get_nvram ( " node " ) or " localnode " )
2024-01-17 12:43:52 -07:00
local myip = uci.cursor ( ) : get ( " network " , " wifi " , " ipaddr " )
2024-05-31 23:44:36 -06:00
local is_supernode = uci.cursor ( ) : get ( " aredn " , " @supernode[0] " , " enable " ) == " 1 "
2023-01-12 13:43:55 -07:00
2024-05-29 23:49:38 -06:00
local wgsupport = nixio.fs . stat ( " /usr/bin/wg " )
2022-05-18 11:49:00 -06:00
-- Clear old data
local f = io.open ( " /tmp/lqm.info " , " w " )
2023-01-12 20:58:27 -07:00
f : write ( ' {"trackers":{},"hidden_nodes":[]} ' )
2022-05-18 11:49:00 -06:00
f : close ( )
-- Get radio
2022-06-07 22:15:38 -06:00
local radiomode = " none "
2023-05-25 22:27:59 -06:00
local wlan = aredn.hardware . get_iface_name ( " wifi " )
local phy = " none "
if wlan : match ( " ^wlan(%d+)$ " ) then
2024-05-21 22:02:58 -06:00
phy = iwinfo.nl80211 . phyname ( wlan )
radiomode = " adhoc "
2022-05-18 11:49:00 -06:00
end
2023-12-13 16:52:44 -07:00
function iw_set ( cmd )
if phy ~= " none " then
os.execute ( IW .. " " .. phy .. " set " .. cmd .. " > /dev/null 2>&1 " )
end
end
2022-05-18 11:49:00 -06:00
function lqm ( )
-- Let things startup for a while before we begin
2023-12-20 01:25:12 -07:00
wait_for_ticks ( math.max ( 0 , 30 - nixio.sysinfo ( ) . uptime ) )
2022-05-18 11:49:00 -06:00
-- Create filters (cannot create during install as they disappear on reboot)
2024-05-12 00:03:29 -06:00
nft ( " flush chain ip fw4 input_lqm 2> /dev/null " )
nft ( " delete chain ip fw4 input_lqm 2> /dev/null " )
nft ( " add chain ip fw4 input_lqm 2> /dev/null " )
2022-12-22 13:22:49 -07:00
local handle = nft_handle ( " input " , " jump input_lqm comment " )
if handle then
2024-05-12 00:03:29 -06:00
nft_delete ( " input " , handle )
2022-12-22 13:22:49 -07:00
end
2024-05-12 00:03:29 -06:00
nft_insert ( " input " , " jump input_lqm comment \\ \" block low quality links \\ \" " )
2022-05-18 11:49:00 -06:00
-- We dont know any distances yet
2023-12-13 16:52:44 -07:00
iw_set ( " distance auto " )
2023-01-12 11:31:28 -07:00
-- Or any hidden nodes
2023-12-13 16:52:44 -07:00
iw_set ( " rts off " )
2024-05-15 00:09:06 -06:00
if config.enable then
-- Set the default retries
iw_set ( " retry short " .. default_short_retries .. " long " .. default_long_retries )
end
2022-05-18 11:49:00 -06:00
2023-02-13 15:04:48 -07:00
-- If the channel bandwidth is less than 20, we need to adjust what we report as the values from 'iw' will not
-- be correct
local channel_bw_scale = 1
local chanbw = read_all ( " /sys/kernel/debug/ieee80211/ " .. phy .. " /ath10k/chanbw " )
if not chanbw then
chanbw = read_all ( " /sys/kernel/debug/ieee80211/ " .. phy .. " /ath9k/chanbw " )
end
if chanbw then
chanbw = tonumber ( chanbw )
if chanbw == 10 then
channel_bw_scale = 0.5
elseif chanbw == 5 then
channel_bw_scale = 0.25
end
end
2022-12-22 13:22:49 -07:00
local noise = - 95
2022-05-18 11:49:00 -06:00
local tracker = { }
2022-06-07 22:15:38 -06:00
local dtdlinks = { }
2023-01-12 11:31:28 -07:00
local rflinks = { }
local hidden_nodes = { }
2023-02-11 12:43:36 -07:00
local last_coverage = - 1
2023-12-13 16:52:44 -07:00
local last_short_retries = - 1
local last_long_retries = - 1
2024-05-21 22:02:58 -06:00
local pending_count = 0
2022-05-18 11:49:00 -06:00
while true
do
now = nixio.sysinfo ( ) . uptime
2024-05-12 00:03:29 -06:00
update_config ( )
2022-05-18 11:49:00 -06:00
2024-01-17 12:43:52 -07:00
local cursor = uci.cursor ( )
local cursorm = uci.cursor ( " /etc/config.mesh " )
2022-12-22 13:22:49 -07:00
local lat = cursor : get ( " aredn " , " @location[0] " , " lat " )
local lon = cursor : get ( " aredn " , " @location[0] " , " lon " )
lat = tonumber ( lat )
lon = tonumber ( lon )
2022-05-18 11:49:00 -06:00
local arps = { }
2024-05-15 19:39:55 -06:00
for line in io.popen ( IPCMD .. " neigh show " ) : lines ( )
do
local ip , dev , mac , probes , state = line : match ( " ^(%S+) dev (%S+) lladdr (%S+) .+ probes (%d+) (.+)$ " )
if ip and ( tonumber ( probes ) < 4 or state ~= " STALE " ) then
arps [ # arps + 1 ] = {
Device = dev ,
[ " HW address " ] = mac : lower ( ) ,
[ " IP address " ] = ip
}
2022-05-18 11:49:00 -06:00
end
2024-05-15 19:39:55 -06:00
end
2022-05-18 11:49:00 -06:00
2024-05-15 19:39:55 -06:00
-- Find all our devices and know our macs so we can exclude them
local devices = { }
for _ , i in ipairs ( nixio.getifaddrs ( ) )
do
if i.name then
local dev = devices [ i.name ]
if not dev then
dev = { name = i.name }
devices [ i.name ] = dev
end
if i.family == " packet " then
if i.addr then
dev.mac = i.addr : lower ( )
end
dev.tx_packets = i.data . tx_packets
dev.tx_fail = i.data . tx_errors
end
if i.family == " inet " then
dev.ip = i.addr
dev.dstip = i.dstaddr
if not dev.mac then
-- Fake a mac from the ip if we need one
local a , b , c , d = dev.ip : match ( " ^(%d+)%.(%d+)%.(%d+)%.(%d+)$ " )
dev.mac = string.format ( " 00:00:%02X:%02X:%02X:%02X " , a , b , c , d )
end
end
2022-09-09 07:50:39 -06:00
end
end
2024-05-29 23:49:38 -06:00
-- Find the wireguard tunnels that are active
local wgtuns = { }
if wgsupport then
local f = io.popen ( " /usr/bin/wg show all latest-handshakes " )
if f then
for line in f : lines ( )
do
local iface , handshake = line : match ( " ^(%S+)%s+%S+%s+(%d+)%s*$ " )
if iface and tonumber ( handshake ) + wireguard_alive_time > os.time ( ) then
wgtuns [ iface ] = true
end
end
f : close ( )
end
end
2022-05-18 11:49:00 -06:00
local stations = { }
2022-06-07 22:15:38 -06:00
2024-05-15 19:39:55 -06:00
-- Legacy and wireguard tunnels
for _ , dev in pairs ( devices )
2022-05-18 11:49:00 -06:00
do
2024-06-01 18:32:38 -06:00
if dev.name : match ( " ^tun%d+ " ) then
2024-05-15 19:39:55 -06:00
stations [ # stations + 1 ] = {
2022-06-07 22:15:38 -06:00
type = " Tunnel " ,
2024-05-15 19:39:55 -06:00
device = dev.name ,
ip = dev.dstip ,
mac = dev.mac ,
tx_packets = dev.tx_packets ,
tx_fail = dev.tx_fail
}
2024-06-01 18:32:38 -06:00
elseif dev.name : match ( " ^wgc%d+ " ) and wgtuns [ dev.name ] then
2024-05-15 19:39:55 -06:00
local ip123 , ip4 = dev.ip : match ( " ^(%d+%.%d+%.%d+%.)(%d+)$ " )
stations [ # stations + 1 ] = {
type = " Wireguard " ,
device = dev.name ,
ip = ip123 .. ( tonumber ( ip4 ) + 1 ) ,
mac = dev.mac ,
tx_packets = dev.tx_packets ,
tx_fail = dev.tx_fail
}
2024-06-01 18:32:38 -06:00
elseif dev.name : match ( " ^wgs%d+ " ) and wgtuns [ dev.name ] then
2024-05-15 19:39:55 -06:00
local ip123 , ip4 = dev.ip : match ( " ^(%d+%.%d+%.%d+%.)(%d+)$ " )
stations [ # stations + 1 ] = {
type = " Wireguard " ,
device = dev.name ,
ip = ip123 .. ( tonumber ( ip4 ) - 1 ) ,
mac = dev.mac ,
tx_packets = dev.tx_packets ,
tx_fail = dev.tx_fail
2022-05-18 11:49:00 -06:00
}
end
end
2024-06-01 18:32:38 -06:00
-- Xlink interfaces
local xlinks = { }
cursorm : foreach ( " xlink " , " interface " ,
function ( section )
if section.ifname then
xlinks [ section.ifname ] = true
end
end
)
-- DtD & Xlinks
2023-02-11 12:45:04 -07:00
for _ , entry in ipairs ( arps )
2022-06-07 22:15:38 -06:00
do
2023-09-18 00:12:33 -06:00
if entry.Device : match ( " %.2$ " ) or entry.Device : match ( " ^br%-dtdlink " ) then
2023-02-01 23:57:06 -07:00
stations [ # stations + 1 ] = {
type = " DtD " ,
device = entry.Device ,
ip = entry [ " IP address " ] ,
2024-05-12 00:03:29 -06:00
mac = entry [ " HW address " ]
2023-02-01 23:57:06 -07:00
}
2024-06-01 18:32:38 -06:00
elseif xlinks [ entry.Device ] then
stations [ # stations + 1 ] = {
type = " Xlink " ,
device = entry.Device ,
ip = entry [ " IP address " ] ,
mac = entry [ " HW address " ]
}
2022-06-07 22:15:38 -06:00
end
end
2024-05-22 20:46:41 -06:00
-- RF
if radiomode == " adhoc " then
local kv = {
[ " signal avg: " ] = " signal " ,
[ " tx packets: " ] = " tx_packets " ,
[ " tx retries: " ] = " tx_retries " ,
[ " tx failed: " ] = " tx_fail " ,
[ " tx bitrate: " ] = " tx_bitrate " ,
[ " rx bitrate: " ] = " rx_bitrate "
}
local station = { }
local cnoise = iwinfo.nl80211 . noise ( wlan )
if cnoise and cnoise < - 70 then
noise = round ( noise * 0.9 + cnoise * 0.1 )
end
for line in io.popen ( IW .. " " .. wlan .. " station dump " ) : lines ( )
do
local mac = line : match ( " ^Station ([0-9a-fA-F:]+) " )
if mac then
station = {
type = " RF " ,
device = wlan ,
mac = mac : lower ( ) ,
2024-06-01 18:32:38 -06:00
signal = nil ,
2024-05-22 20:46:41 -06:00
noise = noise ,
ip = nil ,
tx_bitrate = 0 ,
rx_bitrate = 0
}
for _ , entry in ipairs ( arps )
do
if entry [ " HW address " ] == station.mac and entry.Device : match ( " ^wlan " ) then
station.ip = entry [ " IP address " ]
break
end
end
else
for k , v in pairs ( kv )
do
local val = line : match ( k .. " %s*([%d%-]+) " )
if val then
station [ v ] = tonumber ( val )
if v == " tx_bitrate " or v == " rx_bitrate " then
station [ v ] = station [ v ] * channel_bw_scale
end
2024-06-01 18:32:38 -06:00
if v == " signal " then
stations [ # stations + 1 ] = station
end
2024-05-22 20:46:41 -06:00
end
end
end
end
end
2024-05-12 00:03:29 -06:00
-- Update the trackers based on the latest station information
2022-06-07 22:15:38 -06:00
for _ , station in ipairs ( stations )
2022-05-18 11:49:00 -06:00
do
2024-06-01 18:32:38 -06:00
if not tracker [ station.mac ] then
tracker [ station.mac ] = {
type = station.type ,
device = station.device ,
firstseen = now ,
lastseen = now ,
pending = now + pending_timeout ,
refresh = 0 ,
mac = station.mac ,
ip = nil ,
hostname = nil ,
lat = nil ,
lon = nil ,
distance = nil ,
2024-08-15 21:28:45 -06:00
localarea = nil ,
2024-06-01 18:32:38 -06:00
blocks = {
dtd = false ,
signal = false ,
distance = false ,
pair = false ,
quality = false
} ,
blocked = false ,
snr = nil ,
rev_snr = nil ,
avg_snr = nil ,
last_tx = nil ,
tx_quality = nil ,
ping_quality = nil ,
ping_success_time = nil ,
tx_bitrate = nil ,
rx_bitrate = nil ,
quality = nil ,
quality0_seen = nil ,
quality_block_snr = nil ,
last_tx_fail = nil ,
last_tx_retries = nil ,
avg_tx = nil ,
avg_tx_retries = nil ,
avg_tx_fail = nil ,
node_route_count = 0 ,
leaf = nil ,
rev_leaf = nil
}
end
local track = tracker [ station.mac ]
2022-05-18 11:49:00 -06:00
2024-06-01 18:32:38 -06:00
-- IP and Hostname
if station.ip and station.ip ~= track.ip then
track.ip = station.ip
track.hostname = nil
end
if not track.hostname and track.ip then
track.hostname = canonical_hostname ( nixio.getnameinfo ( track.ip ) )
end
2022-05-18 11:49:00 -06:00
2024-06-01 18:32:38 -06:00
-- Running average SNR
if station.signal and station.noise then
track.snr = round ( av ( track.snr , snr_run_avg , station.signal - station.noise , 0 ) )
end
2022-05-18 11:49:00 -06:00
2024-06-01 18:32:38 -06:00
-- Running average estimate of link quality
local tx = station.tx_packets
local tx_retries = station.tx_retries
local tx_fail = station.tx_fail
2024-05-12 00:03:29 -06:00
2024-06-01 18:32:38 -06:00
if tx and track.tx and tx >= track.tx + quality_min_packets then
track.avg_tx = av ( track.avg_tx , tx_quality_run_avg , tx , track.tx )
track.avg_tx_retries = av ( track.avg_tx_retries , tx_quality_run_avg , tx_retries , track.tx_retries )
track.avg_tx_fail = av ( track.avg_tx_fail , tx_quality_run_avg , tx_fail , track.tx_fail )
2024-05-12 00:03:29 -06:00
2024-06-01 18:32:38 -06:00
local bad = math.max ( ( track.avg_tx_fail or 0 ) , ( track.avg_tx_retries or 0 ) )
track.tx_quality = 100 * ( 1 - math.min ( 1 , math.max ( track.avg_tx > 0 and bad / track.avg_tx or 0 , 0 ) ) )
end
2022-05-18 11:49:00 -06:00
2024-06-01 18:32:38 -06:00
track.tx = tx
track.tx_retries = tx_retries
track.tx_fail = tx_fail
2022-05-18 11:49:00 -06:00
2024-06-01 18:32:38 -06:00
track.tx_bitrate = av ( track.tx_bitrate , bitrate_run_avg , station.tx_bitrate , track.tx_bitrate )
track.rx_bitrate = av ( track.rx_bitrate , bitrate_run_avg , station.rx_bitrate , track.rx_bitrate )
2024-05-12 00:03:29 -06:00
2024-06-01 18:32:38 -06:00
track.lastseen = now
2023-01-14 23:30:19 -07:00
end
2022-05-18 11:49:00 -06:00
-- Update link tracking state
2024-05-21 22:02:58 -06:00
local ip2tracker = { }
pending_count = 0
2022-05-18 11:49:00 -06:00
for _ , track in pairs ( tracker )
2024-05-21 22:02:58 -06:00
do
if not track.ip then
2024-05-12 00:03:29 -06:00
track.routable = false
2024-05-21 22:02:58 -06:00
else
ip2tracker [ track.ip ] = track
2023-01-22 19:15:42 -07:00
2024-05-21 22:02:58 -06:00
-- Update if link is routable
local rt = ip.route ( track.ip )
if rt and tostring ( rt.gw ) == track.ip then
track.routable = true
2024-05-12 00:03:29 -06:00
else
2024-05-21 22:02:58 -06:00
track.routable = false
end
-- Refresh remote attributes periodically as this is expensive
2024-08-19 10:13:12 -06:00
-- We dont do it the very first time so we can populate the LQM state with a new node quickly
if now > track.refresh and track.firstseen ~= track.lastseen then
2024-05-12 00:03:29 -06:00
2024-05-21 22:02:58 -06:00
-- Refresh the hostname periodically as it can change
track.hostname = canonical_hostname ( nixio.getnameinfo ( track.ip ) ) or track.hostname
2024-05-12 00:03:29 -06:00
2024-08-15 21:28:45 -06:00
if track.blocked then
-- Remote is blocked
2024-05-21 22:02:58 -06:00
-- We cannot update so invalidate any information considered stale and set time to attempt refresh
2024-05-12 00:03:29 -06:00
track.refresh = is_pending ( track ) and 0 or now + refresh_retry_timeout
track.rev_snr = nil
2024-05-22 20:46:41 -06:00
track.rev_leaf = nil
2024-05-12 00:03:29 -06:00
else
2024-05-21 22:02:58 -06:00
local raw = io.popen ( CURL .. " --retry 0 --connect-timeout " .. connect_timeout .. " --speed-time " .. speed_time .. " --speed-limit " .. speed_limit .. " -s \" http:// " .. track.ip .. " :8080/cgi-bin/sysinfo.json?link_info=1&lqm=1 \" -o - 2> /dev/null " )
local info = luci.jsonc . parse ( raw : read ( " *a " ) )
raw : close ( )
2024-05-12 00:03:29 -06:00
2024-05-21 22:02:58 -06:00
wait_for_ticks ( 0 )
2024-05-12 00:03:29 -06:00
2024-05-21 22:02:58 -06:00
if not info then
-- Failed to fetch information. Set time for retry and invalidate any information
-- considered stale
track.refresh = is_pending ( track ) and 0 or now + refresh_retry_timeout
track.rev_snr = nil
2024-05-22 20:46:41 -06:00
track.rev_leaf = nil
2024-05-21 22:02:58 -06:00
else
2024-08-19 10:13:12 -06:00
track.refresh = is_pending ( track ) and 0 or now + refresh_timeout ( )
2024-05-21 22:02:58 -06:00
dtdlinks [ track.mac ] = { }
2024-05-12 00:03:29 -06:00
2024-05-21 22:02:58 -06:00
-- Update the distance to the remote node
track.lat = tonumber ( info.lat ) or track.lat
track.lon = tonumber ( info.lon ) or track.lon
if track.lat and track.lon and lat and lon then
track.distance = calc_distance ( lat , lon , track.lat , track.lon )
2024-08-15 21:28:45 -06:00
if track.type == " DtD " and track.distance < dtd_distance then
track.localarea = true
else
track.localarea = false
end
2024-05-21 22:02:58 -06:00
end
2024-08-15 21:28:45 -06:00
-- Keep some useful info
2024-08-17 13:08:23 -06:00
if info.node_details then
track.model = info.node_details . model
track.firmware_version = info.node_details . firmware_version
end
2024-08-15 21:28:45 -06:00
2024-05-21 22:02:58 -06:00
if track.type == " RF " then
rflinks [ track.mac ] = nil
if info.lqm and info.lqm . enabled and info.lqm . info and info.lqm . info.trackers then
rflinks [ track.mac ] = { }
for _ , rtrack in pairs ( info.lqm . info.trackers )
do
if rtrack.type == " RF " or not rtrack.type then
local rhostname = canonical_hostname ( rtrack.hostname )
if rtrack.ip and rtrack.routable then
local rdistance = nil
if tonumber ( rtrack.lat ) and tonumber ( rtrack.lon ) and lat and lon then
rdistance = calc_distance ( lat , lon , tonumber ( rtrack.lat ) , tonumber ( rtrack.lon ) )
end
rflinks [ track.mac ] [ rtrack.ip ] = {
ip = rtrack.ip ,
hostname = rhostname ,
distance = rdistance
}
end
if myhostname == rhostname then
track.rev_snr = ( track.rev_snr and rtrack.snr ) and round ( snr_run_avg * track.rev_snr + ( 1 - snr_run_avg ) * rtrack.snr ) or rtrack.snr
2024-05-22 20:46:41 -06:00
track.rev_leaf = rtrack.leaf
2024-05-12 00:03:29 -06:00
end
2023-01-12 11:31:28 -07:00
end
2022-05-18 11:49:00 -06:00
end
2024-05-21 22:02:58 -06:00
for ip , link in pairs ( info.link_info )
do
if link.hostname and link.linkType == " DTD " then
dtdlinks [ track.mac ] [ canonical_hostname ( link.hostname ) ] = true
end
2024-05-12 00:03:29 -06:00
end
2024-05-21 22:02:58 -06:00
elseif info.link_info then
rflinks [ track.mac ] = { }
-- If there's no LQM information we fallback on using link information.
for ip , link in pairs ( info.link_info )
do
local rhostname = canonical_hostname ( link.hostname )
if link.linkType == " RF " then
rflinks [ track.mac ] [ ip ] = {
ip = ip ,
hostname = rhostname
}
end
if rhostname then
if link.linkType == " DTD " then
dtdlinks [ track.mac ] [ rhostname ] = true
elseif link.linkType == " RF " and link.signal and link.noise and myhostname == rhostname then
local snr = link.signal - link.noise
track.rev_snr = track.rev_snr and round ( snr_run_avg * track.rev_snr + ( 1 - snr_run_avg ) * snr ) or snr
end
2022-05-18 11:49:00 -06:00
end
end
end
end
end
end
end
end
2024-05-12 00:03:29 -06:00
-- Update avg snr using both ends (if we have them)
if track.snr then
if track.rev_snr then
2024-05-12 23:10:53 -06:00
track.avg_snr = round ( ( track.snr + track.rev_snr ) / 2 )
2022-05-20 20:23:57 -06:00
else
2024-05-12 00:03:29 -06:00
track.avg_snr = track.snr
2022-05-20 20:23:57 -06:00
end
2022-05-18 11:49:00 -06:00
else
2024-05-25 17:51:37 -06:00
track.avg_snr = nil
2022-05-18 11:49:00 -06:00
end
2024-05-21 22:02:58 -06:00
-- Count number of pending trackers
if is_pending ( track ) then
pending_count = pending_count + 1
end
2022-05-18 11:49:00 -06:00
-- Ping addresses and penalize quality for excessively slow links
2024-05-12 00:03:29 -06:00
if should_ping ( track ) then
2024-08-17 23:53:31 -06:00
local success = false
2024-03-29 00:01:48 -06:00
local ptime
2024-08-17 23:53:31 -06:00
if track.type ~= " Tunnel " and track.type ~= " Wireguard " then
-- For devices which support ARP, send an ARP request and wait for a reply. This avoids the other ends routing
-- table and firewall messing up the response packet.
local pstart = socket.gettime ( 0 )
if os.execute ( ARPING .. " -q -c 1 -D -w " .. round ( ping_timeout ) .. " -I " .. track.device .. " " .. track.ip ) ~= 0 then
success = true
end
ptime = socket.gettime ( 0 ) - pstart
end
if not success then
-- If that fails, measure the "ping" time directly to the device by sending a UDP packet
2024-03-29 00:01:48 -06:00
local sigsock = nixio.socket ( " inet " , " dgram " )
sigsock : setopt ( " socket " , " rcvtimeo " , ping_timeout )
sigsock : setopt ( " socket " , " bindtodevice " , track.device )
sigsock : setopt ( " socket " , " dontroute " , 1 )
-- Must connect or we wont see the error
sigsock : connect ( track.ip , 8080 )
local pstart = socket.gettime ( 0 )
sigsock : send ( " " )
-- There's no actual UDP server at the other end so recv will either timeout and return 'false' if the link is slow,
-- or will error and return 'nil' if there is a node and it send back an ICMP error quickly (which for our purposes is a positive)
2024-08-17 23:53:31 -06:00
if sigsock : recv ( 0 ) ~= false then
success = true
2024-03-29 00:01:48 -06:00
end
ptime = socket.gettime ( 0 ) - pstart
sigsock : close ( )
2022-05-18 11:49:00 -06:00
end
2024-03-29 00:01:48 -06:00
2023-12-20 01:25:12 -07:00
wait_for_ticks ( 0 )
2023-12-06 13:11:11 -07:00
2024-05-12 23:10:53 -06:00
track.ping_quality = track.ping_quality and ( track.ping_quality + 1 ) or 100
2024-05-22 20:46:41 -06:00
if success then
2024-05-12 00:03:29 -06:00
track.ping_success_time = track.ping_success_time and ( track.ping_success_time * ping_time_run_avg + ptime * ( 1 - ping_time_run_avg ) ) or ptime
2024-05-12 23:10:53 -06:00
else
track.ping_quality = track.ping_quality - config.ping_penalty
2023-01-29 20:21:58 -07:00
end
2024-05-12 23:10:53 -06:00
track.ping_quality = math.max ( 0 , math.min ( 100 , track.ping_quality ) )
2024-05-22 20:46:41 -06:00
if not success and track.type == " DtD " and track.firstseen == now then
2023-09-18 00:12:33 -06:00
-- If local ping immediately fail, ditch this tracker. This can happen sometimes when we
-- find arp entries which aren't valid.
tracker [ track.mac ] = nil
end
2024-05-12 00:03:29 -06:00
else
track.ping_quality = nil
track.ping_success_time = nil
2022-05-18 11:49:00 -06:00
end
2022-05-24 09:35:36 -06:00
-- Calculate overall link quality
2024-05-12 00:03:29 -06:00
if track.tx_quality then
if track.ping_quality then
2024-05-12 23:10:53 -06:00
track.quality = round ( ( track.tx_quality + track.ping_quality ) / 2 )
2024-05-12 00:03:29 -06:00
else
2024-05-12 23:10:53 -06:00
track.quality = round ( track.tx_quality )
2022-05-18 11:49:00 -06:00
end
2024-05-12 00:03:29 -06:00
elseif track.ping_quality then
2024-05-12 23:10:53 -06:00
track.quality = round ( track.ping_quality )
2024-05-12 00:03:29 -06:00
else
track.quality = nil
2022-05-18 11:49:00 -06:00
end
2024-05-15 00:09:06 -06:00
if track.quality and track.quality == 0 and not track.quality0_seen then
track.quality0_seen = now
end
2024-05-21 22:02:58 -06:00
track.node_route_count = 0
end
--
-- Pull in the routing table to see how many node routes are associated with each tracker.
2024-06-01 18:32:38 -06:00
-- We don't do this for supernodes as the table is very big and we don't use the information.
-- Don't pull the data from OLSR as this can be too distruptive to its operation on slower nodes
-- with large routing tables.
2024-05-21 22:02:58 -06:00
--
2024-05-31 23:44:36 -06:00
if not is_supernode then
2024-06-01 18:32:38 -06:00
total_node_route_count = 0
for line in io.popen ( IPCMD .. " route show table 30 " ) : lines ( )
2024-05-31 23:44:36 -06:00
do
2024-06-01 18:32:38 -06:00
local gw = line : match ( " ^10%.%d+%.%d+%.%d+ via (%d+%.%d+%.%d+%.%d+) dev " )
if gw then
local track = ip2tracker [ gw ] ;
2024-05-31 23:44:36 -06:00
if track then
track.node_route_count = track.node_route_count + 1
total_node_route_count = total_node_route_count + 1
end
2024-05-21 22:02:58 -06:00
end
end
2022-05-18 11:49:00 -06:00
end
2024-05-12 00:03:29 -06:00
--
-- At this point we have gather all the data we need to determine which links are best to use and
-- which links should be blocked.
--
2024-05-22 20:46:41 -06:00
local leafs = 0
2022-05-18 11:49:00 -06:00
for _ , track in pairs ( tracker )
do
2024-05-12 00:03:29 -06:00
for _ = 1 , 1
do
-- Clear state
local oldblocks = track.blocks ;
track.blocks = {
dtd = false ,
signal = false ,
distance = false ,
pair = false ,
quality = false
}
2024-05-22 20:46:41 -06:00
-- A leaf link is one where most (but not 0) of the nodes are on one end or the other
-- We try to keep leaf links active even if they wouldn't otherwise be, so they dont get cut off.
track.leaf = nil
if track.node_route_count > 0 then
if total_node_route_count - track.node_route_count <= config.min_routes then
track.leaf = " minor "
elseif track.node_route_count <= config.min_routes then
track.leaf = " major "
end
end
2024-05-12 00:03:29 -06:00
-- Always allow if user requested it
for val in string.gmatch ( config.user_allows , " ([^,]+) " )
do
2024-05-15 19:39:55 -06:00
if val : gsub ( " %s+ " , " " ) : gsub ( " - " , " : " ) : lower ( ) == track.mac then
2024-05-12 00:03:29 -06:00
track.user_allow = true
2023-01-13 11:19:57 -07:00
break
end
2022-06-07 22:15:38 -06:00
end
2024-05-12 00:03:29 -06:00
if track.user_allow then
break
2022-06-07 22:15:38 -06:00
end
2024-05-12 00:03:29 -06:00
-- Block if user requested it
for val in string.gmatch ( config.user_blocks , " ([^,]+) " )
do
2024-05-15 19:39:55 -06:00
if val : gsub ( " %s+ " , " " ) : gsub ( " - " , " : " ) : lower ( ) == track.mac then
2024-05-12 00:03:29 -06:00
track.blocks . user = true
break
end
2022-06-07 22:15:38 -06:00
end
2024-05-12 00:03:29 -06:00
if track.blocks . user then
2022-05-18 11:49:00 -06:00
break
end
2024-05-12 00:03:29 -06:00
-- SNR and distance blocks only related to RF links
if track.type == " RF " then
2022-05-18 11:49:00 -06:00
2024-05-12 00:03:29 -06:00
-- Block any nodes which are too distant
if track.distance and ( track.distance < config.min_distance or track.distance > config.max_distance ) then
track.blocks . distance = true
break
end
-- If we have a direct dtd connection to this device, make sure we use that
for _ , dtd in pairs ( tracker ) do
if dtd.type == " DtD " and dtd.hostname == track.hostname then
if dtd.distance and dtd.distance < dtd_distance and dtd.routable then
track.blocks . dtd = true
end
break
end
end
if track.blocks . dtd then
break
end
-- When unblocked link signal becomes too low, block
if not oldblocks.signal then
if track.snr < config.low or ( track.rev_snr and track.rev_snr < config.low ) then
track.blocks . signal = true
break
end
2024-05-21 22:02:58 -06:00
-- when blocked link becomes (low+margin) again, dont maintain block
2024-05-12 00:03:29 -06:00
else
if track.snr < config.low + config.margin or ( track.rev_snr and track.rev_snr < config.low + config.margin ) then
track.blocks . signal = true
break
else
-- When signal is good enough to unblock a link but the quality is low, artificially bump
-- it up to give the link chance to recover
if oldblocks.quality then
track.quality = config.min_quality + config.margin_quality
2024-05-15 00:09:06 -06:00
track.quality0_seen = nil
2024-05-12 00:03:29 -06:00
end
end
end
2024-05-25 17:51:37 -06:00
-- If we have a quality block and the snr gets sufficiently better, bump the quality to unblock it and see if things have improved
if oldblocks.quality and track.quality_block_snr and track.avg_snr and track.avg_snr > track.quality_block_snr + config.margin then
track.quality = config.min_quality + config.margin_quality
track.quality0_seen = nil
end
2022-05-18 11:49:00 -06:00
end
2022-06-12 22:01:12 -06:00
2024-05-12 00:03:29 -06:00
-- Block if quality is poor
if track.quality and ( track.type ~= " DtD " or ( track.distance and track.distance >= dtd_distance ) ) then
if not oldblocks.quality then
if track.quality < config.min_quality then
track.blocks . quality = true
2024-05-25 17:51:37 -06:00
track.quality_block_snr = track.avg_snr
2024-05-12 00:03:29 -06:00
end
else
if track.quality < config.min_quality + config.margin_quality then
track.blocks . quality = true
end
end
2022-06-12 22:01:12 -06:00
end
2024-05-22 20:46:41 -06:00
end
2024-05-12 00:03:29 -06:00
2024-05-22 20:46:41 -06:00
-- Count block leafs
if is_leaf ( track ) and ( track.blocks . distance or track.blocks . signal or track.blocks . quality ) then
leafs = leafs + 1
2022-06-12 22:01:12 -06:00
end
2022-05-18 11:49:00 -06:00
end
-- Eliminate link pairs, where we might have links to multiple radios at the same site
-- Find them and select the one with the best SNR avg on both ends
for _ , track in pairs ( tracker )
do
if track.hostname and not should_nonpair_block ( track ) then
-- Get a list of radio pairs. These are radios we're associated with which are DTD'ed together
local tracklist = { track }
for _ , track2 in pairs ( tracker )
do
if track ~= track2 and track2.hostname and not should_nonpair_block ( track2 ) then
2024-05-12 23:10:53 -06:00
if dtdlinks [ track.mac ] and dtdlinks [ track.mac ] [ track2.hostname ] then
2022-05-26 23:28:37 -06:00
if not ( track.lat and track.lon and track2.lat and track2.lon ) or calc_distance ( track.lat , track.lon , track2.lat , track2.lon ) < dtd_distance then
tracklist [ # tracklist + 1 ] = track2
end
2022-05-18 11:49:00 -06:00
end
end
end
if # tracklist == 1 then
track.blocks . dup = false
else
-- Find the link with the best average snr overall as well as unblocked
local bestany = track
local bestunblocked = nil
for _ , track2 in ipairs ( tracklist )
do
if track2.avg_snr > bestany.avg_snr then
bestany = track2
end
if not track2.blocks . dup and ( not bestunblocked or ( track2.avg_snr > bestunblocked.avg_snr ) ) then
bestunblocked = track2
end
end
-- A new winner if it's sufficiently better than the current
if not bestunblocked or bestany.avg_snr >= bestunblocked.avg_snr + config.margin then
bestunblocked = bestany
end
for _ , track2 in ipairs ( tracklist )
do
if track2 == bestunblocked then
track2.blocks . dup = false
else
track2.blocks . dup = true
end
end
end
end
end
2024-05-12 00:03:29 -06:00
--
-- We now have updated state on what is blocked and what is not.
-- Reflect this state with the firewall and various other
-- node parameters (e.g. rts, coverage)
--
local distance = - 1
2022-05-18 11:49:00 -06:00
-- Update the block state and calculate the routable distance
for _ , track in pairs ( tracker )
do
2024-05-21 22:02:58 -06:00
if is_connected ( track ) then
2022-05-22 20:06:02 -06:00
if update_block ( track ) == " unblocked " then
-- If the link becomes unblocked, return it to pending state
track.pending = now + pending_timeout
end
2022-05-18 11:49:00 -06:00
2023-02-11 12:43:36 -07:00
if track.type == " RF " then
2024-05-22 20:46:41 -06:00
-- Find the most distant, unblocked, RF node
2023-02-11 12:43:36 -07:00
if track.distance then
if track.distance > distance and ( not track.blocked or is_pending ( track ) ) then
2022-05-22 20:06:02 -06:00
distance = track.distance
end
2023-02-11 12:43:36 -07:00
elseif is_pending ( track ) then
distance = config.max_distance
2022-05-18 11:49:00 -06:00
end
end
end
2022-05-24 09:35:36 -06:00
-- Remove any trackers which are too old or if they disconnect when first seen
2024-05-15 00:09:06 -06:00
if ( ( now > track.lastseen + lastseen_timeout ) or
( not is_connected ( track ) and track.firstseen + pending_timeout > now ) or
( track.quality0_seen and now > track.quality0_seen + lastseen_timeout )
) then
2022-05-31 20:54:02 -06:00
force_remove_block ( track )
2022-05-18 11:49:00 -06:00
tracker [ track.mac ] = nil
end
end
2023-02-11 12:43:36 -07:00
-- Default distances if we haven't calcuated anything
if distance < 0 then
if config.auto_distance > 0 then
distance = config.auto_distance
else
distance = config.max_distance
end
end
2022-05-18 11:49:00 -06:00
-- Update the wifi distance
2023-02-11 12:43:36 -07:00
local coverage = math.min ( 255 , math.floor ( ( distance * 2 * 0.0033 ) / 3 ) )
2024-05-12 00:03:29 -06:00
if config.enable and coverage ~= last_coverage then
2023-12-13 16:52:44 -07:00
iw_set ( " coverage " .. coverage )
2023-02-11 12:43:36 -07:00
last_coverage = coverage
2022-05-18 11:49:00 -06:00
end
2023-01-12 11:31:28 -07:00
-- Set the RTS/CTS state depending on whether everyone can see everyone
-- Build a list of all the nodes our neighbors can see
local theres = { }
for mac , rfneighbor in pairs ( rflinks )
do
local track = tracker [ mac ]
if track and not track.blocked and track.routable then
for nip , ninfo in pairs ( rfneighbor )
do
theres [ nip ] = ninfo
end
end
end
-- Remove all the nodes we can see from this set
for _ , track in pairs ( tracker )
do
if track.ip then
theres [ track.ip ] = nil
end
end
2023-01-12 13:43:55 -07:00
-- Including ourself
theres [ myip ] = nil
2023-01-12 11:31:28 -07:00
-- If there are any nodes left, then our neighbors can see hidden nodes we cant. Enable RTS/CTS
local hidden = { }
for _ , ninfo in pairs ( theres )
do
hidden [ # hidden + 1 ] = ninfo
end
2024-05-12 00:03:29 -06:00
if config.enable and ( # hidden == 0 ) ~= ( # hidden_nodes == 0 ) and config.rts_threshold >= 0 and config.rts_threshold <= 2347 then
2023-02-12 14:50:49 -07:00
if # hidden > 0 then
2023-12-13 16:52:44 -07:00
iw_set ( " rts " .. config.rts_threshold )
2023-02-12 14:50:49 -07:00
else
2023-12-13 16:52:44 -07:00
iw_set ( " rts off " )
2023-01-12 11:31:28 -07:00
end
end
hidden_nodes = hidden
2022-05-18 11:49:00 -06:00
-- Save this for the UI
f = io.open ( " /tmp/lqm.info " , " w " )
if f then
2022-10-13 11:07:36 -06:00
f : write ( luci.jsonc . stringify ( {
2022-05-18 11:49:00 -06:00
now = now ,
trackers = tracker ,
distance = distance ,
2023-01-12 11:31:28 -07:00
coverage = coverage ,
2024-05-21 22:02:58 -06:00
hidden_nodes = hidden_nodes ,
total_node_route_count = total_node_route_count
2022-05-18 11:49:00 -06:00
} , true ) )
f : close ( )
end
2024-05-12 00:03:29 -06:00
-- Save valid (unblocked) rf mac list for use by OLSR
if config.enable and phy ~= " none " then
2024-05-22 20:46:41 -06:00
if pending_count > 0 or leafs > 0 then
2024-05-21 22:02:58 -06:00
os.remove ( " /tmp/lqm. " .. phy .. " .macs " )
else
local tmpfile = " /tmp/lqm. " .. phy .. " .macs.tmp "
f = io.open ( tmpfile , " w " )
if f then
for _ , track in pairs ( tracker )
do
if track.device == wlan and is_connected ( track ) and not track.blocked then
f : write ( track.mac .. " \n " )
end
2024-05-12 00:03:29 -06:00
end
2024-05-21 22:02:58 -06:00
f : close ( )
filecopy ( tmpfile , " /tmp/lqm. " .. phy .. " .macs " , true )
os.remove ( tmpfile )
2024-05-12 00:03:29 -06:00
end
end
end
2022-05-18 11:49:00 -06:00
wait_for_ticks ( 60 ) -- 1 minute
end
end
return lqm