@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.
Files changed (101) hide show
  1. package/changelog.md +36 -0
  2. package/dist_rust/rustproxy +0 -0
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/index.d.ts +1 -6
  5. package/dist_ts/index.js +3 -11
  6. package/dist_ts/protocols/common/fragment-handler.js +5 -1
  7. package/dist_ts/proxies/index.d.ts +1 -6
  8. package/dist_ts/proxies/index.js +2 -8
  9. package/dist_ts/proxies/smart-proxy/index.d.ts +5 -10
  10. package/dist_ts/proxies/smart-proxy/index.js +7 -13
  11. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -2
  12. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  13. package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
  14. package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
  15. package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
  16. package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
  17. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
  18. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
  19. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
  20. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
  21. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
  22. package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -621
  23. package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
  24. package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
  25. package/dist_ts/routing/index.d.ts +1 -1
  26. package/dist_ts/routing/index.js +3 -3
  27. package/dist_ts/routing/models/http-types.d.ts +119 -4
  28. package/dist_ts/routing/models/http-types.js +93 -5
  29. package/package.json +1 -1
  30. package/readme.md +444 -219
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/index.ts +4 -15
  33. package/ts/protocols/common/fragment-handler.ts +4 -0
  34. package/ts/proxies/index.ts +1 -12
  35. package/ts/proxies/smart-proxy/index.ts +6 -13
  36. package/ts/proxies/smart-proxy/models/interfaces.ts +6 -4
  37. package/ts/proxies/smart-proxy/models/route-types.ts +0 -2
  38. package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
  39. package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
  40. package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
  41. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
  42. package/ts/proxies/smart-proxy/smart-proxy.ts +282 -798
  43. package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
  44. package/ts/routing/index.ts +2 -2
  45. package/ts/routing/models/http-types.ts +147 -4
  46. package/dist_ts/proxies/nftables-proxy/index.d.ts +0 -6
  47. package/dist_ts/proxies/nftables-proxy/index.js +0 -7
  48. package/dist_ts/proxies/nftables-proxy/models/errors.d.ts +0 -15
  49. package/dist_ts/proxies/nftables-proxy/models/errors.js +0 -28
  50. package/dist_ts/proxies/nftables-proxy/models/index.d.ts +0 -5
  51. package/dist_ts/proxies/nftables-proxy/models/index.js +0 -6
  52. package/dist_ts/proxies/nftables-proxy/models/interfaces.d.ts +0 -75
  53. package/dist_ts/proxies/nftables-proxy/models/interfaces.js +0 -5
  54. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +0 -124
  55. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +0 -1374
  56. package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +0 -9
  57. package/dist_ts/proxies/nftables-proxy/utils/index.js +0 -12
  58. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +0 -66
  59. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +0 -131
  60. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +0 -39
  61. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +0 -112
  62. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +0 -59
  63. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +0 -130
  64. package/ts/proxies/http-proxy/connection-pool.ts +0 -228
  65. package/ts/proxies/http-proxy/context-creator.ts +0 -145
  66. package/ts/proxies/http-proxy/default-certificates.ts +0 -150
  67. package/ts/proxies/http-proxy/function-cache.ts +0 -279
  68. package/ts/proxies/http-proxy/handlers/index.ts +0 -5
  69. package/ts/proxies/http-proxy/http-proxy.ts +0 -669
  70. package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
  71. package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
  72. package/ts/proxies/http-proxy/index.ts +0 -18
  73. package/ts/proxies/http-proxy/models/http-types.ts +0 -148
  74. package/ts/proxies/http-proxy/models/index.ts +0 -5
  75. package/ts/proxies/http-proxy/models/types.ts +0 -125
  76. package/ts/proxies/http-proxy/request-handler.ts +0 -878
  77. package/ts/proxies/http-proxy/security-manager.ts +0 -413
  78. package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
  79. package/ts/proxies/nftables-proxy/index.ts +0 -6
  80. package/ts/proxies/nftables-proxy/models/errors.ts +0 -30
  81. package/ts/proxies/nftables-proxy/models/index.ts +0 -5
  82. package/ts/proxies/nftables-proxy/models/interfaces.ts +0 -94
  83. package/ts/proxies/nftables-proxy/nftables-proxy.ts +0 -1754
  84. package/ts/proxies/nftables-proxy/utils/index.ts +0 -38
  85. package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +0 -162
  86. package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +0 -125
  87. package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +0 -156
  88. package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
  89. package/ts/proxies/smart-proxy/cert-store.ts +0 -92
  90. package/ts/proxies/smart-proxy/certificate-manager.ts +0 -895
  91. package/ts/proxies/smart-proxy/connection-manager.ts +0 -809
  92. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -213
  93. package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
  94. package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
  95. package/ts/proxies/smart-proxy/port-manager.ts +0 -358
  96. package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1712
  97. package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
  98. package/ts/proxies/smart-proxy/security-manager.ts +0 -269
  99. package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
  100. package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
  101. package/ts/proxies/smart-proxy/tls-manager.ts +0 -171
