@push.rocks/smartproxy 21.1.3 → 21.1.5

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 (38) hide show
  1. package/changelog.md +23 -0
  2. package/dist_ts/00_commitinfo_data.js +2 -2
  3. package/dist_ts/core/models/socket-augmentation.d.ts +3 -0
  4. package/dist_ts/core/models/socket-augmentation.js +1 -1
  5. package/dist_ts/core/utils/socket-tracker.d.ts +16 -0
  6. package/dist_ts/core/utils/socket-tracker.js +49 -0
  7. package/dist_ts/detection/detectors/http-detector.js +14 -2
  8. package/dist_ts/detection/protocol-detector.js +2 -1
  9. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +5 -1
  10. package/dist_ts/proxies/http-proxy/http-proxy.js +63 -39
  11. package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +5 -0
  12. package/dist_ts/proxies/smart-proxy/certificate-manager.js +20 -6
  13. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -0
  14. package/dist_ts/proxies/smart-proxy/index.js +2 -1
  15. package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +4 -0
  16. package/dist_ts/proxies/smart-proxy/metrics-collector.js +52 -7
  17. package/dist_ts/proxies/smart-proxy/route-orchestrator.d.ts +56 -0
  18. package/dist_ts/proxies/smart-proxy/route-orchestrator.js +204 -0
  19. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -11
  20. package/dist_ts/proxies/smart-proxy/smart-proxy.js +48 -237
  21. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +42 -7
  22. package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +58 -0
  23. package/dist_ts/proxies/smart-proxy/utils/route-validator.js +405 -0
  24. package/package.json +3 -2
  25. package/readme.md +321 -828
  26. package/ts/00_commitinfo_data.ts +1 -1
  27. package/ts/core/models/socket-augmentation.ts +5 -0
  28. package/ts/core/utils/socket-tracker.ts +63 -0
  29. package/ts/detection/detectors/http-detector.ts +14 -1
  30. package/ts/detection/protocol-detector.ts +1 -0
  31. package/ts/proxies/http-proxy/http-proxy.ts +73 -48
  32. package/ts/proxies/smart-proxy/certificate-manager.ts +21 -5
  33. package/ts/proxies/smart-proxy/index.ts +1 -0
  34. package/ts/proxies/smart-proxy/metrics-collector.ts +58 -6
  35. package/ts/proxies/smart-proxy/route-orchestrator.ts +297 -0
  36. package/ts/proxies/smart-proxy/smart-proxy.ts +66 -270
  37. package/ts/proxies/smart-proxy/utils/route-helpers.ts +45 -6
  38. package/ts/proxies/smart-proxy/utils/route-validator.ts +453 -0
