@push.rocks/smartproxy 25.12.0 → 25.14.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 +17 -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/datagram-handler-server.d.ts +46 -0
- package/dist_ts/proxies/smart-proxy/datagram-handler-server.js +197 -0
- package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +6 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +63 -4
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/route-preprocessor.js +7 -2
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +6 -0
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +7 -1
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +4 -1
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +31 -1
- package/dist_ts/proxies/smart-proxy/utils/route-validator.js +12 -8
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/proxies/smart-proxy/datagram-handler-server.ts +239 -0
- package/ts/proxies/smart-proxy/models/metrics-types.ts +8 -0
- package/ts/proxies/smart-proxy/models/route-types.ts +83 -5
- package/ts/proxies/smart-proxy/route-preprocessor.ts +7 -1
- package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +7 -0
- package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +5 -0
- package/ts/proxies/smart-proxy/smart-proxy.ts +38 -0
- package/ts/proxies/smart-proxy/utils/route-validator.ts +10 -7
|
@@ -74,6 +74,14 @@ export interface IMetrics {
|
|
|
74
74
|
topByErrors(limit?: number): Array<{ backend: string; errors: number }>;
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
+
// UDP metrics
|
|
78
|
+
udp: {
|
|
79
|
+
activeSessions(): number;
|
|
80
|
+
totalSessions(): number;
|
|
81
|
+
datagramsIn(): number;
|
|
82
|
+
datagramsOut(): number;
|
|
83
|
+
};
|
|
84
|
+
|
|
77
85
|
// Performance metrics
|
|
78
86
|
percentiles: {
|
|
79
87
|
connectionDuration(): { p50: number; p95: number; p99: number };
|
|
@@ -20,9 +20,15 @@ export type TSocketHandler = (socket: plugins.net.Socket, context: IRouteContext
|
|
|
20
20
|
export type TTlsMode = 'passthrough' | 'terminate' | 'terminate-and-reencrypt';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Transport protocol for route matching
|
|
24
24
|
*/
|
|
25
|
-
export type
|
|
25
|
+
export type TTransportProtocol = 'tcp' | 'udp' | 'all';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Port range specification format.
|
|
29
|
+
* Supports: single number, array of numbers, array of ranges, or mixed arrays.
|
|
30
|
+
*/
|
|
31
|
+
export type TPortRange = number | Array<number | { from: number; to: number }>;
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
34
|
* Route match criteria for incoming requests
|
|
@@ -31,6 +37,9 @@ export interface IRouteMatch {
|
|
|
31
37
|
// Listen on these ports (required)
|
|
32
38
|
ports: TPortRange;
|
|
33
39
|
|
|
40
|
+
// Transport protocol: 'tcp' (default), 'udp', or 'all' (both TCP and UDP)
|
|
41
|
+
transport?: TTransportProtocol;
|
|
42
|
+
|
|
34
43
|
// Optional domain patterns to match (default: all domains)
|
|
35
44
|
domains?: string | string[];
|
|
36
45
|
|
|
@@ -39,7 +48,7 @@ export interface IRouteMatch {
|
|
|
39
48
|
clientIp?: string[]; // Match specific client IPs
|
|
40
49
|
tlsVersion?: string[]; // Match specific TLS versions
|
|
41
50
|
headers?: Record<string, string | RegExp>; // Match specific HTTP headers
|
|
42
|
-
protocol?: 'http' | 'tcp'; // Match specific protocol
|
|
51
|
+
protocol?: 'http' | 'tcp' | 'udp' | 'quic' | 'http3'; // Match specific protocol
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
|
|
@@ -72,6 +81,9 @@ export interface IRouteTarget {
|
|
|
72
81
|
headers?: IRouteHeaders; // Override route-level headers
|
|
73
82
|
advanced?: IRouteAdvanced; // Override route-level advanced settings
|
|
74
83
|
|
|
84
|
+
// Override transport for backend connection (e.g., receive QUIC but forward as HTTP/1.1 via TCP)
|
|
85
|
+
backendTransport?: 'tcp' | 'udp';
|
|
86
|
+
|
|
75
87
|
// Priority for matching (higher values are checked first, default: 0)
|
|
76
88
|
priority?: number;
|
|
77
89
|
}
|
|
@@ -262,7 +274,7 @@ export interface IRouteAction {
|
|
|
262
274
|
|
|
263
275
|
// Additional options for backend-specific settings
|
|
264
276
|
options?: {
|
|
265
|
-
backendProtocol?: 'http1' | 'http2' | 'auto';
|
|
277
|
+
backendProtocol?: 'http1' | 'http2' | 'http3' | 'auto';
|
|
266
278
|
[key: string]: any;
|
|
267
279
|
};
|
|
268
280
|
|
|
@@ -274,9 +286,15 @@ export interface IRouteAction {
|
|
|
274
286
|
|
|
275
287
|
// Socket handler function (when type is 'socket-handler')
|
|
276
288
|
socketHandler?: TSocketHandler;
|
|
277
|
-
|
|
289
|
+
|
|
290
|
+
// Datagram handler function for UDP (when type is 'socket-handler' and transport is 'udp')
|
|
291
|
+
datagramHandler?: TDatagramHandler;
|
|
292
|
+
|
|
278
293
|
// PROXY protocol support (default for all targets, can be overridden per target)
|
|
279
294
|
sendProxyProtocol?: boolean;
|
|
295
|
+
|
|
296
|
+
// UDP-specific settings (session tracking, datagram limits, QUIC config)
|
|
297
|
+
udp?: IRouteUdp;
|
|
280
298
|
}
|
|
281
299
|
|
|
282
300
|
/**
|
|
@@ -356,4 +374,64 @@ export interface IRouteConfig {
|
|
|
356
374
|
enabled?: boolean; // Whether the route is active (default: true)
|
|
357
375
|
}
|
|
358
376
|
|
|
377
|
+
// ─── UDP & QUIC Types ─────────────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Handler for individual UDP datagrams.
|
|
381
|
+
* Called for each incoming datagram on a socket-handler route with UDP transport.
|
|
382
|
+
*/
|
|
383
|
+
export type TDatagramHandler = (
|
|
384
|
+
datagram: Buffer,
|
|
385
|
+
info: IDatagramInfo,
|
|
386
|
+
reply: (data: Buffer) => void
|
|
387
|
+
) => void | Promise<void>;
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Metadata for a received UDP datagram
|
|
391
|
+
*/
|
|
392
|
+
export interface IDatagramInfo {
|
|
393
|
+
/** Source IP address */
|
|
394
|
+
sourceIp: string;
|
|
395
|
+
/** Source port */
|
|
396
|
+
sourcePort: number;
|
|
397
|
+
/** Destination (local) port the datagram arrived on */
|
|
398
|
+
destPort: number;
|
|
399
|
+
/** Route context */
|
|
400
|
+
context: IRouteContext;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* UDP-specific settings for route actions
|
|
405
|
+
*/
|
|
406
|
+
export interface IRouteUdp {
|
|
407
|
+
/** Idle timeout for a UDP session/flow (keyed by src IP:port), in ms. Default: 60000 */
|
|
408
|
+
sessionTimeout?: number;
|
|
409
|
+
/** Max concurrent UDP sessions per source IP. Default: 1000 */
|
|
410
|
+
maxSessionsPerIP?: number;
|
|
411
|
+
/** Max accepted datagram size in bytes. Oversized datagrams are dropped. Default: 65535 */
|
|
412
|
+
maxDatagramSize?: number;
|
|
413
|
+
/** QUIC-specific configuration. When present, traffic is treated as QUIC. */
|
|
414
|
+
quic?: IRouteQuic;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* QUIC and HTTP/3 settings
|
|
419
|
+
*/
|
|
420
|
+
export interface IRouteQuic {
|
|
421
|
+
/** QUIC connection idle timeout in ms. Default: 30000 */
|
|
422
|
+
maxIdleTimeout?: number;
|
|
423
|
+
/** Max concurrent bidirectional streams per QUIC connection. Default: 100 */
|
|
424
|
+
maxConcurrentBidiStreams?: number;
|
|
425
|
+
/** Max concurrent unidirectional streams per QUIC connection. Default: 100 */
|
|
426
|
+
maxConcurrentUniStreams?: number;
|
|
427
|
+
/** Enable HTTP/3 over this QUIC endpoint. Default: false */
|
|
428
|
+
enableHttp3?: boolean;
|
|
429
|
+
/** Port to advertise in Alt-Svc header on TCP HTTP responses. Default: listening port */
|
|
430
|
+
altSvcPort?: number;
|
|
431
|
+
/** Max age for Alt-Svc advertisement in seconds. Default: 86400 */
|
|
432
|
+
altSvcMaxAge?: number;
|
|
433
|
+
/** Initial congestion window size in bytes. Default: implementation-defined */
|
|
434
|
+
initialCongestionWindow?: number;
|
|
435
|
+
}
|
|
436
|
+
|
|
359
437
|
// Configuration moved to models/interfaces.ts as ISmartProxyOptions
|
|
@@ -74,6 +74,11 @@ export class RoutePreprocessor {
|
|
|
74
74
|
return true;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// Datagram handler routes always need TS
|
|
78
|
+
if (route.action.type === 'socket-handler' && route.action.datagramHandler) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
77
82
|
// Routes with dynamic host/port functions need TS
|
|
78
83
|
if (route.action.targets) {
|
|
79
84
|
for (const target of route.action.targets) {
|
|
@@ -92,8 +97,9 @@ export class RoutePreprocessor {
|
|
|
92
97
|
if (needsTsHandling) {
|
|
93
98
|
// Convert to socket-handler type for Rust (Rust will relay back to TS)
|
|
94
99
|
cleanAction.type = 'socket-handler';
|
|
95
|
-
// Remove the JS
|
|
100
|
+
// Remove the JS handlers (not serializable)
|
|
96
101
|
delete (cleanAction as any).socketHandler;
|
|
102
|
+
delete (cleanAction as any).datagramHandler;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
// Clean targets - replace functions with static values
|
|
@@ -218,6 +218,13 @@ export class RustMetricsAdapter implements IMetrics {
|
|
|
218
218
|
},
|
|
219
219
|
};
|
|
220
220
|
|
|
221
|
+
public udp = {
|
|
222
|
+
activeSessions: (): number => this.cache?.activeUdpSessions ?? 0,
|
|
223
|
+
totalSessions: (): number => this.cache?.totalUdpSessions ?? 0,
|
|
224
|
+
datagramsIn: (): number => this.cache?.totalDatagramsIn ?? 0,
|
|
225
|
+
datagramsOut: (): number => this.cache?.totalDatagramsOut ?? 0,
|
|
226
|
+
};
|
|
227
|
+
|
|
221
228
|
public percentiles = {
|
|
222
229
|
connectionDuration: (): { p50: number; p95: number; p99: number } => {
|
|
223
230
|
return { p50: 0, p95: 0, p99: 0 };
|
|
@@ -20,6 +20,7 @@ type TSmartProxyCommands = {
|
|
|
20
20
|
addListeningPort: { params: { port: number }; result: void };
|
|
21
21
|
removeListeningPort: { params: { port: number }; result: void };
|
|
22
22
|
loadCertificate: { params: { domain: string; cert: string; key: string; ca?: string }; result: void };
|
|
23
|
+
setDatagramHandlerRelay: { params: { socketPath: string }; result: void };
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -177,4 +178,8 @@ export class RustProxyBridge extends plugins.EventEmitter {
|
|
|
177
178
|
public async loadCertificate(domain: string, cert: string, key: string, ca?: string): Promise<void> {
|
|
178
179
|
await this.bridge.sendCommand('loadCertificate', { domain, cert, key, ca });
|
|
179
180
|
}
|
|
181
|
+
|
|
182
|
+
public async setDatagramHandlerRelay(socketPath: string): Promise<void> {
|
|
183
|
+
await this.bridge.sendCommand('setDatagramHandlerRelay', { socketPath });
|
|
184
|
+
}
|
|
180
185
|
}
|
|
@@ -5,6 +5,7 @@ import { logger } from '../../core/utils/logger.js';
|
|
|
5
5
|
import { RustProxyBridge } from './rust-proxy-bridge.js';
|
|
6
6
|
import { RoutePreprocessor } from './route-preprocessor.js';
|
|
7
7
|
import { SocketHandlerServer } from './socket-handler-server.js';
|
|
8
|
+
import { DatagramHandlerServer } from './datagram-handler-server.js';
|
|
8
9
|
import { RustMetricsAdapter } from './rust-metrics-adapter.js';
|
|
9
10
|
|
|
10
11
|
// Route management
|
|
@@ -36,6 +37,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
36
37
|
private bridge: RustProxyBridge;
|
|
37
38
|
private preprocessor: RoutePreprocessor;
|
|
38
39
|
private socketHandlerServer: SocketHandlerServer | null = null;
|
|
40
|
+
private datagramHandlerServer: DatagramHandlerServer | null = null;
|
|
39
41
|
private metricsAdapter: RustMetricsAdapter;
|
|
40
42
|
private routeUpdateLock: Mutex;
|
|
41
43
|
private stopping = false;
|
|
@@ -145,6 +147,16 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
145
147
|
await this.socketHandlerServer.start();
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
// Check if any routes need datagram handler relay (UDP socket-handler routes)
|
|
151
|
+
const hasDatagramHandlers = this.settings.routes.some(
|
|
152
|
+
(r) => r.action.type === 'socket-handler' && r.action.datagramHandler
|
|
153
|
+
);
|
|
154
|
+
if (hasDatagramHandlers) {
|
|
155
|
+
const dgPath = `/tmp/smartproxy-dgram-relay-${process.pid}.sock`;
|
|
156
|
+
this.datagramHandlerServer = new DatagramHandlerServer(dgPath, this.preprocessor);
|
|
157
|
+
await this.datagramHandlerServer.start();
|
|
158
|
+
}
|
|
159
|
+
|
|
148
160
|
// Preprocess routes (strip JS functions, convert socket-handler routes)
|
|
149
161
|
const rustRoutes = this.preprocessor.preprocessForRust(this.settings.routes);
|
|
150
162
|
|
|
@@ -167,6 +179,11 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
167
179
|
await this.bridge.setSocketHandlerRelay(this.socketHandlerServer.getSocketPath());
|
|
168
180
|
}
|
|
169
181
|
|
|
182
|
+
// Configure datagram handler relay
|
|
183
|
+
if (this.datagramHandlerServer) {
|
|
184
|
+
await this.bridge.setDatagramHandlerRelay(this.datagramHandlerServer.getSocketPath());
|
|
185
|
+
}
|
|
186
|
+
|
|
170
187
|
// Load default self-signed fallback certificate (domain: '*')
|
|
171
188
|
if (!this.settings.disableDefaultCert) {
|
|
172
189
|
try {
|
|
@@ -240,6 +257,12 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
240
257
|
this.socketHandlerServer = null;
|
|
241
258
|
}
|
|
242
259
|
|
|
260
|
+
// Stop datagram handler relay
|
|
261
|
+
if (this.datagramHandlerServer) {
|
|
262
|
+
await this.datagramHandlerServer.stop();
|
|
263
|
+
this.datagramHandlerServer = null;
|
|
264
|
+
}
|
|
265
|
+
|
|
243
266
|
logger.log('info', 'SmartProxy shutdown complete.', { component: 'smart-proxy' });
|
|
244
267
|
}
|
|
245
268
|
|
|
@@ -280,6 +303,21 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
280
303
|
this.socketHandlerServer = null;
|
|
281
304
|
}
|
|
282
305
|
|
|
306
|
+
// Update datagram handler relay if datagram handler routes changed
|
|
307
|
+
const hasDatagramHandlers = newRoutes.some(
|
|
308
|
+
(r) => r.action.type === 'socket-handler' && r.action.datagramHandler
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
if (hasDatagramHandlers && !this.datagramHandlerServer) {
|
|
312
|
+
const dgPath = `/tmp/smartproxy-dgram-relay-${process.pid}.sock`;
|
|
313
|
+
this.datagramHandlerServer = new DatagramHandlerServer(dgPath, this.preprocessor);
|
|
314
|
+
await this.datagramHandlerServer.start();
|
|
315
|
+
await this.bridge.setDatagramHandlerRelay(this.datagramHandlerServer.getSocketPath());
|
|
316
|
+
} else if (!hasDatagramHandlers && this.datagramHandlerServer) {
|
|
317
|
+
await this.datagramHandlerServer.stop();
|
|
318
|
+
this.datagramHandlerServer = null;
|
|
319
|
+
}
|
|
320
|
+
|
|
283
321
|
// Update stored routes
|
|
284
322
|
this.settings.routes = newRoutes;
|
|
285
323
|
|
|
@@ -123,10 +123,10 @@ export class RouteValidator {
|
|
|
123
123
|
errors.push(`Invalid action type: ${route.action.type}. Must be one of: ${this.VALID_ACTION_TYPES.join(', ')}`);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
// Validate socket-handler
|
|
126
|
+
// Validate socket-handler (TCP socketHandler or UDP datagramHandler)
|
|
127
127
|
if (route.action.type === 'socket-handler') {
|
|
128
|
-
if (typeof route.action.socketHandler !== 'function') {
|
|
129
|
-
errors.push('socket-handler action requires a socketHandler function');
|
|
128
|
+
if (typeof route.action.socketHandler !== 'function' && typeof route.action.datagramHandler !== 'function') {
|
|
129
|
+
errors.push('socket-handler action requires a socketHandler or datagramHandler function');
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -620,10 +620,12 @@ export function validateRouteAction(action: IRouteAction): { valid: boolean; err
|
|
|
620
620
|
}
|
|
621
621
|
|
|
622
622
|
if (action.type === 'socket-handler') {
|
|
623
|
-
if (!action.socketHandler) {
|
|
624
|
-
errors.push('Socket handler function is required for socket-handler action');
|
|
625
|
-
} else if (typeof action.socketHandler !== 'function') {
|
|
623
|
+
if (!action.socketHandler && !action.datagramHandler) {
|
|
624
|
+
errors.push('Socket handler or datagram handler function is required for socket-handler action');
|
|
625
|
+
} else if (action.socketHandler && typeof action.socketHandler !== 'function') {
|
|
626
626
|
errors.push('Socket handler must be a function');
|
|
627
|
+
} else if (action.datagramHandler && typeof action.datagramHandler !== 'function') {
|
|
628
|
+
errors.push('Datagram handler must be a function');
|
|
627
629
|
}
|
|
628
630
|
}
|
|
629
631
|
|
|
@@ -714,7 +716,8 @@ export function hasRequiredPropertiesForAction(route: IRouteConfig, actionType:
|
|
|
714
716
|
route.action.targets.length > 0 &&
|
|
715
717
|
route.action.targets.every(t => t.host && t.port !== undefined);
|
|
716
718
|
case 'socket-handler':
|
|
717
|
-
return !!route.action.socketHandler && typeof route.action.socketHandler === 'function'
|
|
719
|
+
return (!!route.action.socketHandler && typeof route.action.socketHandler === 'function') ||
|
|
720
|
+
(!!route.action.datagramHandler && typeof route.action.datagramHandler === 'function');
|
|
718
721
|
default:
|
|
719
722
|
return false;
|
|
720
723
|
}
|