@push.rocks/smartproxy 19.5.19 → 19.5.21

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.
Files changed (110) hide show
  1. package/dist_ts/core/models/index.d.ts +2 -0
  2. package/dist_ts/core/models/index.js +3 -1
  3. package/dist_ts/core/models/socket-types.d.ts +14 -0
  4. package/dist_ts/core/models/socket-types.js +15 -0
  5. package/dist_ts/core/models/wrapped-socket.d.ts +34 -0
  6. package/dist_ts/core/models/wrapped-socket.js +82 -0
  7. package/dist_ts/core/routing/index.d.ts +11 -0
  8. package/dist_ts/core/routing/index.js +17 -0
  9. package/dist_ts/core/routing/matchers/domain.d.ts +34 -0
  10. package/dist_ts/core/routing/matchers/domain.js +91 -0
  11. package/dist_ts/core/routing/matchers/header.d.ts +32 -0
  12. package/dist_ts/core/routing/matchers/header.js +94 -0
  13. package/dist_ts/core/routing/matchers/index.d.ts +18 -0
  14. package/dist_ts/core/routing/matchers/index.js +20 -0
  15. package/dist_ts/core/routing/matchers/ip.d.ts +53 -0
  16. package/dist_ts/core/routing/matchers/ip.js +169 -0
  17. package/dist_ts/core/routing/matchers/path.d.ts +44 -0
  18. package/dist_ts/core/routing/matchers/path.js +148 -0
  19. package/dist_ts/core/routing/route-manager.d.ts +88 -0
  20. package/dist_ts/core/routing/route-manager.js +342 -0
  21. package/dist_ts/core/routing/route-utils.d.ts +28 -0
  22. package/dist_ts/core/routing/route-utils.js +67 -0
  23. package/dist_ts/core/routing/specificity.d.ts +30 -0
  24. package/dist_ts/core/routing/specificity.js +115 -0
  25. package/dist_ts/core/routing/types.d.ts +41 -0
  26. package/dist_ts/core/routing/types.js +5 -0
  27. package/dist_ts/core/utils/index.d.ts +1 -2
  28. package/dist_ts/core/utils/index.js +2 -3
  29. package/dist_ts/core/utils/proxy-protocol.d.ts +45 -0
  30. package/dist_ts/core/utils/proxy-protocol.js +201 -0
  31. package/dist_ts/core/utils/route-manager.d.ts +0 -30
  32. package/dist_ts/core/utils/route-manager.js +6 -47
  33. package/dist_ts/core/utils/route-utils.d.ts +2 -68
  34. package/dist_ts/core/utils/route-utils.js +21 -218
  35. package/dist_ts/core/utils/security-utils.js +4 -4
  36. package/dist_ts/index.d.ts +2 -5
  37. package/dist_ts/index.js +5 -11
  38. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +0 -1
  39. package/dist_ts/proxies/http-proxy/http-proxy.js +15 -60
  40. package/dist_ts/proxies/http-proxy/models/types.d.ts +0 -90
  41. package/dist_ts/proxies/http-proxy/models/types.js +1 -242
  42. package/dist_ts/proxies/http-proxy/request-handler.d.ts +3 -5
  43. package/dist_ts/proxies/http-proxy/request-handler.js +20 -171
  44. package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +2 -5
  45. package/dist_ts/proxies/http-proxy/websocket-handler.js +15 -23
  46. package/dist_ts/proxies/index.d.ts +2 -2
  47. package/dist_ts/proxies/index.js +4 -3
  48. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +3 -1
  49. package/dist_ts/proxies/smart-proxy/connection-manager.js +17 -7
  50. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +2 -1
  51. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +5 -2
  52. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
  53. package/dist_ts/proxies/smart-proxy/index.js +2 -2
  54. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +7 -2
  55. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +1 -0
  56. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  57. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +1 -1
  58. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +155 -35
  59. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -1
  60. package/dist_ts/proxies/smart-proxy/smart-proxy.js +15 -4
  61. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +10 -43
  62. package/dist_ts/routing/router/http-router.d.ts +89 -0
  63. package/dist_ts/routing/router/http-router.js +205 -0
  64. package/dist_ts/routing/router/index.d.ts +2 -5
  65. package/dist_ts/routing/router/index.js +3 -4
  66. package/package.json +1 -1
  67. package/readme.delete.md +187 -0
  68. package/readme.hints.md +196 -1
  69. package/readme.plan.md +625 -0
  70. package/readme.proxy-chain-summary.md +112 -0
  71. package/readme.proxy-protocol-example.md +462 -0
  72. package/readme.proxy-protocol.md +415 -0
  73. package/readme.routing.md +341 -0
  74. package/ts/core/models/index.ts +2 -0
  75. package/ts/core/models/socket-types.ts +21 -0
  76. package/ts/core/models/wrapped-socket.ts +99 -0
  77. package/ts/core/routing/index.ts +21 -0
  78. package/ts/core/routing/matchers/domain.ts +119 -0
  79. package/ts/core/routing/matchers/header.ts +120 -0
  80. package/ts/core/routing/matchers/index.ts +22 -0
  81. package/ts/core/routing/matchers/ip.ts +207 -0
  82. package/ts/core/routing/matchers/path.ts +184 -0
  83. package/ts/core/{utils → routing}/route-manager.ts +7 -57
  84. package/ts/core/routing/route-utils.ts +88 -0
  85. package/ts/core/routing/specificity.ts +141 -0
  86. package/ts/core/routing/types.ts +49 -0
  87. package/ts/core/utils/index.ts +1 -2
  88. package/ts/core/utils/proxy-protocol.ts +246 -0
  89. package/ts/core/utils/security-utils.ts +3 -7
  90. package/ts/index.ts +4 -14
  91. package/ts/proxies/http-proxy/http-proxy.ts +13 -68
  92. package/ts/proxies/http-proxy/models/types.ts +0 -324
  93. package/ts/proxies/http-proxy/request-handler.ts +15 -186
  94. package/ts/proxies/http-proxy/websocket-handler.ts +15 -26
  95. package/ts/proxies/index.ts +3 -2
  96. package/ts/proxies/smart-proxy/connection-manager.ts +17 -7
  97. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +6 -2
  98. package/ts/proxies/smart-proxy/index.ts +1 -1
  99. package/ts/proxies/smart-proxy/models/interfaces.ts +9 -2
  100. package/ts/proxies/smart-proxy/models/route-types.ts +3 -0
  101. package/ts/proxies/smart-proxy/route-connection-handler.ts +173 -42
  102. package/ts/proxies/smart-proxy/smart-proxy.ts +15 -3
  103. package/ts/proxies/smart-proxy/utils/route-utils.ts +11 -49
  104. package/ts/routing/router/http-router.ts +266 -0
  105. package/ts/routing/router/index.ts +3 -8
  106. package/readme.problems.md +0 -170
  107. package/ts/core/utils/route-utils.ts +0 -312
  108. package/ts/proxies/smart-proxy/route-manager.ts +0 -554
  109. package/ts/routing/router/proxy-router.ts +0 -437
  110. package/ts/routing/router/route-router.ts +0 -482
