@push.rocks/smartproxy 21.1.7 → 22.6.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 +109 -0
- package/dist_rust/rustproxy +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/utils/shared-security-manager.d.ts +17 -0
- package/dist_ts/core/utils/shared-security-manager.js +66 -1
- package/dist_ts/index.d.ts +1 -5
- package/dist_ts/index.js +3 -9
- package/dist_ts/protocols/common/fragment-handler.js +5 -1
- package/dist_ts/proxies/http-proxy/default-certificates.d.ts +54 -0
- package/dist_ts/proxies/http-proxy/default-certificates.js +127 -0
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -1
- package/dist_ts/proxies/http-proxy/http-proxy.js +9 -14
- package/dist_ts/proxies/http-proxy/index.d.ts +5 -1
- package/dist_ts/proxies/http-proxy/index.js +6 -2
- package/dist_ts/proxies/http-proxy/security-manager.d.ts +4 -12
- package/dist_ts/proxies/http-proxy/security-manager.js +66 -99
- package/dist_ts/proxies/index.d.ts +1 -5
- package/dist_ts/proxies/index.js +2 -6
- package/dist_ts/proxies/nftables-proxy/index.d.ts +1 -0
- package/dist_ts/proxies/nftables-proxy/index.js +2 -1
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +4 -26
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +84 -236
- package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +9 -0
- package/dist_ts/proxies/nftables-proxy/utils/index.js +12 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +66 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +131 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +39 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +112 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +59 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +130 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +4 -3
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +13 -2
- package/dist_ts/proxies/smart-proxy/connection-manager.js +16 -6
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +35 -10
- package/dist_ts/proxies/smart-proxy/index.d.ts +5 -10
- package/dist_ts/proxies/smart-proxy/index.js +7 -13
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -3
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +72 -9
- package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
- package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
- package/dist_ts/proxies/smart-proxy/security-manager.d.ts +14 -12
- package/dist_ts/proxies/smart-proxy/security-manager.js +80 -74
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -622
- package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
- package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
- package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +2 -9
- package/dist_ts/proxies/smart-proxy/tls-manager.js +3 -26
- package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.d.ts +49 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.js +108 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.d.ts +57 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.js +89 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.js +32 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.d.ts +68 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.js +117 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.js +27 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.d.ts +63 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.js +105 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.d.ts +83 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.js +126 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.d.ts +47 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.js +66 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +70 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +287 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.d.ts +46 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.js +67 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +4 -457
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +6 -950
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +2 -2
- package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +67 -1
- package/dist_ts/proxies/smart-proxy/utils/route-validator.js +251 -3
- package/dist_ts/routing/index.d.ts +1 -1
- package/dist_ts/routing/index.js +3 -3
- package/dist_ts/routing/models/http-types.d.ts +119 -4
- package/dist_ts/routing/models/http-types.js +93 -5
- package/npmextra.json +12 -6
- package/package.json +34 -24
- package/readme.hints.md +184 -1
- package/readme.md +580 -266
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/utils/shared-security-manager.ts +98 -13
- package/ts/index.ts +4 -12
- package/ts/protocols/common/fragment-handler.ts +4 -0
- package/ts/proxies/index.ts +1 -9
- package/ts/proxies/nftables-proxy/index.ts +1 -0
- package/ts/proxies/nftables-proxy/nftables-proxy.ts +116 -290
- package/ts/proxies/nftables-proxy/utils/index.ts +38 -0
- package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +162 -0
- package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +125 -0
- package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +156 -0
- package/ts/proxies/smart-proxy/index.ts +6 -13
- package/ts/proxies/smart-proxy/models/interfaces.ts +6 -5
- package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
- package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
- package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
- package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
- package/ts/proxies/smart-proxy/smart-proxy.ts +282 -800
- package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
- package/ts/proxies/smart-proxy/utils/index.ts +3 -5
- package/ts/proxies/smart-proxy/utils/route-helpers/api-helpers.ts +144 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.ts +124 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/http-helpers.ts +40 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/https-helpers.ts +163 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/index.ts +62 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.ts +154 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.ts +202 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/security-helpers.ts +96 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +337 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.ts +98 -0
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +5 -1302
- package/ts/proxies/smart-proxy/utils/route-utils.ts +1 -1
- package/ts/proxies/smart-proxy/utils/route-validator.ts +274 -4
- package/ts/routing/index.ts +2 -2
- package/ts/routing/models/http-types.ts +147 -4
- package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
- package/ts/proxies/http-proxy/connection-pool.ts +0 -228
- package/ts/proxies/http-proxy/context-creator.ts +0 -145
- package/ts/proxies/http-proxy/function-cache.ts +0 -279
- package/ts/proxies/http-proxy/handlers/index.ts +0 -5
- package/ts/proxies/http-proxy/http-proxy.ts +0 -675
- package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
- package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
- package/ts/proxies/http-proxy/index.ts +0 -13
- package/ts/proxies/http-proxy/models/http-types.ts +0 -148
- package/ts/proxies/http-proxy/models/index.ts +0 -5
- package/ts/proxies/http-proxy/models/types.ts +0 -125
- package/ts/proxies/http-proxy/request-handler.ts +0 -878
- package/ts/proxies/http-proxy/security-manager.ts +0 -433
- package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
- package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
- package/ts/proxies/smart-proxy/cert-store.ts +0 -92
- package/ts/proxies/smart-proxy/certificate-manager.ts +0 -894
- package/ts/proxies/smart-proxy/connection-manager.ts +0 -796
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -187
- package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
- package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
- package/ts/proxies/smart-proxy/port-manager.ts +0 -358
- package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1640
- package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
- package/ts/proxies/smart-proxy/security-manager.ts +0 -257
- package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
- package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
- package/ts/proxies/smart-proxy/tls-manager.ts +0 -207
- package/ts/proxies/smart-proxy/utils/route-validators.ts +0 -283
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import type { IConnectionRecord } from './models/interfaces.js';
|
|
2
|
-
import type { SmartProxy } from './smart-proxy.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Manages timeouts and inactivity tracking for connections
|
|
6
|
-
*/
|
|
7
|
-
export class TimeoutManager {
|
|
8
|
-
constructor(private smartProxy: SmartProxy) {}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Ensure timeout values don't exceed Node.js max safe integer
|
|
12
|
-
*/
|
|
13
|
-
public ensureSafeTimeout(timeout: number): number {
|
|
14
|
-
const MAX_SAFE_TIMEOUT = 2147483647; // Maximum safe value (2^31 - 1)
|
|
15
|
-
return Math.min(Math.floor(timeout), MAX_SAFE_TIMEOUT);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Generate a slightly randomized timeout to prevent thundering herd
|
|
20
|
-
*/
|
|
21
|
-
public randomizeTimeout(baseTimeout: number, variationPercent: number = 5): number {
|
|
22
|
-
const safeBaseTimeout = this.ensureSafeTimeout(baseTimeout);
|
|
23
|
-
const variation = safeBaseTimeout * (variationPercent / 100);
|
|
24
|
-
return this.ensureSafeTimeout(
|
|
25
|
-
safeBaseTimeout + Math.floor(Math.random() * variation * 2) - variation
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Update connection activity timestamp
|
|
31
|
-
*/
|
|
32
|
-
public updateActivity(record: IConnectionRecord): void {
|
|
33
|
-
record.lastActivity = Date.now();
|
|
34
|
-
|
|
35
|
-
// Clear any inactivity warning
|
|
36
|
-
if (record.inactivityWarningIssued) {
|
|
37
|
-
record.inactivityWarningIssued = false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Calculate effective inactivity timeout based on connection type
|
|
43
|
-
*/
|
|
44
|
-
public getEffectiveInactivityTimeout(record: IConnectionRecord): number {
|
|
45
|
-
let effectiveTimeout = this.smartProxy.settings.inactivityTimeout || 14400000; // 4 hours default
|
|
46
|
-
|
|
47
|
-
// For immortal keep-alive connections, use an extremely long timeout
|
|
48
|
-
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
49
|
-
return Number.MAX_SAFE_INTEGER;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// For extended keep-alive connections, apply multiplier
|
|
53
|
-
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'extended') {
|
|
54
|
-
const multiplier = this.smartProxy.settings.keepAliveInactivityMultiplier || 6;
|
|
55
|
-
effectiveTimeout = effectiveTimeout * multiplier;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return this.ensureSafeTimeout(effectiveTimeout);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Calculate effective max lifetime based on connection type
|
|
63
|
-
*/
|
|
64
|
-
public getEffectiveMaxLifetime(record: IConnectionRecord): number {
|
|
65
|
-
// Use route-specific timeout if available from the routeConfig
|
|
66
|
-
const baseTimeout = record.routeConfig?.action.advanced?.timeout ||
|
|
67
|
-
this.smartProxy.settings.maxConnectionLifetime ||
|
|
68
|
-
86400000; // 24 hours default
|
|
69
|
-
|
|
70
|
-
// For immortal keep-alive connections, use an extremely long lifetime
|
|
71
|
-
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
72
|
-
return Number.MAX_SAFE_INTEGER;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// For extended keep-alive connections, use the extended lifetime setting
|
|
76
|
-
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'extended') {
|
|
77
|
-
return this.ensureSafeTimeout(
|
|
78
|
-
this.smartProxy.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000 // 7 days default
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Apply randomization if enabled
|
|
83
|
-
if (this.smartProxy.settings.enableRandomizedTimeouts) {
|
|
84
|
-
return this.randomizeTimeout(baseTimeout);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return this.ensureSafeTimeout(baseTimeout);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Setup connection timeout
|
|
92
|
-
* @returns The cleanup timer
|
|
93
|
-
*/
|
|
94
|
-
public setupConnectionTimeout(
|
|
95
|
-
record: IConnectionRecord,
|
|
96
|
-
onTimeout: (record: IConnectionRecord, reason: string) => void
|
|
97
|
-
): NodeJS.Timeout | null {
|
|
98
|
-
// Clear any existing timer
|
|
99
|
-
if (record.cleanupTimer) {
|
|
100
|
-
clearTimeout(record.cleanupTimer);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Skip timeout for immortal keep-alive connections
|
|
104
|
-
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Calculate effective timeout
|
|
109
|
-
const effectiveLifetime = this.getEffectiveMaxLifetime(record);
|
|
110
|
-
|
|
111
|
-
// Set up the timeout
|
|
112
|
-
const timer = setTimeout(() => {
|
|
113
|
-
// Call the provided callback
|
|
114
|
-
onTimeout(record, 'connection_timeout');
|
|
115
|
-
}, effectiveLifetime);
|
|
116
|
-
|
|
117
|
-
// Make sure timeout doesn't keep the process alive
|
|
118
|
-
if (timer.unref) {
|
|
119
|
-
timer.unref();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return timer;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Check for inactivity on a connection
|
|
127
|
-
* @returns Object with check results
|
|
128
|
-
*/
|
|
129
|
-
public checkInactivity(record: IConnectionRecord): {
|
|
130
|
-
isInactive: boolean;
|
|
131
|
-
shouldWarn: boolean;
|
|
132
|
-
inactivityTime: number;
|
|
133
|
-
effectiveTimeout: number;
|
|
134
|
-
} {
|
|
135
|
-
// Skip for connections with inactivity check disabled
|
|
136
|
-
if (this.smartProxy.settings.disableInactivityCheck) {
|
|
137
|
-
return {
|
|
138
|
-
isInactive: false,
|
|
139
|
-
shouldWarn: false,
|
|
140
|
-
inactivityTime: 0,
|
|
141
|
-
effectiveTimeout: 0
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Skip for immortal keep-alive connections
|
|
146
|
-
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
147
|
-
return {
|
|
148
|
-
isInactive: false,
|
|
149
|
-
shouldWarn: false,
|
|
150
|
-
inactivityTime: 0,
|
|
151
|
-
effectiveTimeout: 0
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const now = Date.now();
|
|
156
|
-
const inactivityTime = now - record.lastActivity;
|
|
157
|
-
const effectiveTimeout = this.getEffectiveInactivityTimeout(record);
|
|
158
|
-
|
|
159
|
-
// Check if inactive
|
|
160
|
-
const isInactive = inactivityTime > effectiveTimeout;
|
|
161
|
-
|
|
162
|
-
// For keep-alive connections, we should warn first
|
|
163
|
-
const shouldWarn = record.hasKeepAlive &&
|
|
164
|
-
isInactive &&
|
|
165
|
-
!record.inactivityWarningIssued;
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
isInactive,
|
|
169
|
-
shouldWarn,
|
|
170
|
-
inactivityTime,
|
|
171
|
-
effectiveTimeout
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Apply socket timeout settings
|
|
177
|
-
*/
|
|
178
|
-
public applySocketTimeouts(record: IConnectionRecord): void {
|
|
179
|
-
// Skip for immortal keep-alive connections
|
|
180
|
-
if (record.hasKeepAlive && this.smartProxy.settings.keepAliveTreatment === 'immortal') {
|
|
181
|
-
// Disable timeouts completely for immortal connections
|
|
182
|
-
record.incoming.setTimeout(0);
|
|
183
|
-
if (record.outgoing) {
|
|
184
|
-
record.outgoing.setTimeout(0);
|
|
185
|
-
}
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Apply normal timeouts
|
|
190
|
-
const timeout = this.ensureSafeTimeout(this.smartProxy.settings.socketTimeout || 3600000); // 1 hour default
|
|
191
|
-
record.incoming.setTimeout(timeout);
|
|
192
|
-
if (record.outgoing) {
|
|
193
|
-
record.outgoing.setTimeout(timeout);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../../plugins.js';
|
|
2
|
-
import { SniHandler } from '../../tls/sni/sni-handler.js';
|
|
3
|
-
import { ProtocolDetector, TlsDetector } from '../../detection/index.js';
|
|
4
|
-
import type { SmartProxy } from './smart-proxy.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Interface for connection information used for SNI extraction
|
|
8
|
-
*/
|
|
9
|
-
interface IConnectionInfo {
|
|
10
|
-
sourceIp: string;
|
|
11
|
-
sourcePort: number;
|
|
12
|
-
destIp: string;
|
|
13
|
-
destPort: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Manages TLS-related operations including SNI extraction and validation
|
|
18
|
-
*/
|
|
19
|
-
export class TlsManager {
|
|
20
|
-
constructor(private smartProxy: SmartProxy) {}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Check if a data chunk appears to be a TLS handshake
|
|
24
|
-
*/
|
|
25
|
-
public isTlsHandshake(chunk: Buffer): boolean {
|
|
26
|
-
return SniHandler.isTlsHandshake(chunk);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Check if a data chunk appears to be a TLS ClientHello
|
|
31
|
-
*/
|
|
32
|
-
public isClientHello(chunk: Buffer): boolean {
|
|
33
|
-
return SniHandler.isClientHello(chunk);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Extract Server Name Indication (SNI) from TLS handshake
|
|
38
|
-
*/
|
|
39
|
-
public extractSNI(
|
|
40
|
-
chunk: Buffer,
|
|
41
|
-
connInfo: IConnectionInfo,
|
|
42
|
-
previousDomain?: string
|
|
43
|
-
): string | undefined {
|
|
44
|
-
// Use the SniHandler to process the TLS packet
|
|
45
|
-
return SniHandler.processTlsPacket(
|
|
46
|
-
chunk,
|
|
47
|
-
connInfo,
|
|
48
|
-
this.smartProxy.settings.enableTlsDebugLogging || false,
|
|
49
|
-
previousDomain
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Handle session resumption attempts
|
|
55
|
-
*/
|
|
56
|
-
public handleSessionResumption(
|
|
57
|
-
chunk: Buffer,
|
|
58
|
-
connectionId: string,
|
|
59
|
-
hasSNI: boolean
|
|
60
|
-
): { shouldBlock: boolean; reason?: string } {
|
|
61
|
-
// Skip if session tickets are allowed
|
|
62
|
-
if (this.smartProxy.settings.allowSessionTicket !== false) {
|
|
63
|
-
return { shouldBlock: false };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Check for session resumption attempt
|
|
67
|
-
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
68
|
-
chunk,
|
|
69
|
-
this.smartProxy.settings.enableTlsDebugLogging || false
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// If this is a resumption attempt without SNI, block it
|
|
73
|
-
if (resumptionInfo.isResumption && !hasSNI && !resumptionInfo.hasSNI) {
|
|
74
|
-
if (this.smartProxy.settings.enableTlsDebugLogging) {
|
|
75
|
-
console.log(
|
|
76
|
-
`[${connectionId}] Session resumption detected without SNI and allowSessionTicket=false. ` +
|
|
77
|
-
`Terminating connection to force new TLS handshake.`
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
shouldBlock: true,
|
|
82
|
-
reason: 'session_ticket_blocked'
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return { shouldBlock: false };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Check for SNI mismatch during renegotiation
|
|
91
|
-
*/
|
|
92
|
-
public checkRenegotiationSNI(
|
|
93
|
-
chunk: Buffer,
|
|
94
|
-
connInfo: IConnectionInfo,
|
|
95
|
-
expectedDomain: string,
|
|
96
|
-
connectionId: string
|
|
97
|
-
): { hasMismatch: boolean; extractedSNI?: string } {
|
|
98
|
-
// Only process if this looks like a TLS ClientHello
|
|
99
|
-
if (!this.isClientHello(chunk)) {
|
|
100
|
-
return { hasMismatch: false };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
// Extract SNI with renegotiation support
|
|
105
|
-
const newSNI = SniHandler.extractSNIWithResumptionSupport(
|
|
106
|
-
chunk,
|
|
107
|
-
connInfo,
|
|
108
|
-
this.smartProxy.settings.enableTlsDebugLogging || false
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
// Skip if no SNI was found
|
|
112
|
-
if (!newSNI) return { hasMismatch: false };
|
|
113
|
-
|
|
114
|
-
// Check for SNI mismatch
|
|
115
|
-
if (newSNI !== expectedDomain) {
|
|
116
|
-
if (this.smartProxy.settings.enableTlsDebugLogging) {
|
|
117
|
-
console.log(
|
|
118
|
-
`[${connectionId}] Renegotiation with different SNI: ${expectedDomain} -> ${newSNI}. ` +
|
|
119
|
-
`Terminating connection - SNI domain switching is not allowed.`
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
return { hasMismatch: true, extractedSNI: newSNI };
|
|
123
|
-
} else if (this.smartProxy.settings.enableTlsDebugLogging) {
|
|
124
|
-
console.log(
|
|
125
|
-
`[${connectionId}] Renegotiation detected with same SNI: ${newSNI}. Allowing.`
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
} catch (err) {
|
|
129
|
-
console.log(
|
|
130
|
-
`[${connectionId}] Error processing ClientHello: ${err}. Allowing connection to continue.`
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return { hasMismatch: false };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Create a renegotiation handler function for a connection
|
|
139
|
-
*/
|
|
140
|
-
public createRenegotiationHandler(
|
|
141
|
-
connectionId: string,
|
|
142
|
-
lockedDomain: string,
|
|
143
|
-
connInfo: IConnectionInfo,
|
|
144
|
-
onMismatch: (connectionId: string, reason: string) => void
|
|
145
|
-
): (chunk: Buffer) => void {
|
|
146
|
-
return (chunk: Buffer) => {
|
|
147
|
-
const result = this.checkRenegotiationSNI(chunk, connInfo, lockedDomain, connectionId);
|
|
148
|
-
if (result.hasMismatch) {
|
|
149
|
-
onMismatch(connectionId, 'sni_mismatch');
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Analyze TLS connection for browser fingerprinting
|
|
156
|
-
* This helps identify browser vs non-browser connections
|
|
157
|
-
*/
|
|
158
|
-
public analyzeClientHello(chunk: Buffer): {
|
|
159
|
-
isBrowserConnection: boolean;
|
|
160
|
-
isRenewal: boolean;
|
|
161
|
-
hasSNI: boolean;
|
|
162
|
-
} {
|
|
163
|
-
// Default result
|
|
164
|
-
const result = {
|
|
165
|
-
isBrowserConnection: false,
|
|
166
|
-
isRenewal: false,
|
|
167
|
-
hasSNI: false
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
// Check if it's a ClientHello
|
|
172
|
-
if (!this.isClientHello(chunk)) {
|
|
173
|
-
return result;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Check for session resumption
|
|
177
|
-
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
178
|
-
chunk,
|
|
179
|
-
this.smartProxy.settings.enableTlsDebugLogging || false
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// Extract SNI
|
|
183
|
-
const sni = SniHandler.extractSNI(
|
|
184
|
-
chunk,
|
|
185
|
-
this.smartProxy.settings.enableTlsDebugLogging || false
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
// Update result
|
|
189
|
-
result.isRenewal = resumptionInfo.isResumption;
|
|
190
|
-
result.hasSNI = !!sni;
|
|
191
|
-
|
|
192
|
-
// Browsers typically:
|
|
193
|
-
// 1. Send SNI extension
|
|
194
|
-
// 2. Have a variety of extensions (ALPN, etc.)
|
|
195
|
-
// 3. Use standard cipher suites
|
|
196
|
-
// ...more complex heuristics could be implemented here
|
|
197
|
-
|
|
198
|
-
// Simple heuristic: presence of SNI suggests browser
|
|
199
|
-
result.isBrowserConnection = !!sni;
|
|
200
|
-
|
|
201
|
-
return result;
|
|
202
|
-
} catch (err) {
|
|
203
|
-
console.log(`Error analyzing ClientHello: ${err}`);
|
|
204
|
-
return result;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Route Validators
|
|
3
|
-
*
|
|
4
|
-
* This file provides utility functions for validating route configurations.
|
|
5
|
-
* These validators help ensure that route configurations are valid and correctly structured.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { IRouteConfig, IRouteMatch, IRouteAction, TPortRange } from '../models/route-types.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Validates a port range or port number
|
|
12
|
-
* @param port Port number, port range, or port function
|
|
13
|
-
* @returns True if valid, false otherwise
|
|
14
|
-
*/
|
|
15
|
-
export function isValidPort(port: any): boolean {
|
|
16
|
-
if (typeof port === 'number') {
|
|
17
|
-
return port > 0 && port < 65536; // Valid port range is 1-65535
|
|
18
|
-
} else if (Array.isArray(port)) {
|
|
19
|
-
return port.every(p =>
|
|
20
|
-
(typeof p === 'number' && p > 0 && p < 65536) ||
|
|
21
|
-
(typeof p === 'object' && 'from' in p && 'to' in p &&
|
|
22
|
-
p.from > 0 && p.from < 65536 && p.to > 0 && p.to < 65536)
|
|
23
|
-
);
|
|
24
|
-
} else if (typeof port === 'function') {
|
|
25
|
-
// For function-based ports, we can't validate the result at config time
|
|
26
|
-
// so we just check that it's a function
|
|
27
|
-
return true;
|
|
28
|
-
} else if (typeof port === 'object' && 'from' in port && 'to' in port) {
|
|
29
|
-
return port.from > 0 && port.from < 65536 && port.to > 0 && port.to < 65536;
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Validates a domain string
|
|
36
|
-
* @param domain Domain string to validate
|
|
37
|
-
* @returns True if valid, false otherwise
|
|
38
|
-
*/
|
|
39
|
-
export function isValidDomain(domain: string): boolean {
|
|
40
|
-
// Basic domain validation regex - allows wildcards (*.example.com)
|
|
41
|
-
const domainRegex = /^(\*\.)?([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
42
|
-
return domainRegex.test(domain);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Validates a route match configuration
|
|
47
|
-
* @param match Route match configuration to validate
|
|
48
|
-
* @returns { valid: boolean, errors: string[] } Validation result
|
|
49
|
-
*/
|
|
50
|
-
export function validateRouteMatch(match: IRouteMatch): { valid: boolean; errors: string[] } {
|
|
51
|
-
const errors: string[] = [];
|
|
52
|
-
|
|
53
|
-
// Validate ports
|
|
54
|
-
if (match.ports !== undefined) {
|
|
55
|
-
if (!isValidPort(match.ports)) {
|
|
56
|
-
errors.push('Invalid port number or port range in match.ports');
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Validate domains
|
|
61
|
-
if (match.domains !== undefined) {
|
|
62
|
-
if (typeof match.domains === 'string') {
|
|
63
|
-
if (!isValidDomain(match.domains)) {
|
|
64
|
-
errors.push(`Invalid domain format: ${match.domains}`);
|
|
65
|
-
}
|
|
66
|
-
} else if (Array.isArray(match.domains)) {
|
|
67
|
-
for (const domain of match.domains) {
|
|
68
|
-
if (!isValidDomain(domain)) {
|
|
69
|
-
errors.push(`Invalid domain format: ${domain}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
errors.push('Domains must be a string or an array of strings');
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Validate path
|
|
78
|
-
if (match.path !== undefined) {
|
|
79
|
-
if (typeof match.path !== 'string' || !match.path.startsWith('/')) {
|
|
80
|
-
errors.push('Path must be a string starting with /');
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
valid: errors.length === 0,
|
|
86
|
-
errors
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Validates a route action configuration
|
|
92
|
-
* @param action Route action configuration to validate
|
|
93
|
-
* @returns { valid: boolean, errors: string[] } Validation result
|
|
94
|
-
*/
|
|
95
|
-
export function validateRouteAction(action: IRouteAction): { valid: boolean; errors: string[] } {
|
|
96
|
-
const errors: string[] = [];
|
|
97
|
-
|
|
98
|
-
// Validate action type
|
|
99
|
-
if (!action.type) {
|
|
100
|
-
errors.push('Action type is required');
|
|
101
|
-
} else if (!['forward', 'socket-handler'].includes(action.type)) {
|
|
102
|
-
errors.push(`Invalid action type: ${action.type}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Validate targets for 'forward' action
|
|
106
|
-
if (action.type === 'forward') {
|
|
107
|
-
if (!action.targets || !Array.isArray(action.targets) || action.targets.length === 0) {
|
|
108
|
-
errors.push('Targets array is required for forward action');
|
|
109
|
-
} else {
|
|
110
|
-
// Validate each target
|
|
111
|
-
action.targets.forEach((target, index) => {
|
|
112
|
-
// Validate target host
|
|
113
|
-
if (!target.host) {
|
|
114
|
-
errors.push(`Target[${index}] host is required`);
|
|
115
|
-
} else if (typeof target.host !== 'string' &&
|
|
116
|
-
!Array.isArray(target.host) &&
|
|
117
|
-
typeof target.host !== 'function') {
|
|
118
|
-
errors.push(`Target[${index}] host must be a string, array of strings, or function`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Validate target port
|
|
122
|
-
if (target.port === undefined) {
|
|
123
|
-
errors.push(`Target[${index}] port is required`);
|
|
124
|
-
} else if (typeof target.port !== 'number' &&
|
|
125
|
-
typeof target.port !== 'function' &&
|
|
126
|
-
target.port !== 'preserve') {
|
|
127
|
-
errors.push(`Target[${index}] port must be a number, 'preserve', or a function`);
|
|
128
|
-
} else if (typeof target.port === 'number' && !isValidPort(target.port)) {
|
|
129
|
-
errors.push(`Target[${index}] port must be between 1 and 65535`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Validate match criteria if present
|
|
133
|
-
if (target.match) {
|
|
134
|
-
if (target.match.ports && !Array.isArray(target.match.ports)) {
|
|
135
|
-
errors.push(`Target[${index}] match.ports must be an array`);
|
|
136
|
-
}
|
|
137
|
-
if (target.match.method && !Array.isArray(target.match.method)) {
|
|
138
|
-
errors.push(`Target[${index}] match.method must be an array`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Validate TLS options for forward actions
|
|
145
|
-
if (action.tls) {
|
|
146
|
-
if (!['passthrough', 'terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) {
|
|
147
|
-
errors.push(`Invalid TLS mode: ${action.tls.mode}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// For termination modes, validate certificate
|
|
151
|
-
if (['terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) {
|
|
152
|
-
if (action.tls.certificate !== 'auto' &&
|
|
153
|
-
(!action.tls.certificate || !action.tls.certificate.key || !action.tls.certificate.cert)) {
|
|
154
|
-
errors.push('Certificate must be "auto" or an object with key and cert properties');
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Validate socket handler for 'socket-handler' action
|
|
161
|
-
if (action.type === 'socket-handler') {
|
|
162
|
-
if (!action.socketHandler) {
|
|
163
|
-
errors.push('Socket handler function is required for socket-handler action');
|
|
164
|
-
} else if (typeof action.socketHandler !== 'function') {
|
|
165
|
-
errors.push('Socket handler must be a function');
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
valid: errors.length === 0,
|
|
171
|
-
errors
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Validates a complete route configuration
|
|
177
|
-
* @param route Route configuration to validate
|
|
178
|
-
* @returns { valid: boolean, errors: string[] } Validation result
|
|
179
|
-
*/
|
|
180
|
-
export function validateRouteConfig(route: IRouteConfig): { valid: boolean; errors: string[] } {
|
|
181
|
-
const errors: string[] = [];
|
|
182
|
-
|
|
183
|
-
// Check for required properties
|
|
184
|
-
if (!route.match) {
|
|
185
|
-
errors.push('Route match configuration is required');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (!route.action) {
|
|
189
|
-
errors.push('Route action configuration is required');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Validate match configuration
|
|
193
|
-
if (route.match) {
|
|
194
|
-
const matchValidation = validateRouteMatch(route.match);
|
|
195
|
-
if (!matchValidation.valid) {
|
|
196
|
-
errors.push(...matchValidation.errors.map(err => `Match: ${err}`));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Validate action configuration
|
|
201
|
-
if (route.action) {
|
|
202
|
-
const actionValidation = validateRouteAction(route.action);
|
|
203
|
-
if (!actionValidation.valid) {
|
|
204
|
-
errors.push(...actionValidation.errors.map(err => `Action: ${err}`));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Ensure the route has a unique identifier
|
|
209
|
-
if (!route.id && !route.name) {
|
|
210
|
-
errors.push('Route should have either an id or a name for identification');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
valid: errors.length === 0,
|
|
215
|
-
errors
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Validate an array of route configurations
|
|
221
|
-
* @param routes Array of route configurations to validate
|
|
222
|
-
* @returns { valid: boolean, errors: { index: number, errors: string[] }[] } Validation result
|
|
223
|
-
*/
|
|
224
|
-
export function validateRoutes(routes: IRouteConfig[]): {
|
|
225
|
-
valid: boolean;
|
|
226
|
-
errors: { index: number; errors: string[] }[]
|
|
227
|
-
} {
|
|
228
|
-
const results: { index: number; errors: string[] }[] = [];
|
|
229
|
-
|
|
230
|
-
routes.forEach((route, index) => {
|
|
231
|
-
const validation = validateRouteConfig(route);
|
|
232
|
-
if (!validation.valid) {
|
|
233
|
-
results.push({
|
|
234
|
-
index,
|
|
235
|
-
errors: validation.errors
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
valid: results.length === 0,
|
|
242
|
-
errors: results
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Check if a route configuration has the required properties for a specific action type
|
|
248
|
-
* @param route Route configuration to check
|
|
249
|
-
* @param actionType Expected action type
|
|
250
|
-
* @returns True if the route has the necessary properties, false otherwise
|
|
251
|
-
*/
|
|
252
|
-
export function hasRequiredPropertiesForAction(route: IRouteConfig, actionType: string): boolean {
|
|
253
|
-
if (!route.action || route.action.type !== actionType) {
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
switch (actionType) {
|
|
258
|
-
case 'forward':
|
|
259
|
-
return !!route.action.targets &&
|
|
260
|
-
Array.isArray(route.action.targets) &&
|
|
261
|
-
route.action.targets.length > 0 &&
|
|
262
|
-
route.action.targets.every(t => t.host && t.port !== undefined);
|
|
263
|
-
case 'socket-handler':
|
|
264
|
-
return !!route.action.socketHandler && typeof route.action.socketHandler === 'function';
|
|
265
|
-
default:
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Throws an error if the route config is invalid, returns the config if valid
|
|
272
|
-
* Useful for immediate validation when creating routes
|
|
273
|
-
* @param route Route configuration to validate
|
|
274
|
-
* @returns The validated route configuration
|
|
275
|
-
* @throws Error if the route configuration is invalid
|
|
276
|
-
*/
|
|
277
|
-
export function assertValidRoute(route: IRouteConfig): IRouteConfig {
|
|
278
|
-
const validation = validateRouteConfig(route);
|
|
279
|
-
if (!validation.valid) {
|
|
280
|
-
throw new Error(`Invalid route configuration: ${validation.errors.join(', ')}`);
|
|
281
|
-
}
|
|
282
|
-
return route;
|
|
283
|
-
}
|