LQM improvements. (#1193)

Matched with changes in aredn_packages
This commit is contained in:
Tim Wilkinson 2024-05-11 23:03:29 -07:00 committed by GitHub
parent 4b13d5969f
commit 8bb01e0501
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 333 additions and 321 deletions

View File

@ -37,11 +37,11 @@ require("aredn.info")
local socket = require("socket")
local refresh_timeout = 15 * 60 -- refresh high cost data every 15 minutes
local refresh_retry_timeout = 5 * 60
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
local quality_injection_max = 10 -- number of packets to inject into poor links to update quality
local tx_quality_run_avg = 0.8 -- tx quality running average
local ping_timeout = 1.0 -- timeout before ping gives a qualtiy penalty
local ping_time_run_avg = 0.8 -- ping time runnng average
@ -59,10 +59,12 @@ local ARPING = "/usr/sbin/arping"
local CURL = "/usr/bin/curl"
local now = 0
local config = {}
function get_config()
function update_config()
local c = uci.cursor() -- each time as /etc/config/aredn may have changed
return {
config = {
enable = c:get("aredn", "@lqm[0]", "enable") == "1",
margin = tonumber(c:get("aredn", "@lqm[0]", "margin_snr")),
low = tonumber(c:get("aredn", "@lqm[0]", "min_snr")),
rts_threshold = tonumber(c:get("aredn", "@lqm[0]", "rts_threshold") or "1"),
@ -95,9 +97,18 @@ function is_pending(track)
end
end
function is_user_blocked(track)
if not track.user_allow and track.blocks.user then
return true
end
return false
end
function should_block(track)
if track.user_allow then
return false
elseif not config.enable then
return track.blocks.user
elseif is_pending(track) then
return track.blocks.dtd or track.blocks.user
else
@ -109,31 +120,40 @@ function should_nonpair_block(track)
return track.blocks.dtd or track.blocks.signal or track.blocks.distance or track.blocks.user or track.blocks.quality or track.type ~= "RF"
end
function inject_quality_traffic(track)
return track.ip and track.type ~= "DtD" and track.blocked and track.blocks.quality and not (
track.blocks.dtd or track.blocks.signal or track.blocks.distance or track.blocks.user or track.blocks.dup
)
end
function should_ping(track)
if track.ip and is_connected(track) then
if track.type == "Tunnel" or track.type == "Wireguard" then
-- Can only ping if not blocked
if not track.blocked then
return true
end
else
-- Ping unless we fail a fixed boundary
if not (track.blocks.dtd or track.blocks.distance or track.blocks.user) then
return true
end
if not track.ip or is_user_blocked(track) then
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
end
end
return false
return true
end
function nft_handle(list, query)
for line in io.popen(NFT .. " -a list chain ip fw4 " .. list):lines()
function nft(cmd)
os.execute(NFT .. " " .. cmd)
end
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()
do
local handle = line:match(query .. ".*# handle (%d+)$")
if handle then
@ -148,12 +168,12 @@ function update_block(track)
track.blocked = true
if track.type == "Tunnel" or track.type == "Wireguard" then
if not nft_handle("input_lqm", "iifname \\\"" .. track.device .. "\\\" udp dport 698 drop") then
os.execute(NFT .. " insert rule ip fw4 input_lqm iifname \\\"" .. track.device .. "\\\" udp dport 698 drop 2> /dev/null")
nft_insert("input_lqm", "iifname \\\"" .. track.device .. "\\\" udp dport 698 drop 2> /dev/null")
return "blocked"
end
else
if not nft_handle("input_lqm", "udp dport 698 ether saddr " .. track.mac:lower() .. " drop") then
os.execute(NFT .. " insert rule ip fw4 input_lqm udp dport 698 ether saddr " .. track.mac .. " drop 2> /dev/null")
nft_insert("input_lqm", "udp dport 698 ether saddr " .. track.mac .. " drop 2> /dev/null")
return "blocked"
end
end
@ -162,13 +182,13 @@ function update_block(track)
if track.type == "Tunnel" or track.type == "Wireguard" then
local handle = nft_handle("input_lqm", "iifname \\\"" .. track.device .. "\\\" udp dport 698 drop")
if handle then
os.execute(NFT .. " delete rule ip fw4 input_lqm handle " .. handle)
nft_delete("input_lqm", handle)
return "unblocked"
end
else
local handle = nft_handle("input_lqm", "udp dport 698 ether saddr " .. track.mac:lower() .. " drop")
if handle then
os.execute(NFT .. " delete rule ip fw4 input_lqm handle " .. handle)
nft_delete("input_lqm", handle)
return "unblocked"
end
end
@ -180,11 +200,11 @@ function force_remove_block(track)
track.blocked = false
local handle = nft_handle("input_lqm", "udp dport 698 ether saddr " .. track.mac:lower() .. " drop")
if handle then
os.execute(NFT .. " delete rule ip fw4 input_lqm handle " .. handle)
nft_delete("input_lqm", handle)
end
handle = nft_handle("input_lqm", "iifname \\\"" .. track.device .. "\\\" udp dport 698 drop")
if handle then
os.execute(NFT .. " delete rule ip fw4 input_lqm handle " .. handle)
nft_delete("input_lqm", handle)
end
end
@ -196,12 +216,17 @@ function calc_distance(lat1, lon1, lat2, lon2)
return math.floor(r2 * math.asin(math.sqrt(v)))
end
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
-- Canonical hostname
function canonical_hostname(hostname)
if hostname then
hostname = hostname:lower():gsub("^dtdlink%.",""):gsub("^mid%d+%.",""):gsub("^xlink%d+%.",""):gsub("%.local%.mesh$", "")
end
return hostname
return hostname and hostname:lower():gsub("^dtdlink%.",""):gsub("^mid%d+%.",""):gsub("^xlink%d+%.",""):gsub("%.local%.mesh$", "")
end
local myhostname = canonical_hostname(aredn.info.get_nvram("node") or "localnode")
@ -228,31 +253,24 @@ function iw_set(cmd)
end
function lqm()
if uci.cursor():get("aredn", "@lqm[0]", "enable") ~= "1" then
exit_app()
return
end
-- Let things startup for a while before we begin
wait_for_ticks(math.max(0, 30 - nixio.sysinfo().uptime))
-- Create filters (cannot create during install as they disappear on reboot)
os.execute(NFT .. " flush chain ip fw4 input_lqm 2> /dev/null")
os.execute(NFT .. " delete chain ip fw4 input_lqm 2> /dev/null")
os.execute(NFT .. " add chain ip fw4 input_lqm 2> /dev/null")
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")
local handle = nft_handle("input", "jump input_lqm comment")
if handle then
os.execute(NFT .. " delete rule ip fw4 input handle " .. handle)
nft_delete("input", handle)
end
os.execute(NFT .. " insert rule ip fw4 input jump input_lqm comment \\\"block low quality links\\\"")
nft_insert("input", "jump input_lqm comment \\\"block low quality links\\\"")
-- We dont know any distances yet
iw_set("distance auto")
-- Or any hidden nodes
iw_set("rts off")
-- Set the default retries
--
iw_set("retry short " .. default_short_retries .. " long " .. default_long_retries)
-- If the channel bandwidth is less than 20, we need to adjust what we report as the values from 'iw' will not
@ -283,7 +301,7 @@ function lqm()
do
now = nixio.sysinfo().uptime
local config = get_config()
update_config()
local cursor = uci.cursor()
local cursorm = uci.cursor("/etc/config.mesh")
@ -365,7 +383,7 @@ function lqm()
end
end
-- Tunnels
-- Legacy tunnels
local tunnel = {}
for line in io.popen("ifconfig"):lines()
do
@ -376,12 +394,7 @@ function lqm()
device = tun,
signal = nil,
ip = nil,
mac = nil,
tx_packets = 0,
tx_fail = 0,
tx_retries = 0,
tx_bitrate = 0,
rx_bitrate = 0
mac = nil
}
stations[#stations + 1] = tunnel
elseif line:match("^%s*$") then
@ -413,14 +426,8 @@ function lqm()
stations[#stations + 1] = {
type = "Wireguard",
device = "wgc" .. wgc,
signal = nil,
ip = string.format("%d.%d.%d.%d", a, b, c, d),
mac = string.format("00:00:%02X:%02X:%02X:%02X", a, b, c, d),
tx_packets = 0,
tx_fail = 0,
tx_retries = 0,
tx_bitrate = 0,
rx_bitrate = 0
mac = string.format("00:00:%02X:%02X:%02X:%02X", a, b, c, d)
}
wgc = wgc + 1
end
@ -434,14 +441,8 @@ function lqm()
stations[#stations + 1] = {
type = "Wireguard",
device = "wgs" .. wgs,
signal = nil,
ip = string.format("%d.%d.%d.%d", a, b, c, d),
mac = string.format("00:00:%02X:%02X:%02X:%02X", a, b, c, d),
tx_packets = 0,
tx_fail = 0,
tx_retries = 0,
tx_bitrate = 0,
rx_bitrate = 0
mac = string.format("00:00:%02X:%02X:%02X:%02X", a, b, c, d)
}
wgs = wgs + 1
end
@ -455,45 +456,33 @@ function lqm()
stations[#stations + 1] = {
type = "DtD",
device = entry.Device,
signal = nil,
ip = entry["IP address"],
mac = entry["HW address"],
tx_packets = 0,
tx_fail = 0,
tx_retries = 0,
tx_bitrate = 0,
rx_bitrate = 0
mac = entry["HW address"]
}
end
end
-- Xlink
if nixio.fs.stat("/etc/config.mesh/xlink") then
uci.cursor("/etc/config.mesh"):foreach("xlink", "interface",
function(section)
if section.ifname then
for _, entry in ipairs(arps)
do
if entry["Device"] == section.ifname then
stations[#stations + 1] = {
type = "Xlink",
device = section.ifname,
signal = nil,
ip = entry["IP address"],
mac = entry["HW address"],
tx_packets = 0,
tx_fail = 0,
tx_retries = 0,
tx_bitrate = 0,
rx_bitrate = 0
}
end
cursorm:foreach("xlink", "interface",
function(section)
if section.ifname then
for _, entry in ipairs(arps)
do
if entry["Device"] == section.ifname then
stations[#stations + 1] = {
type = "Xlink",
device = section.ifname,
signal = nil,
ip = entry["IP address"],
mac = entry["HW address"]
}
end
end
end
)
end
end
)
-- Update the trackers based on the latest station information
for _, station in ipairs(stations)
do
if station.signal ~= 0 and not our_macs[station.mac] then
@ -503,11 +492,9 @@ function lqm()
device = station.device,
firstseen = now,
lastseen = now,
lastrouted = now,
pending = now + pending_timeout,
refresh = 0,
mac = station.mac,
station = nil,
ip = nil,
hostname = nil,
lat = nil,
@ -521,18 +508,21 @@ function lqm()
quality = false
},
blocked = false,
snr = 0,
snr = nil,
rev_snr = nil,
avg_snr = 0,
avg_snr = nil,
last_tx = nil,
last_tx_total = nil,
tx_quality = 100,
ping_quality = 100,
ping_success_time = 0,
tx_quality = nil,
ping_quality = nil,
ping_success_time = nil,
tx_bitrate = nil,
rx_bitrate = nil,
quality = 100,
exposed = false
quality = nil,
last_tx_fail = nil,
last_tx_retries = nil,
avg_tx = nil,
avg_tx_retries = nil,
avg_tx_fail = nil
}
end
local track = tracker[station.mac]
@ -543,150 +533,136 @@ function lqm()
track.hostname = nil
end
if not track.hostname and track.ip then
local hostname = nixio.getnameinfo(track.ip)
if hostname then
track.hostname = canonical_hostname(hostname)
end
track.hostname = canonical_hostname(nixio.getnameinfo(track.ip))
end
-- Running average SNR
if station.type == "RF" then
local snr = station.signal - station.noise
if track.snr == 0 then
track.snr = snr
else
track.snr = math.ceil(snr_run_avg * track.snr + (1 - snr_run_avg) * snr)
end
if station.signal and station.noise then
track.snr = math.ceil(av(track.snr, snr_run_avg, station.signal - station.noise, 0))
end
-- Running average estimate of link quality
local tx = station.tx_packets
local tx_total = station.tx_packets + station.tx_fail + station.tx_retries
if not track.last_tx then
track.last_tx = tx
track.last_tx_total = tx_total
track.tx_quality = 100
elseif tx_total >= track.last_tx_total + quality_min_packets then
local tx_quality = 100 * (tx - track.last_tx) / (tx_total - track.last_tx_total)
track.last_tx = tx
track.last_tx_total = tx_total
track.last_quality = tx_quality
track.tx_quality = math.min(100, math.max(0, math.ceil(tx_quality_run_avg * track.tx_quality + (1 - tx_quality_run_avg) * tx_quality)))
end
if not track.tx_bitrate then
track.tx_bitrate = station.tx_bitrate
else
track.tx_bitrate = track.tx_bitrate * bitrate_run_avg + station.tx_bitrate * (1 - bitrate_run_avg)
end
if not track.rx_bitrate then
track.rx_bitrate = station.rx_bitrate
else
track.rx_bitrate = track.rx_bitrate * bitrate_run_avg + station.rx_bitrate * (1 - bitrate_run_avg)
local tx_retries = station.tx_retries
local tx_fail = station.tx_fail
local tx_total = (tx or 0) + (tx_fail or 0) + (tx_retries or 0)
local tx_last_total = (track.tx or 0) + (track.tx_fail or 0) + (track.tx_retries or 0)
if tx_total >= tx_last_total + 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)
local tx_quality = 100 * ((tx or 0) - (track.tx or 0) / (tx_total - tx_last_total))
track.tx_quality = math.min(100, math.max(0, math.ceil(tx_quality_run_avg * (track.tx_quality or 100) + (1 - tx_quality_run_avg) * tx_quality)))
end
track.tx = tx
track.tx_retries = tx_retries
track.tx_fail = tx_fail
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)
track.lastseen = now
end
end
-- Count the RF links we have
local rfcount = 0
for _, track in pairs(tracker)
do
if track.type == "RF" then
rfcount = rfcount + 1
end
end
-- Update link tracking state
for _, track in pairs(tracker)
do
-- Only refresh remote attributes periodically
if track.ip and (now > track.refresh or is_pending(track)) then
track.refresh = now + refresh_timeout
-- Update if link is routable
local rt = track.ip and ip.route(track.ip) or nil
if rt and tostring(rt.gw) == track.ip then
track.routable = true
else
track.routable = false
end
local old_rev_snr = track.rev_snr
track.rev_snr = null
dtdlinks[track.mac] = {}
track.exposed = false
-- Refresh remote attributes periodically as this is expensive
if track.ip and now > track.refresh then
-- Refresh the hostname periodically as it can change
local hostname = nixio.getnameinfo(track.ip)
if hostname then
track.hostname = canonical_hostname(hostname)
end
track.hostname = canonical_hostname(nixio.getnameinfo(track.ip)) or track.hostname
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()
wait_for_ticks(0)
if info then
rflinks[track.mac] = nil
if tonumber(info.lat) and tonumber(info.lon) then
track.lat = tonumber(info.lat)
track.lon = tonumber(info.lon)
if lat and lon then
if track.blocked or not track.routable then
-- Remote is blocked not directly routable
-- 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.rev_snr = nil
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 info = luci.jsonc.parse(raw:read("*a"))
raw:close()
wait_for_ticks(0)
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
else
track.refresh = now + refresh_timeout
dtdlinks[track.mac] = {}
-- 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)
end
end
if track.type == "RF" then
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 not rtrack.type or rtrack.type == "RF" then
local rhostname = canonical_hostname(rtrack.hostname)
if rtrack.ip 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
if rtrack.routable then
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
end
if myhostname == rhostname then
if not old_rev_snr or not rtrack.snr then
track.rev_snr = rtrack.snr
else
track.rev_snr = math.ceil(snr_run_avg * old_rev_snr + (1 - snr_run_avg) * rtrack.snr)
if myhostname == rhostname then
track.rev_snr = (track.rev_snr and rtrack.snr) and math.ceil(snr_run_avg * track.rev_snr + (1 - snr_run_avg) * rtrack.snr) or rtrack.snr
end
end
if rtrack.routable and not tracker[rtrack.mac] and not our_macs[rtrack.mac] and rfcount > 1 then
track.exposed = true
end
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
end
end
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
end
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
if link.linkType == "RF" then
rflinks[track.mac][ip] = {
ip = ip,
hostname = canonical_hostname(link.hostname)
}
end
if link.hostname then
local hostname = canonical_hostname(link.hostname)
if link.linkType == "DTD" then
dtdlinks[track.mac][hostname] = true
elseif link.linkType == "RF" and link.signal and link.noise and myhostname == hostname then
local snr = link.signal - link.noise
if not old_rev_snr then
track.rev_snr = snr
else
track.rev_snr = math.ceil(snr_run_avg * old_rev_snr + (1 - snr_run_avg) * snr)
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 math.ceil(snr_run_avg * track.rev_snr + (1 - snr_run_avg) * snr) or snr
end
end
end
@ -696,28 +672,19 @@ function lqm()
end
end
if is_connected(track) then
-- Update avg snr using both ends (if we have them)
track.avg_snr = (track.snr + (track.rev_snr or track.snr)) / 2
-- Routable
local rt = track.ip and ip.route(track.ip) or nil
if rt and tostring(rt.gw) == track.ip then
track.routable = true
track.lastrouted = now
-- Update avg snr using both ends (if we have them)
if track.snr then
if track.rev_snr then
track.avg_snr = math.ceil((track.snr + track.rev_snr) / 2)
else
track.routable = false
track.avg_snr = track.snr
end
else
-- Clear snr when we've not seen the node this time (disconnected)
track.snr = 0
track.rev_snr = nil
track.routable = false
track.avg_snr = null
end
-- Ping addresses and penalize quality for excessively slow links
if config.ping_penalty <= 0 then
track.ping_quality = 100
elseif should_ping(track) then
if should_ping(track) then
local success = 100
local ptime
@ -740,7 +707,7 @@ function lqm()
sigsock:close()
else
-- For devices which support ARP, send an ARP request and wait for a reply. This avoids the other ends routing
-- table messing up the response packet.
-- table and firewall messing up the response packet.
local pstart = socket.gettime(0)
if os.execute(ARPING .. " -q -c 1 -D -w " .. math.ceil(ping_timeout) .. " -I " .. track.device .. " " .. track.ip) == 0 then
-- Failure
@ -751,111 +718,138 @@ function lqm()
wait_for_ticks(0)
local ping_loss_run_avg = 1 - config.ping_penalty / 100
local ping_loss_run_avg = config.ping_penalty / 100
track.ping_quality = (track.ping_quality or 100) - ping_loss_run_avg
if success > 0 then
track.ping_success_time = track.ping_success_time * ping_time_run_avg + ptime * (1 - ping_time_run_avg)
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_quality = track.ping_quality + ping_loss_run_avg
end
track.ping_quality = math.ceil(ping_loss_run_avg * track.ping_quality + (1 - ping_loss_run_avg) * success)
track.ping_quality = math.ceil(track.ping_quality)
if success == 0 and track.type == "DtD" and track.firstseen == now then
-- 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
else
track.ping_quality = nil
track.ping_success_time = nil
end
-- Calculate overall link quality
track.quality = math.ceil((track.tx_quality + track.ping_quality) / 2)
-- Inject traffic into links with poor quality
-- We do this so we can keep measuring the current link quality otherwise, once it becomes
-- bad, it wont be used and we can never tell if it becomes good again. Beware injecting too
-- much traffic because, on very poor links, this can generate multiple retries per packet, flooding
-- the wifi channel
if inject_quality_traffic(track) then
-- Create socket we use to inject traffic into degraded links
-- This is setup so it ignores routing and will always send to the correct wifi station
local sigsock = nixio.socket("inet", "dgram")
sigsock:setopt("socket", "bindtodevice", track.device)
sigsock:setopt("socket", "dontroute", 1)
for _ = 1,quality_injection_max
do
sigsock:sendto("", track.ip, 8080)
if track.tx_quality then
if track.ping_quality then
track.quality = math.ceil((track.tx_quality + track.ping_quality) / 2)
else
track.quality = track.tx_quality
end
sigsock:close()
elseif track.ping_quality then
track.quality = track.ping_quality
else
track.quality = nil
end
end
-- Work out what to block, unblock and limit
--
-- 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.
--
for _, track in pairs(tracker)
do
-- SNR and distance blocks only related to RF links
if track.type == "RF" then
-- If we have a direct dtd connection to this device, make sure we use that
track.blocks.dtd = false
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
for _ = 1,1
do
-- Clear state
local oldblocks = track.blocks;
track.blocks = {
dtd = false,
signal = false,
distance = false,
pair = false,
quality = false
}
-- Always allow if user requested it
for val in string.gmatch(config.user_allows, "([^,]+)")
do
if val:gsub("%s+", ""):gsub("-", ":"):upper() == track.mac then
track.user_allow = true
break
end
end
if track.user_allow then
break
end
-- When unblocked link signal becomes too low, block
if not track.blocks.signal then
if track.snr < config.low or (track.rev_snr and track.rev_snr < config.low) then
track.blocks.signal = true
end
-- when blocked link becomes (low+margin) again, unblock
else
if track.snr >= config.low + config.margin and (not track.rev_snr or track.rev_snr >= config.low + config.margin) then
track.blocks.signal = false
-- 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 track.blocks.quality then
track.quality = config.min_quality + config.margin_quality
-- Block if user requested it
for val in string.gmatch(config.user_blocks, "([^,]+)")
do
if val:gsub("%s+", ""):gsub("-", ":"):upper() == track.mac then
track.blocks.user = true
break
end
end
if track.blocks.user then
break
end
-- SNR and distance blocks only related to RF links
if track.type == "RF" then
-- 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
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
-- when blocked link becomes (low+margin) again, unblock
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
end
end
end
end
-- Block any nodes which are too distant
if not track.distance or (track.distance >= config.min_distance and track.distance <= config.max_distance) then
track.blocks.distance = false
else
track.blocks.distance = true
-- 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
end
else
if track.quality < config.min_quality + config.margin_quality then
track.blocks.quality = true
end
end
end
end
-- Block if user requested it
track.blocks.user = false
for val in string.gmatch(config.user_blocks, "([^,]+)")
do
if val:gsub("%s+", ""):gsub("-", ":"):upper() == track.mac then
track.blocks.user = true
break
end
end
-- Block if quality is poor
if track.quality then
if not track.blocks.quality and track.quality < config.min_quality and (track.type ~= "DtD" or (track.distance and track.distance >= dtd_distance)) then
track.blocks.quality = true
elseif track.blocks.quality and track.quality >= config.min_quality + config.margin_quality then
track.blocks.quality = false
end
end
-- Always allow if user requested it
track.user_allow = false;
for val in string.gmatch(config.user_allows, "([^,]+)")
do
if val:gsub("%s+", ""):gsub("-", ":"):upper() == track.mac then
track.user_allow = true
break
end
end
end
-- Eliminate link pairs, where we might have links to multiple radios at the same site
@ -906,8 +900,13 @@ function lqm()
end
end
local distance = -1
--
-- 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
-- Update the block state and calculate the routable distance
for _, track in pairs(tracker)
do
@ -946,7 +945,7 @@ function lqm()
end
-- Update the wifi distance
local coverage = math.min(255, math.floor((distance * 2 * 0.0033) / 3))
if coverage ~= last_coverage then
if config.enable and coverage ~= last_coverage then
iw_set("coverage " .. coverage)
last_coverage = coverage
end
@ -980,8 +979,7 @@ function lqm()
do
hidden[#hidden + 1] = ninfo
end
-- Don't adjust RTS on ath10k for the moment - appear to be some bug to be worked out here
if (#hidden == 0) ~= (#hidden_nodes == 0) and config.rts_threshold >= 0 and config.rts_threshold <= 2347 then
if config.enable and (#hidden == 0) ~= (#hidden_nodes == 0) and config.rts_threshold >= 0 and config.rts_threshold <= 2347 then
if #hidden > 0 then
iw_set("rts " .. config.rts_threshold)
else
@ -1003,6 +1001,20 @@ function lqm()
f:close()
end
-- Save valid (unblocked) rf mac list for use by OLSR
if config.enable and phy ~= "none" then
f = io.open("/tmp/lqm." .. phy .. ".macs", "w")
if f then
for _, track in pairs(tracker)
do
if track.device == wlan and not track.blocked then
f:write(track.mac .. "\n")
end
end
f:close()
end
end
wait_for_ticks(60) -- 1 minute
end
end

View File

@ -50,7 +50,7 @@ if cursor:get("aredn", "@lqm[0]", "enable") ~= "1" then
end
local node = info.get_nvram("node")
local node = aredn.info.get_nvram("node")
local node_desc = cursor:get("system", "@system[0]", "description") or ""
local lat_lon = "<strong>Location Not Available</strong>"
local lat = cursor:get("aredn", "@location[0]", "lat")
@ -162,7 +162,7 @@ html.print([[<hr>
return track.exposed ? "idle - exposed" : "idle";
}
const get_snr = track => {
return track.snr <= 0 ? "-" : track.snr + ("rev_snr" in track ? "/" + track.rev_snr : "");
return !track.snr ? "-" : track.snr + ("rev_snr" in track ? "/" + track.rev_snr : "");
}
const name = track => {
if (track.hostname) {

View File

@ -761,7 +761,7 @@ do
else
lqmstatus = "idle"
end
if track.snr > 0 then
if track.snr then
c4b = track.snr
if track.rev_snr then
c4b = c4b .. "/" .. track.rev_snr