2017-08-28 19:27:45 +03:00
/ * *
2018-01-04 23:15:21 +03:00
* @ description MeshCentral Mesh Agent Local Scanner
2017-08-28 19:27:45 +03:00
* @ author Ylian Saint - Hilaire
2021-01-10 01:31:09 +03:00
* @ copyright Intel Corporation 2018 - 2021
2018-01-04 23:15:21 +03:00
* @ license Apache - 2.0
2017-08-28 19:27:45 +03:00
* @ version v0 . 0.1
* /
2018-08-30 22:05:23 +03:00
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
"use strict" ;
2018-08-27 22:24:15 +03:00
2017-08-28 19:27:45 +03:00
// Construct a Mesh Scanner object
// TODO: We need once "server4" and "server6" per interface, or change the default multicast interface as we send.
module . exports . CreateMeshScanner = function ( parent ) {
var obj = { } ;
obj . parent = parent ;
obj . dgram = require ( 'dgram' ) ;
obj . common = require ( './common.js' ) ;
2017-09-01 23:45:25 +03:00
obj . servers4 = { } ;
obj . servers6 = { } ;
2017-08-28 19:27:45 +03:00
obj . mainTimer = null ;
2018-05-03 21:09:29 +03:00
const periodicScanTime = ( 60000 * 20 ) ; // Interval between scans, 20 minutes.
const membershipIPv4 = '239.255.255.235' ;
const membershipIPv6 = 'FF02:0:0:0:0:0:0:FE' ;
2017-10-19 02:28:05 +03:00
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 ( ) ;
2017-08-28 19:27:45 +03:00
obj . error = 0 ;
2020-07-15 01:39:14 +03:00
// Setup the multicast key if present
if ( ( typeof obj . parent . args . localdiscovery == 'object' ) && ( typeof obj . parent . args . localdiscovery . key == 'string' ) && ( obj . parent . args . localdiscovery . key . length > 0 ) ) {
obj . multicastKey = parent . crypto . createHash ( 'sha384' ) . update ( obj . parent . args . localdiscovery . key ) . digest ( 'raw' ) . slice ( 0 , 32 ) ;
}
// Encrypt UDP packet
function encryptPacket ( plainPacket ) {
if ( obj . multicastKey == null ) { return plainPacket ; }
const iv = parent . crypto . randomBytes ( 16 ) , aes = parent . crypto . createCipheriv ( 'aes-256-cbc' , obj . multicastKey , iv ) ;
var ciphertext = aes . update ( plainPacket ) ;
return Buffer . concat ( [ iv , ciphertext , aes . final ( ) ] ) ;
}
// Decrypt UDP packet
function decryptPacket ( packet ) {
if ( obj . multicastKey == null ) { return packet ; }
if ( packet . length < 17 ) { return null ; }
try {
const iv = packet . slice ( 0 , 16 ) , data = packet . slice ( 16 ) ;
const aes = parent . crypto . createDecipheriv ( 'aes-256-cbc' , obj . multicastKey , iv ) ;
var plaintextBytes = Buffer . from ( aes . update ( data ) ) ;
return Buffer . concat ( [ plaintextBytes , aes . final ( ) ] ) ;
} catch ( ex ) { return null ; }
}
2017-09-01 23:45:25 +03:00
// Get a list of IPv4 and IPv6 interface addresses
function getInterfaceList ( ) {
2018-08-30 22:05:23 +03:00
var i ;
2017-10-15 09:22:19 +03:00
var ipv4 = [ '*' ] , ipv6 = [ '*' ] ; // Bind to IN_ADDR_ANY always
2017-10-24 00:09:58 +03:00
var interfaces = require ( 'os' ) . networkInterfaces ( ) ;
2018-08-30 22:05:23 +03:00
for ( i in interfaces ) {
2018-08-27 22:24:15 +03:00
var xinterface = interfaces [ i ] ;
for ( var j in xinterface ) {
var interface2 = xinterface [ j ] ;
2017-10-24 00:09:58 +03:00
if ( ( interface2 . mac != '00:00:00:00:00:00' ) && ( interface2 . internal == false ) ) {
if ( interface2 . family == 'IPv4' ) { ipv4 . push ( interface2 . address ) ; }
if ( interface2 . family == 'IPv6' ) { ipv6 . push ( interface2 . address + '%' + i ) ; }
2017-09-01 23:45:25 +03:00
}
}
2017-10-24 00:09:58 +03:00
}
2017-09-01 23:45:25 +03:00
return { ipv4 : ipv4 , ipv6 : ipv6 } ;
}
// Setup all IPv4 and IPv6 servers
function setupServers ( ) {
2018-08-30 22:05:23 +03:00
var addresses = getInterfaceList ( ) , i , localAddress , bindOptions ;
for ( i in obj . servers4 ) { obj . servers4 [ i ] . xxclear = true ; }
for ( i in obj . servers6 ) { obj . servers6 [ i ] . xxclear = true ; }
for ( i in addresses . ipv4 ) {
localAddress = addresses . ipv4 [ i ] ;
2017-09-01 23:45:25 +03:00
if ( obj . servers4 [ localAddress ] != null ) {
// Server already exists
obj . servers4 [ localAddress ] . xxclear = false ;
} else {
// Create a new IPv4 server
2017-10-03 00:12:29 +03:00
try {
2017-10-19 02:28:05 +03:00
var server4 = obj . dgram . createSocket ( { type : 'udp4' , reuseAddr : true } ) ;
2017-10-03 00:12:29 +03:00
server4 . xxclear = false ;
server4 . xxtype = 4 ;
server4 . xxlocal = localAddress ;
2020-10-28 22:52:05 +03:00
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];*/ } ) ;
2018-08-30 22:05:23 +03:00
bindOptions = { port : 16989 , exclusive : true } ;
2017-10-03 00:12:29 +03:00
if ( server4 . xxlocal != '*' ) { bindOptions . address = server4 . xxlocal ; }
server4 . bind ( bindOptions , function ( ) {
try {
2018-07-24 03:34:24 +03:00
var doscan = true ;
2019-03-05 10:48:45 +03:00
try { this . setBroadcast ( true ) ; this . setMulticastTTL ( 128 ) ; this . addMembership ( membershipIPv4 , this . xxlocal ) ; } catch ( e ) { doscan = false ; }
2020-10-28 22:52:05 +03:00
this . on ( 'error' , function ( error ) { /*console.log('Error: ' + error);*/ } ) ;
2017-10-19 02:28:05 +03:00
this . on ( 'message' , function ( msg , info ) { onUdpPacket ( msg , info , this ) ; } ) ;
2018-07-24 03:34:24 +03:00
if ( doscan == true ) { obj . performScan ( this ) ; obj . performScan ( this ) ; }
2017-10-24 00:09:58 +03:00
} catch ( e ) { console . log ( e ) ; }
2017-10-03 00:12:29 +03:00
} ) ;
obj . servers4 [ localAddress ] = server4 ;
} catch ( e ) {
console . log ( e ) ;
}
2017-09-01 23:45:25 +03:00
}
}
2017-10-03 00:12:29 +03:00
2018-08-30 22:05:23 +03:00
for ( i in addresses . ipv6 ) {
localAddress = addresses . ipv6 [ i ] ;
2017-09-01 23:45:25 +03:00
if ( obj . servers6 [ localAddress ] != null ) {
// Server already exists
obj . servers6 [ localAddress ] . xxclear = false ;
} else {
// Create a new IPv6 server
2017-10-24 00:09:58 +03:00
try {
var server6 = obj . dgram . createSocket ( { type : 'udp6' , reuseAddr : true } ) ;
server6 . xxclear = false ;
server6 . xxtype = 6 ;
server6 . xxlocal = localAddress ;
2020-10-28 22:52:05 +03:00
server6 . on ( 'error' , function ( err ) { /*this.close(); delete obj.servers6[this.xxlocal];*/ } ) ;
2018-08-30 22:05:23 +03:00
bindOptions = { port : 16989 , exclusive : true } ;
2017-10-24 00:09:58 +03:00
if ( server6 . xxlocal != '*' ) { bindOptions . address = server6 . xxlocal ; }
server6 . bind ( bindOptions , function ( ) {
try {
2018-07-24 03:34:24 +03:00
var doscan = true ;
2019-03-05 10:48:45 +03:00
try { this . setBroadcast ( true ) ; this . setMulticastTTL ( 128 ) ; this . addMembership ( membershipIPv6 , this . xxlocal ) ; } catch ( e ) { doscan = false ; }
2017-10-24 00:09:58 +03:00
this . on ( 'error' , function ( error ) { console . log ( 'Error: ' + error ) ; } ) ;
this . on ( 'message' , function ( msg , info ) { onUdpPacket ( msg , info , this ) ; } ) ;
2018-07-24 03:34:24 +03:00
if ( doscan == true ) { obj . performScan ( this ) ; obj . performScan ( this ) ; }
2017-10-24 00:09:58 +03:00
} catch ( e ) { console . log ( e ) ; }
} ) ;
obj . servers6 [ localAddress ] = server6 ;
} catch ( e ) {
console . log ( e ) ;
}
2017-09-01 23:45:25 +03:00
}
}
2017-10-24 00:09:58 +03:00
2018-08-30 22:05:23 +03:00
for ( i in obj . servers4 ) { if ( obj . servers4 [ i ] . xxclear == true ) { obj . servers4 [ i ] . close ( ) ; delete obj . servers4 [ i ] ; } }
for ( i in obj . servers6 ) { if ( obj . servers6 [ i ] . xxclear == true ) { obj . servers6 [ i ] . close ( ) ; delete obj . servers6 [ i ] ; } }
2017-09-01 23:45:25 +03:00
}
// Clear all IPv4 and IPv6 servers
function clearServers ( ) {
2018-08-30 22:05:23 +03:00
var i ;
for ( i in obj . servers4 ) { obj . servers4 [ i ] . close ( ) ; delete obj . servers4 [ i ] ; }
for ( i in obj . servers6 ) { obj . servers6 [ i ] . close ( ) ; delete obj . servers6 [ i ] ; }
2017-09-01 23:45:25 +03:00
}
2017-08-28 19:27:45 +03:00
// Start scanning for local network Mesh Agents
obj . start = function ( ) {
if ( obj . server4 != null ) return ;
2018-09-02 08:05:03 +03:00
// Setup the local discovery values
var name = 'MeshCentral' ;
2020-07-15 01:39:14 +03:00
var info = '' ;
try {
if ( ( typeof obj . parent . config . domains [ '' ] . title == 'string' ) && ( obj . parent . config . domains [ '' ] . title . length > 0 ) ) {
name = obj . parent . config . domains [ '' ] . title ; info = '' ;
try { if ( ( typeof obj . parent . config . domains [ '' ] . title2 == 'string' ) && ( obj . parent . config . domains [ '' ] . title2 . length > 0 ) ) { info = obj . parent . config . domains [ '' ] . title2 ; } } catch ( ex ) { }
}
} catch ( ex ) { }
try {
if ( ( typeof obj . parent . args . localdiscovery . name == 'string' ) && ( obj . parent . args . localdiscovery . name . length > 0 ) ) {
name = obj . parent . args . localdiscovery . name ; info = '' ;
try { if ( ( typeof obj . parent . args . localdiscovery . info == 'string' ) && ( obj . parent . args . localdiscovery . info . length > 0 ) ) { info = obj . parent . args . localdiscovery . info ; } } catch ( ex ) { }
}
} catch ( ex ) { }
if ( info == '' ) { info = parent . certificates . CommonName ; }
2018-09-02 08:05:03 +03:00
2019-09-01 05:40:50 +03:00
// Figure out the correct websocket port
var port = ( parent . args . aliasport ) ? parent . args . aliasport : parent . args . port ;
2018-09-02 08:05:03 +03:00
// Build the IPv4 response
2020-11-05 13:27:39 +03:00
var url = 'wss://%s:' + port + '/agent.ashx' ;
2017-10-19 02:28:05 +03:00
obj . multicastPacket4 = Buffer . from ( "MeshCentral2|" + obj . agentCertificateHashHex + '|' + url , 'ascii' ) ;
2020-11-05 13:27:39 +03:00
if ( parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) { url = 'wss://' + parent . certificates . CommonName + ':' + port + '/agent.ashx' ; }
2018-09-02 08:05:03 +03:00
obj . multicastPacket4x = Buffer . from ( "MeshCentral2|" + obj . agentCertificateHashHex + '|' + url + '|' + name + '|' + info , 'ascii' ) ;
// Build the IPv6 response
2020-11-05 13:27:39 +03:00
url = 'wss://[%s]:' + port + '/agent.ashx' ;
2017-10-19 02:28:05 +03:00
obj . multicastPacket6 = Buffer . from ( "MeshCentral2|" + obj . agentCertificateHashHex + '|' + url , 'ascii' ) ;
2020-11-05 13:27:39 +03:00
if ( parent . certificates . CommonName . indexOf ( '.' ) != - 1 ) { url = 'wss://' + parent . certificates . CommonName + ':' + port + '/agent.ashx' ; }
2018-09-02 08:05:03 +03:00
obj . multicastPacket6x = Buffer . from ( "MeshCentral2|" + obj . agentCertificateHashHex + '|' + url + '|' + name + '|' + info , 'ascii' ) ;
2017-09-01 23:45:25 +03:00
setupServers ( ) ;
2017-08-28 19:27:45 +03:00
obj . mainTimer = setInterval ( obj . performScan , periodicScanTime ) ;
return obj ;
2018-08-30 22:05:23 +03:00
} ;
2017-08-28 19:27:45 +03:00
// Stop scanning for local network Mesh Agents
obj . stop = function ( ) {
if ( obj . mainTimer != null ) { clearInterval ( obj . mainTimer ) ; obj . mainTimer = null ; }
2017-09-01 23:45:25 +03:00
clearServers ( ) ;
2018-08-30 22:05:23 +03:00
} ;
2017-08-28 19:27:45 +03:00
// Look for all Mesh Agents that may be locally reachable, indicating the presense of this server.
2017-09-01 23:45:25 +03:00
obj . performScan = function ( server ) {
2018-08-30 22:05:23 +03:00
var i ;
2017-09-01 23:45:25 +03:00
if ( server != null ) {
2020-07-15 01:39:14 +03:00
if ( server . xxtype == 4 ) { var p = encryptPacket ( obj . multicastPacket4 ) ; try { server . send ( p , 0 , p . length , 16990 , membershipIPv4 ) ; } catch ( e ) { } }
if ( server . xxtype == 6 ) { var p = encryptPacket ( obj . multicastPacket6 ) ; try { server . send ( p , 0 , p . length , 16990 , membershipIPv6 ) ; } catch ( e ) { } }
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 ) { } }
if ( ( server . xxtype == 6 ) && ( server . xxlocal == '*' ) ) { var p = encryptPacket ( obj . multicastPacket6 ) ; try { server . send ( p , 0 , p . length , 16990 , '::1' ) ; } catch ( e ) { } }
2017-09-01 23:45:25 +03:00
} else {
2020-07-15 01:39:14 +03:00
for ( i in obj . servers4 ) { var p = encryptPacket ( obj . multicastPacket4 ) ; try { obj . servers4 [ i ] . send ( p , 0 , p . length , 16990 , membershipIPv4 ) ; } catch ( e ) { } }
for ( i in obj . servers6 ) { var p = encryptPacket ( obj . multicastPacket6 ) ; try { obj . servers6 [ i ] . send ( p , 0 , p . length , 16990 , membershipIPv6 ) ; } catch ( e ) { } }
2017-09-01 23:45:25 +03:00
setupServers ( ) ; // Check if any network interfaces where added or removed
}
2018-08-30 22:05:23 +03:00
} ;
2017-08-28 19:27:45 +03:00
// Called when a UDP packet is received from an agent.
2017-09-01 23:45:25 +03:00
function onUdpPacket ( msg , info , server ) {
2020-07-15 01:39:14 +03:00
// Decrypt the packet if needed
if ( ( msg = decryptPacket ( msg ) ) == null ) return ;
2017-10-03 00:12:29 +03:00
//console.log('Received ' + msg.length + ' bytes from ' + info.address + ':' + info.port + ', on interface: ' + server.xxlocal + '.');
2017-10-19 02:28:05 +03:00
if ( ( msg . length == 96 ) && ( msg . toString ( 'ascii' ) == obj . agentCertificateHashHex ) ) {
2020-07-15 21:31:55 +03:00
if ( server . xxtype == 4 ) { var p = encryptPacket ( obj . multicastPacket4 ) ; try { server . send ( p , 0 , p . length , info . port , info . address ) ; } catch ( e ) { } }
if ( server . xxtype == 6 ) { var p = encryptPacket ( obj . multicastPacket6 ) ; try { server . send ( p , 0 , p . length , info . port , info . address ) ; } catch ( e ) { } }
2018-09-02 08:05:03 +03:00
} else if ( msg . toString ( 'ascii' ) == 'MeshServerScan' ) {
2020-07-15 01:39:14 +03:00
if ( server . xxtype == 4 ) { var p = encryptPacket ( obj . multicastPacket4x ) ; try { server . send ( p , 0 , p . length , info . port , info . address ) ; } catch ( e ) { } }
if ( server . xxtype == 6 ) { var p = encryptPacket ( obj . multicastPacket6x ) ; try { server . send ( p , 0 , p . length , info . port , info . address ) ; } catch ( e ) { } }
2017-08-28 19:27:45 +03:00
}
}
// As a side job, we also send server wake-on-lan packets
2020-10-28 22:52:05 +03:00
obj . wakeOnLan = function ( macs , host ) {
2018-08-30 22:05:23 +03:00
var i , j ;
for ( i in macs ) {
2020-08-16 06:29:40 +03:00
var mac = macs [ i ] . split ( ':' ) . join ( '' ) ;
2017-08-28 19:27:45 +03:00
var hexpacket = 'FFFFFFFFFFFF' ;
2018-08-30 22:05:23 +03:00
for ( j = 0 ; j < 16 ; j ++ ) { hexpacket += mac ; }
2017-08-28 19:27:45 +03:00
var wakepacket = Buffer . from ( hexpacket , 'hex' ) ;
//console.log(wakepacket.toString('hex'));
2020-10-28 22:52:05 +03:00
// Setup the wake function
const func = function wakeFunc ( ) {
for ( j in obj . servers4 ) {
obj . servers4 [ j ] . send ( wakeFunc . wakepacket , 0 , wakeFunc . wakepacket . length , 7 , '255.255.255.255' ) ;
obj . servers4 [ j ] . send ( wakeFunc . wakepacket , 0 , wakeFunc . wakepacket . length , 16990 , membershipIPv4 ) ;
if ( wakeFunc . host != null ) { obj . servers4 [ j ] . send ( wakeFunc . wakepacket , 0 , wakeFunc . wakepacket . length , 7 , wakeFunc . host ) ; }
}
for ( j in obj . servers6 ) {
obj . servers6 [ j ] . send ( wakeFunc . wakepacket , 0 , wakeFunc . wakepacket . length , 16990 , membershipIPv6 ) ;
}
}
func . wakepacket = wakepacket ;
func . host = host ;
// Call the wake function 3 times with small time intervals
func ( ) ;
setTimeout ( func , 200 ) ;
setTimeout ( func , 500 ) ;
2017-08-28 19:27:45 +03:00
}
2018-08-30 22:05:23 +03:00
} ;
2017-08-28 19:27:45 +03:00
return obj ;
2018-08-30 22:05:23 +03:00
} ;