@push.rocks/smartproxy 19.5.19 → 19.5.20

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 (101) 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 +0 -2
  28. package/dist_ts/core/utils/index.js +1 -3
  29. package/dist_ts/core/utils/route-manager.d.ts +0 -30
  30. package/dist_ts/core/utils/route-manager.js +6 -47
  31. package/dist_ts/core/utils/route-utils.d.ts +2 -68
  32. package/dist_ts/core/utils/route-utils.js +21 -218
  33. package/dist_ts/core/utils/security-utils.js +4 -4
  34. package/dist_ts/index.d.ts +2 -5
  35. package/dist_ts/index.js +5 -11
  36. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +0 -1
  37. package/dist_ts/proxies/http-proxy/http-proxy.js +15 -60
  38. package/dist_ts/proxies/http-proxy/models/types.d.ts +0 -90
  39. package/dist_ts/proxies/http-proxy/models/types.js +1 -242
  40. package/dist_ts/proxies/http-proxy/request-handler.d.ts +3 -5
  41. package/dist_ts/proxies/http-proxy/request-handler.js +20 -171
  42. package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +2 -5
  43. package/dist_ts/proxies/http-proxy/websocket-handler.js +15 -23
  44. package/dist_ts/proxies/index.d.ts +2 -2
  45. package/dist_ts/proxies/index.js +4 -3
  46. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +3 -1
  47. package/dist_ts/proxies/smart-proxy/connection-manager.js +15 -7
  48. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +2 -1
  49. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +5 -2
  50. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
  51. package/dist_ts/proxies/smart-proxy/index.js +2 -2
  52. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +6 -2
  53. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +1 -1
  54. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +48 -25
  55. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -1
  56. package/dist_ts/proxies/smart-proxy/smart-proxy.js +15 -4
  57. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +10 -43
  58. package/dist_ts/routing/router/http-router.d.ts +89 -0
  59. package/dist_ts/routing/router/http-router.js +205 -0
  60. package/dist_ts/routing/router/index.d.ts +2 -5
  61. package/dist_ts/routing/router/index.js +3 -4
  62. package/package.json +1 -1
  63. package/readme.delete.md +187 -0
  64. package/readme.hints.md +189 -1
  65. package/readme.plan.md +621 -0
  66. package/readme.routing.md +341 -0
  67. package/ts/core/models/index.ts +2 -0
  68. package/ts/core/models/socket-types.ts +21 -0
  69. package/ts/core/models/wrapped-socket.ts +99 -0
  70. package/ts/core/routing/index.ts +21 -0
  71. package/ts/core/routing/matchers/domain.ts +119 -0
  72. package/ts/core/routing/matchers/header.ts +120 -0
  73. package/ts/core/routing/matchers/index.ts +22 -0
  74. package/ts/core/routing/matchers/ip.ts +207 -0
  75. package/ts/core/routing/matchers/path.ts +184 -0
  76. package/ts/core/{utils → routing}/route-manager.ts +7 -57
  77. package/ts/core/routing/route-utils.ts +88 -0
  78. package/ts/core/routing/specificity.ts +141 -0
  79. package/ts/core/routing/types.ts +49 -0
  80. package/ts/core/utils/index.ts +0 -2
  81. package/ts/core/utils/security-utils.ts +3 -7
  82. package/ts/index.ts +4 -14
  83. package/ts/proxies/http-proxy/http-proxy.ts +13 -68
  84. package/ts/proxies/http-proxy/models/types.ts +0 -324
  85. package/ts/proxies/http-proxy/request-handler.ts +15 -186
  86. package/ts/proxies/http-proxy/websocket-handler.ts +15 -26
  87. package/ts/proxies/index.ts +3 -2
  88. package/ts/proxies/smart-proxy/connection-manager.ts +15 -7
  89. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +6 -2
  90. package/ts/proxies/smart-proxy/index.ts +1 -1
  91. package/ts/proxies/smart-proxy/models/interfaces.ts +8 -2
  92. package/ts/proxies/smart-proxy/route-connection-handler.ts +58 -30
  93. package/ts/proxies/smart-proxy/smart-proxy.ts +15 -3
  94. package/ts/proxies/smart-proxy/utils/route-utils.ts +11 -49
  95. package/ts/routing/router/http-router.ts +266 -0
  96. package/ts/routing/router/index.ts +3 -8
  97. package/readme.problems.md +0 -170
  98. package/ts/core/utils/route-utils.ts +0 -312
  99. package/ts/proxies/smart-proxy/route-manager.ts +0 -554
  100. package/ts/routing/router/proxy-router.ts +0 -437
  101. package/ts/routing/router/route-router.ts +0 -482
