Rebased from upstream / out of band repository.
[librecmc/librecmc.git] / package / luci / modules / luci-base / luasrc / model / network.lua
index d9ef4089c8204d99c5a901c167f382cb385f57b4..49e1657aae46372d62935818df6ec19388011881 100644 (file)
@@ -6,14 +6,12 @@ local type, next, pairs, ipairs, loadfile, table, select
 
 local tonumber, tostring, math = tonumber, tostring, math
 
-local require = require
+local pcall, require, setmetatable = pcall, require, setmetatable
 
 local nxo = require "nixio"
 local nfs = require "nixio.fs"
 local ipc = require "luci.ip"
-local sys = require "luci.sys"
 local utl = require "luci.util"
-local dsp = require "luci.dispatcher"
 local uci = require "luci.model.uci"
 local lng = require "luci.i18n"
 local jsc = require "luci.jsonc"
@@ -25,6 +23,22 @@ IFACE_PATTERNS_VIRTUAL  = { }
 IFACE_PATTERNS_IGNORE   = { "^wmaster%d", "^wifi%d", "^hwsim%d", "^imq%d", "^ifb%d", "^mon%.wlan%d", "^sit%d", "^gre%d", "^gretap%d", "^ip6gre%d", "^ip6tnl%d", "^tunl%d", "^lo$" }
 IFACE_PATTERNS_WIRELESS = { "^wlan%d", "^wl%d", "^ath%d", "^%w+%.network%d" }
 
+IFACE_ERRORS = {
+       CONNECT_FAILED                  = lng.translate("Connection attempt failed"),
+       INVALID_ADDRESS                 = lng.translate("IP address in invalid"),
+       INVALID_GATEWAY                 = lng.translate("Gateway address is invalid"),
+       INVALID_LOCAL_ADDRESS   = lng.translate("Local IP address is invalid"),
+       MISSING_ADDRESS                 = lng.translate("IP address is missing"),
+       MISSING_PEER_ADDRESS    = lng.translate("Peer address is missing"),
+       NO_DEVICE                               = lng.translate("Network device is not present"),
+       NO_IFACE                                = lng.translate("Unable to determine device name"),
+       NO_IFNAME                               = lng.translate("Unable to determine device name"),
+       NO_WAN_ADDRESS                  = lng.translate("Unable to determine external IP address"),
+       NO_WAN_LINK                             = lng.translate("Unable to determine upstream interface"),
+       PEER_RESOLVE_FAIL               = lng.translate("Unable to resolve peer host name"),
+       PIN_FAILED                              = lng.translate("PIN code rejected")
+}
+
 
 protocol = utl.class()
 
@@ -108,6 +122,58 @@ function _set(c, s, o, v)
        end
 end
 
+local function _wifi_state()
+       if not next(_ubuswificache) then
+               _ubuswificache = utl.ubus("network.wireless", "status", {}) or {}
+       end
+       return _ubuswificache
+end
+
+local function _wifi_state_by_sid(sid)
+       local t1, n1 = _uci:get("wireless", sid)
+       if t1 == "wifi-iface" and n1 ~= nil then
+               local radioname, radiostate
+               for radioname, radiostate in pairs(_wifi_state()) do
+                       if type(radiostate) == "table" and
+                          type(radiostate.interfaces) == "table"
+                       then
+                               local netidx, netstate
+                               for netidx, netstate in ipairs(radiostate.interfaces) do
+                                       if type(netstate) == "table" and
+                                          type(netstate.section) == "string"
+                                       then
+                                               local t2, n2 = _uci:get("wireless", netstate.section)
+                                               if t1 == t2 and n1 == n2 then
+                                                       return radioname, radiostate, netstate
+                                               end
+                                       end
+                               end
+                       end
+               end
+       end
+end
+
+local function _wifi_state_by_ifname(ifname)
+       if type(ifname) == "string" then
+               local radioname, radiostate
+               for radioname, radiostate in pairs(_wifi_state()) do
+                       if type(radiostate) == "table" and
+                          type(radiostate.interfaces) == "table"
+                       then
+                               local netidx, netstate
+                               for netidx, netstate in ipairs(radiostate.interfaces) do
+                                       if type(netstate) == "table" and
+                                          type(netstate.ifname) == "string" and
+                                          netstate.ifname == ifname
+                                       then
+                                               return radioname, radiostate, netstate
+                                       end
+                               end
+                       end
+               end
+       end
+end
+
 function _wifi_iface(x)
        local _, p
        for _, p in ipairs(IFACE_PATTERNS_WIRELESS) do
