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