doodle3d-firmware/src/network/netconfig.lua

449 lines
16 KiB
Lua

--
-- This file is part of the Doodle3D project (http://doodle3d.com).
--
-- @copyright 2013, Doodle3D
-- @license This software is licensed under the terms of the GNU GPL v2 or later.
-- See file LICENSE.txt or visit http://www.gnu.org/licenses/gpl.html for full license details.
local utils = require('util.utils')
local log = require('util.logger')
local settings = require('util.settings')
local wifi = require('network.wlanconfig')
local uci = require('uci').cursor()
local signin = require('network.signin')
local M = {}
local reconf = {}
local wifi
local reloadSilent
local MOD_ABBR = "NTCF"
M.WWW_CAPTIVE_PATH = '/usr/share/lua/wifibox/www'
M.WWW_CAPTIVE_INDICATOR = '/www/.wifibox-inplace'
M.WWW_RENAME_NAME = '/www-regular'
M.CONNECTING_FAILED = -1
M.NOT_CONNECTED = 0
M.CONNECTING = 1
M.CONNECTED = 2
M.CREATING = 3
M.CREATED = 4
local function reloadBit(dlist, itemname)
if dlist[itemname] == nil then dlist[itemname] = '' end
if dlist[itemname] == '' then dlist[itemname] = 'r'
elseif dlist[itemname] == 'c' then dlist[itemname] = 'b'
end
end
local function commitBit(dlist, itemname)
if dlist[itemname] == nil then dlist[itemname] = '' end
if dlist[itemname] == '' then dlist[itemname] = 'c'
elseif dlist[itemname] == 'r' then dlist[itemname] = 'b'
end
end
local function bothBits(dlist, itemname) dlist[itemname] = 'b' end
function M.init(wifiInstance, reloadSilent)
wifi = wifiInstance
silent = reloadSilent or false
return true
end
--- Switch configuration between AP and station modes
-- @param table components a table with components as keys with operations as values (add or remove)
-- Valid components (each with add and rm operation) are: apnet, staticaddr, dhcppool, wwwredir, dnsredir, wwwcaptive, natreflect.
-- and additionally: wifiiface/add, network/reload
function M.switchConfiguration(components)
local dirtyList = {} -- laundry list, add config/script name as key with value c (commit), r (reload) or b (both)
for k,v in pairs(components) do
local fname = k .. '_' .. v
if type(reconf[fname]) == 'function' then
log:verbose(MOD_ABBR, "reconfiguring component '" .. k .. "' (" .. v .. ")")
reconf[fname](dirtyList)
else
log:warning(MOD_ABBR, "unknown component or action '" .. fname .. "' skipped")
end
end
-- first run all commits, then perform reloads
for k,v in pairs(dirtyList) do
if v == 'c' or v == 'b' then M.commitComponent(k) end
end
for k,v in pairs(dirtyList) do
if v == 'r' or v == 'b' then M.reloadComponent(k, silent) end
end
end
function M.commitComponent(c)
log:verbose(MOD_ABBR, "committing component '" .. c .. "'")
uci:commit(c)
end
function M.reloadComponent(c, silent)
log:verbose(MOD_ABBR, "reloading component '" .. c .. "'")
local command = 'reload'
local cmd = '/etc/init.d/' .. c .. ' '..command
if silent ~= nil and silent then
cmd = cmd .. ' &> /dev/null'
os.execute(cmd)
else
rv = utils.captureCommandOutput(cmd)
log:verbose(MOD_ABBR, " result reloading component '" .. c .. "' (cmd: '"..cmd.."'): \n"..utils.dump(rv))
end
end
function M.uciTableSet(config, section, options)
for k, v in pairs(options) do uci:set(config, section, k, v) end
end
--[[ Issue '/etc/init.d/network reload' command ]]
function reconf.network_reload(dirtyList) reloadBit(dirtyList, 'network') end
--[[ Issue '/etc/init.d/wireless reload' command ]]
function reconf.wireless_reload(dirtyList) reloadBit(dirtyList, 'wireless') end
--[[ Issue '/etc/init.d/dnsmasq reload' command ]]
function reconf.dhcp_reload(dirtyList) reloadBit(dirtyList, 'dnsmasq') end
--[[ Add wlan interface declaration to /etc/config/network ]]
function reconf.wifiiface_add(dirtyList)
uci:set('network', wifi.NET, 'interface')
commitBit(dirtyList, 'network')
end
--[[ Add/remove access point network ]]
function reconf.apnet_add_noreload(dirtyList) reconf.apnet_add(dirtyList, true) end
function reconf.apnet_add(dirtyList, noReload)
local ourSsid = wifi.getSubstitutedSsid(settings.get('network.ap.ssid'))
local networkKey = settings.get('network.ap.key')
local sname = nil
uci:foreach('wireless', 'wifi-iface', function(s)
if s.ssid == ourSsid then sname = s['.name']; return false end
end)
if sname == nil then sname = uci:add('wireless', 'wifi-iface') end
local encType = networkKey == '' and 'none' or 'psk2'
M.uciTableSet('wireless', sname, {
network = wifi.NET,
ssid = ourSsid,
encryption = encType,
key = networkKey,
device = 'radio0',
mode = 'ap',
})
commitBit(dirtyList, 'wireless')
if noReload == nil or noReload == false then reloadBit(dirtyList, 'network') end
end
function reconf.apnet_rm(dirtyList)
local sname = nil
uci:foreach('wireless', 'wifi-iface', function(s)
if s.ssid == wifi.getSubstitutedSsid(settings.get('network.ap.ssid')) then sname = s['.name']; return false end
end)
if sname == nil then return log:info(MOD_ABBR, "AP network configuration does not exist, nothing to remove") end
uci:delete('wireless', sname)
reloadBit(dirtyList, 'network'); commitBit(dirtyList, 'wireless')
end
--[[ Switch between wireless static IP and DHCP ]]
function reconf.staticaddr_add(dirtyList)
uci:set('network', wifi.NET, 'interface')
--TODO: remove ifname on wlan interface?
--NOTE: 'type = "bridge"' should -not- be added as this prevents defining a separate dhcp pool (http://wiki.openwrt.org/doc/recipes/routedap)
M.uciTableSet('network', wifi.NET, {
proto = 'static',
ipaddr = settings.get('network.ap.address'),
netmask = settings.get('network.ap.netmask')
})
bothBits(dirtyList, 'network')
end
--TODO: replace repeated deletes by M.uciTableDelete
function reconf.staticaddr_rm(dirtyList)
uci:set('network', wifi.NET, 'interface')
uci:set('network', wifi.NET, 'proto', 'dhcp')
uci:delete('network', wifi.NET, 'ipaddr')
uci:delete('network', wifi.NET, 'netmask')
--uci:delete('network', wifi.NET, 'type') --do not remove since it is not added anymore
bothBits(dirtyList, 'network')
end
--[[ Add/remove DHCP pool for wireless net ]]
function reconf.dhcppool_add_noreload(dirtyList) reconf.dhcppool_add(dirtyList, true) end
function reconf.dhcppool_add(dirtyList, noReload)
uci:set('dhcp', wifi.NET, 'dhcp') --create section
M.uciTableSet('dhcp', wifi.NET, {
interface = wifi.NET,
start = '100',
limit = '150',
leasetime = '12h',
})
commitBit(dirtyList, 'dhcp');
if noReload == nil or noReload == false then reloadBit(dirtyList, 'dnsmasq') end
end
function reconf.dhcppool_rm(dirtyList)
uci:delete('dhcp', wifi.NET)
commitBit(dirtyList, 'dhcp'); reloadBit(dirtyList, 'dnsmasq')
end
--[[ Add/remove webserver 404 redirection and denial of dirlisting ]]
function reconf.wwwredir_add(dirtyList)
uci:set('uhttpd', 'main', 'error_page', '/redirect.html')
uci:set('uhttpd', 'main', 'no_dirlist', '1')
bothBits(dirtyList, 'uhttpd')
end
function reconf.wwwredir_rm(dirtyList)
uci:delete('uhttpd', 'main', 'error_page')
uci:delete('uhttpd', 'main', 'no_dirlist')
bothBits(dirtyList, 'uhttpd')
end
--[[ Add/remove redirecton of all DNS requests to self ]]
function reconf.dnsredir_add(dirtyList)
local redirText = '/#/' .. settings.get('network.ap.address')
local sname = utils.getUciSectionName('dhcp', 'dnsmasq')
if sname == nil then return log:error(MOD_ABBR, "dhcp config does not contain a dnsmasq section") end
if uci:get('dhcp', sname, 'address') ~= nil then return log:info(MOD_ABBR, "DNS address redirection already in place, not re-adding", false) end
uci:set('dhcp', sname, 'address', {redirText})
commitBit(dirtyList, 'dhcp'); reloadBit(dirtyList, 'dnsmasq')
end
function reconf.dnsredir_rm(dirtyList)
local sname = utils.getUciSectionName('dhcp', 'dnsmasq')
if sname == nil then return log:error(MOD_ABBR, "dhcp config does not contain a dnsmasq section") end
uci:delete('dhcp', sname, 'address')
commitBit(dirtyList, 'dhcp'); reloadBit(dirtyList, 'dnsmasq')
end
--TODO: handle os.rename() return values (nil+msg on error)
function reconf.wwwcaptive_add(dirtyList)
if utils.exists(M.WWW_CAPTIVE_INDICATOR) then
return log:info(MOD_ABBR, "WWW captive directory already in place, not redoing", false)
end
local rv,reason = os.rename('/www', M.WWW_RENAME_NAME)
if rv == true then
utils.symlink(M.WWW_CAPTIVE_PATH, '/www')
return true
else
return log:error(MOD_ABBR, "Could not rename /www to " .. M.WWW_RENAME_NAME .. "(" .. reason .. ")")
end
end
function reconf.wwwcaptive_rm(dirtyList)
if not utils.exists(M.WWW_CAPTIVE_INDICATOR) then return log:info(MOD_ABBR, "WWW captive directory not in place, not undoing", false) end
os.remove('/www')
if os.rename(M.WWW_RENAME_NAME, '/www') ~= true then
return log:error(MOD_ABBR, "Could not rename " .. M.WWW_RENAME_NAME .. " to /www")
end
return true
end
--[[ Setup/remove NAT reflection to redirect all IPs ]]
function reconf.natreflect_add(dirtyList)
uci:set('firewall', 'portalreflect', 'redirect');
M.uciTableSet('firewall', 'portalreflect', {
src = 'lan',
proto = 'tcp',
src_dport = '80',
dest_port = '80',
dest_ip = settings.get('network.ap.address'),
target = 'DNAT'
})
bothBits(dirtyList, 'firewall')
end
function reconf.natreflect_rm(dirtyList)
uci:delete('firewall', 'portalreflect')
bothBits(dirtyList, 'firewall')
end
--- Sets up access point mode.
-- Note: this function might belong in the wlanconfig module but that would introduce
-- a circular dependency, easiest solution is to place the function here.
-- @tparam string ssid The SSID to use for the access point.
-- @return True on success or nil+msg on error.
function M.setupAccessPoint(ssid)
M.setStatus(M.CREATING,"Creating access point '"..ssid.."'...");
-- add access point configuration
M.switchConfiguration({apnet="add_noreload"})
wifi.activateConfig(ssid)
-- NOTE: dnsmasq must be reloaded after network or it will be unable to serve IP addresses
M.switchConfiguration({ wifiiface="add", network="reload", staticaddr="add", dhcppool="add_noreload", wwwredir="add", dnsredir="add" })
M.switchConfiguration({dhcp="reload"})
M.setStatus(M.CREATED,"Access point created");
local ds = wifi.getDeviceState()
--log:info(MOD_ABBR, " network/status: ")
log:info(MOD_ABBR, " ssid: ".. utils.dump(ds.ssid))
--[[
log:info(MOD_ABBR, " bssid: ".. utils.dump(ds.bssid))
log:info(MOD_ABBR, " channel: ".. utils.dump(ds.channel))
log:info(MOD_ABBR, " mode: ".. utils.dump(ds.mode))
log:info(MOD_ABBR, " encryption: ".. utils.dump(ds.encryption))
log:info(MOD_ABBR, " quality: ".. utils.dump(ds.quality))
log:info(MOD_ABBR, " quality_max: ".. utils.dump(ds.quality_max))
log:info(MOD_ABBR, " txpower: ".. utils.dump(ds.txpower))
log:info(MOD_ABBR, " signal: ".. utils.dump(ds.signal))
log:info(MOD_ABBR, " noise: ".. utils.dump(ds.noise))
log:info(MOD_ABBR, " raw: ".. utils.dump(ds))
local localip = wifi.getLocalIP()
log:info(MOD_ABBR, " localip: "..utils.dump(localip))
--]]
return true
end
--- set the network configuration to accesspoint, but don't reload (used before updating)
-- Note: this function might belong in the wlanconfig module but that would introduce
-- a circular dependency, easiest solution is to place the function here.
-- @tparam string ssid The SSID to use for the access point.
-- @return True on success or nil+msg on error.
function M.enableAccessPoint(ssid)
log:info(MOD_ABBR, "enableAccessPoint ssid: ".. utils.dump(ssid))
M.switchConfiguration{apnet="add_noreload"}
wifi.activateConfig(ssid)
local ds = wifi.getDeviceState()
log:info(MOD_ABBR, " deviceState.ssid: ".. utils.dump(ds.ssid))
return true
end
--- Associates wlan device as client with the given SSID.
-- Note: this function might belong in the wlanconfig module but that would introduce
-- a circular dependency, easiest solution is to place the function here.
-- @tparam string ssid The SSID to associate with.
-- @tparam string passphrase The passphrase (if any) to use, may be left out if a UCI configuration exists.
-- @tparam boolean recreate If true, a new UCI configuration based on scan data will always be created, otherwise an attempt will be made to use an existing configuration.
-- @return True on success or nil+msg on error.
function M.associateSsid(ssid, passphrase, recreate)
log:info(MOD_ABBR, "netconfig:associateSsid: "..(ssid or "<nil>")..", "..(recreate or "<nil>"))
M.setStatus(M.CONNECTING,"Connecting...");
-- see if previously configured network for given ssid exists
local cfg = nil
for _, net in ipairs(wifi.getConfigs()) do
if net.mode ~= "ap" and net.ssid == ssid then
cfg = net
break
end
end
-- if not, or if newly created configuration is requested, create a new configuration
if cfg == nil or recreate ~= nil then
local scanResult, msg = wifi.getScanInfo(ssid)
if scanResult ~= nil then
wifi.createConfigFromScanInfo(scanResult, passphrase)
elseif scanResult == false then
--check for error
M.setStatus(M.CONNECTING_FAILED,msg);
return nil,msg
else
--check for error
local msg = "Wireless network "..ssid.." is not available"
M.setStatus(M.CONNECTING_FAILED,msg);
return nil,msg
end
end
-- try to associate with the network
wifi.activateConfig(ssid)
--M.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
--M.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wireless="reload" }
--M.switchConfiguration{ wifiiface="add", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wireless="reload" }
M.switchConfiguration({ wifiiface="add", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm" })
-- we check if we get a ssid and ip in max 5 seconds
-- if not there is probably a issue
local attemptInterval = 1
local maxAttempts = 5
local attempt = 0
local nextAttemptTime = os.time()
while true do
if os.time() > nextAttemptTime then
log:verbose(MOD_ABBR, "associated check "..utils.dump(attempt).."/"..utils.dump(maxAttempts))
if wifi.getLocalIP() ~= nil and wifi.getDeviceState().ssid == ssid then
break
else
attempt = attempt+1
if attempt >= maxAttempts then
-- still no correct ssid; fail
local msg = "Could not associate with network (incorrect password?)"
M.setStatus(M.CONNECTING_FAILED,msg);
return false, msg
else
nextAttemptTime = os.time() + attemptInterval
end
end
end
end
log:info(MOD_ABBR, "Network associated")
-- signin to connect.doodle3d.com
local success, output = signin.signin()
if success then
log:info(MOD_ABBR, "Signed in")
else
log:info(MOD_ABBR, "Signing in failed")
end
-- report we are connected after signin attempt
M.setStatus(M.CONNECTED,"Connected");
return true
end
--- Disassociate wlan device as client from all SSID's.
-- Note: this function might belong in the wlanconfig module but that would introduce
-- a circular dependency, easiest solution is to place the function here.
-- @return True on success or nil+msg on error.
function M.disassociate()
M.setStatus(M.NOT_CONNECTED,"Not connected");
wifi.activateConfig()
return wifi.restart()
end
function M.getStatus()
log:verbose(MOD_ABBR, "network:getStatus")
local file, error = io.open('/tmp/networkstatus.txt','r')
if file == nil then
--log:error(MOD_ABBR, "Util:Access:Can't read controller file. Error: "..error)
return "",""
else
local status = file:read('*a')
file:close()
local code, msg = string.match(status, '([^|]+)|+(.*)')
log:verbose(MOD_ABBR, " raw status: "..utils.dump(status).." (code: "..utils.dump(code)..", msg: "..utils.dump(msg)..")")
return code,msg
end
end
function M.setStatus(code,msg)
log:info(MOD_ABBR, "network:setStatus: "..code.." | "..msg)
local file = io.open('/tmp/networkstatus.txt','w')
file:write(code.."|"..msg)
file:flush()
file:close()
end
return M