Better leaf node detection (#1214)

* Better leaf node detection

* Only count leafs what would be blocked
This commit is contained in:
Tim Wilkinson 2024-05-22 19:46:41 -07:00 committed by GitHub
parent fbaa54bb23
commit ed422f3550
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 89 additions and 80 deletions

View File

@ -64,7 +64,6 @@ local now = 0
local config = {} local config = {}
local total_node_route_count = 0 local total_node_route_count = 0
local minroutes_count = 0
function update_config() function update_config()
local c = uci.cursor() -- each time as /etc/config/aredn may have changed local c = uci.cursor() -- each time as /etc/config/aredn may have changed
@ -110,6 +109,10 @@ function is_user_blocked(track)
return false return false
end end
function is_leaf(track)
return (track.leaf == "minor" and track.rev_leaf == "major") or (track.leaf == "major" and track.rev_leaf == "minor")
end
function should_block(track) function should_block(track)
if track.user_allow then if track.user_allow then
return false return false
@ -117,7 +120,7 @@ function should_block(track)
return track.blocks.user return track.blocks.user
elseif is_pending(track) then elseif is_pending(track) then
return track.blocks.dtd or track.blocks.user return track.blocks.dtd or track.blocks.user
elseif track.minroutes then elseif is_leaf(track) then
return false return false
else 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 return track.blocks.dtd or track.blocks.signal or track.blocks.distance or track.blocks.user or track.blocks.dup or track.blocks.quality
@ -236,13 +239,6 @@ function round(v)
return math.floor(v + 0.5) return math.floor(v + 0.5)
end end
function set_minroutes(track)
if track.node_route_count <= config.min_routes or (total_node_route_count - track.node_route_count) <= config.min_routes then
track.minroutes = true
minroutes_count = minroutes_count + 1
end
end
-- Canonical hostname -- Canonical hostname
function canonical_hostname(hostname) function canonical_hostname(hostname)
return hostname and hostname:lower():gsub("^dtdlink%.",""):gsub("^mid%d+%.",""):gsub("^xlink%d+%.",""):gsub("%.local%.mesh$", "") return hostname and hostname:lower():gsub("^dtdlink%.",""):gsub("^mid%d+%.",""):gsub("^xlink%d+%.",""):gsub("%.local%.mesh$", "")
@ -379,58 +375,6 @@ function lqm()
local stations = {} local stations = {}
-- 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(),
signal = 0,
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
stations[#stations + 1] = station
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
end
end
end
end
end
-- Legacy and wireguard tunnels -- Legacy and wireguard tunnels
local tunnel = {} local tunnel = {}
for _, dev in pairs(devices) for _, dev in pairs(devices)
@ -497,6 +441,58 @@ function lqm()
end end
) )
-- 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(),
signal = 0,
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
stations[#stations + 1] = station
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
end
end
end
end
end
-- Update the trackers based on the latest station information -- Update the trackers based on the latest station information
for _, station in ipairs(stations) for _, station in ipairs(stations)
do do
@ -540,7 +536,8 @@ function lqm()
avg_tx_retries = nil, avg_tx_retries = nil,
avg_tx_fail = nil, avg_tx_fail = nil,
node_route_count = 0, node_route_count = 0,
minroutes = false leaf = nil,
rev_leaf = nil
} }
end end
local track = tracker[station.mac] local track = tracker[station.mac]
@ -613,6 +610,7 @@ function lqm()
-- We cannot update so invalidate any information considered stale and set time to attempt refresh -- We cannot update so invalidate any information considered stale and set time to attempt refresh
track.refresh = is_pending(track) and 0 or now + refresh_retry_timeout track.refresh = is_pending(track) and 0 or now + refresh_retry_timeout
track.rev_snr = nil track.rev_snr = nil
track.rev_leaf = nil
else else
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 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")) local info = luci.jsonc.parse(raw:read("*a"))
@ -625,8 +623,9 @@ function lqm()
-- considered stale -- considered stale
track.refresh = is_pending(track) and 0 or now + refresh_retry_timeout track.refresh = is_pending(track) and 0 or now + refresh_retry_timeout
track.rev_snr = nil track.rev_snr = nil
track.rev_leaf = nil
else else
track.refresh = now + refresh_timeout track.refresh = is_pending(track) and 0 or now + refresh_timeout
dtdlinks[track.mac] = {} dtdlinks[track.mac] = {}
@ -658,6 +657,7 @@ function lqm()
end end
if myhostname == rhostname then 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 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
track.rev_leaf = rtrack.leaf
end end
end end
end end
@ -713,7 +713,7 @@ function lqm()
-- Ping addresses and penalize quality for excessively slow links -- Ping addresses and penalize quality for excessively slow links
if should_ping(track) then if should_ping(track) then
local success = 100 local success = true
local ptime local ptime
if track.type == "Tunnel" or track.type == "Wireguard" then if track.type == "Tunnel" or track.type == "Wireguard" then
@ -729,7 +729,7 @@ function lqm()
-- There's no actual UDP server at the other end so recv will either timeout and return 'false' if the link is slow, -- 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) -- 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)
if sigsock:recv(0) == false then if sigsock:recv(0) == false then
success = 0 success = false
end end
ptime = socket.gettime(0) - pstart ptime = socket.gettime(0) - pstart
sigsock:close() sigsock:close()
@ -739,7 +739,7 @@ function lqm()
local pstart = socket.gettime(0) local pstart = socket.gettime(0)
if os.execute(ARPING .. " -q -c 1 -D -w " .. round(ping_timeout) .. " -I " .. track.device .. " " .. track.ip) == 0 then if os.execute(ARPING .. " -q -c 1 -D -w " .. round(ping_timeout) .. " -I " .. track.device .. " " .. track.ip) == 0 then
-- Failure -- Failure
success = 0 success = false
end end
ptime = socket.gettime(0) - pstart ptime = socket.gettime(0) - pstart
end end
@ -747,13 +747,13 @@ function lqm()
wait_for_ticks(0) wait_for_ticks(0)
track.ping_quality = track.ping_quality and (track.ping_quality + 1) or 100 track.ping_quality = track.ping_quality and (track.ping_quality + 1) or 100
if success > 0 then if success then
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 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
else else
track.ping_quality = track.ping_quality - config.ping_penalty track.ping_quality = track.ping_quality - config.ping_penalty
end end
track.ping_quality = math.max(0, math.min(100, track.ping_quality)) track.ping_quality = math.max(0, math.min(100, track.ping_quality))
if success == 0 and track.type == "DtD" and track.firstseen == now then if not success and track.type == "DtD" and track.firstseen == now then
-- If local ping immediately fail, ditch this tracker. This can happen sometimes when we -- If local ping immediately fail, ditch this tracker. This can happen sometimes when we
-- find arp entries which aren't valid. -- find arp entries which aren't valid.
tracker[track.mac] = nil tracker[track.mac] = nil
@ -803,7 +803,7 @@ function lqm()
-- At this point we have gather all the data we need to determine which links are best to use and -- 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. -- which links should be blocked.
-- --
minroutes_count = 0 local leafs = 0
for _, track in pairs(tracker) for _, track in pairs(tracker)
do do
for _ = 1,1 for _ = 1,1
@ -817,7 +817,17 @@ function lqm()
pair = false, pair = false,
quality = false quality = false
} }
track.minroutes = false
-- 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
-- Always allow if user requested it -- Always allow if user requested it
for val in string.gmatch(config.user_allows, "([^,]+)") for val in string.gmatch(config.user_allows, "([^,]+)")
@ -849,7 +859,6 @@ function lqm()
-- Block any nodes which are too distant -- Block any nodes which are too distant
if track.distance and (track.distance < config.min_distance or track.distance > config.max_distance) then if track.distance and (track.distance < config.min_distance or track.distance > config.max_distance) then
track.blocks.distance = true track.blocks.distance = true
set_minroutes(track)
break break
end end
@ -870,14 +879,12 @@ function lqm()
if not oldblocks.signal then if not oldblocks.signal then
if track.snr < config.low or (track.rev_snr and track.rev_snr < config.low) then if track.snr < config.low or (track.rev_snr and track.rev_snr < config.low) then
track.blocks.signal = true track.blocks.signal = true
set_minroutes(track)
break break
end end
-- when blocked link becomes (low+margin) again, dont maintain block -- when blocked link becomes (low+margin) again, dont maintain block
else else
if track.snr < config.low + config.margin or (track.rev_snr and track.rev_snr < config.low + config.margin) then if track.snr < config.low + config.margin or (track.rev_snr and track.rev_snr < config.low + config.margin) then
track.blocks.signal = true track.blocks.signal = true
set_minroutes(track)
break break
else else
-- When signal is good enough to unblock a link but the quality is low, artificially bump -- When signal is good enough to unblock a link but the quality is low, artificially bump
@ -895,16 +902,18 @@ function lqm()
if not oldblocks.quality then if not oldblocks.quality then
if track.quality < config.min_quality then if track.quality < config.min_quality then
track.blocks.quality = true track.blocks.quality = true
set_minroutes(track)
end end
else else
if track.quality < config.min_quality + config.margin_quality then if track.quality < config.min_quality + config.margin_quality then
track.blocks.quality = true track.blocks.quality = true
set_minroutes(track)
end end
end end
end end
end
-- Count block leafs
if is_leaf(track) and (track.blocks.distance or track.blocks.signal or track.blocks.quality) then
leafs = leafs + 1
end end
end end
@ -971,8 +980,8 @@ function lqm()
track.pending = now + pending_timeout track.pending = now + pending_timeout
end end
-- Find the most distant, unblocked, RF node
if track.type == "RF" then if track.type == "RF" then
-- Find the most distant, unblocked, RF node
if track.distance then if track.distance then
if track.distance > distance and (not track.blocked or is_pending(track)) then if track.distance > distance and (not track.blocked or is_pending(track)) then
distance = track.distance distance = track.distance
@ -1062,7 +1071,7 @@ function lqm()
-- Save valid (unblocked) rf mac list for use by OLSR -- Save valid (unblocked) rf mac list for use by OLSR
if config.enable and phy ~= "none" then if config.enable and phy ~= "none" then
if minroutes_count > 0 or pending_count > 0 then if pending_count > 0 or leafs > 0 then
os.remove( "/tmp/lqm." .. phy .. ".macs") os.remove( "/tmp/lqm." .. phy .. ".macs")
else else
local tmpfile = "/tmp/lqm." .. phy .. ".macs.tmp" local tmpfile = "/tmp/lqm." .. phy .. ".macs.tmp"

View File

@ -761,8 +761,8 @@ do
else else
lqmstatus = "idle" lqmstatus = "idle"
end end
if track.minroutes then if (track.leaf == "minor" and track.rev_leaf == "major") or (track.leaf == "major" and track.rev_leaf == "minor") then
lqmstatus = "minroutes" lqmstatus = ac(lqmstatus, "leaf")
end end
if track.snr then if track.snr then
c4b = track.snr c4b = track.snr
@ -790,7 +790,7 @@ do
end end
if nodeiface or waniface or lqmstatus then if nodeiface or waniface or lqmstatus then
c1 = c1 .. "&nbsp;<small>(" .. ac(ac(nodeiface, waniface), lqmstatus) .. ")</small>" c1 = c1 .. "&nbsp;<small>(" .. ac(ac(nodeiface, waniface), x) .. ")</small>"
end end
-- print node services if any -- print node services if any