libs/core: fix network is_empty() check for wifi-ifaces which are part of multiple...
[project/luci.git] / libs / core / luasrc / model / network.lua
1 --[[
2 LuCI - Network model
3
4 Copyright 2009-2010 Jo-Philipp Wich <xm@subsignal.org>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10         http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17
18 ]]--
19
20 local type, next, pairs, ipairs, loadfile, table
21         = type, next, pairs, ipairs, loadfile, table
22
23 local tonumber, tostring, math, i18n
24         = tonumber, tostring, math, luci.i18n
25
26 local require = require
27
28 local bus = require "ubus"
29 local nxo = require "nixio"
30 local nfs = require "nixio.fs"
31 local ipc = require "luci.ip"
32 local sys = require "luci.sys"
33 local utl = require "luci.util"
34 local dsp = require "luci.dispatcher"
35 local uci = require "luci.model.uci"
36
37 module "luci.model.network"
38
39
40 IFACE_PATTERNS_VIRTUAL  = { }
41 IFACE_PATTERNS_IGNORE   = { "^wmaster%d", "^wifi%d", "^hwsim%d", "^imq%d", "^ifb%d", "^mon%.wlan%d", "^sit%d", "^lo$" }
42 IFACE_PATTERNS_WIRELESS = { "^wlan%d", "^wl%d", "^ath%d", "^%w+%.network%d" }
43
44
45 protocol = utl.class()
46
47 local _protocols = { }
48
49 local _interfaces, _bridge, _switch, _tunnel
50 local _ubus, _ubusnetcache, _ubusdevcache
51 local _uci_real, _uci_state
52
53 function _filter(c, s, o, r)
54         local val = _uci_real:get(c, s, o)
55         if val then
56                 local l = { }
57                 if type(val) == "string" then
58                         for val in val:gmatch("%S+") do
59                                 if val ~= r then
60                                         l[#l+1] = val
61                                 end
62                         end
63                         if #l > 0 then
64                                 _uci_real:set(c, s, o, table.concat(l, " "))
65                         else
66                                 _uci_real:delete(c, s, o)
67                         end
68                 elseif type(val) == "table" then
69                         for _, val in ipairs(val) do
70                                 if val ~= r then
71                                         l[#l+1] = val
72                                 end
73                         end
74                         if #l > 0 then
75                                 _uci_real:set(c, s, o, l)
76                         else
77                                 _uci_real:delete(c, s, o)
78                         end
79                 end
80         end
81 end
82
83 function _append(c, s, o, a)
84         local val = _uci_real:get(c, s, o) or ""
85         if type(val) == "string" then
86                 local l = { }
87                 for val in val:gmatch("%S+") do
88                         if val ~= a then
89                                 l[#l+1] = val
90                         end
91                 end
92                 l[#l+1] = a
93                 _uci_real:set(c, s, o, table.concat(l, " "))
94         elseif type(val) == "table" then
95                 local l = { }
96                 for _, val in ipairs(val) do
97                         if val ~= a then
98                                 l[#l+1] = val
99                         end
100                 end
101                 l[#l+1] = a
102                 _uci_real:set(c, s, o, l)
103         end
104 end
105
106 function _stror(s1, s2)
107         if not s1 or #s1 == 0 then
108                 return s2 and #s2 > 0 and s2
109         else
110                 return s1
111         end
112 end
113
114 function _get(c, s, o)
115         return _uci_real:get(c, s, o)
116 end
117
118 function _set(c, s, o, v)
119         if v ~= nil then
120                 if type(v) == "boolean" then v = v and "1" or "0" end
121                 return _uci_real:set(c, s, o, v)
122         else
123                 return _uci_real:delete(c, s, o)
124         end
125 end
126
127 function _wifi_iface(x)
128         local _, p
129         for _, p in ipairs(IFACE_PATTERNS_WIRELESS) do
130                 if x:match(p) then
131                         return true
132                 end
133         end
134         return false
135 end
136
137 function _wifi_lookup(ifn)
138         -- got a radio#.network# pseudo iface, locate the corresponding section
139         local radio, ifnidx = ifn:match("^(%w+)%.network(%d+)$")
140         if radio and ifnidx then
141                 local sid = nil
142                 local num = 0
143
144                 ifnidx = tonumber(ifnidx)
145                 _uci_real:foreach("wireless", "wifi-iface",
146                         function(s)
147                                 if s.device == radio then
148                                         num = num + 1
149                                         if num == ifnidx then
150                                                 sid = s['.name']
151                                                 return false
152                                         end
153                                 end
154                         end)
155
156                 return sid
157
158         -- looks like wifi, try to locate the section via state vars
159         elseif _wifi_iface(ifn) then
160                 local sid = nil
161
162                 _uci_state:foreach("wireless", "wifi-iface",
163                         function(s)
164                                 if s.ifname == ifn then
165                                         sid = s['.name']
166                                         return false
167                                 end
168                         end)
169
170                 return sid
171         end
172 end
173
174 function _iface_virtual(x)
175         local _, p
176         for _, p in ipairs(IFACE_PATTERNS_VIRTUAL) do
177                 if x:match(p) then
178                         return true
179                 end
180         end
181         return false
182 end
183
184 function _iface_ignore(x)
185         local _, p
186         for _, p in ipairs(IFACE_PATTERNS_IGNORE) do
187                 if x:match(p) then
188                         return true
189                 end
190         end
191         return _iface_virtual(x)
192 end
193
194
195 function init(cursor)
196         _uci_real  = cursor or _uci_real or uci.cursor()
197         _uci_state = _uci_real:substate()
198
199         _interfaces = { }
200         _bridge     = { }
201         _switch     = { }
202         _tunnel     = { }
203
204         _ubus         = bus.connect()
205         _ubusnetcache = { }
206         _ubusdevcache = { }
207
208         -- read interface information
209         local n, i
210         for n, i in ipairs(nxo.getifaddrs()) do
211                 local name = i.name:match("[^:]+")
212                 local prnt = name:match("^([^%.]+)%.")
213
214                 if _iface_virtual(name) then
215                         _tunnel[name] = true
216                 end
217
218                 if _tunnel[name] or not _iface_ignore(name) then
219                         _interfaces[name] = _interfaces[name] or {
220                                 idx      = i.ifindex or n,
221                                 name     = name,
222                                 rawname  = i.name,
223                                 flags    = { },
224                                 ipaddrs  = { },
225                                 ip6addrs = { }
226                         }
227
228                         if prnt then
229                                 _switch[name] = true
230                                 _switch[prnt] = true
231                         end
232
233                         if i.family == "packet" then
234                                 _interfaces[name].flags   = i.flags
235                                 _interfaces[name].stats   = i.data
236                                 _interfaces[name].macaddr = i.addr
237                         elseif i.family == "inet" then
238                                 _interfaces[name].ipaddrs[#_interfaces[name].ipaddrs+1] = ipc.IPv4(i.addr, i.netmask)
239                         elseif i.family == "inet6" then
240                                 _interfaces[name].ip6addrs[#_interfaces[name].ip6addrs+1] = ipc.IPv6(i.addr, i.netmask)
241                         end
242                 end
243         end
244
245         -- read bridge informaton
246         local b, l
247         for l in utl.execi("brctl show") do
248                 if not l:match("STP") then
249                         local r = utl.split(l, "%s+", nil, true)
250                         if #r == 4 then
251                                 b = {
252                                         name    = r[1],
253                                         id      = r[2],
254                                         stp     = r[3] == "yes",
255                                         ifnames = { _interfaces[r[4]] }
256                                 }
257                                 if b.ifnames[1] then
258                                         b.ifnames[1].bridge = b
259                                 end
260                                 _bridge[r[1]] = b
261                         elseif b then
262                                 b.ifnames[#b.ifnames+1] = _interfaces[r[2]]
263                                 b.ifnames[#b.ifnames].bridge = b
264                         end
265                 end
266         end
267
268         return _M
269 end
270
271 function save(self, ...)
272         _uci_real:save(...)
273         _uci_real:load(...)
274 end
275
276 function commit(self, ...)
277         _uci_real:commit(...)
278         _uci_real:load(...)
279 end
280
281 function ifnameof(self, x)
282         if utl.instanceof(x, interface) then
283                 return x:name()
284         elseif utl.instanceof(x, protocol) then
285                 return x:ifname()
286         elseif type(x) == "string" then
287                 return x:match("^[^:]+")
288         end
289 end
290
291 function get_protocol(self, protoname, netname)
292         local v = _protocols[protoname]
293         if v then
294                 return v(netname or "__dummy__")
295         end
296 end
297
298 function get_protocols(self)
299         local p = { }
300         local _, v
301         for _, v in ipairs(_protocols) do
302                 p[#p+1] = v("__dummy__")
303         end
304         return p
305 end
306
307 function register_protocol(self, protoname)
308         local proto = utl.class(protocol)
309
310         function proto.__init__(self, name)
311                 self.sid = name
312         end
313
314         function proto.proto(self)
315                 return protoname
316         end
317
318         _protocols[#_protocols+1] = proto
319         _protocols[protoname]     = proto
320
321         return proto
322 end
323
324 function register_pattern_virtual(self, pat)
325         IFACE_PATTERNS_VIRTUAL[#IFACE_PATTERNS_VIRTUAL+1] = pat
326 end
327
328
329 function has_ipv6(self)
330         return nfs.access("/proc/net/ipv6_route")
331 end
332
333 function add_network(self, n, options)
334         local oldnet = self:get_network(n)
335         if n and #n > 0 and n:match("^[a-zA-Z0-9_]+$") and not oldnet then
336                 if _uci_real:section("network", "interface", n, options) then
337                         return network(n)
338                 end
339         elseif oldnet and oldnet:is_empty() then
340                 if options then
341                         local k, v
342                         for k, v in pairs(options) do
343                                 oldnet:set(k, v)
344                         end
345                 end
346                 return oldnet
347         end
348 end
349
350 function get_network(self, n)
351         if n and _uci_real:get("network", n) == "interface" then
352                 return network(n)
353         end
354 end
355
356 function get_networks(self)
357         local nets = { }
358         local nls = { }
359
360         _uci_real:foreach("network", "interface",
361                 function(s)
362                         nls[s['.name']] = network(s['.name'])
363                 end)
364
365         local n
366         for n in utl.kspairs(nls) do
367                 nets[#nets+1] = nls[n]
368         end
369
370         return nets
371 end
372
373 function del_network(self, n)
374         local r = _uci_real:delete("network", n)
375         if r then
376                 _uci_real:delete_all("network", "alias",
377                         function(s) return (s.interface == n) end)
378
379                 _uci_real:delete_all("network", "route",
380                         function(s) return (s.interface == n) end)
381
382                 _uci_real:delete_all("network", "route6",
383                         function(s) return (s.interface == n) end)
384
385                 _uci_real:foreach("wireless", "wifi-iface",
386                         function(s)
387                                 if s.network == n then
388                                         _uci_real:delete("wireless", s['.name'], "network")
389                                 end
390                         end)
391         end
392         return r
393 end
394
395 function rename_network(self, old, new)
396         local r
397         if new and #new > 0 and new:match("^[a-zA-Z0-9_]+$") and not self:get_network(new) then
398                 r = _uci_real:section("network", "interface", new, _uci_real:get_all("network", old))
399
400                 if r then
401                         _uci_real:foreach("network", "alias",
402                                 function(s)
403                                         if s.interface == old then
404                                                 _uci_real:set("network", s['.name'], "interface", new)
405                                         end
406                                 end)
407
408                         _uci_real:foreach("network", "route",
409                                 function(s)
410                                         if s.interface == old then
411                                                 _uci_real:set("network", s['.name'], "interface", new)
412                                         end
413                                 end)
414
415                         _uci_real:foreach("network", "route6",
416                                 function(s)
417                                         if s.interface == old then
418                                                 _uci_real:set("network", s['.name'], "interface", new)
419                                         end
420                                 end)
421
422                         _uci_real:foreach("wireless", "wifi-iface",
423                                 function(s)
424                                         if s.network == old then
425                                                 _uci_real:set("wireless", s['.name'], "network", new)
426                                         end
427                                 end)
428
429                         _uci_real:delete("network", old)
430                 end
431         end
432         return r or false
433 end
434
435 function get_interface(self, i)
436         if _interfaces[i] or _wifi_iface(i) then
437                 return interface(i)
438         else
439                 local ifc
440                 local num = { }
441                 _uci_real:foreach("wireless", "wifi-iface",
442                         function(s)
443                                 if s.device then
444                                         num[s.device] = num[s.device] and num[s.device] + 1 or 1
445                                         if s['.name'] == i then
446                                                 ifc = interface(
447                                                         "%s.network%d" %{s.device, num[s.device] })
448                                                 return false
449                                         end
450                                 end
451                         end)
452                 return ifc
453         end
454 end
455
456 function get_interfaces(self)
457         local iface
458         local ifaces = { }
459         local seen = { }
460         local nfs = { }
461         local baseof = { }
462
463         -- find normal interfaces
464         _uci_real:foreach("network", "interface",
465                 function(s)
466                         for iface in utl.imatch(s.ifname) do
467                                 if not _iface_ignore(iface) and not _wifi_iface(iface) then
468                                         seen[iface] = true
469                                         nfs[iface] = interface(iface)
470                                 end
471                         end
472                 end)
473
474         for iface in utl.kspairs(_interfaces) do
475                 if not (seen[iface] or _iface_ignore(iface) or _wifi_iface(iface)) then
476                         nfs[iface] = interface(iface)
477                 end
478         end
479
480         -- find vlan interfaces
481         _uci_real:foreach("network", "switch_vlan",
482                 function(s)
483                         if not s.device then
484                                 return
485                         end
486
487                         local base = baseof[s.device]
488                         if not base then
489                                 if not s.device:match("^eth%d") then
490                                         local l
491                                         for l in utl.execi("swconfig dev %q help 2>/dev/null" % s.device) do
492                                                 if not base then
493                                                         base = l:match("^%w+: (%w+)")
494                                                 end
495                                         end
496                                         if not base or not base:match("^eth%d") then
497                                                 base = "eth0"
498                                         end
499                                 else
500                                         base = s.device
501                                 end
502                                 baseof[s.device] = base
503                         end
504
505                         local vid = tonumber(s.vid or s.vlan)
506                         if vid ~= nil and vid >= 0 and vid <= 4095 then
507                                 local iface = "%s.%d" %{ base, vid }
508                                 if not seen[iface] then
509                                         seen[iface] = true
510                                         nfs[iface] = interface(iface)
511                                 end
512                         end
513                 end)
514
515         for iface in utl.kspairs(nfs) do
516                 ifaces[#ifaces+1] = nfs[iface]
517         end
518
519         -- find wifi interfaces
520         local num = { }
521         local wfs = { }
522         _uci_real:foreach("wireless", "wifi-iface",
523                 function(s)
524                         if s.device then
525                                 num[s.device] = num[s.device] and num[s.device] + 1 or 1
526                                 local i = "%s.network%d" %{ s.device, num[s.device] }
527                                 wfs[i] = interface(i)
528                         end
529                 end)
530
531         for iface in utl.kspairs(wfs) do
532                 ifaces[#ifaces+1] = wfs[iface]
533         end
534
535         return ifaces
536 end
537
538 function ignore_interface(self, x)
539         return _iface_ignore(x)
540 end
541
542 function get_wifidev(self, dev)
543         if _uci_real:get("wireless", dev) == "wifi-device" then
544                 return wifidev(dev)
545         end
546 end
547
548 function get_wifidevs(self)
549         local devs = { }
550         local wfd  = { }
551
552         _uci_real:foreach("wireless", "wifi-device",
553                 function(s) wfd[#wfd+1] = s['.name'] end)
554
555         local dev
556         for _, dev in utl.vspairs(wfd) do
557                 devs[#devs+1] = wifidev(dev)
558         end
559
560         return devs
561 end
562
563 function get_wifinet(self, net)
564         local wnet = _wifi_lookup(net)
565         if wnet then
566                 return wifinet(wnet)
567         end
568 end
569
570 function add_wifinet(self, net, options)
571         if type(options) == "table" and options.device and
572                 _uci_real:get("wireless", options.device) == "wifi-device"
573         then
574                 local wnet = _uci_real:section("wireless", "wifi-iface", nil, options)
575                 return wifinet(wnet)
576         end
577 end
578
579 function del_wifinet(self, net)
580         local wnet = _wifi_lookup(net)
581         if wnet then
582                 _uci_real:delete("wireless", wnet)
583                 return true
584         end
585         return false
586 end
587
588
589 function network(name, proto)
590         if name then
591                 local p = proto or _uci_real:get("network", name, "proto")
592                 local c = p and _protocols[p] or protocol
593                 return c(name)
594         end
595 end
596
597 function protocol.__init__(self, name)
598         self.sid = name
599 end
600
601 function protocol._get(self, opt)
602         local v = _uci_real:get("network", self.sid, opt)
603         if type(v) == "table" then
604                 return table.concat(v, " ")
605         end
606         return v or ""
607 end
608
609 function protocol._ubus(self, field)
610         if not _ubusnetcache[self.sid] then
611                 _ubusnetcache[self.sid] = _ubus:call("network.interface.%s" % self.sid,
612                                                      "status", { })
613         end
614         if _ubusnetcache[self.sid] and field then
615                 return _ubusnetcache[self.sid][field]
616         end
617         return _ubusnetcache[self.sid]
618 end
619
620 function protocol.get(self, opt)
621         return _get("network", self.sid, opt)
622 end
623
624 function protocol.set(self, opt, val)
625         return _set("network", self.sid, opt, val)
626 end
627
628 function protocol.ifname(self)
629         local ifname
630         if self:is_floating() then
631                 ifname = self:_ubus("l3_device")
632         else
633                 ifname = self:_ubus("device")
634         end
635         if not ifname then
636                 local num = { }
637                 _uci_real:foreach("wireless", "wifi-iface",
638                         function(s)
639                                 if s.device then
640                                         num[s.device] = num[s.device]
641                                                 and num[s.device] + 1 or 1
642
643                                         if s.network == self.sid then
644                                                 ifname = "%s.network%d" %{ s.device, num[s.device] }
645                                                 return false
646                                         end
647                                 end
648                         end)
649         end
650         return ifname
651 end
652
653 function protocol.proto(self)
654         return "none"
655 end
656
657 function protocol.get_i18n(self)
658         local p = self:proto()
659         if p == "none" then
660                 return i18n.translate("Unmanaged")
661         elseif p == "static" then
662                 return i18n.translate("Static address")
663         elseif p == "dhcp" then
664                 return i18n.translate("DHCP client")
665         else
666                 return i18n.translate("Unknown")
667         end
668 end
669
670 function protocol.type(self)
671         return self:_get("type")
672 end
673
674 function protocol.name(self)
675         return self.sid
676 end
677
678 function protocol.uptime(self)
679         return self:_ubus("uptime") or 0
680 end
681
682 function protocol.expires(self)
683         local a = tonumber(_uci_state:get("network", self.sid, "lease_acquired"))
684         local l = tonumber(_uci_state:get("network", self.sid, "lease_lifetime"))
685         if a and l then
686                 l = l - (nxo.sysinfo().uptime - a)
687                 return l > 0 and l or 0
688         end
689         return -1
690 end
691
692 function protocol.metric(self)
693         return tonumber(_uci_state:get("network", self.sid, "metric")) or 0
694 end
695
696 function protocol.ipaddr(self)
697         local addrs = self:_ubus("ipv4-address")
698         return addrs and #addrs > 0 and addrs[1].address
699 end
700
701 function protocol.netmask(self)
702         local addrs = self:_ubus("ipv4-address")
703         return addrs and #addrs > 0 and
704                 ipc.IPv4("0.0.0.0/%d" % addrs[1].mask):mask():string()
705 end
706
707 function protocol.gwaddr(self)
708         local _, route
709         for _, route in ipairs(self:_ubus("route") or { }) do
710                 if route.target == "0.0.0.0" and route.mask == 0 then
711                         return route.nexthop
712                 end
713         end
714 end
715
716 function protocol.dnsaddrs(self)
717         local dns = { }
718         local _, addr
719         for _, addr in ipairs(self:_ubus("dns-server") or { }) do
720                 if not addr:match(":") then
721                         dns[#dns+1] = addr
722                 end
723         end
724         return dns
725 end
726
727 function protocol.ip6addr(self)
728         local addrs = self:_ubus("ipv6-address")
729         return addrs and #addrs > 0
730                 and "%s/%d" %{ addrs[1].address, addrs[1].mask }
731 end
732
733 function protocol.gw6addr(self)
734         local _, route
735         for _, route in ipairs(self:_ubus("route") or { }) do
736                 if route.target == "::" and route.mask == 0 then
737                         return ipc.IPv6(route.nexthop):string()
738                 end
739         end
740 end
741
742 function protocol.dns6addrs(self)
743         local dns = { }
744         local _, addr
745         for _, addr in ipairs(self:_ubus("dns-server") or { }) do
746                 if addr:match(":") then
747                         dns[#dns+1] = addr
748                 end
749         end
750         return dns
751 end
752
753 function protocol.is_bridge(self)
754         return (not self:is_virtual() and self:type() == "bridge")
755 end
756
757 function protocol.opkg_package(self)
758         return nil
759 end
760
761 function protocol.is_installed(self)
762         return true
763 end
764
765 function protocol.is_virtual(self)
766         return false
767 end
768
769 function protocol.is_floating(self)
770         return false
771 end
772
773 function protocol.is_empty(self)
774         if self:is_floating() then
775                 return false
776         else
777                 local rv = true
778
779                 if (self:_get("ifname") or ""):match("%S+") then
780                         rv = false
781                 end
782
783                 _uci_real:foreach("wireless", "wifi-iface",
784                         function(s)
785                                 local n
786                                 for n in utl.imatch(s.network) do
787                                         if n == self.sid then
788                                                 rv = false
789                                                 return false
790                                         end
791                                 end
792                         end)
793
794                 return rv
795         end
796 end
797
798 function protocol.add_interface(self, ifname)
799         ifname = _M:ifnameof(ifname)
800         if ifname and not self:is_floating() then
801                 -- if its a wifi interface, change its network option
802                 local wif = _wifi_lookup(ifname)
803                 if wif then
804                         _append("wireless", wif, "network", self.sid)
805
806                 -- add iface to our iface list
807                 else
808                         _append("network", self.sid, "ifname", ifname)
809                 end
810         end
811 end
812
813 function protocol.del_interface(self, ifname)
814         ifname = _M:ifnameof(ifname)
815         if ifname and not self:is_floating() then
816                 -- if its a wireless interface, clear its network option
817                 local wif = _wifi_lookup(ifname)
818                 if wif then _filter("wireless", wif, "network", self.sid) end
819
820                 -- remove the interface
821                 _filter("network", self.sid, "ifname", ifname)
822         end
823 end
824
825 function protocol.get_interface(self)
826         if self:is_virtual() then
827                 _tunnel[self:proto() .. "-" .. self.sid] = true
828                 return interface(self:proto() .. "-" .. self.sid, self)
829         elseif self:is_bridge() then
830                 _bridge["br-" .. self.sid] = true
831                 return interface("br-" .. self.sid, self)
832         else
833                 local ifn = nil
834                 local num = { }
835                 for ifn in utl.imatch(_uci_real:get("network", self.sid, "ifname")) do
836                         ifn = ifn:match("^[^:/]+")
837                         return ifn and interface(ifn, self)
838                 end
839                 ifn = nil
840                 _uci_real:foreach("wireless", "wifi-iface",
841                         function(s)
842                                 if s.device then
843                                         num[s.device] = num[s.device] and num[s.device] + 1 or 1
844                                         if s.network == self.sid then
845                                                 ifn = "%s.network%d" %{ s.device, num[s.device] }
846                                                 return false
847                                         end
848                                 end
849                         end)
850                 return ifn and interface(ifn, self)
851         end
852 end
853
854 function protocol.get_interfaces(self)
855         if self:is_bridge() or (self:is_virtual() and not self:is_floating()) then
856                 local ifaces = { }
857
858                 local ifn
859                 local nfs = { }
860                 for ifn in utl.imatch(self:get("ifname")) do
861                         ifn = ifn:match("^[^:/]+")
862                         nfs[ifn] = interface(ifn, self)
863                 end
864
865                 for ifn in utl.kspairs(nfs) do
866                         ifaces[#ifaces+1] = nfs[ifn]
867                 end
868
869                 local num = { }
870                 local wfs = { }
871                 _uci_real:foreach("wireless", "wifi-iface",
872                         function(s)
873                                 if s.device then
874                                         num[s.device] = num[s.device] and num[s.device] + 1 or 1
875                                         if s.network == self.sid then
876                                                 ifn = "%s.network%d" %{ s.device, num[s.device] }
877                                                 wfs[ifn] = interface(ifn, self)
878                                         end
879                                 end
880                         end)
881
882                 for ifn in utl.kspairs(wfs) do
883                         ifaces[#ifaces+1] = wfs[ifn]
884                 end
885
886                 return ifaces
887         end
888 end
889
890 function protocol.contains_interface(self, ifname)
891         ifname = _M:ifnameof(ifname)
892         if not ifname then
893                 return false
894         elseif self:is_virtual() and self:proto() .. "-" .. self.sid == ifname then
895                 return true
896         elseif self:is_bridge() and "br-" .. self.sid == ifname then
897                 return true
898         else
899                 local ifn
900                 for ifn in utl.imatch(self:get("ifname")) do
901                         ifn = ifn:match("[^:]+")
902                         if ifn == ifname then
903                                 return true
904                         end
905                 end
906
907                 local wif = _wifi_lookup(ifname)
908                 if wif then
909                         local n
910                         for n in utl.imatch(_uci_real:get("wireless", wif, "network")) do
911                                 if n == self.sid then
912                                         return true
913                                 end
914                         end
915                 end
916         end
917
918         return false
919 end
920
921 function protocol.adminlink(self)
922         return dsp.build_url("admin", "network", "network", self.sid)
923 end
924
925
926 interface = utl.class()
927
928 function interface.__init__(self, ifname, network)
929         local wif = _wifi_lookup(ifname)
930         if wif then
931                 self.wif    = wifinet(wif)
932                 self.ifname = _uci_state:get("wireless", wif, "ifname")
933         end
934
935         self.ifname  = self.ifname or ifname
936         self.dev     = _interfaces[self.ifname]
937         self.network = network
938 end
939
940 function interface._ubus(self, field)
941         if not _ubusdevcache[self.ifname] then
942                 _ubusdevcache[self.ifname] = _ubus:call("network.device", "status",
943                                                         { name = self.ifname })
944         end
945         if _ubusdevcache[self.ifname] and field then
946                 return _ubusdevcache[self.ifname][field]
947         end
948         return _ubusdevcache[self.ifname]
949 end
950
951 function interface.name(self)
952         return self.wif and self.wif:ifname() or self.ifname
953 end
954
955 function interface.mac(self)
956         return (self:_ubus("macaddr") or "00:00:00:00:00:00"):upper()
957 end
958
959 function interface.ipaddrs(self)
960         return self.dev and self.dev.ipaddrs or { }
961 end
962
963 function interface.ip6addrs(self)
964         return self.dev and self.dev.ip6addrs or { }
965 end
966
967 function interface.type(self)
968         if self.wif or _wifi_iface(self.ifname) then
969                 return "wifi"
970         elseif _bridge[self.ifname] then
971                 return "bridge"
972         elseif _tunnel[self.ifname] then
973                 return "tunnel"
974         elseif self.ifname:match("%.") then
975                 return "vlan"
976         elseif _switch[self.ifname] then
977                 return "switch"
978         else
979                 return "ethernet"
980         end
981 end
982
983 function interface.shortname(self)
984         if self.wif then
985                 return "%s %q" %{
986                         self.wif:active_mode(),
987                         self.wif:active_ssid() or self.wif:active_bssid()
988                 }
989         else
990                 return self.ifname
991         end
992 end
993
994 function interface.get_i18n(self)
995         if self.wif then
996                 return "%s: %s %q" %{
997                         i18n.translate("Wireless Network"),
998                         self.wif:active_mode(),
999                         self.wif:active_ssid() or self.wif:active_bssid()
1000                 }
1001         else
1002                 return "%s: %q" %{ self:get_type_i18n(), self:name() }
1003         end
1004 end
1005
1006 function interface.get_type_i18n(self)
1007         local x = self:type()
1008         if x == "wifi" then
1009                 return i18n.translate("Wireless Adapter")
1010         elseif x == "bridge" then
1011                 return i18n.translate("Bridge")
1012         elseif x == "switch" then
1013                 return i18n.translate("Ethernet Switch")
1014         elseif x == "vlan" then
1015                 return i18n.translate("VLAN Interface")
1016         elseif x == "tunnel" then
1017                 return i18n.translate("Tunnel Interface")
1018         else
1019                 return i18n.translate("Ethernet Adapter")
1020         end
1021 end
1022
1023 function interface.adminlink(self)
1024         if self.wif then
1025                 return self.wif:adminlink()
1026         end
1027 end
1028
1029 function interface.ports(self)
1030         local members = self:_ubus("bridge-members")
1031         if members then
1032                 local _, iface
1033                 local ifaces = { }
1034                 for _, iface in ipairs(members) do
1035                         ifaces[#ifaces+1] = interface(iface)
1036                 end
1037         end
1038 end
1039
1040 function interface.bridge_id(self)
1041         if self.br then
1042                 return self.br.id
1043         else
1044                 return nil
1045         end
1046 end
1047
1048 function interface.bridge_stp(self)
1049         if self.br then
1050                 return self.br.stp
1051         else
1052                 return false
1053         end
1054 end
1055
1056 function interface.is_up(self)
1057         if self.wif then
1058                 return self.wif:is_up()
1059         else
1060                 return self:_ubus("up") or false
1061         end
1062 end
1063
1064 function interface.is_bridge(self)
1065         return (self:type() == "bridge")
1066 end
1067
1068 function interface.is_bridgeport(self)
1069         return self.dev and self.dev.bridge and true or false
1070 end
1071
1072 local function uint(x)
1073         if x then
1074                 return (x < 0) and ((2^32) + x) or x
1075         end
1076         return 0
1077 end
1078
1079 function interface.tx_bytes(self)
1080         local stat = self:_ubus("statistics")
1081         return stat and uint(stat.tx_bytes) or 0
1082 end
1083
1084 function interface.rx_bytes(self)
1085         local stat = self:_ubus("statistics")
1086         return stat and uint(stat.rx_bytes) or 0
1087 end
1088
1089 function interface.tx_packets(self)
1090         local stat = self:_ubus("statistics")
1091         return stat and uint(stat.tx_packets) or 0
1092 end
1093
1094 function interface.rx_packets(self)
1095         local stat = self:_ubus("statistics")
1096         return stat and uint(stat.rx_packets) or 0
1097 end
1098
1099 function interface.get_network(self)
1100         return self:get_networks()[1]
1101 end
1102
1103 function interface.get_networks(self)
1104         if not self.networks then
1105                 local nets = { }
1106                 local _, net
1107                 for _, net in ipairs(_M:get_networks()) do
1108                         if net:contains_interface(self.ifname) or
1109                            net:ifname() == self.ifname
1110                         then
1111                                 nets[#nets+1] = net
1112                         end
1113                 end
1114                 table.sort(nets, function(a, b) return a.sid < b.sid end)
1115                 self.networks = nets
1116                 return nets
1117         else
1118                 return self.networks
1119         end
1120 end
1121
1122 function interface.get_wifinet(self)
1123         return self.wif
1124 end
1125
1126
1127 wifidev = utl.class()
1128
1129 function wifidev.__init__(self, dev)
1130         self.sid    = dev
1131         self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { }
1132 end
1133
1134 function wifidev.get(self, opt)
1135         return _get("wireless", self.sid, opt)
1136 end
1137
1138 function wifidev.set(self, opt, val)
1139         return _set("wireless", self.sid, opt, val)
1140 end
1141
1142 function wifidev.name(self)
1143         return self.sid
1144 end
1145
1146 function wifidev.hwmodes(self)
1147         local l = self.iwinfo.hwmodelist
1148         if l and next(l) then
1149                 return l
1150         else
1151                 return { b = true, g = true }
1152         end
1153 end
1154
1155 function wifidev.get_i18n(self)
1156         local t = "Generic"
1157         if self.iwinfo.type == "wl" then
1158                 t = "Broadcom"
1159         elseif self.iwinfo.type == "madwifi" then
1160                 t = "Atheros"
1161         end
1162
1163         local m = ""
1164         local l = self:hwmodes()
1165         if l.a then m = m .. "a" end
1166         if l.b then m = m .. "b" end
1167         if l.g then m = m .. "g" end
1168         if l.n then m = m .. "n" end
1169
1170         return "%s 802.11%s Wireless Controller (%s)" %{ t, m, self:name() }
1171 end
1172
1173 function wifidev.is_up(self)
1174         local up = false
1175
1176         _uci_state:foreach("wireless", "wifi-iface",
1177                 function(s)
1178                         if s.device == self.sid then
1179                                 if s.up == "1" then
1180                                         up = true
1181                                         return false
1182                                 end
1183                         end
1184                 end)
1185
1186         return up
1187 end
1188
1189 function wifidev.get_wifinet(self, net)
1190         if _uci_real:get("wireless", net) == "wifi-iface" then
1191                 return wifinet(net)
1192         else
1193                 local wnet = _wifi_lookup(net)
1194                 if wnet then
1195                         return wifinet(wnet)
1196                 end
1197         end
1198 end
1199
1200 function wifidev.get_wifinets(self)
1201         local nets = { }
1202
1203         _uci_real:foreach("wireless", "wifi-iface",
1204                 function(s)
1205                         if s.device == self.sid then
1206                                 nets[#nets+1] = wifinet(s['.name'])
1207                         end
1208                 end)
1209
1210         return nets
1211 end
1212
1213 function wifidev.add_wifinet(self, options)
1214         options = options or { }
1215         options.device = self.sid
1216
1217         local wnet = _uci_real:section("wireless", "wifi-iface", nil, options)
1218         if wnet then
1219                 return wifinet(wnet, options)
1220         end
1221 end
1222
1223 function wifidev.del_wifinet(self, net)
1224         if utl.instanceof(net, wifinet) then
1225                 net = net.sid
1226         elseif _uci_real:get("wireless", net) ~= "wifi-iface" then
1227                 net = _wifi_lookup(net)
1228         end
1229
1230         if net and _uci_real:get("wireless", net, "device") == self.sid then
1231                 _uci_real:delete("wireless", net)
1232                 return true
1233         end
1234
1235         return false
1236 end
1237
1238
1239 wifinet = utl.class()
1240
1241 function wifinet.__init__(self, net, data)
1242         self.sid = net
1243
1244         local num = { }
1245         local netid
1246         _uci_real:foreach("wireless", "wifi-iface",
1247                 function(s)
1248                         if s.device then
1249                                 num[s.device] = num[s.device] and num[s.device] + 1 or 1
1250                                 if s['.name'] == self.sid then
1251                                         netid = "%s.network%d" %{ s.device, num[s.device] }
1252                                         return false
1253                                 end
1254                         end
1255                 end)
1256
1257         local dev = _uci_state:get("wireless", self.sid, "ifname") or netid
1258
1259         self.netid  = netid
1260         self.wdev   = dev
1261         self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { }
1262         self.iwdata = data or _uci_state:get_all("wireless", self.sid) or
1263                 _uci_real:get_all("wireless", self.sid) or { }
1264 end
1265
1266 function wifinet.get(self, opt)
1267         return _get("wireless", self.sid, opt)
1268 end
1269
1270 function wifinet.set(self, opt, val)
1271         return _set("wireless", self.sid, opt, val)
1272 end
1273
1274 function wifinet.mode(self)
1275         return _uci_state:get("wireless", self.sid, "mode") or "ap"
1276 end
1277
1278 function wifinet.ssid(self)
1279         return _uci_state:get("wireless", self.sid, "ssid")
1280 end
1281
1282 function wifinet.bssid(self)
1283         return _uci_state:get("wireless", self.sid, "bssid")
1284 end
1285
1286 function wifinet.network(self)
1287         return _uci_state:get("wifinet", self.sid, "network")
1288 end
1289
1290 function wifinet.id(self)
1291         return self.netid
1292 end
1293
1294 function wifinet.name(self)
1295         return self.sid
1296 end
1297
1298 function wifinet.ifname(self)
1299         local ifname = self.iwinfo.ifname
1300         if not ifname or ifname:match("^wifi%d") or ifname:match("^radio%d") then
1301                 ifname = self.wdev
1302         end
1303         return ifname
1304 end
1305
1306 function wifinet.get_device(self)
1307         if self.iwdata.device then
1308                 return wifidev(self.iwdata.device)
1309         end
1310 end
1311
1312 function wifinet.is_up(self)
1313         return (self.iwdata.up == "1")
1314 end
1315
1316 function wifinet.active_mode(self)
1317         local m = _stror(self.iwinfo.mode, self.iwdata.mode) or "ap"
1318
1319         if     m == "ap"      then m = "Master"
1320         elseif m == "sta"     then m = "Client"
1321         elseif m == "adhoc"   then m = "Ad-Hoc"
1322         elseif m == "mesh"    then m = "Mesh"
1323         elseif m == "monitor" then m = "Monitor"
1324         end
1325
1326         return m
1327 end
1328
1329 function wifinet.active_mode_i18n(self)
1330         return i18n.translate(self:active_mode())
1331 end
1332
1333 function wifinet.active_ssid(self)
1334         return _stror(self.iwinfo.ssid, self.iwdata.ssid)
1335 end
1336
1337 function wifinet.active_bssid(self)
1338         return _stror(self.iwinfo.bssid, self.iwdata.bssid) or "00:00:00:00:00:00"
1339 end
1340
1341 function wifinet.active_encryption(self)
1342         local enc = self.iwinfo and self.iwinfo.encryption
1343         return enc and enc.description or "-"
1344 end
1345
1346 function wifinet.assoclist(self)
1347         return self.iwinfo.assoclist or { }
1348 end
1349
1350 function wifinet.frequency(self)
1351         local freq = self.iwinfo.frequency
1352         if freq and freq > 0 then
1353                 return "%.03f" % (freq / 1000)
1354         end
1355 end
1356
1357 function wifinet.bitrate(self)
1358         local rate = self.iwinfo.bitrate
1359         if rate and rate > 0 then
1360                 return (rate / 1000)
1361         end
1362 end
1363
1364 function wifinet.channel(self)
1365         return self.iwinfo.channel or
1366                 tonumber(_uci_state:get("wireless", self.iwdata.device, "channel"))
1367 end
1368
1369 function wifinet.signal(self)
1370         return self.iwinfo.signal or 0
1371 end
1372
1373 function wifinet.noise(self)
1374         return self.iwinfo.noise or 0
1375 end
1376
1377 function wifinet.country(self)
1378         return self.iwinfo.country or "00"
1379 end
1380
1381 function wifinet.txpower(self)
1382         local pwr = (self.iwinfo.txpower or 0)
1383         return pwr + self:txpower_offset()
1384 end
1385
1386 function wifinet.txpower_offset(self)
1387         return self.iwinfo.txpower_offset or 0
1388 end
1389
1390 function wifinet.signal_level(self, s, n)
1391         if self:active_bssid() ~= "00:00:00:00:00:00" then
1392                 local signal = s or self:signal()
1393                 local noise  = n or self:noise()
1394
1395                 if signal < 0 and noise < 0 then
1396                         local snr = -1 * (noise - signal)
1397                         return math.floor(snr / 5)
1398                 else
1399                         return 0
1400                 end
1401         else
1402                 return -1
1403         end
1404 end
1405
1406 function wifinet.signal_percent(self)
1407         local qc = self.iwinfo.quality or 0
1408         local qm = self.iwinfo.quality_max or 0
1409
1410         if qc > 0 and qm > 0 then
1411                 return math.floor((100 / qm) * qc)
1412         else
1413                 return 0
1414         end
1415 end
1416
1417 function wifinet.shortname(self)
1418         return "%s %q" %{
1419                 i18n.translate(self:active_mode()),
1420                 self:active_ssid() or self:active_bssid()
1421         }
1422 end
1423
1424 function wifinet.get_i18n(self)
1425         return "%s: %s %q (%s)" %{
1426                 i18n.translate("Wireless Network"),
1427                 i18n.translate(self:active_mode()),
1428                 self:active_ssid() or self:active_bssid(),
1429                 self:ifname()
1430         }
1431 end
1432
1433 function wifinet.adminlink(self)
1434         return dsp.build_url("admin", "network", "wireless", self.netid)
1435 end
1436
1437 function wifinet.get_network(self)
1438         return self:get_networks()[1]
1439 end
1440
1441 function wifinet.get_networks(self)
1442         local nets = { }
1443         local net
1444         for net in utl.imatch(tostring(self.iwdata.network)) do
1445                 if _uci_real:get("network", net) == "interface" then
1446                         nets[#nets+1] = network(net)
1447                 end
1448         end
1449         table.sort(nets, function(a, b) return a.sid < b.sid end)
1450         return nets
1451 end
1452
1453 function wifinet.get_interface(self)
1454         return interface(self:ifname())
1455 end
1456
1457
1458 -- setup base protocols
1459 _M:register_protocol("static")
1460 _M:register_protocol("dhcp")
1461 _M:register_protocol("none")
1462
1463 -- load protocol extensions
1464 local exts = nfs.dir(utl.libpath() .. "/model/network")
1465 if exts then
1466         local ext
1467         for ext in exts do
1468                 if ext:match("%.lua$") then
1469                         require("luci.model.network." .. ext:gsub("%.lua$", ""))
1470                 end
1471         end
1472 end