'use strict'; // @ts-check // ================================================================================== // wifi.js // ---------------------------------------------------------------------------------- // Description: System Information - library // for Node.js // Copyright: (c) 2014 - 2022 // Author: Sebastian Hildebrandt // ---------------------------------------------------------------------------------- // License: MIT // ================================================================================== // 9. wifi // ---------------------------------------------------------------------------------- const os = require('os'); const exec = require('child_process').exec; const execSync = require('child_process').execSync; const util = require('./util'); let _platform = process.platform; const _linux = (_platform === 'linux' || _platform === 'android'); const _darwin = (_platform === 'darwin'); const _windows = (_platform === 'win32'); function wifiDBFromQuality(quality) { return (parseFloat(quality) / 2 - 100); } function wifiQualityFromDB(db) { const result = 2 * (parseFloat(db) + 100); return result <= 100 ? result : 100; } const _wifi_frequencies = { 1: 2412, 2: 2417, 3: 2422, 4: 2427, 5: 2432, 6: 2437, 7: 2442, 8: 2447, 9: 2452, 10: 2457, 11: 2462, 12: 2467, 13: 2472, 14: 2484, 32: 5160, 34: 5170, 36: 5180, 38: 5190, 40: 5200, 42: 5210, 44: 5220, 46: 5230, 48: 5240, 50: 5250, 52: 5260, 54: 5270, 56: 5280, 58: 5290, 60: 5300, 62: 5310, 64: 5320, 68: 5340, 96: 5480, 100: 5500, 102: 5510, 104: 5520, 106: 5530, 108: 5540, 110: 5550, 112: 5560, 114: 5570, 116: 5580, 118: 5590, 120: 5600, 122: 5610, 124: 5620, 126: 5630, 128: 5640, 132: 5660, 134: 5670, 136: 5680, 138: 5690, 140: 5700, 142: 5710, 144: 5720, 149: 5745, 151: 5755, 153: 5765, 155: 5775, 157: 5785, 159: 5795, 161: 5805, 165: 5825, 169: 5845, 173: 5865, 183: 4915, 184: 4920, 185: 4925, 187: 4935, 188: 4940, 189: 4945, 192: 4960, 196: 4980 }; function wifiFrequencyFromChannel(channel) { return {}.hasOwnProperty.call(_wifi_frequencies, channel) ? _wifi_frequencies[channel] : null; } function wifiChannelFromFrequencs(frequency) { let channel = 0; for (let key in _wifi_frequencies) { if ({}.hasOwnProperty.call(_wifi_frequencies, key)) { if (_wifi_frequencies[key] === frequency) { channel = util.toInt(key); } } } return channel; } function ifaceListLinux() { const result = []; const cmd = 'iw dev 2>/dev/null'; try { const all = execSync(cmd).toString().split('\n').map(line => line.trim()).join('\n'); const parts = all.split('\nInterface '); parts.shift(); parts.forEach(ifaceDetails => { const lines = ifaceDetails.split('\n'); const iface = lines[0]; const id = util.toInt(util.getValue(lines, 'ifindex', ' ')); const mac = util.getValue(lines, 'addr', ' '); const channel = util.toInt(util.getValue(lines, 'channel', ' ')); result.push({ id, iface, mac, channel }); }); return result; } catch (e) { try { const all = execSync('nmcli -t -f general,wifi-properties,wired-properties,interface-flags,capabilities,nsp device show 2>/dev/null').toString(); const parts = all.split('\nGENERAL.DEVICE:'); let i = 1; parts.forEach(ifaceDetails => { const lines = ifaceDetails.split('\n'); const iface = util.getValue(lines, 'GENERAL.DEVICE'); const type = util.getValue(lines, 'GENERAL.TYPE'); const id = i++; // // util.getValue(lines, 'GENERAL.PATH'); const mac = util.getValue(lines, 'GENERAL.HWADDR'); const channel = ''; if (type.toLowerCase() === 'wifi') { result.push({ id, iface, mac, channel }); } }); return result; } catch (e) { return []; } } } function nmiDeviceLinux(iface) { const cmd = `nmcli -t -f general,wifi-properties,capabilities,ip4,ip6 device show ${iface} 2>/dev/null`; try { const lines = execSync(cmd).toString().split('\n'); const ssid = util.getValue(lines, 'GENERAL.CONNECTION'); return { iface, type: util.getValue(lines, 'GENERAL.TYPE'), vendor: util.getValue(lines, 'GENERAL.VENDOR'), product: util.getValue(lines, 'GENERAL.PRODUCT'), mac: util.getValue(lines, 'GENERAL.HWADDR').toLowerCase(), ssid: ssid !== '--' ? ssid : null }; } catch (e) { return {}; } } function nmiConnectionLinux(ssid) { const cmd = `nmcli -t --show-secrets connection show ${ssid} 2>/dev/null`; try { const lines = execSync(cmd).toString().split('\n'); const bssid = util.getValue(lines, '802-11-wireless.seen-bssids').toLowerCase(); return { ssid: ssid !== '--' ? ssid : null, uuid: util.getValue(lines, 'connection.uuid'), type: util.getValue(lines, 'connection.type'), autoconnect: util.getValue(lines, 'connection.autoconnect') === 'yes', security: util.getValue(lines, '802-11-wireless-security.key-mgmt'), bssid: bssid !== '--' ? bssid : null }; } catch (e) { return {}; } } function wpaConnectionLinux(iface) { const cmd = `wpa_cli -i ${iface} status 2>&1`; try { const lines = execSync(cmd).toString().split('\n'); const freq = util.toInt(util.getValue(lines, 'freq', '=')); return { ssid: util.getValue(lines, 'ssid', '='), uuid: util.getValue(lines, 'uuid', '='), security: util.getValue(lines, 'key_mgmt', '='), freq, channel: wifiChannelFromFrequencs(freq), bssid: util.getValue(lines, 'bssid', '=').toLowerCase() }; } catch (e) { return {}; } } function getWifiNetworkListNmi() { const result = []; const cmd = 'nmcli -t -m multiline --fields active,ssid,bssid,mode,chan,freq,signal,security,wpa-flags,rsn-flags device wifi list 2>/dev/null'; try { const stdout = execSync(cmd, { maxBuffer: 1024 * 20000 }); const parts = stdout.toString().split('ACTIVE:'); parts.shift(); parts.forEach(part => { part = 'ACTIVE:' + part; const lines = part.split(os.EOL); const channel = util.getValue(lines, 'CHAN'); const frequency = util.getValue(lines, 'FREQ').toLowerCase().replace('mhz', '').trim(); const security = util.getValue(lines, 'SECURITY').replace('(', '').replace(')', ''); const wpaFlags = util.getValue(lines, 'WPA-FLAGS').replace('(', '').replace(')', ''); const rsnFlags = util.getValue(lines, 'RSN-FLAGS').replace('(', '').replace(')', ''); result.push({ ssid: util.getValue(lines, 'SSID'), bssid: util.getValue(lines, 'BSSID').toLowerCase(), mode: util.getValue(lines, 'MODE'), channel: channel ? parseInt(channel, 10) : null, frequency: frequency ? parseInt(frequency, 10) : null, signalLevel: wifiDBFromQuality(util.getValue(lines, 'SIGNAL')), quality: parseFloat(util.getValue(lines, 'SIGNAL')), security: security && security !== 'none' ? security.split(' ') : [], wpaFlags: wpaFlags && wpaFlags !== 'none' ? wpaFlags.split(' ') : [], rsnFlags: rsnFlags && rsnFlags !== 'none' ? rsnFlags.split(' ') : [] }); }); return result; } catch (e) { return []; } } function getWifiNetworkListIw(iface) { const result = []; try { let iwlistParts = execSync(`export LC_ALL=C; iwlist ${iface} scan 2>&1; unset LC_ALL`).toString().split(' Cell '); if (iwlistParts[0].indexOf('resource busy') >= 0) { return -1; } if (iwlistParts.length > 1) { iwlistParts.shift(); iwlistParts.forEach(element => { const lines = element.split('\n'); const channel = util.getValue(lines, 'channel', ':', true); const address = (lines && lines.length && lines[0].indexOf('Address:') >= 0 ? lines[0].split('Address:')[1].trim().toLowerCase() : ''); const mode = util.getValue(lines, 'mode', ':', true); const frequency = util.getValue(lines, 'frequency', ':', true); const qualityString = util.getValue(lines, 'Quality', '=', true); const dbParts = qualityString.toLowerCase().split('signal level='); const db = dbParts.length > 1 ? util.toInt(dbParts[1]) : 0; const quality = db ? wifiQualityFromDB(db) : 0; const ssid = util.getValue(lines, 'essid', ':', true); // security and wpa-flags const isWpa = element.indexOf(' WPA ') >= 0; const isWpa2 = element.indexOf('WPA2 ') >= 0; const security = []; if (isWpa) { security.push('WPA'); } if (isWpa2) { security.push('WPA2'); } const wpaFlags = []; let wpaFlag = ''; lines.forEach(function (line) { const l = line.trim().toLowerCase(); if (l.indexOf('group cipher') >= 0) { if (wpaFlag) { wpaFlags.push(wpaFlag); } const parts = l.split(':'); if (parts.length > 1) { wpaFlag = parts[1].trim().toUpperCase(); } } if (l.indexOf('pairwise cipher') >= 0) { const parts = l.split(':'); if (parts.length > 1) { if (parts[1].indexOf('tkip')) { wpaFlag = (wpaFlag ? 'TKIP/' + wpaFlag : 'TKIP'); } else if (parts[1].indexOf('ccmp')) { wpaFlag = (wpaFlag ? 'CCMP/' + wpaFlag : 'CCMP'); } else if (parts[1].indexOf('proprietary')) { wpaFlag = (wpaFlag ? 'PROP/' + wpaFlag : 'PROP'); } } } if (l.indexOf('authentication suites') >= 0) { const parts = l.split(':'); if (parts.length > 1) { if (parts[1].indexOf('802.1x')) { wpaFlag = (wpaFlag ? '802.1x/' + wpaFlag : '802.1x'); } else if (parts[1].indexOf('psk')) { wpaFlag = (wpaFlag ? 'PSK/' + wpaFlag : 'PSK'); } } } }); if (wpaFlag) { wpaFlags.push(wpaFlag); } result.push({ ssid, bssid: address, mode, channel: channel ? util.toInt(channel) : null, frequency: frequency ? util.toInt(frequency.replace('.', '')) : null, signalLevel: db, quality, security, wpaFlags, rsnFlags: [] }); }); } return result; } catch (e) { return -1; } } function parseWifiDarwin(wifiObj) { const result = []; if (wifiObj) { wifiObj.forEach(function (wifiItem) { const signalLevel = wifiItem.RSSI; let security = []; let wpaFlags = []; if (wifiItem.WPA_IE) { security.push('WPA'); if (wifiItem.WPA_IE.IE_KEY_WPA_UCIPHERS) { wifiItem.WPA_IE.IE_KEY_WPA_UCIPHERS.forEach(function (ciphers) { if (ciphers === 0 && wpaFlags.indexOf('unknown/TKIP') === -1) { wpaFlags.push('unknown/TKIP'); } if (ciphers === 2 && wpaFlags.indexOf('PSK/TKIP') === -1) { wpaFlags.push('PSK/TKIP'); } if (ciphers === 4 && wpaFlags.indexOf('PSK/AES') === -1) { wpaFlags.push('PSK/AES'); } }); } } if (wifiItem.RSN_IE) { security.push('WPA2'); if (wifiItem.RSN_IE.IE_KEY_RSN_UCIPHERS) { wifiItem.RSN_IE.IE_KEY_RSN_UCIPHERS.forEach(function (ciphers) { if (ciphers === 0 && wpaFlags.indexOf('unknown/TKIP') === -1) { wpaFlags.push('unknown/TKIP'); } if (ciphers === 2 && wpaFlags.indexOf('TKIP/TKIP') === -1) { wpaFlags.push('TKIP/TKIP'); } if (ciphers === 4 && wpaFlags.indexOf('PSK/AES') === -1) { wpaFlags.push('PSK/AES'); } }); } } result.push({ ssid: wifiItem.SSID_STR, bssid: wifiItem.BSSID, mode: '', channel: wifiItem.CHANNEL, frequency: wifiFrequencyFromChannel(wifiItem.CHANNEL), signalLevel: signalLevel ? parseInt(signalLevel, 10) : null, quality: wifiQualityFromDB(signalLevel), security, wpaFlags, rsnFlags: [] }); }); } return result; } function wifiNetworks(callback) { return new Promise((resolve) => { process.nextTick(() => { let result = []; if (_linux) { result = getWifiNetworkListNmi(); if (result.length === 0) { try { const iwconfigParts = execSync('export LC_ALL=C; iwconfig 2>/dev/null; unset LC_ALL').toString().split('\n\n'); let iface = ''; iwconfigParts.forEach(element => { if (element.indexOf('no wireless') === -1 && element.trim() !== '') { iface = element.split(' ')[0]; } }); if (iface) { const res = getWifiNetworkListIw(iface); if (res === -1) { // try again after 4 secs setTimeout(function (iface) { const res = getWifiNetworkListIw(iface); if (res != -1) { result = res; } if (callback) { callback(result); } resolve(result); }, 4000); } else { result = res; if (callback) { callback(result); } resolve(result); } } else { if (callback) { callback(result); } resolve(result); } } catch (e) { if (callback) { callback(result); } resolve(result); } } else { if (callback) { callback(result); } resolve(result); } } else if (_darwin) { let cmd = '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s -x'; exec(cmd, { maxBuffer: 1024 * 40000 }, function (error, stdout) { const output = stdout.toString(); result = parseWifiDarwin(util.plistParser(output)); if (callback) { callback(result); } resolve(result); }); } else if (_windows) { let cmd = 'netsh wlan show networks mode=Bssid'; util.powerShell(cmd).then((stdout) => { const ssidParts = stdout.toString('utf8').split(os.EOL + os.EOL + 'SSID '); ssidParts.shift(); ssidParts.forEach(ssidPart => { const ssidLines = ssidPart.split(os.EOL); if (ssidLines && ssidLines.length >= 8 && ssidLines[0].indexOf(':') >= 0) { const bssidsParts = ssidPart.split(' BSSID'); bssidsParts.shift(); bssidsParts.forEach((bssidPart) => { const bssidLines = bssidPart.split(os.EOL); const bssidLine = bssidLines[0].split(':'); bssidLine.shift(); const bssid = bssidLine.join(':').trim().toLowerCase(); const channel = bssidLines[3].split(':').pop().trim(); const quality = bssidLines[1].split(':').pop().trim(); result.push({ ssid: ssidLines[0].split(':').pop().trim(), bssid, mode: '', channel: channel ? parseInt(channel, 10) : null, frequency: wifiFrequencyFromChannel(channel), signalLevel: wifiDBFromQuality(quality), quality: quality ? parseInt(quality, 10) : null, security: [ssidLines[2].split(':').pop().trim()], wpaFlags: [ssidLines[3].split(':').pop().trim()], rsnFlags: [] }); }); } }); if (callback) { callback(result); } resolve(result); }); } else { if (callback) { callback(result); } resolve(result); } }); }); } exports.wifiNetworks = wifiNetworks; function getVendor(model) { model = model.toLowerCase(); let result = ''; if (model.indexOf('intel') >= 0) { result = 'Intel'; } else if (model.indexOf('realtek') >= 0) { result = 'Realtek'; } else if (model.indexOf('qualcom') >= 0) { result = 'Qualcom'; } else if (model.indexOf('broadcom') >= 0) { result = 'Broadcom'; } else if (model.indexOf('cavium') >= 0) { result = 'Cavium'; } else if (model.indexOf('cisco') >= 0) { result = 'Cisco'; } else if (model.indexOf('marvel') >= 0) { result = 'Marvel'; } else if (model.indexOf('zyxel') >= 0) { result = 'Zyxel'; } else if (model.indexOf('melanox') >= 0) { result = 'Melanox'; } else if (model.indexOf('d-link') >= 0) { result = 'D-Link'; } else if (model.indexOf('tp-link') >= 0) { result = 'TP-Link'; } else if (model.indexOf('asus') >= 0) { result = 'Asus'; } else if (model.indexOf('linksys') >= 0) { result = 'Linksys'; } return result; } function wifiConnections(callback) { return new Promise((resolve) => { process.nextTick(() => { const result = []; if (_linux) { const ifaces = ifaceListLinux(); const networkList = getWifiNetworkListNmi(); ifaces.forEach(ifaceDetail => { const nmiDetails = nmiDeviceLinux(ifaceDetail.iface); const wpaDetails = wpaConnectionLinux(ifaceDetail.iface); const ssid = nmiDetails.ssid || wpaDetails.ssid; const network = networkList.filter(nw => nw.ssid === ssid); const nmiConnection = nmiConnectionLinux(ssid); const channel = network && network.length && network[0].channel ? network[0].channel : (wpaDetails.channel ? wpaDetails.channel : null); const bssid = network && network.length && network[0].bssid ? network[0].bssid : (wpaDetails.bssid ? wpaDetails.bssid : null); if (ssid && bssid) { result.push({ id: ifaceDetail.id, iface: ifaceDetail.iface, model: nmiDetails.product, ssid, bssid: network && network.length && network[0].bssid ? network[0].bssid : (wpaDetails.bssid ? wpaDetails.bssid : null), channel, frequency: channel ? wifiFrequencyFromChannel(channel) : null, type: nmiConnection.type ? nmiConnection.type : '802.11', security: nmiConnection.security ? nmiConnection.security : (wpaDetails.security ? wpaDetails.security : null), signalLevel: network && network.length && network[0].signalLevel ? network[0].signalLevel : null, txRate: null }); } }); if (callback) { callback(result); } resolve(result); } else if (_darwin) { let cmd = 'system_profiler SPNetworkDataType'; exec(cmd, function (error, stdout) { const parts1 = stdout.toString().split('\n\n Wi-Fi:\n\n'); if (parts1.length > 1) { const lines = parts1[1].split('\n\n')[0].split('\n'); const iface = util.getValue(lines, 'BSD Device Name', ':', true); const model = util.getValue(lines, 'hardware', ':', true); cmd = '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I'; exec(cmd, function (error, stdout) { const lines2 = stdout.toString().split('\n'); if (lines.length > 10) { const ssid = util.getValue(lines2, 'ssid', ':', true); const bssid = util.getValue(lines2, 'bssid', ':', true); const security = util.getValue(lines2, 'link auth', ':', true); const txRate = util.getValue(lines2, 'lastTxRate', ':', true); const channel = util.getValue(lines2, 'channel', ':', true).split(',')[0]; const type = '802.11'; const rssi = util.toInt(util.getValue(lines2, 'agrCtlRSSI', ':', true)); const noise = util.toInt(util.getValue(lines2, 'agrCtlNoise', ':', true)); const signalLevel = rssi - noise; if (ssid || bssid) { result.push({ id: 'Wi-Fi', iface, model, ssid, bssid, channel: util.toInt(channel), frequency: channel ? wifiFrequencyFromChannel(channel) : null, type, security, signalLevel, txRate }); } } if (callback) { callback(result); } resolve(result); }); } }); } else if (_windows) { let cmd = 'netsh wlan show interfaces'; util.powerShell(cmd).then(function (stdout) { const allLines = stdout.toString().split('\r\n'); for (let i = 0; i < allLines.length; i++) { allLines[i] = allLines[i].trim(); } const parts = allLines.join('\r\n').split(':\r\n\r\n'); parts.shift(); parts.forEach(part => { const lines = part.split('\r\n'); if (lines.length >= 5) { const iface = lines[0].indexOf(':') >= 0 ? lines[0].split(':')[1].trim() : ''; const model = lines[1].indexOf(':') >= 0 ? lines[1].split(':')[1].trim() : ''; const id = lines[2].indexOf(':') >= 0 ? lines[2].split(':')[1].trim() : ''; const ssid = util.getValue(lines, 'SSID', ':', true); const bssid = util.getValue(lines, 'BSSID', ':', true); const signalLevel = util.getValue(lines, 'Signal', ':', true); const type = util.getValue(lines, 'Radio type', ':', true) || util.getValue(lines, 'Type de radio', ':', true) || util.getValue(lines, 'Funktyp', ':', true) || null; const security = util.getValue(lines, 'authentication', ':', true) || util.getValue(lines, 'Authentification', ':', true) || util.getValue(lines, 'Authentifizierung', ':', true) || null; const channel = util.getValue(lines, 'Channel', ':', true) || util.getValue(lines, 'Canal', ':', true) || util.getValue(lines, 'Kanal', ':', true) || null; const txRate = util.getValue(lines, 'Transmit rate (mbps)', ':', true) || util.getValue(lines, 'Transmission (mbit/s)', ':', true) || util.getValue(lines, 'Empfangsrate (MBit/s)', ':', true) || null; if (model && id && ssid && bssid) { result.push({ id, iface, model, ssid, bssid, channel: util.toInt(channel), frequency: channel ? wifiFrequencyFromChannel(channel) : null, type, security, signalLevel, txRate: util.toInt(txRate) || null }); } } }); if (callback) { callback(result); } resolve(result); }); } else { if (callback) { callback(result); } resolve(result); } }); }); } exports.wifiConnections = wifiConnections; function wifiInterfaces(callback) { return new Promise((resolve) => { process.nextTick(() => { const result = []; if (_linux) { const ifaces = ifaceListLinux(); ifaces.forEach(ifaceDetail => { const nmiDetails = nmiDeviceLinux(ifaceDetail.iface); result.push({ id: ifaceDetail.id, iface: ifaceDetail.iface, model: nmiDetails.product ? nmiDetails.product : null, vendor: nmiDetails.vendor ? nmiDetails.vendor : null, mac: ifaceDetail.mac, }); }); if (callback) { callback(result); } resolve(result); } else if (_darwin) { let cmd = 'system_profiler SPNetworkDataType'; exec(cmd, function (error, stdout) { const parts1 = stdout.toString().split('\n\n Wi-Fi:\n\n'); if (parts1.length > 1) { const lines = parts1[1].split('\n\n')[0].split('\n'); const iface = util.getValue(lines, 'BSD Device Name', ':', true); const mac = util.getValue(lines, 'MAC Address', ':', true); const model = util.getValue(lines, 'hardware', ':', true); result.push({ id: 'Wi-Fi', iface, model, vendor: '', mac }); } if (callback) { callback(result); } resolve(result); }); } else if (_windows) { let cmd = 'netsh wlan show interfaces'; util.powerShell(cmd).then(function (stdout) { const allLines = stdout.toString().split('\r\n'); for (let i = 0; i < allLines.length; i++) { allLines[i] = allLines[i].trim(); } const parts = allLines.join('\r\n').split(':\r\n\r\n'); parts.shift(); parts.forEach(part => { const lines = part.split('\r\n'); if (lines.length >= 5) { const iface = lines[0].indexOf(':') >= 0 ? lines[0].split(':')[1].trim() : ''; const model = lines[1].indexOf(':') >= 0 ? lines[1].split(':')[1].trim() : ''; const id = lines[2].indexOf(':') >= 0 ? lines[2].split(':')[1].trim() : ''; const macParts = lines[3].indexOf(':') >= 0 ? lines[3].split(':') : []; macParts.shift(); const mac = macParts.join(':').trim(); const vendor = getVendor(model); if (iface && model && id && mac) { result.push({ id, iface, model, vendor, mac, }); } } }); if (callback) { callback(result); } resolve(result); }); } else { if (callback) { callback(result); } resolve(result); } }); }); } exports.wifiInterfaces = wifiInterfaces;