@push.rocks/smartproxy 19.3.0 → 19.3.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/proxies/network-proxy/network-proxy.d.ts +0 -4
- package/dist_ts/proxies/network-proxy/network-proxy.js +1 -8
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +6 -6
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +9 -9
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +185 -46
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/proxies/network-proxy/network-proxy.ts +0 -9
- package/ts/proxies/smart-proxy/certificate-manager.ts +9 -8
- package/ts/proxies/smart-proxy/models/route-types.ts +1 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +363 -181
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
|
-
import type {
|
|
3
|
-
IConnectionRecord,
|
|
4
|
-
ISmartProxyOptions
|
|
5
|
-
} from './models/interfaces.js';
|
|
2
|
+
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
|
|
6
3
|
// Route checking functions have been removed
|
|
7
|
-
import type {
|
|
8
|
-
IRouteConfig,
|
|
9
|
-
IRouteAction,
|
|
10
|
-
IRouteContext
|
|
11
|
-
} from './models/route-types.js';
|
|
4
|
+
import type { IRouteConfig, IRouteAction, IRouteContext } from './models/route-types.js';
|
|
12
5
|
import { ConnectionManager } from './connection-manager.js';
|
|
13
6
|
import { SecurityManager } from './security-manager.js';
|
|
14
7
|
import { TlsManager } from './tls-manager.js';
|
|
@@ -75,7 +68,7 @@ export class RouteConnectionHandler {
|
|
|
75
68
|
|
|
76
69
|
// Additional properties
|
|
77
70
|
timestamp: Date.now(),
|
|
78
|
-
connectionId: options.connectionId
|
|
71
|
+
connectionId: options.connectionId,
|
|
79
72
|
};
|
|
80
73
|
}
|
|
81
74
|
|
|
@@ -232,16 +225,19 @@ export class RouteConnectionHandler {
|
|
|
232
225
|
console.log(`[${connectionId}] No SNI detected in TLS ClientHello; sending TLS alert.`);
|
|
233
226
|
if (record.incomingTerminationReason === null) {
|
|
234
227
|
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
|
|
235
|
-
this.connectionManager.incrementTerminationStat(
|
|
228
|
+
this.connectionManager.incrementTerminationStat(
|
|
229
|
+
'incoming',
|
|
230
|
+
'session_ticket_blocked_no_sni'
|
|
231
|
+
);
|
|
236
232
|
}
|
|
237
233
|
const alert = Buffer.from([0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x70]);
|
|
238
|
-
try {
|
|
239
|
-
socket.cork();
|
|
240
|
-
socket.write(alert);
|
|
241
|
-
socket.uncork();
|
|
242
|
-
socket.end();
|
|
243
|
-
} catch {
|
|
244
|
-
socket.end();
|
|
234
|
+
try {
|
|
235
|
+
socket.cork();
|
|
236
|
+
socket.write(alert);
|
|
237
|
+
socket.uncork();
|
|
238
|
+
socket.end();
|
|
239
|
+
} catch {
|
|
240
|
+
socket.end();
|
|
245
241
|
}
|
|
246
242
|
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
|
247
243
|
return;
|
|
@@ -262,7 +258,7 @@ export class RouteConnectionHandler {
|
|
|
262
258
|
* Route the connection based on match criteria
|
|
263
259
|
*/
|
|
264
260
|
private routeConnection(
|
|
265
|
-
socket: plugins.net.Socket,
|
|
261
|
+
socket: plugins.net.Socket,
|
|
266
262
|
record: IConnectionRecord,
|
|
267
263
|
serverName: string,
|
|
268
264
|
initialChunk?: Buffer
|
|
@@ -277,11 +273,13 @@ export class RouteConnectionHandler {
|
|
|
277
273
|
domain: serverName,
|
|
278
274
|
clientIp: remoteIP,
|
|
279
275
|
path: undefined, // We don't have path info at this point
|
|
280
|
-
tlsVersion: undefined // We don't extract TLS version yet
|
|
276
|
+
tlsVersion: undefined, // We don't extract TLS version yet
|
|
281
277
|
});
|
|
282
278
|
|
|
283
279
|
if (!routeMatch) {
|
|
284
|
-
console.log(
|
|
280
|
+
console.log(
|
|
281
|
+
`[${connectionId}] No route found for ${serverName || 'connection'} on port ${localPort}`
|
|
282
|
+
);
|
|
285
283
|
|
|
286
284
|
// No matching route, use default/fallback handling
|
|
287
285
|
console.log(`[${connectionId}] Using default route handling for connection`);
|
|
@@ -304,7 +302,7 @@ export class RouteConnectionHandler {
|
|
|
304
302
|
}
|
|
305
303
|
}
|
|
306
304
|
}
|
|
307
|
-
|
|
305
|
+
|
|
308
306
|
// Setup direct connection with default settings
|
|
309
307
|
if (this.settings.defaults?.target) {
|
|
310
308
|
// Use defaults from configuration
|
|
@@ -328,54 +326,56 @@ export class RouteConnectionHandler {
|
|
|
328
326
|
return;
|
|
329
327
|
}
|
|
330
328
|
}
|
|
331
|
-
|
|
329
|
+
|
|
332
330
|
// A matching route was found
|
|
333
331
|
const route = routeMatch.route;
|
|
334
|
-
|
|
332
|
+
|
|
335
333
|
if (this.settings.enableDetailedLogging) {
|
|
336
334
|
console.log(
|
|
337
|
-
`[${connectionId}] Route matched: "${route.name || 'unnamed'}" for ${
|
|
335
|
+
`[${connectionId}] Route matched: "${route.name || 'unnamed'}" for ${
|
|
336
|
+
serverName || 'connection'
|
|
337
|
+
} on port ${localPort}`
|
|
338
338
|
);
|
|
339
339
|
}
|
|
340
|
-
|
|
340
|
+
|
|
341
341
|
// Check if this route uses NFTables for forwarding
|
|
342
342
|
if (route.action.forwardingEngine === 'nftables') {
|
|
343
343
|
// For NFTables routes, we don't need to do anything at the application level
|
|
344
344
|
// The packet is forwarded at the kernel level
|
|
345
|
-
|
|
345
|
+
|
|
346
346
|
// Log the connection
|
|
347
347
|
console.log(
|
|
348
348
|
`[${connectionId}] Connection forwarded by NFTables: ${record.remoteIP} -> port ${record.localPort}`
|
|
349
349
|
);
|
|
350
|
-
|
|
350
|
+
|
|
351
351
|
// Just close the socket in our application since it's handled at kernel level
|
|
352
352
|
socket.end();
|
|
353
353
|
this.connectionManager.cleanupConnection(record, 'nftables_handled');
|
|
354
354
|
return;
|
|
355
355
|
}
|
|
356
|
-
|
|
356
|
+
|
|
357
357
|
// Handle the route based on its action type
|
|
358
358
|
switch (route.action.type) {
|
|
359
359
|
case 'forward':
|
|
360
360
|
return this.handleForwardAction(socket, record, route, initialChunk);
|
|
361
|
-
|
|
361
|
+
|
|
362
362
|
case 'redirect':
|
|
363
363
|
return this.handleRedirectAction(socket, record, route);
|
|
364
|
-
|
|
364
|
+
|
|
365
365
|
case 'block':
|
|
366
366
|
return this.handleBlockAction(socket, record, route);
|
|
367
|
-
|
|
367
|
+
|
|
368
368
|
case 'static':
|
|
369
369
|
this.handleStaticAction(socket, record, route);
|
|
370
370
|
return;
|
|
371
|
-
|
|
371
|
+
|
|
372
372
|
default:
|
|
373
373
|
console.log(`[${connectionId}] Unknown action type: ${(route.action as any).type}`);
|
|
374
374
|
socket.end();
|
|
375
375
|
this.connectionManager.cleanupConnection(record, 'unknown_action');
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
|
-
|
|
378
|
+
|
|
379
379
|
/**
|
|
380
380
|
* Handle a forward action for a route
|
|
381
381
|
*/
|
|
@@ -394,33 +394,35 @@ export class RouteConnectionHandler {
|
|
|
394
394
|
if (this.settings.enableDetailedLogging) {
|
|
395
395
|
console.log(
|
|
396
396
|
`[${record.id}] Connection forwarded by NFTables (kernel-level): ` +
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
`${record.remoteIP}:${socket.remotePort} -> ${socket.localAddress}:${record.localPort}` +
|
|
398
|
+
` (Route: "${route.name || 'unnamed'}", Domain: ${record.lockedDomain || 'n/a'})`
|
|
399
399
|
);
|
|
400
400
|
} else {
|
|
401
401
|
console.log(
|
|
402
|
-
`[${record.id}] NFTables forwarding: ${record.remoteIP} -> port ${
|
|
402
|
+
`[${record.id}] NFTables forwarding: ${record.remoteIP} -> port ${
|
|
403
|
+
record.localPort
|
|
404
|
+
} (Route: "${route.name || 'unnamed'}")`
|
|
403
405
|
);
|
|
404
406
|
}
|
|
405
|
-
|
|
407
|
+
|
|
406
408
|
// Additional NFTables-specific logging if configured
|
|
407
409
|
if (action.nftables) {
|
|
408
410
|
const nftConfig = action.nftables;
|
|
409
411
|
if (this.settings.enableDetailedLogging) {
|
|
410
412
|
console.log(
|
|
411
413
|
`[${record.id}] NFTables config: ` +
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
414
|
+
`protocol=${nftConfig.protocol || 'tcp'}, ` +
|
|
415
|
+
`preserveSourceIP=${nftConfig.preserveSourceIP || false}, ` +
|
|
416
|
+
`priority=${nftConfig.priority || 'default'}, ` +
|
|
417
|
+
`maxRate=${nftConfig.maxRate || 'unlimited'}`
|
|
416
418
|
);
|
|
417
419
|
}
|
|
418
420
|
}
|
|
419
|
-
|
|
421
|
+
|
|
420
422
|
// This connection is handled at the kernel level, no need to process at application level
|
|
421
423
|
// Close the socket gracefully in our application layer
|
|
422
424
|
socket.end();
|
|
423
|
-
|
|
425
|
+
|
|
424
426
|
// Mark the connection as handled by NFTables for proper cleanup
|
|
425
427
|
record.nftablesHandled = true;
|
|
426
428
|
this.connectionManager.initiateCleanupOnce(record, 'nftables_handled');
|
|
@@ -445,7 +447,7 @@ export class RouteConnectionHandler {
|
|
|
445
447
|
isTls: record.isTLS || false,
|
|
446
448
|
tlsVersion: record.tlsVersion,
|
|
447
449
|
routeName: route.name,
|
|
448
|
-
routeId: route.id
|
|
450
|
+
routeId: route.id,
|
|
449
451
|
});
|
|
450
452
|
|
|
451
453
|
// Cache the context for potential reuse
|
|
@@ -457,7 +459,11 @@ export class RouteConnectionHandler {
|
|
|
457
459
|
try {
|
|
458
460
|
targetHost = action.target.host(routeContext);
|
|
459
461
|
if (this.settings.enableDetailedLogging) {
|
|
460
|
-
console.log(
|
|
462
|
+
console.log(
|
|
463
|
+
`[${connectionId}] Dynamic host resolved to: ${
|
|
464
|
+
Array.isArray(targetHost) ? targetHost.join(', ') : targetHost
|
|
465
|
+
}`
|
|
466
|
+
);
|
|
461
467
|
}
|
|
462
468
|
} catch (err) {
|
|
463
469
|
console.log(`[${connectionId}] Error in host mapping function: ${err}`);
|
|
@@ -480,7 +486,9 @@ export class RouteConnectionHandler {
|
|
|
480
486
|
try {
|
|
481
487
|
targetPort = action.target.port(routeContext);
|
|
482
488
|
if (this.settings.enableDetailedLogging) {
|
|
483
|
-
console.log(
|
|
489
|
+
console.log(
|
|
490
|
+
`[${connectionId}] Dynamic port mapping: ${record.localPort} -> ${targetPort}`
|
|
491
|
+
);
|
|
484
492
|
}
|
|
485
493
|
// Store the resolved target port in the context for potential future use
|
|
486
494
|
routeContext.targetPort = targetPort;
|
|
@@ -509,7 +517,7 @@ export class RouteConnectionHandler {
|
|
|
509
517
|
if (this.settings.enableDetailedLogging) {
|
|
510
518
|
console.log(`[${connectionId}] Using TLS passthrough to ${selectedHost}:${targetPort}`);
|
|
511
519
|
}
|
|
512
|
-
|
|
520
|
+
|
|
513
521
|
return this.setupDirectConnection(
|
|
514
522
|
socket,
|
|
515
523
|
record,
|
|
@@ -519,7 +527,7 @@ export class RouteConnectionHandler {
|
|
|
519
527
|
selectedHost,
|
|
520
528
|
targetPort
|
|
521
529
|
);
|
|
522
|
-
|
|
530
|
+
|
|
523
531
|
case 'terminate':
|
|
524
532
|
case 'terminate-and-reencrypt':
|
|
525
533
|
// For TLS termination, use NetworkProxy
|
|
@@ -529,7 +537,7 @@ export class RouteConnectionHandler {
|
|
|
529
537
|
`[${connectionId}] Using NetworkProxy for TLS termination to ${action.target.host}`
|
|
530
538
|
);
|
|
531
539
|
}
|
|
532
|
-
|
|
540
|
+
|
|
533
541
|
// If we have an initial chunk with TLS data, start processing it
|
|
534
542
|
if (initialChunk && record.isTLS) {
|
|
535
543
|
this.networkProxyBridge.forwardToNetworkProxy(
|
|
@@ -542,7 +550,7 @@ export class RouteConnectionHandler {
|
|
|
542
550
|
);
|
|
543
551
|
return;
|
|
544
552
|
}
|
|
545
|
-
|
|
553
|
+
|
|
546
554
|
// This shouldn't normally happen - we should have TLS data at this point
|
|
547
555
|
console.log(`[${connectionId}] TLS termination route without TLS data`);
|
|
548
556
|
socket.end();
|
|
@@ -558,9 +566,11 @@ export class RouteConnectionHandler {
|
|
|
558
566
|
} else {
|
|
559
567
|
// No TLS settings - basic forwarding
|
|
560
568
|
if (this.settings.enableDetailedLogging) {
|
|
561
|
-
console.log(
|
|
569
|
+
console.log(
|
|
570
|
+
`[${connectionId}] Using basic forwarding to ${action.target.host}:${action.target.port}`
|
|
571
|
+
);
|
|
562
572
|
}
|
|
563
|
-
|
|
573
|
+
|
|
564
574
|
// Get the appropriate host value
|
|
565
575
|
let targetHost: string;
|
|
566
576
|
|
|
@@ -602,7 +612,7 @@ export class RouteConnectionHandler {
|
|
|
602
612
|
);
|
|
603
613
|
}
|
|
604
614
|
}
|
|
605
|
-
|
|
615
|
+
|
|
606
616
|
/**
|
|
607
617
|
* Handle a redirect action for a route
|
|
608
618
|
*/
|
|
@@ -613,7 +623,7 @@ export class RouteConnectionHandler {
|
|
|
613
623
|
): void {
|
|
614
624
|
const connectionId = record.id;
|
|
615
625
|
const action = route.action;
|
|
616
|
-
|
|
626
|
+
|
|
617
627
|
// We should have a redirect configuration
|
|
618
628
|
if (!action.redirect) {
|
|
619
629
|
console.log(`[${connectionId}] Redirect action missing redirect configuration`);
|
|
@@ -621,7 +631,7 @@ export class RouteConnectionHandler {
|
|
|
621
631
|
this.connectionManager.cleanupConnection(record, 'missing_redirect');
|
|
622
632
|
return;
|
|
623
633
|
}
|
|
624
|
-
|
|
634
|
+
|
|
625
635
|
// For TLS connections, we can't do redirects at the TCP level
|
|
626
636
|
if (record.isTLS) {
|
|
627
637
|
console.log(`[${connectionId}] Cannot redirect TLS connection at TCP level`);
|
|
@@ -629,16 +639,16 @@ export class RouteConnectionHandler {
|
|
|
629
639
|
this.connectionManager.cleanupConnection(record, 'tls_redirect_error');
|
|
630
640
|
return;
|
|
631
641
|
}
|
|
632
|
-
|
|
642
|
+
|
|
633
643
|
// Wait for the first HTTP request to perform the redirect
|
|
634
644
|
const dataListeners: ((chunk: Buffer) => void)[] = [];
|
|
635
|
-
|
|
645
|
+
|
|
636
646
|
const httpDataHandler = (chunk: Buffer) => {
|
|
637
647
|
// Remove all data listeners to avoid duplicated processing
|
|
638
648
|
for (const listener of dataListeners) {
|
|
639
649
|
socket.removeListener('data', listener);
|
|
640
650
|
}
|
|
641
|
-
|
|
651
|
+
|
|
642
652
|
// Parse HTTP request to get path
|
|
643
653
|
try {
|
|
644
654
|
const headersEnd = chunk.indexOf('\r\n\r\n');
|
|
@@ -648,21 +658,21 @@ export class RouteConnectionHandler {
|
|
|
648
658
|
dataListeners.push(httpDataHandler);
|
|
649
659
|
return;
|
|
650
660
|
}
|
|
651
|
-
|
|
661
|
+
|
|
652
662
|
const httpHeaders = chunk.slice(0, headersEnd).toString();
|
|
653
663
|
const requestLine = httpHeaders.split('\r\n')[0];
|
|
654
664
|
const [method, path] = requestLine.split(' ');
|
|
655
|
-
|
|
665
|
+
|
|
656
666
|
// Extract Host header
|
|
657
667
|
const hostMatch = httpHeaders.match(/Host: (.+?)(\r\n|\r|\n|$)/i);
|
|
658
668
|
const host = hostMatch ? hostMatch[1].trim() : record.lockedDomain || '';
|
|
659
|
-
|
|
669
|
+
|
|
660
670
|
// Process the redirect URL with template variables
|
|
661
671
|
let redirectUrl = action.redirect.to;
|
|
662
672
|
redirectUrl = redirectUrl.replace(/\{domain\}/g, host);
|
|
663
673
|
redirectUrl = redirectUrl.replace(/\{path\}/g, path || '');
|
|
664
674
|
redirectUrl = redirectUrl.replace(/\{port\}/g, record.localPort.toString());
|
|
665
|
-
|
|
675
|
+
|
|
666
676
|
// Prepare the HTTP redirect response
|
|
667
677
|
const redirectResponse = [
|
|
668
678
|
`HTTP/1.1 ${action.redirect.status} Moved`,
|
|
@@ -670,13 +680,15 @@ export class RouteConnectionHandler {
|
|
|
670
680
|
'Connection: close',
|
|
671
681
|
'Content-Length: 0',
|
|
672
682
|
'',
|
|
673
|
-
''
|
|
683
|
+
'',
|
|
674
684
|
].join('\r\n');
|
|
675
|
-
|
|
685
|
+
|
|
676
686
|
if (this.settings.enableDetailedLogging) {
|
|
677
|
-
console.log(
|
|
687
|
+
console.log(
|
|
688
|
+
`[${connectionId}] Redirecting to ${redirectUrl} with status ${action.redirect.status}`
|
|
689
|
+
);
|
|
678
690
|
}
|
|
679
|
-
|
|
691
|
+
|
|
680
692
|
// Send the redirect response
|
|
681
693
|
socket.end(redirectResponse);
|
|
682
694
|
this.connectionManager.initiateCleanupOnce(record, 'redirect_complete');
|
|
@@ -686,12 +698,12 @@ export class RouteConnectionHandler {
|
|
|
686
698
|
this.connectionManager.initiateCleanupOnce(record, 'redirect_error');
|
|
687
699
|
}
|
|
688
700
|
};
|
|
689
|
-
|
|
701
|
+
|
|
690
702
|
// Setup the HTTP data handler
|
|
691
703
|
socket.once('data', httpDataHandler);
|
|
692
704
|
dataListeners.push(httpDataHandler);
|
|
693
705
|
}
|
|
694
|
-
|
|
706
|
+
|
|
695
707
|
/**
|
|
696
708
|
* Handle a block action for a route
|
|
697
709
|
*/
|
|
@@ -701,16 +713,18 @@ export class RouteConnectionHandler {
|
|
|
701
713
|
route: IRouteConfig
|
|
702
714
|
): void {
|
|
703
715
|
const connectionId = record.id;
|
|
704
|
-
|
|
716
|
+
|
|
705
717
|
if (this.settings.enableDetailedLogging) {
|
|
706
|
-
console.log(
|
|
718
|
+
console.log(
|
|
719
|
+
`[${connectionId}] Blocking connection based on route "${route.name || 'unnamed'}"`
|
|
720
|
+
);
|
|
707
721
|
}
|
|
708
|
-
|
|
722
|
+
|
|
709
723
|
// Simply close the connection
|
|
710
724
|
socket.end();
|
|
711
725
|
this.connectionManager.initiateCleanupOnce(record, 'route_blocked');
|
|
712
726
|
}
|
|
713
|
-
|
|
727
|
+
|
|
714
728
|
/**
|
|
715
729
|
* Handle a static action for a route
|
|
716
730
|
*/
|
|
@@ -720,55 +734,222 @@ export class RouteConnectionHandler {
|
|
|
720
734
|
route: IRouteConfig
|
|
721
735
|
): Promise<void> {
|
|
722
736
|
const connectionId = record.id;
|
|
723
|
-
|
|
737
|
+
|
|
724
738
|
if (!route.action.handler) {
|
|
725
739
|
console.error(`[${connectionId}] Static route '${route.name}' has no handler`);
|
|
726
740
|
socket.end();
|
|
727
741
|
this.connectionManager.cleanupConnection(record, 'no_handler');
|
|
728
742
|
return;
|
|
729
743
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// Call the handler
|
|
748
|
-
const response = await route.action.handler(context);
|
|
749
|
-
|
|
750
|
-
// Send HTTP response
|
|
751
|
-
const headers = response.headers || {};
|
|
752
|
-
headers['Content-Length'] = Buffer.byteLength(response.body).toString();
|
|
753
|
-
|
|
754
|
-
let httpResponse = `HTTP/1.1 ${response.status} ${getStatusText(response.status)}\r\n`;
|
|
755
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
756
|
-
httpResponse += `${key}: ${value}\r\n`;
|
|
744
|
+
|
|
745
|
+
let buffer = Buffer.alloc(0);
|
|
746
|
+
let processingData = false;
|
|
747
|
+
|
|
748
|
+
const handleHttpData = async (chunk: Buffer) => {
|
|
749
|
+
// Accumulate the data
|
|
750
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
751
|
+
|
|
752
|
+
// Prevent concurrent processing of the same buffer
|
|
753
|
+
if (processingData) return;
|
|
754
|
+
processingData = true;
|
|
755
|
+
|
|
756
|
+
try {
|
|
757
|
+
// Process data until we have a complete request or need more data
|
|
758
|
+
await processBuffer();
|
|
759
|
+
} finally {
|
|
760
|
+
processingData = false;
|
|
757
761
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
const processBuffer = async () => {
|
|
765
|
+
// Look for end of HTTP headers
|
|
766
|
+
const headerEndIndex = buffer.indexOf('\r\n\r\n');
|
|
767
|
+
if (headerEndIndex === -1) {
|
|
768
|
+
// Need more data
|
|
769
|
+
if (buffer.length > 8192) {
|
|
770
|
+
// Prevent excessive buffering
|
|
771
|
+
console.error(`[${connectionId}] HTTP headers too large`);
|
|
772
|
+
socket.end();
|
|
773
|
+
this.connectionManager.cleanupConnection(record, 'headers_too_large');
|
|
774
|
+
}
|
|
775
|
+
return; // Wait for more data to arrive
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Parse the HTTP request
|
|
779
|
+
const headerBuffer = buffer.slice(0, headerEndIndex);
|
|
780
|
+
const headers = headerBuffer.toString();
|
|
781
|
+
const lines = headers.split('\r\n');
|
|
782
|
+
|
|
783
|
+
if (lines.length === 0) {
|
|
784
|
+
console.error(`[${connectionId}] Invalid HTTP request`);
|
|
785
|
+
socket.end();
|
|
786
|
+
this.connectionManager.cleanupConnection(record, 'invalid_request');
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Parse request line
|
|
791
|
+
const requestLine = lines[0];
|
|
792
|
+
const requestParts = requestLine.split(' ');
|
|
793
|
+
if (requestParts.length < 3) {
|
|
794
|
+
console.error(`[${connectionId}] Invalid HTTP request line`);
|
|
795
|
+
socket.end();
|
|
796
|
+
this.connectionManager.cleanupConnection(record, 'invalid_request_line');
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const [method, path, httpVersion] = requestParts;
|
|
801
|
+
|
|
802
|
+
// Parse headers
|
|
803
|
+
const headersMap: Record<string, string> = {};
|
|
804
|
+
for (let i = 1; i < lines.length; i++) {
|
|
805
|
+
const colonIndex = lines[i].indexOf(':');
|
|
806
|
+
if (colonIndex > 0) {
|
|
807
|
+
const key = lines[i].slice(0, colonIndex).trim().toLowerCase();
|
|
808
|
+
const value = lines[i].slice(colonIndex + 1).trim();
|
|
809
|
+
headersMap[key] = value;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Check for Content-Length to handle request body
|
|
814
|
+
const requestBodyLength = parseInt(headersMap['content-length'] || '0', 10);
|
|
815
|
+
const bodyStartIndex = headerEndIndex + 4; // Skip the \r\n\r\n
|
|
816
|
+
|
|
817
|
+
// If there's a body, ensure we have the full body
|
|
818
|
+
if (requestBodyLength > 0) {
|
|
819
|
+
const totalExpectedLength = bodyStartIndex + requestBodyLength;
|
|
820
|
+
|
|
821
|
+
// If we don't have the complete body yet, wait for more data
|
|
822
|
+
if (buffer.length < totalExpectedLength) {
|
|
823
|
+
// Implement a reasonable body size limit to prevent memory issues
|
|
824
|
+
if (requestBodyLength > 1024 * 1024) {
|
|
825
|
+
// 1MB limit
|
|
826
|
+
console.error(`[${connectionId}] Request body too large`);
|
|
827
|
+
socket.end();
|
|
828
|
+
this.connectionManager.cleanupConnection(record, 'body_too_large');
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
return; // Wait for more data
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Extract query string if present
|
|
836
|
+
let pathname = path;
|
|
837
|
+
let query: string | undefined;
|
|
838
|
+
const queryIndex = path.indexOf('?');
|
|
839
|
+
if (queryIndex !== -1) {
|
|
840
|
+
pathname = path.slice(0, queryIndex);
|
|
841
|
+
query = path.slice(queryIndex + 1);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
try {
|
|
845
|
+
// Get request body if present
|
|
846
|
+
let requestBody: Buffer | undefined;
|
|
847
|
+
if (requestBodyLength > 0) {
|
|
848
|
+
requestBody = buffer.slice(bodyStartIndex, bodyStartIndex + requestBodyLength);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Pause socket to prevent data loss during async processing
|
|
852
|
+
socket.pause();
|
|
853
|
+
|
|
854
|
+
// Remove the data listener since we're handling the request
|
|
855
|
+
socket.removeListener('data', handleHttpData);
|
|
856
|
+
|
|
857
|
+
// Build route context with parsed HTTP information
|
|
858
|
+
const context: IRouteContext = {
|
|
859
|
+
port: record.localPort,
|
|
860
|
+
domain: record.lockedDomain || headersMap['host']?.split(':')[0],
|
|
861
|
+
clientIp: record.remoteIP,
|
|
862
|
+
serverIp: socket.localAddress!,
|
|
863
|
+
path: pathname,
|
|
864
|
+
query: query,
|
|
865
|
+
headers: headersMap,
|
|
866
|
+
method: method,
|
|
867
|
+
isTls: record.isTLS,
|
|
868
|
+
tlsVersion: record.tlsVersion,
|
|
869
|
+
routeName: route.name,
|
|
870
|
+
routeId: route.id,
|
|
871
|
+
timestamp: Date.now(),
|
|
872
|
+
connectionId,
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
// Since IRouteContext doesn't have a body property,
|
|
876
|
+
// we need an alternative approach to handle the body
|
|
877
|
+
let response;
|
|
878
|
+
|
|
879
|
+
if (requestBody) {
|
|
880
|
+
if (this.settings.enableDetailedLogging) {
|
|
881
|
+
console.log(
|
|
882
|
+
`[${connectionId}] Processing request with body (${requestBody.length} bytes)`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Pass the body as an additional parameter by extending the context object
|
|
887
|
+
// This is not type-safe, but it allows handlers that expect a body to work
|
|
888
|
+
const extendedContext = {
|
|
889
|
+
...context,
|
|
890
|
+
// Provide both raw buffer and string representation
|
|
891
|
+
requestBody: requestBody,
|
|
892
|
+
requestBodyText: requestBody.toString(),
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
// Call the handler with the extended context
|
|
896
|
+
// The handler needs to know to look for the non-standard properties
|
|
897
|
+
response = await route.action.handler(extendedContext as any);
|
|
898
|
+
} else {
|
|
899
|
+
// Call the handler with the standard context
|
|
900
|
+
response = await route.action.handler(context);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Prepare the HTTP response
|
|
904
|
+
const responseHeaders = response.headers || {};
|
|
905
|
+
const contentLength = Buffer.byteLength(response.body || '');
|
|
906
|
+
responseHeaders['Content-Length'] = contentLength.toString();
|
|
907
|
+
|
|
908
|
+
if (!responseHeaders['Content-Type']) {
|
|
909
|
+
responseHeaders['Content-Type'] = 'text/plain';
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Build the response
|
|
913
|
+
let httpResponse = `HTTP/1.1 ${response.status} ${getStatusText(response.status)}\r\n`;
|
|
914
|
+
for (const [key, value] of Object.entries(responseHeaders)) {
|
|
915
|
+
httpResponse += `${key}: ${value}\r\n`;
|
|
916
|
+
}
|
|
917
|
+
httpResponse += '\r\n';
|
|
918
|
+
|
|
919
|
+
// Send response
|
|
920
|
+
socket.write(httpResponse);
|
|
921
|
+
if (response.body) {
|
|
922
|
+
socket.write(response.body);
|
|
923
|
+
}
|
|
924
|
+
socket.end();
|
|
925
|
+
|
|
926
|
+
this.connectionManager.cleanupConnection(record, 'completed');
|
|
927
|
+
} catch (error) {
|
|
928
|
+
console.error(`[${connectionId}] Error in static handler: ${error}`);
|
|
929
|
+
|
|
930
|
+
// Send error response
|
|
931
|
+
const errorResponse =
|
|
932
|
+
'HTTP/1.1 500 Internal Server Error\r\n' +
|
|
933
|
+
'Content-Type: text/plain\r\n' +
|
|
934
|
+
'Content-Length: 21\r\n' +
|
|
935
|
+
'\r\n' +
|
|
936
|
+
'Internal Server Error';
|
|
937
|
+
socket.write(errorResponse);
|
|
938
|
+
socket.end();
|
|
939
|
+
|
|
940
|
+
this.connectionManager.cleanupConnection(record, 'handler_error');
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// Listen for data
|
|
945
|
+
socket.on('data', handleHttpData);
|
|
946
|
+
|
|
947
|
+
// Ensure cleanup on socket close
|
|
948
|
+
socket.once('close', () => {
|
|
949
|
+
socket.removeListener('data', handleHttpData);
|
|
950
|
+
});
|
|
770
951
|
}
|
|
771
|
-
|
|
952
|
+
|
|
772
953
|
/**
|
|
773
954
|
* Sets up a direct connection to the target
|
|
774
955
|
*/
|
|
@@ -784,22 +965,23 @@ export class RouteConnectionHandler {
|
|
|
784
965
|
const connectionId = record.id;
|
|
785
966
|
|
|
786
967
|
// Determine target host and port if not provided
|
|
787
|
-
const finalTargetHost =
|
|
788
|
-
record.targetHost ||
|
|
789
|
-
(this.settings.defaults?.target?.host || 'localhost');
|
|
968
|
+
const finalTargetHost =
|
|
969
|
+
targetHost || record.targetHost || this.settings.defaults?.target?.host || 'localhost';
|
|
790
970
|
|
|
791
971
|
// Determine target port
|
|
792
|
-
const finalTargetPort =
|
|
972
|
+
const finalTargetPort =
|
|
973
|
+
targetPort ||
|
|
793
974
|
record.targetPort ||
|
|
794
|
-
(overridePort !== undefined ? overridePort :
|
|
795
|
-
(this.settings.defaults?.target?.port || 443));
|
|
975
|
+
(overridePort !== undefined ? overridePort : this.settings.defaults?.target?.port || 443);
|
|
796
976
|
|
|
797
977
|
// Update record with final target information
|
|
798
978
|
record.targetHost = finalTargetHost;
|
|
799
979
|
record.targetPort = finalTargetPort;
|
|
800
980
|
|
|
801
981
|
if (this.settings.enableDetailedLogging) {
|
|
802
|
-
console.log(
|
|
982
|
+
console.log(
|
|
983
|
+
`[${connectionId}] Setting up direct connection to ${finalTargetHost}:${finalTargetPort}`
|
|
984
|
+
);
|
|
803
985
|
}
|
|
804
986
|
|
|
805
987
|
// Setup connection options
|
|
@@ -812,53 +994,53 @@ export class RouteConnectionHandler {
|
|
|
812
994
|
if (this.settings.defaults?.preserveSourceIP || this.settings.preserveSourceIP) {
|
|
813
995
|
connectionOptions.localAddress = record.remoteIP.replace('::ffff:', '');
|
|
814
996
|
}
|
|
815
|
-
|
|
997
|
+
|
|
816
998
|
// Create a safe queue for incoming data
|
|
817
999
|
const dataQueue: Buffer[] = [];
|
|
818
1000
|
let queueSize = 0;
|
|
819
1001
|
let processingQueue = false;
|
|
820
1002
|
let drainPending = false;
|
|
821
1003
|
let pipingEstablished = false;
|
|
822
|
-
|
|
1004
|
+
|
|
823
1005
|
// Pause the incoming socket to prevent buffer overflows
|
|
824
1006
|
socket.pause();
|
|
825
|
-
|
|
1007
|
+
|
|
826
1008
|
// Function to safely process the data queue without losing events
|
|
827
1009
|
const processDataQueue = () => {
|
|
828
1010
|
if (processingQueue || dataQueue.length === 0 || pipingEstablished) return;
|
|
829
|
-
|
|
1011
|
+
|
|
830
1012
|
processingQueue = true;
|
|
831
|
-
|
|
1013
|
+
|
|
832
1014
|
try {
|
|
833
1015
|
// Process all queued chunks with the current active handler
|
|
834
1016
|
while (dataQueue.length > 0) {
|
|
835
1017
|
const chunk = dataQueue.shift()!;
|
|
836
1018
|
queueSize -= chunk.length;
|
|
837
|
-
|
|
1019
|
+
|
|
838
1020
|
// Once piping is established, we shouldn't get here,
|
|
839
1021
|
// but just in case, pass to the outgoing socket directly
|
|
840
1022
|
if (pipingEstablished && record.outgoing) {
|
|
841
1023
|
record.outgoing.write(chunk);
|
|
842
1024
|
continue;
|
|
843
1025
|
}
|
|
844
|
-
|
|
1026
|
+
|
|
845
1027
|
// Track bytes received
|
|
846
1028
|
record.bytesReceived += chunk.length;
|
|
847
|
-
|
|
1029
|
+
|
|
848
1030
|
// Check for TLS handshake
|
|
849
1031
|
if (!record.isTLS && this.tlsManager.isTlsHandshake(chunk)) {
|
|
850
1032
|
record.isTLS = true;
|
|
851
|
-
|
|
1033
|
+
|
|
852
1034
|
if (this.settings.enableTlsDebugLogging) {
|
|
853
1035
|
console.log(
|
|
854
1036
|
`[${connectionId}] TLS handshake detected in tempDataHandler, ${chunk.length} bytes`
|
|
855
1037
|
);
|
|
856
1038
|
}
|
|
857
1039
|
}
|
|
858
|
-
|
|
1040
|
+
|
|
859
1041
|
// Check if adding this chunk would exceed the buffer limit
|
|
860
1042
|
const newSize = record.pendingDataSize + chunk.length;
|
|
861
|
-
|
|
1043
|
+
|
|
862
1044
|
if (this.settings.maxPendingDataSize && newSize > this.settings.maxPendingDataSize) {
|
|
863
1045
|
console.log(
|
|
864
1046
|
`[${connectionId}] Buffer limit exceeded for connection from ${record.remoteIP}: ${newSize} bytes > ${this.settings.maxPendingDataSize} bytes`
|
|
@@ -867,7 +1049,7 @@ export class RouteConnectionHandler {
|
|
|
867
1049
|
this.connectionManager.initiateCleanupOnce(record, 'buffer_limit_exceeded');
|
|
868
1050
|
return;
|
|
869
1051
|
}
|
|
870
|
-
|
|
1052
|
+
|
|
871
1053
|
// Buffer the chunk and update the size counter
|
|
872
1054
|
record.pendingData.push(Buffer.from(chunk));
|
|
873
1055
|
record.pendingDataSize = newSize;
|
|
@@ -875,7 +1057,7 @@ export class RouteConnectionHandler {
|
|
|
875
1057
|
}
|
|
876
1058
|
} finally {
|
|
877
1059
|
processingQueue = false;
|
|
878
|
-
|
|
1060
|
+
|
|
879
1061
|
// If there's a pending drain and we've processed everything,
|
|
880
1062
|
// signal we're ready for more data if we haven't established piping yet
|
|
881
1063
|
if (drainPending && dataQueue.length === 0 && !pipingEstablished) {
|
|
@@ -884,48 +1066,48 @@ export class RouteConnectionHandler {
|
|
|
884
1066
|
}
|
|
885
1067
|
}
|
|
886
1068
|
};
|
|
887
|
-
|
|
1069
|
+
|
|
888
1070
|
// Unified data handler that safely queues incoming data
|
|
889
1071
|
const safeDataHandler = (chunk: Buffer) => {
|
|
890
1072
|
// If piping is already established, just let the pipe handle it
|
|
891
1073
|
if (pipingEstablished) return;
|
|
892
|
-
|
|
1074
|
+
|
|
893
1075
|
// Add to our queue for orderly processing
|
|
894
1076
|
dataQueue.push(Buffer.from(chunk)); // Make a copy to be safe
|
|
895
1077
|
queueSize += chunk.length;
|
|
896
|
-
|
|
1078
|
+
|
|
897
1079
|
// If queue is getting large, pause socket until we catch up
|
|
898
1080
|
if (this.settings.maxPendingDataSize && queueSize > this.settings.maxPendingDataSize * 0.8) {
|
|
899
1081
|
socket.pause();
|
|
900
1082
|
drainPending = true;
|
|
901
1083
|
}
|
|
902
|
-
|
|
1084
|
+
|
|
903
1085
|
// Process the queue
|
|
904
1086
|
processDataQueue();
|
|
905
1087
|
};
|
|
906
|
-
|
|
1088
|
+
|
|
907
1089
|
// Add our safe data handler
|
|
908
1090
|
socket.on('data', safeDataHandler);
|
|
909
|
-
|
|
1091
|
+
|
|
910
1092
|
// Add initial chunk to pending data if present
|
|
911
1093
|
if (initialChunk) {
|
|
912
1094
|
record.bytesReceived += initialChunk.length;
|
|
913
1095
|
record.pendingData.push(Buffer.from(initialChunk));
|
|
914
1096
|
record.pendingDataSize = initialChunk.length;
|
|
915
1097
|
}
|
|
916
|
-
|
|
1098
|
+
|
|
917
1099
|
// Create the target socket but don't set up piping immediately
|
|
918
1100
|
const targetSocket = plugins.net.connect(connectionOptions);
|
|
919
1101
|
record.outgoing = targetSocket;
|
|
920
1102
|
record.outgoingStartTime = Date.now();
|
|
921
|
-
|
|
1103
|
+
|
|
922
1104
|
// Apply socket optimizations
|
|
923
1105
|
targetSocket.setNoDelay(this.settings.noDelay);
|
|
924
|
-
|
|
1106
|
+
|
|
925
1107
|
// Apply keep-alive settings to the outgoing connection as well
|
|
926
1108
|
if (this.settings.keepAlive) {
|
|
927
1109
|
targetSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
928
|
-
|
|
1110
|
+
|
|
929
1111
|
// Apply enhanced TCP keep-alive options if enabled
|
|
930
1112
|
if (this.settings.enableKeepAliveProbes) {
|
|
931
1113
|
try {
|
|
@@ -945,7 +1127,7 @@ export class RouteConnectionHandler {
|
|
|
945
1127
|
}
|
|
946
1128
|
}
|
|
947
1129
|
}
|
|
948
|
-
|
|
1130
|
+
|
|
949
1131
|
// Setup specific error handler for connection phase
|
|
950
1132
|
targetSocket.once('error', (err) => {
|
|
951
1133
|
// This handler runs only once during the initial connection phase
|
|
@@ -953,10 +1135,10 @@ export class RouteConnectionHandler {
|
|
|
953
1135
|
console.log(
|
|
954
1136
|
`[${connectionId}] Connection setup error to ${finalTargetHost}:${connectionOptions.port}: ${err.message} (${code})`
|
|
955
1137
|
);
|
|
956
|
-
|
|
1138
|
+
|
|
957
1139
|
// Resume the incoming socket to prevent it from hanging
|
|
958
1140
|
socket.resume();
|
|
959
|
-
|
|
1141
|
+
|
|
960
1142
|
if (code === 'ECONNREFUSED') {
|
|
961
1143
|
console.log(
|
|
962
1144
|
`[${connectionId}] Target ${finalTargetHost}:${connectionOptions.port} refused connection`
|
|
@@ -972,28 +1154,28 @@ export class RouteConnectionHandler {
|
|
|
972
1154
|
} else if (code === 'EHOSTUNREACH') {
|
|
973
1155
|
console.log(`[${connectionId}] Host ${finalTargetHost} is unreachable`);
|
|
974
1156
|
}
|
|
975
|
-
|
|
1157
|
+
|
|
976
1158
|
// Clear any existing error handler after connection phase
|
|
977
1159
|
targetSocket.removeAllListeners('error');
|
|
978
|
-
|
|
1160
|
+
|
|
979
1161
|
// Re-add the normal error handler for established connections
|
|
980
1162
|
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
|
981
|
-
|
|
1163
|
+
|
|
982
1164
|
if (record.outgoingTerminationReason === null) {
|
|
983
1165
|
record.outgoingTerminationReason = 'connection_failed';
|
|
984
1166
|
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
|
|
985
1167
|
}
|
|
986
|
-
|
|
1168
|
+
|
|
987
1169
|
// Route-based configuration doesn't use domain handlers
|
|
988
|
-
|
|
1170
|
+
|
|
989
1171
|
// Clean up the connection
|
|
990
1172
|
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
|
|
991
1173
|
});
|
|
992
|
-
|
|
1174
|
+
|
|
993
1175
|
// Setup close handler
|
|
994
1176
|
targetSocket.on('close', this.connectionManager.handleClose('outgoing', record));
|
|
995
1177
|
socket.on('close', this.connectionManager.handleClose('incoming', record));
|
|
996
|
-
|
|
1178
|
+
|
|
997
1179
|
// Handle timeouts with keep-alive awareness
|
|
998
1180
|
socket.on('timeout', () => {
|
|
999
1181
|
// For keep-alive connections, just log a warning instead of closing
|
|
@@ -1007,7 +1189,7 @@ export class RouteConnectionHandler {
|
|
|
1007
1189
|
);
|
|
1008
1190
|
return;
|
|
1009
1191
|
}
|
|
1010
|
-
|
|
1192
|
+
|
|
1011
1193
|
// For non-keep-alive connections, proceed with normal cleanup
|
|
1012
1194
|
console.log(
|
|
1013
1195
|
`[${connectionId}] Timeout on incoming side from ${
|
|
@@ -1020,7 +1202,7 @@ export class RouteConnectionHandler {
|
|
|
1020
1202
|
}
|
|
1021
1203
|
this.connectionManager.initiateCleanupOnce(record, 'timeout_incoming');
|
|
1022
1204
|
});
|
|
1023
|
-
|
|
1205
|
+
|
|
1024
1206
|
targetSocket.on('timeout', () => {
|
|
1025
1207
|
// For keep-alive connections, just log a warning instead of closing
|
|
1026
1208
|
if (record.hasKeepAlive) {
|
|
@@ -1033,7 +1215,7 @@ export class RouteConnectionHandler {
|
|
|
1033
1215
|
);
|
|
1034
1216
|
return;
|
|
1035
1217
|
}
|
|
1036
|
-
|
|
1218
|
+
|
|
1037
1219
|
// For non-keep-alive connections, proceed with normal cleanup
|
|
1038
1220
|
console.log(
|
|
1039
1221
|
`[${connectionId}] Timeout on outgoing side from ${
|
|
@@ -1046,40 +1228,40 @@ export class RouteConnectionHandler {
|
|
|
1046
1228
|
}
|
|
1047
1229
|
this.connectionManager.initiateCleanupOnce(record, 'timeout_outgoing');
|
|
1048
1230
|
});
|
|
1049
|
-
|
|
1231
|
+
|
|
1050
1232
|
// Apply socket timeouts
|
|
1051
1233
|
this.timeoutManager.applySocketTimeouts(record);
|
|
1052
|
-
|
|
1234
|
+
|
|
1053
1235
|
// Track outgoing data for bytes counting
|
|
1054
1236
|
targetSocket.on('data', (chunk: Buffer) => {
|
|
1055
1237
|
record.bytesSent += chunk.length;
|
|
1056
1238
|
this.timeoutManager.updateActivity(record);
|
|
1057
1239
|
});
|
|
1058
|
-
|
|
1240
|
+
|
|
1059
1241
|
// Wait for the outgoing connection to be ready before setting up piping
|
|
1060
1242
|
targetSocket.once('connect', () => {
|
|
1061
1243
|
// Clear the initial connection error handler
|
|
1062
1244
|
targetSocket.removeAllListeners('error');
|
|
1063
|
-
|
|
1245
|
+
|
|
1064
1246
|
// Add the normal error handler for established connections
|
|
1065
1247
|
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
|
1066
|
-
|
|
1248
|
+
|
|
1067
1249
|
// Process any remaining data in the queue before switching to piping
|
|
1068
1250
|
processDataQueue();
|
|
1069
|
-
|
|
1251
|
+
|
|
1070
1252
|
// Set up piping immediately
|
|
1071
1253
|
pipingEstablished = true;
|
|
1072
|
-
|
|
1254
|
+
|
|
1073
1255
|
// Flush all pending data to target
|
|
1074
1256
|
if (record.pendingData.length > 0) {
|
|
1075
1257
|
const combinedData = Buffer.concat(record.pendingData);
|
|
1076
|
-
|
|
1258
|
+
|
|
1077
1259
|
if (this.settings.enableDetailedLogging) {
|
|
1078
1260
|
console.log(
|
|
1079
1261
|
`[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`
|
|
1080
1262
|
);
|
|
1081
1263
|
}
|
|
1082
|
-
|
|
1264
|
+
|
|
1083
1265
|
// Write pending data immediately
|
|
1084
1266
|
targetSocket.write(combinedData, (err) => {
|
|
1085
1267
|
if (err) {
|
|
@@ -1087,19 +1269,19 @@ export class RouteConnectionHandler {
|
|
|
1087
1269
|
return this.connectionManager.initiateCleanupOnce(record, 'write_error');
|
|
1088
1270
|
}
|
|
1089
1271
|
});
|
|
1090
|
-
|
|
1272
|
+
|
|
1091
1273
|
// Clear the buffer now that we've processed it
|
|
1092
1274
|
record.pendingData = [];
|
|
1093
1275
|
record.pendingDataSize = 0;
|
|
1094
1276
|
}
|
|
1095
|
-
|
|
1277
|
+
|
|
1096
1278
|
// Setup piping in both directions without any delays
|
|
1097
1279
|
socket.pipe(targetSocket);
|
|
1098
1280
|
targetSocket.pipe(socket);
|
|
1099
|
-
|
|
1281
|
+
|
|
1100
1282
|
// Resume the socket to ensure data flows
|
|
1101
1283
|
socket.resume();
|
|
1102
|
-
|
|
1284
|
+
|
|
1103
1285
|
// Process any data that might be queued in the interim
|
|
1104
1286
|
if (dataQueue.length > 0) {
|
|
1105
1287
|
// Write any remaining queued data directly to the target socket
|
|
@@ -1110,7 +1292,7 @@ export class RouteConnectionHandler {
|
|
|
1110
1292
|
dataQueue.length = 0;
|
|
1111
1293
|
queueSize = 0;
|
|
1112
1294
|
}
|
|
1113
|
-
|
|
1295
|
+
|
|
1114
1296
|
if (this.settings.enableDetailedLogging) {
|
|
1115
1297
|
console.log(
|
|
1116
1298
|
`[${connectionId}] Connection established: ${record.remoteIP} -> ${finalTargetHost}:${connectionOptions.port}` +
|
|
@@ -1137,7 +1319,7 @@ export class RouteConnectionHandler {
|
|
|
1137
1319
|
}`
|
|
1138
1320
|
);
|
|
1139
1321
|
}
|
|
1140
|
-
|
|
1322
|
+
|
|
1141
1323
|
// Add the renegotiation handler for SNI validation
|
|
1142
1324
|
if (serverName) {
|
|
1143
1325
|
// Create connection info object for the existing connection
|
|
@@ -1147,7 +1329,7 @@ export class RouteConnectionHandler {
|
|
|
1147
1329
|
destIp: record.incoming.localAddress || '',
|
|
1148
1330
|
destPort: record.incoming.localPort || 0,
|
|
1149
1331
|
};
|
|
1150
|
-
|
|
1332
|
+
|
|
1151
1333
|
// Create a renegotiation handler function
|
|
1152
1334
|
const renegotiationHandler = this.tlsManager.createRenegotiationHandler(
|
|
1153
1335
|
connectionId,
|
|
@@ -1155,13 +1337,13 @@ export class RouteConnectionHandler {
|
|
|
1155
1337
|
connInfo,
|
|
1156
1338
|
(connectionId, reason) => this.connectionManager.initiateCleanupOnce(record, reason)
|
|
1157
1339
|
);
|
|
1158
|
-
|
|
1340
|
+
|
|
1159
1341
|
// Store the handler in the connection record so we can remove it during cleanup
|
|
1160
1342
|
record.renegotiationHandler = renegotiationHandler;
|
|
1161
|
-
|
|
1343
|
+
|
|
1162
1344
|
// Add the handler to the socket
|
|
1163
1345
|
socket.on('data', renegotiationHandler);
|
|
1164
|
-
|
|
1346
|
+
|
|
1165
1347
|
if (this.settings.enableDetailedLogging) {
|
|
1166
1348
|
console.log(
|
|
1167
1349
|
`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`
|
|
@@ -1173,7 +1355,7 @@ export class RouteConnectionHandler {
|
|
|
1173
1355
|
}
|
|
1174
1356
|
}
|
|
1175
1357
|
}
|
|
1176
|
-
|
|
1358
|
+
|
|
1177
1359
|
// Set connection timeout
|
|
1178
1360
|
record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(record, (record, reason) => {
|
|
1179
1361
|
console.log(
|
|
@@ -1181,11 +1363,11 @@ export class RouteConnectionHandler {
|
|
|
1181
1363
|
);
|
|
1182
1364
|
this.connectionManager.initiateCleanupOnce(record, reason);
|
|
1183
1365
|
});
|
|
1184
|
-
|
|
1366
|
+
|
|
1185
1367
|
// Mark TLS handshake as complete for TLS connections
|
|
1186
1368
|
if (record.isTLS) {
|
|
1187
1369
|
record.tlsHandshakeComplete = true;
|
|
1188
|
-
|
|
1370
|
+
|
|
1189
1371
|
if (this.settings.enableTlsDebugLogging) {
|
|
1190
1372
|
console.log(
|
|
1191
1373
|
`[${connectionId}] TLS handshake complete for connection from ${record.remoteIP}`
|
|
@@ -1201,7 +1383,7 @@ function getStatusText(status: number): string {
|
|
|
1201
1383
|
const statusTexts: Record<number, string> = {
|
|
1202
1384
|
200: 'OK',
|
|
1203
1385
|
404: 'Not Found',
|
|
1204
|
-
500: 'Internal Server Error'
|
|
1386
|
+
500: 'Internal Server Error',
|
|
1205
1387
|
};
|
|
1206
1388
|
return statusTexts[status] || 'Unknown';
|
|
1207
|
-
}
|
|
1389
|
+
}
|