diff --git a/libs/lua-geolocate/Makefile b/libs/lua-geolocate/Makefile new file mode 100644 index 0000000..4bd58ab --- /dev/null +++ b/libs/lua-geolocate/Makefile @@ -0,0 +1,23 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=lua-geolocate +PKG_VERSION:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/lua-geolocate + SECTION:=libs + CATEGORY:=Libraries + TITLE:=WLAN-based geolocation librari based on openwifi.su + DEPENDS:=+libubus-lua +libiwinfo-lua +lua-jsonc +endef + +define Build/Compile +endef + +define Package/lua-geolocate/install + $(INSTALL_DIR) $(1)/usr/lib/lua + $(INSTALL_DATA) src/geolocate.lua $(1)/usr/lib/lua/ +endef + +$(eval $(call BuildPackage,lua-geolocate)) diff --git a/libs/lua-geolocate/src/geolocate.lua b/libs/lua-geolocate/src/geolocate.lua new file mode 100644 index 0000000..b6f2968 --- /dev/null +++ b/libs/lua-geolocate/src/geolocate.lua @@ -0,0 +1,87 @@ +local iwinfo = require 'iwinfo' +local json = require 'jsonc' +local ubus = require 'ubus' + + +local function canon_bssid(bssid) + return bssid:upper():gsub(':', '') +end + +-- Iterates over all active WLAN interfaces +-- Returning true from the callback function will skip all remaining +-- interfaces of the same radio +local function foreach_radio(f) + local uconn = assert(ubus.connect(), 'failed to connect to ubus') + local status = uconn:call('network.wireless', 'status', {}) + ubus.close(uconn) + + for name, radio in pairs(status) do + for _, iface in ipairs(radio.interfaces) do + if f(iface.ifname) then + break + end + end + end +end + +local function receive_json(request) + local f = assert(io.popen(string.format("exec wget -T 15 -q -O- '%s'", request)), 'failed to run wget') + local data = f:read('*a') + f:close() + + return json.parse(data) +end + +local function locate(blacklist) + local done_bssids = {} + for _, bssid in ipairs(blacklist or {}) do + done_bssids[canon_bssid(bssid)] = true + end + + local found_bssids = {} + + foreach_radio(function(ifname) + local iw = iwinfo[iwinfo.type(ifname)] + if not iw then + -- Skip other ifaces of this radio, as they + -- will have the same type + return true + end + + local scanlist = iw.scanlist(ifname) + if not scanlist then + return false + end + + for _, entry in ipairs(scanlist) do + local bssid = canon_bssid(entry.bssid) + if not done_bssids[bssid] then + table.insert(found_bssids, bssid) + done_bssids[bssid] = true + end + end + + return true + end) + + assert(#found_bssids >= 12, 'insufficient BSSIDs found') + + local data = receive_json('http://openwifi.su/api/v1/bssids/' .. table.concat(found_bssids, ',')) + assert(type(data) == 'table' and data.lon and data.lat, 'location not available') + + return data +end + + +local geolocate = {} + +function geolocate.locate(blacklist) + ok, result = pcall(locate, blacklist) + if ok then + return result + else + return nil, result + end +end + +return geolocate