@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,796 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import type { IConnectionRecord } from './models/interfaces.js';
3
- import { logger } from '../../core/utils/logger.js';
4
- import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
5
- import { LifecycleComponent } from '../../core/utils/lifecycle-component.js';
6
- import { cleanupSocket } from '../../core/utils/socket-utils.js';
7
- import { WrappedSocket } from '../../core/models/wrapped-socket.js';
8
- import { ProtocolDetector } from '../../detection/index.js';
9
- import type { SmartProxy } from './smart-proxy.js';
10
-
11
- /**
12
- * Manages connection lifecycle, tracking, and cleanup with performance optimizations
13
- */
14
- export class ConnectionManager extends LifecycleComponent {
15
- private connectionRecords: Map<string, IConnectionRecord> = new Map();
16
- private terminationStats: {
17
- incoming: Record<string, number>;
18
- outgoing: Record<string, number>;
19
- } = { incoming: {}, outgoing: {} };
20
-
21
- // Performance optimization: Track connections needing inactivity check
22
- private nextInactivityCheck: Map<string, number> = new Map();
23
-
24
- // Connection limits
25
- private readonly maxConnections: number;
26
- private readonly cleanupBatchSize: number = 100;
27
-
28
- // Cleanup queue for batched processing
29
- private cleanupQueue: Set<string> = new Set();
30
- private cleanupTimer: NodeJS.Timeout | null = null;
31
- private isProcessingCleanup: boolean = false;
32
-
33
- // Route-level connection tracking
34
- private connectionsByRoute: Map<string, Set<string>> = new Map();
35
-
36
- constructor(
37
- private smartProxy: SmartProxy
38
- ) {
39
- super();
40
-
41
- // Set reasonable defaults for connection limits
42
- this.maxConnections = smartProxy.settings.defaults?.security?.maxConnections || 10000;
43
-
44
- // Start inactivity check timer if not disabled
45
- if (!smartProxy.settings.disableInactivityCheck) {
46
- this.startInactivityCheckTimer();
47
- }
48
- }
49
-
50
- /**
51
- * Generate a unique connection ID
52
- */
53
- public generateConnectionId(): string {
54
- return Math.random().toString(36).substring(2, 15) +
55
- Math.random().toString(36).substring(2, 15);
56
- }
57
-
58
- /**
59
- * Create and track a new connection
60
- * Accepts either a regular net.Socket or a WrappedSocket for transparent PROXY protocol support
61
- */
62
- public createConnection(socket: plugins.net.Socket | WrappedSocket): IConnectionRecord | null {
63
- // Enforce connection limit
64
- if (this.connectionRecords.size >= this.maxConnections) {
65
- // Use deduplicated logging for connection limit
66
- connectionLogDeduplicator.log(
67
- 'connection-rejected',
68
- 'warn',
69
- 'Global connection limit reached',
70
- {
71
- reason: 'global-limit',
72
- currentConnections: this.connectionRecords.size,
73
- maxConnections: this.maxConnections,
74
- component: 'connection-manager'
75
- },
76
- 'global-limit'
77
- );
78
- socket.destroy();
79
- return null;
80
- }
81
-
82
- const connectionId = this.generateConnectionId();
83
- const remoteIP = socket.remoteAddress || '';
84
- const remotePort = socket.remotePort || 0;
85
- const localPort = socket.localPort || 0;
86
- const now = Date.now();
87
-
88
- const record: IConnectionRecord = {
89
- id: connectionId,
90
- incoming: socket,
91
- outgoing: null,
92
- incomingStartTime: now,
93
- lastActivity: now,
94
- connectionClosed: false,
95
- pendingData: [],
96
- pendingDataSize: 0,
97
- bytesReceived: 0,
98
- bytesSent: 0,
99
- remoteIP,
100
- remotePort,
101
- localPort,
102
- isTLS: false,
103
- tlsHandshakeComplete: false,
104
- hasReceivedInitialData: false,
105
- hasKeepAlive: false,
106
- incomingTerminationReason: null,
107
- outgoingTerminationReason: null,
108
- usingNetworkProxy: false,
109
- isBrowserConnection: false,
110
- domainSwitches: 0
111
- };
112
-
113
- this.trackConnection(connectionId, record);
114
- return record;
115
- }
116
-
117
- /**
118
- * Track an existing connection
119
- */
120
- public trackConnection(connectionId: string, record: IConnectionRecord): void {
121
- this.connectionRecords.set(connectionId, record);
122
- this.smartProxy.securityManager.trackConnectionByIP(record.remoteIP, connectionId);
123
-
124
- // Schedule inactivity check
125
- if (!this.smartProxy.settings.disableInactivityCheck) {
126
- this.scheduleInactivityCheck(connectionId, record);
127
- }
128
- }
129
-
130
- /**
131
- * Schedule next inactivity check for a connection
132
- */
133
- private scheduleInactivityCheck(connectionId: string, record: IConnectionRecord): void {
134
- let timeout = this.smartProxy.settings.inactivityTimeout!;
135
-
136
- if (record.hasKeepAlive) {
137
- if (this.smartProxy.settings.keepAliveTreatment === 'immortal') {
138
- // Don't schedule check for immortal connections
139
- return;
140
- } else if (this.smartProxy.settings.keepAliveTreatment === 'extended') {
141
- const multiplier = this.smartProxy.settings.keepAliveInactivityMultiplier || 6;
142
- timeout = timeout * multiplier;
143
- }
144
- }
145
-
146
- const checkTime = Date.now() + timeout;
147
- this.nextInactivityCheck.set(connectionId, checkTime);
148
- }
149
-
150
- /**
151
- * Start the inactivity check timer
152
- */
153
- private startInactivityCheckTimer(): void {
154
- // Check more frequently (every 10 seconds) to catch zombies and stuck connections faster
155
- this.setInterval(() => {
156
- this.performOptimizedInactivityCheck();
157
- }, 10000);
158
- // Note: LifecycleComponent's setInterval already calls unref()
159
- }
160
-
161
- /**
162
- * Get a connection by ID
163
- */
164
- public getConnection(connectionId: string): IConnectionRecord | undefined {
165
- return this.connectionRecords.get(connectionId);
166
- }
167
-
168
- /**
169
- * Get all active connections
170
- */
171
- public getConnections(): Map<string, IConnectionRecord> {
172
- return this.connectionRecords;
173
- }
174
-
175
- /**
176
- * Get count of active connections
177
- */
178
- public getConnectionCount(): number {
179
- return this.connectionRecords.size;
180
- }
181
-
182
- /**
183
- * Track connection by route
184
- */
185
- public trackConnectionByRoute(routeId: string, connectionId: string): void {
186
- if (!this.connectionsByRoute.has(routeId)) {
187
- this.connectionsByRoute.set(routeId, new Set());
188
- }
189
- this.connectionsByRoute.get(routeId)!.add(connectionId);
190
- }
191
-
192
- /**
193
- * Remove connection tracking for a route
194
- */
195
- public removeConnectionByRoute(routeId: string, connectionId: string): void {
196
- if (this.connectionsByRoute.has(routeId)) {
197
- const connections = this.connectionsByRoute.get(routeId)!;
198
- connections.delete(connectionId);
199
- if (connections.size === 0) {
200
- this.connectionsByRoute.delete(routeId);
201
- }
202
- }
203
- }
204
-
205
- /**
206
- * Get connection count by route
207
- */
208
- public getConnectionCountByRoute(routeId: string): number {
209
- return this.connectionsByRoute.get(routeId)?.size || 0;
210
- }
211
-
212
- /**
213
- * Initiates cleanup once for a connection
214
- */
215
- public initiateCleanupOnce(record: IConnectionRecord, reason: string = 'normal'): void {
216
- // Use deduplicated logging for cleanup events
217
- connectionLogDeduplicator.log(
218
- 'connection-cleanup',
219
- 'info',
220
- `Connection cleanup: ${reason}`,
221
- {
222
- connectionId: record.id,
223
- remoteIP: record.remoteIP,
224
- reason,
225
- component: 'connection-manager'
226
- },
227
- reason
228
- );
229
-
230
- if (record.incomingTerminationReason == null) {
231
- record.incomingTerminationReason = reason;
232
- this.incrementTerminationStat('incoming', reason);
233
- }
234
-
235
- // Add to cleanup queue for batched processing
236
- this.queueCleanup(record.id);
237
- }
238
-
239
- /**
240
- * Queue a connection for cleanup
241
- */
242
- private queueCleanup(connectionId: string): void {
243
- // Check if connection is already being processed
244
- const record = this.connectionRecords.get(connectionId);
245
- if (!record || record.connectionClosed) {
246
- // Already cleaned up or doesn't exist, skip
247
- return;
248
- }
249
-
250
- this.cleanupQueue.add(connectionId);
251
-
252
- // Process immediately if queue is getting large and not already processing
253
- if (this.cleanupQueue.size >= this.cleanupBatchSize && !this.isProcessingCleanup) {
254
- this.processCleanupQueue();
255
- } else if (!this.cleanupTimer && !this.isProcessingCleanup) {
256
- // Otherwise, schedule batch processing
257
- this.cleanupTimer = this.setTimeout(() => {
258
- this.processCleanupQueue();
259
- }, 100);
260
- }
261
- }
262
-
263
- /**
264
- * Process the cleanup queue in batches
265
- */
266
- private processCleanupQueue(): void {
267
- // Prevent concurrent processing
268
- if (this.isProcessingCleanup) {
269
- return;
270
- }
271
-
272
- this.isProcessingCleanup = true;
273
-
274
- if (this.cleanupTimer) {
275
- this.clearTimeout(this.cleanupTimer);
276
- this.cleanupTimer = null;
277
- }
278
-
279
- try {
280
- // Take a snapshot of items to process
281
- const toCleanup = Array.from(this.cleanupQueue).slice(0, this.cleanupBatchSize);
282
-
283
- // Remove only the items we're processing from the queue
284
- for (const connectionId of toCleanup) {
285
- this.cleanupQueue.delete(connectionId);
286
- const record = this.connectionRecords.get(connectionId);
287
- if (record) {
288
- this.cleanupConnection(record, record.incomingTerminationReason || 'normal');
289
- }
290
- }
291
- } finally {
292
- // Always reset the processing flag
293
- this.isProcessingCleanup = false;
294
-
295
- // Check if more items were added while we were processing
296
- if (this.cleanupQueue.size > 0) {
297
- this.cleanupTimer = this.setTimeout(() => {
298
- this.processCleanupQueue();
299
- }, 10);
300
- }
301
- }
302
- }
303
-
304
- /**
305
- * Clean up a connection record
306
- */
307
- public cleanupConnection(record: IConnectionRecord, reason: string = 'normal'): void {
308
- if (!record.connectionClosed) {
309
- record.connectionClosed = true;
310
-
311
- // Remove from inactivity check
312
- this.nextInactivityCheck.delete(record.id);
313
-
314
- // Track connection termination
315
- this.smartProxy.securityManager.removeConnectionByIP(record.remoteIP, record.id);
316
-
317
- // Remove from route tracking
318
- if (record.routeId) {
319
- this.removeConnectionByRoute(record.routeId, record.id);
320
- }
321
-
322
- // Remove from metrics tracking
323
- if (this.smartProxy.metricsCollector) {
324
- this.smartProxy.metricsCollector.removeConnection(record.id);
325
- }
326
-
327
- // Clean up protocol detection fragments
328
- const context = ProtocolDetector.createConnectionContext({
329
- sourceIp: record.remoteIP,
330
- sourcePort: record.incoming?.remotePort || 0,
331
- destIp: record.incoming?.localAddress || '',
332
- destPort: record.localPort,
333
- socketId: record.id
334
- });
335
-
336
- // Clean up any pending detection fragments for this connection
337
- ProtocolDetector.cleanupConnection(context);
338
-
339
- if (record.cleanupTimer) {
340
- clearTimeout(record.cleanupTimer);
341
- record.cleanupTimer = undefined;
342
- }
343
-
344
- // Calculate metrics once
345
- const duration = Date.now() - record.incomingStartTime;
346
- const logData = {
347
- connectionId: record.id,
348
- remoteIP: record.remoteIP,
349
- localPort: record.localPort,
350
- reason,
351
- duration: plugins.prettyMs(duration),
352
- bytes: { in: record.bytesReceived, out: record.bytesSent },
353
- tls: record.isTLS,
354
- keepAlive: record.hasKeepAlive,
355
- usingNetworkProxy: record.usingNetworkProxy,
356
- domainSwitches: record.domainSwitches || 0,
357
- component: 'connection-manager'
358
- };
359
-
360
- // Remove all data handlers to make sure we clean up properly
361
- if (record.incoming) {
362
- try {
363
- record.incoming.removeAllListeners('data');
364
- record.renegotiationHandler = undefined;
365
- } catch (err) {
366
- logger.log('error', `Error removing data handlers: ${err}`, {
367
- connectionId: record.id,
368
- error: err,
369
- component: 'connection-manager'
370
- });
371
- }
372
- }
373
-
374
- // Handle socket cleanup - check if sockets are still active
375
- const cleanupPromises: Promise<void>[] = [];
376
-
377
- if (record.incoming) {
378
- // Extract underlying socket if it's a WrappedSocket
379
- const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
380
- if (!record.incoming.writable || record.incoming.destroyed) {
381
- // Socket is not active, clean up immediately
382
- cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { immediate: true }));
383
- } else {
384
- // Socket is still active, allow graceful cleanup
385
- cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { allowDrain: true, gracePeriod: 5000 }));
386
- }
387
- }
388
-
389
- if (record.outgoing) {
390
- // Extract underlying socket if it's a WrappedSocket
391
- const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
392
- if (!record.outgoing.writable || record.outgoing.destroyed) {
393
- // Socket is not active, clean up immediately
394
- cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { immediate: true }));
395
- } else {
396
- // Socket is still active, allow graceful cleanup
397
- cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { allowDrain: true, gracePeriod: 5000 }));
398
- }
399
- }
400
-
401
- // Wait for cleanup to complete
402
- Promise.all(cleanupPromises).catch(err => {
403
- logger.log('error', `Error during socket cleanup: ${err}`, {
404
- connectionId: record.id,
405
- error: err,
406
- component: 'connection-manager'
407
- });
408
- });
409
-
410
- // Clear pendingData to avoid memory leaks
411
- record.pendingData = [];
412
- record.pendingDataSize = 0;
413
-
414
- // Remove the record from the tracking map
415
- this.connectionRecords.delete(record.id);
416
-
417
- // Use deduplicated logging for connection termination
418
- if (this.smartProxy.settings.enableDetailedLogging) {
419
- // For detailed logging, include more info but still deduplicate by IP+reason
420
- connectionLogDeduplicator.log(
421
- 'connection-terminated',
422
- 'info',
423
- `Connection terminated: ${record.remoteIP}:${record.localPort}`,
424
- {
425
- ...logData,
426
- duration_ms: duration,
427
- bytesIn: record.bytesReceived,
428
- bytesOut: record.bytesSent
429
- },
430
- `${record.remoteIP}-${reason}`
431
- );
432
- } else {
433
- // For normal logging, deduplicate by termination reason
434
- connectionLogDeduplicator.log(
435
- 'connection-terminated',
436
- 'info',
437
- `Connection terminated`,
438
- {
439
- remoteIP: record.remoteIP,
440
- reason,
441
- activeConnections: this.connectionRecords.size,
442
- component: 'connection-manager'
443
- },
444
- reason // Group by termination reason
445
- );
446
- }
447
- }
448
- }
449
-
450
-
451
- /**
452
- * Creates a generic error handler for incoming or outgoing sockets
453
- */
454
- public handleError(side: 'incoming' | 'outgoing', record: IConnectionRecord) {
455
- return (err: Error) => {
456
- const code = (err as any).code;
457
- let reason = 'error';
458
-
459
- const now = Date.now();
460
- const connectionDuration = now - record.incomingStartTime;
461
- const lastActivityAge = now - record.lastActivity;
462
-
463
- // Update activity tracking
464
- if (side === 'incoming') {
465
- record.lastActivity = now;
466
- this.scheduleInactivityCheck(record.id, record);
467
- }
468
-
469
- const errorData = {
470
- connectionId: record.id,
471
- side,
472
- remoteIP: record.remoteIP,
473
- error: err.message,
474
- duration: plugins.prettyMs(connectionDuration),
475
- lastActivity: plugins.prettyMs(lastActivityAge),
476
- component: 'connection-manager'
477
- };
478
-
479
- switch (code) {
480
- case 'ECONNRESET':
481
- reason = 'econnreset';
482
- logger.log('warn', `ECONNRESET on ${side}: ${record.remoteIP}`, errorData);
483
- break;
484
- case 'ETIMEDOUT':
485
- reason = 'etimedout';
486
- logger.log('warn', `ETIMEDOUT on ${side}: ${record.remoteIP}`, errorData);
487
- break;
488
- default:
489
- logger.log('error', `Error on ${side}: ${record.remoteIP} - ${err.message}`, errorData);
490
- }
491
-
492
- if (side === 'incoming' && record.incomingTerminationReason == null) {
493
- record.incomingTerminationReason = reason;
494
- this.incrementTerminationStat('incoming', reason);
495
- } else if (side === 'outgoing' && record.outgoingTerminationReason == null) {
496
- record.outgoingTerminationReason = reason;
497
- this.incrementTerminationStat('outgoing', reason);
498
- }
499
-
500
- this.initiateCleanupOnce(record, reason);
501
- };
502
- }
503
-
504
- /**
505
- * Creates a generic close handler for incoming or outgoing sockets
506
- */
507
- public handleClose(side: 'incoming' | 'outgoing', record: IConnectionRecord) {
508
- return () => {
509
- if (this.smartProxy.settings.enableDetailedLogging) {
510
- logger.log('info', `Connection closed on ${side} side`, {
511
- connectionId: record.id,
512
- side,
513
- remoteIP: record.remoteIP,
514
- component: 'connection-manager'
515
- });
516
- }
517
-
518
- if (side === 'incoming' && record.incomingTerminationReason == null) {
519
- record.incomingTerminationReason = 'normal';
520
- this.incrementTerminationStat('incoming', 'normal');
521
- } else if (side === 'outgoing' && record.outgoingTerminationReason == null) {
522
- record.outgoingTerminationReason = 'normal';
523
- this.incrementTerminationStat('outgoing', 'normal');
524
- record.outgoingClosedTime = Date.now();
525
- }
526
-
527
- this.initiateCleanupOnce(record, 'closed_' + side);
528
- };
529
- }
530
-
531
- /**
532
- * Increment termination statistics
533
- */
534
- public incrementTerminationStat(side: 'incoming' | 'outgoing', reason: string): void {
535
- this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
536
- }
537
-
538
- /**
539
- * Get termination statistics
540
- */
541
- public getTerminationStats(): { incoming: Record<string, number>; outgoing: Record<string, number> } {
542
- return this.terminationStats;
543
- }
544
-
545
- /**
546
- * Optimized inactivity check - only checks connections that are due
547
- */
548
- private performOptimizedInactivityCheck(): void {
549
- const now = Date.now();
550
- const connectionsToCheck: string[] = [];
551
-
552
- // Find connections that need checking
553
- for (const [connectionId, checkTime] of this.nextInactivityCheck) {
554
- if (checkTime <= now) {
555
- connectionsToCheck.push(connectionId);
556
- }
557
- }
558
-
559
- // Also check ALL connections for zombie state (destroyed sockets but not cleaned up)
560
- // This is critical for proxy chains where sockets can be destroyed without events
561
- for (const [connectionId, record] of this.connectionRecords) {
562
- if (!record.connectionClosed) {
563
- const incomingDestroyed = record.incoming?.destroyed || false;
564
- const outgoingDestroyed = record.outgoing?.destroyed || false;
565
-
566
- // Check for zombie connections: both sockets destroyed but connection not cleaned up
567
- if (incomingDestroyed && outgoingDestroyed) {
568
- logger.log('warn', `Zombie connection detected: ${connectionId} - both sockets destroyed but not cleaned up`, {
569
- connectionId,
570
- remoteIP: record.remoteIP,
571
- age: plugins.prettyMs(now - record.incomingStartTime),
572
- component: 'connection-manager'
573
- });
574
-
575
- // Clean up immediately
576
- this.cleanupConnection(record, 'zombie_cleanup');
577
- continue;
578
- }
579
-
580
- // Check for half-zombie: one socket destroyed
581
- if (incomingDestroyed || outgoingDestroyed) {
582
- const age = now - record.incomingStartTime;
583
- // Use longer grace period for encrypted connections (5 minutes vs 30 seconds)
584
- const gracePeriod = record.isTLS ? 300000 : 30000;
585
-
586
- // Also ensure connection is old enough to avoid premature cleanup
587
- if (age > gracePeriod && age > 10000) {
588
- logger.log('warn', `Half-zombie connection detected: ${connectionId} - ${incomingDestroyed ? 'incoming' : 'outgoing'} destroyed`, {
589
- connectionId,
590
- remoteIP: record.remoteIP,
591
- age: plugins.prettyMs(age),
592
- incomingDestroyed,
593
- outgoingDestroyed,
594
- isTLS: record.isTLS,
595
- gracePeriod: plugins.prettyMs(gracePeriod),
596
- component: 'connection-manager'
597
- });
598
-
599
- // Clean up
600
- this.cleanupConnection(record, 'half_zombie_cleanup');
601
- }
602
- }
603
-
604
- // Check for stuck connections: no data sent back to client
605
- if (!record.connectionClosed && record.outgoing && record.bytesReceived > 0 && record.bytesSent === 0) {
606
- const age = now - record.incomingStartTime;
607
- // Use longer grace period for encrypted connections (5 minutes vs 60 seconds)
608
- const stuckThreshold = record.isTLS ? 300000 : 60000;
609
-
610
- // If connection is older than threshold and no data sent back, likely stuck
611
- if (age > stuckThreshold) {
612
- logger.log('warn', `Stuck connection detected: ${connectionId} - received ${record.bytesReceived} bytes but sent 0 bytes`, {
613
- connectionId,
614
- remoteIP: record.remoteIP,
615
- age: plugins.prettyMs(age),
616
- bytesReceived: record.bytesReceived,
617
- targetHost: record.targetHost,
618
- targetPort: record.targetPort,
619
- isTLS: record.isTLS,
620
- threshold: plugins.prettyMs(stuckThreshold),
621
- component: 'connection-manager'
622
- });
623
-
624
- // Set termination reason and increment stats
625
- if (record.incomingTerminationReason == null) {
626
- record.incomingTerminationReason = 'stuck_no_response';
627
- this.incrementTerminationStat('incoming', 'stuck_no_response');
628
- }
629
-
630
- // Clean up
631
- this.cleanupConnection(record, 'stuck_no_response');
632
- }
633
- }
634
- }
635
- }
636
-
637
- // Process only connections that need checking
638
- for (const connectionId of connectionsToCheck) {
639
- const record = this.connectionRecords.get(connectionId);
640
- if (!record || record.connectionClosed) {
641
- this.nextInactivityCheck.delete(connectionId);
642
- continue;
643
- }
644
-
645
- const inactivityTime = now - record.lastActivity;
646
-
647
- // Use extended timeout for extended-treatment keep-alive connections
648
- let effectiveTimeout = this.smartProxy.settings.inactivityTimeout!;
649
- if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'extended') {
650
- const multiplier = this.smartProxy.settings.keepAliveInactivityMultiplier || 6;
651
- effectiveTimeout = effectiveTimeout * multiplier;
652
- }
653
-
654
- if (inactivityTime > effectiveTimeout) {
655
- // For keep-alive connections, issue a warning first
656
- if (record.hasKeepAlive && !record.inactivityWarningIssued) {
657
- logger.log('warn', `Keep-alive connection inactive: ${record.remoteIP}`, {
658
- connectionId,
659
- remoteIP: record.remoteIP,
660
- inactiveFor: plugins.prettyMs(inactivityTime),
661
- component: 'connection-manager'
662
- });
663
-
664
- record.inactivityWarningIssued = true;
665
-
666
- // Reschedule check for 10 minutes later
667
- this.nextInactivityCheck.set(connectionId, now + 600000);
668
-
669
- // Try to stimulate activity with a probe packet
670
- if (record.outgoing && !record.outgoing.destroyed) {
671
- try {
672
- record.outgoing.write(Buffer.alloc(0));
673
- } catch (err) {
674
- logger.log('error', `Error sending probe packet: ${err}`, {
675
- connectionId,
676
- error: err,
677
- component: 'connection-manager'
678
- });
679
- }
680
- }
681
- } else {
682
- // Close the connection
683
- logger.log('warn', `Closing inactive connection: ${record.remoteIP}`, {
684
- connectionId,
685
- remoteIP: record.remoteIP,
686
- inactiveFor: plugins.prettyMs(inactivityTime),
687
- hasKeepAlive: record.hasKeepAlive,
688
- component: 'connection-manager'
689
- });
690
- this.cleanupConnection(record, 'inactivity');
691
- }
692
- } else {
693
- // Reschedule next check
694
- this.scheduleInactivityCheck(connectionId, record);
695
- }
696
-
697
- // Parity check: if outgoing socket closed and incoming remains active
698
- // Increased from 2 minutes to 30 minutes for long-lived connections
699
- if (
700
- record.outgoingClosedTime &&
701
- !record.incoming.destroyed &&
702
- !record.connectionClosed &&
703
- now - record.outgoingClosedTime > 1800000 // 30 minutes
704
- ) {
705
- // Only close if no data activity for 10 minutes
706
- if (now - record.lastActivity > 600000) {
707
- logger.log('warn', `Parity check failed after extended timeout: ${record.remoteIP}`, {
708
- connectionId,
709
- remoteIP: record.remoteIP,
710
- timeElapsed: plugins.prettyMs(now - record.outgoingClosedTime),
711
- inactiveFor: plugins.prettyMs(now - record.lastActivity),
712
- component: 'connection-manager'
713
- });
714
- this.cleanupConnection(record, 'parity_check');
715
- }
716
- }
717
- }
718
- }
719
-
720
- /**
721
- * Legacy method for backward compatibility
722
- */
723
- public performInactivityCheck(): void {
724
- this.performOptimizedInactivityCheck();
725
- }
726
-
727
- /**
728
- * Clear all connections (for shutdown)
729
- */
730
- public async clearConnections(): Promise<void> {
731
- // Delegate to LifecycleComponent's cleanup
732
- await this.cleanup();
733
- }
734
-
735
- /**
736
- * Override LifecycleComponent's onCleanup method
737
- */
738
- protected async onCleanup(): Promise<void> {
739
-
740
- // Process connections in batches to avoid blocking
741
- const connections = Array.from(this.connectionRecords.values());
742
- const batchSize = 100;
743
- let index = 0;
744
-
745
- const processBatch = () => {
746
- const batch = connections.slice(index, index + batchSize);
747
-
748
- for (const record of batch) {
749
- try {
750
- if (record.cleanupTimer) {
751
- clearTimeout(record.cleanupTimer);
752
- record.cleanupTimer = undefined;
753
- }
754
-
755
- // Immediate destruction using socket-utils
756
- const shutdownPromises: Promise<void>[] = [];
757
-
758
- if (record.incoming) {
759
- const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
760
- shutdownPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming-shutdown`, { immediate: true }));
761
- }
762
-
763
- if (record.outgoing) {
764
- const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
765
- shutdownPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing-shutdown`, { immediate: true }));
766
- }
767
-
768
- // Don't wait for shutdown cleanup in this batch processing
769
- Promise.all(shutdownPromises).catch(() => {});
770
- } catch (err) {
771
- logger.log('error', `Error during connection cleanup: ${err}`, {
772
- connectionId: record.id,
773
- error: err,
774
- component: 'connection-manager'
775
- });
776
- }
777
- }
778
-
779
- index += batchSize;
780
-
781
- // Continue with next batch if needed
782
- if (index < connections.length) {
783
- setImmediate(processBatch);
784
- } else {
785
- // Clear all maps
786
- this.connectionRecords.clear();
787
- this.nextInactivityCheck.clear();
788
- this.cleanupQueue.clear();
789
- this.terminationStats = { incoming: {}, outgoing: {} };
790
- }
791
- };
792
-
793
- // Start batch processing
794
- setImmediate(processBatch);
795
- }
796
- }