@push.rocks/smartproxy 19.3.1 → 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/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/route-connection-handler.js +79 -19
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/proxies/smart-proxy/certificate-manager.ts +9 -8
- package/ts/proxies/smart-proxy/route-connection-handler.ts +264 -176
|
@@ -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,43 +734,59 @@ 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
|
-
|
|
744
|
+
|
|
731
745
|
let buffer = Buffer.alloc(0);
|
|
732
|
-
|
|
746
|
+
let processingData = false;
|
|
747
|
+
|
|
733
748
|
const handleHttpData = async (chunk: Buffer) => {
|
|
749
|
+
// Accumulate the data
|
|
734
750
|
buffer = Buffer.concat([buffer, chunk]);
|
|
735
|
-
|
|
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;
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
const processBuffer = async () => {
|
|
736
765
|
// Look for end of HTTP headers
|
|
737
766
|
const headerEndIndex = buffer.indexOf('\r\n\r\n');
|
|
738
767
|
if (headerEndIndex === -1) {
|
|
739
768
|
// Need more data
|
|
740
|
-
if (buffer.length > 8192) {
|
|
769
|
+
if (buffer.length > 8192) {
|
|
770
|
+
// Prevent excessive buffering
|
|
741
771
|
console.error(`[${connectionId}] HTTP headers too large`);
|
|
742
772
|
socket.end();
|
|
743
773
|
this.connectionManager.cleanupConnection(record, 'headers_too_large');
|
|
744
774
|
}
|
|
745
|
-
return;
|
|
775
|
+
return; // Wait for more data to arrive
|
|
746
776
|
}
|
|
747
|
-
|
|
777
|
+
|
|
748
778
|
// Parse the HTTP request
|
|
749
779
|
const headerBuffer = buffer.slice(0, headerEndIndex);
|
|
750
780
|
const headers = headerBuffer.toString();
|
|
751
781
|
const lines = headers.split('\r\n');
|
|
752
|
-
|
|
782
|
+
|
|
753
783
|
if (lines.length === 0) {
|
|
754
784
|
console.error(`[${connectionId}] Invalid HTTP request`);
|
|
755
785
|
socket.end();
|
|
756
786
|
this.connectionManager.cleanupConnection(record, 'invalid_request');
|
|
757
787
|
return;
|
|
758
788
|
}
|
|
759
|
-
|
|
789
|
+
|
|
760
790
|
// Parse request line
|
|
761
791
|
const requestLine = lines[0];
|
|
762
792
|
const requestParts = requestLine.split(' ');
|
|
@@ -766,9 +796,9 @@ export class RouteConnectionHandler {
|
|
|
766
796
|
this.connectionManager.cleanupConnection(record, 'invalid_request_line');
|
|
767
797
|
return;
|
|
768
798
|
}
|
|
769
|
-
|
|
799
|
+
|
|
770
800
|
const [method, path, httpVersion] = requestParts;
|
|
771
|
-
|
|
801
|
+
|
|
772
802
|
// Parse headers
|
|
773
803
|
const headersMap: Record<string, string> = {};
|
|
774
804
|
for (let i = 1; i < lines.length; i++) {
|
|
@@ -779,7 +809,29 @@ export class RouteConnectionHandler {
|
|
|
779
809
|
headersMap[key] = value;
|
|
780
810
|
}
|
|
781
811
|
}
|
|
782
|
-
|
|
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
|
+
|
|
783
835
|
// Extract query string if present
|
|
784
836
|
let pathname = path;
|
|
785
837
|
let query: string | undefined;
|
|
@@ -788,8 +840,20 @@ export class RouteConnectionHandler {
|
|
|
788
840
|
pathname = path.slice(0, queryIndex);
|
|
789
841
|
query = path.slice(queryIndex + 1);
|
|
790
842
|
}
|
|
791
|
-
|
|
843
|
+
|
|
792
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
|
+
|
|
793
857
|
// Build route context with parsed HTTP information
|
|
794
858
|
const context: IRouteContext = {
|
|
795
859
|
port: record.localPort,
|
|
@@ -803,66 +867,89 @@ export class RouteConnectionHandler {
|
|
|
803
867
|
isTls: record.isTLS,
|
|
804
868
|
tlsVersion: record.tlsVersion,
|
|
805
869
|
routeName: route.name,
|
|
806
|
-
routeId: route.
|
|
870
|
+
routeId: route.id,
|
|
807
871
|
timestamp: Date.now(),
|
|
808
|
-
connectionId
|
|
872
|
+
connectionId,
|
|
809
873
|
};
|
|
810
|
-
|
|
811
|
-
//
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
+
|
|
817
903
|
// Prepare the HTTP response
|
|
818
904
|
const responseHeaders = response.headers || {};
|
|
819
905
|
const contentLength = Buffer.byteLength(response.body || '');
|
|
820
906
|
responseHeaders['Content-Length'] = contentLength.toString();
|
|
821
|
-
|
|
907
|
+
|
|
822
908
|
if (!responseHeaders['Content-Type']) {
|
|
823
909
|
responseHeaders['Content-Type'] = 'text/plain';
|
|
824
910
|
}
|
|
825
|
-
|
|
911
|
+
|
|
826
912
|
// Build the response
|
|
827
913
|
let httpResponse = `HTTP/1.1 ${response.status} ${getStatusText(response.status)}\r\n`;
|
|
828
914
|
for (const [key, value] of Object.entries(responseHeaders)) {
|
|
829
915
|
httpResponse += `${key}: ${value}\r\n`;
|
|
830
916
|
}
|
|
831
917
|
httpResponse += '\r\n';
|
|
832
|
-
|
|
918
|
+
|
|
833
919
|
// Send response
|
|
834
920
|
socket.write(httpResponse);
|
|
835
921
|
if (response.body) {
|
|
836
922
|
socket.write(response.body);
|
|
837
923
|
}
|
|
838
924
|
socket.end();
|
|
839
|
-
|
|
925
|
+
|
|
840
926
|
this.connectionManager.cleanupConnection(record, 'completed');
|
|
841
927
|
} catch (error) {
|
|
842
928
|
console.error(`[${connectionId}] Error in static handler: ${error}`);
|
|
843
|
-
|
|
929
|
+
|
|
844
930
|
// Send error response
|
|
845
|
-
const errorResponse =
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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';
|
|
850
937
|
socket.write(errorResponse);
|
|
851
938
|
socket.end();
|
|
852
|
-
|
|
939
|
+
|
|
853
940
|
this.connectionManager.cleanupConnection(record, 'handler_error');
|
|
854
941
|
}
|
|
855
942
|
};
|
|
856
|
-
|
|
943
|
+
|
|
857
944
|
// Listen for data
|
|
858
945
|
socket.on('data', handleHttpData);
|
|
859
|
-
|
|
946
|
+
|
|
860
947
|
// Ensure cleanup on socket close
|
|
861
948
|
socket.once('close', () => {
|
|
862
949
|
socket.removeListener('data', handleHttpData);
|
|
863
950
|
});
|
|
864
951
|
}
|
|
865
|
-
|
|
952
|
+
|
|
866
953
|
/**
|
|
867
954
|
* Sets up a direct connection to the target
|
|
868
955
|
*/
|
|
@@ -878,22 +965,23 @@ export class RouteConnectionHandler {
|
|
|
878
965
|
const connectionId = record.id;
|
|
879
966
|
|
|
880
967
|
// Determine target host and port if not provided
|
|
881
|
-
const finalTargetHost =
|
|
882
|
-
record.targetHost ||
|
|
883
|
-
(this.settings.defaults?.target?.host || 'localhost');
|
|
968
|
+
const finalTargetHost =
|
|
969
|
+
targetHost || record.targetHost || this.settings.defaults?.target?.host || 'localhost';
|
|
884
970
|
|
|
885
971
|
// Determine target port
|
|
886
|
-
const finalTargetPort =
|
|
972
|
+
const finalTargetPort =
|
|
973
|
+
targetPort ||
|
|
887
974
|
record.targetPort ||
|
|
888
|
-
(overridePort !== undefined ? overridePort :
|
|
889
|
-
(this.settings.defaults?.target?.port || 443));
|
|
975
|
+
(overridePort !== undefined ? overridePort : this.settings.defaults?.target?.port || 443);
|
|
890
976
|
|
|
891
977
|
// Update record with final target information
|
|
892
978
|
record.targetHost = finalTargetHost;
|
|
893
979
|
record.targetPort = finalTargetPort;
|
|
894
980
|
|
|
895
981
|
if (this.settings.enableDetailedLogging) {
|
|
896
|
-
console.log(
|
|
982
|
+
console.log(
|
|
983
|
+
`[${connectionId}] Setting up direct connection to ${finalTargetHost}:${finalTargetPort}`
|
|
984
|
+
);
|
|
897
985
|
}
|
|
898
986
|
|
|
899
987
|
// Setup connection options
|
|
@@ -906,53 +994,53 @@ export class RouteConnectionHandler {
|
|
|
906
994
|
if (this.settings.defaults?.preserveSourceIP || this.settings.preserveSourceIP) {
|
|
907
995
|
connectionOptions.localAddress = record.remoteIP.replace('::ffff:', '');
|
|
908
996
|
}
|
|
909
|
-
|
|
997
|
+
|
|
910
998
|
// Create a safe queue for incoming data
|
|
911
999
|
const dataQueue: Buffer[] = [];
|
|
912
1000
|
let queueSize = 0;
|
|
913
1001
|
let processingQueue = false;
|
|
914
1002
|
let drainPending = false;
|
|
915
1003
|
let pipingEstablished = false;
|
|
916
|
-
|
|
1004
|
+
|
|
917
1005
|
// Pause the incoming socket to prevent buffer overflows
|
|
918
1006
|
socket.pause();
|
|
919
|
-
|
|
1007
|
+
|
|
920
1008
|
// Function to safely process the data queue without losing events
|
|
921
1009
|
const processDataQueue = () => {
|
|
922
1010
|
if (processingQueue || dataQueue.length === 0 || pipingEstablished) return;
|
|
923
|
-
|
|
1011
|
+
|
|
924
1012
|
processingQueue = true;
|
|
925
|
-
|
|
1013
|
+
|
|
926
1014
|
try {
|
|
927
1015
|
// Process all queued chunks with the current active handler
|
|
928
1016
|
while (dataQueue.length > 0) {
|
|
929
1017
|
const chunk = dataQueue.shift()!;
|
|
930
1018
|
queueSize -= chunk.length;
|
|
931
|
-
|
|
1019
|
+
|
|
932
1020
|
// Once piping is established, we shouldn't get here,
|
|
933
1021
|
// but just in case, pass to the outgoing socket directly
|
|
934
1022
|
if (pipingEstablished && record.outgoing) {
|
|
935
1023
|
record.outgoing.write(chunk);
|
|
936
1024
|
continue;
|
|
937
1025
|
}
|
|
938
|
-
|
|
1026
|
+
|
|
939
1027
|
// Track bytes received
|
|
940
1028
|
record.bytesReceived += chunk.length;
|
|
941
|
-
|
|
1029
|
+
|
|
942
1030
|
// Check for TLS handshake
|
|
943
1031
|
if (!record.isTLS && this.tlsManager.isTlsHandshake(chunk)) {
|
|
944
1032
|
record.isTLS = true;
|
|
945
|
-
|
|
1033
|
+
|
|
946
1034
|
if (this.settings.enableTlsDebugLogging) {
|
|
947
1035
|
console.log(
|
|
948
1036
|
`[${connectionId}] TLS handshake detected in tempDataHandler, ${chunk.length} bytes`
|
|
949
1037
|
);
|
|
950
1038
|
}
|
|
951
1039
|
}
|
|
952
|
-
|
|
1040
|
+
|
|
953
1041
|
// Check if adding this chunk would exceed the buffer limit
|
|
954
1042
|
const newSize = record.pendingDataSize + chunk.length;
|
|
955
|
-
|
|
1043
|
+
|
|
956
1044
|
if (this.settings.maxPendingDataSize && newSize > this.settings.maxPendingDataSize) {
|
|
957
1045
|
console.log(
|
|
958
1046
|
`[${connectionId}] Buffer limit exceeded for connection from ${record.remoteIP}: ${newSize} bytes > ${this.settings.maxPendingDataSize} bytes`
|
|
@@ -961,7 +1049,7 @@ export class RouteConnectionHandler {
|
|
|
961
1049
|
this.connectionManager.initiateCleanupOnce(record, 'buffer_limit_exceeded');
|
|
962
1050
|
return;
|
|
963
1051
|
}
|
|
964
|
-
|
|
1052
|
+
|
|
965
1053
|
// Buffer the chunk and update the size counter
|
|
966
1054
|
record.pendingData.push(Buffer.from(chunk));
|
|
967
1055
|
record.pendingDataSize = newSize;
|
|
@@ -969,7 +1057,7 @@ export class RouteConnectionHandler {
|
|
|
969
1057
|
}
|
|
970
1058
|
} finally {
|
|
971
1059
|
processingQueue = false;
|
|
972
|
-
|
|
1060
|
+
|
|
973
1061
|
// If there's a pending drain and we've processed everything,
|
|
974
1062
|
// signal we're ready for more data if we haven't established piping yet
|
|
975
1063
|
if (drainPending && dataQueue.length === 0 && !pipingEstablished) {
|
|
@@ -978,48 +1066,48 @@ export class RouteConnectionHandler {
|
|
|
978
1066
|
}
|
|
979
1067
|
}
|
|
980
1068
|
};
|
|
981
|
-
|
|
1069
|
+
|
|
982
1070
|
// Unified data handler that safely queues incoming data
|
|
983
1071
|
const safeDataHandler = (chunk: Buffer) => {
|
|
984
1072
|
// If piping is already established, just let the pipe handle it
|
|
985
1073
|
if (pipingEstablished) return;
|
|
986
|
-
|
|
1074
|
+
|
|
987
1075
|
// Add to our queue for orderly processing
|
|
988
1076
|
dataQueue.push(Buffer.from(chunk)); // Make a copy to be safe
|
|
989
1077
|
queueSize += chunk.length;
|
|
990
|
-
|
|
1078
|
+
|
|
991
1079
|
// If queue is getting large, pause socket until we catch up
|
|
992
1080
|
if (this.settings.maxPendingDataSize && queueSize > this.settings.maxPendingDataSize * 0.8) {
|
|
993
1081
|
socket.pause();
|
|
994
1082
|
drainPending = true;
|
|
995
1083
|
}
|
|
996
|
-
|
|
1084
|
+
|
|
997
1085
|
// Process the queue
|
|
998
1086
|
processDataQueue();
|
|
999
1087
|
};
|
|
1000
|
-
|
|
1088
|
+
|
|
1001
1089
|
// Add our safe data handler
|
|
1002
1090
|
socket.on('data', safeDataHandler);
|
|
1003
|
-
|
|
1091
|
+
|
|
1004
1092
|
// Add initial chunk to pending data if present
|
|
1005
1093
|
if (initialChunk) {
|
|
1006
1094
|
record.bytesReceived += initialChunk.length;
|
|
1007
1095
|
record.pendingData.push(Buffer.from(initialChunk));
|
|
1008
1096
|
record.pendingDataSize = initialChunk.length;
|
|
1009
1097
|
}
|
|
1010
|
-
|
|
1098
|
+
|
|
1011
1099
|
// Create the target socket but don't set up piping immediately
|
|
1012
1100
|
const targetSocket = plugins.net.connect(connectionOptions);
|
|
1013
1101
|
record.outgoing = targetSocket;
|
|
1014
1102
|
record.outgoingStartTime = Date.now();
|
|
1015
|
-
|
|
1103
|
+
|
|
1016
1104
|
// Apply socket optimizations
|
|
1017
1105
|
targetSocket.setNoDelay(this.settings.noDelay);
|
|
1018
|
-
|
|
1106
|
+
|
|
1019
1107
|
// Apply keep-alive settings to the outgoing connection as well
|
|
1020
1108
|
if (this.settings.keepAlive) {
|
|
1021
1109
|
targetSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
1022
|
-
|
|
1110
|
+
|
|
1023
1111
|
// Apply enhanced TCP keep-alive options if enabled
|
|
1024
1112
|
if (this.settings.enableKeepAliveProbes) {
|
|
1025
1113
|
try {
|
|
@@ -1039,7 +1127,7 @@ export class RouteConnectionHandler {
|
|
|
1039
1127
|
}
|
|
1040
1128
|
}
|
|
1041
1129
|
}
|
|
1042
|
-
|
|
1130
|
+
|
|
1043
1131
|
// Setup specific error handler for connection phase
|
|
1044
1132
|
targetSocket.once('error', (err) => {
|
|
1045
1133
|
// This handler runs only once during the initial connection phase
|
|
@@ -1047,10 +1135,10 @@ export class RouteConnectionHandler {
|
|
|
1047
1135
|
console.log(
|
|
1048
1136
|
`[${connectionId}] Connection setup error to ${finalTargetHost}:${connectionOptions.port}: ${err.message} (${code})`
|
|
1049
1137
|
);
|
|
1050
|
-
|
|
1138
|
+
|
|
1051
1139
|
// Resume the incoming socket to prevent it from hanging
|
|
1052
1140
|
socket.resume();
|
|
1053
|
-
|
|
1141
|
+
|
|
1054
1142
|
if (code === 'ECONNREFUSED') {
|
|
1055
1143
|
console.log(
|
|
1056
1144
|
`[${connectionId}] Target ${finalTargetHost}:${connectionOptions.port} refused connection`
|
|
@@ -1066,28 +1154,28 @@ export class RouteConnectionHandler {
|
|
|
1066
1154
|
} else if (code === 'EHOSTUNREACH') {
|
|
1067
1155
|
console.log(`[${connectionId}] Host ${finalTargetHost} is unreachable`);
|
|
1068
1156
|
}
|
|
1069
|
-
|
|
1157
|
+
|
|
1070
1158
|
// Clear any existing error handler after connection phase
|
|
1071
1159
|
targetSocket.removeAllListeners('error');
|
|
1072
|
-
|
|
1160
|
+
|
|
1073
1161
|
// Re-add the normal error handler for established connections
|
|
1074
1162
|
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
|
1075
|
-
|
|
1163
|
+
|
|
1076
1164
|
if (record.outgoingTerminationReason === null) {
|
|
1077
1165
|
record.outgoingTerminationReason = 'connection_failed';
|
|
1078
1166
|
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
|
|
1079
1167
|
}
|
|
1080
|
-
|
|
1168
|
+
|
|
1081
1169
|
// Route-based configuration doesn't use domain handlers
|
|
1082
|
-
|
|
1170
|
+
|
|
1083
1171
|
// Clean up the connection
|
|
1084
1172
|
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
|
|
1085
1173
|
});
|
|
1086
|
-
|
|
1174
|
+
|
|
1087
1175
|
// Setup close handler
|
|
1088
1176
|
targetSocket.on('close', this.connectionManager.handleClose('outgoing', record));
|
|
1089
1177
|
socket.on('close', this.connectionManager.handleClose('incoming', record));
|
|
1090
|
-
|
|
1178
|
+
|
|
1091
1179
|
// Handle timeouts with keep-alive awareness
|
|
1092
1180
|
socket.on('timeout', () => {
|
|
1093
1181
|
// For keep-alive connections, just log a warning instead of closing
|
|
@@ -1101,7 +1189,7 @@ export class RouteConnectionHandler {
|
|
|
1101
1189
|
);
|
|
1102
1190
|
return;
|
|
1103
1191
|
}
|
|
1104
|
-
|
|
1192
|
+
|
|
1105
1193
|
// For non-keep-alive connections, proceed with normal cleanup
|
|
1106
1194
|
console.log(
|
|
1107
1195
|
`[${connectionId}] Timeout on incoming side from ${
|
|
@@ -1114,7 +1202,7 @@ export class RouteConnectionHandler {
|
|
|
1114
1202
|
}
|
|
1115
1203
|
this.connectionManager.initiateCleanupOnce(record, 'timeout_incoming');
|
|
1116
1204
|
});
|
|
1117
|
-
|
|
1205
|
+
|
|
1118
1206
|
targetSocket.on('timeout', () => {
|
|
1119
1207
|
// For keep-alive connections, just log a warning instead of closing
|
|
1120
1208
|
if (record.hasKeepAlive) {
|
|
@@ -1127,7 +1215,7 @@ export class RouteConnectionHandler {
|
|
|
1127
1215
|
);
|
|
1128
1216
|
return;
|
|
1129
1217
|
}
|
|
1130
|
-
|
|
1218
|
+
|
|
1131
1219
|
// For non-keep-alive connections, proceed with normal cleanup
|
|
1132
1220
|
console.log(
|
|
1133
1221
|
`[${connectionId}] Timeout on outgoing side from ${
|
|
@@ -1140,40 +1228,40 @@ export class RouteConnectionHandler {
|
|
|
1140
1228
|
}
|
|
1141
1229
|
this.connectionManager.initiateCleanupOnce(record, 'timeout_outgoing');
|
|
1142
1230
|
});
|
|
1143
|
-
|
|
1231
|
+
|
|
1144
1232
|
// Apply socket timeouts
|
|
1145
1233
|
this.timeoutManager.applySocketTimeouts(record);
|
|
1146
|
-
|
|
1234
|
+
|
|
1147
1235
|
// Track outgoing data for bytes counting
|
|
1148
1236
|
targetSocket.on('data', (chunk: Buffer) => {
|
|
1149
1237
|
record.bytesSent += chunk.length;
|
|
1150
1238
|
this.timeoutManager.updateActivity(record);
|
|
1151
1239
|
});
|
|
1152
|
-
|
|
1240
|
+
|
|
1153
1241
|
// Wait for the outgoing connection to be ready before setting up piping
|
|
1154
1242
|
targetSocket.once('connect', () => {
|
|
1155
1243
|
// Clear the initial connection error handler
|
|
1156
1244
|
targetSocket.removeAllListeners('error');
|
|
1157
|
-
|
|
1245
|
+
|
|
1158
1246
|
// Add the normal error handler for established connections
|
|
1159
1247
|
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
|
1160
|
-
|
|
1248
|
+
|
|
1161
1249
|
// Process any remaining data in the queue before switching to piping
|
|
1162
1250
|
processDataQueue();
|
|
1163
|
-
|
|
1251
|
+
|
|
1164
1252
|
// Set up piping immediately
|
|
1165
1253
|
pipingEstablished = true;
|
|
1166
|
-
|
|
1254
|
+
|
|
1167
1255
|
// Flush all pending data to target
|
|
1168
1256
|
if (record.pendingData.length > 0) {
|
|
1169
1257
|
const combinedData = Buffer.concat(record.pendingData);
|
|
1170
|
-
|
|
1258
|
+
|
|
1171
1259
|
if (this.settings.enableDetailedLogging) {
|
|
1172
1260
|
console.log(
|
|
1173
1261
|
`[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`
|
|
1174
1262
|
);
|
|
1175
1263
|
}
|
|
1176
|
-
|
|
1264
|
+
|
|
1177
1265
|
// Write pending data immediately
|
|
1178
1266
|
targetSocket.write(combinedData, (err) => {
|
|
1179
1267
|
if (err) {
|
|
@@ -1181,19 +1269,19 @@ export class RouteConnectionHandler {
|
|
|
1181
1269
|
return this.connectionManager.initiateCleanupOnce(record, 'write_error');
|
|
1182
1270
|
}
|
|
1183
1271
|
});
|
|
1184
|
-
|
|
1272
|
+
|
|
1185
1273
|
// Clear the buffer now that we've processed it
|
|
1186
1274
|
record.pendingData = [];
|
|
1187
1275
|
record.pendingDataSize = 0;
|
|
1188
1276
|
}
|
|
1189
|
-
|
|
1277
|
+
|
|
1190
1278
|
// Setup piping in both directions without any delays
|
|
1191
1279
|
socket.pipe(targetSocket);
|
|
1192
1280
|
targetSocket.pipe(socket);
|
|
1193
|
-
|
|
1281
|
+
|
|
1194
1282
|
// Resume the socket to ensure data flows
|
|
1195
1283
|
socket.resume();
|
|
1196
|
-
|
|
1284
|
+
|
|
1197
1285
|
// Process any data that might be queued in the interim
|
|
1198
1286
|
if (dataQueue.length > 0) {
|
|
1199
1287
|
// Write any remaining queued data directly to the target socket
|
|
@@ -1204,7 +1292,7 @@ export class RouteConnectionHandler {
|
|
|
1204
1292
|
dataQueue.length = 0;
|
|
1205
1293
|
queueSize = 0;
|
|
1206
1294
|
}
|
|
1207
|
-
|
|
1295
|
+
|
|
1208
1296
|
if (this.settings.enableDetailedLogging) {
|
|
1209
1297
|
console.log(
|
|
1210
1298
|
`[${connectionId}] Connection established: ${record.remoteIP} -> ${finalTargetHost}:${connectionOptions.port}` +
|
|
@@ -1231,7 +1319,7 @@ export class RouteConnectionHandler {
|
|
|
1231
1319
|
}`
|
|
1232
1320
|
);
|
|
1233
1321
|
}
|
|
1234
|
-
|
|
1322
|
+
|
|
1235
1323
|
// Add the renegotiation handler for SNI validation
|
|
1236
1324
|
if (serverName) {
|
|
1237
1325
|
// Create connection info object for the existing connection
|
|
@@ -1241,7 +1329,7 @@ export class RouteConnectionHandler {
|
|
|
1241
1329
|
destIp: record.incoming.localAddress || '',
|
|
1242
1330
|
destPort: record.incoming.localPort || 0,
|
|
1243
1331
|
};
|
|
1244
|
-
|
|
1332
|
+
|
|
1245
1333
|
// Create a renegotiation handler function
|
|
1246
1334
|
const renegotiationHandler = this.tlsManager.createRenegotiationHandler(
|
|
1247
1335
|
connectionId,
|
|
@@ -1249,13 +1337,13 @@ export class RouteConnectionHandler {
|
|
|
1249
1337
|
connInfo,
|
|
1250
1338
|
(connectionId, reason) => this.connectionManager.initiateCleanupOnce(record, reason)
|
|
1251
1339
|
);
|
|
1252
|
-
|
|
1340
|
+
|
|
1253
1341
|
// Store the handler in the connection record so we can remove it during cleanup
|
|
1254
1342
|
record.renegotiationHandler = renegotiationHandler;
|
|
1255
|
-
|
|
1343
|
+
|
|
1256
1344
|
// Add the handler to the socket
|
|
1257
1345
|
socket.on('data', renegotiationHandler);
|
|
1258
|
-
|
|
1346
|
+
|
|
1259
1347
|
if (this.settings.enableDetailedLogging) {
|
|
1260
1348
|
console.log(
|
|
1261
1349
|
`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`
|
|
@@ -1267,7 +1355,7 @@ export class RouteConnectionHandler {
|
|
|
1267
1355
|
}
|
|
1268
1356
|
}
|
|
1269
1357
|
}
|
|
1270
|
-
|
|
1358
|
+
|
|
1271
1359
|
// Set connection timeout
|
|
1272
1360
|
record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(record, (record, reason) => {
|
|
1273
1361
|
console.log(
|
|
@@ -1275,11 +1363,11 @@ export class RouteConnectionHandler {
|
|
|
1275
1363
|
);
|
|
1276
1364
|
this.connectionManager.initiateCleanupOnce(record, reason);
|
|
1277
1365
|
});
|
|
1278
|
-
|
|
1366
|
+
|
|
1279
1367
|
// Mark TLS handshake as complete for TLS connections
|
|
1280
1368
|
if (record.isTLS) {
|
|
1281
1369
|
record.tlsHandshakeComplete = true;
|
|
1282
|
-
|
|
1370
|
+
|
|
1283
1371
|
if (this.settings.enableTlsDebugLogging) {
|
|
1284
1372
|
console.log(
|
|
1285
1373
|
`[${connectionId}] TLS handshake complete for connection from ${record.remoteIP}`
|
|
@@ -1295,7 +1383,7 @@ function getStatusText(status: number): string {
|
|
|
1295
1383
|
const statusTexts: Record<number, string> = {
|
|
1296
1384
|
200: 'OK',
|
|
1297
1385
|
404: 'Not Found',
|
|
1298
|
-
500: 'Internal Server Error'
|
|
1386
|
+
500: 'Internal Server Error',
|
|
1299
1387
|
};
|
|
1300
1388
|
return statusTexts[status] || 'Unknown';
|
|
1301
|
-
}
|
|
1389
|
+
}
|