@@ -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.`, {
@@ -282,22 +284,26 @@ export class ConnectionManager extends LifecycleComponent {
282
284
  const cleanupPromises: Promise<void>[] = [];
283
285
 
284
286
  if (record.incoming) {
287
+ // Extract underlying socket if it's a WrappedSocket
288
+ const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
285
289
  if (!record.incoming.writable || record.incoming.destroyed) {
286
290
  // Socket is not active, clean up immediately
287
- cleanupPromises.push(cleanupSocket(record.incoming, `${record.id}-incoming`, { immediate: true }));
291
+ cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { immediate: true }));
288
292
  } else {
289
293
  // Socket is still active, allow graceful cleanup
290
- cleanupPromises.push(cleanupSocket(record.incoming, `${record.id}-incoming`, { allowDrain: true, gracePeriod: 5000 }));
294
+ cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { allowDrain: true, gracePeriod: 5000 }));
291
295
  }
292
296
  }
293
297
 
294
298
  if (record.outgoing) {
299
+ // Extract underlying socket if it's a WrappedSocket
300
+ const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
295
301
  if (!record.outgoing.writable || record.outgoing.destroyed) {
296
302
  // Socket is not active, clean up immediately
297
- cleanupPromises.push(cleanupSocket(record.outgoing, `${record.id}-outgoing`, { immediate: true }));
303
+ cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { immediate: true }));
298
304
  } else {
299
305
  // Socket is still active, allow graceful cleanup
300
- cleanupPromises.push(cleanupSocket(record.outgoing, `${record.id}-outgoing`, { allowDrain: true, gracePeriod: 5000 }));
306
+ cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { allowDrain: true, gracePeriod: 5000 }));
301
307
  }
302
308
  }
303
309
 
@@ -570,11 +576,13 @@ export class ConnectionManager extends LifecycleComponent {
570
576
  const shutdownPromises: Promise<void>[] = [];
571
577
 
572
578
  if (record.incoming) {
573
- shutdownPromises.push(cleanupSocket(record.incoming, `${record.id}-incoming-shutdown`, { immediate: true }));
579
+ const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
580
+ shutdownPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming-shutdown`, { immediate: true }));
574
581
  }
575
582
 
