mirror of
https://github.com/Ylianst/MeshCommander
synced 2025-12-05 21:53:19 +00:00
235 lines
10 KiB
JavaScript
235 lines
10 KiB
JavaScript
/**
|
|
* @description Meshcentral Intel AMT Local Scanner
|
|
* @author Ylian Saint-Hilaire
|
|
* @version v0.0.1
|
|
*/
|
|
|
|
// Construct a Intel AMT Scanner object
|
|
var CreateAmtScanner = function (func) {
|
|
var obj = {};
|
|
obj.active = false;
|
|
obj.dgram = require('dgram');
|
|
obj.servers = {};
|
|
obj.rserver = {};
|
|
obj.rpacket = null;
|
|
obj.tagToId = {}; // Tag --> { lastpong: time, id: NodeId }
|
|
obj.scanTable = {}; // Handle --> ScanInfo : { lastping: time, lastpong: time, nodeinfo:{node} }
|
|
obj.scanTableTags = {}; // Tag --> ScanInfo
|
|
obj.pendingSends = []; // We was to stagger the sends using a 10ms timer
|
|
obj.pendingSendTimer = null;
|
|
obj.mainTimer = null;
|
|
obj.nextTag = 0;
|
|
obj.computerPresenceFunc = func;
|
|
var PeriodicScanTime = 30000; // Interval between scan sweeps
|
|
var PeriodicScanTimeout = 65000; // After this time, timeout the device.
|
|
|
|
// Build a RMCP packet with a given tag field
|
|
obj.buildRmcpPing = function (tag) {
|
|
var packet = new Buffer(hex2rstr('06000006000011BE80000000'), 'ascii');
|
|
packet[9] = tag;
|
|
return packet;
|
|
}
|
|
|
|
// Start scanning for local network Intel AMT computers
|
|
obj.start = function () {
|
|
if (obj.active == false) {
|
|
obj.active = true;
|
|
obj.performScan();
|
|
obj.mainTimer = setInterval(obj.performScan, PeriodicScanTime);
|
|
}
|
|
}
|
|
|
|
// Stop scanning for local network Intel AMT computers
|
|
obj.stop = function () {
|
|
obj.active = false;
|
|
for (var i in obj.servers) { obj.servers[i].close(); } // Stop all servers
|
|
obj.servers = {};
|
|
if (obj.mainTimer != null) { clearInterval(obj.mainTimer); obj.mainTimer = null; }
|
|
}
|
|
|
|
// Scan for Intel AMT computers using network multicast
|
|
obj.performRangeScan = function (id, rangestr, func) {
|
|
if (obj.rpacket == null) { obj.rpacket = obj.buildRmcpPing(0); }
|
|
var range = obj.parseIpv4Range(rangestr);
|
|
//console.log(obj.IPv4NumToStr(range.min), obj.IPv4NumToStr(range.max));
|
|
if (range == null || (range.min > range.max)) return false;
|
|
var rangeinfo = { id: id, range: rangestr, min: range.min, max: range.max, results: {}, resultCount: 0 };
|
|
obj.rserver[id] = rangeinfo;
|
|
rangeinfo.server = obj.dgram.createSocket("udp4");
|
|
rangeinfo.server.bind(0);
|
|
rangeinfo.server.on('error', function(err) { console.log(err); });
|
|
rangeinfo.server.on('message', function(data, rinfo) { obj.parseRmcpPacket(data, rinfo, 0, func, rangeinfo); });
|
|
rangeinfo.server.on('listening', function () {
|
|
for (var i = rangeinfo.min; i <= rangeinfo.max; i++) {
|
|
rangeinfo.server.send(obj.rpacket, 0, obj.rpacket.length, 623, obj.IPv4NumToStr(i));
|
|
rangeinfo.server.send(obj.rpacket, 0, obj.rpacket.length, 623, obj.IPv4NumToStr(i));
|
|
}
|
|
});
|
|
rangeinfo.timer = setTimeout(function () {
|
|
if (rangeinfo.resultCount == 0) { func(obj, null); }
|
|
rangeinfo.server.close();
|
|
delete rangeinfo.server;
|
|
delete rangeinfo;
|
|
}, 5000);
|
|
return true;
|
|
}
|
|
|
|
// Parse range, used to parse "ip", "ip/mask" or "ip-ip" notation.
|
|
// Return the start and end value of the scan
|
|
obj.parseIpv4Range = function (range) {
|
|
if (range == undefined || range == null) return null;
|
|
var x = range.split('-');
|
|
if (x.length == 2) { return { min: obj.parseIpv4Addr(x[0]), max: obj.parseIpv4Addr(x[1]) }; }
|
|
x = range.split('/');
|
|
if (x.length == 2) {
|
|
var ip = obj.parseIpv4Addr(x[0]), masknum = parseInt(x[1]), mask = 0;
|
|
if (masknum < 16) return null;
|
|
for (var i = 0; i < (32 - masknum) ; i++) { mask = (mask << 1); mask++; }
|
|
return { min: ip & (0xFFFFFFFF - mask), max: (ip & (0xFFFFFFFF - mask)) + mask };
|
|
}
|
|
x = obj.parseIpv4Addr(range);
|
|
if (x == null) return null;
|
|
return { min: x, max: x };
|
|
}
|
|
|
|
// Parse IP address. Takes a
|
|
obj.parseIpv4Addr = function (addr) {
|
|
var x = addr.split('.');
|
|
if (x.length == 4) { return (parseInt(x[0]) << 24) + (parseInt(x[1]) << 16) + (parseInt(x[2]) << 8) + (parseInt(x[3]) << 0); }
|
|
return null;
|
|
}
|
|
|
|
// IP address number to string
|
|
obj.IPv4NumToStr = function (num) {
|
|
return ((num >> 24) & 0xFF) + '.' + ((num >> 16) & 0xFF) + '.' + ((num >> 8) & 0xFF) + '.' + (num & 0xFF);
|
|
}
|
|
|
|
// Scan the list of all computers
|
|
obj.performScan = function () {
|
|
if (obj.active == false || computerlist == undefined) return;
|
|
for (var i in obj.scanTable) { obj.scanTable[i].present = false; }
|
|
if (computerlist.length > 0) {
|
|
for (var i in computerlist) {
|
|
var computer = computerlist[i];
|
|
var host = computer.host.toLowerCase();
|
|
if ((host != '127.0.0.1') && (host != '::1') && (host != 'localhost') && (host.split(':').length == 1)) {
|
|
var scaninfo = obj.scanTable[computer.h];
|
|
if (scaninfo == undefined) {
|
|
var tag = obj.nextTag++;
|
|
obj.scanTableTags[tag] = obj.scanTable[computer.h] = scaninfo = { computer: computer, present: true, tag: tag, scanstate: -1, lastpong: Date.now() };
|
|
} else {
|
|
scaninfo.present = true;
|
|
var delta = Date.now() - scaninfo.lastpong;
|
|
if ((delta > PeriodicScanTimeout) && (scaninfo.scanstate != 0)) { // More than the timeout without a response, mark the node as unknown state
|
|
scaninfo.scanstate = 0;
|
|
if (obj.computerPresenceFunc) { obj.computerPresenceFunc(scaninfo.computer.h, false); }
|
|
}
|
|
}
|
|
// Start scanning this node
|
|
scaninfo.lastping = Date.now();
|
|
obj.checkAmtPresence(computer.host, scaninfo.tag);
|
|
}
|
|
}
|
|
}
|
|
for (var i in obj.scanTable) {
|
|
if (obj.scanTable[i].present == false) {
|
|
// Stop scanning this node
|
|
delete obj.scanTableTags[obj.scanTable[i].tag];
|
|
delete obj.scanTable[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check the presense of a specific Intel AMT computer
|
|
obj.checkAmtPresence = function (host, tag) {
|
|
var serverid = Math.floor(tag / 255);
|
|
var servertag = (tag % 255);
|
|
var packet = obj.buildRmcpPing(servertag);
|
|
var server = obj.servers[serverid];
|
|
if (server == undefined) {
|
|
// Start new server
|
|
server = obj.dgram.createSocket('udp4');
|
|
server.on('error', function(err) { });
|
|
server.on('message', function (data, rinfo) { obj.parseRmcpPacket(data, rinfo, serverid, obj.changeConnectState, null); });
|
|
server.on('listening', function() { obj.pendingSends.push([ server, packet, host ]); if (obj.pendingSendTimer == null) { obj.pendingSendTimer = setInterval(obj.sendPendingPacket, 10); } });
|
|
server.bind(0);
|
|
obj.servers[serverid] = server;
|
|
} else {
|
|
// Use existing server
|
|
obj.pendingSends.push([ server, packet, host ]);
|
|
if (obj.pendingSendTimer == null) { obj.pendingSendTimer = setInterval(obj.sendPendingPacket, 10); }
|
|
}
|
|
}
|
|
|
|
// Send a pending RMCP packet
|
|
obj.sendPendingPacket = function () {
|
|
try {
|
|
var p = obj.pendingSends.shift();
|
|
if (p != undefined) {
|
|
p[0].send(p[1], 0, p[1].length, 623, p[2]);
|
|
p[0].send(p[1], 0, p[1].length, 623, p[2]);
|
|
} else {
|
|
clearInterval(obj.pendingSendTimer);
|
|
obj.pendingSendTimer = null;
|
|
}
|
|
} catch (e) { }
|
|
}
|
|
|
|
// Parse RMCP packet
|
|
obj.parseRmcpPacket = function (data, rinfo, serverid, func, rangeinfo) {
|
|
if (data == null || data.length < 20) return;
|
|
if (((data[12] == 0) || (data[13] != 0) || (data[14] != 1) || (data[15] != 0x57)) && (data[21] & 32)) {
|
|
var result = { tag: (serverid * 255) + data[9], ver: ((data[18] >> 4) & 0x0F) + '.' + (data[18] & 0x0F), state: (data[19] & 0x03), port: ((data[16] * 256) + data[17]), dualport: (((data[19] & 0x04) != 0) ? true : false), rinfo: rinfo };
|
|
result.tls = (((result.openPort == 16993) || (result.dualPorts == true)) ? 1 : 0)
|
|
if (rangeinfo != null) {
|
|
if ((result.state <= 2) && (!rangeinfo.results[rinfo.address])) {
|
|
rangeinfo.results[rinfo.address] = result;
|
|
rangeinfo.resultCount++;
|
|
obj.reverseLookup(rinfo.address, function (err, domains) {
|
|
result.domains = domains;
|
|
func(obj, result, rangeinfo);
|
|
});
|
|
}
|
|
} else {
|
|
func(obj, result, rangeinfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use the RMCP packet to change the computer state
|
|
obj.changeConnectState = function (obj, result, rangeinfo) {
|
|
var scaninfo = obj.scanTableTags[result.tag];
|
|
if (scaninfo) {
|
|
scaninfo.lastpong = Date.now();
|
|
if (scaninfo.scanstate != 1) {
|
|
scaninfo.scanstate = 1;
|
|
if (obj.computerPresenceFunc) obj.computerPresenceFunc(scaninfo.computer.h, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Perform DNS reverse lookup
|
|
obj.reverseLookup = function(ip, callback) {
|
|
var callbackCalled = false, timer = null;
|
|
var doCallback = function (err, domains) {
|
|
if (err) domains = [ip];
|
|
if (callbackCalled) return;
|
|
callbackCalled = true;
|
|
if (timer != null) {
|
|
// Cancelling timer to safe few seconds
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
callback(err, domains);
|
|
}
|
|
// throw failed upon timeout
|
|
timer = setTimeout(function () {
|
|
timer = null; // Ensure timer is nullified
|
|
doCallback(new Error("Timeout exceeded"), null);
|
|
}, 3000);
|
|
|
|
require("dns").reverse(ip, doCallback);
|
|
}
|
|
|
|
return obj;
|
|
} |