diff --git a/decompressed/gui_file/usr/lib/lua/tch/inet.lua b/decompressed/gui_file/usr/lib/lua/tch/inet.lua index 526baeba7..1e1370fef 100644 --- a/decompressed/gui_file/usr/lib/lua/tch/inet.lua +++ b/decompressed/gui_file/usr/lib/lua/tch/inet.lua @@ -16,16 +16,20 @@ See LICENSE file for more details. -- -- @module tch.inet +local require = require +local pcall = pcall +local error = error + local posix = require("tch.posix") local bit = require("bit") local AF_INET = posix.AF_INET local AF_INET6 = posix.AF_INET6 local inet_pton = posix.inet_pton +local inet_ntop = posix.inet_ntop local match, format = string.match, string.format local tonumber, type = tonumber, type -local max = math.max -local min = math.min +local floor = math.floor local M = {} @@ -53,19 +57,97 @@ function M.isValidIPv6(ip) return nil, err end +local AddressFamilyToAF_SPEC = { + IPv4 = AF_INET, + IPv6 = AF_INET6, +} + +local AF_SPECToAddressFamily = { + [AF_INET] = "IPv4", + [AF_INET6] = "IPv6" +} + +local function ipBinary(ip, family) + local af + local bin + if not family then + bin = posix.inet_pton(AF_INET, ip) + if bin then + af = AF_INET + else + bin = posix.inet_pton(AF_INET6, ip) + if bin then + af = AF_INET6 + else + return nil, "not a valid IP address (v4 nor v6)" + end + end + else + af = AddressFamilyToAF_SPEC[family] + if not af then + return nil, "invalid address family, must be IPv4 or IPv6" + end + local err + bin, err = posix.inet_pton(af, ip) + if not bin then + return nil, err + end + end + return bin, af +end + --- Check if the given address is a valid IP address. -- @tparam string ip The IP address to test. -- @treturn string "IPv4" if `ip` is a valid IPv4 address or "IPv6" if -- `ip` is a valid IPv6 address. -- @error Error message. function M.isValidIP(ip) - if M.isValidIPv4(ip) then - return "IPv4" + local bin, af = ipBinary(ip) + if not bin then + return nil, af + end + return AF_SPECToAddressFamily[af] +end + +--- Normalize the given IP address to canonical representation +-- +-- This is needed especially for IPv6 where there is a lot of freedom +-- in what qualifies as a valid address. eg. "0:0::0" is valid and it is +-- equivalent to "0000::0000". This function will reduce it to it's shortest +-- form "::". +-- @string ip the IP address to mormalize +-- @string[opt] family the address family IPv4 or IPv6. If not specified +-- it is determined automatically +-- @return normalized ip and address family +-- @error Error message +function M.normalizeIP(ip, family) + local bin, af = ipBinary(ip, family) + if not bin then + return nil, af + end + return inet_ntop(af, bin), AF_SPECToAddressFamily[af] +end + +--- Is the given IP address zero +-- +-- An IP address is zero if it consist of only NUL bytes. +-- Depending on the context this either means ANY address or UNASSIGNED. +-- @string ip the ip address to check +-- @string[opt] family the address family, either IPv4 or IPv6. If not specified +-- it is determined automatically. +-- @return true if the ip address is zero +-- @raise error if ip is invalid +function M.ipIsZero(ip, family) + local bin, af = ipBinary(ip, family) + if not bin then + return error(af) end - if M.isValidIPv6(ip) then - return "IPv6" + for i=1,#bin do + if bin:byte(i)~=0 then + return false + end end - return nil, "not a valid IP address (v4 nor v6)" + return true end --- Convert the given hexadecimal IPv4 address string to @@ -103,14 +185,140 @@ function M.isValidGlobalUnicastv6Address(ipv6addr) return nil, "Invalid Global Unicast Address" end +--- convert ip address string to a number (in host order) +-- @string ip the ip address string eg "192.168.1.1" +-- @treturn number the 32bit integer representation of the ip +-- @error Error Message +function M.ipv4ToNumber(ip) + if ip then + local ok, bin, num = pcall(inet_pton, AF_INET, ip) + if ok and bin then + return num + else + return nil, "invalid input data" + end + end +end +local ipv4ToNumber = M.ipv4ToNumber + +--- convert a 32bit integer to an IPv4 string representation +-- @number n the number to convert +-- @treturn string the dotted ip address string +-- @error Error Message +function M.numberToIpv4(n) + local ok, ip = pcall(inet_ntop, AF_INET, n) + if ok then + return ip + end + return nil, "invalid data" +end +local numberToIpv4 = M.numberToIpv4 + +--- Check that the given value is a valid IPv4 netmask. +-- In particular it will check that the netmask falls in the +-- range of /8 and /30 (both inclusive) which is what makes +-- sense for DHCP pool configuration. +-- @string value The netmask in dotted decimal notation. +-- @treturn boolean True if it's a valid subnet mask. +-- @treturn number The number of bits in the host part of the subnet mask. +-- @error Error message. +function M.validateIPv4Netmask(value) + -- A valid subnet mask consists of (in binary) consecutive 1's + -- followed by consecutive 0's. + local netmask = ipv4ToNumber(value) + if not netmask then + return nil, "String is not an IPv4 address." + end + local ones = 0 + local expecting = 0 + for i = 0, 31 do + local bitmask = bit.lshift(1, i) + local result = bit.band(netmask, bitmask) + if result == 0 then + if expecting ~= 0 then + return nil, "Invalid subnet." + end + else + if expecting == 0 then + expecting = 1 + end + ones = ones + 1 + end + end + if (ones < 8) or (ones > 30) then + return nil, "Invalid subnet." + end + return true, 32 - ones +end + +--- Calculate the number of effective hosts possible in the network with the given subnet mask. +-- @string subnetmask The subnet mask, in dotted-decimal notation. +-- @treturn number The number of effective hosts possible in the network with the given subnet mask. +-- @error Error message. +function M.getPossibleHostsInIPv4Subnet(subnetmask) + local valid, host_bits = M.validateIPv4Netmask(subnetmask) + if not valid then + return nil, host_bits + end + return (2^host_bits) - 2 +end + +local netmask_bits_to_ip +local netmask_dotted_to_bits + +local function netmask_BitsToDotted(nBits) + if nBits and 1 <= nBits and nBits <= 32 then + return (2^nBits-1)*(2^(32-nBits)) + end +end + +local function fill_netmask_tables() + if netmask_bits_to_ip then + return + end + netmask_bits_to_ip = {} + netmask_dotted_to_bits = {} + for bits=1,32 do + local ip = netmask_BitsToDotted(bits) + netmask_bits_to_ip[bits] = ip + local dotted = inet_ntop(AF_INET, ip) + netmask_dotted_to_bits[dotted] = bits + end +end + --- Convert a number of bits to a number representing the netmask -- In particular it will check that the input netmask falls in the range of 1 to 32 --- @tnumber num the subnet value as number +-- @number num the subnet value as number -- @treturn number representing the netmask or nil function M.netmaskToNumber(num) - if num and 1 <= num and num <= 32 then - return (2^num-1)*(2^(32-num)) + fill_netmask_tables() + return netmask_bits_to_ip[num] +end + +--- Convert a dotted ip netmask to the number of network bits +-- @string ip the dotted netmask (eg 255.255.255.0) +-- @return number of netmask bits +-- @error if ip is not a proper netmask +function M.mask2netmask(ip) + fill_netmask_tables() + local bits = netmask_dotted_to_bits[ip] + if bits and bits<31 then + return bits + end +end + +--- broadcast address for the given network +-- @string ip The ip address (eg 192.168.1.1) +-- @number network_bits The number of bits in the network mask (1 to 32) +-- @treturn string The broadcast address (eg 192.168.1.255) +-- @error an error message +function M.ipv4BroadcastAddr(ip, network_bits) + local n = ipv4ToNumber(ip) + if not n or network_bits<1 or 32 " .. location .. filename) else @@ -71,7 +76,7 @@ local function main(...) if persistenlog_enabled and rotate >= 1 then execute("cat `ls -r " .. name .."*` > " .. location .. filename) else - execute("cp " .. name .. " " .. location .. filename) + execute("ln -fs " .. name .. " " .. location .. filename) end end return 0 diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/WLANConfigurationCommon.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/WLANConfigurationCommon.lua index a27f77646..b983bac7c 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/WLANConfigurationCommon.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/WLANConfigurationCommon.lua @@ -1,84 +1,14 @@ +local M = {} local uciHelper = require("transformer.mapper.ucihelper") -local ubus = require("ubus") -local bandSteerHelper = require("transformer.shared.bandsteerhelper") -local nwWifi = require("transformer.shared.wifi") -local nwCommon = require("transformer.mapper.nwcommon") +local conn = require("transformer.mapper.ubus").connect() -local conn = ubus.connect() local wirelessBinding = { config = "wireless" } -local wirelessDefaultsBinding = { config = "wireless-defaults" } -local pairs, string, table, tonumber, tostring = pairs, string, table, tonumber, tostring +local bandSteerHelper = require("transformer.shared.bandsteerhelper") local envBinding = { config = "env", sectionname = "var" } -local floor = math.floor -local transactions = {} - -local beaconTypeMap = { - ["none"] = "Basic", - ["wep"] = "Basic", - ["wpa-psk"] = "WPA", - ["wpa2-psk"] = "11i", - ["wpa-wpa2-psk"] = "WPAand11i", - ["wpa"] = "WPA", - ["wpa2"] = "11i", - ["wpa-wpa2"] = "WPAand11i", - ["WPA"] = "wpa", - ["WPAand11i"] = "wpa-wpa2", - ["11i"] = "wpa2" -} - -local encryptionModeMap = { - wep = "WEPEncryption", - none = "None" -} - -local wpaAuthenticationModeMap = { - ["wpa2-psk"] = "PSKAuthentication", - ["wpa-wpa2-psk"] = "PSKAuthentication", - ["wpa2"] = "EAPAuthentication", - ["wpa-wpa2"] = "EAPAuthentication", -} - -local authServiceModeMap = { - ["none"] = "None", - ["wep"] = "None", - ["wpa"] = "RadiusClient", - ["wpa2"] = "RadiusClient", - ["wpa-wpa2"] = "RadiusClient", - ["None"] = "none", - ["LinkAuthentication"] = "wpa2-psk", - ["RadiusClient"] = "wpa2" -} - -local powerLevelMap = { - ["-6"] = "1", - ["-3"] = "2", - ["-1"] = "3", - ["0"] = "4", - ["1"] = "-6", - ["2"] = "-3", - ["3"] = "-1", - ["4"] = "0", -} - -local transmitPowerMap = { - ["-6"] = "25", - ["-3"] = "50", - ["-1"] = "75", - ["0"] = "100", - ["25"] = "-6", - ["50"] = "-3", - ["75"] = "-1", - ["100"]= "0", -} - -local wpsStateMap = { - configured = "Configured", - notconfigured = "Not configured", -} - -local DFSChannels = { 52, 56, 60, 64, 100, 104, 108, 112, 116, 132, 136, 140 } +local pairs, tostring = pairs, tostring +local configChanged -local function getFromUci(sectionname, option, default) +function M.getFromWireless(sectionname, option, default) wirelessBinding.sectionname = sectionname if option then wirelessBinding.option = option @@ -88,54 +18,18 @@ local function getFromUci(sectionname, option, default) return uciHelper.getall_from_uci(wirelessBinding) end -local function commit() - for config, changed in pairs(transactions) do - if changed then - uciHelper.commit({ config = config }) - transactions[config] = false - end - end -end - -local function revert() - for config, changed in pairs(transactions) do - if changed then - uciHelper.revert({ config = config }) - transactions[config] = false - end - end - transactions = {} -end - -local function getFromWirelessDefaults(sectionname, option, default) - wirelessDefaultsBinding.sectionname = sectionname - if option then - wirelessDefaultsBinding.option = option - wirelessDefaultsBinding.default = default - return uciHelper.get_from_uci(wirelessDefaultsBinding) - end - return uciHelper.getall_from_uci(wirelessDefaultsBinding) -end - -local function setOnUci(sectionname, option, value, commitapply) +function M.setOnWireless(sectionname, option, value, commitapply) wirelessBinding.sectionname = sectionname wirelessBinding.option = option uciHelper.set_on_uci(wirelessBinding, value, commitapply) - transactions[wirelessBinding.config] = true -end - -local function deleteOnUci(sectionname, commitapply) - wirelessBinding.sectionname = sectionname - wirelessBinding.option = nil - uciHelper.delete_on_uci(wirelessBinding, commitapply) - transactions[wirelessBinding.config] = true + configChanged = true end --- Retrieves the ap for the given iface -- @function getAPFromIface -- @param iface interface name -- @return ap name if present or "" -local function getAPFromIface(iface) +function M.getAPFromIface(iface) local ifaceVal = iface:gsub("_remote", "") local apInfo = conn:call("wireless.accesspoint", "get", {}) or {} for ap, data in pairs(apInfo) do @@ -150,9 +44,9 @@ end -- @function getRadioFromIface -- @param iface interface name -- @return the radio name if present or "" -local function getRadioFromIface(iface) +function M.getRadioFromIface(iface) local ifaceVal = iface:gsub("_remote", "") - local radio = getFromUci(ifaceVal, "device") + local radio = M.getFromWireless(ifaceVal, "device") if radio == "" then local ssidInfo = conn:call("wireless.ssid", "get", { name = ifaceVal }) or {} radio = ssidInfo[ifaceVal] and ssidInfo[ifaceVal].radio or "" @@ -160,98 +54,26 @@ local function getRadioFromIface(iface) return radio end ---- retrieve the accesspoint data --- @function getDataFromAP --- @param iface interface name --- @param option if present only particular value is returned(used in get) else the entire AP info is returned(used in getall) --- @return the table containing AP information for the given interface if present or {} -local function getDataFromAP(iface, option) - local ap = getAPFromIface(iface) - local apInfo = conn:call("wireless.accesspoint", "get", { name = ap }) or {} - if option then - return apInfo[ap] and tostring(apInfo[ap][option] or "") or "" - end - return apInfo[ap] and apInfo[ap] or {} -end - ---- Retrieve the accesspoint security data --- @function getDataFromAPSecurity --- @param iface interface name --- @param option if present only particular value is returned(used in get) else the entire AP security info is returned(used in getall) --- @return the table containing AP Security information for the given interface or {} -local function getDataFromAPSecurity(iface, option) - local ap = getAPFromIface(iface) - local apSecInfo = conn:call("wireless.accesspoint.security", "get", { name = ap }) or {} - if option then - return apSecInfo[ap] and tostring(apSecInfo[ap][option] or "") or "" - end - return apSecInfo[ap] or {} -end - ---- Retrieves the SSID information for the given interface --- @function getDataFromSsid --- @param iface interface name --- @param option if present only particular value is returned(used in get) else the entire SSID info is returned(used in getall) --- @return the table containing the SSID information for the given interface or {} -local function getDataFromSsid(iface, option) - local ifaceVal = iface:gsub("_remote", "") - local ssidInfo = conn:call("wireless.ssid", "get", { name = ifaceVal }) or {} - if option then - return ssidInfo[ifaceVal] and tostring(ssidInfo[ifaceVal][option] or "") or "" - end - return ssidInfo[ifaceVal] or {} -end - ---- Retrieve the Radio information --- @function getDataFromRadio --- @param radio radio name --- @param option if present only particular value is returned(used in get) else the entire radio info is returned(used in getall) --- @return the table containing the radio information or {} -local function getDataFromRadio(radio, option) - local radioInfo = conn:call("wireless.radio", "get", { name = radio }) or {} - if option then - return radioInfo[radio] and tostring(radioInfo[radio][option] or "") or "" - end - return radioInfo[radio] or {} -end +local ubusTable = { + ["accesspoint"] = "wireless.accesspoint", + ["security"] = "wireless.accesspoint.security", + ["ssid"] = "wireless.ssid", + ["radio"] = "wireless.radio", + ["radiostats"] = "wireless.radio.stats", + ["acs"] = "wireless.radio.acs", + ["upgrade"] = "wireless.radio.remote.upgrade", +} ---- Retrieves the Radio stats information --- @function getDataFromRadioStats --- @param radio radio name --- @param option if present only particular value is returned(used in get) else the entire radio stats info is returned(used in getall) --- @return the table containing the radio stats information or {} -local function getDataFromRadioStats(radio, option) - local radioStatsData = conn:call("wireless.radio.stats", "get", { name = radio }) or {} - if option then - return radioStatsData[radio] and tostring(radioStatsData[radio][option] or "") or "" +function M.getWirelessUbus(ubus, iface, option, default) + default = default or "" + if ubus == "accesspoint" or ubus == "security" then + iface = M.getAPFromIface(iface) end - return radioStatsData[radio] or {} -end - ---- Retrieves the acs information --- @function getDataFromAcs --- @param radio radio name --- @param option if present only particular value is returned(used in get) else the entire radio stats info is returned(used in getall) --- @return the table containing the acs information or {} -local function getDataFromAcs(radio, option) - local acsData = conn:call("wireless.radio.acs", "get", { name = radio }) or {} + local info = conn:call(ubusTable[ubus], "get", { name = iface }) or {} if option then - return acsData[radio] and tostring(acsData[radio][option] or "") or "" + return info[iface] and tostring(info[iface][option] or default) or default end - return acsData[radio] or {} -end - ---- Retrieves the remote upgrade information --- @function getDataFromRadioRemoteUpgrade --- @param radio radio name --- @param option if present only particular value is returned(used in get) else the entire remote upgrade info is returned(used in getall) --- @return the table containing the remote upgrade information or {} -local function getDataFromRadioRemoteUpgrade(radio, option) - local remoteUpgradeData = conn:call("wireless.radio.remote.upgrade", "get", { name = radio }) or {} - if option then - return remoteUpgradeData[radio] and tostring(remoteUpgradeData[radio][option] or "") or "" - end - return remoteUpgradeData[radio] or {} + return info[iface] or {} end --- Retrieves the bandsteering related nodes @@ -262,12 +84,12 @@ end -- @return peerAP peer accesspoint -- @return key the base interface -- @return iface the peer interface -local function getBandSteerRelatedNode(ap, key) +function M.getBandSteerRelatedNode(ap, key) local iface = bandSteerHelper.getBandSteerPeerIface(key) if not iface then return nil, "Band steering switching node does not exist." end - local peerAP = getAPFromIface(iface) + local peerAP = M.getAPFromIface(iface) if peerAP == "" then return nil, "Band steering peer AP is invalid." end @@ -284,15 +106,15 @@ end -- @param relatedAP peer accesspoint -- @param bsid the bandsteering id -- @param enable boolean value to indicate enable/disable bandsteer -local function setBandSteerID(ap, relatedAP, bsid, enable, commitapply) +function M.setBandSteerID(ap, relatedAP, bsid, enable, commitapply) if enable then -- set the "bandsteer_id" option in both the AP and related AP to enable BandSteer - setOnUci(ap, "bandsteer_id", bsid, commitapply) - setOnUci(relatedAP, "bandsteer_id", bsid, commitapply) + M.setOnWireless(ap, "bandsteer_id", bsid, commitapply) + M.setOnWireless(relatedAP, "bandsteer_id", bsid, commitapply) else -- set the "bandsteer_id" option to "off" in both the AP and related AP to disable BandSteer - setOnUci(ap, "bandsteer_id", "off", commitapply) - setOnUci(relatedAP, "bandsteer_id", "off", commitapply) + M.setOnWireless(ap, "bandsteer_id", "off", commitapply) + M.setOnWireless(relatedAP, "bandsteer_id", "off", commitapply) end end @@ -304,22 +126,22 @@ end local function setBandSteerPeerIfaceSSID(baseIface, relatedIface, enable, commitapply) local ssid if enable then - ssid = getFromUci(baseIface, "ssid") + ssid = M.getFromWireless(baseIface, "ssid") else - ssid = getFromUci(relatedIface, "ssid") envBinding.option = "commonssid_suffix" local suffix = uciHelper.get_from_uci(envBinding) + ssid = M.getFromWireless(relatedIface, "ssid") ssid = ssid ~= "" and ssid .. suffix or "" end - setOnUci(relatedIface, "ssid", ssid, commitapply) + M.setOnWireless(relatedIface, "ssid", ssid, commitapply) end --- Enables bandsteering -- @function enableBandSteer -- @param key the interface name -local function enableBandSteer(key, commitapply) - local ap = getAPFromIface(key) - local ret, err = bandSteerHelper.canEnableBandSteer(ap, getDataFromAP(key), key) +function M.enableBandSteer(key, commitapply) + local ap = M.getAPFromIface(key) + local ret, err = bandSteerHelper.canEnableBandSteer(ap, M.getWirelessUbus("accesspoint", key), key) if not ret then return nil, err end @@ -327,25 +149,25 @@ local function enableBandSteer(key, commitapply) if not bsid then return nil, errmsg end - local baseAP, relatedAP, baseIface, relatedIface = getBandSteerRelatedNode(ap, key) - setBandSteerID(baseAP, relatedAP, bsid, true, commitapply) + local baseAP, relatedAP, baseIface, relatedIface = M.getBandSteerRelatedNode(ap, key) + M.setBandSteerID(baseAP, relatedAP, bsid, true, commitapply) setBandSteerPeerIfaceSSID(baseIface, relatedIface, true, commitapply) -- set the authentication according to base AP authentication - setOnUci(relatedAP, "security_mode", getFromUci(baseAP, "security_mode"), commitapply) - setOnUci(relatedAP, "wpa_psk_key", getFromUci(baseAP, "wpa_psk_key"), commitapply) + M.setOnWireless(relatedAP, "security_mode", M.getFromWireless(baseAP, "security_mode"), commitapply) + M.setOnWireless(relatedAP, "wpa_psk_key", M.getFromWireless(baseAP, "wpa_psk_key"), commitapply) end -- Disables bandsteering -- @function disableBandSteer -- @param key the interface name -local function disableBandSteer(key, commitapply) - local ap = getAPFromIface(key) +function M.disableBandSteer(key, commitapply) + local ap = M.getAPFromIface(key) local ret, err = bandSteerHelper.canDisableBandSteer(ap, key) if not ret then return nil, err end - local baseAP, relatedAP, baseIface, relatedIface = getBandSteerRelatedNode(ap, key) - setBandSteerID(baseAP, relatedAP, "off", nil, commitapply) + local baseAP, relatedAP, baseIface, relatedIface = M.getBandSteerRelatedNode(ap, key) + M.setBandSteerID(baseAP, relatedAP, "off", nil, commitapply) setBandSteerPeerIfaceSSID(baseIface, relatedIface, nil, commitapply) end @@ -354,8 +176,8 @@ end -- @param option the option to be modified -- @param value the value to be set in the given option -- @param iface the interface name -local function modifyBSPeerNodeAuthentication(option, value, iface, commitapply) - local ap = getAPFromIface(iface) +function M.modifyBSPeerNodeAuthentication(option, value, iface, commitapply) + local ap = M.getAPFromIface(iface) local bandSteerid = bandSteerHelper.getApBandSteerId(ap) if not bandSteerid or bandSteerid == "" or bandSteerid == "off" then return nil, "Bandsteer id is not available or disabled" @@ -369,1579 +191,47 @@ local function modifyBSPeerNodeAuthentication(option, value, iface, commitapply) if option == "ssid" then sectionname = bsPeerIface else - local bsPeerAP = getAPFromIface(bsPeerIface) + local bsPeerAP = M.getAPFromIface(bsPeerIface) sectionname = bsPeerAP end - setOnUci(sectionname, option, value, commitapply) + M.setOnWireless(sectionname, option, value, commitapply) end return end ---- Retrieves the standard used by the given radio --- @function getRadioStandard --- @param radio the radio name --- @return the standard value -local function getRadioStandard(radio) - local standard = getDataFromRadio(radio, "standard") - if standard:find("n") then - return "n" - elseif standard:find("g") then - if standard:find("b") then - return "g" - else - return "g-only" - end - else - return "b" - end -end - ---- Retrieves the channels used by the given interface --- @function getChannels --- @param key the interface name --- @param val the channel value(passed from getall and it is nil when this function is called from get) --- @param channel ubus option to fetch the channel --- @return the string of comma separated channels -local function getChannels(key, val, channel) - local channels - if not val then - local radio = getRadioFromIface(key) - channels = getDataFromRadio(radio, channel) - else - channels = val - end - channels = channels:gsub("%s+", ",") - return channels:match("^,?(.-),?$") or "" -end - ---- Retrieves the bsslist info --- @function getACSBssList --- @param radio the radio name --- @param return the string of bssinfo -local function getACSBssList(radio) - local bssList = conn:call("wireless.radio.bsslist", "get", { name = radio }) or {} - local bssData = "" - for mac, data in pairs(bssList[radio] or {}) do - -- all the colons are removed from the mac - local bssid = mac:gsub(":","") or "" - local bssInfo = string.format("%s:%s:%s:%s:%s:%s;", bssid, data.ssid or "", data.channel or "", data.rssi or "", data.sec or "", data.cap or "") - if ((#bssData + #bssInfo) <= 16*1024) then - bssData = bssData .. bssInfo - else - return bssData - end - end - return bssData -end - ---- Validates whether the pin following the Wifi certification, we need to check if the pin with 8 digits the last digit is the --- the checksum of the others --- @function validatePin --- @param pin the 8 digit pin to be validated --- @return true if the given pin is valid else nil and error is returned -local function validatePin(pin) - -- check whether the last digit of the pin is the the checksum of the others - local accum = 0 - accum = accum + 3*(floor(pin/10000000)%10) - accum = accum + (floor(pin/1000000)%10) - accum = accum + 3*(floor(pin/100000)%10) - accum = accum + (floor(pin/10000)%10) - accum = accum + 3*(floor(pin/1000)%10) - accum = accum + (floor(pin/100)%10) - accum = accum + 3*(floor(pin/10)%10) - accum = accum + (pin%10) - if (accum % 10) == 0 then - return true - end - return nil, "Invalid Pin" -end - ---- validate WPS pin code --- @function validateWPSPIN --- @param pin the pin to be validated --- @return true if the given pin is valid else nil and error is returned -local function validateWPSPIN(pin) - if pin == "" or pin:match("^%d%d%d%d$") then - return true - end - if pin:match("^%d%d%d%d%d%d%d%d$") then - return validatePin(pin) - end - return nil, "Invalid Pin" -end - ---- Retrieves the state of ap --- @function getEnable --- @param key the interface name(eg. wl0) --- @return the value from uci(if present) or from ubus -local function getEnable(key) - local ap = getAPFromIface(key) - local val = getFromUci(ap, "state") - if val ~= "" then - return val - end - return getDataFromAP(key, "admin_state") -end - ---- Retrieves the security mode of ap --- @function getAPMode --- @param key the interface name(eg. wl0) --- @return the value from uci(if present) or from ubus -local function getAPMode(key) - local ap = getAPFromIface(key) - local mode = getFromUci(ap, "security_mode") - if mode == "" then - mode = getDataFromAPSecurity(key, "mode") - end - return mode -end - ---- Retrieves the acl mode of ap --- @function getMACAddressControlEnabled --- @param key the interface name(eg. wl0) --- @return the value "1" if acl mode is enabled or "0" is returned -local function getMACAddressControlEnabled(key) - local ap = getAPFromIface(key) - local aclMode = getFromUci(ap, "acl_mode") - return (aclMode == "lock" or aclMode == "register") and "1" or "0" -end - -local ratePatternMap = { - BasicDataTransmitRates = "([%d.]+)%(b%)", - OperationalDataTransmitRates = "([%d.]+)", - PossibleDataTransmitRates = "([%d.]+)" -} - ---- Retrieves the transmit rates for the given iface --- @function getRates --- @param param the parameter name(BasicDataTransmitRates/OperationalDataTransmitRates/PossibleDataTransmitRates) --- @return the string of comma separated rates -local function getRates(param, key) - local radio = getRadioFromIface(key) - local rateSet = getFromUci(radio, "rateset") - local rates = {} - for rate in rateSet:gmatch(ratePatternMap[param]) do - rates[#rates +1] = rate - end - return table.concat(rates, ",") -end - ---- Retrieves the value of ssid advertisement enabled --- @function getSSIDAdvertisementEnabled --- @param key the interface name --- @return the value from uci(if present) or from ubus -local function getSSIDAdvertisementEnabled(key) - local ap = getAPFromIface(key) - local val = getFromUci(ap, "public") - if val ~= "" then - return val - end - return getDataFromAP(key, "public") -end - ---- Retrieves the state of radio --- @function getRadioEnabled --- @param key the interface name --- @return the value from uci(if present) or from ubus -local function getRadioEnabled(key) - local radio = getRadioFromIface(key) - local state = getFromUci(radio, "state") - if state == "" then - state = getDataFromRadio(radio, "admin_state") - end - return state ~= "" and tostring(state) or "0" -end - ---- Retrieves the security mode of ap --- @function getAuthenticationServiceMode --- @param key the interface name --- @return the value from uci(if present) or from ubus -local function getAuthenticationServiceMode(key) - local ap = getAPFromIface(key) - local mode = getFromUci(ap, "security_mode") - if mode == "" then - mode = getDataFromAPSecurity(key, "mode") - end - return authServiceModeMap[mode] or "LinkAuthentication" -end - ---- set authentication mode for ap --- @function setAuthenticationMode --- @param key the interface name --- @param value the value to be set in the given option -local function setAuthenticationMode(key, value, commitapply) - local ap = getAPFromIface(key) - local secMode = getDataFromAPSecurity(key, "mode") - local val - if not wpaAuthenticationModeMap[secMode] then - return nil, "Authentication mode cannot be set for this capability" - end - if value == "PSKAuthentication" then - if (secMode == "wpa-wpa2" or secMode == "wpa-wpa2-psk") then - val = "wpa-wpa2-psk" - else - val = "wpa2-psk" - end - elseif value == "EAPAuthentication" then - if (secMode == "wpa-wpa2" or secMode == "wpa-wpa2-psk") then - val = "wpa-wpa2" - else - val = "wpa2" - end - end - setOnUci(ap, "security_mode", val, commitapply) - modifyBSPeerNodeAuthentication("security_mode", val, key, commitapply) -end - -local uciACSOptionMap = { - X_000E50_ACSCHMonitorPeriod = "acs_channel_monitor_period", - X_000E50_ACSRescanPeriod = "acs_rescan_period", - X_000E50_ACSRescanDelayPolicy = "acs_rescan_delay_policy", - X_000E50_ACSRescanDelay = "acs_rescan_delay", - X_000E50_ACSRescanDelayMaxEvents = "acs_rescan_delay_max_events", - X_000E50_ACSCHFailLockoutPeriod = "acs_channel_fail_lockout_period", -} - -local ubusACSOptionMap = { - X_000E50_ACSCHMonitorPeriod = "channel_monitor_period", - X_000E50_ACSRescanPeriod = "rescan_period", - X_000E50_ACSRescanDelayPolicy = "rescan_delay_policy", - X_000E50_ACSRescanDelay = "rescan_delay", - X_000E50_ACSRescanDelayMaxEvents = "rescan_delay_max_events", - X_000E50_ACSCHFailLockoutPeriod = "channel_lockout_period", -} - ---- Retrieves the acs info for the given param --- @function getACSOptionValue --- @param the parameter name --- @param key the interface name --- @return the value for the given param from uci(if present) or from ubus -local function getACSOptionValue(param, key) - local radio = getRadioFromIface(key) - local val = getFromUci(radio, uciACSOptionMap[param]) - if val == "" then - val = getDataFromAcs(radio, ubusACSOptionMap[param]) - end - return val -end - ---- retrieves the channel mode(Auto/Manual) --- @function getChannelMode --- @param key the interface name --- @return Auto/Manual based on the mode of the channel -local function getChannelMode(key) - local radio = getRadioFromIface(key) - local channel = getFromUci(radio, "channel") - if channel == "" then - channel = getDataFromRadio(radio, "requested_channel") - end - return (channel == "auto" ) and "Auto" or "Manual" -end - ---- retrieves the transmit power of the radio --- @function getTransmitPower --- @param key the interface name --- @return transmit power value -local function getTransmitPower(key) - local radio = getRadioFromIface(key) - local power = getFromUci(radio, "tx_power_adjust") - return power == "" and "50" or transmitPowerMap[power] or "" -end - -local uciUpgradeOptionMap = { - X_000E50_UpgradeURL = "remote_upgrade_url", - X_000E50_UpgradeCheckPeriod = "remote_upgrade_check_period" -} - -local ubusUpgradeOptionMap = { - X_000E50_UpgradeURL = "url", - X_000E50_UpgradeCheckPeriod = "check_period" -} - ---- Retrieves the remote upgrade information --- @function getUpgradeInfo --- @param the parameter name --- @param key the interface name --- @return the value for the given param from uci(if present) or the value is retrieved from ubus -local function getUpgradeInfo(param, key) - local radio = getRadioFromIface(key) - local val = getFromUci(radio, uciUpgradeOptionMap[param]) - if val == "" then - val = getDataFromRadioRemoteUpgrade(radio, ubusUpgradeOptionMap[param]) or "" - end - return val -end - ---- Sets the device password --- @function setDevicePassword --- @param value the pin to be set --- @param key the interface name -local function setDevicePassword(value, key) - local ap = getAPFromIface(key) - local pin = value - local res, err = validateWPSPIN(value) - if res then - conn:call("wireless.accesspoint.wps", "enrollee_pin", { name = ap, value = pin }) - else - return nil, err - end -end - -local function getUUID(key) - local uuid = getDataFromAP(key, "uuid") - local uuidValue = {} - local pattern = { uuid:sub(1,8), uuid:sub(9,12), uuid:sub(13,16), uuid:sub(17,20), uuid:sub(21,32) } - if uuid ~= "" then - for _, v in ipairs(pattern) do - uuidValue[ #uuidValue + 1] = v - end - end - return table.concat(uuidValue, "-") -end - -local function getConfigurationState(key) - local ap = getAPFromIface(key) - local state = getFromUci(ap, "wsc_state") - if state == "" then - local data = conn:call("wireless.accesspoint.wps", "get", { name = ap }) or {} - state = data[ap] and data[ap].wsc_state or "" - end - return wpsStateMap[state] or "" -end - ---- retrieves either allowed or denied MAC addresses --- @function getMACAddresses --- @param iface the interface name --- @param option the option to get either allowed or denied MAC addresses -local function getMACAddresses(iface, option) - local macList = {} - local result = getFromUci(getAPFromIface(iface), option) - if result ~= "" then - for _,v in ipairs(result) do - macList[#macList+1] = v - end - end - return table.concat(macList, ',') -end - ---- sets either allowed or denied MAC addresses --- @function setMACAddresses --- @param iface the interface name --- @param option the option to set either allowed or denied MAC addresses -local function setMACAddresses(iface, option, value) - local macList = {} - for mac in string.gmatch(value, '([^,]+)') do - if nwCommon.isMAC(mac) then - macList[#macList + 1] = mac - else - return nil, "Invalid MAC address; cannot set" - end - end - setOnUci(getAPFromIface(iface), option, macList, commitapply) -end - ---- returns the supported standards list --- @function convertToList --- @param standards the supported standards -local function convertToList(standards) - local stdList = {} - for std in standards:gmatch("[abgn]c?") do - stdList[#stdList+1] = std - end - return table.concat(stdList, ",") -end - ---- Deletes existing section and creates new section --- @param sectionType section type --- @param sectionName section name -local function createSection(sectionType, sectionName) - deleteOnUci(sectionName, commitapply) - wirelessBinding.sectionname = sectionName - uciHelper.set_on_uci(wirelessBinding, sectionType) -end - ---- Restores the default configuration from wireless-defaults config --- @param sectionType section type --- @param sectionName section name -local function restoreSection(sectionType, sectionName) - createSection(sectionType, sectionName) - local wirelessDefaults = getFromWirelessDefaults(sectionName) - for option, value in pairs(wirelessDefaults) do - if not option:match("^%.") then - setOnUci(sectionName, option, value, commitapply) - end - end -end - ---- Converts channel string into list --- @param str channel string -local function channelStrToList(str) - local list = {} - for channel in str:gmatch("(%d+)") do - list[#list+1] = tonumber(channel) - end - return list -end - ---- Checks if the given channel is present in the given channel list --- @param channels channels list --- @param chan channel number -local function channelExist(channels, chan) - for _, channel in pairs(channels) do - if chan == channel then - return true +function M.getStationDataFromIface(iface, macAddress, option, default) + local ssid = M.getFromWireless(iface, "ssid") + local ap = M.getAPFromIface(iface) + local stationInfo = conn:call("wireless.accesspoint.station", "get", { name = ap }) or {} + if stationInfo[ap] and option then + for mac, data in pairs(stationInfo[ap]) do + if mac == macAddress and data.state:match("Associated") and data.last_ssid == ssid then + return tostring(data[option] or default) + end end + return default end - return false + return stationInfo[ap] or {} end ---- Checks whether DFS channels are enabled --- @param radio the radio name -local function getDFSStatus(radio) - local channels = channelStrToList(getFromUci(radio, "allowed_channels")) - for _, channel in pairs(channels) do - if channelExist(DFSChannels, channel) then - return "1" - end - end - return "0" +function M.getDataFromWDS(key, option, default) + local wdsIdx = key:match("[%S+%_]+%_(wds%S+)%_[%da-fA-F:]+$") + local wdsData = conn:call("wireless.wds", "get", {}) or {} + return wdsData[wdsIdx] and wdsData[wdsIdx][option] and tostring(wdsData[wdsIdx][option]) or default end ---- Adds DFS channels to allowed channels list --- @param channels the allowed channels list -local function addDFSChannels(channels) - for _, dfsChannel in pairs(DFSChannels) do - local exist = channelExist(channels, dfsChannel) - if not exist then - channels[#channels + 1] = tonumber(dfsChannel) - end +function M.commit() + if configChanged then + uciHelper.commit(wirelessBinding) + configChanged = false end - table.sort(channels) - return table.concat(channels, " ") end ---- Removes DFS channels from allowed channels list --- @param channels the allowed channels list -local function removeDFSChannels(channels) - for key, channel in pairs(channels) do - if channelExist(DFSChannels, channel) then - channels[key] = nil - end +function M.revert() + if configChanged then + uciHelper.revert(wirelessBinding) + configChanged = false end - table.sort(channels) - return table.concat(channels, " ") end -local M = {} - -M.getMappings = function(commitapply) - - local wepKeys = {} - local wepKeyIndex = {} - uciHelper.foreach_on_uci({ config = "wireless", sectionname = "wifi-ap" }, function(s) - local name = s[".name"] - wepKeyIndex[name] = 1 - wepKeys[name] = { "", "", "", "" } - end) - - local getWLANDevice = { - WMMEnable = "1", - UAPSDEnable = "0", - WMMSupported = "1", - UAPSDSupported = "0", - Enable = function(mapping, param, key) - return getEnable(key) - end, - Status = function(mapping, param, key) - local status = getDataFromSsid(key, "oper_state") - return tostring(status) == "1" and "Up" or "Disabled" - end, - BSSID = function(mapping, param, key) - return getDataFromSsid(key, "bssid") - end, - MaxBitRate = function(mapping, param, key) - local radio = getRadioFromIface(key) - local bitRate = getDataFromRadio(radio, "max_phy_rate") - return bitRate ~= "" and tostring(tonumber(bitRate)/1000) or "Auto" - end, - Channel = function(mapping, param, key) - local radio = getRadioFromIface(key) - local val = getFromUci(radio, "channel") - if val ~= "" and val ~= "auto" then - return val - end - return getDataFromRadio(radio, "channel") - end, - Name = function(mapping, param, key) - return key:gsub("_remote", "") or "" - end, - SSID = function(mapping, param, key) - local iface = key:gsub("_remote", "") - return getFromUci(iface, "ssid") - end, - TransmitPowerSupported = "25,50,75,100", - TransmitPower = function(mapping, param, key) - return getTransmitPower(key) - end, - BeaconType = function(mapping, param, key) - return beaconTypeMap[getAPMode(key)] or "" - end, - MACAddressControlEnabled = function(mapping, param, key) - return getMACAddressControlEnabled(key) - end, - Standard = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getRadioStandard(radio) - end, - WEPKeyIndex = function(mapping, param, key) - local ap = getAPFromIface(key) - return tostring(wepKeyIndex[ap]) - end, - KeyPassphrase = function(mapping, param, key) - local ap = getAPFromIface(key) - return getFromUci(ap, "wpa_psk_key") - end, - WEPEncryptionLevel = "Disabled,40-bit,104-bit", - BasicEncryptionModes = function(mapping, param, key) - return encryptionModeMap[getAPMode(key)] or "" - end, - BasicAuthenticationMode = "None", - WPAEncryptionModes = "TKIPEncryption", - WPAAuthenticationMode = function(mapping, param, key) - local mode = getDataFromAPSecurity(key, "mode") - return wpaAuthenticationModeMap[mode] or "" - end, - IEEE11iEncryptionModes = "AESEncryption", - IEEE11iAuthenticationMode = function(mapping, param, key) - return wpaAuthenticationModeMap[getAPMode(key)] or "" - end, - PossibleChannels = function(mapping, param, key) - return getChannels(key, nil, "allowed_channels") - end, - BasicDataTransmitRates = function(mapping, param, key) - return getRates(param, key) - end, - OperationalDataTransmitRates = function(mapping, param, key) - return getRates(param, key) - end, - PossibleDataTransmitRates = function(mapping, param, key) - return getRates(param, key) - end, - InsecureOOBAccessEnabled = "1", - BeaconAdvertisementEnabled = "1", - SSIDAdvertisementEnabled = function(mapping, param, key) - return getSSIDAdvertisementEnabled(key) - end, - RadioEnabled = function(mapping, param, key) - return getRadioEnabled(key) - end, - AutoRateFallBackEnabled = "1", - LocationDescription = "", - RegulatoryDomain = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getFromUci(radio, "country") - end, - TotalPSKFailures = "0", - TotalIntegrityFailures = "0", - ChannelsInUse = function(mapping, param, key) - return getChannels(key, nil, "used_channels") - end, - DeviceOperationMode = "InfrastructureAccessPoint", - DistanceFromRoot = "0", - PeerBSSID = "", - AuthenticationServiceMode = function(mapping, param, key) - return getAuthenticationServiceMode(key) - end, - TotalBytesSent = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromRadioStats(radio, "tx_bytes") - end, - TotalBytesReceived = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromRadioStats(radio, "rx_bytes") - end, - TotalPacketsSent = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromRadioStats(radio, "tx_packets") - end, - TotalPacketsReceived = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromRadioStats(radio, "rx_packets") - end, - X_000E50_ACSState = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromAcs(radio, "state") - end, - X_000E50_ACSMode = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromAcs(radio, "policy") - end, - X_000E50_ACSCHMonitorPeriod = function(mapping, param, key) - return getACSOptionValue(param, key) - end, - X_000E50_ACSScanReport = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromAcs(radio, "scan_report") - end, - X_000E50_ACSScanHistory = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromAcs(radio, "scan_history") - end, - X_000E50_ACSRescanPeriod = function(mapping, param, key) - return getACSOptionValue(param, key) - end, - X_000E50_ACSRescanDelayPolicy = function(mapping, param, key) - return getACSOptionValue(param, key):lower() - end, - X_000E50_ACSRescanDelay = function(mapping, param, key) - return getACSOptionValue(param, key) - end, - X_000E50_ACSRescanDelayMaxEvents = function(mapping, param, key) - return getACSOptionValue(param, key) - end, - X_000E50_ACSCHFailLockoutPeriod = function(mapping, param, key) - return getACSOptionValue(param, key) - end, - AutoChannelEnable = function(mapping, param, key) - local radio = getRadioFromIface(key) - local channel = getFromUci(radio, "channel") - return (channel == "auto") and "1" or "0" - end, - X_AutoChannelReselectionTimeout = function(mapping, param, key) - local radio = getRadioFromIface(key) - local acsdata = getDataFromAcs(radio) - return acsdata["rescan_period"] and tostring(acsdata["rescan_period"]) or "0" - end, - X_AutoChannelReselectionEnable = function(mapping, param, key) - local radio = getRadioFromIface(key) - local channel = getFromUci(radio, "channel") - return (channel == "auto") and "1" or "0" - end, - X_WPS_V2_ENABLE = function(mapping, param, key) - local ap = getAPFromIface(key) - return getFromUci(ap, "wps_state") - end, - X_000E50_ACSRescan = "0", - X_000E50_ACSBssList = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getACSBssList(radio) - end, - X_000E50_ChannelMode = function(mapping, param , key) - return getChannelMode(key) - end, - X_000E50_Power = function(mapping, param , key) - local radio = getRadioFromIface(key) - local power = getFromUci(radio, "tx_power_adjust") - return power ~= "" and powerLevelMap[power] or "4" - end, - X_000E50_PowerDefault = "1", - X_000E50_PowerList = "1,2,3,4", - X_000E50_PacketsDropped = function(mapping, param, key) - local radio = getRadioFromIface(key) - local stats = getDataFromRadioStats(radio) - return stats and tostring((tonumber(stats.rx_discards) or 0) + (tonumber(stats.tx_discards) or 0)) or "0" - end, - X_000E50_PacketsErrored = function(mapping, param, key) - local radio = getRadioFromIface(key) - local stats = getDataFromRadioStats(radio) - return stats and tostring((tonumber(stats.rx_errors) or 0) + (tonumber(stats.tx_errors) or 0)) or "0" - end, - X_000E50_RemotelyManaged = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromRadio(radio, "remotely_managed") - end, - X_000E50_UpgradeURL = function(mapping, param, key) - return getUpgradeInfo(param, key) - end, - X_000E50_UpgradeCheckPeriod = function(mapping, param, key) - return getUpgradeInfo(param, key) - end, - X_000E50_UpgradeSWVersion = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromRadioRemoteUpgrade(radio, "software_version") or "" - end, - X_000E50_BandSteerEnable = function(mapping, param, key) - local ap = getAPFromIface(key) - return bandSteerHelper.isBandSteerEnabledByAp(ap) and "1" or "0" - end, - X_000E50_ChannelWidth = function(mapping, param , key) - local radio = getRadioFromIface(key) - return getFromUci(radio, "channelwidth") - end, - X_000E50_ShortGuardInterval = function(mapping, param , key) - local radio = getRadioFromIface(key) - return getFromUci(radio, "sgi") - end, - X_000E50_SpaceTimeBlockCoding = function(mapping, param , key) - local radio = getRadioFromIface(key) - return getFromUci(radio, "stbc") - end, - X_000E50_CyclicDelayDiversity = function(mapping, param , key) - local radio = getRadioFromIface(key) - return getFromUci(radio, "cdd") - end, - X_000E50_ChannelBandwidth = function(mapping, param, key) - local radio = getRadioFromIface(key) - local channelBandWidth = getFromUci(radio, "channelwidth") - return channelBandWidth == "auto" and "Auto" or tostring(channelBandWidth) - end, - X_0876FF_AllowedMACAddresses = function(mapping, param, key) - return getMACAddresses(key, "acl_accept_list") - end, - X_0876FF_DeniedMACAddresses = function(mapping, param, key) - return getMACAddresses(key, "acl_deny_list") - end, - X_0876FF_SupportedFrequencyBands = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromRadio(radio, "supported_frequency_bands") - end, - X_0876FF_OperatingFrequencyBand = function(mapping, param, key) - local radio = getRadioFromIface(key) - return getDataFromRadio(radio, "band") - end, - X_0876FF_SupportedStandards = function(mapping, param, key) - local standards = getDataFromRadio(getRadioFromIface(key), "supported_standards") - return convertToList(standards) - end, - X_0876FF_KeyPassphrase = function(mapping, param, key) - local ap = getAPFromIface(key) - return getFromUci(ap, "wpa_psk_key") - end, - X_0876FF_RestoreDefaultKey = "0", -- always returns "0", If enabled sets the default key from wireless-defaults - X_0876FF_RestoreDefaultWireless = "0", -- always returns "0", If enabled, resets the default configuration of particular interface and ap - X_0876FF_DFSAvailable = function(mapping, param, key) - local radio = getRadioFromIface(key) - if radio == "radio_2G" then - return "0" -- always returns "0", since DFS Channels are supported only for radio_5G - else - return "1" -- always returns "1", since DFS Channels are supported - end - end, - X_0876FF_DFSEnable = function(mapping, param, key) - local radio = getRadioFromIface(key) - if radio == "radio_2G" then - return "0" -- always returns "0", since DFS Channels are supported only for radio_5G - else - return getDFSStatus(radio) - end - end, - X_0876FF_MaxConcurrentDevices = function(mapping, param, key) - local ap = getAPFromIface(key) - return getFromUci(ap, "max_assoc", "0") - end, - } - - local function getallWLANDevice(mapping, key) - local radio = getRadioFromIface(key) - local uciValues = getFromUci(radio) - local ap = getAPFromIface(key) - local radioData = getDataFromRadio(radio) - local apSecData = getDataFromAPSecurity(key) - local acsData = getDataFromAcs(radio) - local ssidData = getDataFromSsid(key) - local radioStats = getDataFromRadioStats(radio) - local ifaceName = key:gsub("_remote", "") or "" - return { - Enable = getEnable(key), - Status = ssidData.oper_state and tostring(ssidData.oper_state) == "1" and "Up" or "Disabled", - BSSID = ssidData.bssid and tostring(ssidData.bssid) or "", - MaxBitRate = radioData.max_phy_rate and tostring(tonumber(radioData.max_phy_rate)/1000) or "Auto", - Channel = radioData.channel and tostring(radioData.channel) or "", - Name = ifaceName, - SSID = getFromUci(ifaceName, "ssid"), - TransmitPowerSupported = "25,50,75,100", - TransmitPower = getTransmitPower(key), - BeaconType = beaconTypeMap[getAPMode(key)] or "", - MACAddressControlEnabled = getMACAddressControlEnabled(key), - Standard = getRadioStandard(radio), - WEPKeyIndex = tostring(wepKeyIndex[ap]), - KeyPassphrase = getFromUci(ap, "wpa_psk_key"), - BasicEncryptionModes = encryptionModeMap[getAPMode(key)] or "", - WPAAuthenticationMode = apSecData.mode and wpaAuthenticationModeMap[apSecData.mode] or "", - IEEE11iAuthenticationMode = wpaAuthenticationModeMap[getAPMode(key)] or "", - OperationalDataTransmitRates = getRates("OperationalDataTransmitRates", key), - PossibleDataTransmitRates = getRates("PossibleDataTransmitRates", key), - BasicDataTransmitRates = getRates("BasicDataTransmitRates", key), - PossibleChannels = getChannels(key, radioData.allowed_channels), - SSIDAdvertisementEnabled = getSSIDAdvertisementEnabled(key), - RadioEnabled = getRadioEnabled(key), - RegulatoryDomain = radioData.country and tostring(radioData.country) or "", - ChannelsInUse = getChannels(key, radioData.used_channels), - AuthenticationServiceMode = getAuthenticationServiceMode(key), - TotalBytesSent = radioStats.tx_bytes and tostring(radioStats.tx_bytes) or "", - TotalBytesReceived = radioStats.rx_bytes and tostring(radioStats.rx_bytes) or "", - TotalPacketsSent = radioStats.tx_packets and tostring(radioStats.tx_packets) or "", - TotalPacketsReceived = radioStats.rx_packets and tostring(radioStats.rx_packets) or "", - X_000E50_ACSState = acsData.state and tostring(acsData.state) or "", - X_000E50_ACSMode = acsData.policy and tostring(acsData.policy) or "", - X_000E50_ACSCHMonitorPeriod = getACSOptionValue("X_000E50_ACSCHMonitorPeriod", key), - X_000E50_ACSScanReport = acsData.scan_report and tostring(acsData.scan_report) or "", - X_000E50_ACSScanHistory = acsData.scan_history and tostring(acsData.scan_history) or "", - X_000E50_ACSRescanPeriod = getACSOptionValue("X_000E50_ACSRescanPeriod", key), - X_000E50_ACSRescanDelayPolicy = getACSOptionValue("X_000E50_ACSRescanDelayPolicy", key):lower(), - X_000E50_ACSRescanDelay = getACSOptionValue("X_000E50_ACSRescanDelay", key), - X_000E50_ACSRescanDelayMaxEvents = getACSOptionValue("X_000E50_ACSRescanDelayMaxEvents", key), - X_000E50_ACSCHFailLockoutPeriod = getACSOptionValue("X_000E50_ACSCHFailLockoutPeriod", key), - AutoChannelEnable = uciValues.channel and uciValues.channel == "auto" and "1" or "0", - X_000E50_ACSBssList = getACSBssList(radio), - X_000E50_ChannelMode = getChannelMode(key), - X_000E50_Power = uciValues.tx_power_adjust and powerLevelMap[uciValues.tx_power_adjust] or "4", - X_000E50_PacketsDropped = tostring((radioStats.rx_discards or 0) + (radioStats.tx_discards or 0)) or "0", - X_000E50_PacketsErrored = tostring((radioStats.rx_errors or 0) + (radioStats.tx_errors + 0)) or "0", - X_000E50_RemotelyManaged = radioData.remotely_managed and tostring(radioData.remotely_managed) or "0", - X_000E50_UpgradeURL = getUpgradeInfo("X_000E50_UpgradeURL", key), - X_000E50_UpgradeCheckPeriod = getUpgradeInfo("X_000E50_UpgradeCheckPeriod", key), - X_000E50_UpgradeSWVersion = getDataFromRadioRemoteUpgrade(radio, "software_version") or "", - X_000E50_BandSteerEnable = bandSteerHelper.isBandSteerEnabledByAp(ap) and "1" or "0", - X_000E50_ChannelWidth = uciValues.channelwidth and uciValues.channelwidth or "", - X_000E50_ShortGuardInterval = uciValues.sgi and uciValues.sgi or "", - X_000E50_SpaceTimeBlockCoding = uciValues.stbc and uciValues.stbc or "", - X_000E50_CyclicDelayDiversity = uciValues.cdd and uciValues.cdd or "", - X_000E50_ChannelBandwidth = uciValues.channelwidth and (uciValues.channelwidth == "auto" and "Auto" or tostring(uciValues.channelwidth)) or "", - X_0876FF_AllowedMACAddresses = getMACAddresses(key, "acl_accept_list"), - X_0876FF_DeniedMACAddresses = getMACAddresses(key, "acl_deny_list"), - X_0876FF_SupportedFrequencyBands = radioData.supported_frequency_bands and tostring(radioData.supported_frequency_bands) or "", - X_0876FF_OperatingFrequencyBand = radioData.band and tostring(radioData.band) or "", - X_0876FF_SupportedStandards = convertToList(radioData.supported_standards), - X_0876FF_KeyPassphrase = getFromUci(ap, "wpa_psk_key"), - X_0876FF_DFSAvailable = (radio == "radio_2G") and "0" or "1", - X_0876FF_DFSEnable = (radio == "radio_2G") and "0" or getDFSStatus(radio), - X_0876FF_MaxConcurrentDevices = getFromUci(ap, "max_assoc", "0"), - } - end - - local setWLANDevice = { - Enable = function(mapping, param, value, key) - local ap = getAPFromIface(key) - setOnUci(ap, "state", value, commitapply) - end, - Channel = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - local allowedChannels = getDataFromRadio(radio, "allowed_channels") - -- set the given channel if the allowed channels list is empty - if allowedChannels == "" then - return setOnUci(radio, "channel", value, commitapply) - end - for channel in allowedChannels:gmatch("(%d+)") do - -- set the given channel if it is present in the allowed channels list - if channel == value then - return setOnUci(radio, "channel", value, commitapply) - end - end - return nil, "Given channel is not allowed" - end, - SSID = function(mapping, param, value, key) - local iface = key:gsub("_remote", "") - if not bandSteerHelper.isBaseIface(iface) and bandSteerHelper.isBandSteerEnabledByIface(iface) then - return nil, "Cannot modify SSID when band steer enabled" - else - if value and value ~= "" then - setOnUci(iface, "ssid", value, commitapply) - modifyBSPeerNodeAuthentication("ssid", value, key, commitapply) - else - return nil, "SSID can not be empty" - end - end - end, - TransmitPower = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - local power = transmitPowerMap[value] - if power then - return setOnUci(radio, "tx_power_adjust", power, commitapply) - end - return nil,"Invalid power value" - end, - BeaconType = function(mapping, param, value, key) - local iface = key:gsub("_remote", "") - if not bandSteerHelper.isBaseIface(iface) and bandSteerHelper.isBandSteerEnabledByIface(iface) then - return nil, "Cannot modify the value when bandsteer is enabled" - end - local ap = getAPFromIface(key) - local supportedSecMode = getDataFromAPSecurity(key, "supported_modes") - local secMode = getFromUci(ap, "security_mode") - local beaconType = beaconTypeMap[value] - if not beaconType then - if value == "Basic" then - if secMode ~= "none" and secMode ~= "wep" then - if supportedSecMode:match("wep") then - beaconType = "wep" - else - beaconType = "none" - end - else - beaconType = secMode - end - else - return nil, "Unsupported BeaconType Value" - end - else - if secMode:match("psk") then - beaconType = beaconType .. "-psk" - end - end - setOnUci(ap, "security_mode", beaconType, commitapply) - modifyBSPeerNodeAuthentication("security_mode", beaconType, key, commitapply) - end, - MACAddressControlEnabled = function(mapping, param, value, key) - local ap = getAPFromIface(key) - local aclmode = "disabled" - if value == "1" then - aclmode = "lock" - end - setOnUci(ap, "acl_mode", aclmode, commitapply) - end, - WEPKeyIndex = function(mapping, param, value, key) - local index = tonumber(value) - local ap = getAPFromIface(key) - if index ~= wepKeyIndex[ap] then - wepKeyIndex[ap] = index - setOnUci(ap, "wep_key", wepKeys[ap][index], commitapply) - end - end, - KeyPassphrase = function(mapping, param, value, key) - local len = value:len() - if (len < 8 or len > 63) then - return nil,"invalid value" - end - local iface = key:gsub("_remote", "") - if not bandSteerHelper.isBaseIface(iface) and bandSteerHelper.isBandSteerEnabledByIface(iface) then - return nil, "Cannot modify KeyPassphrase when bandsteer is enabled" - else - local ap = getAPFromIface(key) - setOnUci(ap, "wpa_psk_key", value, commitapply) - modifyBSPeerNodeAuthentication("wpa_psk_key", value, key, commitapply) - for wepKey in pairs(wepKeys[ap]) do - wepKeys[ap][wepKey] = value - end - end - end, - BasicEncryptionModes = function(mapping, param, value, key) - local iface = key:gsub("_remote", "") - if not bandSteerHelper.isBaseIface(iface) and bandSteerHelper.isBandSteerEnabledByIface(iface) and value == "WEPEncryption" then - return nil, "Cannot modify BasicEncryptionModes when band steer enabled" - end - local ap = getAPFromIface(key) - local supportedModes = getDataFromAPSecurity(key, "supported_modes") - local secMode = getFromUci(ap, "security_mode") - local mode = "" - -- BasicEncryptionModes is effect only when BeaconType is Basic - if secMode == "none" or secMode == "wep" then - if value == "WEPEncryption" then - if not supportedModes:match("wep") then - return nil, "wep is not supported" - end - mode = "wep" - elseif value == "None" then - mode = "none" - end - if mode ~= "" then - setOnUci(ap, "security_mode", mode, commitapply) - modifyBSPeerNodeAuthentication("security_mode", mode, key, commitapply) - end - else - return nil, "Not supported if BeaconType is not 'Basic'" - end - end, - WPAEncryptionModes = function(mapping, param, value, key) - local iface = key:gsub("_remote", "") - if not bandSteerHelper.isBaseIface(iface) and bandSteerHelper.isBandSteerEnabledByIface(iface) then - return nil, "Cannot modify the value when bandsteer is enabled" - end - -- hardcoded to TKIPEncrytption, based on lower layer support. - end, - WPAAuthenticationMode = function(mapping, param, value, key) - local iface = key:gsub("_remote", "") - if not bandSteerHelper.isBaseIface(iface) and bandSteerHelper.isBandSteerEnabledByIface(iface) then - return nil, "Cannot modify the value when bandsteer is enabled" - end - return setAuthenticationMode(key, value, commitapply) - end, - IEEE11iEncryptionModes = function(mapping, param, value, key) - local iface = key:gsub("_remote", "") - if not bandSteerHelper.isBaseIface(iface) and bandSteerHelper.isBandSteerEnabledByIface(iface) then - return nil, "Cannot modify the value when bandsteer is enabled" - end - -- hardcoded to AESEncrytption, based on lower layer support. - end, - IEEE11iAuthenticationMode = function(mapping, param, value, key) - local iface = key:gsub("_remote", "") - if not bandSteerHelper.isBaseIface(iface) and bandSteerHelper.isBandSteerEnabledByIface(iface) then - return nil, "Cannot modify the value when bandsteer is enabled" - end - return setAuthenticationMode(key, value, commitapply) - end, - BasicDataTransmitRates = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - local rateSet = getFromUci(radio, "rateset") - local rateSetVal, err = nwWifi.setBasicRateset(value, rateSet) - if rateSetVal then - setOnUci(radio, "rateset", rateSetVal, commitapply) - else - return nil, err - end - end, - OperationalDataTransmitRates = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - local rateSet = getFromUci(radio, "rateset") - local rateSetVal, err = nwWifi.setOperationalRateset(value, rateSet) - if rateSetVal then - setOnUci(radio, "rateset", rateSetVal, commitapply) - else - return nil, err - end - end, - SSIDAdvertisementEnabled = function(mapping, param, value, key) - local ap = getAPFromIface(key) - setOnUci(ap, "public", value, commitapply) - end, - RadioEnabled = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - setOnUci(radio, "state", value, commitapply) - end, - RegulatoryDomain = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - setOnUci(radio, "country", value, commitapply) - end, - AuthenticationServiceMode = function(mapping, param, value, key) - local mode = authServiceModeMap[value] - local iface = key:gsub("_remote", "") - if not bandSteerHelper.isBaseIface(iface) and bandSteerHelper.isBandSteerEnabledByIface(iface) and mode == "wep" then - return nil, "Can not modify the value to wep when band steer enabled" - end - setOnUci(getAPFromIface(key), "security_mode", mode, commitapply) - modifyBSPeerNodeAuthentication("security_mode", mode, key, commitapply) - end, - X_000E50_ACSCHMonitorPeriod = function(mapping, param, value, key) - setOnUci(getRadioFromIface(key), "acs_channel_monitor_period", value, commitapply) - end, - X_000E50_ACSRescanPeriod = function(mapping, param, value, key) - setOnUci(getRadioFromIface(key), "acs_rescan_period", value, commitapply) - end, - X_000E50_ACSRescanDelayPolicy = function(mapping, param, value, key) - setOnUci(getRadioFromIface(key), "acs_rescan_delay_policy", value, commitapply) - end, - X_000E50_ACSRescanDelay = function(mapping, param, value, key) - setOnUci(getRadioFromIface(key), "acs_rescan_delay", value, commitapply) - end, - X_000E50_ACSRescanDelayMaxEvents = function(mapping, param, value, key) - setOnUci(getRadioFromIface(key), "acs_rescan_delay_max_events", value, commitapply) - end, - X_000E50_ACSCHFailLockoutPeriod = function(mapping, param, value, key) - setOnUci(getRadioFromIface(key), "acs_channel_fail_lockout_period", value, commitapply) - end, - AutoChannelEnable = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - if value == "1" then - value = "auto" - elseif value == "0" then - local channel = getFromUci(radio, "channel") - channel = channel == "" and "auto" or channel - if channel ~= "auto" then - value = channel - else - value = getDataFromRadio(radio, "channel") - end - end - setOnUci(radio, "channel", value, commitapply) - end, - X_AutoChannelReselectionTimeout = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - setOnUci(radio, "acs_rescan_period", value, commitapply) - end, - X_AutoChannelReselectionEnable = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - if value == "1" then - value = "auto" - elseif value == "0" then - local channel = getFromUci(radio, "channel") - channel = channel == "" and "auto" or channel - if channel ~= "auto" then - value = channel - else - value = getDataFromRadio(radio, "channel") - end - end - setOnUci(radio, "channel", value, commitapply) - end, - X_WPS_V2_ENABLE = function(mapping, param, value, key) - local ap = getAPFromIface(key) - setOnUci(ap, "wps_state", value, commitapply) - end, - X_000E50_ACSRescan = function(mapping, param, value, key) - conn:call("wireless.radio.acs", "rescan", { name = getRadioFromIface(key), act = value }) - end, - X_000E50_ChannelMode = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - local channel = getFromUci(radio, "channel") - channel = channel == "" and "auto" or channel - if value == "Manual" then - if channel == "auto" then - channel = getDataFromRadio(radio, "channel") - end - elseif value == "Auto" then - channel = "auto" - end - setOnUci(radio, "channel", channel, commitapply) - end, - X_000E50_Power = function(mapping, param, value, key) - local power = powerLevelMap[value] or "-3" - setOnUci(getRadioFromIface(key), "tx_power_adjust", power, commitapply) - end, - X_000E50_UpgradeURL = function(mapping, param, value, key) - setOnUci(getRadioFromIface(key), "remote_upgrade_url", value, commitapply) - end, - X_000E50_UpgradeCheckPeriod = function(mapping, param, value, key) - setOnUci(getRadioFromIface(key), "remote_upgrade_check_period", value, commitapply) - end, - X_000E50_BandSteerEnable = function(mapping, param, value, key) - if value == "1" then - return enableBandSteer(key, commitapply) - end - return disableBandSteer(key, commitapply) - end, - X_000E50_ChannelWidth = function(mapping, param, value, key) - setOnUci(getRadioFromIface(key), "channelwidth", value, commitapply) - end, - X_000E50_ShortGuardInterval = function(mapping, param , value, key) - setOnUci(getRadioFromIface(key), "sgi", value, commitapply) - end, - X_000E50_SpaceTimeBlockCoding = function(mapping, param , value, key) - local radio = getRadioFromIface(key) - if radio == "radio_2G" then - setOnUci(radio, "stbc", value, commitapply) - else - return nil, "For 5G mode, BCM setting is not available" - end - end, - X_000E50_CyclicDelayDiversity = function(mapping, param , value, key) - setOnUci(getRadioFromIface(key), "cdd", value, commitapply) - end, - X_000E50_ChannelBandwidth = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - value = value == "Auto" and "auto" or tostring(value) - if value == "20/40MHz" and radio == "radio_2G" then - return nil, "Invalid channelwidth for 2.4G" - end - setOnUci(radio, "channelwidth", value, commitapply) - end, - X_0876FF_AllowedMACAddresses = function(mapping, param, value, key) - return setMACAddresses(key, "acl_accept_list", value) - end, - X_0876FF_DeniedMACAddresses = function(mapping, param, value, key) - return setMACAddresses(key, "acl_deny_list", value) - end, - X_0876FF_KeyPassphrase = function(mapping, param, value, key) - local ap = getAPFromIface(key) - setOnUci(ap, "wpa_psk_key", value, commitapply) - end, - X_0876FF_RestoreDefaultKey = function(mapping, param, value, key) - if value == "1" then - local ap = getAPFromIface(key) - local defaultKey = getFromWirelessDefaults(ap, "wpa_psk_key") - setOnUci(ap, "wpa_psk_key", defaultKey, commitapply) - end - end, - X_0876FF_RestoreDefaultWireless = function(mapping, param, value, key) - if value == "1" then - local ifaceName = key:gsub("_remote", "") or "" - local ap = getAPFromIface(key) - -- restore the default configuration for interface - restoreSection("wifi-iface", ifaceName) - -- restore the default configuration for accesspoint - restoreSection("wifi-ap", ap) - end - end, - X_0876FF_DFSEnable = function(mapping, param, value, key) - local radio = getRadioFromIface(key) - if radio == "radio_5G" then - local channels = channelStrToList(getDataFromRadio(radio, "allowed_channels")) - if value == "1" then - channels = addDFSChannels(channels) - else - channels = removeDFSChannels(channels) - end - setOnUci(radio, "allowed_channels", channels, commitapply) - else - return nil, "For 2G mode, DFSEnable is not available" - end - end, - } - - -- WEPKey section - local function entriesWEPKey(mapping, parentkey) - return { parentkey .. "_wep_1", parentkey .. "_wep_2", parentkey .. "_wep_3", parentkey .. "_wep_4" } - end - - local function getWEPKey(mapping, param, key, parentkey) - return getFromUci(getAPFromIface(parentkey), "wep_key") - end - - -- 5,10,13 and 26 characters are allowed for the WEP key - -- 5 and 13 can contain ASCII characters - -- 10 and 26 can only contain Hexadecimal values - local function setWEPKey(mapping, param, value, key, parentKey) - if (#value ~= 5 and #value ~= 10 and #value ~= 13 and #value ~= 26) then - return nil, "WEP key must be 5, 10, 13 or 26 characters long" - end - if (#value == 10 or #value == 26) and (not value:match("^[%x]+$")) then - return nil, "WEP key of length 10 or 26 can only contain the hexadecimal digits" - end - local keyNumber = key:match(".+_wep_(%d+)$") - local ap = getAPFromIface(parentKey) - if tonumber(wepKeyIndex[ap]) == tonumber(keyNumber) then - setOnUci(ap, "wep_key", value, commitapply) - wepKeys[ap][keyNumber] = value - end - end - - -- PSK section - local function entriesPreSharedKey(mapping, parentKey) - local entries = {} - -- The size of this table is fixed with exactly 10 entries - for i = 1, 10 do - entries[i] = parentKey .. "psk_" .. i - end - return entries - end - - local getPreSharedKey = { - PreSharedKey = "", - KeyPassphrase = function(mapping, param, key, parentKey) - return getFromUci(getAPFromIface(parentKey), "wpa_psk_key") - end, - AssociatedDeviceMACAddress = "", - X_0876FF_PreSharedKey = "", - X_0876FF_KeyPassphrase = function(mapping, param, key, parentKey) - return getFromUci(getAPFromIface(parentKey), "wpa_psk_key") - end - } - - local setPreSharedKey = { - KeyPassphrase = function(mapping, param, value, key, parentKey) - local keyNumber = key:match(".*(%d+)") - if keyNumber == "1" then - if not bandSteerHelper.isBaseIface(parentKey) and bandSteerHelper.isBandSteerEnabledByIface(parentKey) then - return nil, "Cannot modify the value when bandsteer is enabled" - else - setOnUci(getAPFromIface(parentKey), "wpa_psk_key", value, commitapply) - modifyBSPeerNodeAuthentication("wpa_psk_key", value, parentKey, commitapply) - end - else - return nil, "Error setting KeyPassphrase! Invalid PreSharedKey!" - end - end, - } - - -- Associated devices section - local getStaDataFromIface = function(iface, macAddress, option, default) - local ssid = getFromUci(iface, "ssid") - local ap = getAPFromIface(iface) - local stationInfo = conn:call("wireless.accesspoint.station", "get", { name = ap }) or {} - if stationInfo[ap] and option then - for mac, data in pairs(stationInfo[ap]) do - if mac == macAddress and data.state:match("Associated") and data.last_ssid == ssid then - return tostring(data[option] or default) - end - end - end - return stationInfo[ap] or {} - end - - -- Function to retrieve information from SSID ubus call - local function getStatus(mapping, param, key) - local ssid = key:match("^(%S+)_sta*") - local ssiddata = ssid and getDataFromSsid(ssid) - local state = ssiddata and ssiddata["oper_state"] - return state and tostring(state) == "1" and "Up" or "Down" - end - - local paramMap = { - AssociatedDeviceAuthenticationState = "state", - LastRequestedUnicastCipher = "encryption", - LastDataTransmitRate = "tx_data_rate_history", - X_LastDataUplinkRate = "tx_phy_rate", - X_000E50_LastDataUplinkRate = "tx_phy_rate", - X_LastDataDownlinkRate = "rx_phy_rate", - X_000E50_LastDataDownlinkRate = "rx_phy_rate", - X_000E50_AssociatedDeviceRSSI = "rssi", - X_000E50_LastDisconnectBy = "last_disconnect_by", - X_000E50_LastDisconnectReason = "last_disconnect_reason", - X_000E50_TxNoAckFailures = "tx_noack_failures", - X_000E50_TxPhyRate = "tx_phy_rate", - X_000E50_RxPhyRate = "rx_phy_rate", - X_000E50_RSSIHistory = "rssi_history", - X_000E50_Capabilities = "capabilities" - } - - local defaultValueMap = { - AssociatedDeviceAuthenticationState = "", - LastRequestedUnicastCipher = "", - LastDataTransmitRate = "", - X_LastDataUplinkRate = "0", - X_000E50_LastDataUplinkRate = "0", - X_LastDataDownlinkRate = "0", - X_000E50_LastDataDownlinkRate = "0", - X_000E50_AssociatedDeviceRSSI = "0", - X_000E50_LastDisconnectBy = "0", - X_000E50_LastDisconnectReason = "0", - X_000E50_TxNoAckFailures = "0", - X_000E50_TxPhyRate = "0", - X_000E50_RxPhyRate = "0", - X_000E50_RSSIHistory = "0", - X_000E50_Capabilities = "" - } - - -- Function to retrieve information from accesspoint.station ubus call - local function getStationInfo(_, param, key, parentKey) - local macAddress = key:match("_sta_([%da-fA-F:]+)$") or "" - return getStaDataFromIface(parentKey, macAddress, paramMap[param], defaultValueMap[param]) - end - - local entriesAssociatedDevice = function(mapping, parentKey) - local entries = {} - local ssid = getFromUci(parentKey, "ssid") - local stationInfo = getStaDataFromIface(parentKey) - if stationInfo then - for mac, data in pairs(stationInfo) do - if data.state:match("Associated") and data.last_ssid == ssid then - entries[#entries + 1] = parentKey .. "_sta_" .. mac - end - end - end - return entries - end - - local getAssociatedDevice = { - AssociatedDeviceMACAddress = function(mapping, param, key) - return key:match("_sta_([%da-fA-F:]+)$") or "" - end, - AssociatedDeviceIPAddress = function(mapping, param, key) - local macAddress = key:match("_sta_([%da-fA-F:]+)$") or "" - local hostData = conn:call("hostmanager.device", "get", { ["mac-address"] = macAddress }) or {} - local ipv4Address = {} - local ipv6Address = {} - for _, data in pairs(hostData) do - ipv4Address = data.ipv4 or {} - ipv6Address = data.ipv6 or {} - end - for _, info in pairs(ipv4Address) do - if info.state and info.state == "connected" then - return info.address or "" - end - end - for _, ip6Info in pairs(ipv6Address) do - if ip6Info.state and ip6Info.state == "connected" then - return ip6Info.address or "" - end - end - return "" - end, - AssociatedDeviceAuthenticationState = function(mapping, param, key, parentKey) - local state = getStationInfo(nil, param, key, parentKey) - return state:match("Authenticated") and "1" or "0" - end, - LastRequestedUnicastCipher = getStationInfo, - LastRequestedMulticastCipher = "", - LastPMKId = "", - LastDataTransmitRate = function(mapping, param, key, parentKey) - local txRate = getStationInfo(nil, param, key, parentKey) - return txRate:match("%d+") or "" - end, - X_Status = getStatus, - X_000E50_Status = getStatus, - X_LastDataUplinkRate = getStationInfo, - X_000E50_LastDataUplinkRate = getStationInfo, - X_LastDataDownlinkRate = getStationInfo, - X_000E50_LastDataDownlinkRate = getStationInfo, - X_000E50_AssociatedDeviceRSSI = getStationInfo, - X_000E50_LastDisconnectBy = getStationInfo, - X_000E50_LastDisconnectReason = getStationInfo, - X_000E50_TxNoAckFailures = getStationInfo, - X_000E50_TxPhyRate = getStationInfo, - X_000E50_RxPhyRate = getStationInfo, - X_000E50_RSSIHistory = getStationInfo, - X_000E50_Capabilities = getStationInfo, - } - - -- WPS Section - local getWPS = { - Enable = function(mapping, param, key) - return getFromUci(getAPFromIface(key), "wps_state") - end, - DevicePassword = "0", - ConfigMethodsSupported = "Label,PushButton", - X_0876FF_DevicePassword = function(mapping, param, key) - return getFromUci(getAPFromIface(key), "wps_ap_pin") - end, - UUID = function(mapping, param, key) - return getUUID(key) - end, - ConfigMethodsEnabled = "PushButton", - ConfigurationState = function(mapping, param, key) - return getConfigurationState(key) - end, - DeviceName = function(mapping, param, key) - return getAPFromIface(key) - end, - SetupLockedState = function(mapping, param, key) - local setupLock = getFromUci(getAPFromIface(key), "wps_ap_setup_locked", "0") - return setupLock == "1" and "LockedByLocalManagement" or "Unlocked" - end, - SetupLock = function(mapping, param, key) - return getFromUci(getAPFromIface(key), "wps_ap_setup_locked", "0") - end, - X_0876FF_PushButton = "0", - X_000E50_PushButton = "0", - } - - local getallWPS = function(mapping, key) - local ap = getAPFromIface(key) - local apData = getFromUci(ap) - local setupLock = apData.wps_ap_setup_locked or "0" - return { - Enable = apData.wps_state and apData.wps_state or "", - X_0876FF_DevicePassword = apData.wps_ap_pin and apData.wps_ap_pin or "", - SetupLock = setupLock, - SetupLockedState = setupLock == "1" and "LockedByLocalManagement" or "Unlocked", - UUID = getUUID(key), - ConfigurationState = getConfigurationState(key), - DeviceName = ap - } - end - - local function triggerWPSPushButton(value) - if value == "1" then - conn:call("wireless", "wps_button", {}) - end - end - - local setWPS = { - Enable = function(mapping, param, value, key) - local ap = getAPFromIface(key) - setOnUci(ap, "wps_state", value, commitapply) - end, - DevicePassword = function(mapping, param, value, key) - return setDevicePassword(value, key) - end, - SetupLock = function(mapping, param, value, key) - setOnUci(getAPFromIface(key), "wps_ap_setup_locked", value, commitapply) - end, - X_0876FF_PushButton = function(mapping, param, value) - triggerWPSPushButton(value) - end, - X_000E50_PushButton = function(mapping, param, value) - triggerWPSPushButton(value) - end, - } - - -- X_Stats Section - local getWifiStats = { - SignalStrength = function(mapping, param, key, parentKey) - local macAddress = key:match("_sta_([%da-fA-F:]+)$") or "" - return getStaDataFromIface(parentKey, macAddress, "rssi") or "0" - end, - Retransmissions = function(mapping, param, key, parentKey) - local macAddress = key:match("_sta_([%da-fA-F:]+)$") or "" - return getStaDataFromIface(parentKey, macAddress, "av_txbw_used") or "0" - end, - } - - -- WLAN Stats section - local function getDataFromSsidStats(key, option) - local iface = key:gsub("_remote", "") - local ssidStats = conn:call("wireless.ssid.stats", "get", { name = iface }) or {} - if option then - return ssidStats[iface] and ssidStats[iface][option] or "" - end - return ssidStats[iface] or {} - end - local wlanStatsMap = { - UnicastPacketsSent = "tx_unicast_packets", - UnicastPacketsReceived = "rx_unicast_packets", - MulticastPacketsSent = "tx_multicast_packets", - MulticastPacketsReceived = "rx_multicast_packets", - BroadcastPacketsSent = "tx_broadcast_packets", - BroadcastPacketsReceived = "rx_broadcast_packets", - DiscardPacketsSent = "tx_discards", - DiscardPacketsReceived = "rx_discards", - ErrorsSent = "tx_errors", - ErrorsReceived = "rx_errors", - } - - local function getWLANStats(mapping, param, key) - if param == "UnknownProtoPacketsReceived" then - return nwCommon.getIntfStats(key, "rxerr", "") - end - return tostring(getDataFromSsidStats(key, wlanStatsMap[param])) - end - - local function getallWLANStats(mapping, key) - local ssidStats = getDataFromSsidStats(key)[key] - return { - UnicastPacketsSent = ssidStats[wlanStatsMap["UnicastPacketsSent"]] and tostring(ssidStats[wlanStatsMap["UnicastPacketsSent"]]) or "", - UnicastPacketsReceived = ssidStats[wlanStatsMap["UnicastPacketsReceived"]] and tostring(ssidStats[wlanStatsMap["UnicastPacketsReceived"]]) or "", - MulticastPacketsSent = ssidStats[wlanStatsMap["MulticastPacketsSent"]] and tostring(ssidStats[wlanStatsMap["MulticastPacketsSent"]]) or "", - MulticastPacketsReceived = ssidStats[wlanStatsMap["MulticastPacketsReceived"]] and tostring(ssidStats[wlanStatsMap["MulticastPacketsReceived"]]) or "", - BroadcastPacketsSent = ssidStats[wlanStatsMap["BroadcastPacketsSent"]] and tostring(ssidStats[wlanStatsMap["BroadcastPacketsSent"]]) or "", - BroadcastPacketsReceived = ssidStats[wlanStatsMap["BroadcastPacketsReceived"]] and tostring(ssidStats[wlanStatsMap["BroadcastPacketsReceived"]]) or "", - DiscardPacketsSent = ssidStats[wlanStatsMap["DiscardPacketsSent"]] and tostring(ssidStats[wlanStatsMap["DiscardPacketsSent"]]) or "", - DiscardPacketsReceived = ssidStats[wlanStatsMap["DiscardPacketsReceived"]] and tostring(ssidStats[wlanStatsMap["DiscardPacketsReceived"]]) or "", - ErrorsSent = ssidStats[wlanStatsMap["ErrorsSent"]] and tostring(ssidStats[wlanStatsMap["ErrorsSent"]]) or "", - ErrorsReceived = ssidStats[wlanStatsMap["ErrorsReceived"]] and tostring(ssidStats[wlanStatsMap["ErrorsReceived"]]) or "", - UnknownProtoPacketsReceived = nwCommon.getIntfStats(key, "rxerr", "") - } - end - - return { - wlan = { - getAll = getallWLANDevice, - get = getWLANDevice, - set = setWLANDevice, - commit = commit, - revert = revert - }, - wps = { - get = getWPS, - getall = getallWPS, - set = setWPS, - commit = commit, - revert = revert - }, - stats = { - getAll = getallWLANStats, - get = getWLANStats, - }, - wepkey = { - entries = entriesWEPKey, - get = getWEPKey, - set = setWEPKey, - commit = commit, - revert = revert - }, - psk = { - entries = entriesPreSharedKey, - get = getPreSharedKey, - set = setPreSharedKey, - commit = commit, - revert = revert, - }, - X_Stats = { - get = getWifiStats, - }, - assoc = { - entries = entriesAssociatedDevice, - get = getAssociatedDevice, - } - } - -end - -M.setBandSteerID = setBandSteerID -M.enableBandSteer = enableBandSteer -M.getAPFromIface = getAPFromIface -M.getFromUci = getFromUci -M.setOnUci = setOnUci -M.commit = commit -M.revert = revert - return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/bandsteerhelper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/bandsteerhelper.lua index 042e04e00..2daa93681 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/bandsteerhelper.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/bandsteerhelper.lua @@ -5,8 +5,9 @@ local ubus = require("ubus") local binding_wireless = {config = "wireless"} local conn = ubus.connect() local strmatch, format = string.match, string.format -local envBinding = { config = "env", sectionname = "var" } local network = require("transformer.shared.common.network") +local envBinding = { config = "env", sectionname = "var" } +local configChanged function M.isBaseIface(iface) return "0" == strmatch(iface, "%d+") @@ -34,9 +35,9 @@ local function getAllSSID() return entries end -local function getSecurityMode(ap) - binding_wireless.sectionname = ap - binding_wireless.option = "security_mode" +local function getWirelessUciValue(sectionName, option) + binding_wireless.sectionname = sectionName + binding_wireless.option = option return uci_helper.get_from_uci(binding_wireless) end @@ -44,6 +45,7 @@ local function setWirelessUciValue(value, sectionName, option, commitapply) binding_wireless.sectionname = sectionName binding_wireless.option = option uci_helper.set_on_uci(binding_wireless, value, commitapply) + configChanged = true end function M.getBandSteerPeerIface(key) @@ -74,7 +76,7 @@ function M.isBandSteerSectionConfigured(bandsteerID) return false, "Please configure band steer section " .. bandsteerID end - for k, _ in pairs(data) do + for k in pairs(data) do if k == bandsteerID then return true end @@ -102,9 +104,7 @@ function M.getBandSteerId(iface) end function M.getApBandSteerId(ap) - binding_wireless.sectionname = ap - binding_wireless.option = "bandsteer_id" - return uci_helper.get_from_uci(binding_wireless) + return getWirelessUciValue(ap, "bandsteer_id") end function M.isBandSteerEnabledByAp(ap) @@ -115,18 +115,6 @@ function M.isBandSteerEnabledByAp(ap) return false end -local function getAllSSID() - local data = conn:call("wireless.ssid", "get", { }) - if data == nil then - return {} - end - local entries = {} - for k in pairs(data) do - entries[#entries + 1] = k - end - return entries -end - --For 5G when bandsteer enabled, the ssid and authentication related option cannot be modified function M.isBandSteerEnabledByIface(iface) local ap = M.getBsAp(iface) @@ -151,7 +139,7 @@ function M.canEnableBandSteer(apKey, apData, iface) return false, "Band steering has already been enabled." end - if "wep" == getSecurityMode(apKey) then + if getWirelessUciValue(apKey, "security_mode") == "wep" then return false, "Band steering cannot be supported in wep mode." end @@ -165,11 +153,11 @@ function M.canEnableBandSteer(apKey, apData, iface) return false, "Band steering switching node does not exist." end - if "1" ~= tostring(peerAPNode.admin_state) then + if tostring(peerAPNode.admin_state) ~= "1" then return false, "Please enable network for band steering switching node firstly." end - if "wep" == getSecurityMode(peerAP) then + if getWirelessUciValue(peerAP, "security_mode") == "wep" then return false, "Band steering cannot be supported in wep mode." end @@ -191,7 +179,7 @@ function M.canDisableBandSteer(apKey, iface) return false, "Band steering switching node does not exist." end - local peerAP, peerAPNode = M.getBsAp(peerIface) + local peerAP = M.getBsAp(peerIface) if not peerAP then return false, "Band steering switching node does not exist." end @@ -199,35 +187,23 @@ function M.canDisableBandSteer(apKey, iface) return true end -function M.setBandSteerPeerIfaceSSIDByLocalIface(baseiface, needsetiface, oper) +function M.setBandSteerPeerIfaceSSIDByLocalIface(baseiface, needsetiface, oper, commitapply) if "1" == oper then --to get the baseiface ssid - binding_wireless.sectionname = baseiface - binding_wireless.option = "ssid" - local baseifacessid = uci_helper.get_from_uci(binding_wireless) + local baseifacessid = getWirelessUciValue(baseiface, "ssid") if "" ~= baseifacessid then - binding_wireless.sectionname = needsetiface - binding_wireless.option = "ssid" - uci_helper.set_on_uci(binding_wireless, baseifacessid, commitapply) + setWirelessUciValue(baseifacessid, needsetiface, "ssid", commitapply) end else - binding_wireless.sectionname = needsetiface - binding_wireless.option = "ssid" envBinding.option = "commonssid_suffix" local suffix = uci_helper.get_from_uci(envBinding) - uci_helper.set_on_uci(binding_wireless, uci_helper.get_from_uci(binding_wireless) .. suffix, commitapply) + setWirelessUciValue(getWirelessUciValue(needsetiface, "ssid") .. suffix, needsetiface, "ssid", commitapply) end - - return end function M.setBandSteerPeerIfaceSSIDValue(needsetiface, value) - binding_wireless.sectionname = needsetiface - binding_wireless.option = "ssid" - uci_helper.set_on_uci(binding_wireless, value, commitapply) - - return + setWirelessUciValue(value, needsetiface, "ssid") end local function getBandSteerRelatedNode(apKey, apNode) @@ -248,6 +224,14 @@ local function getBandSteerRelatedNode(apKey, apNode) end end +--to set the authentication related content +local function setBandSteerPeerApAuthentication(baseap, needsetap) + local value = getWirelessUciValue(baseap, "security_mode") + setWirelessUciValue(value, needsetap, "security_mode") + value = getWirelessUciValue(baseap, "wpa_psk_key") + setWirelessUciValue(value, needsetap, "wpa_psk_key") +end + local function setBandSteerID(ap, bspeerap, bsid, commitapply) setWirelessUciValue(bsid, ap, "bandsteer_id", commitapply) setWirelessUciValue(bsid, bspeerap, "bandsteer_id", commitapply) @@ -269,7 +253,6 @@ local function disableBandSteer(key, commitapply) --to reset the ssid M.setBandSteerPeerIfaceSSIDByLocalIface(baseiface, needsetiface, "0", commitapply) - return true end --1\Only the admin_state enabled, then enable bandsteering @@ -293,7 +276,6 @@ local function enableBandSteer(key, commitapply) setBandSteerID(baseap, needsetap, bsid) M.setBandSteerPeerIfaceSSIDByLocalIface(baseiface, needsetiface, "1", commitapply) setBandSteerPeerApAuthentication(baseap, needsetap) - return true end function M.setBandSteerValue(value, key, commitapply) @@ -309,4 +291,18 @@ function M.setBandSteerValue(value, key, commitapply) return bandSteer end +function M.uci_commit() + if configChanged then + uci_helper.commit(binding_wireless) + configChanged = false + end +end + +function M.uci_revert() + if configChanged then + uci_helper.revert(binding_wireless) + configChanged = false + end +end + return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/bulkhelper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/bulkhelper.lua new file mode 100644 index 000000000..f5348025a --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/bulkhelper.lua @@ -0,0 +1,376 @@ +local require = require +local setmetatable = setmetatable +local ipairs = ipairs +local tostring = tostring +local format = string.format +local match = string.match +local insert = table.insert +local remove = table.remove +local sort = table.sort + +local uci = require('transformer.mapper.ucihelper') +local duplicator = require('transformer.mapper.multiroot').duplicate + +local mt = { __index = function() return "" end } +local M = {} +M.entries = {} + +local ProfileMap = {} +ProfileMap.__index = ProfileMap + +-- iterate over all uci interfaces calling a function for each +-- @param f [function] the function to call +local uci_profiles = { config="bulkdata", sectionname="profile"} +local function foreach_profile(f) + return uci.foreach_on_uci(uci_profiles, f) +end + +local boolean_table = setmetatable({ + ['0'] = 'false', + ['1'] = 'true', + ['true'] = '1', + ['false'] = '0' +}, mt) + +-- convert different format boolean types and server for configuration and mapping implementation +-- @param value [string] the value to be converted +local function convert_boolean(value) + return boolean_table[value] +end +M.convert_boolean = convert_boolean + +-- sorted by number +local function nsort(a,b) + local na = tonumber(match(a, ".*_(%d+)$")) + local nb = tonumber(match(b, ".*_(%d+)$")) + return na < nb +end + +--- retrieve keys for bulk data profile map for transformer +-- @param parentkey [string] the key of the parent instance. +-- @returns a list (table) of keys to pass to transformer +function ProfileMap:getProfileKeys(parentkey) + self.keys = {} + M.entries = {} + foreach_profile(function(s) + self.keys[#self.keys+1] = s['.name'] + M.entries[s['.name']] = s + end) + sort(self.keys, nsort) + return self.keys +end + +--- retrieve keys for parament maps for transformer +-- @param id [string] the identification for parameters +-- @param parentkey [string] the key of the parent instance. +-- @returns a list (table) of keys to pass to transformer +function ProfileMap:getParamKeys(id, parentkey) + self.keys = {} + if type(M.entries[parentkey][id]) == "table" then + for i,_ in ipairs(M.entries[parentkey][id]) do + local key = format("%s|%s|%d", parentkey, id, #self.keys+1) + self.keys[#self.keys+1] = key + end + end + return self.keys +end + +--- retrieve object for from saved objects in entries +-- @param key [string] the key of the current instance and key is also the index of objects +-- @returns an object (table) of saved all the profile items, if the key is invalid, return one empty table +-- @returns an index identify (string) served for the list of parameters +local function getObject(key) + local keyid, indexid = key:match("(.*)|.*|(.*)") + if keyid and indexid then + indexid = tonumber(indexid) + else + keyid = key + end + return M.entries[keyid] or {}, indexid +end + + +--- get all parameter's values for the given mapping +-- @param mapping [table] a mapping +-- @param key [string] the key of the current instance +-- @returns a list (table) of parameter values +function M.getall(mapping, key) + local data = {} + local object, indexid = getObject(key) + local map = mapping._profile.map.get + local parameters = mapping.objectType.parameters + for p in pairs(parameters) do + if (map[p]) then + if type(map[p]) == "function" then + data[p] = map[p](object) + elseif type(map[p]) == "table" then + -- for parameters or http_uri + if not data.Name or not data.Reference then + local ref, name = match((object[map[p][1]] and object[map[p][1]][indexid]) or "", "(.*)|(.*)") + data.Name = name or "" + data.Reference = ref or "" + end + else + data[p] = object[map[p]] + end + end + data[p] = data[p] or parameters[p].default or "" + end + return data +end + +--- get one parameter's value for the given mapping and given parameter name +-- @param mapping [table] a mapping +-- @param param [string] the parameter name to be gotten +-- @param key [string] the key of the current instance +-- @returns a value (string) of parameter +function M.get(mapping, param, key) + local object, indexid = getObject(key) + local map = mapping._profile.map.get + local parameters = mapping.objectType.parameters + + if (map[param]) then + if type(map[param]) == "function" then + return map[param](object) + elseif type(map[param]) == "table" then + -- for parameters or http_uri + local ref, name = match((object[map[param][1]] and object[map[param][1]][indexid]) or "", "(.*)|(.*)") + if param == "Name" then + return name + else + return ref + end + else + return object[map[param]] or parameters[param].default or "" + end + end +end + +--- set one parameter's value for the given mapping and given parameter name +-- @param mapping [table] a mapping +-- @param param [string] the parameter name to be set +-- @param value [string] the parameter value to be set +-- @param key [string] the key of the current instance +-- @returns true (boolean) if setting successful, or nil and error message (string) if failed +function M.set(mapping, param, value, key) + local object, indexid = getObject(key) + local map = mapping._profile.map.set + local commitapply = mapping._profile.commitapply + local transactions = mapping._profile.transactions + local binding = {config = "bulkdata"} + + binding.sectionname = object['.name'] + if map[param] then + if type(map[param]) == "function" then + local ok, msg = map[param](binding, value) + if not ok then + return nil, msg + end + elseif type(map[param]) == "string" then + binding.option = map[param] + uci.set_on_uci(binding, value, commitapply) + elseif type(map[param]) == "table" then + -- for parameters or http_uri + binding.option = map[param][1] + uci.delete_on_uci(binding, commitapply) + local ref, name = match(object[binding.option][indexid] or "", "(.*)|(.*)") + if param == "Name" then + name = value + else + ref = value + end + object[binding.option][indexid] = format("%s|%s", ref or "", name or "") + uci.set_on_uci(binding, object[binding.option], commitapply) + end + transactions[binding.config] = true + return true + else + return nil, "Invalid or not supported parameter" + end +end + +--- generate a profile name according to the 1..N sequence first found first used +-- @param max [int] the max number of profiles allowed to be existed +-- @returns profile name (string), the format is "profile_N" +local function get_profile_name(max) + local all = {} + local id + foreach_profile(function(s) + id = tonumber(s['.name']:match("(%d+)$")) + all[id] = id + end) + + return #all < max and format("profile_%d", #all+1) or nil +end + +--- add a new profile for the given mapping +-- @param mapping [table] a mapping +-- @param parentkey [string] the key of the parent instance. +-- @returns new profile key (string) +function M.add_profile(mapping, parentkey) + local binding = {config = "bulkdata"} + local commitapply = mapping._profile.commitapply + local transactions = mapping._profile.transactions + + binding.sectionname = "global" + binding.option = "max_num_profiles" + local max = uci.get_from_uci(binding) + + local name = get_profile_name(tonumber(max)) + if name == nil then + return nil, "The profile's instance number has reached the maximize and can't add any new!" + end + binding.sectionname = name + binding.option = nil + uci.set_on_uci(binding, "profile", commitapply) + transactions[binding.config] = true + return binding.sectionname +end + +--- delete an existed profile for the given mapping +-- @param mapping [table] a mapping +-- @param key [string] the key of the current instance to be deleted +-- @param parentkey [string] the key of the parent instance. +-- @returns executed status (boolean) +function M.delete_profile(mapping, key, parentkey) + local binding = {config = "bulkdata"} + local commitapply = mapping._profile.commitapply + local transactions = mapping._profile.transactions + binding.sectionname = key + binding.option = nil + uci.delete_on_uci(binding, commitapply) + transactions[binding.config] = true + return true +end + +--- add a new element for the given mapping and profile +-- @param mapping [table] a mapping +-- @param parentkey [string] the key of the parent instance. +-- @param option [string] the parameter option to be saved in uci +-- @returns the index (string) be added in the list +local function add_elements(mapping, parentkey, option) + local commitapply = mapping._profile.commitapply + local transactions = mapping._profile.transactions + local binding = {config = "bulkdata"} + + binding.sectionname = parentkey + binding.option = option + local elements = uci.get_from_uci(binding) + if type(elements) ~= "table" then + elements = {"|"} + else + insert(elements, "|") + uci.delete_on_uci(binding, commitapply) + end + uci.set_on_uci(binding, elements, commitapply) + transactions[binding.config] = true + return tostring(#elements) +end + +--- delete an existed element for the given mapping and profile +-- @param mapping [table] a mapping +-- @param key [string] the key of the current instance to be deleted +-- @param parentkey [string] the key of the parent instance. +-- @param option [string] the parameter option to be saved in uci +-- @returns true (boolean) if setting successful, or nil and error message (string) if failed +local function delete_elements(mapping, key, parentkey, option) + local commitapply = mapping._profile.commitapply + local transactions = mapping._profile.transactions + local binding = {config = "bulkdata"} + binding.sectionname = parentkey + binding.option = option + local elements = uci.get_from_uci(binding) + if type(elements) ~= "table" then + return nil, "No elements!" + end + local id = key:match(".*|.*|(.*)") + remove(elements, tonumber(id)) + uci.delete_on_uci(binding, commitapply) + if #elements > 0 then + uci.set_on_uci(binding, elements, commitapply) + end + transactions[binding.config] = true + return true +end + +--- add a new parameter for the given mapping and profile +-- @param option [string] the parameter option value +-- @returns function [function] to be used by mapping +function M.add_parameter(option) + return function(mapping, parentkey) + if reference == "parameters" then + local binding = {config = "bulkdata"} + binding.sectionname = "global" + binding.option = "max_num_parameters" + local max = uci.get_from_uci(binding) + binding.sectionname = parentkey + binding.option = reference + local elements = uci.get_from_uci(binding) + if #elements >= tonumber(max) then + return nil, "The parameter's instance number has reached the maximize and can't add any new!" + end + end + local key = add_elements(mapping, parentkey, option) + return format("%s|%s|%d", parentkey, option, key) + end +end + +--- delete an existed parameter for the given mapping and profile +-- @param option [string] the parameter option value +-- @returns function [function] to be used by mapping +function M.delete_parameter(option) + return function(mapping, key, parentkey) + return delete_elements(mapping, key, parentkey, option) + end +end + +--- perform given action on all outstanding transaction +-- @param profile [table] a connection object +-- @param action [function] the function to call for each transaction +local function finalize_transactions(profile, action) + local binding = {} + for config in pairs(profile.transactions) do + binding.config = config + action(binding) + end + profile.transactions = {} +end + +--- commit the pending transactions +-- @param mapping [table] the mapping +function M.commit(mapping) + finalize_transactions(mapping._profile, uci.commit) +end + +--- revert the pending transactions +-- @param mapping [table] the mapping +function M.revert(mapping) + finalize_transactions(mapping._profile, uci.revert) +end + +--- register the mapping to IGD and Device +-- @param mapping [table] the mapping +-- @param register [function] the register function +function M.register(mapping, register) + local duplicates = duplicator(mapping, "#ROOT", {"InternetGatewayDevice", "Device"}) + for _, dupli in ipairs(duplicates) do + register(dupli) + end +end + +--- create a new profile map for the given mapping +-- @param mapping [table] a mapping +-- @param map [table] the map format is {get={}, set={}} +-- @param commitapply [] a commit and apply context +function M.SetProfileMap(mapping, map, commitapply) + local profile = { + map = map, + commitapply = commitapply, + transactions = {} + } + + mapping._profile = setmetatable(profile, ProfileMap) + return profile +end + +return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/common/list.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/common/list.lua new file mode 100644 index 000000000..5303ce46e --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/common/list.lua @@ -0,0 +1,48 @@ +local M = {} +local type = type + +--- Copy a table with some limitations like copying only values and not metatables. +-- @table tbl The table to be copied into other table. +-- @treturn table result The table copied from the given table. +local function copyTable(tbl) + local result = {} + for key, value in pairs(tbl) do + if type(value) == "table" then + result[key] = copyTable(value) + else + result[key] = value + end + end + return result +end + +M.copyTable = copyTable + +--- Converts the given comma separated string into table. +-- @string str The comma separated string which needs to be converted to table. +-- @treturn table The array containing the separated values of the given string. +function M.csvSplit(str) + local result = {} + for val in str:gmatch("([^,]*),?") do + result[#result + 1] = val + end + if not str:match(",$") then + result[#result] = nil + end + return result +end + +--- Ensures the given value is a list. +-- @string/table value The comma separated string or a table which needs to be converted to table. +-- @treturn table The array containing the separated values of the given string. +function M.ensureList(value) + if not value then + return {""} + end + if type(value) ~= "table" then + return M.csvSplit(value) + end + return value +end + +return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/common/network.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/common/network.lua index 03de82b43..9f2fc6f0e 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/common/network.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/common/network.lua @@ -1,4 +1,9 @@ local M = {} +local pairs = pairs +local ipairs = ipairs +local tonumber = tonumber +local string= string +local type = type local uciHelper = require("transformer.mapper.ucihelper") local conn = require("transformer.mapper.ubus").connect() @@ -6,34 +11,66 @@ local dhcp = require("transformer.shared.dhcp") local bit = require("bit") local math = require("math") local posix = require("tch.posix") +local io = require("io") local dhcpBinding = { config = "dhcp" } local ethernetBinding = { config = "ethernet", sectionname = "mapping" } local firewallZoneBinding = { config = "firewall", sectionname = "zone" } local networkBinding = { config = "network" } +local wirelessBinding = { config = "wireless" } +local binding = {} local AF_INET = posix.AF_INET local emptyTable = {} -local pairs, tonumber, string = pairs, tonumber, string +local open = io.open local integratedQtnMAC = string.lower(uciHelper.get_from_uci({config = "env", sectionname = "var", option = "qtn_eth_mac"})) +local lxcMAC = string.lower(uciHelper.get_from_uci({config = "env", sectionname = "var", option = "local_eth_mac_lxc"})) +local match, find, sub = string.match, string.find, string.sub + +local intfType, macAddr, keyValue, remotelyManaged + +--- Load uci info for firewall zones +-- @returns table keyed on zone name. Values are the uci sections. +function M.firewallZones() + local zones = {} + uciHelper.foreach_on_uci(firewallZoneBinding, function(zone) + if type(zone.network) ~= "table" then + zone.network = {zone.network} + end + zones[zone.name or zone['.name']] = zone + end) + return zones +end +local firewallZones = M.firewallZones + +--- the firewall zone of the given interface +-- @string intf the interface name +-- @tparam table the zones as returned by firewalZones, or nil in which case the function +-- retrieves the data on its own +-- @return the zone data if found or nil if the interface is in none of the given zones +function M.firewallZoneForInterface(intf, zones) + zones = zones or firewallZones() + for _, zone in pairs(zones) do + for _, network in ipairs(zone.network) do + if network==intf then + return zone + end + end + end +end -- Returns the list of interfaces based on the interface type. local function getInterfacesByType(interfaceType) local interfaces = {} local expectedWan = interfaceType == "wan" - -- iterate over all firewall zones. If zone wan flag is set/not set, add interfaces to result - uciHelper.foreach_on_uci(firewallZoneBinding, function(s) - local wanIntf = s.wan == "1" + for _, zone in pairs(firewallZones()) do + local wanIntf = zone.wan == "1" if wanIntf == expectedWan then - if type(s.network) == "table" then - for _, v in pairs(s.network) do - interfaces[v] = true - end - else - interfaces[s.name] = true + for _, intf in pairs(zone.network) do + interfaces[intf] = true end end - end) + end return interfaces end @@ -99,6 +136,12 @@ local function overlapCheck(network, poolname, ipStart, limit, ipMax) return true end +-- Retrieves the data for the given ubus call. +local function fetchUbusData(call, method, name) + local data = conn:call(call, method, { name = name }) or {} + return data[name] or {} +end + --- Retrieves the list of wan interfaces. -- @treturn table The array containing wan interfaces. function M.getWanInterfaces() @@ -111,6 +154,20 @@ function M.getLanInterfaces() return getInterfacesByType("lan") end +-- Retrieves the connected host information based on the MAC address. +-- @string mac The MAC address for which the information to be retrieved. +-- @treturn info The table containing the information of the connected host. +-- @return string dev The device name of the connected host. +function M.getHostDataByMAC(mac) + local data = conn:call("hostmanager.device", "get", { }) or {} + for dev, info in pairs(data) do + if info and info["mac-address"] == mac then + return info, dev + end + end + return {}, "" +end + --- Retrieves the list of mac-addresses or the device names of the connected hosts. -- @table hostData The table containing the information of the connected hosts. -- @function getInfo It specifies the function to retrieve information of the hosts and @@ -121,7 +178,7 @@ function M.getHostInfo(hostData, getInfo) local data = hostData or conn:call("hostmanager.device", "get", emptyTable) or emptyTable local lanInterfaces = M.getLanInterfaces() for dev, info in pairs(data) do - if lanInterfaces[info.interface] and info["mac-address"] ~= integratedQtnMAC and checkHostTechnology(info) then + if lanInterfaces[info.interface] and info["mac-address"] ~= integratedQtnMAC and info["mac-address"] ~= lxcMAC and checkHostTechnology(info) then hosts[#hosts+1] = getInfo and getInfo(info) or dev end end @@ -131,9 +188,11 @@ end --- Triggers the ACS rescan on the particular radio if radio is specified else on all the radios. -- @string radio Specifies the radio name on which the acs rescan should be triggered -- and if radio is nil, then rescan is triggered on all the radios. -function M.triggerACSRescan(radio) +function M.triggerACSRescan(radio, act_val) + local act_val = tonumber(act_val) or 0 if radio then - conn:call("wireless.radio.acs", "rescan", { name = radio, act = 0 }) + conn:call("wireless.radio.acs", "rescan", { name = radio, act = act_val }) + else conn:call("wireless.radio.acs", "rescan", {}) end @@ -163,7 +222,7 @@ function M.setDHCPMinAddress(interface, address, commitapply) return nil, "Invalid IP Address" end local data = dhcp.parseDHCPData(interface) - if newStart < data.ipMin or newStart >= data.ipEnd then + if newStart < data.ipMin or newStart > data.ipEnd then return nil, "Invalid start address" end local newValue = newStart - data.network @@ -239,8 +298,7 @@ end -- @string devname The device name for which the information to be retrieved. -- @treturn table The array containing the host information like mac-address, ip address, etc. function M.getHostDataByName(devname) - local data = conn:call("hostmanager.device", "get", { name = devname }) or {} - return data[devname] or {} + return fetchUbusData("hostmanager.device", "get", devname) end --- Converts the given epoch time to ISO 8601 format for combined date @@ -253,11 +311,31 @@ function M.convertEpochToISO(time) end --- Retrieves the accesspoint information. --- @string devname The accesspoint name name for which the information to be retrieved. +-- @string apName The accesspoint name name for which the information to be retrieved. -- @treturn table The array containing the accesspoint information like state, ssid, etc. function M.getAccessPointInfo(apName) - local data = conn:call("wireless.accesspoint", "get", { name = apName }) or {} - return data[apName] or {} + return fetchUbusData("wireless.accesspoint", "get", apName) +end + +--- Retrieves the station information connected to the given accesspoint. +-- @string apName The accesspoint name for which the station information to be retrieved. +-- @treturn table The array containing the station information like state, ssid, etc. +function M.getAccessPointStationInfo(apName) + return fetchUbusData("wireless.accesspoint.station", "get", apName) +end + +--- Retrieves the radio information. +-- @string radio The radio for which the information to be retrieved. +-- @treturn table The array containing the radio information like state, ssid, etc. +function M.getRadioInfo(radio) + return fetchUbusData("wireless.radio", "get", radio) +end + +--- Retrieves the ssid stats information. +-- @string ssid The ssid name for which the information to be retrieved. +-- @treturn table The array containing the ssid stats information. +function M.getSSIDStats(ssid) + return fetchUbusData("wireless.ssid.stats", "get", ssid) end --- Converts the given string to hex value. @@ -267,4 +345,201 @@ function M.stringToHex(strValue) return strValue and (strValue:gsub('.', function (c) return string.format('%02x', string.byte(c)) end)) end +--- Retrieves whether the given object is IGD or Device2. +-- @table mapping The mapping table containing the object information(eg. name, parameters, etc.). +-- @treturn string Returns igd for IGD object name or device2 for thr Device objects. +function M.getMappingType(mapping) + local objName = mapping.objectType and mapping.objectType.name or "" + if objName:match("^InternetGatewayDevice") then + return "igd" + elseif objName:match("^Device") then + return "device2" + end +end + +--- Retrieves the accesspoint name for the given interface. +-- @string iface The wifi-iface name. +-- @treturn ap The accesspoint for the given wifi interface. +function M.getAPForIface(iface) + local ap = "" + wirelessBinding.sectionname = "wifi-ap" + uciHelper.foreach_on_uci(wirelessBinding, function(s) + if s.iface == iface then + ap = s[".name"] + return false + end + end) + return ap +end + +--- Retrieves the first line from the given input file. +-- @string filename The file in which the first line to be retrieved. +-- @treturn string result The first line of the given file else empty string in case of any error. +function M.getFirstLine(filename) + local result = "" + local fd = open(filename) + if fd then + result = fd:read("*l") or "" + fd:close() + end + return result +end + +--- Executes the given command and returns the result. +-- @string cmd The command to be executed. +-- @treturn string result The output of the given command. +function M.executeCommand(cmd) + local result = "" + local fp = io.popen(cmd) + if fp then + result = fp:read("*l") or "" + fp:close() + end + return result +end + +--- Retrieves the list of reboot reasons. +-- @treturn table rebootreasons The array containing the reboot reasons. +function M.getRebootReasons() + local rebootreasons = {} + local fd = io.open("/lib/functions/reboot_reason.sh") + if fd then + for line in fd:lines() do + -- Read until comment containing 'REASONS_END' is found. + if line:match("#.*REASONS_END") then + break + end + -- Known reboot reasons have format like 'PWR=0'. + local reason = line:match('(%w+)=%d+') + if reason then + -- Exclude future-use reserved reasons. + if not reason:match("RES") then + rebootreasons[#rebootreasons + 1] = reason + end + end + end + fd:close() + else + -- On platforms which are not reboot-reason enabled (LTE, Qualcomm): + -- Provide a default set of reasons which are called from the code, + -- since GUI and CWMP code is taken along on all platforms, + -- and hence set e.g. "rpc.system.reboot" to "GUI". + rebootreasons = {"GUI", "CWMP", "STS", "CLI"} + end + return rebootreasons +end + +--- Retrieves the interface type and mac of the external wifi. +-- @treturn string intfType of the external wifi. +-- @treturn string macAddr of the external wifi +function M.getExternalWifiIntfType() + local ssid + if not keyValue and not remotelyManaged then + local wirelessRadio = conn:call("wireless.radio", "get" , {}) + if wirelessRadio then + for key, value in pairs(wirelessRadio) do + if value.remotely_managed == 1 and value.integrated_ap == 1 then + keyValue = key + remotelyManaged = true + end + end + end + end + if keyValue then + ssid = conn:call("wireless.ssid", "get", {}) + if not ssid then + return intfType, macAddr + end + --get radio-remote information + local radioremoteMAC, radioremoteIntf + local radioremote = conn:call("wireless.radio.remote", "get", { name = keyValue }) + if radioremote then + local _, v = next(radioremote) + if v then + radioremoteMAC = v.macaddr + radioremoteIntf = v.ifname + end + end + for _, info in pairs(ssid) do + if info.radio == keyValue then + local result = ("0x" .. string.sub(info.mac_address,(#info.mac_address -1))) + local obtMacAddr = string.format("%02x",((result -1)%256)) + local mac_prefix = string.sub(info.mac_address, 1, (#info.mac_address -2)) + macAddr = string.format("%s%s", mac_prefix, obtMacAddr) + if integratedQtnMAC == macAddr then + break + end + end + -- In case the ssid is remotely managed, integrated and disabled, the received macaddress via wireless.ssid is 0, + -- then we need to get the mac via wireless.radio.remote + if ((macAddr == "ff:ff:ff:ff:ff:ff" ) and keyValue ) then + if not intfType or not macAddr then + macAddr = radioremoteMAC + intfType = radioremoteIntf + end + end + end + if not intfType then + local hosts = conn:call("hostmanager.device", "get", {}) or {} + for _, info in pairs(hosts) do + if info["mac-address"] == macAddr then + intfType= info.l2interface + end + end + end + --Bridged to Routed mode change will not have host for Quantenna, intfType will not be available. + if not intfType and macAddr == radioremoteMAC then + intfType = radioremoteIntf + end + else + macAddr = integratedQtnMAC + end + return intfType, macAddr +end + +-- Checks whether the value is a valid domain name. +-- This function satisfies RFC1033 and RFC1035 so this function is not suitable for hostname validation. +function M.domainValidation(value) + local count, currLabelIndex = 0, 0 + if type(value) ~= "string" or #value == 0 or #value > 255 then + return nil, "Domain name cannot be empty or greater than 255 characters or non string value" + end + repeat + count = count + 1 + currLabelIndex = find(value, ".", count, true) + local label = sub(value, count, currLabelIndex) + local strippedLabel = match(label, "[^%.]*") + if strippedLabel ~= nil then + if #strippedLabel == 0 or #strippedLabel > 63 then + return nil, "Label should not be empty or more than 63 characters" + end + local correctLabel = match(strippedLabel, "^[a-zA-z0-9][a-zA-Z0-9-_]*[a-zA-Z0-9]") + if #strippedLabel == 1 then + if not match(strippedLabel, "[a-zA-Z0-9]") then + return nil, "Label within domain name has invalid syntax" + end + elseif strippedLabel ~= correctLabel then + return nil, "Label within domain name has invalid syntax" + end + end + count = currLabelIndex + until not currLabelIndex + return true +end + +function M.getNewSection(config, section) + local sectionName + local sectionList = {} + binding.config = config + binding.sectionname = section + uciHelper.foreach_on_uci(binding, function(s) + sectionList[s[".name"]] = true + end) + repeat + sectionName = uciHelper.generate_key() + sectionName = section .. "_" .. string.sub(sectionName, -4) + until not sectionList[sectionName] + return sectionName +end + return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/custo/wifisecurity.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/custo/wifisecurity.lua index cf5239cb6..63c5ccacd 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/custo/wifisecurity.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/custo/wifisecurity.lua @@ -13,7 +13,8 @@ local function get_default_option_names(ap) return { wep_key = format("default_wep_key_%s_s0", rname), wpa_psk_key = format("default_key_%s_s0", rname), - wps_ap_pin = format("default_wps_ap_pin_%s_s0", rname) + wps_ap_pin = format("default_wps_ap_pin_%s_s0", rname), + security_mode = format("default_security_mode_%s_s0", rname) } end end @@ -26,10 +27,7 @@ end local function load_default_values(ap) local options = get_default_option_names(ap) if options then - local defaults = { - -- these are static defaults, the same on all CPE's - security_mode = "none", - } + local defaults = {} local envvars = load_uci_env_vars() for option, varname in pairs(options) do local v = envvars[varname] diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/dhcp.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/dhcp.lua index b14303ef4..21efc6e17 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/dhcp.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/dhcp.lua @@ -164,8 +164,8 @@ function M.parseDHCPData(dhcpIntfName, nwIntfName) dhcpBinding.sectionname = dhcpIntfName local dhcpConfig = getAllFromUci(dhcpBinding) - local netMask = nwCommon.ipv4ToNum(networkConfig["netmask"]) - local baseIp = nwCommon.ipv4ToNum(networkConfig["ipaddr"]) + local netMask = networkConfig.netmask and nwCommon.ipv4ToNum(networkConfig.netmask) + local baseIp = networkConfig.ipaddr and nwCommon.ipv4ToNum(networkConfig.ipaddr) local start = tonumber(dhcpConfig["start"] or "100") local numIps = tonumber(dhcpConfig["limit"] or "150") @@ -192,4 +192,35 @@ function M.parseDHCPData(dhcpIntfName, nwIntfName) } end +local function getLeaseFiles() + local path = {} + dhcpBinding.sectionname = "dnsmasq" + uciHelper.foreach_on_uci(dhcpBinding, function(s) + path[#path + 1] = s.leasefile + end) + return path +end + +function M.getDhcpInfo(mac) + local dhcpInfo = {} + for _, leasefile in ipairs(getLeaseFiles()) do + local f = io.open(leasefile,"r") + if f then + for line in f:lines() do + local leasetime, macaddr, ip, hostname = line:match("^(%d+)%s+(%x%x:%x%x:%x%x:%x%x:%x%x:%x%x)%s+(%S+)%s+(%S+)%s+") + if macaddr then + dhcpInfo[macaddr] = { + leasetime = leasetime, + macaddr = macaddr, + ip = ip, + hostname = hostname, + } + end + end + f:close() + end + end + return mac and dhcpInfo[mac] or dhcpInfo +end + return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/firewall_helper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/firewall_helper.lua index f5b165980..1c75168c0 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/firewall_helper.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/firewall_helper.lua @@ -50,8 +50,16 @@ function M.set_firewall_mode(paramvalue, commitapply) end if paramvalue == "user" then uci_helper.set_on_uci({config= "firewall", sectionname="userrules_v6", option="enabled"}, "1", commitapply) + uci_helper.set_on_uci({config= "firewall", sectionname="userrules_pxsservices", option="enabled"}, "1", commitapply) else uci_helper.set_on_uci({config= "firewall", sectionname="userrules_v6", option="enabled"}, "0", commitapply) + uci_helper.set_on_uci({config= "firewall", sectionname="userrules_pxsservices", option="enabled"}, "0", commitapply) + end + + if paramvalue == "user" or paramvalue == "high" then + uci_helper.set_on_uci({config= "firewall", sectionname="pinholerules", option="enabled"}, "0", commitapply) + else + uci_helper.set_on_uci({config= "firewall", sectionname="pinholerules", option="enabled"}, "1", commitapply) end local policy = getoutgoingpolicyformode(paramvalue) @@ -64,6 +72,13 @@ function M.set_firewall_mode(paramvalue, commitapply) uci_helper.set_on_uci({config= "firewall", sectionname="dmzredirects", option="enabled"}, dmz_enabled, commitapply) uci_helper.set_on_uci({config= "firewall", sectionname="userredirects", option="enabled"}, blocked["userredirects"] and "0" or "1", commitapply) + local blocked_redirect = uci_helper.get_from_uci({config = "firewall", sectionname = "fwconfig", option = "blocked_redirects_user"}) + if blocked_redirect ~= "" then + uci_helper.set_on_uci({config = "firewall", sectionname = "guiredirects", option = "enabled"}, blocked["guiredirects"] and "0" or "1", commitapply) + uci_helper.set_on_uci({config = "firewall", sectionname = "cliredirects", option = "enabled"}, blocked["cliredirects"] and "0" or "1", commitapply) + uci_helper.set_on_uci({config = "firewall", sectionname = "acsredirects", option = "enabled"}, blocked["acsredirects"] and "0" or "1", commitapply) + end + uci_helper.set_on_uci({config= "firewall", sectionname="fwconfig", option="level"}, paramvalue, commitapply) -- reset acs_admin_config flag if any client other than ACS has changed the mode @@ -162,7 +177,7 @@ function M.ip2mac(ubus_connect, ipFamily, ipAddr, ipConfiguration) for _, dev in pairs(devices) do if type(dev[ipFamily]) == "table" then for _, ip in pairs(dev[ipFamily]) do - if ip["address"] == ipAddr and ip["state"] == "connected" then + if ip["address"] == ipAddr and (ip["state"] == "connected" or ip["state"] == "stale") then if ipConfiguration and ip["configuration"] ~= ipConfiguration then return nil end diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/ippinghelper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/ippinghelper.lua index b341a108b..5467093c5 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/ippinghelper.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/ippinghelper.lua @@ -102,8 +102,8 @@ function M.startup(user, binding) uci.set_on_uci(uci_binding[user]["Timeout"], 10000) uci.set_on_uci(uci_binding[user]["DataBlockSize"], 56) uci.set_on_uci(uci_binding[user]["DSCP"], 0) + uci.set_on_uci(uci_binding[user]["ProtocolVersion"], "IPv4") if user == "webui" then - uci.set_on_uci(uci_binding[user]["ipType"], "IPv4") uci.set_on_uci(uci_binding[user]["NumberOfRepetitions"], 4) else uci.set_on_uci(uci_binding[user]["NumberOfRepetitions"], 3) @@ -115,8 +115,8 @@ function M.startup(user, binding) uci.set_on_uci(uci_binding[user]["Timeout"], 10000) uci.set_on_uci(uci_binding[user]["DataBlockSize"], 56) uci.set_on_uci(uci_binding[user]["DSCP"], 0) + uci.set_on_uci(uci_binding[user]["ProtocolVersion"], "IPv4") if user == "webui" then - uci.set_on_uci(uci_binding[user]["ipType"], "IPv4") uci.set_on_uci(uci_binding[user]["NumberOfRepetitions"], 4) else uci.set_on_uci(uci_binding[user]["NumberOfRepetitions"], 3) @@ -141,6 +141,7 @@ function M.uci_ipping_get(user, pname) Timeout = { config = config, sectionname = user, option = "timeout" }, DataBlockSize = { config = config, sectionname = user, option = "size" }, DSCP = { config = config, sectionname = user, option = "dscp" }, + ProtocolVersion = { config = config, sectionname = user, option = "iptype" }, } end if uci_binding[user][pname] then diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/leds.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/leds.lua new file mode 100644 index 000000000..d0d10fc0e --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/leds.lua @@ -0,0 +1,244 @@ +local open, string = io.open, string +local match = string.match +local lfs = require("lfs") +local uci = require("transformer.mapper.ucihelper") + +local M = {} +local ledPath = "/sys/class/leds/" + + +local function isDir(path) + local mode = lfs.attributes(path, "mode") + if mode and mode == "directory" then + return true + end + return false +end + +--- Get the led(seven-color) mixed status +-- @param #table red led info +-- @param #table green led info +-- @param #table blue led info +-- @return #string led status /Blinking/Netdev/On/Off +local function getLedMixStatus(red, green, blue) + local mixStatus = "" + local rStatus, gStatus, bStatus + if red then + rStatus = M.getLedStatus(red.trigger, red.brightness) + end + if green then + gStatus = M.getLedStatus(green.trigger, green.brightness) + end + if blue then + bStatus = M.getLedStatus(blue.trigger, blue.brightness) + end + if rStatus == "Blinking" or gStatus == "Blinking" or bStatus == "Blinking" then + mixStatus = "Blinking" + elseif rStatus == "Netdev" or gStatus == "Netdev" or bStatus == "Netdev" then + mixStatus = "Netdev" + elseif rStatus == "On" or gStatus == "On" or bStatus == "On" then + mixStatus = "On" + else + mixStatus = "Off" + end + return mixStatus +end + +--- Get led(color is red/green/blue) status +-- @param #string led trigger mode /none/default-on/pattern/timer/netdev +-- @param #integer led brightness +-- @return #string led status /Blinking/Netdev/On/Off +function M.getLedStatus(mode, brightness) + local status = "" + if mode == "none" then + if brightness == 0 then + status = "Off" + else + status = "On" + end + elseif mode == "default-on" then + status = "On" + elseif mode == "pattern" or mode == "timer" then + status = "Blinking" + elseif mode == "netdev" then + status = "Netdev" + end + return status +end + +--- Get the led(seven-color) mixed color +-- @param #table red led info +-- @param #table green led info +-- @param #table blue led info +-- @return #string led mixed color /White/Orange/Magenta/Red/Cyan/Green/Blue/None +local function getLedMixColor(redInfo, greenInfo, blueInfo) + local rBrightness, gBrightness, bBrightness + if redInfo ~= nil then + rBrightness = redInfo.brightness + end + if greenInfo ~= nil then + gBrightness = greenInfo.brightness + end + if blueInfo ~= nil then + bBrightness = blueInfo.brightness + end + local color = "None" + local red, green, blue = false, false, false + if rBrightness ~= nil and rBrightness > 0 then + red = true + end + if gBrightness ~= nil and gBrightness > 0 then + green = true + end + if bBrightness ~= nil and bBrightness > 0 then + blue = true + end + if red and green and blue then + color = "White" + elseif red and green then + color = "Orange" + elseif red and blue then + color = "Magenta" + elseif red then + color = "Red" + elseif green and blue then + color = "Cyan" + elseif green then + color = "Green" + elseif blue then + color = "Blue" + end + return color +end + +--- Get led(color is red/green/blue) brightness level +-- @param #integer led brightness +-- @param #integer led max brightness +--@return #string led brightness level /None/Low/Middle/High +function M.getLedLevel(brightness, maxBrightness) + if brightness == nil or type(brightness) ~= "number" then + return "" + end + if maxBrightness == nil or type(maxBrightness) ~= "number" then + return "" + end + local level = "" + if brightness == 0 then + level = "None" + elseif brightness <= maxBrightness/3 + 1 then + level = "Low" + elseif (brightness > maxBrightness/3 + 1) and (brightness <= maxBrightness*2/3) then + level = "Middle" + elseif brightness <= maxBrightness then + level = "High" + end + return level +end + +--- Get the led(seven-color) brightness level +---- @param #table red led info +---- @param #table green led info +---- @param #table blue led info +---- @return #string led brightness level /None/Low/Middle/High +local function getLedMixBrightness(red, green, blue) + local mixLevel = "" + local rLevel, gLevel, bLevel + if red then + rLevel = M.getLedLevel(red.brightness, red.max_brightness) + end + if green then + gLevel = M.getLedLevel(green.brightness, green.max_brightness) + end + if blue then + bLevel = M.getLedLevel(blue.brightness, blue.max_brightness) + end + if rLevel == "High" or gLevel == "High" or bLevel == "High" then + mixLevel = "High" + elseif rLevel == "Middle" or gLevel == "Middle" or bLevel == "Middle" then + mixLevel = "Middle" + elseif rLevel == "Low" or gLevel == "Low" or bLevel == "Low" then + mixLevel = "Low" + else + mixLevel = "None" + end + return mixLevel +end + +--- Get all the leds information from path /sys/class/leds/ +-- @return #table led info +function M.getLedsInfo() + local ledsInfo = {} + if not isDir(ledPath) then + return ledsInfo + end + for file in lfs.dir(ledPath) do + local name = match(file, "(.+):") + local color = match(file, ":(.+)") + if name and color then + if ledsInfo[name] == nil then + ledsInfo[name] = {} + end + ledsInfo[name][color] = {} + local ledFile = ledPath .. file + if lfs.attributes(ledFile, "mode") == "directory" then + local fd = open(ledFile .. "/trigger", "r") + if not fd then + break + end + local output = fd:read("*all") + if output then + local trigger = match(output, "%[(.+)%]") + if trigger then + ledsInfo[name][color].trigger = trigger + end + end + fd:close() + fd = open(ledFile .. "/brightness", "r") + if not fd then + break + end + output = fd:read("*all") + if output then + local brightness = tonumber(output) + if brightness then + ledsInfo[name][color].brightness = brightness + end + end + fd:close() + fd = open(ledFile .. "/max_brightness", "r") + if not fd then + break + end + output = fd:read("*all") + if output then + local max_brightness = tonumber(output) + if max_brightness then + ledsInfo[name][color].max_brightness = max_brightness + end + end + fd:close() + end + end + end + for k1, v1 in pairs(ledsInfo) do + local redInfo, greenInfo, blueInfo + for k2, v2 in pairs(v1) do + if k2 == "red" then + redInfo = v2 + elseif k2 == "green" then + greenInfo = v2 + elseif k2 == "blue" then + blueInfo = v2 + end + end + local mixColor = getLedMixColor(redInfo, greenInfo, blueInfo) + local mixStatus = getLedMixStatus(redInfo, greenInfo, blueInfo) + local mixBrightness = getLedMixBrightness(redInfo, greenInfo, blueInfo) + ledsInfo[k1].mixColor = mixColor + ledsInfo[k1].mixStatus = mixStatus + ledsInfo[k1].mixBrightness = mixBrightness + end + return ledsInfo +end + +return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/ltedoctor.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/ltedoctor.lua new file mode 100644 index 000000000..58100712d --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/ltedoctor.lua @@ -0,0 +1,114 @@ +local M = { + time_entries = { + last_five_minutes = { + period_seconds = 300 + }, + last_twenty_minutes = { + period_seconds = 1200 + }, + last_hour = { + period_seconds = 3600 + }, + last_twentyfour_hours = { + period_seconds = 86400 + }, + last_week = { + period_seconds = 604800 + }, + last_month = { + period_seconds = 2678400 + }, + diff = {}, + all = {} + } +} + +local dev_idx = 1 +local export_location = "/tmp/" +local signal_quality_diff_since_uptime, alarms_diff_since_uptime + +function M.getUptime(conn) + local data = conn:call("system", "info", {}) + if data then + return tonumber(data.uptime) + end +end + +function M.setSignalQualityDiffSinceUptime(uptime) + signal_quality_diff_since_uptime = uptime +end + +function M.getSignalQualityDiffSinceUptime() + return signal_quality_diff_since_uptime +end + +function M.setAlarmsDiffSinceUptime(uptime) + alarms_diff_since_uptime = uptime +end + +function M.getAlarmsDiffSinceUptime() + return alarms_diff_since_uptime +end + +function M.setDeviceIndex(idx) + dev_idx = tonumber(idx) or 1 +end + +function M.getDeviceIndex() + return dev_idx +end + +local function get_error_info(err) + if type(err) == 'string' then return err end + if type(err) ~= 'table' or type(err.info) ~= 'table' then return nil end + return string.format("line=%d, name=%s", err.info.currentline, err.info.name or "?") +end + +local function export_set_error(export_mapdata, info) + export_mapdata.state = "Error" + export_mapdata.info = info or "" +end + +function M.export_reset(export_mapdata) + export_mapdata.state = "None" + export_mapdata.info = "" +end + +function M.export_init(location) + local export_mapdata = {} + M.export_reset(export_mapdata) + if location then + export_mapdata.location = location + else + export_mapdata.location = export_location + end + return export_mapdata +end + +local function export_execute(export_mapdata) + local rv + local conn = require("ubus").connect() + if not conn then + return + end + -- write export data to file + export_mapdata.info = "writing export data" + rv = conn:call("ltedoctor", "export", { path = export_mapdata.location .. export_mapdata.filename }) + if rv and rv.error then + export_set_error(export_mapdata, string.format("write export data failed (%s)", get_error_info(rv.error) or "?")) + return + end + + export_mapdata.state = "Complete" + export_mapdata.info = "export succesfully completed" +end + +function M.export_start(export_mapdata) + if not export_mapdata.filename or export_mapdata.filename == "" then + export_set_error(export_mapdata, "invalid filename") + return + end + export_execute(export_mapdata) +end + +return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/mobiled_helper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/mobiled_helper.lua new file mode 100644 index 000000000..16cf94442 --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/mobiled_helper.lua @@ -0,0 +1,13 @@ +local M = {} + +local dataMaxAge = {} + +function M.setDataMaxAge(dev_idx, age) + dataMaxAge[dev_idx] = age +end + +function M.getDataMaxAge(dev_idx) + return dataMaxAge[dev_idx] +end + +return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/models/device2/ipaddr.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/models/device2/ipaddr.lua index c8694797a..52ae858c4 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/models/device2/ipaddr.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/models/device2/ipaddr.lua @@ -52,18 +52,18 @@ end local function load_ubus_mobile_data() local data = ubus:call("mobiled.network", "sessions", {}) if data then - data.interface = {} - data.sessions = data.sessions or {} - for _, session in ipairs(data.sessions) do - if session.session_state=='connected' then - data.interface[session.interface] = session - end - end + data.interface = {} + data.sessions = data.sessions or {} + for _, session in ipairs(data.sessions) do + if session.session_state=='connected' and session.interface then + data.interface[session.interface] = session + end + end else - data = { - sessions = {}, - interface = {} - } + data = { + sessions = {}, + interface = {} + } end return data end diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/models/device2/network.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/models/device2/network.lua index 9a767d33c..90a8c792f 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/models/device2/network.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/models/device2/network.lua @@ -25,6 +25,12 @@ local uciconfig = require("transformer.shared.models.uciconfig").Loader() local dmordering = require "transformer.shared.models.dmordering" local xdsl = require("transformer.shared.models.xdsl") +local ucihelper = require("transformer.mapper.ucihelper") +local get_from_uci = ucihelper.get_from_uci +local set_on_uci = ucihelper.set_on_uci +local delete_on_uci = ucihelper.delete_on_uci +local ethBinding = { config = "ethernet", sectionname = "", option = "", default = "" } + local M = {} local function errorf(fmt, ...) @@ -124,6 +130,7 @@ local function newModel() networks = {}, key_aliases = {}, key_ignore = {}, + _explicit_links = {}, } for _, tp in ipairs(allTypes) do obj.typed[tp] = {} @@ -162,7 +169,7 @@ function Model:add(objType, name, position) list[name] = obj all[#all+1] = obj all[name] = obj - + return obj end @@ -644,9 +651,9 @@ end local function getBridgeDevice(model, bridgeName, member) return model:get(member) - or model:get("vlan:"..member) - or model:get("vlan:"..bridgeName..':'..member) - or model:get("link:"..member) + or model:get("vlan:"..member) + or model:get("vlan:"..bridgeName..':'..member) + or model:get("link:"..member) end local function getBridgeMembers(members) @@ -728,14 +735,47 @@ local function create_bridge(model, name, members, placeholder) return mgmt.name, bridge end +local function map_explicit_link(model, linkedto, linkname) + model._explicit_links[linkedto] = linkname +end + +local function create_explicit_link_objects(model, sections) + for _, link in ipairs(sections) do + local linkname = "link:"..link['.name'] + local dev = model:add("EthLink", linkname) + if link.linkedto then + map_explicit_link(model, link.linkedto, linkname) + end + if link.ifname then + model.setLower(dev, link.ifname) + dev.device = link.ifname + end + end +end + +local function explicit_link_for(model, section) + return model._explicit_links[section] +end + +function Model:explicit_link_for(section) + return explicit_link_for(self, section) +end + local function create_device(model, s) + local linkname -- add the link first if not s['.placeholder'] then - local dev = model:add("EthLink", "link:"..s['.name']) - if s.ifname then - model.setLower(dev, s.ifname) + linkname = explicit_link_for(model, s['.name']) + if not linkname and (s.dev2_dynamic~="1") then + linkname = "link:"..s['.name'] + local dev = model:add("EthLink", "link:"..s['.name']) + if s.ifname then + model.setLower(dev, s.ifname) + end + dev.device = s.name end - dev.device = s.name + else + linkname = "link:"..s['.name'] end local devtype = s.type or "8021q" @@ -747,9 +787,9 @@ local function create_device(model, s) vlan.device = s.name end if s.ifname then - model.setLower(vlan, "link:"..s['.name']) + model.setLower(vlan, linkname) elseif s.type then - local lower = model:get("link:"..s['.name']) + local lower = model:get(linkname) if lower then model.setLower(vlan, lower.name) end @@ -886,6 +926,9 @@ local function interface_has_ip_layer(s) if not s.proto then return false end + if s.proto == 'none' then + return false + end if (s.proto=='static') and not s.ipaddr then return false end @@ -914,9 +957,12 @@ end local function createIntfLowerLayer(model, s, cfg, ifname) local lower ifname = ifname or s.ifname + local linkname = explicit_link_for(model, s['.name']) if getDevice(ifname, cfg) then -- refers to device section, lower layer is from that device lower = getIPLowerLayer(model, ifname) + elseif linkname then + lower = linkname elseif ifname then -- refers to physical device, possibly with a VLAN reference. -- create the EthLink (and VLAN) ourselves @@ -945,9 +991,9 @@ local function createIntfLowerLayer(model, s, cfg, ifname) end local function fixup_gre_phys(intf, phys) - if phys and intf and (intf.proto or ""):match("^gre") then - return "gre-"..phys - end + if phys and intf and (intf.proto or ""):match("^gre") then + return "gre-"..phys + end end local function replaceAliasInterface(phys, cfg) @@ -963,38 +1009,38 @@ local function replaceAliasInterface(phys, cfg) phys = "" end else - phys = fixup_gre_phys(cfg.interface[phys], phys) or phys + phys = fixup_gre_phys(cfg.interface[phys], phys) or phys end return phys end local function ifname_to_device(ifname, cfg) - if not ifname then - return - end - local devices = cfg.device or {} - if devices[ifname] then - -- name refers to a device - return ifname - end - if ifname:match("^@") then - -- refers to interface - local intfname = ifname:match("^@([^.]+)") - local intf = cfg.interface[intfname] - if intf then - if intf.proto == "gretap" then - return ifname:gsub("^@", "gre4t-") - elseif intf.proto == "grev6tap" then - return ifname:gsub("^@", "gre6t-") - elseif intf.proto:match("^gre") then - return ifname:gsub("^@", "") - else - return ifname_to_device(intf.ifname, cfg) - end - end - return - end - return ifname:match("^([^.]+)") + if not ifname then + return + end + local devices = cfg.device or {} + if devices[ifname] then + -- name refers to a device + return ifname + end + if ifname:match("^@") then + -- refers to interface + local intfname = ifname:match("^@([^.]+)") + local intf = cfg.interface[intfname] + if intf then + if intf.proto == "gretap" then + return ifname:gsub("^@", "gre4t-") + elseif intf.proto == "grev6tap" then + return ifname:gsub("^@", "gre6t-") + elseif intf.proto:match("^gre") then + return ifname:gsub("^@", "") + else + return ifname_to_device(intf.ifname, cfg) + end + end + return + end + return ifname:match("^([^.]+)") end local function createBridgeMembersLowerLayers(model, s, cfg) @@ -1089,14 +1135,16 @@ function create_interface(model, s, cfg) end end if s.proto and s.proto:match('^ppp') then - lower = createPPPInterface(model, s, lower) + if not explicit_link_for(model, name) then + lower = createPPPInterface(model, s, lower) + end end intf = model:add("IPInterface", name) intf.device = s.device or s.ifname intf.refers_to = referend intf.has_ip_layer = interface_has_ip_layer(s) if not intf.has_ip_layer then - intf.hide_in_datamodel = true + intf.hide_in_datamodel = s.dev2_dynamic~="1" end local linkto = dmordering.linked("network.interface", name) if linkto then @@ -1125,9 +1173,28 @@ local function findNetworkBridge(model, network) return intf end +local function create_explicit_ppp_objects(model, all_ppp) + for _, pppcfg in ipairs(all_ppp) do + if not pppcfg['.placeholder'] then + -- a dynanmic one + local ppp = create_PPP_interface(model, pppcfg['.name']) + local lower = explicit_link_for(model, pppcfg['.name']) + if lower then + model.setLower(ppp, lower) + end + if pppcfg.linkedto then + map_explicit_link(model, pppcfg.linkedto, ppp.name) + end + end + end +end + local function loadNetwork(model) local cfg = uciconfig:load("network") - + + create_explicit_link_objects(model, cfg.dev2_link or {}) + create_explicit_ppp_objects(model, cfg.ppp or {}) + for _, dev in ipairs(cfg.device or {}) do create_device(model, dev) end @@ -1142,15 +1209,18 @@ local function loadNetwork(model) for _, pppcfg in ipairs(cfg.ppp or {}) do local name = pppcfg.uciname or pppcfg['.name'] - local ppp, placeholder_ignored = create_PPP_interface(model, name, pppcfg['.placeholder']) - if not placeholder_ignored then - ppp.ucikey = pppcfg['.name'] - if pppcfg.interface then - ppp.interface = pppcfg.interface - else - ppp.interface = name:match("^[^-]+%-(.*)") or pppcfg['.name'] - end - ppp.present = false + local placeholder = pppcfg['.placeholder'] + if placeholder then + local ppp, placeholder_ignored = create_PPP_interface(model, name, placeholder) + if placeholder and not placeholder_ignored then + ppp.ucikey = pppcfg['.name'] + if pppcfg.interface then + ppp.interface = pppcfg.interface + else + ppp.interface = name:match("^[^-]+%-(.*)") or pppcfg['.name'] + end + ppp.present = false + end end end end @@ -1248,13 +1318,13 @@ local function loadOrdering(model) end model.key_aliases = aliases model.key_ignore = ignore - + local bridgeports = {} for _, bridge in ipairs(cfg.bridgeports or {}) do local name = bridge.name or bridge['.name'] bridgeports[name] = bridge.port end - + return bridgeports end @@ -1348,4 +1418,87 @@ load_model = function() return current_model end +function M.invalidate() + current_model = nil +end + +-- Function to get the ShapingRate value from the ethernet config +local function getEthUciValue(devName, option, default) + ethBinding.sectionname = devName + ethBinding.option = option + ethBinding.default = default + return get_from_uci(ethBinding) or "" +end + +-- Function to set the ShapingRate value in the ethernet config +local function setEthUciValue(value, sectionName, option) + ethBinding.sectionname = sectionName + ethBinding.option = option + set_on_uci(ethBinding, value, commitapply) +end + +function M.getShapingRate(devName, key) + local trafficDesc = getEthUciValue(devName, "td", "") + if trafficDesc ~= "" then + local value = getEthUciValue(trafficDesc, "max_bit_rate", "") + return value ~= "" and tostring(tonumber(value) * 1000) or "-1" + end + return "-1" +end + +local function remove_trafficdesc_section(devName, td_name) + ethBinding.sectionname = td_name + ethBinding.option = nil + delete_on_uci(ethBinding, commitapply) + setEthUciValue("", devName, "td") + return true +end + +-- Function to set the ShapingRate value +local function shaping_set(devName, shapingValue) + setEthUciValue(shapingValue.max_bit_rate, devName, "max_bit_rate") + setEthUciValue(shapingValue.max_burst_size, devName, "max_burst_size") + setEthUciValue(shapingValue.rate, devName, "rate") + setEthUciValue(shapingValue.ratio, devName, "ratio") + return true +end + +function M.setShapingRate(value, key, devName) + value = tonumber(value) + local max_burst_size = "2000" + if value then + local trafficdesc = getEthUciValue(devName, "td", "") + local new_td_name = "td" .. devName + if trafficdesc ~= "" then + max_burst_size = getEthUciValue(trafficdesc, "max_burst_size", "2000") + end + if trafficdesc == "" and value == -1 then + return true + elseif value == 0 then + return nil, "Not supported" + elseif trafficdesc == "" and (value > -1) and (value <= 100) then + set_on_uci(ethBinding, new_td_name, commitapply) + setEthUciValue("trafficdesc", new_td_name) + return shaping_set(new_td_name, { max_bit_rate = value, max_burst_size = max_burst_size, rate = "", ratio = "enabled", }) + elseif trafficdesc == "" and (value > 100) then + if value < 1000 then + return nil, "Absolute value should be at least 1000 bps" + end + set_on_uci(ethBinding, new_td_name, commitapply) + setEthUciValue("trafficdesc", new_td_name) + return shaping_set(new_td_name, { max_bit_rate = value/1000, max_burst_size = max_burst_size, rate = "enabled", ratio = "" }) + elseif trafficdesc ~= "" and value == -1 then + return remove_trafficdesc_section(devName, trafficdesc) + elseif trafficdesc ~= "" and (value > -1) and (value <= 100) then + return shaping_set(new_td_name, { max_bit_rate = value, max_burst_size = max_burst_size, rate = "", ratio = "" }) + elseif trafficdesc ~= "" and (value > 100) then + if value < 1000 then + return nil, "Absolute value should be at least 1000 bps" + end + return shaping_set(new_td_name, { max_bit_rate = value/1000, max_burst_size = max_burst_size, rate = "enabled", ratio = "" }) + end + end + return nil,"Not supported" +end + return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/nslookupdiaghelper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/nslookupdiaghelper.lua new file mode 100644 index 000000000..16c5c3c89 --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/nslookupdiaghelper.lua @@ -0,0 +1,147 @@ +local M = {} +local open = io.open +local uci = require("transformer.mapper.ucihelper") +local pairs = pairs + +local transactions = {} +local clearusers={} +local nslookupdiag_pid = 0 +local uci_binding={} + + +local function transaction_set(binding, pvalue, commitapply) + uci.set_on_uci(binding, pvalue, commitapply) + transactions[binding.config] = true +end + + +function M.clear_nslookupdiag_results(user) + os.remove("/tmp/nslookupdiag_".. user) +end + +function M.read_nslookupdiag_results(user) + + local return_results={} + + -- check if nslookupdiag command is still running + if nslookupdiag_pid ~= 0 then return "0" end + + -- read results from nslookupdiag + local fh = open("/tmp/nslookupdiag_".. user) + if not fh then + -- no results present + return "0" + end + local SuccessCount = tonumber(fh:read()) + + local index = 0 + for line in fh:lines() do + local results = {line:match("(%S*)%,(%S*)%,(%S*)%,(%S*)%,(%S*)%,(%d*)")} + return_results[index + 1] = { + Status = results[1], + AnswerType = results[2], + HostNameReturned = results[3], + IPAddresses = results[4], + DNSServerIP = results[5], + ResponseTime = results[6], + } + index = index + 1 + end + fh:close() + + return return_results, SuccessCount +end + +function M.startup(user, binding) + uci_binding[user] = binding + -- check if /etc/config/nslookupdiag exists, if not create it + local f = open("/etc/config/nslookupdiag") + if not f then + f = open("/etc/config/nslookupdiag", "w") + if not f then + error("could not create /etc/config/nslookupdiag") + end + f:write("config user '".. user .."'\n") + f:close() + uci.set_on_uci(uci_binding[user]["NumberOfRepetitions"], 3) + uci.set_on_uci(uci_binding[user]["Timeout"], 5000) + else + local value = uci.get_from_uci({config = "nslookupdiag", sectionname = user}) + if value == '' then + uci.set_on_uci({ config = "nslookupdiag", sectionname = user},"user") + uci.set_on_uci(uci_binding[user]["NumberOfRepetitions"], 3) + uci.set_on_uci(uci_binding[user]["Timeout"], 5000) + end + end + uci.set_on_uci(uci_binding[user]["DiagnosticsState"], "None") + uci.commit({ config = "nslookupdiag"}) + return user +end + +function M.uci_nslookupdiag_get(user, pname) + local value + local config = "nslookupdiag" + if uci_binding[user] == nil then + uci_binding[user] = { + DiagnosticsState = { config = config, sectionname = user, option = "state" }, + Interface = { config = config, sectionname = user, option = "interface" }, + HostName = { config = config, sectionname = user, option = "hostname" }, + DNSServer = { config = config, sectionname = user, option = "dnsserver" }, + NumberOfRepetitions = { config = config, sectionname = user, option = "repetitions" }, + Timeout = { config = config, sectionname = user, option = "timeout" }, + } + end + if uci_binding[user][pname] then + value = uci.get_from_uci(uci_binding[user][pname]) + -- Internally, we need to distinguish between Requested and InProgress; IGD does not + if pname == "DiagnosticsState" and value == "InProgress" then + value = "Requested" + end + else + value = M.read_nslookupdiag_results(user,pname) + end + return value +end + + +function M.uci_nslookupdiag_set(user, pname, pvalue, commitapply) + if pname == "DiagnosticsState" then + if (pvalue ~= "Requested" and pvalue ~= "None") then + return nil, "invalid value" + end + clearusers[user] = true + transaction_set(uci_binding[user]["DiagnosticsState"], pvalue, commitapply) + else + local state = uci.get_from_uci(uci_binding[user]["DiagnosticsState"]) + if (state ~= "Requested" and state ~= "None") then + transaction_set(uci_binding[user]["DiagnosticsState"], "None", commitapply) + end + transaction_set(uci_binding[user][pname], pvalue, commitapply) + end +end + +local function endTransaction(action) + clearusers={} + for config,_ in pairs(transactions) do + local binding = {config = config} + action(binding) + end + transactions = {} +end + +local function clear_lookup_results() + for cl_user,_ in pairs(clearusers) do + M.clear_nslookupdiag_results(cl_user) + end +end + +function M.uci_nslookupdiag_commit() + clear_lookup_results() + endTransaction(uci.commit) +end + +function M.uci_nslookupdiag_revert() + endTransaction(uci.revert) +end + +return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/nudhelper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/nudhelper.lua index 393eef298..84912b2a3 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/nudhelper.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/nudhelper.lua @@ -19,6 +19,7 @@ local valueMap = { enable = "0", result = "fail", rtt = "0", + payload = "0", } -- set the default values to the options in uci diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/optical.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/optical.lua new file mode 100644 index 000000000..9b33fd0df --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/optical.lua @@ -0,0 +1,560 @@ +local popen, string = io.popen, string +local match, find, floor, len, sub, format = string.match, string.find, math.floor, string.len, string.sub, string.format +local ubus = require("transformer.mapper.ubus").connect() +local upper = string.upper +local lfs = require("lfs") +local open = io.open +local uci = require("transformer.mapper.ucihelper") +local getFromUci = uci.get_from_uci +local forEachOnUci = uci.foreach_on_uci + +local log = require('tch.logger') + +local M = {} + +local boardType + +function M.getBoardtype() + if boardType then + return boardType + end + local bType + local sfpFlag = getFromUci({config = "env", sectionname = "rip", option = "sfp"}) + if sfpFlag == '1' then + sfpFlag = true + else + sfpFlag = false + end + local pspFlag = true + local ctl = io.open("/bin/pspctl", "r") + if not ctl then + pspFlag = false + else + ctl:close() + end + if pspFlag and sfpFlag then + bType = "gpon_sfp" + elseif pspFlag and not sfpFlag then + bType = "gpon" + elseif not pspFlag and sfpFlag then + bType = "sfp" + else + bType = "none" + end + boardType = bType + return bType +end + +local LevelEntries = { + TransmitOpticalLevel = "US_TX_Power", + OpticalSignalLevel = "DS_Rx_RSSI" +} + +-- default threshold values for gpon and p2p +local opticalThresholdValues = { + gpon = { + LowerOpticalThreshold = "-29000", + UpperOpticalThreshold = "-7000", + LowerTransmitPowerThreshold = "-500", + UpperTransmitPowerThreshold = "6000", + }, + p2p = { + LowerOpticalThreshold = "-33000", + UpperOpticalThreshold = "0", + LowerTransmitPowerThreshold = "-10000", + UpperTransmitPowerThreshold = "-2700", + }, + none = { + LowerOpticalThreshold = "-127500", + UpperOpticalThreshold = "-127500", + LowerTransmitPowerThreshold = "-63500", + UpperTransmitPowerThreshold = "-63500", + }, +} + +function M.getUbusObject() + local bType = M.getBoardtype() + local str + if bType == "sfp" then + str = "optical" + elseif bType == "gpon" or bType == "gpon_sfp" then + str = "gpon" + end + return str +end + +function M.getTrsvInfo(info, paramName) + local str = M.getUbusObject() + if str then + str = string.format("%s.trsv", str) + else + log:error("No ubus call for trsv is provided\n") + return nil + end + local result = ubus:call(str, "get_info", { info = info }) + if result == nil then + log:error(string.format("Cannot retrieve trsv info %s\n", info)) + return nil + end + local data = result[info] + local key, val = "", "" + for key, val in pairs (data) do + key = key:match ("[%S]+") + if (key == paramName) then + return val + end + end +end + +function M.getWanconf(paramName) + local str = M.getUbusObject() + if str then + str = string.format("%s.wanconf", str) + else + log:error("No ubus call for wanconf is provided\n") + return nil + end + local result = ubus:call(str, "get", {}) + if result == nil then + log:error("Cannot retrieve wanconf info\n") + return nil + end + local data = result["WanConf"] + local key, val = "", "" + for key, val in pairs (data) do + if (key == paramName) then + return val + end + end +end + +function M.getVendorName() + local vendName = M.getTrsvInfo("vendor", "Name") + return vendName or "" +end + +--- Get SFP OpticalSignalLevel or TransmitOpticalLevel value +-- @param #string level item "OpticalSignalLevel/TransmitOpticalLevel" +-- @return #string level value +function M.getLevel(param) + local entry = LevelEntries[param] + if entry then + local value = M.getTrsvInfo("status", entry) + if value and value ~= "" then + value = tonumber(value) + value = value and floor(value * 1000) or 0 + else + return "0" + end + if param == "OpticalSignalLevel" then + return value <= -65536 and "-65536" or value >= 65534 and "65534" or tostring(value) + elseif param == "TransmitOpticalLevel" then + return value <= -127500 and "-127500" or value >= 0 and "0" or tostring(value) + end + end + return "0" +end + +function M.getLinkStatus() + local value = M.getTrsvInfo("status", "DS_Rx_RSSI") + local status = "unknown" + if value and value ~= "" then + value = tonumber(value) + if value == -255.00000 then + status = "unplug" + elseif value >= -99.00000 and value <= -35.00000 then + status = "plugin" + elseif value > -35.00000 then + status = "linkup" + end + end + return status +end + +function M.getThresholdValues(param) + local wanType = M.getWantype() + if wanType == "xepon_ae_p2p" then + return opticalThresholdValues["p2p"][param] + elseif wanType == "gpon" or wanType == "xepon_ae" then + return opticalThresholdValues["gpon"][param] + else + return opticalThresholdValues["none"][param] + end +end + +function M.getEnable() + local enable = M.getTrsvInfo("control", "US_TX_Control") + if enable == "Disabled" then + enable = "0" + elseif enable == "Enabled" then + enable = "1" + end + return enable or "" +end + +function M.setEnable(value) + local str = M.getUbusObject() + if str then + str = string.format("%s.trsv", str) + else + log:error("No ubus call for trsv is provided\n") + return "" + end + local enable = M.getEnable() + if enable ~= value then + if value == "1" then + enable = true + elseif value == "0" then + enable = false + end + ubus:call(str, "set_ustx", { enable = enable }) + local boardtype = M.getBoardtype() + if boardtype == "sfp" then + if not enable then + ubus:send("sfp", {status = "tx_disable"}) + else + ubus:send("sfp", {status = "tx_enable"}) + end + end + end +end + +function M.getGponstate() + local ctl = popen("gponctl getstate") + local output = ctl:read("*a") + ctl:close() + local status = "LowerLayerDown" + if output and output ~= "" then + if match(output, "%(O5%)") then + status = "Up" + elseif match(output, "%(O1%)") then + status = "Dormant" + end + end + return status +end + +function M.getWantype() + local wanType = "unknown" + local RdpaWanType = M.getWanconf("WAN Type") + if RdpaWanType then + if RdpaWanType == "GBE" then + local wanOEMac = M.getWanconf("WAN EMAC") + if wanOEMac then + if wanOEMac == "EPONMAC" then + wanType = "xepon_ae" + else + wanType = "gbe" + end + end + elseif RdpaWanType == "AE" or RdpaWanType == "ETH_WAN" then + wanType = "gbe" + elseif RdpaWanType == "XGS" or RdpaWanType == "XGPON1" or RdpaWanType == "GPON" then + wanType = "gpon" + elseif RdpaWanType == "SFP_GPON" then + wanType = "xepon_ae" + elseif RdpaWanType == "SFP_P2P" then + wanType = "xepon_ae_p2p" + end + end + return wanType +end + +local ethernetBinding = { config = "ethernet", sectionname = "port" } + +--- Function to retrieve Ethwan interface +-- @return #string wan interface +function M.getWanInterface() + local wanInterface + forEachOnUci(ethernetBinding, function(s) + if s.wan == "1" then + wanInterface = s[".name"] + return false + end + end) + return wanInterface +end + +function M.getP2pState() + local ubusData = ubus:call("network.interface.wan", "status", {}) or {} + local up = ubusData["up"] + if up == true then + return "up" + end + return "LowerLayerDown" +end + +--- Get GPON status +-- @return #string gpon status +function M.getSfpState() + local status = "LowerLayerDown" + local output = M.getSFPInfo("state") + if output ~= "" then + if match(output, "%(O5%)") then + status = "Up" + elseif match(output, "%(O1%)") then + status = "Dormant" + end + end + return status +end + +--- Get SFP status +-- @return #string SFP status +function M.getStatus() + local status = "Unknown" + local phyState = M.getLinkStatus() + if match(phyState, "unplug") then + return "NotPresent" + elseif match(phyState, "plugin") then + local enable = M.getEnable() + if enable == "0" then + return "Down" + end + return "Dormant" + elseif match(phyState, "linkup") then + local enable = M.getEnable() + if enable == "0" then + return "Down" + end + local wanType = M.getWantype() + if wanType == "xepon_ae" then + return M.getSfpState() + elseif wanType == "xepon_ae_p2p" then + return M.getP2pState() + else --GPON XGPON XGS + return M.getGponstate() + end + end + return status +end + +--- Calls sfp_get.sh with the given option and returns the output +-- @param #string option value is as following +-- allstats : All SFP stats +-- state : ONU state +-- optical_info : SFP optical info +-- bytes_sent : SFP sent bytes +-- bytes_rec : SFP received bytes +-- packets_sent : SFP sent packets +-- packets_rec : SFP received packets +-- errors_sent : SFP sent errors +-- errors_rec : SFP received errors +-- discardpackets_sent : SFP sent discard packets +-- discardpackets_rec : SFP received discard packets +function M.getSFPInfo(option) + local PhyState = M.getLinkStatus() + if PhyState == "linkup" then + local cmd = popen("sfp_get.sh --" .. option) + local output = cmd:read("*a") + cmd:close() + return output or "" + else + return "" + end +end + +local statsEntries = { + BytesSent = "bytes_sent", + BytesReceived = "bytes_rec", + PacketsSent = "packets_sent", + PacketsReceived = "packets_rec", + ErrorsSent = "errors_sent", + ErrorsReceived = "errors_rec", + DiscardPacketsSent = "discardpackets_sent", + DiscardPacketsReceived = "discardpackets_rec", +} + +local function getStatsMatch(output, param) + local value = match(output, param..":%s+(.-)%c") + return value or "0" +end + +--- Get GPON separate statistics information +-- @param #string statistics item as following +-- BytesSent +-- BytesReceived +-- PacketsSent +-- PacketsReceived +-- ErrorsSent +-- ErrorsReceived +-- DiscardPacketsSent +-- DiscardPacketsReceived +-- @return #string statistics information +function M.getPonAeStats(param) + local output = M.getSFPInfo(statsEntries[param]) + if output == "" then + return "0" + end + return getStatsMatch(output, param) +end + +local intfStatsEntries = { + BytesSent = "tx_bytes", + BytesReceived = "rx_bytes", + PacketsSent = "tx_packets", + PacketsReceived = "rx_packets", + ErrorsSent = "tx_errors", + ErrorsReceived = "rx_errors", + DiscardPacketsSent = "tx_dropped", + DiscardPacketsReceived = "rx_dropped", +} + +function M.getIntfstats(intf, param) + local gponPath = string.format("/sys/class/net/%s/statistics/", intf) + param = intfStatsEntries[param] + local value + if lfs.attributes(gponPath, "mode") == "directory" then + local statsFile = gponPath .. param + local fd = open(statsFile, "r") + if fd then + value = fd:read("*all") + if value then + value = value:match("(.-)%c") + end + fd:close() + end + end + return value or "0" +end + +function M.getGponstats(param) + return M.getIntfstats("gpondef", param) +end + +function M.getP2pStats(param) + local intf = M.getWanInterface() + return M.getIntfstats(intf, param) +end + +--- Get All GPON statistics information +-- @return #table includes tr181 statistics information +function M.getPonAeAllStats() + local StatsValues = {} + local output = M.getSFPInfo("allstats") + for param in pairs(statsEntries) do + if output == "" then + StatsValues[param] = "0" + else + StatsValues[param] = getStatsMatch(output, param) + end + end + return StatsValues +end + +function M.getGponAllStats() + local StatsValues = {} + for param in pairs(intfStatsEntries) do + StatsValues[param] = M.getIntfstats("gpondef", param) + end + return StatsValues +end + +function M.getP2pAllStats() + local intf = M.getWanInterface() + local StatsValues = {} + for param in pairs(intfStatsEntries) do + StatsValues[param] = M.getIntfstats(intf, param) + end + return StatsValues +end + +--- Get SFP separate statistics information +-- @param #string statistics item as following +-- BytesSent +-- BytesReceived +-- PacketsSent +-- PacketsReceived +-- ErrorsSent +-- ErrorsReceived +-- DiscardPacketsSent +-- DiscardPacketsReceived +-- @return #string statistics information +function M.getStats(param) + local wanType = M.getWantype() + if wanType == "xepon_ae" then + return M.getPonAeStats(param) + elseif wanType == "xepon_ae_p2p" then + return M.getP2pStats(param) + elseif wanType == "gpon" then + return M.getGponstats(param) + end + return "0" +end + +--- Get All SFP statistics information +-- @return #table includes tr181 statistics information +function M.getAllStats() + local wanType = M.getWantype() + if wanType == "xepon_ae" then + return M.getPonAeAllStats() + elseif wanType == "xepon_ae_p2p" then + return getP2pAllStats() + elseif wanType == "gpon" then + return M.getGponAllStats() + end + return "0" +end + +function M.getCrossbarStatus() + local ctl = popen("cat /proc/ethernet/crossbar_status") + local output = ctl:read("*a") + ctl:close() + return output +end + +function M.getPortStatus(port) + local link = "" + local ctl = popen("ethctl eth4 media-type port " .. port .. " 2>&1") + local output = ctl:read("*a") + ctl:close() + if output then + if match(output, "Link is up") then + link = "Up" + else + link = "Down" + end + end + return link +end + +function M.getGPHY4LinkStatus() + return M.getPortStatus(10) +end + +function M.getSfpLinkStatus() + return M.getPortStatus(9) +end + +function M.getWanType() + local status = M.getCrossbarStatus() + local wanType = "" + if status then + if match(status, "WAN Port is connected to: AE") then + wanType = "SFP" + elseif match(status, "WAN Port is connected to: GPHY4") then + wanType = "GPHY4" + end + end + return wanType +end + +function M.getGPHY4Mode() + local status = M.getCrossbarStatus() + local mode = "" + if status then + if match(status, "Switch Port %d is connected to: GPHY4") then + mode = "Lan" + else + mode = "Wan" + end + end + return mode +end + +-- Reset all stats +function M.resetStatsGponSFP() + os.execute("sfp_get.sh --counter_reset") +end + +return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/processinfo.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/processinfo.lua index 37e0ea742..b77e33deb 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/processinfo.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/processinfo.lua @@ -3,6 +3,7 @@ local io, math = io, math local floor = math.floor local open = io.open local tostring = tostring +local process = require("tch.process") -- Calculates CPU usage since boot from the /proc/stat file. This value is a ratio of the non-idle time to the total usage in "USER_HZ". -- @function M.getCPUUsage @@ -25,4 +26,22 @@ function M.getCPUUsage() return tostring(cpuUsage) end +-- Calculates Current cpu usage using top command. The value is addition of usr, sys and nice vlaues. +-- @funciton M.getCurrentCPUUsage +-- @return #string, returns the current CPU usage value. +function M.getCurrentCPUUsage() + local cpuUsage + local getData = process.popen("top", {"-b", "-n1"}) + local usr,sys,nic + for line in getData:lines() do + usr, sys, nic = line:match("^CPU:%s*(%d+)%%%s*u.*%s*(%d+)%%%s*s.*%s*(%d+)%%%s*n.*") + if usr then + cpuUsage = tonumber(usr) + tonumber(sys) + tonumber(nic) + break + end + end + getData:close() + return tostring(cpuUsage) or "0" +end + return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/profile_helper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/profile_helper.lua index 771c90106..20f3efc38 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/profile_helper.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/profile_helper.lua @@ -1,6 +1,6 @@ local M = {} local open = io.open -local format, match, lower = string.format, string.match, string.lower +local format, match, lower, sub = string.format, string.match, string.lower, string.sub local pairs = pairs local sort, concat = table.sort, table.concat @@ -540,14 +540,10 @@ end function M.find_device_support(parentkey) local numOfFxs, numOfDect, numOfSipdev = 0, 0, 0 - local entries = {} binding.config = "mmpbx" binding.sectionname = "device" uci_helper.foreach_on_uci(binding, function(s) - if parentkey:match(s.config) then - entries[#entries + 1] = s['.name'] - end if s['.name']:sub(1,1) == "f" then numOfFxs = numOfFxs + 1 end @@ -562,4 +558,74 @@ function M.find_device_support(parentkey) return numOfFxs, numOfDect, numOfSipdev end +function M.validateDigitMapString(dmstring) + if not dmstring or dmstring == "" then + return true + end + if #dmstring > 256 then + return false + end + local endPattern = "" + local patternIndex = 1 + local patternCount = 1 + local index = 1 + local character = "" + local endChar = "" + local patternTable = { + ['('] = ")", + ['['] = "]", + ['{'] = "}", + } + local ignorepatternTable = { + [')'] = true, + [']'] = true, + ['}'] = true, + } + while index <= #dmstring do + character = sub(dmstring, index, index) + if not match(character, '[-+|0-9TXx%[%]%.#%*(){}]') then + return false + end + if ignorepatternTable[character] then + patternCount = patternCount-1 + end + if patternTable[character] then + patternCount = patternCount+1 + endPattern = patternTable[character] + endChar = sub(dmstring, #dmstring, #dmstring) + patternIndex = index + local char = "" + while patternIndex and patternIndex <= #dmstring do + char = sub(dmstring, patternIndex, patternIndex) + if ignorepatternTable[char] then + break + end + if char == endPattern or patternIndex == #dmstring then + endPatternIndex = patternIndex + setString = string.sub(dmstring, index, endPatternIndex) + local setIndex = 1 + local iserror = 0 + while setIndex and setIndex <= #setString do + setChar = sub(setString, setIndex, setIndex) + if setChar == endPattern or endChar == endPattern then + iserror = 1 + end + setIndex = setIndex + 1 + end + if iserror == 0 then + return false + end + break + end + patternIndex = patternIndex + 1 + end + end + index = index + 1 + end + if patternCount ~= 1 then + return false + end + return true +end + return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/qoshelper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/qoshelper.lua index 4b383616c..efca6df21 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/qoshelper.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/qoshelper.lua @@ -1,7 +1,5 @@ local M = {} -local nwCommon = require("transformer.mapper.nwcommon") -local hex2Decimal = nwCommon.hex2Decimal local match, format = string.match, string.format local dscpMap = { @@ -20,12 +18,12 @@ function M.mapDSCP(dscp, name) end return "-1" end - return dscpMap[dscp] and dscpMap[dscp] or hex2Decimal(dscp) + return dscpMap[dscp] and dscpMap[dscp] or tostring(tonumber(dscp, 16)) end -- Map the decimal value to corresponding dscp value -- @value the deimal value to be mapped -function mapDecimal(value) +local function mapDecimal(value) local dscp for dscpHex, dscpDecimal in pairs(dscpMap) do if value == dscpDecimal then diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/remoteconsoleaccess.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/remoteconsoleaccess.lua new file mode 100644 index 000000000..6d2479254 --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/remoteconsoleaccess.lua @@ -0,0 +1,502 @@ + +local string = string +local pairs = pairs +local os = os +local uciHelper = require("transformer.mapper.ucihelper") +local get_from_uci = uciHelper.get_from_uci +local set_on_uci = uciHelper.set_on_uci +local foreach_on_uci = uciHelper.foreach_on_uci +local match = string.match +local find = string.find +local format = string.format +local lower = string.lower + +local lfs = require("lfs") + +local ubus = require("transformer.mapper.ubus").connect() + +local open = io.open +local remove = os.remove +local configBinding = {} +local transactions = {} +local process = require("tch.process") + +local M = {} + +-- data model binding templates tied to uci settings +local dropbearBinding = { config = "dropbear" } +local firewallBinding = { config = "firewall" } +local clashBinding = { config = "clash" } + +-- lookup table for idle disconnect timeout -> drop-down conversion +local IDLE_UCI_TO_WEBUI = { + ["0"] = "0", + ["300"] = "1", + ["900"] = "2", + ["1800"] = "3", + ["3600"] = "4", + ["21600"] = "5", + ["43200"] = "6", + ["86400"] = "7", + ["259200"] = "8", + ["604800"] = "9" +} + +local IDLE_WEBUI_TO_UCI = {} +for i,j in pairs(IDLE_UCI_TO_WEBUI) do + IDLE_WEBUI_TO_UCI[j] = i +end + +-- applicable console type options +local CONSOLE_DISABLED = "0" +local CONSOLE_LAN_SSH = "1" +local CONSOLE_WAN_SSH = "2" +local CONSOLE_LANWAN_SSH = "3" +local CONSOLE_LAN_TELNET = "4" +local CONSOLE_WAN_TELNET = "5" +local CONSOLE_LANWAN_TELNET = "6" + +--- get console type based on dropbear configuration +--- Retrieves current remote console type +-- @return #string remote console state derived from dropbear settings +function M.getRemoteConsoleType() + -- holds final console type + local consoleType = CONSOLE_DISABLED + -- checks for which console type is being used + local sshLan, sshWan, telnetLan, telnetWan + dropbearBinding.sectionname = "dropbear" + dropbearBinding.option = nil + foreach_on_uci(dropbearBinding, function(s) + if s.Port == "22" and s.Interface == "lan" then + sshLan = s.enable == "1" + elseif s.Port == "22" and s.Interface == "wan" then + sshWan = s.enable == "1" + elseif s.Port == "23" and s.Interface == "lan" then + telnetLan = s.enable == "1" + elseif s.Port == "23" and s.Interface == "wan" then + telnetWan = s.enable == "1" + end + end) + -- determine final console type from configuration + if not sshLan and not sshWan and not telnetLan and not telnetWan then + consoleType = CONSOLE_DISABLED + elseif sshLan and not sshWan and not telnetLan and not telnetWan then + consoleType = CONSOLE_LAN_SSH + elseif not sshLan and sshWan and not telnetLan and not telnetWan then + consoleType = CONSOLE_WAN_SSH + elseif sshLan and sshWan then + consoleType = CONSOLE_LANWAN_SSH + elseif not sshLan and not sshWan and telnetLan and not telnetWan then + consoleType = CONSOLE_LAN_TELNET + elseif not sshLan and not sshWan and not telnetLan and telnetWan then + consoleType = CONSOLE_WAN_TELNET + elseif not sshLan and not sshWan and telnetLan and telnetWan then + consoleType = CONSOLE_LANWAN_TELNET + end + return consoleType +end + +-- @param #string iface Interface name ("lan", "wan") +-- @param #string port Port number to match based on console (ssh = "22", telnet = "23") +local function get_dropbear_section(intf, port) + local sectionName + dropbearBinding.sectionname = "dropbear" + dropbearBinding.option = nil + foreach_on_uci(dropbearBinding, function(s) + if s.Interface == intf and s.Port == port then + sectionName = s[".name"] + return false + end + end) + return sectionName or "" +end + +--- get value from dropbear section data +-- @param #string iface Interface name ("lan", "wan") +-- @param #string port Port number to match based on console (ssh = "22", telnet = "23") +-- @param #string opt UCI option to update +local function get_dropbear_param(iface, port, opt) + dropbearBinding.sectionname = get_dropbear_section(iface, port) + dropbearBinding.option = opt + return get_from_uci(dropbearBinding) +end + +--- update dropbear section data +-- @param #string iface Interface name ("lan", "wan") +-- @param #string port Port number to match based on console (ssh = "22", telnet = "23") +-- @param #string opt UCI option to update +-- @param #string value Target value to set +local function set_dropbear_param(iface, port, opt, value, commitapply) + dropbearBinding.sectionname = get_dropbear_section(iface, port) + dropbearBinding.option = opt + set_on_uci(dropbearBinding, value, commitapply) + transactions[dropbearBinding.config] = true +end + +local function set_clash_param(curruser, opt, value, commitapply) + clashBinding.sectionname = curruser + clashBinding.option = opt + set_on_uci(clashBinding, value, commitapply) + transactions[clashBinding.config] = true +end + +-- validate if user name actually exists +-- @param #string, username to be validated +-- @return #string, #boolean User account and true/false depending on whether it exists +local function validate_user_account(user) + local exists = false + local fp = open("/etc/passwd","r") + if fp then + for line in fp:lines() do + if user and line:match("^"..user..":") then + -- found the account + exists = true + break + end + end + fp:close() + end + return user, exists +end + +--- get user account data to aid in password update +-- @return #string, #boolean User account and true/false depending on whether it exists +local function get_user_to_update() + -- holds user account name + local user + -- first find out which account to update + -- assumption is only one clash user section exists within configuration, + -- so return after acquiring first one + clashBinding.sectionname = "user" + clashBinding.option = nil + foreach_on_uci(clashBinding, function(s) + -- Skip the "superuser", as it is required for EFU control. Only update the other section. + if s[".name"] ~= "superuser" then + user = s[".name"] + return false + end + end) + return validate_user_account(user) +end + +--- update SSH/Telnet firewall rules and their states +-- @param #string srcif Source Interface ("lan", "wan") +-- @param #string port Port number to match based on console (ssh = "22", telnet = "23") +-- @param #string target How to handle packets originating from source interface and port ("ACCEPT", "DROP", "REJECT") +local function set_iptables_rule(srcif, port, target, commitapply) + firewallBinding.sectionname = "rule" + firewallBinding.option = nil + foreach_on_uci(firewallBinding, function(s) + if s.src == srcif and s.dest_port == port then + -- update detected firewall rule target + firewallBinding.sectionname = s[".name"] + firewallBinding.option = "target" + set_on_uci(firewallBinding, target, commitapply) + transactions[firewallBinding.config] = true + return false + end + end) +end + +--- change clash user name +-- @param #string curruser Current remote console username to read (from clash settings) +-- @param #string newuser New remote console username to update +local function renameClashUser(curruser, newuser, commitapply) + clashBinding.sectionname = curruser + uciHelper.rename_on_uci(clashBinding, newuser, commitapply) + return true +end + +--- remove old history file and create new history file +-- @param #string curruser Current remote console username to read (from clash settings) +-- @param #string newuser New remote console username to update +local function removeAndCreateHistoryFile(curruser, newuser, commitapply) + local newhistfile = "/etc/clash/history" .. newuser .. ".txt" + local oldhistfile = "/etc/clash/history" .. curruser .. ".txt" + -- update command history file configuration + clashBinding.sectionname = newuser + clashBinding.option = "historyfile" + set_on_uci(clashBinding, newhistfile, commitapply) + -- remove previous historyfile + remove(oldhistfile) + transactions[clashBinding.config] = true +end + +--- change username for remote console access +-- @param #string curruser Current remote console username to read (from clash settings) +-- @param #string newuser New remote console username to update +-- @return true if operation succeeded or nil plus error message on failure +local function change_remoteconsole_userdata(curruser, newuser, commitapply) + if not curruser or not newuser then + return nil, "cannot change user account without proper info" + end + -- convert user names to lower-case to handle issue with + -- usermod not allowing upper-case letters when changing login account + curruser = lower(curruser) + newuser = lower(newuser) + -- redirect configured 'root' username to a restricted account (requires LD_PRELOAD to override credentials) + -- this keeps the current 'root' account (UID = 0) intact + dropbearBinding.sectionname = "global" + dropbearBinding.option = "restrictRoot" + if lower(newuser) == "root" then + -- changing to restricted root user, redirecting clash user + newuser = get_from_uci(dropbearBinding) + end + -- first check for presence of tools + if lfs.attributes("/usr/sbin/usermod", "mode") ~= "file" or + lfs.attributes("/usr/sbin/groupmod", "mode") ~= "file" then + return nil , "could not find required tools" + end + + local exitCode + -- now update username + exitCode = process.execute("usermod", {"-l", newuser, curruser}) + if exitCode ~= 0 then + return nil, "could not change username" + end + -- now update group name + exitCode = process.execute("groupmod", {"-n", newuser, curruser}) + if exitCode ~= 0 then + return nil, "could not change group name" + end + -- rename clash user section in uci + local result = renameClashUser(curruser, newuser, commitapply) + if not result then + return nil, "could not change clash user section" + end + removeAndCreateHistoryFile(curruser, newuser, commitapply) + -- construct string for new command history file + return true +end + +-- list of invalid sequences (case-insensitive) for remote console password +-- based on TCH.VALID.ConsolePassword() method in validator.js +local badSeq = { "qwerty", "uiop[]", "asdfgh", "jkl;'", "zxcvbn", "m,./" } + +--- validate new password based on console password criteria +-- @param #string value Data to be checked +-- @return true if valid, false otherwise +local function check_remoteconsole_password(value) + -- checks if password is 8 characters long with at least one lower case letter, + -- one upper case letter, one number and no spaces + local isGood = (#value >= 8) and + match(value, "%l+") and + match(value, "%u+") and + match(value, "%d+") and + not match(value, "%s+") + + if not isGood then + return false + end + -- check to make sure it doesn't match any special sequences + local lStr = lower(value) + for _, seq in ipairs(badSeq) do + if find(lStr, seq, 1, true) then + isGood = false + break + end + end + return isGood +end + +--- Retrieves current remote console username +function M.getRemoteConsoleUserName() + dropbearBinding.sectionname = "global" + dropbearBinding.option = "AdminUser" + return get_from_uci(dropbearBinding) +end + +--- Retrieves current remote console password +function M.getRemoteConsolePassword() + dropbearBinding.sectionname = "global" + dropbearBinding.option = "hPass" + return get_from_uci(dropbearBinding) +end + +--- Retrieves current remote console idle timeout value +function M.getRemoteConsoleIdleTimeout() + local consoleType = M.getRemoteConsoleType() + local timeout + if consoleType == CONSOLE_DISABLED or + consoleType == CONSOLE_LAN_SSH or + consoleType == CONSOLE_LANWAN_SSH then + timeout = get_dropbear_param("lan", "22", "IdleTimeout") + elseif consoleType == CONSOLE_LAN_TELNET or + consoleType == CONSOLE_LANWAN_TELNET then + timeout = get_dropbear_param("lan", "23", "IdleTimeout") + elseif consoleType == CONSOLE_WAN_TELNET then + timeout = get_dropbear_param("wan", "23", "IdleTimeout") + else -- for all other console types, by default ssh wan side idle timeout is shown + timeout = get_dropbear_param("wan", "22", "IdleTimeout") + end + return IDLE_UCI_TO_WEBUI[timeout] or "0" +end + +--- Retrieves whether current telnet via wan is enabled or not +function M.getRemoteTelnetEnable() + local enable = get_dropbear_param("wan", "23", "enable") + return enable ~= "" and enable or "0" +end + +--- Retrieves timeout value of remote telnet access +function M.getRemoteTelnetIdleTimeout() + local timeout = get_dropbear_param("wan", "23", "IdleTimeout") + return IDLE_UCI_TO_WEBUI[timeout] or "0" +end + +local dropBearSettings = { + [CONSOLE_DISABLED] = {"0","0","0","0"}, + [CONSOLE_LAN_SSH] = {"1","0","0","0"}, + [CONSOLE_WAN_SSH]= { "0", "1", "0", "0"}, + [CONSOLE_LANWAN_SSH] = { "1", "1", "0", "0"}, + [CONSOLE_LAN_TELNET] = { "0", "0", "1", "0"}, + [CONSOLE_WAN_TELNET] = { "0", "0", "0", "1"}, + [CONSOLE_LANWAN_TELNET] = { "0", "0", "1", "1"}, +} + +local fireWallSettings = { + [CONSOLE_DISABLED] = {"REJECT","REJECT","REJECT"}, + [CONSOLE_LAN_SSH] = {"REJECT","REJECT","REJECT"}, + [CONSOLE_WAN_SSH]= {"ACCEPT", "REJECT", "REJECT"}, + [CONSOLE_LANWAN_SSH] = {"ACCEPT", "REJECT", "REJECT"}, + [CONSOLE_LAN_TELNET] = {"REJECT" ,"REJECT","ACCEPT"}, + [CONSOLE_WAN_TELNET] = {"REJECT" ,"ACCEPT","REJECT"}, + [CONSOLE_LANWAN_TELNET] = {"REJECT" ,"ACCEPT","ACCEPT"}, +} + +local clashSettings = { + [CONSOLE_DISABLED] = "0", + [CONSOLE_LAN_SSH] = "0", + [CONSOLE_WAN_SSH]= "0", + [CONSOLE_LANWAN_SSH] = "0", + [CONSOLE_LAN_TELNET] = "1", + [CONSOLE_WAN_TELNET] = "1", + [CONSOLE_LANWAN_TELNET] = "1", +} + +--- Changes remote console type, based on required access via lan, wan, ssh or telnet. +-- @param #string Console type enum to be set. +-- @return true +function M.setRemoteConsoleType(value, commitapply) +-- update dropbear console flags based on CURRENT value + local curruser, exists = get_user_to_update() + set_dropbear_param("lan", "22", "enable", dropBearSettings[value][1], commitapply) + set_dropbear_param("wan", "22", "enable", dropBearSettings[value][2], commitapply) + set_dropbear_param("lan", "23", "enable", dropBearSettings[value][3], commitapply) + set_dropbear_param("wan", "23", "enable", dropBearSettings[value][4], commitapply) + set_clash_param(curruser, "telnet", clashSettings[value], commitapply) + -- toggle wan based firewall rules + set_iptables_rule("wan", "22", fireWallSettings[value][1], commitapply) + set_iptables_rule("wan", "23", fireWallSettings[value][2], commitapply) + -- toggle lan based firewall rules / lan ssh rule is not modified as per legacy behaviour + set_iptables_rule("lan", "23", fireWallSettings[value][3], commitapply) + return true +end + +--- Modifies username of existing user other than super user. +-- @param #string value username to be set. +-- @return nil +function M.setRemoteConsoleUserName(value, commitapply) + -- invoke method to update new login (provided current user account exists) + local curruser, exists = get_user_to_update() + if not curruser or not exists then + return nil, format("user account %s not found, cannot change username", curruser) + end + -- update uci settings + dropbearBinding.sectionname = "global" + dropbearBinding.option = "AdminUser" + set_on_uci(dropbearBinding, value, commitapply) + transactions[dropbearBinding.config] = true + -- process information + if lower(value) ~= "superuser" then + return change_remoteconsole_userdata(curruser, value, commitapply) + else + -- Blacklist superuser as a configurable username + return nil, "username not allowed" + end +end + +--- Sets Remote console user's password after checking current user, password strength. +-- @param #string value password of user to be set. +-- @return true or nil +function M.setRemoteConsolePassword(value, commitapply) + local curruser, exists = get_user_to_update() + if not curruser or not exists then + return nil, format("user account %s not found, cannot change passwd", curruser) + end + -- don't change password if invalid data received + if check_remoteconsole_password(value) then + local p = io.popen("chpasswd", "w") + if p then + p:write(format("%s:%s", curruser, value)) + p:close() + end + -- update uci settings + dropbearBinding.sectionname = "global" + dropbearBinding.option = "hPass" + -- replace with hidden characters to avoid exposing as plain-text + value = value:gsub("(%S)", "*") + set_on_uci(dropbearBinding, value, commitapply) + transactions[dropbearBinding.config] = true + else + return nil, "password doesn't meet the requirements" + end + return true +end + +--- Sets Remote console idle timeout. +-- @param #string value enum code of idle timeout. +-- @return true +function M.setRemoteConsoleIdleTimeout(value, commitapply) + set_dropbear_param("lan", "22", "IdleTimeout", IDLE_WEBUI_TO_UCI[value] or "0", commitapply) + set_dropbear_param("wan", "22", "IdleTimeout", IDLE_WEBUI_TO_UCI[value] or "0", commitapply) + set_dropbear_param("lan", "23", "IdleTimeout", IDLE_WEBUI_TO_UCI[value] or "0", commitapply) + set_dropbear_param("wan", "23", "IdleTimeout", IDLE_WEBUI_TO_UCI[value] or "0", commitapply) + -- send ubus event to trigger remote console access handler (shellremoteaccessd-tch.lua) + -- event is not sent from shelleventerd-tch daemon because + -- we want to make sure only one event is sent out for shell configuration change + ubus:send("shellremoteaccess", { access = "start", timeout = IDLE_WEBUI_TO_UCI[value] or "0" }) + return true +end + +local wanTelnetRule = { + ['0'] = "REJECT", + ['1'] = "ACCEPT" +} + +--- Enables/Disables Remote console access via Telnet. +-- @param #string value of 0 (Disable) or 1 (Enable). +function M.setRemoteTelnetEnable(value, commitapply) + set_dropbear_param("wan", "23", "enable", value, commitapply) + return set_iptables_rule("wan","23",wanTelnetRule[value], commitapply) +end + +--- sets timeout for Remote console access via Telnet. +-- @param #string value of timeout. +function M.setRemoteTelnetTimeout(value, commitapply) + set_dropbear_param("wan", "23", "IdleTimeout", IDLE_WEBUI_TO_UCI[value] or "0", commitapply) + -- send ubus event to trigger remote console access handler (shellremoteaccessd-tch.lua) + -- event is not sent from shelleventerd-tch daemon because + -- we want to make sure only one event is sent out for shell configuration change + ubus:send("shellremoteaccess", { access = "start", timeout = IDLE_WEBUI_TO_UCI[value] or "0" }) +end + +--- Commits recent set actions in dropbear, firewall and clash config. +function M.commit_remoteconsole_data() + for config in pairs(transactions) do + configBinding.config = config + uciHelper.commit(configBinding) + end + transactions = {} +end + +--- Reverts recent set actions in dropbear, firewall and clash config. +function M.revert_remoteconsole_data() + for config in pairs(transactions) do + configBinding.config = config + uciHelper.revert(configBinding) + end + transactions = {} +end + +return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/servicedefault.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/servicedefault.lua index 6eee0edf2..1f38c4c88 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/servicedefault.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/servicedefault.lua @@ -60,6 +60,11 @@ local add_cfg = { activated = "0", servicetype = "profile" }, + DND = { + provisioned = "1", + activated = "0", + servicetype = "profile" + }, } -- This talbe is all the default configuration for services @@ -68,7 +73,7 @@ local add_cfg = { local services_default_cfg = { append = append_cfg, add = add_cfg, - named_service_section = false + named_service_section = true } -- Default configuration for a dial_plan_entry when added via TR-69 is defined here diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/timezone.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/timezone.lua index a06737bf6..510b28af9 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/timezone.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/timezone.lua @@ -219,7 +219,7 @@ function M.getLocaltimezoneWithoutSDTDST(value) local timeZoneFlag = false local timezoneValue, dsttimezoneValue = value:match("^%a+([-+]?%d+:?%d*)%a+([-+]?%d+:?%d*)$") if not timezoneValue then - timezoneValue, dsttimezoneValue = value:match("^%a+([-+]?%d+:?%d*)%a+$") + timezoneValue, dsttimezoneValue = value:match("^%a+([-+]?%d+:?%d*)%s?%a+$") end if not timezoneValue then timezoneValue = value:match("^%a+([-+]?%d+:?%d*)$") diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/tr143helper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/tr143helper.lua index 409652adb..55c0f5e31 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/tr143helper.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/tr143helper.lua @@ -4,7 +4,7 @@ local pairs = pairs local uci = require 'transformer.mapper.ucihelper' local common = require 'transformer.mapper.nwcommon' local split_key = common.split_key -local findLanWanInterfaces = common.findLanWanInterfaces +local network = require("transformer.shared.common.network") local wanconn = require 'transformer.shared.wanconnection' local transactions = {} local resolve, tokey @@ -18,21 +18,22 @@ local paramMap = { X_0876FF_TestBytesSentUnderFullLoading = "TestBytesSentUnderFullLoading", } -local function resolveInterface(user, value) - local path - local lanInterfaces = findLanWanInterfaces(false) - local isLan = false - for _,j in pairs(lanInterfaces) do - if (value == j) then - isLan = true - break +local function isLanInterface(value) + local lanInterfaces = network.getLanInterfaces() + for intf in pairs(lanInterfaces) do + if value == intf then + return true end - end + end + return false +end +local function resolveInterface(user, value) + local path = "" if user == "device2" then path = resolve("Device.IP.Interface.{i}.", value) else - if (isLan) then + if isLanInterface(value) then path = resolve('InternetGatewayDevice.LANDevice.{i}.LANHostConfigManagement.IPInterface.{i}.', value) else local key, status = wanconn.get_connection_key(value) @@ -80,7 +81,7 @@ local function setInterface(user, param, value) end function M.tr143_get(config, user, pname) - local value + local value = "" if pname == "UploadTransports" or pname == "DownloadTransports" then return "HTTP,FTP" @@ -121,7 +122,6 @@ function M.tr143_get(config, user, pname) tr143binding.option = paramMap[pname] or pname value = uci.get_from_uci(tr143binding) - if pname == "Interface" and value ~= "" then value = resolveInterface(user, value) end diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/traceroutehelper.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/traceroutehelper.lua index bfee7df18..f58f2d33f 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/traceroutehelper.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/traceroutehelper.lua @@ -126,10 +126,11 @@ function M.startup(user, binding) uci.set_on_uci(uci_binding[user]["DataBlockSize"], 38) uci.set_on_uci(uci_binding[user]["DSCP"], 0) uci.set_on_uci(uci_binding[user]["MaxHopCount"], 30) + uci.set_on_uci(uci_binding[user]["ProtocolVersion"], "IPv4") if user == "webui" then - uci.set_on_uci(uci_binding[user]["ipType"], "ipv4") + uci.set_on_uci(uci_binding[user]["Timeout"], 3000) end - else + else local value = uci.get_from_uci({config = "traceroute", sectionname = user}) if value == '' then uci.set_on_uci({config = "traceroute", sectionname = user},"user") @@ -139,8 +140,9 @@ function M.startup(user, binding) uci.set_on_uci(uci_binding[user]["DataBlockSize"], 38) uci.set_on_uci(uci_binding[user]["DSCP"], 0) uci.set_on_uci(uci_binding[user]["MaxHopCount"], 30) + uci.set_on_uci(uci_binding[user]["ProtocolVersion"], "IPv4") if user == "webui" then - uci.set_on_uci(uci_binding[user]["ipType"], "ipv4") + uci.set_on_uci(uci_binding[user]["Timeout"], 3000) end end end @@ -162,6 +164,7 @@ function M.uci_traceroute_get(user, pname) DataBlockSize = { config = config, sectionname = user, option = "size" }, DSCP = { config = config, sectionname = user, option = "dscp" }, MaxHopCount = { config = config, sectionname = user, option = "hopcount" }, + ProtocolVersion = { config = config, sectionname = user, option = "type" }, } end diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/uciobject.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/uciobject.lua new file mode 100644 index 000000000..30b26013b --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/uciobject.lua @@ -0,0 +1,77 @@ + +local ucihelper = require 'transformer.mapper.ucihelper' + +local M = {} + +local _uci_to_commit = {} + +local function register_uci_change(config) + _uci_to_commit[config] = true +end + +local function complete_uci_transaction(action) + for config, _ in pairs(_uci_to_commit) do + action{config=config} + end + _uci_to_commit = {} +end + +function M.commit() + complete_uci_transaction(ucihelper.commit) +end + +function M.revert() + complete_uci_transaction(ucihelper.revert) +end + +local GeneratedKey = {} +M.GeneratedKey = GeneratedKey + +local function create_new_uci_section(binding, prefix, commitapply) + local index = 0 + local pattern = prefix.."_(%d+)" + ucihelper.foreach_on_uci(binding, function(s) + local suffix = tonumber(s['.name']:match(pattern) or "0") + if suffix and (suffix>index) then + index = suffix + end + end) + local section = binding.sectionname + local name = (prefix.."_%d"):format(index+1) + binding.sectionname = name + ucihelper.set_on_uci(binding, section, commitapply) + register_uci_change(binding.config) + return name +end + +function M.create(config, section, prefix, defaults, commitapply) + local binding = { + config = config, + sectionname = section, + } + local key = create_new_uci_section(binding, prefix, commitapply) + for option, value in pairs(defaults or {}) do + binding.option = option + if value == GeneratedKey then + value = binding.sectionname + end + ucihelper.set_on_uci(binding, value, commitapply) + end + return key +end + +function M.delete(config, section, commitapply) + ucihelper.delete_on_uci({config=config, sectionname=section}, commitapply) + register_uci_change(config) +end + +function M.is_dynamic(config, section) + local binding = { + config = config, + sectionname = section, + option = "dev2_dynamic" + } + return ucihelper.get_from_uci(binding)=="1" +end + +return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/uciswitch.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/uciswitch.lua index 28bfe58ca..cf175ccbe 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/uciswitch.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/uciswitch.lua @@ -120,25 +120,26 @@ local function log_error(switcher, msg) end local function initSwitcher(switcher, configname) - local map, err = loadConfig(configname) + local config_map, err = loadConfig(configname) if err then log_error(switcher, err) end - map = map or {} - - local map_valid = true - for _, sectionDef in ipairs(map) do + config_map = config_map or {} + + local map = {} + for _, sectionDef in ipairs(config_map) do if type(sectionDef)~='table' then -- the map is invalid, do not use it. - map_valid = false log_error(switcher, "all entries in the config map must be tables") break end sectionDef.switcher = switcher - + if sectionDef.execute~=false then + map[#map+1] = sectionDef + end end - switcher._action_map = map_valid and map or {} + switcher._action_map = map end local function newSwitcher(configname, commitapply) diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/wanconnection.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/wanconnection.lua index 9022e9aad..ecb7bc23e 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/wanconnection.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/wanconnection.lua @@ -33,6 +33,7 @@ Network object hierarchy: --]] local M = {} +local mobile_interface_map = {} --[[ wanconfig is a uci config file stating which connections should be considered @@ -227,7 +228,7 @@ end local proto_list = { ip = {"static", "dhcp", "dhcpv6", "mobiled"}, ppp = {"pppoe", "pppoa","dhcpv6"}, - ipv6 = {"static", "dhcp", "dhcpv6", "mobiled","pppoe", "pppoa"} + ipv6 = {"static", "dhcp", "dhcpv6", "mobiled","pppoe", "pppoa", "6rd"} } -- check if the given proto matches the connection @@ -250,35 +251,103 @@ local function make_key(interface, physical) return format("%s|%s", interface, key) end +----retrive mobile sectionname when interface different with it +----@param interface [string], such as wwan_4 wwan_ppp +----@return sectionname [string], such as wwan +local function getMobileSectionName(interface) + local network_sectionname + if mobile_interface_map[interface] then + network_sectionname = mobile_interface_map[interface] + else + network_sectionname = string.gsub(interface, "%_ppp$", "") + if network_sectionname == interface then + network_sectionname = string.gsub(interface, "%_4$", "") + end + ucinw.sectionname = network_sectionname + ucinw.option = "proto" + local proto = uci.get_from_uci(ucinw) + if proto ~= "mobiled" then + return + end + end + return network_sectionname +end + + +local wandevice = require "transformer.shared.mappings.wan.wandevice" +local function allWanDevices() + local wans = {} + local devices = wandevice.listDevices() + for _, dev in ipairs(devices) do + wans[dev.name] = dev.type + end + return wans +end + +local function lowerLayerForInterface(interface) + local ll_intfs, status = common.get_lower_layers_with_status(interface) + if #ll_intfs>1 then + local wans = allWanDevices() + local active + local ethernet + local first + for _, intf in ipairs(ll_intfs) do + local wanType = wans[intf] + if wanType then + if common.getIntfInfo(intf, "carrier")=="1" then + active = intf + break + elseif not ethernet and (wanType=="ETH") then + ethernet = intf + elseif not first then + first = intf + end + end + end + return active or ethernet or first, true, status + else + return ll_intfs[1], false, status + end +end + --- retrieve the correct key for the given wan interface connection -- @param interface [string] the logical name of the interface --- @return key, status, vlaninfo. vlaninfo is optional and it is not returned for active interface +-- @return key, status, bridge_key +-- in case the interface is a bridged interface the key returned is nil +-- and the bridge_key refers to the most likely device to reference in the bridge -- this function is meant to be used by other mapping that need to retrieve -- a key for a given wan connection. This function will properly handle -- the different vlan scenarios. local function get_connection_key(interface) + ucinw.sectionname = interface + ucinw.option = "proto" + local proto = uci.get_from_uci(ucinw) if not activedevice.isActiveInterface(interface) then - local key, vlaninfo - local conn = {} - conn.vlans, conn.vlanmode = get_vlans() - local ll_intfs, status = common.get_lower_layers_with_status(interface) - if #ll_intfs>1 then - -- ignore bridged devices - return - end - local lower_intf = ll_intfs[1] - if lower_intf then - vlaninfo = make_vlan_info(conn, lower_intf) - key = make_key(interface, vlaninfo.devname) - end - return key, status, vlaninfo - else - local key = "ACTIVE|"..interface - ucinw.sectionname = interface - ucinw.option = "proto" - local proto = uci.get_from_uci(ucinw) - return key, {proto=proto} - end + local key, vlaninfo + local conn = {} + conn.vlans, conn.vlanmode = get_vlans() + local lower_intf, bridge, status = lowerLayerForInterface(interface) + local mobile_sectionname + if proto == "" then + mobile_sectionname = getMobileSectionName(interface) + end + if lower_intf then + vlaninfo = make_vlan_info(conn, lower_intf) + if mobile_sectionname then + key = make_key(interface, mobile_sectionname) + else + key = make_key(interface, vlaninfo.devname) + end + end + if not bridge then + return key, status + else + return nil, status, key + end + else + local key = "ACTIVE|"..interface + return key, {proto=proto} + end end M.get_connection_key = get_connection_key @@ -305,11 +374,24 @@ local function remove_entries(list, to_remove) return list end +--- Checks if the given interface is present in the wanInterfaces list +-- @param intf interface name to be checked in the list +-- @param wanInterfaces wan interfaces list +-- @return true if the given interface is a wan interface, otherwise false. +local function isWanInterface(intf, wanInterfaces) + for _, wanInterface in ipairs(wanInterfaces) do + if intf == wanInterface then + return true + end + end + return false +end + local function real_getKeys(self, devname) -- add the fixed IP devices local wan_intf = get_wanconfig(self.connType) - -- devname may be of format atm_wan|dsl1. This is due to two dsl configured and the 2nd key will have dslname appended. Split and take the first key - local dev = devname:match("^([^|]+)") + -- devname may be of format atm_wan|dsl1. This is due to two dsl configured and the 2nd key will have dslname appended. Split and take the first key + local dev = devname:match("^([^|]+)") for _, s in ipairs(wan_intf) do local vlaninfo = make_vlan_info(self, s.ifname) if vlaninfo.devname==dev then @@ -318,11 +400,32 @@ local function real_getKeys(self, devname) add_entry(self, make_key(s.interface, devname), vlaninfo) end end - + local wanInterfaces = common.findLanWanInterfaces(true) foreach_interface(function(s) - if match_proto(self, s.proto) and not activedevice.isActiveInterface(s['.name']) then + if match_proto(self, s.proto) and not activedevice.isActiveInterface(s['.name']) and isWanInterface(s['.name'], wanInterfaces) then local ref_intf = s.ifname and s.ifname:match("^@(.*)") or s.device and s.device:match("^@(.*)") - if ref_intf and s.proto == "dhcpv6" then + if s.proto == "mobiled" and s[".name"] == devname then + local extensions = { + { v4 = "_4", v6 = "_6" }, + { v4 = "_ppp", v6 = "_ppp_6" } + } + for _, extension in pairs(extensions) do + local interface = s[".name"] .. extension.v4 + local ll_intfs, status = common.get_lower_layers_with_status(interface) + -- Layer 3 interfaces get populated dynamically in mobile interfaces + if ll_intfs and next(ll_intfs) then + local entry = add_entry(self, make_key(interface, devname)) + mobile_interface_map[interface] = devname + entry.active = true + entry.status = status + if not entry.interface then + entry.interface = interface + end + self.interfaces_dhcp6[entry.interface] = s[".name"] .. extension.v6 + end + end + end + if ref_intf and (s.proto == "dhcpv6" or s.proto == "6rd") then self.interfaces_dhcp6[ref_intf] = s[".name"] else local interface = s['.name'] @@ -408,10 +511,11 @@ end --- get a physical interface parameter (from /sys/class/net) -- @param key [string] the key for the entry +-- @param default [] the default value -- @param req_value [string] the name of the info item (eg 'operstate') -- @param layer [string] "L2" for layer2 or "L3" for layer3, L2 is the default -- interface -function ConnectionList:getPhysicalInfo(key, req_value, layer) +function ConnectionList:getPhysicalInfo(key, req_value, layer, default) layer = layer or "L2" local entry = self.entries[key] local devname @@ -421,9 +525,9 @@ function ConnectionList:getPhysicalInfo(key, req_value, layer) devname = entry.status.l3_device or entry.vlandevice end if devname then - return common.getIntfInfo(devname, req_value) + return common.getIntfInfo(devname, req_value, default) end - return "" + return default or "" end --- is the logical interface active @@ -459,7 +563,7 @@ end --- get the device name for the given key -- @param key [string] the transformer key --- @returns the deivde name associated with the key or nil +-- @returns the device name associated with the key or nil function ConnectionList:getDevice(key) local entry = self.entries[key] return entry.devname @@ -497,7 +601,14 @@ function ConnectionList:getInterfaceOption(key, option, default, active) intf_option.sectionname = interface intf_option.option = option intf_option.default = default - return uci.get_from_uci(intf_option) + local data = uci.get_from_uci(intf_option) + if data == "" then + local status = self:getInterfaceStatus(key) + if status and status.dynamic then + return status[option] or default or "" + end + end + return data end return active or default or "" end diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/wifi.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/wifi.lua index 9aeaa7bee..0cf4d5ce2 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/wifi.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/wifi.lua @@ -1,5 +1,7 @@ local M = {} local gmatch, ipairs, concat = string.gmatch, ipairs, table.concat +local uciHelper = require("transformer.mapper.ucihelper") +local wirelessBinding = { config = "wireless" } --- function to convert a string into a map based on the match pattern -- @param #string str the input string that needs to be converted into a map @@ -48,7 +50,7 @@ end function M.setBasicRateset(value,rateset) local ratesetTable = string.gsub(rateset, "%d*%.*%d*%(b%)[,%s]?", "") -- removes all the values containing (b) ratesetTable = toList(ratesetTable, "([^,%s]+)") - local basicRatesetMap, errMsg = toMap(value, "([^,%s]+)", "%d+") -- match all comma or space separated values, validate if match contains numbers + local basicRatesetMap, errMsg = toMap(value, "([^,%s]+)", "^%d+%.?%d*$") -- match all comma or space separated values, validate if match contains numbers if not basicRatesetMap then return nil, errMsg end @@ -75,7 +77,7 @@ end function M.setOperationalRateset(value,rateset) local errMsg local basicRatesetMap = toMap(rateset, "([^,%s]+)%(b%),?") -- match only values containing '(b)' - value, errMsg = toList(value, "([^,%s]+)", "%d+") -- match all comma or space separated values, validate if match contains numbers + value, errMsg = toList(value, "([^,%s]+)", "^%d+%.?%d*$") -- match all comma or space separated values, validate if match contains numbers if not value then return nil, errMsg end @@ -91,4 +93,39 @@ function M.setOperationalRateset(value,rateset) return concat(value," ") end +--- Checks if the given security mode is supported or not +-- @function isSupportedMode +-- @param ap the accesspoint name +-- @param mode given mode to check whether it is in supported security modes +function M.isSupportedMode(ap, mode) + wirelessBinding.sectionname = ap + wirelessBinding.option = "supported_security_modes" + local modeList = uciHelper.get_from_uci(wirelessBinding) + for imode in modeList:gmatch('([^%s]+)') do + if imode == mode then + return true + end + end + return false +end + +-- function to calculate the signal strength of wireless device +function M.getSignalStrength(rssi) + local strength = 1 + if rssi then + if rssi <= -127 then + strength = "1" + elseif rssi < -85 and rssi > -127 then + strength = "2" + elseif rssi == -85 then + strength = "3" + elseif rssi < -75 and rssi > -85 then + strength = "4" + else + strength = "5" + end + end + return strength +end + return M diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/xdslctl.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/xdslctl.lua index 2b1fee7fa..9ce3ee8ac 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/xdslctl.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/xdslctl.lua @@ -323,6 +323,7 @@ local function createTableWithEmptyValues() addEmptyAdslMib( "StandardsSupported", " ") addEmptyAdslMib( "SuccessFailureCause", 0) addEmptyAdslMib( "UPBOKLE", 0) + addEmptyAdslMib( "UPBOKLER", 0) addEmptyAdslMib( "US0MASK", 0) addEmptyAdslMib( "VirtualNoisePSDds", " ", "VirtualNoisePSDus", " ") addEmptyAdslMib( "XTUCANSIRev", 0) @@ -367,7 +368,7 @@ local function createTableWithEmptyValues() addEmptyAdslMib( "ShowtimeSRAStartedDs", 0 ) addEmptyAdslMib( "ShowtimeSRAStartedUs", 0 ) addEmptyAdslMib( "ShowtimeSRACompletedDs", 0 ) - addEmptyAdslMib( "ShowtimeSRACOmpletedUs", 0 ) + addEmptyAdslMib( "ShowtimeSRACompletedUs", 0 ) addEmptyAdslMib( "ShowtimeFRAStartedDs", 0 ) addEmptyAdslMib( "ShowtimeFRAStartedUs", 0 ) addEmptyAdslMib( "ShowtimeFRACompletedDs", 0 ) @@ -509,7 +510,7 @@ end local function getAdslMibInfo(lineid) local line = getLineNum(lineid) local diff = os.time() - lastUpdateTime[line] - if diff > 5 then + if diff > 5 or diff < 0 then tmp = luabcm.getAdslMib(line) lastUpdateTime[line] = os.time() if tostring(tmp) == "-1" then @@ -642,6 +643,7 @@ local paramMap = { ["StandardsSupported"] = {"StandardsSupported"}, ["SuccessFailureCause"] = {"SuccessFailureCause"}, ["UPBOKLE"] = {"UPBOKLE"}, + ["UPBOKLER"] = {"UPBOKLER"}, ["US0MASK"] = {"US0MASK"}, ["VirtualNoisePSD"] = {"VirtualNoisePSDds", "VirtualNoisePSDus"}, ["XTUCANSIRev"] = {"XTUCANSIRev"}, diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/xtmctl.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/xtmctl.lua index f16b30194..af524ff50 100644 --- a/decompressed/gui_file/usr/lib/lua/transformer/shared/xtmctl.lua +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/xtmctl.lua @@ -42,7 +42,8 @@ end -- \param bActive (bool) if true enable else disable -- \returns true if disable works, otherwise return false function M.enableXtmDevice(addr, bActive) - if execute("xtmctl operate conn --state " .. addr .. " " .. (bActive and "enable" or "disable")) ~= 0 then + local Active = bActive and "enable" or "disable" + if execute("xtmctl operate conn --state " .. addr .. " " .. Active) ~= 0 then log:error("enable/disable XTM Device failed") return false end diff --git a/decompressed/gui_file/usr/lib/lua/transformer/shared/zonenames.lua b/decompressed/gui_file/usr/lib/lua/transformer/shared/zonenames.lua new file mode 100644 index 000000000..ef96deedd --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/transformer/shared/zonenames.lua @@ -0,0 +1,422 @@ +return { +{'UTC_timezone_UTC', 'UTC'}, +{'GMT0_timezone_Africa/Abidjan', 'Africa/Abidjan'}, +{'GMT0_timezone_Africa/Accra', 'Africa/Accra'}, +{'EAT-3_timezone_Africa/Addis Ababa', 'Africa/Addis Ababa'}, +{'CET-1_timezone_Africa/Algiers', 'Africa/Algiers'}, +{'EAT-3_timezone_Africa/Asmara', 'Africa/Asmara'}, +{'GMT0_timezone_Africa/Bamako', 'Africa/Bamako'}, +{'WAT-1_timezone_Africa/Bangui', 'Africa/Bangui'}, +{'GMT0_timezone_Africa/Banjul', 'Africa/Banjul'}, +{'GMT0_timezone_Africa/Bissau', 'Africa/Bissau'}, +{'CAT-2_timezone_Africa/Blantyre', 'Africa/Blantyre'}, +{'WAT-1_timezone_Africa/Brazzaville', 'Africa/Brazzaville'}, +{'CAT-2_timezone_Africa/Bujumbura', 'Africa/Bujumbura'}, +{'WET0_timezone_Africa/Casablanca', 'Africa/Casablanca'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Africa/Ceuta', 'Africa/Ceuta'}, +{'GMT0_timezone_Africa/Conakry', 'Africa/Conakry'}, +{'GMT0_timezone_Africa/Dakar', 'Africa/Dakar'}, +{'EAT-3_timezone_Africa/Dar es Salaam', 'Africa/Dar es Salaam'}, +{'EAT-3_timezone_Africa/Djibouti', 'Africa/Djibouti'}, +{'WAT-1_timezone_Africa/Douala', 'Africa/Douala'}, +{'WET0_timezone_Africa/El Aaiun', 'Africa/El Aaiun'}, +{'GMT0_timezone_Africa/Freetown', 'Africa/Freetown'}, +{'CAT-2_timezone_Africa/Gaborone', 'Africa/Gaborone'}, +{'CAT-2_timezone_Africa/Harare', 'Africa/Harare'}, +{'SAST-2_timezone_Africa/Johannesburg', 'Africa/Johannesburg'}, +{'EAT-3_timezone_Africa/Kampala', 'Africa/Kampala'}, +{'EAT-3_timezone_Africa/Khartoum', 'Africa/Khartoum'}, +{'CAT-2_timezone_Africa/Kigali', 'Africa/Kigali'}, +{'WAT-1_timezone_Africa/Kinshasa', 'Africa/Kinshasa'}, +{'WAT-1_timezone_Africa/Lagos', 'Africa/Lagos'}, +{'WAT-1_timezone_Africa/Libreville', 'Africa/Libreville'}, +{'GMT0_timezone_Africa/Lome', 'Africa/Lome'}, +{'WAT-1_timezone_Africa/Luanda', 'Africa/Luanda'}, +{'CAT-2_timezone_Africa/Lubumbashi', 'Africa/Lubumbashi'}, +{'CAT-2_timezone_Africa/Lusaka', 'Africa/Lusaka'}, +{'WAT-1_timezone_Africa/Malabo', 'Africa/Malabo'}, +{'CAT-2_timezone_Africa/Maputo', 'Africa/Maputo'}, +{'SAST-2_timezone_Africa/Maseru', 'Africa/Maseru'}, +{'SAST-2_timezone_Africa/Mbabane', 'Africa/Mbabane'}, +{'EAT-3_timezone_Africa/Mogadishu', 'Africa/Mogadishu'}, +{'GMT0_timezone_Africa/Monrovia', 'Africa/Monrovia'}, +{'EAT-3_timezone_Africa/Nairobi', 'Africa/Nairobi'}, +{'WAT-1_timezone_Africa/Ndjamena', 'Africa/Ndjamena'}, +{'WAT-1_timezone_Africa/Niamey', 'Africa/Niamey'}, +{'GMT0_timezone_Africa/Nouakchott', 'Africa/Nouakchott'}, +{'GMT0_timezone_Africa/Ouagadougou', 'Africa/Ouagadougou'}, +{'WAT-1_timezone_Africa/Porto-Novo', 'Africa/Porto-Novo'}, +{'GMT0_timezone_Africa/Sao Tome', 'Africa/Sao Tome'}, +{'EET-2_timezone_Africa/Tripoli', 'Africa/Tripoli'}, +{'CET-1_timezone_Africa/Tunis', 'Africa/Tunis'}, +{'WAT-1WAST,M9.1.0,M4.1.0_timezone_Africa/Windhoek', 'Africa/Windhoek'}, +{'HAST10HADT,M3.2.0,M11.1.0_timezone_America/Adak', 'America/Adak'}, +{'AKST9AKDT,M3.2.0,M11.1.0_timezone_America/Anchorage', 'America/Anchorage'}, +{'AST4_timezone_America/Anguilla', 'America/Anguilla'}, +{'AST4_timezone_America/Antigua', 'America/Antigua'}, +{'BRT3_timezone_America/Araguaina', 'America/Araguaina'}, +{'ART3_timezone_America/Argentina/Buenos Aires', 'America/Argentina/Buenos Aires'}, +{'ART3_timezone_America/Argentina/Catamarca', 'America/Argentina/Catamarca'}, +{'ART3_timezone_America/Argentina/Cordoba', 'America/Argentina/Cordoba'}, +{'ART3_timezone_America/Argentina/Jujuy', 'America/Argentina/Jujuy'}, +{'ART3_timezone_America/Argentina/La Rioja', 'America/Argentina/La Rioja'}, +{'ART3_timezone_America/Argentina/Mendoza', 'America/Argentina/Mendoza'}, +{'ART3_timezone_America/Argentina/Rio Gallegos', 'America/Argentina/Rio Gallegos'}, +{'ART3_timezone_America/Argentina/Salta', 'America/Argentina/Salta'}, +{'ART3_timezone_America/Argentina/San Juan', 'America/Argentina/San Juan'}, +{'ART3_timezone_America/Argentina/Tucuman', 'America/Argentina/Tucuman'}, +{'ART3_timezone_America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'}, +{'AST4_timezone_America/Aruba', 'America/Aruba'}, +{'PYT4PYST,M10.1.0/0,M4.2.0/0_timezone_America/Asuncion', 'America/Asuncion'}, +{'EST5_timezone_America/Atikokan', 'America/Atikokan'}, +{'BRT3_timezone_America/Bahia', 'America/Bahia'}, +{'AST4_timezone_America/Barbados', 'America/Barbados'}, +{'BRT3_timezone_America/Belem', 'America/Belem'}, +{'CST6_timezone_America/Belize', 'America/Belize'}, +{'AST4_timezone_America/Blanc-Sablon', 'America/Blanc-Sablon'}, +{'AMT4_timezone_America/Boa Vista', 'America/Boa Vista'}, +{'COT5_timezone_America/Bogota', 'America/Bogota'}, +{'MST7MDT,M3.2.0,M11.1.0_timezone_America/Boise', 'America/Boise'}, +{'MST7MDT,M3.2.0,M11.1.0_timezone_America/Cambridge Bay', 'America/Cambridge Bay'}, +{'AMT4AMST,M10.3.0/0,M2.3.0/0_timezone_America/Campo Grande', 'America/Campo Grande'}, +{'CST6CDT,M4.1.0,M10.5.0_timezone_America/Cancun', 'America/Cancun'}, +{'VET4:30_timezone_America/Caracas', 'America/Caracas'}, +{'GFT3_timezone_America/Cayenne', 'America/Cayenne'}, +{'EST5_timezone_America/Cayman', 'America/Cayman'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/Chicago', 'America/Chicago'}, +{'MST7MDT,M4.1.0,M10.5.0_timezone_America/Chihuahua', 'America/Chihuahua'}, +{'CST6_timezone_America/Costa Rica', 'America/Costa Rica'}, +{'AMT4AMST,M10.3.0/0,M2.3.0/0_timezone_America/Cuiaba', 'America/Cuiaba'}, +{'AST4_timezone_America/Curacao', 'America/Curacao'}, +{'GMT0_timezone_America/Danmarkshavn', 'America/Danmarkshavn'}, +{'PST8PDT,M3.2.0,M11.1.0_timezone_America/Dawson', 'America/Dawson'}, +{'MST7_timezone_America/Dawson Creek', 'America/Dawson Creek'}, +{'MST7MDT,M3.2.0,M11.1.0_timezone_America/Denver', 'America/Denver'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Detroit', 'America/Detroit'}, +{'AST4_timezone_America/Dominica', 'America/Dominica'}, +{'MST7MDT,M3.2.0,M11.1.0_timezone_America/Edmonton', 'America/Edmonton'}, +{'AMT4_timezone_America/Eirunepe', 'America/Eirunepe'}, +{'CST6_timezone_America/El Salvador', 'America/El Salvador'}, +{'BRT3_timezone_America/Fortaleza', 'America/Fortaleza'}, +{'AST4ADT,M3.2.0,M11.1.0_timezone_America/Glace Bay', 'America/Glace Bay'}, +{'AST4ADT,M3.2.0/0:01,M11.1.0/0:01_timezone_America/Goose Bay', 'America/Goose Bay'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Grand Turk', 'America/Grand Turk'}, +{'AST4_timezone_America/Grenada', 'America/Grenada'}, +{'AST4_timezone_America/Guadeloupe', 'America/Guadeloupe'}, +{'CST6_timezone_America/Guatemala', 'America/Guatemala'}, +{'ECT5_timezone_America/Guayaquil', 'America/Guayaquil'}, +{'GYT4_timezone_America/Guyana', 'America/Guyana'}, +{'AST4ADT,M3.2.0,M11.1.0_timezone_America/Halifax', 'America/Halifax'}, +{'CST5CDT,M3.2.0/0,M10.5.0/1_timezone_America/Havana', 'America/Havana'}, +{'MST7_timezone_America/Hermosillo', 'America/Hermosillo'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/Indiana/Knox', 'America/Indiana/Knox'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Indiana/Marengo', 'America/Indiana/Marengo'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Indiana/Petersburg', 'America/Indiana/Petersburg'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/Indiana/Tell City', 'America/Indiana/Tell City'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Indiana/Vevay', 'America/Indiana/Vevay'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Indiana/Vincennes', 'America/Indiana/Vincennes'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Indiana/Winamac', 'America/Indiana/Winamac'}, +{'MST7MDT,M3.2.0,M11.1.0_timezone_America/Inuvik', 'America/Inuvik'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Iqaluit', 'America/Iqaluit'}, +{'EST5_timezone_America/Jamaica', 'America/Jamaica'}, +{'AKST9AKDT,M3.2.0,M11.1.0_timezone_America/Juneau', 'America/Juneau'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Kentucky/Louisville', 'America/Kentucky/Louisville'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Kentucky/Monticello', 'America/Kentucky/Monticello'}, +{'BOT4_timezone_America/La Paz', 'America/La Paz'}, +{'PET5_timezone_America/Lima', 'America/Lima'}, +{'PST8PDT,M3.2.0,M11.1.0_timezone_America/Los Angeles', 'America/Los Angeles'}, +{'BRT3_timezone_America/Maceio', 'America/Maceio'}, +{'CST6_timezone_America/Managua', 'America/Managua'}, +{'AMT4_timezone_America/Manaus', 'America/Manaus'}, +{'AST4_timezone_America/Marigot', 'America/Marigot'}, +{'AST4_timezone_America/Martinique', 'America/Martinique'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/Matamoros', 'America/Matamoros'}, +{'MST7MDT,M4.1.0,M10.5.0_timezone_America/Mazatlan', 'America/Mazatlan'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/Menominee', 'America/Menominee'}, +{'CST6CDT,M4.1.0,M10.5.0_timezone_America/Merida', 'America/Merida'}, +{'CST6CDT,M4.1.0,M10.5.0_timezone_America/Mexico City', 'America/Mexico City'}, +{'PMST3PMDT,M3.2.0,M11.1.0_timezone_America/Miquelon', 'America/Miquelon'}, +{'AST4ADT,M3.2.0,M11.1.0_timezone_America/Moncton', 'America/Moncton'}, +{'CST6CDT,M4.1.0,M10.5.0_timezone_America/Monterrey', 'America/Monterrey'}, +{'UYT3UYST,M10.1.0,M3.2.0_timezone_America/Montevideo', 'America/Montevideo'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Montreal', 'America/Montreal'}, +{'AST4_timezone_America/Montserrat', 'America/Montserrat'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Nassau', 'America/Nassau'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/New York', 'America/New York'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Nipigon', 'America/Nipigon'}, +{'AKST9AKDT,M3.2.0,M11.1.0_timezone_America/Nome', 'America/Nome'}, +{'FNT2_timezone_America/Noronha', 'America/Noronha'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/North Dakota/Center', 'America/North Dakota/Center'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/North Dakota/New Salem', 'America/North Dakota/New Salem'}, +{'MST7MDT,M3.2.0,M11.1.0_timezone_America/Ojinaga', 'America/Ojinaga'}, +{'EST5_timezone_America/Panama', 'America/Panama'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Pangnirtung', 'America/Pangnirtung'}, +{'SRT3_timezone_America/Paramaribo', 'America/Paramaribo'}, +{'MST7_timezone_America/Phoenix', 'America/Phoenix'}, +{'AST4_timezone_America/Port of Spain', 'America/Port of Spain'}, +{'EST5_timezone_America/Port-au-Prince', 'America/Port-au-Prince'}, +{'AMT4_timezone_America/Porto Velho', 'America/Porto Velho'}, +{'AST4_timezone_America/Puerto Rico', 'America/Puerto Rico'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/Rainy River', 'America/Rainy River'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/Rankin Inlet', 'America/Rankin Inlet'}, +{'BRT3_timezone_America/Recife', 'America/Recife'}, +{'CST6_timezone_America/Regina', 'America/Regina'}, +{'AMT4_timezone_America/Rio Branco', 'America/Rio Branco'}, +{'PST8PDT,M4.1.0,M10.5.0_timezone_America/Santa Isabel', 'America/Santa Isabel'}, +{'BRT3_timezone_America/Santarem', 'America/Santarem'}, +{'AST4_timezone_America/Santo Domingo', 'America/Santo Domingo'}, +{'BRT3BRST,M10.3.0/0,M2.3.0/0_timezone_America/Sao Paulo', 'America/Sao Paulo'}, +{'EGT1EGST,M3.5.0/0,M10.5.0/1_timezone_America/Scoresbysund', 'America/Scoresbysund'}, +{'MST7MDT,M3.2.0,M11.1.0_timezone_America/Shiprock', 'America/Shiprock'}, +{'AST4_timezone_America/St Barthelemy', 'America/St Barthelemy'}, +{'NST3:30NDT,M3.2.0/0:01,M11.1.0/0:01_timezone_America/St Johns', 'America/St Johns'}, +{'AST4_timezone_America/St Kitts', 'America/St Kitts'}, +{'AST4_timezone_America/St Lucia', 'America/St Lucia'}, +{'AST4_timezone_America/St Thomas', 'America/St Thomas'}, +{'AST4_timezone_America/St Vincent', 'America/St Vincent'}, +{'CST6_timezone_America/Swift Current', 'America/Swift Current'}, +{'CST6_timezone_America/Tegucigalpa', 'America/Tegucigalpa'}, +{'AST4ADT,M3.2.0,M11.1.0_timezone_America/Thule', 'America/Thule'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Thunder Bay', 'America/Thunder Bay'}, +{'PST8PDT,M3.2.0,M11.1.0_timezone_America/Tijuana', 'America/Tijuana'}, +{'EST5EDT,M3.2.0,M11.1.0_timezone_America/Toronto', 'America/Toronto'}, +{'AST4_timezone_America/Tortola', 'America/Tortola'}, +{'PST8PDT,M3.2.0,M11.1.0_timezone_America/Vancouver', 'America/Vancouver'}, +{'PST8PDT,M3.2.0,M11.1.0_timezone_America/Whitehorse', 'America/Whitehorse'}, +{'CST6CDT,M3.2.0,M11.1.0_timezone_America/Winnipeg', 'America/Winnipeg'}, +{'AKST9AKDT,M3.2.0,M11.1.0_timezone_America/Yakutat', 'America/Yakutat'}, +{'MST7MDT,M3.2.0,M11.1.0_timezone_America/Yellowknife', 'America/Yellowknife'}, +{'ADT3_timezone_America/Atlantic Daylight', 'America/Atlantic Daylight'}, +{'AST4_timezone_America/Atlantic Standard', 'America/Atlantic Standard'}, +{'AKDT8_timezone_America/Alaska Daylight', 'America/Alaska Daylight'}, +{'AKST9_timezone_America/Alaska Standard', 'America/Alaska Standard'}, +{'CDT5_timezone_America/Central Daylight', 'America/Central Daylight'}, +{'CST6_timezone_America/Central Standard', 'America/Central Standard'}, +{'EDT4_timezone_America/Eastern Daylight', 'America/Eastern Daylight'}, +{'EST5_timezone_America/Eastern Standard', 'America/Eastern Standard'}, +{'EGT1_timezone_America/Eastern Greenland', 'America/Eastern Greenland'}, +{'EGST0_timezone_America/Eastern Greenland Summer', 'America/Eastern Greenland Summer'}, +{'HADT9_timezone_America/Hawaii-Aleutian Daylight', 'America/Hawaii-Aleutian Daylight'}, +{'HAST10_timezone_America/Hawaii-Aleutian Standard', 'America/Hawaii-Aleutian Standard'}, +{'HST10_timezone_America/Hawaii Standard', 'America/Hawaii Standard'}, +{'MDT6_timezone_America/Mountain Daylight', 'America/Mountain Daylight'}, +{'MST7_timezone_America/Mountain Standard', 'America/Mountain Standard'}, +{'MeST8_timezone_America/Metlakatla', 'America/Metlakatla'}, +{'NDT2:30_timezone_America/Newfoundland Daylight', 'America/Newfoundland Daylight'}, +{'NST3:30_timezone_America/Newfoundland Standard', 'America/Newfoundland Standard'}, +{'PDT7_timezone_America/Pacific Daylight', 'America/Pacific Daylight'}, +{'PST8_timezone_America/Pacific Standard', 'America/Pacific Standard'}, +{'PMDT2_timezone_America/Pierre & Miquelon Daylight', 'America/Pierre & Miquelon Daylight'}, +{'PMST3_timezone_America/Pierre & Miquelon Standard', 'America/Pierre & Miquelon Standard'}, +{'WGT3_timezone_America/Western Greenland', 'America/Western Greenland'}, +{'WGST2_timezone_America/Western Greenland Summer', 'America/Western Greenland Summer'}, +{'WST-8_timezone_Antarctica/Casey', 'Antarctica/Casey'}, +{'DAVT-7_timezone_Antarctica/Davis', 'Antarctica/Davis'}, +{'DDUT-10_timezone_Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'}, +{'MIST-11_timezone_Antarctica/Macquarie', 'Antarctica/Macquarie'}, +{'MAWT-5_timezone_Antarctica/Mawson', 'Antarctica/Mawson'}, +{'NZST-12NZDT,M9.5.0,M4.1.0/3_timezone_Antarctica/McMurdo', 'Antarctica/McMurdo'}, +{'ROTT3_timezone_Antarctica/Rothera', 'Antarctica/Rothera'}, +{'NZST-12NZDT,M9.5.0,M4.1.0/3_timezone_Antarctica/South Pole', 'Antarctica/South Pole'}, +{'SYOT-3_timezone_Antarctica/Syowa', 'Antarctica/Syowa'}, +{'VOST-6_timezone_Antarctica/Vostok', 'Antarctica/Vostok'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Arctic/Longyearbyen', 'Arctic/Longyearbyen'}, +{'AST-3_timezone_Asia/Aden', 'Asia/Aden'}, +{'ALMT-6_timezone_Asia/Almaty', 'Asia/Almaty'}, +{'ANAT-11ANAST,M3.5.0,M10.5.0/3_timezone_Asia/Anadyr', 'Asia/Anadyr'}, +{'AQTT-5_timezone_Asia/Aqtau', 'Asia/Aqtau'}, +{'AQTT-5_timezone_Asia/Aqtobe', 'Asia/Aqtobe'}, +{'TMT-5_timezone_Asia/Ashgabat', 'Asia/Ashgabat'}, +{'AST-3_timezone_Asia/Baghdad', 'Asia/Baghdad'}, +{'AST-3_timezone_Asia/Bahrain', 'Asia/Bahrain'}, +{'AZT-4AZST,M3.5.0/4,M10.5.0/5_timezone_Asia/Baku', 'Asia/Baku'}, +{'ICT-7_timezone_Asia/Bangkok', 'Asia/Bangkok'}, +{'EET-2EEST,M3.5.0/0,M10.5.0/0_timezone_Asia/Beirut', 'Asia/Beirut'}, +{'KGT-6_timezone_Asia/Bishkek', 'Asia/Bishkek'}, +{'BNT-8_timezone_Asia/Brunei', 'Asia/Brunei'}, +{'CHOT-8_timezone_Asia/Choibalsan', 'Asia/Choibalsan'}, +{'CST-8_timezone_Asia/Chongqing', 'Asia/Chongqing'}, +{'IST-5:30_timezone_Asia/Colombo', 'Asia/Colombo'}, +{'EET-2EEST,M4.1.5/0,M10.5.5/0_timezone_Asia/Damascus', 'Asia/Damascus'}, +{'BDT-6_timezone_Asia/Dhaka', 'Asia/Dhaka'}, +{'TLT-9_timezone_Asia/Dili', 'Asia/Dili'}, +{'GST-4_timezone_Asia/Dubai', 'Asia/Dubai'}, +{'TJT-5_timezone_Asia/Dushanbe', 'Asia/Dushanbe'}, +{'EET-2EEST,M3.5.6/0:01,M9.1.5_timezone_Asia/Gaza', 'Asia/Gaza'}, +{'CST-8_timezone_Asia/Harbin', 'Asia/Harbin'}, +{'ICT-7_timezone_Asia/Ho Chi Minh', 'Asia/Ho Chi Minh'}, +{'HKT-8_timezone_Asia/Hong Kong', 'Asia/Hong Kong'}, +{'HOVT-7_timezone_Asia/Hovd', 'Asia/Hovd'}, +{'IRKT-8IRKST,M3.5.0,M10.5.0/3_timezone_Asia/Irkutsk', 'Asia/Irkutsk'}, +{'WIT-7_timezone_Asia/Jakarta', 'Asia/Jakarta'}, +{'EIT-9_timezone_Asia/Jayapura', 'Asia/Jayapura'}, +{'AFT-4:30_timezone_Asia/Kabul', 'Asia/Kabul'}, +{'PETT-11PETST,M3.5.0,M10.5.0/3_timezone_Asia/Kamchatka', 'Asia/Kamchatka'}, +{'PKT-5_timezone_Asia/Karachi', 'Asia/Karachi'}, +{'CST-8_timezone_Asia/Kashgar', 'Asia/Kashgar'}, +{'NPT-5:45_timezone_Asia/Kathmandu', 'Asia/Kathmandu'}, +{'IST-5:30_timezone_Asia/Kolkata', 'Asia/Kolkata'}, +{'KRAT-7KRAST,M3.5.0,M10.5.0/3_timezone_Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'}, +{'MYT-8_timezone_Asia/Kuala Lumpur', 'Asia/Kuala Lumpur'}, +{'MYT-8_timezone_Asia/Kuching', 'Asia/Kuching'}, +{'AST-3_timezone_Asia/Kuwait', 'Asia/Kuwait'}, +{'CST-8_timezone_Asia/Macau', 'Asia/Macau'}, +{'MAGT-11MAGST,M3.5.0,M10.5.0/3_timezone_Asia/Magadan', 'Asia/Magadan'}, +{'CIT-8_timezone_Asia/Makassar', 'Asia/Makassar'}, +{'PHT-8_timezone_Asia/Manila', 'Asia/Manila'}, +{'GST-4_timezone_Asia/Muscat', 'Asia/Muscat'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Asia/Nicosia', 'Asia/Nicosia'}, +{'NOVT-6NOVST,M3.5.0,M10.5.0/3_timezone_Asia/Novokuznetsk', 'Asia/Novokuznetsk'}, +{'NOVT-6NOVST,M3.5.0,M10.5.0/3_timezone_Asia/Novosibirsk', 'Asia/Novosibirsk'}, +{'OMST-7_timezone_Asia/Omsk', 'Asia/Omsk'}, +{'ORAT-5_timezone_Asia/Oral', 'Asia/Oral'}, +{'ICT-7_timezone_Asia/Phnom Penh', 'Asia/Phnom Penh'}, +{'WIT-7_timezone_Asia/Pontianak', 'Asia/Pontianak'}, +{'KST-9_timezone_Asia/Pyongyang', 'Asia/Pyongyang'}, +{'AST-3_timezone_Asia/Qatar', 'Asia/Qatar'}, +{'QYZT-6_timezone_Asia/Qyzylorda', 'Asia/Qyzylorda'}, +{'MMT-6:30_timezone_Asia/Rangoon', 'Asia/Rangoon'}, +{'AST-3_timezone_Asia/Riyadh', 'Asia/Riyadh'}, +{'SAKT-10SAKST,M3.5.0,M10.5.0/3_timezone_Asia/Sakhalin', 'Asia/Sakhalin'}, +{'UZT-5_timezone_Asia/Samarkand', 'Asia/Samarkand'}, +{'KST-9_timezone_Asia/Seoul', 'Asia/Seoul'}, +{'CST-8_timezone_Asia/Shanghai', 'Asia/Shanghai'}, +{'SGT-8_timezone_Asia/Singapore', 'Asia/Singapore'}, +{'CST-8_timezone_Asia/Taipei', 'Asia/Taipei'}, +{'UZT-5_timezone_Asia/Tashkent', 'Asia/Tashkent'}, +{'GET-4_timezone_Asia/Tbilisi', 'Asia/Tbilisi'}, +{'BTT-6_timezone_Asia/Thimphu', 'Asia/Thimphu'}, +{'JST-9_timezone_Asia/Tokyo', 'Asia/Tokyo'}, +{'ULAT-8_timezone_Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'}, +{'CST-8_timezone_Asia/Urumqi', 'Asia/Urumqi'}, +{'ICT-7_timezone_Asia/Vientiane', 'Asia/Vientiane'}, +{'VLAT-10VLAST,M3.5.0,M10.5.0/3_timezone_Asia/Vladivostok', 'Asia/Vladivostok'}, +{'YAKT-9YAKST,M3.5.0,M10.5.0/3_timezone_Asia/Yakutsk', 'Asia/Yakutsk'}, +{'YEKT-5YEKST,M3.5.0,M10.5.0/3_timezone_Asia/Yekaterinburg', 'Asia/Yekaterinburg'}, +{'AMT-4AMST,M3.5.0,M10.5.0/3_timezone_Asia/Yerevan', 'Asia/Yerevan'}, +{'AZOT1AZOST,M3.5.0/0,M10.5.0/1_timezone_Atlantic/Azores', 'Atlantic/Azores'}, +{'AST4ADT,M3.2.0,M11.1.0_timezone_Atlantic/Bermuda', 'Atlantic/Bermuda'}, +{'WET0WEST,M3.5.0/1,M10.5.0_timezone_Atlantic/Canary', 'Atlantic/Canary'}, +{'CVT1_timezone_Atlantic/Cape Verde', 'Atlantic/Cape Verde'}, +{'WET0WEST,M3.5.0/1,M10.5.0_timezone_Atlantic/Faroe', 'Atlantic/Faroe'}, +{'WET0WEST,M3.5.0/1,M10.5.0_timezone_Atlantic/Madeira', 'Atlantic/Madeira'}, +{'GMT0_timezone_Atlantic/Reykjavik', 'Atlantic/Reykjavik'}, +{'GST2_timezone_Atlantic/South Georgia', 'Atlantic/South Georgia'}, +{'GMT0_timezone_Atlantic/St Helena', 'Atlantic/St Helena'}, +{'FKT4FKST,M9.1.0,M4.3.0_timezone_Atlantic/Stanley', 'Atlantic/Stanley'}, +{'ACST-9:30ACDT,M10.1.0,M4.1.0/3_timezone_Australia/Adelaide', 'Australia/Adelaide'}, +{'AEST-10_timezone_Australia/Brisbane', 'Australia/Brisbane'}, +{'ACST-9:30ACDT,M10.1.0,M4.1.0/3_timezone_Australia/Broken Hill', 'Australia/Broken Hill'}, +{'AEST-10AEDT,M10.1.0,M4.1.0/3_timezone_Australia/Currie', 'Australia/Currie'}, +{'ACST-9:30_timezone_Australia/Darwin', 'Australia/Darwin'}, +{'CWST-8:45_timezone_Australia/Eucla', 'Australia/Eucla'}, +{'AEST-10AEDT,M10.1.0,M4.1.0/3_timezone_Australia/Hobart', 'Australia/Hobart'}, +{'AEST-10_timezone_Australia/Lindeman', 'Australia/Lindeman'}, +{'LHST-10:30LHDT-11,M10.1.0,M4.1.0_timezone_Australia/Lord Howe', 'Australia/Lord Howe'}, +{'AEST-10AEDT,M10.1.0,M4.1.0/3_timezone_Australia/Melbourne', 'Australia/Melbourne'}, +{'AWST-8_timezone_Australia/Perth', 'Australia/Perth'}, +{'AEST-10AEDT,M10.1.0,M4.1.0/3_timezone_Australia/Sydney', 'Australia/Sydney'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Amsterdam', 'Europe/Amsterdam'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Andorra', 'Europe/Andorra'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Athens', 'Europe/Athens'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Belgrade', 'Europe/Belgrade'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Berlin', 'Europe/Berlin'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Bratislava', 'Europe/Bratislava'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Brussels', 'Europe/Brussels'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Bucharest', 'Europe/Bucharest'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Budapest', 'Europe/Budapest'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Chisinau', 'Europe/Chisinau'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Copenhagen', 'Europe/Copenhagen'}, +{'GMT0IST,M3.5.0/1,M10.5.0_timezone_Europe/Dublin', 'Europe/Dublin'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Gibraltar', 'Europe/Gibraltar'}, +{'GMT0BST,M3.5.0/1,M10.5.0_timezone_Europe/Guernsey', 'Europe/Guernsey'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Helsinki', 'Europe/Helsinki'}, +{'GMT0BST,M3.5.0/1,M10.5.0_timezone_Europe/Isle of Man', 'Europe/Isle of Man'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Istanbul', 'Europe/Istanbul'}, +{'GMT0BST,M3.5.0/1,M10.5.0_timezone_Europe/Jersey', 'Europe/Jersey'}, +{'EET-2EEST,M3.5.0,M10.5.0/3_timezone_Europe/Kaliningrad', 'Europe/Kaliningrad'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Kiev', 'Europe/Kiev'}, +{'WET0WEST,M3.5.0/1,M10.5.0_timezone_Europe/Lisbon', 'Europe/Lisbon'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Ljubljana', 'Europe/Ljubljana'}, +{'GMT0BST,M3.5.0/1,M10.5.0_timezone_Europe/London', 'Europe/London'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Luxembourg', 'Europe/Luxembourg'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Madrid', 'Europe/Madrid'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Malta', 'Europe/Malta'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Mariehamn', 'Europe/Mariehamn'}, +{'EET-2EEST,M3.5.0,M10.5.0/3_timezone_Europe/Minsk', 'Europe/Minsk'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Monaco', 'Europe/Monaco'}, +{'MSK-4_timezone_Europe/Moscow', 'Europe/Moscow'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Oslo', 'Europe/Oslo'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Paris', 'Europe/Paris'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Podgorica', 'Europe/Podgorica'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Prague', 'Europe/Prague'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Riga', 'Europe/Riga'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Rome', 'Europe/Rome'}, +{'SAMT-3SAMST,M3.5.0,M10.5.0/3_timezone_Europe/Samara', 'Europe/Samara'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/San Marino', 'Europe/San Marino'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Sarajevo', 'Europe/Sarajevo'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Simferopol', 'Europe/Simferopol'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Skopje', 'Europe/Skopje'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Sofia', 'Europe/Sofia'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Stockholm', 'Europe/Stockholm'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Tallinn', 'Europe/Tallinn'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Tirane', 'Europe/Tirane'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Uzhgorod', 'Europe/Uzhgorod'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Vaduz', 'Europe/Vaduz'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Vatican', 'Europe/Vatican'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Vienna', 'Europe/Vienna'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Vilnius', 'Europe/Vilnius'}, +{'VOLT-3VOLST,M3.5.0,M10.5.0/3_timezone_Europe/Volgograd', 'Europe/Volgograd'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Warsaw', 'Europe/Warsaw'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Zagreb', 'Europe/Zagreb'}, +{'EET-2EEST,M3.5.0/3,M10.5.0/4_timezone_Europe/Zaporozhye', 'Europe/Zaporozhye'}, +{'CET-1CEST,M3.5.0,M10.5.0/3_timezone_Europe/Zurich', 'Europe/Zurich'}, +{'EAT-3_timezone_Indian/Antananarivo', 'Indian/Antananarivo'}, +{'IOT-6_timezone_Indian/Chagos', 'Indian/Chagos'}, +{'CXT-7_timezone_Indian/Christmas', 'Indian/Christmas'}, +{'CCT-6:30_timezone_Indian/Cocos', 'Indian/Cocos'}, +{'EAT-3_timezone_Indian/Comoro', 'Indian/Comoro'}, +{'TFT-5_timezone_Indian/Kerguelen', 'Indian/Kerguelen'}, +{'SCT-4_timezone_Indian/Mahe', 'Indian/Mahe'}, +{'MVT-5_timezone_Indian/Maldives', 'Indian/Maldives'}, +{'MUT-4_timezone_Indian/Mauritius', 'Indian/Mauritius'}, +{'EAT-3_timezone_Indian/Mayotte', 'Indian/Mayotte'}, +{'RET-4_timezone_Indian/Reunion', 'Indian/Reunion'}, +{'WST11_timezone_Pacific/Apia', 'Pacific/Apia'}, +{'NZST-12NZDT,M9.5.0,M4.1.0/3_timezone_Pacific/Auckland', 'Pacific/Auckland'}, +{'CHAST-12:45CHADT,M9.5.0/2:45,M4.1.0/3:45_timezone_Pacific/Chatham', 'Pacific/Chatham'}, +{'VUT-11_timezone_Pacific/Efate', 'Pacific/Efate'}, +{'PHOT-13_timezone_Pacific/Enderbury', 'Pacific/Enderbury'}, +{'TKT10_timezone_Pacific/Fakaofo', 'Pacific/Fakaofo'}, +{'FJT-12_timezone_Pacific/Fiji', 'Pacific/Fiji'}, +{'TVT-12_timezone_Pacific/Funafuti', 'Pacific/Funafuti'}, +{'GALT6_timezone_Pacific/Galapagos', 'Pacific/Galapagos'}, +{'GAMT9_timezone_Pacific/Gambier', 'Pacific/Gambier'}, +{'SBT-11_timezone_Pacific/Guadalcanal', 'Pacific/Guadalcanal'}, +{'ChST-10_timezone_Pacific/Guam', 'Pacific/Guam'}, +{'HST10_timezone_Pacific/Honolulu', 'Pacific/Honolulu'}, +{'HST10_timezone_Pacific/Johnston', 'Pacific/Johnston'}, +{'LINT-14_timezone_Pacific/Kiritimati', 'Pacific/Kiritimati'}, +{'KOST-11_timezone_Pacific/Kosrae', 'Pacific/Kosrae'}, +{'MHT-12_timezone_Pacific/Kwajalein', 'Pacific/Kwajalein'}, +{'MHT-12_timezone_Pacific/Majuro', 'Pacific/Majuro'}, +{'MART9:30_timezone_Pacific/Marquesas', 'Pacific/Marquesas'}, +{'SST11_timezone_Pacific/Midway', 'Pacific/Midway'}, +{'NRT-12_timezone_Pacific/Nauru', 'Pacific/Nauru'}, +{'NUT11_timezone_Pacific/Niue', 'Pacific/Niue'}, +{'NFT-11:30_timezone_Pacific/Norfolk', 'Pacific/Norfolk'}, +{'NCT-11_timezone_Pacific/Noumea', 'Pacific/Noumea'}, +{'SST11_timezone_Pacific/Pago Pago', 'Pacific/Pago Pago'}, +{'PWT-9_timezone_Pacific/Palau', 'Pacific/Palau'}, +{'PST8_timezone_Pacific/Pitcairn', 'Pacific/Pitcairn'}, +{'PONT-11_timezone_Pacific/Ponape', 'Pacific/Ponape'}, +{'PGT-10_timezone_Pacific/Port Moresby', 'Pacific/Port Moresby'}, +{'CKT10_timezone_Pacific/Rarotonga', 'Pacific/Rarotonga'}, +{'ChST-10_timezone_Pacific/Saipan', 'Pacific/Saipan'}, +{'TAHT10_timezone_Pacific/Tahiti', 'Pacific/Tahiti'}, +{'GILT-12_timezone_Pacific/Tarawa', 'Pacific/Tarawa'}, +{'TOT-13_timezone_Pacific/Tongatapu', 'Pacific/Tongatapu'}, +{'TRUT-10_timezone_Pacific/Truk', 'Pacific/Truk'}, +{'WAKT-12_timezone_Pacific/Wake', 'Pacific/Wake'}, +{'WFT-12_timezone_Pacific/Wallis', 'Pacific/Wallis'},} diff --git a/decompressed/gui_file/usr/lib/lua/wansensingfw/scripthelpers.lua b/decompressed/gui_file/usr/lib/lua/wansensingfw/scripthelpers.lua index e5c69cc11..ad509b9dd 100644 --- a/decompressed/gui_file/usr/lib/lua/wansensingfw/scripthelpers.lua +++ b/decompressed/gui_file/usr/lib/lua/wansensingfw/scripthelpers.lua @@ -633,7 +633,7 @@ end -- @return {up/down} the linkstate local function run_checkLinkState(intf) local f = io.open('/sys/class/net/' .. intf .. '/carrier') - local linkstate + local linkstate = nil if f then local state = f:read(1) if state == '1' then @@ -655,7 +655,8 @@ local function run_checkLinkState(intf) if pipe then for line in pipe:lines() do if not linkstate then - linkstate = match(line, "^Link is%s+([^%s]+)$") + line = line:gsub("^%s*","") + linkstate = match(line, "^Link is%s+([^%s]+)") end end pipe:close() @@ -678,18 +679,55 @@ M.l2HasCarrier = function(l2intf) end end +--- Helper function that performs the actual Link speed check +-- it is pcalled by l2GetSpeed +-- @param l2intf the interface name (netdevice interface name) +-- @return {number} the link speed +local function run_getLinkSpeed(intf) + local f = io.open('/sys/class/net/' .. intf .. '/speed') + local linkspeed = nil + if f then + linkspeed = f:read("*number") + f:close() + end + + return linkspeed or 0 +end + +--- Returns the link speed for specified l2 device +-- @param l2intf the interface name (netdevice interface name) +-- @return {number} the link speed +M.l2GetLinkSpeed = function(l2intf) + local status, linkspeed = pcall(run_getLinkSpeed,l2intf) + + return status and linkspeed or 0 +end + function M.set_state(uci, param, value) if type(param) ~= "string" or type(value) ~= "string" then error("both param and value parameter should be of type string") end - local config = "wansensing" + local config, section = "wansensing", "state" + local x = uci.cursor(UCI_CONFIG, "/var/state") + + x:load(config) + x:revert(config, section, param) + x:set(config, section, param, value) + x:save(config) +end + +function M.clear_state(uci, param) + if type(param) ~= "string" then + error("param parameter should be of type string") + end + + local config, section = "wansensing", "state" local x = uci.cursor(UCI_CONFIG, "/var/state") x:load(config) - x:revert("wansensing", "state", param) - x:set("wansensing", "state", param, value) - x:save("wansensing") + x:revert(config, section, param) + x:save(config) end --- Format neighbor event name diff --git a/decompressed/gui_file/usr/lib/lua/web/ajax_helper.lua b/decompressed/gui_file/usr/lib/lua/web/ajax_helper.lua index 9b588a246..d9aefdb0f 100644 --- a/decompressed/gui_file/usr/lib/lua/web/ajax_helper.lua +++ b/decompressed/gui_file/usr/lib/lua/web/ajax_helper.lua @@ -48,18 +48,16 @@ function M.handleAjaxQuery(mapParams, transform) local buffer = {} success = json.encode (content, { indent = false, buffer = buffer }) if success then - ngx.say(buffer) + ngx.header.content_type = "application/json" + ngx.print(buffer) ngx.exit(ngx.HTTP_OK ) else - ngx.say("{}") ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end else - ngx.say("{}") ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end else - ngx.say("{}") ngx.exit(ngx.HTTP_BAD_REQUEST) end end diff --git a/decompressed/gui_file/usr/lib/lua/web/assistance.lua b/decompressed/gui_file/usr/lib/lua/web/assistance.lua index b5e08cba8..ed9360d78 100644 --- a/decompressed/gui_file/usr/lib/lua/web/assistance.lua +++ b/decompressed/gui_file/usr/lib/lua/web/assistance.lua @@ -60,6 +60,7 @@ local function loadState(name) password=""; mode="0"; ifname = ""; + ifname6 = ""; } local f = io.open(stateFile:format(name), 'r') if f then @@ -101,6 +102,18 @@ local function getInterfaceIP(ifname) end end +--- Get the IPv6 address of the named interface +-- \param ifname (string) the interface (wan6, lan, ...) +-- \returns the ipv6 address string or nil if not found +local function getInterfaceIPv6(ifname) + if ifname and ifname ~= "" then + local info = dm.get(format('rpc.network.interface.@%s.ip6addr', ifname)) + if info and info[1] and (info[1].param=='ip6addr') then + return info[1].value and string.match(info[1].value, "^%S+") + end + end +end + local function genpsw(size, pswchars) local psw = {} for i=1,size do @@ -144,6 +157,19 @@ function Assistant:URL() end end +--- Get the full URL with IPv6 address for the remote assistance +-- @return [string] the URL +-- @return nil if not enabled or no IPv6 address on the interface. +function Assistant:URL6() + if self:enabled() then + local ipv6 = getInterfaceIPv6(self._interface6) + local port = self._port + if ipv6 and port then + return format("https://[%s]:%d", ipv6, port) + end + end +end + --- Get the password for the assistant to use -- This is only relevant if the assistent is enabled -- There is no need to show password when random password is not enabled @@ -167,12 +193,29 @@ end local function differentSrpPassword(pswcfg, password) if password.salt and pswcfg.salt~=password.salt then return true - end + end if password.verifier and pswcfg.verifier~=password.verifier then return true end end +local function updateConfig(assistant, new_config) + if new_config then + assistant._config_update = nil + if (assistant._fromPort~=new_config.fromPort) or (assistant._toPort~=new_config.toPort) then + assistant._fromPort = new_config.fromPort + assistant._toPort = new_config.toPort + end + if assistant._interface ~= new_config.interface then + assistant._interface = new_config.interface + end + if assistant._interface6 ~= new_config.interface6 then + assistant._interface6 = new_config.interface6 + end + end + return changed +end + -- check if there is any change for _mode and _pswcfg local function checkUpdate(assistant, permanent, password) local pswcfg = assistant._pswcfg @@ -181,10 +224,18 @@ local function checkUpdate(assistant, permanent, password) if differentSrpPassword(pswcfg, password) then return true end - elseif pswcfg~=password then + elseif (password~=false) and (pswcfg~=password) then return true end - return assistant._permanent~=(permanent or false) + if assistant._permanent~=(permanent or false) then + return true + end + if assistant._wanip ~= (getInterfaceIP(assistant._interface) or '') then + return true + end + if assistant._wanipv6 ~= getInterfaceIPv6(assistant._interface6) then + return true + end end --- update assistant cfg @@ -215,6 +266,7 @@ local function updatecfg(assistant, bPermanent, password) config.password='_DUMMY_PASSWORD_' end config.ifname = assistant._interface + config.ifname6 = assistant._interface6 writeState(assistant._name, config, true) return true end @@ -258,6 +310,8 @@ local function persist(assistant) dm.set(sets) end +local load_config_update + --- enable or disable the assistant -- \param bActive (bool) if true enable else disable -- \param bPermanent (bool) if true permanent mode else temporary mode @@ -268,6 +322,10 @@ end -- otherwise, previous password cfg will be used -- \returns true if no error or nil, errmsg is case of error function Assistant:enable(bActive, bPermanent, password) + if self._reload_config and not self:enabled() then + updateConfig(self, load_config_update(self._name)) + self._reload_config = nil + end local changed = false bPermanent = bPermanent or self._persistent -- disable assistance if its cfg needs update @@ -304,6 +362,11 @@ function Assistant:enable(bActive, bPermanent, password) return r end +function Assistant:enable_with_reload(bActive, bPermanent, password) + self._reload_config = true + return self:enable(bActive, bPermanent, password) +end + local function restore(assistant) if not assistant._persistent then return @@ -313,13 +376,12 @@ local function restore(assistant) if (not state) or (state.enabled ~= '1') then return end - - local port = tonumber(untaint(state.port)) - if not port then - return - end - - state.port = port + + local port = tonumber(untaint(state.port)) + if not port then + return + end + state.port = port assistant._restore_state = state assistant:enable(true) @@ -378,6 +440,9 @@ function assistant_enable(self) --restore_state data comes from transformer, so they are tainted user.srp_salt = untaint(self._restore_state.salt) user.srp_verifier = untaint(self._restore_state.verifier) + if not self._pswcfg then + self._pswcfg = { salt = user.srp_salt, verifier = user.srp_verifier } + end -- here we explicitly opt to set the password to an empty string -- this way no password is shown (but the actual password is set) self._psw = "" @@ -400,17 +465,19 @@ function assistant_enable(self) self._port = port self._wanip = getInterfaceIP(self._interface) or '' + self._wanipv6 = getInterfaceIPv6(self._interface6) self:activity() writeState(self._name, { wanip=self._wanip; - wanport=port; + wanipv6=self._wanipv6; + wanport=tostring(port); lanport=self._lanport; enabled="1"; password=pwd or ''; mode = self._permanent and "1" or "0"; - ifname = self._interface + ifname = self._interface; + ifname6 = self._interface6 }) - return true end return nil, "internal error: user disappeared" @@ -471,15 +538,18 @@ end -- \returns true if expired, false if not -- if expired the assistant is disabled function assistant_checkTimeout(self) - local expired = not self._permanent and self.timestamp and (self.timestamp + self._timeout) < clock_gettime(CLOCK_MONOTONIC) or false + local expired = not self._permanent and + self.timestamp and + (self.timestamp + self._timeout) < clock_gettime(CLOCK_MONOTONIC) or + false if expired then self:enable(false) end return expired end -local function newAssistant(config, sessionmgr) - local persistent = false +local function makeInternalConfig(config) + config.persistent = false local timeout = tonumber(config.timeout) if not timeout then ngx.log(ngx.ERR, format("invalid timeout value (%s) for assistant %s", tostring(config.timeout), config._name)) @@ -487,11 +557,14 @@ local function newAssistant(config, sessionmgr) end if timeout<1 then if timeout==-1 then - persistent = true + config.persistent = true else ngx.log(ngx.ERR, format("negative timeout value (%d) for assistant %s", timeout, config._name)) ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end + else + -- convert minutes to seconds + config.timeout = timeout*60 end local fromPort, toPort = config.port:match('^%s*(%d+)%s*-%s*(%d+)%s*$') if fromPort then @@ -508,21 +581,30 @@ local function newAssistant(config, sessionmgr) if toPort= 60 and num <= 86400 and M.getValidateWholeNumber(value) then + if num and num >= 60 and num <= 600000 and M.getValidateWholeNumber(value) then return true end - return nil, T"Expire Time is invalid. It should be a whole number, between 60 and 86400." + return nil, T"Expire Time is invalid. It should be a whole number, between 60 and 600000." end --- @@ -1070,7 +1105,7 @@ function M.getValidateStringLengthInRange(minl, maxl) end --- --- Return a validation function that will be enabled only if a given property is present and otherwise return true +-- Return a validation function that will be enabled only if a given property is present otherwise remove the parameter from the object and return true -- @function [parent=#post_helper] getValidationIfPropInList -- @param validation function (prototype is of type (value, object) -- @param #string prop name of the property used as a trigger @@ -1084,17 +1119,21 @@ function M.getValidationIfPropInList(func, prop, values) options[v] = true end - -- This function should apply the given validation function if the property is in the allowed values and return true otherwise + -- This function should apply the given validation function if the property is in the allowed values otherwise remove the parameter from the object and return true return function(value, object, key) - if object and object[prop] and options[object[prop]] then + if not object then + return true + end + if object[prop] and options[object[prop]] then return func(value, object, key) end + object[key] = nil return true end end --- --- Return a validation function that will be enabled only if a given checkboxswitch property is present and otherwise return true +-- Return a validation function that will be enabled only if a given checkboxswitch property is present otherwise remove the parameter from the object and return true -- @function [parent=#post_helper] getValidationIfPropInList -- @param validation function (prototype is of type (value, object) -- @param #string prop name of the property used as a trigger @@ -1108,9 +1147,12 @@ function M.getValidationIfCheckboxSwitchPropInList(func, prop, values) options[v] = true end - -- This function should apply the given validation function if the property is in the allowed values and return true otherwise + -- This function should apply the given validation function if the property is in the allowed values otherwise remove the parameter from the object and return true return function(value, object, key) - if object and object[prop] then + if not object then + return true + end + if object[prop] then -- Before M.getValidateCheckboxSwitch is called, -- the post value of a switchcheck box is still {"_DUMMY_", "_TRUE_"} or "_DUMMY_" -- these values need to be converted to "1" or "0" @@ -1129,6 +1171,7 @@ function M.getValidationIfCheckboxSwitchPropInList(func, prop, values) return func(value, object, key) end end + object[key] = nil return true end end @@ -1239,66 +1282,60 @@ function M.getConditionalValidation(condition, istrue, isfalse) end end +local function onlyFunctions(validators) + local onlyFunc = {} + for _, v in pairs(validators) do + if type(v) == "function" then + onlyFunc[#onlyFunc + 1] = v + end + end + return onlyFunc +end + --- --- This function uses 2 validation functions and will only return true --- if both return true. +-- This function does function(s) validations and will only return true +-- if all the functions return's true, else return's nil, error message. -- @function [parent=#post_helper] getAndValidation --- @param #function valid1 --- @param #function valid2 +-- @param #function ... validation functions -- @return #boolean, #string -function M.getAndValidation(valid1, valid2) - local v1,v2 = valid1,valid2 - if type(v1) ~= "function" then - v1 = alwaysTrue - end - if type(v2) ~= "function" then - v2 = alwaysTrue - end - +function M.getAndValidation(...) + local validators = onlyFunctions{...} return function(value, object, key) - local r1,h1 = v1(value, object, key) - local r2,h2 = v2(value, object, key) local help = {} - if not r1 then - help[#help+1] = h1 or "" - end - if not r2 then - help[#help+1] = h2 or "" + local result = true + for _, v in ipairs(validators) do + local r, h = v(value, object, key) + result = result and r + if not r then + help[#help+1] = h + end end - - return r1 and r2, concat(help, " ") + return result, concat(help, " ") end end --- --- This function uses 2 validation functions and will only return true --- if one of them returns true. --- @function [parent=#post_helper] getAndValidation --- @param #function valid1 --- @param #function valid2 +-- This function does function(s) validations and will only return true +-- if one of them returns true, else return's nil, error message. +-- @function [parent=#post_helper] getOrValidation +-- @param #function ... validation functions -- @return #boolean, #string -function M.getOrValidation(valid1, valid2) - local v1,v2 = valid1,valid2 - if type(v1) ~= "function" then - v1 = alwaysTrue - end - if type(v2) ~= "function" then - v2 = alwaysTrue - end - +function M.getOrValidation(...) + local validators = onlyFunctions{...} return function(value, object, key) - local r1,h1 = v1(value, object, key) - local r2,h2 = v2(value, object, key) local help = {} - if not r1 and not r2 then - help[#help+1] = h1 or "" - help[#help+1] = h2 or "" + local result + for _, v in ipairs(validators) do + local r, h = v(value, object, key) + result = result or r + if not r then + help[#help+1] = h + end end - return r1 or r2, concat(help, " ") + return result, concat(help, " ") end end - local psklength = M.getValidateStringLengthInRange(8,63) local pskmatch = "^[ -~]+$" --- This function validates a WPA/WPA2 PSK key @@ -1349,7 +1386,7 @@ end --- valide WPS pin code. Must be 4-8 digits (can have a space or - in the middle) -- @param #string value the PIN code that was entered function M.validateWPSPIN(value) - local errmsg = T"PIN code must composed of 4 or 8 digits with potentially a dash or space in the middle." + local errmsg = T"PIN code must be 4 or 8 digits with potentially a dash or space in the middle." if value == nil or #value == 0 then -- empty pin code just means that we don't want to set one return true @@ -1400,6 +1437,19 @@ local function ipv42num(ipstr) return bit.tobit((b1*16777216) + (b2*65536) + (b3*256) + b4) end end + +-- Return broadcast address as number to calling function +local function broadcastAddress(network, netmask) + local broadcast = bit.bor(network, bit.bnot(netmask)) + return broadcast +end + +-- Return network address as number to calling function +local function networkAddress(ipAddr, netmask) + local network = bit.band(ipAddr,netmask) + return network +end + --- Return the number representing the given IPv4 address (or netmask) string. -- @function [parent=#post_helper] ipv42num -- @param #string ip @@ -1415,55 +1465,67 @@ M.ipv42num = ipv42num -- @treturn number The number of bits in the host part of the subnet mask. -- @error Error message. function M.validateIPv4Netmask(value) - -- A valid subnet mask consists of (in binary) consecutive 1's - -- followed by consecutive 0's. - local netmask = ipv42num(value) - if not netmask then - return nil, T"String is not an IPv4 address." + setlanguage() + -- validateIPv4Netmask function uses inet_pton which requires string to be passed as an argument. + -- value when it comes from GUI, will be of type userdata so untainting it. + value = string.untaint(value) + local valid, result = inet.validateIPv4Netmask(value) + if not valid then + return valid, T"Invalid netmask." end - local ones = 0 - local expecting = 0 - for i = 0, 31 do - local bitmask = bit.lshift(1, i) - local result = bit.band(netmask, bitmask) - if result == 0 then - if expecting ~= 0 then - return nil, T"Invalid subnet." - end - else - if expecting == 0 then - expecting = 1 - end - ones = ones + 1 - end + return valid +end + +-- Validates the Destination IP. +-- In particular if the IPs are reserved/network/invalid IPs, then this function returns an error message. +-- @param #string ipAddr The destination IP in dotted decimal notation. +-- @tparam table object The table containing the IPv4 Static Routes Configuration userdata. +-- @error Error message. +function M.validateDestinationIP(ipAddr, object) + local resIP, errMsg = M.validateStringIsIP(ipAddr) + if not resIP then + return nil, errMsg end - if (ones < 8) or (ones > 30) then - return nil, T"Invalid subnet." + + resIP, errMsg = M.reservedIPValidation(ipAddr) + if not resIP then + return nil, errMsg end - return true, 32 - ones + + resIP, errMsg = M.getValidateStringIsIPv4InNetwork(object.Gateway, object.Mask) + if not resIP then + return nil, errMsg + end + + resIP, errMsg = resIP(ipAddr) + if not resIP then + return nil,T"Destination is not in the Gateway network" + end + return resIP end --- This function returns a validator that will check that the provided value is an IPv4 in the same network -- as the network based on the GW IP + Netmask -- @param #string gw the gateway IP@ on the considered network -- @param #string nm the netmask to use --- @return true or nil+error message +-- @treturns a function or nil function M.getValidateStringIsIPv4InNetwork(gw, nm) local gwip = ipv42num(gw) local netmask = ipv42num(nm) - local network = bit.band(gwip, netmask) - local broadcast = bit.bor(network, bit.bnot(netmask)) + if gwip and netmask then + local network = networkAddress(gwip, netmask) - return function(value) - if(GetIPType(value) ~= 4) then - return nil, T"String is not an IPv4 address." - end - local ip = ipv42num(value) + return function(value) + if(GetIPType(value) ~= 4) then + return nil, T"String is not an IPv4 address." + end + local ip = ipv42num(value) - if network ~= bit.band(ip, netmask) then - return nil, format(T"IP is not in the same network as the gateway %s.", gw) + if network ~= networkAddress(ip, netmask) then + return nil, format(T"IP is not in the same network as the gateway %s.", gw) + end + return true end - return true end end @@ -1476,8 +1538,8 @@ function M.getValidateStringIsDeviceIPv4(gw, nm) local gwip = ipv42num(gw) local netmask = ipv42num(nm) if gwip and netmask then - local network = bit.band(gwip, netmask) - local broadcast = bit.bor(network, bit.bnot(netmask)) + local network = networkAddress(gwip, netmask) + local broadcast = broadcastAddress(network, netmask) local mainValid = M.getValidateStringIsIPv4InNetwork(gw, nm) return function(value) @@ -1522,57 +1584,68 @@ function M.validateStringIsIPv6(value) end end +local startLoopback = ipv42num("127.0.0.0") +local endLoopback = ipv42num("127.255.255.255") +local startMulticastRange = ipv42num("224.0.0.0") +local endMulticastRange = ipv42num("239.255.255.255") +local limitedBroadcast = ipv42num("255.255.255.255") +local classEStartIP = ipv42num("240.0.0.0") +local classEEndIP = ipv42num("255.255.255.254") +local softwareStartIP = ipv42num("0.0.0.1") +local softwareEndIP = ipv42num("0.255.255.255") --- Return broadcast address as number to calling function -local function broadcastAddress(network, netmask) - local broadcast = bit.bor(network, bit.bnot(netmask)) - return broadcast -end - --- Return network address as number to calling function -local function networkAddress(ipAddr, netmask) - local network = bit.band(ipAddr,netmask) - return network -end - --- validate the given ip/subnet is broadcast address or not -function M.isBroadcastAddress(ip, subnetMask) - local netmask = ipv42num(subnetMask) - local network = networkAddress(ip, netmask) - local broadcast = broadcastAddress(network, netmask) - - return broadcast == ip +-- Check whether the given IPv4 address is a public IP address. +-- @param @tstring value the IP Address$ +-- @param @ttable object has $localcpeip +-- @param @tstring key the random key value$ +function M.publicIPValidation(value, object, key) + if value ~= "" then + if not M.isPublicIP(value) then + return nil, T"Not a Public address" + end + local ip = M.ipv42num(value) + if classEStartIP <= ip and classEEndIP >= ip then + return nil, T"Cannot use a reserved IP address." + end + if softwareStartIP <= ip and softwareEndIP >= ip then + return nil, T"Cannot use software address range." + end + if startLoopback <= ip and ip <= endLoopback then + return nil, T"Cannot use IPv4 loopback address range." + end + if startMulticastRange <= ip and endMulticastRange >= ip then + return nil, T"Cannot use a multicast address." + end + if object.localpublicmask then + local success1 = M.isNetworkAddress(value, object.localpublicmask) + if success1 then + return nil, T"Cannot use the network address" + end + local success2 = M.isBroadcastAddress(ip, object.localpublicmask) + if success2 then + return nil, T"Cannot use the broadcast address" + end + end + return true + end end ---- This function is used for Local Device IP validation and NTP server validation. It is validating that: --- if object["localdevmask"] is a valid netmask --- the [value] a valid IPv4 address, --- the [value] is not the broadcast or network address based on the network mask --- don't allow if ip is in the CLASS A IP range 0.0.0.0/2, except the private 10.0.0.0/24 range --- the [value] is not in the multicast range 224.0.0.0/4 --- the [value] is not in the limited broadcast destination address 255.255.255.255/32 --- @return true or nil+error message +--- is the given IP address valid as a Local Device IP and NTP server$ +-- @param @tstring value the IP Address$ +-- @param @ttable object has localdevmask$ +-- @param @tstring key the random key value$ +-- @return true or nil+error message$ function M.advancedIPValidation(value, object, key) if not value then return nil, T"Invalid IP Address." end - --valid netmask? - if object["localdevmask"] then - local val, msg = M.validateIPv4Netmask(object["localdevmask"]) - if not val then - return nil, "[netmask] " .. msg - end - end - local ip = M.ipv42num(value) if not ip then return nil, T"Invalid IP Address." end - local startLoopback = "127.0.0.0" - local endLoopback = "127.255.255.255" - if ipv42num(startLoopback) <= ip and ip <= ipv42num(endLoopback) then + if startLoopback <= ip and ip <= endLoopback then return nil,T"Cannot use IPv4 loopback address range." end @@ -1581,7 +1654,7 @@ function M.advancedIPValidation(value, object, key) local startClassAPrivRange = "10.0.0.0" local endClassAPrivRange = "10.255.255.255" -- ip 0.0.0.0/32 is not valid - if object["localdevmask"] then + if object.localdevmask then if 0 < ip and M.ipv42num(endClassARange) >= ip then if ipv42num(startClassAPrivRange) >= ip or ipv42num(endClassAPrivRange) <= ip then return nil, T"Cannot use an address in this address range." @@ -1592,19 +1665,15 @@ function M.advancedIPValidation(value, object, key) return nil, T"Cannot use an address in this address range." end --check if ip is not in the multicast range 224.0.0.0/4 - local startMulticastRange = "224.0.0.0" - local endMulticastRange = "239.255.255.255" - if ipv42num(startMulticastRange) <= ip and ipv42num(endMulticastRange) >= ip then + if startMulticastRange <= ip and endMulticastRange >= ip then return nil, T"Cannot use a multicast address." end --check if ip is not in the limited broadcast destination address 255.255.255.255/32 - local limitedBroadcast = "255.255.255.255" - if ipv42num(limitedBroadcast) == ip then + if limitedBroadcast == ip then return nil, T"Cannot use the limited broadcast destination address." end - --in case of valid ip is the broadcast or network adress based on the network mask if object.localdevmask then local success1, errmsg = M.isNetworkAddress(value, object.localdevmask) @@ -1617,6 +1686,7 @@ function M.advancedIPValidation(value, object, key) return nil, T"Cannot use the broadcast address" end end + return true end @@ -1715,6 +1785,52 @@ function M.isWANIP(ipAddress, all_intfs) end end +--- Check whether the given IP address is in any LAN interface subnet range +-- @tstring ipAddress the IP address +-- @ttable all_intfs the all interfaces +-- @tstring curif the current interrface that will not be checked +-- @treturn true and interface name if the given IP address is in any LAN subnet range otherwise nil + +function M.isLANIP(ipAddress, all_intfs, curif) + local ip = ipv42num(ipAddress) + if not ip then + return nil, T"Invalid input" + end + local wanInterface = {} + local firewall_zone = proxy.get("uci.firewall.zone.") + local firewall_zones = content_helper.convertResultToObject("uci.firewall.zone.", firewall_zone) + for _, zone in ipairs(firewall_zones) do + if zone.wan == "1" and zone.name == "wan" then + local wanInterfaceList = content_helper.convertResultToObject("uci.firewall.zone.@" .. zone.name .. ".network.", zone) + for _, v in ipairs(wanInterfaceList) do + wanInterface[untaint(v.value)] = true + end + end + end + + for _, v in ipairs(all_intfs) do + if not wanInterface[v.paramindex] and v.ipaddr ~= "" and v.paramindex ~= curif then + local networkLan, lanIpMax + local baseip = ipv42num(v.ipaddr) + local netmask = inet.netmaskToNumber(tonumber(v.ipmask)) + if not netmask then + return nil, T"Invalid netmask" + end + + if baseip and netmask then + networkLan = networkAddress(baseip, netmask) + lanIpMax = broadcastAddress(networkLan, netmask) + end + + if networkLan and lanIpMax then + if networkLan <= ip and ip <= lanIpMax then + return true, v.paramindex + end + end + end + end +end + ---This function converts a CIDR(Classless Inter-domain routing) notation to a subnet mask. Eg, convert 24 to 255.255.255.0 -- @param #string 'cidr' The CIDR notation number. Eg: "24" -- @return #string network mask or nil+error message. Eg: "255.255.255.0" @@ -1784,6 +1900,7 @@ function M.validateIPAndSubnet(ipTypeV4orV6) return true end end + function M.validateURL(url,proto) if url then local protocol, domain = match(url,"([%w]+)://([^/]*)/?") @@ -1806,6 +1923,7 @@ function M.validateURL(url,proto) end return nil, T"Invalid URL" end + --Validate the given ip/mac is Quantenna. function M.validateQTN(value) local qtnMac = { mac = "uci.env.var.qtn_eth_mac" } @@ -1814,6 +1932,9 @@ function M.validateQTN(value) if not success then return true end + if not value then + return nil, "Invalid input" + end value = untaint(value) if M.validateStringIsMAC(value) then if lower(qtnMac.mac) == lower(value) then @@ -1822,26 +1943,41 @@ function M.validateQTN(value) return true elseif inet.isValidIPv4(value) == true then local qtnIP = content_helper.getMatchedContent("sys.proc.net.arp.",{ hw_address = lower(qtnMac.mac)}) - if #qtnIP > 0 and qtnIP[1].ip_address == value then - return nil, format(T"Cannot assign, %s in use by system.", value) + if #qtnIP > 0 then + for _,v in ipairs(qtnIP) do + if v.ip_address == value then + return nil, format(T"Cannot assign, %s in use by system.", value) + end + end end return true end return nil, T"Invalid input." end +-- validate the given ip/subnet is broadcast address or not +function M.isBroadcastAddress(ip, subnetMask) + local netmask = ipv42num(subnetMask) + if netmask then + local network = networkAddress(ip, netmask) + local broadcast = broadcastAddress(network, netmask) + return broadcast == ip + end +end + -- validate the given ip/subnet is network address or not function M.isNetworkAddress(ipAddress, subnetMask) local netMask = ipv42num(subnetMask) - local ip = M.ipv42num(ipAddress) - if not ip then - return nil, T"Invalid IP Address." - end - --if network == ip - if bit.band(ip, netMask) == ip then - return true + if netMask then + local ip = M.ipv42num(ipAddress) + if not ip then + return nil, T"Invalid IP Address." + end + if networkAddress(ip, netMask) == ip then + return true + end + return nil, T"Invalid Network Address." end - return nil, T"Invalid Network Address." end --- This function is used to get default subnet mask. @@ -1879,11 +2015,15 @@ end -- @treturn number The number of effective hosts possible in the network with the given subnet mask. -- @error Error message. function M.getPossibleHostsInSubnet(subnetmask) - local valid, host_bits = M.validateIPv4Netmask(subnetmask) - if not valid then - return nil, host_bits + setlanguage() + -- getPossibleHostsInIPv4Subnet function will use inet_pton which requires string to be passed as an argument. + -- subnetmask when it comes from GUI, will be of type userdata so untainting it. + subnetmask = string.untaint(subnetmask) + local result = inet.getPossibleHostsInIPv4Subnet(subnetmask) + if not result then + return nil, T"Invalid subnet." end - return (2^host_bits) - 2 + return result end -- Validate the given IP address is not in the broadcast, multicast, loopback, reserved, gatewayip, network. @@ -1896,7 +2036,7 @@ function M.staticLeaseIPValidation(value, object) if not valid then return nil, errmsg end - local networkvalid = M.getValidateStringIsDeviceIPv4(object["localdevIP"], object["localdevmask"]) + local networkvalid = M.getValidateStringIsDeviceIPv4(object.localdevIP, object.localdevmask) local isnetworkvaild, msg = networkvalid(value) if not isnetworkvaild then return isnetworkvaild, msg @@ -1904,6 +2044,25 @@ function M.staticLeaseIPValidation(value, object) return true end +-- Validate the given IP address is not in the Reserved IP list. +-- @string value The IPv4 address. +-- @return true valid IP address not present in Reserved IP list, nil+error message. +function M.reservedIPValidation(ip) + if inet.isValidIPv4(untaint(ip)) then + local reservedIPList = proxy.get("uci.dhcp.host.") + reservedIPList = content_helper.convertResultToObject("uci.dhcp.host.", reservedIPList) or {} + for _, v in ipairs(reservedIPList) do + if match(v.name, "^ReservedStatic") and v.mac == "" then + if ip == v.ip then + return nil, T"The IP is internally used for other services." + end + end + end + return true + end + return nil, T"Invalid input." +end + --Generate random key for new rule --@return 16 digit random key. function M.getRandomKey() @@ -1932,6 +2091,70 @@ function M.validateSSID(value) return true end +--Validates if the 'dhcpIgnore' value is '1' or '0' +--Changes the 'dhcpIgnore' to '0' if it is '1' and the 'dhcpv4State' is 'server' +--@string value the 'dhcpIgnore' state +--@table object the table containing 'dhcpv4State' and 'dhcpIgnore' +--@return true if the 'dhcpIgnore' is valid +--@return nil+error message if the 'dhcpIgnore' is not valid +function M.validateDHCPIgnore(value, object) + if not M.getOptionalValidation(M.validateBoolean)(value) then + return nil, T"Invalid value." + end + --While board boot up, dhcpv4State will be "", in that case it needs to be enabled in GUI. + if object.dhcpv4State == "" or object.dhcpv4State == "server" then + if object.dhcpIgnore == "1" then + object.dhcpIgnore = "0" + end + end + return true +end + +--- converts given number to IPv4 address. +-- @function [parent=#post_helper] num2ipv4 +-- @param #number a number that needs to be converted +-- @treturn #string IPv4 address +-- @error Error message +function M.num2ipv4(num) + if type(num)~="number" then + return nil, T"Invalid Number" + end + -- unbit + if num<0 then + num = (2^32)+num + end + local ip = inet.numberToIpv4(num) + if not ip then + return nil, T"Invalid Number" + end + return ip +end + +-- Checks whether the received 'value' is in HH:MM format +-- @function [parent=#post_helper] validateTime +-- @param value +-- @treturn #boolean true if valid nil+msg if invalid +-- @error Error message +function M.validateTime(value) + if not value then + return nil, T"Invalid input" + end + local time_pattern = "^(%d+):(%d+)$" + local hour, min = value:match(time_pattern) + if min then + hour = tonumber(hour) + min = tonumber(min) + if hour < 0 or 23 < hour then + return nil, T"Invalid hour, must be between 0 and 23" + end + if min < 0 or 59 < min then + return nil, T"Invalid minutes, must be between 0 and 59" + end + return true + end + return nil, T"Invalid time (must be hh:mm)" +end + local function formatAttenuation(attenuation, direction, index) return format("%s%s%s%s%s", direction or "", @@ -1942,20 +2165,29 @@ local function formatAttenuation(attenuation, direction, index) ) end ---- Formats the attenuation values for ADSL in the format "20.2 dB" (OR) "8.7 dB", ---- VDSL in the format "DS0 20.2 dB, DS1 53.6 dB, DS2 N/A (OR) US0 8.0 dB, US1 N/A, US2 41.6 dB" +local function attenuationIndexOffset(direction) + local offset = 0 + if direction == "DS" then + offset = 1 + end + return offset +end + +--- Formats the attenuation values for ADSL and Gfast in the format "20.2 dB" (OR) "8.7 dB", +--- VDSL in the format "DS1 20.2 dB, DS2 53.6 dB, DS3 N/A (OR) US0 8.0 dB, US1 N/A, US2 41.6 dB" -- @string attenuation the attenuation value -- @string direction upstream or downstream direction --- @treturn string the formatted string of attenuation values for VDSL/ADSL +-- @treturn string the formatted string of attenuation values for VDSL/ADSL/Gfast function M.populateAttenuation(attenuation, direction) if not attenuation then return end if find(attenuation, "[,%s]") then local attenTable = {} + local offset = attenuationIndexOffset(direction) for atten in attenuation:gmatch('([^,%s]+)') do local n = #attenTable - attenTable[n + 1] = formatAttenuation(atten, direction, n) + attenTable[n + 1] = formatAttenuation(atten, direction, n + offset) end return table.concat(attenTable, ", ") else @@ -1963,6 +2195,100 @@ function M.populateAttenuation(attenuation, direction) end end +---Validator that will check whether the given value is valid IPv4 address +-- @function [parent=#post_helper] validateStringIsIPv4 +-- @param ip +-- @return true or nil+error message +function M.validateStringIsIPv4(ip) + if not ip then + return nil, T"Invalid input" + end + ip = untaint(ip) + if inet.isValidIPv4(ip) then + return true + end + return nil, T"String is not an IPv4 address." +end +-- convert string value to table value +-- @function [parent=#post_helper] stringToTable +-- @string value, pattern +-- @treturn table +function M.stringToTable(value, pattern) + local table = {} + if value and pattern then + for v in value:gmatch(pattern) do + table[#table + 1] = v + end + end + return table +end + +---Compares two mac addresses and return true if +-- they are same, nil otherwise. +-- Comparator is case insensitive +-- Delimiter can be : or - +-- @param mac1 first mac address +-- @param mac2 second mac address +-- @return boolean true or nil +function M.compareMACAddresses(mac1, mac2) + if not (M.validateStringIsMAC(mac1) or M.validateStringIsMAC(mac2)) then + return nil, T"Invalid MAC Address" + end + if lower(mac1:gsub("[:-]", "")) == lower(mac2:gsub("[:-]", "")) then + return true + end +end + +---Validator that will check whether the given value +-- is present in inputList table or not, +-- it optionally accepts comparator. +-- If no comparator is present, it just checks both values are equal. +-- @function [parent=#post_helper] valueInList +-- @param inputList table +-- @param value value to be checked +-- @param comparator function optional +-- @return boolean true or nil +function M.valueInList(inputList, value, comparator) + if inputList then + for _, v in pairs(inputList) do + if type(comparator) == "function" then + if comparator(v.value, value) then + return true + end + elseif v.value == value then + return true + end + end + end +end + +--- Validator that will check whether the given IP address is in Network Range. +--- Validate the given IP address is not in the Reserved IP list. +-- @return true or nil+error message +function M.validateDMZ(value, object) + local network = { + gateway_ip = "uci.network.interface.@lan.ipaddr", + netmask = "uci.network.interface.@lan.netmask", + } + content_helper.getExactContent(network) + if object.DMZ_enable ~= "1" and value == "" then + return true + end + local isDestIP, errormsg = M.getValidateStringIsDeviceIPv4(network.gateway_ip, network.netmask)(value) + if not isDestIP then + return nil, errormsg + end + isDestIP, errormsg = M.reservedIPValidation(value) + if not isDestIP then + return nil, errormsg + end + isDestIP, errormsg = M.validateQTN(value) + if not isDestIP then + return nil, errormsg + end + return true +end + --- Is upgrade allowed or not -- @param upgradefw upgradefw config value -- @param userRole Role of the user @@ -1994,4 +2320,91 @@ function M.isSpaceInString(value) return true end +-- Calculates DHCP start and end addresses +-- @function calculateDHCPStartAndLimitAddress +-- @param baseip gateway address +-- @param netmask subnet mask +-- @param start DHCP start address +-- @param limit DHCP Limit address +-- @return DHCP startAddress, DHCP endAddress, network address +function M.DHCPStartAndLimitAddress(baseip, netmask, start, limit) + if start and limit and baseip and netmask then + baseip = ipv42num(baseip) + netmask = ipv42num(netmask) + if not baseip or not netmask then + return nil, T"Invalid IP Address." + end + local network = bit.band(baseip, netmask) + local ipmax = bit.bor(network, bit.bnot(netmask)) - 1 + local startAddress = bit.bor(network, bit.band(start, bit.bnot(netmask))) + local endAddress = startAddress+limit - 1 + startAddress = M.num2ipv4(startAddress) + if endAddress > ipmax then + endAddress = ipmax + end + endAddress = M.num2ipv4(endAddress) + network = M.num2ipv4(network) + return startAddress, endAddress, network + end +end + +--- Is feature enabled or not +-- @param feature Specific feature +-- @param userRole Role of the user +-- @return true if feature is enabled +-- @return nil if feature is disabled +function M.isFeatureEnabled(feature, userRole) + local allowedRoles = proxy.get("uci.web.feature.@"..feature..".") + if not allowedRoles then + return true + end + for _, role in ipairs(allowedRoles) do + if role.value == userRole then + return true + end + end +end + +-- Function to validate a UciName +-- value must be between 1 and 63 characters long +-- valid name must be non-empty string that consists of letters, digits or underscores +-- @return true or nil+error message +function M.validateUciName(value) + if not match(value, "^[%w_]+$") or #value > 63 then + return nil, T"Invalid input." + end + return true +end + +--- Validate the given IP/MAC is LXC's IP/MAC +-- @param value IP/MAC address +-- @return true if the value is not an LXC's IP/MAC Address +-- @return nil+error message if the given input is LXC's IP/MAC Address +function M.validateLXC(value) + if not value then + return nil, "Invalid input" + end + + local lxcMac = { mac = "uci.env.var.local_eth_mac_lxc" } + local lxcAvailable = content_helper.getExactContent(lxcMac) + if not lxcAvailable then + return true + end + if M.validateStringIsMAC(value) then + if lower(lxcMac.mac) == lower(value) then + return nil, format(T"Cannot assign, %s in use by system.", value) + end + return true + elseif inet.isValidIPv4(untaint(value)) then + local lxcIP = content_helper.getMatchedContent("sys.proc.net.arp.",{ hw_address = lower(lxcMac.mac)}) + for _, v in ipairs(lxcIP) do + if v.ip_address == value then + return nil, format(T"Cannot assign, %s in use by system.", value) + end + end + return true + end + return nil, T"Invalid input." +end + return M diff --git a/decompressed/gui_file/usr/lib/lua/web/reload_assistance.lua b/decompressed/gui_file/usr/lib/lua/web/reload_assistance.lua new file mode 100644 index 000000000..89e63785a --- /dev/null +++ b/decompressed/gui_file/usr/lib/lua/web/reload_assistance.lua @@ -0,0 +1,135 @@ +local require = require +local pairs = pairs +local ipairs = ipairs + +local untaint = string.untaint + +local assistance = require 'web.assistance' +local dm = require 'datamodel' + +local M = {} + +local function parse_actions(args) + local actions = {} + for assistant, action in pairs(args) do + local enable, mode, pwdcfg, pwd = action:match("(.*)_(.*)_(.*)_(.*)") + actions[#actions+1] = { + assistant = assistant:untaint(), + enable = enable, + mode = mode, + pwdcfg = pwdcfg, + pwd = pwd + } + end + return actions +end + +local function load_users() + local users = {} + local user_params = dm.get("uci.web.user.") or {} + for _, param in ipairs(user_params) do + local sectionname = param.path:match("^uci.web.user.@([^.]+)%.") + local section = users[sectionname] + if not section then + section = {} + users[sectionname] = section + end + section[param.param] = param.value + end + return users +end + +local function get_user_byname(username, users) + for _, user in pairs(users) do + if user.name == username then + return user + end + end +end + +local function get_assistant_user(assistant) + return get_user_byname(assistant:username(), load_users()) +end + +local _boolean = {} +_boolean.__index = _boolean + +local function Boolean(trueValue, falseValue) + return setmetatable({ + trueValue = trueValue, + falseValue = falseValue + }, _boolean) +end + +function _boolean:__call(s, default) + if s == self.trueValue then + return true + elseif s == self.falseValue then + return false + else + return default + end +end + +local enableAsBoolean = Boolean("on", "off") +local permanentAsBoolean = Boolean("permanent", "temporary") + +local function perform_action(action) + local assistant = assistance.getAssistant(action.assistant) + if not assistant then + return + end + local pwd = action.pwd + local pwdcfg = action.pwdcfg or "keep" + if pwdcfg == "random" then + pwd=nil + elseif pwdcfg == "keep" then + pwd=false + elseif pwdcfg == "srpuci" then + local user = get_assistant_user(assistant) + pwd = { + salt = untaint(user.srp_salt), + verifier = untaint(user.srp_verifier), + } + if not (pwd.salt and pwd.verifier) then + pwd = nil + end + end + local enable = enableAsBoolean(action.enable, assistant:enabled()) + local permanent = permanentAsBoolean(action.mode, assistant:isPermanentMode()) + assistant:enable_with_reload(enable, permanent, pwd) +end + +local function perform_all_actions(actions) + for _, action in ipairs(actions) do + perform_action(action) + end +end + +local function refresh_assistant(name) + local assistant = assistance.getAssistant(name) + if assistant then + local enabled = assistant:enabled() + local permanent = assistant:isPermanentMode() + local pswcfg = false --keep + assistant:enable_with_reload(enabled, permanent, pswcfg) + end +end + +local function refresh_all_assistants() + local assistants = assistance.assistantNames() + for _, name in ipairs(assistants) do + refresh_assistant(name) + end +end + +function M.reload(args) + local actions = parse_actions(args) + if #actions>0 then + perform_all_actions(actions) + else + refresh_all_assistants() + end +end + +return M \ No newline at end of file diff --git a/decompressed/gui_file/usr/lib/lua/web/session.lua b/decompressed/gui_file/usr/lib/lua/web/session.lua index 3d58ce14e..58b878d67 100644 --- a/decompressed/gui_file/usr/lib/lua/web/session.lua +++ b/decompressed/gui_file/usr/lib/lua/web/session.lua @@ -43,7 +43,7 @@ end --- Return the username associated with the session. -- @return #string The username associated with this session. function Session:getusername() - return sessionUser(self).name + return string.taint(sessionUser(self).name) end --- Returns whether the current user is the default user. @@ -236,7 +236,7 @@ function Session:reloadAllUsers() self.mgr.sessioncontrol.reloadUsers() end ---get currently logged-in user sessions +--get currently logged-in user sessions function Session:getUserCount() return self.mgr:getUserCount() end diff --git a/decompressed/gui_file/usr/lib/lua/web/sessionmgr.lua b/decompressed/gui_file/usr/lib/lua/web/sessionmgr.lua index a8d94743a..987418b63 100644 --- a/decompressed/gui_file/usr/lib/lua/web/sessionmgr.lua +++ b/decompressed/gui_file/usr/lib/lua/web/sessionmgr.lua @@ -1,7 +1,10 @@ local setmetatable, pairs, ipairs, tonumber, require, type = setmetatable, pairs, ipairs, tonumber, require, type local ngx, tostring = ngx, tostring + local dm = require("datamodel") local srp = require("srp") +local check_host = require("web.check_host") + local printf = require("web.web").printf local get_cookies = require("web.web").get_cookies local string = string @@ -223,50 +226,18 @@ function SessionMgr:authorizeRequest(session, resource) return true end -local function validHostDMPaths() - local intf = dm.get({"uci.dhcp.dhcp."}, false) or {} - local paths = {"uci.system.system.@system[0].hostname", - "uci.dhcp.dnsmasq.@dnsmasq[0].hostname."} - for _, v in ipairs(intf) do - if v.path:match("^uci%.dhcp%.dhcp%.") then - if v.param == "interface" and v.value ~= "" then - paths[#paths + 1] = format('rpc.network.interface.@%s.ipaddr', v.value) - paths[#paths + 1] = format('rpc.network.interface.@%s.ip6addr', v.value) - end - end - end - return paths -end - ---- Verify if the given hostname a valid hostname or IP address for this system --- @param http_req_host The hostname/IP address that needs to be authenticated --- @return True if the hostname is valid. Otherwise false -local function hostIsValid(http_req_host) - local host = dm.get(validHostDMPaths(), false) or {} - for _, v in ipairs(host) do - if v.path == "uci.system.system.@system[0]." then - if http_req_host == v.value then - return true - end - elseif v.path:match("^uci%.dhcp%.dnsmasq%.@dnsmasq%[0%]%.hostname%.") then - if v.param == "value" and http_req_host == v.value then - return true - end - elseif v.path:match("^rpc%.network%.interface%.") then - if (v.param == "ipaddr" or v.param == "ip6addr") then - if v.value ~= "" and http_req_host == v.value then - return true - end - end - end - end - return false +local function hostWithoutPort(host) + return host:match("^([^:]+):?%d*$") or host:match("^%[(.+)%]:?%d*$") end -local function preventDNSRebind() - --if not hostIsValid(untaint(ngx.var.http_host)) then - -- ngx.exit(ngx.HTTP_UNAUTHORIZED) - --end +local function preventDNSRebind(http_host) + if http_host then + http_host = hostWithoutPort(http_host) + if not check_host.refersToUs(http_host) then + ngx.log(ngx.ERR, "HTTP Host header does not refer to us") + ngx.exit(ngx.HTTP_UNAUTHORIZED) + end + end end local function redirectIfNotAuthorized(mgr, session, sessionID) @@ -302,6 +273,7 @@ local function getSessionAddress() return { remote = untaint(ngx_var.remote_addr), server = untaint(ngx_var.server_addr), + http_host = untaint(ngx_var.http_host), } end @@ -354,8 +326,7 @@ local function cleanupIfNoSession(self , session) end end -local function getSessionForRequest(mgr, noActivityUpdate) - local address = getSessionAddress() +local function getSessionForRequest(mgr, address, noActivityUpdate) local session, sessionID = verifySession(mgr, address.remote) if not session then session, sessionID = newSession(mgr, address) @@ -376,10 +347,11 @@ end -- @param noActivityUpdate (optional) if true, the user activity timer -- for the session won't be updated. function SessionMgr:checkrequest(noActivityUpdate) - local session, sessionID = getSessionForRequest(self, noActivityUpdate) + local address = getSessionAddress() + --preventDNSRebind(address.http_host) + local session, sessionID = getSessionForRequest(self, address, noActivityUpdate) cleanupIfNoSession(self,session) redirectIfServiceNotAvailable(session) - preventDNSRebind() redirectIfNotAuthorized(self, session, sessionID) storeSessionInNginx(session) end @@ -426,9 +398,22 @@ local function clearWrongPasswordInfo(username) wrongPassInfo[username] = nil end -local function getWaitTime(username) +local algorithms = { + linear = function(count, lockTime, clockTime, backOffRepeat) + return math.floor(count/backOffRepeat) * 10 + lockTime - clockTime + end, + exponential = function(count, lockTime, clockTime) + return (2^(count) + lockTime) - clockTime + end, +} + +local function backOffAlgorithm(backOffMethod) + return algorithms[backOffMethod] or algorithms.exponential +end + +local function getWaitTime(username, mgr) local info = getWrongPasswordInfo(username) - return (2^(info.count) + info.lockTime) - clock_gettime(CLOCK_MONOTONIC) + return backOffAlgorithm(mgr.backOffMethod)(info.count, info.lockTime, clock_gettime(CLOCK_MONOTONIC), mgr.backOffRepeat) end -- Handle scenario where user refreshes browser, when wait popup @@ -443,7 +428,7 @@ local function checkBruteForceWaitingTimeForUser(mgr, username) if mgr.relaxfirstattempt == "1" and info.count == 1 then return end - local waitTime = getWaitTime(username) + local waitTime = getWaitTime(username, mgr) if waitTime > 0 then printf('{ "error": { "msg":"%s","waitTime":"%d","wrongCount":"%s" }}', "failed", waitTime, info.count) return true @@ -469,7 +454,7 @@ local function preventBruteForce(mgr, username, errMsg) if mgr.relaxfirstattempt == "1" and count == 1 then return end - local waitTime = getWaitTime(username) + local waitTime = getWaitTime(username, mgr) printf('{ "error": { "msg":"%s","waitTime":"%s","wrongCount":"%s" }}', errMsg or "failed", waitTime, count) return true end @@ -499,7 +484,6 @@ function SessionMgr:handleAuth() if uri ~= self.authpath and uri ~= self.passpath then return end - -- only POST is allowed if ngx.req.get_method() ~= "POST" then ngx.exit(ngx.HTTP_FORBIDDEN) -- doesn't return @@ -525,7 +509,7 @@ function SessionMgr:handleAuth() -- TODO: shouldn't reveal that the username is unknown; instead -- we should generate a fake salt and B and in the second -- step simply report that authentication failed - ngx.log(ngx.ERR, "Invalid login credentials") + ngx.log(ngx.ERR, string.format("Failed logon attempt for user %s", post_args.I)) ngx.print('{ "error":"failed" }') else if not checkBruteForceWaitingTimeForUser(self, I) then @@ -563,7 +547,7 @@ function SessionMgr:handleAuth() printf('{ "M":"%s" }', M2) else if not preventBruteForce(self, verifier:username(), errmsg) then - ngx.log(ngx.ERR, "Invalid login credentials") + ngx.log(ngx.ERR, "Failed logon attempt for user " .. verifier:username()) printf('{ "error":"%s" }', errmsg or "failed") end end diff --git a/decompressed/gui_file/usr/lib/lua/web/ui_helper.lua b/decompressed/gui_file/usr/lib/lua/web/ui_helper.lua index f7d732966..7da34eaaa 100644 --- a/decompressed/gui_file/usr/lib/lua/web/ui_helper.lua +++ b/decompressed/gui_file/usr/lib/lua/web/ui_helper.lua @@ -1,10 +1,3 @@ --- NG-78956; NG-91245 ---NG-96253 GPON-Diagnostics/Network needs to be lifted to TI specific functionalities ---NG-94758 GUI: Mobile card and modal are not completely translated ---NG-102545 GUI broadband is showing SFP Broadband GUI page when Ethernet 4 is connected ---NG-103913 GUI new generic ui_helper.lua is taken ---NG-103998 GUI generic ui_helper.lua causes an issue in every modal head ---[NG-107029] GUI Diagnostics status NOK for sfp and LAN4 local require, pairs, ipairs, type, tonumber, getfenv = require, pairs, ipairs, type, tonumber, getfenv local format, find, gsub, gmatch = string.format, string.find, string.gsub, string.gmatch local huge = math.huge @@ -327,7 +320,7 @@ function M.createSimpleInputPassword(name, value, attributes, helpmsg) input = { class = "edit-input span3", type = "password", - autocomplete = "off", + autocomplete = "new-password", name = name, value = "", id = name, @@ -1264,6 +1257,28 @@ local function createTableDataEditAggregElem(v, data, helpmsg) return content end +--- copy table data +-- @param #table +-- @return #table new table +local function copy_table(value) + local copy = {} + for k, v in pairs(value) do + copy[k] = v + end + return copy +end + +--- This function create the table of subColumnbutton attributes +-- @param #table attributes +-- @return #table subColumnbutton attributes +local function attrSubColumnButton(attributes) + local attr = copy_table(attributes.button) + attr["data-original-title"] = nil + attr["class"] = gsub(attr["class"], "%s*tooltip%-on", "" ) + attr["class"] = gsub(attr["class"], "btn%-mini", "btn-small") + return {button=attr} +end + --- Create a line of edit component to add a new element -- @param #table columns -- @param #table data array @@ -1349,12 +1364,12 @@ local function createTableDataEdit(columns, data, add, helpmsg) if #aggreg_lines > 0 then content[#content + 1] = format(' ', numcolumns) if not add then - content[#content + 1] = M.createSimpleButton("","icon-ok", attrModify) + content[#content + 1] = M.createSimpleButton(T"Apply","", attrSubColumnButton(attrModify)) else - content[#content + 1] = M.createSimpleButton("","icon-plus-sign", attrAdd) + content[#content + 1] = M.createSimpleButton(T"Save","", attrSubColumnButton(attrAdd)) end content[#content + 1] = " " - content[#content + 1] = M.createSimpleButton("","icon-remove", attrCancel) + content[#content + 1] = M.createSimpleButton(T"Cancel","", attrSubColumnButton(attrCancel)) content[#content + 1] = "" end @@ -1811,7 +1826,7 @@ end -- @param #table helpLink if defined, the header contains the help text which links to the help page indicated by link -- typical use: { data-toggle="modal", data-remote = "/help/index.lp"} or { href="/help/index.lp" } -- @return #table for ngx.print -function M.createHeader(name, hasAdvanced, hasRefresh, autorefresh, helpLink) +function M.createHeader(name, hasAdvanced, hasRefresh, autorefresh, helpLink, hasReload) setlanguage() local htmlautorefresh = "" if type(autorefresh) == "number" then @@ -1838,12 +1853,19 @@ function M.createHeader(name, hasAdvanced, hasRefresh, autorefresh, helpLink) end -- Display the refresh button in the modal header if required - if hasRefresh == true then + if hasRefresh then html[#html + 1] = format(' %s', T"refresh data") end + if hasReload then + html[#html + 1] = [[ + + Reload Icon + + ]] + end -- Display the show advanced button in the modal header if required - if hasAdvanced == true then + if hasAdvanced then html[#html + 1] = format([[ %s @@ -1900,7 +1922,7 @@ end -- @param #string switchName if nil, then no switch will be added -- @param #string switchValue -- @return #string for ngx.print -function M.createCardHeader(title, modalPath, switchName, switchValue, attributes, mobile) +function M.createCardHeader(title, modalPath, switchName, switchValue, attributes) local dataId if modalPath then dataId = modalPath:match("^.*/([^.]*).lp$") @@ -1941,17 +1963,12 @@ function M.createCardHeader(title, modalPath, switchName, switchValue, attribute [[
]], format("

%s

", header, title, title) } - html[#html + 1] = [[
]] - if modalPath and modalPath ~= "" then - html[#html + 1] = format("
", div, icon) - end if switchName and switchName ~= "" then html[#html + 1] = M.createSimpleSwitch(switchName,switchValue, attributes) end - if mobile == 1 then - html[#html + 1] = [[
]] + if modalPath and modalPath ~= "" then + html[#html + 1] = format("
", div, icon) end - html[#html + 1] = [[
]] html[#html + 1] = [[
]] return html end @@ -2303,6 +2320,37 @@ function M.createLabelWithButton(desc, value, buttontext, icon, attributes, help return html end +-- @function [parent=#ui_helper] createTab +-- @param #table items: used as list items in nav tabs +-- @param #string uri: used to update which page is active +-- @return #string +function M.createTab(items, uri) + local html = {} + + for _, v in ipairs(items) do + local active = "" + if uri == ("/modals/" .. v[1]) then + active = "active" + end + html[#html+1] = string.format('
  • %s
  • ', active, v[1], v[2]) + end + return html +end + +-- @function [parent=#ui_helper] alertAttributes +-- @param #string message: to display in alert box +-- @param #string className: used to define class for alert message +-- @param #string elementId: used to define unique id for alert message +-- @return #string for ngx.print +function M.alertAttributes(message, elemClassName, elemId) + local alertBlock = { + alert = { + class = elemClassName, + id = elemId + } + } + return M.createAlertBlock(message, alertBlock) +end return M diff --git a/decompressed/gui_file/usr/lib/lua/web/uinetwork_helper.lua b/decompressed/gui_file/usr/lib/lua/web/uinetwork_helper.lua index c594d71b8..0b4d16df7 100644 --- a/decompressed/gui_file/usr/lib/lua/web/uinetwork_helper.lua +++ b/decompressed/gui_file/usr/lib/lua/web/uinetwork_helper.lua @@ -5,6 +5,7 @@ local format = string.format local ch = require("web.content_helper") local proxy = require("datamodel") local ww = require("web.web") +local bit = require("bit") --- -- @param #string basepath @@ -15,61 +16,67 @@ function M.getHostsList() local result = {} if type(data) == "table" then - result = ch.convertResultToObject(basepath, data) + result = ch.convertResultToObject(basepath, data) end return result end --- local ipv6_pattern = "%x*:%x*:%x*:%x*:%x*:%x*:%x*:%x*" +local ipv4_pattern = "%d+%.%d+%.%d+%.%d+" local mac_pattern = "%x*:%x*:%x*:%x*:%x*:%x*" +-- @param skipIPv6LinkLocal [optional] to get only IPv6 address (not link local address) -- @return 2 tables: 1 IPv4 hosts, 2 IPv6 hosts -function M.getAutocompleteHostsList() +function M.getAutocompleteHostsList(skipIPv6LinkLocal) local hosts = M.getHostsList() local ipv4hosts={} local ipv6hosts={} for i,v in ipairs(hosts) do - local name = ww.html_escape(v.FriendlyName) - local iplist = ww.html_escape(v.IPAddress) - local macaddr = ww.html_escape(v.MACAddress) - local friendlyName + local name = ww.html_escape(v.FriendlyName) + local iplist = ww.html_escape(v.IPAddress) + local macaddr = ww.html_escape(v.MACAddress) + local friendlyName - --Get the IPv4 hosts - local ip = iplist:match("%d+%.%d+%.%d+%.%d+") -- match first IPv4 in list (will have to do for now) - if ip then - --The rpc.hosts.host will never return empty value for FriendlyName. - --For example the default value will be Unknown-b4:ef:fa:b7:f5:98 if host name is empty - if name:match(mac_pattern) then - friendlyName = ip - else - friendlyName = name .. " (" .. ip .. ")" - end - friendlyName = friendlyName .. " [" .. macaddr .. "]" - ipv4hosts[friendlyName] = ip - end + --Get the IPv4 hosts + for ipv4 in iplist:gmatch(ipv4_pattern) do + --The rpc.hosts.host will never return empty value for FriendlyName. + --For example the default value will be Unknown-b4:ef:fa:b7:f5:98 if host name is empty + if name:match(mac_pattern) then + friendlyName = ipv4 + else + friendlyName = name .. " (" .. ipv4 .. ")" + end + friendlyName = friendlyName .. " [" .. macaddr .. "]" + ipv4hosts[friendlyName] = ipv4 + end - --Get the IPv6 hosts - for ipv6 in iplist:gmatch(ipv6_pattern) do - if ipv6 then - if name:match(mac_pattern) then - friendlyName = ipv6 - else - friendlyName = name .. "(" .. ipv6 .. ")" + --Get the IPv6 hosts + for ipv6 in iplist:gmatch(ipv6_pattern) do + if ipv6 then + if name:match(mac_pattern) then + friendlyName = ipv6 + else + friendlyName = name .. "(" .. ipv6 .. ")" + end + friendlyName = friendlyName .. " [" .. macaddr .. "]" + if skipIPv6LinkLocal then + if bit.band(ipv6:byte(1), 0xE0) == 0x20 then + ipv6hosts[friendlyName] = ipv6 end - friendlyName = friendlyName .. " [" .. macaddr .. "]" + else ipv6hosts[friendlyName] = ipv6 end - end - end + end + end + end return ipv4hosts, ipv6hosts end function M.getAutocompleteHostsListMac() local hosts = M.getHostsList() - local ipv4hosts={} - local ipv6hosts={} local ipv4hostsMac={} + local ipv6hostsMac={} for i,v in ipairs(hosts) do local name = ww.html_escape(v.FriendlyName) @@ -77,22 +84,39 @@ function M.getAutocompleteHostsListMac() local macaddr = ww.html_escape(v.MACAddress) local friendlyName - --Get the IPv4 hosts - local ip = iplist:match("%d+%.%d+%.%d+%.%d+") -- match first IPv4 in list (will have to do for now) - if ip then - --The rpc.hosts.host will never return empty value for FriendlyName. - --For example the default value will be Unknown-b4:ef:fa:b7:f5:98 if host name is empty - if name:match(mac_pattern) then - friendlyName = ip - else - friendlyName = name .. " (" .. ip .. ")" - end - friendlyName = friendlyName .. " [" .. macaddr .. "]" - ipv4hostsMac[friendlyName] = macaddr - end + --Get the IPv4 hosts + for ipv4 in iplist:gmatch(ipv4_pattern) do + --The rpc.hosts.host will never return empty value for FriendlyName. + --For example the default value will be Unknown-b4:ef:fa:b7:f5:98 if host name is empty + if name:match(mac_pattern) then + friendlyName = ipv4 + else + friendlyName = name + end + friendlyName = friendlyName .. " [" .. ipv4 .. "]" + ipv4hosts[friendlyName] = macaddr + end + --Get the IPv6 hosts + for ipv6 in iplist:gmatch(ipv6_pattern) do + if ipv6 then + if name:match(mac_pattern) then + friendlyName = ipv6 + else + friendlyName = name + end + friendlyName = friendlyName .. " [" .. ipv6 .. "]" + if skipIPv6LinkLocal then + if bit.band(ipv6:byte(1), 0xE0) == 0x20 then + ipv6hosts[friendlyName] = macaddr + end + else + ipv6hosts[friendlyName] = macaddr + end + end + end end - return ipv4hostsMac + return ipv4hostsMac, ipv6hostsMac end --- diff --git a/decompressed/gui_file/usr/lib/lua/web/web.lua b/decompressed/gui_file/usr/lib/lua/web/web.lua index fa814c89d..90a94f6cf 100644 --- a/decompressed/gui_file/usr/lib/lua/web/web.lua +++ b/decompressed/gui_file/usr/lib/lua/web/web.lua @@ -349,7 +349,7 @@ function M.isDemoBuild() local data = require("datamodel").get("uci.version.version.@version[0].mask") if data then local versionmask = data[1].value - cached_isDemoBuild = (tonumber(versionmask:sub(3,3)) or 0) % 2 == 1 or versionmask:sub(4,4) == '9' + cached_isDemoBuild = tonumber(versionmask:sub(3,3)) % 2 == 1 or versionmask:sub(4,4) == '9' else cached_isDemoBuild = true end