@@ -4,11 +4,9 @@ import {
4
4
  type IHttpProxyOptions,
5
5
  type ILogger,
6
6
  createLogger,
7
- type IReverseProxyConfig,
8
- RouteManager
9
7
  } from './models/types.js';
8
+ import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
10
9
  import { ConnectionPool } from './connection-pool.js';
11
- import { ProxyRouter } from '../../routing/router/index.js';
12
10
  import { ContextCreator } from './context-creator.js';
13
11
  import { HttpRequestHandler } from './http-request-handler.js';
14
12
  import { Http2RequestHandler } from './http2-request-handler.js';
@@ -48,10 +46,9 @@ export class RequestHandler {
48
46
  constructor(
49
47
  private options: IHttpProxyOptions,
50
48
  private connectionPool: ConnectionPool,
51
- private legacyRouter: ProxyRouter, // Legacy router for backward compatibility
52
49
  private routeManager?: RouteManager,
53
50
  private functionCache?: any, // FunctionCache - using any to avoid circular dependency
54
- private router?: any // RouteRouter - using any to avoid circular dependency
51
+ private router?: any // HttpRouter - using any to avoid circular dependency
55
52
  ) {
56
53
  this.logger = createLogger(options.logLevel || 'info');
57
54
  this.securityManager = new SecurityManager(this.logger);
@@ -373,7 +370,8 @@ export class RequestHandler {
373
370
  tlsVersion: req.socket.getTLSVersion?.() || undefined
374
371
  });
375
372
 
376
- matchingRoute = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
373
+ const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
374
+ matchingRoute = matchResult?.route || null;
377
375
  } catch (err) {
378
376
  this.logger.error('Error finding matching route', err);
379
377
  }
