luci-base: network.js: don't fail loading network config on missing wireless
[oweals/luci.git] / modules / luci-base / htdocs / luci-static / resources / network.js
1 'use strict';
2 'require uci';
3 'require rpc';
4 'require validation';
5 'require baseclass';
6 'require firewall';
7
8 var proto_errors = {
9         CONNECT_FAILED:                 _('Connection attempt failed'),
10         INVALID_ADDRESS:                _('IP address is invalid'),
11         INVALID_GATEWAY:                _('Gateway address is invalid'),
12         INVALID_LOCAL_ADDRESS:  _('Local IP address is invalid'),
13         MISSING_ADDRESS:                _('IP address is missing'),
14         MISSING_PEER_ADDRESS:   _('Peer address is missing'),
15         NO_DEVICE:                              _('Network device is not present'),
16         NO_IFACE:                               _('Unable to determine device name'),
17         NO_IFNAME:                              _('Unable to determine device name'),
18         NO_WAN_ADDRESS:                 _('Unable to determine external IP address'),
19         NO_WAN_LINK:                    _('Unable to determine upstream interface'),
20         PEER_RESOLVE_FAIL:              _('Unable to resolve peer host name'),
21         PIN_FAILED:                             _('PIN code rejected')
22 };
23
24 var iface_patterns_ignore = [
25         /^wmaster\d+/,
26         /^wifi\d+/,
27         /^hwsim\d+/,
28         /^imq\d+/,
29         /^ifb\d+/,
30         /^mon\.wlan\d+/,
31         /^sit\d+/,
32         /^gre\d+/,
33         /^gretap\d+/,
34         /^ip6gre\d+/,
35         /^ip6tnl\d+/,
36         /^tunl\d+/,
37         /^lo$/
38 ];
39
40 var iface_patterns_wireless = [
41         /^wlan\d+/,
42         /^wl\d+/,
43         /^ath\d+/,
44         /^\w+\.network\d+/
45 ];
46
47 var iface_patterns_virtual = [ ];
48
49 var callLuciNetworkDevices = rpc.declare({
50         object: 'luci-rpc',
51         method: 'getNetworkDevices',
52         expect: { '': {} }
53 });
54
55 var callLuciWirelessDevices = rpc.declare({
56         object: 'luci-rpc',
57         method: 'getWirelessDevices',
58         expect: { '': {} }
59 });
60
61 var callLuciBoardJSON = rpc.declare({
62         object: 'luci-rpc',
63         method: 'getBoardJSON'
64 });
65
66 var callLuciHostHints = rpc.declare({
67         object: 'luci-rpc',
68         method: 'getHostHints',
69         expect: { '': {} }
70 });
71
72 var callIwinfoAssoclist = rpc.declare({
73         object: 'iwinfo',
74         method: 'assoclist',
75         params: [ 'device', 'mac' ],
76         expect: { results: [] }
77 });
78
79 var callIwinfoScan = rpc.declare({
80         object: 'iwinfo',
81         method: 'scan',
82         params: [ 'device' ],
83         nobatch: true,
84         expect: { results: [] }
85 });
86
87 var callNetworkInterfaceDump = rpc.declare({
88         object: 'network.interface',
89         method: 'dump',
90         expect: { 'interface': [] }
91 });
92
93 var callNetworkProtoHandlers = rpc.declare({
94         object: 'network',
95         method: 'get_proto_handlers',
96         expect: { '': {} }
97 });
98
99 var _init = null,
100     _state = null,
101     _protocols = {},
102     _protospecs = {};
103
104 function getProtocolHandlers(cache) {
105         return callNetworkProtoHandlers().then(function(protos) {
106                 /* Register "none" protocol */
107                 if (!protos.hasOwnProperty('none'))
108                         Object.assign(protos, { none: { no_device: false } });
109
110                 /* Hack: emulate relayd protocol */
111                 if (!protos.hasOwnProperty('relay') && L.hasSystemFeature('relayd'))
112                         Object.assign(protos, { relay: { no_device: true } });
113
114                 Object.assign(_protospecs, protos);
115
116                 return Promise.all(Object.keys(protos).map(function(p) {
117                         return Promise.resolve(L.require('protocol.%s'.format(p))).catch(function(err) {
118                                 if (L.isObject(err) && err.name != 'NetworkError')
119                                         L.error(err);
120                         });
121                 })).then(function() {
122                         return protos;
123                 });
124         }).catch(function() {
125                 return {};
126         });
127 }
128
129 function getWifiStateBySid(sid) {
130         var s = uci.get('wireless', sid);
131
132         if (s != null && s['.type'] == 'wifi-iface') {
133                 for (var radioname in _state.radios) {
134                         for (var i = 0; i < _state.radios[radioname].interfaces.length; i++) {
135                                 var netstate = _state.radios[radioname].interfaces[i];
136
137                                 if (typeof(netstate.section) != 'string')
138                                         continue;
139
140                                 var s2 = uci.get('wireless', netstate.section);
141
142                                 if (s2 != null && s['.type'] == s2['.type'] && s['.name'] == s2['.name']) {
143                                         if (s2['.anonymous'] == false && netstate.section.charAt(0) == '@')
144                                                 return null;
145
146                                         return [ radioname, _state.radios[radioname], netstate ];
147                                 }
148                         }
149                 }
150         }
151
152         return null;
153 }
154
155 function getWifiStateByIfname(ifname) {
156         for (var radioname in _state.radios) {
157                 for (var i = 0; i < _state.radios[radioname].interfaces.length; i++) {
158                         var netstate = _state.radios[radioname].interfaces[i];
159
160                         if (typeof(netstate.ifname) != 'string')
161                                 continue;
162
163                         if (netstate.ifname == ifname)
164                                 return [ radioname, _state.radios[radioname], netstate ];
165                 }
166         }
167
168         return null;
169 }
170
171 function isWifiIfname(ifname) {
172         for (var i = 0; i < iface_patterns_wireless.length; i++)
173                 if (iface_patterns_wireless[i].test(ifname))
174                         return true;
175
176         return false;
177 }
178
179 function getWifiSidByNetid(netid) {
180         var m = /^(\w+)\.network(\d+)$/.exec(netid);
181         if (m) {
182                 var sections = uci.sections('wireless', 'wifi-iface');
183                 for (var i = 0, n = 0; i < sections.length; i++) {
184                         if (sections[i].device != m[1])
185                                 continue;
186
187                         if (++n == +m[2])
188                                 return sections[i]['.name'];
189                 }
190         }
191
192         return null;
193 }
194
195 function getWifiSidByIfname(ifname) {
196         var sid = getWifiSidByNetid(ifname);
197
198         if (sid != null)
199                 return sid;
200
201         var res = getWifiStateByIfname(ifname);
202
203         if (res != null && L.isObject(res[2]) && typeof(res[2].section) == 'string')
204                 return res[2].section;
205
206         return null;
207 }
208
209 function getWifiNetidBySid(sid) {
210         var s = uci.get('wireless', sid);
211         if (s != null && s['.type'] == 'wifi-iface') {
212                 var radioname = s.device;
213                 if (typeof(s.device) == 'string') {
214                         var i = 0, netid = null, sections = uci.sections('wireless', 'wifi-iface');
215                         for (var i = 0, n = 0; i < sections.length; i++) {
216                                 if (sections[i].device != s.device)
217                                         continue;
218
219                                 n++;
220
221                                 if (sections[i]['.name'] != s['.name'])
222                                         continue;
223
224                                 return [ '%s.network%d'.format(s.device, n), s.device ];
225                         }
226
227                 }
228         }
229
230         return null;
231 }
232
233 function getWifiNetidByNetname(name) {
234         var sections = uci.sections('wireless', 'wifi-iface');
235         for (var i = 0; i < sections.length; i++) {
236                 if (typeof(sections[i].network) != 'string')
237                         continue;
238
239                 var nets = sections[i].network.split(/\s+/);
240                 for (var j = 0; j < nets.length; j++) {
241                         if (nets[j] != name)
242                                 continue;
243
244                         return getWifiNetidBySid(sections[i]['.name']);
245                 }
246         }
247
248         return null;
249 }
250
251 function isVirtualIfname(ifname) {
252         for (var i = 0; i < iface_patterns_virtual.length; i++)
253                 if (iface_patterns_virtual[i].test(ifname))
254                         return true;
255
256         return false;
257 }
258
259 function isIgnoredIfname(ifname) {
260         for (var i = 0; i < iface_patterns_ignore.length; i++)
261                 if (iface_patterns_ignore[i].test(ifname))
262                         return true;
263
264         return false;
265 }
266
267 function appendValue(config, section, option, value) {
268         var values = uci.get(config, section, option),
269             isArray = Array.isArray(values),
270             rv = false;
271
272         if (isArray == false)
273                 values = L.toArray(values);
274
275         if (values.indexOf(value) == -1) {
276                 values.push(value);
277                 rv = true;
278         }
279
280         uci.set(config, section, option, isArray ? values : values.join(' '));
281
282         return rv;
283 }
284
285 function removeValue(config, section, option, value) {
286         var values = uci.get(config, section, option),
287             isArray = Array.isArray(values),
288             rv = false;
289
290         if (isArray == false)
291                 values = L.toArray(values);
292
293         for (var i = values.length - 1; i >= 0; i--) {
294                 if (values[i] == value) {
295                         values.splice(i, 1);
296                         rv = true;
297                 }
298         }
299
300         if (values.length > 0)
301                 uci.set(config, section, option, isArray ? values : values.join(' '));
302         else
303                 uci.unset(config, section, option);
304
305         return rv;
306 }
307
308 function prefixToMask(bits, v6) {
309         var w = v6 ? 128 : 32,
310             m = [];
311
312         if (bits > w)
313                 return null;
314
315         for (var i = 0; i < w / 16; i++) {
316                 var b = Math.min(16, bits);
317                 m.push((0xffff << (16 - b)) & 0xffff);
318                 bits -= b;
319         }
320
321         if (v6)
322                 return String.prototype.format.apply('%x:%x:%x:%x:%x:%x:%x:%x', m).replace(/:0(?::0)+$/, '::');
323         else
324                 return '%d.%d.%d.%d'.format(m[0] >>> 8, m[0] & 0xff, m[1] >>> 8, m[1] & 0xff);
325 }
326
327 function maskToPrefix(mask, v6) {
328         var m = v6 ? validation.parseIPv6(mask) : validation.parseIPv4(mask);
329
330         if (!m)
331                 return null;
332
333         var bits = 0;
334
335         for (var i = 0, z = false; i < m.length; i++) {
336                 z = z || !m[i];
337
338                 while (!z && (m[i] & (v6 ? 0x8000 : 0x80))) {
339                         m[i] = (m[i] << 1) & (v6 ? 0xffff : 0xff);
340                         bits++;
341                 }
342
343                 if (m[i])
344                         return null;
345         }
346
347         return bits;
348 }
349
350 function initNetworkState(refresh) {
351         if (_state == null || refresh) {
352                 _init = _init || Promise.all([
353                         L.resolveDefault(callNetworkInterfaceDump(), []),
354                         L.resolveDefault(callLuciBoardJSON(), {}),
355                         L.resolveDefault(callLuciNetworkDevices(), {}),
356                         L.resolveDefault(callLuciWirelessDevices(), {}),
357                         L.resolveDefault(callLuciHostHints(), {}),
358                         getProtocolHandlers(),
359                         L.resolveDefault(uci.load('network')),
360                         L.resolveDefault(uci.load('wireless')),
361                         L.resolveDefault(uci.load('luci'))
362                 ]).then(function(data) {
363                         var netifd_ifaces = data[0],
364                             board_json    = data[1],
365                             luci_devs     = data[2];
366
367                         var s = {
368                                 isTunnel: {}, isBridge: {}, isSwitch: {}, isWifi: {},
369                                 ifaces: netifd_ifaces, radios: data[3], hosts: data[4],
370                                 netdevs: {}, bridges: {}, switches: {}, hostapd: {}
371                         };
372
373                         for (var name in luci_devs) {
374                                 var dev = luci_devs[name];
375
376                                 if (isVirtualIfname(name))
377                                         s.isTunnel[name] = true;
378
379                                 if (!s.isTunnel[name] && isIgnoredIfname(name))
380                                         continue;
381
382                                 s.netdevs[name] = s.netdevs[name] || {
383                                         idx:      dev.ifindex,
384                                         name:     name,
385                                         rawname:  name,
386                                         flags:    dev.flags,
387                                         stats:    dev.stats,
388                                         macaddr:  dev.mac,
389                                         type:     dev.type,
390                                         mtu:      dev.mtu,
391                                         qlen:     dev.qlen,
392                                         wireless: dev.wireless,
393                                         ipaddrs:  [],
394                                         ip6addrs: []
395                                 };
396
397                                 if (Array.isArray(dev.ipaddrs))
398                                         for (var i = 0; i < dev.ipaddrs.length; i++)
399                                                 s.netdevs[name].ipaddrs.push(dev.ipaddrs[i].address + '/' + dev.ipaddrs[i].netmask);
400
401                                 if (Array.isArray(dev.ip6addrs))
402                                         for (var i = 0; i < dev.ip6addrs.length; i++)
403                                                 s.netdevs[name].ip6addrs.push(dev.ip6addrs[i].address + '/' + dev.ip6addrs[i].netmask);
404                         }
405
406                         for (var name in luci_devs) {
407                                 var dev = luci_devs[name];
408
409                                 if (!dev.bridge)
410                                         continue;
411
412                                 var b = {
413                                         name:    name,
414                                         id:      dev.id,
415                                         stp:     dev.stp,
416                                         ifnames: []
417                                 };
418
419                                 for (var i = 0; dev.ports && i < dev.ports.length; i++) {
420                                         var subdev = s.netdevs[dev.ports[i]];
421
422                                         if (subdev == null)
423                                                 continue;
424
425                                         b.ifnames.push(subdev);
426                                         subdev.bridge = b;
427                                 }
428
429                                 s.bridges[name] = b;
430                                 s.isBridge[name] = true;
431                         }
432
433                         if (L.isObject(board_json.switch)) {
434                                 for (var switchname in board_json.switch) {
435                                         var layout = board_json.switch[switchname],
436                                             netdevs = {},
437                                             nports = {},
438                                             ports = [],
439                                             pnum = null,
440                                             role = null;
441
442                                         if (L.isObject(layout) && Array.isArray(layout.ports)) {
443                                                 for (var i = 0, port; (port = layout.ports[i]) != null; i++) {
444                                                         if (typeof(port) == 'object' && typeof(port.num) == 'number' &&
445                                                                 (typeof(port.role) == 'string' || typeof(port.device) == 'string')) {
446                                                                 var spec = {
447                                                                         num:   port.num,
448                                                                         role:  port.role || 'cpu',
449                                                                         index: (port.index != null) ? port.index : port.num
450                                                                 };
451
452                                                                 if (port.device != null) {
453                                                                         spec.device = port.device;
454                                                                         spec.tagged = spec.need_tag;
455                                                                         netdevs[port.num] = port.device;
456                                                                 }
457
458                                                                 ports.push(spec);
459
460                                                                 if (port.role != null)
461                                                                         nports[port.role] = (nports[port.role] || 0) + 1;
462                                                         }
463                                                 }
464
465                                                 ports.sort(function(a, b) {
466                                                         if (a.role != b.role)
467                                                                 return (a.role < b.role) ? -1 : 1;
468
469                                                         return (a.index - b.index);
470                                                 });
471
472                                                 for (var i = 0, port; (port = ports[i]) != null; i++) {
473                                                         if (port.role != role) {
474                                                                 role = port.role;
475                                                                 pnum = 1;
476                                                         }
477
478                                                         if (role == 'cpu')
479                                                                 port.label = 'CPU (%s)'.format(port.device);
480                                                         else if (nports[role] > 1)
481                                                                 port.label = '%s %d'.format(role.toUpperCase(), pnum++);
482                                                         else
483                                                                 port.label = role.toUpperCase();
484
485                                                         delete port.role;
486                                                         delete port.index;
487                                                 }
488
489                                                 s.switches[switchname] = {
490                                                         ports: ports,
491                                                         netdevs: netdevs
492                                                 };
493                                         }
494                                 }
495                         }
496
497                         if (L.isObject(board_json.dsl) && L.isObject(board_json.dsl.modem)) {
498                                 s.hasDSLModem = board_json.dsl.modem;
499                         }
500
501                         _init = null;
502
503                         var objects = [];
504
505                         if (L.isObject(s.radios))
506                                 for (var radio in s.radios)
507                                         if (L.isObject(s.radios[radio]) && Array.isArray(s.radios[radio].interfaces))
508                                                 for (var i = 0; i < s.radios[radio].interfaces.length; i++)
509                                                         if (L.isObject(s.radios[radio].interfaces[i]) && s.radios[radio].interfaces[i].ifname)
510                                                                 objects.push('hostapd.%s'.format(s.radios[radio].interfaces[i].ifname));
511
512                         return (objects.length ? L.resolveDefault(rpc.list.apply(rpc, objects), {}) : Promise.resolve({})).then(function(res) {
513                                 for (var k in res) {
514                                         var m = k.match(/^hostapd\.(.+)$/);
515                                         if (m)
516                                                 s.hostapd[m[1]] = res[k];
517                                 }
518
519                                 return (_state = s);
520                         });
521                 });
522         }
523
524         return (_state != null ? Promise.resolve(_state) : _init);
525 }
526
527 function ifnameOf(obj) {
528         if (obj instanceof Protocol)
529                 return obj.getIfname();
530         else if (obj instanceof Device)
531                 return obj.getName();
532         else if (obj instanceof WifiDevice)
533                 return obj.getName();
534         else if (obj instanceof WifiNetwork)
535                 return obj.getIfname();
536         else if (typeof(obj) == 'string')
537                 return obj.replace(/:.+$/, '');
538
539         return null;
540 }
541
542 function networkSort(a, b) {
543         return a.getName() > b.getName();
544 }
545
546 function deviceSort(a, b) {
547         var typeWeigth = { wifi: 2, alias: 3 },
548         weightA = typeWeigth[a.getType()] || 1,
549         weightB = typeWeigth[b.getType()] || 1;
550
551     if (weightA != weightB)
552         return weightA - weightB;
553
554         return a.getName() > b.getName();
555 }
556
557 function formatWifiEncryption(enc) {
558         if (!L.isObject(enc))
559                 return null;
560
561         if (!enc.enabled)
562                 return 'None';
563
564         var ciphers = Array.isArray(enc.ciphers)
565                 ? enc.ciphers.map(function(c) { return c.toUpperCase() }) : [ 'NONE' ];
566
567         if (Array.isArray(enc.wep)) {
568                 var has_open = false,
569                     has_shared = false;
570
571                 for (var i = 0; i < enc.wep.length; i++)
572                         if (enc.wep[i] == 'open')
573                                 has_open = true;
574                         else if (enc.wep[i] == 'shared')
575                                 has_shared = true;
576
577                 if (has_open && has_shared)
578                         return 'WEP Open/Shared (%s)'.format(ciphers.join(', '));
579                 else if (has_open)
580                         return 'WEP Open System (%s)'.format(ciphers.join(', '));
581                 else if (has_shared)
582                         return 'WEP Shared Auth (%s)'.format(ciphers.join(', '));
583
584                 return 'WEP';
585         }
586
587         if (Array.isArray(enc.wpa)) {
588                 var versions = [],
589                     suites = Array.isArray(enc.authentication)
590                         ? enc.authentication.map(function(a) { return a.toUpperCase() }) : [ 'NONE' ];
591
592                 for (var i = 0; i < enc.wpa.length; i++)
593                         switch (enc.wpa[i]) {
594                         case 1:
595                                 versions.push('WPA');
596                                 break;
597
598                         default:
599                                 versions.push('WPA%d'.format(enc.wpa[i]));
600                                 break;
601                         }
602
603                 if (versions.length > 1)
604                         return 'mixed %s %s (%s)'.format(versions.join('/'), suites.join(', '), ciphers.join(', '));
605
606                 return '%s %s (%s)'.format(versions[0], suites.join(', '), ciphers.join(', '));
607         }
608
609         return 'Unknown';
610 }
611
612 function enumerateNetworks() {
613         var uciInterfaces = uci.sections('network', 'interface'),
614             networks = {};
615
616         for (var i = 0; i < uciInterfaces.length; i++)
617                 networks[uciInterfaces[i]['.name']] = this.instantiateNetwork(uciInterfaces[i]['.name']);
618
619         for (var i = 0; i < _state.ifaces.length; i++)
620                 if (networks[_state.ifaces[i].interface] == null)
621                         networks[_state.ifaces[i].interface] =
622                                 this.instantiateNetwork(_state.ifaces[i].interface, _state.ifaces[i].proto);
623
624         var rv = [];
625
626         for (var network in networks)
627                 if (networks.hasOwnProperty(network))
628                         rv.push(networks[network]);
629
630         rv.sort(networkSort);
631
632         return rv;
633 }
634
635
636 var Hosts, Network, Protocol, Device, WifiDevice, WifiNetwork;
637
638 /**
639  * @class network
640  * @memberof LuCI
641  * @hideconstructor
642  * @classdesc
643  *
644  * The `LuCI.network` class combines data from multiple `ubus` apis to
645  * provide an abstraction of the current network configuration state.
646  *
647  * It provides methods to enumerate interfaces and devices, to query
648  * current configuration details and to manipulate settings.
649  */
650 Network = baseclass.extend(/** @lends LuCI.network.prototype */ {
651         /**
652          * Converts the given prefix size in bits to a netmask.
653          *
654          * @method
655          *
656          * @param {number} bits
657          * The prefix size in bits.
658          *
659          * @param {boolean} [v6=false]
660          * Whether to convert the bits value into an IPv4 netmask (`false`) or
661          * an IPv6 netmask (`true`).
662          *
663          * @returns {null|string}
664          * Returns a string containing the netmask corresponding to the bit count
665          * or `null` when the given amount of bits exceeds the maximum possible
666          * value of `32` for IPv4 or `128` for IPv6.
667          */
668         prefixToMask: prefixToMask,
669
670         /**
671          * Converts the given netmask to a prefix size in bits.
672          *
673          * @method
674          *
675          * @param {string} netmask
676          * The netmask to convert into a bit count.
677          *
678          * @param {boolean} [v6=false]
679          * Whether to parse the given netmask as IPv4 (`false`) or IPv6 (`true`)
680          * address.
681          *
682          * @returns {null|number}
683          * Returns the number of prefix bits contained in the netmask or `null`
684          * if the given netmask value was invalid.
685          */
686         maskToPrefix: maskToPrefix,
687
688         /**
689          * An encryption entry describes active wireless encryption settings
690          * such as the used key management protocols, active ciphers and
691          * protocol versions.
692          *
693          * @typedef {Object<string, boolean|Array<number|string>>} LuCI.network.WifiEncryption
694          * @memberof LuCI.network
695          *
696          * @property {boolean} enabled
697          * Specifies whether any kind of encryption, such as `WEP` or `WPA` is
698          * enabled. If set to `false`, then no encryption is active and the
699          * corresponding network is open.
700          *
701          * @property {string[]} [wep]
702          * When the `wep` property exists, the network uses WEP encryption.
703          * In this case, the property is set to an array of active WEP modes
704          * which might be either `open`, `shared` or both.
705          *
706          * @property {number[]} [wpa]
707          * When the `wpa` property exists, the network uses WPA security.
708          * In this case, the property is set to an array containing the WPA
709          * protocol versions used, e.g. `[ 1, 2 ]` for WPA/WPA2 mixed mode or
710          * `[ 3 ]` for WPA3-SAE.
711          *
712          * @property {string[]} [authentication]
713          * The `authentication` property only applies to WPA encryption and
714          * is defined when the `wpa` property is set as well. It points to
715          * an array of active authentication suites used by the network, e.g.
716          * `[ "psk" ]` for a WPA(2)-PSK network or `[ "psk", "sae" ]` for
717          * mixed WPA2-PSK/WPA3-SAE encryption.
718          *
719          * @property {string[]} [ciphers]
720          * If either WEP or WPA encryption is active, then the `ciphers`
721          * property will be set to an array describing the active encryption
722          * ciphers used by the network, e.g. `[ "tkip", "ccmp" ]` for a
723          * WPA/WPA2-PSK mixed network or `[ "wep-40", "wep-104" ]` for an
724          * WEP network.
725          */
726
727         /**
728          * Converts a given {@link LuCI.network.WifiEncryption encryption entry}
729          * into a human readable string such as `mixed WPA/WPA2 PSK (TKIP, CCMP)`
730          * or `WPA3 SAE (CCMP)`.
731          *
732          * @method
733          *
734          * @param {LuCI.network.WifiEncryption} encryption
735          * The wireless encryption entry to convert.
736          *
737          * @returns {null|string}
738          * Returns the description string for the given encryption entry or
739          * `null` if the given entry was invalid.
740          */
741         formatWifiEncryption: formatWifiEncryption,
742
743         /**
744          * Flushes the local network state cache and fetches updated information
745          * from the remote `ubus` apis.
746          *
747          * @returns {Promise<Object>}
748          * Returns a promise resolving to the internal network state object.
749          */
750         flushCache: function() {
751                 initNetworkState(true);
752                 return _init;
753         },
754
755         /**
756          * Instantiates the given {@link LuCI.network.Protocol Protocol} backend,
757          * optionally using the given network name.
758          *
759          * @param {string} protoname
760          * The protocol backend to use, e.g. `static` or `dhcp`.
761          *
762          * @param {string} [netname=__dummy__]
763          * The network name to use for the instantiated protocol. This should be
764          * usually set to one of the interfaces described in /etc/config/network
765          * but it is allowed to omit it, e.g. to query protocol capabilities
766          * without the need for an existing interface.
767          *
768          * @returns {null|LuCI.network.Protocol}
769          * Returns the instantiated protocol backend class or `null` if the given
770          * protocol isn't known.
771          */
772         getProtocol: function(protoname, netname) {
773                 var v = _protocols[protoname];
774                 if (v != null)
775                         return new v(netname || '__dummy__');
776
777                 return null;
778         },
779
780         /**
781          * Obtains instances of all known {@link LuCI.network.Protocol Protocol}
782          * backend classes.
783          *
784          * @returns {Array<LuCI.network.Protocol>}
785          * Returns an array of protocol class instances.
786          */
787         getProtocols: function() {
788                 var rv = [];
789
790                 for (var protoname in _protocols)
791                         rv.push(new _protocols[protoname]('__dummy__'));
792
793                 return rv;
794         },
795
796         /**
797          * Registers a new {@link LuCI.network.Protocol Protocol} subclass
798          * with the given methods and returns the resulting subclass value.
799          *
800          * This functions internally calls
801          * {@link LuCI.Class.extend Class.extend()} on the `Network.Protocol`
802          * base class.
803          *
804          * @param {string} protoname
805          * The name of the new protocol to register.
806          *
807          * @param {Object<string, *>} methods
808          * The member methods and values of the new `Protocol` subclass to
809          * be passed to {@link LuCI.Class.extend Class.extend()}.
810          *
811          * @returns {LuCI.network.Protocol}
812          * Returns the new `Protocol` subclass.
813          */
814         registerProtocol: function(protoname, methods) {
815                 var spec = L.isObject(_protospecs) ? _protospecs[protoname] : null;
816                 var proto = Protocol.extend(Object.assign({
817                         getI18n: function() {
818                                 return protoname;
819                         },
820
821                         isFloating: function() {
822                                 return false;
823                         },
824
825                         isVirtual: function() {
826                                 return (L.isObject(spec) && spec.no_device == true);
827                         },
828
829                         renderFormOptions: function(section) {
830
831                         }
832                 }, methods, {
833                         __init__: function(name) {
834                                 this.sid = name;
835                         },
836
837                         getProtocol: function() {
838                                 return protoname;
839                         }
840                 }));
841
842                 _protocols[protoname] = proto;
843
844                 return proto;
845         },
846
847         /**
848          * Registers a new regular expression pattern to recognize
849          * virtual interfaces.
850          *
851          * @param {RegExp} pat
852          * A `RegExp` instance to match a virtual interface name
853          * such as `6in4-wan` or `tun0`.
854          */
855         registerPatternVirtual: function(pat) {
856                 iface_patterns_virtual.push(pat);
857         },
858
859         /**
860          * Registers a new human readable translation string for a `Protocol`
861          * error code.
862          *
863          * @param {string} code
864          * The `ubus` protocol error code to register a translation for, e.g.
865          * `NO_DEVICE`.
866          *
867          * @param {string} message
868          * The message to use as translation for the given protocol error code.
869          *
870          * @returns {boolean}
871          * Returns `true` if the error code description has been added or `false`
872          * if either the arguments were invalid or if there already was a
873          * description for the given code.
874          */
875         registerErrorCode: function(code, message) {
876                 if (typeof(code) == 'string' &&
877                     typeof(message) == 'string' &&
878                     !proto_errors.hasOwnProperty(code)) {
879                         proto_errors[code] = message;
880                         return true;
881                 }
882
883                 return false;
884         },
885
886         /**
887          * Adds a new network of the given name and update it with the given
888          * uci option values.
889          *
890          * If a network with the given name already exist but is empty, then
891          * this function will update its option, otherwise it will do nothing.
892          *
893          * @param {string} name
894          * The name of the network to add. Must be in the format `[a-zA-Z0-9_]+`.
895          *
896          * @param {Object<string, string|string[]>} [options]
897          * An object of uci option values to set on the new network or to
898          * update in an existing, empty network.
899          *
900          * @returns {Promise<null|LuCI.network.Protocol>}
901          * Returns a promise resolving to the `Protocol` subclass instance
902          * describing the added network or resolving to `null` if the name
903          * was invalid or if a non-empty network of the given name already
904          * existed.
905          */
906         addNetwork: function(name, options) {
907                 return this.getNetwork(name).then(L.bind(function(existingNetwork) {
908                         if (name != null && /^[a-zA-Z0-9_]+$/.test(name) && existingNetwork == null) {
909                                 var sid = uci.add('network', 'interface', name);
910
911                                 if (sid != null) {
912                                         if (L.isObject(options))
913                                                 for (var key in options)
914                                                         if (options.hasOwnProperty(key))
915                                                                 uci.set('network', sid, key, options[key]);
916
917                                         return this.instantiateNetwork(sid);
918                                 }
919                         }
920                         else if (existingNetwork != null && existingNetwork.isEmpty()) {
921                                 if (L.isObject(options))
922                                         for (var key in options)
923                                                 if (options.hasOwnProperty(key))
924                                                         existingNetwork.set(key, options[key]);
925
926                                 return existingNetwork;
927                         }
928                 }, this));
929         },
930
931         /**
932          * Get a {@link LuCI.network.Protocol Protocol} instance describing
933          * the network with the given name.
934          *
935          * @param {string} name
936          * The logical interface name of the network get, e.g. `lan` or `wan`.
937          *
938          * @returns {Promise<null|LuCI.network.Protocol>}
939          * Returns a promise resolving to a
940          * {@link LuCI.network.Protocol Protocol} subclass instance describing
941          * the network or `null` if the network did not exist.
942          */
943         getNetwork: function(name) {
944                 return initNetworkState().then(L.bind(function() {
945                         var section = (name != null) ? uci.get('network', name) : null;
946
947                         if (section != null && section['.type'] == 'interface') {
948                                 return this.instantiateNetwork(name);
949                         }
950                         else if (name != null) {
951                                 for (var i = 0; i < _state.ifaces.length; i++)
952                                         if (_state.ifaces[i].interface == name)
953                                                 return this.instantiateNetwork(name, _state.ifaces[i].proto);
954                         }
955
956                         return null;
957                 }, this));
958         },
959
960         /**
961          * Gets an array containing all known networks.
962          *
963          * @returns {Promise<Array<LuCI.network.Protocol>>}
964          * Returns a promise resolving to a name-sorted array of
965          * {@link LuCI.network.Protocol Protocol} subclass instances
966          * describing all known networks.
967          */
968         getNetworks: function() {
969                 return initNetworkState().then(L.bind(enumerateNetworks, this));
970         },
971
972         /**
973          * Deletes the given network and its references from the network and
974          * firewall configuration.
975          *
976          * @param {string} name
977          * The name of the network to delete.
978          *
979          * @returns {Promise<boolean>}
980          * Returns a promise resolving to either `true` if the network and
981          * references to it were successfully deleted from the configuration or
982          * `false` if the given network could not be found.
983          */
984         deleteNetwork: function(name) {
985                 var requireFirewall = Promise.resolve(L.require('firewall')).catch(function() {}),
986                     network = this.instantiateNetwork(name);
987
988                 return Promise.all([ requireFirewall, initNetworkState() ]).then(function(res) {
989                         var uciInterface = uci.get('network', name),
990                             firewall = res[0];
991
992                         if (uciInterface != null && uciInterface['.type'] == 'interface') {
993                                 return Promise.resolve(network ? network.deleteConfiguration() : null).then(function() {
994                                         uci.remove('network', name);
995
996                                         uci.sections('luci', 'ifstate', function(s) {
997                                                 if (s.interface == name)
998                                                         uci.remove('luci', s['.name']);
999                                         });
1000
1001                                         uci.sections('network', 'alias', function(s) {
1002                                                 if (s.interface == name)
1003                                                         uci.remove('network', s['.name']);
1004                                         });
1005
1006                                         uci.sections('network', 'route', function(s) {
1007                                                 if (s.interface == name)
1008                                                         uci.remove('network', s['.name']);
1009                                         });
1010
1011                                         uci.sections('network', 'route6', function(s) {
1012                                                 if (s.interface == name)
1013                                                         uci.remove('network', s['.name']);
1014                                         });
1015
1016                                         uci.sections('wireless', 'wifi-iface', function(s) {
1017                                                 var networks = L.toArray(s.network).filter(function(network) { return network != name });
1018
1019                                                 if (networks.length > 0)
1020                                                         uci.set('wireless', s['.name'], 'network', networks.join(' '));
1021                                                 else
1022                                                         uci.unset('wireless', s['.name'], 'network');
1023                                         });
1024
1025                                         if (firewall)
1026                                                 return firewall.deleteNetwork(name).then(function() { return true });
1027
1028                                         return true;
1029                                 }).catch(function() {
1030                                         return false;
1031                                 });
1032                         }
1033
1034                         return false;
1035                 });
1036         },
1037
1038         /**
1039          * Rename the given network and its references to a new name.
1040          *
1041          * @param {string} oldName
1042          * The current name of the network.
1043          *
1044          * @param {string} newName
1045          * The name to rename the network to, must be in the format
1046          * `[a-z-A-Z0-9_]+`.
1047          *
1048          * @returns {Promise<boolean>}
1049          * Returns a promise resolving to either `true` if the network was
1050          * successfully renamed or `false` if the new name was invalid, if
1051          * a network with the new name already exists or if the network to
1052          * rename could not be found.
1053          */
1054         renameNetwork: function(oldName, newName) {
1055                 return initNetworkState().then(function() {
1056                         if (newName == null || !/^[a-zA-Z0-9_]+$/.test(newName) || uci.get('network', newName) != null)
1057                                 return false;
1058
1059                         var oldNetwork = uci.get('network', oldName);
1060
1061                         if (oldNetwork == null || oldNetwork['.type'] != 'interface')
1062                                 return false;
1063
1064                         var sid = uci.add('network', 'interface', newName);
1065
1066                         for (var key in oldNetwork)
1067                                 if (oldNetwork.hasOwnProperty(key) && key.charAt(0) != '.')
1068                                         uci.set('network', sid, key, oldNetwork[key]);
1069
1070                         uci.sections('luci', 'ifstate', function(s) {
1071                                 if (s.interface == oldName)
1072                                         uci.set('luci', s['.name'], 'interface', newName);
1073                         });
1074
1075                         uci.sections('network', 'alias', function(s) {
1076                                 if (s.interface == oldName)
1077                                         uci.set('network', s['.name'], 'interface', newName);
1078                         });
1079
1080                         uci.sections('network', 'route', function(s) {
1081                                 if (s.interface == oldName)
1082                                         uci.set('network', s['.name'], 'interface', newName);
1083                         });
1084
1085                         uci.sections('network', 'route6', function(s) {
1086                                 if (s.interface == oldName)
1087                                         uci.set('network', s['.name'], 'interface', newName);
1088                         });
1089
1090                         uci.sections('wireless', 'wifi-iface', function(s) {
1091                                 var networks = L.toArray(s.network).map(function(network) { return (network == oldName ? newName : network) });
1092
1093                                 if (networks.length > 0)
1094                                         uci.set('wireless', s['.name'], 'network', networks.join(' '));
1095                         });
1096
1097                         uci.remove('network', oldName);
1098
1099                         return true;
1100                 });
1101         },
1102
1103         /**
1104          * Get a {@link LuCI.network.Device Device} instance describing the
1105          * given network device.
1106          *
1107          * @param {string} name
1108          * The name of the network device to get, e.g. `eth0` or `br-lan`.
1109          *
1110          * @returns {Promise<null|LuCI.network.Device>}
1111          * Returns a promise resolving to the `Device` instance describing
1112          * the network device or `null` if the given device name could not
1113          * be found.
1114          */
1115         getDevice: function(name) {
1116                 return initNetworkState().then(L.bind(function() {
1117                         if (name == null)
1118                                 return null;
1119
1120                         if (_state.netdevs.hasOwnProperty(name) || isWifiIfname(name))
1121                                 return this.instantiateDevice(name);
1122
1123                         var netid = getWifiNetidBySid(name);
1124                         if (netid != null)
1125                                 return this.instantiateDevice(netid[0]);
1126
1127                         return null;
1128                 }, this));
1129         },
1130
1131         /**
1132          * Get a sorted list of all found network devices.
1133          *
1134          * @returns {Promise<Array<LuCI.network.Device>>}
1135          * Returns a promise resolving to a sorted array of `Device` class
1136          * instances describing the network devices found on the system.
1137          */
1138         getDevices: function() {
1139                 return initNetworkState().then(L.bind(function() {
1140                         var devices = {};
1141
1142                         /* find simple devices */
1143                         var uciInterfaces = uci.sections('network', 'interface');
1144                         for (var i = 0; i < uciInterfaces.length; i++) {
1145                                 var ifnames = L.toArray(uciInterfaces[i].ifname);
1146
1147                                 for (var j = 0; j < ifnames.length; j++) {
1148                                         if (ifnames[j].charAt(0) == '@')
1149                                                 continue;
1150
1151                                         if (isIgnoredIfname(ifnames[j]) || isVirtualIfname(ifnames[j]) || isWifiIfname(ifnames[j]))
1152                                                 continue;
1153
1154                                         devices[ifnames[j]] = this.instantiateDevice(ifnames[j]);
1155                                 }
1156                         }
1157
1158                         for (var ifname in _state.netdevs) {
1159                                 if (devices.hasOwnProperty(ifname))
1160                                         continue;
1161
1162                                 if (isIgnoredIfname(ifname) || isWifiIfname(ifname))
1163                                         continue;
1164
1165                                 if (_state.netdevs[ifname].wireless)
1166                                         continue;
1167
1168                                 devices[ifname] = this.instantiateDevice(ifname);
1169                         }
1170
1171                         /* find VLAN devices */
1172                         var uciSwitchVLANs = uci.sections('network', 'switch_vlan');
1173                         for (var i = 0; i < uciSwitchVLANs.length; i++) {
1174                                 if (typeof(uciSwitchVLANs[i].ports) != 'string' ||
1175                                     typeof(uciSwitchVLANs[i].device) != 'string' ||
1176                                     !_state.switches.hasOwnProperty(uciSwitchVLANs[i].device))
1177                                         continue;
1178
1179                                 var ports = uciSwitchVLANs[i].ports.split(/\s+/);
1180                                 for (var j = 0; j < ports.length; j++) {
1181                                         var m = ports[j].match(/^(\d+)([tu]?)$/);
1182                                         if (m == null)
1183                                                 continue;
1184
1185                                         var netdev = _state.switches[uciSwitchVLANs[i].device].netdevs[m[1]];
1186                                         if (netdev == null)
1187                                                 continue;
1188
1189                                         if (!devices.hasOwnProperty(netdev))
1190                                                 devices[netdev] = this.instantiateDevice(netdev);
1191
1192                                         _state.isSwitch[netdev] = true;
1193
1194                                         if (m[2] != 't')
1195                                                 continue;
1196
1197                                         var vid = uciSwitchVLANs[i].vid || uciSwitchVLANs[i].vlan;
1198                                             vid = (vid != null ? +vid : null);
1199
1200                                         if (vid == null || vid < 0 || vid > 4095)
1201                                                 continue;
1202
1203                                         var vlandev = '%s.%d'.format(netdev, vid);
1204
1205                                         if (!devices.hasOwnProperty(vlandev))
1206                                                 devices[vlandev] = this.instantiateDevice(vlandev);
1207
1208                                         _state.isSwitch[vlandev] = true;
1209                                 }
1210                         }
1211
1212                         /* find wireless interfaces */
1213                         var uciWifiIfaces = uci.sections('wireless', 'wifi-iface'),
1214                             networkCount = {};
1215
1216                         for (var i = 0; i < uciWifiIfaces.length; i++) {
1217                                 if (typeof(uciWifiIfaces[i].device) != 'string')
1218                                         continue;
1219
1220                                 networkCount[uciWifiIfaces[i].device] = (networkCount[uciWifiIfaces[i].device] || 0) + 1;
1221
1222                                 var netid = '%s.network%d'.format(uciWifiIfaces[i].device, networkCount[uciWifiIfaces[i].device]);
1223
1224                                 devices[netid] = this.instantiateDevice(netid);
1225                         }
1226
1227                         var rv = [];
1228
1229                         for (var netdev in devices)
1230                                 if (devices.hasOwnProperty(netdev))
1231                                         rv.push(devices[netdev]);
1232
1233                         rv.sort(deviceSort);
1234
1235                         return rv;
1236                 }, this));
1237         },
1238
1239         /**
1240          * Test if a given network device name is in the list of patterns for
1241          * device names to ignore.
1242          *
1243          * Ignored device names are usually Linux network devices which are
1244          * spawned implicitly by kernel modules such as `tunl0` or `hwsim0`
1245          * and which are unsuitable for use in network configuration.
1246          *
1247          * @param {string} name
1248          * The device name to test.
1249          *
1250          * @returns {boolean}
1251          * Returns `true` if the given name is in the ignore pattern list,
1252          * else returns `false`.
1253          */
1254         isIgnoredDevice: function(name) {
1255                 return isIgnoredIfname(name);
1256         },
1257
1258         /**
1259          * Get a {@link LuCI.network.WifiDevice WifiDevice} instance describing
1260          * the given wireless radio.
1261          *
1262          * @param {string} devname
1263          * The configuration name of the wireless radio to lookup, e.g. `radio0`
1264          * for the first mac80211 phy on the system.
1265          *
1266          * @returns {Promise<null|LuCI.network.WifiDevice>}
1267          * Returns a promise resolving to the `WifiDevice` instance describing
1268          * the underlying radio device or `null` if the wireless radio could not
1269          * be found.
1270          */
1271         getWifiDevice: function(devname) {
1272                 return initNetworkState().then(L.bind(function() {
1273                         var existingDevice = uci.get('wireless', devname);
1274
1275                         if (existingDevice == null || existingDevice['.type'] != 'wifi-device')
1276                                 return null;
1277
1278                         return this.instantiateWifiDevice(devname, _state.radios[devname] || {});
1279                 }, this));
1280         },
1281
1282         /**
1283          * Obtain a list of all configured radio devices.
1284          *
1285          * @returns {Promise<Array<LuCI.network.WifiDevice>>}
1286          * Returns a promise resolving to an array of `WifiDevice` instances
1287          * describing the wireless radios configured in the system.
1288          * The order of the array corresponds to the order of the radios in
1289          * the configuration.
1290          */
1291         getWifiDevices: function() {
1292                 return initNetworkState().then(L.bind(function() {
1293                         var uciWifiDevices = uci.sections('wireless', 'wifi-device'),
1294                             rv = [];
1295
1296                         for (var i = 0; i < uciWifiDevices.length; i++) {
1297                                 var devname = uciWifiDevices[i]['.name'];
1298                                 rv.push(this.instantiateWifiDevice(devname, _state.radios[devname] || {}));
1299                         }
1300
1301                         return rv;
1302                 }, this));
1303         },
1304
1305         /**
1306          * Get a {@link LuCI.network.WifiNetwork WifiNetwork} instance describing
1307          * the given wireless network.
1308          *
1309          * @param {string} netname
1310          * The name of the wireless network to lookup. This may be either an uci
1311          * configuration section ID, a network ID in the form `radio#.network#`
1312          * or a Linux network device name like `wlan0` which is resolved to the
1313          * corresponding configuration section through `ubus` runtime information.
1314          *
1315          * @returns {Promise<null|LuCI.network.WifiNetwork>}
1316          * Returns a promise resolving to the `WifiNetwork` instance describing
1317          * the wireless network or `null` if the corresponding network could not
1318          * be found.
1319          */
1320         getWifiNetwork: function(netname) {
1321                 return initNetworkState()
1322                         .then(L.bind(this.lookupWifiNetwork, this, netname));
1323         },
1324
1325         /**
1326          * Get an array of all {@link LuCI.network.WifiNetwork WifiNetwork}
1327          * instances describing the wireless networks present on the system.
1328          *
1329          * @returns {Promise<Array<LuCI.network.WifiNetwork>>}
1330          * Returns a promise resolving to an array of `WifiNetwork` instances
1331          * describing the wireless networks. The array will be empty if no networks
1332          * are found.
1333          */
1334         getWifiNetworks: function() {
1335                 return initNetworkState().then(L.bind(function() {
1336                         var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
1337                             rv = [];
1338
1339                         for (var i = 0; i < wifiIfaces.length; i++)
1340                                 rv.push(this.lookupWifiNetwork(wifiIfaces[i]['.name']));
1341
1342                         rv.sort(function(a, b) {
1343                                 return (a.getID() > b.getID());
1344                         });
1345
1346                         return rv;
1347                 }, this));
1348         },
1349
1350         /**
1351          * Adds a new wireless network to the configuration and sets its options
1352          * to the provided values.
1353          *
1354          * @param {Object<string, string|string[]>} options
1355          * The options to set for the newly added wireless network. This object
1356          * must at least contain a `device` property which is set to the radio
1357          * name the new network belongs to.
1358          *
1359          * @returns {Promise<null|LuCI.network.WifiNetwork>}
1360          * Returns a promise resolving to a `WifiNetwork` instance describing
1361          * the newly added wireless network or `null` if the given options
1362          * were invalid or if the associated radio device could not be found.
1363          */
1364         addWifiNetwork: function(options) {
1365                 return initNetworkState().then(L.bind(function() {
1366                         if (options == null ||
1367                             typeof(options) != 'object' ||
1368                             typeof(options.device) != 'string')
1369                             return null;
1370
1371                         var existingDevice = uci.get('wireless', options.device);
1372                         if (existingDevice == null || existingDevice['.type'] != 'wifi-device')
1373                                 return null;
1374
1375                         /* XXX: need to add a named section (wifinet#) here */
1376                         var sid = uci.add('wireless', 'wifi-iface');
1377                         for (var key in options)
1378                                 if (options.hasOwnProperty(key))
1379                                         uci.set('wireless', sid, key, options[key]);
1380
1381                         var radioname = existingDevice['.name'],
1382                             netid = getWifiNetidBySid(sid) || [];
1383
1384                         return this.instantiateWifiNetwork(sid, radioname, _state.radios[radioname], netid[0], null);
1385                 }, this));
1386         },
1387
1388         /**
1389          * Deletes the given wireless network from the configuration.
1390          *
1391          * @param {string} netname
1392          * The name of the network to remove. This may be either a
1393          * network ID in the form `radio#.network#` or a Linux network device
1394          * name like `wlan0` which is resolved to the corresponding configuration
1395          * section through `ubus` runtime information.
1396          *
1397          * @returns {Promise<boolean>}
1398          * Returns a promise resolving to `true` if the wireless network has been
1399          * successfully deleted from the configuration or `false` if it could not
1400          * be found.
1401          */
1402         deleteWifiNetwork: function(netname) {
1403                 return initNetworkState().then(L.bind(function() {
1404                         var sid = getWifiSidByIfname(netname);
1405
1406                         if (sid == null)
1407                                 return false;
1408
1409                         uci.remove('wireless', sid);
1410                         return true;
1411                 }, this));
1412         },
1413
1414         /* private */
1415         getStatusByRoute: function(addr, mask) {
1416                 return initNetworkState().then(L.bind(function() {
1417                         var rv = [];
1418
1419                         for (var i = 0; i < _state.ifaces.length; i++) {
1420                                 if (!Array.isArray(_state.ifaces[i].route))
1421                                         continue;
1422
1423                                 for (var j = 0; j < _state.ifaces[i].route.length; j++) {
1424                                         if (typeof(_state.ifaces[i].route[j]) != 'object' ||
1425                                             typeof(_state.ifaces[i].route[j].target) != 'string' ||
1426                                             typeof(_state.ifaces[i].route[j].mask) != 'number')
1427                                             continue;
1428
1429                                         if (_state.ifaces[i].route[j].table)
1430                                                 continue;
1431
1432                                         if (_state.ifaces[i].route[j].target != addr ||
1433                                             _state.ifaces[i].route[j].mask != mask)
1434                                             continue;
1435
1436                                         rv.push(_state.ifaces[i]);
1437                                 }
1438                         }
1439
1440                         return rv;
1441                 }, this));
1442         },
1443
1444         /* private */
1445         getStatusByAddress: function(addr) {
1446                 return initNetworkState().then(L.bind(function() {
1447                         var rv = [];
1448
1449                         for (var i = 0; i < _state.ifaces.length; i++) {
1450                                 if (Array.isArray(_state.ifaces[i]['ipv4-address']))
1451                                         for (var j = 0; j < _state.ifaces[i]['ipv4-address'].length; j++)
1452                                                 if (typeof(_state.ifaces[i]['ipv4-address'][j]) == 'object' &&
1453                                                     _state.ifaces[i]['ipv4-address'][j].address == addr)
1454                                                         return _state.ifaces[i];
1455
1456                                 if (Array.isArray(_state.ifaces[i]['ipv6-address']))
1457                                         for (var j = 0; j < _state.ifaces[i]['ipv6-address'].length; j++)
1458                                                 if (typeof(_state.ifaces[i]['ipv6-address'][j]) == 'object' &&
1459                                                     _state.ifaces[i]['ipv6-address'][j].address == addr)
1460                                                         return _state.ifaces[i];
1461
1462                                 if (Array.isArray(_state.ifaces[i]['ipv6-prefix-assignment']))
1463                                         for (var j = 0; j < _state.ifaces[i]['ipv6-prefix-assignment'].length; j++)
1464                                                 if (typeof(_state.ifaces[i]['ipv6-prefix-assignment'][j]) == 'object' &&
1465                                                         typeof(_state.ifaces[i]['ipv6-prefix-assignment'][j]['local-address']) == 'object' &&
1466                                                     _state.ifaces[i]['ipv6-prefix-assignment'][j]['local-address'].address == addr)
1467                                                         return _state.ifaces[i];
1468                         }
1469
1470                         return null;
1471                 }, this));
1472         },
1473
1474         /**
1475          * Get IPv4 wan networks.
1476          *
1477          * This function looks up all networks having a default `0.0.0.0/0` route
1478          * and returns them as array.
1479          *
1480          * @returns {Promise<Array<LuCI.network.Protocol>>}
1481          * Returns a promise resolving to an array of `Protocol` subclass
1482          * instances describing the found default route interfaces.
1483          */
1484         getWANNetworks: function() {
1485                 return this.getStatusByRoute('0.0.0.0', 0).then(L.bind(function(statuses) {
1486                         var rv = [], seen = {};
1487
1488                         for (var i = 0; i < statuses.length; i++) {
1489                                 if (!seen.hasOwnProperty(statuses[i].interface)) {
1490                                         rv.push(this.instantiateNetwork(statuses[i].interface, statuses[i].proto));
1491                                         seen[statuses[i].interface] = true;
1492                                 }
1493                         }
1494
1495                         return rv;
1496                 }, this));
1497         },
1498
1499         /**
1500          * Get IPv6 wan networks.
1501          *
1502          * This function looks up all networks having a default `::/0` route
1503          * and returns them as array.
1504          *
1505          * @returns {Promise<Array<LuCI.network.Protocol>>}
1506          * Returns a promise resolving to an array of `Protocol` subclass
1507          * instances describing the found IPv6 default route interfaces.
1508          */
1509         getWAN6Networks: function() {
1510                 return this.getStatusByRoute('::', 0).then(L.bind(function(statuses) {
1511                         var rv = [], seen = {};
1512
1513                         for (var i = 0; i < statuses.length; i++) {
1514                                 if (!seen.hasOwnProperty(statuses[i].interface)) {
1515                                         rv.push(this.instantiateNetwork(statuses[i].interface, statuses[i].proto));
1516                                         seen[statuses[i].interface] = true;
1517                                 }
1518                         }
1519
1520                         return rv;
1521                 }, this));
1522         },
1523
1524         /**
1525          * Describes an swconfig switch topology by specifying the CPU
1526          * connections and external port labels of a switch.
1527          *
1528          * @typedef {Object<string, Object|Array>} SwitchTopology
1529          * @memberof LuCI.network
1530          *
1531          * @property {Object<number, string>} netdevs
1532          * The `netdevs` property points to an object describing the CPU port
1533          * connections of the switch. The numeric key of the enclosed object is
1534          * the port number, the value contains the Linux network device name the
1535          * port is hardwired to.
1536          *
1537          * @property {Array<Object<string, boolean|number|string>>} ports
1538          * The `ports` property points to an array describing the populated
1539          * ports of the switch in the external label order. Each array item is
1540          * an object containg the following keys:
1541          *  - `num` - the internal switch port number
1542          *  - `label` - the label of the port, e.g. `LAN 1` or `CPU (eth0)`
1543          *  - `device` - the connected Linux network device name (CPU ports only)
1544          *  - `tagged` - a boolean indicating whether the port must be tagged to
1545          *     function (CPU ports only)
1546          */
1547
1548         /**
1549          * Returns the topologies of all swconfig switches found on the system.
1550          *
1551          * @returns {Promise<Object<string, LuCI.network.SwitchTopology>>}
1552          * Returns a promise resolving to an object containing the topologies
1553          * of each switch. The object keys correspond to the name of the switches
1554          * such as `switch0`, the values are
1555          * {@link LuCI.network.SwitchTopology SwitchTopology} objects describing
1556          * the layout.
1557          */
1558         getSwitchTopologies: function() {
1559                 return initNetworkState().then(function() {
1560                         return _state.switches;
1561                 });
1562         },
1563
1564         /* private */
1565         instantiateNetwork: function(name, proto) {
1566                 if (name == null)
1567                         return null;
1568
1569                 proto = (proto == null ? uci.get('network', name, 'proto') : proto);
1570
1571                 var protoClass = _protocols[proto] || Protocol;
1572                 return new protoClass(name);
1573         },
1574
1575         /* private */
1576         instantiateDevice: function(name, network, extend) {
1577                 if (extend != null)
1578                         return new (Device.extend(extend))(name, network);
1579
1580                 return new Device(name, network);
1581         },
1582
1583         /* private */
1584         instantiateWifiDevice: function(radioname, radiostate) {
1585                 return new WifiDevice(radioname, radiostate);
1586         },
1587
1588         /* private */
1589         instantiateWifiNetwork: function(sid, radioname, radiostate, netid, netstate, hostapd) {
1590                 return new WifiNetwork(sid, radioname, radiostate, netid, netstate, hostapd);
1591         },
1592
1593         /* private */
1594         lookupWifiNetwork: function(netname) {
1595                 var sid, res, netid, radioname, radiostate, netstate;
1596
1597                 sid = getWifiSidByNetid(netname);
1598
1599                 if (sid != null) {
1600                         res        = getWifiStateBySid(sid);
1601                         netid      = netname;
1602                         radioname  = res ? res[0] : null;
1603                         radiostate = res ? res[1] : null;
1604                         netstate   = res ? res[2] : null;
1605                 }
1606                 else {
1607                         res = getWifiStateByIfname(netname);
1608
1609                         if (res != null) {
1610                                 radioname  = res[0];
1611                                 radiostate = res[1];
1612                                 netstate   = res[2];
1613                                 sid        = netstate.section;
1614                                 netid      = L.toArray(getWifiNetidBySid(sid))[0];
1615                         }
1616                         else {
1617                                 res = getWifiStateBySid(netname);
1618
1619                                 if (res != null) {
1620                                         radioname  = res[0];
1621                                         radiostate = res[1];
1622                                         netstate   = res[2];
1623                                         sid        = netname;
1624                                         netid      = L.toArray(getWifiNetidBySid(sid))[0];
1625                                 }
1626                                 else {
1627                                         res = getWifiNetidBySid(netname);
1628
1629                                         if (res != null) {
1630                                                 netid     = res[0];
1631                                                 radioname = res[1];
1632                                                 sid       = netname;
1633                                         }
1634                                 }
1635                         }
1636                 }
1637
1638                 return this.instantiateWifiNetwork(sid || netname, radioname,
1639                         radiostate, netid, netstate,
1640                         netstate ? _state.hostapd[netstate.ifname] : null);
1641         },
1642
1643         /**
1644          * Obtains the the network device name of the given object.
1645          *
1646          * @param {LuCI.network.Protocol|LuCI.network.Device|LuCI.network.WifiDevice|LuCI.network.WifiNetwork|string} obj
1647          * The object to get the device name from.
1648          *
1649          * @returns {null|string}
1650          * Returns a string containing the device name or `null` if the given
1651          * object could not be converted to a name.
1652          */
1653         getIfnameOf: function(obj) {
1654                 return ifnameOf(obj);
1655         },
1656
1657         /**
1658          * Queries the internal DSL modem type from board information.
1659          *
1660          * @returns {Promise<null|string>}
1661          * Returns a promise resolving to the type of the internal modem
1662          * (e.g. `vdsl`) or to `null` if no internal modem is present.
1663          */
1664         getDSLModemType: function() {
1665                 return initNetworkState().then(function() {
1666                         return _state.hasDSLModem ? _state.hasDSLModem.type : null;
1667                 });
1668         },
1669
1670         /**
1671          * Queries aggregated information about known hosts.
1672          *
1673          * This function aggregates information from various sources such as
1674          * DHCP lease databases, ARP and IPv6 neighbour entries, wireless
1675          * association list etc. and returns a {@link LuCI.network.Hosts Hosts}
1676          * class instance describing the found hosts.
1677          *
1678          * @returns {Promise<LuCI.network.Hosts>}
1679          * Returns a `Hosts` instance describing host known on the system.
1680          */
1681         getHostHints: function() {
1682                 return initNetworkState().then(function() {
1683                         return new Hosts(_state.hosts);
1684                 });
1685         }
1686 });
1687
1688 /**
1689  * @class
1690  * @memberof LuCI.network
1691  * @hideconstructor
1692  * @classdesc
1693  *
1694  * The `LuCI.network.Hosts` class encapsulates host information aggregated
1695  * from multiple sources and provides convenience functions to access the
1696  * host information by different criteria.
1697  */
1698 Hosts = baseclass.extend(/** @lends LuCI.network.Hosts.prototype */ {
1699         __init__: function(hosts) {
1700                 this.hosts = hosts;
1701         },
1702
1703         /**
1704          * Lookup the hostname associated with the given MAC address.
1705          *
1706          * @param {string} mac
1707          * The MAC address to lookup.
1708          *
1709          * @returns {null|string}
1710          * Returns the hostname associated with the given MAC or `null` if
1711          * no matching host could be found or if no hostname is known for
1712          * the corresponding host.
1713          */
1714         getHostnameByMACAddr: function(mac) {
1715                 return this.hosts[mac] ? this.hosts[mac].name : null;
1716         },
1717
1718         /**
1719          * Lookup the IPv4 address associated with the given MAC address.
1720          *
1721          * @param {string} mac
1722          * The MAC address to lookup.
1723          *
1724          * @returns {null|string}
1725          * Returns the IPv4 address associated with the given MAC or `null` if
1726          * no matching host could be found or if no IPv4 address is known for
1727          * the corresponding host.
1728          */
1729         getIPAddrByMACAddr: function(mac) {
1730                 return this.hosts[mac] ? this.hosts[mac].ipv4 : null;
1731         },
1732
1733         /**
1734          * Lookup the IPv6 address associated with the given MAC address.
1735          *
1736          * @param {string} mac
1737          * The MAC address to lookup.
1738          *
1739          * @returns {null|string}
1740          * Returns the IPv6 address associated with the given MAC or `null` if
1741          * no matching host could be found or if no IPv6 address is known for
1742          * the corresponding host.
1743          */
1744         getIP6AddrByMACAddr: function(mac) {
1745                 return this.hosts[mac] ? this.hosts[mac].ipv6 : null;
1746         },
1747
1748         /**
1749          * Lookup the hostname associated with the given IPv4 address.
1750          *
1751          * @param {string} ipaddr
1752          * The IPv4 address to lookup.
1753          *
1754          * @returns {null|string}
1755          * Returns the hostname associated with the given IPv4 or `null` if
1756          * no matching host could be found or if no hostname is known for
1757          * the corresponding host.
1758          */
1759         getHostnameByIPAddr: function(ipaddr) {
1760                 for (var mac in this.hosts)
1761                         if (this.hosts[mac].ipv4 == ipaddr && this.hosts[mac].name != null)
1762                                 return this.hosts[mac].name;
1763                 return null;
1764         },
1765
1766         /**
1767          * Lookup the MAC address associated with the given IPv4 address.
1768          *
1769          * @param {string} ipaddr
1770          * The IPv4 address to lookup.
1771          *
1772          * @returns {null|string}
1773          * Returns the MAC address associated with the given IPv4 or `null` if
1774          * no matching host could be found or if no MAC address is known for
1775          * the corresponding host.
1776          */
1777         getMACAddrByIPAddr: function(ipaddr) {
1778                 for (var mac in this.hosts)
1779                         if (this.hosts[mac].ipv4 == ipaddr)
1780                                 return mac;
1781                 return null;
1782         },
1783
1784         /**
1785          * Lookup the hostname associated with the given IPv6 address.
1786          *
1787          * @param {string} ipaddr
1788          * The IPv6 address to lookup.
1789          *
1790          * @returns {null|string}
1791          * Returns the hostname associated with the given IPv6 or `null` if
1792          * no matching host could be found or if no hostname is known for
1793          * the corresponding host.
1794          */
1795         getHostnameByIP6Addr: function(ip6addr) {
1796                 for (var mac in this.hosts)
1797                         if (this.hosts[mac].ipv6 == ip6addr && this.hosts[mac].name != null)
1798                                 return this.hosts[mac].name;
1799                 return null;
1800         },
1801
1802         /**
1803          * Lookup the MAC address associated with the given IPv6 address.
1804          *
1805          * @param {string} ipaddr
1806          * The IPv6 address to lookup.
1807          *
1808          * @returns {null|string}
1809          * Returns the MAC address associated with the given IPv6 or `null` if
1810          * no matching host could be found or if no MAC address is known for
1811          * the corresponding host.
1812          */
1813         getMACAddrByIP6Addr: function(ip6addr) {
1814                 for (var mac in this.hosts)
1815                         if (this.hosts[mac].ipv6 == ip6addr)
1816                                 return mac;
1817                 return null;
1818         },
1819
1820         /**
1821          * Return an array of (MAC address, name hint) tuples sorted by
1822          * MAC address.
1823          *
1824          * @param {boolean} [preferIp6=false]
1825          * Whether to prefer IPv6 addresses (`true`) or IPv4 addresses (`false`)
1826          * as name hint when no hostname is known for a specific MAC address.
1827          *
1828          * @returns {Array<Array<string>>}
1829          * Returns an array of arrays containing a name hint for each found
1830          * MAC address on the system. The array is sorted ascending by MAC.
1831          *
1832          * Each item of the resulting array is a two element array with the
1833          * MAC being the first element and the name hint being the second
1834          * element. The name hint is either the hostname, an IPv4 or an IPv6
1835          * address related to the MAC address.
1836          *
1837          * If no hostname but both IPv4 and IPv6 addresses are known, the
1838          * `preferIP6` flag specifies whether the IPv6 or the IPv4 address
1839          * is used as hint.
1840          */
1841         getMACHints: function(preferIp6) {
1842                 var rv = [];
1843                 for (var mac in this.hosts) {
1844                         var hint = this.hosts[mac].name ||
1845                                 this.hosts[mac][preferIp6 ? 'ipv6' : 'ipv4'] ||
1846                                 this.hosts[mac][preferIp6 ? 'ipv4' : 'ipv6'];
1847
1848                         rv.push([mac, hint]);
1849                 }
1850                 return rv.sort(function(a, b) { return a[0] > b[0] });
1851         }
1852 });
1853
1854 /**
1855  * @class
1856  * @memberof LuCI.network
1857  * @hideconstructor
1858  * @classdesc
1859  *
1860  * The `Network.Protocol` class serves as base for protocol specific
1861  * subclasses which describe logical UCI networks defined by `config
1862  * interface` sections in `/etc/config/network`.
1863  */
1864 Protocol = baseclass.extend(/** @lends LuCI.network.Protocol.prototype */ {
1865         __init__: function(name) {
1866                 this.sid = name;
1867         },
1868
1869         _get: function(opt) {
1870                 var val = uci.get('network', this.sid, opt);
1871
1872                 if (Array.isArray(val))
1873                         return val.join(' ');
1874
1875                 return val || '';
1876         },
1877
1878         _ubus: function(field) {
1879                 for (var i = 0; i < _state.ifaces.length; i++) {
1880                         if (_state.ifaces[i].interface != this.sid)
1881                                 continue;
1882
1883                         return (field != null ? _state.ifaces[i][field] : _state.ifaces[i]);
1884                 }
1885         },
1886
1887         /**
1888          * Read the given UCI option value of this network.
1889          *
1890          * @param {string} opt
1891          * The UCI option name to read.
1892          *
1893          * @returns {null|string|string[]}
1894          * Returns the UCI option value or `null` if the requested option is
1895          * not found.
1896          */
1897         get: function(opt) {
1898                 return uci.get('network', this.sid, opt);
1899         },
1900
1901         /**
1902          * Set the given UCI option of this network to the given value.
1903          *
1904          * @param {string} opt
1905          * The name of the UCI option to set.
1906          *
1907          * @param {null|string|string[]} val
1908          * The value to set or `null` to remove the given option from the
1909          * configuration.
1910          */
1911         set: function(opt, val) {
1912                 return uci.set('network', this.sid, opt, val);
1913         },
1914
1915         /**
1916          * Get the associared Linux network device of this network.
1917          *
1918          * @returns {null|string}
1919          * Returns the name of the associated network device or `null` if
1920          * it could not be determined.
1921          */
1922         getIfname: function() {
1923                 var ifname;
1924
1925                 if (this.isFloating())
1926                         ifname = this._ubus('l3_device');
1927                 else
1928                         ifname = this._ubus('device') || this._ubus('l3_device');
1929
1930                 if (ifname != null)
1931                         return ifname;
1932
1933                 var res = getWifiNetidByNetname(this.sid);
1934                 return (res != null ? res[0] : null);
1935         },
1936
1937         /**
1938          * Get the name of this network protocol class.
1939          *
1940          * This function will be overwritten by subclasses created by
1941          * {@link LuCI.network#registerProtocol Network.registerProtocol()}.
1942          *
1943          * @abstract
1944          * @returns {string}
1945          * Returns the name of the network protocol implementation, e.g.
1946          * `static` or `dhcp`.
1947          */
1948         getProtocol: function() {
1949                 return null;
1950         },
1951
1952         /**
1953          * Return a human readable description for the protcol, such as
1954          * `Static address` or `DHCP client`.
1955          *
1956          * This function should be overwritten by subclasses.
1957          *
1958          * @abstract
1959          * @returns {string}
1960          * Returns the description string.
1961          */
1962         getI18n: function() {
1963                 switch (this.getProtocol()) {
1964                 case 'none':   return _('Unmanaged');
1965                 case 'static': return _('Static address');
1966                 case 'dhcp':   return _('DHCP client');
1967                 default:       return _('Unknown');
1968                 }
1969         },
1970
1971         /**
1972          * Get the type of the underlying interface.
1973          *
1974          * This function actually is a convenience wrapper around
1975          * `proto.get("type")` and is mainly used by other `LuCI.network` code
1976          * to check whether the interface is declared as bridge in UCI.
1977          *
1978          * @returns {null|string}
1979          * Returns the value of the `type` option of the associated logical
1980          * interface or `null` if no `type` option is set.
1981          */
1982         getType: function() {
1983                 return this._get('type');
1984         },
1985
1986         /**
1987          * Get the name of the associated logical interface.
1988          *
1989          * @returns {string}
1990          * Returns the logical interface name, such as `lan` or `wan`.
1991          */
1992         getName: function() {
1993                 return this.sid;
1994         },
1995
1996         /**
1997          * Get the uptime of the logical interface.
1998          *
1999          * @returns {number}
2000          * Returns the uptime of the associated interface in seconds.
2001          */
2002         getUptime: function() {
2003                 return this._ubus('uptime') || 0;
2004         },
2005
2006         /**
2007          * Get the logical interface expiry time in seconds.
2008          *
2009          * For protocols that have a concept of a lease, such as DHCP or
2010          * DHCPv6, this function returns the remaining time in seconds
2011          * until the lease expires.
2012          *
2013          * @returns {number}
2014          * Returns the amount of seconds until the lease expires or `-1`
2015          * if it isn't applicable to the associated protocol.
2016          */
2017         getExpiry: function() {
2018                 var u = this._ubus('uptime'),
2019                     d = this._ubus('data');
2020
2021                 if (typeof(u) == 'number' && d != null &&
2022                     typeof(d) == 'object' && typeof(d.leasetime) == 'number') {
2023                         var r = d.leasetime - (u % d.leasetime);
2024                         return (r > 0 ? r : 0);
2025                 }
2026
2027                 return -1;
2028         },
2029
2030         /**
2031          * Get the metric value of the logical interface.
2032          *
2033          * @returns {number}
2034          * Returns the current metric value used for device and network
2035          * routes spawned by the associated logical interface.
2036          */
2037         getMetric: function() {
2038                 return this._ubus('metric') || 0;
2039         },
2040
2041         /**
2042          * Get the requested firewall zone name of the logical interface.
2043          *
2044          * Some protocol implementations request a specific firewall zone
2045          * to trigger inclusion of their resulting network devices into the
2046          * firewall rule set.
2047          *
2048          * @returns {null|string}
2049          * Returns the requested firewall zone name as published in the
2050          * `ubus` runtime information or `null` if the remote protocol
2051          * handler didn't request a zone.
2052          */
2053         getZoneName: function() {
2054                 var d = this._ubus('data');
2055
2056                 if (L.isObject(d) && typeof(d.zone) == 'string')
2057                         return d.zone;
2058
2059                 return null;
2060         },
2061
2062         /**
2063          * Query the first (primary) IPv4 address of the logical interface.
2064          *
2065          * @returns {null|string}
2066          * Returns the primary IPv4 address registered by the protocol handler
2067          * or `null` if no IPv4 addresses were set.
2068          */
2069         getIPAddr: function() {
2070                 var addrs = this._ubus('ipv4-address');
2071                 return ((Array.isArray(addrs) && addrs.length) ? addrs[0].address : null);
2072         },
2073
2074         /**
2075          * Query all IPv4 addresses of the logical interface.
2076          *
2077          * @returns {string[]}
2078          * Returns an array of IPv4 addresses in CIDR notation which have been
2079          * registered by the protocol handler. The order of the resulting array
2080          * follows the order of the addresses in `ubus` runtime information.
2081          */
2082         getIPAddrs: function() {
2083                 var addrs = this._ubus('ipv4-address'),
2084                     rv = [];
2085
2086                 if (Array.isArray(addrs))
2087                         for (var i = 0; i < addrs.length; i++)
2088                                 rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
2089
2090                 return rv;
2091         },
2092
2093         /**
2094          * Query the first (primary) IPv4 netmask of the logical interface.
2095          *
2096          * @returns {null|string}
2097          * Returns the netmask of the primary IPv4 address registered by the
2098          * protocol handler or `null` if no IPv4 addresses were set.
2099          */
2100         getNetmask: function() {
2101                 var addrs = this._ubus('ipv4-address');
2102                 if (Array.isArray(addrs) && addrs.length)
2103                         return prefixToMask(addrs[0].mask, false);
2104         },
2105
2106         /**
2107          * Query the gateway (nexthop) of the default route associated with
2108          * this logical interface.
2109          *
2110          * @returns {string}
2111          * Returns a string containing the IPv4 nexthop address of the associated
2112          * default route or `null` if no default route was found.
2113          */
2114         getGatewayAddr: function() {
2115                 var routes = this._ubus('route');
2116
2117                 if (Array.isArray(routes))
2118                         for (var i = 0; i < routes.length; i++)
2119                                 if (typeof(routes[i]) == 'object' &&
2120                                     routes[i].target == '0.0.0.0' &&
2121                                     routes[i].mask == 0)
2122                                     return routes[i].nexthop;
2123
2124                 return null;
2125         },
2126
2127         /**
2128          * Query the IPv4 DNS servers associated with the logical interface.
2129          *
2130          * @returns {string[]}
2131          * Returns an array of IPv4 DNS servers registered by the remote
2132          * protocol backend.
2133          */
2134         getDNSAddrs: function() {
2135                 var addrs = this._ubus('dns-server'),
2136                     rv = [];
2137
2138                 if (Array.isArray(addrs))
2139                         for (var i = 0; i < addrs.length; i++)
2140                                 if (!/:/.test(addrs[i]))
2141                                         rv.push(addrs[i]);
2142
2143                 return rv;
2144         },
2145
2146         /**
2147          * Query the first (primary) IPv6 address of the logical interface.
2148          *
2149          * @returns {null|string}
2150          * Returns the primary IPv6 address registered by the protocol handler
2151          * in CIDR notation or `null` if no IPv6 addresses were set.
2152          */
2153         getIP6Addr: function() {
2154                 var addrs = this._ubus('ipv6-address');
2155
2156                 if (Array.isArray(addrs) && L.isObject(addrs[0]))
2157                         return '%s/%d'.format(addrs[0].address, addrs[0].mask);
2158
2159                 addrs = this._ubus('ipv6-prefix-assignment');
2160
2161                 if (Array.isArray(addrs) && L.isObject(addrs[0]) && L.isObject(addrs[0]['local-address']))
2162                         return '%s/%d'.format(addrs[0]['local-address'].address, addrs[0]['local-address'].mask);
2163
2164                 return null;
2165         },
2166
2167         /**
2168          * Query all IPv6 addresses of the logical interface.
2169          *
2170          * @returns {string[]}
2171          * Returns an array of IPv6 addresses in CIDR notation which have been
2172          * registered by the protocol handler. The order of the resulting array
2173          * follows the order of the addresses in `ubus` runtime information.
2174          */
2175         getIP6Addrs: function() {
2176                 var addrs = this._ubus('ipv6-address'),
2177                     rv = [];
2178
2179                 if (Array.isArray(addrs))
2180                         for (var i = 0; i < addrs.length; i++)
2181                                 if (L.isObject(addrs[i]))
2182                                         rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
2183
2184                 addrs = this._ubus('ipv6-prefix-assignment');
2185
2186                 if (Array.isArray(addrs))
2187                         for (var i = 0; i < addrs.length; i++)
2188                                 if (L.isObject(addrs[i]) && L.isObject(addrs[i]['local-address']))
2189                                         rv.push('%s/%d'.format(addrs[i]['local-address'].address, addrs[i]['local-address'].mask));
2190
2191                 return rv;
2192         },
2193
2194         /**
2195          * Query the gateway (nexthop) of the IPv6 default route associated with
2196          * this logical interface.
2197          *
2198          * @returns {string}
2199          * Returns a string containing the IPv6 nexthop address of the associated
2200          * default route or `null` if no default route was found.
2201          */
2202         getGateway6Addr: function() {
2203                 var routes = this._ubus('route');
2204
2205                 if (Array.isArray(routes))
2206                         for (var i = 0; i < routes.length; i++)
2207                                 if (typeof(routes[i]) == 'object' &&
2208                                     routes[i].target == '::' &&
2209                                     routes[i].mask == 0)
2210                                     return routes[i].nexthop;
2211
2212                 return null;
2213         },
2214
2215         /**
2216          * Query the IPv6 DNS servers associated with the logical interface.
2217          *
2218          * @returns {string[]}
2219          * Returns an array of IPv6 DNS servers registered by the remote
2220          * protocol backend.
2221          */
2222         getDNS6Addrs: function() {
2223                 var addrs = this._ubus('dns-server'),
2224                     rv = [];
2225
2226                 if (Array.isArray(addrs))
2227                         for (var i = 0; i < addrs.length; i++)
2228                                 if (/:/.test(addrs[i]))
2229                                         rv.push(addrs[i]);
2230
2231                 return rv;
2232         },
2233
2234         /**
2235          * Query the routed IPv6 prefix associated with the logical interface.
2236          *
2237          * @returns {null|string}
2238          * Returns the routed IPv6 prefix registered by the remote protocol
2239          * handler or `null` if no prefix is present.
2240          */
2241         getIP6Prefix: function() {
2242                 var prefixes = this._ubus('ipv6-prefix');
2243
2244                 if (Array.isArray(prefixes) && L.isObject(prefixes[0]))
2245                         return '%s/%d'.format(prefixes[0].address, prefixes[0].mask);
2246
2247                 return null;
2248         },
2249
2250         /**
2251          * Query interface error messages published in `ubus` runtime state.
2252          *
2253          * Interface errors are emitted by remote protocol handlers if the setup
2254          * of the underlying logical interface failed, e.g. due to bad
2255          * configuration or network connectivity issues.
2256          *
2257          * This function will translate the found error codes to human readable
2258          * messages using the descriptions registered by
2259          * {@link LuCI.network#registerErrorCode Network.registerErrorCode()}
2260          * and fall back to `"Unknown error (%s)"` where `%s` is replaced by the
2261          * error code in case no translation can be found.
2262          *
2263          * @returns {string[]}
2264          * Returns an array of translated interface error messages.
2265          */
2266         getErrors: function() {
2267                 var errors = this._ubus('errors'),
2268                     rv = null;
2269
2270                 if (Array.isArray(errors)) {
2271                         for (var i = 0; i < errors.length; i++) {
2272                                 if (!L.isObject(errors[i]) || typeof(errors[i].code) != 'string')
2273                                         continue;
2274
2275                                 rv = rv || [];
2276                                 rv.push(proto_errors[errors[i].code] || _('Unknown error (%s)').format(errors[i].code));
2277                         }
2278                 }
2279
2280                 return rv;
2281         },
2282
2283         /**
2284          * Checks whether the underlying logical interface is declared as bridge.
2285          *
2286          * @returns {boolean}
2287          * Returns `true` when the interface is declared with `option type bridge`
2288          * and when the associated protocol implementation is not marked virtual
2289          * or `false` when the logical interface is no bridge.
2290          */
2291         isBridge: function() {
2292                 return (!this.isVirtual() && this.getType() == 'bridge');
2293         },
2294
2295         /**
2296          * Get the name of the opkg package providing the protocol functionality.
2297          *
2298          * This function should be overwritten by protocol specific subclasses.
2299          *
2300          * @abstract
2301          *
2302          * @returns {string}
2303          * Returns the name of the opkg package required for the protocol to
2304          * function, e.g. `odhcp6c` for the `dhcpv6` prototocol.
2305          */
2306         getOpkgPackage: function() {
2307                 return null;
2308         },
2309
2310         /**
2311          * Check function for the protocol handler if a new interface is createable.
2312          *
2313          * This function should be overwritten by protocol specific subclasses.
2314          *
2315          * @abstract
2316          *
2317          * @param {string} ifname
2318          * The name of the interface to be created.
2319          *
2320          * @returns {Promise<void>}
2321          * Returns a promise resolving if new interface is createable, else
2322          * rejects with an error message string.
2323          */
2324         isCreateable: function(ifname) {
2325                 return Promise.resolve(null);
2326         },
2327
2328         /**
2329          * Checks whether the protocol functionality is installed.
2330          *
2331          * This function exists for compatibility with old code, it always
2332          * returns `true`.
2333          *
2334          * @deprecated
2335          * @abstract
2336          *
2337          * @returns {boolean}
2338          * Returns `true` if the protocol support is installed, else `false`.
2339          */
2340         isInstalled: function() {
2341                 return true;
2342         },
2343
2344         /**
2345          * Checks whether this protocol is "virtual".
2346          *
2347          * A "virtual" protocol is a protocol which spawns its own interfaces
2348          * on demand instead of using existing physical interfaces.
2349          *
2350          * Examples for virtual protocols are `6in4` which `gre` spawn tunnel
2351          * network device on startup, examples for non-virtual protcols are
2352          * `dhcp` or `static` which apply IP configuration to existing interfaces.
2353          *
2354          * This function should be overwritten by subclasses.
2355          *
2356          * @returns {boolean}
2357          * Returns a boolean indicating whether the underlying protocol spawns
2358          * dynamic interfaces (`true`) or not (`false`).
2359          */
2360         isVirtual: function() {
2361                 return false;
2362         },
2363
2364         /**
2365          * Checks whether this protocol is "floating".
2366          *
2367          * A "floating" protocol is a protocol which spawns its own interfaces
2368          * on demand, like a virtual one but which relies on an existinf lower
2369          * level interface to initiate the connection.
2370          *
2371          * An example for such a protocol is "pppoe".
2372          *
2373          * This function exists for backwards compatibility with older code
2374          * but should not be used anymore.
2375          *
2376          * @deprecated
2377          * @returns {boolean}
2378          * Returns a boolean indicating whether this protocol is floating (`true`)
2379          * or not (`false`).
2380          */
2381         isFloating: function() {
2382                 return false;
2383         },
2384
2385         /**
2386          * Checks whether this logical interface is dynamic.
2387          *
2388          * A dynamic interface is an interface which has been created at runtime,
2389          * e.g. as sub-interface of another interface, but which is not backed by
2390          * any user configuration. Such dynamic interfaces cannot be edited but
2391          * only brought down or restarted.
2392          *
2393          * @returns {boolean}
2394          * Returns a boolean indicating whether this interface is dynamic (`true`)
2395          * or not (`false`).
2396          */
2397         isDynamic: function() {
2398                 return (this._ubus('dynamic') == true);
2399         },
2400
2401         /**
2402          * Checks whether this interface is an alias interface.
2403          *
2404          * Alias interfaces are interfaces layering on top of another interface
2405          * and are denoted by a special `@interfacename` notation in the
2406          * underlying `ifname` option.
2407          *
2408          * @returns {null|string}
2409          * Returns the name of the parent interface if this logical interface
2410          * is an alias or `null` if it is not an alias interface.
2411          */
2412         isAlias: function() {
2413                 var ifnames = L.toArray(uci.get('network', this.sid, 'ifname')),
2414                     parent = null;
2415
2416                 for (var i = 0; i < ifnames.length; i++)
2417                         if (ifnames[i].charAt(0) == '@')
2418                                 parent = ifnames[i].substr(1);
2419                         else if (parent != null)
2420                                 parent = null;
2421
2422                 return parent;
2423         },
2424
2425         /**
2426          * Checks whether this logical interface is "empty", meaning that ut
2427          * has no network devices attached.
2428          *
2429          * @returns {boolean}
2430          * Returns `true` if this logical interface is empty, else `false`.
2431          */
2432         isEmpty: function() {
2433                 if (this.isFloating())
2434                         return false;
2435
2436                 var empty = true,
2437                     ifname = this._get('ifname');
2438
2439                 if (ifname != null && ifname.match(/\S+/))
2440                         empty = false;
2441
2442                 if (empty == true && getWifiNetidBySid(this.sid) != null)
2443                         empty = false;
2444
2445                 return empty;
2446         },
2447
2448         /**
2449          * Checks whether this logical interface is configured and running.
2450          *
2451          * @returns {boolean}
2452          * Returns `true` when the interface is active or `false` when it is not.
2453          */
2454         isUp: function() {
2455                 return (this._ubus('up') == true);
2456         },
2457
2458         /**
2459          * Add the given network device to the logical interface.
2460          *
2461          * @param {LuCI.network.Protocol|LuCI.network.Device|LuCI.network.WifiDevice|LuCI.network.WifiNetwork|string} device
2462          * The object or device name to add to the logical interface. In case the
2463          * given argument is not a string, it is resolved though the
2464          * {@link LuCI.network#getIfnameOf Network.getIfnameOf()} function.
2465          *
2466          * @returns {boolean}
2467          * Returns `true` if the device name has been added or `false` if any
2468          * argument was invalid, if the device was already part of the logical
2469          * interface or if the logical interface is virtual.
2470          */
2471         addDevice: function(ifname) {
2472                 ifname = ifnameOf(ifname);
2473
2474                 if (ifname == null || this.isFloating())
2475                         return false;
2476
2477                 var wif = getWifiSidByIfname(ifname);
2478
2479                 if (wif != null)
2480                         return appendValue('wireless', wif, 'network', this.sid);
2481
2482                 return appendValue('network', this.sid, 'ifname', ifname);
2483         },
2484
2485         /**
2486          * Remove the given network device from the logical interface.
2487          *
2488          * @param {LuCI.network.Protocol|LuCI.network.Device|LuCI.network.WifiDevice|LuCI.network.WifiNetwork|string} device
2489          * The object or device name to remove from the logical interface. In case
2490          * the given argument is not a string, it is resolved though the
2491          * {@link LuCI.network#getIfnameOf Network.getIfnameOf()} function.
2492          *
2493          * @returns {boolean}
2494          * Returns `true` if the device name has been added or `false` if any
2495          * argument was invalid, if the device was already part of the logical
2496          * interface or if the logical interface is virtual.
2497          */
2498         deleteDevice: function(ifname) {
2499                 var rv = false;
2500
2501                 ifname = ifnameOf(ifname);
2502
2503                 if (ifname == null || this.isFloating())
2504                         return false;
2505
2506                 var wif = getWifiSidByIfname(ifname);
2507
2508                 if (wif != null)
2509                         rv = removeValue('wireless', wif, 'network', this.sid);
2510
2511                 if (removeValue('network', this.sid, 'ifname', ifname))
2512                         rv = true;
2513
2514                 return rv;
2515         },
2516
2517         /**
2518          * Returns the Linux network device associated with this logical
2519          * interface.
2520          *
2521          * @returns {LuCI.network.Device}
2522          * Returns a `Network.Device` class instance representing the
2523          * expected Linux network device according to the configuration.
2524          */
2525         getDevice: function() {
2526                 if (this.isVirtual()) {
2527                         var ifname = '%s-%s'.format(this.getProtocol(), this.sid);
2528                         _state.isTunnel[this.getProtocol() + '-' + this.sid] = true;
2529                         return Network.prototype.instantiateDevice(ifname, this);
2530                 }
2531                 else if (this.isBridge()) {
2532                         var ifname = 'br-%s'.format(this.sid);
2533                         _state.isBridge[ifname] = true;
2534                         return new Device(ifname, this);
2535                 }
2536                 else {
2537                         var ifnames = L.toArray(uci.get('network', this.sid, 'ifname'));
2538
2539                         for (var i = 0; i < ifnames.length; i++) {
2540                                 var m = ifnames[i].match(/^([^:/]+)/);
2541                                 return ((m && m[1]) ? Network.prototype.instantiateDevice(m[1], this) : null);
2542                         }
2543
2544                         ifname = getWifiNetidByNetname(this.sid);
2545
2546                         return (ifname != null ? Network.prototype.instantiateDevice(ifname[0], this) : null);
2547                 }
2548         },
2549
2550         /**
2551          * Returns the layer 2 linux network device currently associated
2552          * with this logical interface.
2553          *
2554          * @returns {LuCI.network.Device}
2555          * Returns a `Network.Device` class instance representing the Linux
2556          * network device currently associated with the logical interface.
2557          */
2558         getL2Device: function() {
2559                 var ifname = this._ubus('device');
2560                 return (ifname != null ? Network.prototype.instantiateDevice(ifname, this) : null);
2561         },
2562
2563         /**
2564          * Returns the layer 3 linux network device currently associated
2565          * with this logical interface.
2566          *
2567          * @returns {LuCI.network.Device}
2568          * Returns a `Network.Device` class instance representing the Linux
2569          * network device currently associated with the logical interface.
2570          */
2571         getL3Device: function() {
2572                 var ifname = this._ubus('l3_device');
2573                 return (ifname != null ? Network.prototype.instantiateDevice(ifname, this) : null);
2574         },
2575
2576         /**
2577          * Returns a list of network sub-devices associated with this logical
2578          * interface.
2579          *
2580          * @returns {null|Array<LuCI.network.Device>}
2581          * Returns an array of of `Network.Device` class instances representing
2582          * the sub-devices attached to this logical interface or `null` if the
2583          * logical interface does not support sub-devices, e.g. because it is
2584          * virtual and not a bridge.
2585          */
2586         getDevices: function() {
2587                 var rv = [];
2588
2589                 if (!this.isBridge() && !(this.isVirtual() && !this.isFloating()))
2590                         return null;
2591
2592                 var ifnames = L.toArray(uci.get('network', this.sid, 'ifname'));
2593
2594                 for (var i = 0; i < ifnames.length; i++) {
2595                         if (ifnames[i].charAt(0) == '@')
2596                                 continue;
2597
2598                         var m = ifnames[i].match(/^([^:/]+)/);
2599                         if (m != null)
2600                                 rv.push(Network.prototype.instantiateDevice(m[1], this));
2601                 }
2602
2603                 var uciWifiIfaces = uci.sections('wireless', 'wifi-iface');
2604
2605                 for (var i = 0; i < uciWifiIfaces.length; i++) {
2606                         if (typeof(uciWifiIfaces[i].device) != 'string')
2607                                 continue;
2608
2609                         var networks = L.toArray(uciWifiIfaces[i].network);
2610
2611                         for (var j = 0; j < networks.length; j++) {
2612                                 if (networks[j] != this.sid)
2613                                         continue;
2614
2615                                 var netid = getWifiNetidBySid(uciWifiIfaces[i]['.name']);
2616
2617                                 if (netid != null)
2618                                         rv.push(Network.prototype.instantiateDevice(netid[0], this));
2619                         }
2620                 }
2621
2622                 rv.sort(deviceSort);
2623
2624                 return rv;
2625         },
2626
2627         /**
2628          * Checks whether this logical interface contains the given device
2629          * object.
2630          *
2631          * @param {LuCI.network.Protocol|LuCI.network.Device|LuCI.network.WifiDevice|LuCI.network.WifiNetwork|string} device
2632          * The object or device name to check. In case the given argument is not
2633          * a string, it is resolved though the
2634          * {@link LuCI.network#getIfnameOf Network.getIfnameOf()} function.
2635          *
2636          * @returns {boolean}
2637          * Returns `true` when this logical interface contains the given network
2638          * device or `false` if not.
2639          */
2640         containsDevice: function(ifname) {
2641                 ifname = ifnameOf(ifname);
2642
2643                 if (ifname == null)
2644                         return false;
2645                 else if (this.isVirtual() && '%s-%s'.format(this.getProtocol(), this.sid) == ifname)
2646                         return true;
2647                 else if (this.isBridge() && 'br-%s'.format(this.sid) == ifname)
2648                         return true;
2649
2650                 var ifnames = L.toArray(uci.get('network', this.sid, 'ifname'));
2651
2652                 for (var i = 0; i < ifnames.length; i++) {
2653                         var m = ifnames[i].match(/^([^:/]+)/);
2654                         if (m != null && m[1] == ifname)
2655                                 return true;
2656                 }
2657
2658                 var wif = getWifiSidByIfname(ifname);
2659
2660                 if (wif != null) {
2661                         var networks = L.toArray(uci.get('wireless', wif, 'network'));
2662
2663                         for (var i = 0; i < networks.length; i++)
2664                                 if (networks[i] == this.sid)
2665                                         return true;
2666                 }
2667
2668                 return false;
2669         },
2670
2671         /**
2672          * Cleanup related configuration entries.
2673          *
2674          * This function will be invoked if an interface is about to be removed
2675          * from the configuration and is responsible for performing any required
2676          * cleanup tasks, such as unsetting uci entries in related configurations.
2677          *
2678          * It should be overwritten by protocol specific subclasses.
2679          *
2680          * @abstract
2681          *
2682          * @returns {*|Promise<*>}
2683          * This function may return a promise which is awaited before the rest of
2684          * the configuration is removed. Any non-promise return value and any
2685          * resolved promise value is ignored. If the returned promise is rejected,
2686          * the interface removal will be aborted.
2687          */
2688         deleteConfiguration: function() {}
2689 });
2690
2691 /**
2692  * @class
2693  * @memberof LuCI.network
2694  * @hideconstructor
2695  * @classdesc
2696  *
2697  * A `Network.Device` class instance represents an underlying Linux network
2698  * device and allows querying device details such as packet statistics or MTU.
2699  */
2700 Device = baseclass.extend(/** @lends LuCI.network.Device.prototype */ {
2701         __init__: function(ifname, network) {
2702                 var wif = getWifiSidByIfname(ifname);
2703
2704                 if (wif != null) {
2705                         var res = getWifiStateBySid(wif) || [],
2706                             netid = getWifiNetidBySid(wif) || [];
2707
2708                         this.wif    = new WifiNetwork(wif, res[0], res[1], netid[0], res[2], { ifname: ifname });
2709                         this.ifname = this.wif.getIfname();
2710                 }
2711
2712                 this.ifname  = this.ifname || ifname;
2713                 this.dev     = _state.netdevs[this.ifname];
2714                 this.network = network;
2715         },
2716
2717         _devstate: function(/* ... */) {
2718                 var rv = this.dev;
2719
2720                 for (var i = 0; i < arguments.length; i++)
2721                         if (L.isObject(rv))
2722                                 rv = rv[arguments[i]];
2723                         else
2724                                 return null;
2725
2726                 return rv;
2727         },
2728
2729         /**
2730          * Get the name of the network device.
2731          *
2732          * @returns {string}
2733          * Returns the name of the device, e.g. `eth0` or `wlan0`.
2734          */
2735         getName: function() {
2736                 return (this.wif != null ? this.wif.getIfname() : this.ifname);
2737         },
2738
2739         /**
2740          * Get the MAC address of the device.
2741          *
2742          * @returns {null|string}
2743          * Returns the MAC address of the device or `null` if not applicable,
2744          * e.g. for non-ethernet tunnel devices.
2745          */
2746         getMAC: function() {
2747                 var mac = this._devstate('macaddr');
2748                 return mac ? mac.toUpperCase() : null;
2749         },
2750
2751         /**
2752          * Get the MTU of the device.
2753          *
2754          * @returns {number}
2755          * Returns the MTU of the device.
2756          */
2757         getMTU: function() {
2758                 return this._devstate('mtu');
2759         },
2760
2761         /**
2762          * Get the IPv4 addresses configured on the device.
2763          *
2764          * @returns {string[]}
2765          * Returns an array of IPv4 address strings.
2766          */
2767         getIPAddrs: function() {
2768                 var addrs = this._devstate('ipaddrs');
2769                 return (Array.isArray(addrs) ? addrs : []);
2770         },
2771
2772         /**
2773          * Get the IPv6 addresses configured on the device.
2774          *
2775          * @returns {string[]}
2776          * Returns an array of IPv6 address strings.
2777          */
2778         getIP6Addrs: function() {
2779                 var addrs = this._devstate('ip6addrs');
2780                 return (Array.isArray(addrs) ? addrs : []);
2781         },
2782
2783         /**
2784          * Get the type of the device..
2785          *
2786          * @returns {string}
2787          * Returns a string describing the type of the network device:
2788          *  - `alias` if it is an abstract alias device (`@` notation)
2789          *  - `wifi` if it is a wireless interface (e.g. `wlan0`)
2790          *  - `bridge` if it is a bridge device (e.g. `br-lan`)
2791          *  - `tunnel` if it is a tun or tap device (e.g. `tun0`)
2792          *  - `vlan` if it is a vlan device (e.g. `eth0.1`)
2793          *  - `switch` if it is a switch device (e.g.`eth1` connected to switch0)
2794          *  - `ethernet` for all other device types
2795          */
2796         getType: function() {
2797                 if (this.ifname != null && this.ifname.charAt(0) == '@')
2798                         return 'alias';
2799                 else if (this.wif != null || isWifiIfname(this.ifname))
2800                         return 'wifi';
2801                 else if (_state.isBridge[this.ifname])
2802                         return 'bridge';
2803                 else if (_state.isTunnel[this.ifname])
2804                         return 'tunnel';
2805                 else if (this.ifname.indexOf('.') > -1)
2806                         return 'vlan';
2807                 else if (_state.isSwitch[this.ifname])
2808                         return 'switch';
2809                 else
2810                         return 'ethernet';
2811         },
2812
2813         /**
2814          * Get a short description string for the device.
2815          *
2816          * @returns {string}
2817          * Returns the device name for non-wifi devices or a string containing
2818          * the operation mode and SSID for wifi devices.
2819          */
2820         getShortName: function() {
2821                 if (this.wif != null)
2822                         return this.wif.getShortName();
2823
2824                 return this.ifname;
2825         },
2826
2827         /**
2828          * Get a long description string for the device.
2829          *
2830          * @returns {string}
2831          * Returns a string containing the type description and device name
2832          * for non-wifi devices or operation mode and ssid for wifi ones.
2833          */
2834         getI18n: function() {
2835                 if (this.wif != null) {
2836                         return '%s: %s "%s"'.format(
2837                                 _('Wireless Network'),
2838                                 this.wif.getActiveMode(),
2839                                 this.wif.getActiveSSID() || this.wif.getActiveBSSID() || this.wif.getID() || '?');
2840                 }
2841
2842                 return '%s: "%s"'.format(this.getTypeI18n(), this.getName());
2843         },
2844
2845         /**
2846          * Get a string describing the device type.
2847          *
2848          * @returns {string}
2849          * Returns a string describing the type, e.g. "Wireless Adapter" or
2850          * "Bridge".
2851          */
2852         getTypeI18n: function() {
2853                 switch (this.getType()) {
2854                 case 'alias':
2855                         return _('Alias Interface');
2856
2857                 case 'wifi':
2858                         return _('Wireless Adapter');
2859
2860                 case 'bridge':
2861                         return _('Bridge');
2862
2863                 case 'switch':
2864                         return _('Ethernet Switch');
2865
2866                 case 'vlan':
2867                         return (_state.isSwitch[this.ifname] ? _('Switch VLAN') : _('Software VLAN'));
2868
2869                 case 'tunnel':
2870                         return _('Tunnel Interface');
2871
2872                 default:
2873                         return _('Ethernet Adapter');
2874                 }
2875         },
2876
2877         /**
2878          * Get the associated bridge ports of the device.
2879          *
2880          * @returns {null|Array<LuCI.network.Device>}
2881          * Returns an array of `Network.Device` instances representing the ports
2882          * (slave interfaces) of the bridge or `null` when this device isn't
2883          * a Linux bridge.
2884          */
2885         getPorts: function() {
2886                 var br = _state.bridges[this.ifname],
2887                     rv = [];
2888
2889                 if (br == null || !Array.isArray(br.ifnames))
2890                         return null;
2891
2892                 for (var i = 0; i < br.ifnames.length; i++)
2893                         rv.push(Network.prototype.instantiateDevice(br.ifnames[i].name));
2894
2895                 rv.sort(deviceSort);
2896
2897                 return rv;
2898         },
2899
2900         /**
2901          * Get the bridge ID
2902          *
2903          * @returns {null|string}
2904          * Returns the ID of this network bridge or `null` if this network
2905          * device is not a Linux bridge.
2906          */
2907         getBridgeID: function() {
2908                 var br = _state.bridges[this.ifname];
2909                 return (br != null ? br.id : null);
2910         },
2911
2912         /**
2913          * Get the bridge STP setting
2914          *
2915          * @returns {boolean}
2916          * Returns `true` when this device is a Linux bridge and has `stp`
2917          * enabled, else `false`.
2918          */
2919         getBridgeSTP: function() {
2920                 var br = _state.bridges[this.ifname];
2921                 return (br != null ? !!br.stp : false);
2922         },
2923
2924         /**
2925          * Checks whether this device is up.
2926          *
2927          * @returns {boolean}
2928          * Returns `true` when the associated device is running pr `false`
2929          * when it is down or absent.
2930          */
2931         isUp: function() {
2932                 var up = this._devstate('flags', 'up');
2933
2934                 if (up == null)
2935                         up = (this.getType() == 'alias');
2936
2937                 return up;
2938         },
2939
2940         /**
2941          * Checks whether this device is a Linux bridge.
2942          *
2943          * @returns {boolean}
2944          * Returns `true` when the network device is present and a Linux bridge,
2945          * else `false`.
2946          */
2947         isBridge: function() {
2948                 return (this.getType() == 'bridge');
2949         },
2950
2951         /**
2952          * Checks whether this device is part of a Linux bridge.
2953          *
2954          * @returns {boolean}
2955          * Returns `true` when this network device is part of a bridge,
2956          * else `false`.
2957          */
2958         isBridgePort: function() {
2959                 return (this._devstate('bridge') != null);
2960         },
2961
2962         /**
2963          * Get the amount of transmitted bytes.
2964          *
2965          * @returns {number}
2966          * Returns the amount of bytes transmitted by the network device.
2967          */
2968         getTXBytes: function() {
2969                 var stat = this._devstate('stats');
2970                 return (stat != null ? stat.tx_bytes || 0 : 0);
2971         },
2972
2973         /**
2974          * Get the amount of received bytes.
2975          *
2976          * @returns {number}
2977          * Returns the amount of bytes received by the network device.
2978          */
2979         getRXBytes: function() {
2980                 var stat = this._devstate('stats');
2981                 return (stat != null ? stat.rx_bytes || 0 : 0);
2982         },
2983
2984         /**
2985          * Get the amount of transmitted packets.
2986          *
2987          * @returns {number}
2988          * Returns the amount of packets transmitted by the network device.
2989          */
2990         getTXPackets: function() {
2991                 var stat = this._devstate('stats');
2992                 return (stat != null ? stat.tx_packets || 0 : 0);
2993         },
2994
2995         /**
2996          * Get the amount of received packets.
2997          *
2998          * @returns {number}
2999          * Returns the amount of packets received by the network device.
3000          */
3001         getRXPackets: function() {
3002                 var stat = this._devstate('stats');
3003                 return (stat != null ? stat.rx_packets || 0 : 0);
3004         },
3005
3006         /**
3007          * Get the primary logical interface this device is assigned to.
3008          *
3009          * @returns {null|LuCI.network.Protocol}
3010          * Returns a `Network.Protocol` instance representing the logical
3011          * interface this device is attached to or `null` if it is not
3012          * assigned to any logical interface.
3013          */
3014         getNetwork: function() {
3015                 return this.getNetworks()[0];
3016         },
3017
3018         /**
3019          * Get the logical interfaces this device is assigned to.
3020          *
3021          * @returns {Array<LuCI.network.Protocol>}
3022          * Returns an array of `Network.Protocol` instances representing the
3023          * logical interfaces this device is assigned to.
3024          */
3025         getNetworks: function() {
3026                 if (this.networks == null) {
3027                         this.networks = [];
3028
3029                         var networks = enumerateNetworks.apply(L.network);
3030
3031                         for (var i = 0; i < networks.length; i++)
3032                                 if (networks[i].containsDevice(this.ifname) || networks[i].getIfname() == this.ifname)
3033                                         this.networks.push(networks[i]);
3034
3035                         this.networks.sort(networkSort);
3036                 }
3037
3038                 return this.networks;
3039         },
3040
3041         /**
3042          * Get the related wireless network this device is related to.
3043          *
3044          * @returns {null|LuCI.network.WifiNetwork}
3045          * Returns a `Network.WifiNetwork` instance representing the wireless
3046          * network corresponding to this network device or `null` if this device
3047          * is not a wireless device.
3048          */
3049         getWifiNetwork: function() {
3050                 return (this.wif != null ? this.wif : null);
3051         }
3052 });
3053
3054 /**
3055  * @class
3056  * @memberof LuCI.network
3057  * @hideconstructor
3058  * @classdesc
3059  *
3060  * A `Network.WifiDevice` class instance represents a wireless radio device
3061  * present on the system and provides wireless capability information as
3062  * well as methods for enumerating related wireless networks.
3063  */
3064 WifiDevice = baseclass.extend(/** @lends LuCI.network.WifiDevice.prototype */ {
3065         __init__: function(name, radiostate) {
3066                 var uciWifiDevice = uci.get('wireless', name);
3067
3068                 if (uciWifiDevice != null &&
3069                     uciWifiDevice['.type'] == 'wifi-device' &&
3070                     uciWifiDevice['.name'] != null) {
3071                         this.sid    = uciWifiDevice['.name'];
3072                 }
3073
3074                 this.sid    = this.sid || name;
3075                 this._ubusdata = {
3076                         radio: name,
3077                         dev:   radiostate
3078                 };
3079         },
3080
3081         /* private */
3082         ubus: function(/* ... */) {
3083                 var v = this._ubusdata;
3084
3085                 for (var i = 0; i < arguments.length; i++)
3086                         if (L.isObject(v))
3087                                 v = v[arguments[i]];
3088                         else
3089                                 return null;
3090
3091                 return v;
3092         },
3093
3094         /**
3095          * Read the given UCI option value of this wireless device.
3096          *
3097          * @param {string} opt
3098          * The UCI option name to read.
3099          *
3100          * @returns {null|string|string[]}
3101          * Returns the UCI option value or `null` if the requested option is
3102          * not found.
3103          */
3104         get: function(opt) {
3105                 return uci.get('wireless', this.sid, opt);
3106         },
3107
3108         /**
3109          * Set the given UCI option of this network to the given value.
3110          *
3111          * @param {string} opt
3112          * The name of the UCI option to set.
3113          *
3114          * @param {null|string|string[]} val
3115          * The value to set or `null` to remove the given option from the
3116          * configuration.
3117          */
3118         set: function(opt, value) {
3119                 return uci.set('wireless', this.sid, opt, value);
3120         },
3121
3122         /**
3123          * Checks whether this wireless radio is disabled.
3124          *
3125          * @returns {boolean}
3126          * Returns `true` when the wireless radio is marked as disabled in `ubus`
3127          * runtime state or when the `disabled` option is set in the corresponding
3128          * UCI configuration.
3129          */
3130         isDisabled: function() {
3131                 return this.ubus('dev', 'disabled') || this.get('disabled') == '1';
3132         },
3133
3134         /**
3135          * Get the configuration name of this wireless radio.
3136          *
3137          * @returns {string}
3138          * Returns the UCI section name (e.g. `radio0`) of the corresponding
3139          * radio configuration which also serves as unique logical identifier
3140          * for the wireless phy.
3141          */
3142         getName: function() {
3143                 return this.sid;
3144         },
3145
3146         /**
3147          * Gets a list of supported hwmodes.
3148          *
3149          * The hwmode values describe the frequency band and wireless standard
3150          * versions supported by the wireless phy.
3151          *
3152          * @returns {string[]}
3153          * Returns an array of valid hwmode values for this radio. Currently
3154          * known mode values are:
3155          *  - `a` - Legacy 802.11a mode, 5 GHz, up to 54 Mbit/s
3156          *  - `b` - Legacy 802.11b mode, 2.4 GHz, up to 11 Mbit/s
3157          *  - `g` - Legacy 802.11g mode, 2.4 GHz, up to 54 Mbit/s
3158          *  - `n` - IEEE 802.11n mode, 2.4 or 5 GHz, up to 600 Mbit/s
3159          *  - `ac` - IEEE 802.11ac mode, 5 GHz, up to 6770 Mbit/s
3160          */
3161         getHWModes: function() {
3162                 var hwmodes = this.ubus('dev', 'iwinfo', 'hwmodes');
3163                 return Array.isArray(hwmodes) ? hwmodes : [ 'b', 'g' ];
3164         },
3165
3166         /**
3167          * Gets a list of supported htmodes.
3168          *
3169          * The htmode values describe the wide-frequency options supported by
3170          * the wireless phy.
3171          *
3172          * @returns {string[]}
3173          * Returns an array of valid htmode values for this radio. Currently
3174          * known mode values are:
3175          *  - `HT20` - applicable to IEEE 802.11n, 20 MHz wide channels
3176          *  - `HT40` - applicable to IEEE 802.11n, 40 MHz wide channels
3177          *  - `VHT20` - applicable to IEEE 802.11ac, 20 MHz wide channels
3178          *  - `VHT40` - applicable to IEEE 802.11ac, 40 MHz wide channels
3179          *  - `VHT80` - applicable to IEEE 802.11ac, 80 MHz wide channels
3180          *  - `VHT160` - applicable to IEEE 802.11ac, 160 MHz wide channels
3181          */
3182         getHTModes: function() {
3183                 var htmodes = this.ubus('dev', 'iwinfo', 'htmodes');
3184                 return (Array.isArray(htmodes) && htmodes.length) ? htmodes : null;
3185         },
3186
3187         /**
3188          * Get a string describing the wireless radio hardware.
3189          *
3190          * @returns {string}
3191          * Returns the description string.
3192          */
3193         getI18n: function() {
3194                 var hw = this.ubus('dev', 'iwinfo', 'hardware'),
3195                     type = L.isObject(hw) ? hw.name : null;
3196
3197                 if (this.ubus('dev', 'iwinfo', 'type') == 'wl')
3198                         type = 'Broadcom';
3199
3200                 var hwmodes = this.getHWModes(),
3201                     modestr = '';
3202
3203                 hwmodes.sort(function(a, b) {
3204                         return (a.length != b.length ? a.length > b.length : a > b);
3205                 });
3206
3207                 modestr = hwmodes.join('');
3208
3209                 return '%s 802.11%s Wireless Controller (%s)'.format(type || 'Generic', modestr, this.getName());
3210         },
3211
3212         /**
3213          * A wireless scan result object describes a neighbouring wireless
3214          * network found in the vincinity.
3215          *
3216          * @typedef {Object<string, number|string|LuCI.network.WifiEncryption>} WifiScanResult
3217          * @memberof LuCI.network
3218          *
3219          * @property {string} ssid
3220          * The SSID / Mesh ID of the network.
3221          *
3222          * @property {string} bssid
3223          * The BSSID if the network.
3224          *
3225          * @property {string} mode
3226          * The operation mode of the network (`Master`, `Ad-Hoc`, `Mesh Point`).
3227          *
3228          * @property {number} channel
3229          * The wireless channel of the network.
3230          *
3231          * @property {number} signal
3232          * The received signal strength of the network in dBm.
3233          *
3234          * @property {number} quality
3235          * The numeric quality level of the signal, can be used in conjunction
3236          * with `quality_max` to calculate a quality percentage.
3237          *
3238          * @property {number} quality_max
3239          * The maximum possible quality level of the signal, can be used in
3240          * conjunction with `quality` to calculate a quality percentage.
3241          *
3242          * @property {LuCI.network.WifiEncryption} encryption
3243          * The encryption used by the wireless network.
3244          */
3245
3246         /**
3247          * Trigger a wireless scan on this radio device and obtain a list of
3248          * nearby networks.
3249          *
3250          * @returns {Promise<Array<LuCI.network.WifiScanResult>>}
3251          * Returns a promise resolving to an array of scan result objects
3252          * describing the networks found in the vincinity.
3253          */
3254         getScanList: function() {
3255                 return callIwinfoScan(this.sid);
3256         },
3257
3258         /**
3259          * Check whether the wireless radio is marked as up in the `ubus`
3260          * runtime state.
3261          *
3262          * @returns {boolean}
3263          * Returns `true` when the radio device is up, else `false`.
3264          */
3265         isUp: function() {
3266                 if (L.isObject(_state.radios[this.sid]))
3267                         return (_state.radios[this.sid].up == true);
3268
3269                 return false;
3270         },
3271
3272         /**
3273          * Get the wifi network of the given name belonging to this radio device
3274          *
3275          * @param {string} network
3276          * The name of the wireless network to lookup. This may be either an uci
3277          * configuration section ID, a network ID in the form `radio#.network#`
3278          * or a Linux network device name like `wlan0` which is resolved to the
3279          * corresponding configuration section through `ubus` runtime information.
3280          *
3281          * @returns {Promise<LuCI.network.WifiNetwork>}
3282          * Returns a promise resolving to a `Network.WifiNetwork` instance
3283          * representing the wireless network and rejecting with `null` if
3284          * the given network could not be found or is not associated with
3285          * this radio device.
3286          */
3287         getWifiNetwork: function(network) {
3288                 return Network.prototype.getWifiNetwork(network).then(L.bind(function(networkInstance) {
3289                         var uciWifiIface = (networkInstance.sid ? uci.get('wireless', networkInstance.sid) : null);
3290
3291                         if (uciWifiIface == null || uciWifiIface['.type'] != 'wifi-iface' || uciWifiIface.device != this.sid)
3292                                 return Promise.reject();
3293
3294                         return networkInstance;
3295                 }, this));
3296         },
3297
3298         /**
3299          * Get all wireless networks associated with this wireless radio device.
3300          *
3301          * @returns {Promise<Array<LuCI.network.WifiNetwork>>}
3302          * Returns a promise resolving to an array of `Network.WifiNetwork`
3303          * instances respresenting the wireless networks associated with this
3304          * radio device.
3305          */
3306         getWifiNetworks: function() {
3307                 return Network.prototype.getWifiNetworks().then(L.bind(function(networks) {
3308                         var rv = [];
3309
3310                         for (var i = 0; i < networks.length; i++)
3311                                 if (networks[i].getWifiDeviceName() == this.getName())
3312                                         rv.push(networks[i]);
3313
3314                         return rv;
3315                 }, this));
3316         },
3317
3318         /**
3319          * Adds a new wireless network associated with this radio device to the
3320          * configuration and sets its options to the provided values.
3321          *
3322          * @param {Object<string, string|string[]>} [options]
3323          * The options to set for the newly added wireless network.
3324          *
3325          * @returns {Promise<null|LuCI.network.WifiNetwork>}
3326          * Returns a promise resolving to a `WifiNetwork` instance describing
3327          * the newly added wireless network or `null` if the given options
3328          * were invalid.
3329          */
3330         addWifiNetwork: function(options) {
3331                 if (!L.isObject(options))
3332                         options = {};
3333
3334                 options.device = this.sid;
3335
3336                 return Network.prototype.addWifiNetwork(options);
3337         },
3338
3339         /**
3340          * Deletes the wireless network with the given name associated with this
3341          * radio device.
3342          *
3343          * @param {string} network
3344          * The name of the wireless network to lookup. This may be either an uci
3345          * configuration section ID, a network ID in the form `radio#.network#`
3346          * or a Linux network device name like `wlan0` which is resolved to the
3347          * corresponding configuration section through `ubus` runtime information.
3348          *
3349          * @returns {Promise<boolean>}
3350          * Returns a promise resolving to `true` when the wireless network was
3351          * successfully deleted from the configuration or `false` when the given
3352          * network could not be found or if the found network was not associated
3353          * with this wireless radio device.
3354          */
3355         deleteWifiNetwork: function(network) {
3356                 var sid = null;
3357
3358                 if (network instanceof WifiNetwork) {
3359                         sid = network.sid;
3360                 }
3361                 else {
3362                         var uciWifiIface = uci.get('wireless', network);
3363
3364                         if (uciWifiIface == null || uciWifiIface['.type'] != 'wifi-iface')
3365                                 sid = getWifiSidByIfname(network);
3366                 }
3367
3368                 if (sid == null || uci.get('wireless', sid, 'device') != this.sid)
3369                         return Promise.resolve(false);
3370
3371                 uci.delete('wireless', network);
3372
3373                 return Promise.resolve(true);
3374         }
3375 });
3376
3377 /**
3378  * @class
3379  * @memberof LuCI.network
3380  * @hideconstructor
3381  * @classdesc
3382  *
3383  * A `Network.WifiNetwork` instance represents a wireless network (vif)
3384  * configured on top of a radio device and provides functions for querying
3385  * the runtime state of the network. Most radio devices support multiple
3386  * such networks in parallel.
3387  */
3388 WifiNetwork = baseclass.extend(/** @lends LuCI.network.WifiNetwork.prototype */ {
3389         __init__: function(sid, radioname, radiostate, netid, netstate, hostapd) {
3390                 this.sid    = sid;
3391                 this.netid  = netid;
3392                 this._ubusdata = {
3393                         hostapd: hostapd,
3394                         radio:   radioname,
3395                         dev:     radiostate,
3396                         net:     netstate
3397                 };
3398         },
3399
3400         ubus: function(/* ... */) {
3401                 var v = this._ubusdata;
3402
3403                 for (var i = 0; i < arguments.length; i++)
3404                         if (L.isObject(v))
3405                                 v = v[arguments[i]];
3406                         else
3407                                 return null;
3408
3409                 return v;
3410         },
3411
3412         /**
3413          * Read the given UCI option value of this wireless network.
3414          *
3415          * @param {string} opt
3416          * The UCI option name to read.
3417          *
3418          * @returns {null|string|string[]}
3419          * Returns the UCI option value or `null` if the requested option is
3420          * not found.
3421          */
3422         get: function(opt) {
3423                 return uci.get('wireless', this.sid, opt);
3424         },
3425
3426         /**
3427          * Set the given UCI option of this network to the given value.
3428          *
3429          * @param {string} opt
3430          * The name of the UCI option to set.
3431          *
3432          * @param {null|string|string[]} val
3433          * The value to set or `null` to remove the given option from the
3434          * configuration.
3435          */
3436         set: function(opt, value) {
3437                 return uci.set('wireless', this.sid, opt, value);
3438         },
3439
3440         /**
3441          * Checks whether this wireless network is disabled.
3442          *
3443          * @returns {boolean}
3444          * Returns `true` when the wireless radio is marked as disabled in `ubus`
3445          * runtime state or when the `disabled` option is set in the corresponding
3446          * UCI configuration.
3447          */
3448         isDisabled: function() {
3449                 return this.ubus('dev', 'disabled') || this.get('disabled') == '1';
3450         },
3451
3452         /**
3453          * Get the configured operation mode of the wireless network.
3454          *
3455          * @returns {string}
3456          * Returns the configured operation mode. Possible values are:
3457          *  - `ap` - Master (Access Point) mode
3458          *  - `sta` - Station (client) mode
3459          *  - `adhoc` - Ad-Hoc (IBSS) mode
3460          *  - `mesh` - Mesh (IEEE 802.11s) mode
3461          *  - `monitor` - Monitor mode
3462          */
3463         getMode: function() {
3464                 return this.ubus('net', 'config', 'mode') || this.get('mode') || 'ap';
3465         },
3466
3467         /**
3468          * Get the configured SSID of the wireless network.
3469          *
3470          * @returns {null|string}
3471          * Returns the configured SSID value or `null` when this network is
3472          * in mesh mode.
3473          */
3474         getSSID: function() {
3475                 if (this.getMode() == 'mesh')
3476                         return null;
3477
3478                 return this.ubus('net', 'config', 'ssid') || this.get('ssid');
3479         },
3480
3481         /**
3482          * Get the configured Mesh ID of the wireless network.
3483          *
3484          * @returns {null|string}
3485          * Returns the configured mesh ID value or `null` when this network
3486          * is not in mesh mode.
3487          */
3488         getMeshID: function() {
3489                 if (this.getMode() != 'mesh')
3490                         return null;
3491
3492                 return this.ubus('net', 'config', 'mesh_id') || this.get('mesh_id');
3493         },
3494
3495         /**
3496          * Get the configured BSSID of the wireless network.
3497          *
3498          * @returns {null|string}
3499          * Returns the BSSID value or `null` if none has been specified.
3500          */
3501         getBSSID: function() {
3502                 return this.ubus('net', 'config', 'bssid') || this.get('bssid');
3503         },
3504
3505         /**
3506          * Get the names of the logical interfaces this wireless network is
3507          * attached to.
3508          *
3509          * @returns {string[]}
3510          * Returns an array of logical interface names.
3511          */
3512         getNetworkNames: function() {
3513                 return L.toArray(this.ubus('net', 'config', 'network') || this.get('network'));
3514         },
3515
3516         /**
3517          * Get the internal network ID of this wireless network.
3518          *
3519          * The network ID is a LuCI specific identifer in the form
3520          * `radio#.network#` to identify wireless networks by their corresponding
3521          * radio and network index numbers.
3522          *
3523          * @returns {string}
3524          * Returns the LuCI specific network ID.
3525          */
3526         getID: function() {
3527                 return this.netid;
3528         },
3529
3530         /**
3531          * Get the configuration ID of this wireless network.
3532          *
3533          * @returns {string}
3534          * Returns the corresponding UCI section ID of the network.
3535          */
3536         getName: function() {
3537                 return this.sid;
3538         },
3539
3540         /**
3541          * Get the Linux network device name.
3542          *
3543          * @returns {null|string}
3544          * Returns the current Linux network device name as resolved from
3545          * `ubus` runtime information or `null` if this network has no
3546          * associated network device, e.g. when not configured or up.
3547          */
3548         getIfname: function() {
3549                 var ifname = this.ubus('net', 'ifname') || this.ubus('net', 'iwinfo', 'ifname');
3550
3551                 if (ifname == null || ifname.match(/^(wifi|radio)\d/))
3552                         ifname = this.netid;
3553
3554                 return ifname;
3555         },
3556
3557         /**
3558          * Get the name of the corresponding wifi radio device.
3559          *
3560          * @returns {null|string}
3561          * Returns the name of the radio device this network is configured on
3562          * or `null` if it cannot be determined.
3563          */
3564         getWifiDeviceName: function() {
3565                 return this.ubus('radio') || this.get('device');
3566         },
3567
3568         /**
3569          * Get the corresponding wifi radio device.
3570          *
3571          * @returns {null|LuCI.network.WifiDevice}
3572          * Returns a `Network.WifiDevice` instance representing the corresponding
3573          * wifi radio device or `null` if the related radio device could not be
3574          * found.
3575          */
3576         getWifiDevice: function() {
3577                 var radioname = this.getWifiDeviceName();
3578
3579                 if (radioname == null)
3580                         return Promise.reject();
3581
3582                 return Network.prototype.getWifiDevice(radioname);
3583         },
3584
3585         /**
3586          * Check whether the radio network is up.
3587          *
3588          * This function actually queries the up state of the related radio
3589          * device and assumes this network to be up as well when the parent
3590          * radio is up. This is due to the fact that OpenWrt does not control
3591          * virtual interfaces individually but within one common hostapd
3592          * instance.
3593          *
3594          * @returns {boolean}
3595          * Returns `true` when the network is up, else `false`.
3596          */
3597         isUp: function() {
3598                 var device = this.getDevice();
3599
3600                 if (device == null)
3601                         return false;
3602
3603                 return device.isUp();
3604         },
3605
3606         /**
3607          * Query the current operation mode from runtime information.
3608          *
3609          * @returns {string}
3610          * Returns the human readable mode name as reported by `ubus` runtime
3611          * state. Possible returned values are:
3612          *  - `Master`
3613          *  - `Ad-Hoc`
3614          *  - `Client`
3615          *  - `Monitor`
3616          *  - `Master (VLAN)`
3617          *  - `WDS`
3618          *  - `Mesh Point`
3619          *  - `P2P Client`
3620          *  - `P2P Go`
3621          *  - `Unknown`
3622          */
3623         getActiveMode: function() {
3624                 var mode = this.ubus('net', 'iwinfo', 'mode') || this.ubus('net', 'config', 'mode') || this.get('mode') || 'ap';
3625
3626                 switch (mode) {
3627                 case 'ap':      return 'Master';
3628                 case 'sta':     return 'Client';
3629                 case 'adhoc':   return 'Ad-Hoc';
3630                 case 'mesh':    return 'Mesh';
3631                 case 'monitor': return 'Monitor';
3632                 default:        return mode;
3633                 }
3634         },
3635
3636         /**
3637          * Query the current operation mode from runtime information as
3638          * translated string.
3639          *
3640          * @returns {string}
3641          * Returns the translated, human readable mode name as reported by
3642          *`ubus` runtime state.
3643          */
3644         getActiveModeI18n: function() {
3645                 var mode = this.getActiveMode();
3646
3647                 switch (mode) {
3648                 case 'Master':  return _('Master');
3649                 case 'Client':  return _('Client');
3650                 case 'Ad-Hoc':  return _('Ad-Hoc');
3651                 case 'Mash':    return _('Mesh');
3652                 case 'Monitor': return _('Monitor');
3653                 default:        return mode;
3654                 }
3655         },
3656
3657         /**
3658          * Query the current SSID from runtime information.
3659          *
3660          * @returns {string}
3661          * Returns the current SSID or Mesh ID as reported by `ubus` runtime
3662          * information.
3663          */
3664         getActiveSSID: function() {
3665                 return this.ubus('net', 'iwinfo', 'ssid') || this.ubus('net', 'config', 'ssid') || this.get('ssid');
3666         },
3667
3668         /**
3669          * Query the current BSSID from runtime information.
3670          *
3671          * @returns {string}
3672          * Returns the current BSSID or Mesh ID as reported by `ubus` runtime
3673          * information.
3674          */
3675         getActiveBSSID: function() {
3676                 return this.ubus('net', 'iwinfo', 'bssid') || this.ubus('net', 'config', 'bssid') || this.get('bssid');
3677         },
3678
3679         /**
3680          * Query the current encryption settings from runtime information.
3681          *
3682          * @returns {string}
3683          * Returns a string describing the current encryption or `-` if the the
3684          * encryption state could not be found in `ubus` runtime information.
3685          */
3686         getActiveEncryption: function() {
3687                 return formatWifiEncryption(this.ubus('net', 'iwinfo', 'encryption')) || '-';
3688         },
3689
3690         /**
3691          * A wireless peer entry describes the properties of a remote wireless
3692          * peer associated with a local network.
3693          *
3694          * @typedef {Object<string, boolean|number|string|LuCI.network.WifiRateEntry>} WifiPeerEntry
3695          * @memberof LuCI.network
3696          *
3697          * @property {string} mac
3698          * The MAC address (BSSID).
3699          *
3700          * @property {number} signal
3701          * The received signal strength.
3702          *
3703          * @property {number} [signal_avg]
3704          * The average signal strength if supported by the driver.
3705          *
3706          * @property {number} [noise]
3707          * The current noise floor of the radio. May be `0` or absent if not
3708          * supported by the driver.
3709          *
3710          * @property {number} inactive
3711          * The amount of milliseconds the peer has been inactive, e.g. due
3712          * to powersave.
3713          *
3714          * @property {number} connected_time
3715          * The amount of milliseconds the peer is associated to this network.
3716          *
3717          * @property {number} [thr]
3718          * The estimated throughput of the peer, May be `0` or absent if not
3719          * supported by the driver.
3720          *
3721          * @property {boolean} authorized
3722          * Specifies whether the peer is authorized to associate to this network.
3723          *
3724          * @property {boolean} authenticated
3725          * Specifies whether the peer completed authentication to this network.
3726          *
3727          * @property {string} preamble
3728          * The preamble mode used by the peer. May be `long` or `short`.
3729          *
3730          * @property {boolean} wme
3731          * Specifies whether the peer supports WME/WMM capabilities.
3732          *
3733          * @property {boolean} mfp
3734          * Specifies whether management frame protection is active.
3735          *
3736          * @property {boolean} tdls
3737          * Specifies whether TDLS is active.
3738          *
3739          * @property {number} [mesh llid]
3740          * The mesh LLID, may be `0` or absent if not applicable or supported
3741          * by the driver.
3742          *
3743          * @property {number} [mesh plid]
3744          * The mesh PLID, may be `0` or absent if not applicable or supported
3745          * by the driver.
3746          *
3747          * @property {string} [mesh plink]
3748          * The mesh peer link state description, may be an empty string (`''`)
3749          * or absent if not applicable or supported by the driver.
3750          *
3751          * The following states are known:
3752          *  - `LISTEN`
3753          *  - `OPN_SNT`
3754          *  - `OPN_RCVD`
3755          *  - `CNF_RCVD`
3756          *  - `ESTAB`
3757          *  - `HOLDING`
3758          *  - `BLOCKED`
3759          *  - `UNKNOWN`
3760          *
3761          * @property {number} [mesh local PS]
3762          * The local powersafe mode for the peer link, may be an empty
3763          * string (`''`) or absent if not applicable or supported by
3764          * the driver.
3765          *
3766          * The following modes are known:
3767          *  - `ACTIVE` (no power save)
3768          *  - `LIGHT SLEEP`
3769          *  - `DEEP SLEEP`
3770          *  - `UNKNOWN`
3771          *
3772          * @property {number} [mesh peer PS]
3773          * The remote powersafe mode for the peer link, may be an empty
3774          * string (`''`) or absent if not applicable or supported by
3775          * the driver.
3776          *
3777          * The following modes are known:
3778          *  - `ACTIVE` (no power save)
3779          *  - `LIGHT SLEEP`
3780          *  - `DEEP SLEEP`
3781          *  - `UNKNOWN`
3782          *
3783          * @property {number} [mesh non-peer PS]
3784          * The powersafe mode for all non-peer neigbours, may be an empty
3785          * string (`''`) or absent if not applicable or supported by the driver.
3786          *
3787          * The following modes are known:
3788          *  - `ACTIVE` (no power save)
3789          *  - `LIGHT SLEEP`
3790          *  - `DEEP SLEEP`
3791          *  - `UNKNOWN`
3792          *
3793          * @property {LuCI.network.WifiRateEntry} rx
3794          * Describes the receiving wireless rate from the peer.
3795          *
3796          * @property {LuCI.network.WifiRateEntry} tx
3797          * Describes the transmitting wireless rate to the peer.
3798          */
3799
3800         /**
3801          * A wireless rate entry describes the properties of a wireless
3802          * transmission rate to or from a peer.
3803          *
3804          * @typedef {Object<string, boolean|number>} WifiRateEntry
3805          * @memberof LuCI.network
3806          *
3807          * @property {number} [drop_misc]
3808          * The amount of received misc. packages that have been dropped, e.g.
3809          * due to corruption or missing authentication. Only applicable to
3810          * receiving rates.
3811          *
3812          * @property {number} packets
3813          * The amount of packets that have been received or sent.
3814          *
3815          * @property {number} bytes
3816          * The amount of bytes that have been received or sent.
3817          *
3818          * @property {number} [failed]
3819          * The amount of failed tranmission attempts. Only applicable to
3820          * transmit rates.
3821          *
3822          * @property {number} [retries]
3823          * The amount of retried transmissions. Only applicable to transmit
3824          * rates.
3825          *
3826          * @property {boolean} is_ht
3827          * Specifies whether this rate is an HT (IEEE 802.11n) rate.
3828          *
3829          * @property {boolean} is_vht
3830          * Specifies whether this rate is an VHT (IEEE 802.11ac) rate.
3831          *
3832          * @property {number} mhz
3833          * The channel width in MHz used for the transmission.
3834          *
3835          * @property {number} rate
3836          * The bitrate in bit/s of the transmission.
3837          *
3838          * @property {number} [mcs]
3839          * The MCS index of the used transmission rate. Only applicable to
3840          * HT or VHT rates.
3841          *
3842          * @property {number} [40mhz]
3843          * Specifies whether the tranmission rate used 40MHz wide channel.
3844          * Only applicable to HT or VHT rates.
3845          *
3846          * Note: this option exists for backwards compatibility only and its
3847          * use is discouraged. The `mhz` field should be used instead to
3848          * determine the channel width.
3849          *
3850          * @property {boolean} [short_gi]
3851          * Specifies whether a short guard interval is used for the transmission.
3852          * Only applicable to HT or VHT rates.
3853          *
3854          * @property {number} [nss]
3855          * Specifies the number of spatial streams used by the transmission.
3856          * Only applicable to VHT rates.
3857          */
3858
3859         /**
3860          * Fetch the list of associated peers.
3861          *
3862          * @returns {Promise<Array<LuCI.network.WifiPeerEntry>>}
3863          * Returns a promise resolving to an array of wireless peers associated
3864          * with this network.
3865          */
3866         getAssocList: function() {
3867                 return callIwinfoAssoclist(this.getIfname());
3868         },
3869
3870         /**
3871          * Query the current operating frequency of the wireless network.
3872          *
3873          * @returns {null|string}
3874          * Returns the current operating frequency of the network from `ubus`
3875          * runtime information in GHz or `null` if the information is not
3876          * available.
3877          */
3878         getFrequency: function() {
3879                 var freq = this.ubus('net', 'iwinfo', 'frequency');
3880
3881                 if (freq != null && freq > 0)
3882                         return '%.03f'.format(freq / 1000);
3883
3884                 return null;
3885         },
3886
3887         /**
3888          * Query the current average bitrate of all peers associated to this
3889          * wireless network.
3890          *
3891          * @returns {null|number}
3892          * Returns the average bit rate among all peers associated to the network
3893          * as reported by `ubus` runtime information or `null` if the information
3894          * is not available.
3895          */
3896         getBitRate: function() {
3897                 var rate = this.ubus('net', 'iwinfo', 'bitrate');
3898
3899                 if (rate != null && rate > 0)
3900                         return (rate / 1000);
3901
3902                 return null;
3903         },
3904
3905         /**
3906          * Query the current wireless channel.
3907          *
3908          * @returns {null|number}
3909          * Returns the wireless channel as reported by `ubus` runtime information
3910          * or `null` if it cannot be determined.
3911          */
3912         getChannel: function() {
3913                 return this.ubus('net', 'iwinfo', 'channel') || this.ubus('dev', 'config', 'channel') || this.get('channel');
3914         },
3915
3916         /**
3917          * Query the current wireless signal.
3918          *
3919          * @returns {null|number}
3920          * Returns the wireless signal in dBm as reported by `ubus` runtime
3921          * information or `null` if it cannot be determined.
3922          */
3923         getSignal: function() {
3924                 return this.ubus('net', 'iwinfo', 'signal') || 0;
3925         },
3926
3927         /**
3928          * Query the current radio noise floor.
3929          *
3930          * @returns {number}
3931          * Returns the radio noise floor in dBm as reported by `ubus` runtime
3932          * information or `0` if it cannot be determined.
3933          */
3934         getNoise: function() {
3935                 return this.ubus('net', 'iwinfo', 'noise') || 0;
3936         },
3937
3938         /**
3939          * Query the current country code.
3940          *
3941          * @returns {string}
3942          * Returns the wireless country code as reported by `ubus` runtime
3943          * information or `00` if it cannot be determined.
3944          */
3945         getCountryCode: function() {
3946                 return this.ubus('net', 'iwinfo', 'country') || this.ubus('dev', 'config', 'country') || '00';
3947         },
3948
3949         /**
3950          * Query the current radio TX power.
3951          *
3952          * @returns {null|number}
3953          * Returns the wireless network transmit power in dBm as reported by
3954          * `ubus` runtime information or `null` if it cannot be determined.
3955          */
3956         getTXPower: function() {
3957                 return this.ubus('net', 'iwinfo', 'txpower');
3958         },
3959
3960         /**
3961          * Query the radio TX power offset.
3962          *
3963          * Some wireless radios have a fixed power offset, e.g. due to the
3964          * use of external amplifiers.
3965          *
3966          * @returns {number}
3967          * Returns the wireless network transmit power offset in dBm as reported
3968          * by `ubus` runtime information or `0` if there is no offset, or if it
3969          * cannot be determined.
3970          */
3971         getTXPowerOffset: function() {
3972                 return this.ubus('net', 'iwinfo', 'txpower_offset') || 0;
3973         },
3974
3975         /**
3976          * Calculate the current signal.
3977          *
3978          * @deprecated
3979          * @returns {number}
3980          * Returns the calculated signal level, which is the difference between
3981          * noise and signal (SNR), divided by 5.
3982          */
3983         getSignalLevel: function(signal, noise) {
3984                 if (this.getActiveBSSID() == '00:00:00:00:00:00')
3985                         return -1;
3986
3987                 signal = signal || this.getSignal();
3988                 noise  = noise  || this.getNoise();
3989
3990                 if (signal < 0 && noise < 0) {
3991                         var snr = -1 * (noise - signal);
3992                         return Math.floor(snr / 5);
3993                 }
3994
3995                 return 0;
3996         },
3997
3998         /**
3999          * Calculate the current signal quality percentage.
4000          *
4001          * @returns {number}
4002          * Returns the calculated signal quality in percent. The value is
4003          * calculated from the `quality` and `quality_max` indicators reported
4004          * by `ubus` runtime state.
4005          */
4006         getSignalPercent: function() {
4007                 var qc = this.ubus('net', 'iwinfo', 'quality') || 0,
4008                     qm = this.ubus('net', 'iwinfo', 'quality_max') || 0;
4009
4010                 if (qc > 0 && qm > 0)
4011                         return Math.floor((100 / qm) * qc);
4012
4013                 return 0;
4014         },
4015
4016         /**
4017          * Get a short description string for this wireless network.
4018          *
4019          * @returns {string}
4020          * Returns a string describing this network, consisting of the
4021          * active operation mode, followed by either the SSID, BSSID or
4022          * internal network ID, depending on which information is available.
4023          */
4024         getShortName: function() {
4025                 return '%s "%s"'.format(
4026                         this.getActiveModeI18n(),
4027                         this.getActiveSSID() || this.getActiveBSSID() || this.getID());
4028         },
4029
4030         /**
4031          * Get a description string for this wireless network.
4032          *
4033          * @returns {string}
4034          * Returns a string describing this network, consisting of the
4035          * term `Wireless Network`, followed by the active operation mode,
4036          * the SSID, BSSID or internal network ID and the Linux network device
4037          * name, depending on which information is available.
4038          */
4039         getI18n: function() {
4040                 return '%s: %s "%s" (%s)'.format(
4041                         _('Wireless Network'),
4042                         this.getActiveModeI18n(),
4043                         this.getActiveSSID() || this.getActiveBSSID() || this.getID(),
4044                         this.getIfname());
4045         },
4046
4047         /**
4048          * Get the primary logical interface this wireless network is attached to.
4049          *
4050          * @returns {null|LuCI.network.Protocol}
4051          * Returns a `Network.Protocol` instance representing the logical
4052          * interface or `null` if this network is not attached to any logical
4053          * interface.
4054          */
4055         getNetwork: function() {
4056                 return this.getNetworks()[0];
4057         },
4058
4059         /**
4060          * Get the logical interfaces this wireless network is attached to.
4061          *
4062          * @returns {Array<LuCI.network.Protocol>}
4063          * Returns an array of `Network.Protocol` instances representing the
4064          * logical interfaces this wireless network is attached to.
4065          */
4066         getNetworks: function() {
4067                 var networkNames = this.getNetworkNames(),
4068                     networks = [];
4069
4070                 for (var i = 0; i < networkNames.length; i++) {
4071                         var uciInterface = uci.get('network', networkNames[i]);
4072
4073                         if (uciInterface == null || uciInterface['.type'] != 'interface')
4074                                 continue;
4075
4076                         networks.push(Network.prototype.instantiateNetwork(networkNames[i]));
4077                 }
4078
4079                 networks.sort(networkSort);
4080
4081                 return networks;
4082         },
4083
4084         /**
4085          * Get the associated Linux network device.
4086          *
4087          * @returns {LuCI.network.Device}
4088          * Returns a `Network.Device` instance representing the Linux network
4089          * device associted with this wireless network.
4090          */
4091         getDevice: function() {
4092                 return Network.prototype.instantiateDevice(this.getIfname());
4093         },
4094
4095         /**
4096          * Check whether this wifi network supports deauthenticating clients.
4097          *
4098          * @returns {boolean}
4099          * Returns `true` when this wifi network instance supports forcibly
4100          * deauthenticating clients, otherwise `false`.
4101          */
4102         isClientDisconnectSupported: function() {
4103                 return L.isObject(this.ubus('hostapd', 'del_client'));
4104         },
4105
4106         /**
4107          * Forcibly disconnect the given client from the wireless network.
4108          *
4109          * @param {string} mac
4110          * The MAC address of the client to disconnect.
4111          *
4112          * @param {boolean} [deauth=false]
4113          * Specifies whether to deauthenticate (`true`) or disassociate (`false`)
4114          * the client.
4115          *
4116          * @param {number} [reason=1]
4117          * Specifies the IEEE 802.11 reason code to disassoc/deauth the client
4118          * with. Default is `1` which corresponds to `Unspecified reason`.
4119          *
4120          * @param {number} [ban_time=0]
4121          * Specifies the amount of milliseconds to ban the client from
4122          * reconnecting. By default, no ban time is set which allows the client
4123          * to reassociate / reauthenticate immediately.
4124          *
4125          * @returns {Promise<number>}
4126          * Returns a promise resolving to the underlying ubus call result code
4127          * which is typically `0`, even for not existing MAC addresses.
4128          * The promise might reject with an error in case invalid arguments
4129          * are passed.
4130          */
4131         disconnectClient: function(mac, deauth, reason, ban_time) {
4132                 if (reason == null || reason == 0)
4133                         reason = 1;
4134
4135                 if (ban_time == 0)
4136                         ban_time = null;
4137
4138                 return rpc.declare({
4139                         object: 'hostapd.%s'.format(this.getIfname()),
4140                         method: 'del_client',
4141                         params: [ 'addr', 'deauth', 'reason', 'ban_time' ]
4142                 })(mac, deauth, reason, ban_time);
4143         }
4144 });
4145
4146 return Network;