diff --git a/meshipkvm.js b/meshipkvm.js index 1522b7f6..fb054a62 100644 --- a/meshipkvm.js +++ b/meshipkvm.js @@ -67,6 +67,7 @@ function CreateIPKVMManager(parent) { if (mesh.kvm.model == 1) { // Raritan KX III const manager = CreateRaritanKX3Manager(obj, host, port, mesh.kvm.user, mesh.kvm.pass); manager.meshid = mesh._id; + manager.relayid = mesh.relayid; manager.domainid = mesh._id.split('/')[1]; obj.managedGroups[mesh._id] = manager; manager.onStateChanged = onStateChanged; @@ -76,6 +77,7 @@ function CreateIPKVMManager(parent) { else if (mesh.kvm.model == 2) { // WebPowerSwitch 7 const manager = CreateWebPowerSwitch(obj, host, port, mesh.kvm.user, mesh.kvm.pass); manager.meshid = mesh._id; + manager.relayid = mesh.relayid; manager.domainid = mesh._id.split('/')[1]; obj.managedGroups[mesh._id] = manager; manager.onStateChanged = onStateChanged; @@ -774,7 +776,14 @@ function CreateWebPowerSwitch(parent, hostname, port, username, password) { obj.start = function () { if (obj.started) return; obj.started = true; - if (obj.state == 0) connect(); + if (obj.state == 0) { + if (obj.relayid) { + obj.router = CreateMiniRouter(parent, obj.relayid, hostname, port); + obj.router.start(function () { connect(); }); + } else { + connect(); + } + } } obj.stop = function () { @@ -783,6 +792,7 @@ function CreateWebPowerSwitch(parent, hostname, port, username, password) { if (retryTimer != null) { clearTimeout(retryTimer); retryTimer = null; } setState(0); obj.ports = []; + if (obj.router) { obj.router.stop(); delete obj.router; } } function setState(newState) { @@ -856,8 +866,8 @@ function CreateWebPowerSwitch(parent, hostname, port, username, password) { var rdata = []; const options = { - hostname: hostname, - port: port, + hostname: obj.router ? 'localhost' : hostname, + port: obj.router ? obj.router.tcpServerPort : port, rejectUnauthorized: false, checkServerIdentity: onCheckServerIdentity, path: url, @@ -945,4 +955,104 @@ function CreateWebPowerSwitch(parent, hostname, port, username, password) { } +// Mini TCP port router +function CreateMiniRouter(parent, nodeid, targetHost, targetPort) { + const Net = require('net'); + const WebSocket = require('ws'); + const obj = {}; + const tcpSockets = {} + obj.tcpServerPort = 0; + obj.nodeid = nodeid; + obj.targetHost = targetHost; + obj.targetPort = targetPort; + + parent.parent.debug('relay', 'MiniRouter: Request relay for ' + obj.targetHost + ':' + obj.targetPort + ' thru ' + nodeid + '.'); + + // Close a TCP socket and the coresponding web socket. + function closeTcpSocket(tcpSocket) { + if (tcpSockets[tcpSocket]) { + delete tcpSockets[tcpSocket]; + try { tcpSocket.close(); } catch (ex) { } + if (tcpSocket.relaySocket) { try { tcpSocket.relaySocket.close(); } catch (ex) { } } + delete tcpSocket.relaySocket.tcpSocket; + delete tcpSocket.relaySocket; + } + } + + // Close a web socket and the coresponding TCP socket. + function closeWebSocket(webSocket) { + const tcpSocket = webSocket.tcpSocket; + if (tcpSocket) { closeTcpSocket(tcpSocket); } + } + + // Start the looppback server + obj.start = function(onReadyFunc) { + obj.tcpServer = new Net.Server(); + obj.tcpServer.listen(0, 'localhost', function () { + obj.tcpServerPort = obj.tcpServer.address().port; + parent.parent.debug('relay', 'MiniRouter: Request for relay ' + obj.targetHost + ':' + obj.targetPort + ' started on port ' + obj.tcpServerPort); + onReadyFunc(obj.tcpServerPort); + }); + obj.tcpServer.on('connection', function (socket) { + tcpSockets[socket] = 1; + socket.pause(); + socket.on('data', function (chunk) { // Make sure to handle flow control. + console.log('<-- ' + chunk); + const f = function sendDone() { sendDone.tcpSocket.resume(); } + f.tcpSocket = this; + if (this.relaySocket && this.relaySocket.active) { this.pause(); this.relaySocket.send(chunk, f); } + }); + socket.on('end', function () { close(this); }); + socket.on('error', function (err) { close(this); }); + + // Encode the device relay cookie. Note that there si no userid in this cookie. + const domainid = obj.nodeid.split('/')[1]; + const cookie = parent.parent.encodeCookie({ domainid: domainid, nodeid: obj.nodeid, tcpaddr: obj.targetHost, tcpport: obj.targetPort }, parent.parent.loginCookieEncryptionKey); + const domain = parent.parent.config.domains[domainid]; + + // Setup the correct URL with domain and use TLS only if needed. + const options = { rejectUnauthorized: false }; + const protocol = (parent.parent.args.tlsoffload) ? 'ws' : 'wss'; + var domainadd = ''; + if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' } + const url = protocol + '://localhost:' + parent.parent.args.port + '/' + domainadd + 'meshrelay.ashx?noping=1&auth=' + cookie; // TODO: &p=10, Protocol 10 is Web-RDP, Specify TCP routing protocol? + parent.parent.debug('relay', 'MiniRouter: Connection websocket to ' + url); + socket.relaySocket = new WebSocket(url, options); + socket.relaySocket.tcpSocket = socket; + socket.relaySocket.on('open', function () { parent.parent.debug('relay', 'MiniRouter: Relay websocket open'); }); + socket.relaySocket.on('message', function (data) { // Make sure to handle flow control. + console.log('--> ' + data); + if (!this.active) { + if (data == 'c') { + // Relay Web socket is connected, start data relay + this.active = true; + this.tcpSocket.resume(); + } else { + // Could not connect web socket, close it + console.log('ERR', data); + closeWebSocket(this); + } + } else { + // Relay more data + this._socket.pause(); + const f = function sendDone() { sendDone.webSocket._socket.resume(); } + f.webSocket = this; + this.tcpSocket.write(data, f); + } + }); + socket.relaySocket.on('close', function () { parent.parent.debug('relay', 'MiniRouter: Relay websocket closed'); closeWebSocket(this); }); + socket.relaySocket.on('error', function (err) { parent.parent.debug('relay', 'MiniRouter: Relay websocket error: ' + err); closeWebSocket(this); }); + }); + } + + // Stop the looppback server and all relay sockets + obj.stop = function () { + for (var tcpSocket in tcpSockets) { closeTcpSocket(tcpSocket); } + obj.tcpServer.close(); + obj.tcpServer = null; + } + + return obj; +} + module.exports.CreateIPKVMManager = CreateIPKVMManager; \ No newline at end of file