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