First pass at adding WebRTC support.

This commit is contained in:
Ylian Saint-Hilaire 2018-01-16 17:30:34 -08:00
parent 65d6775303
commit 92aaf754fb
12 changed files with 164 additions and 141 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -20,7 +20,6 @@ function createMeshCore(agent) {
// MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent. // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent.
obj.meshCoreInfo = "MeshCore v4"; obj.meshCoreInfo = "MeshCore v4";
obj.meshCoreCapabilities = 14; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript obj.meshCoreCapabilities = 14; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript
obj.useNativePipes = true; //(process.platform == 'win32');
var meshServerConnectionState = 0; var meshServerConnectionState = 0;
var tunnels = {}; var tunnels = {};
var lastSelfInfo = null; var lastSelfInfo = null;
@ -54,6 +53,7 @@ function createMeshCore(agent) {
scan.scan("10.2.55.128/25", 2000); scan.scan("10.2.55.128/25", 2000);
*/ */
/*
// Try to load up the network monitor // Try to load up the network monitor
try { try {
networkMonitor = require('NetworkMonitor'); networkMonitor = require('NetworkMonitor');
@ -61,6 +61,7 @@ function createMeshCore(agent) {
networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); }); networkMonitor.on('add', function (addr) { sendNetworkUpdateNagle(); });
networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); }); networkMonitor.on('remove', function (addr) { sendNetworkUpdateNagle(); });
} catch (e) { networkMonitor = null; } } catch (e) { networkMonitor = null; }
*/
// Try to load up the Intel AMT scanner // Try to load up the Intel AMT scanner
try { try {
@ -506,28 +507,7 @@ function createMeshCore(agent) {
if (len > 0) { this.write(buf.slice(0, len)); } else { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; this.end(); } if (len > 0) { this.write(buf.slice(0, len)); } else { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; this.end(); }
return; return;
} }
// Setup remote desktop & terminal without using native pipes
if ((this.httprequest.desktop) && (obj.useNativePipes == false)) {
if (data.length > 21 && data.toString().startsWith('**********%%%%%%###**')) {
var controlMsg = JSON.parse(data.toString().substring(21));
if (controlMsg.type == 'offer') {
this.webrtc = rtc.createConnection();
this.webrtc.on('connected', function () { sendConsoleText('OnWebRTC_Connected'); });
this.webrtc.on('dataChannel', function () { sendConsoleText('OnWebRTC_DataChannel'); });
var counterOffer = this.webrtc.setOffer(controlMsg.sdp);
this.write('**********%%%%%%###**' + JSON.stringify({ type: 'answer', sdp: counterOffer }));
sendConsoleText('counterOfferSent');
} else {
sendConsoleText(JSON.stringify(controlMsg));
}
} else {
this.httprequest.desktop.kvm.write(data);
}
return;
}
if ((this.httprequest.terminal) && (obj.useNativePipes == false)) { this.httprequest.terminal.write(data); return; }
if (this.httprequest.state == 0) { if (this.httprequest.state == 0) {
// Check if this is a relay connection // Check if this is a relay connection
if (data == 'c') { this.httprequest.state = 1; sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid); } if (data == 'c') { this.httprequest.state = 1; sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid); }
@ -538,59 +518,39 @@ function createMeshCore(agent) {
this.httprequest.protocol = parseInt(data); this.httprequest.protocol = parseInt(data);
if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; } if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
if (this.httprequest.protocol == 1) { if (this.httprequest.protocol == 1) {
if (obj.useNativePipes == false) { // Remote terminal using native pipes
// Remote Terminal without using native pipes if (process.platform == "win32") {
if (process.platform == "win32") { this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe");
this.httprequest.terminal = childProcess.execFile("%windir%\\system32\\cmd.exe");
} else {
this.httprequest.terminal = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
}
this.httprequest.terminal.tunnel = this;
this.httprequest.terminal.on('exit', function (ecode, sig) { this.tunnel.end(); });
this.httprequest.terminal.stdout.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
this.httprequest.terminal.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
} else { } else {
// Remote terminal using native pipes this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
if (process.platform == "win32") {
this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe");
} else {
this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
}
this.httprequest.process.tunnel = this;
this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
this.prependListener('end', function () { this.httprequest.process.kill(); });
} }
this.httprequest.process.tunnel = this;
this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
this.prependListener('end', function () { this.httprequest.process.kill(); });
this.removeAllListeners('data');
this.on('data', onTunnelControlData);
//this.write('MeshCore Terminal Hello!1');
} }
if (this.httprequest.protocol == 2) { if (this.httprequest.protocol == 2) {
if (obj.useNativePipes == false) { // Remote desktop using native pipes
// Remote Desktop without using native pipes this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(), tunnel: this };
this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(), tunnel: this }; this.httprequest.desktop.kvm.parent = this.httprequest.desktop;
this.httprequest.desktop.kvm.tunnel = this; this.desktop = this.httprequest.desktop;
this.httprequest.desktop.kvm.on('data', function (data) { this.tunnel.write(data); }); this.end = function () {
this.desktop = this.httprequest.desktop; --this.desktop.kvm.connectionCount;
this.end = function () { if (--this.desktop.kvm.connectionCount == 0) { this.httprequest.desktop.kvm.end(); } }; this.unpipe(this.httprequest.desktop.kvm);
if (this.httprequest.desktop.kvm.hasOwnProperty("connectionCount")) { this.httprequest.desktop.kvm.connectionCount++; } else { this.httprequest.desktop.kvm.connectionCount = 1; } this.httprequest.desktop.kvm.unpipe(this);
} else { if (this.desktop.kvm.connectionCount == 0) { this.httprequest.desktop.kvm.end(); }
// Remote desktop using native pipes };
this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(), tunnel: this }; if (this.httprequest.desktop.kvm.hasOwnProperty("connectionCount")) { this.httprequest.desktop.kvm.connectionCount++; } else { this.httprequest.desktop.kvm.connectionCount = 1; }
this.httprequest.desktop.kvm.parent = this.httprequest.desktop; this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
this.desktop = this.httprequest.desktop; this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
this.end = function () { this.removeAllListeners('data');
--this.desktop.kvm.connectionCount; this.on('data', onTunnelControlData);
this.unpipe(this.httprequest.desktop.kvm); //this.write('MeshCore KVM Hello!1');
this.httprequest.desktop.kvm.unpipe(this);
if (this.desktop.kvm.connectionCount == 0) { this.httprequest.desktop.kvm.end(); }
};
if (this.httprequest.desktop.kvm.hasOwnProperty("connectionCount")) { this.httprequest.desktop.kvm.connectionCount++; } else { this.httprequest.desktop.kvm.connectionCount = 1; }
//this.write('Hello!');
//sendConsoleText('KVM WriteHello');
this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
//this.on('data', function (data) { sendConsoleText('KVM: ' + data); });
}
} }
else if (this.httprequest.protocol == 5) { else if (this.httprequest.protocol == 5) {
// Setup files // Setup files
@ -700,7 +660,49 @@ function createMeshCore(agent) {
//sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid);
} }
} }
// Attempt to setup and switch the tunnel over to WebRTC
function onTunnelControlData(data) {
sendConsoleText('onTunnelControlData: ' + data);
var obj = JSON.parse(data);
if (obj.type == 'offer') {
// This is a WebRTC offer.
this.webrtc = rtc.createConnection();
this.webrtc.websocket = this;
this.webrtc.on('connected', function () { sendConsoleText('WebRTC connected'); });
this.webrtc.on('dataChannel', function (rtcchannel) {
sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
this.rtcchannel = rtcchannel;
if (this.websocket.httprequest.protocol == 1) { // Terminal
// This is a terminal data stream, re-setup the pipes
// Un-pipe
this.websocket.unpipe(this.websocket.httprequest.process.stdin);
//this.websocket.httprequest.process.stdout.unpipe(this.websocket);
// Re-pipe
rtcchannel.pipe(this.websocket.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
//this.websocket.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
} else if (this.websocket.httprequest.protocol == 2) { // Desktop
// This is a KVM data stream, re-setup the pipes
// Un-pipe
this.websocket.unpipe(this.websocket.httprequest.desktop.kvm);
//this.websocket.httprequest.desktop.kvm.unpipe(this.websocket);
// Re-pipe
rtcchannel.pipe(this.websocket.httprequest.desktop.kvm, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
//this.websocket.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
}
/*
else {
// Debug, just display on agent console
rtcchannel.on('data', function (buffer) { sendConsoleText("RTCReceived: " + buffer.length + " bytes"); });
rtcchannel.on('end', function () { sendConsoleText("RTCChannel: " + this.name + " was closed"); });
channel.write('WebRTC HELLO!');
}
*/
});
this.write({ type: "answer", sdp: this.webrtc.setOffer(obj.sdp) });
}
}
// Console state // Console state
var consoleWebSockets = {}; var consoleWebSockets = {};
var consoleHttpRequest = null; var consoleHttpRequest = null;
@ -740,7 +742,7 @@ function createMeshCore(agent) {
break; break;
} }
case 'info': { // Return information about the agent and agent core module case 'info': { // Return information about the agent and agent core module
response = 'Current Core: ' + obj.meshCoreInfo + '.\r\nAgent Time: ' + Date() + '.\r\nUser Rights: 0x' + rights.toString(16) + '.\r\nPlatform Info: ' + process.platform + '.\r\nCapabilities: ' + obj.meshCoreCapabilities + '.\r\nNative Pipes: ' + obj.useNativePipes + '.\r\nServer URL: ' + mesh.ServerUrl + '.'; response = 'Current Core: ' + obj.meshCoreInfo + '.\r\nAgent Time: ' + Date() + '.\r\nUser Rights: 0x' + rights.toString(16) + '.\r\nPlatform Info: ' + process.platform + '.\r\nCapabilities: ' + obj.meshCoreCapabilities + '.\r\nServer URL: ' + mesh.ServerUrl + '.';
if (amtLmsState >= 0) { response += '\r\nBuilt -in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amtLmsState] + '.'; } if (amtLmsState >= 0) { response += '\r\nBuilt -in LMS: ' + ['Disabled', 'Connecting..', 'Connected'][amtLmsState] + '.'; }
response += '\r\nModules: ' + JSON.stringify(addedModules) + ''; response += '\r\nModules: ' + JSON.stringify(addedModules) + '';
response += '\r\nServerConnected: ' + mesh.isControlChannelConnected + ''; response += '\r\nServerConnected: ' + mesh.isControlChannelConnected + '';

View File

@ -203,17 +203,19 @@ module.exports.CertificateOperations = function () {
} }
// If CA certificates are present, load them // If CA certificates are present, load them
var caok, caindex = 1, calist = []; if (r.web != null) {
do { var caok, caindex = 1, calist = [];
caok = false; do {
if (obj.fileExists(directory + '/webserver-cert-chain' + caindex + '.crt')) { caok = false;
var caCertificate = obj.fs.readFileSync(directory + '/webserver-cert-chain' + caindex + '.crt', 'utf8'); if (obj.fileExists(directory + '/webserver-cert-chain' + caindex + '.crt')) {
calist.push(caCertificate); var caCertificate = obj.fs.readFileSync(directory + '/webserver-cert-chain' + caindex + '.crt', 'utf8');
caok = true; calist.push(caCertificate);
} caok = true;
caindex++; }
} while (caok == true); caindex++;
r.web.ca = calist; } while (caok == true);
r.web.ca = calist;
}
// Decode certificate arguments // Decode certificate arguments
var commonName = 'un-configured', country, organization, forceWebCertGen = 0; var commonName = 'un-configured', country, organization, forceWebCertGen = 0;
@ -374,7 +376,7 @@ module.exports.CertificateOperations = function () {
amtConsoleName = consoleCertAndKey.cert.subject.getField('CN').value; amtConsoleName = consoleCertAndKey.cert.subject.getField('CN').value;
} }
var r = { root: { cert: rootCertificate, key: rootPrivateKey }, web: { cert: webCertificate, key: webPrivateKey }, mps: { cert: mpsCertificate, key: mpsPrivateKey }, agent: { cert: agentCertificate, key: agentPrivateKey }, console: { cert: consoleCertificate, key: consolePrivateKey }, ca: calist, CommonName: commonName, RootName: rootName, AmtConsoleName: amtConsoleName, dns: {} }; var r = { root: { cert: rootCertificate, key: rootPrivateKey }, web: { cert: webCertificate, key: webPrivateKey, ca: [] }, mps: { cert: mpsCertificate, key: mpsPrivateKey }, agent: { cert: agentCertificate, key: agentPrivateKey }, console: { cert: consoleCertificate, key: consolePrivateKey }, ca: calist, CommonName: commonName, RootName: rootName, AmtConsoleName: amtConsoleName, dns: {} };
// Look for domains with DNS names that have no certificates and generated them. // Look for domains with DNS names that have no certificates and generated them.
for (var i in config.domains) { for (var i in config.domains) {

View File

@ -183,21 +183,32 @@ module.exports.CreateHttpInterceptor = function (args) {
obj.ws.count -= rl; obj.ws.count -= rl;
if (obj.ws.count == 0) { obj.ws.mode = 0; } if (obj.ws.count == 0) { obj.ws.mode = 0; }
return r; return r;
} else if (obj.ws.mode == 2) { // Chunked Body Mode } else if (obj.amt.mode == 2) { // Chunked Body Mode
// Send data one chunk at a time // Send data one chunk at a time
var headerend = obj.ws.acc.indexOf('\r\n'); var headerend = obj.amt.acc.indexOf('\r\n');
if (headerend < 0) return ""; if (headerend < 0) return "";
var chunksize = parseInt(obj.ws.acc.substring(0, headerend), 16); var chunksize = parseInt(obj.amt.acc.substring(0, headerend), 16);
if (chunksize == 0 && obj.ws.acc.length >= headerend + 4) { if (isNaN(chunksize)) { // TODO: Check this path
// Send the ending chunk (NOTE: We do not support trailing headers) // Chunk is not in this batch, move one
var r = obj.ws.acc.substring(0, headerend + 4); var r = obj.amt.acc.substring(0, headerend + 2);
obj.ws.acc = obj.ws.acc.substring(headerend + 4); obj.amt.acc = obj.amt.acc.substring(headerend + 2);
obj.ws.mode = 0; // Peek if we next is the end of chunked transfer
headerend = obj.amt.acc.indexOf('\r\n');
if (headerend > 0) {
chunksize = parseInt(obj.amt.acc.substring(0, headerend), 16);
if (chunksize == 0) { obj.amt.mode = 0; }
}
return r; return r;
} else if (chunksize > 0 && obj.ws.acc.length >= headerend + 4) { } else if (chunksize == 0 && obj.amt.acc.length >= headerend + 4) {
// Send the ending chunk (NOTE: We do not support trailing headers)
var r = obj.amt.acc.substring(0, headerend + 4);
obj.amt.acc = obj.amt.acc.substring(headerend + 4);
obj.amt.mode = 0;
return r;
} else if (chunksize > 0 && obj.amt.acc.length >= headerend + 4) {
// Send a chunk // Send a chunk
var r = obj.ws.acc.substring(0, headerend + chunksize + 4); var r = obj.amt.acc.substring(0, headerend + chunksize + 4);
obj.ws.acc = obj.ws.acc.substring(headerend + chunksize + 4); obj.amt.acc = obj.amt.acc.substring(headerend + chunksize + 4);
return r; return r;
} }
} else if (obj.ws.mode == 3) { // Until Close Mode } else if (obj.ws.mode == 3) { // Until Close Mode

View File

@ -175,16 +175,16 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain) {
} }
} }
} }
ws.flushSink = function () { ws.flushSink = function () { try { ws.resume(); } catch (e) { } };
try { ws.resume(); } catch (e) { }
};
// When data is received from the mesh relay web socket // When data is received from the mesh relay web socket
ws.on('message', function (data) { ws.on('message', function (data) {
//console.log(typeof data, data.length); //console.log(typeof data, data.length);
//if (typeof data == 'string') console.log(data); if (this.peer != null) {
if (this.peer != null) { try { this.pause(); this.peer.send(data, ws.flushSink); } catch (e) { } } //if (typeof data == 'string') { console.log('Relay: ' + data); }
try { this.pause(); this.peer.send(data, ws.flushSink); } catch (e) { }
}
}); });
// If error, do nothing // If error, do nothing

