@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
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { logger } from '../../core/utils/logger.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Locates the RustProxy binary using a priority-ordered search strategy:
|
|
6
|
+
* 1. SMARTPROXY_RUST_BINARY environment variable
|
|
7
|
+
* 2. Platform-specific optional npm package
|
|
8
|
+
* 3. Local development build at ./rust/target/release/rustproxy
|
|
9
|
+
* 4. System PATH
|
|
10
|
+
*/
|
|
11
|
+
export class RustBinaryLocator {
|
|
12
|
+
private cachedPath: string | null = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Find the RustProxy binary path.
|
|
16
|
+
* Returns null if no binary is available.
|
|
17
|
+
*/
|
|
18
|
+
public async findBinary(): Promise<string | null> {
|
|
19
|
+
if (this.cachedPath !== null) {
|
|
20
|
+
return this.cachedPath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const path = await this.searchBinary();
|
|
24
|
+
this.cachedPath = path;
|
|
25
|
+
return path;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Clear the cached binary path (e.g., after a failed launch).
|
|
30
|
+
*/
|
|
31
|
+
public clearCache(): void {
|
|
32
|
+
this.cachedPath = null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private async searchBinary(): Promise<string | null> {
|
|
36
|
+
// 1. Environment variable override
|
|
37
|
+
const envPath = process.env.SMARTPROXY_RUST_BINARY;
|
|
38
|
+
if (envPath) {
|
|
39
|
+
if (await this.isExecutable(envPath)) {
|
|
40
|
+
logger.log('info', `RustProxy binary found via SMARTPROXY_RUST_BINARY: ${envPath}`, { component: 'rust-locator' });
|
|
41
|
+
return envPath;
|
|
42
|
+
}
|
|
43
|
+
logger.log('warn', `SMARTPROXY_RUST_BINARY set but not executable: ${envPath}`, { component: 'rust-locator' });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Platform-specific optional npm package
|
|
47
|
+
const platformBinary = await this.findPlatformPackageBinary();
|
|
48
|
+
if (platformBinary) {
|
|
49
|
+
logger.log('info', `RustProxy binary found in platform package: ${platformBinary}`, { component: 'rust-locator' });
|
|
50
|
+
return platformBinary;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Local development build
|
|
54
|
+
const localPaths = [
|
|
55
|
+
plugins.path.resolve(process.cwd(), 'rust/target/release/rustproxy'),
|
|
56
|
+
plugins.path.resolve(process.cwd(), 'rust/target/debug/rustproxy'),
|
|
57
|
+
];
|
|
58
|
+
for (const localPath of localPaths) {
|
|
59
|
+
if (await this.isExecutable(localPath)) {
|
|
60
|
+
logger.log('info', `RustProxy binary found at local path: ${localPath}`, { component: 'rust-locator' });
|
|
61
|
+
return localPath;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 4. System PATH
|
|
66
|
+
const systemPath = await this.findInPath('rustproxy');
|
|
67
|
+
if (systemPath) {
|
|
68
|
+
logger.log('info', `RustProxy binary found in system PATH: ${systemPath}`, { component: 'rust-locator' });
|
|
69
|
+
return systemPath;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logger.log('error', 'No RustProxy binary found. Set SMARTPROXY_RUST_BINARY, install the platform package, or build with: cd rust && cargo build --release', { component: 'rust-locator' });
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async findPlatformPackageBinary(): Promise<string | null> {
|
|
77
|
+
const platform = process.platform;
|
|
78
|
+
const arch = process.arch;
|
|
79
|
+
const packageName = `@push.rocks/smartproxy-${platform}-${arch}`;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Try to resolve the platform-specific package
|
|
83
|
+
const packagePath = require.resolve(`${packageName}/rustproxy`);
|
|
84
|
+
if (await this.isExecutable(packagePath)) {
|
|
85
|
+
return packagePath;
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// Package not installed - expected for development
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private async isExecutable(filePath: string): Promise<boolean> {
|
|
94
|
+
try {
|
|
95
|
+
await plugins.fs.promises.access(filePath, plugins.fs.constants.X_OK);
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private async findInPath(binaryName: string): Promise<string | null> {
|
|
103
|
+
const pathDirs = (process.env.PATH || '').split(plugins.path.delimiter);
|
|
104
|
+
for (const dir of pathDirs) {
|
|
105
|
+
const fullPath = plugins.path.join(dir, binaryName);
|
|
106
|
+
if (await this.isExecutable(fullPath)) {
|
|
107
|
+
return fullPath;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { IMetrics, IThroughputData, IThroughputHistoryPoint } from './models/metrics-types.js';
|
|
2
|
+
import type { RustProxyBridge } from './rust-proxy-bridge.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Adapts Rust JSON metrics to the IMetrics interface.
|
|
6
|
+
*
|
|
7
|
+
* Polls the Rust binary periodically via the bridge and caches the result.
|
|
8
|
+
* All IMetrics getters read from the cache synchronously.
|
|
9
|
+
*
|
|
10
|
+
* Rust Metrics JSON fields (camelCase via serde):
|
|
11
|
+
* activeConnections, totalConnections, bytesIn, bytesOut,
|
|
12
|
+
* throughputInBytesPerSec, throughputOutBytesPerSec,
|
|
13
|
+
* routes: { [routeName]: { activeConnections, totalConnections, bytesIn, bytesOut, ... } }
|
|
14
|
+
*/
|
|
15
|
+
export class RustMetricsAdapter implements IMetrics {
|
|
16
|
+
private bridge: RustProxyBridge;
|
|
17
|
+
private cache: any = null;
|
|
18
|
+
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
19
|
+
private pollIntervalMs: number;
|
|
20
|
+
|
|
21
|
+
constructor(bridge: RustProxyBridge, pollIntervalMs = 1000) {
|
|
22
|
+
this.bridge = bridge;
|
|
23
|
+
this.pollIntervalMs = pollIntervalMs;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Poll Rust for metrics once. Can be awaited to ensure cache is fresh.
|
|
28
|
+
*/
|
|
29
|
+
public async poll(): Promise<void> {
|
|
30
|
+
try {
|
|
31
|
+
this.cache = await this.bridge.getMetrics();
|
|
32
|
+
} catch {
|
|
33
|
+
// Ignore poll errors (bridge may be shutting down)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public startPolling(): void {
|
|
38
|
+
if (this.pollTimer) return;
|
|
39
|
+
// Immediate first poll so cache is populated ASAP
|
|
40
|
+
this.poll();
|
|
41
|
+
this.pollTimer = setInterval(() => {
|
|
42
|
+
this.poll();
|
|
43
|
+
}, this.pollIntervalMs);
|
|
44
|
+
if (this.pollTimer.unref) {
|
|
45
|
+
this.pollTimer.unref();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public stopPolling(): void {
|
|
50
|
+
if (this.pollTimer) {
|
|
51
|
+
clearInterval(this.pollTimer);
|
|
52
|
+
this.pollTimer = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --- IMetrics implementation ---
|
|
57
|
+
|
|
58
|
+
public connections = {
|
|
59
|
+
active: (): number => {
|
|
60
|
+
return this.cache?.activeConnections ?? 0;
|
|
61
|
+
},
|
|
62
|
+
total: (): number => {
|
|
63
|
+
return this.cache?.totalConnections ?? 0;
|
|
64
|
+
},
|
|
65
|
+
byRoute: (): Map<string, number> => {
|
|
66
|
+
const result = new Map<string, number>();
|
|
67
|
+
if (this.cache?.routes) {
|
|
68
|
+
for (const [name, rm] of Object.entries(this.cache.routes)) {
|
|
69
|
+
result.set(name, (rm as any).activeConnections ?? 0);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
},
|
|
74
|
+
byIP: (): Map<string, number> => {
|
|
75
|
+
// Per-IP tracking not yet available from Rust
|
|
76
|
+
return new Map();
|
|
77
|
+
},
|
|
78
|
+
topIPs: (_limit?: number): Array<{ ip: string; count: number }> => {
|
|
79
|
+
// Per-IP tracking not yet available from Rust
|
|
80
|
+
return [];
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
public throughput = {
|
|
85
|
+
instant: (): IThroughputData => {
|
|
86
|
+
return {
|
|
87
|
+
in: this.cache?.throughputInBytesPerSec ?? 0,
|
|
88
|
+
out: this.cache?.throughputOutBytesPerSec ?? 0,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
recent: (): IThroughputData => {
|
|
92
|
+
return this.throughput.instant();
|
|
93
|
+
},
|
|
94
|
+
average: (): IThroughputData => {
|
|
95
|
+
return this.throughput.instant();
|
|
96
|
+
},
|
|
97
|
+
custom: (_seconds: number): IThroughputData => {
|
|
98
|
+
return this.throughput.instant();
|
|
99
|
+
},
|
|
100
|
+
history: (_seconds: number): Array<IThroughputHistoryPoint> => {
|
|
101
|
+
// Throughput history not yet available from Rust
|
|
102
|
+
return [];
|
|
103
|
+
},
|
|
104
|
+
byRoute: (_windowSeconds?: number): Map<string, IThroughputData> => {
|
|
105
|
+
const result = new Map<string, IThroughputData>();
|
|
106
|
+
if (this.cache?.routes) {
|
|
107
|
+
for (const [name, rm] of Object.entries(this.cache.routes)) {
|
|
108
|
+
result.set(name, {
|
|
109
|
+
in: (rm as any).throughputInBytesPerSec ?? 0,
|
|
110
|
+
out: (rm as any).throughputOutBytesPerSec ?? 0,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
},
|
|
116
|
+
byIP: (_windowSeconds?: number): Map<string, IThroughputData> => {
|
|
117
|
+
return new Map();
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
public requests = {
|
|
122
|
+
perSecond: (): number => {
|
|
123
|
+
// Rust tracks connections, not HTTP requests (TCP-level proxy)
|
|
124
|
+
return 0;
|
|
125
|
+
},
|
|
126
|
+
perMinute: (): number => {
|
|
127
|
+
return 0;
|
|
128
|
+
},
|
|
129
|
+
total: (): number => {
|
|
130
|
+
// Use total connections as a proxy for total requests
|
|
131
|
+
return this.cache?.totalConnections ?? 0;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
public totals = {
|
|
136
|
+
bytesIn: (): number => {
|
|
137
|
+
return this.cache?.bytesIn ?? 0;
|
|
138
|
+
},
|
|
139
|
+
bytesOut: (): number => {
|
|
140
|
+
return this.cache?.bytesOut ?? 0;
|
|
141
|
+
},
|
|
142
|
+
connections: (): number => {
|
|
143
|
+
return this.cache?.totalConnections ?? 0;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
public percentiles = {
|
|
148
|
+
connectionDuration: (): { p50: number; p95: number; p99: number } => {
|
|
149
|
+
return { p50: 0, p95: 0, p99: 0 };
|
|
150
|
+
},
|
|
151
|
+
bytesTransferred: (): {
|
|
152
|
+
in: { p50: number; p95: number; p99: number };
|
|
153
|
+
out: { p50: number; p95: number; p99: number };
|
|
154
|
+
} => {
|
|
155
|
+
return {
|
|
156
|
+
in: { p50: 0, p95: 0, p99: 0 },
|
|
157
|
+
out: { p50: 0, p95: 0, p99: 0 },
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { logger } from '../../core/utils/logger.js';
|
|
3
|
+
import { RustBinaryLocator } from './rust-binary-locator.js';
|
|
4
|
+
import type { IRouteConfig } from './models/route-types.js';
|
|
5
|
+
import { ChildProcess, spawn } from 'child_process';
|
|
6
|
+
import { createInterface, Interface as ReadlineInterface } from 'readline';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Management request sent to the Rust binary via stdin.
|
|
10
|
+
*/
|
|
11
|
+
interface IManagementRequest {
|
|
12
|
+
id: string;
|
|
13
|
+
method: string;
|
|
14
|
+
params: Record<string, any>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Management response received from the Rust binary via stdout.
|
|
19
|
+
*/
|
|
20
|
+
interface IManagementResponse {
|
|
21
|
+
id: string;
|
|
22
|
+
success: boolean;
|
|
23
|
+
result?: any;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Management event received from the Rust binary (unsolicited).
|
|
29
|
+
*/
|
|
30
|
+
interface IManagementEvent {
|
|
31
|
+
event: string;
|
|
32
|
+
data: any;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Bridge between TypeScript SmartProxy and the Rust binary.
|
|
37
|
+
* Communicates via JSON-over-stdin/stdout IPC protocol.
|
|
38
|
+
*/
|
|
39
|
+
export class RustProxyBridge extends plugins.EventEmitter {
|
|
40
|
+
private locator = new RustBinaryLocator();
|
|
41
|
+
private process: ChildProcess | null = null;
|
|
42
|
+
private readline: ReadlineInterface | null = null;
|
|
43
|
+
private pendingRequests = new Map<string, {
|
|
44
|
+
resolve: (value: any) => void;
|
|
45
|
+
reject: (error: Error) => void;
|
|
46
|
+
timer: NodeJS.Timeout;
|
|
47
|
+
}>();
|
|
48
|
+
private requestCounter = 0;
|
|
49
|
+
private isRunning = false;
|
|
50
|
+
private binaryPath: string | null = null;
|
|
51
|
+
private readonly requestTimeoutMs = 30000;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Spawn the Rust binary in management mode.
|
|
55
|
+
* Returns true if the binary was found and spawned successfully.
|
|
56
|
+
*/
|
|
57
|
+
public async spawn(): Promise<boolean> {
|
|
58
|
+
this.binaryPath = await this.locator.findBinary();
|
|
59
|
+
if (!this.binaryPath) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return new Promise<boolean>((resolve) => {
|
|
64
|
+
try {
|
|
65
|
+
this.process = spawn(this.binaryPath!, ['--management'], {
|
|
66
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
|
+
env: { ...process.env },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Handle stderr (logging from Rust goes here)
|
|
71
|
+
const stderrHandler = (data: Buffer) => {
|
|
72
|
+
const lines = data.toString().split('\n').filter(l => l.trim());
|
|
73
|
+
for (const line of lines) {
|
|
74
|
+
logger.log('debug', `[rustproxy] ${line}`, { component: 'rust-bridge' });
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
this.process.stderr?.on('data', stderrHandler);
|
|
78
|
+
|
|
79
|
+
// Handle stdout (JSON IPC)
|
|
80
|
+
this.readline = createInterface({ input: this.process.stdout! });
|
|
81
|
+
this.readline.on('line', (line: string) => {
|
|
82
|
+
this.handleLine(line.trim());
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Handle process exit
|
|
86
|
+
this.process.on('exit', (code, signal) => {
|
|
87
|
+
logger.log('info', `RustProxy process exited (code=${code}, signal=${signal})`, { component: 'rust-bridge' });
|
|
88
|
+
this.cleanup();
|
|
89
|
+
this.emit('exit', code, signal);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
this.process.on('error', (err) => {
|
|
93
|
+
logger.log('error', `RustProxy process error: ${err.message}`, { component: 'rust-bridge' });
|
|
94
|
+
this.cleanup();
|
|
95
|
+
resolve(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Wait for the 'ready' event from Rust
|
|
99
|
+
const readyTimeout = setTimeout(() => {
|
|
100
|
+
logger.log('error', 'RustProxy did not send ready event within 10s', { component: 'rust-bridge' });
|
|
101
|
+
this.kill();
|
|
102
|
+
resolve(false);
|
|
103
|
+
}, 10000);
|
|
104
|
+
|
|
105
|
+
this.once('management:ready', () => {
|
|
106
|
+
clearTimeout(readyTimeout);
|
|
107
|
+
this.isRunning = true;
|
|
108
|
+
logger.log('info', 'RustProxy bridge connected', { component: 'rust-bridge' });
|
|
109
|
+
resolve(true);
|
|
110
|
+
});
|
|
111
|
+
} catch (err: any) {
|
|
112
|
+
logger.log('error', `Failed to spawn RustProxy: ${err.message}`, { component: 'rust-bridge' });
|
|
113
|
+
resolve(false);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Send a management command to the Rust process and wait for the response.
|
|
120
|
+
*/
|
|
121
|
+
public async sendCommand(method: string, params: Record<string, any> = {}): Promise<any> {
|
|
122
|
+
if (!this.process || !this.isRunning) {
|
|
123
|
+
throw new Error('RustProxy bridge is not running');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const id = `req_${++this.requestCounter}`;
|
|
127
|
+
const request: IManagementRequest = { id, method, params };
|
|
128
|
+
|
|
129
|
+
return new Promise<any>((resolve, reject) => {
|
|
130
|
+
const timer = setTimeout(() => {
|
|
131
|
+
this.pendingRequests.delete(id);
|
|
132
|
+
reject(new Error(`RustProxy command '${method}' timed out after ${this.requestTimeoutMs}ms`));
|
|
133
|
+
}, this.requestTimeoutMs);
|
|
134
|
+
|
|
135
|
+
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
136
|
+
|
|
137
|
+
const json = JSON.stringify(request) + '\n';
|
|
138
|
+
this.process!.stdin!.write(json, (err) => {
|
|
139
|
+
if (err) {
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
this.pendingRequests.delete(id);
|
|
142
|
+
reject(new Error(`Failed to write to RustProxy stdin: ${err.message}`));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Convenience methods for each management command
|
|
149
|
+
|
|
150
|
+
public async startProxy(config: any): Promise<void> {
|
|
151
|
+
await this.sendCommand('start', { config });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public async stopProxy(): Promise<void> {
|
|
155
|
+
await this.sendCommand('stop');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public async updateRoutes(routes: IRouteConfig[]): Promise<void> {
|
|
159
|
+
await this.sendCommand('updateRoutes', { routes });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public async getMetrics(): Promise<any> {
|
|
163
|
+
return this.sendCommand('getMetrics');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public async getStatistics(): Promise<any> {
|
|
167
|
+
return this.sendCommand('getStatistics');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public async provisionCertificate(routeName: string): Promise<void> {
|
|
171
|
+
await this.sendCommand('provisionCertificate', { routeName });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public async renewCertificate(routeName: string): Promise<void> {
|
|
175
|
+
await this.sendCommand('renewCertificate', { routeName });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public async getCertificateStatus(routeName: string): Promise<any> {
|
|
179
|
+
return this.sendCommand('getCertificateStatus', { routeName });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public async getListeningPorts(): Promise<number[]> {
|
|
183
|
+
const result = await this.sendCommand('getListeningPorts');
|
|
184
|
+
return result?.ports ?? [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public async getNftablesStatus(): Promise<any> {
|
|
188
|
+
return this.sendCommand('getNftablesStatus');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public async setSocketHandlerRelay(socketPath: string): Promise<void> {
|
|
192
|
+
await this.sendCommand('setSocketHandlerRelay', { socketPath });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public async addListeningPort(port: number): Promise<void> {
|
|
196
|
+
await this.sendCommand('addListeningPort', { port });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public async removeListeningPort(port: number): Promise<void> {
|
|
200
|
+
await this.sendCommand('removeListeningPort', { port });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public async loadCertificate(domain: string, cert: string, key: string, ca?: string): Promise<void> {
|
|
204
|
+
await this.sendCommand('loadCertificate', { domain, cert, key, ca });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Kill the Rust process and clean up all stdio streams.
|
|
209
|
+
*/
|
|
210
|
+
public kill(): void {
|
|
211
|
+
if (this.process) {
|
|
212
|
+
const proc = this.process;
|
|
213
|
+
this.process = null;
|
|
214
|
+
this.isRunning = false;
|
|
215
|
+
|
|
216
|
+
// Close readline (reads from stdout)
|
|
217
|
+
if (this.readline) {
|
|
218
|
+
this.readline.close();
|
|
219
|
+
this.readline = null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Reject pending requests
|
|
223
|
+
for (const [, pending] of this.pendingRequests) {
|
|
224
|
+
clearTimeout(pending.timer);
|
|
225
|
+
pending.reject(new Error('RustProxy process killed'));
|
|
226
|
+
}
|
|
227
|
+
this.pendingRequests.clear();
|
|
228
|
+
|
|
229
|
+
// Remove all listeners so nothing keeps references
|
|
230
|
+
proc.removeAllListeners();
|
|
231
|
+
proc.stdout?.removeAllListeners();
|
|
232
|
+
proc.stderr?.removeAllListeners();
|
|
233
|
+
proc.stdin?.removeAllListeners();
|
|
234
|
+
|
|
235
|
+
// Kill the process
|
|
236
|
+
try { proc.kill('SIGTERM'); } catch { /* already dead */ }
|
|
237
|
+
|
|
238
|
+
// Destroy all stdio pipes to free handles
|
|
239
|
+
try { proc.stdin?.destroy(); } catch { /* ignore */ }
|
|
240
|
+
try { proc.stdout?.destroy(); } catch { /* ignore */ }
|
|
241
|
+
try { proc.stderr?.destroy(); } catch { /* ignore */ }
|
|
242
|
+
|
|
243
|
+
// Unref process so Node doesn't wait for it
|
|
244
|
+
try { proc.unref(); } catch { /* ignore */ }
|
|
245
|
+
|
|
246
|
+
// Force kill after 5 seconds
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
try { proc.kill('SIGKILL'); } catch { /* already dead */ }
|
|
249
|
+
}, 5000).unref();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Whether the bridge is currently running.
|
|
255
|
+
*/
|
|
256
|
+
public get running(): boolean {
|
|
257
|
+
return this.isRunning;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private handleLine(line: string): void {
|
|
261
|
+
if (!line) return;
|
|
262
|
+
|
|
263
|
+
let parsed: any;
|
|
264
|
+
try {
|
|
265
|
+
parsed = JSON.parse(line);
|
|
266
|
+
} catch {
|
|
267
|
+
logger.log('warn', `Non-JSON output from RustProxy: ${line}`, { component: 'rust-bridge' });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Check if it's an event (has 'event' field)
|
|
272
|
+
if ('event' in parsed) {
|
|
273
|
+
const event = parsed as IManagementEvent;
|
|
274
|
+
this.emit(`management:${event.event}`, event.data);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Otherwise it's a response (has 'id' field)
|
|
279
|
+
if ('id' in parsed) {
|
|
280
|
+
const response = parsed as IManagementResponse;
|
|
281
|
+
const pending = this.pendingRequests.get(response.id);
|
|
282
|
+
if (pending) {
|
|
283
|
+
clearTimeout(pending.timer);
|
|
284
|
+
this.pendingRequests.delete(response.id);
|
|
285
|
+
if (response.success) {
|
|
286
|
+
pending.resolve(response.result);
|
|
287
|
+
} else {
|
|
288
|
+
pending.reject(new Error(response.error || 'Unknown error from RustProxy'));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private cleanup(): void {
|
|
295
|
+
this.isRunning = false;
|
|
296
|
+
this.process = null;
|
|
297
|
+
|
|
298
|
+
if (this.readline) {
|
|
299
|
+
this.readline.close();
|
|
300
|
+
this.readline = null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Reject all pending requests
|
|
304
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
305
|
+
clearTimeout(pending.timer);
|
|
306
|
+
pending.reject(new Error('RustProxy process exited'));
|
|
307
|
+
}
|
|
308
|
+
this.pendingRequests.clear();
|
|
309
|
+
}
|
|
310
|
+
}
|