@@ -581,86 +579,11 @@ export class RequestHandler {
581
579
  }
582
580
  }
583
581
 
584
- // Try modern router first, then fall back to legacy routing if needed
585
- if (this.router) {
586
- try {
587
- // Try to find a matching route using the modern router
588
- const route = this.router.routeReq(req);
589
- if (route && route.action.type === 'forward' && route.action.target) {
590
- // Handle this route similarly to RouteManager logic
591
- this.logger.debug(`Found matching route via modern router: ${route.name || 'unnamed'}`);
592
-
593
- // No need to do anything here, we'll continue with legacy routing
594
- // The routeManager would have already found this route if applicable
595
- }
596
- } catch (err) {
597
- this.logger.error('Error using modern router', err);
598
- // Continue with legacy routing
599
- }
600
- }
601
-
602
- // Fall back to legacy routing if no matching route found via RouteManager
603
- let proxyConfig: IReverseProxyConfig | undefined;
604
- try {
605
- proxyConfig = this.legacyRouter.routeReq(req);
606
- } catch (err) {
607
- this.logger.error('Error routing request with legacy router', err);
608
- res.statusCode = 500;
609
- res.end('Internal Server Error');
610
- if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
611
- return;
612
- }
613
- if (!proxyConfig) {
614
- this.logger.warn(`No proxy configuration for host: ${req.headers.host}`);
615
- res.statusCode = 404;
616
- res.end('Not Found: No proxy configuration for this host');
617
- if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
618
- return;
619
- }
620
- // Determine protocol to backend (per-domain override or global)
621
- const backendProto = proxyConfig.backendProtocol || this.options.backendProtocol;
622
- if (backendProto === 'http2') {
623
- const destination = this.connectionPool.getNextTarget(
624
- proxyConfig.destinationIps,
625
- proxyConfig.destinationPorts[0]
626
- );
627
- const key = `${destination.host}:${destination.port}`;
628
- let session = this.h2Sessions.get(key);
629
- if (!session || session.closed || (session as any).destroyed) {
630
- session = plugins.http2.connect(`http://${destination.host}:${destination.port}`);
631
- this.h2Sessions.set(key, session);
632
- session.on('error', () => this.h2Sessions.delete(key));
633
- session.on('close', () => this.h2Sessions.delete(key));
634
- }
635
- // Build headers for HTTP/2 request
636
- const hdrs: Record<string, any> = {
637
- ':method': req.method,
638
- ':path': req.url,
639
- ':authority': `${destination.host}:${destination.port}`
640
- };
641
- for (const [hk, hv] of Object.entries(req.headers)) {
642
- if (typeof hv === 'string') hdrs[hk] = hv;
643
- }
644
- const h2Stream = session.request(hdrs);
645
- req.pipe(h2Stream);
646
- h2Stream.on('response', (hdrs2: any) => {
647
- const status = (hdrs2[':status'] as number) || 502;
648
- res.statusCode = status;
649
- // Copy headers from HTTP/2 response to HTTP/1 response
650
- for (const [hk, hv] of Object.entries(hdrs2)) {
651
- if (!hk.startsWith(':') && hv != null) {
652
- res.setHeader(hk, hv as string | string[]);
653
- }
654
- }
655
- h2Stream.pipe(res);
656
- });
657
- h2Stream.on('error', (err) => {
658
- res.statusCode = 502;
659
- res.end(`Bad Gateway: ${err.message}`);
660
- if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
661
- });
662
- return;
663
- }
582
+ // If no route was found, return 404
583
+ this.logger.warn(`No route configuration for host: ${req.headers.host}`);
584
+ res.statusCode = 404;
585
+ res.end('Not Found: No route configuration for this host');
586
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
664
587
  }
