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