@push.rocks/smartproxy 25.17.10 → 26.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 (184) hide show
  1. package/changelog.md +15 -0
  2. package/dist_rust/rustproxy_linux_amd64 +0 -0
  3. package/dist_rust/rustproxy_linux_arm64 +0 -0
  4. package/dist_ts/00_commitinfo_data.js +2 -2
  5. package/dist_ts/core/index.d.ts +0 -1
  6. package/dist_ts/core/index.js +1 -2
  7. package/dist_ts/core/models/index.d.ts +0 -1
  8. package/dist_ts/core/models/index.js +1 -2
  9. package/dist_ts/core/utils/index.d.ts +0 -12
  10. package/dist_ts/core/utils/index.js +1 -13
  11. package/dist_ts/index.d.ts +0 -3
  12. package/dist_ts/index.js +2 -7
  13. package/dist_ts/protocols/http/index.d.ts +0 -1
  14. package/dist_ts/protocols/http/index.js +1 -2
  15. package/dist_ts/protocols/index.d.ts +0 -7
  16. package/dist_ts/protocols/index.js +1 -8
  17. package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +20 -0
  18. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +2 -1
  19. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +4 -1
  20. package/dist_ts/proxies/smart-proxy/socket-handler-server.js +6 -1
  21. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +0 -7
  22. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +50 -51
  23. package/dist_ts/routing/index.d.ts +0 -1
  24. package/dist_ts/routing/index.js +1 -3
  25. package/package.json +1 -1
  26. package/ts/00_commitinfo_data.ts +1 -1
  27. package/ts/core/index.ts +0 -1
  28. package/ts/core/models/index.ts +0 -1
  29. package/ts/core/utils/index.ts +0 -12
  30. package/ts/index.ts +1 -7
  31. package/ts/protocols/http/index.ts +1 -2
  32. package/ts/protocols/index.ts +0 -7
  33. package/ts/proxies/smart-proxy/models/metrics-types.ts +21 -0
  34. package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +4 -1
  35. package/ts/proxies/smart-proxy/socket-handler-server.ts +6 -0
  36. package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +60 -59
  37. package/ts/routing/index.ts +0 -3
  38. package/dist_ts/core/events/index.d.ts +0 -4
  39. package/dist_ts/core/events/index.js +0 -5
  40. package/dist_ts/core/models/socket-augmentation.d.ts +0 -15
  41. package/dist_ts/core/models/socket-augmentation.js +0 -18
  42. package/dist_ts/core/utils/async-utils.d.ts +0 -81
  43. package/dist_ts/core/utils/async-utils.js +0 -216
  44. package/dist_ts/core/utils/binary-heap.d.ts +0 -73
  45. package/dist_ts/core/utils/binary-heap.js +0 -193
  46. package/dist_ts/core/utils/enhanced-connection-pool.d.ts +0 -110
  47. package/dist_ts/core/utils/enhanced-connection-pool.js +0 -325
  48. package/dist_ts/core/utils/fs-utils.d.ts +0 -144
  49. package/dist_ts/core/utils/fs-utils.js +0 -252
  50. package/dist_ts/core/utils/ip-utils.d.ts +0 -69
  51. package/dist_ts/core/utils/ip-utils.js +0 -270
  52. package/dist_ts/core/utils/lifecycle-component.d.ts +0 -59
  53. package/dist_ts/core/utils/lifecycle-component.js +0 -211
  54. package/dist_ts/core/utils/log-deduplicator.d.ts +0 -39
  55. package/dist_ts/core/utils/log-deduplicator.js +0 -305
  56. package/dist_ts/core/utils/security-utils.d.ts +0 -111
  57. package/dist_ts/core/utils/security-utils.js +0 -212
  58. package/dist_ts/core/utils/shared-security-manager.d.ts +0 -128
  59. package/dist_ts/core/utils/shared-security-manager.js +0 -362
  60. package/dist_ts/core/utils/socket-utils.d.ts +0 -63
  61. package/dist_ts/core/utils/socket-utils.js +0 -249
  62. package/dist_ts/core/utils/template-utils.d.ts +0 -37
  63. package/dist_ts/core/utils/template-utils.js +0 -104
  64. package/dist_ts/core/utils/validation-utils.d.ts +0 -61
  65. package/dist_ts/core/utils/validation-utils.js +0 -149
  66. package/dist_ts/core/utils/websocket-utils.d.ts +0 -22
  67. package/dist_ts/core/utils/websocket-utils.js +0 -30
  68. package/dist_ts/detection/detectors/http-detector.d.ts +0 -33
  69. package/dist_ts/detection/detectors/http-detector.js +0 -101
  70. package/dist_ts/detection/detectors/quick-detector.d.ts +0 -28
  71. package/dist_ts/detection/detectors/quick-detector.js +0 -131
  72. package/dist_ts/detection/detectors/routing-extractor.d.ts +0 -28
  73. package/dist_ts/detection/detectors/routing-extractor.js +0 -122
  74. package/dist_ts/detection/detectors/tls-detector.d.ts +0 -47
  75. package/dist_ts/detection/detectors/tls-detector.js +0 -183
  76. package/dist_ts/detection/index.d.ts +0 -17
  77. package/dist_ts/detection/index.js +0 -22
  78. package/dist_ts/detection/models/detection-types.d.ts +0 -87
  79. package/dist_ts/detection/models/detection-types.js +0 -5
  80. package/dist_ts/detection/models/interfaces.d.ts +0 -97
  81. package/dist_ts/detection/models/interfaces.js +0 -5
  82. package/dist_ts/detection/protocol-detector.d.ts +0 -79
  83. package/dist_ts/detection/protocol-detector.js +0 -253
  84. package/dist_ts/detection/utils/buffer-utils.d.ts +0 -61
  85. package/dist_ts/detection/utils/buffer-utils.js +0 -127
  86. package/dist_ts/detection/utils/fragment-manager.d.ts +0 -31
  87. package/dist_ts/detection/utils/fragment-manager.js +0 -53
  88. package/dist_ts/detection/utils/parser-utils.d.ts +0 -42
  89. package/dist_ts/detection/utils/parser-utils.js +0 -63
  90. package/dist_ts/protocols/common/fragment-handler.d.ts +0 -73
  91. package/dist_ts/protocols/common/fragment-handler.js +0 -121
  92. package/dist_ts/protocols/common/index.d.ts +0 -7
  93. package/dist_ts/protocols/common/index.js +0 -8
  94. package/dist_ts/protocols/common/types.d.ts +0 -68
  95. package/dist_ts/protocols/common/types.js +0 -7
  96. package/dist_ts/protocols/http/parser.d.ts +0 -58
  97. package/dist_ts/protocols/http/parser.js +0 -184
  98. package/dist_ts/protocols/proxy/index.d.ts +0 -5
  99. package/dist_ts/protocols/proxy/index.js +0 -6
  100. package/dist_ts/protocols/proxy/types.d.ts +0 -47
  101. package/dist_ts/protocols/proxy/types.js +0 -6
  102. package/dist_ts/protocols/tls/alerts/index.d.ts +0 -4
  103. package/dist_ts/protocols/tls/alerts/index.js +0 -5
  104. package/dist_ts/protocols/tls/alerts/tls-alert.d.ts +0 -150
  105. package/dist_ts/protocols/tls/alerts/tls-alert.js +0 -226
  106. package/dist_ts/protocols/tls/index.d.ts +0 -12
  107. package/dist_ts/protocols/tls/index.js +0 -27
  108. package/dist_ts/protocols/tls/sni/client-hello-parser.d.ts +0 -100
  109. package/dist_ts/protocols/tls/sni/client-hello-parser.js +0 -463
  110. package/dist_ts/protocols/tls/sni/index.d.ts +0 -5
  111. package/dist_ts/protocols/tls/sni/index.js +0 -6
  112. package/dist_ts/protocols/tls/sni/sni-extraction.d.ts +0 -58
  113. package/dist_ts/protocols/tls/sni/sni-extraction.js +0 -275
  114. package/dist_ts/protocols/tls/utils/index.d.ts +0 -4
  115. package/dist_ts/protocols/tls/utils/index.js +0 -5
  116. package/dist_ts/protocols/tls/utils/tls-utils.d.ts +0 -158
  117. package/dist_ts/protocols/tls/utils/tls-utils.js +0 -187
  118. package/dist_ts/protocols/websocket/constants.d.ts +0 -55
  119. package/dist_ts/protocols/websocket/constants.js +0 -58
  120. package/dist_ts/protocols/websocket/index.d.ts +0 -7
  121. package/dist_ts/protocols/websocket/index.js +0 -8
  122. package/dist_ts/protocols/websocket/types.d.ts +0 -47
  123. package/dist_ts/protocols/websocket/types.js +0 -5
  124. package/dist_ts/protocols/websocket/utils.d.ts +0 -25
  125. package/dist_ts/protocols/websocket/utils.js +0 -103
  126. package/dist_ts/routing/router/http-router.d.ts +0 -89
  127. package/dist_ts/routing/router/http-router.js +0 -205
  128. package/dist_ts/routing/router/index.d.ts +0 -5
  129. package/dist_ts/routing/router/index.js +0 -6
  130. package/dist_ts/tls/index.d.ts +0 -16
  131. package/dist_ts/tls/index.js +0 -24
  132. package/dist_ts/tls/sni/index.d.ts +0 -4
  133. package/dist_ts/tls/sni/index.js +0 -5
  134. package/dist_ts/tls/sni/sni-handler.d.ts +0 -154
  135. package/dist_ts/tls/sni/sni-handler.js +0 -191
  136. package/ts/core/events/index.ts +0 -3
  137. package/ts/core/models/socket-augmentation.ts +0 -38
  138. package/ts/core/utils/async-utils.ts +0 -275
  139. package/ts/core/utils/binary-heap.ts +0 -225
  140. package/ts/core/utils/enhanced-connection-pool.ts +0 -425
  141. package/ts/core/utils/fs-utils.ts +0 -270
  142. package/ts/core/utils/ip-utils.ts +0 -303
  143. package/ts/core/utils/lifecycle-component.ts +0 -251
  144. package/ts/core/utils/log-deduplicator.ts +0 -370
  145. package/ts/core/utils/security-utils.ts +0 -305
  146. package/ts/core/utils/shared-security-manager.ts +0 -470
  147. package/ts/core/utils/socket-utils.ts +0 -322
  148. package/ts/core/utils/template-utils.ts +0 -124
  149. package/ts/core/utils/validation-utils.ts +0 -177
  150. package/ts/core/utils/websocket-utils.ts +0 -33
  151. package/ts/detection/detectors/http-detector.ts +0 -127
  152. package/ts/detection/detectors/quick-detector.ts +0 -148
  153. package/ts/detection/detectors/routing-extractor.ts +0 -147
  154. package/ts/detection/detectors/tls-detector.ts +0 -223
  155. package/ts/detection/index.ts +0 -25
  156. package/ts/detection/models/detection-types.ts +0 -102
  157. package/ts/detection/models/interfaces.ts +0 -115
  158. package/ts/detection/protocol-detector.ts +0 -319
  159. package/ts/detection/utils/buffer-utils.ts +0 -141
  160. package/ts/detection/utils/fragment-manager.ts +0 -64
  161. package/ts/detection/utils/parser-utils.ts +0 -77
  162. package/ts/protocols/common/fragment-handler.ts +0 -167
  163. package/ts/protocols/common/index.ts +0 -8
  164. package/ts/protocols/common/types.ts +0 -76
  165. package/ts/protocols/http/parser.ts +0 -219
  166. package/ts/protocols/proxy/index.ts +0 -6
  167. package/ts/protocols/proxy/types.ts +0 -53
  168. package/ts/protocols/tls/alerts/index.ts +0 -3
  169. package/ts/protocols/tls/alerts/tls-alert.ts +0 -259
  170. package/ts/protocols/tls/index.ts +0 -37
  171. package/ts/protocols/tls/sni/client-hello-parser.ts +0 -629
  172. package/ts/protocols/tls/sni/index.ts +0 -6
  173. package/ts/protocols/tls/sni/sni-extraction.ts +0 -353
  174. package/ts/protocols/tls/utils/index.ts +0 -3
  175. package/ts/protocols/tls/utils/tls-utils.ts +0 -201
  176. package/ts/protocols/websocket/constants.ts +0 -60
  177. package/ts/protocols/websocket/index.ts +0 -8
  178. package/ts/protocols/websocket/types.ts +0 -53
  179. package/ts/protocols/websocket/utils.ts +0 -98
  180. package/ts/routing/router/http-router.ts +0 -266
  181. package/ts/routing/router/index.ts +0 -7
  182. package/ts/tls/index.ts +0 -29
  183. package/ts/tls/sni/index.ts +0 -3
  184. package/ts/tls/sni/sni-handler.ts +0 -264
