@push.rocks/smartproxy 27.5.0 → 27.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +15 -0
- package/dist_rust/rustproxy_linux_amd64 +0 -0
- package/dist_rust/rustproxy_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +8 -0
- package/dist_ts/proxies/smart-proxy/models/rust-types.d.ts +136 -0
- package/dist_ts/proxies/smart-proxy/models/rust-types.js +2 -0
- package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +2 -1
- package/dist_ts/proxies/smart-proxy/route-preprocessor.js +11 -10
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +6 -0
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +46 -15
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +6 -6
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +1 -1
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +3 -2
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +3 -31
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +17 -6
- package/dist_ts/proxies/smart-proxy/utils/rust-config.d.ts +11 -0
- package/dist_ts/proxies/smart-proxy/utils/rust-config.js +133 -0
- package/package.json +1 -1
- package/readme.metrics.md +484 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/proxies/smart-proxy/models/metrics-types.ts +4 -0
- package/ts/proxies/smart-proxy/models/rust-types.ts +160 -0
- package/ts/proxies/smart-proxy/route-preprocessor.ts +13 -11
- package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +64 -32
- package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +24 -18
- package/ts/proxies/smart-proxy/smart-proxy.ts +6 -33
- package/ts/proxies/smart-proxy/utils/route-utils.ts +22 -8
- package/ts/proxies/smart-proxy/utils/rust-config.ts +187 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IRouteConfig, IRouteAction, IRouteTarget } from './models/route-types.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { IRustRouteConfig } from './models/rust-types.js';
|
|
3
|
+
import { serializeRouteForRust } from './utils/rust-config.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Preprocesses routes before sending them to Rust.
|
|
@@ -24,7 +25,7 @@ export class RoutePreprocessor {
|
|
|
24
25
|
* - Non-serializable fields are stripped
|
|
25
26
|
* - Original routes are preserved in the local map for handler lookup
|
|
26
27
|
*/
|
|
27
|
-
public preprocessForRust(routes: IRouteConfig[]):
|
|
28
|
+
public preprocessForRust(routes: IRouteConfig[]): IRustRouteConfig[] {
|
|
28
29
|
this.originalRoutes.clear();
|
|
29
30
|
return routes.map((route, index) => this.preprocessRoute(route, index));
|
|
30
31
|
}
|
|
@@ -43,7 +44,7 @@ export class RoutePreprocessor {
|
|
|
43
44
|
return new Map(this.originalRoutes);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
private preprocessRoute(route: IRouteConfig, index: number):
|
|
47
|
+
private preprocessRoute(route: IRouteConfig, index: number): IRustRouteConfig {
|
|
47
48
|
const routeKey = route.name || route.id || `route_${index}`;
|
|
48
49
|
|
|
49
50
|
// Check if this route needs TS-side handling
|
|
@@ -57,7 +58,7 @@ export class RoutePreprocessor {
|
|
|
57
58
|
// Create a clean copy for Rust
|
|
58
59
|
const cleanRoute: IRouteConfig = {
|
|
59
60
|
...route,
|
|
60
|
-
action: this.cleanAction(route.action,
|
|
61
|
+
action: this.cleanAction(route.action, needsTsHandling),
|
|
61
62
|
};
|
|
62
63
|
|
|
63
64
|
// Ensure we have a name for handler lookup
|
|
@@ -65,7 +66,7 @@ export class RoutePreprocessor {
|
|
|
65
66
|
cleanRoute.name = routeKey;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
return cleanRoute;
|
|
69
|
+
return serializeRouteForRust(cleanRoute);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
private routeNeedsTsHandling(route: IRouteConfig): boolean {
|
|
@@ -91,15 +92,16 @@ export class RoutePreprocessor {
|
|
|
91
92
|
return false;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
private cleanAction(action: IRouteAction,
|
|
95
|
-
|
|
95
|
+
private cleanAction(action: IRouteAction, needsTsHandling: boolean): IRouteAction {
|
|
96
|
+
let cleanAction: IRouteAction = { ...action };
|
|
96
97
|
|
|
97
98
|
if (needsTsHandling) {
|
|
98
99
|
// Convert to socket-handler type for Rust (Rust will relay back to TS)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
const { socketHandler: _socketHandler, datagramHandler: _datagramHandler, ...serializableAction } = cleanAction;
|
|
101
|
+
cleanAction = {
|
|
102
|
+
...serializableAction,
|
|
103
|
+
type: 'socket-handler',
|
|
104
|
+
};
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
// Clean targets - replace functions with static values
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IMetrics, IBackendMetrics, IProtocolCacheEntry, IProtocolDistribution, IThroughputData, IThroughputHistoryPoint } from './models/metrics-types.js';
|
|
2
2
|
import type { RustProxyBridge } from './rust-proxy-bridge.js';
|
|
3
|
+
import type { IRustBackendMetrics, IRustIpMetrics, IRustMetricsSnapshot, IRustRouteMetrics } from './models/rust-types.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Adapts Rust JSON metrics to the IMetrics interface.
|
|
@@ -14,7 +15,7 @@ import type { RustProxyBridge } from './rust-proxy-bridge.js';
|
|
|
14
15
|
*/
|
|
15
16
|
export class RustMetricsAdapter implements IMetrics {
|
|
16
17
|
private bridge: RustProxyBridge;
|
|
17
|
-
private cache:
|
|
18
|
+
private cache: IRustMetricsSnapshot | null = null;
|
|
18
19
|
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
19
20
|
private pollIntervalMs: number;
|
|
20
21
|
|
|
@@ -65,8 +66,8 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
65
66
|
byRoute: (): Map<string, number> => {
|
|
66
67
|
const result = new Map<string, number>();
|
|
67
68
|
if (this.cache?.routes) {
|
|
68
|
-
for (const [name, rm] of Object.entries(this.cache.routes)) {
|
|
69
|
-
result.set(name,
|
|
69
|
+
for (const [name, rm] of Object.entries(this.cache.routes) as Array<[string, IRustRouteMetrics]>) {
|
|
70
|
+
result.set(name, rm.activeConnections ?? 0);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
return result;
|
|
@@ -74,8 +75,8 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
74
75
|
byIP: (): Map<string, number> => {
|
|
75
76
|
const result = new Map<string, number>();
|
|
76
77
|
if (this.cache?.ips) {
|
|
77
|
-
for (const [ip, im] of Object.entries(this.cache.ips)) {
|
|
78
|
-
result.set(ip,
|
|
78
|
+
for (const [ip, im] of Object.entries(this.cache.ips) as Array<[string, IRustIpMetrics]>) {
|
|
79
|
+
result.set(ip, im.activeConnections ?? 0);
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
return result;
|
|
@@ -83,8 +84,41 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
83
84
|
topIPs: (limit: number = 10): Array<{ ip: string; count: number }> => {
|
|
84
85
|
const result: Array<{ ip: string; count: number }> = [];
|
|
85
86
|
if (this.cache?.ips) {
|
|
86
|
-
for (const [ip, im] of Object.entries(this.cache.ips)) {
|
|
87
|
-
result.push({ ip, count:
|
|
87
|
+
for (const [ip, im] of Object.entries(this.cache.ips) as Array<[string, IRustIpMetrics]>) {
|
|
88
|
+
result.push({ ip, count: im.activeConnections ?? 0 });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
result.sort((a, b) => b.count - a.count);
|
|
92
|
+
return result.slice(0, limit);
|
|
93
|
+
},
|
|
94
|
+
domainRequestsByIP: (): Map<string, Map<string, number>> => {
|
|
95
|
+
const result = new Map<string, Map<string, number>>();
|
|
96
|
+
if (this.cache?.ips) {
|
|
97
|
+
for (const [ip, im] of Object.entries(this.cache.ips) as Array<[string, IRustIpMetrics]>) {
|
|
98
|
+
const dr = im.domainRequests;
|
|
99
|
+
if (dr && typeof dr === 'object') {
|
|
100
|
+
const domainMap = new Map<string, number>();
|
|
101
|
+
for (const [domain, count] of Object.entries(dr)) {
|
|
102
|
+
domainMap.set(domain, count as number);
|
|
103
|
+
}
|
|
104
|
+
if (domainMap.size > 0) {
|
|
105
|
+
result.set(ip, domainMap);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
},
|
|
112
|
+
topDomainRequests: (limit: number = 20): Array<{ ip: string; domain: string; count: number }> => {
|
|
113
|
+
const result: Array<{ ip: string; domain: string; count: number }> = [];
|
|
114
|
+
if (this.cache?.ips) {
|
|
115
|
+
for (const [ip, im] of Object.entries(this.cache.ips) as Array<[string, IRustIpMetrics]>) {
|
|
116
|
+
const dr = im.domainRequests;
|
|
117
|
+
if (dr && typeof dr === 'object') {
|
|
118
|
+
for (const [domain, count] of Object.entries(dr)) {
|
|
119
|
+
result.push({ ip, domain, count: count as number });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
88
122
|
}
|
|
89
123
|
}
|
|
90
124
|
result.sort((a, b) => b.count - a.count);
|
|
@@ -143,7 +177,7 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
143
177
|
},
|
|
144
178
|
history: (seconds: number): Array<IThroughputHistoryPoint> => {
|
|
145
179
|
if (!this.cache?.throughputHistory) return [];
|
|
146
|
-
return this.cache.throughputHistory.slice(-seconds).map((p
|
|
180
|
+
return this.cache.throughputHistory.slice(-seconds).map((p) => ({
|
|
147
181
|
timestamp: p.timestampMs,
|
|
148
182
|
in: p.bytesIn,
|
|
149
183
|
out: p.bytesOut,
|
|
@@ -152,10 +186,10 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
152
186
|
byRoute: (_windowSeconds?: number): Map<string, IThroughputData> => {
|
|
153
187
|
const result = new Map<string, IThroughputData>();
|
|
154
188
|
if (this.cache?.routes) {
|
|
155
|
-
for (const [name, rm] of Object.entries(this.cache.routes)) {
|
|
189
|
+
for (const [name, rm] of Object.entries(this.cache.routes) as Array<[string, IRustRouteMetrics]>) {
|
|
156
190
|
result.set(name, {
|
|
157
|
-
in:
|
|
158
|
-
out:
|
|
191
|
+
in: rm.throughputInBytesPerSec ?? 0,
|
|
192
|
+
out: rm.throughputOutBytesPerSec ?? 0,
|
|
159
193
|
});
|
|
160
194
|
}
|
|
161
195
|
}
|
|
@@ -164,10 +198,10 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
164
198
|
byIP: (_windowSeconds?: number): Map<string, IThroughputData> => {
|
|
165
199
|
const result = new Map<string, IThroughputData>();
|
|
166
200
|
if (this.cache?.ips) {
|
|
167
|
-
for (const [ip, im] of Object.entries(this.cache.ips)) {
|
|
201
|
+
for (const [ip, im] of Object.entries(this.cache.ips) as Array<[string, IRustIpMetrics]>) {
|
|
168
202
|
result.set(ip, {
|
|
169
|
-
in:
|
|
170
|
-
out:
|
|
203
|
+
in: im.throughputInBytesPerSec ?? 0,
|
|
204
|
+
out: im.throughputOutBytesPerSec ?? 0,
|
|
171
205
|
});
|
|
172
206
|
}
|
|
173
207
|
}
|
|
@@ -203,23 +237,22 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
203
237
|
byBackend: (): Map<string, IBackendMetrics> => {
|
|
204
238
|
const result = new Map<string, IBackendMetrics>();
|
|
205
239
|
if (this.cache?.backends) {
|
|
206
|
-
for (const [key, bm] of Object.entries(this.cache.backends)) {
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
const poolMisses = m.poolMisses ?? 0;
|
|
240
|
+
for (const [key, bm] of Object.entries(this.cache.backends) as Array<[string, IRustBackendMetrics]>) {
|
|
241
|
+
const totalTimeUs = bm.totalConnectTimeUs ?? 0;
|
|
242
|
+
const count = bm.connectCount ?? 0;
|
|
243
|
+
const poolHits = bm.poolHits ?? 0;
|
|
244
|
+
const poolMisses = bm.poolMisses ?? 0;
|
|
212
245
|
const poolTotal = poolHits + poolMisses;
|
|
213
246
|
result.set(key, {
|
|
214
|
-
protocol:
|
|
215
|
-
activeConnections:
|
|
216
|
-
totalConnections:
|
|
217
|
-
connectErrors:
|
|
218
|
-
handshakeErrors:
|
|
219
|
-
requestErrors:
|
|
247
|
+
protocol: bm.protocol ?? 'unknown',
|
|
248
|
+
activeConnections: bm.activeConnections ?? 0,
|
|
249
|
+
totalConnections: bm.totalConnections ?? 0,
|
|
250
|
+
connectErrors: bm.connectErrors ?? 0,
|
|
251
|
+
handshakeErrors: bm.handshakeErrors ?? 0,
|
|
252
|
+
requestErrors: bm.requestErrors ?? 0,
|
|
220
253
|
avgConnectTimeMs: count > 0 ? (totalTimeUs / count) / 1000 : 0,
|
|
221
254
|
poolHitRate: poolTotal > 0 ? poolHits / poolTotal : 0,
|
|
222
|
-
h2Failures:
|
|
255
|
+
h2Failures: bm.h2Failures ?? 0,
|
|
223
256
|
});
|
|
224
257
|
}
|
|
225
258
|
}
|
|
@@ -228,8 +261,8 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
228
261
|
protocols: (): Map<string, string> => {
|
|
229
262
|
const result = new Map<string, string>();
|
|
230
263
|
if (this.cache?.backends) {
|
|
231
|
-
for (const [key, bm] of Object.entries(this.cache.backends)) {
|
|
232
|
-
result.set(key,
|
|
264
|
+
for (const [key, bm] of Object.entries(this.cache.backends) as Array<[string, IRustBackendMetrics]>) {
|
|
265
|
+
result.set(key, bm.protocol ?? 'unknown');
|
|
233
266
|
}
|
|
234
267
|
}
|
|
235
268
|
return result;
|
|
@@ -237,9 +270,8 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
237
270
|
topByErrors: (limit: number = 10): Array<{ backend: string; errors: number }> => {
|
|
238
271
|
const result: Array<{ backend: string; errors: number }> = [];
|
|
239
272
|
if (this.cache?.backends) {
|
|
240
|
-
for (const [key, bm] of Object.entries(this.cache.backends)) {
|
|
241
|
-
const
|
|
242
|
-
const errors = (m.connectErrors ?? 0) + (m.handshakeErrors ?? 0) + (m.requestErrors ?? 0);
|
|
273
|
+
for (const [key, bm] of Object.entries(this.cache.backends) as Array<[string, IRustBackendMetrics]>) {
|
|
274
|
+
const errors = (bm.connectErrors ?? 0) + (bm.handshakeErrors ?? 0) + (bm.requestErrors ?? 0);
|
|
243
275
|
if (errors > 0) result.push({ backend: key, errors });
|
|
244
276
|
}
|
|
245
277
|
}
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import { logger } from '../../core/utils/logger.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
IRustCertificateStatus,
|
|
5
|
+
IRustMetricsSnapshot,
|
|
6
|
+
IRustProxyOptions,
|
|
7
|
+
IRustRouteConfig,
|
|
8
|
+
IRustStatistics,
|
|
9
|
+
} from './models/rust-types.js';
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Type-safe command definitions for the Rust proxy IPC protocol.
|
|
7
13
|
*/
|
|
8
14
|
type TSmartProxyCommands = {
|
|
9
|
-
start:
|
|
10
|
-
stop:
|
|
11
|
-
updateRoutes:
|
|
12
|
-
getMetrics:
|
|
13
|
-
getStatistics:
|
|
14
|
-
provisionCertificate:
|
|
15
|
-
renewCertificate:
|
|
16
|
-
getCertificateStatus:
|
|
17
|
-
getListeningPorts:
|
|
18
|
-
setSocketHandlerRelay:
|
|
19
|
-
addListeningPort:
|
|
20
|
-
removeListeningPort:
|
|
15
|
+
start: { params: { config: IRustProxyOptions }; result: void };
|
|
16
|
+
stop: { params: Record<string, never>; result: void };
|
|
17
|
+
updateRoutes: { params: { routes: IRustRouteConfig[] }; result: void };
|
|
18
|
+
getMetrics: { params: Record<string, never>; result: IRustMetricsSnapshot };
|
|
19
|
+
getStatistics: { params: Record<string, never>; result: IRustStatistics };
|
|
20
|
+
provisionCertificate: { params: { routeName: string }; result: void };
|
|
21
|
+
renewCertificate: { params: { routeName: string }; result: void };
|
|
22
|
+
getCertificateStatus: { params: { routeName: string }; result: IRustCertificateStatus | null };
|
|
23
|
+
getListeningPorts: { params: Record<string, never>; result: { ports: number[] } };
|
|
24
|
+
setSocketHandlerRelay: { params: { socketPath: string }; result: void };
|
|
25
|
+
addListeningPort: { params: { port: number }; result: void };
|
|
26
|
+
removeListeningPort: { params: { port: number }; result: void };
|
|
21
27
|
loadCertificate: { params: { domain: string; cert: string; key: string; ca?: string }; result: void };
|
|
22
28
|
setDatagramHandlerRelay: { params: { socketPath: string }; result: void };
|
|
23
29
|
};
|
|
@@ -121,7 +127,7 @@ export class RustProxyBridge extends plugins.EventEmitter {
|
|
|
121
127
|
|
|
122
128
|
// --- Convenience methods for each management command ---
|
|
123
129
|
|
|
124
|
-
public async startProxy(config:
|
|
130
|
+
public async startProxy(config: IRustProxyOptions): Promise<void> {
|
|
125
131
|
await this.bridge.sendCommand('start', { config });
|
|
126
132
|
}
|
|
127
133
|
|
|
@@ -129,15 +135,15 @@ export class RustProxyBridge extends plugins.EventEmitter {
|
|
|
129
135
|
await this.bridge.sendCommand('stop', {} as Record<string, never>);
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
public async updateRoutes(routes:
|
|
138
|
+
public async updateRoutes(routes: IRustRouteConfig[]): Promise<void> {
|
|
133
139
|
await this.bridge.sendCommand('updateRoutes', { routes });
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
public async getMetrics(): Promise<
|
|
142
|
+
public async getMetrics(): Promise<IRustMetricsSnapshot> {
|
|
137
143
|
return this.bridge.sendCommand('getMetrics', {} as Record<string, never>);
|
|
138
144
|
}
|
|
139
145
|
|
|
140
|
-
public async getStatistics(): Promise<
|
|
146
|
+
public async getStatistics(): Promise<IRustStatistics> {
|
|
141
147
|
return this.bridge.sendCommand('getStatistics', {} as Record<string, never>);
|
|
142
148
|
}
|
|
143
149
|
|
|
@@ -149,7 +155,7 @@ export class RustProxyBridge extends plugins.EventEmitter {
|
|
|
149
155
|
await this.bridge.sendCommand('renewCertificate', { routeName });
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
public async getCertificateStatus(routeName: string): Promise<
|
|
158
|
+
public async getCertificateStatus(routeName: string): Promise<IRustCertificateStatus | null> {
|
|
153
159
|
return this.bridge.sendCommand('getCertificateStatus', { routeName });
|
|
154
160
|
}
|
|
155
161
|
|
|
@@ -11,6 +11,7 @@ import { RustMetricsAdapter } from './rust-metrics-adapter.js';
|
|
|
11
11
|
// Route management
|
|
12
12
|
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
13
13
|
import { RouteValidator } from './utils/route-validator.js';
|
|
14
|
+
import { buildRustProxyOptions } from './utils/rust-config.js';
|
|
14
15
|
import { generateDefaultCertificate } from './utils/default-cert-generator.js';
|
|
15
16
|
import { Mutex } from './utils/mutex.js';
|
|
16
17
|
import { ConcurrencySemaphore } from './utils/concurrency-semaphore.js';
|
|
@@ -19,6 +20,7 @@ import { ConcurrencySemaphore } from './utils/concurrency-semaphore.js';
|
|
|
19
20
|
import type { ISmartProxyOptions, TSmartProxyCertProvisionObject, IAcmeOptions, ICertProvisionEventComms, ICertificateIssuedEvent, ICertificateFailedEvent } from './models/interfaces.js';
|
|
20
21
|
import type { IRouteConfig } from './models/route-types.js';
|
|
21
22
|
import type { IMetrics } from './models/metrics-types.js';
|
|
23
|
+
import type { IRustCertificateStatus, IRustProxyOptions, IRustStatistics } from './models/rust-types.js';
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* SmartProxy - Rust-backed proxy engine with TypeScript configuration API.
|
|
@@ -365,7 +367,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
365
367
|
/**
|
|
366
368
|
* Get certificate status for a route (async - calls Rust).
|
|
367
369
|
*/
|
|
368
|
-
public async getCertificateStatus(routeName: string): Promise<
|
|
370
|
+
public async getCertificateStatus(routeName: string): Promise<IRustCertificateStatus | null> {
|
|
369
371
|
return this.bridge.getCertificateStatus(routeName);
|
|
370
372
|
}
|
|
371
373
|
|
|
@@ -379,7 +381,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
379
381
|
/**
|
|
380
382
|
* Get statistics (async - calls Rust).
|
|
381
383
|
*/
|
|
382
|
-
public async getStatistics(): Promise<
|
|
384
|
+
public async getStatistics(): Promise<IRustStatistics> {
|
|
383
385
|
return this.bridge.getStatistics();
|
|
384
386
|
}
|
|
385
387
|
|
|
@@ -484,37 +486,8 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
484
486
|
/**
|
|
485
487
|
* Build the Rust configuration object from TS settings.
|
|
486
488
|
*/
|
|
487
|
-
private buildRustConfig(routes:
|
|
488
|
-
|
|
489
|
-
return {
|
|
490
|
-
routes,
|
|
491
|
-
defaults: this.settings.defaults,
|
|
492
|
-
acme: acme
|
|
493
|
-
? {
|
|
494
|
-
enabled: acme.enabled,
|
|
495
|
-
email: acme.email,
|
|
496
|
-
useProduction: acme.useProduction,
|
|
497
|
-
port: acme.port,
|
|
498
|
-
renewThresholdDays: acme.renewThresholdDays,
|
|
499
|
-
autoRenew: acme.autoRenew,
|
|
500
|
-
renewCheckIntervalHours: acme.renewCheckIntervalHours,
|
|
501
|
-
}
|
|
502
|
-
: undefined,
|
|
503
|
-
connectionTimeout: this.settings.connectionTimeout,
|
|
504
|
-
initialDataTimeout: this.settings.initialDataTimeout,
|
|
505
|
-
socketTimeout: this.settings.socketTimeout,
|
|
506
|
-
maxConnectionLifetime: this.settings.maxConnectionLifetime,
|
|
507
|
-
gracefulShutdownTimeout: this.settings.gracefulShutdownTimeout,
|
|
508
|
-
maxConnectionsPerIp: this.settings.maxConnectionsPerIP,
|
|
509
|
-
connectionRateLimitPerMinute: this.settings.connectionRateLimitPerMinute,
|
|
510
|
-
keepAliveTreatment: this.settings.keepAliveTreatment,
|
|
511
|
-
keepAliveInactivityMultiplier: this.settings.keepAliveInactivityMultiplier,
|
|
512
|
-
extendedKeepAliveLifetime: this.settings.extendedKeepAliveLifetime,
|
|
513
|
-
proxyIps: this.settings.proxyIPs,
|
|
514
|
-
acceptProxyProtocol: this.settings.acceptProxyProtocol,
|
|
515
|
-
sendProxyProtocol: this.settings.sendProxyProtocol,
|
|
516
|
-
metrics: this.settings.metrics,
|
|
517
|
-
};
|
|
489
|
+
private buildRustConfig(routes: IRustProxyOptions['routes'], acmeOverride?: IAcmeOptions): IRustProxyOptions {
|
|
490
|
+
return buildRustProxyOptions(this.settings, routes, acmeOverride);
|
|
518
491
|
}
|
|
519
492
|
|
|
520
493
|
/**
|
|
@@ -168,14 +168,28 @@ export function routeMatchesHeaders(
|
|
|
168
168
|
if (!route.match?.headers || Object.keys(route.match.headers).length === 0) {
|
|
169
169
|
return true; // No headers specified means it matches any headers
|
|
170
170
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
|
|
172
|
+
for (const [headerName, expectedValue] of Object.entries(route.match.headers)) {
|
|
173
|
+
const actualKey = Object.keys(headers).find((key) => key.toLowerCase() === headerName.toLowerCase());
|
|
174
|
+
const actualValue = actualKey ? headers[actualKey] : undefined;
|
|
175
|
+
|
|
176
|
+
if (actualValue === undefined) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (expectedValue instanceof RegExp) {
|
|
181
|
+
if (!expectedValue.test(actualValue)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!HeaderMatcher.match(expectedValue, actualValue)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
176
190
|
}
|
|
177
|
-
|
|
178
|
-
return
|
|
191
|
+
|
|
192
|
+
return true;
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
/**
|
|
@@ -283,4 +297,4 @@ export function generateRouteId(route: IRouteConfig): string {
|
|
|
283
297
|
*/
|
|
284
298
|
export function cloneRoute(route: IRouteConfig): IRouteConfig {
|
|
285
299
|
return JSON.parse(JSON.stringify(route));
|
|
286
|
-
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { IAcmeOptions, ISmartProxyOptions } from '../models/interfaces.js';
|
|
2
|
+
import type { IRouteAction, IRouteConfig, IRouteMatch, IRouteTarget, ITargetMatch } from '../models/route-types.js';
|
|
3
|
+
import type {
|
|
4
|
+
IRustAcmeOptions,
|
|
5
|
+
IRustDefaultConfig,
|
|
6
|
+
IRustProxyOptions,
|
|
7
|
+
IRustRouteAction,
|
|
8
|
+
IRustRouteConfig,
|
|
9
|
+
IRustRouteMatch,
|
|
10
|
+
IRustRouteTarget,
|
|
11
|
+
IRustTargetMatch,
|
|
12
|
+
IRustRouteUdp,
|
|
13
|
+
TRustHeaderMatchers,
|
|
14
|
+
} from '../models/rust-types.js';
|
|
15
|
+
|
|
16
|
+
const SUPPORTED_REGEX_FLAGS = new Set(['i', 'm', 's', 'u', 'g']);
|
|
17
|
+
|
|
18
|
+
export function serializeHeaderMatchValue(value: string | RegExp): string {
|
|
19
|
+
if (typeof value === 'string') {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const unsupportedFlags = Array.from(new Set(value.flags)).filter((flag) => !SUPPORTED_REGEX_FLAGS.has(flag));
|
|
24
|
+
if (unsupportedFlags.length > 0) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Header RegExp uses unsupported flags for Rust serialization: ${unsupportedFlags.join(', ')}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return `/${value.source}/${value.flags}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function serializeHeaderMatchers(headers?: Record<string, string | RegExp>): TRustHeaderMatchers | undefined {
|
|
34
|
+
if (!headers) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return Object.fromEntries(
|
|
39
|
+
Object.entries(headers).map(([key, value]) => [key, serializeHeaderMatchValue(value)])
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function serializeTargetMatchForRust(match?: ITargetMatch): IRustTargetMatch | undefined {
|
|
44
|
+
if (!match) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...match,
|
|
50
|
+
headers: serializeHeaderMatchers(match.headers),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function serializeRouteMatchForRust(match: IRouteMatch): IRustRouteMatch {
|
|
55
|
+
return {
|
|
56
|
+
...match,
|
|
57
|
+
headers: serializeHeaderMatchers(match.headers),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function serializeRouteTargetForRust(target: IRouteTarget): IRustRouteTarget {
|
|
62
|
+
if (typeof target.host !== 'string' && !Array.isArray(target.host)) {
|
|
63
|
+
throw new Error('Route target host must be serialized before sending to Rust');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof target.port !== 'number' && target.port !== 'preserve') {
|
|
67
|
+
throw new Error('Route target port must be serialized before sending to Rust');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
...target,
|
|
72
|
+
host: target.host,
|
|
73
|
+
port: target.port,
|
|
74
|
+
match: serializeTargetMatchForRust(target.match),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function serializeUdpForRust(udp?: IRouteAction['udp']): IRustRouteUdp | undefined {
|
|
79
|
+
if (!udp) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { maxSessionsPerIP, ...rest } = udp;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
...rest,
|
|
87
|
+
maxSessionsPerIp: maxSessionsPerIP,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function serializeRouteActionForRust(action: IRouteAction): IRustRouteAction {
|
|
92
|
+
const {
|
|
93
|
+
socketHandler: _socketHandler,
|
|
94
|
+
datagramHandler: _datagramHandler,
|
|
95
|
+
forwardingEngine: _forwardingEngine,
|
|
96
|
+
nftables: _nftables,
|
|
97
|
+
targets,
|
|
98
|
+
udp,
|
|
99
|
+
...rest
|
|
100
|
+
} = action;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
...rest,
|
|
104
|
+
targets: targets?.map((target) => serializeRouteTargetForRust(target)),
|
|
105
|
+
udp: serializeUdpForRust(udp),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function serializeRouteForRust(route: IRouteConfig): IRustRouteConfig {
|
|
110
|
+
return {
|
|
111
|
+
...route,
|
|
112
|
+
match: serializeRouteMatchForRust(route.match),
|
|
113
|
+
action: serializeRouteActionForRust(route.action),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function serializeAcmeForRust(acme?: IAcmeOptions): IRustAcmeOptions | undefined {
|
|
118
|
+
if (!acme) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
enabled: acme.enabled,
|
|
124
|
+
email: acme.email,
|
|
125
|
+
environment: acme.environment,
|
|
126
|
+
accountEmail: acme.accountEmail,
|
|
127
|
+
port: acme.port,
|
|
128
|
+
useProduction: acme.useProduction,
|
|
129
|
+
renewThresholdDays: acme.renewThresholdDays,
|
|
130
|
+
autoRenew: acme.autoRenew,
|
|
131
|
+
skipConfiguredCerts: acme.skipConfiguredCerts,
|
|
132
|
+
renewCheckIntervalHours: acme.renewCheckIntervalHours,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function serializeDefaultsForRust(defaults?: ISmartProxyOptions['defaults']): IRustDefaultConfig | undefined {
|
|
137
|
+
if (!defaults) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const { preserveSourceIP, ...rest } = defaults;
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
...rest,
|
|
145
|
+
preserveSourceIp: preserveSourceIP,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function buildRustProxyOptions(
|
|
150
|
+
settings: ISmartProxyOptions,
|
|
151
|
+
routes: IRustRouteConfig[],
|
|
152
|
+
acmeOverride?: IAcmeOptions,
|
|
153
|
+
): IRustProxyOptions {
|
|
154
|
+
const acme = acmeOverride !== undefined ? acmeOverride : settings.acme;
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
routes,
|
|
158
|
+
preserveSourceIp: settings.preserveSourceIP,
|
|
159
|
+
proxyIps: settings.proxyIPs,
|
|
160
|
+
acceptProxyProtocol: settings.acceptProxyProtocol,
|
|
161
|
+
sendProxyProtocol: settings.sendProxyProtocol,
|
|
162
|
+
defaults: serializeDefaultsForRust(settings.defaults),
|
|
163
|
+
connectionTimeout: settings.connectionTimeout,
|
|
164
|
+
initialDataTimeout: settings.initialDataTimeout,
|
|
165
|
+
socketTimeout: settings.socketTimeout,
|
|
166
|
+
inactivityCheckInterval: settings.inactivityCheckInterval,
|
|
167
|
+
maxConnectionLifetime: settings.maxConnectionLifetime,
|
|
168
|
+
inactivityTimeout: settings.inactivityTimeout,
|
|
169
|
+
gracefulShutdownTimeout: settings.gracefulShutdownTimeout,
|
|
170
|
+
noDelay: settings.noDelay,
|
|
171
|
+
keepAlive: settings.keepAlive,
|
|
172
|
+
keepAliveInitialDelay: settings.keepAliveInitialDelay,
|
|
173
|
+
maxPendingDataSize: settings.maxPendingDataSize,
|
|
174
|
+
disableInactivityCheck: settings.disableInactivityCheck,
|
|
175
|
+
enableKeepAliveProbes: settings.enableKeepAliveProbes,
|
|
176
|
+
enableDetailedLogging: settings.enableDetailedLogging,
|
|
177
|
+
enableTlsDebugLogging: settings.enableTlsDebugLogging,
|
|
178
|
+
enableRandomizedTimeouts: settings.enableRandomizedTimeouts,
|
|
179
|
+
maxConnectionsPerIp: settings.maxConnectionsPerIP,
|
|
180
|
+
connectionRateLimitPerMinute: settings.connectionRateLimitPerMinute,
|
|
181
|
+
keepAliveTreatment: settings.keepAliveTreatment,
|
|
182
|
+
keepAliveInactivityMultiplier: settings.keepAliveInactivityMultiplier,
|
|
183
|
+
extendedKeepAliveLifetime: settings.extendedKeepAliveLifetime,
|
|
184
|
+
metrics: settings.metrics,
|
|
185
|
+
acme: serializeAcmeForRust(acme),
|
|
186
|
+
};
|
|
187
|
+
}
|