2* @description MeshCentral Mesh Agent Local Scanner
3* @author Ylian Saint-Hilaire
4* @copyright Intel Corporation 2018-2022
11/*jshint strict:false */
13/*jshint esversion: 6 */
16// Construct a Mesh Scanner object
17// TODO: We need once "server4" and "server6" per interface, or change the default multicast interface as we send.
18module.exports.CreateMeshScanner = function (parent) {
21 obj.dgram = require('dgram');
22 obj.common = require('./common.js');
26 const periodicScanTime = (60000 * 20); // Interval between scans, 20 minutes.
27 const membershipIPv4 = '239.255.255.235';
28 const membershipIPv6 = 'FF02:0:0:0:0:0:0:FE';
29 obj.agentCertificateHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'hex' }).toUpperCase();
31 obj.pendingOutboundPackets = [];
32 obj.pendingOutboundTimer = null;
34 // Setup the multicast key if present
35 if ((typeof obj.parent.args.localdiscovery == 'object') && (typeof obj.parent.args.localdiscovery.key == 'string') && (obj.parent.args.localdiscovery.key.length > 0)) {
36 obj.multicastKey = parent.crypto.createHash('sha384').update(obj.parent.args.localdiscovery.key).digest('raw').slice(0, 32);
40 function encryptPacket(plainPacket) {
41 if (obj.multicastKey == null) { return plainPacket; }
42 const iv = parent.crypto.randomBytes(16), aes = parent.crypto.createCipheriv('aes-256-cbc', obj.multicastKey, iv);
43 var ciphertext = aes.update(plainPacket);
44 return Buffer.concat([iv, ciphertext, aes.final()]);
48 function decryptPacket(packet) {
49 if (obj.multicastKey == null) { return packet; }
50 if (packet.length < 17) { return null; }
52 const iv = packet.slice(0, 16), data = packet.slice(16);
53 const aes = parent.crypto.createDecipheriv('aes-256-cbc', obj.multicastKey, iv);
54 var plaintextBytes = Buffer.from(aes.update(data));
55 return Buffer.concat([plaintextBytes, aes.final()]);
56 } catch (ex) { return null; }
59 // Get a list of IPv4 and IPv6 interface addresses
60 function getInterfaceList() {
62 var ipv4 = ['*'], ipv6 = ['*']; // Bind to IN_ADDR_ANY always
63 var interfaces = require('os').networkInterfaces();
64 for (i in interfaces) {
65 var xinterface = interfaces[i];
66 for (var j in xinterface) {
67 var interface2 = xinterface[j];
68 if ((interface2.mac != '00:00:00:00:00:00') && (interface2.internal == false)) {
69 if (interface2.family == 'IPv4') { ipv4.push(interface2.address); }
70 if (interface2.family == 'IPv6') { ipv6.push(interface2.address + '%' + i); }
74 return { ipv4: ipv4, ipv6: ipv6 };
77 // Setup all IPv4 and IPv6 servers
78 function setupServers() {
79 var addresses = getInterfaceList(), i, localAddress, bindOptions;
80 for (i in obj.servers4) { obj.servers4[i].xxclear = true; }
81 for (i in obj.servers6) { obj.servers6[i].xxclear = true; }
82 for (i in addresses.ipv4) {
83 localAddress = addresses.ipv4[i];
84 if (obj.servers4[localAddress] != null) {
85 // Server already exists
86 obj.servers4[localAddress].xxclear = false;
88 // Create a new IPv4 server
90 var server4 = obj.dgram.createSocket({ type: 'udp4', reuseAddr: true });
91 server4.xxclear = false;
93 server4.xxlocal = localAddress;
94 server4.on('error', function (err) { /*if (this.xxlocal == '*') { console.log("ERROR: Server port 16989 not available, check if server is running twice."); } this.close(); delete obj.servers6[this.xxlocal];*/ });
95 bindOptions = { port: 16989, exclusive: true };
96 if (server4.xxlocal != '*') { bindOptions.address = server4.xxlocal; }
97 server4.bind(bindOptions, function () {
100 try { this.setBroadcast(true); this.setMulticastTTL(128); this.addMembership(membershipIPv4, this.xxlocal); } catch (e) { doscan = false; }
101 this.on('error', function (error) { /*console.log('Error: ' + error);*/ });
102 this.on('message', function (msg, info) { onUdpPacket(msg, info, this); });
103 if (doscan == true) { obj.performScan(this); obj.performScan(this); }
104 } catch (e) { console.log(e); }
106 obj.servers4[localAddress] = server4;
113 for (i in addresses.ipv6) {
114 localAddress = addresses.ipv6[i];
115 if (obj.servers6[localAddress] != null) {
116 // Server already exists
117 obj.servers6[localAddress].xxclear = false;
119 // Create a new IPv6 server
121 var server6 = obj.dgram.createSocket({ type: 'udp6', reuseAddr: true });
122 server6.xxclear = false;
124 server6.xxlocal = localAddress;
125 server6.on('error', function (err) { /*this.close(); delete obj.servers6[this.xxlocal];*/ });
126 bindOptions = { port: 16989, exclusive: true };
127 if (server6.xxlocal != '*') { bindOptions.address = server6.xxlocal; }
128 server6.bind(bindOptions, function () {
131 try { this.setBroadcast(true); this.setMulticastTTL(128); this.addMembership(membershipIPv6, this.xxlocal); } catch (e) { doscan = false; }
132 this.on('error', function (error) { console.log('Error: ' + error); });
133 this.on('message', function (msg, info) { onUdpPacket(msg, info, this); });
134 if (doscan == true) { obj.performScan(this); obj.performScan(this); }
135 } catch (e) { console.log(e); }
137 obj.servers6[localAddress] = server6;
144 for (i in obj.servers4) { if (obj.servers4[i].xxclear == true) { obj.servers4[i].close(); delete obj.servers4[i]; } }
145 for (i in obj.servers6) { if (obj.servers6[i].xxclear == true) { obj.servers6[i].close(); delete obj.servers6[i]; } }
148 // Clear all IPv4 and IPv6 servers
149 function clearServers() {
151 for (i in obj.servers4) { obj.servers4[i].close(); delete obj.servers4[i]; }
152 for (i in obj.servers6) { obj.servers6[i].close(); delete obj.servers6[i]; }
155 // Start scanning for local network Mesh Agents
156 obj.start = function () {
157 if (obj.server4 != null) return;
159 // Setup the local discovery values
160 var name = 'MeshCentral';
163 if ((typeof obj.parent.config.domains[''].title == 'string') && (obj.parent.config.domains[''].title.length > 0)) {
164 name = obj.parent.config.domains[''].title; info = '';
166 if ((typeof obj.parent.config.domains[''].title2 == 'string') && (obj.parent.config.domains[''].title2.length > 0)) {
167 info = obj.common.replacePlaceholders(obj.parent.config.domains[''].title2, {
168 'serverversion': obj.parent.currentVer,
169 'servername': obj.getWebServerName(domain, req),
170 'agentsessions': Object.keys(parent.webserver.wsagents).length,
171 'connectedusers': Object.keys(parent.webserver.wssessions).length,
172 'userssessions': Object.keys(parent.webserver.wssessions2).length,
173 'relaysessions': parent.webserver.relaySessionCount,
174 'relaycount': Object.keys(parent.webserver.wsrelays).length
181 if ((typeof obj.parent.args.localdiscovery.name == 'string') && (obj.parent.args.localdiscovery.name.length > 0)) {
182 name = obj.parent.args.localdiscovery.name; info = '';
183 try { if ((typeof obj.parent.args.localdiscovery.info == 'string') && (obj.parent.args.localdiscovery.info.length > 0)) { info = obj.parent.args.localdiscovery.info; } } catch (ex) { }
186 if (info == '') { info = parent.certificates.CommonName; }
188 // Figure out the correct websocket port
189 var port = (parent.args.aliasport)?parent.args.aliasport:parent.args.port;
191 // Build the IPv4 response
192 var url = 'wss://%s:' + port + '/agent.ashx';
193 obj.multicastPacket4 = Buffer.from("MeshCentral2|" + obj.agentCertificateHashHex + '|' + url, 'ascii');
194 if (parent.certificates.CommonName.indexOf('.') != -1) { url = 'wss://' + parent.certificates.CommonName + ':' + port + '/agent.ashx'; }
195 obj.multicastPacket4x = Buffer.from("MeshCentral2|" + obj.agentCertificateHashHex + '|' + url + '|' + name + '|' + info, 'ascii');
197 // Build the IPv6 response
198 url = 'wss://[%s]:' + port + '/agent.ashx';
199 obj.multicastPacket6 = Buffer.from("MeshCentral2|" + obj.agentCertificateHashHex + '|' + url, 'ascii');
200 if (parent.certificates.CommonName.indexOf('.') != -1) { url = 'wss://' + parent.certificates.CommonName + ':' + port + '/agent.ashx'; }
201 obj.multicastPacket6x = Buffer.from("MeshCentral2|" + obj.agentCertificateHashHex + '|' + url + '|' + name + '|' + info, 'ascii');
204 obj.mainTimer = setInterval(obj.performScan, periodicScanTime);
208 // Stop scanning for local network Mesh Agents
209 obj.stop = function () {
210 if (obj.mainTimer != null) { clearInterval(obj.mainTimer); obj.mainTimer = null; }
214 // Look for all Mesh Agents that may be locally reachable, indicating the presense of this server.
215 obj.performScan = function (server) {
217 if (server != null) {
218 if (server.xxtype == 4) { var p = encryptPacket(obj.multicastPacket4); try { server.send(p, 0, p.length, 16990, membershipIPv4); } catch (e) { } }
219 if (server.xxtype == 6) { var p = encryptPacket(obj.multicastPacket6); try { server.send(p, 0, p.length, 16990, membershipIPv6); } catch (e) { } }
220 if ((server.xxtype == 4) && (server.xxlocal == '*')) { var p = encryptPacket(obj.multicastPacket4); try { server.send(p, 0, p.length, 16990, '127.0.0.1'); } catch (e) { } try { server.send(p, 0, p.length, 16990, '255.255.255.255'); } catch (e) { } }
221 if ((server.xxtype == 6) && (server.xxlocal == '*')) { var p = encryptPacket(obj.multicastPacket6); try { server.send(p, 0, p.length, 16990, '::1'); } catch (e) { } }
223 for (i in obj.servers4) { var p = encryptPacket(obj.multicastPacket4); try { obj.servers4[i].send(p, 0, p.length, 16990, membershipIPv4); } catch (e) { } }
224 for (i in obj.servers6) { var p = encryptPacket(obj.multicastPacket6); try { obj.servers6[i].send(p, 0, p.length, 16990, membershipIPv6); } catch (e) { } }
225 setupServers(); // Check if any network interfaces where added or removed
229 // Called when a UDP packet is received from an agent.
230 function onUdpPacket(msg, info, server) {
231 // Decrypt the packet if needed
232 if ((msg = decryptPacket(msg)) == null) return;
234 //console.log('Received ' + msg.length + ' bytes from ' + info.address + ':' + info.port + ', on interface: ' + server.xxlocal + '.');
235 if ((msg.length == 96) && (msg.toString('ascii') == obj.agentCertificateHashHex)) {
236 if (server.xxtype == 4) { var p = encryptPacket(obj.multicastPacket4); try { server.send(p, 0, p.length, info.port, info.address); } catch (e) { } }
237 if (server.xxtype == 6) { var p = encryptPacket(obj.multicastPacket6); try { server.send(p, 0, p.length, info.port, info.address); } catch (e) { } }
238 } else if (msg.toString('ascii') == 'MeshServerScan') {
239 if (server.xxtype == 4) { var p = encryptPacket(obj.multicastPacket4x); try { server.send(p, 0, p.length, info.port, info.address); } catch (e) { } }
240 if (server.xxtype == 6) { var p = encryptPacket(obj.multicastPacket6x); try { server.send(p, 0, p.length, info.port, info.address); } catch (e) { } }
244 // Send the next packet in the pending list, stop the timer if we are done.
245 function sendPendingPacket() {
246 if (obj.pendingOutboundPackets.length == 0) { if (obj.pendingOutboundTimer != null) { clearInterval(obj.pendingOutboundTimer); obj.pendingOutboundTimer = null; } return; }
247 var packet = obj.pendingOutboundPackets.shift();
248 if (packet != null) { packet[0].send(packet[1], 0, packet[1].length, packet[2], packet[3]); }
251 // As a side job, we also send server wake-on-lan packets
252 obj.wakeOnLan = function (macs, host) {
253 var i, j, futureTime = 0;
255 var mac = macs[i].split(':').join('');
256 var hexpacket = 'FFFFFFFFFFFF';
257 for (j = 0; j < 16; j++) { hexpacket += mac; }
258 var wakepacket = Buffer.from(hexpacket, 'hex');
260 // Add all wake packets to the pending list
261 for (var k = 0; k < 5; k++) {
262 for (j in obj.servers4) {
263 obj.pendingOutboundPackets.push([obj.servers4[j], wakepacket, 7, '255.255.255.255']); // IPv4 Broadcast
264 obj.pendingOutboundPackets.push([obj.servers4[j], wakepacket, 16990, membershipIPv4]); // IPv4 Multicast
265 if (host != null) { obj.pendingOutboundPackets.push([obj.servers4[j], wakepacket, 7, host]); } // IPv4 Directed
267 for (j in obj.servers6) {
268 obj.pendingOutboundPackets.push([obj.servers6[j], wakepacket, 16990, membershipIPv6]); // IPv6 Multicast
272 // Send each packet at 10ms interval
273 // This packet spacing is absolutly required, otherwise the outbound buffer gets filled up and packets get lost which often causes the machine not to wake.
274 if (obj.pendingOutboundTimer == null) { obj.pendingOutboundTimer = setInterval(sendPendingPacket, 10); }