meshscanner.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /**
  2. * @description MeshCentral Mesh Agent Local Scanner
  3. * @author Ylian Saint-Hilaire
  4. * @copyright Intel Corporation 2018-2022
  5. * @license Apache-2.0
  6. * @version v0.0.1
  7. */
  8. /*jslint node: true */
  9. /*jshint node: true */
  10. /*jshint strict:false */
  11. /*jshint -W097 */
  12. /*jshint esversion: 6 */
  13. "use strict";
  14. // Construct a Mesh Scanner object
  15. // TODO: We need once "server4" and "server6" per interface, or change the default multicast interface as we send.
  16. module.exports.CreateMeshScanner = function (parent) {
  17. var obj = {};
  18. obj.parent = parent;
  19. obj.dgram = require('dgram');
  20. obj.common = require('./common.js');
  21. obj.servers4 = {};
  22. obj.servers6 = {};
  23. obj.mainTimer = null;
  24. const periodicScanTime = (60000 * 20); // Interval between scans, 20 minutes.
  25. const membershipIPv4 = '239.255.255.235';
  26. const membershipIPv6 = 'FF02:0:0:0:0:0:0:FE';
  27. 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();
  28. obj.error = 0;
  29. obj.pendingOutboundPackets = [];
  30. obj.pendingOutboundTimer = null;
  31. // Setup the multicast key if present
  32. if ((typeof obj.parent.args.localdiscovery == 'object') && (typeof obj.parent.args.localdiscovery.key == 'string') && (obj.parent.args.localdiscovery.key.length > 0)) {
  33. obj.multicastKey = parent.crypto.createHash('sha384').update(obj.parent.args.localdiscovery.key).digest('raw').slice(0, 32);
  34. }
  35. // Encrypt UDP packet
  36. function encryptPacket(plainPacket) {
  37. if (obj.multicastKey == null) { return plainPacket; }
  38. const iv = parent.crypto.randomBytes(16), aes = parent.crypto.createCipheriv('aes-256-cbc', obj.multicastKey, iv);
  39. var ciphertext = aes.update(plainPacket);
  40. return Buffer.concat([iv, ciphertext, aes.final()]);
  41. }
  42. // Decrypt UDP packet
  43. function decryptPacket(packet) {
  44. if (obj.multicastKey == null) { return packet; }
  45. if (packet.length < 17) { return null; }
  46. try {
  47. const iv = packet.slice(0, 16), data = packet.slice(16);
  48. const aes = parent.crypto.createDecipheriv('aes-256-cbc', obj.multicastKey, iv);
  49. var plaintextBytes = Buffer.from(aes.update(data));
  50. return Buffer.concat([plaintextBytes, aes.final()]);
  51. } catch (ex) { return null; }
  52. }
  53. // Get a list of IPv4 and IPv6 interface addresses
  54. function getInterfaceList() {
  55. var i;
  56. var ipv4 = ['*'], ipv6 = ['*']; // Bind to IN_ADDR_ANY always
  57. var interfaces = require('os').networkInterfaces();
  58. for (i in interfaces) {
  59. var xinterface = interfaces[i];
  60. for (var j in xinterface) {
  61. var interface2 = xinterface[j];
  62. if ((interface2.mac != '00:00:00:00:00:00') && (interface2.internal == false)) {
  63. if (interface2.family == 'IPv4') { ipv4.push(interface2.address); }
  64. if (interface2.family == 'IPv6') { ipv6.push(interface2.address + '%' + i); }
  65. }
  66. }
  67. }
  68. return { ipv4: ipv4, ipv6: ipv6 };
  69. }
  70. // Setup all IPv4 and IPv6 servers
  71. function setupServers() {
  72. var addresses = getInterfaceList(), i, localAddress, bindOptions;
  73. for (i in obj.servers4) { obj.servers4[i].xxclear = true; }
  74. for (i in obj.servers6) { obj.servers6[i].xxclear = true; }
  75. for (i in addresses.ipv4) {
  76. localAddress = addresses.ipv4[i];
  77. if (obj.servers4[localAddress] != null) {
  78. // Server already exists
  79. obj.servers4[localAddress].xxclear = false;
  80. } else {
  81. // Create a new IPv4 server
  82. try {
  83. var server4 = obj.dgram.createSocket({ type: 'udp4', reuseAddr: true });
  84. server4.xxclear = false;
  85. server4.xxtype = 4;
  86. server4.xxlocal = localAddress;
  87. 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];*/ });
  88. bindOptions = { port: 16989, exclusive: true };
  89. if (server4.xxlocal != '*') { bindOptions.address = server4.xxlocal; }
  90. server4.bind(bindOptions, function () {
  91. try {
  92. var doscan = true;
  93. try { this.setBroadcast(true); this.setMulticastTTL(128); this.addMembership(membershipIPv4, this.xxlocal); } catch (e) { doscan = false; }
  94. this.on('error', function (error) { /*console.log('Error: ' + error);*/ });
  95. this.on('message', function (msg, info) { onUdpPacket(msg, info, this); });
  96. if (doscan == true) { obj.performScan(this); obj.performScan(this); }
  97. } catch (e) { console.log(e); }
  98. });
  99. obj.servers4[localAddress] = server4;
  100. } catch (e) {
  101. console.log(e);
  102. }
  103. }
  104. }
  105. for (i in addresses.ipv6) {
  106. localAddress = addresses.ipv6[i];
  107. if (obj.servers6[localAddress] != null) {
  108. // Server already exists
  109. obj.servers6[localAddress].xxclear = false;
  110. } else {
  111. // Create a new IPv6 server
  112. try {
  113. var server6 = obj.dgram.createSocket({ type: 'udp6', reuseAddr: true });
  114. server6.xxclear = false;
  115. server6.xxtype = 6;
  116. server6.xxlocal = localAddress;
  117. server6.on('error', function (err) { /*this.close(); delete obj.servers6[this.xxlocal];*/ });
  118. bindOptions = { port: 16989, exclusive: true };
  119. if (server6.xxlocal != '*') { bindOptions.address = server6.xxlocal; }
  120. server6.bind(bindOptions, function () {
  121. try {
  122. var doscan = true;
  123. try { this.setBroadcast(true); this.setMulticastTTL(128); this.addMembership(membershipIPv6, this.xxlocal); } catch (e) { doscan = false; }
  124. this.on('error', function (error) { console.log('Error: ' + error); });
  125. this.on('message', function (msg, info) { onUdpPacket(msg, info, this); });
  126. if (doscan == true) { obj.performScan(this); obj.performScan(this); }
  127. } catch (e) { console.log(e); }
  128. });
  129. obj.servers6[localAddress] = server6;
  130. } catch (e) {
  131. console.log(e);
  132. }
  133. }
  134. }
  135. for (i in obj.servers4) { if (obj.servers4[i].xxclear == true) { obj.servers4[i].close(); delete obj.servers4[i]; } }
  136. for (i in obj.servers6) { if (obj.servers6[i].xxclear == true) { obj.servers6[i].close(); delete obj.servers6[i]; } }
  137. }
  138. // Clear all IPv4 and IPv6 servers
  139. function clearServers() {
  140. var i;
  141. for (i in obj.servers4) { obj.servers4[i].close(); delete obj.servers4[i]; }
  142. for (i in obj.servers6) { obj.servers6[i].close(); delete obj.servers6[i]; }
  143. }
  144. // Start scanning for local network Mesh Agents
  145. obj.start = function () {
  146. if (obj.server4 != null) return;
  147. // Setup the local discovery values
  148. var name = 'MeshCentral';
  149. var info = '';
  150. try {
  151. if ((typeof obj.parent.config.domains[''].title == 'string') && (obj.parent.config.domains[''].title.length > 0)) {
  152. name = obj.parent.config.domains[''].title; info = '';
  153. try { if ((typeof obj.parent.config.domains[''].title2 == 'string') && (obj.parent.config.domains[''].title2.length > 0)) { info = obj.parent.config.domains[''].title2; } } catch (ex) { }
  154. }
  155. } catch (ex) { }
  156. try {
  157. if ((typeof obj.parent.args.localdiscovery.name == 'string') && (obj.parent.args.localdiscovery.name.length > 0)) {
  158. name = obj.parent.args.localdiscovery.name; info = '';
  159. try { if ((typeof obj.parent.args.localdiscovery.info == 'string') && (obj.parent.args.localdiscovery.info.length > 0)) { info = obj.parent.args.localdiscovery.info; } } catch (ex) { }
  160. }
  161. } catch (ex) { }
  162. if (info == '') { info = parent.certificates.CommonName; }
  163. // Figure out the correct websocket port
  164. var port = (parent.args.aliasport)?parent.args.aliasport:parent.args.port;
  165. // Build the IPv4 response
  166. var url = 'wss://%s:' + port + '/agent.ashx';
  167. obj.multicastPacket4 = Buffer.from("MeshCentral2|" + obj.agentCertificateHashHex + '|' + url, 'ascii');
  168. if (parent.certificates.CommonName.indexOf('.') != -1) { url = 'wss://' + parent.certificates.CommonName + ':' + port + '/agent.ashx'; }
  169. obj.multicastPacket4x = Buffer.from("MeshCentral2|" + obj.agentCertificateHashHex + '|' + url + '|' + name + '|' + info, 'ascii');
  170. // Build the IPv6 response
  171. url = 'wss://[%s]:' + port + '/agent.ashx';
  172. obj.multicastPacket6 = Buffer.from("MeshCentral2|" + obj.agentCertificateHashHex + '|' + url, 'ascii');
  173. if (parent.certificates.CommonName.indexOf('.') != -1) { url = 'wss://' + parent.certificates.CommonName + ':' + port + '/agent.ashx'; }
  174. obj.multicastPacket6x = Buffer.from("MeshCentral2|" + obj.agentCertificateHashHex + '|' + url + '|' + name + '|' + info, 'ascii');
  175. setupServers();
  176. obj.mainTimer = setInterval(obj.performScan, periodicScanTime);
  177. return obj;
  178. };
  179. // Stop scanning for local network Mesh Agents
  180. obj.stop = function () {
  181. if (obj.mainTimer != null) { clearInterval(obj.mainTimer); obj.mainTimer = null; }
  182. clearServers();
  183. };
  184. // Look for all Mesh Agents that may be locally reachable, indicating the presense of this server.
  185. obj.performScan = function (server) {
  186. var i;
  187. if (server != null) {
  188. if (server.xxtype == 4) { var p = encryptPacket(obj.multicastPacket4); try { server.send(p, 0, p.length, 16990, membershipIPv4); } catch (e) { } }
  189. if (server.xxtype == 6) { var p = encryptPacket(obj.multicastPacket6); try { server.send(p, 0, p.length, 16990, membershipIPv6); } catch (e) { } }
  190. 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) { } }
  191. if ((server.xxtype == 6) && (server.xxlocal == '*')) { var p = encryptPacket(obj.multicastPacket6); try { server.send(p, 0, p.length, 16990, '::1'); } catch (e) { } }
  192. } else {
  193. for (i in obj.servers4) { var p = encryptPacket(obj.multicastPacket4); try { obj.servers4[i].send(p, 0, p.length, 16990, membershipIPv4); } catch (e) { } }
  194. for (i in obj.servers6) { var p = encryptPacket(obj.multicastPacket6); try { obj.servers6[i].send(p, 0, p.length, 16990, membershipIPv6); } catch (e) { } }
  195. setupServers(); // Check if any network interfaces where added or removed
  196. }
  197. };
  198. // Called when a UDP packet is received from an agent.
  199. function onUdpPacket(msg, info, server) {
  200. // Decrypt the packet if needed
  201. if ((msg = decryptPacket(msg)) == null) return;
  202. //console.log('Received ' + msg.length + ' bytes from ' + info.address + ':' + info.port + ', on interface: ' + server.xxlocal + '.');
  203. if ((msg.length == 96) && (msg.toString('ascii') == obj.agentCertificateHashHex)) {
  204. if (server.xxtype == 4) { var p = encryptPacket(obj.multicastPacket4); try { server.send(p, 0, p.length, info.port, info.address); } catch (e) { } }
  205. if (server.xxtype == 6) { var p = encryptPacket(obj.multicastPacket6); try { server.send(p, 0, p.length, info.port, info.address); } catch (e) { } }
  206. } else if (msg.toString('ascii') == 'MeshServerScan') {
  207. if (server.xxtype == 4) { var p = encryptPacket(obj.multicastPacket4x); try { server.send(p, 0, p.length, info.port, info.address); } catch (e) { } }
  208. if (server.xxtype == 6) { var p = encryptPacket(obj.multicastPacket6x); try { server.send(p, 0, p.length, info.port, info.address); } catch (e) { } }
  209. }
  210. }
  211. // Send the next packet in the pending list, stop the timer if we are done.
  212. function sendPendingPacket() {
  213. if (obj.pendingOutboundPackets.length == 0) { if (obj.pendingOutboundTimer != null) { clearInterval(obj.pendingOutboundTimer); obj.pendingOutboundTimer = null; } return; }
  214. var packet = obj.pendingOutboundPackets.shift();
  215. if (packet != null) { packet[0].send(packet[1], 0, packet[1].length, packet[2], packet[3]); }
  216. }
  217. // As a side job, we also send server wake-on-lan packets
  218. obj.wakeOnLan = function (macs, host) {
  219. var i, j, futureTime = 0;
  220. for (i in macs) {
  221. var mac = macs[i].split(':').join('');
  222. var hexpacket = 'FFFFFFFFFFFF';
  223. for (j = 0; j < 16; j++) { hexpacket += mac; }
  224. var wakepacket = Buffer.from(hexpacket, 'hex');
  225. // Add all wake packets to the pending list
  226. for (var k = 0; k < 5; k++) {
  227. for (j in obj.servers4) {
  228. obj.pendingOutboundPackets.push([obj.servers4[j], wakepacket, 7, '255.255.255.255']); // IPv4 Broadcast
  229. obj.pendingOutboundPackets.push([obj.servers4[j], wakepacket, 16990, membershipIPv4]); // IPv4 Multicast
  230. if (host != null) { obj.pendingOutboundPackets.push([obj.servers4[j], wakepacket, 7, host]); } // IPv4 Directed
  231. }
  232. for (j in obj.servers6) {
  233. obj.pendingOutboundPackets.push([obj.servers6[j], wakepacket, 16990, membershipIPv6]); // IPv6 Multicast
  234. }
  235. }
  236. // Send each packet at 10ms interval
  237. // 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.
  238. if (obj.pendingOutboundTimer == null) { obj.pendingOutboundTimer = setInterval(sendPendingPacket, 10); }
  239. }
  240. };
  241. return obj;
  242. };