@@ -1,370 +0,0 @@
1
- import { logger } from './logger.js';
2
-
3
- interface ILogEvent {
4
- level: 'info' | 'warn' | 'error' | 'debug';
5
- message: string;
6
- data?: any;
7
- count: number;
8
- firstSeen: number;
9
- lastSeen: number;
10
- }
11
-
12
- interface IAggregatedEvent {
13
- key: string;
14
- events: Map<string, ILogEvent>;
15
- flushTimer?: NodeJS.Timeout;
16
- }
17
-
18
- /**
19
- * Log deduplication utility to reduce log spam for repetitive events
20
- */
21
- export class LogDeduplicator {
22
- private globalFlushTimer?: NodeJS.Timeout;
23
- private aggregatedEvents: Map<string, IAggregatedEvent> = new Map();
24
- private flushInterval: number = 5000; // 5 seconds
25
- private maxBatchSize: number = 100;
26
- private rapidEventThreshold: number = 50; // Flush early if this many events in 1 second
27
- private lastRapidCheck: number = Date.now();
28
-
29
- constructor(flushInterval?: number) {
30
- if (flushInterval) {
31
- this.flushInterval = flushInterval;
32
- }
33
-
34
- // Set up global periodic flush to ensure logs are emitted regularly
35
- this.globalFlushTimer = setInterval(() => {
36
- this.flushAll();
37
- }, this.flushInterval * 2); // Flush everything every 2x the normal interval
38
-
39
- if (this.globalFlushTimer.unref) {
40
- this.globalFlushTimer.unref();
41
- }
42
- }
43
-
44
- /**
45
- * Log a deduplicated event
46
- * @param key - Aggregation key (e.g., 'connection-rejected', 'cleanup-batch')
47
- * @param level - Log level
48
- * @param message - Log message template
49
- * @param data - Additional data
50
- * @param dedupeKey - Deduplication key within the aggregation (e.g., IP address, reason)
51
- */
52
- public log(
53
- key: string,
54
- level: 'info' | 'warn' | 'error' | 'debug',
55
- message: string,
56
- data?: any,
57
- dedupeKey?: string
58
- ): void {
59
- const eventKey = dedupeKey || message;
60
- const now = Date.now();
61
-
62
- if (!this.aggregatedEvents.has(key)) {
63
- this.aggregatedEvents.set(key, {
64
- key,
65
- events: new Map(),
66
- flushTimer: undefined
67
- });
68
- }
69
-
70
- const aggregated = this.aggregatedEvents.get(key)!;
71
-
72
- if (aggregated.events.has(eventKey)) {
73
- const event = aggregated.events.get(eventKey)!;
74
- event.count++;
75
- event.lastSeen = now;
76
- if (data) {
77
- event.data = { ...event.data, ...data };
78
- }
79
- } else {
80
- aggregated.events.set(eventKey, {
81
- level,
82
- message,
83
- data,
84
- count: 1,
85
- firstSeen: now,
86
- lastSeen: now
87
- });
88
- }
89
-
90
- // Check for rapid events (many events in short time)
91
- const totalEvents = Array.from(aggregated.events.values()).reduce((sum, e) => sum + e.count, 0);
92
-
93
- // If we're getting flooded with events, flush more frequently
94
- if (now - this.lastRapidCheck < 1000 && totalEvents >= this.rapidEventThreshold) {
95
- this.flush(key);
96
- this.lastRapidCheck = now;
97
- } else if (aggregated.events.size >= this.maxBatchSize) {
98
- // Check if we should flush due to size
99
- this.flush(key);
100
- } else if (!aggregated.flushTimer) {
101
- // Schedule flush
102
- aggregated.flushTimer = setTimeout(() => {
103
- this.flush(key);
104
- }, this.flushInterval);
105
-
106
- if (aggregated.flushTimer.unref) {
107
- aggregated.flushTimer.unref();
108
- }
109
- }
110
-
111
- // Update rapid check time
112
- if (now - this.lastRapidCheck >= 1000) {
113
- this.lastRapidCheck = now;
114
- }
115
- }
116
-
117
- /**
118
- * Flush aggregated events for a specific key
119
- */
120
- public flush(key: string): void {
121
- const aggregated = this.aggregatedEvents.get(key);
122
- if (!aggregated || aggregated.events.size === 0) {
123
- return;
124
- }
125
-
126
- if (aggregated.flushTimer) {
127
- clearTimeout(aggregated.flushTimer);
128
- aggregated.flushTimer = undefined;
129
- }
130
-
131
- // Emit aggregated log based on the key
132
- switch (key) {
133
- case 'connection-rejected':
134
- this.flushConnectionRejections(aggregated);
135
- break;
136
- case 'connection-cleanup':
137
- this.flushConnectionCleanups(aggregated);
138
- break;
139
- case 'connection-terminated':
140
- this.flushConnectionTerminations(aggregated);
141
- break;
142
- case 'ip-rejected':
143
- this.flushIPRejections(aggregated);
144
- break;
145
- default:
146
- this.flushGeneric(aggregated);
147
- }
148
-
149
- // Clear events
150
- aggregated.events.clear();
151
- }
152
-
153
- /**
154
- * Flush all pending events
155
- */
156
- public flushAll(): void {
157
- for (const key of this.aggregatedEvents.keys()) {
158
- this.flush(key);
159
- }
160
- }
161
-
162
- private flushConnectionRejections(aggregated: IAggregatedEvent): void {
163
- const totalCount = Array.from(aggregated.events.values()).reduce((sum, e) => sum + e.count, 0);
164
- const byReason = new Map<string, number>();
165
-
166
- for (const [, event] of aggregated.events) {
167
- const reason = event.data?.reason || 'unknown';
168
- byReason.set(reason, (byReason.get(reason) || 0) + event.count);
169
- }
170
-
171
- const reasonSummary = Array.from(byReason.entries())
172
- .sort((a, b) => b[1] - a[1])
173
- .map(([reason, count]) => `${reason}: ${count}`)
174
- .join(', ');
175
-
176
- const duration = Date.now() - Math.min(...Array.from(aggregated.events.values()).map(e => e.firstSeen));
177
- logger.log('warn', `[SUMMARY] Rejected ${totalCount} connections in ${Math.round(duration/1000)}s`, {
178
- reasons: reasonSummary,
179
- uniqueIPs: aggregated.events.size,
180
- component: 'connection-dedup'
181
- });
182
- }
183
-
184
- private flushConnectionCleanups(aggregated: IAggregatedEvent): void {
185
- const totalCount = Array.from(aggregated.events.values()).reduce((sum, e) => sum + e.count, 0);
186
- const byReason = new Map<string, number>();
187
-
188
- for (const [, event] of aggregated.events) {
189
- const reason = event.data?.reason || 'normal';
190
- byReason.set(reason, (byReason.get(reason) || 0) + event.count);
191
- }
192
-
193
- const reasonSummary = Array.from(byReason.entries())
194
- .sort((a, b) => b[1] - a[1])
195
- .slice(0, 5) // Top 5 reasons
196
- .map(([reason, count]) => `${reason}: ${count}`)
197
- .join(', ');
198
-
199
- logger.log('info', `Cleaned up ${totalCount} connections`, {
200
- reasons: reasonSummary,
201
- duration: Date.now() - Math.min(...Array.from(aggregated.events.values()).map(e => e.firstSeen)),
202
- component: 'connection-dedup'
203
- });
204
- }
205
-
206
- private flushConnectionTerminations(aggregated: IAggregatedEvent): void {
207
- const totalCount = Array.from(aggregated.events.values()).reduce((sum, e) => sum + e.count, 0);
208
- const byReason = new Map<string, number>();
209
- const byIP = new Map<string, number>();
210
- let lastActiveCount = 0;
211
-
212
- for (const [, event] of aggregated.events) {
213
- const reason = event.data?.reason || 'unknown';
214
- const ip = event.data?.remoteIP || 'unknown';
215
-
216
- byReason.set(reason, (byReason.get(reason) || 0) + event.count);
217
-
218
- // Track by IP
219
- if (ip !== 'unknown') {
220
- byIP.set(ip, (byIP.get(ip) || 0) + event.count);
221
- }
222
-
223
- // Track the last active connection count
224
- if (event.data?.activeConnections !== undefined) {
225
- lastActiveCount = event.data.activeConnections;
226
- }
227
- }
228
-
229
- const reasonSummary = Array.from(byReason.entries())
230
- .sort((a, b) => b[1] - a[1])
231
- .slice(0, 5) // Top 5 reasons
232
- .map(([reason, count]) => `${reason}: ${count}`)
233
- .join(', ');
234
-
235
- // Show top IPs if there are many different ones
236
- let ipInfo = '';
237
- if (byIP.size > 3) {
238
- const topIPs = Array.from(byIP.entries())
239
- .sort((a, b) => b[1] - a[1])
240
- .slice(0, 3)
241
- .map(([ip, count]) => `${ip} (${count})`)
242
- .join(', ');
243
- ipInfo = `, from ${byIP.size} IPs (top: ${topIPs})`;
244
- } else if (byIP.size > 0) {
245
- ipInfo = `, IPs: ${Array.from(byIP.keys()).join(', ')}`;
246
- }
247
-
248
- const duration = Date.now() - Math.min(...Array.from(aggregated.events.values()).map(e => e.firstSeen));
249
-
250
- // Special handling for localhost connections (HttpProxy)
251
- const localhostCount = byIP.get('::ffff:127.0.0.1') || 0;
252
- if (localhostCount > 0 && byIP.size === 1) {
253
- // All connections are from localhost (HttpProxy)
254
- logger.log('info', `[SUMMARY] ${totalCount} HttpProxy connections terminated in ${Math.round(duration/1000)}s`, {
255
- reasons: reasonSummary,
256
- activeConnections: lastActiveCount,
257
- component: 'connection-dedup'
258
- });
259
- } else {
260
- logger.log('info', `[SUMMARY] ${totalCount} connections terminated in ${Math.round(duration/1000)}s`, {
261
- reasons: reasonSummary,
262
- activeConnections: lastActiveCount,
263
- uniqueReasons: byReason.size,
264
- ...(ipInfo ? { ips: ipInfo } : {}),
265
- component: 'connection-dedup'
266
- });
267
- }
268
- }
269
-
270
- private flushIPRejections(aggregated: IAggregatedEvent): void {
271
- const byIP = new Map<string, { count: number; reasons: Set<string> }>();
272
- const allReasons = new Map<string, number>();
273
-
274
- for (const [ip, event] of aggregated.events) {
275
- if (!byIP.has(ip)) {
276
- byIP.set(ip, { count: 0, reasons: new Set() });
277
- }
278
- const ipData = byIP.get(ip)!;
279
- ipData.count += event.count;
280
- if (event.data?.reason) {
281
- ipData.reasons.add(event.data.reason);
282
- // Track overall reason counts
283
- allReasons.set(event.data.reason, (allReasons.get(event.data.reason) || 0) + event.count);
284
- }
285
- }
286
-
287
- // Create reason summary
288
- const reasonSummary = Array.from(allReasons.entries())
289
- .sort((a, b) => b[1] - a[1])
290
- .map(([reason, count]) => `${reason}: ${count}`)
291
- .join(', ');
292
-
293
- // Log top offenders
294
- const topOffenders = Array.from(byIP.entries())
295
- .sort((a, b) => b[1].count - a[1].count)
296
- .slice(0, 10)
297
- .map(([ip, data]) => `${ip} (${data.count}x, ${Array.from(data.reasons).join('/')})`)
298
- .join(', ');
299
-
300
- const totalRejections = Array.from(byIP.values()).reduce((sum, data) => sum + data.count, 0);
301
-
302
- const duration = Date.now() - Math.min(...Array.from(aggregated.events.values()).map(e => e.firstSeen));
303
- logger.log('warn', `[SUMMARY] Rejected ${totalRejections} connections from ${byIP.size} IPs in ${Math.round(duration/1000)}s (${reasonSummary})`, {
304
- topOffenders,
305
- component: 'ip-dedup'
306
- });
307
- }
308
-
309
- private flushGeneric(aggregated: IAggregatedEvent): void {
310
- const totalCount = Array.from(aggregated.events.values()).reduce((sum, e) => sum + e.count, 0);
311
- const level = aggregated.events.values().next().value?.level || 'info';
312
-
313
- // Special handling for IP cleanup events
314
- if (aggregated.key === 'ip-cleanup') {
315
- const totalCleaned = Array.from(aggregated.events.values()).reduce((sum, e) => {
316
- return sum + (e.data?.cleanedIPs || 0) + (e.data?.cleanedRateLimits || 0);
317
- }, 0);
318
-
319
- if (totalCleaned > 0) {
320
- logger.log(level as any, `IP tracking cleanup: removed ${totalCleaned} entries across ${totalCount} cleanup cycles`, {
321
- duration: Date.now() - Math.min(...Array.from(aggregated.events.values()).map(e => e.firstSeen)),
322
- component: 'log-dedup'
323
- });
324
- }
325
- } else {
326
- logger.log(level as any, `${aggregated.key}: ${totalCount} events`, {
327
- uniqueEvents: aggregated.events.size,
328
- duration: Date.now() - Math.min(...Array.from(aggregated.events.values()).map(e => e.firstSeen)),
329
- component: 'log-dedup'
330
- });
331
- }
332
- }
333
-
334
- /**
335
- * Cleanup and stop deduplication
336
- */
337
- public cleanup(): void {
338
- this.flushAll();
339
-
340
- if (this.globalFlushTimer) {
341
- clearInterval(this.globalFlushTimer);
342
- this.globalFlushTimer = undefined;
343
- }
344
-
345
- for (const aggregated of this.aggregatedEvents.values()) {
346
- if (aggregated.flushTimer) {
347
- clearTimeout(aggregated.flushTimer);
348
- }
349
- }
350
- this.aggregatedEvents.clear();
351
- }
352
- }
353
-
354
- // Global instance for connection-related log deduplication
355
- export const connectionLogDeduplicator = new LogDeduplicator(5000); // 5 second batches
356
-
357
- // Ensure logs are flushed on process exit.
358
- // Only use beforeExit — do NOT call process.exit() from SIGINT/SIGTERM handlers
359
- // as that kills the host process's graceful shutdown (e.g., dcrouter connection draining).
360
- process.on('beforeExit', () => {
361
- connectionLogDeduplicator.flushAll();
362
- });
363
-
364
- process.on('SIGINT', () => {
365
- connectionLogDeduplicator.cleanup();
366
- });
367
-
368
- process.on('SIGTERM', () => {
369
- connectionLogDeduplicator.cleanup();
370
- });
@@ -1,305 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import { IpMatcher } from '../routing/matchers/ip.js';
3
-
4
- /**
5
- * Security utilities for IP validation, rate limiting,
6
- * authentication, and other security features
7
- */
8
-
9
- /**
10
- * Result of IP validation
11
- */
12
- export interface IIpValidationResult {
13
- allowed: boolean;
14
- reason?: string;
15
- }
16
-
17
- /**
18
- * IP connection tracking information
19
- */
20
- export interface IIpConnectionInfo {
21
- connections: Set<string>; // ConnectionIDs
22
- timestamps: number[]; // Connection timestamps
23
- ipVariants: string[]; // Normalized IP variants (e.g., ::ffff:127.0.0.1 and 127.0.0.1)
24
- }
25
-
26
- /**
27
- * Rate limit tracking
28
- */
29
- export interface IRateLimitInfo {
30
- count: number;
31
- expiry: number;
32
- }
33
-
34
- /**
35
- * Logger interface for security utilities
36
- */
37
- export interface ISecurityLogger {
38
- info: (message: string, ...args: any[]) => void;
39
- warn: (message: string, ...args: any[]) => void;
40
- error: (message: string, ...args: any[]) => void;
41
- debug?: (message: string, ...args: any[]) => void;
42
- }
43
-
44
- /**
45
- * Normalize IP addresses for comparison
46
- * Handles IPv4-mapped IPv6 addresses (::ffff:127.0.0.1)
47
- *
48
- * @param ip IP address to normalize
49
- * @returns Array of equivalent IP representations
50
- */
51
- export function normalizeIP(ip: string): string[] {
52
- if (!ip) return [];
53
-
54
- // Handle IPv4-mapped IPv6 addresses (::ffff:127.0.0.1)
55
- if (ip.startsWith('::ffff:')) {
56
- const ipv4 = ip.slice(7);
57
- return [ip, ipv4];
58
- }
59
-
60
- // Handle IPv4 addresses by also checking IPv4-mapped form
61
- if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
62
- return [ip, `::ffff:${ip}`];
63
- }
64
-
65
- return [ip];
66
- }
67
-
68
- /**
69
- * Check if an IP is authorized based on allow and block lists
70
- *
71
- * @param ip - The IP address to check
72
- * @param allowedIPs - Array of allowed IP patterns
73
- * @param blockedIPs - Array of blocked IP patterns
74
- * @returns Whether the IP is authorized
75
- */
76
- export function isIPAuthorized(
77
- ip: string,
78
- allowedIPs: string[] = ['*'],
79
- blockedIPs: string[] = []
80
- ): boolean {
81
- // Skip IP validation if no rules
82
- if (!ip || (allowedIPs.length === 0 && blockedIPs.length === 0)) {
83
- return true;
84
- }
85
-
86
- // First check if IP is blocked - blocked IPs take precedence
87
- if (blockedIPs.length > 0) {
88
- for (const pattern of blockedIPs) {
89
- if (IpMatcher.match(pattern, ip)) {
90
- return false;
91
- }
92
- }
93
- }
94
-
95
- // If allowed IPs list has wildcard, all non-blocked IPs are allowed
96
- if (allowedIPs.includes('*')) {
97
- return true;
98
- }
99
-
100
- // Then check if IP is allowed in the explicit allow list
101
- if (allowedIPs.length > 0) {
102
- for (const pattern of allowedIPs) {
103
- if (IpMatcher.match(pattern, ip)) {
104
- return true;
105
- }
106
- }
107
- // If allowedIPs is specified but no match, deny access
108
- return false;
109
- }
110
-
111
- // Default allow if no explicit allow list
112
- return true;
113
- }
114
-
115
- /**
116
- * Check if an IP exceeds maximum connections
117
- *
118
- * @param ip - The IP address to check
119
- * @param ipConnectionsMap - Map of IPs to connection info
120
- * @param maxConnectionsPerIP - Maximum allowed connections per IP
121
- * @returns Result with allowed status and reason if blocked
122
- */
123
- export function checkMaxConnections(
124
- ip: string,
125
- ipConnectionsMap: Map<string, IIpConnectionInfo>,
126
- maxConnectionsPerIP: number
127
- ): IIpValidationResult {
128
- if (!ipConnectionsMap.has(ip)) {
129
- return { allowed: true };
130
- }
131
-
132
- const connectionCount = ipConnectionsMap.get(ip)!.connections.size;
133
-
134
- if (connectionCount >= maxConnectionsPerIP) {
135
- return {
136
- allowed: false,
137
- reason: `Maximum connections per IP (${maxConnectionsPerIP}) exceeded`
138
- };
139
- }
140
-
141
- return { allowed: true };
142
- }
143
-
144
- /**
145
- * Check if an IP exceeds connection rate limit
146
- *
147
- * @param ip - The IP address to check
148
- * @param ipConnectionsMap - Map of IPs to connection info
149
- * @param rateLimit - Maximum connections per minute
150
- * @returns Result with allowed status and reason if blocked
151
- */
152
- export function checkConnectionRate(
153
- ip: string,
154
- ipConnectionsMap: Map<string, IIpConnectionInfo>,
155
- rateLimit: number
156
- ): IIpValidationResult {
157
- const now = Date.now();
158
- const minute = 60 * 1000;
159
-
160
- // Get or create connection info
161
- if (!ipConnectionsMap.has(ip)) {
162
- const info: IIpConnectionInfo = {
163
- connections: new Set(),
164
- timestamps: [now],
165
- ipVariants: normalizeIP(ip)
166
- };
167
- ipConnectionsMap.set(ip, info);
168
- return { allowed: true };
169
- }
170
-
171
- // Get timestamps and filter out entries older than 1 minute
172
- const info = ipConnectionsMap.get(ip)!;
173
- const timestamps = info.timestamps.filter(time => now - time < minute);
174
- timestamps.push(now);
175
- info.timestamps = timestamps;
176
-
177
- // Check if rate exceeds limit
178
- if (timestamps.length > rateLimit) {
179
- return {
180
- allowed: false,
181
- reason: `Connection rate limit (${rateLimit}/min) exceeded`
182
- };
183
- }
184
-
185
- return { allowed: true };
186
- }
187
-
188
- /**
189
- * Track a connection for an IP
190
- *
191
- * @param ip - The IP address
192
- * @param connectionId - The connection ID to track
193
- * @param ipConnectionsMap - Map of IPs to connection info
194
- */
195
- export function trackConnection(
196
- ip: string,
197
- connectionId: string,
198
- ipConnectionsMap: Map<string, IIpConnectionInfo>
199
- ): void {
200
- if (!ipConnectionsMap.has(ip)) {
201
- ipConnectionsMap.set(ip, {
202
- connections: new Set([connectionId]),
203
- timestamps: [Date.now()],
204
- ipVariants: normalizeIP(ip)
205
- });
206
- return;
207
- }
208
-
209
- const info = ipConnectionsMap.get(ip)!;
210
- info.connections.add(connectionId);
211
- }
212
-
213
- /**
214
- * Remove connection tracking for an IP
215
- *
216
- * @param ip - The IP address
217
- * @param connectionId - The connection ID to remove
218
- * @param ipConnectionsMap - Map of IPs to connection info
219
- */
220
- export function removeConnection(
221
- ip: string,
222
- connectionId: string,
223
- ipConnectionsMap: Map<string, IIpConnectionInfo>
224
- ): void {
225
- if (!ipConnectionsMap.has(ip)) return;
226
-
227
- const info = ipConnectionsMap.get(ip)!;
228
- info.connections.delete(connectionId);
229
-
230
- if (info.connections.size === 0) {
231
- ipConnectionsMap.delete(ip);
232
- }
233
- }
234
-
235
- /**
236
- * Clean up expired rate limits
237
- *
238
- * @param rateLimits - Map of rate limits to clean up
239
- * @param logger - Logger for debug messages
240
- */
241
- export function cleanupExpiredRateLimits(
242
- rateLimits: Map<string, Map<string, IRateLimitInfo>>,
243
- logger?: ISecurityLogger
244
- ): void {
245
- const now = Date.now();
246
- let totalRemoved = 0;
247
-
248
- for (const [routeId, routeLimits] of rateLimits.entries()) {
249
- let removed = 0;
250
- for (const [key, limit] of routeLimits.entries()) {
251
- if (limit.expiry < now) {
252
- routeLimits.delete(key);
253
- removed++;
254
- totalRemoved++;
255
- }
256
- }
257
-
258
- if (removed > 0 && logger?.debug) {
259
- logger.debug(`Cleaned up ${removed} expired rate limits for route ${routeId}`);
260
- }
261
- }
262
-
263
- if (totalRemoved > 0 && logger?.info) {
264
- logger.info(`Cleaned up ${totalRemoved} expired rate limits total`);
265
- }
266
- }
267
-
268
- /**
269
- * Generate basic auth header value from username and password
270
- *
271
- * @param username - The username
272
- * @param password - The password
273
- * @returns Base64 encoded basic auth string
274
- */
275
- export function generateBasicAuthHeader(username: string, password: string): string {
276
- return `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
277
- }
278
-
279
- /**
280
- * Parse basic auth header
281
- *
282
- * @param authHeader - The Authorization header value
283
- * @returns Username and password, or null if invalid
284
- */
285
- export function parseBasicAuthHeader(
286
- authHeader: string
287
- ): { username: string; password: string } | null {
288
- if (!authHeader || !authHeader.startsWith('Basic ')) {
289
- return null;
290
- }
291
-
292
- try {
293
- const base64 = authHeader.slice(6); // Remove 'Basic '
294
- const decoded = Buffer.from(base64, 'base64').toString();
295
- const [username, password] = decoded.split(':');
296
-
297
- if (!username || !password) {
298
- return null;
299
- }
300
-
301
- return { username, password };
302
- } catch (err) {
303
- return null;
304
- }
305
- }