@push.rocks/smartproxy 5.0.0 → 6.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.pp.interfaces.d.ts +23 -0
- package/dist_ts/classes.pp.networkproxybridge.d.ts +15 -1
- package/dist_ts/classes.pp.networkproxybridge.js +116 -21
- package/dist_ts/classes.pp.portproxy.d.ts +20 -4
- package/dist_ts/classes.pp.portproxy.js +321 -22
- package/dist_ts/index.d.ts +6 -6
- package/dist_ts/index.js +7 -7
- package/dist_ts/networkproxy/classes.np.certificatemanager.d.ts +77 -0
- package/dist_ts/networkproxy/classes.np.certificatemanager.js +354 -0
- package/dist_ts/networkproxy/classes.np.connectionpool.d.ts +47 -0
- package/dist_ts/networkproxy/classes.np.connectionpool.js +210 -0
- package/dist_ts/networkproxy/classes.np.networkproxy.d.ts +117 -0
- package/dist_ts/networkproxy/classes.np.networkproxy.js +375 -0
- package/dist_ts/networkproxy/classes.np.requesthandler.d.ts +51 -0
- package/dist_ts/networkproxy/classes.np.requesthandler.js +210 -0
- package/dist_ts/networkproxy/classes.np.types.d.ts +82 -0
- package/dist_ts/networkproxy/classes.np.types.js +35 -0
- package/dist_ts/networkproxy/classes.np.websockethandler.d.ts +38 -0
- package/dist_ts/networkproxy/classes.np.websockethandler.js +188 -0
- package/dist_ts/networkproxy/index.d.ts +6 -0
- package/dist_ts/networkproxy/index.js +8 -0
- package/dist_ts/nfttablesproxy/classes.nftablesproxy.d.ts +219 -0
- package/dist_ts/nfttablesproxy/classes.nftablesproxy.js +1542 -0
- package/dist_ts/port80handler/classes.port80handler.d.ts +260 -0
- package/dist_ts/port80handler/classes.port80handler.js +928 -0
- package/dist_ts/smartproxy/classes.pp.connectionhandler.d.ts +39 -0
- package/dist_ts/smartproxy/classes.pp.connectionhandler.js +754 -0
- package/dist_ts/smartproxy/classes.pp.connectionmanager.d.ts +78 -0
- package/dist_ts/smartproxy/classes.pp.connectionmanager.js +378 -0
- package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +55 -0
- package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +103 -0
- package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +133 -0
- package/dist_ts/smartproxy/classes.pp.interfaces.js +2 -0
- package/dist_ts/smartproxy/classes.pp.networkproxybridge.d.ts +57 -0
- package/dist_ts/smartproxy/classes.pp.networkproxybridge.js +306 -0
- package/dist_ts/smartproxy/classes.pp.portrangemanager.d.ts +56 -0
- package/dist_ts/smartproxy/classes.pp.portrangemanager.js +179 -0
- package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +47 -0
- package/dist_ts/smartproxy/classes.pp.securitymanager.js +126 -0
- package/dist_ts/smartproxy/classes.pp.snihandler.d.ts +153 -0
- package/dist_ts/smartproxy/classes.pp.snihandler.js +1053 -0
- package/dist_ts/smartproxy/classes.pp.timeoutmanager.d.ts +47 -0
- package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +154 -0
- package/dist_ts/smartproxy/classes.pp.tlsalert.d.ts +149 -0
- package/dist_ts/smartproxy/classes.pp.tlsalert.js +225 -0
- package/dist_ts/smartproxy/classes.pp.tlsmanager.d.ts +57 -0
- package/dist_ts/smartproxy/classes.pp.tlsmanager.js +132 -0
- package/dist_ts/smartproxy/classes.smartproxy.d.ts +64 -0
- package/dist_ts/smartproxy/classes.smartproxy.js +567 -0
- package/package.json +1 -1
- package/readme.md +77 -27
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +6 -6
- package/ts/networkproxy/classes.np.certificatemanager.ts +398 -0
- package/ts/networkproxy/classes.np.connectionpool.ts +241 -0
- package/ts/networkproxy/classes.np.networkproxy.ts +469 -0
- package/ts/networkproxy/classes.np.requesthandler.ts +278 -0
- package/ts/networkproxy/classes.np.types.ts +123 -0
- package/ts/networkproxy/classes.np.websockethandler.ts +226 -0
- package/ts/networkproxy/index.ts +7 -0
- package/ts/{classes.port80handler.ts → port80handler/classes.port80handler.ts} +249 -1
- package/ts/{classes.pp.connectionhandler.ts → smartproxy/classes.pp.connectionhandler.ts} +1 -1
- package/ts/{classes.pp.connectionmanager.ts → smartproxy/classes.pp.connectionmanager.ts} +1 -1
- package/ts/{classes.pp.domainconfigmanager.ts → smartproxy/classes.pp.domainconfigmanager.ts} +1 -1
- package/ts/{classes.pp.interfaces.ts → smartproxy/classes.pp.interfaces.ts} +31 -5
- package/ts/{classes.pp.networkproxybridge.ts → smartproxy/classes.pp.networkproxybridge.ts} +129 -28
- package/ts/{classes.pp.securitymanager.ts → smartproxy/classes.pp.securitymanager.ts} +1 -1
- package/ts/{classes.pp.tlsmanager.ts → smartproxy/classes.pp.tlsmanager.ts} +1 -1
- package/ts/smartproxy/classes.smartproxy.ts +679 -0
- package/ts/classes.networkproxy.ts +0 -1730
- package/ts/classes.pp.acmemanager.ts +0 -149
- package/ts/classes.pp.portproxy.ts +0 -344
- /package/ts/{classes.nftablesproxy.ts → nfttablesproxy/classes.nftablesproxy.ts} +0 -0
- /package/ts/{classes.pp.portrangemanager.ts → smartproxy/classes.pp.portrangemanager.ts} +0 -0
- /package/ts/{classes.pp.snihandler.ts → smartproxy/classes.pp.snihandler.ts} +0 -0
- /package/ts/{classes.pp.timeoutmanager.ts → smartproxy/classes.pp.timeoutmanager.ts} +0 -0
- /package/ts/{classes.pp.tlsalert.ts → smartproxy/classes.pp.tlsalert.ts} +0 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import type { IPortProxySettings, IDomainConfig } from './classes.pp.interfaces.js';
|
|
3
|
+
import { ConnectionManager } from './classes.pp.connectionmanager.js';
|
|
4
|
+
import { SecurityManager } from './classes.pp.securitymanager.js';
|
|
5
|
+
import { DomainConfigManager } from './classes.pp.domainconfigmanager.js';
|
|
6
|
+
import { TlsManager } from './classes.pp.tlsmanager.js';
|
|
7
|
+
import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
|
|
8
|
+
import { TimeoutManager } from './classes.pp.timeoutmanager.js';
|
|
9
|
+
import { PortRangeManager } from './classes.pp.portrangemanager.js';
|
|
10
|
+
import { ConnectionHandler } from './classes.pp.connectionhandler.js';
|
|
11
|
+
import { Port80Handler, Port80HandlerEvents } from '../port80handler/classes.port80handler.js';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* SmartProxy - Main class that coordinates all components
|
|
17
|
+
*/
|
|
18
|
+
export class SmartProxy {
|
|
19
|
+
private netServers: plugins.net.Server[] = [];
|
|
20
|
+
private connectionLogger: NodeJS.Timeout | null = null;
|
|
21
|
+
private isShuttingDown: boolean = false;
|
|
22
|
+
|
|
23
|
+
// Component managers
|
|
24
|
+
private connectionManager: ConnectionManager;
|
|
25
|
+
private securityManager: SecurityManager;
|
|
26
|
+
public domainConfigManager: DomainConfigManager;
|
|
27
|
+
private tlsManager: TlsManager;
|
|
28
|
+
private networkProxyBridge: NetworkProxyBridge;
|
|
29
|
+
private timeoutManager: TimeoutManager;
|
|
30
|
+
private portRangeManager: PortRangeManager;
|
|
31
|
+
private connectionHandler: ConnectionHandler;
|
|
32
|
+
|
|
33
|
+
// Port80Handler for ACME certificate management
|
|
34
|
+
private port80Handler: Port80Handler | null = null;
|
|
35
|
+
|
|
36
|
+
constructor(settingsArg: IPortProxySettings) {
|
|
37
|
+
// Set reasonable defaults for all settings
|
|
38
|
+
this.settings = {
|
|
39
|
+
...settingsArg,
|
|
40
|
+
targetIP: settingsArg.targetIP || 'localhost',
|
|
41
|
+
initialDataTimeout: settingsArg.initialDataTimeout || 120000,
|
|
42
|
+
socketTimeout: settingsArg.socketTimeout || 3600000,
|
|
43
|
+
inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000,
|
|
44
|
+
maxConnectionLifetime: settingsArg.maxConnectionLifetime || 86400000,
|
|
45
|
+
inactivityTimeout: settingsArg.inactivityTimeout || 14400000,
|
|
46
|
+
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000,
|
|
47
|
+
noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
|
|
48
|
+
keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
|
|
49
|
+
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000,
|
|
50
|
+
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024,
|
|
51
|
+
disableInactivityCheck: settingsArg.disableInactivityCheck || false,
|
|
52
|
+
enableKeepAliveProbes:
|
|
53
|
+
settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true,
|
|
54
|
+
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
|
55
|
+
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
|
56
|
+
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
|
57
|
+
allowSessionTicket:
|
|
58
|
+
settingsArg.allowSessionTicket !== undefined ? settingsArg.allowSessionTicket : true,
|
|
59
|
+
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
|
|
60
|
+
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300,
|
|
61
|
+
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
|
|
62
|
+
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
|
|
63
|
+
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000,
|
|
64
|
+
networkProxyPort: settingsArg.networkProxyPort || 8443,
|
|
65
|
+
port80HandlerConfig: settingsArg.port80HandlerConfig || {},
|
|
66
|
+
globalPortRanges: settingsArg.globalPortRanges || [],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Set port80HandlerConfig defaults, using legacy acme config if available
|
|
70
|
+
if (!this.settings.port80HandlerConfig || Object.keys(this.settings.port80HandlerConfig).length === 0) {
|
|
71
|
+
if (this.settings.acme) {
|
|
72
|
+
// Migrate from legacy acme config
|
|
73
|
+
this.settings.port80HandlerConfig = {
|
|
74
|
+
enabled: this.settings.acme.enabled,
|
|
75
|
+
port: this.settings.acme.port || 80,
|
|
76
|
+
contactEmail: this.settings.acme.contactEmail || 'admin@example.com',
|
|
77
|
+
useProduction: this.settings.acme.useProduction || false,
|
|
78
|
+
renewThresholdDays: this.settings.acme.renewThresholdDays || 30,
|
|
79
|
+
autoRenew: this.settings.acme.autoRenew !== false, // Default to true
|
|
80
|
+
certificateStore: this.settings.acme.certificateStore || './certs',
|
|
81
|
+
skipConfiguredCerts: this.settings.acme.skipConfiguredCerts || false,
|
|
82
|
+
httpsRedirectPort: this.settings.fromPort,
|
|
83
|
+
renewCheckIntervalHours: 24
|
|
84
|
+
};
|
|
85
|
+
} else {
|
|
86
|
+
// Set defaults if no config provided
|
|
87
|
+
this.settings.port80HandlerConfig = {
|
|
88
|
+
enabled: false,
|
|
89
|
+
port: 80,
|
|
90
|
+
contactEmail: 'admin@example.com',
|
|
91
|
+
useProduction: false,
|
|
92
|
+
renewThresholdDays: 30,
|
|
93
|
+
autoRenew: true,
|
|
94
|
+
certificateStore: './certs',
|
|
95
|
+
skipConfiguredCerts: false,
|
|
96
|
+
httpsRedirectPort: this.settings.fromPort,
|
|
97
|
+
renewCheckIntervalHours: 24
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Initialize component managers
|
|
103
|
+
this.timeoutManager = new TimeoutManager(this.settings);
|
|
104
|
+
this.securityManager = new SecurityManager(this.settings);
|
|
105
|
+
this.connectionManager = new ConnectionManager(
|
|
106
|
+
this.settings,
|
|
107
|
+
this.securityManager,
|
|
108
|
+
this.timeoutManager
|
|
109
|
+
);
|
|
110
|
+
this.domainConfigManager = new DomainConfigManager(this.settings);
|
|
111
|
+
this.tlsManager = new TlsManager(this.settings);
|
|
112
|
+
this.networkProxyBridge = new NetworkProxyBridge(this.settings);
|
|
113
|
+
this.portRangeManager = new PortRangeManager(this.settings);
|
|
114
|
+
|
|
115
|
+
// Initialize connection handler
|
|
116
|
+
this.connectionHandler = new ConnectionHandler(
|
|
117
|
+
this.settings,
|
|
118
|
+
this.connectionManager,
|
|
119
|
+
this.securityManager,
|
|
120
|
+
this.domainConfigManager,
|
|
121
|
+
this.tlsManager,
|
|
122
|
+
this.networkProxyBridge,
|
|
123
|
+
this.timeoutManager,
|
|
124
|
+
this.portRangeManager
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The settings for the port proxy
|
|
130
|
+
*/
|
|
131
|
+
public settings: IPortProxySettings;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Initialize the Port80Handler for ACME certificate management
|
|
135
|
+
*/
|
|
136
|
+
private async initializePort80Handler(): Promise<void> {
|
|
137
|
+
const config = this.settings.port80HandlerConfig;
|
|
138
|
+
|
|
139
|
+
if (!config || !config.enabled) {
|
|
140
|
+
console.log('Port80Handler is disabled in configuration');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// Ensure the certificate store directory exists
|
|
146
|
+
if (config.certificateStore) {
|
|
147
|
+
const certStorePath = path.resolve(config.certificateStore);
|
|
148
|
+
if (!fs.existsSync(certStorePath)) {
|
|
149
|
+
fs.mkdirSync(certStorePath, { recursive: true });
|
|
150
|
+
console.log(`Created certificate store directory: ${certStorePath}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Create Port80Handler with options from config
|
|
155
|
+
this.port80Handler = new Port80Handler({
|
|
156
|
+
port: config.port,
|
|
157
|
+
contactEmail: config.contactEmail,
|
|
158
|
+
useProduction: config.useProduction,
|
|
159
|
+
renewThresholdDays: config.renewThresholdDays,
|
|
160
|
+
httpsRedirectPort: config.httpsRedirectPort || this.settings.fromPort,
|
|
161
|
+
renewCheckIntervalHours: config.renewCheckIntervalHours,
|
|
162
|
+
enabled: config.enabled,
|
|
163
|
+
autoRenew: config.autoRenew,
|
|
164
|
+
certificateStore: config.certificateStore,
|
|
165
|
+
skipConfiguredCerts: config.skipConfiguredCerts
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Register domain forwarding configurations
|
|
169
|
+
if (config.domainForwards) {
|
|
170
|
+
for (const forward of config.domainForwards) {
|
|
171
|
+
this.port80Handler.addDomain({
|
|
172
|
+
domainName: forward.domain,
|
|
173
|
+
sslRedirect: true,
|
|
174
|
+
acmeMaintenance: true,
|
|
175
|
+
forward: forward.forwardConfig,
|
|
176
|
+
acmeForward: forward.acmeForwardConfig
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
console.log(`Registered domain forwarding for ${forward.domain}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Register all non-wildcard domains from domain configs
|
|
184
|
+
for (const domainConfig of this.settings.domainConfigs) {
|
|
185
|
+
for (const domain of domainConfig.domains) {
|
|
186
|
+
// Skip wildcards
|
|
187
|
+
if (domain.includes('*')) continue;
|
|
188
|
+
|
|
189
|
+
this.port80Handler.addDomain({
|
|
190
|
+
domainName: domain,
|
|
191
|
+
sslRedirect: true,
|
|
192
|
+
acmeMaintenance: true
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
console.log(`Registered domain ${domain} with Port80Handler`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Set up event listeners
|
|
200
|
+
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, (certData) => {
|
|
201
|
+
console.log(`Certificate issued for ${certData.domain}, valid until ${certData.expiryDate.toISOString()}`);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, (certData) => {
|
|
205
|
+
console.log(`Certificate renewed for ${certData.domain}, valid until ${certData.expiryDate.toISOString()}`);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, (failureData) => {
|
|
209
|
+
console.log(`Certificate ${failureData.isRenewal ? 'renewal' : 'issuance'} failed for ${failureData.domain}: ${failureData.error}`);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, (expiryData) => {
|
|
213
|
+
console.log(`Certificate for ${expiryData.domain} is expiring in ${expiryData.daysRemaining} days`);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Share Port80Handler with NetworkProxyBridge
|
|
217
|
+
this.networkProxyBridge.setPort80Handler(this.port80Handler);
|
|
218
|
+
|
|
219
|
+
// Start Port80Handler
|
|
220
|
+
await this.port80Handler.start();
|
|
221
|
+
console.log(`Port80Handler started on port ${config.port}`);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.log(`Error initializing Port80Handler: ${err}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Start the proxy server
|
|
229
|
+
*/
|
|
230
|
+
public async start() {
|
|
231
|
+
// Don't start if already shutting down
|
|
232
|
+
if (this.isShuttingDown) {
|
|
233
|
+
console.log("Cannot start PortProxy while it's shutting down");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Initialize Port80Handler if enabled
|
|
238
|
+
await this.initializePort80Handler();
|
|
239
|
+
|
|
240
|
+
// Initialize and start NetworkProxy if needed
|
|
241
|
+
if (
|
|
242
|
+
this.settings.useNetworkProxy &&
|
|
243
|
+
this.settings.useNetworkProxy.length > 0
|
|
244
|
+
) {
|
|
245
|
+
await this.networkProxyBridge.initialize();
|
|
246
|
+
await this.networkProxyBridge.start();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Validate port configuration
|
|
250
|
+
const configWarnings = this.portRangeManager.validateConfiguration();
|
|
251
|
+
if (configWarnings.length > 0) {
|
|
252
|
+
console.log("Port configuration warnings:");
|
|
253
|
+
for (const warning of configWarnings) {
|
|
254
|
+
console.log(` - ${warning}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Get listening ports from PortRangeManager
|
|
259
|
+
const listeningPorts = this.portRangeManager.getListeningPorts();
|
|
260
|
+
|
|
261
|
+
// Create servers for each port
|
|
262
|
+
for (const port of listeningPorts) {
|
|
263
|
+
const server = plugins.net.createServer((socket) => {
|
|
264
|
+
// Check if shutting down
|
|
265
|
+
if (this.isShuttingDown) {
|
|
266
|
+
socket.end();
|
|
267
|
+
socket.destroy();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Delegate to connection handler
|
|
272
|
+
this.connectionHandler.handleConnection(socket);
|
|
273
|
+
}).on('error', (err: Error) => {
|
|
274
|
+
console.log(`Server Error on port ${port}: ${err.message}`);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
server.listen(port, () => {
|
|
278
|
+
const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
|
|
279
|
+
console.log(
|
|
280
|
+
`PortProxy -> OK: Now listening on port ${port}${
|
|
281
|
+
this.settings.sniEnabled && !isNetworkProxyPort ? ' (SNI passthrough enabled)' : ''
|
|
282
|
+
}${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}`
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
this.netServers.push(server);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Set up periodic connection logging and inactivity checks
|
|
290
|
+
this.connectionLogger = setInterval(() => {
|
|
291
|
+
// Immediately return if shutting down
|
|
292
|
+
if (this.isShuttingDown) return;
|
|
293
|
+
|
|
294
|
+
// Perform inactivity check
|
|
295
|
+
this.connectionManager.performInactivityCheck();
|
|
296
|
+
|
|
297
|
+
// Log connection statistics
|
|
298
|
+
const now = Date.now();
|
|
299
|
+
let maxIncoming = 0;
|
|
300
|
+
let maxOutgoing = 0;
|
|
301
|
+
let tlsConnections = 0;
|
|
302
|
+
let nonTlsConnections = 0;
|
|
303
|
+
let completedTlsHandshakes = 0;
|
|
304
|
+
let pendingTlsHandshakes = 0;
|
|
305
|
+
let keepAliveConnections = 0;
|
|
306
|
+
let networkProxyConnections = 0;
|
|
307
|
+
|
|
308
|
+
// Get connection records for analysis
|
|
309
|
+
const connectionRecords = this.connectionManager.getConnections();
|
|
310
|
+
|
|
311
|
+
// Analyze active connections
|
|
312
|
+
for (const record of connectionRecords.values()) {
|
|
313
|
+
// Track connection stats
|
|
314
|
+
if (record.isTLS) {
|
|
315
|
+
tlsConnections++;
|
|
316
|
+
if (record.tlsHandshakeComplete) {
|
|
317
|
+
completedTlsHandshakes++;
|
|
318
|
+
} else {
|
|
319
|
+
pendingTlsHandshakes++;
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
nonTlsConnections++;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (record.hasKeepAlive) {
|
|
326
|
+
keepAliveConnections++;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (record.usingNetworkProxy) {
|
|
330
|
+
networkProxyConnections++;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
|
|
334
|
+
if (record.outgoingStartTime) {
|
|
335
|
+
maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Get termination stats
|
|
340
|
+
const terminationStats = this.connectionManager.getTerminationStats();
|
|
341
|
+
|
|
342
|
+
// Log detailed stats
|
|
343
|
+
console.log(
|
|
344
|
+
`Active connections: ${connectionRecords.size}. ` +
|
|
345
|
+
`Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` +
|
|
346
|
+
`Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, NetworkProxy=${networkProxyConnections}. ` +
|
|
347
|
+
`Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(maxOutgoing)}. ` +
|
|
348
|
+
`Termination stats: ${JSON.stringify({
|
|
349
|
+
IN: terminationStats.incoming,
|
|
350
|
+
OUT: terminationStats.outgoing,
|
|
351
|
+
})}`
|
|
352
|
+
);
|
|
353
|
+
}, this.settings.inactivityCheckInterval || 60000);
|
|
354
|
+
|
|
355
|
+
// Make sure the interval doesn't keep the process alive
|
|
356
|
+
if (this.connectionLogger.unref) {
|
|
357
|
+
this.connectionLogger.unref();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Stop the proxy server
|
|
363
|
+
*/
|
|
364
|
+
public async stop() {
|
|
365
|
+
console.log('PortProxy shutting down...');
|
|
366
|
+
this.isShuttingDown = true;
|
|
367
|
+
|
|
368
|
+
// Stop the Port80Handler if running
|
|
369
|
+
if (this.port80Handler) {
|
|
370
|
+
try {
|
|
371
|
+
await this.port80Handler.stop();
|
|
372
|
+
console.log('Port80Handler stopped');
|
|
373
|
+
this.port80Handler = null;
|
|
374
|
+
} catch (err) {
|
|
375
|
+
console.log(`Error stopping Port80Handler: ${err}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Stop accepting new connections
|
|
380
|
+
const closeServerPromises: Promise<void>[] = this.netServers.map(
|
|
381
|
+
(server) =>
|
|
382
|
+
new Promise<void>((resolve) => {
|
|
383
|
+
if (!server.listening) {
|
|
384
|
+
resolve();
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
server.close((err) => {
|
|
388
|
+
if (err) {
|
|
389
|
+
console.log(`Error closing server: ${err.message}`);
|
|
390
|
+
}
|
|
391
|
+
resolve();
|
|
392
|
+
});
|
|
393
|
+
})
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// Stop the connection logger
|
|
397
|
+
if (this.connectionLogger) {
|
|
398
|
+
clearInterval(this.connectionLogger);
|
|
399
|
+
this.connectionLogger = null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Wait for servers to close
|
|
403
|
+
await Promise.all(closeServerPromises);
|
|
404
|
+
console.log('All servers closed. Cleaning up active connections...');
|
|
405
|
+
|
|
406
|
+
// Clean up all active connections
|
|
407
|
+
this.connectionManager.clearConnections();
|
|
408
|
+
|
|
409
|
+
// Stop NetworkProxy
|
|
410
|
+
await this.networkProxyBridge.stop();
|
|
411
|
+
|
|
412
|
+
// Clear all servers
|
|
413
|
+
this.netServers = [];
|
|
414
|
+
|
|
415
|
+
console.log('PortProxy shutdown complete.');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Updates the domain configurations for the proxy
|
|
420
|
+
*/
|
|
421
|
+
public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> {
|
|
422
|
+
console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
|
|
423
|
+
|
|
424
|
+
// Update domain configs in DomainConfigManager
|
|
425
|
+
this.domainConfigManager.updateDomainConfigs(newDomainConfigs);
|
|
426
|
+
|
|
427
|
+
// If NetworkProxy is initialized, resync the configurations
|
|
428
|
+
if (this.networkProxyBridge.getNetworkProxy()) {
|
|
429
|
+
await this.networkProxyBridge.syncDomainConfigsToNetworkProxy();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// If Port80Handler is running, register non-wildcard domains
|
|
433
|
+
if (this.port80Handler && this.settings.port80HandlerConfig?.enabled) {
|
|
434
|
+
for (const domainConfig of newDomainConfigs) {
|
|
435
|
+
for (const domain of domainConfig.domains) {
|
|
436
|
+
// Skip wildcards
|
|
437
|
+
if (domain.includes('*')) continue;
|
|
438
|
+
|
|
439
|
+
this.port80Handler.addDomain({
|
|
440
|
+
domainName: domain,
|
|
441
|
+
sslRedirect: true,
|
|
442
|
+
acmeMaintenance: true
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
console.log('Registered non-wildcard domains with Port80Handler');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Updates the Port80Handler configuration
|
|
453
|
+
*/
|
|
454
|
+
public async updatePort80HandlerConfig(config: IPortProxySettings['port80HandlerConfig']): Promise<void> {
|
|
455
|
+
if (!config) return;
|
|
456
|
+
|
|
457
|
+
console.log('Updating Port80Handler configuration');
|
|
458
|
+
|
|
459
|
+
// Update the settings
|
|
460
|
+
this.settings.port80HandlerConfig = {
|
|
461
|
+
...this.settings.port80HandlerConfig,
|
|
462
|
+
...config
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Check if we need to restart Port80Handler
|
|
466
|
+
let needsRestart = false;
|
|
467
|
+
|
|
468
|
+
// Restart if enabled state changed
|
|
469
|
+
if (this.port80Handler && config.enabled === false) {
|
|
470
|
+
needsRestart = true;
|
|
471
|
+
} else if (!this.port80Handler && config.enabled === true) {
|
|
472
|
+
needsRestart = true;
|
|
473
|
+
} else if (this.port80Handler && (
|
|
474
|
+
config.port !== undefined ||
|
|
475
|
+
config.contactEmail !== undefined ||
|
|
476
|
+
config.useProduction !== undefined ||
|
|
477
|
+
config.renewThresholdDays !== undefined ||
|
|
478
|
+
config.renewCheckIntervalHours !== undefined
|
|
479
|
+
)) {
|
|
480
|
+
// Restart if critical settings changed
|
|
481
|
+
needsRestart = true;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (needsRestart) {
|
|
485
|
+
// Stop if running
|
|
486
|
+
if (this.port80Handler) {
|
|
487
|
+
try {
|
|
488
|
+
await this.port80Handler.stop();
|
|
489
|
+
this.port80Handler = null;
|
|
490
|
+
console.log('Stopped Port80Handler for configuration update');
|
|
491
|
+
} catch (err) {
|
|
492
|
+
console.log(`Error stopping Port80Handler: ${err}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Start with new config if enabled
|
|
497
|
+
if (this.settings.port80HandlerConfig.enabled) {
|
|
498
|
+
await this.initializePort80Handler();
|
|
499
|
+
console.log('Restarted Port80Handler with new configuration');
|
|
500
|
+
}
|
|
501
|
+
} else if (this.port80Handler) {
|
|
502
|
+
// Just update domain forwards if they changed
|
|
503
|
+
if (config.domainForwards) {
|
|
504
|
+
for (const forward of config.domainForwards) {
|
|
505
|
+
this.port80Handler.addDomain({
|
|
506
|
+
domainName: forward.domain,
|
|
507
|
+
sslRedirect: true,
|
|
508
|
+
acmeMaintenance: true,
|
|
509
|
+
forward: forward.forwardConfig,
|
|
510
|
+
acmeForward: forward.acmeForwardConfig
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
console.log('Updated domain forwards in Port80Handler');
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Request a certificate for a specific domain
|
|
520
|
+
*/
|
|
521
|
+
public async requestCertificate(domain: string): Promise<boolean> {
|
|
522
|
+
// Validate domain format
|
|
523
|
+
if (!this.isValidDomain(domain)) {
|
|
524
|
+
console.log(`Invalid domain format: ${domain}`);
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Use Port80Handler if available
|
|
529
|
+
if (this.port80Handler) {
|
|
530
|
+
try {
|
|
531
|
+
// Check if we already have a certificate
|
|
532
|
+
const cert = this.port80Handler.getCertificate(domain);
|
|
533
|
+
if (cert) {
|
|
534
|
+
console.log(`Certificate already exists for ${domain}, valid until ${cert.expiryDate.toISOString()}`);
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Register domain for certificate issuance
|
|
539
|
+
this.port80Handler.addDomain({
|
|
540
|
+
domainName: domain,
|
|
541
|
+
sslRedirect: true,
|
|
542
|
+
acmeMaintenance: true
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
console.log(`Domain ${domain} registered for certificate issuance`);
|
|
546
|
+
return true;
|
|
547
|
+
} catch (err) {
|
|
548
|
+
console.log(`Error registering domain with Port80Handler: ${err}`);
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Fall back to NetworkProxyBridge
|
|
554
|
+
return this.networkProxyBridge.requestCertificate(domain);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Validates if a domain name is valid for certificate issuance
|
|
559
|
+
*/
|
|
560
|
+
private isValidDomain(domain: string): boolean {
|
|
561
|
+
// Very basic domain validation
|
|
562
|
+
if (!domain || domain.length === 0) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Check for wildcard domains (they can't get ACME certs)
|
|
567
|
+
if (domain.includes('*')) {
|
|
568
|
+
console.log(`Wildcard domains like "${domain}" are not supported for ACME certificates`);
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Check if domain has at least one dot and no invalid characters
|
|
573
|
+
const validDomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
574
|
+
if (!validDomainRegex.test(domain)) {
|
|
575
|
+
console.log(`Domain "${domain}" has invalid format`);
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Get statistics about current connections
|
|
584
|
+
*/
|
|
585
|
+
public getStatistics(): any {
|
|
586
|
+
const connectionRecords = this.connectionManager.getConnections();
|
|
587
|
+
const terminationStats = this.connectionManager.getTerminationStats();
|
|
588
|
+
|
|
589
|
+
let tlsConnections = 0;
|
|
590
|
+
let nonTlsConnections = 0;
|
|
591
|
+
let keepAliveConnections = 0;
|
|
592
|
+
let networkProxyConnections = 0;
|
|
593
|
+
|
|
594
|
+
// Analyze active connections
|
|
595
|
+
for (const record of connectionRecords.values()) {
|
|
596
|
+
if (record.isTLS) tlsConnections++;
|
|
597
|
+
else nonTlsConnections++;
|
|
598
|
+
if (record.hasKeepAlive) keepAliveConnections++;
|
|
599
|
+
if (record.usingNetworkProxy) networkProxyConnections++;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
activeConnections: connectionRecords.size,
|
|
604
|
+
tlsConnections,
|
|
605
|
+
nonTlsConnections,
|
|
606
|
+
keepAliveConnections,
|
|
607
|
+
networkProxyConnections,
|
|
608
|
+
terminationStats,
|
|
609
|
+
acmeEnabled: !!this.port80Handler,
|
|
610
|
+
port80HandlerPort: this.port80Handler ? this.settings.port80HandlerConfig?.port : null
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Get a list of eligible domains for ACME certificates
|
|
616
|
+
*/
|
|
617
|
+
public getEligibleDomainsForCertificates(): string[] {
|
|
618
|
+
// Collect all non-wildcard domains from domain configs
|
|
619
|
+
const domains: string[] = [];
|
|
620
|
+
|
|
621
|
+
for (const config of this.settings.domainConfigs) {
|
|
622
|
+
// Skip domains that can't be used with ACME
|
|
623
|
+
const eligibleDomains = config.domains.filter(domain =>
|
|
624
|
+
!domain.includes('*') && this.isValidDomain(domain)
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
domains.push(...eligibleDomains);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return domains;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Get status of certificates managed by Port80Handler
|
|
635
|
+
*/
|
|
636
|
+
public getCertificateStatus(): any {
|
|
637
|
+
if (!this.port80Handler) {
|
|
638
|
+
return {
|
|
639
|
+
enabled: false,
|
|
640
|
+
message: 'Port80Handler is not enabled'
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Get eligible domains
|
|
645
|
+
const eligibleDomains = this.getEligibleDomainsForCertificates();
|
|
646
|
+
const certificateStatus: Record<string, any> = {};
|
|
647
|
+
|
|
648
|
+
// Check each domain
|
|
649
|
+
for (const domain of eligibleDomains) {
|
|
650
|
+
const cert = this.port80Handler.getCertificate(domain);
|
|
651
|
+
|
|
652
|
+
if (cert) {
|
|
653
|
+
const now = new Date();
|
|
654
|
+
const expiryDate = cert.expiryDate;
|
|
655
|
+
const daysRemaining = Math.floor((expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000));
|
|
656
|
+
|
|
657
|
+
certificateStatus[domain] = {
|
|
658
|
+
status: 'valid',
|
|
659
|
+
expiryDate: expiryDate.toISOString(),
|
|
660
|
+
daysRemaining,
|
|
661
|
+
renewalNeeded: daysRemaining <= this.settings.port80HandlerConfig.renewThresholdDays
|
|
662
|
+
};
|
|
663
|
+
} else {
|
|
664
|
+
certificateStatus[domain] = {
|
|
665
|
+
status: 'missing',
|
|
666
|
+
message: 'No certificate found'
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return {
|
|
672
|
+
enabled: true,
|
|
673
|
+
port: this.settings.port80HandlerConfig.port,
|
|
674
|
+
useProduction: this.settings.port80HandlerConfig.useProduction,
|
|
675
|
+
autoRenew: this.settings.port80HandlerConfig.autoRenew,
|
|
676
|
+
certificates: certificateStatus
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
}
|