@push.rocks/smartproxy 21.1.7 → 22.6.0

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 (155) hide show
  1. package/changelog.md +109 -0
  2. package/dist_rust/rustproxy +0 -0
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/core/utils/shared-security-manager.d.ts +17 -0
  5. package/dist_ts/core/utils/shared-security-manager.js +66 -1
  6. package/dist_ts/index.d.ts +1 -5
  7. package/dist_ts/index.js +3 -9
  8. package/dist_ts/protocols/common/fragment-handler.js +5 -1
  9. package/dist_ts/proxies/http-proxy/default-certificates.d.ts +54 -0
  10. package/dist_ts/proxies/http-proxy/default-certificates.js +127 -0
  11. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -1
  12. package/dist_ts/proxies/http-proxy/http-proxy.js +9 -14
  13. package/dist_ts/proxies/http-proxy/index.d.ts +5 -1
  14. package/dist_ts/proxies/http-proxy/index.js +6 -2
  15. package/dist_ts/proxies/http-proxy/security-manager.d.ts +4 -12
  16. package/dist_ts/proxies/http-proxy/security-manager.js +66 -99
  17. package/dist_ts/proxies/index.d.ts +1 -5
  18. package/dist_ts/proxies/index.js +2 -6
  19. package/dist_ts/proxies/nftables-proxy/index.d.ts +1 -0
  20. package/dist_ts/proxies/nftables-proxy/index.js +2 -1
  21. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +4 -26
  22. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +84 -236
  23. package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +9 -0
  24. package/dist_ts/proxies/nftables-proxy/utils/index.js +12 -0
  25. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +66 -0
  26. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +131 -0
  27. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +39 -0
  28. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +112 -0
  29. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +59 -0
  30. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +130 -0
  31. package/dist_ts/proxies/smart-proxy/certificate-manager.js +4 -3
  32. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +13 -2
  33. package/dist_ts/proxies/smart-proxy/connection-manager.js +16 -6
  34. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +35 -10
  35. package/dist_ts/proxies/smart-proxy/index.d.ts +5 -10
  36. package/dist_ts/proxies/smart-proxy/index.js +7 -13
  37. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -3
  38. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +17 -0
  39. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +72 -9
  40. package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
  41. package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
  42. package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
  43. package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
  44. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
  45. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
  46. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
  47. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
  48. package/dist_ts/proxies/smart-proxy/security-manager.d.ts +14 -12
  49. package/dist_ts/proxies/smart-proxy/security-manager.js +80 -74
  50. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
  51. package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -622
  52. package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
  53. package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
  54. package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +2 -9
  55. package/dist_ts/proxies/smart-proxy/tls-manager.js +3 -26
  56. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -1
  57. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
  58. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.d.ts +49 -0
  59. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.js +108 -0
  60. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.d.ts +57 -0
  61. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.js +89 -0
  62. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.d.ts +17 -0
  63. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.js +32 -0
  64. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.d.ts +68 -0
  65. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.js +117 -0
  66. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.d.ts +17 -0
  67. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.js +27 -0
  68. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.d.ts +63 -0
  69. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.js +105 -0
  70. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.d.ts +83 -0
  71. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.js +126 -0
  72. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.d.ts +47 -0
  73. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.js +66 -0
  74. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +70 -0
  75. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +287 -0
  76. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.d.ts +46 -0
  77. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.js +67 -0
  78. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +4 -457
  79. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +6 -950
  80. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +2 -2
  81. package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +67 -1
  82. package/dist_ts/proxies/smart-proxy/utils/route-validator.js +251 -3
  83. package/dist_ts/routing/index.d.ts +1 -1
  84. package/dist_ts/routing/index.js +3 -3
  85. package/dist_ts/routing/models/http-types.d.ts +119 -4
  86. package/dist_ts/routing/models/http-types.js +93 -5
  87. package/npmextra.json +12 -6
  88. package/package.json +34 -24
  89. package/readme.hints.md +184 -1
  90. package/readme.md +580 -266
  91. package/ts/00_commitinfo_data.ts +1 -1
  92. package/ts/core/utils/shared-security-manager.ts +98 -13
  93. package/ts/index.ts +4 -12
  94. package/ts/protocols/common/fragment-handler.ts +4 -0
  95. package/ts/proxies/index.ts +1 -9
  96. package/ts/proxies/nftables-proxy/index.ts +1 -0
  97. package/ts/proxies/nftables-proxy/nftables-proxy.ts +116 -290
  98. package/ts/proxies/nftables-proxy/utils/index.ts +38 -0
  99. package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +162 -0
  100. package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +125 -0
  101. package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +156 -0
  102. package/ts/proxies/smart-proxy/index.ts +6 -13
  103. package/ts/proxies/smart-proxy/models/interfaces.ts +6 -5
  104. package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
  105. package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
  106. package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
  107. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
  108. package/ts/proxies/smart-proxy/smart-proxy.ts +282 -800
  109. package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
  110. package/ts/proxies/smart-proxy/utils/index.ts +3 -5
  111. package/ts/proxies/smart-proxy/utils/route-helpers/api-helpers.ts +144 -0
  112. package/ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.ts +124 -0
  113. package/ts/proxies/smart-proxy/utils/route-helpers/http-helpers.ts +40 -0
  114. package/ts/proxies/smart-proxy/utils/route-helpers/https-helpers.ts +163 -0
  115. package/ts/proxies/smart-proxy/utils/route-helpers/index.ts +62 -0
  116. package/ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.ts +154 -0
  117. package/ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.ts +202 -0
  118. package/ts/proxies/smart-proxy/utils/route-helpers/security-helpers.ts +96 -0
  119. package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +337 -0
  120. package/ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.ts +98 -0
  121. package/ts/proxies/smart-proxy/utils/route-helpers.ts +5 -1302
  122. package/ts/proxies/smart-proxy/utils/route-utils.ts +1 -1
  123. package/ts/proxies/smart-proxy/utils/route-validator.ts +274 -4
  124. package/ts/routing/index.ts +2 -2
  125. package/ts/routing/models/http-types.ts +147 -4
  126. package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
  127. package/ts/proxies/http-proxy/connection-pool.ts +0 -228
  128. package/ts/proxies/http-proxy/context-creator.ts +0 -145
  129. package/ts/proxies/http-proxy/function-cache.ts +0 -279
  130. package/ts/proxies/http-proxy/handlers/index.ts +0 -5
  131. package/ts/proxies/http-proxy/http-proxy.ts +0 -675
  132. package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
  133. package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
  134. package/ts/proxies/http-proxy/index.ts +0 -13
  135. package/ts/proxies/http-proxy/models/http-types.ts +0 -148
  136. package/ts/proxies/http-proxy/models/index.ts +0 -5
  137. package/ts/proxies/http-proxy/models/types.ts +0 -125
  138. package/ts/proxies/http-proxy/request-handler.ts +0 -878
  139. package/ts/proxies/http-proxy/security-manager.ts +0 -433
  140. package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
  141. package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
  142. package/ts/proxies/smart-proxy/cert-store.ts +0 -92
  143. package/ts/proxies/smart-proxy/certificate-manager.ts +0 -894
  144. package/ts/proxies/smart-proxy/connection-manager.ts +0 -796
  145. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -187
  146. package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
  147. package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
  148. package/ts/proxies/smart-proxy/port-manager.ts +0 -358
  149. package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1640
  150. package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
  151. package/ts/proxies/smart-proxy/security-manager.ts +0 -257
  152. package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
  153. package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
  154. package/ts/proxies/smart-proxy/tls-manager.ts +0 -207
  155. package/ts/proxies/smart-proxy/utils/route-validators.ts +0 -283
