@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.
Files changed (27) hide show
  1. package/changelog.md +17 -0
  2. package/dist_rust/rustproxy_linux_amd64 +0 -0
  3. package/dist_rust/rustproxy_linux_arm64 +0 -0
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/proxies/smart-proxy/datagram-handler-server.d.ts +46 -0
  6. package/dist_ts/proxies/smart-proxy/datagram-handler-server.js +197 -0
  7. package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +6 -0
  8. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +63 -4
  9. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  10. package/dist_ts/proxies/smart-proxy/route-preprocessor.js +7 -2
  11. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +6 -0
  12. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +7 -1
  13. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +1 -0
  14. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +4 -1
  15. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -0
  16. package/dist_ts/proxies/smart-proxy/smart-proxy.js +31 -1
  17. package/dist_ts/proxies/smart-proxy/utils/route-validator.js +12 -8
  18. package/package.json +1 -1
  19. package/ts/00_commitinfo_data.ts +1 -1
  20. package/ts/proxies/smart-proxy/datagram-handler-server.ts +239 -0
  21. package/ts/proxies/smart-proxy/models/metrics-types.ts +8 -0
  22. package/ts/proxies/smart-proxy/models/route-types.ts +83 -5
  23. package/ts/proxies/smart-proxy/route-preprocessor.ts +7 -1
  24. package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +7 -0
  25. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +5 -0
  26. package/ts/proxies/smart-proxy/smart-proxy.ts +38 -0
  27. 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
- * Port range specification format
23
+ * Transport protocol for route matching
24
24
  */
25
- export type TPortRange = number | number[] | Array<{ from: number; to: number }>;
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 (http includes h2 + websocket upgrades)
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 handler (not serializable)
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
  }