665
588
 
666
589
  /**
@@ -688,7 +611,8 @@ export class RequestHandler {
688
611
  let matchingRoute: IRouteConfig | null = null;
689
612
  if (this.routeManager) {
690
613
  try {
691
- matchingRoute = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
614
+ const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
615
+ matchingRoute = matchResult?.route || null;
692
616
  } catch (err) {
693
617
  this.logger.error('Error finding matching route for HTTP/2 request', err);
694
618
  }
@@ -812,104 +736,9 @@ export class RequestHandler {
812
736
  const method = headers[':method'] || 'GET';
813
737
  const path = headers[':path'] || '/';
814
738
 
815
- // If configured to proxy to backends over HTTP/2, use HTTP/2 client sessions
816
- if (this.options.backendProtocol === 'http2') {
817
- const authority = headers[':authority'] as string || '';
818
- const host = authority.split(':')[0];
819
- const fakeReq: any = {
820
- headers: { host },
821
- method: headers[':method'],
822
- url: headers[':path'],
823
- socket: (stream.session as any).socket
824
- };
825
- // Try modern router first if available
826
- let route;
827
- if (this.router) {
828
- try {
829
- route = this.router.routeReq(fakeReq);
830
- if (route && route.action.type === 'forward' && route.action.target) {
831
- this.logger.debug(`Found matching HTTP/2 route via modern router: ${route.name || 'unnamed'}`);
832
- // The routeManager would have already found this route if applicable
833
- }
834
- } catch (err) {
835
- this.logger.error('Error using modern router for HTTP/2', err);
836
- }
837
- }
838
-
839
- // Fall back to legacy routing
840
- const proxyConfig = this.legacyRouter.routeReq(fakeReq);
841
- if (!proxyConfig) {
842
- stream.respond({ ':status': 404 });
843
- stream.end('Not Found');
844
- if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
845
- return;
846
- }
847
- const destination = this.connectionPool.getNextTarget(proxyConfig.destinationIps, proxyConfig.destinationPorts[0]);
848
-
849
- // Use the helper for HTTP/2 to HTTP/2 routing
850
- return Http2RequestHandler.handleHttp2WithHttp2Destination(
851
- stream,
852
- headers,
853
- destination,
854
- routeContext,
855
- this.h2Sessions,
856
- this.logger,
857
- this.metricsTracker
858
- );
859
- }
860
-
861
- try {
862
- // Determine host for routing
863
- const authority = headers[':authority'] as string || '';
864
- const host = authority.split(':')[0];
865
- // Fake request object for routing
866
- const fakeReq: any = {
867
- headers: { host },
868
- method,
869
- url: path,
870
- socket: (stream.session as any).socket
871
- };
872
- // Try modern router first if available
873
- if (this.router) {
874
- try {
875
- const route = this.router.routeReq(fakeReq);
876
- if (route && route.action.type === 'forward' && route.action.target) {
877
- this.logger.debug(`Found matching HTTP/2 route via modern router: ${route.name || 'unnamed'}`);
878
- // The routeManager would have already found this route if applicable
879
- }
880
- } catch (err) {
881
- this.logger.error('Error using modern router for HTTP/2', err);
882
- }
883
- }
884
-
885
- // Fall back to legacy routing
886
- const proxyConfig = this.legacyRouter.routeReq(fakeReq as any);
887
- if (!proxyConfig) {
888
- stream.respond({ ':status': 404 });
889
- stream.end('Not Found');
890
- if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
891
- return;
892
- }
893
-
894
- // Select backend target
895
- const destination = this.connectionPool.getNextTarget(
896
- proxyConfig.destinationIps,
897
- proxyConfig.destinationPorts[0]
898
- );
899
-
900
- // Use the helper for HTTP/2 to HTTP/1 routing
901
- return Http2RequestHandler.handleHttp2WithHttp1Destination(
902
- stream,
903
- headers,
904
- destination,
905
- routeContext,
906
- this.logger,
907
- this.metricsTracker
908
- );
909
- } catch (err: any) {
910
- stream.respond({ ':status': 500 });
911
- stream.end('Internal Server Error');
912
- if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
913
- }
739
+ // No route was found
740
+ stream.respond({ ':status': 404 });
741
+ stream.end('Not Found: No route configuration for this request');
742
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
914
743
  }
915
744
  }
@@ -1,8 +1,8 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import '../../core/models/socket-augmentation.js';
3
- import { type IHttpProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger, type IReverseProxyConfig } from './models/types.js';
3
+ import { type IHttpProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger } from './models/types.js';
4
4
  import { ConnectionPool } from './connection-pool.js';
5
- import { ProxyRouter, RouteRouter } from '../../routing/router/index.js';
5
+ import { HttpRouter } from '../../routing/router/index.js';
6
6
  import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
7
7
  import type { IRouteContext } from '../../core/models/route-context.js';
8
8
  import { toBaseContext } from '../../core/models/route-context.js';
@@ -19,21 +19,20 @@ export class WebSocketHandler {
19
19
  private wsServer: plugins.ws.WebSocketServer | null = null;
20
20
  private logger: ILogger;
21
21
  private contextCreator: ContextCreator = new ContextCreator();
22
- private routeRouter: RouteRouter | null = null;
22
+ private router: HttpRouter | null = null;
23
23
  private securityManager: SecurityManager;
24
24
 
25
25
  constructor(
26
26
  private options: IHttpProxyOptions,
27
27
  private connectionPool: ConnectionPool,
28
- private legacyRouter: ProxyRouter, // Legacy router for backward compatibility
29
- private routes: IRouteConfig[] = [] // Routes for modern router
28
+ private routes: IRouteConfig[] = []
30
29
  ) {
31
30
  this.logger = createLogger(options.logLevel || 'info');
32
31
  this.securityManager = new SecurityManager(this.logger, routes);
33
32
 
34
- // Initialize modern router if we have routes
33
+ // Initialize router if we have routes
35
34
  if (routes.length > 0) {
36
- this.routeRouter = new RouteRouter(routes, this.logger);
35
+ this.router = new HttpRouter(routes, this.logger);
37
36
  }
38
37
  }
39
38
 
@@ -44,10 +43,10 @@ export class WebSocketHandler {
44
43
  this.routes = routes;
45
44
 
46
45
  // Initialize or update the route router
47
- if (!this.routeRouter) {
48
- this.routeRouter = new RouteRouter(routes, this.logger);
46
+ if (!this.router) {
47
+ this.router = new HttpRouter(routes, this.logger);
49
48
  } else {
50
- this.routeRouter.setRoutes(routes);
49
+ this.router.setRoutes(routes);
51
50
  }
52
51
 
53
52
  // Update the security manager
@@ -139,8 +138,8 @@ export class WebSocketHandler {
139
138
 
140
139
  // Try modern router first if available
141
140
  let route: IRouteConfig | undefined;
142
- if (this.routeRouter) {
143
- route = this.routeRouter.routeReq(req);
141
+ if (this.router) {
142
+ route = this.router.routeReq(req);
144
143
  }
145
144
 
146
145
  // Define destination variables
@@ -227,20 +226,10 @@ export class WebSocketHandler {
227
226
  return;
228
227
  }
229
228
  } else {
230
- // Fall back to legacy routing if no matching route found via modern router
231
- const proxyConfig = this.legacyRouter.routeReq(req);
232
-
233
- if (!proxyConfig) {
234
- this.logger.warn(`No proxy configuration for WebSocket host: ${req.headers.host}`);
235
- wsIncoming.close(1008, 'No proxy configuration for this host');
236
- return;
237
- }
238
-
239
- // Get destination target using round-robin if multiple targets
240
- destination = this.connectionPool.getNextTarget(
241
- proxyConfig.destinationIps,
242
- proxyConfig.destinationPorts[0]
243
- );
229
+ // No route found
230
+ this.logger.warn(`No route configuration for WebSocket host: ${req.headers.host}`);
231
+ wsIncoming.close(1008, 'No route configuration for this host');
232
+ return;
244
233
  }
245
234
 
246
235
  // Build target URL with potential path rewriting
@@ -7,11 +7,12 @@ export { HttpProxy, CertificateManager, ConnectionPool, RequestHandler, WebSocke
7
7
  export type { IMetricsTracker, MetricsTracker } from './http-proxy/index.js';
8
8
  // Export http-proxy models except IAcmeOptions
9
9
  export type { IHttpProxyOptions, ICertificateEntry, ILogger } from './http-proxy/models/types.js';
10
- export { RouteManager as HttpProxyRouteManager } from './http-proxy/models/types.js';
10
+ // RouteManager has been unified - use SharedRouteManager from core/routing
11
+ export { SharedRouteManager as HttpProxyRouteManager } from '../core/routing/route-manager.js';
11
12
 
12
13
  // Export SmartProxy with selective imports to avoid conflicts
13
14
  export { SmartProxy, ConnectionManager, SecurityManager, TimeoutManager, TlsManager, HttpProxyBridge, RouteConnectionHandler } from './smart-proxy/index.js';
14
- export { RouteManager as SmartProxyRouteManager } from './smart-proxy/route-manager.js';
15
+ export { SharedRouteManager as SmartProxyRouteManager } from '../core/routing/route-manager.js';
15
16
  export * from './smart-proxy/utils/index.js';
16
17
  // Export smart-proxy models except IAcmeOptions
17
18
  export type { ISmartProxyOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteContext } from './smart-proxy/models/index.js';
@@ -5,6 +5,7 @@ import { TimeoutManager } from './timeout-manager.js';
5
5
  import { logger } from '../../core/utils/logger.js';
6
6
  import { LifecycleComponent } from '../../core/utils/lifecycle-component.js';
7
7
  import { cleanupSocket } from '../../core/utils/socket-utils.js';
8
+ import { WrappedSocket } from '../../core/models/wrapped-socket.js';
8
9
 
9
10
  /**
10
11
  * Manages connection lifecycle, tracking, and cleanup with performance optimizations
@@ -53,8 +54,9 @@ export class ConnectionManager extends LifecycleComponent {
53
54
 
54
55
  /**
55
56
  * Create and track a new connection
57
+ * Accepts either a regular net.Socket or a WrappedSocket for transparent PROXY protocol support
56
58
  */
