#!/usr/bin/lua --[[ Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks Copyright (C) 2021 Tim Wilkinson Original Perl Copyright (C) 2019 Joe Ayers AE6XE Original Perl Copyright (c) 2013 David Rivenburg et al. BroadBand-HamNet See Contributors file for additional contributors 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(TM) 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.http") require("aredn.utils") require("aredn.hardware") require("uci") require('luci.http') require('luci.sys') local html = require("aredn.html") local aredn_info = require("aredn.info") local errors = {} local output = {} local hidden = {} -- uci cursor local cursor = uci.cursor() -- helpers start local rf_channel_map = { ["900"] = {}, ["2400"] = {}, ["3400"] = {}, ["5500"] = {}, ["5800ubntus"] = {} } for i = 4,7 do rf_channel_map["900"][i - 3] = { label = i .. " (" .. (887 + i * 5) .. ")", number = i, frequency = 887 + i * 5 } end for i = -2,11 do rf_channel_map["2400"][i + (i <= 0 and 3 or 2)] = { label = i .. " (" .. (2407 + i * 5) .. ")", number = i, frequency = 2407 + i * 5 } end for i = 76,99 do rf_channel_map["3400"][i - 75] = { label = i .. " (" .. (3000 + i * 5) .. ")", number = i, frequency = 3000 + i * 5 } end for i = 36,64,4 do rf_channel_map["5500"][(i - 32) / 4] = { label = i .. " (" .. (5000 + i * 5) .. ")", number = i, frequency = 5000 + i * 5 } end for i = 100,140,4 do rf_channel_map["5500"][(i - 64) / 4] = { label = i .. " (" .. (5000 + i * 5) .. ")", number = i, frequency = 5000 + i * 5 } end for i = 149,165,4 do rf_channel_map["5500"][(i - 69) / 4] = { label = i .. " (" .. (5000 + i * 5) .. ")", number = i, frequency = 5000 + i * 5 } end for i = 131,184 do rf_channel_map["5800ubntus"][i - 130] = { label = i .. " (" .. (5000 + i * 5) .. ")", number = i, frequency = 5000 + i * 5 } end function capture_and_match(cmd, pattern) local f = io.popen(cmd) if f then for line in f:lines() do local r = line:match(pattern) if r then f:close() return r end end f:close() end end function rf_channels_list(wifiintf) local channels = {} local rfband = aredn.hardware.get_rfband() if rfband and rf_channel_map[rfband] then return rf_channel_map[rfband] else local f = io.popen("iwinfo " .. wifiintf .. " freqlist") if f then for line in f:lines() do local freq, num = line:match("(%d+%.%d+) GHz %(Channel (%d+)%)") if freq and not line:match("restricted") then freq = freq:gsub("%.", "") num = num:gsub("^0+", "") channels[#channels + 1] = { label = num .. " (" .. freq .. ")", number = tonumber(num), frequency = freq } end end f:close() end end return channels end function wifi_txpoweroffset(wifiintf) local doesiwoffset = capture_and_match("iwinfo " .. wifiintf .. " info", "TX power offset: (%d+)") if doesiwoffset then return tonumber(doesiwoffset) end return 0 end function reboot() local node = aredn_info.get_nvram("node") if node == "" then node = "Node" end local lanip, _, lanmask = aredn.hardware.get_interface_ip4(aredn.hardware.get_iface_name("lan")) local browser = os.getenv("REMOTE_ADDR") local browser6 = browser:match("::ffff:([%d%.]+)") if browser6 then browser = browser6 end local fromlan = false local subnet_change = false if lanip then fromlan = validate_same_subnet(browser, lanip, lanmask) if fromlan then lanmask = ip_to_decimal(lanmask) local cfgip = cursor:get("network", "lan", "ipaddr") local cfgmask = ip_to_decimal(cursor:get("network", "lan", "netmask")) if lanmask ~= cfgmask or decimal_to_ip(nixio.bit.band(ip_to_decimal(lanip), lanmask)) ~= nixio.bit.band(ip_to_decimal(cfgip), cfgmask) then subnet_change = true end end end http_header() if fromlan and subnet_change then html.header(node .. " rebooting", true); html.print("
") html.print("

