luci-base: network.js: fix, rework and improve wireless state handling
authorJo-Philipp Wich <jo@mein.io>
Tue, 3 Sep 2019 14:56:24 +0000 (16:56 +0200)
committerJo-Philipp Wich <jo@mein.io>
Tue, 10 Sep 2019 13:28:16 +0000 (15:28 +0200)
 - Use new getWirelessDevices rpc method to optimize data fetching
 - Implement further getters to access iwinfo information
 - Implement assoc- and scan list functions
 - Simplify internal data model

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
modules/luci-base/htdocs/luci-static/resources/network.js
modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json

index 05510d828cbbf8d6120bb2a8ebac0f3dde44ecc3..70e360b68f9fd797ee9509720e74a5613d89a8b4 100644 (file)
@@ -44,17 +44,18 @@ var iface_patterns_wireless = [
 
 var iface_patterns_virtual = [ ];
 
-var callNetworkWirelessStatus = rpc.declare({
-       object: 'network.wireless',
-       method: 'status'
-});
-
 var callLuciNetdevs = rpc.declare({
        object: 'luci',
        method: 'getNetworkDevices',
        expect: { '': {} }
 });
 
+var callLuciWifidevs = rpc.declare({
+       object: 'luci',
+       method: 'getWirelessDevices',
+       expect: { '': {} }
+});
+
 var callLuciIfaddrs = rpc.declare({
        object: 'luci',
        method: 'getIfaddrs',
@@ -66,10 +67,18 @@ var callLuciBoardjson = rpc.declare({
        method: 'getBoardJSON'
 });
 
-var callIwinfoInfo = rpc.declare({
+var callIwinfoAssoclist = rpc.declare({
+       object: 'iwinfo',
+       method: 'assoclist',
+       params: [ 'device', 'mac' ],
+       expect: { results: [] }
+});
+
+var callIwinfoScan = rpc.declare({
        object: 'iwinfo',
-       method: 'info',
-       params: [ 'device' ]
+       method: 'scan',
+       params: [ 'device' ],
+       expect: { results: [] }
 });
 
 var callNetworkInterfaceStatus = rpc.declare({
@@ -95,16 +104,6 @@ var _init = null,
     _protocols = {},
     _protospecs = {};
 
-function getWifiState(cache) {
-       return callNetworkWirelessStatus().then(function(state) {
-               if (!L.isObject(state))
-                       throw !1;
-               return state;
-       }).catch(function() {
-               return {};
-       });
-}
-
 function getInterfaceState(cache) {
        return callNetworkInterfaceStatus().then(function(state) {
                if (!Array.isArray(state))
@@ -145,6 +144,16 @@ function getNetdevState(cache) {
        });
 }
 
+function getWifidevState(cache) {
+       return callLuciWifidevs().then(function(state) {
+               if (!L.isObject(state))
+                       throw !1;
+               return state;
+       }).catch(function() {
+               return {};
+       });
+}
+
 function getBoardState(cache) {
        return callLuciBoardjson().then(function(state) {
                if (!L.isObject(state))
@@ -192,8 +201,12 @@ function getWifiStateBySid(sid) {
 
                                var s2 = uci.get('wireless', netstate.section);
 
-                               if (s2 != null && s['.type'] == s2['.type'] && s['.name'] == s2['.name'])
+                               if (s2 != null && s['.type'] == s2['.type'] && s['.name'] == s2['.name']) {
+                                       if (s2['.anonymous'] == false && netstate.section.charAt(0) == '@')
+                                               return null;
+
                                        return [ radioname, _state.radios[radioname], netstate ];
+                               }
                        }
                }
        }
@@ -225,37 +238,6 @@ function isWifiIfname(ifname) {
        return false;
 }
 
-function getWifiIwinfoByIfname(ifname, forcePhyOnly) {
-       var tasks = [ callIwinfoInfo(ifname) ];
-
-       if (!forcePhyOnly)
-               tasks.push(getNetdevState());
-
-       return Promise.all(tasks).then(function(info) {
-               var iwinfo = info[0],
-                   devstate = info[1],
-                   phyonly = forcePhyOnly || !devstate[ifname] || (devstate[ifname].type != 1);
-
-               if (L.isObject(iwinfo)) {
-                       if (phyonly) {
-                               delete iwinfo.bitrate;
-                               delete iwinfo.quality;
-                               delete iwinfo.quality_max;
-                               delete iwinfo.mode;
-                               delete iwinfo.ssid;
-                               delete iwinfo.bssid;
-                               delete iwinfo.encryption;
-                       }
-
-                       iwinfo.ifname = ifname;
-               }
-
-               return iwinfo;
-       }).catch(function() {
-               return null;
-       });
-}
-
 function getWifiSidByNetid(netid) {
        var m = /^(\w+)\.network(\d+)$/.exec(netid);
        if (m) {
@@ -431,13 +413,13 @@ function initNetworkState(refresh) {
        if (_state == null || refresh) {
                _init = _init || Promise.all([
                        getInterfaceState(), getDeviceState(), getBoardState(),
-                       getWifiState(), getIfaddrState(), getNetdevState(), getProtocolHandlers(),
+                       getIfaddrState(), getNetdevState(), getWifidevState(), getProtocolHandlers(),
                        uci.load('network'), uci.load('wireless'), uci.load('luci')
                ]).then(function(data) {
-                       var board = data[2], ifaddrs = data[4], devices = data[5];
+                       var board = data[2], ifaddrs = data[3], devices = data[4];
                        var s = {
                                isTunnel: {}, isBridge: {}, isSwitch: {}, isWifi: {},
-                               ifaces: data[0], devices: data[1], radios: data[3],
+                               ifaces: data[0], devices: data[1], radios: data[5],
                                netdevs: {}, bridges: {}, switches: {}
                        };
 
@@ -613,12 +595,91 @@ function deviceSort(a, b) {
        return a.getName() > b.getName();
 }
 
+function formatWifiEncryption(enc) {
+       if (!L.isObject(enc))
+               return null;
+
+       if (!enc.enabled)
+               return 'None';
+
+       var ciphers = Array.isArray(enc.ciphers)
+               ? enc.ciphers.map(function(c) { return c.toUpperCase() }) : [ 'NONE' ];
+
+       if (Array.isArray(enc.wep)) {
+               var has_open = false,
+                   has_shared = false;
+
+               for (var i = 0; i < enc.wep.length; i++)
+                       if (enc.wep[i] == 'open')
+                               has_open = true;
+                       else if (enc.wep[i] == 'shared')
+                               has_shared = true;
+
+               if (has_open && has_shared)
+                       return 'WEP Open/Shared (%s)'.format(ciphers.join(', '));
+               else if (has_open)
+                       return 'WEP Open System (%s)'.format(ciphers.join(', '));
+               else if (has_shared)
+                       return 'WEP Shared Auth (%s)'.format(ciphers.join(', '));
+
+               return 'WEP';
+       }
+
+       if (Array.isArray(enc.wpa)) {
+               var versions = [],
+                   suites = Array.isArray(enc.authentication)
+                       ? enc.authentication.map(function(a) { return a.toUpperCase() }) : [ 'NONE' ];
+
+               for (var i = 0; i < enc.wpa.length; i++)
+                       switch (enc.wpa[i]) {
+                       case 1:
+                               versions.push('WPA');
+                               break;
+
+                       default:
+                               versions.push('WPA%d'.format(enc.wpa[i]));
+                               break;
+                       }
+
+               if (versions.length > 1)
+                       return 'mixed %s %s (%s)'.format(versions.join('/'), suites.join(', '), ciphers.join(', '));
+
+               return '%s %s (%s)'.format(versions[0], suites.join(', '), ciphers.join(', '));
+       }
+
+       return 'Unknown';
+}
+
+function enumerateNetworks() {
+       var uciInterfaces = uci.sections('network', 'interface'),
+           networks = {};
+
+       for (var i = 0; i < uciInterfaces.length; i++)
+               networks[uciInterfaces[i]['.name']] = this.instantiateNetwork(uciInterfaces[i]['.name']);
+
+       for (var i = 0; i < _state.ifaces.length; i++)
+               if (networks[_state.ifaces[i].interface] == null)
+                       networks[_state.ifaces[i].interface] =
+                               this.instantiateNetwork(_state.ifaces[i].interface, _state.ifaces[i].proto);
+
+       var rv = [];
+
+       for (var network in networks)
+               if (networks.hasOwnProperty(network))
+                       rv.push(networks[network]);
+
+       rv.sort(networkSort);
+
+       return rv;
+}
+
 
 var Network, Protocol, Device, WifiDevice, WifiNetwork;
 
 Network = L.Class.extend({
        prefixToMask: prefixToMask,
        maskToPrefix: maskToPrefix,
+       formatWifiEncryption: formatWifiEncryption,
 
        flushCache: function() {
                initNetworkState(true);
@@ -733,28 +794,7 @@ Network = L.Class.extend({
        },
 
        getNetworks: function() {
-               return initNetworkState().then(L.bind(function() {
-                       var uciInterfaces = uci.sections('network', 'interface'),
-                           networks = {};
-
-                       for (var i = 0; i < uciInterfaces.length; i++)
-                               networks[uciInterfaces[i]['.name']] = this.instantiateNetwork(uciInterfaces[i]['.name']);
-
-                       for (var i = 0; i < _state.ifaces.length; i++)
-                               if (networks[_state.ifaces[i].interface] == null)
-                                       networks[_state.ifaces[i].interface] =
-                                               this.instantiateNetwork(_state.ifaces[i].interface, _state.ifaces[i].proto);
-
-                       var rv = [];
-
-                       for (var network in networks)
-                               if (networks.hasOwnProperty(network))
-                                       rv.push(networks[network]);
-
-                       rv.sort(networkSort);
-
-                       return rv;
-               }, this));
+               return initNetworkState().then(L.bind(enumerateNetworks, this));
        },
 
        deleteNetwork: function(name) {
@@ -971,38 +1011,26 @@ Network = L.Class.extend({
        },
 
        getWifiDevice: function(devname) {
-               return Promise.all([ getWifiIwinfoByIfname(devname, true), initNetworkState() ]).then(L.bind(function(res) {
+               return initNetworkState().then(L.bind(function() {
                        var existingDevice = uci.get('wireless', devname);
 
                        if (existingDevice == null || existingDevice['.type'] != 'wifi-device')
                                return null;
 
-                       return this.instantiateWifiDevice(devname, res[0]);
+                       return this.instantiateWifiDevice(devname, _state.radios[devname] || {});
                }, this));
        },
 
        getWifiDevices: function() {
-               var deviceNames = [];
-
                return initNetworkState().then(L.bind(function() {
                        var uciWifiDevices = uci.sections('wireless', 'wifi-device'),
-                           tasks = [];
+                           rv = [];
 
                        for (var i = 0; i < uciWifiDevices.length; i++) {
-                               tasks.push(callIwinfoInfo(uciWifiDevices['.name'], true));
-                               deviceNames.push(uciWifiDevices['.name']);
+                               var devname = uciWifiDevices[i]['.name'];
+                               rv.push(this.instantiateWifiDevice(devname, _state.radios[devname] || {}));
                        }
 
-                       return Promise.all(tasks);
-               }, this)).then(L.bind(function(iwinfos) {
-                       var rv = [];
-
-                       for (var i = 0; i < deviceNames.length; i++)
-                               if (L.isObject(iwinfos[i]))
-                                       rv.push(this.instantiateWifiDevice(deviceNames[i], iwinfos[i]));
-
-                       rv.sort(function(a, b) { return a.getName() < b.getName() });
-
                        return rv;
                }, this));
        },
@@ -1052,11 +1080,7 @@ Network = L.Class.extend({
                                }
                        }
 
-                       return (netstate ? getWifiIwinfoByIfname(netstate.ifname) : Promise.reject())
-                               .catch(function() { return radioname ? getWifiIwinfoByIfname(radioname) : Promise.reject() })
-                               .catch(function() { return Promise.resolve({ ifname: netid || sid || netname }) });
-               }, this)).then(L.bind(function(iwinfo) {
-                       return this.instantiateWifiNetwork(sid || netname, radioname, radiostate, netid, netstate, iwinfo);
+                       return this.instantiateWifiNetwork(sid || netname, radioname, radiostate, netid, netstate);
                }, this));
        },
 
@@ -1079,7 +1103,7 @@ Network = L.Class.extend({
                        var radioname = existingDevice['.name'],
                            netid = getWifiNetidBySid(sid) || [];
 
-                       return this.instantiateWifiNetwork(sid, radioname, _state.radios[radioname], netid[0], null, { ifname: netid });
+                       return this.instantiateWifiNetwork(sid, radioname, _state.radios[radioname], netid[0], null);
                }, this));
        },
 
@@ -1198,12 +1222,12 @@ Network = L.Class.extend({
                return new Device(name, network);
        },
 
-       instantiateWifiDevice: function(radioname, iwinfo) {
-               return new WifiDevice(radioname, iwinfo);
+       instantiateWifiDevice: function(radioname, radiostate) {
+               return new WifiDevice(radioname, radiostate);
        },
 
-       instantiateWifiNetwork: function(sid, radioname, radiostate, netid, netstate, iwinfo) {
-               return new WifiNetwork(sid, radioname, radiostate, netid, netstate, iwinfo);
+       instantiateWifiNetwork: function(sid, radioname, radiostate, netid, netstate) {
+               return new WifiNetwork(sid, radioname, radiostate, netid, netstate);
        },
 
        getIfnameOf: function(obj) {
@@ -1809,7 +1833,7 @@ Device = L.Class.extend({
                if (this.networks == null) {
                        this.networks = [];
 
-                       var networks = L.network.getNetworks();
+                       var networks = enumerateNetworks.apply(L.network);
 
                        for (var i = 0; i < networks.length; i++)
                                if (networks[i].containsDevice(this.ifname) || networks[i].getIfname() == this.ifname)
@@ -1827,18 +1851,32 @@ Device = L.Class.extend({
 });
 
 WifiDevice = L.Class.extend({
-       __init__: function(name, iwinfo) {
+       __init__: function(name, radiostate) {
                var uciWifiDevice = uci.get('wireless', name);
 
                if (uciWifiDevice != null &&
                    uciWifiDevice['.type'] == 'wifi-device' &&
                    uciWifiDevice['.name'] != null) {
                        this.sid    = uciWifiDevice['.name'];
-                       this.iwinfo = iwinfo;
                }
 
-               this.sid    = this.sid    || name;
-               this.iwinfo = this.iwinfo || { ifname: this.sid };
+               this.sid    = this.sid || name;
+               this._ubusdata = {
+                       radio: name,
+                       dev:   radiostate
+               };
+       },
+
+       ubus: function(/* ... */) {
+               var v = this._ubusdata;
+
+               for (var i = 0; i < arguments.length; i++)
+                       if (L.isObject(v))
+                               v = v[arguments[i]];
+                       else
+                               return null;
+
+               return v;
        },
 
        get: function(opt) {
@@ -1849,34 +1887,45 @@ WifiDevice = L.Class.extend({
                return uci.set('wireless', this.sid, opt, value);
        },
 
+       isDisabled: function() {
+               return this.ubus('dev', 'disabled') || this.get('disabled') == '1';
+       },
+
        getName: function() {
                return this.sid;
        },
 
        getHWModes: function() {
-               if (L.isObject(this.iwinfo.hwmodelist))
-                       for (var k in this.iwinfo.hwmodelist)
-                               return this.iwinfo.hwmodelist;
+               var hwmodes = this.ubus('dev', 'iwinfo', 'hwmodes');
+               return Array.isArray(hwmodes) ? hwmodes : [ 'b', 'g' ];
+       },
 
-               return { b: true, g: true };
+       getHTModes: function() {
+               var htmodes = this.ubus('dev', 'iwinfo', 'htmodes');
+               return (Array.isArray(htmodes) && htmodes.length) ? htmodes : null;
        },
 
        getI18n: function() {
-               var type = this.iwinfo.hardware_name || 'Generic';
+               var hw = this.ubus('dev', 'iwinfo', 'hardware'),
+                   type = L.isObject(hw) ? hw.name : null;
 
-               if (this.iwinfo.type == 'wl')
+               if (this.ubus('dev', 'iwinfo', 'type') == 'wl')
                        type = 'Broadcom';
 
                var hwmodes = this.getHWModes(),
                    modestr = '';
 
-               if (hwmodes.a) modestr += 'a';
-               if (hwmodes.b) modestr += 'b';
-               if (hwmodes.g) modestr += 'g';
-               if (hwmodes.n) modestr += 'n';
-               if (hwmodes.ad) modestr += 'ac';
+               hwmodes.sort(function(a, b) {
+                       return (a.length != b.length ? a.length > b.length : a > b);
+               });
+
+               modestr = hwmodes.join('');
 
-               return '%s 802.11%s Wireless Controller (%s)'.format(type, modestr, this.getName());
+               return '%s 802.11%s Wireless Controller (%s)'.format(type || 'Generic', modestr, this.getName());
+       },
+
+       getScanList: function() {
+               return callIwinfoScan(this.sid);
        },
 
        isUp: function() {
@@ -1940,10 +1989,8 @@ WifiDevice = L.Class.extend({
 });
 
 WifiNetwork = L.Class.extend({
-       __init__: function(sid, radioname, radiostate, netid, netstate, iwinfo) {
+       __init__: function(sid, radioname, radiostate, netid, netstate) {
                this.sid    = sid;
-               this.wdev   = iwinfo.ifname;
-               this.iwinfo = iwinfo;
                this.netid  = netid;
                this._ubusdata = {
                        radio: radioname,
@@ -1972,6 +2019,10 @@ WifiNetwork = L.Class.extend({
                return uci.set('wireless', this.sid, opt, value);
        },
 
+       isDisabled: function() {
+               return this.ubus('dev', 'disabled') || this.get('disabled') == '1';
+       },
+
        getMode: function() {
                return this.ubus('net', 'config', 'mode') || this.get('mode') || 'ap';
        },
@@ -1997,7 +2048,7 @@ WifiNetwork = L.Class.extend({
        },
 
        getIfname: function() {
-               var ifname = this.ubus('net', 'ifname') || this.iwinfo.ifname;
+               var ifname = this.ubus('net', 'ifname') || this.ubus('net', 'iwinfo', 'ifname');
 
                if (ifname == null || ifname.match(/^(wifi|radio)\d/))
                        ifname = this.netid;
@@ -2005,8 +2056,12 @@ WifiNetwork = L.Class.extend({
                return ifname;
        },
 
+       getWifiDeviceName: function() {
+               return this.ubus('radio') || this.get('device');
+       },
+
        getWifiDevice: function() {
-               var radioname = this.ubus('radio') || this.get('device');
+               var radioname = this.getWifiDeviceName();
 
                if (radioname == null)
                        return Promise.reject();
@@ -2024,7 +2079,7 @@ WifiNetwork = L.Class.extend({
        },
 
        getActiveMode: function() {
-               var mode = this.iwinfo.mode || this.ubus('net', 'config', 'mode') || this.get('mode') || 'ap';
+               var mode = this.ubus('net', 'iwinfo', 'mode') || this.ubus('net', 'config', 'mode') || this.get('mode') || 'ap';
 
                switch (mode) {
                case 'ap':      return 'Master';
@@ -2050,25 +2105,23 @@ WifiNetwork = L.Class.extend({
        },
 
        getActiveSSID: function() {
-               return this.iwinfo.ssid || this.ubus('net', 'config', 'ssid') || this.get('ssid');
+               return this.ubus('net', 'iwinfo', 'ssid') || this.ubus('net', 'config', 'ssid') || this.get('ssid');
        },
 
        getActiveBSSID: function() {
-               return this.iwinfo.bssid || this.ubus('net', 'config', 'bssid') || this.get('bssid');
+               return this.ubus('net', 'iwinfo', 'bssid') || this.ubus('net', 'config', 'bssid') || this.get('bssid');
        },
 
        getActiveEncryption: function() {
-               var encryption = this.iwinfo.encryption;
-
-               return (L.isObject(encryption) ? encryption.description || '-' : '-');
+               return formatWifiEncryption(this.ubus('net', 'iwinfo', 'encryption')) || '-';
        },
 
        getAssocList: function() {
-               // XXX tbd
+               return callIwinfoAssoclist(this.getIfname());
        },
 
        getFrequency: function() {
-               var freq = this.iwinfo.frequency;
+               var freq = this.ubus('net', 'iwinfo', 'frequency');
 
                if (freq != null && freq > 0)
                        return '%.03f'.format(freq / 1000);
@@ -2077,7 +2130,7 @@ WifiNetwork = L.Class.extend({
        },
 
        getBitRate: function() {
-               var rate = this.iwinfo.bitrate;
+               var rate = this.ubus('net', 'iwinfo', 'bitrate');
 
                if (rate != null && rate > 0)
                        return (rate / 1000);
@@ -2086,28 +2139,27 @@ WifiNetwork = L.Class.extend({
        },
 
        getChannel: function() {
-               return this.iwinfo.channel || this.ubus('dev', 'config', 'channel') || this.get('channel');
+               return this.ubus('net', 'iwinfo', 'channel') || this.ubus('dev', 'config', 'channel') || this.get('channel');
        },
 
        getSignal: function() {
-               return this.iwinfo.signal || 0;
+               return this.ubus('net', 'iwinfo', 'signal') || 0;
        },
 
        getNoise: function() {
-               return this.iwinfo.noise || 0;
+               return this.ubus('net', 'iwinfo', 'noise') || 0;
        },
 
        getCountryCode: function() {
-               return this.iwinfo.country || this.ubus('dev', 'config', 'country') || '00';
+               return this.ubus('net', 'iwinfo', 'country') || this.ubus('dev', 'config', 'country') || '00';
        },
 
        getTXPower: function() {
-               var pwr = this.iwinfo.txpower || 0;
-               return (pwr + this.getTXPowerOffset());
+               return this.ubus('net', 'iwinfo', 'txpower');
        },
 
        getTXPowerOffset: function() {
-               return this.iwinfo.txpower_offset || 0;
+               return this.ubus('net', 'iwinfo', 'txpower_offset') || 0;
        },
 
        getSignalLevel: function(signal, noise) {
@@ -2126,8 +2178,8 @@ WifiNetwork = L.Class.extend({
        },
 
        getSignalPercent: function() {
-               var qc = this.iwinfo.quality || 0,
-                   qm = this.iwinfo.quality_max || 0;
+               var qc = this.ubus('net', 'iwinfo', 'quality') || 0,
+                   qm = this.ubus('net', 'iwinfo', 'quality_max') || 0;
 
                if (qc > 0 && qm > 0)
                        return Math.floor((100 / qm) * qc);
index 34183da6d8631705acf1036dd580aa3ad866745f..2804cc7003dbc7ef1be2fd67487d753fcb1a8192 100644 (file)
@@ -21,8 +21,8 @@
                "description": "Grant access to basic LuCI procedures",
                "read": {
                        "ubus": {
-                               "iwinfo": [ "info" ],
-                               "luci": [ "getBoardJSON", "getDUIDHints", "getHostHints", "getIfaddrs", "getInitList", "getLocaltime", "getTimezones", "getDHCPLeases", "getLEDs", "getNetworkDevices", "getUSBDevices", "getHostname", "getTTYDevices" ],
+                               "iwinfo": [ "assoclist" ],
+                               "luci": [ "getBoardJSON", "getDUIDHints", "getHostHints", "getIfaddrs", "getInitList", "getLocaltime", "getTimezones", "getDHCPLeases", "getLEDs", "getNetworkDevices", "getUSBDevices", "getHostname", "getTTYDevices", "getWirelessDevices" ],
                                "network.device": [ "status" ],
                                "network.interface": [ "dump" ],
                                "network.wireless": [ "status" ],
@@ -33,6 +33,7 @@
                },
                "write": {
                        "ubus": {
+                               "iwinfo": [ "scan" ],
                                "luci": [ "setInitAction", "setLocaltime" ],
                                "uci": [ "add", "apply", "confirm", "delete", "order", "set" ]
                        },