@push.rocks/smartproxy 22.4.2 → 23.0.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.
- package/changelog.md +36 -0
- package/dist_rust/rustproxy +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -6
- package/dist_ts/index.js +3 -11
- package/dist_ts/protocols/common/fragment-handler.js +5 -1
- package/dist_ts/proxies/index.d.ts +1 -6
- package/dist_ts/proxies/index.js +2 -8
- package/dist_ts/proxies/smart-proxy/index.d.ts +5 -10
- package/dist_ts/proxies/smart-proxy/index.js +7 -13
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -2
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
- package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -621
- package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
- package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
- package/dist_ts/routing/index.d.ts +1 -1
- package/dist_ts/routing/index.js +3 -3
- package/dist_ts/routing/models/http-types.d.ts +119 -4
- package/dist_ts/routing/models/http-types.js +93 -5
- package/package.json +1 -1
- package/readme.md +444 -219
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +4 -15
- package/ts/protocols/common/fragment-handler.ts +4 -0
- package/ts/proxies/index.ts +1 -12
- package/ts/proxies/smart-proxy/index.ts +6 -13
- package/ts/proxies/smart-proxy/models/interfaces.ts +6 -4
- package/ts/proxies/smart-proxy/models/route-types.ts +0 -2
- package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
- package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
- package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
- package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
- package/ts/proxies/smart-proxy/smart-proxy.ts +282 -798
- package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
- package/ts/routing/index.ts +2 -2
- package/ts/routing/models/http-types.ts +147 -4
- package/dist_ts/proxies/nftables-proxy/index.d.ts +0 -6
- package/dist_ts/proxies/nftables-proxy/index.js +0 -7
- package/dist_ts/proxies/nftables-proxy/models/errors.d.ts +0 -15
- package/dist_ts/proxies/nftables-proxy/models/errors.js +0 -28
- package/dist_ts/proxies/nftables-proxy/models/index.d.ts +0 -5
- package/dist_ts/proxies/nftables-proxy/models/index.js +0 -6
- package/dist_ts/proxies/nftables-proxy/models/interfaces.d.ts +0 -75
- package/dist_ts/proxies/nftables-proxy/models/interfaces.js +0 -5
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +0 -124
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +0 -1374
- package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +0 -9
- package/dist_ts/proxies/nftables-proxy/utils/index.js +0 -12
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +0 -66
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +0 -131
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +0 -39
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +0 -112
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +0 -59
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +0 -130
- package/ts/proxies/http-proxy/connection-pool.ts +0 -228
- package/ts/proxies/http-proxy/context-creator.ts +0 -145
- package/ts/proxies/http-proxy/default-certificates.ts +0 -150
- package/ts/proxies/http-proxy/function-cache.ts +0 -279
- package/ts/proxies/http-proxy/handlers/index.ts +0 -5
- package/ts/proxies/http-proxy/http-proxy.ts +0 -669
- package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
- package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
- package/ts/proxies/http-proxy/index.ts +0 -18
- package/ts/proxies/http-proxy/models/http-types.ts +0 -148
- package/ts/proxies/http-proxy/models/index.ts +0 -5
- package/ts/proxies/http-proxy/models/types.ts +0 -125
- package/ts/proxies/http-proxy/request-handler.ts +0 -878
- package/ts/proxies/http-proxy/security-manager.ts +0 -413
- package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
- package/ts/proxies/nftables-proxy/index.ts +0 -6
- package/ts/proxies/nftables-proxy/models/errors.ts +0 -30
- package/ts/proxies/nftables-proxy/models/index.ts +0 -5
- package/ts/proxies/nftables-proxy/models/interfaces.ts +0 -94
- package/ts/proxies/nftables-proxy/nftables-proxy.ts +0 -1754
- package/ts/proxies/nftables-proxy/utils/index.ts +0 -38
- package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +0 -162
- package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +0 -125
- package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +0 -156
- package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
- package/ts/proxies/smart-proxy/cert-store.ts +0 -92
- package/ts/proxies/smart-proxy/certificate-manager.ts +0 -895
- package/ts/proxies/smart-proxy/connection-manager.ts +0 -809
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -213
- package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
- package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
- package/ts/proxies/smart-proxy/port-manager.ts +0 -358
- package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1712
- package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
- package/ts/proxies/smart-proxy/security-manager.ts +0 -269
- package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
- package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
- package/ts/proxies/smart-proxy/tls-manager.ts +0 -171
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../../plugins.js';
|
|
2
|
-
import { NfTablesProxy } from '../nftables-proxy/nftables-proxy.js';
|
|
3
|
-
import type {
|
|
4
|
-
NfTableProxyOptions,
|
|
5
|
-
PortRange,
|
|
6
|
-
NfTablesStatus
|
|
7
|
-
} from '../nftables-proxy/models/interfaces.js';
|
|
8
|
-
import type {
|
|
9
|
-
IRouteConfig,
|
|
10
|
-
TPortRange,
|
|
11
|
-
INfTablesOptions
|
|
12
|
-
} from './models/route-types.js';
|
|
13
|
-
import type { SmartProxy } from './smart-proxy.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Manages NFTables rules based on SmartProxy route configurations
|
|
17
|
-
*
|
|
18
|
-
* This class bridges the gap between SmartProxy routes and the NFTablesProxy,
|
|
19
|
-
* allowing high-performance kernel-level packet forwarding for routes that
|
|
20
|
-
* specify NFTables as their forwarding engine.
|
|
21
|
-
*/
|
|
22
|
-
export class NFTablesManager {
|
|
23
|
-
private rulesMap: Map<string, NfTablesProxy> = new Map();
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Creates a new NFTablesManager
|
|
27
|
-
*
|
|
28
|
-
* @param smartProxy The SmartProxy instance
|
|
29
|
-
*/
|
|
30
|
-
constructor(private smartProxy: SmartProxy) {}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Provision NFTables rules for a route
|
|
34
|
-
*
|
|
35
|
-
* @param route The route configuration
|
|
36
|
-
* @returns A promise that resolves to true if successful, false otherwise
|
|
37
|
-
*/
|
|
38
|
-
public async provisionRoute(route: IRouteConfig): Promise<boolean> {
|
|
39
|
-
// Generate a unique ID for this route
|
|
40
|
-
const routeId = this.generateRouteId(route);
|
|
41
|
-
|
|
42
|
-
// Skip if route doesn't use NFTables
|
|
43
|
-
if (route.action.forwardingEngine !== 'nftables') {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Create NFTables options from route configuration
|
|
48
|
-
const nftOptions = this.createNfTablesOptions(route);
|
|
49
|
-
|
|
50
|
-
// Create and start an NFTablesProxy instance
|
|
51
|
-
const proxy = new NfTablesProxy(nftOptions);
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
await proxy.start();
|
|
55
|
-
this.rulesMap.set(routeId, proxy);
|
|
56
|
-
return true;
|
|
57
|
-
} catch (err) {
|
|
58
|
-
console.error(`Failed to provision NFTables rules for route ${route.name || 'unnamed'}: ${err.message}`);
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Remove NFTables rules for a route
|
|
65
|
-
*
|
|
66
|
-
* @param route The route configuration
|
|
67
|
-
* @returns A promise that resolves to true if successful, false otherwise
|
|
68
|
-
*/
|
|
69
|
-
public async deprovisionRoute(route: IRouteConfig): Promise<boolean> {
|
|
70
|
-
const routeId = this.generateRouteId(route);
|
|
71
|
-
|
|
72
|
-
const proxy = this.rulesMap.get(routeId);
|
|
73
|
-
if (!proxy) {
|
|
74
|
-
return true; // Nothing to remove
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
await proxy.stop();
|
|
79
|
-
this.rulesMap.delete(routeId);
|
|
80
|
-
return true;
|
|
81
|
-
} catch (err) {
|
|
82
|
-
console.error(`Failed to deprovision NFTables rules for route ${route.name || 'unnamed'}: ${err.message}`);
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Update NFTables rules when route changes
|
|
89
|
-
*
|
|
90
|
-
* @param oldRoute The previous route configuration
|
|
91
|
-
* @param newRoute The new route configuration
|
|
92
|
-
* @returns A promise that resolves to true if successful, false otherwise
|
|
93
|
-
*/
|
|
94
|
-
public async updateRoute(oldRoute: IRouteConfig, newRoute: IRouteConfig): Promise<boolean> {
|
|
95
|
-
// Remove old rules and add new ones
|
|
96
|
-
await this.deprovisionRoute(oldRoute);
|
|
97
|
-
return this.provisionRoute(newRoute);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Generate a unique ID for a route
|
|
102
|
-
*
|
|
103
|
-
* @param route The route configuration
|
|
104
|
-
* @returns A unique ID string
|
|
105
|
-
*/
|
|
106
|
-
private generateRouteId(route: IRouteConfig): string {
|
|
107
|
-
// Generate a unique ID based on route properties
|
|
108
|
-
// Include the route name, match criteria, and a timestamp
|
|
109
|
-
const matchStr = JSON.stringify({
|
|
110
|
-
ports: route.match.ports,
|
|
111
|
-
domains: route.match.domains
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
return `${route.name || 'unnamed'}-${matchStr}-${route.id || Date.now().toString()}`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Create NFTablesProxy options from a route configuration
|
|
119
|
-
*
|
|
120
|
-
* @param route The route configuration
|
|
121
|
-
* @returns NFTableProxyOptions object
|
|
122
|
-
*/
|
|
123
|
-
private createNfTablesOptions(route: IRouteConfig): NfTableProxyOptions {
|
|
124
|
-
const { action } = route;
|
|
125
|
-
|
|
126
|
-
// Ensure we have targets
|
|
127
|
-
if (!action.targets || action.targets.length === 0) {
|
|
128
|
-
throw new Error('Route must have targets to use NFTables forwarding');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// NFTables can only handle a single target, so we use the first target without match criteria
|
|
132
|
-
// or the first target if all have match criteria
|
|
133
|
-
const defaultTarget = action.targets.find(t => !t.match) || action.targets[0];
|
|
134
|
-
|
|
135
|
-
// Convert port specifications
|
|
136
|
-
const fromPorts = this.expandPortRange(route.match.ports);
|
|
137
|
-
|
|
138
|
-
// Determine target port
|
|
139
|
-
let toPorts: number | PortRange | Array<number | PortRange>;
|
|
140
|
-
|
|
141
|
-
if (defaultTarget.port === 'preserve') {
|
|
142
|
-
// 'preserve' means use the same ports as the source
|
|
143
|
-
toPorts = fromPorts;
|
|
144
|
-
} else if (typeof defaultTarget.port === 'function') {
|
|
145
|
-
// For function-based ports, we can't determine at setup time
|
|
146
|
-
// Use the "preserve" approach and let NFTables handle it
|
|
147
|
-
toPorts = fromPorts;
|
|
148
|
-
} else {
|
|
149
|
-
toPorts = defaultTarget.port;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Determine target host
|
|
153
|
-
let toHost: string;
|
|
154
|
-
if (typeof defaultTarget.host === 'function') {
|
|
155
|
-
// Can't determine at setup time, use localhost as a placeholder
|
|
156
|
-
// and rely on run-time handling
|
|
157
|
-
toHost = 'localhost';
|
|
158
|
-
} else if (Array.isArray(defaultTarget.host)) {
|
|
159
|
-
// Use first host for now - NFTables will do simple round-robin
|
|
160
|
-
toHost = defaultTarget.host[0];
|
|
161
|
-
} else {
|
|
162
|
-
toHost = defaultTarget.host;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Create options
|
|
166
|
-
const options: NfTableProxyOptions = {
|
|
167
|
-
fromPort: fromPorts,
|
|
168
|
-
toPort: toPorts,
|
|
169
|
-
toHost: toHost,
|
|
170
|
-
protocol: action.nftables?.protocol || 'tcp',
|
|
171
|
-
preserveSourceIP: action.nftables?.preserveSourceIP !== undefined ?
|
|
172
|
-
action.nftables.preserveSourceIP :
|
|
173
|
-
this.smartProxy.settings.preserveSourceIP,
|
|
174
|
-
useIPSets: action.nftables?.useIPSets !== false,
|
|
175
|
-
useAdvancedNAT: action.nftables?.useAdvancedNAT,
|
|
176
|
-
enableLogging: this.smartProxy.settings.enableDetailedLogging,
|
|
177
|
-
deleteOnExit: true,
|
|
178
|
-
tableName: action.nftables?.tableName || 'smartproxy'
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Add security-related options
|
|
182
|
-
if (route.security?.ipAllowList?.length) {
|
|
183
|
-
options.ipAllowList = route.security.ipAllowList;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (route.security?.ipBlockList?.length) {
|
|
187
|
-
options.ipBlockList = route.security.ipBlockList;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Add QoS options
|
|
191
|
-
if (action.nftables?.maxRate || action.nftables?.priority) {
|
|
192
|
-
options.qos = {
|
|
193
|
-
enabled: true,
|
|
194
|
-
maxRate: action.nftables.maxRate,
|
|
195
|
-
priority: action.nftables.priority
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return options;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Expand port range specifications
|
|
204
|
-
*
|
|
205
|
-
* @param ports The port range specification
|
|
206
|
-
* @returns Expanded port range
|
|
207
|
-
*/
|
|
208
|
-
private expandPortRange(ports: TPortRange): number | PortRange | Array<number | PortRange> {
|
|
209
|
-
// Process different port specifications
|
|
210
|
-
if (typeof ports === 'number') {
|
|
211
|
-
return ports;
|
|
212
|
-
} else if (Array.isArray(ports)) {
|
|
213
|
-
const result: Array<number | PortRange> = [];
|
|
214
|
-
|
|
215
|
-
for (const item of ports) {
|
|
216
|
-
if (typeof item === 'number') {
|
|
217
|
-
result.push(item);
|
|
218
|
-
} else if ('from' in item && 'to' in item) {
|
|
219
|
-
result.push({ from: item.from, to: item.to });
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return result;
|
|
224
|
-
} else if (typeof ports === 'object' && ports !== null && 'from' in ports && 'to' in ports) {
|
|
225
|
-
return { from: (ports as any).from, to: (ports as any).to };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Fallback to port 80 if something went wrong
|
|
229
|
-
console.warn('Invalid port range specification, using port 80 as fallback');
|
|
230
|
-
return 80;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get status of all managed rules
|
|
235
|
-
*
|
|
236
|
-
* @returns A promise that resolves to a record of NFTables status objects
|
|
237
|
-
*/
|
|
238
|
-
public async getStatus(): Promise<Record<string, NfTablesStatus>> {
|
|
239
|
-
const result: Record<string, NfTablesStatus> = {};
|
|
240
|
-
|
|
241
|
-
for (const [routeId, proxy] of this.rulesMap.entries()) {
|
|
242
|
-
result[routeId] = await proxy.getStatus();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return result;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Check if a route is currently provisioned
|
|
250
|
-
*
|
|
251
|
-
* @param route The route configuration
|
|
252
|
-
* @returns True if the route is provisioned, false otherwise
|
|
253
|
-
*/
|
|
254
|
-
public isRouteProvisioned(route: IRouteConfig): boolean {
|
|
255
|
-
const routeId = this.generateRouteId(route);
|
|
256
|
-
return this.rulesMap.has(routeId);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Stop all NFTables rules
|
|
261
|
-
*
|
|
262
|
-
* @returns A promise that resolves when all rules have been stopped
|
|
263
|
-
*/
|
|
264
|
-
public async stop(): Promise<void> {
|
|
265
|
-
// Stop all NFTables proxies
|
|
266
|
-
const stopPromises = Array.from(this.rulesMap.values()).map(proxy => proxy.stop());
|
|
267
|
-
await Promise.all(stopPromises);
|
|
268
|
-
|
|
269
|
-
this.rulesMap.clear();
|
|
270
|
-
}
|
|
271
|
-
}
|
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../../plugins.js';
|
|
2
|
-
import { logger } from '../../core/utils/logger.js';
|
|
3
|
-
import { cleanupSocket } from '../../core/utils/socket-utils.js';
|
|
4
|
-
import type { SmartProxy } from './smart-proxy.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* PortManager handles the dynamic creation and removal of port listeners
|
|
8
|
-
*
|
|
9
|
-
* This class provides methods to add and remove listening ports at runtime,
|
|
10
|
-
* allowing SmartProxy to adapt to configuration changes without requiring
|
|
11
|
-
* a full restart.
|
|
12
|
-
*
|
|
13
|
-
* It includes a reference counting system to track how many routes are using
|
|
14
|
-
* each port, so ports can be automatically released when they are no longer needed.
|
|
15
|
-
*/
|
|
16
|
-
export class PortManager {
|
|
17
|
-
private servers: Map<number, plugins.net.Server> = new Map();
|
|
18
|
-
private isShuttingDown: boolean = false;
|
|
19
|
-
// Track how many routes are using each port
|
|
20
|
-
private portRefCounts: Map<number, number> = new Map();
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Create a new PortManager
|
|
24
|
-
*
|
|
25
|
-
* @param smartProxy The SmartProxy instance
|
|
26
|
-
*/
|
|
27
|
-
constructor(
|
|
28
|
-
private smartProxy: SmartProxy
|
|
29
|
-
) {}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Start listening on a specific port
|
|
33
|
-
*
|
|
34
|
-
* @param port The port number to listen on
|
|
35
|
-
* @returns Promise that resolves when the server is listening or rejects on error
|
|
36
|
-
*/
|
|
37
|
-
public async addPort(port: number): Promise<void> {
|
|
38
|
-
// Check if we're already listening on this port
|
|
39
|
-
if (this.servers.has(port)) {
|
|
40
|
-
// Port is already bound, just increment the reference count
|
|
41
|
-
this.incrementPortRefCount(port);
|
|
42
|
-
try {
|
|
43
|
-
logger.log('debug', `PortManager: Port ${port} is already bound by SmartProxy, reusing binding`, {
|
|
44
|
-
port,
|
|
45
|
-
component: 'port-manager'
|
|
46
|
-
});
|
|
47
|
-
} catch (e) {
|
|
48
|
-
console.log(`[DEBUG] PortManager: Port ${port} is already bound by SmartProxy, reusing binding`);
|
|
49
|
-
}
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Initialize reference count for new port
|
|
54
|
-
this.portRefCounts.set(port, 1);
|
|
55
|
-
|
|
56
|
-
// Create a server for this port
|
|
57
|
-
const server = plugins.net.createServer((socket) => {
|
|
58
|
-
// Check if shutting down
|
|
59
|
-
if (this.isShuttingDown) {
|
|
60
|
-
cleanupSocket(socket, 'port-manager-shutdown', { immediate: true });
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Delegate to route connection handler
|
|
65
|
-
this.smartProxy.routeConnectionHandler.handleConnection(socket);
|
|
66
|
-
}).on('error', (err: Error) => {
|
|
67
|
-
try {
|
|
68
|
-
logger.log('error', `Server Error on port ${port}: ${err.message}`, {
|
|
69
|
-
port,
|
|
70
|
-
error: err.message,
|
|
71
|
-
component: 'port-manager'
|
|
72
|
-
});
|
|
73
|
-
} catch (e) {
|
|
74
|
-
console.error(`[ERROR] Server Error on port ${port}: ${err.message}`);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Start listening on the port
|
|
79
|
-
return new Promise<void>((resolve, reject) => {
|
|
80
|
-
server.listen(port, () => {
|
|
81
|
-
const isHttpProxyPort = this.smartProxy.settings.useHttpProxy?.includes(port);
|
|
82
|
-
try {
|
|
83
|
-
logger.log('info', `SmartProxy -> OK: Now listening on port ${port}${
|
|
84
|
-
isHttpProxyPort ? ' (HttpProxy forwarding enabled)' : ''
|
|
85
|
-
}`, {
|
|
86
|
-
port,
|
|
87
|
-
isHttpProxyPort: !!isHttpProxyPort,
|
|
88
|
-
component: 'port-manager'
|
|
89
|
-
});
|
|
90
|
-
} catch (e) {
|
|
91
|
-
console.log(`[INFO] SmartProxy -> OK: Now listening on port ${port}${
|
|
92
|
-
isHttpProxyPort ? ' (HttpProxy forwarding enabled)' : ''
|
|
93
|
-
}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Store the server reference
|
|
97
|
-
this.servers.set(port, server);
|
|
98
|
-
resolve();
|
|
99
|
-
}).on('error', (err) => {
|
|
100
|
-
// Check if this is an external conflict
|
|
101
|
-
const { isConflict, isExternal } = this.isPortConflict(err);
|
|
102
|
-
|
|
103
|
-
if (isConflict && !isExternal) {
|
|
104
|
-
// This is an internal conflict (port already bound by SmartProxy)
|
|
105
|
-
// This shouldn't normally happen because we check servers.has(port) above
|
|
106
|
-
logger.log('warn', `Port ${port} binding conflict: already in use by SmartProxy`, {
|
|
107
|
-
port,
|
|
108
|
-
component: 'port-manager'
|
|
109
|
-
});
|
|
110
|
-
// Still increment reference count to maintain tracking
|
|
111
|
-
this.incrementPortRefCount(port);
|
|
112
|
-
resolve();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Log the error and propagate it
|
|
117
|
-
logger.log('error', `Failed to listen on port ${port}: ${err.message}`, {
|
|
118
|
-
port,
|
|
119
|
-
error: err.message,
|
|
120
|
-
code: (err as any).code,
|
|
121
|
-
component: 'port-manager'
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Clean up reference count since binding failed
|
|
125
|
-
this.portRefCounts.delete(port);
|
|
126
|
-
|
|
127
|
-
reject(err);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Stop listening on a specific port
|
|
134
|
-
*
|
|
135
|
-
* @param port The port to stop listening on
|
|
136
|
-
* @returns Promise that resolves when the server is closed
|
|
137
|
-
*/
|
|
138
|
-
public async removePort(port: number): Promise<void> {
|
|
139
|
-
// Decrement the reference count first
|
|
140
|
-
const newRefCount = this.decrementPortRefCount(port);
|
|
141
|
-
|
|
142
|
-
// If there are still references to this port, keep it open
|
|
143
|
-
if (newRefCount > 0) {
|
|
144
|
-
logger.log('debug', `PortManager: Port ${port} still has ${newRefCount} references, keeping open`, {
|
|
145
|
-
port,
|
|
146
|
-
refCount: newRefCount,
|
|
147
|
-
component: 'port-manager'
|
|
148
|
-
});
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Get the server for this port
|
|
153
|
-
const server = this.servers.get(port);
|
|
154
|
-
if (!server) {
|
|
155
|
-
logger.log('warn', `PortManager: Not listening on port ${port}`, {
|
|
156
|
-
port,
|
|
157
|
-
component: 'port-manager'
|
|
158
|
-
});
|
|
159
|
-
// Ensure reference count is reset
|
|
160
|
-
this.portRefCounts.delete(port);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Close the server
|
|
165
|
-
return new Promise<void>((resolve) => {
|
|
166
|
-
server.close((err) => {
|
|
167
|
-
if (err) {
|
|
168
|
-
logger.log('error', `Error closing server on port ${port}: ${err.message}`, {
|
|
169
|
-
port,
|
|
170
|
-
error: err.message,
|
|
171
|
-
component: 'port-manager'
|
|
172
|
-
});
|
|
173
|
-
} else {
|
|
174
|
-
logger.log('info', `SmartProxy -> Stopped listening on port ${port}`, {
|
|
175
|
-
port,
|
|
176
|
-
component: 'port-manager'
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Remove the server reference and clean up reference counting
|
|
181
|
-
this.servers.delete(port);
|
|
182
|
-
this.portRefCounts.delete(port);
|
|
183
|
-
resolve();
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Add multiple ports at once
|
|
190
|
-
*
|
|
191
|
-
* @param ports Array of ports to add
|
|
192
|
-
* @returns Promise that resolves when all servers are listening
|
|
193
|
-
*/
|
|
194
|
-
public async addPorts(ports: number[]): Promise<void> {
|
|
195
|
-
const uniquePorts = [...new Set(ports)];
|
|
196
|
-
await Promise.all(uniquePorts.map(port => this.addPort(port)));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Remove multiple ports at once
|
|
201
|
-
*
|
|
202
|
-
* @param ports Array of ports to remove
|
|
203
|
-
* @returns Promise that resolves when all servers are closed
|
|
204
|
-
*/
|
|
205
|
-
public async removePorts(ports: number[]): Promise<void> {
|
|
206
|
-
const uniquePorts = [...new Set(ports)];
|
|
207
|
-
await Promise.all(uniquePorts.map(port => this.removePort(port)));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Update listening ports to match the provided list
|
|
212
|
-
*
|
|
213
|
-
* This will add any ports that aren't currently listening,
|
|
214
|
-
* and remove any ports that are no longer needed.
|
|
215
|
-
*
|
|
216
|
-
* @param ports Array of ports that should be listening
|
|
217
|
-
* @returns Promise that resolves when all operations are complete
|
|
218
|
-
*/
|
|
219
|
-
public async updatePorts(ports: number[]): Promise<void> {
|
|
220
|
-
const targetPorts = new Set(ports);
|
|
221
|
-
const currentPorts = new Set(this.servers.keys());
|
|
222
|
-
|
|
223
|
-
// Find ports to add and remove
|
|
224
|
-
const portsToAdd = ports.filter(port => !currentPorts.has(port));
|
|
225
|
-
const portsToRemove = Array.from(currentPorts).filter(port => !targetPorts.has(port));
|
|
226
|
-
|
|
227
|
-
// Log the changes
|
|
228
|
-
if (portsToAdd.length > 0) {
|
|
229
|
-
console.log(`PortManager: Adding new listeners for ports: ${portsToAdd.join(', ')}`);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (portsToRemove.length > 0) {
|
|
233
|
-
console.log(`PortManager: Removing listeners for ports: ${portsToRemove.join(', ')}`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Add and remove ports
|
|
237
|
-
await this.removePorts(portsToRemove);
|
|
238
|
-
await this.addPorts(portsToAdd);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Get all ports that are currently listening
|
|
243
|
-
*
|
|
244
|
-
* @returns Array of port numbers
|
|
245
|
-
*/
|
|
246
|
-
public getListeningPorts(): number[] {
|
|
247
|
-
return Array.from(this.servers.keys());
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Mark the port manager as shutting down
|
|
252
|
-
*/
|
|
253
|
-
public setShuttingDown(isShuttingDown: boolean): void {
|
|
254
|
-
this.isShuttingDown = isShuttingDown;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Close all listening servers
|
|
259
|
-
*
|
|
260
|
-
* @returns Promise that resolves when all servers are closed
|
|
261
|
-
*/
|
|
262
|
-
public async closeAll(): Promise<void> {
|
|
263
|
-
const allPorts = Array.from(this.servers.keys());
|
|
264
|
-
await this.removePorts(allPorts);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Get all server instances (for testing or debugging)
|
|
269
|
-
*/
|
|
270
|
-
public getServers(): Map<number, plugins.net.Server> {
|
|
271
|
-
return new Map(this.servers);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Check if a port is bound by this SmartProxy instance
|
|
276
|
-
*
|
|
277
|
-
* @param port The port number to check
|
|
278
|
-
* @returns True if the port is currently bound by SmartProxy
|
|
279
|
-
*/
|
|
280
|
-
public isPortBoundBySmartProxy(port: number): boolean {
|
|
281
|
-
return this.servers.has(port);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Get the current reference count for a port
|
|
286
|
-
*
|
|
287
|
-
* @param port The port number to check
|
|
288
|
-
* @returns The number of routes using this port, 0 if none
|
|
289
|
-
*/
|
|
290
|
-
public getPortRefCount(port: number): number {
|
|
291
|
-
return this.portRefCounts.get(port) || 0;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Increment the reference count for a port
|
|
296
|
-
*
|
|
297
|
-
* @param port The port number to increment
|
|
298
|
-
* @returns The new reference count
|
|
299
|
-
*/
|
|
300
|
-
public incrementPortRefCount(port: number): number {
|
|
301
|
-
const currentCount = this.portRefCounts.get(port) || 0;
|
|
302
|
-
const newCount = currentCount + 1;
|
|
303
|
-
this.portRefCounts.set(port, newCount);
|
|
304
|
-
|
|
305
|
-
logger.log('debug', `Port ${port} reference count increased to ${newCount}`, {
|
|
306
|
-
port,
|
|
307
|
-
refCount: newCount,
|
|
308
|
-
component: 'port-manager'
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
return newCount;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Decrement the reference count for a port
|
|
316
|
-
*
|
|
317
|
-
* @param port The port number to decrement
|
|
318
|
-
* @returns The new reference count
|
|
319
|
-
*/
|
|
320
|
-
public decrementPortRefCount(port: number): number {
|
|
321
|
-
const currentCount = this.portRefCounts.get(port) || 0;
|
|
322
|
-
|
|
323
|
-
if (currentCount <= 0) {
|
|
324
|
-
logger.log('warn', `Attempted to decrement reference count for port ${port} below zero`, {
|
|
325
|
-
port,
|
|
326
|
-
component: 'port-manager'
|
|
327
|
-
});
|
|
328
|
-
return 0;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const newCount = currentCount - 1;
|
|
332
|
-
this.portRefCounts.set(port, newCount);
|
|
333
|
-
|
|
334
|
-
logger.log('debug', `Port ${port} reference count decreased to ${newCount}`, {
|
|
335
|
-
port,
|
|
336
|
-
refCount: newCount,
|
|
337
|
-
component: 'port-manager'
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
return newCount;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Determine if a port binding error is due to an external or internal conflict
|
|
345
|
-
*
|
|
346
|
-
* @param error The error object from a failed port binding
|
|
347
|
-
* @returns Object indicating if this is a conflict and if it's external
|
|
348
|
-
*/
|
|
349
|
-
private isPortConflict(error: any): { isConflict: boolean; isExternal: boolean } {
|
|
350
|
-
if (error.code !== 'EADDRINUSE') {
|
|
351
|
-
return { isConflict: false, isExternal: false };
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Check if we already have this port
|
|
355
|
-
const isBoundInternally = this.servers.has(Number(error.port));
|
|
356
|
-
return { isConflict: true, isExternal: !isBoundInternally };
|
|
357
|
-
}
|
|
358
|
-
}
|