@@ -118,61 +184,113 @@ function _wifi_iface(x)
        return false
 end
 
-function _wifi_state(key, val, field)
-       local radio, radiostate, ifc, ifcstate
-
-       if not next(_ubuswificache) then
-               _ubuswificache = utl.ubus("network.wireless", "status", {}) or {}
+local function _wifi_iwinfo_by_ifname(ifname, force_phy_only)
+       local stat, iwinfo = pcall(require, "iwinfo")
+       local iwtype = stat and type(ifname) == "string" and iwinfo.type(ifname)
+       local is_nonphy_op = {
+               bitrate     = true,
+               quality     = true,
+               quality_max = true,
+               mode        = true,
+               ssid        = true,
+               bssid       = true,
+               assoclist   = true,
+               encryption  = true
+       }
 
-               -- workaround extended section format
-               for radio, radiostate in pairs(_ubuswificache) do
-                       for ifc, ifcstate in pairs(radiostate.interfaces) do
-                               if ifcstate.section and ifcstate.section:sub(1, 1) == '@' then
-                                       local s = _uci:get_all('wireless.%s' % ifcstate.section)
-                                       if s then
-                                               ifcstate.section = s['.name']
-                                       end
+       if iwtype then
+               -- if we got a type but no real netdev, we're referring to a phy
+               local phy_only = force_phy_only or (ipc.link(ifname).type ~= 1)
+
+               return setmetatable({}, {
+                       __index = function(t, k)
+                               if k == "ifname" then
+                                       return ifname
+                               elseif phy_only and is_nonphy_op[k] then
+                                       return nil
+                               elseif iwinfo[iwtype][k] then
+                                       return iwinfo[iwtype][k](ifname)
                                end
                        end
-               end
+               })
        end
+end
 
-       for radio, radiostate in pairs(_ubuswificache) do
-               for ifc, ifcstate in pairs(radiostate.interfaces) do
-                       if ifcstate[key] == val then
-                               return ifcstate[field]
-                       end
+local function _wifi_sid_by_netid(netid)
+       if type(netid) == "string" then
+               local radioname, netidx = netid:match("^(%w+)%.network(%d+)$")
+               if radioname and netidx then
+                       local i, n = 0, nil
+
+                       netidx = tonumber(netidx)
+                       _uci:foreach("wireless", "wifi-iface",
+                               function(s)
+                                       if s.device == radioname then
+                                               i = i + 1
+                                               if i == netidx then
+                                                       n = s[".name"]
+                                                       return false
+                                               end
+                                       end
+                               end)
+
+                       return n
                end
        end
 end
 
-function _wifi_lookup(ifn)
-       -- got a radio#.network# pseudo iface, locate the corresponding section
-       local radio, ifnidx = ifn:match("^(%w+)%.network(%d+)$")
-       if radio and ifnidx then
-               local sid = nil
-               local num = 0
+function _wifi_sid_by_ifname(ifn)
+       local sid = _wifi_sid_by_netid(ifn)
+       if sid then
+               return sid
+       end
 
-               ifnidx = tonumber(ifnidx)
-               _uci:foreach("wireless", "wifi-iface",
-                       function(s)
-                               if s.device == radio then
-                                       num = num + 1
-                                       if num == ifnidx then
-                                               sid = s['.name']
-                                               return false
-                                       end
-                               end
-                       end)
+       local _, _, netstate = _wifi_state_by_ifname(ifn)
+       if netstate and type(netstate.section) == "string" then
+               return netstate.section
+       end
+end
 
-               return sid
+local function _wifi_netid_by_sid(sid)
+       local t, n = _uci:get("wireless", sid)
+       if t == "wifi-iface" and n ~= nil then
+               local radioname = _uci:get("wireless", n, "device")
+               if type(radioname) == "string" then
+                       local i, netid = 0, nil
+
+                       _uci:foreach("wireless", "wifi-iface",
+                               function(s)
+                                       if s.device == radioname then
+                                               i = i + 1
+                                               if s[".name"] == n then
+                                                       netid = "%s.network%d" %{ radioname, i }
+                                                       return false
+                                               end
+                                       end
+                               end)
 
