223 lines
7.5 KiB
JavaScript
223 lines
7.5 KiB
JavaScript
'use strict';
|
|
|
|
const os = require('os');
|
|
const { exec } = require('child_process');
|
|
|
|
const macLookup = require('./macLookup.js');
|
|
|
|
var flag,
|
|
ipCommand,
|
|
osType = os.type();
|
|
|
|
switch(osType) {
|
|
case 'Windows_NT':
|
|
flag = '-w';
|
|
ipCommand = 'ipconfig';
|
|
break;
|
|
case 'Linux':
|
|
flag = '-w';
|
|
ipCommand = 'ifconfig';
|
|
break;
|
|
case 'Darwin':
|
|
flag = '-t';
|
|
ipCommand = 'ifconfig';
|
|
break;
|
|
default:
|
|
throw new Error(`Unsupported OS: ${osType}`);
|
|
}
|
|
|
|
function Arpping({ timeout = 5, includeEndpoints = false, useCache = true, cacheTimeout = 3600 } = {}) {
|
|
if (timeout < 1 || timeout > 60) throw new Error(`Invalid timeout parameter: ${timeout}. Timeout should be between 1 and 60.`);
|
|
this.timeout = parseInt(timeout) || timeout.toFixed(0);
|
|
|
|
this.includeEndpoints = includeEndpoints;
|
|
this.myIP = null;
|
|
|
|
this.useCache = useCache;
|
|
this.cache = [];
|
|
this.cacheTimeout = cacheTimeout;
|
|
this.cacheUpdate = 0;
|
|
}
|
|
|
|
/**
|
|
* Build array of full ip range (xxx.xxx.x.1-255) given example ip address
|
|
* @param {String} ip
|
|
*/
|
|
Arpping.prototype._getFullRange = function(ip) {
|
|
// don't use default assignment so false-y values are overwritten
|
|
ip = ip || this.myIP;
|
|
var ipStart = ip.substr(0, ip.lastIndexOf('.') + 1);
|
|
return this.includeEndpoints ?
|
|
Array.from({ length: 255 }, (_, i) => ipStart + (i + 1)):
|
|
Array.from({ length: 253 }, (_, i) => ipStart + (i + 2));
|
|
}
|
|
|
|
/**
|
|
* Find ip and mac addresses of host device
|
|
*/
|
|
Arpping.prototype.findMyInfo = function() {
|
|
return new Promise((resolve, reject) => {
|
|
exec(ipCommand, (err, stdout, stderr) => {
|
|
if (err) return reject(err);
|
|
|
|
var output = null;
|
|
if (osType == 'Linux') {
|
|
if (stdout.indexOf('wlan0') == -1) return reject(new Error('No wifi connection'));
|
|
output = stdout.split('wlan0')[1];
|
|
}
|
|
else {
|
|
output = stdout.slice(stdout.indexOf('en0'));
|
|
output = output.slice(0, output.indexOf('active\n')) + 'active';
|
|
if (output.split('status: ')[1] == 'inactive') return reject(new Error('No wifi connection'));
|
|
}
|
|
var ip = output.slice(output.indexOf('inet ') + 5, output.indexOf(' netmask')).trim();
|
|
var mac = output.slice(output.indexOf('ether ')).split('\n')[0].split(' ')[1].trim();
|
|
var type = macLookup(mac);
|
|
|
|
this.myIP = ip;
|
|
return resolve(type ? { ip, mac, type }: { ip, mac });
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Discover all hosts connected to your local network or based on a reference IP address
|
|
* @param {String} refIP
|
|
* @param {Boolean} retry
|
|
*/
|
|
Arpping.prototype.discover = function(refIP, retry = true) {
|
|
if (this.useCache && this.cache.length && Date.now() - this.cacheUpdate < this.cacheTimeout * 1000) {
|
|
return new Promise((resolve, reject) => resolve(this.cache));
|
|
}
|
|
if (!refIP && !this.myIP) {
|
|
if (retry) return this.findMyInfo().then(info => this.discover(info.ip, false));
|
|
return new Promise((resolve, reject) => reject(new Error('Failed to find host IP address')));
|
|
}
|
|
|
|
var range = this._getFullRange(refIP);
|
|
return this.ping(range).then(({ hosts }) => this.arp(hosts)).then(({ hosts }) => {
|
|
this.cache = hosts.slice(0);
|
|
this.cacheUpdate = Date.now();
|
|
return hosts;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ping a range of ip addresses
|
|
* @param {Array} range
|
|
*/
|
|
Arpping.prototype.ping = function(range) {
|
|
if (!(Array.isArray(range) && range.length)) {
|
|
if (!this.myIP) return this.findMyInfo().then(() => this.ping(range));
|
|
range = this._getFullRange();
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
var hosts = [],
|
|
missing =[],
|
|
checked = 0;
|
|
range.forEach(ip => {
|
|
exec(`ping ${flag} ${this.timeout} ${ip}`, (err, stdout, stderr) => {
|
|
checked++;
|
|
if (err || stdout.indexOf('100% packet loss') > -1) missing.push(ip);
|
|
else hosts.push(ip);
|
|
|
|
if (checked == range.length) resolve({ hosts, missing });
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Arp a range of ip addresses
|
|
* @param {Array} range
|
|
*/
|
|
Arpping.prototype.arp = function(range) {
|
|
return new Promise((resolve, reject) => {
|
|
if (typeof range == 'string') range = [ range ];
|
|
else if (!Array.isArray(range)) return reject(new Error('range must be an array of IP addresses'));
|
|
else if (!range.length) return resolve({ hosts: [], missing: [] });
|
|
|
|
var hosts = [],
|
|
missing = [],
|
|
checked = 0;
|
|
range.forEach(ip => {
|
|
exec(`arp ${ip}`, (err, stdout, stderr) => {
|
|
checked++;
|
|
if (err || stdout.indexOf('no entry') > -1) missing.push(ip);
|
|
else {
|
|
var mac = (osType == 'Linux') ?
|
|
stdout.split('\n')[1].replace(/ +/g, ' ').split(' ')[2]:
|
|
stdout.split(' ')[3];
|
|
if (mac.includes('incomplete')) missing.push(ip);
|
|
else {
|
|
var host = { ip, mac };
|
|
var type = macLookup(mac);
|
|
if (type) host.type = type;
|
|
if (ip == this.myIP) host.isHostDevice = true;
|
|
hosts.push(host);
|
|
}
|
|
}
|
|
|
|
if (checked == range.length) resolve({ hosts, missing });
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Search for one or multiple IP addresses
|
|
* @param {String/Array} ipArray
|
|
* @param {String} refIP
|
|
*/
|
|
Arpping.prototype.searchByIpAddress = function(ipArray, refIP) {
|
|
if (typeof ipArray === 'string') ipArray = [ ipArray ];
|
|
else if (!Array.isArray(ipArray) || !ipArray.length) {
|
|
return new Promise((resolve, reject) => reject(new Error(`Invalid ipArray: ${ipArray}. Search input should be one ip address string or an array of ip strings.`)));
|
|
}
|
|
|
|
return this.discover(refIP || ipArray[0]).then(hosts => {
|
|
var hostIPs = hosts.map(h => h.ip);
|
|
return {
|
|
hosts: hosts.filter(h => ipArray.includes(h.ip)),
|
|
missing: ipArray.filter(ip => !hostIPs.includes(ip))
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Search for one or multiple, full or partial mac addresses
|
|
* @param {String/Array} macArray
|
|
* @param {String} refIP
|
|
*/
|
|
Arpping.prototype.searchByMacAddress = function(macArray, refIP) {
|
|
if (typeof macArray == 'string') macArray = [ macArray ];
|
|
else if (!Array.isArray(macArray) || !macArray.length) {
|
|
return new Promise((resolve, reject) => reject(`Invalid macArray: ${macArray}. Search input should be one mac address string or an array of mac address strings.`));
|
|
}
|
|
|
|
return this.discover(refIP).then(hosts => {
|
|
var check = JSON.stringify(hosts);
|
|
return {
|
|
hosts: hosts.filter(h => {
|
|
h.matched = [];
|
|
for (var m of macArray) if (h.mac.indexOf(m) > -1) h.matched.push(m);
|
|
return h.matched.length;
|
|
}),
|
|
missing: macArray.filter(m => check.indexOf(m) == -1)
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Search for devices with the designated mac address type
|
|
* @param {String} macType
|
|
* @param {String} refIP
|
|
*/
|
|
Arpping.prototype.searchByMacType = function(macType, refIP) {
|
|
macType = macType.toLowerCase();
|
|
return this.discover(refIP).then(hosts => hosts.filter(h => h.type && h.type.toLowerCase() == macType));
|
|
}
|
|
|
|
module.exports = Arpping;
|