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