-       -- looks like wifi, try to locate the section via ubus state
-       elseif _wifi_iface(ifn) then
-               return _wifi_state("ifname", ifn, "section")
+                       return netid, radioname
+               end
        end
 end
 
+local function _wifi_netid_by_netname(name)
+       local netid = nil
+
+       _uci:foreach("wireless", "wifi-iface",
+               function(s)
+                       local net
+                       for net in utl.imatch(s.network) do
+                               if net == name then
+                                       netid = _wifi_netid_by_sid(s[".name"])
+                                       return false
+                               end
+                       end
+               end)
+
+       return netid
+end
+
 function _iface_virtual(x)
        local _, p
        for _, p in ipairs(IFACE_PATTERNS_VIRTUAL) do
@@ -228,7 +346,7 @@ function init(cursor)
                        if i.family == "packet" then
                                _interfaces[name].flags   = i.flags
                                _interfaces[name].stats   = i.data
-                               _interfaces[name].macaddr = i.addr
+                               _interfaces[name].macaddr = ipc.checkmac(i.addr)
                        elseif i.family == "inet" then
                                _interfaces[name].ipaddrs[#_interfaces[name].ipaddrs+1] = ipc.IPv4(i.addr, i.netmask)
                        elseif i.family == "inet6" then
@@ -393,6 +511,17 @@ function register_pattern_virtual(self, pat)
        IFACE_PATTERNS_VIRTUAL[#IFACE_PATTERNS_VIRTUAL+1] = pat
 end
 
+function register_error_code(self, code, message)
+       if type(code) == "string" and
+          type(message) == "string" and
+          not IFACE_ERRORS[code]
+       then
+               IFACE_ERRORS[code] = message
+               return true
+       end
+
+       return false
+end
 
 function has_ipv6(self)
        return nfs.access("/proc/net/ipv6_route")
@@ -418,6 +547,13 @@ end
 function get_network(self, n)
        if n and _uci:get("network", n) == "interface" then
                return network(n)
+       elseif n then
+               local stat = utl.ubus("network.interface", "status", { interface = n })
+               if type(stat) == "table" and
+                  type(stat.proto) == "string"
+               then
+                       return network(n, stat.proto)
+               end
        end
 end
 
@@ -430,6 +566,23 @@ function get_networks(self)
                        nls[s['.name']] = network(s['.name'])
                end)
 
+       local dump = utl.ubus("network.interface", "dump", { })
+       if type(dump) == "table" and
+          type(dump.interface) == "table"
+       then
+               local _, net
+               for _, net in ipairs(dump.interface) do
+                       if type(net) == "table" and
+                          type(net.proto) == "string" and
+                          type(net.interface) == "string"
+                       then
+                               if not nls[net.interface] then
+                                       nls[net.interface] = network(net.interface, net.proto)
+                               end
+                       end
+               end
+       end
+
        local n
        for n in utl.kspairs(nls) do
                nets[#nets+1] = nls[n]
@@ -441,6 +594,9 @@ end
 function del_network(self, n)
        local r = _uci:delete("network", n)
        if r then
+               _uci:delete_all("luci", "ifstate",
+                       function(s) return (s.interface == n) end)
+
                _uci:delete_all("network", "alias",
                        function(s) return (s.interface == n) end)
 
@@ -524,20 +680,8 @@ function get_interface(self, i)
        if _interfaces[i] or _wifi_iface(i) then
                return interface(i)
        else
-               local ifc
-               local num = { }
-               _uci:foreach("wireless", "wifi-iface",
-                       function(s)
-                               if s.device then
-                                       num[s.device] = num[s.device] and num[s.device] + 1 or 1
-                                       if s['.name'] == i then
-                                               ifc = interface(
-                                                       "%s.network%d" %{s.device, num[s.device] })
-                                               return false
-                                       end
-                               end
-                       end)
-               return ifc
+               local netid = _wifi_netid_by_sid(i)
+               return netid and interface(netid)
        end
 end
 
@@ -644,7 +788,7 @@ function get_wifidevs(self)
 end
 
 function get_wifinet(self, net)
-       local wnet = _wifi_lookup(net)
+       local wnet = _wifi_sid_by_ifname(net)
        if wnet then
                return wifinet(wnet)
        end
@@ -660,7 +804,7 @@ function add_wifinet(self, net, options)
 end
 
 function del_wifinet(self, net)
-       local wnet = _wifi_lookup(net)
+       local wnet = _wifi_sid_by_ifname(net)
        if wnet then
                _uci:delete("wireless", wnet)
                return true
@@ -669,6 +813,7 @@ function del_wifinet(self, net)
 end
 
 function get_status_by_route(self, addr, mask)
+       local route_statuses = { }
        local _, object
        for _, object in ipairs(utl.ubus()) do
                local net = object:match("^network%.interface%.(.+)")
@@ -678,12 +823,14 @@ function get_status_by_route(self, addr, mask)
                                local rt
                                for _, rt in ipairs(s.route) do
                                        if not rt.table and rt.target == addr and rt.mask == mask then
-                                               return net, s
+                                               route_statuses[net] = s
                                        end
                                end
                        end
                end
        end
+
+       return route_statuses
 end
 
 function get_status_by_address(self, addr)
@@ -708,28 +855,40 @@ function get_status_by_address(self, addr)
                                        end
                                end
                        end
+                       if s and s['ipv6-prefix-assignment'] then
+                               local a
+                               for _, a in ipairs(s['ipv6-prefix-assignment']) do
+                                       if a and a['local-address'] and a['local-address'].address == addr then
+                                               return net, s
+                                       end
+                               end
+                       end
                end
        end
 end
 
-function get_wannet(self)
-       local net, stat = self:get_status_by_route("0.0.0.0", 0)
-       return net and network(net, stat.proto)
-end
+function get_wan_networks(self)
+       local k, v
+       local wan_nets = { }
+       local route_statuses = self:get_status_by_route("0.0.0.0", 0)
 
-function get_wandev(self)
-       local _, stat = self:get_status_by_route("0.0.0.0", 0)
-       return stat and interface(stat.l3_device or stat.device)
-end
+       for k, v in pairs(route_statuses) do
+               wan_nets[#wan_nets+1] = network(k, v.proto)
+       end
 
-function get_wan6net(self)
-       local net, stat = self:get_status_by_route("::", 0)
-       return net and network(net, stat.proto)
+       return wan_nets
 end
 
-function get_wan6dev(self)
-       local _, stat = self:get_status_by_route("::", 0)
-       return stat and interface(stat.l3_device or stat.device)
+function get_wan6_networks(self)
+       local k, v
+       local wan6_nets = { }
+       local route_statuses = self:get_status_by_route("::", 0)
+
+       for k, v in pairs(route_statuses) do
+               wan6_nets[#wan6_nets+1] = network(k, v.proto)
+       end
+
+       return wan6_nets
 end
 
 function get_switch_topologies(self)
@@ -784,22 +943,7 @@ function protocol.ifname(self)
                ifname = self:_ubus("device")
        end
        if not ifname then
-               local num = { }
-               _uci:foreach("wireless", "wifi-iface",
-                       function(s)
-                               if s.device then
-                                       num[s.device] = num[s.device]
-                                               and num[s.device] + 1 or 1
-
-                                       local net
-                                       for net in utl.imatch(s.network) do
-                                               if net == self.sid then
-                                                       ifname = "%s.network%d" %{ s.device, num[s.device] }
-                                                       return false
-                                               end
-                                       end
-                               end
-                       end)
+               ifname = _wifi_netid_by_netname(self.sid)
        end
        return ifname
 end
@@ -851,6 +995,16 @@ function protocol.metric(self)
        return self:_ubus("metric") or 0
 end
 
+function protocol.zonename(self)
+       local d = self:_ubus("data")
+
+       if type(d) == "table" and type(d.zone) == "string" then
+               return d.zone
+       end
+
+       return nil
+end
+
 function protocol.ipaddr(self)
        local addrs = self:_ubus("ipv4-address")
        return addrs and #addrs > 0 and addrs[1].address
@@ -923,7 +1077,15 @@ function protocol.ip6addrs(self)
 
        if type(addrs) == "table" then
                for n, addr in ipairs(addrs) do
-                       rv[#rv+1] = "%s1/%d" %{ addr.address, addr.mask }
+                       if type(addr["local-address"]) == "table" and
+                          type(addr["local-address"].mask) == "number" and
+                          type(addr["local-address"].address) == "string"
+                       then
+                               rv[#rv+1] = "%s/%d" %{
+                                       addr["local-address"].address,
+                                       addr["local-address"].mask
+                               }
+                       end
                end
        end
 
@@ -957,6 +1119,22 @@ function protocol.ip6prefix(self)
        end
 end
 
+function protocol.errors(self)
+       local _, err, rv
+       local errors = self:_ubus("errors")
+       if type(errors) == "table" then
+               for _, err in ipairs(errors) do
+                       if type(err) == "table" and
+                          type(err.code) == "string"
+                       then
+                               rv = rv or { }
+                               rv[#rv+1] = IFACE_ERRORS[err.code] or lng.translatef("Unknown error (%s)", err.code)
+                       end
+               end
+       end
+       return rv
+end
+
 function protocol.is_bridge(self)
        return (not self:is_virtual() and self:type() == "bridge")
 end
@@ -977,36 +1155,51 @@ function protocol.is_floating(self)
        return false
 end
 
+function protocol.is_dynamic(self)
+       return (self:_ubus("dynamic") == true)
+end
+
+function protocol.is_alias(self)
+       local ifn, parent = nil, nil
+
+       for ifn in utl.imatch(_uci:get("network", self.sid, "ifname")) do
+               if #ifn > 1 and ifn:byte(1) == 64 then
+                       parent = ifn:sub(2)
+               elseif parent ~= nil then
+                       parent = nil
+               end
+       end
+
+       return parent
+end
+
 function protocol.is_empty(self)
        if self:is_floating() then
                return false
        else
-               local rv = true
+               local empty = true
 
                if (self:_get("ifname") or ""):match("%S+") then
-                       rv = false
+                       empty = false
                end
 
-               _uci:foreach("wireless", "wifi-iface",
-                       function(s)
-                               local n
-                               for n in utl.imatch(s.network) do
-                                       if n == self.sid then
-                                               rv = false
-                                               return false
-                                       end
-                               end
-                       end)
+               if empty and _wifi_netid_by_netname(self.sid) then
+                       empty = false
+               end
 
-               return rv
+               return empty
        end
 end
 
+function protocol.is_up(self)
+       return (self:_ubus("up") == true)
+end
+
 function protocol.add_interface(self, ifname)
        ifname = _M:ifnameof(ifname)
        if ifname and not self:is_floating() then
                -- if its a wifi interface, change its network option
-               local wif = _wifi_lookup(ifname)
+               local wif = _wifi_sid_by_ifname(ifname)
                if wif then
                        _append("wireless", wif, "network", self.sid)
 
@@ -1021,7 +1214,7 @@ function protocol.del_interface(self, ifname)
        ifname = _M:ifnameof(ifname)
        if ifname and not self:is_floating() then
                -- if its a wireless interface, clear its network option
-               local wif = _wifi_lookup(ifname)
+               local wif = _wifi_sid_by_ifname(ifname)
                if wif then _filter("wireless", wif, "network", self.sid) end
 
                -- remove the interface
@@ -1037,27 +1230,17 @@ function protocol.get_interface(self)
                _bridge["br-" .. self.sid] = true
                return interface("br-" .. self.sid, self)
        else
-               local ifn = nil
-               local num = { }
+               local ifn = self:_ubus("l3_device") or self:_ubus("device")
+               if ifn then
+                       return interface(ifn, self)
+               end
+
                for ifn in utl.imatch(_uci:get("network", self.sid, "ifname")) do
                        ifn = ifn:match("^[^:/]+")
                        return ifn and interface(ifn, self)
                end
-               ifn = nil
-               _uci:foreach("wireless", "wifi-iface",
-                       function(s)
-                               if s.device then
-                                       num[s.device] = num[s.device] and num[s.device] + 1 or 1
 
-                                       local net
-                                       for net in utl.imatch(s.network) do
-                                               if net == self.sid then
-                                                       ifn = "%s.network%d" %{ s.device, num[s.device] }
-                                                       return false
-                                               end
-                                       end
-                               end
-                       end)
+               ifn = _wifi_netid_by_netname(self.sid)
                return ifn and interface(ifn, self)
        end
 end
@@ -1077,18 +1260,17 @@ function protocol.get_interfaces(self)
                        ifaces[#ifaces+1] = nfs[ifn]
                end
 
-               local num = { }
                local wfs = { }
                _uci:foreach("wireless", "wifi-iface",
                        function(s)
                                if s.device then
-                                       num[s.device] = num[s.device] and num[s.device] + 1 or 1
-
                                        local net
                                        for net in utl.imatch(s.network) do
                                                if net == self.sid then
-                                                       ifn = "%s.network%d" %{ s.device, num[s.device] }
-                                                       wfs[ifn] = interface(ifn, self)
+                                                       ifn = _wifi_netid_by_sid(s[".name"])
+                                                       if ifn then
+                                                               wfs[ifn] = interface(ifn, self)
+                                                       end
                                                end
                                        end
                                end
@@ -1119,7 +1301,7 @@ function protocol.contains_interface(self, ifname)
                        end
                end
 
-               local wif = _wifi_lookup(ifname)
+               local wif = _wifi_sid_by_ifname(ifname)
                if wif then
                        local n
                        for n in utl.imatch(_uci:get("wireless", wif, "network")) do
@@ -1134,17 +1316,18 @@ function protocol.contains_interface(self, ifname)
 end
 
 function protocol.adminlink(self)
-       return dsp.build_url("admin", "network", "network", self.sid)
+       local stat, dsp = pcall(require, "luci.dispatcher")
+       return stat and dsp.build_url("admin", "network", "network", self.sid)
 end
 
 
 interface = utl.class()
 
 function interface.__init__(self, ifname, network)
-       local wif = _wifi_lookup(ifname)
+       local wif = _wifi_sid_by_ifname(ifname)
        if wif then
                self.wif    = wifinet(wif)
-               self.ifname = _wifi_state("section", wif, "ifname")
+               self.ifname = self.wif:ifname()
        end
 
        self.ifname  = self.ifname or ifname
@@ -1168,8 +1351,7 @@ function interface.name(self)
 end
 
 function interface.mac(self)
-       local mac = self:_ubus("macaddr")
-       return mac and mac:upper()
+       return ipc.checkmac(self:_ubus("macaddr"))
 end
 
 function interface.ipaddrs(self)
@@ -1181,7 +1363,9 @@ function interface.ip6addrs(self)
 end
 
 function interface.type(self)
-       if self.wif or _wifi_iface(self.ifname) then
+       if self.ifname and self.ifname:byte(1) == 64 then
+               return "alias"
+       elseif self.wif or _wifi_iface(self.ifname) then
                return "wifi"
        elseif _bridge[self.ifname] then
                return "bridge"
@@ -1209,7 +1393,7 @@ function interface.get_i18n(self)
                return "%s: %s %q" %{
                        lng.translate("Wireless Network"),
                        self.wif:active_mode(),
-                       self.wif:active_ssid() or self.wif:active_bssid() or self.wif:id()
+                       self.wif:active_ssid() or self.wif:active_bssid() or self.wif:id() or "?"
                }
        else
                return "%s: %q" %{ self:get_type_i18n(), self:name() }
@@ -1218,7 +1402,9 @@ end
 
 function interface.get_type_i18n(self)
        local x = self:type()
-       if x == "wifi" then
+       if x == "alias" then
+               return lng.translate("Alias Interface")
+       elseif x == "wifi" then
                return lng.translate("Wireless Adapter")
        elseif x == "bridge" then
                return lng.translate("Bridge")
@@ -1271,7 +1457,11 @@ function interface.bridge_stp(self)
 end
 
 function interface.is_up(self)
-       return self:_ubus("up") or false
+       local up = self:_ubus("up")
+       if up == nil then
+               up = (self:type() == "alias")
+       end
+       return up or false
 end
 
 function interface.is_bridge(self)
@@ -1332,9 +1522,14 @@ end
 
 wifidev = utl.class()
 
-function wifidev.__init__(self, dev)
-       self.sid    = dev
-       self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { }
+function wifidev.__init__(self, name)
+       local t, n = _uci:get("wireless", name)
+       if t == "wifi-device" and n ~= nil then
+               self.sid    = n
+               self.iwinfo = _wifi_iwinfo_by_ifname(self.sid, true)
+       end
+       self.sid    = self.sid    or name
+       self.iwinfo = self.iwinfo or { ifname = self.sid }
 end
 
 function wifidev.get(self, opt)
@@ -1359,7 +1554,7 @@ function wifidev.hwmodes(self)
 end
 
 function wifidev.get_i18n(self)
-       local t = "Generic"
+       local t = self.iwinfo.hardware_name or "Generic"
        if self.iwinfo.type == "wl" then
                t = "Broadcom"
        end
@@ -1387,7 +1582,7 @@ function wifidev.get_wifinet(self, net)
        if _uci:get("wireless", net) == "wifi-iface" then
                return wifinet(net)
        else
-               local wnet = _wifi_lookup(net)
+               local wnet = _wifi_sid_by_ifname(net)
                if wnet then
                        return wifinet(wnet)
                end
@@ -1421,7 +1616,7 @@ function wifidev.del_wifinet(self, net)
        if utl.instanceof(net, wifinet) then
                net = net.sid
        elseif _uci:get("wireless", net) ~= "wifi-iface" then
-               net = _wifi_lookup(net)
+               net = _wifi_sid_by_ifname(net)
        end
 
        if net and _uci:get("wireless", net, "device") == self.sid then
@@ -1435,49 +1630,50 @@ end
 
 wifinet = utl.class()
 
-function wifinet.__init__(self, net, data)
-       self.sid = net
-
-       local n = 0
-       local num = { }
-       local netid, sid
-       _uci:foreach("wireless", "wifi-iface",
-               function(s)
-                       n = n + 1
-                       if s.device then
-                               num[s.device] = num[s.device] and num[s.device] + 1 or 1
-                               if s['.name'] == self.sid then
-                                       sid = "@wifi-iface[%d]" % n
-                                       netid = "%s.network%d" %{ s.device, num[s.device] }
-                                       return false
-                               end
-                       end
-               end)
+function wifinet.__init__(self, name, data)
+       local sid, netid, radioname, radiostate, netstate
 
+       -- lookup state by radio#.network# notation
+       sid = _wifi_sid_by_netid(name)
        if sid then
-               local _, k, r, i
-               for k, r in pairs(_ubuswificache) do
-                       if type(r) == "table" and
-                          type(r.interfaces) == "table"
-                       then
-                               for _, i in ipairs(r.interfaces) do
-                                       if type(i) == "table" and i.section == sid then
-                                               self._ubusdata = {
-                                                       radio = k,
-                                                       dev = r,
-                                                       net = i
-                                               }
-                                       end
+               netid = name
+               radioname, radiostate, netstate = _wifi_state_by_sid(sid)
+       else
+               -- lookup state by ifname (e.g. wlan0)
+               radioname, radiostate, netstate = _wifi_state_by_ifname(name)
+               if radioname and radiostate and netstate then
+                       sid = netstate.section
+                       netid = _wifi_netid_by_sid(sid)
+               else
+                       -- lookup state by uci section id (e.g. cfg053579)
+                       radioname, radiostate, netstate = _wifi_state_by_sid(name)
+                       if radioname and radiostate and netstate then
+                               sid = name
+                               netid = _wifi_netid_by_sid(sid)
+                       else
+                               -- no state available, try to resolve from uci
+                               netid, radioname = _wifi_netid_by_sid(name)
+                               if netid and radioname then
+                                       sid = name
                                end
                        end
                end
        end
 
-       local dev = _wifi_state("section", self.sid, "ifname") or netid
+       local iwinfo =
+               (netstate and _wifi_iwinfo_by_ifname(netstate.ifname)) or
+               (radioname and _wifi_iwinfo_by_ifname(radioname)) or
+               { ifname = (netid or sid or name) }
 
-       self.netid  = netid
-       self.wdev   = dev
-       self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { }
+       self.sid       = sid or name
+       self.wdev      = iwinfo.ifname
+       self.iwinfo    = iwinfo
+       self.netid     = netid
+       self._ubusdata = {
+               radio = radioname,
+               dev   = radiostate,
+               net   = netstate
+       }
 end
 
 function wifinet.ubus(self, ...)
@@ -1531,7 +1727,7 @@ end
 function wifinet.ifname(self)
        local ifname = self:ubus("net", "ifname") or self.iwinfo.ifname
        if not ifname or ifname:match("^wifi%d") or ifname:match("^radio%d") then
-               ifname = self.wdev
+               ifname = self.netid
        end
        return ifname
 end
@@ -1664,7 +1860,8 @@ function wifinet.get_i18n(self)
 end
 
 function wifinet.adminlink(self)
-       return dsp.build_url("admin", "network", "wireless", self.netid)
+       local stat, dsp = pcall(require, "luci.dispatcher")
+       return dsp and dsp.build_url("admin", "network", "wireless", self.netid)
 end
 
 function wifinet.get_network(self)