@push.rocks/smartproxy 16.0.2 → 16.0.3

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 (115) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/core/models/index.d.ts +2 -0
  3. package/dist_ts/core/models/index.js +3 -1
  4. package/dist_ts/core/models/route-context.d.ts +62 -0
  5. package/dist_ts/core/models/route-context.js +43 -0
  6. package/dist_ts/core/models/socket-augmentation.d.ts +12 -0
  7. package/dist_ts/core/models/socket-augmentation.js +18 -0
  8. package/dist_ts/core/utils/event-system.d.ts +200 -0
  9. package/dist_ts/core/utils/event-system.js +224 -0
  10. package/dist_ts/core/utils/index.d.ts +7 -0
  11. package/dist_ts/core/utils/index.js +8 -1
  12. package/dist_ts/core/utils/route-manager.d.ts +118 -0
  13. package/dist_ts/core/utils/route-manager.js +383 -0
  14. package/dist_ts/core/utils/route-utils.d.ts +94 -0
  15. package/dist_ts/core/utils/route-utils.js +264 -0
  16. package/dist_ts/core/utils/security-utils.d.ts +111 -0
  17. package/dist_ts/core/utils/security-utils.js +212 -0
  18. package/dist_ts/core/utils/shared-security-manager.d.ts +110 -0
  19. package/dist_ts/core/utils/shared-security-manager.js +252 -0
  20. package/dist_ts/core/utils/template-utils.d.ts +37 -0
  21. package/dist_ts/core/utils/template-utils.js +104 -0
  22. package/dist_ts/core/utils/websocket-utils.d.ts +23 -0
  23. package/dist_ts/core/utils/websocket-utils.js +86 -0
  24. package/dist_ts/http/router/index.d.ts +5 -1
  25. package/dist_ts/http/router/index.js +4 -2
  26. package/dist_ts/http/router/route-router.d.ts +108 -0
  27. package/dist_ts/http/router/route-router.js +393 -0
  28. package/dist_ts/index.d.ts +8 -2
  29. package/dist_ts/index.js +10 -3
  30. package/dist_ts/proxies/index.d.ts +7 -2
  31. package/dist_ts/proxies/index.js +10 -4
  32. package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +21 -0
  33. package/dist_ts/proxies/network-proxy/certificate-manager.js +92 -1
  34. package/dist_ts/proxies/network-proxy/context-creator.d.ts +34 -0
  35. package/dist_ts/proxies/network-proxy/context-creator.js +108 -0
  36. package/dist_ts/proxies/network-proxy/function-cache.d.ts +90 -0
  37. package/dist_ts/proxies/network-proxy/function-cache.js +198 -0
  38. package/dist_ts/proxies/network-proxy/http-request-handler.d.ts +40 -0
  39. package/dist_ts/proxies/network-proxy/http-request-handler.js +256 -0
  40. package/dist_ts/proxies/network-proxy/http2-request-handler.d.ts +24 -0
  41. package/dist_ts/proxies/network-proxy/http2-request-handler.js +201 -0
  42. package/dist_ts/proxies/network-proxy/models/types.d.ts +73 -1
  43. package/dist_ts/proxies/network-proxy/models/types.js +242 -1
  44. package/dist_ts/proxies/network-proxy/network-proxy.d.ts +23 -20
  45. package/dist_ts/proxies/network-proxy/network-proxy.js +147 -60
  46. package/dist_ts/proxies/network-proxy/request-handler.d.ts +38 -5
  47. package/dist_ts/proxies/network-proxy/request-handler.js +584 -198
  48. package/dist_ts/proxies/network-proxy/security-manager.d.ts +65 -0
  49. package/dist_ts/proxies/network-proxy/security-manager.js +255 -0
  50. package/dist_ts/proxies/network-proxy/websocket-handler.d.ts +13 -2
  51. package/dist_ts/proxies/network-proxy/websocket-handler.js +238 -20
  52. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
  53. package/dist_ts/proxies/smart-proxy/index.js +3 -3
  54. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +3 -5
  55. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +56 -3
  56. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +4 -57
  57. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +19 -228
  58. package/dist_ts/proxies/smart-proxy/port-manager.d.ts +81 -0
  59. package/dist_ts/proxies/smart-proxy/port-manager.js +166 -0
  60. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +5 -0
  61. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +131 -15
  62. package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +3 -1
  63. package/dist_ts/proxies/smart-proxy/route-helpers/index.js +5 -3
  64. package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +5 -178
  65. package/dist_ts/proxies/smart-proxy/route-helpers.js +8 -296
  66. package/dist_ts/proxies/smart-proxy/route-manager.d.ts +11 -2
  67. package/dist_ts/proxies/smart-proxy/route-manager.js +79 -10
  68. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +29 -2
  69. package/dist_ts/proxies/smart-proxy/smart-proxy.js +48 -43
  70. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +67 -1
  71. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +120 -1
  72. package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +3 -3
  73. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +27 -5
  74. package/package.json +1 -1
  75. package/readme.md +102 -14
  76. package/readme.plan.md +103 -168
  77. package/ts/00_commitinfo_data.ts +1 -1
  78. package/ts/core/models/index.ts +2 -0
  79. package/ts/core/models/route-context.ts +113 -0
  80. package/ts/core/models/socket-augmentation.ts +33 -0
  81. package/ts/core/utils/event-system.ts +376 -0
  82. package/ts/core/utils/index.ts +7 -0
  83. package/ts/core/utils/route-manager.ts +489 -0
  84. package/ts/core/utils/route-utils.ts +312 -0
  85. package/ts/core/utils/security-utils.ts +309 -0
  86. package/ts/core/utils/shared-security-manager.ts +333 -0
  87. package/ts/core/utils/template-utils.ts +124 -0
  88. package/ts/core/utils/websocket-utils.ts +81 -0
  89. package/ts/http/router/index.ts +8 -1
  90. package/ts/http/router/route-router.ts +482 -0
  91. package/ts/index.ts +14 -2
  92. package/ts/proxies/index.ts +12 -3
  93. package/ts/proxies/network-proxy/certificate-manager.ts +114 -10
  94. package/ts/proxies/network-proxy/context-creator.ts +145 -0
  95. package/ts/proxies/network-proxy/function-cache.ts +259 -0
  96. package/ts/proxies/network-proxy/http-request-handler.ts +330 -0
  97. package/ts/proxies/network-proxy/http2-request-handler.ts +255 -0
  98. package/ts/proxies/network-proxy/models/types.ts +312 -1
  99. package/ts/proxies/network-proxy/network-proxy.ts +195 -86
  100. package/ts/proxies/network-proxy/request-handler.ts +698 -246
  101. package/ts/proxies/network-proxy/security-manager.ts +298 -0
  102. package/ts/proxies/network-proxy/websocket-handler.ts +276 -33
  103. package/ts/proxies/smart-proxy/index.ts +2 -12
  104. package/ts/proxies/smart-proxy/models/interfaces.ts +7 -4
  105. package/ts/proxies/smart-proxy/models/route-types.ts +78 -10
  106. package/ts/proxies/smart-proxy/network-proxy-bridge.ts +20 -257
  107. package/ts/proxies/smart-proxy/port-manager.ts +195 -0
  108. package/ts/proxies/smart-proxy/route-connection-handler.ts +156 -21
  109. package/ts/proxies/smart-proxy/route-manager.ts +98 -14
  110. package/ts/proxies/smart-proxy/smart-proxy.ts +56 -55
  111. package/ts/proxies/smart-proxy/utils/route-helpers.ts +167 -1
  112. package/ts/proxies/smart-proxy/utils/route-validators.ts +24 -5
  113. package/ts/proxies/smart-proxy/domain-config-manager.ts.bak +0 -441
  114. package/ts/proxies/smart-proxy/route-helpers/index.ts +0 -9
  115. package/ts/proxies/smart-proxy/route-helpers.ts +0 -498
