From 86271040a0b56f3e404cb0fc74f86c43e2fc13ae Mon Sep 17 00:00:00 2001 From: Tim Wilkinson Date: Wed, 10 Apr 2024 18:30:29 -0400 Subject: [PATCH] Scan cache (#1160) * Cache the last wifi scan and update it when a re-scan is requested. This change was suggested as a way of handling Ubiquiti AC devices which disconnect while scanning and making retrieving the results problematic if that was your connection. Now we scan and store the results so they can be retrieved later. In fact we no longer scan when navigating to this page but require an explicity scan button push. This make the page generally more responsive when initially navigated to. --- files/www/cgi-bin/scan | 404 +++++++++++++++++++++++------------------ 1 file changed, 228 insertions(+), 176 deletions(-) diff --git a/files/www/cgi-bin/scan b/files/www/cgi-bin/scan index 547ae50d..197385e0 100755 --- a/files/www/cgi-bin/scan +++ b/files/www/cgi-bin/scan @@ -36,6 +36,7 @@ --]] require("nixio") +require("luci.jsonc") require("aredn.http") require("aredn.hardware") require("aredn.utils") @@ -55,125 +56,208 @@ if board_type:match("^ubnt,") and board_type:match("ac") then ubnt_ac = true end -local channels = aredn.hardware.get_rfchannels(wifiiface) -local scan_list = "" -for _, channel in ipairs(channels) -do - scan_list = scan_list .. " " .. channel.frequency -end +local rescan = string.find((nixio.getenv("QUERY_STRING") or ""):lower(),"rescan=1") --- scan start - -local scanned = {} - -local f = io.popen("iw dev " .. wifiiface .. " station dump") -if f then - local scan = {} - local myssid = aredn.info.getSSID() - for line in f:lines() - do - local m = line:match("^Station ([%da-fA-F:]+) %(on " .. wifiiface .. "%)") - if m then - scan = scanned[m] - if not scan then - scan = { - mac = m, - signal = 9999, - freq = {}, - key = "", - joined = false - } - scanned[m] = scan - end - scan.mode = "Connected Ad-Hoc Station" - scan.ssid = myssid - scan.freq[myfreq] = true - end - m = line:match("signal avg:%s+([%d%-]+)") - if m then - scan.signal = tonumber(m) - end +local scanlist = {} +-- Show the last scan if we have one, it's not too old, and we're not rescanning +if not rescan then + local lscan = io.open("/tmp/last-scan.json") + if lscan then + scanlist = luci.jsonc.parse(lscan:read("*a") or "") + lscan:close() end - f:close() end --- Ubiquiti AC device workaround -if ubnt_ac then - os.execute("iw dev " .. wifiiface .. " ibss leave > /dev/null 2>&1") - os.execute("wifi up > /dev/null 2>&1") - local attempt = 10 - while attempt > 0 +if rescan then + local channels = aredn.hardware.get_rfchannels(wifiiface) + local scan_list = "" + for _, channel in ipairs(channels) do - attempt = attempt - 1 - for line in io.popen("iw dev " .. wifiiface .. " scan"):lines() + scan_list = scan_list .. " " .. channel.frequency + end + + -- scan start + + local scanned = {} + + local f = io.popen("iw dev " .. wifiiface .. " station dump") + if f then + local scan = {} + local myssid = aredn.info.getSSID() + for line in f:lines() do - if line:match("^BSS ") then - attempt = 0 + local m = line:match("^Station ([%da-fA-F:]+) %(on " .. wifiiface .. "%)") + if m then + scan = scanned[m] + if not scan then + scan = { + mac = m, + signal = 9999, + freq = {}, + key = "", + joined = false + } + scanned[m] = scan + end + scan.mode = "Connected Ad-Hoc Station" + scan.ssid = myssid + scan.freq[tostring(myfreq)] = true + end + m = line:match("signal avg:%s+([%d%-]+)") + if m then + scan.signal = tonumber(m) end - break end - nixio.nanosleep(2, 0) + f:close() end -end -local f = io.popen("iw dev " .. wifiiface .. " scan freq" .. scan_list .. " passive") -if f then - local scan = {} - for line in f:lines() + -- Ubiquiti AC device workaround + if ubnt_ac then + os.execute("iw dev " .. wifiiface .. " ibss leave > /dev/null 2>&1") + os.execute("wifi up > /dev/null 2>&1") + local attempt = 10 + while attempt > 0 + do + attempt = attempt - 1 + for line in io.popen("iw dev " .. wifiiface .. " scan"):lines() + do + if line:match("^BSS ") then + attempt = 0 + end + break + end + nixio.nanosleep(2, 0) + end + end + + local f = io.popen("iw dev " .. wifiiface .. " scan freq" .. scan_list .. " passive") + if f then + local scan = {} + for line in f:lines() + do + local m = line:match("^BSS ([%da-fA-F:]+)") + if m then + scan = scanned[m] + if not scan then + scan = { + mac = m, + mode = "AP", + ssid = "", + signal = 9999, + freq = {}, + key = "", + joined = false + } + scanned[m] = scan + elseif scan.joined then + scan = { + freq = {} + } + end + if line:match("joined") then + scan.mode = "My Ad-Hoc Network" + scan.joined = true + end + end + m = line:match("freq: (%d+)") + if m then + scan.freq[m] = true + if tonumber(m) == myfreq and scan.mode == "AP" then + scan.mode = "My Ad-Hoc Network" + scan.joined = true + end + end + m = line:match("SSID: (.+)") + if m then + scan.ssid = m + end + m = line:match("signal: ([%d%-]+)") + if m then + scan.signal = tonumber(m) + end + m = line:match("Group cipher: (.+)") + if m then + scan.key = m + end + if line:match("capability: IBSS") and scan.mode == "AP" then + scan.mode = "Foreign Ad-Hoc Network" + end + end + f:close() + end + + -- scan end + + -- load arp cache + local arpcache = {} + arptable(function(a) + arpcache[a["HW address"]] = a["IP address"] + end) + + scanlist = {} + for _, v in pairs(scanned) do - local m = line:match("^BSS ([%da-fA-F:]+)") - if m then - scan = scanned[m] - if not scan then - scan = { - mac = m, - mode = "AP", - ssid = "", - signal = 9999, - freq = {}, - key = "", - joined = false - } - scanned[m] = scan - elseif scan.joined then - scan = { - freq = {} - } - end - if line:match("joined") then - scan.mode = "My Ad-Hoc Network" - scan.joined = true - end - end - m = line:match("freq: (%d+)") - if m then - scan.freq[m] = true - if tonumber(m) == myfreq and scan.mode == "AP" then - scan.mode = "My Ad-Hoc Network" - scan.joined = true - end - end - m = line:match("SSID: (.+)") - if m then - scan.ssid = m - end - m = line:match("signal: ([%d%-]+)") - if m then - scan.signal = tonumber(m) - end - m = line:match("Group cipher: (.+)") - if m then - scan.key = m - end - if line:match("capability: IBSS") and scan.mode == "AP" then - scan.mode = "Foreign Ad-Hoc Network" + if v.signal ~= 9999 or v.joined then + scanlist[#scanlist + 1] = v end end - f:close() + table.sort(scanlist, function(a, b) return a.signal > b.signal end) + for _, scan in ipairs(scanlist) + do + -- freq to chan + local chan = {} + for f, _ in pairs(scan.freq) + do + f = tonumber(f) + if f < 256 then + elseif f == 2484 then + chan[#chan + 1] = 14 + elseif f == 2407 then + chan[#chan + 1] = 0 + elseif f < 2484 then + chan[#chan + 1] = (f - 2407) / 5 + elseif f < 5000 then + elseif f < 5380 then + chan[#chan + 1] = (f - 5000) / 5 + elseif f < 5500 then + chan[#chan + 1] = f - 2000 + elseif f < 6000 then + chan[#chan + 1] = (f - 5000) / 5 + end + end + table.sort(chan) + scan.chan = table.concat(chan, " ") + if scan.joined then + scan.hostname = node + else + -- ip lookup then host lookup + local ip = arpcache[scan.mac] + if ip then + scan.hostname = ip + local f = io.popen("nslookup " .. ip) + if f then + for line in f:lines() + do + local m = line:match("name = (.*)%.local%.mesh") + if m then + scan.hostname = m:gsub("^mid[0-9]*%.",""):gsub("^dtdlink%.",""):gsub("%.local%.mesh$","") + break + end + end + f:close() + end + else + scan.hostname = "-" + end + end + end + lscan = io.open("/tmp/last-scan.json", "w") + if lscan then + lscan:write(luci.jsonc.stringify(scanlist, true)) + lscan:close() + end end --- scan end - -- generate page http_header() html.header(node .. " WiFi scan", false) @@ -181,21 +265,30 @@ local autoscan = string.find((nixio.getenv("QUERY_STRING") or ""):lower(),"autos if autoscan then html.print("") end +if rescan then + html.print([[ + +]]) +end html.print([[ -
+
]]) @@ -205,9 +298,11 @@ html.print("

" .. node .. " WiFi scan


") if autoscan then html.print("") else - html.print("") - html.print("   ") - html.print([[]]) + html.print([[]]) + if not ubnt_ac then + html.print("   ") + html.print([[]]) + end end html.print("   ") @@ -217,77 +312,34 @@ html.print("