@@ -1,297 +0,0 @@
1
- import { logger } from '../../core/utils/logger.js';
2
- import type { IRouteConfig } from './models/route-types.js';
3
- import type { ILogger } from '../http-proxy/models/types.js';
4
- import { RouteValidator } from './utils/route-validator.js';
5
- import { Mutex } from './utils/mutex.js';
6
- import type { PortManager } from './port-manager.js';
7
- import type { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
8
- import type { HttpProxyBridge } from './http-proxy-bridge.js';
9
- import type { NFTablesManager } from './nftables-manager.js';
10
- import type { SmartCertManager } from './certificate-manager.js';
11
-
12
- /**
13
- * Orchestrates route updates and coordination between components
14
- * Extracted from SmartProxy to reduce class complexity
15
- */
16
- export class RouteOrchestrator {
17
- private routeUpdateLock: Mutex;
18
- private portManager: PortManager;
19
- private routeManager: RouteManager;
20
- private httpProxyBridge: HttpProxyBridge;
21
- private nftablesManager: NFTablesManager;
22
- private certManager: SmartCertManager | null = null;
23
- private logger: ILogger;
24
-
25
- constructor(
26
- portManager: PortManager,
27
- routeManager: RouteManager,
28
- httpProxyBridge: HttpProxyBridge,
29
- nftablesManager: NFTablesManager,
30
- certManager: SmartCertManager | null,
31
- logger: ILogger
32
- ) {
33
- this.portManager = portManager;
34
- this.routeManager = routeManager;
35
- this.httpProxyBridge = httpProxyBridge;
36
- this.nftablesManager = nftablesManager;
37
- this.certManager = certManager;
38
- this.logger = logger;
39
- this.routeUpdateLock = new Mutex();
40
- }
41
-
42
- /**
43
- * Set or update certificate manager reference
44
- */
45
- public setCertManager(certManager: SmartCertManager | null): void {
46
- this.certManager = certManager;
47
- }
48
-
49
- /**
50
- * Get certificate manager reference
51
- */
52
- public getCertManager(): SmartCertManager | null {
53
- return this.certManager;
54
- }
55
-
56
- /**
57
- * Update routes with validation and coordination
58
- */
59
- public async updateRoutes(
60
- oldRoutes: IRouteConfig[],
61
- newRoutes: IRouteConfig[],
62
- options: {
63
- acmePort?: number;
64
- acmeOptions?: any;
65
- acmeState?: any;
66
- globalChallengeRouteActive?: boolean;
67
- createCertificateManager?: (
68
- routes: IRouteConfig[],
69
- certStore: string,
70
- acmeOptions?: any,
71
- initialState?: any
72
- ) => Promise<SmartCertManager>;
73
- verifyChallengeRouteRemoved?: () => Promise<void>;
74
- } = {}
75
- ): Promise<{
76
- portUsageMap: Map<number, Set<string>>;
77
- newChallengeRouteActive: boolean;
78
- newCertManager?: SmartCertManager;
79
- }> {
80
- return this.routeUpdateLock.runExclusive(async () => {
81
- // Validate route configurations
82
- const validation = RouteValidator.validateRoutes(newRoutes);
83
- if (!validation.valid) {
84
- RouteValidator.logValidationErrors(validation.errors);
85
- throw new Error(`Route validation failed: ${validation.errors.size} route(s) have errors`);
86
- }
87
-
88
- // Track port usage before and after updates
89
- const oldPortUsage = this.updatePortUsageMap(oldRoutes);
90
- const newPortUsage = this.updatePortUsageMap(newRoutes);
91
-
92
- // Get the lists of currently listening ports and new ports needed
93
- const currentPorts = new Set(this.portManager.getListeningPorts());
94
- const newPortsSet = new Set(newPortUsage.keys());
95
-
96
- // Log the port usage for debugging
97
- this.logger.debug(`Current listening ports: ${Array.from(currentPorts).join(', ')}`);
98
- this.logger.debug(`Ports needed for new routes: ${Array.from(newPortsSet).join(', ')}`);
99
-
100
- // Find orphaned ports - ports that no longer have any routes
101
- const orphanedPorts = this.findOrphanedPorts(oldPortUsage, newPortUsage);
102
-
103
- // Find new ports that need binding (only ports that we aren't already listening on)
104
- const newBindingPorts = Array.from(newPortsSet).filter(p => !currentPorts.has(p));
105
-
106
- // Check for ACME challenge port to give it special handling
107
- const acmePort = options.acmePort || 80;
108
- const acmePortNeeded = newPortsSet.has(acmePort);
109
- const acmePortListed = newBindingPorts.includes(acmePort);
110
-
111
- if (acmePortNeeded && acmePortListed) {
112
- this.logger.info(`Adding ACME challenge port ${acmePort} to routes`);
113
- }
114
-
115
- // Update NFTables routes
116
- await this.updateNfTablesRoutes(oldRoutes, newRoutes);
117
-
118
- // Update routes in RouteManager
119
- this.routeManager.updateRoutes(newRoutes);
120
-
121
- // Release orphaned ports first to free resources
122
- if (orphanedPorts.length > 0) {
123
- this.logger.info(`Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`);
124
- await this.portManager.removePorts(orphanedPorts);
125
- }
126
-
127
- // Add new ports if needed
128
- if (newBindingPorts.length > 0) {
129
- this.logger.info(`Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`);
130
-
131
- // Handle port binding with improved error recovery
132
- try {
133
- await this.portManager.addPorts(newBindingPorts);
134
- } catch (error) {
135
- // Special handling for port binding errors
136
- if ((error as any).code === 'EADDRINUSE') {
137
- const port = (error as any).port || newBindingPorts[0];
138
- const isAcmePort = port === acmePort;
139
-
140
- if (isAcmePort) {
141
- this.logger.warn(`Could not bind to ACME challenge port ${port}. It may be in use by another application.`);
142
-
143
- // Re-throw with more helpful message
144
- throw new Error(
145
- `ACME challenge port ${port} is already in use by another application. ` +
146
- `Configure a different port in settings.acme.port (e.g., 8080) or free up port ${port}.`
147
- );
148
- }
149
- }
150
-
151
- // Re-throw the original error for other cases
152
- throw error;
153
- }
154
- }
155
-
156
- // If HttpProxy is initialized, resync the configurations
157
- if (this.httpProxyBridge.getHttpProxy()) {
158
- await this.httpProxyBridge.syncRoutesToHttpProxy(newRoutes);
159
- }
160
-
161
- // Update certificate manager if needed
162
- let newCertManager: SmartCertManager | undefined;
163
- let newChallengeRouteActive = options.globalChallengeRouteActive || false;
164
-
165
- if (this.certManager && options.createCertificateManager) {
166
- const existingAcmeOptions = this.certManager.getAcmeOptions();
167
- const existingState = this.certManager.getState();
168
-
169
- // Store global state before stopping
170
- newChallengeRouteActive = existingState.challengeRouteActive;
171
-
172
- // Keep certificate manager routes in sync before stopping
173
- this.certManager.setRoutes(newRoutes);
174
-
175
- await this.certManager.stop();
176
-
177
- // Verify the challenge route has been properly removed
178
- if (options.verifyChallengeRouteRemoved) {
179
- await options.verifyChallengeRouteRemoved();
180
- }
181
-
182
- // Create new certificate manager with preserved state
183
- newCertManager = await options.createCertificateManager(
184
- newRoutes,
185
- './certs',
186
- existingAcmeOptions,
187
- { challengeRouteActive: newChallengeRouteActive }
188
- );
189
-
190
- this.certManager = newCertManager;
191
- }
192
-
193
- return {
194
- portUsageMap: newPortUsage,
195
- newChallengeRouteActive,
196
- newCertManager
197
- };
198
- });
199
- }
200
-
201
- /**
202
- * Update port usage map based on the provided routes
203
- */
204
- public updatePortUsageMap(routes: IRouteConfig[]): Map<number, Set<string>> {
205
- const portUsage = new Map<number, Set<string>>();
206
-
207
- for (const route of routes) {
208
- // Get the ports for this route
209
- const portsConfig = Array.isArray(route.match.ports)
210
- ? route.match.ports
211
- : [route.match.ports];
212
-
213
- // Expand port range objects to individual port numbers
214
- const expandedPorts: number[] = [];
215
- for (const portConfig of portsConfig) {
216
- if (typeof portConfig === 'number') {
217
- expandedPorts.push(portConfig);
218
- } else if (typeof portConfig === 'object' && 'from' in portConfig && 'to' in portConfig) {
219
- // Expand the port range
220
- for (let p = portConfig.from; p <= portConfig.to; p++) {
221
- expandedPorts.push(p);
222
- }
223
- }
224
- }
225
-
226
- // Use route name if available, otherwise generate a unique ID
227
- const routeName = route.name || `unnamed_${Math.random().toString(36).substring(2, 9)}`;
228
-
229
- // Add each port to the usage map
230
- for (const port of expandedPorts) {
231
- if (!portUsage.has(port)) {
232
- portUsage.set(port, new Set());
233
- }
234
- portUsage.get(port)!.add(routeName);
235
- }
236
- }
237
-
238
- // Log port usage for debugging
239
- for (const [port, routes] of portUsage.entries()) {
240
- this.logger.debug(`Port ${port} is used by ${routes.size} routes: ${Array.from(routes).join(', ')}`);
241
- }
242
-
243
- return portUsage;
244
- }
245
-
246
- /**
247
- * Find ports that have no routes in the new configuration
248
- */
249
- private findOrphanedPorts(oldUsage: Map<number, Set<string>>, newUsage: Map<number, Set<string>>): number[] {
250
- const orphanedPorts: number[] = [];
251
-
252
- for (const [port, routes] of oldUsage.entries()) {
253
- if (!newUsage.has(port) || newUsage.get(port)!.size === 0) {
254
- orphanedPorts.push(port);
255
- }
256
- }
257
-
258
- return orphanedPorts;
259
- }
260
-
261
- /**
262
- * Update NFTables routes
263
- */
264
- private async updateNfTablesRoutes(oldRoutes: IRouteConfig[], newRoutes: IRouteConfig[]): Promise<void> {
265
- // Get existing routes that use NFTables and update them
266
- const oldNfTablesRoutes = oldRoutes.filter(
267
- r => r.action.forwardingEngine === 'nftables'
268
- );
269
-
270
- const newNfTablesRoutes = newRoutes.filter(
271
- r => r.action.forwardingEngine === 'nftables'
272
- );
273
-
274
- // Update existing NFTables routes
275
- for (const oldRoute of oldNfTablesRoutes) {
276
- const newRoute = newNfTablesRoutes.find(r => r.name === oldRoute.name);
277
-
278
- if (!newRoute) {
279
- // Route was removed
280
- await this.nftablesManager.deprovisionRoute(oldRoute);
281
- } else {
282
- // Route was updated
283
- await this.nftablesManager.updateRoute(oldRoute, newRoute);
284
- }
285
- }
286
-
287
- // Add new NFTables routes
288
- for (const newRoute of newNfTablesRoutes) {
289
- const oldRoute = oldNfTablesRoutes.find(r => r.name === newRoute.name);
290
-
291
- if (!oldRoute) {
292
- // New route
293
- await this.nftablesManager.provisionRoute(newRoute);
294
- }
295
- }
296
- }
297
- }
@@ -1,269 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import type { SmartProxy } from './smart-proxy.js';
3
- import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
4
- import { isIPAuthorized, normalizeIP } from '../../core/utils/security-utils.js';
5
-
6
- /**
7
- * Handles security aspects like IP tracking, rate limiting, and authorization
8
- * for SmartProxy. This is a lightweight wrapper that uses shared utilities.
9
- */
10
- export class SecurityManager {
11
- private connectionsByIP: Map<string, Set<string>> = new Map();
12
- private connectionRateByIP: Map<string, number[]> = new Map();
13
- private cleanupInterval: NodeJS.Timeout | null = null;
14
-
15
- constructor(private smartProxy: SmartProxy) {
16
- // Start periodic cleanup every 60 seconds
17
- this.startPeriodicCleanup();
18
- }
19
-
20
- /**
21
- * Get connections count by IP (checks normalized variants)
22
- */
23
- public getConnectionCountByIP(ip: string): number {
24
- // Check all normalized variants of the IP
25
- const variants = normalizeIP(ip);
26
- for (const variant of variants) {
27
- const connections = this.connectionsByIP.get(variant);
28
- if (connections) {
29
- return connections.size;
30
- }
31
- }
32
- return 0;
33
- }
34
-
35
- /**
36
- * Check and update connection rate for an IP
37
- * @returns true if within rate limit, false if exceeding limit
38
- */
39
- public checkConnectionRate(ip: string): boolean {
40
- const now = Date.now();
41
- const minute = 60 * 1000;
42
-
43
- // Find existing rate tracking (check normalized variants)
44
- const variants = normalizeIP(ip);
45
- let existingKey: string | null = null;
46
- for (const variant of variants) {
47
- if (this.connectionRateByIP.has(variant)) {
48
- existingKey = variant;
49
- break;
50
- }
51
- }
52
-
53
- const key = existingKey || ip;
54
-
55
- if (!this.connectionRateByIP.has(key)) {
56
- this.connectionRateByIP.set(key, [now]);
57
- return true;
58
- }
59
-
60
- // Get timestamps and filter out entries older than 1 minute
61
- const timestamps = this.connectionRateByIP.get(key)!.filter((time) => now - time < minute);
62
- timestamps.push(now);
63
- this.connectionRateByIP.set(key, timestamps);
64
-
65
- // Check if rate exceeds limit
66
- return timestamps.length <= this.smartProxy.settings.connectionRateLimitPerMinute!;
67
- }
68
-
69
- /**
70
- * Track connection by IP
71
- */
72
- public trackConnectionByIP(ip: string, connectionId: string): void {
73
- // Check if any variant already exists
74
- const variants = normalizeIP(ip);
75
- let existingKey: string | null = null;
76
-
77
- for (const variant of variants) {
78
- if (this.connectionsByIP.has(variant)) {
79
- existingKey = variant;
80
- break;
81
- }
82
- }
83
-
84
- const key = existingKey || ip;
85
- if (!this.connectionsByIP.has(key)) {
86
- this.connectionsByIP.set(key, new Set());
87
- }
88
- this.connectionsByIP.get(key)!.add(connectionId);
89
- }
90
-
91
- /**
92
- * Remove connection tracking for an IP
93
- */
94
- public removeConnectionByIP(ip: string, connectionId: string): void {
95
- // Check all variants to find where the connection is tracked
96
- const variants = normalizeIP(ip);
97
-
98
- for (const variant of variants) {
99
- if (this.connectionsByIP.has(variant)) {
100
- const connections = this.connectionsByIP.get(variant)!;
101
- connections.delete(connectionId);
102
- if (connections.size === 0) {
103
- this.connectionsByIP.delete(variant);
104
- }
105
- break;
106
- }
107
- }
108
- }
109
-
110
- /**
111
- * Check if an IP is authorized using security rules
112
- *
113
- * This method is used to determine if an IP is allowed to connect, based on security
114
- * rules configured in the route configuration. The allowed and blocked IPs are
115
- * typically derived from route.security.ipAllowList and ipBlockList.
116
- *
117
- * @param ip - The IP address to check
118
- * @param allowedIPs - Array of allowed IP patterns from security.ipAllowList
119
- * @param blockedIPs - Array of blocked IP patterns from security.ipBlockList
120
- * @returns true if IP is authorized, false if blocked
121
- */
122
- public isIPAuthorized(ip: string, allowedIPs: string[], blockedIPs: string[] = []): boolean {
123
- return isIPAuthorized(ip, allowedIPs, blockedIPs);
124
- }
125
-
126
- /**
127
- * Check if IP should be allowed considering connection rate and max connections
128
- * @returns Object with result and reason
129
- */
130
- public validateIP(ip: string): { allowed: boolean; reason?: string } {
131
- // Check connection count limit
132
- if (
133
- this.smartProxy.settings.maxConnectionsPerIP &&
134
- this.getConnectionCountByIP(ip) >= this.smartProxy.settings.maxConnectionsPerIP
135
- ) {
136
- return {
137
- allowed: false,
138
- reason: `Maximum connections per IP (${this.smartProxy.settings.maxConnectionsPerIP}) exceeded`
139
- };
140
- }
141
-
142
- // Check connection rate limit
143
- if (
144
- this.smartProxy.settings.connectionRateLimitPerMinute &&
145
- !this.checkConnectionRate(ip)
146
- ) {
147
- return {
148
- allowed: false,
149
- reason: `Connection rate limit (${this.smartProxy.settings.connectionRateLimitPerMinute}/min) exceeded`
150
- };
151
- }
152
-
153
- return { allowed: true };
154
- }
155
-
156
- /**
157
- * Atomically validate an IP and track the connection if allowed.
158
- * This prevents race conditions where concurrent connections could bypass per-IP limits.
159
- *
160
- * @param ip - The IP address to validate
161
- * @param connectionId - The connection ID to track if validation passes
162
- * @returns Object with validation result and reason
163
- */
164
- public validateAndTrackIP(ip: string, connectionId: string): { allowed: boolean; reason?: string } {
165
- // Check connection count limit BEFORE tracking
166
- if (
167
- this.smartProxy.settings.maxConnectionsPerIP &&
168
- this.getConnectionCountByIP(ip) >= this.smartProxy.settings.maxConnectionsPerIP
169
- ) {
170
- return {
171
- allowed: false,
172
- reason: `Maximum connections per IP (${this.smartProxy.settings.maxConnectionsPerIP}) exceeded`
173
- };
174
- }
175
-
176
- // Check connection rate limit
177
- if (
178
- this.smartProxy.settings.connectionRateLimitPerMinute &&
179
- !this.checkConnectionRate(ip)
180
- ) {
181
- return {
182
- allowed: false,
183
- reason: `Connection rate limit (${this.smartProxy.settings.connectionRateLimitPerMinute}/min) exceeded`
184
- };
185
- }
186
-
187
- // Validation passed - immediately track to prevent race conditions
188
- this.trackConnectionByIP(ip, connectionId);
189
-
190
- return { allowed: true };
191
- }
192
-
193
- /**
194
- * Clears all IP tracking data (for shutdown)
195
- */
196
- public clearIPTracking(): void {
197
- if (this.cleanupInterval) {
198
- clearInterval(this.cleanupInterval);
199
- this.cleanupInterval = null;
200
- }
201
- this.connectionsByIP.clear();
202
- this.connectionRateByIP.clear();
203
- }
204
-
205
- /**
206
- * Start periodic cleanup of expired data
207
- */
208
- private startPeriodicCleanup(): void {
209
- this.cleanupInterval = setInterval(() => {
210
- this.performCleanup();
211
- }, 60000); // Run every minute
212
-
213
- // Unref the timer so it doesn't keep the process alive
214
- if (this.cleanupInterval.unref) {
215
- this.cleanupInterval.unref();
216
- }
217
- }
218
-
219
- /**
220
- * Perform cleanup of expired rate limits and empty IP entries
221
- */
222
- private performCleanup(): void {
223
- const now = Date.now();
224
- const minute = 60 * 1000;
225
- let cleanedRateLimits = 0;
226
- let cleanedIPs = 0;
227
-
228
- // Clean up expired rate limit timestamps
229
- for (const [ip, timestamps] of this.connectionRateByIP.entries()) {
230
- const validTimestamps = timestamps.filter(time => now - time < minute);
231
-
232
- if (validTimestamps.length === 0) {
233
- // No valid timestamps, remove the IP entry
234
- this.connectionRateByIP.delete(ip);
235
- cleanedRateLimits++;
236
- } else if (validTimestamps.length < timestamps.length) {
237
- // Some timestamps expired, update with valid ones
238
- this.connectionRateByIP.set(ip, validTimestamps);
239
- }
240
- }
241
-
242
- // Clean up IPs with no active connections
243
- for (const [ip, connections] of this.connectionsByIP.entries()) {
244
- if (connections.size === 0) {
245
- this.connectionsByIP.delete(ip);
246
- cleanedIPs++;
247
- }
248
- }
249
-
250
- // Log cleanup stats if anything was cleaned
251
- if (cleanedRateLimits > 0 || cleanedIPs > 0) {
252
- if (this.smartProxy.settings.enableDetailedLogging) {
253
- connectionLogDeduplicator.log(
254
- 'ip-cleanup',
255
- 'debug',
256
- 'IP tracking cleanup completed',
257
- {
258
- cleanedRateLimits,
259
- cleanedIPs,
260
- remainingIPs: this.connectionsByIP.size,
261
- remainingRateLimits: this.connectionRateByIP.size,
262
- component: 'security-manager'
263
- },
264
- 'periodic-cleanup'
265
- );
266
- }
267
- }
268
- }
269
- }
@@ -1,138 +0,0 @@
1
- import type { IThroughputSample, IThroughputData, IThroughputHistoryPoint } from './models/metrics-types.js';
2
-
3
- /**
4
- * Tracks throughput data using time-series sampling
5
- */
6
- export class ThroughputTracker {
7
- private samples: IThroughputSample[] = [];
8
- private readonly maxSamples: number;
9
- private accumulatedBytesIn: number = 0;
10
- private accumulatedBytesOut: number = 0;
11
- private lastSampleTime: number = 0;
12
-
13
- constructor(retentionSeconds: number = 3600) {
14
- // Keep samples for the retention period at 1 sample per second
15
- this.maxSamples = retentionSeconds;
16
- }
17
-
18
- /**
19
- * Record bytes transferred (called on every data transfer)
20
- */
21
- public recordBytes(bytesIn: number, bytesOut: number): void {
22
- this.accumulatedBytesIn += bytesIn;
23
- this.accumulatedBytesOut += bytesOut;
24
- }
25
-
26
- /**
27
- * Take a sample of accumulated bytes (called every second)
28
- */
29
- public takeSample(): void {
30
- const now = Date.now();
31
-
32
- // Record accumulated bytes since last sample
33
- this.samples.push({
34
- timestamp: now,
35
- bytesIn: this.accumulatedBytesIn,
36
- bytesOut: this.accumulatedBytesOut
37
- });
38
-
39
- // Reset accumulators
40
- this.accumulatedBytesIn = 0;
41
- this.accumulatedBytesOut = 0;
42
- this.lastSampleTime = now;
43
-
44
- // Maintain circular buffer - remove oldest samples
45
- if (this.samples.length > this.maxSamples) {
46
- this.samples.shift();
47
- }
48
- }
49
-
50
- /**
51
- * Get throughput rate over specified window (bytes per second)
52
- */
53
- public getRate(windowSeconds: number): IThroughputData {
54
- if (this.samples.length === 0) {
55
- return { in: 0, out: 0 };
56
- }
57
-
58
- const now = Date.now();
59
- const windowStart = now - (windowSeconds * 1000);
60
-
61
- // Find samples within the window
62
- const relevantSamples = this.samples.filter(s => s.timestamp > windowStart);
63
-
64
- if (relevantSamples.length === 0) {
65
- return { in: 0, out: 0 };
66
- }
67
-
68
- // Calculate total bytes in window
69
- const totalBytesIn = relevantSamples.reduce((sum, s) => sum + s.bytesIn, 0);
70
- const totalBytesOut = relevantSamples.reduce((sum, s) => sum + s.bytesOut, 0);
71
-
72
- // Use actual number of seconds covered by samples for accurate rate
73
- const oldestSampleTime = relevantSamples[0].timestamp;
74
- const newestSampleTime = relevantSamples[relevantSamples.length - 1].timestamp;
75
- const actualSeconds = Math.max(1, (newestSampleTime - oldestSampleTime) / 1000 + 1);
76
-
77
- return {
78
- in: Math.round(totalBytesIn / actualSeconds),
79
- out: Math.round(totalBytesOut / actualSeconds)
80
- };
81
- }
82
-
83
- /**
84
- * Get throughput history for specified duration
85
- */
86
- public getHistory(durationSeconds: number): IThroughputHistoryPoint[] {
87
- const now = Date.now();
88
- const startTime = now - (durationSeconds * 1000);
89
-
90
- // Filter samples within duration
91
- const relevantSamples = this.samples.filter(s => s.timestamp > startTime);
92
-
93
- // Convert to history points with per-second rates
94
- const history: IThroughputHistoryPoint[] = [];
95
-
96
- for (let i = 0; i < relevantSamples.length; i++) {
97
- const sample = relevantSamples[i];
98
-
99
- // For the first sample or samples after gaps, we can't calculate rate
100
- if (i === 0 || sample.timestamp - relevantSamples[i - 1].timestamp > 2000) {
101
- history.push({
102
- timestamp: sample.timestamp,
103
- in: sample.bytesIn,
104
- out: sample.bytesOut
105
- });
106
- } else {
107
- // Calculate rate based on time since previous sample
108
- const prevSample = relevantSamples[i - 1];
109
- const timeDelta = (sample.timestamp - prevSample.timestamp) / 1000;
110
-
111
- history.push({
112
- timestamp: sample.timestamp,
113
- in: Math.round(sample.bytesIn / timeDelta),
114
- out: Math.round(sample.bytesOut / timeDelta)
115
- });
116
- }
117
- }
118
-
119
- return history;
120
- }
121
-
122
- /**
123
- * Clear all samples
124
- */
125
- public clear(): void {
126
- this.samples = [];
127
- this.accumulatedBytesIn = 0;
128
- this.accumulatedBytesOut = 0;
129
- this.lastSampleTime = 0;
130
- }
131
-
132
- /**
133
- * Get sample count for debugging
134
- */
135
- public getSampleCount(): number {
136
- return this.samples.length;
137
- }
138
- }