@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.
Files changed (155) hide show
  1. package/changelog.md +109 -0
  2. package/dist_rust/rustproxy +0 -0
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/core/utils/shared-security-manager.d.ts +17 -0
  5. package/dist_ts/core/utils/shared-security-manager.js +66 -1
  6. package/dist_ts/index.d.ts +1 -5
  7. package/dist_ts/index.js +3 -9
  8. package/dist_ts/protocols/common/fragment-handler.js +5 -1
  9. package/dist_ts/proxies/http-proxy/default-certificates.d.ts +54 -0
  10. package/dist_ts/proxies/http-proxy/default-certificates.js +127 -0
  11. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -1
  12. package/dist_ts/proxies/http-proxy/http-proxy.js +9 -14
  13. package/dist_ts/proxies/http-proxy/index.d.ts +5 -1
  14. package/dist_ts/proxies/http-proxy/index.js +6 -2
  15. package/dist_ts/proxies/http-proxy/security-manager.d.ts +4 -12
  16. package/dist_ts/proxies/http-proxy/security-manager.js +66 -99
  17. package/dist_ts/proxies/index.d.ts +1 -5
  18. package/dist_ts/proxies/index.js +2 -6
  19. package/dist_ts/proxies/nftables-proxy/index.d.ts +1 -0
  20. package/dist_ts/proxies/nftables-proxy/index.js +2 -1
  21. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +4 -26
  22. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +84 -236
  23. package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +9 -0
  24. package/dist_ts/proxies/nftables-proxy/utils/index.js +12 -0
  25. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +66 -0
  26. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +131 -0
  27. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +39 -0
  28. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +112 -0
  29. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +59 -0
  30. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +130 -0
  31. package/dist_ts/proxies/smart-proxy/certificate-manager.js +4 -3
  32. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +13 -2
  33. package/dist_ts/proxies/smart-proxy/connection-manager.js +16 -6
  34. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +35 -10
  35. package/dist_ts/proxies/smart-proxy/index.d.ts +5 -10
  36. package/dist_ts/proxies/smart-proxy/index.js +7 -13
  37. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -3
  38. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +17 -0
  39. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +72 -9
  40. package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
  41. package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
  42. package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
  43. package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
  44. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
  45. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
  46. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
  47. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
  48. package/dist_ts/proxies/smart-proxy/security-manager.d.ts +14 -12
  49. package/dist_ts/proxies/smart-proxy/security-manager.js +80 -74
  50. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
  51. package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -622
  52. package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
  53. package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
  54. package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +2 -9
  55. package/dist_ts/proxies/smart-proxy/tls-manager.js +3 -26
  56. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -1
  57. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
  58. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.d.ts +49 -0
  59. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.js +108 -0
  60. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.d.ts +57 -0
  61. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.js +89 -0
  62. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.d.ts +17 -0
  63. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.js +32 -0
  64. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.d.ts +68 -0
  65. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.js +117 -0
  66. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.d.ts +17 -0
  67. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.js +27 -0
  68. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.d.ts +63 -0
  69. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.js +105 -0
  70. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.d.ts +83 -0
  71. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.js +126 -0
  72. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.d.ts +47 -0
  73. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.js +66 -0
  74. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +70 -0
  75. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +287 -0
  76. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.d.ts +46 -0
  77. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.js +67 -0
  78. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +4 -457
  79. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +6 -950
  80. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +2 -2
  81. package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +67 -1
  82. package/dist_ts/proxies/smart-proxy/utils/route-validator.js +251 -3
  83. package/dist_ts/routing/index.d.ts +1 -1
  84. package/dist_ts/routing/index.js +3 -3
  85. package/dist_ts/routing/models/http-types.d.ts +119 -4
  86. package/dist_ts/routing/models/http-types.js +93 -5
  87. package/npmextra.json +12 -6
  88. package/package.json +34 -24
  89. package/readme.hints.md +184 -1
  90. package/readme.md +580 -266
  91. package/ts/00_commitinfo_data.ts +1 -1
  92. package/ts/core/utils/shared-security-manager.ts +98 -13
  93. package/ts/index.ts +4 -12
  94. package/ts/protocols/common/fragment-handler.ts +4 -0
  95. package/ts/proxies/index.ts +1 -9
  96. package/ts/proxies/nftables-proxy/index.ts +1 -0
  97. package/ts/proxies/nftables-proxy/nftables-proxy.ts +116 -290
  98. package/ts/proxies/nftables-proxy/utils/index.ts +38 -0
  99. package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +162 -0
  100. package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +125 -0
  101. package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +156 -0
  102. package/ts/proxies/smart-proxy/index.ts +6 -13
  103. package/ts/proxies/smart-proxy/models/interfaces.ts +6 -5
  104. package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
  105. package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
  106. package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
  107. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
  108. package/ts/proxies/smart-proxy/smart-proxy.ts +282 -800
  109. package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
  110. package/ts/proxies/smart-proxy/utils/index.ts +3 -5
  111. package/ts/proxies/smart-proxy/utils/route-helpers/api-helpers.ts +144 -0
  112. package/ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.ts +124 -0
  113. package/ts/proxies/smart-proxy/utils/route-helpers/http-helpers.ts +40 -0
  114. package/ts/proxies/smart-proxy/utils/route-helpers/https-helpers.ts +163 -0
  115. package/ts/proxies/smart-proxy/utils/route-helpers/index.ts +62 -0
  116. package/ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.ts +154 -0
  117. package/ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.ts +202 -0
  118. package/ts/proxies/smart-proxy/utils/route-helpers/security-helpers.ts +96 -0
  119. package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +337 -0
  120. package/ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.ts +98 -0
  121. package/ts/proxies/smart-proxy/utils/route-helpers.ts +5 -1302
  122. package/ts/proxies/smart-proxy/utils/route-utils.ts +1 -1
  123. package/ts/proxies/smart-proxy/utils/route-validator.ts +274 -4
  124. package/ts/routing/index.ts +2 -2
  125. package/ts/routing/models/http-types.ts +147 -4
  126. package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
  127. package/ts/proxies/http-proxy/connection-pool.ts +0 -228
  128. package/ts/proxies/http-proxy/context-creator.ts +0 -145
  129. package/ts/proxies/http-proxy/function-cache.ts +0 -279
  130. package/ts/proxies/http-proxy/handlers/index.ts +0 -5
  131. package/ts/proxies/http-proxy/http-proxy.ts +0 -675
  132. package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
  133. package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
  134. package/ts/proxies/http-proxy/index.ts +0 -13
  135. package/ts/proxies/http-proxy/models/http-types.ts +0 -148
  136. package/ts/proxies/http-proxy/models/index.ts +0 -5
  137. package/ts/proxies/http-proxy/models/types.ts +0 -125
  138. package/ts/proxies/http-proxy/request-handler.ts +0 -878
  139. package/ts/proxies/http-proxy/security-manager.ts +0 -433
  140. package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
  141. package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
  142. package/ts/proxies/smart-proxy/cert-store.ts +0 -92
  143. package/ts/proxies/smart-proxy/certificate-manager.ts +0 -894
  144. package/ts/proxies/smart-proxy/connection-manager.ts +0 -796
  145. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -187
  146. package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
  147. package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
  148. package/ts/proxies/smart-proxy/port-manager.ts +0 -358
  149. package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1640
  150. package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
  151. package/ts/proxies/smart-proxy/security-manager.ts +0 -257
  152. package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
  153. package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
  154. package/ts/proxies/smart-proxy/tls-manager.ts +0 -207
  155. package/ts/proxies/smart-proxy/utils/route-validators.ts +0 -283