576
583
  if (record.outgoing) {
577
- shutdownPromises.push(cleanupSocket(record.outgoing, `${record.id}-outgoing-shutdown`, { immediate: true }));
584
+ const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
585
+ shutdownPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing-shutdown`, { immediate: true }));
578
586
  }
579
587
 
580
588
  // 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;
@@ -2,14 +2,17 @@ import * as plugins from '../../plugins.js';
2
2
  import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
3
3
  import { logger } from '../../core/utils/logger.js';
4
4
  // Route checking functions have been removed
5
- import type { IRouteConfig, IRouteAction, IRouteContext } from './models/route-types.js';
5
+ import type { IRouteConfig, IRouteAction } from './models/route-types.js';
6
+ import type { IRouteContext } from '../../core/models/route-context.js';
6
7
  import { ConnectionManager } from './connection-manager.js';
7
8
  import { SecurityManager } from './security-manager.js';
8
9
  import { TlsManager } from './tls-manager.js';
9
10
  import { HttpProxyBridge } from './http-proxy-bridge.js';
10
11
  import { TimeoutManager } from './timeout-manager.js';
11
- import { RouteManager } from './route-manager.js';
12
+ import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
12
13
  import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
14
+ import { WrappedSocket } from '../../core/models/wrapped-socket.js';
15
+ import { getUnderlyingSocket } from '../../core/models/socket-types.js';
13
16
 
14
17
  /**
15
18
  * Handles new connection processing and setup logic with support for route-based configuration
@@ -80,39 +83,52 @@ export class RouteConnectionHandler {
80
83
  const remoteIP = socket.remoteAddress || '';
81
84
  const localPort = socket.localPort || 0;
82
85
 
86
+ // Always wrap the socket to prepare for potential PROXY protocol
87
+ const wrappedSocket = new WrappedSocket(socket);
88
+
89
+ // If this is from a trusted proxy, log it
90
+ if (this.settings.proxyIPs?.includes(remoteIP)) {
91
+ logger.log('debug', `Connection from trusted proxy ${remoteIP}, PROXY protocol parsing will be enabled`, {
92
+ remoteIP,
93
+ component: 'route-handler'
94
+ });
95
+ }
96
+
83
97
  // Validate IP against rate limits and connection limits
84
- const ipValidation = this.securityManager.validateIP(remoteIP);
98
+ // Note: For wrapped sockets, this will use the underlying socket IP until PROXY protocol is parsed
99
+ const ipValidation = this.securityManager.validateIP(wrappedSocket.remoteAddress || '');
85
100
  if (!ipValidation.allowed) {
86
- logger.log('warn', `Connection rejected`, { remoteIP, reason: ipValidation.reason, component: 'route-handler' });
87
- cleanupSocket(socket, `rejected-${ipValidation.reason}`, { immediate: true });
101
+ logger.log('warn', `Connection rejected`, { remoteIP: wrappedSocket.remoteAddress, reason: ipValidation.reason, component: 'route-handler' });
102
+ cleanupSocket(wrappedSocket.socket, `rejected-${ipValidation.reason}`, { immediate: true });
88
103
  return;
89
104
  }
90
105
 
91
- // Create a new connection record
92
- const record = this.connectionManager.createConnection(socket);
106
+ // Create a new connection record with the wrapped socket
107
+ const record = this.connectionManager.createConnection(wrappedSocket);
93
108
  if (!record) {
94
109
  // Connection was rejected due to limit - socket already destroyed by connection manager
95
110
  return;
96
111
  }
97
112
  const connectionId = record.id;
98
113
 
99
- // Apply socket optimizations
100
- socket.setNoDelay(this.settings.noDelay);
114
+ // Apply socket optimizations (apply to underlying socket)
115
+ const underlyingSocket = wrappedSocket.socket;
116
+ underlyingSocket.setNoDelay(this.settings.noDelay);
101
117
 
102
118
  // Apply keep-alive settings if enabled
103
119
  if (this.settings.keepAlive) {
104
- socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
120
+ underlyingSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
105
121
  record.hasKeepAlive = true;
106
122
 
107
123
  // Apply enhanced TCP keep-alive options if enabled
108
124
  if (this.settings.enableKeepAliveProbes) {
109
125
  try {
110
126
  // These are platform-specific and may not be available
111
- if ('setKeepAliveProbes' in socket) {
112
- (socket as any).setKeepAliveProbes(10);
127
+ if ('setKeepAliveProbes' in underlyingSocket) {
128
+ (underlyingSocket as any).setKeepAliveProbes(10);
113
129
  }
114
- if ('setKeepAliveInterval' in socket) {
115
- (socket as any).setKeepAliveInterval(1000);
130
+ if ('setKeepAliveInterval' in underlyingSocket) {
131
+ (underlyingSocket as any).setKeepAliveInterval(1000);
116
132
  }
117
133
  } catch (err) {
118
134
  // Ignore errors - these are optional enhancements
@@ -150,19 +166,19 @@ export class RouteConnectionHandler {
150
166
  }
151
167
 
152
168
  // Handle the connection - wait for initial data to determine if it's TLS
153
- this.handleInitialData(socket, record);
169
+ this.handleInitialData(wrappedSocket, record);
154
170
  }
155
171
 
156
172
  /**
157
173
  * Handle initial data from a connection to determine routing
158
174
  */
159
- private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
175
+ private handleInitialData(socket: plugins.net.Socket | WrappedSocket, record: IConnectionRecord): void {
160
176
  const connectionId = record.id;
161
177
  const localPort = record.localPort;
162
178
  let initialDataReceived = false;
163
179
 
164
180
  // Check if any routes on this port require TLS handling
165
- const allRoutes = this.routeManager.getAllRoutes();
181
+ const allRoutes = this.routeManager.getRoutes();
166
182
  const needsTlsHandling = allRoutes.some(route => {
167
183
  // Check if route matches this port
168
184
  const matchesPort = this.routeManager.getRoutesForPort(localPort).includes(route);
@@ -176,9 +192,11 @@ export class RouteConnectionHandler {
176
192
 
177
193
  // If no routes require TLS handling and it's not port 443, route immediately
178
194
  if (!needsTlsHandling && localPort !== 443) {
195
+ // Extract underlying socket for socket-utils functions
196
+ const underlyingSocket = getUnderlyingSocket(socket);
179
197
  // Set up proper socket handlers for immediate routing
180
198
  setupSocketHandlers(
181
- socket,
199
+ underlyingSocket,
182
200
  (reason) => {
183
201
  // Only cleanup if connection hasn't been fully established
184
202
  // Check if outgoing connection exists and is connected
@@ -370,7 +388,7 @@ export class RouteConnectionHandler {
370
388
  * Route the connection based on match criteria
371
389
  */
372
390
  private routeConnection(
373
- socket: plugins.net.Socket,
391
+ socket: plugins.net.Socket | WrappedSocket,
374
392
  record: IConnectionRecord,
375
393
  serverName: string,
376
394
  initialChunk?: Buffer
@@ -385,15 +403,21 @@ export class RouteConnectionHandler {
385
403
  // For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
386
404
  const skipDomainCheck = isHttpProxyPort && !record.isTLS;
387
405
 
388
- // Find matching route
389
- const routeMatch = this.routeManager.findMatchingRoute({
406
+ // Create route context for matching
407
+ const routeContext: IRouteContext = {
390
408
  port: localPort,
391
- domain: serverName,
409
+ domain: skipDomainCheck ? undefined : serverName, // Skip domain if HTTP proxy without TLS
392
410
  clientIp: remoteIP,
411
+ serverIp: socket.localAddress || '',
393
412
  path: undefined, // We don't have path info at this point
413
+ isTls: record.isTLS,
394
414
  tlsVersion: undefined, // We don't extract TLS version yet
395
- skipDomainCheck: skipDomainCheck,
396
- });
415
+ timestamp: Date.now(),
416
+ connectionId: record.id
417
+ };
418
+
419
+ // Find matching route
420
+ const routeMatch = this.routeManager.findMatchingRoute(routeContext);
397
421
 
398
422
  if (!routeMatch) {
399
423
  logger.log('warn', `No route found for ${serverName || 'connection'} on port ${localPort} (connection: ${connectionId})`, {
@@ -552,7 +576,7 @@ export class RouteConnectionHandler {
552
576
  * Handle a forward action for a route
553
577
  */
554
578
  private handleForwardAction(
555
- socket: plugins.net.Socket,
579
+ socket: plugins.net.Socket | WrappedSocket,
556
580
  record: IConnectionRecord,
557
581
  route: IRouteConfig,
558
582
  initialChunk?: Buffer
@@ -869,7 +893,7 @@ export class RouteConnectionHandler {
869
893
  * Handle a socket-handler action for a route
870
894
  */
871
895
  private async handleSocketHandlerAction(
872
- socket: plugins.net.Socket,
896
+ socket: plugins.net.Socket | WrappedSocket,
873
897
  record: IConnectionRecord,
874
898
  route: IRouteConfig,
875
899
  initialChunk?: Buffer
@@ -933,8 +957,9 @@ export class RouteConnectionHandler {
933
957
  });
934
958
 
935
959
  try {
936
- // Call the handler with socket AND context
937
- const result = route.action.socketHandler(socket, routeContext);
960
+ // Call the handler with the appropriate socket (extract underlying if needed)
961
+ const handlerSocket = getUnderlyingSocket(socket);
962
+ const result = route.action.socketHandler(handlerSocket, routeContext);
938
963
 
939
964
  // Handle async handlers properly
940
965
  if (result instanceof Promise) {
@@ -988,7 +1013,7 @@ export class RouteConnectionHandler {
988
1013
  * Sets up a direct connection to the target
989
1014
  */
990
1015
  private setupDirectConnection(
991
- socket: plugins.net.Socket,
1016
+ socket: plugins.net.Socket | WrappedSocket,
992
1017
  record: IConnectionRecord,
993
1018
  serverName?: string,
994
1019
  initialChunk?: Buffer,
@@ -1138,7 +1163,10 @@ export class RouteConnectionHandler {
1138
1163
  }
1139
1164
 
1140
1165
  // Use centralized bidirectional forwarding setup
1141
- setupBidirectionalForwarding(socket, targetSocket, {
1166
+ // Extract underlying sockets for socket-utils functions
1167
+ const incomingSocket = getUnderlyingSocket(socket);
1168
+
1169
+ setupBidirectionalForwarding(incomingSocket, targetSocket, {
1142
1170
  onClientData: (chunk) => {
1143
1171
  record.bytesReceived += chunk.length;
1144
1172
  this.timeoutManager.updateActivity(record);
@@ -8,7 +8,7 @@ import { TlsManager } from './tls-manager.js';
8
8
  import { HttpProxyBridge } from './http-proxy-bridge.js';
9
9
  import { TimeoutManager } from './timeout-manager.js';
10
10
  import { PortManager } from './port-manager.js';
11
- import { RouteManager } from './route-manager.js';
11
+ import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
12
12
  import { RouteConnectionHandler } from './route-connection-handler.js';
13
13
  import { NFTablesManager } from './nftables-manager.js';
14
14
 
@@ -162,8 +162,20 @@ export class SmartProxy extends plugins.EventEmitter {
162
162
  this.timeoutManager
163
163
  );
164
164
 
165
- // Create the route manager
166
- this.routeManager = new RouteManager(this.settings);
165
+ // Create the route manager with SharedRouteManager API
166
+ // Create a logger adapter to match ILogger interface
167
+ const loggerAdapter = {
168
+ debug: (message: string, data?: any) => logger.log('debug', message, data),
169
+ info: (message: string, data?: any) => logger.log('info', message, data),
170
+ warn: (message: string, data?: any) => logger.log('warn', message, data),
171
+ error: (message: string, data?: any) => logger.log('error', message, data)
172
+ };
173
+
174
+ this.routeManager = new RouteManager({
175
+ logger: loggerAdapter,
176
+ enableDetailedLogging: this.settings.enableDetailedLogging,
177
+ routes: this.settings.routes
178
+ });
167
179
 
168
180
 
169
181
  // Create other required components
@@ -92,6 +92,8 @@ export function mergeRouteConfigs(
92
92
  return mergedRoute;
93
93
  }
94
94
 
95
+ import { DomainMatcher, PathMatcher, HeaderMatcher } from '../../../core/routing/matchers/index.js';
96
+
95
97
  /**
96
98
  * Check if a route matches a domain
97
99
  * @param route The route to check
@@ -107,14 +109,7 @@ export function routeMatchesDomain(route: IRouteConfig, domain: string): boolean
107
109
  ? route.match.domains
108
110
  : [route.match.domains];
109
111
 
110
- return domains.some(d => {
111
- // Handle wildcard domains
112
- if (d.startsWith('*.')) {
113
- const suffix = d.substring(2);
114
- return domain.endsWith(suffix) && domain.split('.').length > suffix.split('.').length;
115
- }
116
- return d.toLowerCase() === domain.toLowerCase();
117
- });
112
+ return domains.some(d => DomainMatcher.match(d, domain));
118
113
  }
119
114
 
120
115
  /**
@@ -160,28 +155,7 @@ export function routeMatchesPath(route: IRouteConfig, path: string): boolean {
160
155
  return true; // No path specified means it matches any path
161
156
  }
162
157
 
163
- // Handle exact path
164
- if (route.match.path === path) {
165
- return true;
166
- }
167
-
168
- // Handle path prefix with trailing slash (e.g., /api/)
169
- if (route.match.path.endsWith('/') && path.startsWith(route.match.path)) {
170
- return true;
171
- }
172
-
173
- // Handle exact path match without trailing slash
174
- if (!route.match.path.endsWith('/') && path === route.match.path) {
175
- return true;
176
- }
177
-
178
- // Handle wildcard paths (e.g., /api/*)
179
- if (route.match.path.endsWith('*')) {
180
- const prefix = route.match.path.slice(0, -1);
181
- return path.startsWith(prefix);
182
- }
183
-
184
- return false;
158
+ return PathMatcher.match(route.match.path, path).matches;
185
159
  }
186
160
 
187
161
  /**
@@ -198,25 +172,13 @@ export function routeMatchesHeaders(
198
172
  return true; // No headers specified means it matches any headers
199
173
  }
200
174
 
201
- // Check each header in the route's match criteria
202
- return Object.entries(route.match.headers).every(([key, value]) => {
203
- // If the header isn't present in the request, it doesn't match
204
- if (!headers[key]) {
205
- return false;
206
- }
207
-
208
- // Handle exact match
209
- if (typeof value === 'string') {
210
- return headers[key] === value;
211
- }
212
-
213
- // Handle regex match
214
- if (value instanceof RegExp) {
215
- return value.test(headers[key]);
216
- }
217
-
218
- return false;
219
- });
175
+ // Convert RegExp patterns to strings for HeaderMatcher
176
+ const stringHeaders: Record<string, string> = {};
177
+ for (const [key, value] of Object.entries(route.match.headers)) {
178
+ stringHeaders[key] = value instanceof RegExp ? value.source : value;
179
+ }
180
+
181
+ return HeaderMatcher.matchAll(stringHeaders, headers);
220
182
  }
221
183
 
222
184
  /**