@@ -1,1640 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
3
- import { logger } from '../../core/utils/logger.js';
4
- import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
5
- // Route checking functions have been removed
6
- import type { IRouteConfig, IRouteAction, IRouteTarget } from './models/route-types.js';
7
- import type { IRouteContext } from '../../core/models/route-context.js';
8
- import { cleanupSocket, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
9
- import { WrappedSocket } from '../../core/models/wrapped-socket.js';
10
- import { getUnderlyingSocket } from '../../core/models/socket-types.js';
11
- import { ProxyProtocolParser } from '../../core/utils/proxy-protocol.js';
12
- import type { SmartProxy } from './smart-proxy.js';
13
- import { ProtocolDetector } from '../../detection/index.js';
14
-
15
- /**
16
- * Handles new connection processing and setup logic with support for route-based configuration
17
- */
18
- export class RouteConnectionHandler {
19
- // Note: Route context caching was considered but not implemented
20
- // as route contexts are lightweight and should be created fresh
21
- // for each connection to ensure accurate context data
22
-
23
- // RxJS Subject for new connections
24
- public newConnectionSubject = new plugins.smartrx.rxjs.Subject<IConnectionRecord>();
25
-
26
- constructor(
27
- private smartProxy: SmartProxy
28
- ) {}
29
-
30
-
31
- /**
32
- * Create a route context object for port and host mapping functions
33
- */
34
- private createRouteContext(options: {
35
- connectionId: string;
36
- port: number;
37
- domain?: string;
38
- clientIp: string;
39
- serverIp: string;
40
- isTls: boolean;
41
- tlsVersion?: string;
42
- routeName?: string;
43
- routeId?: string;
44
- path?: string;
45
- query?: string;
46
- headers?: Record<string, string>;
47
- }): IRouteContext {
48
- return {
49
- // Connection information
50
- port: options.port,
51
- domain: options.domain,
52
- clientIp: options.clientIp,
53
- serverIp: options.serverIp,
54
- path: options.path,
55
- query: options.query,
56
- headers: options.headers,
57
-
58
- // TLS information
59
- isTls: options.isTls,
60
- tlsVersion: options.tlsVersion,
61
-
62
- // Route information
63
- routeName: options.routeName,
64
- routeId: options.routeId,
65
-
66
- // Additional properties
67
- timestamp: Date.now(),
68
- connectionId: options.connectionId,
69
- };
70
- }
71
-
72
- /**
73
- * Handle a new incoming connection
74
- */
75
- public handleConnection(socket: plugins.net.Socket): void {
76
- const remoteIP = socket.remoteAddress || '';
77
- const localPort = socket.localPort || 0;
78
-
79
- // Always wrap the socket to prepare for potential PROXY protocol
80
- const wrappedSocket = new WrappedSocket(socket);
81
-
82
- // If this is from a trusted proxy, log it
83
- if (this.smartProxy.settings.proxyIPs?.includes(remoteIP)) {
84
- logger.log('debug', `Connection from trusted proxy ${remoteIP}, PROXY protocol parsing will be enabled`, {
85
- remoteIP,
86
- component: 'route-handler'
87
- });
88
- }
89
-
90
- // Validate IP against rate limits and connection limits
91
- // Note: For wrapped sockets, this will use the underlying socket IP until PROXY protocol is parsed
92
- const ipValidation = this.smartProxy.securityManager.validateIP(wrappedSocket.remoteAddress || '');
93
- if (!ipValidation.allowed) {
94
- connectionLogDeduplicator.log(
95
- 'ip-rejected',
96
- 'warn',
97
- `Connection rejected from ${wrappedSocket.remoteAddress}`,
98
- { remoteIP: wrappedSocket.remoteAddress, reason: ipValidation.reason, component: 'route-handler' },
99
- wrappedSocket.remoteAddress
100
- );
101
- cleanupSocket(wrappedSocket.socket, `rejected-${ipValidation.reason}`, { immediate: true });
102
- return;
103
- }
104
-
105
- // Create a new connection record with the wrapped socket
106
- const record = this.smartProxy.connectionManager.createConnection(wrappedSocket);
107
- if (!record) {
108
- // Connection was rejected due to limit - socket already destroyed by connection manager
109
- return;
110
- }
111
-
112
- // Emit new connection event
113
- this.newConnectionSubject.next(record);
114
- const connectionId = record.id;
115
-
116
- // Apply socket optimizations (apply to underlying socket)
117
- const underlyingSocket = wrappedSocket.socket;
118
- underlyingSocket.setNoDelay(this.smartProxy.settings.noDelay);
119
-
120
- // Apply keep-alive settings if enabled
121
- if (this.smartProxy.settings.keepAlive) {
122
- underlyingSocket.setKeepAlive(true, this.smartProxy.settings.keepAliveInitialDelay);
123
- record.hasKeepAlive = true;
124
-
125
- // Apply enhanced TCP keep-alive options if enabled
126
- if (this.smartProxy.settings.enableKeepAliveProbes) {
127
- try {
128
- // These are platform-specific and may not be available
129
- if ('setKeepAliveProbes' in underlyingSocket) {
130
- (underlyingSocket as any).setKeepAliveProbes(10);
131
- }
132
- if ('setKeepAliveInterval' in underlyingSocket) {
133
- (underlyingSocket as any).setKeepAliveInterval(1000);
134
- }
135
- } catch (err) {
136
- // Ignore errors - these are optional enhancements
137
- if (this.smartProxy.settings.enableDetailedLogging) {
138
- logger.log('warn', `Enhanced TCP keep-alive settings not supported`, { connectionId, error: err, component: 'route-handler' });
139
- }
140
- }
141
- }
142
- }
143
-
144
- if (this.smartProxy.settings.enableDetailedLogging) {
145
- logger.log('info',
146
- `New connection from ${remoteIP} on port ${localPort}. ` +
147
- `Keep-Alive: ${record.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
148
- `Active connections: ${this.smartProxy.connectionManager.getConnectionCount()}`,
149
- {
150
- connectionId,
151
- remoteIP,
152
- localPort,
153
- keepAlive: record.hasKeepAlive ? 'Enabled' : 'Disabled',
154
- activeConnections: this.smartProxy.connectionManager.getConnectionCount(),
155
- component: 'route-handler'
156
- }
157
- );
158
- } else {
159
- logger.log('info',
160
- `New connection from ${remoteIP} on port ${localPort}. Active connections: ${this.smartProxy.connectionManager.getConnectionCount()}`,
161
- {
162
- remoteIP,
163
- localPort,
164
- activeConnections: this.smartProxy.connectionManager.getConnectionCount(),
165
- component: 'route-handler'
166
- }
167
- );
168
- }
169
-
170
- // Handle the connection - wait for initial data to determine if it's TLS
171
- this.handleInitialData(wrappedSocket, record);
172
- }
173
-
174
- /**
175
- * Handle initial data from a connection to determine routing
176
- */
177
- private handleInitialData(socket: plugins.net.Socket | WrappedSocket, record: IConnectionRecord): void {
178
- const connectionId = record.id;
179
- const localPort = record.localPort;
180
- let initialDataReceived = false;
181
-
182
- // Check if any routes on this port require TLS handling
183
- const allRoutes = this.smartProxy.routeManager.getRoutes();
184
- const needsTlsHandling = allRoutes.some(route => {
185
- // Check if route matches this port
186
- const matchesPort = this.smartProxy.routeManager.getRoutesForPort(localPort).includes(route);
187
-
188
- return matchesPort &&
189
- route.action.type === 'forward' &&
190
- route.action.tls &&
191
- (route.action.tls.mode === 'terminate' ||
192
- route.action.tls.mode === 'passthrough');
193
- });
194
-
195
- // If no routes require TLS handling and it's not port 443, route immediately
196
- if (!needsTlsHandling && localPort !== 443) {
197
- // Extract underlying socket for socket-utils functions
198
- const underlyingSocket = getUnderlyingSocket(socket);
199
- // Set up proper socket handlers for immediate routing
200
- setupSocketHandlers(
201
- underlyingSocket,
202
- (reason) => {
203
- // Always cleanup when incoming socket closes
204
- // This prevents connection accumulation in proxy chains
205
- logger.log('debug', `Connection ${connectionId} closed during immediate routing: ${reason}`, {
206
- connectionId,
207
- remoteIP: record.remoteIP,
208
- reason,
209
- hasOutgoing: !!record.outgoing,
210
- outgoingState: record.outgoing?.readyState,
211
- component: 'route-handler'
212
- });
213
-
214
- // If there's a pending or established outgoing connection, destroy it
215
- if (record.outgoing && !record.outgoing.destroyed) {
216
- logger.log('debug', `Destroying outgoing connection for ${connectionId}`, {
217
- connectionId,
218
- outgoingState: record.outgoing.readyState,
219
- component: 'route-handler'
220
- });
221
- record.outgoing.destroy();
222
- }
223
-
224
- // Always cleanup the connection record
225
- this.smartProxy.connectionManager.cleanupConnection(record, reason);
226
- },
227
- undefined, // Use default timeout handler
228
- 'immediate-route-client'
229
- );
230
-
231
- // Route immediately for non-TLS connections
232
- this.routeConnection(socket, record, '', undefined);
233
- return;
234
- }
235
-
236
- // Otherwise, wait for initial data to check if it's TLS
237
- // Set an initial timeout for handshake data
238
- let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
239
- if (!initialDataReceived) {
240
- logger.log('warn', `No initial data received from ${record.remoteIP} after ${this.smartProxy.settings.initialDataTimeout}ms for connection ${connectionId}`, {
241
- connectionId,
242
- timeout: this.smartProxy.settings.initialDataTimeout,
243
- remoteIP: record.remoteIP,
244
- component: 'route-handler'
245
- });
246
-
247
- // Add a grace period
248
- setTimeout(() => {
249
- if (!initialDataReceived) {
250
- logger.log('warn', `Final initial data timeout after grace period for connection ${connectionId}`, {
251
- connectionId,
252
- component: 'route-handler'
253
- });
254
- if (record.incomingTerminationReason === null) {
255
- record.incomingTerminationReason = 'initial_timeout';
256
- this.smartProxy.connectionManager.incrementTerminationStat('incoming', 'initial_timeout');
257
- }
258
- socket.end();
259
- this.smartProxy.connectionManager.cleanupConnection(record, 'initial_timeout');
260
- }
261
- }, 30000);
262
- }
263
- }, this.smartProxy.settings.initialDataTimeout!);
264
-
265
- // Make sure timeout doesn't keep the process alive
266
- if (initialTimeout.unref) {
267
- initialTimeout.unref();
268
- }
269
-
270
- // Set up error handler
271
- socket.on('error', this.smartProxy.connectionManager.handleError('incoming', record));
272
-
273
- // Add close/end handlers to catch immediate disconnections
274
- socket.once('close', () => {
275
- if (!initialDataReceived) {
276
- logger.log('warn', `Connection ${connectionId} closed before sending initial data`, {
277
- connectionId,
278
- remoteIP: record.remoteIP,
279
- component: 'route-handler'
280
- });
281
- if (initialTimeout) {
282
- clearTimeout(initialTimeout);
283
- initialTimeout = null;
284
- }
285
- this.smartProxy.connectionManager.cleanupConnection(record, 'closed_before_data');
286
- }
287
- });
288
-
289
- socket.once('end', () => {
290
- if (!initialDataReceived) {
291
- logger.log('debug', `Connection ${connectionId} ended before sending initial data`, {
292
- connectionId,
293
- remoteIP: record.remoteIP,
294
- component: 'route-handler'
295
- });
296
- if (initialTimeout) {
297
- clearTimeout(initialTimeout);
298
- initialTimeout = null;
299
- }
300
- // Don't cleanup on 'end' - wait for 'close'
301
- }
302
- });
303
-
304
- // Handler for processing initial data (after potential PROXY protocol)
305
- const processInitialData = async (chunk: Buffer) => {
306
- // Create connection context for protocol detection
307
- const context = ProtocolDetector.createConnectionContext({
308
- sourceIp: record.remoteIP,
309
- sourcePort: socket.remotePort || 0,
310
- destIp: socket.localAddress || '',
311
- destPort: socket.localPort || 0,
312
- socketId: record.id
313
- });
314
-
315
- const detectionResult = await ProtocolDetector.detectWithContext(
316
- chunk,
317
- context,
318
- { extractFullHeaders: false } // Only extract essential info for routing
319
- );
320
-
321
- // Block non-TLS connections on port 443
322
- if (localPort === 443 && detectionResult.protocol !== 'tls') {
323
- logger.log('warn', `Non-TLS connection ${record.id} detected on port 443. Terminating connection - only TLS traffic is allowed on standard HTTPS port.`, {
324
- connectionId: record.id,
325
- detectedProtocol: detectionResult.protocol,
326
- message: 'Terminating connection - only TLS traffic is allowed on standard HTTPS port.',
327
- component: 'route-handler'
328
- });
329
- if (record.incomingTerminationReason === null) {
330
- record.incomingTerminationReason = 'non_tls_blocked';
331
- this.smartProxy.connectionManager.incrementTerminationStat('incoming', 'non_tls_blocked');
332
- }
333
- socket.end();
334
- this.smartProxy.connectionManager.cleanupConnection(record, 'non_tls_blocked');
335
- return;
336
- }
337
-
338
- // Extract domain and protocol info
339
- let serverName = '';
340
- if (detectionResult.protocol === 'tls') {
341
- record.isTLS = true;
342
- serverName = detectionResult.connectionInfo.domain || '';
343
-
344
- // Lock the connection to the negotiated SNI
345
- record.lockedDomain = serverName;
346
-
347
- // Check if we should reject connections without SNI
348
- if (!serverName && this.smartProxy.settings.allowSessionTicket === false) {
349
- logger.log('warn', `No SNI detected in TLS ClientHello for connection ${record.id}; sending TLS alert`, {
350
- connectionId: record.id,
351
- component: 'route-handler'
352
- });
353
- if (record.incomingTerminationReason === null) {
354
- record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
355
- this.smartProxy.connectionManager.incrementTerminationStat(
356
- 'incoming',
357
- 'session_ticket_blocked_no_sni'
358
- );
359
- }
360
- const alert = Buffer.from([0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x70]);
361
- try {
362
- // Count the alert bytes being sent
363
- record.bytesSent += alert.length;
364
- if (this.smartProxy.metricsCollector) {
365
- this.smartProxy.metricsCollector.recordBytes(record.id, 0, alert.length);
366
- }
367
-
368
- socket.cork();
369
- socket.write(alert);
370
- socket.uncork();
371
- socket.end();
372
- } catch {
373
- socket.end();
374
- }
375
- this.smartProxy.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
376
- return;
377
- }
378
-
379
- if (this.smartProxy.settings.enableDetailedLogging) {
380
- logger.log('info', `TLS connection with SNI`, {
381
- connectionId: record.id,
382
- serverName: serverName || '(empty)',
383
- component: 'route-handler'
384
- });
385
- }
386
- } else if (detectionResult.protocol === 'http') {
387
- // For HTTP, extract domain from Host header
388
- serverName = detectionResult.connectionInfo.domain || '';
389
-
390
- // Store HTTP-specific info for later use
391
- record.httpInfo = {
392
- method: detectionResult.connectionInfo.method,
393
- path: detectionResult.connectionInfo.path,
394
- headers: detectionResult.connectionInfo.headers
395
- };
396
-
397
- if (this.smartProxy.settings.enableDetailedLogging) {
398
- logger.log('info', `HTTP connection detected`, {
399
- connectionId: record.id,
400
- domain: serverName || '(no host header)',
401
- method: detectionResult.connectionInfo.method,
402
- path: detectionResult.connectionInfo.path,
403
- component: 'route-handler'
404
- });
405
- }
406
- }
407
-
408
- // Find the appropriate route for this connection
409
- this.routeConnection(socket, record, serverName, chunk, detectionResult);
410
- };
411
-
412
- // First data handler to capture initial TLS handshake or PROXY protocol
413
- socket.once('data', async (chunk: Buffer) => {
414
- // Clear the initial timeout since we've received data
415
- if (initialTimeout) {
416
- clearTimeout(initialTimeout);
417
- initialTimeout = null;
418
- }
419
-
420
- initialDataReceived = true;
421
- record.hasReceivedInitialData = true;
422
-
423
- // Check if this is from a trusted proxy and might have PROXY protocol
424
- if (this.smartProxy.settings.proxyIPs?.includes(socket.remoteAddress || '') && this.smartProxy.settings.acceptProxyProtocol !== false) {
425
- // Check if this starts with PROXY protocol
426
- if (chunk.toString('ascii', 0, Math.min(6, chunk.length)).startsWith('PROXY ')) {
427
- try {
428
- const parseResult = ProxyProtocolParser.parse(chunk);
429
-
430
- if (parseResult.proxyInfo) {
431
- // Update the wrapped socket with real client info (if it's a WrappedSocket)
432
- if (socket instanceof WrappedSocket) {
433
- socket.setProxyInfo(parseResult.proxyInfo.sourceIP, parseResult.proxyInfo.sourcePort);
434
- }
435
-
436
- // Update connection record with real client info
437
- record.remoteIP = parseResult.proxyInfo.sourceIP;
438
- record.remotePort = parseResult.proxyInfo.sourcePort;
439
-
440
- logger.log('info', `PROXY protocol parsed successfully`, {
441
- connectionId,
442
- realClientIP: parseResult.proxyInfo.sourceIP,
443
- realClientPort: parseResult.proxyInfo.sourcePort,
444
- proxyIP: socket.remoteAddress,
445
- component: 'route-handler'
446
- });
447
-
448
- // Process remaining data if any
449
- if (parseResult.remainingData.length > 0) {
450
- processInitialData(parseResult.remainingData);
451
- } else {
452
- // Wait for more data
453
- socket.once('data', processInitialData);
454
- }
455
- return;
456
- }
457
- } catch (error) {
458
- logger.log('error', `Failed to parse PROXY protocol from trusted proxy`, {
459
- connectionId,
460
- error: error.message,
461
- proxyIP: socket.remoteAddress,
462
- component: 'route-handler'
463
- });
464
- // Continue processing as normal data
465
- }
466
- }
467
- }
468
-
469
- // Process as normal data (no PROXY protocol)
470
- processInitialData(chunk);
471
- });
472
- }
473
-
474
- /**
475
- * Route the connection based on match criteria
476
- */
477
- private routeConnection(
478
- socket: plugins.net.Socket | WrappedSocket,
479
- record: IConnectionRecord,
480
- serverName: string,
481
- initialChunk?: Buffer,
482
- detectionResult?: any // Using any temporarily to avoid circular dependency issues
483
- ): void {
484
- const connectionId = record.id;
485
- const localPort = record.localPort;
486
- const remoteIP = record.remoteIP;
487
-
488
- // Check if this is an HTTP proxy port
489
- const isHttpProxyPort = this.smartProxy.settings.useHttpProxy?.includes(localPort);
490
-
491
- // For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
492
- const skipDomainCheck = isHttpProxyPort && !record.isTLS;
493
-
494
- // Create route context for matching
495
- const routeContext: IRouteContext = {
496
- port: localPort,
497
- domain: skipDomainCheck ? undefined : serverName, // Skip domain if HTTP proxy without TLS
498
- clientIp: remoteIP,
499
- serverIp: socket.localAddress || '',
500
- path: undefined, // We don't have path info at this point
501
- isTls: record.isTLS,
502
- tlsVersion: undefined, // We don't extract TLS version yet
503
- timestamp: Date.now(),
504
- connectionId: record.id
505
- };
506
-
507
- // Find matching route
508
- const routeMatch = this.smartProxy.routeManager.findMatchingRoute(routeContext);
509
-
510
- if (!routeMatch) {
511
- logger.log('warn', `No route found for ${serverName || 'connection'} on port ${localPort} (connection: ${connectionId})`, {
512
- connectionId,
513
- serverName: serverName || 'connection',
514
- localPort,
515
- component: 'route-handler'
516
- });
517
-
518
- // No matching route, use default/fallback handling
519
- logger.log('info', `Using default route handling for connection ${connectionId}`, {
520
- connectionId,
521
- component: 'route-handler'
522
- });
523
-
524
- // Check default security settings
525
- const defaultSecuritySettings = this.smartProxy.settings.defaults?.security;
526
- if (defaultSecuritySettings) {
527
- if (defaultSecuritySettings.ipAllowList && defaultSecuritySettings.ipAllowList.length > 0) {
528
- const isAllowed = this.smartProxy.securityManager.isIPAuthorized(
529
- remoteIP,
530
- defaultSecuritySettings.ipAllowList,
531
- defaultSecuritySettings.ipBlockList || []
532
- );
533
-
534
- if (!isAllowed) {
535
- logger.log('warn', `IP ${remoteIP} not in default allowed list for connection ${connectionId}`, {
536
- connectionId,
537
- remoteIP,
538
- component: 'route-handler'
539
- });
540
- socket.end();
541
- this.smartProxy.connectionManager.cleanupConnection(record, 'ip_blocked');
542
- return;
543
- }
544
- }
545
- }
546
-
547
- // Setup direct connection with default settings
548
- if (this.smartProxy.settings.defaults?.target) {
549
- // Use defaults from configuration
550
- const targetHost = this.smartProxy.settings.defaults.target.host;
551
- const targetPort = this.smartProxy.settings.defaults.target.port;
552
-
553
- return this.setupDirectConnection(
554
- socket,
555
- record,
556
- serverName,
557
- initialChunk,
558
- undefined,
559
- targetHost,
560
- targetPort
561
- );
562
- } else {
563
- // No default target available, terminate the connection
564
- logger.log('warn', `No default target configured for connection ${connectionId}. Closing connection`, {
565
- connectionId,
566
- component: 'route-handler'
567
- });
568
- socket.end();
569
- this.smartProxy.connectionManager.cleanupConnection(record, 'no_default_target');
570
- return;
571
- }
572
- }
573
-
574
- // A matching route was found
575
- const route = routeMatch.route;
576
-
577
- if (this.smartProxy.settings.enableDetailedLogging) {
578
- logger.log('info', `Route matched`, {
579
- connectionId,
580
- routeName: route.name || 'unnamed',
581
- serverName: serverName || 'connection',
582
- localPort,
583
- component: 'route-handler'
584
- });
585
- }
586
-
587
- // Apply route-specific security checks
588
- if (route.security) {
589
- // Check IP allow/block lists
590
- if (route.security.ipAllowList || route.security.ipBlockList) {
591
- const isIPAllowed = this.smartProxy.securityManager.isIPAuthorized(
592
- remoteIP,
593
- route.security.ipAllowList || [],
594
- route.security.ipBlockList || []
595
- );
596
-
597
- if (!isIPAllowed) {
598
- // Deduplicated logging for route IP blocks
599
- connectionLogDeduplicator.log(
600
- 'ip-rejected',
601
- 'warn',
602
- `IP blocked by route security`,
603
- {
604
- connectionId,
605
- remoteIP,
606
- routeName: route.name || 'unnamed',
607
- reason: 'route-ip-blocked',
608
- component: 'route-handler'
609
- },
610
- remoteIP
611
- );
612
- socket.end();
613
- this.smartProxy.connectionManager.cleanupConnection(record, 'route_ip_blocked');
614
- return;
615
- }
616
- }
617
-
618
- // Check max connections per route
619
- if (route.security.maxConnections !== undefined) {
620
- const routeId = route.id || route.name || 'unnamed';
621
- const currentConnections = this.smartProxy.connectionManager.getConnectionCountByRoute(routeId);
622
-
623
- if (currentConnections >= route.security.maxConnections) {
624
- // Deduplicated logging for route connection limits
625
- connectionLogDeduplicator.log(
626
- 'connection-rejected',
627
- 'warn',
628
- `Route connection limit reached`,
629
- {
630
- connectionId,
631
- routeName: route.name,
632
- currentConnections,
633
- maxConnections: route.security.maxConnections,
634
- reason: 'route-limit',
635
- component: 'route-handler'
636
- },
637
- `route-limit-${route.name}`
638
- );
639
- socket.end();
640
- this.smartProxy.connectionManager.cleanupConnection(record, 'route_connection_limit');
641
- return;
642
- }
643
- }
644
-
645
- // Check authentication requirements
646
- if (route.security.authentication || route.security.basicAuth || route.security.jwtAuth) {
647
- // Authentication checks would typically happen at the HTTP layer
648
- // For non-HTTP connections or passthrough, we can't enforce authentication
649
- if (route.action.type === 'forward' && route.action.tls?.mode !== 'terminate') {
650
- logger.log('warn', `Route ${route.name} has authentication configured but it cannot be enforced for non-terminated connections`, {
651
- connectionId,
652
- routeName: route.name,
653
- tlsMode: route.action.tls?.mode || 'none',
654
- component: 'route-handler'
655
- });
656
- }
657
- }
658
- }
659
-
660
- // Handle the route based on its action type
661
- switch (route.action.type) {
662
- case 'forward':
663
- return this.handleForwardAction(socket, record, route, initialChunk, detectionResult);
664
-
665
- case 'socket-handler':
666
- logger.log('info', `Handling socket-handler action for route ${route.name}`, {
667
- connectionId,
668
- routeName: route.name,
669
- component: 'route-handler'
670
- });
671
- this.handleSocketHandlerAction(socket, record, route, initialChunk);
672
- return;
673
-
674
- default:
675
- logger.log('error', `Unknown action type '${(route.action as any).type}' for connection ${connectionId}`, {
676
- connectionId,
677
- actionType: (route.action as any).type,
678
- component: 'route-handler'
679
- });
680
- socket.end();
681
- this.smartProxy.connectionManager.cleanupConnection(record, 'unknown_action');
682
- }
683
- }
684
-
685
- /**
686
- * Select the appropriate target from the targets array based on sub-matching criteria
687
- */
688
- private selectTarget(
689
- targets: IRouteTarget[],
690
- context: {
691
- port: number;
692
- path?: string;
693
- headers?: Record<string, string>;
694
- method?: string;
695
- }
696
- ): IRouteTarget | null {
697
- // Sort targets by priority (higher first)
698
- const sortedTargets = [...targets].sort((a, b) => (b.priority || 0) - (a.priority || 0));
699
-
700
- // Find the first matching target
701
- for (const target of sortedTargets) {
702
- if (!target.match) {
703
- // No match criteria means this is a default/fallback target
704
- return target;
705
- }
706
-
707
- // Check port match
708
- if (target.match.ports && !target.match.ports.includes(context.port)) {
709
- continue;
710
- }
711
-
712
- // Check path match (supports wildcards)
713
- if (target.match.path && context.path) {
714
- const pathPattern = target.match.path.replace(/\*/g, '.*');
715
- const pathRegex = new RegExp(`^${pathPattern}$`);
716
- if (!pathRegex.test(context.path)) {
717
- continue;
718
- }
719
- }
720
-
721
- // Check method match
722
- if (target.match.method && context.method && !target.match.method.includes(context.method)) {
723
- continue;
724
- }
725
-
726
- // Check headers match
727
- if (target.match.headers && context.headers) {
728
- let headersMatch = true;
729
- for (const [key, pattern] of Object.entries(target.match.headers)) {
730
- const headerValue = context.headers[key.toLowerCase()];
731
- if (!headerValue) {
732
- headersMatch = false;
733
- break;
734
- }
735
-
736
- if (pattern instanceof RegExp) {
737
- if (!pattern.test(headerValue)) {
738
- headersMatch = false;
739
- break;
740
- }
741
- } else if (headerValue !== pattern) {
742
- headersMatch = false;
743
- break;
744
- }
745
- }
746
- if (!headersMatch) {
747
- continue;
748
- }
749
- }
750
-
751
- // All criteria matched
752
- return target;
753
- }
754
-
755
- // No matching target found, return the first target without match criteria (default)
756
- return sortedTargets.find(t => !t.match) || null;
757
- }
758
-
759
- /**
760
- * Handle a forward action for a route
761
- */
762
- private handleForwardAction(
763
- socket: plugins.net.Socket | WrappedSocket,
764
- record: IConnectionRecord,
765
- route: IRouteConfig,
766
- initialChunk?: Buffer,
767
- detectionResult?: any // Using any temporarily to avoid circular dependency issues
768
- ): void {
769
- const connectionId = record.id;
770
- const action = route.action as IRouteAction;
771
-
772
- // Store the route config in the connection record for metrics and other uses
773
- record.routeConfig = route;
774
- record.routeId = route.id || route.name || 'unnamed';
775
-
776
- // Track connection by route
777
- this.smartProxy.connectionManager.trackConnectionByRoute(record.routeId, record.id);
778
-
779
- // Check if this route uses NFTables for forwarding
780
- if (action.forwardingEngine === 'nftables') {
781
- // NFTables handles packet forwarding at the kernel level
782
- // The application should NOT interfere with these connections
783
-
784
- // Log the connection for monitoring purposes
785
- if (this.smartProxy.settings.enableDetailedLogging) {
786
- logger.log('info', `NFTables forwarding (kernel-level)`, {
787
- connectionId: record.id,
788
- source: `${record.remoteIP}:${socket.remotePort}`,
789
- destination: `${socket.localAddress}:${record.localPort}`,
790
- routeName: route.name || 'unnamed',
791
- domain: record.lockedDomain || 'n/a',
792
- component: 'route-handler'
793
- });
794
- } else {
795
- logger.log('info', `NFTables forwarding`, {
796
- connectionId: record.id,
797
- remoteIP: record.remoteIP,
798
- localPort: record.localPort,
799
- routeName: route.name || 'unnamed',
800
- component: 'route-handler'
801
- });
802
- }
803
-
804
- // Additional NFTables-specific logging if configured
805
- if (action.nftables) {
806
- const nftConfig = action.nftables;
807
- if (this.smartProxy.settings.enableDetailedLogging) {
808
- logger.log('info', `NFTables config`, {
809
- connectionId: record.id,
810
- protocol: nftConfig.protocol || 'tcp',
811
- preserveSourceIP: nftConfig.preserveSourceIP || false,
812
- priority: nftConfig.priority || 'default',
813
- maxRate: nftConfig.maxRate || 'unlimited',
814
- component: 'route-handler'
815
- });
816
- }
817
- }
818
-
819
- // For NFTables routes, we should still track the connection but not interfere
820
- // Mark the connection as using network proxy so it's cleaned up properly
821
- record.usingNetworkProxy = true;
822
-
823
- // We don't close the socket - just let it remain open
824
- // The kernel-level NFTables rules will handle the actual forwarding
825
-
826
- // Set up cleanup when the socket eventually closes
827
- socket.once('close', () => {
828
- this.smartProxy.connectionManager.cleanupConnection(record, 'nftables_closed');
829
- });
830
-
831
- return;
832
- }
833
-
834
- // Select the appropriate target from the targets array
835
- if (!action.targets || action.targets.length === 0) {
836
- logger.log('error', `Forward action missing targets configuration for connection ${connectionId}`, {
837
- connectionId,
838
- component: 'route-handler'
839
- });
840
- socket.end();
841
- this.smartProxy.connectionManager.cleanupConnection(record, 'missing_targets');
842
- return;
843
- }
844
-
845
- // Create context for target selection
846
- const targetSelectionContext = {
847
- port: record.localPort,
848
- path: record.httpInfo?.path,
849
- headers: record.httpInfo?.headers,
850
- method: record.httpInfo?.method
851
- };
852
-
853
- const selectedTarget = this.selectTarget(action.targets, targetSelectionContext);
854
- if (!selectedTarget) {
855
- logger.log('error', `No matching target found for connection ${connectionId}`, {
856
- connectionId,
857
- port: targetSelectionContext.port,
858
- component: 'route-handler'
859
- });
860
- socket.end();
861
- this.smartProxy.connectionManager.cleanupConnection(record, 'no_matching_target');
862
- return;
863
- }
864
-
865
- // Create the routing context for this connection
866
- const routeContext = this.createRouteContext({
867
- connectionId: record.id,
868
- port: record.localPort,
869
- domain: record.lockedDomain,
870
- clientIp: record.remoteIP,
871
- serverIp: socket.localAddress || '',
872
- isTls: record.isTLS || false,
873
- tlsVersion: record.tlsVersion,
874
- routeName: route.name,
875
- routeId: route.id,
876
- });
877
-
878
- // Note: Route contexts are not cached to ensure fresh data for each connection
879
-
880
- // Determine host using function or static value
881
- let targetHost: string | string[];
882
- if (typeof selectedTarget.host === 'function') {
883
- try {
884
- targetHost = selectedTarget.host(routeContext);
885
- if (this.smartProxy.settings.enableDetailedLogging) {
886
- logger.log('info', `Dynamic host resolved to ${Array.isArray(targetHost) ? targetHost.join(', ') : targetHost} for connection ${connectionId}`, {
887
- connectionId,
888
- targetHost: Array.isArray(targetHost) ? targetHost.join(', ') : targetHost,
889
- component: 'route-handler'
890
- });
891
- }
892
- } catch (err) {
893
- logger.log('error', `Error in host mapping function for connection ${connectionId}: ${err}`, {
894
- connectionId,
895
- error: err,
896
- component: 'route-handler'
897
- });
898
- socket.end();
899
- this.smartProxy.connectionManager.cleanupConnection(record, 'host_mapping_error');
900
- return;
901
- }
902
- } else {
903
- targetHost = selectedTarget.host;
904
- }
905
-
906
- // If an array of hosts, select one randomly for load balancing
907
- const selectedHost = Array.isArray(targetHost)
908
- ? targetHost[Math.floor(Math.random() * targetHost.length)]
909
- : targetHost;
910
-
911
- // Determine port using function or static value
912
- let targetPort: number;
913
- if (typeof selectedTarget.port === 'function') {
914
- try {
915
- targetPort = selectedTarget.port(routeContext);
916
- if (this.smartProxy.settings.enableDetailedLogging) {
917
- logger.log('info', `Dynamic port mapping from ${record.localPort} to ${targetPort} for connection ${connectionId}`, {
918
- connectionId,
919
- sourcePort: record.localPort,
920
- targetPort,
921
- component: 'route-handler'
922
- });
923
- }
924
- // Store the resolved target port in the context for potential future use
925
- routeContext.targetPort = targetPort;
926
- } catch (err) {
927
- logger.log('error', `Error in port mapping function for connection ${connectionId}: ${err}`, {
928
- connectionId,
929
- error: err,
930
- component: 'route-handler'
931
- });
932
- socket.end();
933
- this.smartProxy.connectionManager.cleanupConnection(record, 'port_mapping_error');
934
- return;
935
- }
936
- } else if (selectedTarget.port === 'preserve') {
937
- // Use incoming port if port is 'preserve'
938
- targetPort = record.localPort;
939
- } else {
940
- // Use static port from configuration
941
- targetPort = selectedTarget.port;
942
- }
943
-
944
- // Store the resolved host in the context
945
- routeContext.targetHost = selectedHost;
946
-
947
- // Get effective settings (target overrides route-level settings)
948
- const effectiveTls = selectedTarget.tls || action.tls;
949
- const effectiveWebsocket = selectedTarget.websocket || action.websocket;
950
- const effectiveSendProxyProtocol = selectedTarget.sendProxyProtocol !== undefined
951
- ? selectedTarget.sendProxyProtocol
952
- : action.sendProxyProtocol;
953
-
954
- // Determine if this needs TLS handling
955
- if (effectiveTls) {
956
- switch (effectiveTls.mode) {
957
- case 'passthrough':
958
- // For TLS passthrough, just forward directly
959
- if (this.smartProxy.settings.enableDetailedLogging) {
960
- logger.log('info', `Using TLS passthrough to ${selectedHost}:${targetPort} for connection ${connectionId}`, {
961
- connectionId,
962
- targetHost: selectedHost,
963
- targetPort,
964
- component: 'route-handler'
965
- });
966
- }
967
-
968
- return this.setupDirectConnection(
969
- socket,
970
- record,
971
- record.lockedDomain,
972
- initialChunk,
973
- undefined,
974
- selectedHost,
975
- targetPort
976
- );
977
-
978
- case 'terminate':
979
- case 'terminate-and-reencrypt':
980
- // For TLS termination, use HttpProxy
981
- if (this.smartProxy.httpProxyBridge.getHttpProxy()) {
982
- if (this.smartProxy.settings.enableDetailedLogging) {
983
- logger.log('info', `Using HttpProxy for TLS termination to ${Array.isArray(selectedTarget.host) ? selectedTarget.host.join(', ') : selectedTarget.host} for connection ${connectionId}`, {
984
- connectionId,
985
- targetHost: selectedTarget.host,
986
- component: 'route-handler'
987
- });
988
- }
989
-
990
- // If we have an initial chunk with TLS data, start processing it
991
- if (initialChunk && record.isTLS) {
992
- this.smartProxy.httpProxyBridge.forwardToHttpProxy(
993
- connectionId,
994
- socket,
995
- record,
996
- initialChunk,
997
- this.smartProxy.settings.httpProxyPort || 8443,
998
- (reason) => this.smartProxy.connectionManager.cleanupConnection(record, reason)
999
- );
1000
- return;
1001
- }
1002
-
1003
- // This shouldn't normally happen - we should have TLS data at this point
1004
- logger.log('error', `TLS termination route without TLS data for connection ${connectionId}`, {
1005
- connectionId,
1006
- component: 'route-handler'
1007
- });
1008
- socket.end();
1009
- this.smartProxy.connectionManager.cleanupConnection(record, 'tls_error');
1010
- return;
1011
- } else {
1012
- logger.log('error', `HttpProxy not available for TLS termination for connection ${connectionId}`, {
1013
- connectionId,
1014
- component: 'route-handler'
1015
- });
1016
- socket.end();
1017
- this.smartProxy.connectionManager.cleanupConnection(record, 'no_http_proxy');
1018
- return;
1019
- }
1020
- }
1021
- } else {
1022
- // No TLS settings - check if this port should use HttpProxy
1023
- const isHttpProxyPort = this.smartProxy.settings.useHttpProxy?.includes(record.localPort);
1024
-
1025
- // Debug logging
1026
- if (this.smartProxy.settings.enableDetailedLogging) {
1027
- logger.log('debug', `Checking HttpProxy forwarding: port=${record.localPort}, useHttpProxy=${JSON.stringify(this.smartProxy.settings.useHttpProxy)}, isHttpProxyPort=${isHttpProxyPort}, hasHttpProxy=${!!this.smartProxy.httpProxyBridge.getHttpProxy()}`, {
1028
- connectionId,
1029
- localPort: record.localPort,
1030
- useHttpProxy: this.smartProxy.settings.useHttpProxy,
1031
- isHttpProxyPort,
1032
- hasHttpProxy: !!this.smartProxy.httpProxyBridge.getHttpProxy(),
1033
- component: 'route-handler'
1034
- });
1035
- }
1036
-
1037
- if (isHttpProxyPort && this.smartProxy.httpProxyBridge.getHttpProxy()) {
1038
- // Forward non-TLS connections to HttpProxy if configured
1039
- if (this.smartProxy.settings.enableDetailedLogging) {
1040
- logger.log('info', `Using HttpProxy for non-TLS connection ${connectionId} on port ${record.localPort}`, {
1041
- connectionId,
1042
- port: record.localPort,
1043
- component: 'route-handler'
1044
- });
1045
- }
1046
-
1047
- this.smartProxy.httpProxyBridge.forwardToHttpProxy(
1048
- connectionId,
1049
- socket,
1050
- record,
1051
- initialChunk,
1052
- this.smartProxy.settings.httpProxyPort || 8443,
1053
- (reason) => this.smartProxy.connectionManager.cleanupConnection(record, reason)
1054
- );
1055
- return;
1056
- } else {
1057
- // Basic forwarding
1058
- if (this.smartProxy.settings.enableDetailedLogging) {
1059
- logger.log('info', `Using basic forwarding to ${Array.isArray(selectedTarget.host) ? selectedTarget.host.join(', ') : selectedTarget.host}:${selectedTarget.port} for connection ${connectionId}`, {
1060
- connectionId,
1061
- targetHost: selectedTarget.host,
1062
- targetPort: selectedTarget.port,
1063
- component: 'route-handler'
1064
- });
1065
- }
1066
-
1067
- // Get the appropriate host value
1068
- let targetHost: string;
1069
-
1070
- if (typeof selectedTarget.host === 'function') {
1071
- // For function-based host, use the same routeContext created earlier
1072
- const hostResult = selectedTarget.host(routeContext);
1073
- targetHost = Array.isArray(hostResult)
1074
- ? hostResult[Math.floor(Math.random() * hostResult.length)]
1075
- : hostResult;
1076
- } else {
1077
- // For static host value
1078
- targetHost = Array.isArray(selectedTarget.host)
1079
- ? selectedTarget.host[Math.floor(Math.random() * selectedTarget.host.length)]
1080
- : selectedTarget.host;
1081
- }
1082
-
1083
- // Determine port - either function-based, static, or preserve incoming port
1084
- let targetPort: number;
1085
- if (typeof selectedTarget.port === 'function') {
1086
- targetPort = selectedTarget.port(routeContext);
1087
- } else if (selectedTarget.port === 'preserve') {
1088
- targetPort = record.localPort;
1089
- } else {
1090
- targetPort = selectedTarget.port;
1091
- }
1092
-
1093
- // Update the connection record and context with resolved values
1094
- record.targetHost = targetHost;
1095
- record.targetPort = targetPort;
1096
-
1097
- return this.setupDirectConnection(
1098
- socket,
1099
- record,
1100
- record.lockedDomain,
1101
- initialChunk,
1102
- undefined,
1103
- targetHost,
1104
- targetPort
1105
- );
1106
- }
1107
- }
1108
- }
1109
-
1110
- /**
1111
- * Handle a socket-handler action for a route
1112
- */
1113
- private async handleSocketHandlerAction(
1114
- socket: plugins.net.Socket | WrappedSocket,
1115
- record: IConnectionRecord,
1116
- route: IRouteConfig,
1117
- initialChunk?: Buffer
1118
- ): Promise<void> {
1119
- const connectionId = record.id;
1120
-
1121
- // Store the route config in the connection record for metrics and other uses
1122
- record.routeConfig = route;
1123
- record.routeId = route.id || route.name || 'unnamed';
1124
-
1125
- // Track connection by route
1126
- this.smartProxy.connectionManager.trackConnectionByRoute(record.routeId, record.id);
1127
-
1128
- if (!route.action.socketHandler) {
1129
- logger.log('error', 'socket-handler action missing socketHandler function', {
1130
- connectionId,
1131
- routeName: route.name,
1132
- component: 'route-handler'
1133
- });
1134
- socket.destroy();
1135
- this.smartProxy.connectionManager.cleanupConnection(record, 'missing_handler');
1136
- return;
1137
- }
1138
-
1139
- // Track event listeners added by the handler so we can clean them up
1140
- const originalOn = socket.on.bind(socket);
1141
- const originalOnce = socket.once.bind(socket);
1142
- const trackedListeners: Array<{event: string; listener: (...args: any[]) => void}> = [];
1143
-
1144
- // Override socket.on to track listeners
1145
- socket.on = function(event: string, listener: (...args: any[]) => void) {
1146
- trackedListeners.push({event, listener});
1147
- return originalOn(event, listener);
1148
- } as any;
1149
-
1150
- // Override socket.once to track listeners
1151
- socket.once = function(event: string, listener: (...args: any[]) => void) {
1152
- trackedListeners.push({event, listener});
1153
- return originalOnce(event, listener);
1154
- } as any;
1155
-
1156
- // Set up automatic cleanup when socket closes
1157
- const cleanupHandler = () => {
1158
- // Remove all tracked listeners
1159
- for (const {event, listener} of trackedListeners) {
1160
- socket.removeListener(event, listener);
1161
- }
1162
- // Restore original methods
1163
- socket.on = originalOn;
1164
- socket.once = originalOnce;
1165
- };
1166
-
1167
- // Listen for socket close to trigger cleanup
1168
- originalOnce('close', cleanupHandler);
1169
- originalOnce('error', cleanupHandler);
1170
-
1171
- // Create route context for the handler
1172
- const routeContext = this.createRouteContext({
1173
- connectionId: record.id,
1174
- port: record.localPort,
1175
- domain: record.lockedDomain,
1176
- clientIp: record.remoteIP,
1177
- serverIp: socket.localAddress || '',
1178
- isTls: record.isTLS || false,
1179
- tlsVersion: record.tlsVersion,
1180
- routeName: route.name,
1181
- routeId: route.id,
1182
- });
1183
-
1184
- try {
1185
- // Call the handler with the appropriate socket (extract underlying if needed)
1186
- const handlerSocket = getUnderlyingSocket(socket);
1187
- const result = route.action.socketHandler(handlerSocket, routeContext);
1188
-
1189
- // Handle async handlers properly
1190
- if (result instanceof Promise) {
1191
- result
1192
- .then(() => {
1193
- // Emit initial chunk after async handler completes
1194
- if (initialChunk && initialChunk.length > 0) {
1195
- socket.emit('data', initialChunk);
1196
- }
1197
- })
1198
- .catch(error => {
1199
- logger.log('error', 'Socket handler error', {
1200
- connectionId,
1201
- routeName: route.name,
1202
- error: error.message,
1203
- component: 'route-handler'
1204
- });
1205
- // Remove all event listeners before destroying to prevent memory leaks
1206
- socket.removeAllListeners();
1207
- if (!socket.destroyed) {
1208
- socket.destroy();
1209
- }
1210
- this.smartProxy.connectionManager.cleanupConnection(record, 'handler_error');
1211
- });
1212
- } else {
1213
- // For sync handlers, emit on next tick
1214
- if (initialChunk && initialChunk.length > 0) {
1215
- process.nextTick(() => {
1216
- socket.emit('data', initialChunk);
1217
- });
1218
- }
1219
- }
1220
- } catch (error) {
1221
- logger.log('error', 'Socket handler error', {
1222
- connectionId,
1223
- routeName: route.name,
1224
- error: error.message,
1225
- component: 'route-handler'
1226
- });
1227
- // Remove all event listeners before destroying to prevent memory leaks
1228
- socket.removeAllListeners();
1229
- if (!socket.destroyed) {
1230
- socket.destroy();
1231
- }
1232
- this.smartProxy.connectionManager.cleanupConnection(record, 'handler_error');
1233
- }
1234
- }
1235
-
1236
-
1237
- /**
1238
- * Sets up a direct connection to the target
1239
- */
1240
- private setupDirectConnection(
1241
- socket: plugins.net.Socket | WrappedSocket,
1242
- record: IConnectionRecord,
1243
- serverName?: string,
1244
- initialChunk?: Buffer,
1245
- overridePort?: number,
1246
- targetHost?: string,
1247
- targetPort?: number
1248
- ): void {
1249
- const connectionId = record.id;
1250
-
1251
- // Determine target host and port if not provided
1252
- const finalTargetHost =
1253
- targetHost || record.targetHost || this.smartProxy.settings.defaults?.target?.host || 'localhost';
1254
-
1255
- // Determine target port
1256
- const finalTargetPort =
1257
- targetPort ||
1258
- record.targetPort ||
1259
- (overridePort !== undefined ? overridePort : this.smartProxy.settings.defaults?.target?.port || 443);
1260
-
1261
- // Update record with final target information
1262
- record.targetHost = finalTargetHost;
1263
- record.targetPort = finalTargetPort;
1264
-
1265
- if (this.smartProxy.settings.enableDetailedLogging) {
1266
- logger.log('info', `Setting up direct connection ${connectionId} to ${finalTargetHost}:${finalTargetPort}`, {
1267
- connectionId,
1268
- targetHost: finalTargetHost,
1269
- targetPort: finalTargetPort,
1270
- component: 'route-handler'
1271
- });
1272
- }
1273
-
1274
- // Setup connection options
1275
- const connectionOptions: plugins.net.NetConnectOpts = {
1276
- host: finalTargetHost,
1277
- port: finalTargetPort,
1278
- };
1279
-
1280
- // Preserve source IP if configured
1281
- if (this.smartProxy.settings.defaults?.preserveSourceIP || this.smartProxy.settings.preserveSourceIP) {
1282
- connectionOptions.localAddress = record.remoteIP.replace('::ffff:', '');
1283
- }
1284
-
1285
- // Store initial data if provided
1286
- if (initialChunk) {
1287
- // Don't count bytes here - they will be counted when actually forwarded through bidirectional forwarding
1288
- record.pendingData.push(Buffer.from(initialChunk));
1289
- record.pendingDataSize = initialChunk.length;
1290
- }
1291
-
1292
- // Create the target socket with immediate error handling
1293
- const targetSocket = createSocketWithErrorHandler({
1294
- port: finalTargetPort,
1295
- host: finalTargetHost,
1296
- timeout: this.smartProxy.settings.connectionTimeout || 30000, // Connection timeout (default: 30s)
1297
- onError: (error) => {
1298
- // Connection failed - clean up everything immediately
1299
- // Check if connection record is still valid (client might have disconnected)
1300
- if (record.connectionClosed) {
1301
- logger.log('debug', `Backend connection failed but client already disconnected for ${connectionId}`, {
1302
- connectionId,
1303
- errorCode: (error as any).code,
1304
- component: 'route-handler'
1305
- });
1306
- return;
1307
- }
1308
-
1309
- logger.log('error',
1310
- `Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${error.message} (${(error as any).code})`,
1311
- {
1312
- connectionId,
1313
- targetHost: finalTargetHost,
1314
- targetPort: finalTargetPort,
1315
- errorMessage: error.message,
1316
- errorCode: (error as any).code,
1317
- component: 'route-handler'
1318
- }
1319
- );
1320
-
1321
- // Log specific error types for easier debugging
1322
- if ((error as any).code === 'ECONNREFUSED') {
1323
- logger.log('error',
1324
- `Connection ${connectionId}: Target ${finalTargetHost}:${finalTargetPort} refused connection. Check if the target service is running and listening on that port.`,
1325
- {
1326
- connectionId,
1327
- targetHost: finalTargetHost,
1328
- targetPort: finalTargetPort,
1329
- recommendation: 'Check if the target service is running and listening on that port.',
1330
- component: 'route-handler'
1331
- }
1332
- );
1333
- }
1334
-
1335
- // Resume the incoming socket to prevent it from hanging
1336
- if (socket && !socket.destroyed) {
1337
- socket.resume();
1338
- }
1339
-
1340
- // Clean up the incoming socket
1341
- if (socket && !socket.destroyed) {
1342
- socket.destroy();
1343
- }
1344
-
1345
- // Clean up the connection record - this is critical!
1346
- this.smartProxy.connectionManager.cleanupConnection(record, `connection_failed_${(error as any).code || 'unknown'}`);
1347
- },
1348
- onConnect: async () => {
1349
- if (this.smartProxy.settings.enableDetailedLogging) {
1350
- logger.log('info', `Connection ${connectionId} established to target ${finalTargetHost}:${finalTargetPort}`, {
1351
- connectionId,
1352
- targetHost: finalTargetHost,
1353
- targetPort: finalTargetPort,
1354
- component: 'route-handler'
1355
- });
1356
- }
1357
-
1358
- // Clear any error listeners added by createSocketWithErrorHandler
1359
- targetSocket.removeAllListeners('error');
1360
-
1361
- // Add the normal error handler for established connections
1362
- targetSocket.on('error', this.smartProxy.connectionManager.handleError('outgoing', record));
1363
-
1364
- // Check if we should send PROXY protocol header
1365
- const shouldSendProxyProtocol = record.routeConfig?.action?.sendProxyProtocol ||
1366
- this.smartProxy.settings.sendProxyProtocol;
1367
-
1368
- if (shouldSendProxyProtocol) {
1369
- try {
1370
- // Generate PROXY protocol header
1371
- const proxyInfo = {
1372
- protocol: (record.remoteIP.includes(':') ? 'TCP6' : 'TCP4') as 'TCP4' | 'TCP6',
1373
- sourceIP: record.remoteIP,
1374
- sourcePort: record.remotePort || socket.remotePort || 0,
1375
- destinationIP: socket.localAddress || '',
1376
- destinationPort: socket.localPort || 0
1377
- };
1378
-
1379
- const proxyHeader = ProxyProtocolParser.generate(proxyInfo);
1380
-
1381
- // Note: PROXY protocol headers are sent to the backend, not to the client
1382
- // They are internal protocol overhead and shouldn't be counted in client-facing metrics
1383
-
1384
- // Send PROXY protocol header first
1385
- await new Promise<void>((resolve, reject) => {
1386
- targetSocket.write(proxyHeader, (err) => {
1387
- if (err) {
1388
- logger.log('error', `Failed to send PROXY protocol header`, {
1389
- connectionId,
1390
- error: err.message,
1391
- component: 'route-handler'
1392
- });
1393
- reject(err);
1394
- } else {
1395
- logger.log('info', `PROXY protocol header sent to backend`, {
1396
- connectionId,
1397
- targetHost: finalTargetHost,
1398
- targetPort: finalTargetPort,
1399
- sourceIP: proxyInfo.sourceIP,
1400
- sourcePort: proxyInfo.sourcePort,
1401
- component: 'route-handler'
1402
- });
1403
- resolve();
1404
- }
1405
- });
1406
- });
1407
- } catch (error) {
1408
- logger.log('error', `Error sending PROXY protocol header`, {
1409
- connectionId,
1410
- error: error.message,
1411
- component: 'route-handler'
1412
- });
1413
- // Continue anyway - don't break the connection
1414
- }
1415
- }
1416
-
1417
- // Flush any pending data to target
1418
- if (record.pendingData.length > 0) {
1419
- const combinedData = Buffer.concat(record.pendingData);
1420
-
1421
- if (this.smartProxy.settings.enableDetailedLogging) {
1422
- console.log(
1423
- `[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`
1424
- );
1425
- }
1426
-
1427
- // Write pending data immediately
1428
- targetSocket.write(combinedData, (err) => {
1429
- if (err) {
1430
- logger.log('error', `Error writing pending data to target for connection ${connectionId}: ${err.message}`, {
1431
- connectionId,
1432
- error: err.message,
1433
- component: 'route-handler'
1434
- });
1435
- return this.smartProxy.connectionManager.cleanupConnection(record, 'write_error');
1436
- }
1437
- });
1438
-
1439
- // Clear the buffer now that we've processed it
1440
- record.pendingData = [];
1441
- record.pendingDataSize = 0;
1442
- }
1443
-
1444
- // Use centralized bidirectional forwarding setup
1445
- // Extract underlying sockets for socket-utils functions
1446
- const incomingSocket = getUnderlyingSocket(socket);
1447
-
1448
- setupBidirectionalForwarding(incomingSocket, targetSocket, {
1449
- onClientData: (chunk) => {
1450
- record.bytesReceived += chunk.length;
1451
- this.smartProxy.timeoutManager.updateActivity(record);
1452
-
1453
- // Record bytes for metrics
1454
- if (this.smartProxy.metricsCollector) {
1455
- this.smartProxy.metricsCollector.recordBytes(record.id, chunk.length, 0);
1456
- }
1457
- },
1458
- onServerData: (chunk) => {
1459
- record.bytesSent += chunk.length;
1460
- this.smartProxy.timeoutManager.updateActivity(record);
1461
-
1462
- // Record bytes for metrics
1463
- if (this.smartProxy.metricsCollector) {
1464
- this.smartProxy.metricsCollector.recordBytes(record.id, 0, chunk.length);
1465
- }
1466
- },
1467
- onCleanup: (reason) => {
1468
- this.smartProxy.connectionManager.cleanupConnection(record, reason);
1469
- },
1470
- enableHalfOpen: false // Default: close both when one closes (required for proxy chains)
1471
- });
1472
-
1473
- // Apply timeouts using TimeoutManager
1474
- const timeout = this.smartProxy.timeoutManager.getEffectiveInactivityTimeout(record);
1475
- // Skip timeout for immortal connections (MAX_SAFE_INTEGER would cause issues)
1476
- if (timeout !== Number.MAX_SAFE_INTEGER) {
1477
- const safeTimeout = this.smartProxy.timeoutManager.ensureSafeTimeout(timeout);
1478
- socket.setTimeout(safeTimeout);
1479
- targetSocket.setTimeout(safeTimeout);
1480
- }
1481
-
1482
- // Log successful connection
1483
- logger.log('info',
1484
- `Connection established: ${record.remoteIP} -> ${finalTargetHost}:${finalTargetPort}` +
1485
- `${serverName ? ` (SNI: ${serverName})` : record.lockedDomain ? ` (Domain: ${record.lockedDomain})` : ''}`,
1486
- {
1487
- remoteIP: record.remoteIP,
1488
- targetHost: finalTargetHost,
1489
- targetPort: finalTargetPort,
1490
- sni: serverName || undefined,
1491
- domain: !serverName && record.lockedDomain ? record.lockedDomain : undefined,
1492
- component: 'route-handler'
1493
- }
1494
- );
1495
-
1496
- // Add TLS renegotiation handler if needed
1497
- if (serverName) {
1498
- // Create connection info object for the existing connection
1499
- const connInfo = {
1500
- sourceIp: record.remoteIP,
1501
- sourcePort: record.incoming.remotePort || 0,
1502
- destIp: record.incoming.localAddress || '',
1503
- destPort: record.incoming.localPort || 0,
1504
- };
1505
-
1506
- // Create a renegotiation handler function
1507
- const renegotiationHandler = this.smartProxy.tlsManager.createRenegotiationHandler(
1508
- connectionId,
1509
- serverName,
1510
- connInfo,
1511
- (_connectionId, reason) => this.smartProxy.connectionManager.cleanupConnection(record, reason)
1512
- );
1513
-
1514
- // Store the handler in the connection record so we can remove it during cleanup
1515
- record.renegotiationHandler = renegotiationHandler;
1516
-
1517
- // Add the handler to the socket
1518
- socket.on('data', renegotiationHandler);
1519
-
1520
- if (this.smartProxy.settings.enableDetailedLogging) {
1521
- logger.log('info', `TLS renegotiation handler installed for connection ${connectionId} with SNI ${serverName}`, {
1522
- connectionId,
1523
- serverName,
1524
- component: 'route-handler'
1525
- });
1526
- }
1527
- }
1528
-
1529
- // Set connection timeout
1530
- record.cleanupTimer = this.smartProxy.timeoutManager.setupConnectionTimeout(record, (record, reason) => {
1531
- logger.log('warn', `Connection ${connectionId} from ${record.remoteIP} exceeded max lifetime, forcing cleanup`, {
1532
- connectionId,
1533
- remoteIP: record.remoteIP,
1534
- component: 'route-handler'
1535
- });
1536
- this.smartProxy.connectionManager.cleanupConnection(record, reason);
1537
- });
1538
-
1539
- // Mark TLS handshake as complete for TLS connections
1540
- if (record.isTLS) {
1541
- record.tlsHandshakeComplete = true;
1542
- }
1543
- }
1544
- });
1545
-
1546
- // Set outgoing socket immediately so it can be cleaned up if client disconnects
1547
- record.outgoing = targetSocket;
1548
- record.outgoingStartTime = Date.now();
1549
-
1550
- // Apply socket optimizations
1551
- targetSocket.setNoDelay(this.smartProxy.settings.noDelay);
1552
-
1553
- // Apply keep-alive settings if enabled
1554
- if (this.smartProxy.settings.keepAlive) {
1555
- targetSocket.setKeepAlive(true, this.smartProxy.settings.keepAliveInitialDelay);
1556
-
1557
- // Apply enhanced TCP keep-alive options if enabled
1558
- if (this.smartProxy.settings.enableKeepAliveProbes) {
1559
- try {
1560
- if ('setKeepAliveProbes' in targetSocket) {
1561
- (targetSocket as any).setKeepAliveProbes(10);
1562
- }
1563
- if ('setKeepAliveInterval' in targetSocket) {
1564
- (targetSocket as any).setKeepAliveInterval(1000);
1565
- }
1566
- } catch (err) {
1567
- // Ignore errors - these are optional enhancements
1568
- if (this.smartProxy.settings.enableDetailedLogging) {
1569
- logger.log('warn', `Enhanced TCP keep-alive not supported for outgoing socket on connection ${connectionId}: ${err}`, {
1570
- connectionId,
1571
- error: err,
1572
- component: 'route-handler'
1573
- });
1574
- }
1575
- }
1576
- }
1577
- }
1578
-
1579
- // Setup error handlers for incoming socket
1580
- socket.on('error', this.smartProxy.connectionManager.handleError('incoming', record));
1581
-
1582
- // Handle timeouts with keep-alive awareness
1583
- socket.on('timeout', () => {
1584
- // For keep-alive connections, just log a warning instead of closing
1585
- if (record.hasKeepAlive) {
1586
- logger.log('warn', `Timeout event on incoming keep-alive connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000)}. Connection preserved.`, {
1587
- connectionId,
1588
- remoteIP: record.remoteIP,
1589
- timeout: plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000),
1590
- status: 'Connection preserved',
1591
- component: 'route-handler'
1592
- });
1593
- return;
1594
- }
1595
-
1596
- // For non-keep-alive connections, proceed with normal cleanup
1597
- logger.log('warn', `Timeout on incoming side for connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000)}`, {
1598
- connectionId,
1599
- remoteIP: record.remoteIP,
1600
- timeout: plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000),
1601
- component: 'route-handler'
1602
- });
1603
- if (record.incomingTerminationReason === null) {
1604
- record.incomingTerminationReason = 'timeout';
1605
- this.smartProxy.connectionManager.incrementTerminationStat('incoming', 'timeout');
1606
- }
1607
- this.smartProxy.connectionManager.cleanupConnection(record, 'timeout_incoming');
1608
- });
1609
-
1610
- targetSocket.on('timeout', () => {
1611
- // For keep-alive connections, just log a warning instead of closing
1612
- if (record.hasKeepAlive) {
1613
- logger.log('warn', `Timeout event on outgoing keep-alive connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000)}. Connection preserved.`, {
1614
- connectionId,
1615
- remoteIP: record.remoteIP,
1616
- timeout: plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000),
1617
- status: 'Connection preserved',
1618
- component: 'route-handler'
1619
- });
1620
- return;
1621
- }
1622
-
1623
- // For non-keep-alive connections, proceed with normal cleanup
1624
- logger.log('warn', `Timeout on outgoing side for connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000)}`, {
1625
- connectionId,
1626
- remoteIP: record.remoteIP,
1627
- timeout: plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000),
1628
- component: 'route-handler'
1629
- });
1630
- if (record.outgoingTerminationReason === null) {
1631
- record.outgoingTerminationReason = 'timeout';
1632
- this.smartProxy.connectionManager.incrementTerminationStat('outgoing', 'timeout');
1633
- }
1634
- this.smartProxy.connectionManager.cleanupConnection(record, 'timeout_outgoing');
1635
- });
1636
-
1637
- // Apply socket timeouts
1638
- this.smartProxy.timeoutManager.applySocketTimeouts(record);
1639
- }
1640
- }