57
- public createConnection(socket: plugins.net.Socket): IConnectionRecord | null {
59
+ public createConnection(socket: plugins.net.Socket | WrappedSocket): IConnectionRecord | null {
58
60
  // Enforce connection limit
59
61
  if (this.connectionRecords.size >= this.maxConnections) {
60
62
  logger.log('warn', `Connection limit reached (${this.maxConnections}). Rejecting new connection.`, {
@@ -68,6 +70,7 @@ export class ConnectionManager extends LifecycleComponent {
68
70
 
69
71
  const connectionId = this.generateConnectionId();
70
72
  const remoteIP = socket.remoteAddress || '';
73
+ const remotePort = socket.remotePort || 0;
71
74
  const localPort = socket.localPort || 0;
72
75
  const now = Date.now();
73
76
 
@@ -83,6 +86,7 @@ export class ConnectionManager extends LifecycleComponent {
83
86
  bytesReceived: 0,
84
87
  bytesSent: 0,
85
88
  remoteIP,
89
+ remotePort,
86
90
  localPort,
87
91
  isTLS: false,
88
92
  tlsHandshakeComplete: false,
@@ -282,22 +286,26 @@ export class ConnectionManager extends LifecycleComponent {
282
286
  const cleanupPromises: Promise<void>[] = [];
283
287
 
284
288
  if (record.incoming) {
289
+ // Extract underlying socket if it's a WrappedSocket
290
+ const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
285
291
  if (!record.incoming.writable || record.incoming.destroyed) {
286
292
  // Socket is not active, clean up immediately
287
- cleanupPromises.push(cleanupSocket(record.incoming, `${record.id}-incoming`, { immediate: true }));
293
+ cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { immediate: true }));
288
294
  } else {
289
295
  // Socket is still active, allow graceful cleanup
290
- cleanupPromises.push(cleanupSocket(record.incoming, `${record.id}-incoming`, { allowDrain: true, gracePeriod: 5000 }));
296
+ cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { allowDrain: true, gracePeriod: 5000 }));
291
297
  }
292
298
  }
293
299
 
294
300
  if (record.outgoing) {
301
+ // Extract underlying socket if it's a WrappedSocket
302
+ const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
295
303
  if (!record.outgoing.writable || record.outgoing.destroyed) {
296
304
  // Socket is not active, clean up immediately
297
- cleanupPromises.push(cleanupSocket(record.outgoing, `${record.id}-outgoing`, { immediate: true }));
305
+ cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { immediate: true }));
298
306
  } else {
299
307
  // Socket is still active, allow graceful cleanup
300
- cleanupPromises.push(cleanupSocket(record.outgoing, `${record.id}-outgoing`, { allowDrain: true, gracePeriod: 5000 }));
308
+ cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { allowDrain: true, gracePeriod: 5000 }));
301
309
  }
302
310
  }
