@push.rocks/smartproxy 3.34.0 → 3.37.1
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 +3 -3
- package/dist_ts/classes.networkproxy.d.ts +85 -0
- package/dist_ts/classes.networkproxy.js +385 -6
- package/dist_ts/classes.portproxy.d.ts +31 -0
- package/dist_ts/classes.portproxy.js +196 -189
- package/dist_ts/classes.snihandler.d.ts +45 -0
- package/dist_ts/classes.snihandler.js +274 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/package.json +5 -5
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/classes.networkproxy.ts +461 -6
- package/ts/classes.portproxy.ts +232 -204
- package/ts/classes.snihandler.ts +331 -0
- package/ts/index.ts +1 -0
package/ts/classes.portproxy.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { NetworkProxy } from './classes.networkproxy.js';
|
|
3
|
+
import { SniHandler } from './classes.snihandler.js';
|
|
3
4
|
|
|
4
5
|
/** Domain configuration with per-domain allowed port ranges */
|
|
5
6
|
export interface IDomainConfig {
|
|
@@ -56,9 +57,21 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
|
|
56
57
|
keepAliveInactivityMultiplier?: number; // Multiplier for inactivity timeout for keep-alive connections
|
|
57
58
|
extendedKeepAliveLifetime?: number; // Extended lifetime for keep-alive connections (ms)
|
|
58
59
|
|
|
59
|
-
//
|
|
60
|
+
// NetworkProxy integration
|
|
60
61
|
useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy
|
|
61
62
|
networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443)
|
|
63
|
+
|
|
64
|
+
// ACME certificate management options
|
|
65
|
+
acme?: {
|
|
66
|
+
enabled?: boolean; // Whether to enable automatic certificate management
|
|
67
|
+
port?: number; // Port to listen on for ACME challenges (default: 80)
|
|
68
|
+
contactEmail?: string; // Email for Let's Encrypt account
|
|
69
|
+
useProduction?: boolean; // Whether to use Let's Encrypt production (default: false for staging)
|
|
70
|
+
renewThresholdDays?: number; // Days before expiry to renew certificates (default: 30)
|
|
71
|
+
autoRenew?: boolean; // Whether to automatically renew certificates (default: true)
|
|
72
|
+
certificateStore?: string; // Directory to store certificates (default: ./certs)
|
|
73
|
+
skipConfiguredCerts?: boolean; // Skip domains that already have certificates configured
|
|
74
|
+
};
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
/**
|
|
@@ -105,192 +118,8 @@ interface IConnectionRecord {
|
|
|
105
118
|
domainSwitches?: number; // Number of times the domain has been switched on this connection
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
* Enhanced for robustness and detailed logging.
|
|
111
|
-
* @param buffer - Buffer containing the TLS ClientHello.
|
|
112
|
-
* @param enableLogging - Whether to enable detailed logging.
|
|
113
|
-
* @returns The server name if found, otherwise undefined.
|
|
114
|
-
*/
|
|
115
|
-
function extractSNI(buffer: Buffer, enableLogging: boolean = false): string | undefined {
|
|
116
|
-
try {
|
|
117
|
-
// Check if buffer is too small for TLS
|
|
118
|
-
if (buffer.length < 5) {
|
|
119
|
-
if (enableLogging) console.log('Buffer too small for TLS header');
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Check record type (has to be handshake - 22)
|
|
124
|
-
const recordType = buffer.readUInt8(0);
|
|
125
|
-
if (recordType !== 22) {
|
|
126
|
-
if (enableLogging) console.log(`Not a TLS handshake. Record type: ${recordType}`);
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Check TLS version (has to be 3.1 or higher)
|
|
131
|
-
const majorVersion = buffer.readUInt8(1);
|
|
132
|
-
const minorVersion = buffer.readUInt8(2);
|
|
133
|
-
if (enableLogging) console.log(`TLS Version: ${majorVersion}.${minorVersion}`);
|
|
134
|
-
|
|
135
|
-
// Check record length
|
|
136
|
-
const recordLength = buffer.readUInt16BE(3);
|
|
137
|
-
if (buffer.length < 5 + recordLength) {
|
|
138
|
-
if (enableLogging)
|
|
139
|
-
console.log(
|
|
140
|
-
`Buffer too small for TLS record. Expected: ${5 + recordLength}, Got: ${buffer.length}`
|
|
141
|
-
);
|
|
142
|
-
return undefined;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let offset = 5;
|
|
146
|
-
const handshakeType = buffer.readUInt8(offset);
|
|
147
|
-
if (handshakeType !== 1) {
|
|
148
|
-
if (enableLogging) console.log(`Not a ClientHello. Handshake type: ${handshakeType}`);
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
offset += 4; // Skip handshake header (type + length)
|
|
153
|
-
|
|
154
|
-
// Client version
|
|
155
|
-
const clientMajorVersion = buffer.readUInt8(offset);
|
|
156
|
-
const clientMinorVersion = buffer.readUInt8(offset + 1);
|
|
157
|
-
if (enableLogging) console.log(`Client Version: ${clientMajorVersion}.${clientMinorVersion}`);
|
|
158
|
-
|
|
159
|
-
offset += 2 + 32; // Skip client version and random
|
|
160
|
-
|
|
161
|
-
// Session ID
|
|
162
|
-
const sessionIDLength = buffer.readUInt8(offset);
|
|
163
|
-
if (enableLogging) console.log(`Session ID Length: ${sessionIDLength}`);
|
|
164
|
-
offset += 1 + sessionIDLength; // Skip session ID
|
|
165
|
-
|
|
166
|
-
// Cipher suites
|
|
167
|
-
if (offset + 2 > buffer.length) {
|
|
168
|
-
if (enableLogging) console.log('Buffer too small for cipher suites length');
|
|
169
|
-
return undefined;
|
|
170
|
-
}
|
|
171
|
-
const cipherSuitesLength = buffer.readUInt16BE(offset);
|
|
172
|
-
if (enableLogging) console.log(`Cipher Suites Length: ${cipherSuitesLength}`);
|
|
173
|
-
offset += 2 + cipherSuitesLength; // Skip cipher suites
|
|
174
|
-
|
|
175
|
-
// Compression methods
|
|
176
|
-
if (offset + 1 > buffer.length) {
|
|
177
|
-
if (enableLogging) console.log('Buffer too small for compression methods length');
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
const compressionMethodsLength = buffer.readUInt8(offset);
|
|
181
|
-
if (enableLogging) console.log(`Compression Methods Length: ${compressionMethodsLength}`);
|
|
182
|
-
offset += 1 + compressionMethodsLength; // Skip compression methods
|
|
183
|
-
|
|
184
|
-
// Extensions
|
|
185
|
-
if (offset + 2 > buffer.length) {
|
|
186
|
-
if (enableLogging) console.log('Buffer too small for extensions length');
|
|
187
|
-
return undefined;
|
|
188
|
-
}
|
|
189
|
-
const extensionsLength = buffer.readUInt16BE(offset);
|
|
190
|
-
if (enableLogging) console.log(`Extensions Length: ${extensionsLength}`);
|
|
191
|
-
offset += 2;
|
|
192
|
-
const extensionsEnd = offset + extensionsLength;
|
|
193
|
-
|
|
194
|
-
if (extensionsEnd > buffer.length) {
|
|
195
|
-
if (enableLogging)
|
|
196
|
-
console.log(
|
|
197
|
-
`Buffer too small for extensions. Expected end: ${extensionsEnd}, Buffer length: ${buffer.length}`
|
|
198
|
-
);
|
|
199
|
-
return undefined;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Parse extensions
|
|
203
|
-
while (offset + 4 <= extensionsEnd) {
|
|
204
|
-
const extensionType = buffer.readUInt16BE(offset);
|
|
205
|
-
const extensionLength = buffer.readUInt16BE(offset + 2);
|
|
206
|
-
|
|
207
|
-
if (enableLogging)
|
|
208
|
-
console.log(`Extension Type: 0x${extensionType.toString(16)}, Length: ${extensionLength}`);
|
|
209
|
-
|
|
210
|
-
offset += 4;
|
|
211
|
-
|
|
212
|
-
if (extensionType === 0x0000) {
|
|
213
|
-
// SNI extension
|
|
214
|
-
if (offset + 2 > buffer.length) {
|
|
215
|
-
if (enableLogging) console.log('Buffer too small for SNI list length');
|
|
216
|
-
return undefined;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const sniListLength = buffer.readUInt16BE(offset);
|
|
220
|
-
if (enableLogging) console.log(`SNI List Length: ${sniListLength}`);
|
|
221
|
-
offset += 2;
|
|
222
|
-
const sniListEnd = offset + sniListLength;
|
|
223
|
-
|
|
224
|
-
if (sniListEnd > buffer.length) {
|
|
225
|
-
if (enableLogging)
|
|
226
|
-
console.log(
|
|
227
|
-
`Buffer too small for SNI list. Expected end: ${sniListEnd}, Buffer length: ${buffer.length}`
|
|
228
|
-
);
|
|
229
|
-
return undefined;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
while (offset + 3 < sniListEnd) {
|
|
233
|
-
const nameType = buffer.readUInt8(offset++);
|
|
234
|
-
const nameLen = buffer.readUInt16BE(offset);
|
|
235
|
-
offset += 2;
|
|
236
|
-
|
|
237
|
-
if (enableLogging) console.log(`Name Type: ${nameType}, Name Length: ${nameLen}`);
|
|
238
|
-
|
|
239
|
-
if (nameType === 0) {
|
|
240
|
-
// host_name
|
|
241
|
-
if (offset + nameLen > buffer.length) {
|
|
242
|
-
if (enableLogging)
|
|
243
|
-
console.log(
|
|
244
|
-
`Buffer too small for hostname. Expected: ${offset + nameLen}, Got: ${
|
|
245
|
-
buffer.length
|
|
246
|
-
}`
|
|
247
|
-
);
|
|
248
|
-
return undefined;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const serverName = buffer.toString('utf8', offset, offset + nameLen);
|
|
252
|
-
if (enableLogging) console.log(`Extracted SNI: ${serverName}`);
|
|
253
|
-
return serverName;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
offset += nameLen;
|
|
257
|
-
}
|
|
258
|
-
break;
|
|
259
|
-
} else {
|
|
260
|
-
offset += extensionLength;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (enableLogging) console.log('No SNI extension found');
|
|
265
|
-
return undefined;
|
|
266
|
-
} catch (err) {
|
|
267
|
-
console.log(`Error extracting SNI: ${err}`);
|
|
268
|
-
return undefined;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Checks if a TLS record is a proper ClientHello message (more accurate than just checking record type)
|
|
274
|
-
* @param buffer - Buffer containing the TLS record
|
|
275
|
-
* @returns true if the buffer contains a proper ClientHello message
|
|
276
|
-
*/
|
|
277
|
-
function isClientHello(buffer: Buffer): boolean {
|
|
278
|
-
try {
|
|
279
|
-
if (buffer.length < 9) return false; // Too small for a proper ClientHello
|
|
280
|
-
|
|
281
|
-
// Check record type (has to be handshake - 22)
|
|
282
|
-
if (buffer.readUInt8(0) !== 22) return false;
|
|
283
|
-
|
|
284
|
-
// After the TLS record header (5 bytes), check the handshake type (1 for ClientHello)
|
|
285
|
-
if (buffer.readUInt8(5) !== 1) return false;
|
|
286
|
-
|
|
287
|
-
// Basic checks passed, this appears to be a ClientHello
|
|
288
|
-
return true;
|
|
289
|
-
} catch (err) {
|
|
290
|
-
console.log(`Error checking for ClientHello: ${err}`);
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
121
|
+
// SNI functions are now imported from SniHandler class
|
|
122
|
+
// No need for wrapper functions
|
|
294
123
|
|
|
295
124
|
// Helper: Check if a port falls within any of the given port ranges
|
|
296
125
|
const isPortInRanges = (port: number, ranges: Array<{ from: number; to: number }>): boolean => {
|
|
@@ -334,10 +163,7 @@ const generateConnectionId = (): string => {
|
|
|
334
163
|
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
335
164
|
};
|
|
336
165
|
|
|
337
|
-
//
|
|
338
|
-
const isTlsHandshake = (buffer: Buffer): boolean => {
|
|
339
|
-
return buffer.length > 0 && buffer[0] === 22; // ContentType.handshake
|
|
340
|
-
};
|
|
166
|
+
// SNI functions are now imported from SniHandler class
|
|
341
167
|
|
|
342
168
|
// Helper: Ensure timeout values don't exceed Node.js max safe integer
|
|
343
169
|
const ensureSafeTimeout = (timeout: number): number => {
|
|
@@ -418,6 +244,18 @@ export class PortProxy {
|
|
|
418
244
|
|
|
419
245
|
// NetworkProxy settings
|
|
420
246
|
networkProxyPort: settingsArg.networkProxyPort || 8443, // Default NetworkProxy port
|
|
247
|
+
|
|
248
|
+
// ACME certificate settings with reasonable defaults
|
|
249
|
+
acme: settingsArg.acme || {
|
|
250
|
+
enabled: false,
|
|
251
|
+
port: 80,
|
|
252
|
+
contactEmail: 'admin@example.com',
|
|
253
|
+
useProduction: false,
|
|
254
|
+
renewThresholdDays: 30,
|
|
255
|
+
autoRenew: true,
|
|
256
|
+
certificateStore: './certs',
|
|
257
|
+
skipConfiguredCerts: false
|
|
258
|
+
}
|
|
421
259
|
};
|
|
422
260
|
|
|
423
261
|
// Initialize NetworkProxy if enabled
|
|
@@ -429,15 +267,182 @@ export class PortProxy {
|
|
|
429
267
|
/**
|
|
430
268
|
* Initialize NetworkProxy instance
|
|
431
269
|
*/
|
|
432
|
-
private initializeNetworkProxy(): void {
|
|
270
|
+
private async initializeNetworkProxy(): Promise<void> {
|
|
433
271
|
if (!this.networkProxy) {
|
|
434
|
-
|
|
272
|
+
// Configure NetworkProxy options based on PortProxy settings
|
|
273
|
+
const networkProxyOptions: any = {
|
|
435
274
|
port: this.settings.networkProxyPort!,
|
|
436
275
|
portProxyIntegration: true,
|
|
437
276
|
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info'
|
|
438
|
-
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Add ACME settings if configured
|
|
280
|
+
if (this.settings.acme) {
|
|
281
|
+
networkProxyOptions.acme = { ...this.settings.acme };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.networkProxy = new NetworkProxy(networkProxyOptions);
|
|
439
285
|
|
|
440
286
|
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
|
|
287
|
+
|
|
288
|
+
// Convert and apply domain configurations to NetworkProxy
|
|
289
|
+
await this.syncDomainConfigsToNetworkProxy();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Updates the domain configurations for the proxy
|
|
295
|
+
* @param newDomainConfigs The new domain configurations
|
|
296
|
+
*/
|
|
297
|
+
public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> {
|
|
298
|
+
console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
|
|
299
|
+
this.settings.domainConfigs = newDomainConfigs;
|
|
300
|
+
|
|
301
|
+
// If NetworkProxy is initialized, resync the configurations
|
|
302
|
+
if (this.networkProxy) {
|
|
303
|
+
await this.syncDomainConfigsToNetworkProxy();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Updates the ACME certificate settings
|
|
309
|
+
* @param acmeSettings New ACME settings
|
|
310
|
+
*/
|
|
311
|
+
public async updateAcmeSettings(acmeSettings: IPortProxySettings['acme']): Promise<void> {
|
|
312
|
+
console.log('Updating ACME certificate settings');
|
|
313
|
+
|
|
314
|
+
// Update settings
|
|
315
|
+
this.settings.acme = {
|
|
316
|
+
...this.settings.acme,
|
|
317
|
+
...acmeSettings
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// If NetworkProxy is initialized, update its ACME settings
|
|
321
|
+
if (this.networkProxy) {
|
|
322
|
+
try {
|
|
323
|
+
// Recreate NetworkProxy with new settings if ACME enabled state has changed
|
|
324
|
+
if (this.settings.acme.enabled !== acmeSettings.enabled) {
|
|
325
|
+
console.log(`ACME enabled state changed to: ${acmeSettings.enabled}`);
|
|
326
|
+
|
|
327
|
+
// Stop the current NetworkProxy
|
|
328
|
+
await this.networkProxy.stop();
|
|
329
|
+
this.networkProxy = null;
|
|
330
|
+
|
|
331
|
+
// Reinitialize with new settings
|
|
332
|
+
await this.initializeNetworkProxy();
|
|
333
|
+
|
|
334
|
+
// Use start() to make sure ACME gets initialized if newly enabled
|
|
335
|
+
await this.networkProxy.start();
|
|
336
|
+
} else {
|
|
337
|
+
// Update existing NetworkProxy with new settings
|
|
338
|
+
// Note: Some settings may require a restart to take effect
|
|
339
|
+
console.log('Updating ACME settings in NetworkProxy');
|
|
340
|
+
|
|
341
|
+
// For certificate renewals, we might want to trigger checks with the new settings
|
|
342
|
+
if (acmeSettings.renewThresholdDays) {
|
|
343
|
+
console.log(`Setting new renewal threshold to ${acmeSettings.renewThresholdDays} days`);
|
|
344
|
+
// This is implementation-dependent but gives an example
|
|
345
|
+
if (this.networkProxy.options.acme) {
|
|
346
|
+
this.networkProxy.options.acme.renewThresholdDays = acmeSettings.renewThresholdDays;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
console.log(`Error updating ACME settings: ${err}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Synchronizes PortProxy domain configurations to NetworkProxy
|
|
358
|
+
* This allows domains configured in PortProxy to be used by NetworkProxy
|
|
359
|
+
*/
|
|
360
|
+
private async syncDomainConfigsToNetworkProxy(): Promise<void> {
|
|
361
|
+
if (!this.networkProxy) {
|
|
362
|
+
console.log('Cannot sync configurations - NetworkProxy not initialized');
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
// Get SSL certificates from assets
|
|
368
|
+
// Import fs directly since it's not in plugins
|
|
369
|
+
const fs = await import('fs');
|
|
370
|
+
|
|
371
|
+
let certPair;
|
|
372
|
+
try {
|
|
373
|
+
certPair = {
|
|
374
|
+
key: fs.readFileSync('assets/certs/key.pem', 'utf8'),
|
|
375
|
+
cert: fs.readFileSync('assets/certs/cert.pem', 'utf8')
|
|
376
|
+
};
|
|
377
|
+
} catch (certError) {
|
|
378
|
+
console.log(`Warning: Could not read default certificates: ${certError}`);
|
|
379
|
+
console.log('Using empty certificate placeholders - ACME will generate proper certificates if enabled');
|
|
380
|
+
|
|
381
|
+
// Use empty placeholders - NetworkProxy will use its internal defaults
|
|
382
|
+
// or ACME will generate proper ones if enabled
|
|
383
|
+
certPair = {
|
|
384
|
+
key: '',
|
|
385
|
+
cert: ''
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Convert domain configs to NetworkProxy configs
|
|
390
|
+
const proxyConfigs = this.networkProxy.convertPortProxyConfigs(
|
|
391
|
+
this.settings.domainConfigs,
|
|
392
|
+
certPair
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
// Log ACME-eligible domains if ACME is enabled
|
|
396
|
+
if (this.settings.acme?.enabled) {
|
|
397
|
+
const acmeEligibleDomains = proxyConfigs
|
|
398
|
+
.filter(config => !config.hostName.includes('*')) // Exclude wildcards
|
|
399
|
+
.map(config => config.hostName);
|
|
400
|
+
|
|
401
|
+
if (acmeEligibleDomains.length > 0) {
|
|
402
|
+
console.log(`Domains eligible for ACME certificates: ${acmeEligibleDomains.join(', ')}`);
|
|
403
|
+
} else {
|
|
404
|
+
console.log('No domains eligible for ACME certificates found in configuration');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Update NetworkProxy with the converted configs
|
|
409
|
+
this.networkProxy.updateProxyConfigs(proxyConfigs).then(() => {
|
|
410
|
+
console.log(`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`);
|
|
411
|
+
}).catch(err => {
|
|
412
|
+
console.log(`Error synchronizing configurations: ${err.message}`);
|
|
413
|
+
});
|
|
414
|
+
} catch (err) {
|
|
415
|
+
console.log(`Failed to sync configurations: ${err}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Requests a certificate for a specific domain
|
|
421
|
+
* @param domain The domain to request a certificate for
|
|
422
|
+
* @returns Promise that resolves to true if the request was successful, false otherwise
|
|
423
|
+
*/
|
|
424
|
+
public async requestCertificate(domain: string): Promise<boolean> {
|
|
425
|
+
if (!this.networkProxy) {
|
|
426
|
+
console.log('Cannot request certificate - NetworkProxy not initialized');
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (!this.settings.acme?.enabled) {
|
|
431
|
+
console.log('Cannot request certificate - ACME is not enabled');
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
const result = await this.networkProxy.requestCertificate(domain);
|
|
437
|
+
if (result) {
|
|
438
|
+
console.log(`Certificate request for ${domain} submitted successfully`);
|
|
439
|
+
} else {
|
|
440
|
+
console.log(`Certificate request for ${domain} failed`);
|
|
441
|
+
}
|
|
442
|
+
return result;
|
|
443
|
+
} catch (err) {
|
|
444
|
+
console.log(`Error requesting certificate: ${err}`);
|
|
445
|
+
return false;
|
|
441
446
|
}
|
|
442
447
|
}
|
|
443
448
|
|
|
@@ -570,7 +575,7 @@ export class PortProxy {
|
|
|
570
575
|
record.bytesReceived += chunk.length;
|
|
571
576
|
|
|
572
577
|
// Check for TLS handshake
|
|
573
|
-
if (!record.isTLS && isTlsHandshake(chunk)) {
|
|
578
|
+
if (!record.isTLS && SniHandler.isTlsHandshake(chunk)) {
|
|
574
579
|
record.isTLS = true;
|
|
575
580
|
|
|
576
581
|
if (this.settings.enableTlsDebugLogging) {
|
|
@@ -858,10 +863,10 @@ export class PortProxy {
|
|
|
858
863
|
// Define a handler for checking renegotiation with improved detection
|
|
859
864
|
const renegotiationHandler = (renegChunk: Buffer) => {
|
|
860
865
|
// Only process if this looks like a TLS ClientHello
|
|
861
|
-
if (isClientHello(renegChunk)) {
|
|
866
|
+
if (SniHandler.isClientHello(renegChunk)) {
|
|
862
867
|
try {
|
|
863
868
|
// Extract SNI from ClientHello
|
|
864
|
-
const newSNI =
|
|
869
|
+
const newSNI = SniHandler.extractSNIWithResumptionSupport(renegChunk, this.settings.enableTlsDebugLogging);
|
|
865
870
|
|
|
866
871
|
// Skip if no SNI was found
|
|
867
872
|
if (!newSNI) return;
|
|
@@ -1278,10 +1283,27 @@ export class PortProxy {
|
|
|
1278
1283
|
return;
|
|
1279
1284
|
}
|
|
1280
1285
|
|
|
1286
|
+
// Initialize NetworkProxy if needed (useNetworkProxy is set but networkProxy isn't initialized)
|
|
1287
|
+
if (this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0 && !this.networkProxy) {
|
|
1288
|
+
await this.initializeNetworkProxy();
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1281
1291
|
// Start NetworkProxy if configured
|
|
1282
1292
|
if (this.networkProxy) {
|
|
1283
1293
|
await this.networkProxy.start();
|
|
1284
1294
|
console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`);
|
|
1295
|
+
|
|
1296
|
+
// Log ACME status
|
|
1297
|
+
if (this.settings.acme?.enabled) {
|
|
1298
|
+
console.log(`ACME certificate management is enabled (${this.settings.acme.useProduction ? 'Production' : 'Staging'} mode)`);
|
|
1299
|
+
console.log(`ACME HTTP challenge server on port ${this.settings.acme.port}`);
|
|
1300
|
+
|
|
1301
|
+
// Register domains for ACME certificates if enabled
|
|
1302
|
+
if (this.networkProxy.options.acme?.enabled) {
|
|
1303
|
+
console.log('Registering domains with ACME certificate manager...');
|
|
1304
|
+
// The NetworkProxy will handle this internally via registerDomainsWithAcmeManager()
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1285
1307
|
}
|
|
1286
1308
|
|
|
1287
1309
|
// Define a unified connection handler for all listening ports.
|
|
@@ -1436,7 +1458,7 @@ export class PortProxy {
|
|
|
1436
1458
|
connectionRecord.hasReceivedInitialData = true;
|
|
1437
1459
|
|
|
1438
1460
|
// Check if this looks like a TLS handshake
|
|
1439
|
-
if (isTlsHandshake(chunk)) {
|
|
1461
|
+
if (SniHandler.isTlsHandshake(chunk)) {
|
|
1440
1462
|
connectionRecord.isTLS = true;
|
|
1441
1463
|
|
|
1442
1464
|
// Forward directly to NetworkProxy without SNI processing
|
|
@@ -1498,7 +1520,7 @@ export class PortProxy {
|
|
|
1498
1520
|
this.updateActivity(connectionRecord);
|
|
1499
1521
|
|
|
1500
1522
|
// Check for TLS handshake if this is the first chunk
|
|
1501
|
-
if (!connectionRecord.isTLS && isTlsHandshake(chunk)) {
|
|
1523
|
+
if (!connectionRecord.isTLS && SniHandler.isTlsHandshake(chunk)) {
|
|
1502
1524
|
connectionRecord.isTLS = true;
|
|
1503
1525
|
|
|
1504
1526
|
if (this.settings.enableTlsDebugLogging) {
|
|
@@ -1506,7 +1528,7 @@ export class PortProxy {
|
|
|
1506
1528
|
`[${connectionId}] TLS handshake detected from ${remoteIP}, ${chunk.length} bytes`
|
|
1507
1529
|
);
|
|
1508
1530
|
// Try to extract SNI and log detailed debug info
|
|
1509
|
-
|
|
1531
|
+
SniHandler.extractSNIWithResumptionSupport(chunk, true);
|
|
1510
1532
|
}
|
|
1511
1533
|
}
|
|
1512
1534
|
});
|
|
@@ -1535,7 +1557,7 @@ export class PortProxy {
|
|
|
1535
1557
|
connectionRecord.hasReceivedInitialData = true;
|
|
1536
1558
|
|
|
1537
1559
|
// Check if this looks like a TLS handshake
|
|
1538
|
-
const isTlsHandshakeDetected = initialChunk && isTlsHandshake(initialChunk);
|
|
1560
|
+
const isTlsHandshakeDetected = initialChunk && SniHandler.isTlsHandshake(initialChunk);
|
|
1539
1561
|
if (isTlsHandshakeDetected) {
|
|
1540
1562
|
connectionRecord.isTLS = true;
|
|
1541
1563
|
|
|
@@ -1704,7 +1726,7 @@ export class PortProxy {
|
|
|
1704
1726
|
// Try to extract SNI
|
|
1705
1727
|
let serverName = '';
|
|
1706
1728
|
|
|
1707
|
-
if (isTlsHandshake(chunk)) {
|
|
1729
|
+
if (SniHandler.isTlsHandshake(chunk)) {
|
|
1708
1730
|
connectionRecord.isTLS = true;
|
|
1709
1731
|
|
|
1710
1732
|
if (this.settings.enableTlsDebugLogging) {
|
|
@@ -1713,7 +1735,7 @@ export class PortProxy {
|
|
|
1713
1735
|
);
|
|
1714
1736
|
}
|
|
1715
1737
|
|
|
1716
|
-
serverName =
|
|
1738
|
+
serverName = SniHandler.extractSNIWithResumptionSupport(chunk, this.settings.enableTlsDebugLogging) || '';
|
|
1717
1739
|
}
|
|
1718
1740
|
|
|
1719
1741
|
// Lock the connection to the negotiated SNI.
|
|
@@ -2036,11 +2058,17 @@ export class PortProxy {
|
|
|
2036
2058
|
}
|
|
2037
2059
|
}
|
|
2038
2060
|
|
|
2039
|
-
// Stop NetworkProxy if it was started
|
|
2061
|
+
// Stop NetworkProxy if it was started (which also stops ACME manager)
|
|
2040
2062
|
if (this.networkProxy) {
|
|
2041
2063
|
try {
|
|
2064
|
+
console.log('Stopping NetworkProxy...');
|
|
2042
2065
|
await this.networkProxy.stop();
|
|
2043
2066
|
console.log('NetworkProxy stopped successfully');
|
|
2067
|
+
|
|
2068
|
+
// Log ACME shutdown if it was enabled
|
|
2069
|
+
if (this.settings.acme?.enabled) {
|
|
2070
|
+
console.log('ACME certificate manager stopped');
|
|
2071
|
+
}
|
|
2044
2072
|
} catch (err) {
|
|
2045
2073
|
console.log(`Error stopping NetworkProxy: ${err}`);
|
|
2046
2074
|
}
|