@push.rocks/smartproxy 19.6.6 → 19.6.8
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/dist_ts/core/models/wrapped-socket.d.ts +4 -0
- package/dist_ts/core/models/wrapped-socket.js +18 -1
- package/dist_ts/core/routing/matchers/path.js +3 -2
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +4 -7
- package/dist_ts/proxies/smart-proxy/connection-manager.js +22 -22
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +4 -3
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +9 -9
- package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +68 -56
- package/dist_ts/proxies/smart-proxy/metrics-collector.js +252 -176
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +6 -1
- package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +99 -47
- package/dist_ts/proxies/smart-proxy/nftables-manager.d.ts +4 -4
- package/dist_ts/proxies/smart-proxy/nftables-manager.js +6 -6
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +4 -7
- package/dist_ts/proxies/smart-proxy/port-manager.js +6 -9
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -15
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +133 -130
- package/dist_ts/proxies/smart-proxy/security-manager.d.ts +3 -3
- package/dist_ts/proxies/smart-proxy/security-manager.js +9 -9
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +20 -13
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +16 -13
- package/dist_ts/proxies/smart-proxy/throughput-tracker.d.ts +36 -0
- package/dist_ts/proxies/smart-proxy/throughput-tracker.js +117 -0
- package/dist_ts/proxies/smart-proxy/timeout-manager.d.ts +5 -4
- package/dist_ts/proxies/smart-proxy/timeout-manager.js +20 -16
- package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +3 -3
- package/dist_ts/proxies/smart-proxy/tls-manager.js +12 -12
- package/dist_ts/routing/router/http-router.js +2 -2
- package/package.json +1 -1
- package/readme.hints.md +0 -0
- package/readme.md +239 -73
- package/readme.plan.md +364 -0
- package/ts/core/models/wrapped-socket.ts +18 -0
- package/ts/core/routing/matchers/path.ts +2 -1
- package/ts/proxies/smart-proxy/connection-manager.ts +23 -21
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +9 -8
- package/ts/proxies/smart-proxy/metrics-collector.ts +305 -188
- package/ts/proxies/smart-proxy/models/interfaces.ts +8 -1
- package/ts/proxies/smart-proxy/models/metrics-types.ts +99 -41
- package/ts/proxies/smart-proxy/nftables-manager.ts +5 -5
- package/ts/proxies/smart-proxy/port-manager.ts +6 -14
- package/ts/proxies/smart-proxy/route-connection-handler.ts +141 -138
- package/ts/proxies/smart-proxy/security-manager.ts +8 -8
- package/ts/proxies/smart-proxy/smart-proxy.ts +26 -35
- package/ts/proxies/smart-proxy/throughput-tracker.ts +144 -0
- package/ts/proxies/smart-proxy/timeout-manager.ts +22 -16
- package/ts/proxies/smart-proxy/tls-manager.ts +11 -11
- package/ts/routing/router/http-router.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { SmartProxy } from './smart-proxy.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Handles security aspects like IP tracking, rate limiting, and authorization
|
|
@@ -8,7 +8,7 @@ export class SecurityManager {
|
|
|
8
8
|
private connectionsByIP: Map<string, Set<string>> = new Map();
|
|
9
9
|
private connectionRateByIP: Map<string, number[]> = new Map();
|
|
10
10
|
|
|
11
|
-
constructor(private
|
|
11
|
+
constructor(private smartProxy: SmartProxy) {}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Get connections count by IP
|
|
@@ -36,7 +36,7 @@ export class SecurityManager {
|
|
|
36
36
|
this.connectionRateByIP.set(ip, timestamps);
|
|
37
37
|
|
|
38
38
|
// Check if rate exceeds limit
|
|
39
|
-
return timestamps.length <= this.settings.connectionRateLimitPerMinute!;
|
|
39
|
+
return timestamps.length <= this.smartProxy.settings.connectionRateLimitPerMinute!;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -137,23 +137,23 @@ export class SecurityManager {
|
|
|
137
137
|
public validateIP(ip: string): { allowed: boolean; reason?: string } {
|
|
138
138
|
// Check connection count limit
|
|
139
139
|
if (
|
|
140
|
-
this.settings.maxConnectionsPerIP &&
|
|
141
|
-
this.getConnectionCountByIP(ip) >= this.settings.maxConnectionsPerIP
|
|
140
|
+
this.smartProxy.settings.maxConnectionsPerIP &&
|
|
141
|
+
this.getConnectionCountByIP(ip) >= this.smartProxy.settings.maxConnectionsPerIP
|
|
142
142
|
) {
|
|
143
143
|
return {
|
|
144
144
|
allowed: false,
|
|
145
|
-
reason: `Maximum connections per IP (${this.settings.maxConnectionsPerIP}) exceeded`
|
|
145
|
+
reason: `Maximum connections per IP (${this.smartProxy.settings.maxConnectionsPerIP}) exceeded`
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// Check connection rate limit
|
|
150
150
|
if (
|
|
151
|
-
this.settings.connectionRateLimitPerMinute &&
|
|
151
|
+
this.smartProxy.settings.connectionRateLimitPerMinute &&
|
|
152
152
|
!this.checkConnectionRate(ip)
|
|
153
153
|
) {
|
|
154
154
|
return {
|
|
155
155
|
allowed: false,
|
|
156
|
-
reason: `Connection rate limit (${this.settings.connectionRateLimitPerMinute}/min) exceeded`
|
|
156
|
+
reason: `Connection rate limit (${this.smartProxy.settings.connectionRateLimitPerMinute}/min) exceeded`
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -29,7 +29,7 @@ import { AcmeStateManager } from './acme-state-manager.js';
|
|
|
29
29
|
|
|
30
30
|
// Import metrics collector
|
|
31
31
|
import { MetricsCollector } from './metrics-collector.js';
|
|
32
|
-
import type {
|
|
32
|
+
import type { IMetrics } from './models/metrics-types.js';
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* SmartProxy - Pure route-based API
|
|
@@ -52,24 +52,24 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
52
52
|
|
|
53
53
|
// Component managers
|
|
54
54
|
public connectionManager: ConnectionManager;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
public routeManager: RouteManager;
|
|
60
|
-
public routeConnectionHandler: RouteConnectionHandler;
|
|
61
|
-
|
|
55
|
+
public securityManager: SecurityManager;
|
|
56
|
+
public tlsManager: TlsManager;
|
|
57
|
+
public httpProxyBridge: HttpProxyBridge;
|
|
58
|
+
public timeoutManager: TimeoutManager;
|
|
59
|
+
public routeManager: RouteManager;
|
|
60
|
+
public routeConnectionHandler: RouteConnectionHandler;
|
|
61
|
+
public nftablesManager: NFTablesManager;
|
|
62
62
|
|
|
63
63
|
// Certificate manager for ACME and static certificates
|
|
64
|
-
|
|
64
|
+
public certManager: SmartCertManager | null = null;
|
|
65
65
|
|
|
66
66
|
// Global challenge route tracking
|
|
67
67
|
private globalChallengeRouteActive: boolean = false;
|
|
68
68
|
private routeUpdateLock: any = null; // Will be initialized as AsyncMutex
|
|
69
|
-
|
|
69
|
+
public acmeStateManager: AcmeStateManager;
|
|
70
70
|
|
|
71
71
|
// Metrics collector
|
|
72
|
-
|
|
72
|
+
public metricsCollector: MetricsCollector;
|
|
73
73
|
|
|
74
74
|
// Track port usage across route updates
|
|
75
75
|
private portUsageMap: Map<number, Set<string>> = new Map();
|
|
@@ -161,13 +161,9 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// Initialize component managers
|
|
164
|
-
this.timeoutManager = new TimeoutManager(this
|
|
165
|
-
this.securityManager = new SecurityManager(this
|
|
166
|
-
this.connectionManager = new ConnectionManager(
|
|
167
|
-
this.settings,
|
|
168
|
-
this.securityManager,
|
|
169
|
-
this.timeoutManager
|
|
170
|
-
);
|
|
164
|
+
this.timeoutManager = new TimeoutManager(this);
|
|
165
|
+
this.securityManager = new SecurityManager(this);
|
|
166
|
+
this.connectionManager = new ConnectionManager(this);
|
|
171
167
|
|
|
172
168
|
// Create the route manager with SharedRouteManager API
|
|
173
169
|
// Create a logger adapter to match ILogger interface
|
|
@@ -186,25 +182,17 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
186
182
|
|
|
187
183
|
|
|
188
184
|
// Create other required components
|
|
189
|
-
this.tlsManager = new TlsManager(this
|
|
190
|
-
this.httpProxyBridge = new HttpProxyBridge(this
|
|
185
|
+
this.tlsManager = new TlsManager(this);
|
|
186
|
+
this.httpProxyBridge = new HttpProxyBridge(this);
|
|
191
187
|
|
|
192
188
|
// Initialize connection handler with route support
|
|
193
|
-
this.routeConnectionHandler = new RouteConnectionHandler(
|
|
194
|
-
this.settings,
|
|
195
|
-
this.connectionManager,
|
|
196
|
-
this.securityManager,
|
|
197
|
-
this.tlsManager,
|
|
198
|
-
this.httpProxyBridge,
|
|
199
|
-
this.timeoutManager,
|
|
200
|
-
this.routeManager
|
|
201
|
-
);
|
|
189
|
+
this.routeConnectionHandler = new RouteConnectionHandler(this);
|
|
202
190
|
|
|
203
191
|
// Initialize port manager
|
|
204
|
-
this.portManager = new PortManager(this
|
|
192
|
+
this.portManager = new PortManager(this);
|
|
205
193
|
|
|
206
194
|
// Initialize NFTablesManager
|
|
207
|
-
this.nftablesManager = new NFTablesManager(this
|
|
195
|
+
this.nftablesManager = new NFTablesManager(this);
|
|
208
196
|
|
|
209
197
|
// Initialize route update mutex for synchronization
|
|
210
198
|
this.routeUpdateLock = new Mutex();
|
|
@@ -213,7 +201,10 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
213
201
|
this.acmeStateManager = new AcmeStateManager();
|
|
214
202
|
|
|
215
203
|
// Initialize metrics collector with reference to this SmartProxy instance
|
|
216
|
-
this.metricsCollector = new MetricsCollector(this
|
|
204
|
+
this.metricsCollector = new MetricsCollector(this, {
|
|
205
|
+
sampleIntervalMs: this.settings.metrics?.sampleIntervalMs,
|
|
206
|
+
retentionSeconds: this.settings.metrics?.retentionSeconds
|
|
207
|
+
});
|
|
217
208
|
}
|
|
218
209
|
|
|
219
210
|
/**
|
|
@@ -922,11 +913,11 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
922
913
|
}
|
|
923
914
|
|
|
924
915
|
/**
|
|
925
|
-
* Get proxy
|
|
916
|
+
* Get proxy metrics with clean API
|
|
926
917
|
*
|
|
927
|
-
* @returns
|
|
918
|
+
* @returns IMetrics interface with grouped metrics methods
|
|
928
919
|
*/
|
|
929
|
-
public
|
|
920
|
+
public getMetrics(): IMetrics {
|
|
930
921
|
return this.metricsCollector;
|
|
931
922
|
}
|
|
932
923
|
|
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
// Sum bytes in the 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
|
+
// 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
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
in: Math.round(totalBytesIn / actualWindowSeconds),
|
|
85
|
+
out: Math.round(totalBytesOut / actualWindowSeconds)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get throughput history for specified duration
|
|
91
|
+
*/
|
|
92
|
+
public getHistory(durationSeconds: number): IThroughputHistoryPoint[] {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const startTime = now - (durationSeconds * 1000);
|
|
95
|
+
|
|
96
|
+
// Filter samples within duration
|
|
97
|
+
const relevantSamples = this.samples.filter(s => s.timestamp > startTime);
|
|
98
|
+
|
|
99
|
+
// Convert to history points with per-second rates
|
|
100
|
+
const history: IThroughputHistoryPoint[] = [];
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < relevantSamples.length; i++) {
|
|
103
|
+
const sample = relevantSamples[i];
|
|
104
|
+
|
|
105
|
+
// For the first sample or samples after gaps, we can't calculate rate
|
|
106
|
+
if (i === 0 || sample.timestamp - relevantSamples[i - 1].timestamp > 2000) {
|
|
107
|
+
history.push({
|
|
108
|
+
timestamp: sample.timestamp,
|
|
109
|
+
in: sample.bytesIn,
|
|
110
|
+
out: sample.bytesOut
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
// Calculate rate based on time since previous sample
|
|
114
|
+
const prevSample = relevantSamples[i - 1];
|
|
115
|
+
const timeDelta = (sample.timestamp - prevSample.timestamp) / 1000;
|
|
116
|
+
|
|
117
|
+
history.push({
|
|
118
|
+
timestamp: sample.timestamp,
|
|
119
|
+
in: Math.round(sample.bytesIn / timeDelta),
|
|
120
|
+
out: Math.round(sample.bytesOut / timeDelta)
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return history;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Clear all samples
|
|
130
|
+
*/
|
|
131
|
+
public clear(): void {
|
|
132
|
+
this.samples = [];
|
|
133
|
+
this.accumulatedBytesIn = 0;
|
|
134
|
+
this.accumulatedBytesOut = 0;
|
|
135
|
+
this.lastSampleTime = 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get sample count for debugging
|
|
140
|
+
*/
|
|
141
|
+
public getSampleCount(): number {
|
|
142
|
+
return this.samples.length;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { IConnectionRecord
|
|
1
|
+
import type { IConnectionRecord } from './models/interfaces.js';
|
|
2
|
+
import type { SmartProxy } from './smart-proxy.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Manages timeouts and inactivity tracking for connections
|
|
5
6
|
*/
|
|
6
7
|
export class TimeoutManager {
|
|
7
|
-
constructor(private
|
|
8
|
+
constructor(private smartProxy: SmartProxy) {}
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Ensure timeout values don't exceed Node.js max safe integer
|
|
@@ -41,16 +42,16 @@ export class TimeoutManager {
|
|
|
41
42
|
* Calculate effective inactivity timeout based on connection type
|
|
42
43
|
*/
|
|
43
44
|
public getEffectiveInactivityTimeout(record: IConnectionRecord): number {
|
|
44
|
-
let effectiveTimeout = this.settings.inactivityTimeout || 14400000; // 4 hours default
|
|
45
|
+
let effectiveTimeout = this.smartProxy.settings.inactivityTimeout || 14400000; // 4 hours default
|
|
45
46
|
|
|
46
47
|
// For immortal keep-alive connections, use an extremely long timeout
|
|
47
|
-
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
48
|
+
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
48
49
|
return Number.MAX_SAFE_INTEGER;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
// For extended keep-alive connections, apply multiplier
|
|
52
|
-
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
53
|
-
const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
|
|
53
|
+
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'extended') {
|
|
54
|
+
const multiplier = this.smartProxy.settings.keepAliveInactivityMultiplier || 6;
|
|
54
55
|
effectiveTimeout = effectiveTimeout * multiplier;
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -63,23 +64,23 @@ export class TimeoutManager {
|
|
|
63
64
|
public getEffectiveMaxLifetime(record: IConnectionRecord): number {
|
|
64
65
|
// Use route-specific timeout if available from the routeConfig
|
|
65
66
|
const baseTimeout = record.routeConfig?.action.advanced?.timeout ||
|
|
66
|
-
this.settings.maxConnectionLifetime ||
|
|
67
|
+
this.smartProxy.settings.maxConnectionLifetime ||
|
|
67
68
|
86400000; // 24 hours default
|
|
68
69
|
|
|
69
70
|
// For immortal keep-alive connections, use an extremely long lifetime
|
|
70
|
-
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
71
|
+
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
71
72
|
return Number.MAX_SAFE_INTEGER;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
// For extended keep-alive connections, use the extended lifetime setting
|
|
75
|
-
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
76
|
+
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'extended') {
|
|
76
77
|
return this.ensureSafeTimeout(
|
|
77
|
-
this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000 // 7 days default
|
|
78
|
+
this.smartProxy.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000 // 7 days default
|
|
78
79
|
);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// Apply randomization if enabled
|
|
82
|
-
if (this.settings.enableRandomizedTimeouts) {
|
|
83
|
+
if (this.smartProxy.settings.enableRandomizedTimeouts) {
|
|
83
84
|
return this.randomizeTimeout(baseTimeout);
|
|
84
85
|
}
|
|
85
86
|
|
|
@@ -93,12 +94,17 @@ export class TimeoutManager {
|
|
|
93
94
|
public setupConnectionTimeout(
|
|
94
95
|
record: IConnectionRecord,
|
|
95
96
|
onTimeout: (record: IConnectionRecord, reason: string) => void
|
|
96
|
-
): NodeJS.Timeout {
|
|
97
|
+
): NodeJS.Timeout | null {
|
|
97
98
|
// Clear any existing timer
|
|
98
99
|
if (record.cleanupTimer) {
|
|
99
100
|
clearTimeout(record.cleanupTimer);
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
// Skip timeout for immortal keep-alive connections
|
|
104
|
+
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
102
108
|
// Calculate effective timeout
|
|
103
109
|
const effectiveLifetime = this.getEffectiveMaxLifetime(record);
|
|
104
110
|
|
|
@@ -127,7 +133,7 @@ export class TimeoutManager {
|
|
|
127
133
|
effectiveTimeout: number;
|
|
128
134
|
} {
|
|
129
135
|
// Skip for connections with inactivity check disabled
|
|
130
|
-
if (this.settings.disableInactivityCheck) {
|
|
136
|
+
if (this.smartProxy.settings.disableInactivityCheck) {
|
|
131
137
|
return {
|
|
132
138
|
isInactive: false,
|
|
133
139
|
shouldWarn: false,
|
|
@@ -137,7 +143,7 @@ export class TimeoutManager {
|
|
|
137
143
|
}
|
|
138
144
|
|
|
139
145
|
// Skip for immortal keep-alive connections
|
|
140
|
-
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
146
|
+
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
141
147
|
return {
|
|
142
148
|
isInactive: false,
|
|
143
149
|
shouldWarn: false,
|
|
@@ -171,7 +177,7 @@ export class TimeoutManager {
|
|
|
171
177
|
*/
|
|
172
178
|
public applySocketTimeouts(record: IConnectionRecord): void {
|
|
173
179
|
// Skip for immortal keep-alive connections
|
|
174
|
-
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
180
|
+
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
175
181
|
// Disable timeouts completely for immortal connections
|
|
176
182
|
record.incoming.setTimeout(0);
|
|
177
183
|
if (record.outgoing) {
|
|
@@ -181,7 +187,7 @@ export class TimeoutManager {
|
|
|
181
187
|
}
|
|
182
188
|
|
|
183
189
|
// Apply normal timeouts
|
|
184
|
-
const timeout = this.ensureSafeTimeout(this.settings.socketTimeout || 3600000); // 1 hour default
|
|
190
|
+
const timeout = this.ensureSafeTimeout(this.smartProxy.settings.socketTimeout || 3600000); // 1 hour default
|
|
185
191
|
record.incoming.setTimeout(timeout);
|
|
186
192
|
if (record.outgoing) {
|
|
187
193
|
record.outgoing.setTimeout(timeout);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
|
-
import type { ISmartProxyOptions } from './models/interfaces.js';
|
|
3
2
|
import { SniHandler } from '../../tls/sni/sni-handler.js';
|
|
3
|
+
import type { SmartProxy } from './smart-proxy.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Interface for connection information used for SNI extraction
|
|
@@ -16,7 +16,7 @@ interface IConnectionInfo {
|
|
|
16
16
|
* Manages TLS-related operations including SNI extraction and validation
|
|
17
17
|
*/
|
|
18
18
|
export class TlsManager {
|
|
19
|
-
constructor(private
|
|
19
|
+
constructor(private smartProxy: SmartProxy) {}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Check if a data chunk appears to be a TLS handshake
|
|
@@ -44,7 +44,7 @@ export class TlsManager {
|
|
|
44
44
|
return SniHandler.processTlsPacket(
|
|
45
45
|
chunk,
|
|
46
46
|
connInfo,
|
|
47
|
-
this.settings.enableTlsDebugLogging || false,
|
|
47
|
+
this.smartProxy.settings.enableTlsDebugLogging || false,
|
|
48
48
|
previousDomain
|
|
49
49
|
);
|
|
50
50
|
}
|
|
@@ -58,19 +58,19 @@ export class TlsManager {
|
|
|
58
58
|
hasSNI: boolean
|
|
59
59
|
): { shouldBlock: boolean; reason?: string } {
|
|
60
60
|
// Skip if session tickets are allowed
|
|
61
|
-
if (this.settings.allowSessionTicket !== false) {
|
|
61
|
+
if (this.smartProxy.settings.allowSessionTicket !== false) {
|
|
62
62
|
return { shouldBlock: false };
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Check for session resumption attempt
|
|
66
66
|
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
67
67
|
chunk,
|
|
68
|
-
this.settings.enableTlsDebugLogging || false
|
|
68
|
+
this.smartProxy.settings.enableTlsDebugLogging || false
|
|
69
69
|
);
|
|
70
70
|
|
|
71
71
|
// If this is a resumption attempt without SNI, block it
|
|
72
72
|
if (resumptionInfo.isResumption && !hasSNI && !resumptionInfo.hasSNI) {
|
|
73
|
-
if (this.settings.enableTlsDebugLogging) {
|
|
73
|
+
if (this.smartProxy.settings.enableTlsDebugLogging) {
|
|
74
74
|
console.log(
|
|
75
75
|
`[${connectionId}] Session resumption detected without SNI and allowSessionTicket=false. ` +
|
|
76
76
|
`Terminating connection to force new TLS handshake.`
|
|
@@ -104,7 +104,7 @@ export class TlsManager {
|
|
|
104
104
|
const newSNI = SniHandler.extractSNIWithResumptionSupport(
|
|
105
105
|
chunk,
|
|
106
106
|
connInfo,
|
|
107
|
-
this.settings.enableTlsDebugLogging || false
|
|
107
|
+
this.smartProxy.settings.enableTlsDebugLogging || false
|
|
108
108
|
);
|
|
109
109
|
|
|
110
110
|
// Skip if no SNI was found
|
|
@@ -112,14 +112,14 @@ export class TlsManager {
|
|
|
112
112
|
|
|
113
113
|
// Check for SNI mismatch
|
|
114
114
|
if (newSNI !== expectedDomain) {
|
|
115
|
-
if (this.settings.enableTlsDebugLogging) {
|
|
115
|
+
if (this.smartProxy.settings.enableTlsDebugLogging) {
|
|
116
116
|
console.log(
|
|
117
117
|
`[${connectionId}] Renegotiation with different SNI: ${expectedDomain} -> ${newSNI}. ` +
|
|
118
118
|
`Terminating connection - SNI domain switching is not allowed.`
|
|
119
119
|
);
|
|
120
120
|
}
|
|
121
121
|
return { hasMismatch: true, extractedSNI: newSNI };
|
|
122
|
-
} else if (this.settings.enableTlsDebugLogging) {
|
|
122
|
+
} else if (this.smartProxy.settings.enableTlsDebugLogging) {
|
|
123
123
|
console.log(
|
|
124
124
|
`[${connectionId}] Renegotiation detected with same SNI: ${newSNI}. Allowing.`
|
|
125
125
|
);
|
|
@@ -175,13 +175,13 @@ export class TlsManager {
|
|
|
175
175
|
// Check for session resumption
|
|
176
176
|
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
177
177
|
chunk,
|
|
178
|
-
this.settings.enableTlsDebugLogging || false
|
|
178
|
+
this.smartProxy.settings.enableTlsDebugLogging || false
|
|
179
179
|
);
|
|
180
180
|
|
|
181
181
|
// Extract SNI
|
|
182
182
|
const sni = SniHandler.extractSNI(
|
|
183
183
|
chunk,
|
|
184
|
-
this.settings.enableTlsDebugLogging || false
|
|
184
|
+
this.smartProxy.settings.enableTlsDebugLogging || false
|
|
185
185
|
);
|
|
186
186
|
|
|
187
187
|
// Update result
|