@push.rocks/smartproxy 19.6.12 → 19.6.14

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 (34) hide show
  1. package/dist_ts/core/utils/log-deduplicator.d.ts +36 -0
  2. package/dist_ts/core/utils/log-deduplicator.js +224 -0
  3. package/dist_ts/core/utils/shared-security-manager.d.ts +2 -1
  4. package/dist_ts/core/utils/shared-security-manager.js +22 -2
  5. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -0
  6. package/dist_ts/proxies/http-proxy/http-proxy.js +94 -9
  7. package/dist_ts/proxies/http-proxy/models/types.d.ts +2 -0
  8. package/dist_ts/proxies/http-proxy/models/types.js +1 -1
  9. package/dist_ts/proxies/http-proxy/security-manager.d.ts +42 -1
  10. package/dist_ts/proxies/http-proxy/security-manager.js +121 -2
  11. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +14 -0
  12. package/dist_ts/proxies/smart-proxy/connection-manager.js +74 -26
  13. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +5 -1
  14. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +1 -0
  15. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +24 -8
  16. package/dist_ts/proxies/smart-proxy/security-manager.d.ts +9 -0
  17. package/dist_ts/proxies/smart-proxy/security-manager.js +63 -1
  18. package/dist_ts/proxies/smart-proxy/smart-proxy.js +4 -1
  19. package/dist_ts/proxies/smart-proxy/throughput-tracker.js +8 -10
  20. package/package.json +1 -1
  21. package/readme.hints.md +121 -1
  22. package/readme.plan.md +34 -353
  23. package/ts/core/utils/log-deduplicator.ts +280 -0
  24. package/ts/core/utils/shared-security-manager.ts +24 -1
  25. package/ts/proxies/http-proxy/http-proxy.ts +129 -9
  26. package/ts/proxies/http-proxy/models/types.ts +4 -0
  27. package/ts/proxies/http-proxy/security-manager.ts +136 -1
  28. package/ts/proxies/smart-proxy/connection-manager.ts +93 -27
  29. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +5 -0
  30. package/ts/proxies/smart-proxy/models/interfaces.ts +1 -0
  31. package/ts/proxies/smart-proxy/route-connection-handler.ts +45 -14
  32. package/ts/proxies/smart-proxy/security-manager.ts +76 -1
  33. package/ts/proxies/smart-proxy/smart-proxy.ts +4 -0
  34. package/ts/proxies/smart-proxy/throughput-tracker.ts +7 -13
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
3
3
  import { logger } from '../../core/utils/logger.js';
4
+ import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
4
5
  // Route checking functions have been removed
5
6
  import type { IRouteConfig, IRouteAction } from './models/route-types.js';
6
7
  import type { IRouteContext } from '../../core/models/route-context.js';
@@ -563,12 +564,20 @@ export class RouteConnectionHandler {
563
564
  );
564
565
 
