#!/usr/bin/lua --[[ Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks Copyright (C) 2021 Tim Wilkinson Original Perl Copyright (c) 2015 Joe Ayers AE6XE Original Perl Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet See Contributors file for additional contributors 2015-04-01 AE6XE update to display neighbor nodes, replace vendor with mode 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 . Additional Terms: Additional use restrictions exist on the AREDN® trademark and logo. See AREDNLicense.txt for more info. Attributions to the AREDN® Project must be retained in the source code. If importing this code into a new or existing project attribution to the AREDN® project must be added to the source code. You must not misrepresent the origin of the material contained within. Modified versions must be modified to attribute to the original source and be marked in reasonable ways as differentiate it from the original version --]] require("nixio") require("aredn.utils") require("aredn.hardware") require("aredn.info") function usage() print("usage: wscan [-1abnor] [-i iface]") print(" -1 run once and quit") print(" -a average mode") print(" -b batch mode") print(" -n number of times to scan") print(" -o show only open access points") print(" -r raw mode") os.exit() end function die(msg) print(msg) os.exit(1) end function freq_to_chan(freq) local chan = freq if chan < 256 then elseif chan == 2484 then return 14 elseif chan == 2407 then return 0 elseif chan < 2484 then return (chan - 2407) / 5 elseif chan < 5000 then elseif chan < 5380 then return (chan - 5000) / 5 elseif chan < 5500 then return chan - 2000 elseif chan < 6000 then return (chan - 5000) / 5 end return "?" end -- load arp cache local arpcache = {} arptable(function(a) arpcache[a["HW address"]] = a["IP address"] end) local hostcache = {} function mac_to_host(mac) if not mac then return "N/A" end local host = hostcache[mac] if host then return host end local ip = arpcache[mac] if ip then 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 f:close() hostcache[mac] = m return m end end f:close() end else hostcache[mac] = "N/A" return "N/A" end end local avg = false -- average mode local batch = false -- batch mode local loops = 0 -- number of times to run 0=inf local raw = false -- raw mode local openap = false -- show open ap's local iface = aredn.hardware.get_iface_name("wifi") -- wifi interface local iters = 0 -- number of iterations local avgs = {} -- average statistics local i = 1 while i <= #arg do local a = arg[i] i = i + 1 if a == "-h" then usage() elseif a == "-1" then loops = 1 elseif a == "-a" then avg = true elseif a == "-b" then batch = true elseif a == "-o" then openap = true elseif a == "-r" then raw = true elseif a == "-i" then iface = arg[i] i = i + 1 elseif a == "-n" then loops = tonumber(arg[i]) i = i + 1 else die("bad arg " .. a) end end if not iface or iface == "" then die("bad interface") end if raw then os.execute("iw dev " .. iface .. " scan passive") os.execute("iw dev " .. iface .. " station dump") os.exit() end if loops == 0 then loops = math.huge end local myssid = aredn.info.getSSID() local myfreq = tonumber(aredn.info.getFreq(aredn.info.getMeshRadioDevice())) for _ = 1,loops do -- scan start local scanned = {} local f = io.popen("iw dev " .. iface .. " scan passive") if f then local scan for line in f:lines() do local m = line:match("^BSS ([%da-fA-F:]+)") if m then scan = { mac = m, mode = "AP", ssid = "", signal = 0, freq = 0, key = "" } scanned[#scanned + 1] = scan if line:match("joined") then scan.mode = "My Ad-Hoc Network" end end m = line:match("freq: (%d+)") if m then scan.freq = tonumber(m) 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 local f = io.popen("iw dev " .. iface .. " station dump") if f then local scan for line in f:lines() do local m = line:match("^Station ([%da-fA-F:]+) %(on " .. iface .. "%)") if m then scan = { mac = m, mode = "Connected Ad-Hoc Station", ssid = myssid, signal = 0, freq = myfreq, key = "" } scanned[#scanned + 1] = scan end m = line:match("signal avg:%s+([%d-]+)") if m and scan then scan.signal = tonumber(m) end end f:close() end -- scan end -- update running averages for _, scan in ipairs(scanned) do local v = avgs[scan.mac] if not v then v = { num = 1, total = scan.signal } avgs[scan.mac] = v else v.num = v.num + 1 v.total = v.total + scan.signal end v.mac = scan.mac v.mode = scan.mode v.ssid = scan.ssid v.signal = scan.signal v.freq = scan.freq v.key = scan.key end if #scanned == 0 and loops ~= 1 then nixio.nanosleep(1, 0) end iters = iters + 1 -- create output local output = {} if avg then for _, scan in pairs(avgs) do if scan.signal ~= 0 and (not openap or scan.key == "") then local chan = freq_to_chan(scan.freq) local ssid = scan.ssid == "" and "(hidden)" or scan.ssid local key = scan.key == "" and " " or "*" output[#output + 1] = string.format("%3d %3d %3d %s %-32s\t%s\t%s\t%s", math.floor((scan.total - scan.num + 1) / scan.num), math.floor(100 * scan.num / iters), chan, key, ssid, mac_to_host(scan.host), scan.mac:upper(), scan.mode) end end else for _, scan in ipairs(scanned) do if scan.signal ~= 0 and (not openap or scan.key == "") then local chan = freq_to_chan(scan.freq) local ssid = scan.ssid == "" and "(hidden)" or scan.ssid local key = scan.key == "" and " " or "*" output[#output + 1] = string.format("%3d %2d %s %-32s\t%s\t%s\t%s", scan.signal, chan, key, ssid, mac_to_host(scan.host), scan.mac:upper(), scan.mode) end end end table.sort(output, function(a,b) return a < b end) if not batch then if avg then os.execute("clear") print(string.format("Sig Rel Ch E SSID Hostname MAC/BSSID 802.11 Mode %6d", iters)) print("--- --- -- - -------------------------------- ----------------- ------------- -----------") else print(string.format("Sig Ch E SSID Hostname MAC/BSSID 802.11 Mode %6d", iters)) print("--- -- - -------------------------------- --------------------- ------------- ------------") end end for _, out in ipairs(output) do print(out) end end