@push.rocks/smartproxy 3.37.1 → 3.37.3
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 +121 -63
- package/dist_ts/classes.snihandler.d.ts +17 -2
- package/dist_ts/classes.snihandler.js +176 -8
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.portproxy.ts +142 -80
- package/ts/classes.snihandler.ts +195 -7
|
@@ -10,6 +10,8 @@ export class SniHandler {
|
|
|
10
10
|
static { this.TLS_SNI_EXTENSION_TYPE = 0x0000; }
|
|
11
11
|
static { this.TLS_SESSION_TICKET_EXTENSION_TYPE = 0x0023; }
|
|
12
12
|
static { this.TLS_SNI_HOST_NAME_TYPE = 0; }
|
|
13
|
+
static { this.TLS_PSK_EXTENSION_TYPE = 0x0029; } // Pre-Shared Key extension type for TLS 1.3
|
|
14
|
+
static { this.TLS_PSK_KE_MODES_EXTENSION_TYPE = 0x002D; } // PSK Key Exchange Modes
|
|
13
15
|
/**
|
|
14
16
|
* Checks if a buffer contains a TLS handshake message (record type 22)
|
|
15
17
|
* @param buffer - The buffer to check
|
|
@@ -144,6 +146,7 @@ export class SniHandler {
|
|
|
144
146
|
}
|
|
145
147
|
// Track if we found session tickets (for improved resumption handling)
|
|
146
148
|
let hasSessionTicket = false;
|
|
149
|
+
let hasPskExtension = false;
|
|
147
150
|
// Iterate through extensions
|
|
148
151
|
while (pos + 4 <= extensionsEnd) {
|
|
149
152
|
// Parse extension type (2 bytes, big-endian)
|
|
@@ -225,14 +228,21 @@ export class SniHandler {
|
|
|
225
228
|
hasSessionTicket = true;
|
|
226
229
|
pos += extensionLength; // Skip this extension
|
|
227
230
|
}
|
|
231
|
+
else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
|
|
232
|
+
// TLS 1.3 PSK extension - mark for resumption support
|
|
233
|
+
log('Found PSK extension (TLS 1.3 resumption indicator)');
|
|
234
|
+
hasPskExtension = true;
|
|
235
|
+
// We'll skip the extension here and process it separately if needed
|
|
236
|
+
pos += extensionLength;
|
|
237
|
+
}
|
|
228
238
|
else {
|
|
229
239
|
// Skip this extension
|
|
230
240
|
pos += extensionLength;
|
|
231
241
|
}
|
|
232
242
|
}
|
|
233
|
-
// Log if we found
|
|
234
|
-
if (hasSessionTicket) {
|
|
235
|
-
log('Session
|
|
243
|
+
// Log if we found session resumption indicators but no SNI
|
|
244
|
+
if (hasSessionTicket || hasPskExtension) {
|
|
245
|
+
log('Session resumption indicators present but no SNI found');
|
|
236
246
|
}
|
|
237
247
|
log('No SNI extension found in ClientHello');
|
|
238
248
|
return undefined;
|
|
@@ -242,12 +252,157 @@ export class SniHandler {
|
|
|
242
252
|
return undefined;
|
|
243
253
|
}
|
|
244
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Attempts to extract SNI from the PSK extension in a TLS 1.3 ClientHello.
|
|
257
|
+
*
|
|
258
|
+
* In TLS 1.3, when a client attempts to resume a session, it may include
|
|
259
|
+
* the server name in the PSK identity hint rather than in the SNI extension.
|
|
260
|
+
*
|
|
261
|
+
* @param buffer - The buffer containing the TLS ClientHello message
|
|
262
|
+
* @param enableLogging - Whether to enable detailed debug logging
|
|
263
|
+
* @returns The extracted server name or undefined if not found
|
|
264
|
+
*/
|
|
265
|
+
static extractSNIFromPSKExtension(buffer, enableLogging = false) {
|
|
266
|
+
const log = (message) => {
|
|
267
|
+
if (enableLogging) {
|
|
268
|
+
console.log(`[PSK-SNI Extraction] ${message}`);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
try {
|
|
272
|
+
// Ensure this is a ClientHello
|
|
273
|
+
if (!this.isClientHello(buffer)) {
|
|
274
|
+
log('Not a ClientHello message');
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
// Find the start position of extensions
|
|
278
|
+
let pos = 5; // Start after record header
|
|
279
|
+
// Skip handshake type (1 byte)
|
|
280
|
+
pos += 1;
|
|
281
|
+
// Skip handshake length (3 bytes)
|
|
282
|
+
pos += 3;
|
|
283
|
+
// Skip client version (2 bytes)
|
|
284
|
+
pos += 2;
|
|
285
|
+
// Skip client random (32 bytes)
|
|
286
|
+
pos += 32;
|
|
287
|
+
// Skip session ID
|
|
288
|
+
if (pos + 1 > buffer.length)
|
|
289
|
+
return undefined;
|
|
290
|
+
const sessionIdLength = buffer[pos];
|
|
291
|
+
pos += 1 + sessionIdLength;
|
|
292
|
+
// Skip cipher suites
|
|
293
|
+
if (pos + 2 > buffer.length)
|
|
294
|
+
return undefined;
|
|
295
|
+
const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
296
|
+
pos += 2 + cipherSuitesLength;
|
|
297
|
+
// Skip compression methods
|
|
298
|
+
if (pos + 1 > buffer.length)
|
|
299
|
+
return undefined;
|
|
300
|
+
const compressionMethodsLength = buffer[pos];
|
|
301
|
+
pos += 1 + compressionMethodsLength;
|
|
302
|
+
// Check if we have extensions
|
|
303
|
+
if (pos + 2 > buffer.length) {
|
|
304
|
+
log('No extensions present');
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
// Get extensions length
|
|
308
|
+
const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
309
|
+
pos += 2;
|
|
310
|
+
// Extensions end position
|
|
311
|
+
const extensionsEnd = pos + extensionsLength;
|
|
312
|
+
if (extensionsEnd > buffer.length)
|
|
313
|
+
return undefined;
|
|
314
|
+
// Look for PSK extension
|
|
315
|
+
while (pos + 4 <= extensionsEnd) {
|
|
316
|
+
const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
|
|
317
|
+
pos += 2;
|
|
318
|
+
const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
319
|
+
pos += 2;
|
|
320
|
+
if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
|
|
321
|
+
log('Found PSK extension');
|
|
322
|
+
// PSK extension structure:
|
|
323
|
+
// 2 bytes: identities list length
|
|
324
|
+
if (pos + 2 > extensionsEnd)
|
|
325
|
+
break;
|
|
326
|
+
const identitiesLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
327
|
+
pos += 2;
|
|
328
|
+
// End of identities list
|
|
329
|
+
const identitiesEnd = pos + identitiesLength;
|
|
330
|
+
if (identitiesEnd > extensionsEnd)
|
|
331
|
+
break;
|
|
332
|
+
// Process each PSK identity
|
|
333
|
+
while (pos + 2 <= identitiesEnd) {
|
|
334
|
+
// Identity length (2 bytes)
|
|
335
|
+
if (pos + 2 > identitiesEnd)
|
|
336
|
+
break;
|
|
337
|
+
const identityLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
338
|
+
pos += 2;
|
|
339
|
+
if (pos + identityLength > identitiesEnd)
|
|
340
|
+
break;
|
|
341
|
+
// Try to extract hostname from identity
|
|
342
|
+
// Chrome often embeds the hostname in the PSK identity
|
|
343
|
+
// This is a heuristic as there's no standard format
|
|
344
|
+
if (identityLength > 0) {
|
|
345
|
+
const identity = buffer.slice(pos, pos + identityLength);
|
|
346
|
+
// Skip identity bytes
|
|
347
|
+
pos += identityLength;
|
|
348
|
+
// Skip obfuscated ticket age (4 bytes)
|
|
349
|
+
pos += 4;
|
|
350
|
+
// Try to parse the identity as UTF-8
|
|
351
|
+
try {
|
|
352
|
+
const identityStr = identity.toString('utf8');
|
|
353
|
+
log(`PSK identity: ${identityStr}`);
|
|
354
|
+
// Check if the identity contains hostname hints
|
|
355
|
+
// Chrome often embeds the hostname in a known format
|
|
356
|
+
// Try to extract using common patterns
|
|
357
|
+
// Pattern 1: Look for domain name pattern
|
|
358
|
+
const domainPattern = /([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?/i;
|
|
359
|
+
const domainMatch = identityStr.match(domainPattern);
|
|
360
|
+
if (domainMatch && domainMatch[0]) {
|
|
361
|
+
log(`Found domain in PSK identity: ${domainMatch[0]}`);
|
|
362
|
+
return domainMatch[0];
|
|
363
|
+
}
|
|
364
|
+
// Pattern 2: Chrome sometimes uses a specific format with delimiters
|
|
365
|
+
// This is a heuristic approach since the format isn't standardized
|
|
366
|
+
const parts = identityStr.split('|');
|
|
367
|
+
if (parts.length > 1) {
|
|
368
|
+
for (const part of parts) {
|
|
369
|
+
if (part.includes('.') && !part.includes('/')) {
|
|
370
|
+
const possibleDomain = part.trim();
|
|
371
|
+
if (/^[a-z0-9.-]+$/i.test(possibleDomain)) {
|
|
372
|
+
log(`Found possible domain in PSK delimiter format: ${possibleDomain}`);
|
|
373
|
+
return possibleDomain;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
catch (e) {
|
|
380
|
+
log('Failed to parse PSK identity as UTF-8');
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
// Skip this extension
|
|
387
|
+
pos += extensionLength;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
log('No hostname found in PSK extension');
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
log(`Error parsing PSK: ${error instanceof Error ? error.message : String(error)}`);
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
245
398
|
/**
|
|
246
399
|
* Attempts to extract SNI from an initial ClientHello packet and handles
|
|
247
400
|
* session resumption edge cases more robustly than the standard extraction.
|
|
248
401
|
*
|
|
249
|
-
* This method
|
|
250
|
-
*
|
|
402
|
+
* This method handles:
|
|
403
|
+
* 1. Standard SNI extraction
|
|
404
|
+
* 2. TLS 1.3 PSK-based resumption (Chrome, Firefox, etc.)
|
|
405
|
+
* 3. Session ticket-based resumption
|
|
251
406
|
*
|
|
252
407
|
* @param buffer - The buffer containing the TLS ClientHello message
|
|
253
408
|
* @param enableLogging - Whether to enable detailed debug logging
|
|
@@ -257,6 +412,9 @@ export class SniHandler {
|
|
|
257
412
|
// First try the standard SNI extraction
|
|
258
413
|
const standardSni = this.extractSNI(buffer, enableLogging);
|
|
259
414
|
if (standardSni) {
|
|
415
|
+
if (enableLogging) {
|
|
416
|
+
console.log(`[SNI Extraction] Found standard SNI: ${standardSni}`);
|
|
417
|
+
}
|
|
260
418
|
return standardSni;
|
|
261
419
|
}
|
|
262
420
|
// If standard extraction failed and we have a valid ClientHello,
|
|
@@ -265,10 +423,20 @@ export class SniHandler {
|
|
|
265
423
|
if (enableLogging) {
|
|
266
424
|
console.log('[SNI Extraction] Detected ClientHello without standard SNI, possible session resumption');
|
|
267
425
|
}
|
|
268
|
-
//
|
|
269
|
-
|
|
426
|
+
// Try to extract from PSK extension (TLS 1.3 resumption)
|
|
427
|
+
const pskSni = this.extractSNIFromPSKExtension(buffer, enableLogging);
|
|
428
|
+
if (pskSni) {
|
|
429
|
+
if (enableLogging) {
|
|
430
|
+
console.log(`[SNI Extraction] Extracted SNI from PSK extension: ${pskSni}`);
|
|
431
|
+
}
|
|
432
|
+
return pskSni;
|
|
433
|
+
}
|
|
434
|
+
// Could add more browser-specific heuristics here if needed
|
|
435
|
+
if (enableLogging) {
|
|
436
|
+
console.log('[SNI Extraction] Failed to extract SNI from resumption mechanisms');
|
|
437
|
+
}
|
|
270
438
|
}
|
|
271
439
|
return undefined;
|
|
272
440
|
}
|
|
273
441
|
}
|
|
274
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zbmloYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5zbmloYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFFaEM7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLFVBQVU7SUFDckIsaUNBQWlDO2FBQ1QsOEJBQXlCLEdBQUcsRUFBRSxDQUFDO2FBQy9CLG9DQUErQixHQUFHLENBQUMsQ0FBQzthQUNwQywyQkFBc0IsR0FBRyxNQUFNLENBQUM7YUFDaEMsc0NBQWlDLEdBQUcsTUFBTSxDQUFDO2FBQzNDLDJCQUFzQixHQUFHLENBQUMsQ0FBQztJQUVuRDs7OztPQUlHO0lBQ0ksTUFBTSxDQUFDLGNBQWMsQ0FBQyxNQUFjO1FBQ3pDLE9BQU8sTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyx5QkFBeUIsQ0FBQztJQUMzRSxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLE1BQU0sQ0FBQyxhQUFhLENBQUMsTUFBYztRQUN4QyxrRUFBa0U7UUFDbEUsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ3RCLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztRQUVELHdEQUF3RDtRQUN4RCxJQUFJLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMseUJBQXlCLEVBQUUsQ0FBQztZQUNqRCxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCwrREFBK0Q7UUFDL0Qsd0RBQXdEO1FBQ3hELE9BQU8sTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQywrQkFBK0IsQ0FBQztJQUM1RCxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNJLE1BQU0sQ0FBQyxVQUFVLENBQUMsTUFBYyxFQUFFLGdCQUF5QixLQUFLO1FBQ3JFLGlCQUFp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
|
|
442
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zbmloYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5zbmloYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFFaEM7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLFVBQVU7SUFDckIsaUNBQWlDO2FBQ1QsOEJBQXlCLEdBQUcsRUFBRSxDQUFDO2FBQy9CLG9DQUErQixHQUFHLENBQUMsQ0FBQzthQUNwQywyQkFBc0IsR0FBRyxNQUFNLENBQUM7YUFDaEMsc0NBQWlDLEdBQUcsTUFBTSxDQUFDO2FBQzNDLDJCQUFzQixHQUFHLENBQUMsQ0FBQzthQUMzQiwyQkFBc0IsR0FBRyxNQUFNLENBQUMsR0FBQyw0Q0FBNEM7YUFDN0Usb0NBQStCLEdBQUcsTUFBTSxDQUFDLEdBQUMseUJBQXlCO0lBRTNGOzs7O09BSUc7SUFDSSxNQUFNLENBQUMsY0FBYyxDQUFDLE1BQWM7UUFDekMsT0FBTyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsSUFBSSxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLHlCQUF5QixDQUFDO0lBQzNFLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksTUFBTSxDQUFDLGFBQWEsQ0FBQyxNQUFjO1FBQ3hDLGtFQUFrRTtRQUNsRSxJQUFJLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDdEIsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBRUQsd0RBQXdEO1FBQ3hELElBQUksTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyx5QkFBeUIsRUFBRSxDQUFDO1lBQ2pELE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztRQUVELCtEQUErRDtRQUMvRCx3REFBd0Q7UUFDeEQsT0FBTyxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLCtCQUErQixDQUFDO0lBQzVE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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "3.37.
|
|
3
|
+
"version": "3.37.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '3.37.
|
|
6
|
+
version: '3.37.3',
|
|
7
7
|
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
|
|
8
8
|
}
|
package/ts/classes.portproxy.ts
CHANGED
|
@@ -566,44 +566,104 @@ export class PortProxy {
|
|
|
566
566
|
connectionOptions.localAddress = record.remoteIP.replace('::ffff:', '');
|
|
567
567
|
}
|
|
568
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
|
+
|
|
569
580
|
// Pause the incoming socket to prevent buffer overflows
|
|
581
|
+
// This ensures we control the flow of data until piping is set up
|
|
570
582
|
socket.pause();
|
|
571
583
|
|
|
572
|
-
//
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
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;
|
|
576
605
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
606
|
+
// Check for TLS handshake
|
|
607
|
+
if (!record.isTLS && SniHandler.isTlsHandshake(chunk)) {
|
|
608
|
+
record.isTLS = true;
|
|
580
609
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
610
|
+
if (this.settings.enableTlsDebugLogging) {
|
|
611
|
+
console.log(
|
|
612
|
+
`[${connectionId}] TLS handshake detected in tempDataHandler, ${chunk.length} bytes`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
587
616
|
|
|
588
|
-
|
|
589
|
-
|
|
617
|
+
// Check if adding this chunk would exceed the buffer limit
|
|
618
|
+
const newSize = record.pendingDataSize + chunk.length;
|
|
590
619
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
+
}
|
|
597
643
|
}
|
|
644
|
+
};
|
|
598
645
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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();
|
|
603
663
|
};
|
|
604
664
|
|
|
605
|
-
// Add
|
|
606
|
-
socket.on('data',
|
|
665
|
+
// Add our safe data handler
|
|
666
|
+
socket.on('data', safeDataHandler);
|
|
607
667
|
|
|
608
668
|
// Add initial chunk to pending data if present
|
|
609
669
|
if (initialChunk) {
|
|
@@ -776,56 +836,32 @@ export class PortProxy {
|
|
|
776
836
|
// Add the normal error handler for established connections
|
|
777
837
|
targetSocket.on('error', this.handleError('outgoing', record));
|
|
778
838
|
|
|
779
|
-
//
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
//
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
return this.initiateCleanupOnce(record, 'write_error');
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Now set up piping for future data and resume the socket
|
|
792
|
-
socket.pipe(targetSocket);
|
|
793
|
-
targetSocket.pipe(socket);
|
|
794
|
-
socket.resume(); // Resume the socket after piping is established
|
|
795
|
-
|
|
796
|
-
if (this.settings.enableDetailedLogging) {
|
|
797
|
-
console.log(
|
|
798
|
-
`[${connectionId}] Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
799
|
-
`${
|
|
800
|
-
serverName
|
|
801
|
-
? ` (SNI: ${serverName})`
|
|
802
|
-
: domainConfig
|
|
803
|
-
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
804
|
-
: ''
|
|
805
|
-
}` +
|
|
806
|
-
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
807
|
-
record.hasKeepAlive ? 'Yes' : 'No'
|
|
808
|
-
}`
|
|
809
|
-
);
|
|
810
|
-
} else {
|
|
811
|
-
console.log(
|
|
812
|
-
`Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
813
|
-
`${
|
|
814
|
-
serverName
|
|
815
|
-
? ` (SNI: ${serverName})`
|
|
816
|
-
: domainConfig
|
|
817
|
-
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
818
|
-
: ''
|
|
819
|
-
}`
|
|
820
|
-
);
|
|
821
|
-
}
|
|
822
|
-
});
|
|
823
|
-
} else {
|
|
824
|
-
// 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
|
|
825
848
|
socket.pipe(targetSocket);
|
|
826
849
|
targetSocket.pipe(socket);
|
|
827
|
-
|
|
828
|
-
|
|
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
|
+
|
|
829
865
|
if (this.settings.enableDetailedLogging) {
|
|
830
866
|
console.log(
|
|
831
867
|
`[${connectionId}] Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
@@ -852,6 +888,23 @@ export class PortProxy {
|
|
|
852
888
|
}`
|
|
853
889
|
);
|
|
854
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();
|
|
855
908
|
}
|
|
856
909
|
|
|
857
910
|
// Clear the buffer now that we've processed it
|
|
@@ -859,6 +912,7 @@ export class PortProxy {
|
|
|
859
912
|
record.pendingDataSize = 0;
|
|
860
913
|
|
|
861
914
|
// Add the renegotiation handler for SNI validation with strict domain enforcement
|
|
915
|
+
// This will be called after we've established piping
|
|
862
916
|
if (serverName) {
|
|
863
917
|
// Define a handler for checking renegotiation with improved detection
|
|
864
918
|
const renegotiationHandler = (renegChunk: Buffer) => {
|
|
@@ -895,8 +949,13 @@ export class PortProxy {
|
|
|
895
949
|
// Store the handler in the connection record so we can remove it during cleanup
|
|
896
950
|
record.renegotiationHandler = renegotiationHandler;
|
|
897
951
|
|
|
898
|
-
//
|
|
952
|
+
// The renegotiation handler is added when piping is established
|
|
953
|
+
// Making it part of setupPiping ensures proper sequencing of event handlers
|
|
899
954
|
socket.on('data', renegotiationHandler);
|
|
955
|
+
|
|
956
|
+
if (this.settings.enableDetailedLogging) {
|
|
957
|
+
console.log(`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`);
|
|
958
|
+
}
|
|
900
959
|
}
|
|
901
960
|
|
|
902
961
|
// Set connection timeout with simpler logic
|
|
@@ -1056,13 +1115,16 @@ export class PortProxy {
|
|
|
1056
1115
|
const bytesReceived = record.bytesReceived;
|
|
1057
1116
|
const bytesSent = record.bytesSent;
|
|
1058
1117
|
|
|
1059
|
-
// Remove
|
|
1060
|
-
if (record.
|
|
1118
|
+
// Remove all data handlers (both standard and renegotiation) to make sure we clean up properly
|
|
1119
|
+
if (record.incoming) {
|
|
1061
1120
|
try {
|
|
1062
|
-
|
|
1121
|
+
// Remove our safe data handler
|
|
1122
|
+
record.incoming.removeAllListeners('data');
|
|
1123
|
+
|
|
1124
|
+
// Reset the handler references
|
|
1063
1125
|
record.renegotiationHandler = undefined;
|
|
1064
1126
|
} catch (err) {
|
|
1065
|
-
console.log(`[${record.id}] Error removing
|
|
1127
|
+
console.log(`[${record.id}] Error removing data handlers: ${err}`);
|
|
1066
1128
|
}
|
|
1067
1129
|
}
|
|
1068
1130
|
|