" .. node .. " is rebooting


") html.print("

The LAN subnet has changed. You will need to acquire a new DHCP lease
") html.print("and reset any name service caches you may be using.


") html.print("

When the node reboots you get your new DHCP lease and reconnect with
") html.print("http://localnode.local.mesh:8080/
or
") html.print("http://" .. node .. ".local.mesh:8080/

") else html.header(node .. " rebooting", false) html.print("") html.print("
") html.print("

" .. node .. " is rebooting


") html.print("

Your browser should return to this node in 60 seconds.

") html.print("If something goes astray you can try to connect with

") html.print("http://localnode.local.mesh:8080/
") if node ~= "Node" then html.print("or
http://" .. node .. ".local.mesh:8080/

") end end html.print("
") http_footer() luci.sys.reboot() os.exit() end function err(str) errors[#errors + 1] = str end function out(str) output[#output + 1] = str end function is_channel_valid(channel) local list = rf_channels_list(aredn.hardware.get_iface_name("wifi")) for _, c in ipairs(list) do if c.number == channel then return true end end return false end function is_wifi_chanbw_valid(wifi_chanbw, wifi_ssid) return true -- always true end -- helper end -- timezones local tz_db_names = {} for line in io.lines("/etc/zoneinfo") do local name, tz = line:match("^(.*)\t(.*)") tz_db_names[#tz_db_names + 1] = { name = name, tz = tz } end -- online ping local pingOK = false if capture_and_match("ping -W1 -c1 8.8.8.8", "1 packets received") then pingOK = true end -- must be global not local dmz_lan_ip = "" dmz_lan_mask = "" lan_gw = "" wan_ip = "" wan_mask = "" wan_gw = "" passwd1 = "" passwd2 = "" local ctwo = { 1,2,3,4,5,6,7,8,9,10,11 } local cfive = { 36,40,44,48,149,153,157,161,165 } local wifiintf = aredn.hardware.get_iface_name("wifi") local phy = iwinfo.nl80211.phyname(wifiintf) local phycount = tonumber(capture("ls -1d /sys/class/ieee80211/* | wc -l"):chomp()) -- post_data local parms = {} local has_parms = false if os.getenv("REQUEST_METHOD") == "POST" then local request = luci.http.Request(luci.sys.getenv(), function() local v = io.read(1024) if not v then io.close() end return v end ) parms = request:formvalue() for _,_ in pairs(parms) do has_parms = true break end end if parms.button_uploaddata then local si = capture("curl 'http://localnode:8080/cgi-bin/sysinfo.json?hosts=1' 2>/dev/null"):chomp() -- strip closing } si = si.sub(1, #si - 1) -- get olsrd topo information local topo = capture("url 'http://localnode:9090/links' 2>/dev/null"):chomp() -- add topo subdoc and close root doc local newsi = string.format("%s,\"olsr\": %s}", si, topo) -- PUT it to the server local upcurl = os.execute("curl -H 'Accept: application/json' -X PUT -d '" .. newsi .. "' http://data.arednmesh.org/sysinfo >/dev/null 2>&1") if upcurl == 0 then out("AREDN online map updated") else err("ERROR: Cannot update online map. Please ensure this node has access to the internet."); end end if parms.button_default then local node = aredn_info.get_nvram("node") local mac2 = mac_to_ip(aredn.hardware.get_interface_mac(aredn.hardware.get_iface_name("wifi")), 0) local dtdmac = mac_to_ip(aredn.hardware.get_interface_mac(aredn.hardware.get_iface_name("lan")), 0) for line in io.lines("/etc/config.mesh/_setup.default") do if not (line:match("^%s*#") or line:match("^%s*$")) then line = line:gsub("", node):gsub("", mac2):gsub("", dtdmac) local k, v = line:match("^([^%s]*)%s*=%s*(.*)%s*$") _G[k] = v end end else for k, v in pairs(parms) do if k:match("^%w+") then v = v:gsub("^%s+", ""):gsub("%s+$", "") _G[k] = v end end if parms.button_reset or not has_parms then local node = aredn_info.get_nvram("node") local mac2 = mac_to_ip(aredn.hardware.get_interface_mac(aredn.hardware.get_iface_name("wifi")), 0) local dtdmac = mac_to_ip(aredn.hardware.get_interface_mac(aredn.hardware.get_iface_name("lan")), 0) for line in io.lines("/etc/config.mesh/_setup") do if not (line:match("^%s*#") or line:match("^%s*$")) then line = line:gsub("", node):gsub("", mac2):gsub("", dtdmac) local k, v = line:match("^([^%s]*)%s*=%s*(.*)%s*$") _G[k] = v end end local function h2s(hex) local s = "" if hex then for i = 1,#hex,2 do local p = hex:sub(i, i+1) if p:match("[0-9a-f][0-9a-f]") then s = s .. string.char(tonumber(p, 16)) else s = s .. p end end end return s end wifi2_key = h2s(wifi2_key) wifi2_ssid = h2s(wifi2_ssid) wifi3_key = h2s(wifi3_key) wifi3_ssid = h2s(wifi3_ssid) end end -- make sure everything we need is initialized local d0 = { "lan_dhcp", "olsrd_bridge", "olsrd_gw", "wifi2_enable", "lan_dhcp_noroute", "wifi_enable", "wifi3_enable" } for _, k in ipairs(d0) do if not parms[k] then parms[k] = "0" end if not _G[k] then _G[k] = "0" end end for _, k in ipairs({ "wifi_channel", "wifi2_channel", "wifi_txpower", "dhcp_start", "dhcp_end" }) do if _G[k] then _G[k] = tonumber(_G[k]) end end local nodetac if parms.button_reset or parms.button_default or (not nodetac and not has_parms) then nodetac = aredn_info.get_nvram("node") tactical = aredn_info.get_nvram("tactical") if tactical ~= "" then nodetac = nodetac .. " / " .. tactical end else nodetac = parms.nodetac end -- lan is always static local lan_proto = "static" -- enforce direct mode settings -- (formerly known as dmz mode) if not dmz_mode then dmz_mode = cursor:get("aredn", "@dmz[0]", "mode") end dmz_mode = tonumber(dmz_mode) if not dmz_mode or (dmz_mode ~= 0 and dmz_mode < 2) then dmz_mode = 2 elseif dmz_mode > 5 then dmz_mode = 5 end if dmz_mode ~= 0 then local ipshift = (ip_to_decimal(wifi_ip) * math.pow(2, dmz_mode)) % 0x1000000 local a, b = decimal_to_ip(ipshift):match("(%d+%.%d+%.%d+%.)(%d+)") dmz_lan_ip = "1" .. a .. (tonumber(b) + 1) dmz_lan_mask = decimal_to_ip((0xffffffff * math.pow(2, dmz_mode)) % 0x100000000) local octet = dmz_lan_ip:match("%d+%.%d+%.%d+%.(%d+)") dmz_dhcp_start = octet + 1 dmz_dhcp_end = dmz_dhcp_start + math.pow(2, dmz_mode) - 4; parms.dmz_lan_ip = dmz_lan_ip parms.dmz_lan_mask = dmz_lan_mask parms.dmz_dhcp_start = dmz_dhcp_start parms.dmz_dhcp_end = dmz_dhcp_end end parms.dhcp_limit = dhcp_end - dhcp_start + 1 parms.dmz_dhcp_limit = dmz_dhcp_end - dmz_dhcp_start + 1 -- get the active wifi settings on a fresh page load if not parms.reload then wifi_txpower = capture_and_match("iwinfo " .. wifiintf .. " info", "Tx%-Power: (%d+)") local doesiwoffset = capture_and_match("iwinfo " .. wifiintf .. " info", "TX power offset: (%d+)") if wifi_txpower then wifi_txpower = tonumber(wifi_txpower) if doesiwoffset then wifi_txpower = wifi_txpower - tonumber(doesiwoffset) end end end -- sanitize the active settings if not wifi_txpower or wifi_txpower > aredn.hardware.wifi_maxpower(wifi_channel) then wifi_txpower = aredn.hardware.wifi_maxpower(wifi_channel) end if not wifi_power or wifi_power < 1 then wifi_power = 1 end if not wifi_distance then wifi_distance = 0 end if tostring(wifi_distance):match("%D") then wifi_distance = 0 end -- stuff the sanitized data back into the parms tables -- so they get saved correctly parms.wifi_distance = wifi_distance parms.wifi_txpower = wifi_txpower -- apply the wifi settings if (parms.button_apply or parms.button_save) and wifi_enable == "1" and phy then if wifi_distance == 0 then os.execute("iw phy " .. phy .. " set distance auto >/dev/null 2>&1") else os.execute("iw phy " .. phy .. " set distance " .. wifi_distance .. " >/dev/null 2>&1") end os.execute("iw dev " .. wifiintf .. " set txpower fixed " .. wifi_txpower .. "00 >/dev/null 2>&1") end if parms.button_updatelocation then -- process gridsquare local cursora = uci.cursor(); local cursorb = uci.cursor("/etc/config.mesh") if parms.gridsquare ~= "" then if parms.gridsquare:match("^[A-Z][A-Z]%d%d[a-z][a-z]$") then cursora:set("aredn", "@location[0]", "gridsquare", parms.gridsquare) cursorb:set("aredn", "@location[0]", "gridsquare", parms.gridsquare) out("Gridsquare updated.") else err("ERROR: Gridsquare format is: 2-uppercase letters, 2-digits, 2-lowercase letters. (AB12cd)") end else cursora:set("aredn", "@location[0]", "gridsquare", "") cursorb:set("aredn", "@location[0]", "gridsquare", "") out("Gridsquare purged.") end -- process lat/lng if parms.latitude ~= "" and parms.longitude ~= "" then if parms.latitude:match("^[-+]?%d%d?%.%d+$") and parms.longitude:match("^[-+]?%d%d?%d?%.%d+$") then if tonumber(parms.latitude) >= -90 and tonumber(parms.latitude) <= 90 and tonumber(parms.longitude) >= -180 and tonumber(parms.longitude) <= 180 then cursora:set("aredn", "@location[0]", "lat", parms.latitude) cursorb:set("aredn", "@location[0]", "lat", parms.latitude) cursora:set("aredn", "@location[0]", "lon", parms.longitude) cursorb:set("aredn", "@location[0]", "lon", parms.longitude) out("Lat/lon updated.") else err("ERROR: Lat/lon values must be between -90/90 and -180/180, respectively.") end else err("ERROR: Lat/lon format is decimal: (ex. 30.121456 or -95.911154).") end else cursora:set("aredn", "@location[0]", "lat", "") cursorb:set("aredn", "@location[0]", "lat", "") cursora:set("aredn", "@location[0]", "lon", "") cursorb:set("aredn", "@location[0]", "lon", "") out("Lat/lon purged.") end cursora:commit("aredn") cursorb:commit("aredn") end -- retrieve location data lat = cursor:get("aredn", "@location[0]", "lat") if not lat then lat = "" end lon = cursor:get("aredn", "@location[0]", "lon") if not lon then lon = "" end gridsquare = cursor:get("aredn", "@location[0]", "gridsquare") if not gridsquare then gridsquare = "" end -- validate and save configuration if parms.button_save then for _,zone in ipairs(tz_db_names) do if zone.name == time_zone_name then parms.time_zone = zone.tz break end end if not validate_netmask(wifi_mask) then err("invalid Mesh netmask") elseif not validate_ip_netmask(wifi_ip, wifi_mask) then err("invalid Mesh IP address") end if #wifi_ssid > 32 then err("invalid Mesh RF SSID") end if not is_channel_valid(wifi_channel) then err("invalid Mesh RF channel") end if not is_wifi_chanbw_valid(wifi_chanbw, wifi_ssid) then err("Invalid Mesh RF channel width") wifi_chanbw = 20 end local wifi_country_validated = false local countries = { "00","HX","AD","AE","AL","AM","AN","AR","AT","AU","AW","AZ","BA","BB","BD","BE","BG","BH","BL","BN","BO","BR","BY","BZ","CA","CH","CL","CN","CO","CR","CY","CZ","DE","DK","DO","DZ","EC","EE","EG","ES","FI","FR","GE","GB","GD","GR","GL","GT","GU","HN","HK","HR","HT","HU","ID","IE","IL","IN","IS","IR","IT","JM","JP","JO","KE","KH","KP","KR","KW","KZ","LB","LI","LK","LT","LU","LV","MC","MA","MO","MK","MT","MY","MX","NL","NO","NP","NZ","OM","PA","PE","PG","PH","PK","PL","PT","PR","QA","RO","RS","RU","RW","SA","SE","SG","SI","SK","SV","SY","TW","TH","TT","TN","TR","UA","US","UY","UZ","VE","VN","YE","ZA","ZW" } for _,country in ipairs(countries) do if country == wifi_country then wifi_country_validated = true break end end if not wifi_country_validated then wifi_country = "00" err("Invalid country") end if lan_proto == "static" then if not validate_netmask(lan_mask) then err("invalid LAN netmask") elseif not lan_mask:match("^255%.255%.255%.") then err("LAN netmask must begin with 255.255.255") elseif not validate_ip_netmask(lan_ip, lan_mask) then err("invalid LAN IP address") else if lan_dhcp ~= "" then local start_addr = lan_ip:match("^(.*%.)%d$") .. dhcp_start local end_addr = lan_ip:match("^(.*%.)%d$") .. dhcp_end if not (validate_ip_netmask(start_addr, lan_mask) and validate_same_subnet(start_addr, lan_ip, lan_mask)) then err("invalid DHCP start address") end if not (validate_ip_netmask(end_addr, lan_mask) and validate_same_subnet(end_addr, lan_ip, lan_mask)) then err("invalid DHCP end address") end if dhcp_start > dhcp_end then err("invalid DHCP start/end addresses") end end if lan_gw ~= "" and not (validate_ip_netmask(lan_gw, lan_mask) and validate_same_subnet(lan_ip, lan_gw, lan_mask)) then err("invalid LAN gateway") end end end if wan_proto == "static" then if not validate_netmask(wan_mask) then err("invalid WAN netmask") elseif not validate_ip_netmask(wan_ip, wan_mask) then err("invalid WAN IP address") elseif not (validate_ip_netmask(wan_gw, wan_mask) and validate_same_subnet(wan_ip, wan_gw, wan_mask)) then err("invalid WAN gateway") end end if not validate_ip(wan_dns1) then err("invalid WAN DNS 1") end if wan_dns2 ~= '' and not validate_ip(wan_dns2) then err("invaliud WAN DNS 2") end if passwd1 ~= "" or passwd2 ~= "" then if passwd1 ~= passwd2 then err("passwords do not match") end if passwd1:match("#") then err("passwords cannot contain '#'") end if passwd1 == "hsmm" then err("password must be changed") end elseif nixio.fs.stat("/etc/config/unconfigured") then err("password must be changed during initial configuration") end if nodetac:match("/") then node, tactical = nodetac:match("^%s*([%w-]+)%s*/%s*([%w-])%s*$") if tactical == "" then err("invalid node/tactical name") end else node = nodetac tactical = "" if node == "" then err("you must set the node name") end end if node ~= "" and node:match("[^%w-]") or node:match("_") then err("invalid node name") end if tactical:match("[^%w-]") or tactical:match("_") then err("invalid tactical name") end if not validate_fqdn(ntp_server) then err("invalid ntp server") end if wifi2_enable == "1" then if #wifi2_ssid > 32 then err("LAN Access Point SSID musr be 32 or less characters") end if #wifi2_key < 8 or #wifi2_key > 64 then err("LAN Access Point Password must be at least 8 characters, up to 64") end if wifi2_key:match("'") or wifi2_ssid:match("'") then err("The LAN Access Point password and ssid may not contain a single quote character") end if wifi2_channel < 30 and wifi2_hwmode == "11a" then err("Changed to 5GHz Mesh LAN AP, please review channel selection") end if wifi2_channel > 30 and wifi2_hwmode == "11g" then err("Changed to 2GHz Mesh LAN AP, please review channel slection") end end if wifi3_enable == "1" then if #wifi3_key < 8 or #wifi3_key > 64 then err("WAN Wifi Client Password must be between 8 and 64 characters") end if wifi3_key:match("'") or wifi3_ssid:match("'") then err("The WAN Wifi Client password and ssid may not contain a single quote character") end end if phycount > 1 and wifi_enable == "1" and wifi2_channel < 36 and wifi2_enable == "1" then err("Mesh RF and LAN Access Point can not both use the same wireless card, review LAN AP settings") end if phycount > 1 and wifi_enable == "0" and wifi2_hwmode == wifi3_hwmode then err("Some settings auto updated to avoid conflicts, please review and save one more time") end if wifi_enable == "1" and wifi2_enable == "1" and wifi3_enable == "1" then err("Can not enable Mesh RF, LAN AP, and WAN Wifi Client with only 2 wireless cards, WAN Wifi Client turned off") wifi2_enable = "0" end if phycount == 1 and wifi_enable == "1" and (wifi2_enable == "1" or wifi3_enable == "1") then err("Can not enable Mesh RF along with LAN AP or WAN Wifi Client. Only Mesh RF enabled now, please review settings.") wifi2_enable = "0" wifi3_enable = "0" end if #errors == 0 then parms.node = node parms.tactical = tactical if nixio.fs.stat("/etc/config/unconfigured") then io.open("/tmp/unconfigured", "w"):close() end local function s2h(str) local h = "" for i = 1,#str do h = h .. string.format("%02x", string.byte(str, i)) end return h end parms.wifi2_key = s2h(wifi2_key) parms.wifi2_ssid = s2h(wifi2_ssid) parms.wifi3_key = s2h(wifi3_key) parms.wifi3_ssid = s2h(wifi3_ssid) -- save_setup local f = io.open("/etc/config.mesh/_setup", "w") if f then for k, v in pairs(parms) do if k:match("^aprs_") or k:match("^dhcp_") or k:match("^dmz_") or k:match("^lan_") or k:match("^olsrd_") or k:match("^wan_") or k:match("^wifi_") or k:match("^wifi2_") or k:match("^wifi3_") or k:match("^dtdlink_") or k:match("^ntp_") or k:match("^time_") or k:match("^description_") then f:write(k .. " = " .. v .. "\n") end end f:close() end aredn_info.set_nvram("node", parms.node); aredn_info.set_nvram("tactical", parms.tactical) aredn_info.set_nvram("config", parms.config) if not nixio.fs.stat("/tmp/web/save") then nixio.fs.mkdir("/tmp/web/save") end os.execute("/usr/local/bin/node-setup -a " .. parms.config .. " > /tmp/web/save/node-setup.out 2>&1") if nixio.fs.stat("/tmp/web/save/node-setup.out", "size") > 0 then err(read_all("/tmp/web/save/node-setup.out")) else remove_all("/tmp/web/save") -- set password if passwd1 ~= "" then local pw = passwd1:gsub("'", "\\'") local f = io.popen("{ echo '" .. pw .. "'; sleep 1; echo '" .. pw .. "'; } | passwd") f:read("*a") f:close() end io.open("/tmp/reboot-required", "w"):close() end if nixio.fs.stat("/tmp/unconfigured") and #errors == 0 then reboot() end end end remove_all("/tmp/web/save") if parms.button_reboot then reboot() end local desc = cursor:get("system", "@system[0]", "description") if not desc then desc = "" end local maptiles = cursor:get("aredn", "@map[0]", "maptiles") if not maptiles then maptiles = "" end local leafletcss = cursor:get("aredn", "@map[0]", "leafletcss") if not leafletcss then leafletcss = "" end local leafletjs = cursor:get("aredn", "@map[0]", "leafletjs") if not leafletjs then leafletjs = "" end -- generate page http_header() html.header(aredn_info.get_nvram("node") .. " setup", false) html.print([[ ]]) html.print("") html.print("
") html.alert_banner() html.print("
\n") html.print("") html.print("") -- control buttons html.print([[]]) if #output > 0 then html.print("") end if #errors > 0 then html.print("") html.print("") elseif parms.button_save then html.print("") end if #errors == 0 and nixio.fs.stat("/tmp/reboot-required") then html.print("") end -- note name and type, password html.print([[
") -- navbar html.print("
") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") html.print("
Node StatusPort Forwarding,
DHCP, and Services
Tunnel
Server
Tunnel
Client
AdministrationAdvanced
Configuration

") html.print("
Help          
 
") html.print("
    ") for _,o in ipairs(output) do html.print("
  • " .. o .. "
  • ") end html.print("
") html.print("
Configuration NOT saved!
") html.print("
    ") for _,e in ipairs(errors) do html.print("
  • " .. e .. "
  • ") end html.print("
") html.print("
") html.print("Configuration saved.

") html.print("

Reboot is required for changes to take effect

]]) html.print([[ ]]) hidden[#hidden + 1] = "" html.print([[
Node Name Password
Node Description (optional) Verify Password

") -- lan settings html.print([[ ") -- wan settings html.print("
]]) -- mesh rf settings html.print("") if phycount > 1 then html.print("") else html.print("") end hidden[#hidden + 1] = "" -- add enable/disable html.print("") html.print("") -- reset wifi channel/bandwidth to default if nixio.fs.stat("/etc/config/unconfigured") or parms.button_reset then local defaultwifi = aredn.hardware.get_default_channel() wifi_channel = defaultwifi.channel wifi_chanbw = defaultwifi.bandwidth end if wifi_enable == "1" then html.print("") hidden[#hidden + 1] = "" html.print("") html.print("") hidden[#hidden + 1] = "" html.print("") html.print("") html.print("") local wifi_distance = math.floor(tonumber(wifi_distance)) local wifi_distance_disp_km = math.floor(wifi_distance / 1000) local wifi_distance_disp_miles = string.format("%.2f", wifi_distance_disp_km * 0.621371192) html.print("") html.print("") else hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end html.print("
Mesh RF (2GHz)
Mesh RF
Enable
IP Address
Netmask
SSID-" .. wifi_chanbw .. "-v3
Channel
Channel Width

Active Settings
Tx Power  
Distance to
FARTHEST Neighbor

'0' is auto

 mi
") html.print(" km
") html.print(" m
") html.print("
") html.print("") html.print("
") hidden[#hidden + 1] = "" if dmz_mode ~= 0 then html.print("") hidden[#hidden + 1] = "" html.print("") hidden[#hidden + 1] = "" html.print("") html.print("") hidden[#hidden + 1] = "" html.print("") hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" else html.print("") html.print("") if wan_proto == "disabled" then html.print("") else hidden[#hidden + 1] = "" end html.print("") html.print("") html.print("") hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end html.print("") -- $M39model = `/usr/local/bin/get_model | grep -e "M[39]"`; local M39model if (phycount > 1 and (wifi_enable ~= "1" or wifi3_enable ~= "1")) or (phycount == 1 and wifi_enable ~= "1" and wifi3_enable ~= "1") and not M39model then -- lan ap shows as an option -- determine hardware options and set band ahd channels accordingly if phycount == 1 then -- rc3 = -- system("iw phy phy0 info | grep -q '5180 MHz' > /dev/null"); if rc3 ~= "" then wifi2_hwmode = "11g" if wifi2_channel > 14 then wifi2_channel = 1 end chan = ctwo else wifi2_hwmode = "11a" if wifi2_channel < 36 then wifi2_channel = 36 end chan = cfive end else -- 2 band device if wifi_enable == "1" then wifi2_hwmode = "11a" if wifi2_channel < 36 then wifi2_channel = 36 end chan = cfive else if wifi2_enable ~= "1" and wifi3_enable == "1" and wifi3_hwmode == "11a" then wifi2_hwmode = "11g" end if wifi2_enable ~= "1" and wifi3_enable == "1" and wifi3_hwmode == "11g" then wifi2_hwmode = "11a" end if wifi2_hwmode == "11a" then if wifi2_channel < 36 then wifi2_channel = 36 end chan = cfive else if wifi2_channel > 14 then wifi2_channel = 1 end chan = ctwo end end end html.print("") if phycount > 1 then html.print("") else hidden[#hidden + 1] = "" end html.print("") html.print("") else hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end html.print("
LAN
LAN Mode
IP Address" .. dmz_lan_ip .."
Netmask" .. dmz_lan_mask .. "
DHCP Server
DHCP Start" .. dmz_dhcp_start .. "
DHCP End" .. dmz_dhcp_end .. "
IP Address
Netmask
Gateway
DHCP Server
DHCP Start
DHCP End

LAN Access Point
Enable
AP band
SSID
Channel
Encryption
Password
") if wan_proto == "static" then html.print("") html.print("") html.print("") html.print("") html.print("") html.print("") else hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end html.print("") html.print("") html.print("") if wan_proto ~= "disabled" then html.print("") else hidden[#hidden + 1] = "" end html.print("") -- wan wifi client if (phycount > 1 and (wifi_enable ~= "1" or wifi2_enable ~= "1")) or (phycount == 1 and wifi_enable ~= "1" and wifi2_enable ~= "1") and not M390model then -- wifi client shows as an option -- determine hardware options and set band accordingly if phycount == 1 then -- $rc3 = system("iw phy phy0 info | grep -q '5180 MHz' > /dev/null"); local rc3 if rc3 ~= "" then wifi3_hwmode = "11g" else wifi3_hwmode = "11a" end else -- 2 band if wifi_enable == "1" then wifi3_hwmode = "11a" else if wifi2_hwmode == "11g" and wifi2_enable == "1" then wifi3_hwmode = "11a" end if wifi2_hwmode == "11a" and wifi2_enable == "1" then wifi3_hwmode = "11g" end end end html.print("") if wifi_enable ~= "1" and wifi2_enable ~= "1" and phycount > 1 then html.print("") else hidden[#hidden + 1] = "" end html.print("") html.print("") else hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" end -- end wan wifi client html.print("
WAN
Protocol
IP Address
Netmask
Gateway
DNS 1
DNS 2

Advanced WAN Access
Allow others to
use my WAN
Prevent LAN devices
from accessing WAN

WAN Wifi Client
Enable
WAN Wifi Client band
SSID
Password

") -- optional settings html.print("") html.print("") html.print("") html.print("") html.print("") html.print("
Optional Settings

Latitude") html.print(" ") html.print("") html.print("  ") if pingOK then html.print(" ") else html.print(" ") end html.print("
LongitudeGrid Square

TimezoneNTP Server
") hidden[#hidden + 1] = "" hidden[#hidden + 1] = "" for _,hid in ipairs(hidden) do html.print(hid) end html.print("
") html.footer() html.print("") http_footer()