565
566
  if (!isIPAllowed) {
566
- logger.log('warn', `IP ${remoteIP} blocked by route security for route ${route.name || 'unnamed'} (connection: ${connectionId})`, {
567
- connectionId,
568
- remoteIP,
569
- routeName: route.name || 'unnamed',
570
- component: 'route-handler'
571
- });
567
+ // Deduplicated logging for route IP blocks
568
+ connectionLogDeduplicator.log(
569
+ 'ip-rejected',
570
+ 'warn',
571
+ `IP blocked by route security`,
572
+ {
573
+ connectionId,
574
+ remoteIP,
575
+ routeName: route.name || 'unnamed',
576
+ reason: 'route-ip-blocked',
577
+ component: 'route-handler'
578
+ },
579
+ remoteIP
580
+ );
572
581
  socket.end();
573
582
  this.smartProxy.connectionManager.cleanupConnection(record, 'route_ip_blocked');
574
583
  return;
@@ -577,14 +586,28 @@ export class RouteConnectionHandler {
577
586
 
578
587
  // Check max connections per route
579
588
  if (route.security.maxConnections !== undefined) {
580
- // TODO: Implement per-route connection tracking
581
- // For now, log that this feature is not yet implemented
582
- if (this.smartProxy.settings.enableDetailedLogging) {
583
- logger.log('warn', `Route ${route.name} has maxConnections=${route.security.maxConnections} configured but per-route connection limits are not yet implemented`, {
584
- connectionId,
585
- routeName: route.name,
586
- component: 'route-handler'
587
- });
589
+ const routeId = route.id || route.name || 'unnamed';
590
+ const currentConnections = this.smartProxy.connectionManager.getConnectionCountByRoute(routeId);
591
+
592
+ if (currentConnections >= route.security.maxConnections) {
593
+ // Deduplicated logging for route connection limits
594
+ connectionLogDeduplicator.log(
595
+ 'connection-rejected',
596
+ 'warn',
597
+ `Route connection limit reached`,
598
+ {
599
+ connectionId,
600
+ routeName: route.name,
601
+ currentConnections,
602
+ maxConnections: route.security.maxConnections,
603
+ reason: 'route-limit',
604
+ component: 'route-handler'
605
+ },
606
+ `route-limit-${route.name}`
607
+ );
608
+ socket.end();
609
+ this.smartProxy.connectionManager.cleanupConnection(record, 'route_connection_limit');
610
+ return;
588
611
  }
589
612
  }
590
613
 
@@ -642,6 +665,10 @@ export class RouteConnectionHandler {
642
665
 
643
666
  // Store the route config in the connection record for metrics and other uses
644
667
  record.routeConfig = route;
668
+ record.routeId = route.id || route.name || 'unnamed';
669
+
670
+ // Track connection by route
671
+ this.smartProxy.connectionManager.trackConnectionByRoute(record.routeId, record.id);
645
672
 
646
673
  // Check if this route uses NFTables for forwarding
647
674
  if (action.forwardingEngine === 'nftables') {
@@ -960,6 +987,10 @@ export class RouteConnectionHandler {
960
987
 
961
988
  // Store the route config in the connection record for metrics and other uses
962
989
  record.routeConfig = route;
990
+ record.routeId = route.id || route.name || 'unnamed';
991
+
992
+ // Track connection by route
993
+ this.smartProxy.connectionManager.trackConnectionByRoute(record.routeId, record.id);
963
994
 
964
995
  if (!route.action.socketHandler) {
965
996
  logger.log('error', 'socket-handler action missing socketHandler function', {
@@ -1,5 +1,7 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { SmartProxy } from './smart-proxy.js';
3
+ import { logger } from '../../core/utils/logger.js';
4
+ import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
3
5
 
4
6
  /**
5
7
  * Handles security aspects like IP tracking, rate limiting, and authorization
@@ -7,8 +9,12 @@ import type { SmartProxy } from './smart-proxy.js';
7
9
  export class SecurityManager {
8
10
  private connectionsByIP: Map<string, Set<string>> = new Map();
9
11
  private connectionRateByIP: Map<string, number[]> = new Map();
12
+ private cleanupInterval: NodeJS.Timeout | null = null;
10
13
 
11
- constructor(private smartProxy: SmartProxy) {}
14
+ constructor(private smartProxy: SmartProxy) {
15
+ // Start periodic cleanup every 60 seconds
16
+ this.startPeriodicCleanup();
17
+ }
12
18
 
13
19
  /**
14
20
  * Get connections count by IP
@@ -164,7 +170,76 @@ export class SecurityManager {
164
170
  * Clears all IP tracking data (for shutdown)
165
171
  */
166
172
  public clearIPTracking(): void {
173
+ if (this.cleanupInterval) {
174
+ clearInterval(this.cleanupInterval);
175
+ this.cleanupInterval = null;
176
+ }
167
177
  this.connectionsByIP.clear();
168
178
  this.connectionRateByIP.clear();
169
179
  }
180
+
181
+ /**
182
+ * Start periodic cleanup of expired data
183
+ */
184
+ private startPeriodicCleanup(): void {
185
+ this.cleanupInterval = setInterval(() => {
186
+ this.performCleanup();
187
+ }, 60000); // Run every minute
188
+
189
+ // Unref the timer so it doesn't keep the process alive
190
+ if (this.cleanupInterval.unref) {
191
+ this.cleanupInterval.unref();
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Perform cleanup of expired rate limits and empty IP entries
197
+ */
198
+ private performCleanup(): void {
199
+ const now = Date.now();
200
+ const minute = 60 * 1000;
201
+ let cleanedRateLimits = 0;
202
+ let cleanedIPs = 0;
203
+
204
+ // Clean up expired rate limit timestamps
205
+ for (const [ip, timestamps] of this.connectionRateByIP.entries()) {
206
+ const validTimestamps = timestamps.filter(time => now - time < minute);
207
+
208
+ if (validTimestamps.length === 0) {
209
+ // No valid timestamps, remove the IP entry
210
+ this.connectionRateByIP.delete(ip);
211
+ cleanedRateLimits++;
212
+ } else if (validTimestamps.length < timestamps.length) {
213
+ // Some timestamps expired, update with valid ones
214
+ this.connectionRateByIP.set(ip, validTimestamps);
215
+ }
216
+ }
217
+
218
+ // Clean up IPs with no active connections
219
+ for (const [ip, connections] of this.connectionsByIP.entries()) {
220
+ if (connections.size === 0) {
221
+ this.connectionsByIP.delete(ip);
222
+ cleanedIPs++;
223
+ }
224
+ }
225
+
226
+ // Log cleanup stats if anything was cleaned
227
+ if (cleanedRateLimits > 0 || cleanedIPs > 0) {
228
+ if (this.smartProxy.settings.enableDetailedLogging) {
229
+ connectionLogDeduplicator.log(
230
+ 'ip-cleanup',
231
+ 'debug',
232
+ 'IP tracking cleanup completed',
233
+ {
234
+ cleanedRateLimits,
235
+ cleanedIPs,
236
+ remainingIPs: this.connectionsByIP.size,
237
+ remainingRateLimits: this.connectionRateByIP.size,
238
+ component: 'security-manager'
239
+ },
240
+ 'periodic-cleanup'
241
+ );
242
+ }
243
+ }
244
+ }
170
245
  }
@@ -1,5 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { logger } from '../../core/utils/logger.js';
3
+ import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
3
4
 
4
5
  // Importing required components
5
6
  import { ConnectionManager } from './connection-manager.js';
@@ -515,6 +516,9 @@ export class SmartProxy extends plugins.EventEmitter {
515
516
 
516
517
  // Stop metrics collector
517
518
  this.metricsCollector.stop();
519
+
520
+ // Flush any pending deduplicated logs
521
+ connectionLogDeduplicator.flushAll();
518
522
 
519
523
  logger.log('info', 'SmartProxy shutdown complete.');
520
524
  }
@@ -65,24 +65,18 @@ export class ThroughputTracker {
65
65
  return { in: 0, out: 0 };
66
66
  }
67
67
 
68
- // Sum bytes in the window
68
+ // Calculate total bytes in window
69
69
  const totalBytesIn = relevantSamples.reduce((sum, s) => sum + s.bytesIn, 0);
70
70
  const totalBytesOut = relevantSamples.reduce((sum, s) => sum + s.bytesOut, 0);
71
71
 
72
- // Calculate actual window duration (might be less than requested if not enough data)
73
- const actualWindowSeconds = Math.min(
74
- windowSeconds,
75
- (now - relevantSamples[0].timestamp) / 1000
76
- );
77
-
78
- // Avoid division by zero
79
- if (actualWindowSeconds === 0) {
80
- return { in: 0, out: 0 };
81
- }
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);
82
76
 
83
77
  return {
84
- in: Math.round(totalBytesIn / actualWindowSeconds),
85
- out: Math.round(totalBytesOut / actualWindowSeconds)
78
+ in: Math.round(totalBytesIn / actualSeconds),
79
+ out: Math.round(totalBytesOut / actualSeconds)
86
80
  };
87
81
  }
88
82