@push.rocks/smartproxy 16.0.2 → 16.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/models/index.d.ts +2 -0
- package/dist_ts/core/models/index.js +3 -1
- package/dist_ts/core/models/route-context.d.ts +62 -0
- package/dist_ts/core/models/route-context.js +43 -0
- package/dist_ts/core/models/socket-augmentation.d.ts +12 -0
- package/dist_ts/core/models/socket-augmentation.js +18 -0
- package/dist_ts/core/utils/event-system.d.ts +200 -0
- package/dist_ts/core/utils/event-system.js +224 -0
- package/dist_ts/core/utils/index.d.ts +7 -0
- package/dist_ts/core/utils/index.js +8 -1
- package/dist_ts/core/utils/route-manager.d.ts +118 -0
- package/dist_ts/core/utils/route-manager.js +383 -0
- package/dist_ts/core/utils/route-utils.d.ts +94 -0
- package/dist_ts/core/utils/route-utils.js +264 -0
- package/dist_ts/core/utils/security-utils.d.ts +111 -0
- package/dist_ts/core/utils/security-utils.js +212 -0
- package/dist_ts/core/utils/shared-security-manager.d.ts +110 -0
- package/dist_ts/core/utils/shared-security-manager.js +252 -0
- package/dist_ts/core/utils/template-utils.d.ts +37 -0
- package/dist_ts/core/utils/template-utils.js +104 -0
- package/dist_ts/core/utils/websocket-utils.d.ts +23 -0
- package/dist_ts/core/utils/websocket-utils.js +86 -0
- package/dist_ts/http/router/index.d.ts +5 -1
- package/dist_ts/http/router/index.js +4 -2
- package/dist_ts/http/router/route-router.d.ts +108 -0
- package/dist_ts/http/router/route-router.js +393 -0
- package/dist_ts/index.d.ts +8 -2
- package/dist_ts/index.js +10 -3
- package/dist_ts/proxies/index.d.ts +7 -2
- package/dist_ts/proxies/index.js +10 -4
- package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +21 -0
- package/dist_ts/proxies/network-proxy/certificate-manager.js +92 -1
- package/dist_ts/proxies/network-proxy/context-creator.d.ts +34 -0
- package/dist_ts/proxies/network-proxy/context-creator.js +108 -0
- package/dist_ts/proxies/network-proxy/function-cache.d.ts +90 -0
- package/dist_ts/proxies/network-proxy/function-cache.js +198 -0
- package/dist_ts/proxies/network-proxy/http-request-handler.d.ts +40 -0
- package/dist_ts/proxies/network-proxy/http-request-handler.js +256 -0
- package/dist_ts/proxies/network-proxy/http2-request-handler.d.ts +24 -0
- package/dist_ts/proxies/network-proxy/http2-request-handler.js +201 -0
- package/dist_ts/proxies/network-proxy/models/types.d.ts +73 -1
- package/dist_ts/proxies/network-proxy/models/types.js +242 -1
- package/dist_ts/proxies/network-proxy/network-proxy.d.ts +23 -20
- package/dist_ts/proxies/network-proxy/network-proxy.js +149 -60
- package/dist_ts/proxies/network-proxy/request-handler.d.ts +38 -5
- package/dist_ts/proxies/network-proxy/request-handler.js +584 -198
- package/dist_ts/proxies/network-proxy/security-manager.d.ts +65 -0
- package/dist_ts/proxies/network-proxy/security-manager.js +255 -0
- package/dist_ts/proxies/network-proxy/websocket-handler.d.ts +13 -2
- package/dist_ts/proxies/network-proxy/websocket-handler.js +238 -20
- package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/index.js +3 -3
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +3 -5
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +56 -4
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +4 -57
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +19 -228
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +81 -0
- package/dist_ts/proxies/smart-proxy/port-manager.js +166 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +5 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +131 -15
- package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +3 -1
- package/dist_ts/proxies/smart-proxy/route-helpers/index.js +5 -3
- package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +5 -178
- package/dist_ts/proxies/smart-proxy/route-helpers.js +8 -296
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +11 -2
- package/dist_ts/proxies/smart-proxy/route-manager.js +79 -10
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +29 -2
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +48 -43
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +67 -1
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +120 -1
- package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +3 -3
- package/dist_ts/proxies/smart-proxy/utils/route-validators.js +27 -5
- package/package.json +1 -1
- package/readme.md +102 -14
- package/readme.plan.md +103 -168
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/models/index.ts +2 -0
- package/ts/core/models/route-context.ts +113 -0
- package/ts/core/models/socket-augmentation.ts +33 -0
- package/ts/core/utils/event-system.ts +376 -0
- package/ts/core/utils/index.ts +7 -0
- package/ts/core/utils/route-manager.ts +489 -0
- package/ts/core/utils/route-utils.ts +312 -0
- package/ts/core/utils/security-utils.ts +309 -0
- package/ts/core/utils/shared-security-manager.ts +333 -0
- package/ts/core/utils/template-utils.ts +124 -0
- package/ts/core/utils/websocket-utils.ts +81 -0
- package/ts/http/router/index.ts +8 -1
- package/ts/http/router/route-router.ts +482 -0
- package/ts/index.ts +14 -2
- package/ts/proxies/index.ts +12 -3
- package/ts/proxies/network-proxy/certificate-manager.ts +114 -10
- package/ts/proxies/network-proxy/context-creator.ts +145 -0
- package/ts/proxies/network-proxy/function-cache.ts +259 -0
- package/ts/proxies/network-proxy/http-request-handler.ts +330 -0
- package/ts/proxies/network-proxy/http2-request-handler.ts +255 -0
- package/ts/proxies/network-proxy/models/types.ts +312 -1
- package/ts/proxies/network-proxy/network-proxy.ts +197 -85
- package/ts/proxies/network-proxy/request-handler.ts +698 -246
- package/ts/proxies/network-proxy/security-manager.ts +298 -0
- package/ts/proxies/network-proxy/websocket-handler.ts +276 -33
- package/ts/proxies/smart-proxy/index.ts +2 -12
- package/ts/proxies/smart-proxy/models/interfaces.ts +7 -4
- package/ts/proxies/smart-proxy/models/route-types.ts +77 -10
- package/ts/proxies/smart-proxy/network-proxy-bridge.ts +20 -257
- package/ts/proxies/smart-proxy/port-manager.ts +195 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +156 -21
- package/ts/proxies/smart-proxy/route-manager.ts +98 -14
- package/ts/proxies/smart-proxy/smart-proxy.ts +56 -55
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +167 -1
- package/ts/proxies/smart-proxy/utils/route-validators.ts +24 -5
- package/ts/proxies/smart-proxy/domain-config-manager.ts.bak +0 -441
- package/ts/proxies/smart-proxy/route-helpers/index.ts +0 -9
- 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.port === 'preserve') {
|
|
438
|
+
// Use incoming port if port is 'preserve'
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
//
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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.port === 'preserve') {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
186
|
-
|
|
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,
|
|
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
|
-
|
|
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(
|
|
217
|
-
const subnetNum = this.ipToNumber(
|
|
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
|
-
|
|
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
|
|