alnoda-workspaces/workspaces/base-workspace/Cronicle-0.8.61/lib/discovery.js
2021-07-30 12:18:29 +00:00

180 lines
5.2 KiB
JavaScript
Executable file

// Cronicle Server Discovery Layer
// Copyright (c) 2015 Joseph Huckaby
// Released under the MIT License
var dgram = require("dgram");
var os = require('os');
var Netmask = require('netmask').Netmask;
var Class = require("pixl-class");
var Tools = require("pixl-tools");
module.exports = Class.create({
nearbyServers: null,
lastDiscoveryBroadcast: 0,
setupDiscovery: function(callback) {
// setup auto-discovery system
// listen for UDP pings, and broadcast our own ping
var self = this;
this.nearbyServers = {};
this.lastDiscoveryBroadcast = 0;
// disable if port is unset
if (!this.server.config.get('udp_broadcast_port')) {
if (callback) callback();
return;
}
// guess best broadcast IP
this.broadcastIP = this.server.config.get('broadcast_ip') || this.calcBroadcastIP();
this.logDebug(4, "Using broadcast IP: " + this.broadcastIP );
// start UDP socket listener
this.logDebug(4, "Starting UDP server on port: " + this.server.config.get('udp_broadcast_port'));
var listener = this.discoveryListener = dgram.createSocket("udp4");
listener.on("message", function (msg, rinfo) {
self.discoveryReceive( msg, rinfo );
} );
listener.on("error", function (err) {
self.logError('udp', "UDP socket listener error: " + err);
self.discoveryListener = null;
} );
listener.bind( this.server.config.get('udp_broadcast_port'), function() {
if (callback) callback();
} );
},
discoveryTick: function() {
// broadcast pings every N
if (!this.discoveryListener) return;
var now = Tools.timeNow(true);
if (now - this.lastDiscoveryBroadcast >= this.server.config.get('master_ping_freq')) {
this.lastDiscoveryBroadcast = now;
// only broadcast if not part of a cluster
if (!this.multi.cluster) {
this.discoveryBroadcast( 'heartbeat', {
hostname: this.server.hostname,
ip: this.server.ip
} );
}
// prune servers who have stopped broadcasting
for (var hostname in this.nearbyServers) {
var server = this.nearbyServers[hostname];
if (now - server.now >= this.server.config.get('master_ping_timeout')) {
delete this.nearbyServers[hostname];
if (this.multi.master) {
this.authSocketEmit( 'update', { nearby: this.nearbyServers } );
}
}
}
}
},
discoveryBroadcast: function(type, message, callback) {
// broadcast message via UDP
var self = this;
message.action = type;
this.logDebug(10, "Broadcasting message: " + type, message);
var client = dgram.createSocket('udp4');
var message = Buffer.from( JSON.stringify(message) + "\n" );
client.bind( 0, function() {
client.setBroadcast( true );
client.send(message, 0, message.length, self.server.config.get('udp_broadcast_port'), self.broadcastIP, function(err) {
if (err) self.logDebug(9, "UDP broadcast failed: " + err);
client.close();
if (callback) callback();
} );
} );
},
discoveryReceive: function(msg, rinfo) {
// receive UDP message from another server
this.logDebug(10, "Received UDP message: " + msg + " from " + rinfo.address + ":" + rinfo.port);
var text = msg.toString();
if (text.match(/^\{/)) {
// appears to be JSON
var json = null;
try { json = JSON.parse(text); }
catch (e) {
this.logError(9, "Failed to parse UDP JSON message: " + e);
}
if (json && json.action) {
switch (json.action) {
case 'heartbeat':
if (json.hostname && (json.hostname != this.server.hostname)) {
json.now = Tools.timeNow();
delete json.action;
if (!this.nearbyServers[ json.hostname ]) {
// first time we've seen this server
this.nearbyServers[ json.hostname ] = json;
if (this.multi.master) {
this.logDebug(6, "Discovered nearby server: " + json.hostname, json);
this.authSocketEmit( 'update', { nearby: this.nearbyServers } );
}
}
else {
// update from existing server
this.nearbyServers[ json.hostname ] = json;
}
this.logDebug(10, "Received heartbeat from: " + json.hostname, json);
}
break;
} // switch action
} // got json
} // appears to be json
},
calcBroadcastIP: function() {
// Attempt to determine server's Broadcast IP, using the first LAN IP and Netmask
// https://en.wikipedia.org/wiki/Broadcast_address
var ifaces = os.networkInterfaces();
var addrs = [];
for (var key in ifaces) {
if (ifaces[key] && ifaces[key].length) {
Array.from(ifaces[key]).forEach( function(item) { addrs.push(item); } );
}
}
var addr = Tools.findObject( addrs, { family: 'IPv4', internal: false } );
if (addr && addr.address && addr.address.match(/^\d+\.\d+\.\d+\.\d+$/) && addr.netmask && addr.netmask.match(/^\d+\.\d+\.\d+\.\d+$/)) {
// well that was easy
var ip = addr.address;
var mask = addr.netmask;
var block = null;
try { block = new Netmask( ip + '/' + mask ); }
catch (err) {;}
if (block && block.broadcast && block.broadcast.match(/^\d+\.\d+\.\d+\.\d+$/)) {
return block.broadcast;
}
}
return '255.255.255.255';
},
shutdownDiscovery: function() {
// shutdown
var self = this;
// shutdown UDP listener
if (this.discoveryListener) {
this.logDebug(3, "Shutting down UDP server");
this.discoveryListener.close();
}
}
});