@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.
- package/changelog.md +15 -0
- package/dist_rust/rustproxy_linux_amd64 +0 -0
- package/dist_rust/rustproxy_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/core/index.d.ts +0 -1
- package/dist_ts/core/index.js +1 -2
- package/dist_ts/core/models/index.d.ts +0 -1
- package/dist_ts/core/models/index.js +1 -2
- package/dist_ts/core/utils/index.d.ts +0 -12
- package/dist_ts/core/utils/index.js +1 -13
- package/dist_ts/index.d.ts +0 -3
- package/dist_ts/index.js +2 -7
- package/dist_ts/protocols/http/index.d.ts +0 -1
- package/dist_ts/protocols/http/index.js +1 -2
- package/dist_ts/protocols/index.d.ts +0 -7
- package/dist_ts/protocols/index.js +1 -8
- package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +20 -0
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +2 -1
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +4 -1
- package/dist_ts/proxies/smart-proxy/socket-handler-server.js +6 -1
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +0 -7
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +50 -51
- package/dist_ts/routing/index.d.ts +0 -1
- package/dist_ts/routing/index.js +1 -3
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/index.ts +0 -1
- package/ts/core/models/index.ts +0 -1
- package/ts/core/utils/index.ts +0 -12
- package/ts/index.ts +1 -7
- package/ts/protocols/http/index.ts +1 -2
- package/ts/protocols/index.ts +0 -7
- package/ts/proxies/smart-proxy/models/metrics-types.ts +21 -0
- package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +4 -1
- package/ts/proxies/smart-proxy/socket-handler-server.ts +6 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +60 -59
- package/ts/routing/index.ts +0 -3
- package/dist_ts/core/events/index.d.ts +0 -4
- package/dist_ts/core/events/index.js +0 -5
- package/dist_ts/core/models/socket-augmentation.d.ts +0 -15
- package/dist_ts/core/models/socket-augmentation.js +0 -18
- package/dist_ts/core/utils/async-utils.d.ts +0 -81
- package/dist_ts/core/utils/async-utils.js +0 -216
- package/dist_ts/core/utils/binary-heap.d.ts +0 -73
- package/dist_ts/core/utils/binary-heap.js +0 -193
- package/dist_ts/core/utils/enhanced-connection-pool.d.ts +0 -110
- package/dist_ts/core/utils/enhanced-connection-pool.js +0 -325
- package/dist_ts/core/utils/fs-utils.d.ts +0 -144
- package/dist_ts/core/utils/fs-utils.js +0 -252
- package/dist_ts/core/utils/ip-utils.d.ts +0 -69
- package/dist_ts/core/utils/ip-utils.js +0 -270
- package/dist_ts/core/utils/lifecycle-component.d.ts +0 -59
- package/dist_ts/core/utils/lifecycle-component.js +0 -211
- package/dist_ts/core/utils/log-deduplicator.d.ts +0 -39
- package/dist_ts/core/utils/log-deduplicator.js +0 -305
- package/dist_ts/core/utils/security-utils.d.ts +0 -111
- package/dist_ts/core/utils/security-utils.js +0 -212
- package/dist_ts/core/utils/shared-security-manager.d.ts +0 -128
- package/dist_ts/core/utils/shared-security-manager.js +0 -362
- package/dist_ts/core/utils/socket-utils.d.ts +0 -63
- package/dist_ts/core/utils/socket-utils.js +0 -249
- package/dist_ts/core/utils/template-utils.d.ts +0 -37
- package/dist_ts/core/utils/template-utils.js +0 -104
- package/dist_ts/core/utils/validation-utils.d.ts +0 -61
- package/dist_ts/core/utils/validation-utils.js +0 -149
- package/dist_ts/core/utils/websocket-utils.d.ts +0 -22
- package/dist_ts/core/utils/websocket-utils.js +0 -30
- package/dist_ts/detection/detectors/http-detector.d.ts +0 -33
- package/dist_ts/detection/detectors/http-detector.js +0 -101
- package/dist_ts/detection/detectors/quick-detector.d.ts +0 -28
- package/dist_ts/detection/detectors/quick-detector.js +0 -131
- package/dist_ts/detection/detectors/routing-extractor.d.ts +0 -28
- package/dist_ts/detection/detectors/routing-extractor.js +0 -122
- package/dist_ts/detection/detectors/tls-detector.d.ts +0 -47
- package/dist_ts/detection/detectors/tls-detector.js +0 -183
- package/dist_ts/detection/index.d.ts +0 -17
- package/dist_ts/detection/index.js +0 -22
- package/dist_ts/detection/models/detection-types.d.ts +0 -87
- package/dist_ts/detection/models/detection-types.js +0 -5
- package/dist_ts/detection/models/interfaces.d.ts +0 -97
- package/dist_ts/detection/models/interfaces.js +0 -5
- package/dist_ts/detection/protocol-detector.d.ts +0 -79
- package/dist_ts/detection/protocol-detector.js +0 -253
- package/dist_ts/detection/utils/buffer-utils.d.ts +0 -61
- package/dist_ts/detection/utils/buffer-utils.js +0 -127
- package/dist_ts/detection/utils/fragment-manager.d.ts +0 -31
- package/dist_ts/detection/utils/fragment-manager.js +0 -53
- package/dist_ts/detection/utils/parser-utils.d.ts +0 -42
- package/dist_ts/detection/utils/parser-utils.js +0 -63
- package/dist_ts/protocols/common/fragment-handler.d.ts +0 -73
- package/dist_ts/protocols/common/fragment-handler.js +0 -121
- package/dist_ts/protocols/common/index.d.ts +0 -7
- package/dist_ts/protocols/common/index.js +0 -8
- package/dist_ts/protocols/common/types.d.ts +0 -68
- package/dist_ts/protocols/common/types.js +0 -7
- package/dist_ts/protocols/http/parser.d.ts +0 -58
- package/dist_ts/protocols/http/parser.js +0 -184
- package/dist_ts/protocols/proxy/index.d.ts +0 -5
- package/dist_ts/protocols/proxy/index.js +0 -6
- package/dist_ts/protocols/proxy/types.d.ts +0 -47
- package/dist_ts/protocols/proxy/types.js +0 -6
- package/dist_ts/protocols/tls/alerts/index.d.ts +0 -4
- package/dist_ts/protocols/tls/alerts/index.js +0 -5
- package/dist_ts/protocols/tls/alerts/tls-alert.d.ts +0 -150
- package/dist_ts/protocols/tls/alerts/tls-alert.js +0 -226
- package/dist_ts/protocols/tls/index.d.ts +0 -12
- package/dist_ts/protocols/tls/index.js +0 -27
- package/dist_ts/protocols/tls/sni/client-hello-parser.d.ts +0 -100
- package/dist_ts/protocols/tls/sni/client-hello-parser.js +0 -463
- package/dist_ts/protocols/tls/sni/index.d.ts +0 -5
- package/dist_ts/protocols/tls/sni/index.js +0 -6
- package/dist_ts/protocols/tls/sni/sni-extraction.d.ts +0 -58
- package/dist_ts/protocols/tls/sni/sni-extraction.js +0 -275
- package/dist_ts/protocols/tls/utils/index.d.ts +0 -4
- package/dist_ts/protocols/tls/utils/index.js +0 -5
- package/dist_ts/protocols/tls/utils/tls-utils.d.ts +0 -158
- package/dist_ts/protocols/tls/utils/tls-utils.js +0 -187
- package/dist_ts/protocols/websocket/constants.d.ts +0 -55
- package/dist_ts/protocols/websocket/constants.js +0 -58
- package/dist_ts/protocols/websocket/index.d.ts +0 -7
- package/dist_ts/protocols/websocket/index.js +0 -8
- package/dist_ts/protocols/websocket/types.d.ts +0 -47
- package/dist_ts/protocols/websocket/types.js +0 -5
- package/dist_ts/protocols/websocket/utils.d.ts +0 -25
- package/dist_ts/protocols/websocket/utils.js +0 -103
- package/dist_ts/routing/router/http-router.d.ts +0 -89
- package/dist_ts/routing/router/http-router.js +0 -205
- package/dist_ts/routing/router/index.d.ts +0 -5
- package/dist_ts/routing/router/index.js +0 -6
- package/dist_ts/tls/index.d.ts +0 -16
- package/dist_ts/tls/index.js +0 -24
- package/dist_ts/tls/sni/index.d.ts +0 -4
- package/dist_ts/tls/sni/index.js +0 -5
- package/dist_ts/tls/sni/sni-handler.d.ts +0 -154
- package/dist_ts/tls/sni/sni-handler.js +0 -191
- package/ts/core/events/index.ts +0 -3
- package/ts/core/models/socket-augmentation.ts +0 -38
- package/ts/core/utils/async-utils.ts +0 -275
- package/ts/core/utils/binary-heap.ts +0 -225
- package/ts/core/utils/enhanced-connection-pool.ts +0 -425
- package/ts/core/utils/fs-utils.ts +0 -270
- package/ts/core/utils/ip-utils.ts +0 -303
- package/ts/core/utils/lifecycle-component.ts +0 -251
- package/ts/core/utils/log-deduplicator.ts +0 -370
- package/ts/core/utils/security-utils.ts +0 -305
- package/ts/core/utils/shared-security-manager.ts +0 -470
- package/ts/core/utils/socket-utils.ts +0 -322
- package/ts/core/utils/template-utils.ts +0 -124
- package/ts/core/utils/validation-utils.ts +0 -177
- package/ts/core/utils/websocket-utils.ts +0 -33
- package/ts/detection/detectors/http-detector.ts +0 -127
- package/ts/detection/detectors/quick-detector.ts +0 -148
- package/ts/detection/detectors/routing-extractor.ts +0 -147
- package/ts/detection/detectors/tls-detector.ts +0 -223
- package/ts/detection/index.ts +0 -25
- package/ts/detection/models/detection-types.ts +0 -102
- package/ts/detection/models/interfaces.ts +0 -115
- package/ts/detection/protocol-detector.ts +0 -319
- package/ts/detection/utils/buffer-utils.ts +0 -141
- package/ts/detection/utils/fragment-manager.ts +0 -64
- package/ts/detection/utils/parser-utils.ts +0 -77
- package/ts/protocols/common/fragment-handler.ts +0 -167
- package/ts/protocols/common/index.ts +0 -8
- package/ts/protocols/common/types.ts +0 -76
- package/ts/protocols/http/parser.ts +0 -219
- package/ts/protocols/proxy/index.ts +0 -6
- package/ts/protocols/proxy/types.ts +0 -53
- package/ts/protocols/tls/alerts/index.ts +0 -3
- package/ts/protocols/tls/alerts/tls-alert.ts +0 -259
- package/ts/protocols/tls/index.ts +0 -37
- package/ts/protocols/tls/sni/client-hello-parser.ts +0 -629
- package/ts/protocols/tls/sni/index.ts +0 -6
- package/ts/protocols/tls/sni/sni-extraction.ts +0 -353
- package/ts/protocols/tls/utils/index.ts +0 -3
- package/ts/protocols/tls/utils/tls-utils.ts +0 -201
- package/ts/protocols/websocket/constants.ts +0 -60
- package/ts/protocols/websocket/index.ts +0 -8
- package/ts/protocols/websocket/types.ts +0 -53
- package/ts/protocols/websocket/utils.ts +0 -98
- package/ts/routing/router/http-router.ts +0 -266
- package/ts/routing/router/index.ts +0 -7
- package/ts/tls/index.ts +0 -29
- package/ts/tls/sni/index.ts +0 -3
- 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
|
-
}
|