303
311
 
@@ -570,11 +578,13 @@ export class ConnectionManager extends LifecycleComponent {
570
578
  const shutdownPromises: Promise<void>[] = [];
571
579
 
572
580
  if (record.incoming) {
573
- shutdownPromises.push(cleanupSocket(record.incoming, `${record.id}-incoming-shutdown`, { immediate: true }));
581
+ const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
582
+ shutdownPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming-shutdown`, { immediate: true }));
574
583
  }
575
584
 
576
585
  if (record.outgoing) {
577
- shutdownPromises.push(cleanupSocket(record.outgoing, `${record.id}-outgoing-shutdown`, { immediate: true }));
586
+ const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
587
+ shutdownPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing-shutdown`, { immediate: true }));
578
588
  }
579
589
 
580
590
  // Don't wait for shutdown cleanup in this batch processing
@@ -3,6 +3,7 @@ import { HttpProxy } from '../http-proxy/index.js';
3
3
  import { setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
4
4
  import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
5
5
  import type { IRouteConfig } from './models/route-types.js';
6
+ import { WrappedSocket } from '../../core/models/wrapped-socket.js';
6
7
 
7
8
  export class HttpProxyBridge {
8
9
  private httpProxy: HttpProxy | null = null;
@@ -98,7 +99,7 @@ export class HttpProxyBridge {
98
99
  */
99
100
  public async forwardToHttpProxy(
100
101
  connectionId: string,
101
- socket: plugins.net.Socket,
102
+ socket: plugins.net.Socket | WrappedSocket,
102
103
  record: IConnectionRecord,
103
104
  initialChunk: Buffer,
104
105
  httpProxyPort: number,
@@ -125,7 +126,10 @@ export class HttpProxyBridge {
125
126
  }
126
127
 
127
128
  // Use centralized bidirectional forwarding
128
- setupBidirectionalForwarding(socket, proxySocket, {
129
+ // Extract underlying socket if it's a WrappedSocket
130
+ const underlyingSocket = socket instanceof WrappedSocket ? socket.socket : socket;
131
+
132
+ setupBidirectionalForwarding(underlyingSocket, proxySocket, {
129
133
  onClientData: (chunk) => {
130
134
  // Update stats if needed
131
135
  if (record) {
@@ -17,7 +17,7 @@ export { TlsManager } from './tls-manager.js';
17
17
  export { HttpProxyBridge } from './http-proxy-bridge.js';
18
18
 
19
19
  // Export route-based components
20
- export { RouteManager } from './route-manager.js';
20
+ export { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
21
21
  export { RouteConnectionHandler } from './route-connection-handler.js';
22
22
  export { NFTablesManager } from './nftables-manager.js';
23
23
 
@@ -1,4 +1,5 @@
1
1
  import * as plugins from '../../../plugins.js';
2
+ import type { WrappedSocket } from '../../../core/models/wrapped-socket.js';
2
3
  // Certificate types removed - define IAcmeOptions locally
3
4
  export interface IAcmeOptions {
4
5
  enabled?: boolean;
@@ -34,6 +35,11 @@ export interface ISmartProxyOptions {
34
35
  // Port configuration
35
36
  preserveSourceIP?: boolean; // Preserve client IP when forwarding
36
37
 
38
+ // PROXY protocol configuration
39
+ proxyIPs?: string[]; // List of trusted proxy IPs that can send PROXY protocol
40
+ acceptProxyProtocol?: boolean; // Global option to accept PROXY protocol (defaults based on proxyIPs)
41
+ sendProxyProtocol?: boolean; // Global option to send PROXY protocol to all targets
42
+
37
43
  // Global/default settings
38
44
  defaults?: {
39
45
  target?: {
@@ -128,8 +134,8 @@ export interface ISmartProxyOptions {
128
134
  */
129
135
  export interface IConnectionRecord {
130
136
  id: string; // Unique connection identifier
131
- incoming: plugins.net.Socket;
132
- outgoing: plugins.net.Socket | null;
137
+ incoming: plugins.net.Socket | WrappedSocket;
138
+ outgoing: plugins.net.Socket | WrappedSocket | null;
133
139
  incomingStartTime: number;
134
140
  outgoingStartTime?: number;
135
141
  outgoingClosedTime?: number;
@@ -145,6 +151,7 @@ export interface IConnectionRecord {
145
151
  bytesReceived: number; // Total bytes received
146
152
  bytesSent: number; // Total bytes sent
147
153
  remoteIP: string; // Remote IP (cached for logging after socket close)
154
+ remotePort: number; // Remote port (cached for logging after socket close)
148
155
  localPort: number; // Local port (cached for logging)
149
156
  isTLS: boolean; // Whether this connection is a TLS connection
150
157
  tlsHandshakeComplete: boolean; // Whether the TLS handshake is complete
@@ -250,6 +250,9 @@ export interface IRouteAction {
250
250
 
251
251
  // Socket handler function (when type is 'socket-handler')
252
252
  socketHandler?: TSocketHandler;
253
+
254
+ // PROXY protocol support
255
+ sendProxyProtocol?: boolean;
253
256
  }
254
257
 
255
258
  /**