@@ -0,0 +1,297 @@
1
+ import { logger } from '../../core/utils/logger.js';
2
+ import type { IRouteConfig } from './models/route-types.js';
3
+ import type { ILogger } from '../http-proxy/models/types.js';
4
+ import { RouteValidator } from './utils/route-validator.js';
5
+ import { Mutex } from './utils/mutex.js';
6
+ import type { PortManager } from './port-manager.js';
7
+ import type { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
8
+ import type { HttpProxyBridge } from './http-proxy-bridge.js';
9
+ import type { NFTablesManager } from './nftables-manager.js';
10
+ import type { SmartCertManager } from './certificate-manager.js';
11
+
12
+ /**
13
+ * Orchestrates route updates and coordination between components
14
+ * Extracted from SmartProxy to reduce class complexity
15
+ */
16
+ export class RouteOrchestrator {
17
+ private routeUpdateLock: Mutex;
18
+ private portManager: PortManager;
19
+ private routeManager: RouteManager;
20
+ private httpProxyBridge: HttpProxyBridge;
21
+ private nftablesManager: NFTablesManager;
22
+ private certManager: SmartCertManager | null = null;
23
+ private logger: ILogger;
24
+
25
+ constructor(
26
+ portManager: PortManager,
27
+ routeManager: RouteManager,
28
+ httpProxyBridge: HttpProxyBridge,
29
+ nftablesManager: NFTablesManager,
30
+ certManager: SmartCertManager | null,
31
+ logger: ILogger
32
+ ) {
33
+ this.portManager = portManager;
34
+ this.routeManager = routeManager;
35
+ this.httpProxyBridge = httpProxyBridge;
36
+ this.nftablesManager = nftablesManager;
37
+ this.certManager = certManager;
38
+ this.logger = logger;
39
+ this.routeUpdateLock = new Mutex();
40
+ }
41
+
42
+ /**
43
+ * Set or update certificate manager reference
44
+ */
45
+ public setCertManager(certManager: SmartCertManager | null): void {
46
+ this.certManager = certManager;
47
+ }
48
+
49
+ /**
50
+ * Get certificate manager reference
51
+ */
52
+ public getCertManager(): SmartCertManager | null {
53
+ return this.certManager;
54
+ }
55
+
56
+ /**
57
+ * Update routes with validation and coordination
58
+ */
59
+ public async updateRoutes(
60
+ oldRoutes: IRouteConfig[],
61
+ newRoutes: IRouteConfig[],
62
+ options: {
63
+ acmePort?: number;
64
+ acmeOptions?: any;
65
+ acmeState?: any;
66
+ globalChallengeRouteActive?: boolean;
67
+ createCertificateManager?: (
68
+ routes: IRouteConfig[],
69
+ certStore: string,
70
+ acmeOptions?: any,
71
+ initialState?: any
72
+ ) => Promise<SmartCertManager>;
73
+ verifyChallengeRouteRemoved?: () => Promise<void>;
74
+ } = {}
75
+ ): Promise<{
76
+ portUsageMap: Map<number, Set<string>>;
77
+ newChallengeRouteActive: boolean;
78
+ newCertManager?: SmartCertManager;
79
+ }> {
80
+ return this.routeUpdateLock.runExclusive(async () => {
81
+ // Validate route configurations
82
+ const validation = RouteValidator.validateRoutes(newRoutes);
83
+ if (!validation.valid) {
84
+ RouteValidator.logValidationErrors(validation.errors);
85
+ throw new Error(`Route validation failed: ${validation.errors.size} route(s) have errors`);
86
+ }
87
+
88
+ // Track port usage before and after updates
89
+ const oldPortUsage = this.updatePortUsageMap(oldRoutes);
90
+ const newPortUsage = this.updatePortUsageMap(newRoutes);
91
+
92
+ // Get the lists of currently listening ports and new ports needed
93
+ const currentPorts = new Set(this.portManager.getListeningPorts());
94
+ const newPortsSet = new Set(newPortUsage.keys());
95
+
96
+ // Log the port usage for debugging
97
+ this.logger.debug(`Current listening ports: ${Array.from(currentPorts).join(', ')}`);
98
+ this.logger.debug(`Ports needed for new routes: ${Array.from(newPortsSet).join(', ')}`);
99
+
100
+ // Find orphaned ports - ports that no longer have any routes
101
+ const orphanedPorts = this.findOrphanedPorts(oldPortUsage, newPortUsage);
102
+
103
+ // Find new ports that need binding (only ports that we aren't already listening on)
104
+ const newBindingPorts = Array.from(newPortsSet).filter(p => !currentPorts.has(p));
105
+
106
+ // Check for ACME challenge port to give it special handling
107
+ const acmePort = options.acmePort || 80;
108
+ const acmePortNeeded = newPortsSet.has(acmePort);
109
+ const acmePortListed = newBindingPorts.includes(acmePort);
110
+
111
+ if (acmePortNeeded && acmePortListed) {
112
+ this.logger.info(`Adding ACME challenge port ${acmePort} to routes`);
113
+ }
114
+
115
+ // Update NFTables routes
116
+ await this.updateNfTablesRoutes(oldRoutes, newRoutes);
117
+
118
+ // Update routes in RouteManager
119
+ this.routeManager.updateRoutes(newRoutes);
120
+
121
+ // Release orphaned ports first to free resources
122
+ if (orphanedPorts.length > 0) {
123
+ this.logger.info(`Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`);
124
+ await this.portManager.removePorts(orphanedPorts);
125
+ }
126
+
127
+ // Add new ports if needed
128
+ if (newBindingPorts.length > 0) {
129
+ this.logger.info(`Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`);
130
+
131
+ // Handle port binding with improved error recovery
132
+ try {
133
+ await this.portManager.addPorts(newBindingPorts);
134
+ } catch (error) {
135
+ // Special handling for port binding errors
136
+ if ((error as any).code === 'EADDRINUSE') {
137
+ const port = (error as any).port || newBindingPorts[0];
138
+ const isAcmePort = port === acmePort;
139
+
140
+ if (isAcmePort) {
141
+ this.logger.warn(`Could not bind to ACME challenge port ${port}. It may be in use by another application.`);
142
+
143
+ // Re-throw with more helpful message
144
+ throw new Error(
145
+ `ACME challenge port ${port} is already in use by another application. ` +
146
+ `Configure a different port in settings.acme.port (e.g., 8080) or free up port ${port}.`
147
+ );
148
+ }
149
+ }
150
+
151
+ // Re-throw the original error for other cases
152
+ throw error;
153
+ }
154
+ }
155
+
156
+ // If HttpProxy is initialized, resync the configurations
157
+ if (this.httpProxyBridge.getHttpProxy()) {
158
+ await this.httpProxyBridge.syncRoutesToHttpProxy(newRoutes);
159
+ }
160
+
161
+ // Update certificate manager if needed
162
+ let newCertManager: SmartCertManager | undefined;
163
+ let newChallengeRouteActive = options.globalChallengeRouteActive || false;
164
+
165
+ if (this.certManager && options.createCertificateManager) {
166
+ const existingAcmeOptions = this.certManager.getAcmeOptions();
167
+ const existingState = this.certManager.getState();
168
+
169
+ // Store global state before stopping
170
+ newChallengeRouteActive = existingState.challengeRouteActive;
171
+
172
+ // Keep certificate manager routes in sync before stopping
173
+ this.certManager.setRoutes(newRoutes);
174
+
175
+ await this.certManager.stop();
176
+
177
+ // Verify the challenge route has been properly removed
178
+ if (options.verifyChallengeRouteRemoved) {
179
+ await options.verifyChallengeRouteRemoved();
180
+ }
181
+
182
+ // Create new certificate manager with preserved state
183
+ newCertManager = await options.createCertificateManager(
184
+ newRoutes,
185
+ './certs',
186
+ existingAcmeOptions,
187
+ { challengeRouteActive: newChallengeRouteActive }
188
+ );
189
+
190
+ this.certManager = newCertManager;
191
+ }
192
+
193
+ return {
194
+ portUsageMap: newPortUsage,
195
+ newChallengeRouteActive,
196
+ newCertManager
197
+ };
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Update port usage map based on the provided routes
203
+ */
204
+ public updatePortUsageMap(routes: IRouteConfig[]): Map<number, Set<string>> {
205
+ const portUsage = new Map<number, Set<string>>();
206
+
207
+ for (const route of routes) {
208
+ // Get the ports for this route
209
+ const portsConfig = Array.isArray(route.match.ports)
210
+ ? route.match.ports
211
+ : [route.match.ports];
212
+
213
+ // Expand port range objects to individual port numbers
214
+ const expandedPorts: number[] = [];
215
+ for (const portConfig of portsConfig) {
216
+ if (typeof portConfig === 'number') {
217
+ expandedPorts.push(portConfig);
218
+ } else if (typeof portConfig === 'object' && 'from' in portConfig && 'to' in portConfig) {
219
+ // Expand the port range
220
+ for (let p = portConfig.from; p <= portConfig.to; p++) {
221
+ expandedPorts.push(p);
222
+ }
223
+ }
224
+ }
225
+
226
+ // Use route name if available, otherwise generate a unique ID
227
+ const routeName = route.name || `unnamed_${Math.random().toString(36).substring(2, 9)}`;
228
+
229
+ // Add each port to the usage map
230
+ for (const port of expandedPorts) {
231
+ if (!portUsage.has(port)) {
232
+ portUsage.set(port, new Set());
233
+ }
234
+ portUsage.get(port)!.add(routeName);
235
+ }
236
+ }
237
+
238
+ // Log port usage for debugging
239
+ for (const [port, routes] of portUsage.entries()) {
240
+ this.logger.debug(`Port ${port} is used by ${routes.size} routes: ${Array.from(routes).join(', ')}`);
241
+ }
242
+
243
+ return portUsage;
244
+ }
245
+
246
+ /**
247
+ * Find ports that have no routes in the new configuration
248
+ */
249
+ private findOrphanedPorts(oldUsage: Map<number, Set<string>>, newUsage: Map<number, Set<string>>): number[] {
250
+ const orphanedPorts: number[] = [];
251
+
252
+ for (const [port, routes] of oldUsage.entries()) {
253
+ if (!newUsage.has(port) || newUsage.get(port)!.size === 0) {
254
+ orphanedPorts.push(port);
255
+ }
256
+ }
257
+
258
+ return orphanedPorts;
259
+ }
260
+
261
+ /**
262
+ * Update NFTables routes
263
+ */
264
+ private async updateNfTablesRoutes(oldRoutes: IRouteConfig[], newRoutes: IRouteConfig[]): Promise<void> {
265
+ // Get existing routes that use NFTables and update them
266
+ const oldNfTablesRoutes = oldRoutes.filter(
267
+ r => r.action.forwardingEngine === 'nftables'
268
+ );
269
+
270
+ const newNfTablesRoutes = newRoutes.filter(
271
+ r => r.action.forwardingEngine === 'nftables'
272
+ );
273
+
274
+ // Update existing NFTables routes
275
+ for (const oldRoute of oldNfTablesRoutes) {
276
+ const newRoute = newNfTablesRoutes.find(r => r.name === oldRoute.name);
277
+
278
+ if (!newRoute) {
279
+ // Route was removed
280
+ await this.nftablesManager.deprovisionRoute(oldRoute);
281
+ } else {
282
+ // Route was updated
283
+ await this.nftablesManager.updateRoute(oldRoute, newRoute);
284
+ }
285
+ }
286
+
287
+ // Add new NFTables routes
288
+ for (const newRoute of newNfTablesRoutes) {
289
+ const oldRoute = oldNfTablesRoutes.find(r => r.name === newRoute.name);
290
+
291
+ if (!oldRoute) {
292
+ // New route
293
+ await this.nftablesManager.provisionRoute(newRoute);
294
+ }
295
+ }
296
+ }
297
+ }