@push.rocks/smartproxy 3.37.0 → 3.37.2
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.portproxy.js +133 -246
- package/dist_ts/classes.snihandler.d.ts +45 -0
- package/dist_ts/classes.snihandler.js +274 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.portproxy.ts +154 -278
- package/ts/classes.snihandler.ts +331 -0
- package/ts/index.ts +1 -0
package/ts/classes.portproxy.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { NetworkProxy } from './classes.networkproxy.js';
|
|
3
|
+
import { SniHandler } from './classes.snihandler.js';
|
|
3
4
|
|
|
4
5
|
/** Domain configuration with per-domain allowed port ranges */
|
|
5
6
|
export interface IDomainConfig {
|
|
@@ -117,192 +118,8 @@ interface IConnectionRecord {
|
|
|
117
118
|
domainSwitches?: number; // Number of times the domain has been switched on this connection
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
* Enhanced for robustness and detailed logging.
|
|
123
|
-
* @param buffer - Buffer containing the TLS ClientHello.
|
|
124
|
-
* @param enableLogging - Whether to enable detailed logging.
|
|
125
|
-
* @returns The server name if found, otherwise undefined.
|
|
126
|
-
*/
|
|
127
|
-
function extractSNI(buffer: Buffer, enableLogging: boolean = false): string | undefined {
|
|
128
|
-
try {
|
|
129
|
-
// Check if buffer is too small for TLS
|
|
130
|
-
if (buffer.length < 5) {
|
|
131
|
-
if (enableLogging) console.log('Buffer too small for TLS header');
|
|
132
|
-
return undefined;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Check record type (has to be handshake - 22)
|
|
136
|
-
const recordType = buffer.readUInt8(0);
|
|
137
|
-
if (recordType !== 22) {
|
|
138
|
-
if (enableLogging) console.log(`Not a TLS handshake. Record type: ${recordType}`);
|
|
139
|
-
return undefined;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Check TLS version (has to be 3.1 or higher)
|
|
143
|
-
const majorVersion = buffer.readUInt8(1);
|
|
144
|
-
const minorVersion = buffer.readUInt8(2);
|
|
145
|
-
if (enableLogging) console.log(`TLS Version: ${majorVersion}.${minorVersion}`);
|
|
146
|
-
|
|
147
|
-
// Check record length
|
|
148
|
-
const recordLength = buffer.readUInt16BE(3);
|
|
149
|
-
if (buffer.length < 5 + recordLength) {
|
|
150
|
-
if (enableLogging)
|
|
151
|
-
console.log(
|
|
152
|
-
`Buffer too small for TLS record. Expected: ${5 + recordLength}, Got: ${buffer.length}`
|
|
153
|
-
);
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let offset = 5;
|
|
158
|
-
const handshakeType = buffer.readUInt8(offset);
|
|
159
|
-
if (handshakeType !== 1) {
|
|
160
|
-
if (enableLogging) console.log(`Not a ClientHello. Handshake type: ${handshakeType}`);
|
|
161
|
-
return undefined;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
offset += 4; // Skip handshake header (type + length)
|
|
165
|
-
|
|
166
|
-
// Client version
|
|
167
|
-
const clientMajorVersion = buffer.readUInt8(offset);
|
|
168
|
-
const clientMinorVersion = buffer.readUInt8(offset + 1);
|
|
169
|
-
if (enableLogging) console.log(`Client Version: ${clientMajorVersion}.${clientMinorVersion}`);
|
|
170
|
-
|
|
171
|
-
offset += 2 + 32; // Skip client version and random
|
|
172
|
-
|
|
173
|
-
// Session ID
|
|
174
|
-
const sessionIDLength = buffer.readUInt8(offset);
|
|
175
|
-
if (enableLogging) console.log(`Session ID Length: ${sessionIDLength}`);
|
|
176
|
-
offset += 1 + sessionIDLength; // Skip session ID
|
|
177
|
-
|
|
178
|
-
// Cipher suites
|
|
179
|
-
if (offset + 2 > buffer.length) {
|
|
180
|
-
if (enableLogging) console.log('Buffer too small for cipher suites length');
|
|
181
|
-
return undefined;
|
|
182
|
-
}
|
|
183
|
-
const cipherSuitesLength = buffer.readUInt16BE(offset);
|
|
184
|
-
if (enableLogging) console.log(`Cipher Suites Length: ${cipherSuitesLength}`);
|
|
185
|
-
offset += 2 + cipherSuitesLength; // Skip cipher suites
|
|
186
|
-
|
|
187
|
-
// Compression methods
|
|
188
|
-
if (offset + 1 > buffer.length) {
|
|
189
|
-
if (enableLogging) console.log('Buffer too small for compression methods length');
|
|
190
|
-
return undefined;
|
|
191
|
-
}
|
|
192
|
-
const compressionMethodsLength = buffer.readUInt8(offset);
|
|
193
|
-
if (enableLogging) console.log(`Compression Methods Length: ${compressionMethodsLength}`);
|
|
194
|
-
offset += 1 + compressionMethodsLength; // Skip compression methods
|
|
195
|
-
|
|
196
|
-
// Extensions
|
|
197
|
-
if (offset + 2 > buffer.length) {
|
|
198
|
-
if (enableLogging) console.log('Buffer too small for extensions length');
|
|
199
|
-
return undefined;
|
|
200
|
-
}
|
|
201
|
-
const extensionsLength = buffer.readUInt16BE(offset);
|
|
202
|
-
if (enableLogging) console.log(`Extensions Length: ${extensionsLength}`);
|
|
203
|
-
offset += 2;
|
|
204
|
-
const extensionsEnd = offset + extensionsLength;
|
|
205
|
-
|
|
206
|
-
if (extensionsEnd > buffer.length) {
|
|
207
|
-
if (enableLogging)
|
|
208
|
-
console.log(
|
|
209
|
-
`Buffer too small for extensions. Expected end: ${extensionsEnd}, Buffer length: ${buffer.length}`
|
|
210
|
-
);
|
|
211
|
-
return undefined;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Parse extensions
|
|
215
|
-
while (offset + 4 <= extensionsEnd) {
|
|
216
|
-
const extensionType = buffer.readUInt16BE(offset);
|
|
217
|
-
const extensionLength = buffer.readUInt16BE(offset + 2);
|
|
218
|
-
|
|
219
|
-
if (enableLogging)
|
|
220
|
-
console.log(`Extension Type: 0x${extensionType.toString(16)}, Length: ${extensionLength}`);
|
|
221
|
-
|
|
222
|
-
offset += 4;
|
|
223
|
-
|
|
224
|
-
if (extensionType === 0x0000) {
|
|
225
|
-
// SNI extension
|
|
226
|
-
if (offset + 2 > buffer.length) {
|
|
227
|
-
if (enableLogging) console.log('Buffer too small for SNI list length');
|
|
228
|
-
return undefined;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const sniListLength = buffer.readUInt16BE(offset);
|
|
232
|
-
if (enableLogging) console.log(`SNI List Length: ${sniListLength}`);
|
|
233
|
-
offset += 2;
|
|
234
|
-
const sniListEnd = offset + sniListLength;
|
|
235
|
-
|
|
236
|
-
if (sniListEnd > buffer.length) {
|
|
237
|
-
if (enableLogging)
|
|
238
|
-
console.log(
|
|
239
|
-
`Buffer too small for SNI list. Expected end: ${sniListEnd}, Buffer length: ${buffer.length}`
|
|
240
|
-
);
|
|
241
|
-
return undefined;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
while (offset + 3 < sniListEnd) {
|
|
245
|
-
const nameType = buffer.readUInt8(offset++);
|
|
246
|
-
const nameLen = buffer.readUInt16BE(offset);
|
|
247
|
-
offset += 2;
|
|
248
|
-
|
|
249
|
-
if (enableLogging) console.log(`Name Type: ${nameType}, Name Length: ${nameLen}`);
|
|
250
|
-
|
|
251
|
-
if (nameType === 0) {
|
|
252
|
-
// host_name
|
|
253
|
-
if (offset + nameLen > buffer.length) {
|
|
254
|
-
if (enableLogging)
|
|
255
|
-
console.log(
|
|
256
|
-
`Buffer too small for hostname. Expected: ${offset + nameLen}, Got: ${
|
|
257
|
-
buffer.length
|
|
258
|
-
}`
|
|
259
|
-
);
|
|
260
|
-
return undefined;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const serverName = buffer.toString('utf8', offset, offset + nameLen);
|
|
264
|
-
if (enableLogging) console.log(`Extracted SNI: ${serverName}`);
|
|
265
|
-
return serverName;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
offset += nameLen;
|
|
269
|
-
}
|
|
270
|
-
break;
|
|
271
|
-
} else {
|
|
272
|
-
offset += extensionLength;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (enableLogging) console.log('No SNI extension found');
|
|
277
|
-
return undefined;
|
|
278
|
-
} catch (err) {
|
|
279
|
-
console.log(`Error extracting SNI: ${err}`);
|
|
280
|
-
return undefined;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Checks if a TLS record is a proper ClientHello message (more accurate than just checking record type)
|
|
286
|
-
* @param buffer - Buffer containing the TLS record
|
|
287
|
-
* @returns true if the buffer contains a proper ClientHello message
|
|
288
|
-
*/
|
|
289
|
-
function isClientHello(buffer: Buffer): boolean {
|
|
290
|
-
try {
|
|
291
|
-
if (buffer.length < 9) return false; // Too small for a proper ClientHello
|
|
292
|
-
|
|
293
|
-
// Check record type (has to be handshake - 22)
|
|
294
|
-
if (buffer.readUInt8(0) !== 22) return false;
|
|
295
|
-
|
|
296
|
-
// After the TLS record header (5 bytes), check the handshake type (1 for ClientHello)
|
|
297
|
-
if (buffer.readUInt8(5) !== 1) return false;
|
|
298
|
-
|
|
299
|
-
// Basic checks passed, this appears to be a ClientHello
|
|
300
|
-
return true;
|
|
301
|
-
} catch (err) {
|
|
302
|
-
console.log(`Error checking for ClientHello: ${err}`);
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
121
|
+
// SNI functions are now imported from SniHandler class
|
|
122
|
+
// No need for wrapper functions
|
|
306
123
|
|
|
307
124
|
// Helper: Check if a port falls within any of the given port ranges
|
|
308
125
|
const isPortInRanges = (port: number, ranges: Array<{ from: number; to: number }>): boolean => {
|
|
@@ -346,10 +163,7 @@ const generateConnectionId = (): string => {
|
|
|
346
163
|
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
347
164
|
};
|
|
348
165
|
|
|
349
|
-
//
|
|
350
|
-
const isTlsHandshake = (buffer: Buffer): boolean => {
|
|
351
|
-
return buffer.length > 0 && buffer[0] === 22; // ContentType.handshake
|
|
352
|
-
};
|
|
166
|
+
// SNI functions are now imported from SniHandler class
|
|
353
167
|
|
|
354
168
|
// Helper: Ensure timeout values don't exceed Node.js max safe integer
|
|
355
169
|
const ensureSafeTimeout = (timeout: number): number => {
|
|
@@ -752,44 +566,104 @@ export class PortProxy {
|
|
|
752
566
|
connectionOptions.localAddress = record.remoteIP.replace('::ffff:', '');
|
|
753
567
|
}
|
|
754
568
|
|
|
569
|
+
// Create a safe queue for incoming data using a Buffer array
|
|
570
|
+
// We'll use this to ensure we don't lose data during handler transitions
|
|
571
|
+
const dataQueue: Buffer[] = [];
|
|
572
|
+
let queueSize = 0;
|
|
573
|
+
let processingQueue = false;
|
|
574
|
+
let drainPending = false;
|
|
575
|
+
|
|
576
|
+
// Flag to track if we've switched to the final piping mechanism
|
|
577
|
+
// Once this is true, we no longer buffer data in dataQueue
|
|
578
|
+
let pipingEstablished = false;
|
|
579
|
+
|
|
755
580
|
// Pause the incoming socket to prevent buffer overflows
|
|
581
|
+
// This ensures we control the flow of data until piping is set up
|
|
756
582
|
socket.pause();
|
|
757
583
|
|
|
758
|
-
//
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
584
|
+
// Function to safely process the data queue without losing events
|
|
585
|
+
const processDataQueue = () => {
|
|
586
|
+
if (processingQueue || dataQueue.length === 0 || pipingEstablished) return;
|
|
587
|
+
|
|
588
|
+
processingQueue = true;
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
// Process all queued chunks with the current active handler
|
|
592
|
+
while (dataQueue.length > 0) {
|
|
593
|
+
const chunk = dataQueue.shift()!;
|
|
594
|
+
queueSize -= chunk.length;
|
|
595
|
+
|
|
596
|
+
// Once piping is established, we shouldn't get here,
|
|
597
|
+
// but just in case, pass to the outgoing socket directly
|
|
598
|
+
if (pipingEstablished && record.outgoing) {
|
|
599
|
+
record.outgoing.write(chunk);
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Track bytes received
|
|
604
|
+
record.bytesReceived += chunk.length;
|
|
762
605
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
606
|
+
// Check for TLS handshake
|
|
607
|
+
if (!record.isTLS && SniHandler.isTlsHandshake(chunk)) {
|
|
608
|
+
record.isTLS = true;
|
|
766
609
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
610
|
+
if (this.settings.enableTlsDebugLogging) {
|
|
611
|
+
console.log(
|
|
612
|
+
`[${connectionId}] TLS handshake detected in tempDataHandler, ${chunk.length} bytes`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
773
616
|
|
|
774
|
-
|
|
775
|
-
|
|
617
|
+
// Check if adding this chunk would exceed the buffer limit
|
|
618
|
+
const newSize = record.pendingDataSize + chunk.length;
|
|
776
619
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
620
|
+
if (this.settings.maxPendingDataSize && newSize > this.settings.maxPendingDataSize) {
|
|
621
|
+
console.log(
|
|
622
|
+
`[${connectionId}] Buffer limit exceeded for connection from ${record.remoteIP}: ${newSize} bytes > ${this.settings.maxPendingDataSize} bytes`
|
|
623
|
+
);
|
|
624
|
+
socket.end(); // Gracefully close the socket
|
|
625
|
+
this.initiateCleanupOnce(record, 'buffer_limit_exceeded');
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Buffer the chunk and update the size counter
|
|
630
|
+
record.pendingData.push(Buffer.from(chunk));
|
|
631
|
+
record.pendingDataSize = newSize;
|
|
632
|
+
this.updateActivity(record);
|
|
633
|
+
}
|
|
634
|
+
} finally {
|
|
635
|
+
processingQueue = false;
|
|
636
|
+
|
|
637
|
+
// If there's a pending drain and we've processed everything,
|
|
638
|
+
// signal we're ready for more data if we haven't established piping yet
|
|
639
|
+
if (drainPending && dataQueue.length === 0 && !pipingEstablished) {
|
|
640
|
+
drainPending = false;
|
|
641
|
+
socket.resume();
|
|
642
|
+
}
|
|
783
643
|
}
|
|
644
|
+
};
|
|
784
645
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
646
|
+
// Unified data handler that safely queues incoming data
|
|
647
|
+
const safeDataHandler = (chunk: Buffer) => {
|
|
648
|
+
// If piping is already established, just let the pipe handle it
|
|
649
|
+
if (pipingEstablished) return;
|
|
650
|
+
|
|
651
|
+
// Add to our queue for orderly processing
|
|
652
|
+
dataQueue.push(Buffer.from(chunk)); // Make a copy to be safe
|
|
653
|
+
queueSize += chunk.length;
|
|
654
|
+
|
|
655
|
+
// If queue is getting large, pause socket until we catch up
|
|
656
|
+
if (this.settings.maxPendingDataSize && queueSize > this.settings.maxPendingDataSize * 0.8) {
|
|
657
|
+
socket.pause();
|
|
658
|
+
drainPending = true;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Process the queue
|
|
662
|
+
processDataQueue();
|
|
789
663
|
};
|
|
790
664
|
|
|
791
|
-
// Add
|
|
792
|
-
socket.on('data',
|
|
665
|
+
// Add our safe data handler
|
|
666
|
+
socket.on('data', safeDataHandler);
|
|
793
667
|
|
|
794
668
|
// Add initial chunk to pending data if present
|
|
795
669
|
if (initialChunk) {
|
|
@@ -962,56 +836,32 @@ export class PortProxy {
|
|
|
962
836
|
// Add the normal error handler for established connections
|
|
963
837
|
targetSocket.on('error', this.handleError('outgoing', record));
|
|
964
838
|
|
|
965
|
-
//
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
//
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
return this.initiateCleanupOnce(record, 'write_error');
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
// Now set up piping for future data and resume the socket
|
|
978
|
-
socket.pipe(targetSocket);
|
|
979
|
-
targetSocket.pipe(socket);
|
|
980
|
-
socket.resume(); // Resume the socket after piping is established
|
|
981
|
-
|
|
982
|
-
if (this.settings.enableDetailedLogging) {
|
|
983
|
-
console.log(
|
|
984
|
-
`[${connectionId}] Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
985
|
-
`${
|
|
986
|
-
serverName
|
|
987
|
-
? ` (SNI: ${serverName})`
|
|
988
|
-
: domainConfig
|
|
989
|
-
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
990
|
-
: ''
|
|
991
|
-
}` +
|
|
992
|
-
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
993
|
-
record.hasKeepAlive ? 'Yes' : 'No'
|
|
994
|
-
}`
|
|
995
|
-
);
|
|
996
|
-
} else {
|
|
997
|
-
console.log(
|
|
998
|
-
`Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
999
|
-
`${
|
|
1000
|
-
serverName
|
|
1001
|
-
? ` (SNI: ${serverName})`
|
|
1002
|
-
: domainConfig
|
|
1003
|
-
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
1004
|
-
: ''
|
|
1005
|
-
}`
|
|
1006
|
-
);
|
|
1007
|
-
}
|
|
1008
|
-
});
|
|
1009
|
-
} else {
|
|
1010
|
-
// No pending data, so just set up piping
|
|
839
|
+
// Process any remaining data in the queue before switching to piping
|
|
840
|
+
processDataQueue();
|
|
841
|
+
|
|
842
|
+
// Setup function to establish piping - we'll use this after flushing data
|
|
843
|
+
const setupPiping = () => {
|
|
844
|
+
// Mark that we're switching to piping mode
|
|
845
|
+
pipingEstablished = true;
|
|
846
|
+
|
|
847
|
+
// Setup piping in both directions
|
|
1011
848
|
socket.pipe(targetSocket);
|
|
1012
849
|
targetSocket.pipe(socket);
|
|
1013
|
-
|
|
1014
|
-
|
|
850
|
+
|
|
851
|
+
// Resume the socket to ensure data flows
|
|
852
|
+
socket.resume();
|
|
853
|
+
|
|
854
|
+
// Process any data that might be queued in the interim
|
|
855
|
+
if (dataQueue.length > 0) {
|
|
856
|
+
// Write any remaining queued data directly to the target socket
|
|
857
|
+
for (const chunk of dataQueue) {
|
|
858
|
+
targetSocket.write(chunk);
|
|
859
|
+
}
|
|
860
|
+
// Clear the queue
|
|
861
|
+
dataQueue.length = 0;
|
|
862
|
+
queueSize = 0;
|
|
863
|
+
}
|
|
864
|
+
|
|
1015
865
|
if (this.settings.enableDetailedLogging) {
|
|
1016
866
|
console.log(
|
|
1017
867
|
`[${connectionId}] Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
@@ -1038,6 +888,23 @@ export class PortProxy {
|
|
|
1038
888
|
}`
|
|
1039
889
|
);
|
|
1040
890
|
}
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
// Flush all pending data to target
|
|
894
|
+
if (record.pendingData.length > 0) {
|
|
895
|
+
const combinedData = Buffer.concat(record.pendingData);
|
|
896
|
+
targetSocket.write(combinedData, (err) => {
|
|
897
|
+
if (err) {
|
|
898
|
+
console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`);
|
|
899
|
+
return this.initiateCleanupOnce(record, 'write_error');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Establish piping now that we've flushed the buffered data
|
|
903
|
+
setupPiping();
|
|
904
|
+
});
|
|
905
|
+
} else {
|
|
906
|
+
// No pending data, just establish piping immediately
|
|
907
|
+
setupPiping();
|
|
1041
908
|
}
|
|
1042
909
|
|
|
1043
910
|
// Clear the buffer now that we've processed it
|
|
@@ -1045,14 +912,15 @@ export class PortProxy {
|
|
|
1045
912
|
record.pendingDataSize = 0;
|
|
1046
913
|
|
|
1047
914
|
// Add the renegotiation handler for SNI validation with strict domain enforcement
|
|
915
|
+
// This will be called after we've established piping
|
|
1048
916
|
if (serverName) {
|
|
1049
917
|
// Define a handler for checking renegotiation with improved detection
|
|
1050
918
|
const renegotiationHandler = (renegChunk: Buffer) => {
|
|
1051
919
|
// Only process if this looks like a TLS ClientHello
|
|
1052
|
-
if (isClientHello(renegChunk)) {
|
|
920
|
+
if (SniHandler.isClientHello(renegChunk)) {
|
|
1053
921
|
try {
|
|
1054
922
|
// Extract SNI from ClientHello
|
|
1055
|
-
const newSNI =
|
|
923
|
+
const newSNI = SniHandler.extractSNIWithResumptionSupport(renegChunk, this.settings.enableTlsDebugLogging);
|
|
1056
924
|
|
|
1057
925
|
// Skip if no SNI was found
|
|
1058
926
|
if (!newSNI) return;
|
|
@@ -1081,8 +949,13 @@ export class PortProxy {
|
|
|
1081
949
|
// Store the handler in the connection record so we can remove it during cleanup
|
|
1082
950
|
record.renegotiationHandler = renegotiationHandler;
|
|
1083
951
|
|
|
1084
|
-
//
|
|
952
|
+
// The renegotiation handler is added when piping is established
|
|
953
|
+
// Making it part of setupPiping ensures proper sequencing of event handlers
|
|
1085
954
|
socket.on('data', renegotiationHandler);
|
|
955
|
+
|
|
956
|
+
if (this.settings.enableDetailedLogging) {
|
|
957
|
+
console.log(`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`);
|
|
958
|
+
}
|
|
1086
959
|
}
|
|
1087
960
|
|
|
1088
961
|
// Set connection timeout with simpler logic
|
|
@@ -1242,13 +1115,16 @@ export class PortProxy {
|
|
|
1242
1115
|
const bytesReceived = record.bytesReceived;
|
|
1243
1116
|
const bytesSent = record.bytesSent;
|
|
1244
1117
|
|
|
1245
|
-
// Remove
|
|
1246
|
-
if (record.
|
|
1118
|
+
// Remove all data handlers (both standard and renegotiation) to make sure we clean up properly
|
|
1119
|
+
if (record.incoming) {
|
|
1247
1120
|
try {
|
|
1248
|
-
|
|
1121
|
+
// Remove our safe data handler
|
|
1122
|
+
record.incoming.removeAllListeners('data');
|
|
1123
|
+
|
|
1124
|
+
// Reset the handler references
|
|
1249
1125
|
record.renegotiationHandler = undefined;
|
|
1250
1126
|
} catch (err) {
|
|
1251
|
-
console.log(`[${record.id}] Error removing
|
|
1127
|
+
console.log(`[${record.id}] Error removing data handlers: ${err}`);
|
|
1252
1128
|
}
|
|
1253
1129
|
}
|
|
1254
1130
|
|
|
@@ -1644,7 +1520,7 @@ export class PortProxy {
|
|
|
1644
1520
|
connectionRecord.hasReceivedInitialData = true;
|
|
1645
1521
|
|
|
1646
1522
|
// Check if this looks like a TLS handshake
|
|
1647
|
-
if (isTlsHandshake(chunk)) {
|
|
1523
|
+
if (SniHandler.isTlsHandshake(chunk)) {
|
|
1648
1524
|
connectionRecord.isTLS = true;
|
|
1649
1525
|
|
|
1650
1526
|
// Forward directly to NetworkProxy without SNI processing
|
|
@@ -1706,7 +1582,7 @@ export class PortProxy {
|
|
|
1706
1582
|
this.updateActivity(connectionRecord);
|
|
1707
1583
|
|
|
1708
1584
|
// Check for TLS handshake if this is the first chunk
|
|
1709
|
-
if (!connectionRecord.isTLS && isTlsHandshake(chunk)) {
|
|
1585
|
+
if (!connectionRecord.isTLS && SniHandler.isTlsHandshake(chunk)) {
|
|
1710
1586
|
connectionRecord.isTLS = true;
|
|
1711
1587
|
|
|
1712
1588
|
if (this.settings.enableTlsDebugLogging) {
|
|
@@ -1714,7 +1590,7 @@ export class PortProxy {
|
|
|
1714
1590
|
`[${connectionId}] TLS handshake detected from ${remoteIP}, ${chunk.length} bytes`
|
|
1715
1591
|
);
|
|
1716
1592
|
// Try to extract SNI and log detailed debug info
|
|
1717
|
-
|
|
1593
|
+
SniHandler.extractSNIWithResumptionSupport(chunk, true);
|
|
1718
1594
|
}
|
|
1719
1595
|
}
|
|
1720
1596
|
});
|
|
@@ -1743,7 +1619,7 @@ export class PortProxy {
|
|
|
1743
1619
|
connectionRecord.hasReceivedInitialData = true;
|
|
1744
1620
|
|
|
1745
1621
|
// Check if this looks like a TLS handshake
|
|
1746
|
-
const isTlsHandshakeDetected = initialChunk && isTlsHandshake(initialChunk);
|
|
1622
|
+
const isTlsHandshakeDetected = initialChunk && SniHandler.isTlsHandshake(initialChunk);
|
|
1747
1623
|
if (isTlsHandshakeDetected) {
|
|
1748
1624
|
connectionRecord.isTLS = true;
|
|
1749
1625
|
|
|
@@ -1912,7 +1788,7 @@ export class PortProxy {
|
|
|
1912
1788
|
// Try to extract SNI
|
|
1913
1789
|
let serverName = '';
|
|
1914
1790
|
|
|
1915
|
-
if (isTlsHandshake(chunk)) {
|
|
1791
|
+
if (SniHandler.isTlsHandshake(chunk)) {
|
|
1916
1792
|
connectionRecord.isTLS = true;
|
|
1917
1793
|
|
|
1918
1794
|
if (this.settings.enableTlsDebugLogging) {
|
|
@@ -1921,7 +1797,7 @@ export class PortProxy {
|
|
|
1921
1797
|
);
|
|
1922
1798
|
}
|
|
1923
1799
|
|
|
1924
|
-
serverName =
|
|
1800
|
+
serverName = SniHandler.extractSNIWithResumptionSupport(chunk, this.settings.enableTlsDebugLogging) || '';
|
|
1925
1801
|
}
|
|
1926
1802
|
|
|
1927
1803
|
// Lock the connection to the negotiated SNI.
|