@@ -0,0 +1,195 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type { ISmartProxyOptions } from './models/interfaces.js';
3
+ import { RouteConnectionHandler } from './route-connection-handler.js';
4
+
5
+ /**
6
+ * PortManager handles the dynamic creation and removal of port listeners
7
+ *
8
+ * This class provides methods to add and remove listening ports at runtime,
9
+ * allowing SmartProxy to adapt to configuration changes without requiring
10
+ * a full restart.
11
+ */
12
+ export class PortManager {
13
+ private servers: Map<number, plugins.net.Server> = new Map();
14
+ private settings: ISmartProxyOptions;
15
+ private routeConnectionHandler: RouteConnectionHandler;
16
+ private isShuttingDown: boolean = false;
17
+
18
+ /**
19
+ * Create a new PortManager
20
+ *
21
+ * @param settings The SmartProxy settings
22
+ * @param routeConnectionHandler The handler for new connections
23
+ */
24
+ constructor(
25
+ settings: ISmartProxyOptions,
26
+ routeConnectionHandler: RouteConnectionHandler
27
+ ) {
28
+ this.settings = settings;
29
+ this.routeConnectionHandler = routeConnectionHandler;
30
+ }
31
+
32
+ /**
33
+ * Start listening on a specific port
34
+ *
35
+ * @param port The port number to listen on
36
+ * @returns Promise that resolves when the server is listening or rejects on error
37
+ */
38
+ public async addPort(port: number): Promise<void> {
39
+ // Check if we're already listening on this port
40
+ if (this.servers.has(port)) {
41
+ console.log(`PortManager: Already listening on port ${port}`);
42
+ return;
43
+ }
44
+
45
+ // Create a server for this port
46
+ const server = plugins.net.createServer((socket) => {
47
+ // Check if shutting down
48
+ if (this.isShuttingDown) {
49
+ socket.end();
50
+ socket.destroy();
51
+ return;
52
+ }
53
+
54
+ // Delegate to route connection handler
55
+ this.routeConnectionHandler.handleConnection(socket);
56
+ }).on('error', (err: Error) => {
57
+ console.log(`Server Error on port ${port}: ${err.message}`);
58
+ });
59
+
60
+ // Start listening on the port
61
+ return new Promise<void>((resolve, reject) => {
62
+ server.listen(port, () => {
63
+ const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
64
+ console.log(
65
+ `SmartProxy -> OK: Now listening on port ${port}${
66
+ isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''
67
+ }`
68
+ );
69
+
70
+ // Store the server reference
71
+ this.servers.set(port, server);
72
+ resolve();
73
+ }).on('error', (err) => {
74
+ console.log(`Failed to listen on port ${port}: ${err.message}`);
75
+ reject(err);
76
+ });
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Stop listening on a specific port
82
+ *
83
+ * @param port The port to stop listening on
84
+ * @returns Promise that resolves when the server is closed
85
+ */
86
+ public async removePort(port: number): Promise<void> {
87
+ // Get the server for this port
88
+ const server = this.servers.get(port);
89
+ if (!server) {
90
+ console.log(`PortManager: Not listening on port ${port}`);
91
+ return;
92
+ }
93
+
94
+ // Close the server
95
+ return new Promise<void>((resolve) => {
96
+ server.close((err) => {
97
+ if (err) {
98
+ console.log(`Error closing server on port ${port}: ${err.message}`);
99
+ } else {
100
+ console.log(`SmartProxy -> Stopped listening on port ${port}`);
101
+ }
102
+
103
+ // Remove the server reference
104
+ this.servers.delete(port);
105
+ resolve();
106
+ });
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Add multiple ports at once
112
+ *
113
+ * @param ports Array of ports to add
114
+ * @returns Promise that resolves when all servers are listening
115
+ */
116
+ public async addPorts(ports: number[]): Promise<void> {
117
+ const uniquePorts = [...new Set(ports)];
118
+ await Promise.all(uniquePorts.map(port => this.addPort(port)));
119
+ }
120
+
121
+ /**
122
+ * Remove multiple ports at once
123
+ *
124
+ * @param ports Array of ports to remove
125
+ * @returns Promise that resolves when all servers are closed
126
+ */
127
+ public async removePorts(ports: number[]): Promise<void> {
128
+ const uniquePorts = [...new Set(ports)];
129
+ await Promise.all(uniquePorts.map(port => this.removePort(port)));
130
+ }
131
+
132
+ /**
133
+ * Update listening ports to match the provided list
134
+ *
135
+ * This will add any ports that aren't currently listening,
136
+ * and remove any ports that are no longer needed.
137
+ *
138
+ * @param ports Array of ports that should be listening
139
+ * @returns Promise that resolves when all operations are complete
140
+ */
141
+ public async updatePorts(ports: number[]): Promise<void> {
142
+ const targetPorts = new Set(ports);
143
+ const currentPorts = new Set(this.servers.keys());
144
+
145
+ // Find ports to add and remove
146
+ const portsToAdd = ports.filter(port => !currentPorts.has(port));
147
+ const portsToRemove = Array.from(currentPorts).filter(port => !targetPorts.has(port));
148
+
149
+ // Log the changes
150
+ if (portsToAdd.length > 0) {
151
+ console.log(`PortManager: Adding new listeners for ports: ${portsToAdd.join(', ')}`);
152
+ }
153
+
154
+ if (portsToRemove.length > 0) {
155
+ console.log(`PortManager: Removing listeners for ports: ${portsToRemove.join(', ')}`);
156
+ }
157
+
158
+ // Add and remove ports
159
+ await this.removePorts(portsToRemove);
160
+ await this.addPorts(portsToAdd);
161
+ }
162
+
163
+ /**
164
+ * Get all ports that are currently listening
165
+ *
166
+ * @returns Array of port numbers
167
+ */
168
+ public getListeningPorts(): number[] {
169
+ return Array.from(this.servers.keys());
170
+ }
171
+
172
+ /**
173
+ * Mark the port manager as shutting down
174
+ */
175
+ public setShuttingDown(isShuttingDown: boolean): void {
176
+ this.isShuttingDown = isShuttingDown;
177
+ }
178
+
179
+ /**
180
+ * Close all listening servers
181
+ *
182
+ * @returns Promise that resolves when all servers are closed
183
+ */
184
+ public async closeAll(): Promise<void> {
185
+ const allPorts = Array.from(this.servers.keys());
186
+ await this.removePorts(allPorts);
187
+ }
188
+
189
+ /**
190
+ * Get all server instances (for testing or debugging)
191
+ */
192
+ public getServers(): Map<number, plugins.net.Server> {
193
+ return new Map(this.servers);
194
+ }
195
+ }
@@ -8,7 +8,8 @@ import {
8
8
  } from './models/interfaces.js';
9
9
  import type {
10
10
  IRouteConfig,
11
- IRouteAction
11
+ IRouteAction,
12
+ IRouteContext
12
13
  } from './models/route-types.js';
13
14
  import { ConnectionManager } from './connection-manager.js';
14
15
  import { SecurityManager } from './security-manager.js';
@@ -24,6 +25,9 @@ import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.j
24
25
  export class RouteConnectionHandler {
25
26
  private settings: ISmartProxyOptions;
26
27
 
28
+ // Cache for route contexts to avoid recreation
29
+ private routeContextCache: Map<string, IRouteContext> = new Map();
30
+
27
31
  constructor(
28
32
  settings: ISmartProxyOptions,
29
33
  private connectionManager: ConnectionManager,
@@ -36,6 +40,47 @@ export class RouteConnectionHandler {
36
40
  this.settings = settings;
37
41
  }
38
42
 
43
+ /**
44
+ * Create a route context object for port and host mapping functions
45
+ */
46
+ private createRouteContext(options: {
47
+ connectionId: string;
48
+ port: number;
49
+ domain?: string;
50
+ clientIp: string;
51
+ serverIp: string;
52
+ isTls: boolean;
53
+ tlsVersion?: string;
54
+ routeName?: string;
55
+ routeId?: string;
56
+ path?: string;
57
+ query?: string;
58
+ headers?: Record<string, string>;
59
+ }): IRouteContext {
60
+ return {
61
+ // Connection information
62
+ port: options.port,
63
+ domain: options.domain,
64
+ clientIp: options.clientIp,
65
+ serverIp: options.serverIp,
66
+ path: options.path,
67
+ query: options.query,
68
+ headers: options.headers,
69
+
70
+ // TLS information
71
+ isTls: options.isTls,
72
+ tlsVersion: options.tlsVersion,
73
+
74
+ // Route information
75
+ routeName: options.routeName,
76
+ routeId: options.routeId,
77
+
78
+ // Additional properties
79
+ timestamp: Date.now(),
80
+ connectionId: options.connectionId
81
+ };
82
+ }
83
+
39
84
  /**
40
85
  * Handle a new incoming connection
41
86
  */
@@ -325,7 +370,7 @@ export class RouteConnectionHandler {
325
370
  ): void {
326
371
  const connectionId = record.id;
327
372
  const action = route.action;
328
-
373
+
329
374
  // We should have a target configuration for forwarding
330
375
  if (!action.target) {
331
376
  console.log(`[${connectionId}] Forward action missing target configuration`);
@@ -333,24 +378,82 @@ export class RouteConnectionHandler {
333
378
  this.connectionManager.cleanupConnection(record, 'missing_target');
334
379
  return;
335
380
  }
336
-
381
+
382
+ // Create the routing context for this connection
383
+ const routeContext = this.createRouteContext({
384
+ connectionId: record.id,
385
+ port: record.localPort,
386
+ domain: record.lockedDomain,
387
+ clientIp: record.remoteIP,
388
+ serverIp: socket.localAddress || '',
389
+ isTls: record.isTLS || false,
390
+ tlsVersion: record.tlsVersion,
391
+ routeName: route.name,
392
+ routeId: route.id
393
+ });
394
+
395
+ // Cache the context for potential reuse
396
+ this.routeContextCache.set(connectionId, routeContext);
397
+
398
+ // Determine host using function or static value
399
+ let targetHost: string | string[];
400
+ if (typeof action.target.host === 'function') {
401
+ try {
402
+ targetHost = action.target.host(routeContext);
403
+ if (this.settings.enableDetailedLogging) {
404
+ console.log(`[${connectionId}] Dynamic host resolved to: ${Array.isArray(targetHost) ? targetHost.join(', ') : targetHost}`);
405
+ }
406
+ } catch (err) {
407
+ console.log(`[${connectionId}] Error in host mapping function: ${err}`);
408
+ socket.end();
409
+ this.connectionManager.cleanupConnection(record, 'host_mapping_error');
410
+ return;
411
+ }
412
+ } else {
413
+ targetHost = action.target.host;
414
+ }
415
+
416
+ // If an array of hosts, select one randomly for load balancing
417
+ const selectedHost = Array.isArray(targetHost)
418
+ ? targetHost[Math.floor(Math.random() * targetHost.length)]
419
+ : targetHost;
420
+
421
+ // Determine port using function or static value
422
+ let targetPort: number;
423
+ if (typeof action.target.port === 'function') {
424
+ try {
425
+ targetPort = action.target.port(routeContext);
426
+ if (this.settings.enableDetailedLogging) {
427
+ console.log(`[${connectionId}] Dynamic port mapping: ${record.localPort} -> ${targetPort}`);
428
+ }
429
+ // Store the resolved target port in the context for potential future use
430
+ routeContext.targetPort = targetPort;
431
+ } catch (err) {
432
+ console.log(`[${connectionId}] Error in port mapping function: ${err}`);
433
+ socket.end();
434
+ this.connectionManager.cleanupConnection(record, 'port_mapping_error');
435
+ return;
436
+ }
437
+ } else if (action.target.preservePort) {
438
+ // Use incoming port if preservePort is true
439
+ targetPort = record.localPort;
440
+ } else {
441
+ // Use static port from configuration
442
+ targetPort = action.target.port;
443
+ }
444
+
445
+ // Store the resolved host in the context
446
+ routeContext.targetHost = selectedHost;
447
+
337
448
  // Determine if this needs TLS handling
338
449
  if (action.tls) {
339
450
  switch (action.tls.mode) {
340
451
  case 'passthrough':
341
452
  // For TLS passthrough, just forward directly
342
453
  if (this.settings.enableDetailedLogging) {
343
- console.log(`[${connectionId}] Using TLS passthrough to ${action.target.host}`);
454
+ console.log(`[${connectionId}] Using TLS passthrough to ${selectedHost}:${targetPort}`);
344
455
  }
345
456
 
346
- // Allow for array of hosts
347
- const targetHost = Array.isArray(action.target.host)
348
- ? action.target.host[Math.floor(Math.random() * action.target.host.length)]
349
- : action.target.host;
350
-
351
- // Determine target port - either target port or preserve incoming port
352
- const targetPort = action.target.preservePort ? record.localPort : action.target.port;
353
-
354
457
  return this.setupDirectConnection(
355
458
  socket,
356
459
  record,
@@ -358,7 +461,7 @@ export class RouteConnectionHandler {
358
461
  record.lockedDomain,
359
462
  initialChunk,
360
463
  undefined,
361
- targetHost,
464
+ selectedHost,
362
465
  targetPort
363
466
  );
364
467
 
@@ -402,14 +505,36 @@ export class RouteConnectionHandler {
402
505
  console.log(`[${connectionId}] Using basic forwarding to ${action.target.host}:${action.target.port}`);
403
506
  }
404
507
 
405
- // Allow for array of hosts
406
- const targetHost = Array.isArray(action.target.host)
407
- ? action.target.host[Math.floor(Math.random() * action.target.host.length)]
408
- : action.target.host;
409
-
410
- // Determine target port - either target port or preserve incoming port
411
- const targetPort = action.target.preservePort ? record.localPort : action.target.port;
412
-
508
+ // Get the appropriate host value
509
+ let targetHost: string;
510
+
511
+ if (typeof action.target.host === 'function') {
512
+ // For function-based host, use the same routeContext created earlier
513
+ const hostResult = action.target.host(routeContext);
514
+ targetHost = Array.isArray(hostResult)
515
+ ? hostResult[Math.floor(Math.random() * hostResult.length)]
516
+ : hostResult;
517
+ } else {
518
+ // For static host value
519
+ targetHost = Array.isArray(action.target.host)
520
+ ? action.target.host[Math.floor(Math.random() * action.target.host.length)]
521
+ : action.target.host;
522
+ }
523
+
524
+ // Determine port - either function-based, static, or preserve incoming port
525
+ let targetPort: number;
526
+ if (typeof action.target.port === 'function') {
527
+ targetPort = action.target.port(routeContext);
528
+ } else if (action.target.preservePort) {
529
+ targetPort = record.localPort;
530
+ } else {
531
+ targetPort = action.target.port;
532
+ }
533
+
534
+ // Update the connection record and context with resolved values
535
+ record.targetHost = targetHost;
536
+ record.targetPort = targetPort;
537
+
413
538
  return this.setupDirectConnection(
414
539
  socket,
415
540
  record,
@@ -552,13 +677,23 @@ export class RouteConnectionHandler {
552
677
 
553
678
  // Determine target host and port if not provided
554
679
  const finalTargetHost = targetHost ||
680
+ record.targetHost ||
555
681
  (this.settings.defaults?.target?.host || 'localhost');
556
682
 
557
683
  // Determine target port
558
684
  const finalTargetPort = targetPort ||
685
+ record.targetPort ||
559
686
  (overridePort !== undefined ? overridePort :
560
687
  (this.settings.defaults?.target?.port || 443));
561
688
 
689
+ // Update record with final target information
690
+ record.targetHost = finalTargetHost;
691
+ record.targetPort = finalTargetPort;
692
+
693
+ if (this.settings.enableDetailedLogging) {
694
+ console.log(`[${connectionId}] Setting up direct connection to ${finalTargetHost}:${finalTargetPort}`);
695
+ }
696
+
562
697
  // Setup connection options
563
698
  const connectionOptions: plugins.net.NetConnectOpts = {
564
699
  host: finalTargetHost,
@@ -58,36 +58,88 @@ export class RouteManager extends plugins.EventEmitter {
58
58
 
59
59
  /**
60
60
  * Rebuild the port mapping for fast lookups
61
+ * Also logs information about the ports being listened on
61
62
  */
62
63
  private rebuildPortMap(): void {
63
64
  this.portMap.clear();
64
-
65
+ this.portRangeCache.clear(); // Clear cache when rebuilding
66
+
67
+ // Track ports for logging
68
+ const portToRoutesMap = new Map<number, string[]>();
69
+
65
70
  for (const route of this.routes) {
66
71
  const ports = this.expandPortRange(route.match.ports);
67
-
72
+
73
+ // Skip if no ports were found
74
+ if (ports.length === 0) {
75
+ console.warn(`Route ${route.name || 'unnamed'} has no valid ports to listen on`);
76
+ continue;
77
+ }
78
+
68
79
  for (const port of ports) {
80
+ // Add to portMap for routing
69
81
  if (!this.portMap.has(port)) {
70
82
  this.portMap.set(port, []);
71
83
  }
72
84
  this.portMap.get(port)!.push(route);
85
+
86
+ // Add to tracking for logging
87
+ if (!portToRoutesMap.has(port)) {
88
+ portToRoutesMap.set(port, []);
89
+ }
90
+ portToRoutesMap.get(port)!.push(route.name || 'unnamed');
91
+ }
92
+ }
93
+
94
+ // Log summary of ports and routes
95
+ const totalPorts = this.portMap.size;
96
+ const totalRoutes = this.routes.length;
97
+ console.log(`Route manager configured with ${totalRoutes} routes across ${totalPorts} ports`);
98
+
99
+ // Log port details if detailed logging is enabled
100
+ const enableDetailedLogging = this.options.enableDetailedLogging;
101
+ if (enableDetailedLogging) {
102
+ for (const [port, routes] of this.portMap.entries()) {
103
+ console.log(`Port ${port}: ${routes.length} routes (${portToRoutesMap.get(port)!.join(', ')})`);
73
104
  }
74
105
  }
75
106
  }
76
107
 
77
108
  /**
78
109
  * Expand a port range specification into an array of individual ports
110
+ * Uses caching to improve performance for frequently used port ranges
111
+ *
112
+ * @public - Made public to allow external code to interpret port ranges
79
113
  */
80
- private expandPortRange(portRange: TPortRange): number[] {
114
+ public expandPortRange(portRange: TPortRange): number[] {
115
+ // For simple number, return immediately
81
116
  if (typeof portRange === 'number') {
82
117
  return [portRange];
83
118
  }
84
-
119
+
120
+ // Create a cache key for this port range
121
+ const cacheKey = JSON.stringify(portRange);
122
+
123
+ // Check if we have a cached result
124
+ if (this.portRangeCache.has(cacheKey)) {
125
+ return this.portRangeCache.get(cacheKey)!;
126
+ }
127
+
128
+ // Process the port range
129
+ let result: number[] = [];
130
+
85
131
  if (Array.isArray(portRange)) {
86
132
  // Handle array of port objects or numbers
87
- return portRange.flatMap(item => {
133
+ result = portRange.flatMap(item => {
88
134
  if (typeof item === 'number') {
89
135
  return [item];
90
136
  } else if (typeof item === 'object' && 'from' in item && 'to' in item) {
137
+ // Handle port range object - check valid range
138
+ if (item.from > item.to) {
139
+ console.warn(`Invalid port range: from (${item.from}) > to (${item.to})`);
140
+ return [];
141
+ }
142
+
91
143
  // Handle port range object
92
144
  const ports: number[] = [];
93
145
  for (let p = item.from; p <= item.to; p++) {
@@ -98,14 +150,24 @@ export class RouteManager extends plugins.EventEmitter {
98
150
  return [];
99
151
  });
100
152
  }
101
-
102
- return [];
153
+
154
+ // Cache the result
155
+ this.portRangeCache.set(cacheKey, result);
156
+
157
+ return result;
103
158
  }
104
159
 
160
+ /**
161
+ * Memoization cache for expanded port ranges
162
+ */
163
+ private portRangeCache: Map<string, number[]> = new Map();
164
+
105
165
  /**
106
166
  * Get all ports that should be listened on
167
+ * This method automatically infers all required ports from route configurations
107
168
  */
108
169
  public getListeningPorts(): number[] {
170
+ // Return the unique set of ports from all routes
109
171
  return Array.from(this.portMap.keys());
110
172
  }
111
173
 
@@ -182,21 +244,36 @@ export class RouteManager extends plugins.EventEmitter {
182
244
  * Match an IP against a pattern
183
245
  */
184
246
  private matchIpPattern(pattern: string, ip: string): boolean {
185
- // Handle exact match
186
- if (pattern === ip) {
247
+ // Normalize IPv6-mapped IPv4 addresses
248
+ const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
249
+ const normalizedPattern = pattern.startsWith('::ffff:') ? pattern.substring(7) : pattern;
250
+
251
+ // Handle exact match with normalized addresses
252
+ if (pattern === ip || normalizedPattern === normalizedIp ||
253
+ pattern === normalizedIp || normalizedPattern === ip) {
187
254
  return true;
188
255
  }
189
256
 
190
257
  // Handle CIDR notation (e.g., 192.168.1.0/24)
191
258
  if (pattern.includes('/')) {
192
- return this.matchIpCidr(pattern, ip);
259
+ return this.matchIpCidr(pattern, normalizedIp) ||
260
+ (normalizedPattern !== pattern && this.matchIpCidr(normalizedPattern, normalizedIp));
193
261
  }
194
262
 
195
263
  // Handle glob pattern (e.g., 192.168.1.*)
196
264
  if (pattern.includes('*')) {
197
265
  const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
198
266
  const regex = new RegExp(`^${regexPattern}$`);
199
- return regex.test(ip);
267
+ if (regex.test(ip) || regex.test(normalizedIp)) {
268
+ return true;
269
+ }
270
+
271
+ // If pattern was normalized, also test with normalized pattern
272
+ if (normalizedPattern !== pattern) {
273
+ const normalizedRegexPattern = normalizedPattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
274
+ const normalizedRegex = new RegExp(`^${normalizedRegexPattern}$`);
275
+ return normalizedRegex.test(ip) || normalizedRegex.test(normalizedIp);
276
+ }
200
277
  }
201
278
 
202
279
  return false;
@@ -212,9 +289,13 @@ export class RouteManager extends plugins.EventEmitter {
212
289
  const [subnet, bits] = cidr.split('/');
213
290
  const mask = parseInt(bits, 10);
214
291
 
292
+ // Normalize IPv6-mapped IPv4 addresses
293
+ const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
294
+ const normalizedSubnet = subnet.startsWith('::ffff:') ? subnet.substring(7) : subnet;
295
+
215
296
  // Convert IP addresses to numeric values
216
- const ipNum = this.ipToNumber(ip);
217
- const subnetNum = this.ipToNumber(subnet);
297
+ const ipNum = this.ipToNumber(normalizedIp);
298
+ const subnetNum = this.ipToNumber(normalizedSubnet);
218
299
 
219
300
  // Calculate subnet mask
220
301
  const maskNum = ~(2 ** (32 - mask) - 1);
@@ -231,7 +312,10 @@ export class RouteManager extends plugins.EventEmitter {
231
312
  * Convert an IP address to a numeric value
232
313
  */
233
314
  private ipToNumber(ip: string): number {
234
- const parts = ip.split('.').map(part => parseInt(part, 10));
315
+ // Normalize IPv6-mapped IPv4 addresses
316
+ const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
317
+
318
+ const parts = normalizedIp.split('.').map(part => parseInt(part, 10));
235
319
  return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
236
320
  }
237
321