@@ -1,942 +1,424 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { logger } from '../../core/utils/logger.js';
3
- import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
4
-
5
- // Importing required components
6
- import { ConnectionManager } from './connection-manager.js';
7
- import { SecurityManager } from './security-manager.js';
8
- import { TlsManager } from './tls-manager.js';
9
- import { HttpProxyBridge } from './http-proxy-bridge.js';
10
- import { TimeoutManager } from './timeout-manager.js';
11
- import { PortManager } from './port-manager.js';
12
- import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
13
- import { RouteConnectionHandler } from './route-connection-handler.js';
14
- import { NFTablesManager } from './nftables-manager.js';
15
-
16
- // Certificate manager
17
- import { SmartCertManager, type ICertStatus } from './certificate-manager.js';
18
-
19
- // Import types and utilities
20
- import type {
21
- ISmartProxyOptions
22
- } from './models/interfaces.js';
23
- import type { IRouteConfig } from './models/route-types.js';
24
3
 
25
- // Import mutex for route update synchronization
26
- import { Mutex } from './utils/mutex.js';
4
+ // Rust bridge and helpers
5
+ import { RustProxyBridge } from './rust-proxy-bridge.js';
6
+ import { RustBinaryLocator } from './rust-binary-locator.js';
7
+ import { RoutePreprocessor } from './route-preprocessor.js';
8
+ import { SocketHandlerServer } from './socket-handler-server.js';
9
+ import { RustMetricsAdapter } from './rust-metrics-adapter.js';
27
10
 
28
- // Import route validator
11
+ // Route management
12
+ import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
29
13
  import { RouteValidator } from './utils/route-validator.js';
14
+ import { Mutex } from './utils/mutex.js';
30
15
 
31
- // Import route orchestrator for route management
32
- import { RouteOrchestrator } from './route-orchestrator.js';
33
-
34
- // Import ACME state manager
35
- import { AcmeStateManager } from './acme-state-manager.js';
36
-
37
- // Import metrics collector
38
- import { MetricsCollector } from './metrics-collector.js';
16
+ // Types
17
+ import type { ISmartProxyOptions, TSmartProxyCertProvisionObject } from './models/interfaces.js';
18
+ import type { IRouteConfig } from './models/route-types.js';
39
19
  import type { IMetrics } from './models/metrics-types.js';
40
20
 
41
21
  /**
42
- * SmartProxy - Pure route-based API
43
- *
44
- * SmartProxy is a unified proxy system that works with routes to define connection handling behavior.
45
- * Each route contains matching criteria (ports, domains, etc.) and an action to take (forward, redirect, block).
22
+ * SmartProxy - Rust-backed proxy engine with TypeScript configuration API.
46
23
  *
47
- * Configuration is provided through a set of routes, with each route defining:
48
- * - What to match (ports, domains, paths, client IPs)
49
- * - What to do with matching traffic (forward, redirect, block)
50
- * - How to handle TLS (passthrough, terminate, terminate-and-reencrypt)
51
- * - Security settings (IP restrictions, connection limits)
52
- * - Advanced options (timeout, headers, etc.)
24
+ * All networking (TCP, TLS, HTTP reverse proxy, connection management, security,
25
+ * NFTables) is handled by the Rust binary. TypeScript is only:
26
+ * - The npm module interface (types, route helpers)
27
+ * - The thin IPC wrapper (this class)
28
+ * - Socket-handler callback relay (for JS-defined handlers)
29
+ * - Certificate provisioning callbacks (certProvisionFunction)
53
30
  */
