diodejs 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -5
- package/bindPort.js +446 -66
- package/clientManager.js +435 -0
- package/connection.js +186 -72
- package/examples/RPCTest.js +9 -7
- package/examples/nativeBindTest.js +55 -0
- package/examples/nativeForwardTest.js +80 -0
- package/examples/nativeTcpClientTest.js +56 -0
- package/examples/nativeUdpClientTest.js +40 -0
- package/examples/portForwardTest.js +5 -7
- package/examples/publishAndBind.js +6 -10
- package/examples/publishPortTest.js +4 -6
- package/index.js +8 -1
- package/logger.js +10 -5
- package/nativeCrypto.js +321 -0
- package/package.json +6 -8
- package/publishPort.js +575 -94
- package/rpc.js +161 -41
- package/testServers/udpTest.js +1 -2
- package/utils.js +42 -9
package/publishPort.js
CHANGED
|
@@ -8,12 +8,39 @@ const { Buffer } = require('buffer');
|
|
|
8
8
|
const EventEmitter = require('events');
|
|
9
9
|
const { Duplex } = require('stream');
|
|
10
10
|
const DiodeRPC = require('./rpc');
|
|
11
|
-
const { makeReadable } = require('./utils');
|
|
11
|
+
const { makeReadable, parseUInt, toBufferView } = require('./utils');
|
|
12
|
+
const nativeCrypto = require('./nativeCrypto');
|
|
12
13
|
const logger = require('./logger');
|
|
14
|
+
const secp256k1 = require('secp256k1');
|
|
15
|
+
const ethUtil = require('ethereumjs-util');
|
|
16
|
+
|
|
17
|
+
function normalizeDeviceId(raw) {
|
|
18
|
+
if (!raw) return '';
|
|
19
|
+
let buf = toBufferView(raw);
|
|
20
|
+
if (buf.length === 20) {
|
|
21
|
+
return `0x${buf.toString('hex')}`;
|
|
22
|
+
}
|
|
23
|
+
if (buf.length === 32) {
|
|
24
|
+
return `0x${buf.slice(12).toString('hex')}`;
|
|
25
|
+
}
|
|
26
|
+
if (buf.length === 33 || buf.length === 65) {
|
|
27
|
+
try {
|
|
28
|
+
const uncompressed = buf.length === 33 ? secp256k1.publicKeyConvert(buf, false) : buf;
|
|
29
|
+
const addr = ethUtil.pubToAddress(uncompressed, true);
|
|
30
|
+
return `0x${addr.toString('hex')}`;
|
|
31
|
+
} catch (_) {
|
|
32
|
+
// fallback below
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (buf.length > 20) {
|
|
36
|
+
return `0x${buf.slice(buf.length - 20).toString('hex')}`;
|
|
37
|
+
}
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
13
40
|
|
|
14
41
|
class DiodeSocket extends Duplex {
|
|
15
42
|
constructor(ref, rpc) {
|
|
16
|
-
super();
|
|
43
|
+
super({ readableHighWaterMark: 256 * 1024, writableHighWaterMark: 256 * 1024, allowHalfOpen: false });
|
|
17
44
|
this.ref = ref;
|
|
18
45
|
this.rpc = rpc;
|
|
19
46
|
}
|
|
@@ -39,7 +66,11 @@ class PublishPort extends EventEmitter {
|
|
|
39
66
|
constructor(connection, publishedPorts, _certPath = null) {
|
|
40
67
|
super();
|
|
41
68
|
this.connection = connection;
|
|
42
|
-
this.
|
|
69
|
+
this._rpcByConnection = new Map();
|
|
70
|
+
this.rpc = this._isManager() ? null : this._getRpcFor(connection);
|
|
71
|
+
this._listening = false; // ensure startListening is idempotent
|
|
72
|
+
this.nativeSessions = new Map();
|
|
73
|
+
this.handshakeTimeoutMs = parseInt(process.env.DIODE_NATIVE_HANDSHAKE_TIMEOUT_MS, 10) || 10000;
|
|
43
74
|
|
|
44
75
|
// Convert publishedPorts to a Map with configurations
|
|
45
76
|
this.publishedPorts = new Map();
|
|
@@ -51,9 +82,9 @@ class PublishPort extends EventEmitter {
|
|
|
51
82
|
|
|
52
83
|
this.startListening();
|
|
53
84
|
if (this.publishedPorts.size > 0) {
|
|
54
|
-
logger.info(`Publishing ports: ${Array.from(this.publishedPorts.keys())}`);
|
|
85
|
+
logger.info(() => `Publishing ports: ${Array.from(this.publishedPorts.keys())}`);
|
|
55
86
|
} else {
|
|
56
|
-
logger.info("No ports published initially");
|
|
87
|
+
logger.info(() => "No ports published initially");
|
|
57
88
|
}
|
|
58
89
|
}
|
|
59
90
|
|
|
@@ -69,7 +100,7 @@ class PublishPort extends EventEmitter {
|
|
|
69
100
|
|
|
70
101
|
// Add to map
|
|
71
102
|
this.publishedPorts.set(portNum, portConfig);
|
|
72
|
-
logger.info(`Added published port ${portNum} with mode: ${portConfig.mode}`);
|
|
103
|
+
logger.info(() => `Added published port ${portNum} with mode: ${portConfig.mode}`);
|
|
73
104
|
|
|
74
105
|
return true;
|
|
75
106
|
}
|
|
@@ -79,23 +110,34 @@ class PublishPort extends EventEmitter {
|
|
|
79
110
|
const portNum = parseInt(port, 10);
|
|
80
111
|
|
|
81
112
|
if (!this.publishedPorts.has(portNum)) {
|
|
82
|
-
logger.warn(`Port ${portNum} is not published`);
|
|
113
|
+
logger.warn(() => `Port ${portNum} is not published`);
|
|
83
114
|
return false;
|
|
84
115
|
}
|
|
85
116
|
|
|
86
117
|
// Close any active connections for this port
|
|
87
118
|
// This could require tracking active connections by port
|
|
88
119
|
// For now, let's log about active connections
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
let activeConnections = [];
|
|
121
|
+
if (this.connection && typeof this.connection.getConnections === 'function') {
|
|
122
|
+
for (const conn of this.connection.getConnections()) {
|
|
123
|
+
if (conn && conn.connections) {
|
|
124
|
+
activeConnections = activeConnections.concat(
|
|
125
|
+
Array.from(conn.connections.values()).filter(info => info.port === portNum)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} else if (this.connection && this.connection.connections) {
|
|
130
|
+
activeConnections = Array.from(this.connection.connections.values())
|
|
131
|
+
.filter(conn => conn.port === portNum);
|
|
132
|
+
}
|
|
91
133
|
|
|
92
134
|
if (activeConnections.length > 0) {
|
|
93
|
-
logger.warn(`Removing port ${portNum} with ${activeConnections.length} active connections`);
|
|
135
|
+
logger.warn(() => `Removing port ${portNum} with ${activeConnections.length} active connections`);
|
|
94
136
|
// We could close these connections, but they'll be rejected naturally on next data transfer
|
|
95
137
|
}
|
|
96
138
|
|
|
97
139
|
this.publishedPorts.delete(portNum);
|
|
98
|
-
logger.info(`Removed published port ${portNum}`);
|
|
140
|
+
logger.info(() => `Removed published port ${portNum}`);
|
|
99
141
|
|
|
100
142
|
return true;
|
|
101
143
|
}
|
|
@@ -126,43 +168,83 @@ class PublishPort extends EventEmitter {
|
|
|
126
168
|
clearPorts() {
|
|
127
169
|
const portCount = this.publishedPorts.size;
|
|
128
170
|
this.publishedPorts.clear();
|
|
129
|
-
logger.info(`Cleared ${portCount} published ports`);
|
|
171
|
+
logger.info(() => `Cleared ${portCount} published ports`);
|
|
130
172
|
return portCount;
|
|
131
173
|
}
|
|
132
174
|
|
|
175
|
+
_isManager() {
|
|
176
|
+
return this.connection && typeof this.connection.getConnections === 'function';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_getRpcFor(connection) {
|
|
180
|
+
if (!connection) return null;
|
|
181
|
+
let rpc = this._rpcByConnection.get(connection);
|
|
182
|
+
if (!rpc) {
|
|
183
|
+
rpc = connection.RPC || new DiodeRPC(connection);
|
|
184
|
+
this._rpcByConnection.set(connection, rpc);
|
|
185
|
+
}
|
|
186
|
+
return rpc;
|
|
187
|
+
}
|
|
188
|
+
|
|
133
189
|
startListening() {
|
|
190
|
+
if (this._listening) return this; // idempotent
|
|
134
191
|
// Listen for unsolicited messages from the connection
|
|
135
|
-
this.
|
|
192
|
+
this._onUnsolicited = (message, sourceConnection) => {
|
|
193
|
+
const connection = sourceConnection || this.connection;
|
|
194
|
+
if (!connection) {
|
|
195
|
+
logger.warn(() => 'Received unsolicited message without a valid connection context');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
136
198
|
const [sessionIdRaw, messageContent] = message;
|
|
137
199
|
const messageTypeRaw = messageContent[0];
|
|
138
|
-
const messageType =
|
|
200
|
+
const messageType = toBufferView(messageTypeRaw).toString('utf8');
|
|
139
201
|
|
|
140
202
|
if (messageType === 'portopen') {
|
|
141
|
-
this.handlePortOpen(sessionIdRaw, messageContent);
|
|
142
|
-
} else if (messageType === '
|
|
143
|
-
this.
|
|
203
|
+
this.handlePortOpen(sessionIdRaw, messageContent, connection);
|
|
204
|
+
} else if (messageType === 'portopen2') {
|
|
205
|
+
this.handlePortOpen2(sessionIdRaw, messageContent, connection);
|
|
206
|
+
} else if (messageType === 'portclose2') {
|
|
207
|
+
logger.debug(() => 'Received portclose2');
|
|
208
|
+
} else if (messageType === 'portsend' || messageType === 'data') {
|
|
209
|
+
// Accept both synonyms for payload delivery
|
|
210
|
+
this.handlePortSend(sessionIdRaw, messageContent, connection);
|
|
144
211
|
} else if (messageType === 'portclose') {
|
|
145
|
-
this.handlePortClose(sessionIdRaw, messageContent);
|
|
212
|
+
this.handlePortClose(sessionIdRaw, messageContent, connection);
|
|
146
213
|
} else {
|
|
147
|
-
if (messageType
|
|
148
|
-
logger.warn(`Unknown unsolicited message type: ${messageType}`);
|
|
214
|
+
if (messageType !== 'ticket_request') {
|
|
215
|
+
logger.warn(() => `Unknown unsolicited message type: ${messageType}`);
|
|
149
216
|
}
|
|
150
217
|
}
|
|
151
|
-
}
|
|
218
|
+
};
|
|
219
|
+
this.connection.on('unsolicited', this._onUnsolicited);
|
|
220
|
+
this._listening = true;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
stopListening() {
|
|
225
|
+
if (!this._listening) return this;
|
|
226
|
+
if (this._onUnsolicited) {
|
|
227
|
+
this.connection.off('unsolicited', this._onUnsolicited);
|
|
228
|
+
}
|
|
229
|
+
this._listening = false;
|
|
230
|
+
return this;
|
|
152
231
|
}
|
|
153
232
|
|
|
154
|
-
handlePortOpen(sessionIdRaw, messageContent) {
|
|
233
|
+
handlePortOpen(sessionIdRaw, messageContent, connection) {
|
|
234
|
+
const rpc = this._getRpcFor(connection);
|
|
155
235
|
// messageContent: ['portopen', portString, ref, deviceId]
|
|
156
236
|
const portStringRaw = messageContent[1];
|
|
157
237
|
const refRaw = messageContent[2];
|
|
158
238
|
const deviceIdRaw = messageContent[3];
|
|
159
239
|
|
|
160
|
-
const sessionId =
|
|
240
|
+
const sessionId = toBufferView(sessionIdRaw);
|
|
161
241
|
const portString = makeReadable(portStringRaw);
|
|
162
|
-
const ref =
|
|
163
|
-
const deviceId =
|
|
242
|
+
const ref = toBufferView(refRaw);
|
|
243
|
+
const deviceId = normalizeDeviceId(deviceIdRaw);
|
|
164
244
|
|
|
165
|
-
logger.info(`Received portopen request for portString ${portString} with ref ${ref.toString('hex')} from device ${deviceId}`);
|
|
245
|
+
logger.info(() => `Received portopen request for portString ${portString} with ref ${ref.toString('hex')} from device ${deviceId}`);
|
|
246
|
+
|
|
247
|
+
const isHandshake = typeof portString === 'string' && portString.includes('#hs');
|
|
166
248
|
|
|
167
249
|
// Extract protocol and port number from portString
|
|
168
250
|
var protocol = 'tcp';
|
|
@@ -180,9 +262,9 @@ class PublishPort extends EventEmitter {
|
|
|
180
262
|
|
|
181
263
|
// Check if the port is published
|
|
182
264
|
if (!this.publishedPorts.has(port)) {
|
|
183
|
-
logger.warn(`Port ${port} is not published. Rejecting request.`);
|
|
265
|
+
logger.warn(() => `Port ${port} is not published. Rejecting request.`);
|
|
184
266
|
// Send error response
|
|
185
|
-
|
|
267
|
+
rpc.sendError(sessionId, ref, 'Port is not published');
|
|
186
268
|
return;
|
|
187
269
|
}
|
|
188
270
|
|
|
@@ -190,71 +272,463 @@ class PublishPort extends EventEmitter {
|
|
|
190
272
|
const portConfig = this.publishedPorts.get(port);
|
|
191
273
|
if (portConfig.mode === 'private' && Array.isArray(portConfig.whitelist)) {
|
|
192
274
|
if (!portConfig.whitelist.includes(deviceId)) {
|
|
193
|
-
logger.warn(`Device ${deviceId} is not whitelisted for port ${port}. Rejecting request.`);
|
|
194
|
-
|
|
275
|
+
logger.warn(() => `Device ${deviceId} is not whitelisted for port ${port}. Rejecting request.`);
|
|
276
|
+
rpc.sendError(sessionId, ref, 'Device not whitelisted');
|
|
195
277
|
return;
|
|
196
278
|
}
|
|
197
|
-
logger.info(`Device ${deviceId} is whitelisted for port ${port}. Accepting request.`);
|
|
279
|
+
logger.info(() => `Device ${deviceId} is whitelisted for port ${port}. Accepting request.`);
|
|
198
280
|
}
|
|
199
281
|
|
|
200
282
|
// Handle based on protocol
|
|
201
283
|
if (protocol === 'tcp') {
|
|
202
|
-
this.handleTCPConnection(sessionId, ref, port, deviceId);
|
|
284
|
+
this.handleTCPConnection(sessionId, ref, port, deviceId, connection);
|
|
203
285
|
} else if (protocol === 'tls') {
|
|
204
|
-
|
|
286
|
+
if (isHandshake) {
|
|
287
|
+
this.handleTLSHandshake(sessionId, ref, port, deviceId, connection);
|
|
288
|
+
} else {
|
|
289
|
+
this.handleTLSConnection(sessionId, ref, port, deviceId, connection);
|
|
290
|
+
}
|
|
205
291
|
} else if (protocol === 'udp') {
|
|
206
|
-
this.handleUDPConnection(sessionId, ref, port, deviceId);
|
|
292
|
+
this.handleUDPConnection(sessionId, ref, port, deviceId, connection);
|
|
293
|
+
} else {
|
|
294
|
+
logger.warn(() => `Unsupported protocol: ${protocol}`);
|
|
295
|
+
rpc.sendError(sessionId, ref, `Unsupported protocol: ${protocol}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
handleTLSHandshake(sessionId, ref, port, deviceId, connection) {
|
|
300
|
+
const rpc = this._getRpcFor(connection);
|
|
301
|
+
rpc.sendResponse(sessionId, ref, 'ok');
|
|
302
|
+
|
|
303
|
+
const diodeSocket = new DiodeSocket(ref, rpc);
|
|
304
|
+
const certPem = connection.getDeviceCertificate();
|
|
305
|
+
if (!certPem) {
|
|
306
|
+
logger.error(() => 'No device certificate available for TLS handshake');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const tlsOptions = {
|
|
311
|
+
cert: certPem,
|
|
312
|
+
key: certPem,
|
|
313
|
+
rejectUnauthorized: false,
|
|
314
|
+
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
315
|
+
ecdhCurve: 'secp256k1',
|
|
316
|
+
minVersion: 'TLSv1.2',
|
|
317
|
+
maxVersion: 'TLSv1.2',
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const tlsSocket = new tls.TLSSocket(diodeSocket, {
|
|
321
|
+
isServer: true,
|
|
322
|
+
...tlsOptions,
|
|
323
|
+
});
|
|
324
|
+
tlsSocket.setNoDelay(true);
|
|
325
|
+
|
|
326
|
+
connection.addConnection(ref, {
|
|
327
|
+
diodeSocket,
|
|
328
|
+
tlsSocket,
|
|
329
|
+
protocol: 'tls',
|
|
330
|
+
port,
|
|
331
|
+
deviceId,
|
|
332
|
+
handshake: true,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
(async () => {
|
|
336
|
+
let session = null;
|
|
337
|
+
try {
|
|
338
|
+
const peerMessage = await nativeCrypto.readHandshakeMessage(tlsSocket, this.handshakeTimeoutMs);
|
|
339
|
+
session = this.nativeSessions.get(Number(peerMessage.physicalPort));
|
|
340
|
+
if (!session) {
|
|
341
|
+
throw new Error(`No native session for physical port ${peerMessage.physicalPort}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const verification = nativeCrypto.verifyHandshakeMessage(peerMessage, {
|
|
345
|
+
expectedRole: 'bind',
|
|
346
|
+
expectedDeviceId: session.deviceId,
|
|
347
|
+
expectedPhysicalPort: session.physicalPort,
|
|
348
|
+
});
|
|
349
|
+
if (!verification.ok) {
|
|
350
|
+
throw new Error(`Handshake verification failed: ${verification.reason}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const localDeviceId = connection.getEthereumAddress().toLowerCase();
|
|
354
|
+
const { message, privKey, nonce } = nativeCrypto.createHandshakeMessage({
|
|
355
|
+
role: 'publish',
|
|
356
|
+
deviceId: localDeviceId,
|
|
357
|
+
physicalPort: session.physicalPort,
|
|
358
|
+
privateKey: connection.getPrivateKey()
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
nativeCrypto.writeHandshakeMessage(tlsSocket, message);
|
|
362
|
+
|
|
363
|
+
session.session = nativeCrypto.deriveSessionKeys({
|
|
364
|
+
role: 'publish',
|
|
365
|
+
localDeviceId,
|
|
366
|
+
remoteDeviceId: verification.deviceId,
|
|
367
|
+
localEphPriv: privKey,
|
|
368
|
+
remoteEphPub: verification.ephPub,
|
|
369
|
+
localNonce: nonce,
|
|
370
|
+
remoteNonce: verification.nonce,
|
|
371
|
+
physicalPort: session.physicalPort,
|
|
372
|
+
});
|
|
373
|
+
session.ready = true;
|
|
374
|
+
if (session.timer) {
|
|
375
|
+
clearTimeout(session.timer);
|
|
376
|
+
session.timer = null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (session.localSocket && typeof session.localSocket.resume === 'function') {
|
|
380
|
+
session.localSocket.resume();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (session.protocol === 'udp' && session.relaySocket && session.session) {
|
|
384
|
+
const probe = nativeCrypto.createUdpPacket(session.session, Buffer.alloc(0));
|
|
385
|
+
session.relaySocket.send(probe);
|
|
386
|
+
}
|
|
387
|
+
} catch (error) {
|
|
388
|
+
logger.error(() => `TLS handshake failed: ${error}`);
|
|
389
|
+
if (session) {
|
|
390
|
+
session.error = error;
|
|
391
|
+
}
|
|
392
|
+
} finally {
|
|
393
|
+
try { tlsSocket.end(); } catch {}
|
|
394
|
+
try { rpc.portClose(ref); } catch {}
|
|
395
|
+
try { connection.deleteConnection(ref); } catch {}
|
|
396
|
+
}
|
|
397
|
+
})();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
handlePortOpen2(sessionIdRaw, messageContent, connection) {
|
|
401
|
+
const rpc = this._getRpcFor(connection);
|
|
402
|
+
// messageContent: ['portopen2', portName, physicalPort, sourceDeviceAddress, flags]
|
|
403
|
+
const portNameRaw = messageContent[1];
|
|
404
|
+
const physicalPortRaw = messageContent[2];
|
|
405
|
+
const sourceDeviceRaw = messageContent[3];
|
|
406
|
+
const flagsRaw = messageContent[4];
|
|
407
|
+
|
|
408
|
+
const sessionId = toBufferView(sessionIdRaw);
|
|
409
|
+
const portName = makeReadable(portNameRaw);
|
|
410
|
+
const physicalPort = parseUInt(physicalPortRaw);
|
|
411
|
+
const physicalPortRef = Number.isFinite(physicalPort) ? physicalPort : physicalPortRaw;
|
|
412
|
+
const flags = flagsRaw ? makeReadable(flagsRaw) : '';
|
|
413
|
+
const deviceId = normalizeDeviceId(sourceDeviceRaw);
|
|
414
|
+
|
|
415
|
+
logger.info(() => `Received portopen2 request for ${portName} on relay port ${physicalPort} from device ${deviceId}`);
|
|
416
|
+
|
|
417
|
+
// Parse port and protocol from portName
|
|
418
|
+
let port = 0;
|
|
419
|
+
let protocolFromName = null;
|
|
420
|
+
if (typeof portName === 'number') {
|
|
421
|
+
port = portName;
|
|
422
|
+
} else if (typeof portName === 'string') {
|
|
423
|
+
const parts = portName.split(':');
|
|
424
|
+
if (parts.length === 2) {
|
|
425
|
+
protocolFromName = parts[0].toLowerCase();
|
|
426
|
+
port = parseInt(parts[1], 10);
|
|
427
|
+
} else {
|
|
428
|
+
port = parseInt(portName, 10);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
433
|
+
logger.warn(() => `Invalid port in portopen2: ${portName}`);
|
|
434
|
+
rpc.sendError(sessionId, physicalPortRef, 'Invalid port');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Determine protocol
|
|
439
|
+
let protocol = (typeof flags === 'string' && flags.includes('u')) ? 'udp' : 'tcp';
|
|
440
|
+
if (protocolFromName === 'udp' || protocolFromName === 'tcp') {
|
|
441
|
+
protocol = protocolFromName;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Check if the port is published
|
|
445
|
+
if (!this.publishedPorts.has(port)) {
|
|
446
|
+
logger.warn(() => `Port ${port} is not published. Rejecting request.`);
|
|
447
|
+
rpc.sendError(sessionId, physicalPortRef, 'Port is not published');
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Get port configuration and check whitelist if in private mode
|
|
452
|
+
const portConfig = this.publishedPorts.get(port);
|
|
453
|
+
if (portConfig.mode === 'private' && Array.isArray(portConfig.whitelist)) {
|
|
454
|
+
if (!portConfig.whitelist.includes(deviceId)) {
|
|
455
|
+
logger.warn(() => `Device ${deviceId} is not whitelisted for port ${port}. Rejecting request.`);
|
|
456
|
+
rpc.sendError(sessionId, physicalPortRef, 'Device not whitelisted');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
logger.info(() => `Device ${deviceId} is whitelisted for port ${port}. Accepting request.`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (!physicalPort) {
|
|
463
|
+
logger.warn(() => `Invalid physical port in portopen2: ${physicalPortRaw}`);
|
|
464
|
+
rpc.sendError(sessionId, physicalPortRef, 'Invalid physical port');
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const existing = this.nativeSessions.get(physicalPort);
|
|
469
|
+
if (existing) {
|
|
470
|
+
this._cleanupNativeSession(existing);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const session = {
|
|
474
|
+
physicalPort,
|
|
475
|
+
port,
|
|
476
|
+
protocol,
|
|
477
|
+
deviceId: deviceId.toLowerCase(),
|
|
478
|
+
connection,
|
|
479
|
+
ready: false,
|
|
480
|
+
session: null,
|
|
481
|
+
relaySocket: null,
|
|
482
|
+
localSocket: null,
|
|
483
|
+
timer: null,
|
|
484
|
+
};
|
|
485
|
+
session.timer = setTimeout(() => {
|
|
486
|
+
if (!session.ready) {
|
|
487
|
+
logger.warn(() => `Handshake timeout for native session on physical port ${physicalPort}`);
|
|
488
|
+
this._cleanupNativeSession(session);
|
|
489
|
+
}
|
|
490
|
+
}, Math.max(15000, this.handshakeTimeoutMs * 2));
|
|
491
|
+
|
|
492
|
+
this.nativeSessions.set(physicalPort, session);
|
|
493
|
+
|
|
494
|
+
if (protocol === 'udp') {
|
|
495
|
+
this.handleNativeUDPRelay(sessionId, physicalPortRef, session, connection);
|
|
207
496
|
} else {
|
|
208
|
-
|
|
209
|
-
|
|
497
|
+
this.handleNativeTCPRelay(sessionId, physicalPortRef, session, connection);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
_cleanupNativeSession(session) {
|
|
502
|
+
if (!session) return;
|
|
503
|
+
if (session.timer) {
|
|
504
|
+
clearTimeout(session.timer);
|
|
505
|
+
session.timer = null;
|
|
506
|
+
}
|
|
507
|
+
if (session.relaySocket) {
|
|
508
|
+
try {
|
|
509
|
+
if (typeof session.relaySocket.close === 'function') {
|
|
510
|
+
session.relaySocket.close();
|
|
511
|
+
} else {
|
|
512
|
+
session.relaySocket.destroy();
|
|
513
|
+
}
|
|
514
|
+
} catch (_) {}
|
|
515
|
+
}
|
|
516
|
+
if (session.localSocket) {
|
|
517
|
+
try {
|
|
518
|
+
if (typeof session.localSocket.close === 'function') {
|
|
519
|
+
session.localSocket.close();
|
|
520
|
+
} else {
|
|
521
|
+
session.localSocket.destroy();
|
|
522
|
+
}
|
|
523
|
+
} catch (_) {}
|
|
210
524
|
}
|
|
525
|
+
this.nativeSessions.delete(session.physicalPort);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
handleNativeTCPRelay(sessionId, physicalPortRef, session, connection) {
|
|
529
|
+
const rpc = this._getRpcFor(connection);
|
|
530
|
+
const { physicalPort, port, deviceId } = session;
|
|
531
|
+
let responded = false;
|
|
532
|
+
const sendOk = () => {
|
|
533
|
+
if (responded) return;
|
|
534
|
+
responded = true;
|
|
535
|
+
rpc.sendResponse(sessionId, physicalPortRef, 'ok');
|
|
536
|
+
};
|
|
537
|
+
const sendError = (reason) => {
|
|
538
|
+
if (responded) return;
|
|
539
|
+
responded = true;
|
|
540
|
+
rpc.sendError(sessionId, physicalPortRef, reason);
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
const relayHost = connection.getServerRelayHost();
|
|
544
|
+
const relaySocket = net.connect({ host: relayHost, port: physicalPort }, () => {
|
|
545
|
+
relaySocket.setNoDelay(true);
|
|
546
|
+
});
|
|
547
|
+
const localSocket = net.connect({ port }, () => {
|
|
548
|
+
localSocket.setNoDelay(true);
|
|
549
|
+
});
|
|
550
|
+
localSocket.pause();
|
|
551
|
+
|
|
552
|
+
session.relaySocket = relaySocket;
|
|
553
|
+
session.localSocket = localSocket;
|
|
554
|
+
|
|
555
|
+
let relayReady = false;
|
|
556
|
+
let localReady = false;
|
|
557
|
+
const maybeReady = () => {
|
|
558
|
+
if (relayReady && localReady) {
|
|
559
|
+
sendOk();
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
relaySocket.on('connect', () => {
|
|
564
|
+
relayReady = true;
|
|
565
|
+
maybeReady();
|
|
566
|
+
});
|
|
567
|
+
localSocket.on('connect', () => {
|
|
568
|
+
localReady = true;
|
|
569
|
+
maybeReady();
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const cleanup = () => {
|
|
573
|
+
if (!relaySocket.destroyed) relaySocket.destroy();
|
|
574
|
+
if (!localSocket.destroyed) localSocket.destroy();
|
|
575
|
+
this._cleanupNativeSession(session);
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
relaySocket.on('error', (err) => {
|
|
579
|
+
logger.error(() => `Relay socket error (${deviceId}): ${err}`);
|
|
580
|
+
sendError('Relay connection failed');
|
|
581
|
+
cleanup();
|
|
582
|
+
});
|
|
583
|
+
localSocket.on('error', (err) => {
|
|
584
|
+
logger.error(() => `Local TCP service error (${deviceId}): ${err}`);
|
|
585
|
+
sendError('Local service connection failed');
|
|
586
|
+
cleanup();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
relaySocket.on('end', cleanup);
|
|
590
|
+
localSocket.on('end', cleanup);
|
|
591
|
+
|
|
592
|
+
relaySocket.on('data', (data) => {
|
|
593
|
+
if (!session.ready || !session.session) return;
|
|
594
|
+
try {
|
|
595
|
+
const messages = nativeCrypto.consumeTcpFrames(session.session, data);
|
|
596
|
+
for (const msg of messages) {
|
|
597
|
+
localSocket.write(msg);
|
|
598
|
+
}
|
|
599
|
+
} catch (error) {
|
|
600
|
+
logger.error(() => `TCP decrypt error (${deviceId}): ${error}`);
|
|
601
|
+
cleanup();
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
localSocket.on('data', (data) => {
|
|
606
|
+
if (!session.ready || !session.session) return;
|
|
607
|
+
try {
|
|
608
|
+
const frame = nativeCrypto.createTcpFrame(session.session, data);
|
|
609
|
+
relaySocket.write(frame);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
logger.error(() => `TCP encrypt error (${deviceId}): ${error}`);
|
|
612
|
+
cleanup();
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
handleNativeUDPRelay(sessionId, physicalPortRef, session, connection) {
|
|
618
|
+
const rpc = this._getRpcFor(connection);
|
|
619
|
+
const { physicalPort, port, deviceId } = session;
|
|
620
|
+
let responded = false;
|
|
621
|
+
const sendOk = () => {
|
|
622
|
+
if (responded) return;
|
|
623
|
+
responded = true;
|
|
624
|
+
rpc.sendResponse(sessionId, physicalPortRef, 'ok');
|
|
625
|
+
};
|
|
626
|
+
const sendError = (reason) => {
|
|
627
|
+
if (responded) return;
|
|
628
|
+
responded = true;
|
|
629
|
+
rpc.sendError(sessionId, physicalPortRef, reason);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
const relaySocket = dgram.createSocket('udp4');
|
|
633
|
+
const localSocket = dgram.createSocket('udp4');
|
|
634
|
+
|
|
635
|
+
let relayReady = false;
|
|
636
|
+
let localReady = false;
|
|
637
|
+
const maybeReady = () => {
|
|
638
|
+
if (relayReady && localReady) {
|
|
639
|
+
sendOk();
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
relaySocket.on('message', (msg) => {
|
|
644
|
+
if (!session.ready || !session.session) return;
|
|
645
|
+
const plaintext = nativeCrypto.parseUdpPacket(session.session, msg);
|
|
646
|
+
if (!plaintext) return;
|
|
647
|
+
localSocket.send(plaintext);
|
|
648
|
+
});
|
|
649
|
+
localSocket.on('message', (msg) => {
|
|
650
|
+
if (!session.ready || !session.session) return;
|
|
651
|
+
const packet = nativeCrypto.createUdpPacket(session.session, msg);
|
|
652
|
+
relaySocket.send(packet);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
relaySocket.on('error', (err) => {
|
|
656
|
+
logger.error(() => `Relay UDP socket error (${deviceId}): ${err}`);
|
|
657
|
+
sendError('Relay UDP error');
|
|
658
|
+
try { relaySocket.close(); } catch {}
|
|
659
|
+
try { localSocket.close(); } catch {}
|
|
660
|
+
this._cleanupNativeSession(session);
|
|
661
|
+
});
|
|
662
|
+
localSocket.on('error', (err) => {
|
|
663
|
+
logger.error(() => `Local UDP service error (${deviceId}): ${err}`);
|
|
664
|
+
sendError('Local UDP error');
|
|
665
|
+
try { relaySocket.close(); } catch {}
|
|
666
|
+
try { localSocket.close(); } catch {}
|
|
667
|
+
this._cleanupNativeSession(session);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
const relayHost = connection.getServerRelayHost();
|
|
671
|
+
relaySocket.connect(physicalPort, relayHost, () => {
|
|
672
|
+
relayReady = true;
|
|
673
|
+
maybeReady();
|
|
674
|
+
});
|
|
675
|
+
localSocket.connect(port, '127.0.0.1', () => {
|
|
676
|
+
localReady = true;
|
|
677
|
+
maybeReady();
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
session.relaySocket = relaySocket;
|
|
681
|
+
session.localSocket = localSocket;
|
|
211
682
|
}
|
|
212
683
|
|
|
213
|
-
setupLocalSocketHandlers(localSocket, ref, protocol) {
|
|
684
|
+
setupLocalSocketHandlers(localSocket, ref, protocol, rpc, connection) {
|
|
214
685
|
if (protocol === 'udp') {
|
|
215
686
|
|
|
216
687
|
} else {
|
|
217
688
|
localSocket.on('data', (data) => {
|
|
218
689
|
// When data is received from the local service, send it back via Diode
|
|
219
|
-
|
|
690
|
+
rpc.portSend(ref, data);
|
|
220
691
|
});
|
|
221
692
|
|
|
222
693
|
localSocket.on('end', () => {
|
|
223
|
-
logger.info(`Local service disconnected`);
|
|
694
|
+
logger.info(() => `Local service disconnected`);
|
|
224
695
|
// Send portclose message to Diode
|
|
225
|
-
|
|
226
|
-
|
|
696
|
+
rpc.portClose(ref);
|
|
697
|
+
connection.deleteConnection(ref);
|
|
227
698
|
});
|
|
228
699
|
|
|
229
700
|
localSocket.on('error', (err) => {
|
|
230
|
-
logger.error(`Error with local service: ${err}`);
|
|
701
|
+
logger.error(() => `Error with local service: ${err}`);
|
|
231
702
|
// Send portclose message to Diode
|
|
232
|
-
|
|
233
|
-
|
|
703
|
+
rpc.portClose(ref);
|
|
704
|
+
connection.deleteConnection(ref);
|
|
234
705
|
});
|
|
235
706
|
}
|
|
236
707
|
}
|
|
237
708
|
|
|
238
|
-
handleTCPConnection(sessionId, ref, port, deviceId) {
|
|
709
|
+
handleTCPConnection(sessionId, ref, port, deviceId, connection) {
|
|
710
|
+
const rpc = this._getRpcFor(connection);
|
|
239
711
|
// Create a TCP connection to the local service on the specified port
|
|
240
712
|
const localSocket = net.connect({ port: port }, () => {
|
|
241
|
-
|
|
713
|
+
localSocket.setNoDelay(true);
|
|
714
|
+
logger.info(() => `Connected to local TCP service on port ${port}`);
|
|
242
715
|
// Send success response
|
|
243
|
-
|
|
716
|
+
rpc.sendResponse(sessionId, ref, 'ok');
|
|
244
717
|
});
|
|
245
718
|
|
|
246
719
|
// Handle data, end, and error events
|
|
247
|
-
this.setupLocalSocketHandlers(localSocket, ref, 'tcp');
|
|
720
|
+
this.setupLocalSocketHandlers(localSocket, ref, 'tcp', rpc, connection);
|
|
248
721
|
|
|
249
722
|
// Store the local socket with the ref using connection's method
|
|
250
|
-
|
|
723
|
+
connection.addConnection(ref, { socket: localSocket, protocol: 'tcp', port, deviceId });
|
|
251
724
|
}
|
|
252
725
|
|
|
253
|
-
handleTLSConnection(sessionId, ref, port, deviceId) {
|
|
726
|
+
handleTLSConnection(sessionId, ref, port, deviceId, connection) {
|
|
727
|
+
const rpc = this._getRpcFor(connection);
|
|
254
728
|
// Create a DiodeSocket instance
|
|
255
|
-
const diodeSocket = new DiodeSocket(ref,
|
|
729
|
+
const diodeSocket = new DiodeSocket(ref, rpc);
|
|
256
730
|
|
|
257
|
-
const certPem =
|
|
731
|
+
const certPem = connection.getDeviceCertificate();
|
|
258
732
|
|
|
259
733
|
// TLS options with your server's certificate and key
|
|
260
734
|
const tlsOptions = {
|
|
@@ -272,12 +746,12 @@ class PublishPort extends EventEmitter {
|
|
|
272
746
|
isServer: true,
|
|
273
747
|
...tlsOptions,
|
|
274
748
|
});
|
|
275
|
-
|
|
749
|
+
tlsSocket.setNoDelay(true);
|
|
276
750
|
// Connect to the local service (TCP or TLS as needed)
|
|
277
751
|
const localSocket = net.connect({ port: port }, () => {
|
|
278
|
-
logger.info(`Connected to local TCP service on port ${port}`);
|
|
752
|
+
logger.info(() => `Connected to local TCP service on port ${port}`);
|
|
279
753
|
// Send success response
|
|
280
|
-
|
|
754
|
+
rpc.sendResponse(sessionId, ref, 'ok');
|
|
281
755
|
});
|
|
282
756
|
|
|
283
757
|
// Pipe data between the TLS socket and the local service
|
|
@@ -285,17 +759,17 @@ class PublishPort extends EventEmitter {
|
|
|
285
759
|
|
|
286
760
|
// Handle errors and cleanup
|
|
287
761
|
tlsSocket.on('error', (err) => {
|
|
288
|
-
logger.error(`TLS Socket error: ${err}`);
|
|
289
|
-
|
|
290
|
-
|
|
762
|
+
logger.error(() => `TLS Socket error: ${err}`);
|
|
763
|
+
rpc.portClose(ref);
|
|
764
|
+
connection.deleteConnection(ref);
|
|
291
765
|
});
|
|
292
766
|
|
|
293
767
|
tlsSocket.on('close', () => {
|
|
294
|
-
|
|
768
|
+
connection.deleteConnection(ref);
|
|
295
769
|
});
|
|
296
770
|
|
|
297
771
|
// Store the connection info using connection's method
|
|
298
|
-
|
|
772
|
+
connection.addConnection(ref, {
|
|
299
773
|
diodeSocket,
|
|
300
774
|
tlsSocket,
|
|
301
775
|
localSocket,
|
|
@@ -305,18 +779,29 @@ class PublishPort extends EventEmitter {
|
|
|
305
779
|
});
|
|
306
780
|
}
|
|
307
781
|
|
|
308
|
-
handleUDPConnection(sessionId, ref, port, deviceId) {
|
|
782
|
+
handleUDPConnection(sessionId, ref, port, deviceId, connection) {
|
|
783
|
+
const rpc = this._getRpcFor(connection);
|
|
309
784
|
// Create a UDP socket
|
|
310
785
|
const localSocket = dgram.createSocket('udp4');
|
|
311
786
|
|
|
787
|
+
// Try larger kernel buffers if available
|
|
788
|
+
localSocket.on('listening', () => {
|
|
789
|
+
try {
|
|
790
|
+
localSocket.setRecvBufferSize(1 << 20); // ~1MB
|
|
791
|
+
localSocket.setSendBufferSize(1 << 20);
|
|
792
|
+
} catch (e) {
|
|
793
|
+
logger.debug(() => `UDP buffer sizing not supported: ${e.message}`);
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
|
|
312
797
|
// Store the remote address and port from the Diode client
|
|
313
798
|
const remoteInfo = {port, address: '127.0.0.1'};
|
|
314
799
|
|
|
315
800
|
// Send success response
|
|
316
|
-
|
|
801
|
+
rpc.sendResponse(sessionId, ref, 'ok');
|
|
317
802
|
|
|
318
803
|
// Store the connection info using connection's method
|
|
319
|
-
|
|
804
|
+
connection.addConnection(ref, {
|
|
320
805
|
socket: localSocket,
|
|
321
806
|
protocol: 'udp',
|
|
322
807
|
remoteInfo,
|
|
@@ -324,51 +809,47 @@ class PublishPort extends EventEmitter {
|
|
|
324
809
|
deviceId
|
|
325
810
|
});
|
|
326
811
|
|
|
327
|
-
logger.info(`UDP connection set up on port ${port}`);
|
|
812
|
+
logger.info(() => `UDP connection set up on port ${port}`);
|
|
328
813
|
|
|
329
814
|
// Handle messages from the local UDP service
|
|
330
815
|
localSocket.on('message', (msg, rinfo) => {
|
|
331
|
-
|
|
332
|
-
const dataLength = Buffer.alloc(4);
|
|
333
|
-
dataLength.writeUInt32LE(msg.length, 0);
|
|
334
|
-
const data = Buffer.concat([dataLength, msg]);
|
|
335
|
-
// Send the data back to the Diode client via portSend
|
|
336
|
-
this.rpc.portSend(ref, data);
|
|
816
|
+
rpc.portSend(ref, msg);
|
|
337
817
|
});
|
|
338
818
|
|
|
339
819
|
localSocket.on('error', (err) => {
|
|
340
|
-
logger.error(`UDP Socket error: ${err}`);
|
|
341
|
-
|
|
342
|
-
|
|
820
|
+
logger.error(() => `UDP Socket error: ${err}`);
|
|
821
|
+
rpc.portClose(ref);
|
|
822
|
+
connection.deleteConnection(ref);
|
|
343
823
|
});
|
|
344
824
|
}
|
|
345
825
|
|
|
346
|
-
handlePortSend(sessionIdRaw, messageContent) {
|
|
826
|
+
handlePortSend(sessionIdRaw, messageContent, connection) {
|
|
827
|
+
const rpc = this._getRpcFor(connection);
|
|
347
828
|
const refRaw = messageContent[1];
|
|
348
829
|
const dataRaw = messageContent[2];
|
|
349
830
|
|
|
350
|
-
const sessionId =
|
|
351
|
-
const ref =
|
|
352
|
-
const data =
|
|
831
|
+
const sessionId = toBufferView(sessionIdRaw);
|
|
832
|
+
const ref = toBufferView(refRaw);
|
|
833
|
+
const data = toBufferView(dataRaw);//.slice(4);
|
|
353
834
|
|
|
354
|
-
const connectionInfo =
|
|
835
|
+
const connectionInfo = connection.getConnection(ref);
|
|
355
836
|
// Check if the port is still open and address is still in whitelist
|
|
356
837
|
if (connectionInfo) {
|
|
357
838
|
const { socket: localSocket, protocol, remoteInfo, port, deviceId } = connectionInfo;
|
|
358
839
|
|
|
359
840
|
if (!this.publishedPorts.has(port)) {
|
|
360
|
-
logger.warn(`Port ${port} is not published. Sending portclose.`);
|
|
361
|
-
|
|
362
|
-
|
|
841
|
+
logger.warn(() => `Port ${port} is not published. Sending portclose.`);
|
|
842
|
+
rpc.portClose(ref);
|
|
843
|
+
connection.deleteConnection(ref);
|
|
363
844
|
return;
|
|
364
845
|
}
|
|
365
846
|
|
|
366
847
|
const portConfig = this.publishedPorts.get(port);
|
|
367
848
|
if (portConfig.mode === 'private' && Array.isArray(portConfig.whitelist)) {
|
|
368
849
|
if (!portConfig.whitelist.includes(deviceId)) {
|
|
369
|
-
logger.warn(`Device ${deviceId} is not whitelisted for port ${port}. Sending portclose.`);
|
|
370
|
-
|
|
371
|
-
|
|
850
|
+
logger.warn(() => `Device ${deviceId} is not whitelisted for port ${port}. Sending portclose.`);
|
|
851
|
+
rpc.portClose(ref);
|
|
852
|
+
connection.deleteConnection(ref);
|
|
372
853
|
return;
|
|
373
854
|
}
|
|
374
855
|
}
|
|
@@ -396,24 +877,24 @@ class PublishPort extends EventEmitter {
|
|
|
396
877
|
diodeSocket.pushData(data);
|
|
397
878
|
}
|
|
398
879
|
} else {
|
|
399
|
-
const clientSocket =
|
|
880
|
+
const clientSocket = connection.getClientSocket(ref);
|
|
400
881
|
if (clientSocket) {
|
|
401
|
-
logger.debug(`No local connection found for ref: ${ref.toString('hex')}, but client socket exists`);
|
|
882
|
+
logger.debug(() => `No local connection found for ref: ${ref.toString('hex')}, but client socket exists`);
|
|
402
883
|
} else {
|
|
403
|
-
logger.warn(`No local connection found for ref ${ref.toString('hex')}. Sending portclose.`);
|
|
404
|
-
|
|
884
|
+
logger.warn(() => `No local connection found for ref ${ref.toString('hex')}. Sending portclose.`);
|
|
885
|
+
rpc.sendError(sessionId, ref, 'No local connection found');
|
|
405
886
|
}
|
|
406
887
|
}
|
|
407
888
|
}
|
|
408
889
|
|
|
409
|
-
handlePortClose(sessionIdRaw, messageContent) {
|
|
890
|
+
handlePortClose(sessionIdRaw, messageContent, connection) {
|
|
410
891
|
const refRaw = messageContent[1];
|
|
411
|
-
const sessionId =
|
|
412
|
-
const ref =
|
|
892
|
+
const sessionId = toBufferView(sessionIdRaw);
|
|
893
|
+
const ref = toBufferView(refRaw);
|
|
413
894
|
|
|
414
|
-
logger.info(`Received portclose for ref ${ref.toString('hex')}`);
|
|
895
|
+
logger.info(() => `Received portclose for ref ${ref.toString('hex')}`);
|
|
415
896
|
|
|
416
|
-
const connectionInfo =
|
|
897
|
+
const connectionInfo = connection.getConnection(ref);
|
|
417
898
|
if (connectionInfo) {
|
|
418
899
|
const { diodeSocket, tlsSocket, socket: localSocket } = connectionInfo;
|
|
419
900
|
// End all sockets
|
|
@@ -426,7 +907,7 @@ class PublishPort extends EventEmitter {
|
|
|
426
907
|
localSocket.end();
|
|
427
908
|
}
|
|
428
909
|
}
|
|
429
|
-
|
|
910
|
+
connection.deleteConnection(ref);
|
|
430
911
|
}
|
|
431
912
|
}
|
|
432
913
|
}
|