View File

@ -1,6 +1,6 @@
{ {
"name": "meshcentral", "name": "meshcentral",
"version": "0.1.2-s", "version": "0.1.2-t",
"keywords": [ "keywords": [
"Remote Management", "Remote Management",
"Intel AMT", "Intel AMT",

View File

@ -172,7 +172,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
obj.ProcessData = function (str) { obj.ProcessData = function (str) {
if (str.length < 4) return; if (str.length < 4) return;
var cmdmsg = null, X = 0, Y = 0, command = ReadShort(str, 0), cmdsize = ReadShort(str, 2); var cmdmsg = null, X = 0, Y = 0, command = ReadShort(str, 0), cmdsize = ReadShort(str, 2);
if (command >= 18) { console.error("Invalid KVM command " + command + " of size " + cmdsize); obj.parent.Stop(); return; } if (command >= 18) { console.error("Invalid KVM command " + command + " of size " + cmdsize); console.log("Invalid KVM data", str.length, str, rstr2hex(str)); return; }
if (cmdsize > str.length) { console.error("KVM invalid command size", cmdsize, str.length); return; } if (cmdsize > str.length) { console.error("KVM invalid command size", cmdsize, str.length); return; }
//meshOnDebug("KVM Command: " + command + " Len:" + cmdsize); //meshOnDebug("KVM Command: " + command + " Len:" + cmdsize);
if (obj.debugmode == 1) { console.log("KVM Command: " + command + " Len:" + cmdsize); } if (obj.debugmode == 1) { console.log("KVM Command: " + command + " Len:" + cmdsize); }

View File

@ -17,6 +17,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
obj.tunnelid = Math.random().toString(36).substring(2); // Generate a random client tunnel id obj.tunnelid = Math.random().toString(36).substring(2); // Generate a random client tunnel id
obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER, 4 = Files, 5 = FileTransfer obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER, 4 = Files, 5 = FileTransfer
obj.attemptWebRTC = false; obj.attemptWebRTC = false;
obj.webRtcActive = false;
obj.webrtc = null; obj.webrtc = null;
obj.webchannel = null; obj.webchannel = null;
obj.onStateChanged = null; obj.onStateChanged = null;
@ -49,12 +50,10 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
// Called to pass websocket control messages // Called to pass websocket control messages
obj.xxOnControlCommand = function (msg) { obj.xxOnControlCommand = function (msg) {
//console.log(msg);
//obj.socket.send('hellobob');
var controlMsg = JSON.parse(msg); var controlMsg = JSON.parse(msg);
if ((controlMsg.type == 'answer') && (obj.webrtc != null)) { if ((controlMsg.type == 'answer') && (obj.webrtc != null)) {
console.log('gotAnswer', JSON.stringify(controlMsg)); //console.log('gotAnswer', JSON.stringify(controlMsg));
obj.webrtc.setRemoteDescription(new RTCSessionDescription(controlMsg), function () { console.log('WebRTC remote ok'); }, obj.xxCloseWebRTC); obj.webrtc.setRemoteDescription(new RTCSessionDescription(controlMsg), function () { /*console.log('WebRTC remote ok');*/ }, obj.xxCloseWebRTC);
} }
} }
@ -65,7 +64,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
} }
obj.xxOnMessage = function (e) { obj.xxOnMessage = function (e) {
if (obj.debugmode == 1) { console.log('Recv', e.data); } //if (obj.debugmode == 1) { console.log('Recv', e.data); }
if (obj.State < 3) { if (obj.State < 3) {
if (e.data == 'c') { if (e.data == 'c') {
obj.socket.send(obj.protocol); obj.socket.send(obj.protocol);
@ -79,28 +78,27 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
if (obj.webrtc != null) { if (obj.webrtc != null) {
obj.webchannel = obj.webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 } obj.webchannel = obj.webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 }
obj.webchannel.onmessage = function (event) { console.log("DataChannel - onmessage", event.data); }; obj.webchannel.onmessage = function (event) { console.log("DataChannel - onmessage", event.data); obj.xxOnMessage(event.data); };
obj.webchannel.onopen = function () { console.log("DataChannel - onopen"); }; obj.webchannel.onopen = function () { obj.webRtcActive = true; if (obj.onStateChanged != null) { obj.onStateChanged(obj, obj.State); } /*obj.webchannel.send("Browser WebRTC Hello!!!");*/ };
obj.webchannel.onclose = function (event) { console.log("DataChannel - onclose"); } obj.webchannel.onclose = function (event) { obj.Stop(); }
obj.webrtc.ondatachannel = function (e) { console.log('ondatachannel'); } // TODO: Should not be needed
obj.webrtc.onicecandidate = function (e) { obj.webrtc.onicecandidate = function (e) {
if (e.candidate == null) { if (e.candidate == null) {
console.log('createOffer', JSON.stringify(obj.webrtcoffer)); //console.log('createOffer', JSON.stringify(obj.webrtcoffer));
obj.socket.send('**********%%%%%%###**' + JSON.stringify(obj.webrtcoffer)); // End of candidates, send the offer obj.socket.send(JSON.stringify(obj.webrtcoffer)); // End of candidates, send the offer
} else { } else {
obj.webrtcoffer.sdp += ("a=" + e.candidate.candidate + "\r\n"); // New candidate, add it to the SDP obj.webrtcoffer.sdp += ("a=" + e.candidate.candidate + "\r\n"); // New candidate, add it to the SDP
} }
} }
obj.webrtc.oniceconnectionstatechange = function () { obj.webrtc.oniceconnectionstatechange = function () {
if (obj.webrtc != null) { if (obj.webrtc != null) {
console.log('oniceconnectionstatechange', obj.webrtc.iceConnectionState); //console.log('WebRTC ICE', obj.webrtc.iceConnectionState);
if ((obj.webrtc.iceConnectionState == 'disconnected') || (obj.webrtc.iceConnectionState == 'failed')) { obj.xxCloseWebRTC(); } if ((obj.webrtc.iceConnectionState == 'disconnected') || (obj.webrtc.iceConnectionState == 'failed')) { obj.xxCloseWebRTC(); }
} }
} }
obj.webrtc.createOffer(function (offer) { obj.webrtc.createOffer(function (offer) {
// Got the offer // Got the offer
obj.webrtcoffer = offer; obj.webrtcoffer = offer;
obj.webrtc.setLocalDescription(offer, function () { console.log('WebRTC local ok'); }, obj.xxCloseWebRTC); obj.webrtc.setLocalDescription(offer, function () { /*console.log('WebRTC local ok');*/ }, obj.xxCloseWebRTC);
}, obj.xxCloseWebRTC, { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } }); }, obj.xxCloseWebRTC, { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } });
} }
} }
@ -108,10 +106,14 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
return; return;
} }
} }
if (typeof e.data == 'string') { if (typeof e.data == 'string') {
// Control messages, most likely WebRTC setup // Control messages, most likely WebRTC setup
obj.xxOnControlCommand(e.data); obj.xxOnControlCommand(e.data);
} else if (typeof e.data == 'object') { return;
}
if (typeof e.data == 'object') {
var f = new FileReader(); var f = new FileReader();
if (f.readAsBinaryString) { if (f.readAsBinaryString) {
// Chrome & Firefox (Draft) // Chrome & Firefox (Draft)
@ -138,7 +140,6 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
obj.xxOnSocketData = function (data) { obj.xxOnSocketData = function (data) {
if (!data || obj.connectstate == -1) return; if (!data || obj.connectstate == -1) return;
if (typeof data === 'object') { if (typeof data === 'object') {
// This is an ArrayBuffer, convert it to a string array (used in IE) // This is an ArrayBuffer, convert it to a string array (used in IE)
var binary = "", bytes = new Uint8Array(data), length = bytes.byteLength; var binary = "", bytes = new Uint8Array(data), length = bytes.byteLength;
@ -146,39 +147,35 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
data = binary; data = binary;
} }
else if (typeof data !== 'string') return; else if (typeof data !== 'string') return;
// TODO: Don't use a prefix anymore, use string encoding instead
if (data.length > 21 && data.startsWith('**********%%%%%%###**')) { obj.xxOnControlCommand(data.substring(21)); return; }
//console.log("xxOnSocketData", rstr2hex(data)); //console.log("xxOnSocketData", rstr2hex(data));
return obj.m.ProcessData(data); return obj.m.ProcessData(data);
} }
obj.Send = function (x) { obj.Send = function (x) {
//obj.debug("Agent Redir Send(" + x.length + "): " + rstr2hex(x)); //obj.debug("Agent Redir Send(" + obj.webRtcActive + ", " + x.length + "): " + rstr2hex(x));
//console.log("Agent Redir Send(" + obj.webRtcActive + ", " + x.length + "): " + rstr2hex(x));
if (obj.socket != null && obj.socket.readyState == WebSocket.OPEN) { if (obj.socket != null && obj.socket.readyState == WebSocket.OPEN) {
if (typeof x == 'string') { if (typeof x == 'string') {
if (obj.debugmode == 1) { if (obj.debugmode == 1) {
var b = new Uint8Array(x.length), c = []; var b = new Uint8Array(x.length), c = [];
for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); c.push(x.charCodeAt(i)); } for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); c.push(x.charCodeAt(i)); }
obj.socket.send(b.buffer); if (obj.webRtcActive == true) { obj.webchannel.send(b.buffer); } else { obj.socket.send(b.buffer); }
console.log('Send', c); //console.log('Send', c);
} else { } else {
var b = new Uint8Array(x.length); var b = new Uint8Array(x.length);
for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); } for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); }
obj.socket.send(b.buffer); if (obj.webRtcActive == true) { obj.webchannel.send(b.buffer); } else { obj.socket.send(b.buffer); }
} }
} else { } else {
if (obj.debugmode == 1) { console.log('Send', x); } //if (obj.debugmode == 1) { console.log('Send', x); }
obj.socket.send(x); if (obj.webRtcActive == true) { obj.webchannel.send(x); } else { obj.socket.send(x); }
} }
} }
} }
obj.xxOnSocketClosed = function () { obj.xxOnSocketClosed = function () {
//obj.debug("Agent Redir Socket Closed"); //obj.debug("Agent Redir Socket Closed");
if (obj.debugmode == 1) { console.log('onSocketClosed'); } //if (obj.debugmode == 1) { console.log('onSocketClosed'); }
obj.Stop(1); obj.Stop(1);
} }
@ -192,6 +189,9 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
obj.Stop = function (x) { obj.Stop = function (x) {
if (obj.debugmode == 1) { console.log('stop', x); } if (obj.debugmode == 1) { console.log('stop', x); }
//obj.debug("Agent Redir Socket Stopped"); //obj.debug("Agent Redir Socket Stopped");
obj.webRtcActive = false;
obj.webrtc = null;
obj.webchannel = null;
obj.xxStateChange(0); obj.xxStateChange(0);
obj.connectstate = -1; obj.connectstate = -1;
obj.xxCloseWebRTC(); obj.xxCloseWebRTC();

View File

@ -647,6 +647,7 @@
var amtScanResults = null; var amtScanResults = null;
var debugmode = false; var debugmode = false;
var clickOnce = detectClickOnce(); var clickOnce = detectClickOnce();
var attemptWebRTC = false;
function startup() { function startup() {
if ((features & 32) == 0) { if ((features & 32) == 0) {
@ -2934,7 +2935,7 @@
desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort); desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort);
desktop.debugmode = debugmode; desktop.debugmode = debugmode;
desktop.m.debugmode = debugmode; desktop.m.debugmode = debugmode;
//desktop.attemptWebRTC = debugmode; desktop.attemptWebRTC = attemptWebRTC;
desktop.onStateChanged = onDesktopStateChange; desktop.onStateChanged = onDesktopStateChange;
desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best. desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
desktop.m.ScalingLevel = desktopsettings.scaling; desktop.m.ScalingLevel = desktopsettings.scaling;
@ -2954,7 +2955,10 @@
function onDesktopStateChange(xdesktop, state) { function onDesktopStateChange(xdesktop, state) {
var xstate = state; var xstate = state;
if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; } if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; }
QH('deskstatus', StatusStrs[xstate]); var str = StatusStrs[xstate];
if (desktop.webRtcActive == true) { str += ', WebRTC'; }
//if (desktop.m.stopInput == true) { str += ', Loopback'; }
QH('deskstatus', str);
QE('deskSaveBtn', state == 3); QE('deskSaveBtn', state == 3);
QV('deskFocusBtn', (desktop != null) && (desktop.contype == 2) && (state != 0) && (desktopsettings.showfocus)); QV('deskFocusBtn', (desktop != null) && (desktop.contype == 2) && (state != 0) && (desktopsettings.showfocus));
QE('DeskCAD', state == 3); QE('DeskCAD', state == 3);
@ -3143,7 +3147,9 @@
function onTerminalStateChange(xterminal, state) { function onTerminalStateChange(xterminal, state) {
var xstate = state; var xstate = state;
if ((xstate == 3) && (xterminal.contype == 2)) { xstate++; } if ((xstate == 3) && (xterminal.contype == 2)) { xstate++; }
QH('termstatus', StatusStrs[xstate]); var str = StatusStrs[xstate];
if (terminal.webRtcActive == true) { str += ', WebRTC'; }
QH('termstatus', str);
switch (state) { switch (state) {
case 0: case 0:
// Disconnected, clear the terminal // Disconnected, clear the terminal
@ -3180,7 +3186,7 @@
terminal = CreateAgentRedirect(meshserver, CreateAmtRemoteTerminal('Term'), serverPublicNamePort); terminal = CreateAgentRedirect(meshserver, CreateAmtRemoteTerminal('Term'), serverPublicNamePort);
terminal.debugmode = debugmode; terminal.debugmode = debugmode;
terminal.m.debugmode = debugmode; terminal.m.debugmode = debugmode;
//terminal.attemptWebRTC = debugmode; terminal.attemptWebRTC = attemptWebRTC;
terminal.onStateChanged = onTerminalStateChange; terminal.onStateChanged = onTerminalStateChange;
terminal.Start(terminalNode._id); terminal.Start(terminalNode._id);
terminal.contype = 1; terminal.contype = 1;
@ -3251,7 +3257,9 @@
function onFilesStateChange(xfiles, state) { function onFilesStateChange(xfiles, state) {
p13Connect.value = (state == 0) ? 'Connect' : 'Disconnect'; p13Connect.value = (state == 0) ? 'Connect' : 'Disconnect';
Q('p13Status').textContent = StatusStrs[state]; var str = StatusStrs[state];
if (files.webRtcActive == true) { str += ', WebRTC'; }
Q('p13Status').textContent = str;
switch (state) { switch (state) {
case 0: case 0:
// Disconnected, clear the files // Disconnected, clear the files
@ -3286,7 +3294,7 @@
if (!files) { if (!files) {
// Setup a mesh agent files // Setup a mesh agent files
files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort); files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort);
files.attemptWebRTC = debugmode; files.attemptWebRTC = false;
files.onStateChanged = onFilesStateChange; files.onStateChanged = onFilesStateChange;
files.Start(filesNode._id); files.Start(filesNode._id);
} else { } else {
@ -3492,7 +3500,7 @@
// Called by the html page to start a download, arguments are: path, file name and file size. // Called by the html page to start a download, arguments are: path, file name and file size.
function p13downloadfile(x, y, z) { function p13downloadfile(x, y, z) {
downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort); // Create our file transport downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort); // Create our file transport
downloadFile.attemptWebRTC = debugmode; downloadFile.attemptWebRTC = false;
downloadFile.onStateChanged = onFileDownloadStateChange; downloadFile.onStateChanged = onFileDownloadStateChange;
downloadFile.xpath = decodeURIComponent(x); downloadFile.xpath = decodeURIComponent(x);
downloadFile.xfile = decodeURIComponent(y); downloadFile.xfile = decodeURIComponent(y);
@ -3571,7 +3579,7 @@
// Connect again // Connect again
function p13uploadReconnect() { function p13uploadReconnect() {
uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort); uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort);
uploadFile.ws.attemptWebRTC = debugmode; uploadFile.ws.attemptWebRTC = false;
uploadFile.ws.onStateChanged = onFileUploadStateChange; uploadFile.ws.onStateChanged = onFileUploadStateChange;
uploadFile.ws.Start(filesNode._id); uploadFile.ws.Start(filesNode._id);
} }