54
31
  export class SmartProxy extends plugins.EventEmitter {
55
- // Port manager handles dynamic listener management
56
- private portManager: PortManager;
57
- private connectionLogger: NodeJS.Timeout | null = null;
58
- private isShuttingDown: boolean = false;
59
-
60
- // Component managers
61
- public connectionManager: ConnectionManager;
62
- public securityManager: SecurityManager;
63
- public tlsManager: TlsManager;
64
- public httpProxyBridge: HttpProxyBridge;
65
- public timeoutManager: TimeoutManager;
32
+ public settings: ISmartProxyOptions;
66
33
  public routeManager: RouteManager;
67
- public routeConnectionHandler: RouteConnectionHandler;
68
- public nftablesManager: NFTablesManager;
69
-
70
- // Certificate manager for ACME and static certificates
71
- public certManager: SmartCertManager | null = null;
72
-
73
- // Global challenge route tracking
74
- private globalChallengeRouteActive: boolean = false;
34
+
35
+ private bridge: RustProxyBridge;
36
+ private preprocessor: RoutePreprocessor;
37
+ private socketHandlerServer: SocketHandlerServer | null = null;
38
+ private metricsAdapter: RustMetricsAdapter;
75
39
  private routeUpdateLock: Mutex;
76
- public acmeStateManager: AcmeStateManager;
77
-
78
- // Metrics collector
79
- public metricsCollector: MetricsCollector;
80
-
81
- // Route orchestrator for managing route updates
82
- private routeOrchestrator: RouteOrchestrator;
83
-
84
- // Track port usage across route updates
85
- private portUsageMap: Map<number, Set<string>> = new Map();
86
-
87
- /**
88
- * Constructor for SmartProxy
89
- *
90
- * @param settingsArg Configuration options containing routes and other settings
91
- * Routes define how traffic is matched and handled, with each route having:
92
- * - match: criteria for matching traffic (ports, domains, paths, IPs)
93
- * - action: what to do with matched traffic (forward, redirect, block)
94
- *
95
- * Example:
96
- * ```ts
97
- * const proxy = new SmartProxy({
98
- * routes: [
99
- * {
100
- * match: {
101
- * ports: 443,
102
- * domains: ['example.com', '*.example.com']
103
- * },
104
- * action: {
105
- * type: 'forward',
106
- * target: { host: '10.0.0.1', port: 8443 },
107
- * tls: { mode: 'passthrough' }
108
- * }
109
- * }
110
- * ],
111
- * defaults: {
112
- * target: { host: 'localhost', port: 8080 },
113
- * security: { ipAllowList: ['*'] }
114
- * }
115
- * });
116
- * ```
117
- */
40
+ private stopping = false;
41
+
118
42
  constructor(settingsArg: ISmartProxyOptions) {
119
43
  super();
120
-
121
- // Set reasonable defaults for all settings
44
+
45
+ // Apply defaults
122
46
  this.settings = {
123
47
  ...settingsArg,
124
48
  initialDataTimeout: settingsArg.initialDataTimeout || 120000,
125
49
  socketTimeout: settingsArg.socketTimeout || 3600000,
126
- inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000,
127
50
  maxConnectionLifetime: settingsArg.maxConnectionLifetime || 86400000,
128
51
  inactivityTimeout: settingsArg.inactivityTimeout || 14400000,
129
52
  gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000,
130
- noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
131
- keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
132
- keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000,
133
- maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024,
134
- disableInactivityCheck: settingsArg.disableInactivityCheck || false,
135
- enableKeepAliveProbes:
136
- settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true,
137
- enableDetailedLogging: settingsArg.enableDetailedLogging || false,
138
- enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
139
- enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
140
- allowSessionTicket:
141
- settingsArg.allowSessionTicket !== undefined ? settingsArg.allowSessionTicket : true,
142
53
  maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
143
54
  connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300,
144
55
  keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
145
56
  keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
146
57
  extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000,
147
- httpProxyPort: settingsArg.httpProxyPort || 8443,
148
58
  };
149
-
150
- // Normalize ACME options if provided (support both email and accountEmail)
59
+
60
+ // Normalize ACME options
151
61
  if (this.settings.acme) {
152
- // Support both 'email' and 'accountEmail' fields
153
62
  if (this.settings.acme.accountEmail && !this.settings.acme.email) {
154
63
  this.settings.acme.email = this.settings.acme.accountEmail;
155
64
  }
156
-
157
- // Set reasonable defaults for commonly used fields
158
65
  this.settings.acme = {
159
- enabled: this.settings.acme.enabled !== false, // Enable by default if acme object exists
66
+ enabled: this.settings.acme.enabled !== false,
160
67
  port: this.settings.acme.port || 80,
161
68
  email: this.settings.acme.email,
162
69
  useProduction: this.settings.acme.useProduction || false,
163
70
  renewThresholdDays: this.settings.acme.renewThresholdDays || 30,
164
- autoRenew: this.settings.acme.autoRenew !== false, // Enable by default
71
+ autoRenew: this.settings.acme.autoRenew !== false,
165
72
  certificateStore: this.settings.acme.certificateStore || './certs',
166
73
  skipConfiguredCerts: this.settings.acme.skipConfiguredCerts || false,
167
74
  renewCheckIntervalHours: this.settings.acme.renewCheckIntervalHours || 24,
168
75
  routeForwards: this.settings.acme.routeForwards || [],
169
- ...this.settings.acme // Preserve any additional fields
76
+ ...this.settings.acme,
170
77
  };
171
78
  }
172
-
173
- // Initialize component managers
174
- this.timeoutManager = new TimeoutManager(this);
175
- this.securityManager = new SecurityManager(this);
176
- this.connectionManager = new ConnectionManager(this);
177
-
178
- // Create the route manager with SharedRouteManager API
179
- // Create a logger adapter to match ILogger interface
180
- const loggerAdapter = {
181
- debug: (message: string, data?: any) => logger.log('debug', message, data),
182
- info: (message: string, data?: any) => logger.log('info', message, data),
183
- warn: (message: string, data?: any) => logger.log('warn', message, data),
184
- error: (message: string, data?: any) => logger.log('error', message, data)
185
- };
186
-
187
- // Validate initial routes
188
- if (this.settings.routes && this.settings.routes.length > 0) {
79
+
80
+ // Validate routes
81
+ if (this.settings.routes?.length) {
189
82
  const validation = RouteValidator.validateRoutes(this.settings.routes);
190
83
  if (!validation.valid) {
191
84
  RouteValidator.logValidationErrors(validation.errors);
192
85
  throw new Error(`Initial route validation failed: ${validation.errors.size} route(s) have errors`);
193
86
  }
194
87
  }
195
-
88
+
89
+ // Create logger adapter
90
+ const loggerAdapter = {
91
+ debug: (message: string, data?: any) => logger.log('debug', message, data),
92
+ info: (message: string, data?: any) => logger.log('info', message, data),
93
+ warn: (message: string, data?: any) => logger.log('warn', message, data),
94
+ error: (message: string, data?: any) => logger.log('error', message, data),
95
+ };
96
+
97
+ // Initialize components
196
98
  this.routeManager = new RouteManager({
197
99
  logger: loggerAdapter,
198
100
  enableDetailedLogging: this.settings.enableDetailedLogging,
199
- routes: this.settings.routes
101
+ routes: this.settings.routes,
200
102
  });
201
103
 
202
-
203
- // Create other required components
204
- this.tlsManager = new TlsManager(this);
205
- this.httpProxyBridge = new HttpProxyBridge(this);
206
-
207
- // Initialize connection handler with route support
208
- this.routeConnectionHandler = new RouteConnectionHandler(this);
209
-
210
- // Initialize port manager
211
- this.portManager = new PortManager(this);
212
-
213
- // Initialize NFTablesManager
214
- this.nftablesManager = new NFTablesManager(this);
215
-
216
- // Initialize route update mutex for synchronization
217
- this.routeUpdateLock = new Mutex();
218
-
219
- // Initialize ACME state manager
220
- this.acmeStateManager = new AcmeStateManager();
221
-
222
- // Initialize metrics collector with reference to this SmartProxy instance
223
- this.metricsCollector = new MetricsCollector(this, {
224
- sampleIntervalMs: this.settings.metrics?.sampleIntervalMs,
225
- retentionSeconds: this.settings.metrics?.retentionSeconds
226
- });
227
-
228
- // Initialize route orchestrator for managing route updates
229
- this.routeOrchestrator = new RouteOrchestrator(
230
- this.portManager,
231
- this.routeManager,
232
- this.httpProxyBridge,
233
- this.nftablesManager,
234
- null, // certManager will be set later
235
- loggerAdapter
104
+ this.bridge = new RustProxyBridge();
105
+ this.preprocessor = new RoutePreprocessor();
106
+ this.metricsAdapter = new RustMetricsAdapter(
107
+ this.bridge,
108
+ this.settings.metrics?.sampleIntervalMs ?? 1000
236
109
  );
237
- }
238
-
239
- /**
240
- * The settings for the SmartProxy
241
- */
242
- public settings: ISmartProxyOptions;
243
-
244
- /**
245
- * Helper method to create and configure certificate manager
246
- * This ensures consistent setup including the required ACME callback
247
- */
248
- private async createCertificateManager(
249
- routes: IRouteConfig[],
250
- certStore: string = './certs',
251
- acmeOptions?: any,
252
- initialState?: { challengeRouteActive?: boolean }
253
- ): Promise<SmartCertManager> {
254
- const certManager = new SmartCertManager(routes, certStore, acmeOptions, initialState);
255
-
256
- // Always set up the route update callback for ACME challenges
257
- certManager.setUpdateRoutesCallback(async (routes) => {
258
- await this.updateRoutes(routes);
259
- });
260
-
261
- // Connect with HttpProxy if available
262
- if (this.httpProxyBridge.getHttpProxy()) {
263
- certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
264
- }
265
-
266
- // Set the ACME state manager
267
- certManager.setAcmeStateManager(this.acmeStateManager);
268
-
269
- // Pass down the global ACME config if available
270
- if (this.settings.acme) {
271
- certManager.setGlobalAcmeDefaults(this.settings.acme);
272
- }
273
-
274
- // Pass down the custom certificate provision function if available
275
- if (this.settings.certProvisionFunction) {
276
- certManager.setCertProvisionFunction(this.settings.certProvisionFunction);
277
- }
278
-
279
- // Pass down the fallback to ACME setting
280
- if (this.settings.certProvisionFallbackToAcme !== undefined) {
281
- certManager.setCertProvisionFallbackToAcme(this.settings.certProvisionFallbackToAcme);
282
- }
283
-
284
- await certManager.initialize();
285
- return certManager;
110
+ this.routeUpdateLock = new Mutex();
286
111
  }
287
112
 
288
113
  /**
289
- * Initialize certificate manager
114
+ * Start the proxy.
115
+ * Spawns the Rust binary, configures socket relay if needed, sends routes, handles cert provisioning.
290
116
  */
291
- private async initializeCertificateManager(): Promise<void> {
292
- // Extract global ACME options if any routes use auto certificates
293
- const autoRoutes = this.settings.routes.filter(r =>
294
- r.action.tls?.certificate === 'auto'
295
- );
296
-
297
- if (autoRoutes.length === 0 && !this.hasStaticCertRoutes()) {
298
- logger.log('info', 'No routes require certificate management', { component: 'certificate-manager' });
299
- return;
300
- }
301
-
302
- // Prepare ACME options with priority:
303
- // 1. Use top-level ACME config if available
304
- // 2. Fall back to first auto route's ACME config
305
- // 3. Otherwise use undefined
306
- let acmeOptions: { email?: string; useProduction?: boolean; port?: number } | undefined;
307
-
308
- if (this.settings.acme?.email) {
309
- // Use top-level ACME config
310
- acmeOptions = {
311
- email: this.settings.acme.email,
312
- useProduction: this.settings.acme.useProduction || false,
313
- port: this.settings.acme.port || 80
314
- };
315
- logger.log('info', `Using top-level ACME configuration with email: ${acmeOptions.email}`, { component: 'certificate-manager' });
316
- } else if (autoRoutes.length > 0) {
317
- // Check for route-level ACME config
318
- const routeWithAcme = autoRoutes.find(r => r.action.tls?.acme?.email);
319
- if (routeWithAcme?.action.tls?.acme) {
320
- const routeAcme = routeWithAcme.action.tls.acme;
321
- acmeOptions = {
322
- email: routeAcme.email,
323
- useProduction: routeAcme.useProduction || false,
324
- port: routeAcme.challengePort || 80
325
- };
326
- logger.log('info', `Using route-level ACME configuration from route '${routeWithAcme.name}' with email: ${acmeOptions.email}`, { component: 'certificate-manager' });
327
- }
328
- }
329
-
330
- // Validate we have required configuration
331
- if (autoRoutes.length > 0 && !acmeOptions?.email) {
117
+ public async start(): Promise<void> {
118
+ // Spawn Rust binary
119
+ const spawned = await this.bridge.spawn();
120
+ if (!spawned) {
332
121
  throw new Error(
333
- 'ACME email is required for automatic certificate provisioning. ' +
334
- 'Please provide email in either:\n' +
335
- '1. Top-level "acme" configuration\n' +
336
- '2. Individual route\'s "tls.acme" configuration'
122
+ 'RustProxy binary not found. Set SMARTPROXY_RUST_BINARY env var, install the platform package, ' +
123
+ 'or build locally with: cd rust && cargo build --release'
337
124
  );
338
125
  }
339
-
340
- // Use the helper method to create and configure the certificate manager
341
- this.certManager = await this.createCertificateManager(
342
- this.settings.routes,
343
- this.settings.acme?.certificateStore || './certs',
344
- acmeOptions
345
- );
346
- }
347
-
348
- /**
349
- * Check if we have routes with static certificates
350
- */
351
- private hasStaticCertRoutes(): boolean {
352
- return this.settings.routes.some(r =>
353
- r.action.tls?.certificate &&
354
- r.action.tls.certificate !== 'auto'
355
- );
356
- }
357
-
358
- /**
359
- * Start the proxy server with support for both configuration types
360
- */
361
- public async start() {
362
- // Don't start if already shutting down
363
- if (this.isShuttingDown) {
364
- logger.log('warn', "Cannot start SmartProxy while it's in the shutdown process");
365
- return;
366
- }
367
-
368
- // Validate the route configuration
369
- const configWarnings = this.routeManager.validateConfiguration();
370
-
371
- // Also validate ACME configuration
372
- const acmeWarnings = this.validateAcmeConfiguration();
373
- const allWarnings = [...configWarnings, ...acmeWarnings];
374
-
375
- if (allWarnings.length > 0) {
376
- logger.log('warn', `${allWarnings.length} configuration warnings found`, { count: allWarnings.length });
377
- for (const warning of allWarnings) {
378
- logger.log('warn', `${warning}`);
379
- }
380
- }
381
126
 
382
- // Get listening ports from RouteManager
383
- const listeningPorts = this.routeManager.getListeningPorts();
384
-
385
- // Initialize port usage tracking using RouteOrchestrator
386
- this.portUsageMap = this.routeOrchestrator.updatePortUsageMap(this.settings.routes);
387
-
388
- // Log port usage for startup
389
- logger.log('info', `SmartProxy starting with ${listeningPorts.length} ports: ${listeningPorts.join(', ')}`, {
390
- portCount: listeningPorts.length,
391
- ports: listeningPorts,
392
- component: 'smart-proxy'
127
+ // Handle unexpected exit (only emits error if not intentionally stopping)
128
+ this.bridge.on('exit', (code: number | null, signal: string | null) => {
129
+ if (this.stopping) return;
130
+ logger.log('error', `RustProxy exited unexpectedly (code=${code}, signal=${signal})`, { component: 'smart-proxy' });
131
+ this.emit('error', new Error(`RustProxy exited (code=${code}, signal=${signal})`));
393
132
  });
394
133
 
395
- // Provision NFTables rules for routes that use NFTables
396
- for (const route of this.settings.routes) {
397
- if (route.action.forwardingEngine === 'nftables') {
398
- await this.nftablesManager.provisionRoute(route);
399
- }
400
- }
134
+ // Check if any routes need TS-side handling (socket handlers, dynamic functions)
135
+ const hasHandlerRoutes = this.settings.routes.some(
136
+ (r) =>
137
+ (r.action.type === 'socket-handler' && r.action.socketHandler) ||
138
+ r.action.targets?.some((t) => typeof t.host === 'function' || typeof t.port === 'function')
139
+ );
401
140
 
402
- // Initialize and start HttpProxy if needed - before port binding
403
- if (this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) {
404
- await this.httpProxyBridge.initialize();
405
- await this.httpProxyBridge.start();
141
+ // Start socket handler relay server (but don't tell Rust yet - proxy not started)
142
+ if (hasHandlerRoutes) {
143
+ this.socketHandlerServer = new SocketHandlerServer(this.preprocessor);
144
+ await this.socketHandlerServer.start();
406
145
  }
407
146
 
408
- // Start port listeners using the PortManager BEFORE initializing certificate manager
409
- // This ensures all required ports are bound and ready when adding ACME challenge routes
410
- await this.portManager.addPorts(listeningPorts);
411
-
412
- // Initialize certificate manager AFTER port binding is complete
413
- // This ensures the ACME challenge port is already bound and ready when needed
414
- await this.initializeCertificateManager();
415
-
416
- // Connect certificate manager with HttpProxy if both are available
417
- if (this.certManager && this.httpProxyBridge.getHttpProxy()) {
418
- this.certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
419
- }
147
+ // Preprocess routes (strip JS functions, convert socket-handler routes)
148
+ const rustRoutes = this.preprocessor.preprocessForRust(this.settings.routes);
420
149
 
421
- // Now that ports are listening, provision any required certificates
422
- if (this.certManager) {
423
- logger.log('info', 'Starting certificate provisioning now that ports are ready', { component: 'certificate-manager' });
424
- await this.certManager.provisionAllCertificates();
425
- }
426
-
427
- // Start the metrics collector now that all components are initialized
428
- this.metricsCollector.start();
429
-
430
- // Set up periodic connection logging and inactivity checks
431
- this.connectionLogger = setInterval(() => {
432
- // Immediately return if shutting down
433
- if (this.isShuttingDown) return;
434
-
435
- // Perform inactivity check
436
- this.connectionManager.performInactivityCheck();
437
-
438
- // Log connection statistics
439
- const now = Date.now();
440
- let maxIncoming = 0;
441
- let maxOutgoing = 0;
442
- let tlsConnections = 0;
443
- let nonTlsConnections = 0;
444
- let completedTlsHandshakes = 0;
445
- let pendingTlsHandshakes = 0;
446
- let keepAliveConnections = 0;
447
- let httpProxyConnections = 0;
448
-
449
- // Get connection records for analysis
450
- const connectionRecords = this.connectionManager.getConnections();
451
-
452
- // Analyze active connections
453
- for (const record of connectionRecords.values()) {
454
- // Track connection stats
455
- if (record.isTLS) {
456
- tlsConnections++;
457
- if (record.tlsHandshakeComplete) {
458
- completedTlsHandshakes++;
459
- } else {
460
- pendingTlsHandshakes++;
461
- }
462
- } else {
463
- nonTlsConnections++;
464
- }
150
+ // Build Rust config
151
+ const config = this.buildRustConfig(rustRoutes);
465
152
 
466
- if (record.hasKeepAlive) {
467
- keepAliveConnections++;
468
- }
153
+ // Start the Rust proxy
154
+ await this.bridge.startProxy(config);
469
155
 
470
- if (record.usingNetworkProxy) {
471
- httpProxyConnections++;
472
- }
156
+ // Now that Rust proxy is running, configure socket handler relay
157
+ if (this.socketHandlerServer) {
158
+ await this.bridge.setSocketHandlerRelay(this.socketHandlerServer.getSocketPath());
159
+ }
473
160
 
474
- maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
475
- if (record.outgoingStartTime) {
476
- maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
477
- }
478
- }
161
+ // Handle certProvisionFunction
162
+ await this.provisionCertificatesViaCallback();
479
163
 
480
- // Get termination stats
481
- const terminationStats = this.connectionManager.getTerminationStats();
482
-
483
- // Log detailed stats
484
- logger.log('info', 'Connection statistics', {
485
- activeConnections: connectionRecords.size,
486
- tls: {
487
- total: tlsConnections,
488
- completed: completedTlsHandshakes,
489
- pending: pendingTlsHandshakes
490
- },
491
- nonTls: nonTlsConnections,
492
- keepAlive: keepAliveConnections,
493
- httpProxy: httpProxyConnections,
494
- longestRunning: {
495
- incoming: plugins.prettyMs(maxIncoming),
496
- outgoing: plugins.prettyMs(maxOutgoing)
497
- },
498
- terminationStats: {
499
- incoming: terminationStats.incoming,
500
- outgoing: terminationStats.outgoing
501
- },
502
- component: 'connection-manager'
503
- });
504
- }, this.settings.inactivityCheckInterval || 60000);
505
-
506
- // Make sure the interval doesn't keep the process alive
507
- if (this.connectionLogger.unref) {
508
- this.connectionLogger.unref();
509
- }
510
- }
511
-
512
- /**
513
- * Extract domain configurations from routes for certificate provisioning
514
- *
515
- * Note: This method has been removed as we now work directly with routes
516
- */
517
-
518
- /**
519
- * Stop the proxy server
520
- */
521
- public async stop() {
522
- logger.log('info', 'SmartProxy shutting down...');
523
- this.isShuttingDown = true;
524
- this.portManager.setShuttingDown(true);
525
-
526
- // Stop certificate manager
527
- if (this.certManager) {
528
- await this.certManager.stop();
529
- logger.log('info', 'Certificate manager stopped');
530
- }
531
-
532
- // Stop NFTablesManager
533
- await this.nftablesManager.stop();
534
- logger.log('info', 'NFTablesManager stopped');
535
-
536
- // Stop the connection logger
537
- if (this.connectionLogger) {
538
- clearInterval(this.connectionLogger);
539
- this.connectionLogger = null;
540
- }
164
+ // Start metrics polling
165
+ this.metricsAdapter.startPolling();
541
166
 
542
- // Stop all port listeners
543
- await this.portManager.closeAll();
544
- logger.log('info', 'All servers closed. Cleaning up active connections...');
545
-
546
- // Clean up all active connections
547
- await this.connectionManager.clearConnections();
548
-
549
- // Stop HttpProxy
550
- await this.httpProxyBridge.stop();
551
-
552
- // Clear ACME state manager
553
- this.acmeStateManager.clear();
554
-
555
- // Stop metrics collector
556
- this.metricsCollector.stop();
557
-
558
- // Clean up ProtocolDetector singleton
559
- const detection = await import('../../detection/index.js');
560
- detection.ProtocolDetector.destroy();
561
-
562
- // Flush any pending deduplicated logs
563
- connectionLogDeduplicator.flushAll();
564
-
565
- logger.log('info', 'SmartProxy shutdown complete.');
566
- }
567
-
568
- /**
569
- * Updates the domain configurations for the proxy
570
- *
571
- * Note: This legacy method has been removed. Use updateRoutes instead.
572
- */
573
- public async updateDomainConfigs(): Promise<void> {
574
- logger.log('warn', 'Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.');
575
- throw new Error('updateDomainConfigs() is deprecated - use updateRoutes() instead');
167
+ logger.log('info', 'SmartProxy started (Rust engine)', { component: 'smart-proxy' });
576
168
  }
577
-
169
+
578
170
  /**
579
- * Verify the challenge route has been properly removed from routes
171
+ * Stop the proxy.
580
172
  */
581
- private async verifyChallengeRouteRemoved(): Promise<void> {
582
- const maxRetries = 10;
583
- const retryDelay = 100; // milliseconds
584
-
585
- for (let i = 0; i < maxRetries; i++) {
586
- // Check if the challenge route is still in the active routes
587
- const challengeRouteExists = this.settings.routes.some(r => r.name === 'acme-challenge');
588
-
589
- if (!challengeRouteExists) {
590
- try {
591
- logger.log('info', 'Challenge route successfully removed from routes');
592
- } catch (error) {
593
- // Silently handle logging errors
594
- console.log('[INFO] Challenge route successfully removed from routes');
595
- }
596
- return;
597
- }
598
-
599
- // Wait before retrying
600
- await plugins.smartdelay.delayFor(retryDelay);
601
- }
602
-
603
- const error = `Failed to verify challenge route removal after ${maxRetries} attempts`;
173
+ public async stop(): Promise<void> {
174
+ logger.log('info', 'SmartProxy shutting down...', { component: 'smart-proxy' });
175
+ this.stopping = true;
176
+
177
+ // Stop metrics polling
178
+ this.metricsAdapter.stopPolling();
179
+
180
+ // Remove exit listener before killing to avoid spurious error events
181
+ this.bridge.removeAllListeners('exit');
182
+
183
+ // Stop Rust proxy
604
184
  try {
605
- logger.log('error', error);
606
- } catch (logError) {
607
- // Silently handle logging errors
608
- console.log(`[ERROR] ${error}`);
185
+ await this.bridge.stopProxy();
186
+ } catch {
187
+ // Ignore if already stopped
188
+ }
189
+ this.bridge.kill();
190
+
191
+ // Stop socket handler relay
192
+ if (this.socketHandlerServer) {
193
+ await this.socketHandlerServer.stop();
194
+ this.socketHandlerServer = null;
609
195
  }
610
- throw new Error(error);
196
+
197
+ logger.log('info', 'SmartProxy shutdown complete.', { component: 'smart-proxy' });
611
198
  }
612
-
199
+
613
200
  /**
614
- * Update routes with new configuration
615
- *
616
- * This method replaces the current route configuration with the provided routes.
617
- * It also provisions certificates for routes that require TLS termination and have
618
- * `certificate: 'auto'` set in their TLS configuration.
619
- *
620
- * @param newRoutes Array of route configurations to use
621
- *
622
- * Example:
623
- * ```ts
624
- * proxy.updateRoutes([
625
- * {
626
- * match: { ports: 443, domains: 'secure.example.com' },
627
- * action: {
628
- * type: 'forward',
629
- * target: { host: '10.0.0.1', port: 8443 },
630
- * tls: { mode: 'terminate', certificate: 'auto' }
631
- * }
632
- * }
633
- * ]);
634
- * ```
201
+ * Update routes atomically.
635
202
  */
636
203
  public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
637
204
  return this.routeUpdateLock.runExclusive(async () => {
638
- try {
639
- logger.log('info', `Updating routes (${newRoutes.length} routes)`, {
640
- routeCount: newRoutes.length,
641
- component: 'smart-proxy'
642
- });
643
- } catch (error) {
644
- // Silently handle logging errors
645
- console.log(`[INFO] Updating routes (${newRoutes.length} routes)`);
205
+ // Validate
206
+ const validation = RouteValidator.validateRoutes(newRoutes);
207
+ if (!validation.valid) {
208
+ RouteValidator.logValidationErrors(validation.errors);
209
+ throw new Error(`Route validation failed: ${validation.errors.size} route(s) have errors`);
646
210
  }
647
211
 
648
- // Update route orchestrator dependencies if cert manager changed
649
- if (this.certManager && !this.routeOrchestrator.getCertManager()) {
650
- this.routeOrchestrator.setCertManager(this.certManager);
651
- }
652
-
653
- // Delegate the complex route update logic to RouteOrchestrator
654
- const updateResult = await this.routeOrchestrator.updateRoutes(
655
- this.settings.routes,
656
- newRoutes,
657
- {
658
- acmePort: this.settings.acme?.port || 80,
659
- acmeOptions: this.certManager?.getAcmeOptions(),
660
- acmeState: this.certManager?.getState(),
661
- globalChallengeRouteActive: this.globalChallengeRouteActive,
662
- createCertificateManager: this.createCertificateManager.bind(this),
663
- verifyChallengeRouteRemoved: this.verifyChallengeRouteRemoved.bind(this)
664
- }
212
+ // Preprocess for Rust
213
+ const rustRoutes = this.preprocessor.preprocessForRust(newRoutes);
214
+
215
+ // Send to Rust
216
+ await this.bridge.updateRoutes(rustRoutes);
217
+
218
+ // Update local route manager
219
+ this.routeManager.updateRoutes(newRoutes);
220
+
221
+ // Update socket handler relay if handler routes changed
222
+ const hasHandlerRoutes = newRoutes.some(
223
+ (r) =>
224
+ (r.action.type === 'socket-handler' && r.action.socketHandler) ||
225
+ r.action.targets?.some((t) => typeof t.host === 'function' || typeof t.port === 'function')
665
226
  );
666
-
667
- // Update settings with the new routes
668
- this.settings.routes = newRoutes;
669
-
670
- // Update global state from orchestrator results
671
- this.globalChallengeRouteActive = updateResult.newChallengeRouteActive;
672
-
673
- // Update port usage map from orchestrator
674
- this.portUsageMap = updateResult.portUsageMap;
675
-
676
- // If certificate manager was recreated, update our reference
677
- if (updateResult.newCertManager) {
678
- this.certManager = updateResult.newCertManager;
679
- // Update the orchestrator's reference too
680
- this.routeOrchestrator.setCertManager(this.certManager);
227
+
228
+ if (hasHandlerRoutes && !this.socketHandlerServer) {
229
+ this.socketHandlerServer = new SocketHandlerServer(this.preprocessor);
230
+ await this.socketHandlerServer.start();
231
+ await this.bridge.setSocketHandlerRelay(this.socketHandlerServer.getSocketPath());
232
+ } else if (!hasHandlerRoutes && this.socketHandlerServer) {
233
+ await this.socketHandlerServer.stop();
234
+ this.socketHandlerServer = null;
681
235
  }
236
+
237
+ // Update stored routes
238
+ this.settings.routes = newRoutes;
239
+
240
+ // Handle cert provisioning for new routes
241
+ await this.provisionCertificatesViaCallback();
242
+
243
+ logger.log('info', `Routes updated (${newRoutes.length} routes)`, { component: 'smart-proxy' });
682
244
  });
683
245
  }
684
-
246
+
685
247
  /**
686
- * Manually provision a certificate for a route
248
+ * Provision a certificate for a named route.
687
249
  */
688
250
  public async provisionCertificate(routeName: string): Promise<void> {
689
- if (!this.certManager) {
690
- throw new Error('Certificate manager not initialized');
691
- }
692
-
693
- const route = this.settings.routes.find(r => r.name === routeName);
694
- if (!route) {
695
- throw new Error(`Route ${routeName} not found`);
696
- }
697
-
698
- await this.certManager.provisionCertificate(route);
251
+ await this.bridge.provisionCertificate(routeName);
699
252
  }
700
253
 
701
- // Port usage tracking methods moved to RouteOrchestrator
702
-
703
254
  /**
704
- * Force renewal of a certificate
255
+ * Force renewal of a certificate.
705
256
  */
706
257
  public async renewCertificate(routeName: string): Promise<void> {
707
- if (!this.certManager) {
708
- throw new Error('Certificate manager not initialized');
709
- }
710
-
711
- await this.certManager.renewCertificate(routeName);
258
+ await this.bridge.renewCertificate(routeName);
712
259
  }
713
-
260
+
714
261
  /**
715
- * Get certificate status for a route
262
+ * Get certificate status for a route (async - calls Rust).
716
263
  */
717
- public getCertificateStatus(routeName: string): ICertStatus | undefined {
718
- if (!this.certManager) {
719
- return undefined;
720
- }
721
-
722
- return this.certManager.getCertificateStatus(routeName);
264
+ public async getCertificateStatus(routeName: string): Promise<any> {
265
+ return this.bridge.getCertificateStatus(routeName);
723
266
  }
724
-
267
+
725
268
  /**
726
- * Get proxy metrics with clean API
727
- *
728
- * @returns IMetrics interface with grouped metrics methods
269
+ * Get the metrics interface.
729
270
  */
730
271
  public getMetrics(): IMetrics {
731
- return this.metricsCollector;
272
+ return this.metricsAdapter;
732
273
  }
733
-
274
+
734
275
  /**
735
- * Validates if a domain name is valid for certificate issuance
276
+ * Get statistics (async - calls Rust).
736
277
  */
737
- private isValidDomain(domain: string): boolean {
738
- // Very basic domain validation
739
- if (!domain || domain.length === 0) {
740
- return false;
741
- }
742
-
743
- // Check for wildcard domains (they can't get ACME certs)
744
- if (domain.includes('*')) {
745
- logger.log('warn', `Wildcard domains like "${domain}" are not supported for automatic ACME certificates`, { domain, component: 'certificate-manager' });
746
- return false;
747
- }
748
-
749
- // Check if domain has at least one dot and no invalid characters
750
- const validDomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
751
- if (!validDomainRegex.test(domain)) {
752
- logger.log('warn', `Domain "${domain}" has invalid format for certificate issuance`, { domain, component: 'certificate-manager' });
753
- return false;
754
- }
755
-
756
- return true;
278
+ public async getStatistics(): Promise<any> {
279
+ return this.bridge.getStatistics();
757
280
  }
758
-
281
+
759
282
  /**
760
- * Add a new listening port without changing the route configuration
761
- *
762
- * This allows you to add a port listener without updating routes.
763
- * Useful for preparing to listen on a port before adding routes for it.
764
- *
765
- * @param port The port to start listening on
766
- * @returns Promise that resolves when the port is listening
283
+ * Add a listening port at runtime.
767
284
  */
768
285
  public async addListeningPort(port: number): Promise<void> {
769
- return this.portManager.addPort(port);
286
+ await this.bridge.addListeningPort(port);
770
287
  }
771
288
 
772
289
  /**
773
- * Stop listening on a specific port without changing the route configuration
774
- *
775
- * This allows you to stop a port listener without updating routes.
776
- * Useful for temporary maintenance or port changes.
777
- *
778
- * @param port The port to stop listening on
779
- * @returns Promise that resolves when the port is closed
290
+ * Remove a listening port at runtime.
780
291
  */
781
292
  public async removeListeningPort(port: number): Promise<void> {
782
- return this.portManager.removePort(port);
293
+ await this.bridge.removeListeningPort(port);
783
294
  }
784
295
 
785
296
  /**
786
- * Get a list of all ports currently being listened on
787
- *
788
- * @returns Array of port numbers
297
+ * Get all currently listening ports (async - calls Rust).
789
298
  */
790
- public getListeningPorts(): number[] {
791
- return this.portManager.getListeningPorts();
299
+ public async getListeningPorts(): Promise<number[]> {
300
+ if (!this.bridge.running) return [];
301
+ return this.bridge.getListeningPorts();
792
302
  }
793
303
 
794
304
  /**
795
- * Get statistics about current connections
796
- */
797
- public getStatistics(): any {
798
- const connectionRecords = this.connectionManager.getConnections();
799
- const terminationStats = this.connectionManager.getTerminationStats();
800
-
801
- let tlsConnections = 0;
802
- let nonTlsConnections = 0;
803
- let keepAliveConnections = 0;
804
- let httpProxyConnections = 0;
805
-
806
- // Analyze active connections
807
- for (const record of connectionRecords.values()) {
808
- if (record.isTLS) tlsConnections++;
809
- else nonTlsConnections++;
810
- if (record.hasKeepAlive) keepAliveConnections++;
811
- if (record.usingNetworkProxy) httpProxyConnections++;
812
- }
813
-
814
- return {
815
- activeConnections: connectionRecords.size,
816
- tlsConnections,
817
- nonTlsConnections,
818
- keepAliveConnections,
819
- httpProxyConnections,
820
- terminationStats,
821
- acmeEnabled: !!this.certManager,
822
- port80HandlerPort: this.certManager ? 80 : null,
823
- routeCount: this.settings.routes.length,
824
- activePorts: this.portManager.getListeningPorts().length,
825
- listeningPorts: this.portManager.getListeningPorts()
826
- };
827
- }
828
-
829
- /**
830
- * Get a list of eligible domains for ACME certificates
305
+ * Get eligible domains for ACME certificates (sync - reads local routes).
831
306
  */
832
307
  public getEligibleDomainsForCertificates(): string[] {
833
308
  const domains: string[] = [];
834
-
835
- // Get domains from routes
836
- const routes = this.settings.routes || [];
837
-
838
- for (const route of routes) {
309
+ for (const route of this.settings.routes || []) {
839
310
  if (!route.match.domains) continue;
840
-
841
- // Skip routes without TLS termination or auto certificates
842
- if (route.action.type !== 'forward' ||
843
- !route.action.tls ||
844
- route.action.tls.mode === 'passthrough' ||
845
- route.action.tls.certificate !== 'auto') continue;
846
-
847
- const routeDomains = Array.isArray(route.match.domains)
848
- ? route.match.domains
849
- : [route.match.domains];
850
-
851
- // Skip domains that can't be used with ACME
852
- const eligibleDomains = routeDomains.filter(domain =>
853
- !domain.includes('*') && this.isValidDomain(domain)
854
- );
855
-
856
- domains.push(...eligibleDomains);
311
+ if (
312
+ route.action.type !== 'forward' ||
313
+ !route.action.tls ||
314
+ route.action.tls.mode === 'passthrough' ||
315
+ route.action.tls.certificate !== 'auto'
316
+ )
317
+ continue;
318
+
319
+ const routeDomains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains];
320
+ const eligible = routeDomains.filter((d) => !d.includes('*') && this.isValidDomain(d));
321
+ domains.push(...eligible);
857
322
  }
858
-
859
- // Legacy mode is no longer supported
860
-
861
323
  return domains;
862
324
  }
863
-
325
+
864
326
  /**
865
- * Get NFTables status
327
+ * Get NFTables status (async - calls Rust).
866
328
  */
867
329
  public async getNfTablesStatus(): Promise<Record<string, any>> {
868
- return this.nftablesManager.getStatus();
330
+ return this.bridge.getNftablesStatus();
869
331
  }
870
-
332
+
333
+ // --- Private helpers ---
334
+
871
335
  /**
872
- * Validate ACME configuration
336
+ * Build the Rust configuration object from TS settings.
873
337
  */
874
- private validateAcmeConfiguration(): string[] {
875
- const warnings: string[] = [];
876
-
877
- // Check for routes with certificate: 'auto'
878
- const autoRoutes = this.settings.routes.filter(r =>
879
- r.action.tls?.certificate === 'auto'
880
- );
881
-
882
- if (autoRoutes.length === 0) {
883
- return warnings;
884
- }
885
-
886
- // Check if we have ACME email configuration
887
- const hasTopLevelEmail = this.settings.acme?.email;
888
- const routesWithEmail = autoRoutes.filter(r => r.action.tls?.acme?.email);
889
-
890
- if (!hasTopLevelEmail && routesWithEmail.length === 0) {
891
- warnings.push(
892
- 'Routes with certificate: "auto" require ACME email configuration. ' +
893
- 'Add email to either top-level "acme" config or individual route\'s "tls.acme" config.'
894
- );
895
- }
896
-
897
- // Check for port 80 availability for challenges
898
- if (autoRoutes.length > 0) {
899
- const challengePort = this.settings.acme?.port || 80;
900
- const portsInUse = this.routeManager.getListeningPorts();
901
-
902
- if (!portsInUse.includes(challengePort)) {
903
- warnings.push(
904
- `Port ${challengePort} is not configured for any routes but is needed for ACME challenges. ` +
905
- `Add a route listening on port ${challengePort} or ensure it's accessible for HTTP-01 challenges.`
906
- );
907
- }
908
- }
909
-
910
- // Check for mismatched environments
911
- if (this.settings.acme?.useProduction) {
912
- const stagingRoutes = autoRoutes.filter(r =>
913
- r.action.tls?.acme?.useProduction === false
914
- );
915
- if (stagingRoutes.length > 0) {
916
- warnings.push(
917
- 'Top-level ACME uses production but some routes use staging. ' +
918
- 'Consider aligning environments to avoid certificate issues.'
919
- );
920
- }
921
- }
922
-
923
- // Check for wildcard domains with auto certificates
924
- for (const route of autoRoutes) {
925
- const domains = Array.isArray(route.match.domains)
926
- ? route.match.domains
927
- : [route.match.domains];
928
-
929
- const wildcardDomains = domains.filter(d => d?.includes('*'));
930
- if (wildcardDomains.length > 0) {
931
- warnings.push(
932
- `Route "${route.name}" has wildcard domain(s) ${wildcardDomains.join(', ')} ` +
933
- 'with certificate: "auto". Wildcard certificates require DNS-01 challenges, ' +
934
- 'which are not currently supported. Use static certificates instead.'
935
- );
338
+ private buildRustConfig(routes: IRouteConfig[]): any {
339
+ return {
340
+ routes,
341
+ defaults: this.settings.defaults,
342
+ acme: this.settings.acme
343
+ ? {
344
+ enabled: this.settings.acme.enabled,
345
+ email: this.settings.acme.email,
346
+ useProduction: this.settings.acme.useProduction,
347
+ port: this.settings.acme.port,
348
+ renewThresholdDays: this.settings.acme.renewThresholdDays,
349
+ autoRenew: this.settings.acme.autoRenew,
350
+ certificateStore: this.settings.acme.certificateStore,
351
+ renewCheckIntervalHours: this.settings.acme.renewCheckIntervalHours,
352
+ }
353
+ : undefined,
354
+ connectionTimeout: this.settings.connectionTimeout,
355
+ initialDataTimeout: this.settings.initialDataTimeout,
356
+ socketTimeout: this.settings.socketTimeout,
357
+ maxConnectionLifetime: this.settings.maxConnectionLifetime,
358
+ gracefulShutdownTimeout: this.settings.gracefulShutdownTimeout,
359
+ maxConnectionsPerIp: this.settings.maxConnectionsPerIP,
360
+ connectionRateLimitPerMinute: this.settings.connectionRateLimitPerMinute,
361
+ keepAliveTreatment: this.settings.keepAliveTreatment,
362
+ keepAliveInactivityMultiplier: this.settings.keepAliveInactivityMultiplier,
363
+ extendedKeepAliveLifetime: this.settings.extendedKeepAliveLifetime,
364
+ acceptProxyProtocol: this.settings.acceptProxyProtocol,
365
+ sendProxyProtocol: this.settings.sendProxyProtocol,
366
+ };
367
+ }
368
+
369
+ /**
370
+ * For routes with certificate: 'auto', call certProvisionFunction if set.
371
+ * If the callback returns a cert object, load it into Rust.
372
+ * If it returns 'http01', let Rust handle ACME.
373
+ */
374
+ private async provisionCertificatesViaCallback(): Promise<void> {
375
+ const provisionFn = this.settings.certProvisionFunction;
376
+ if (!provisionFn) return;
377
+
378
+ for (const route of this.settings.routes) {
379
+ if (route.action.tls?.certificate !== 'auto') continue;
380
+ if (!route.match.domains) continue;
381
+
382
+ const domains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains];
383
+
384
+ for (const domain of domains) {
385
+ if (domain.includes('*')) continue;
386
+
387
+ try {
388
+ const result: TSmartProxyCertProvisionObject = await provisionFn(domain);
389
+
390
+ if (result === 'http01') {
391
+ // Rust handles ACME for this domain
392
+ continue;
393
+ }
394
+
395
+ // Got a static cert object - load it into Rust
396
+ if (result && typeof result === 'object') {
397
+ const certObj = result as plugins.tsclass.network.ICert;
398
+ await this.bridge.loadCertificate(
399
+ domain,
400
+ certObj.publicKey,
401
+ certObj.privateKey,
402
+ );
403
+ logger.log('info', `Certificate loaded via provision function for ${domain}`, { component: 'smart-proxy' });
404
+ }
405
+ } catch (err: any) {
406
+ logger.log('warn', `certProvisionFunction failed for ${domain}: ${err.message}`, { component: 'smart-proxy' });
407
+
408
+ // Fallback to ACME if enabled
409
+ if (this.settings.certProvisionFallbackToAcme !== false) {
410
+ logger.log('info', `Falling back to ACME for ${domain}`, { component: 'smart-proxy' });
411
+ }
412
+ }
936
413
  }
937
414
  }
938
-
939
- return warnings;
940
415
  }
941
416
 
942
- }
417
+ private isValidDomain(domain: string): boolean {
418
+ if (!domain || domain.length === 0) return false;
419
+ if (domain.includes('*')) return false;
420
+ const validDomainRegex =
421
+ /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
422
+ return validDomainRegex.test(domain);
423
+ }
424
+ }