Compare commits

...

16 Commits

Author SHA1 Message Date
06896b3102 3.41.7
Some checks failed
Default (tags) / security (push) Successful in 35s
Default (tags) / test (push) Failing after 1m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-12 12:19:36 +00:00
7fe455b4df fix(core): Refactor PortProxy and SniHandler: improve configuration handling, logging, and whitespace consistency 2025-03-12 12:19:36 +00:00
21801aa53d 3.41.6
Some checks failed
Default (tags) / security (push) Successful in 37s
Default (tags) / test (push) Failing after 1m1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-12 10:54:24 +00:00
ddfbcdb1f3 fix(SniHandler): Refactor SniHandler: update whitespace, comment formatting, and consistent type definitions 2025-03-12 10:54:24 +00:00
b401d126bc 3.41.5
Some checks failed
Default (tags) / security (push) Successful in 35s
Default (tags) / test (push) Failing after 1m6s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-12 10:27:26 +00:00
baaee0ad4d fix(portproxy): Enforce TLS handshake and SNI validation on port 443 by blocking non-TLS connections and terminating session resumption attempts without SNI when allowSessionTicket is disabled. 2025-03-12 10:27:25 +00:00
fe7c4c2f5e 3.41.4
Some checks failed
Default (tags) / security (push) Successful in 30s
Default (tags) / test (push) Failing after 1m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-12 10:01:54 +00:00
ab1ec84832 fix(tls/sni): Improve logging for TLS session resumption by extracting and logging SNI values from ClientHello messages. 2025-03-12 10:01:54 +00:00
156abbf5b4 3.41.3
Some checks failed
Default (tags) / security (push) Failing after 10m42s
Default (tags) / test (push) Has been cancelled
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-03-12 09:56:21 +00:00
1a90566622 fix(TLS/SNI): Improve TLS session resumption handling and logging. Now, session resumption attempts are always logged with details, and connections without a proper SNI are rejected when allowSessionTicket is disabled. In addition, empty SNI extensions are explicitly treated as missing, ensuring stricter and more consistent TLS handshake validation. 2025-03-12 09:56:21 +00:00
b48b90d613 3.41.2
Some checks failed
Default (tags) / security (push) Successful in 28s
Default (tags) / test (push) Failing after 1m10s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-11 19:41:04 +00:00
124f8d48b7 fix(SniHandler): Refactor hasSessionResumption to return detailed session resumption info 2025-03-11 19:41:04 +00:00
b2a57ada5d 3.41.1
Some checks failed
Default (tags) / security (push) Successful in 30s
Default (tags) / test (push) Failing after 1m12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-11 19:38:41 +00:00
62a3e1f4b7 fix(SniHandler): Improve TLS SNI session resumption handling: connections containing a session ticket are now only rejected when no SNI is present and allowSessionTicket is disabled. Updated return values and logging for clearer resumption detection. 2025-03-11 19:38:41 +00:00
3a1485213a 3.41.0
Some checks failed
Default (tags) / security (push) Failing after 10m42s
Default (tags) / test (push) Has been cancelled
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-03-11 19:31:20 +00:00
9dbf6fdeb5 feat(PortProxy/TLS): Add allowSessionTicket option to control TLS session ticket handling 2025-03-11 19:31:20 +00:00
5 changed files with 979 additions and 330 deletions

View File

@ -1,5 +1,63 @@
# Changelog # Changelog
## 2025-03-12 - 3.41.7 - fix(core)
Refactor PortProxy and SniHandler: improve configuration handling, logging, and whitespace consistency
- Standardized indentation and spacing for configuration properties in PortProxy settings (e.g. ACME options, keepAliveProbes, allowSessionTicket)
- Simplified conditional formatting and improved inline comments in PortProxy
- Enhanced logging messages in SniHandler for TLS handshake and session resumption detection
- Improved debugging output (e.g. hexdump of initial TLS packet) and consistency of multi-line expressions
## 2025-03-12 - 3.41.6 - fix(SniHandler)
Refactor SniHandler: update whitespace, comment formatting, and consistent type definitions
- Unified inline comment style and spacing in SniHandler
- Refactored session cache type declaration for clarity
- Adjusted buffer length calculations to include TLS record header consistently
- Minor improvements to logging messages during ClientHello reassembly and SNI extraction
## 2025-03-12 - 3.41.5 - fix(portproxy)
Enforce TLS handshake and SNI validation on port 443 by blocking non-TLS connections and terminating session resumption attempts without SNI when allowSessionTicket is disabled.
- Added explicit check to block non-TLS connections on port 443 to ensure proper TLS usage.
- Enhanced logging for TLS ClientHello to include details on SNI extraction and session resumption status.
- Terminate connections with missing SNI by setting termination reasons ('session_ticket_blocked' or 'no_sni_blocked').
- Ensured consistent rejection of non-TLS handshakes on standard HTTPS port.
## 2025-03-12 - 3.41.4 - fix(tls/sni)
Improve logging for TLS session resumption by extracting and logging SNI values from ClientHello messages.
- Added logging to output the extracted SNI value during renegotiation, initial ClientHello and in the SNI handler.
- Enhanced error handling during SNI extraction to aid troubleshooting of TLS session resumption issues.
## 2025-03-12 - 3.41.3 - fix(TLS/SNI)
Improve TLS session resumption handling and logging. Now, session resumption attempts are always logged with details, and connections without a proper SNI are rejected when allowSessionTicket is disabled. In addition, empty SNI extensions are explicitly treated as missing, ensuring stricter and more consistent TLS handshake validation.
- Always log session resumption in both renegotiation and initial ClientHello processing.
- Terminate connections that attempt session resumption without SNI when allowSessionTicket is false.
- Treat empty SNI extensions as absence of SNI to improve consistency in TLS handshake processing.
## 2025-03-11 - 3.41.2 - fix(SniHandler)
Refactor hasSessionResumption to return detailed session resumption info
- Changed the return type of hasSessionResumption from boolean to an object with properties isResumption and hasSNI
- Updated early return conditions to return { isResumption: false, hasSNI: false } when buffer is too short or invalid
- Modified corresponding documentation to reflect the new return type
## 2025-03-11 - 3.41.1 - fix(SniHandler)
Improve TLS SNI session resumption handling: connections containing a session ticket are now only rejected when no SNI is present and allowSessionTicket is disabled. Updated return values and logging for clearer resumption detection.
- Changed SniHandler.hasSessionResumption to return an object with 'isResumption' and 'hasSNI' flags.
- Adjusted PortProxy logic to only terminate connections when a session ticket is detected without an accompanying SNI (when allowSessionTicket is false).
- Enhanced debug logging to clearly differentiate between session resumption scenarios with and without SNI.
## 2025-03-11 - 3.41.0 - feat(PortProxy/TLS)
Add allowSessionTicket option to control TLS session ticket handling
- Introduce 'allowSessionTicket' flag (default true) in PortProxy settings to enable or disable TLS session resumption via session tickets.
- Update SniHandler with a new hasSessionResumption method to detect session ticket and PSK extensions in ClientHello messages.
- Force connection cleanup during renegotiation and initial handshake when allowSessionTicket is set to false and a session ticket is detected.
## 2025-03-11 - 3.40.0 - feat(SniHandler) ## 2025-03-11 - 3.40.0 - feat(SniHandler)
Add session cache support and tab reactivation detection to improve SNI extraction in TLS handshakes Add session cache support and tab reactivation detection to improve SNI extraction in TLS handshakes

View File

@ -1,6 +1,6 @@
{ {
"name": "@push.rocks/smartproxy", "name": "@push.rocks/smartproxy",
"version": "3.40.0", "version": "3.41.7",
"private": false, "private": false,
"description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.", "description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartproxy', name: '@push.rocks/smartproxy',
version: '3.40.0', version: '3.41.7',
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.' description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
} }

View File

@ -11,7 +11,7 @@ export interface IDomainConfig {
portRanges?: Array<{ from: number; to: number }>; // Optional port ranges portRanges?: Array<{ from: number; to: number }>; // Optional port ranges
// Allow domain-specific timeout override // Allow domain-specific timeout override
connectionTimeout?: number; // Connection timeout override (ms) connectionTimeout?: number; // Connection timeout override (ms)
// NetworkProxy integration options for this specific domain // NetworkProxy integration options for this specific domain
useNetworkProxy?: boolean; // Whether to use NetworkProxy for this domain useNetworkProxy?: boolean; // Whether to use NetworkProxy for this domain
networkProxyPort?: number; // Override default NetworkProxy port for this domain networkProxyPort?: number; // Override default NetworkProxy port for this domain
@ -51,6 +51,7 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
enableDetailedLogging?: boolean; // Enable detailed connection logging enableDetailedLogging?: boolean; // Enable detailed connection logging
enableTlsDebugLogging?: boolean; // Enable TLS handshake debug logging enableTlsDebugLogging?: boolean; // Enable TLS handshake debug logging
enableRandomizedTimeouts?: boolean; // Randomize timeouts slightly to prevent thundering herd enableRandomizedTimeouts?: boolean; // Randomize timeouts slightly to prevent thundering herd
allowSessionTicket?: boolean; // Allow TLS session ticket for reconnection (default: true)
// Rate limiting and security // Rate limiting and security
maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP
@ -64,17 +65,17 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
// NetworkProxy integration // NetworkProxy integration
useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy
networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443) networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443)
// ACME certificate management options // ACME certificate management options
acme?: { acme?: {
enabled?: boolean; // Whether to enable automatic certificate management enabled?: boolean; // Whether to enable automatic certificate management
port?: number; // Port to listen on for ACME challenges (default: 80) port?: number; // Port to listen on for ACME challenges (default: 80)
contactEmail?: string; // Email for Let's Encrypt account contactEmail?: string; // Email for Let's Encrypt account
useProduction?: boolean; // Whether to use Let's Encrypt production (default: false for staging) useProduction?: boolean; // Whether to use Let's Encrypt production (default: false for staging)
renewThresholdDays?: number; // Days before expiry to renew certificates (default: 30) renewThresholdDays?: number; // Days before expiry to renew certificates (default: 30)
autoRenew?: boolean; // Whether to automatically renew certificates (default: true) autoRenew?: boolean; // Whether to automatically renew certificates (default: true)
certificateStore?: string; // Directory to store certificates (default: ./certs) certificateStore?: string; // Directory to store certificates (default: ./certs)
skipConfiguredCerts?: boolean; // Skip domains that already have certificates configured skipConfiguredCerts?: boolean; // Skip domains that already have certificates configured
}; };
} }
@ -231,11 +232,13 @@ export class PortProxy {
// Feature flags // Feature flags
disableInactivityCheck: settingsArg.disableInactivityCheck || false, disableInactivityCheck: settingsArg.disableInactivityCheck || false,
enableKeepAliveProbes: settingsArg.enableKeepAliveProbes !== undefined enableKeepAliveProbes:
? settingsArg.enableKeepAliveProbes : true, settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true,
enableDetailedLogging: settingsArg.enableDetailedLogging || false, enableDetailedLogging: settingsArg.enableDetailedLogging || false,
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false, enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false, enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
allowSessionTicket:
settingsArg.allowSessionTicket !== undefined ? settingsArg.allowSessionTicket : true,
// Rate limiting defaults // Rate limiting defaults
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
@ -245,10 +248,10 @@ export class PortProxy {
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended', keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6, keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000, // 7 days extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000, // 7 days
// NetworkProxy settings // NetworkProxy settings
networkProxyPort: settingsArg.networkProxyPort || 8443, // Default NetworkProxy port networkProxyPort: settingsArg.networkProxyPort || 8443, // Default NetworkProxy port
// ACME certificate settings with reasonable defaults // ACME certificate settings with reasonable defaults
acme: settingsArg.acme || { acme: settingsArg.acme || {
enabled: false, enabled: false,
@ -258,8 +261,8 @@ export class PortProxy {
renewThresholdDays: 30, renewThresholdDays: 30,
autoRenew: true, autoRenew: true,
certificateStore: './certs', certificateStore: './certs',
skipConfiguredCerts: false skipConfiguredCerts: false,
} },
}; };
// Initialize NetworkProxy if enabled // Initialize NetworkProxy if enabled
@ -277,23 +280,23 @@ export class PortProxy {
const networkProxyOptions: any = { const networkProxyOptions: any = {
port: this.settings.networkProxyPort!, port: this.settings.networkProxyPort!,
portProxyIntegration: true, portProxyIntegration: true,
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info' logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info',
}; };
// Add ACME settings if configured // Add ACME settings if configured
if (this.settings.acme) { if (this.settings.acme) {
networkProxyOptions.acme = { ...this.settings.acme }; networkProxyOptions.acme = { ...this.settings.acme };
} }
this.networkProxy = new NetworkProxy(networkProxyOptions); this.networkProxy = new NetworkProxy(networkProxyOptions);
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`); console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
// Convert and apply domain configurations to NetworkProxy // Convert and apply domain configurations to NetworkProxy
await this.syncDomainConfigsToNetworkProxy(); await this.syncDomainConfigsToNetworkProxy();
} }
} }
/** /**
* Updates the domain configurations for the proxy * Updates the domain configurations for the proxy
* @param newDomainConfigs The new domain configurations * @param newDomainConfigs The new domain configurations
@ -301,47 +304,47 @@ export class PortProxy {
public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> { public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> {
console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`); console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
this.settings.domainConfigs = newDomainConfigs; this.settings.domainConfigs = newDomainConfigs;
// If NetworkProxy is initialized, resync the configurations // If NetworkProxy is initialized, resync the configurations
if (this.networkProxy) { if (this.networkProxy) {
await this.syncDomainConfigsToNetworkProxy(); await this.syncDomainConfigsToNetworkProxy();
} }
} }
/** /**
* Updates the ACME certificate settings * Updates the ACME certificate settings
* @param acmeSettings New ACME settings * @param acmeSettings New ACME settings
*/ */
public async updateAcmeSettings(acmeSettings: IPortProxySettings['acme']): Promise<void> { public async updateAcmeSettings(acmeSettings: IPortProxySettings['acme']): Promise<void> {
console.log('Updating ACME certificate settings'); console.log('Updating ACME certificate settings');
// Update settings // Update settings
this.settings.acme = { this.settings.acme = {
...this.settings.acme, ...this.settings.acme,
...acmeSettings ...acmeSettings,
}; };
// If NetworkProxy is initialized, update its ACME settings // If NetworkProxy is initialized, update its ACME settings
if (this.networkProxy) { if (this.networkProxy) {
try { try {
// Recreate NetworkProxy with new settings if ACME enabled state has changed // Recreate NetworkProxy with new settings if ACME enabled state has changed
if (this.settings.acme.enabled !== acmeSettings.enabled) { if (this.settings.acme.enabled !== acmeSettings.enabled) {
console.log(`ACME enabled state changed to: ${acmeSettings.enabled}`); console.log(`ACME enabled state changed to: ${acmeSettings.enabled}`);
// Stop the current NetworkProxy // Stop the current NetworkProxy
await this.networkProxy.stop(); await this.networkProxy.stop();
this.networkProxy = null; this.networkProxy = null;
// Reinitialize with new settings // Reinitialize with new settings
await this.initializeNetworkProxy(); await this.initializeNetworkProxy();
// Use start() to make sure ACME gets initialized if newly enabled // Use start() to make sure ACME gets initialized if newly enabled
await this.networkProxy.start(); await this.networkProxy.start();
} else { } else {
// Update existing NetworkProxy with new settings // Update existing NetworkProxy with new settings
// Note: Some settings may require a restart to take effect // Note: Some settings may require a restart to take effect
console.log('Updating ACME settings in NetworkProxy'); console.log('Updating ACME settings in NetworkProxy');
// For certificate renewals, we might want to trigger checks with the new settings // For certificate renewals, we might want to trigger checks with the new settings
if (acmeSettings.renewThresholdDays) { if (acmeSettings.renewThresholdDays) {
console.log(`Setting new renewal threshold to ${acmeSettings.renewThresholdDays} days`); console.log(`Setting new renewal threshold to ${acmeSettings.renewThresholdDays} days`);
@ -356,7 +359,7 @@ export class PortProxy {
} }
} }
} }
/** /**
* Synchronizes PortProxy domain configurations to NetworkProxy * Synchronizes PortProxy domain configurations to NetworkProxy
* This allows domains configured in PortProxy to be used by NetworkProxy * This allows domains configured in PortProxy to be used by NetworkProxy
@ -366,60 +369,67 @@ export class PortProxy {
console.log('Cannot sync configurations - NetworkProxy not initialized'); console.log('Cannot sync configurations - NetworkProxy not initialized');
return; return;
} }
try { try {
// Get SSL certificates from assets // Get SSL certificates from assets
// Import fs directly since it's not in plugins // Import fs directly since it's not in plugins
const fs = await import('fs'); const fs = await import('fs');
let certPair; let certPair;
try { try {
certPair = { certPair = {
key: fs.readFileSync('assets/certs/key.pem', 'utf8'), key: fs.readFileSync('assets/certs/key.pem', 'utf8'),
cert: fs.readFileSync('assets/certs/cert.pem', 'utf8') cert: fs.readFileSync('assets/certs/cert.pem', 'utf8'),
}; };
} catch (certError) { } catch (certError) {
console.log(`Warning: Could not read default certificates: ${certError}`); console.log(`Warning: Could not read default certificates: ${certError}`);
console.log('Using empty certificate placeholders - ACME will generate proper certificates if enabled'); console.log(
'Using empty certificate placeholders - ACME will generate proper certificates if enabled'
);
// Use empty placeholders - NetworkProxy will use its internal defaults // Use empty placeholders - NetworkProxy will use its internal defaults
// or ACME will generate proper ones if enabled // or ACME will generate proper ones if enabled
certPair = { certPair = {
key: '', key: '',
cert: '' cert: '',
}; };
} }
// Convert domain configs to NetworkProxy configs // Convert domain configs to NetworkProxy configs
const proxyConfigs = this.networkProxy.convertPortProxyConfigs( const proxyConfigs = this.networkProxy.convertPortProxyConfigs(
this.settings.domainConfigs, this.settings.domainConfigs,
certPair certPair
); );
// Log ACME-eligible domains if ACME is enabled // Log ACME-eligible domains if ACME is enabled
if (this.settings.acme?.enabled) { if (this.settings.acme?.enabled) {
const acmeEligibleDomains = proxyConfigs const acmeEligibleDomains = proxyConfigs
.filter(config => !config.hostName.includes('*')) // Exclude wildcards .filter((config) => !config.hostName.includes('*')) // Exclude wildcards
.map(config => config.hostName); .map((config) => config.hostName);
if (acmeEligibleDomains.length > 0) { if (acmeEligibleDomains.length > 0) {
console.log(`Domains eligible for ACME certificates: ${acmeEligibleDomains.join(', ')}`); console.log(`Domains eligible for ACME certificates: ${acmeEligibleDomains.join(', ')}`);
} else { } else {
console.log('No domains eligible for ACME certificates found in configuration'); console.log('No domains eligible for ACME certificates found in configuration');
} }
} }
// Update NetworkProxy with the converted configs // Update NetworkProxy with the converted configs
this.networkProxy.updateProxyConfigs(proxyConfigs).then(() => { this.networkProxy
console.log(`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`); .updateProxyConfigs(proxyConfigs)
}).catch(err => { .then(() => {
console.log(`Error synchronizing configurations: ${err.message}`); console.log(
}); `Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`
);
})
.catch((err) => {
console.log(`Error synchronizing configurations: ${err.message}`);
});
} catch (err) { } catch (err) {
console.log(`Failed to sync configurations: ${err}`); console.log(`Failed to sync configurations: ${err}`);
} }
} }
/** /**
* Requests a certificate for a specific domain * Requests a certificate for a specific domain
* @param domain The domain to request a certificate for * @param domain The domain to request a certificate for
@ -430,12 +440,12 @@ export class PortProxy {
console.log('Cannot request certificate - NetworkProxy not initialized'); console.log('Cannot request certificate - NetworkProxy not initialized');
return false; return false;
} }
if (!this.settings.acme?.enabled) { if (!this.settings.acme?.enabled) {
console.log('Cannot request certificate - ACME is not enabled'); console.log('Cannot request certificate - ACME is not enabled');
return false; return false;
} }
try { try {
const result = await this.networkProxy.requestCertificate(domain); const result = await this.networkProxy.requestCertificate(domain);
if (result) { if (result) {
@ -543,9 +553,7 @@ export class PortProxy {
proxySocket.on('data', () => this.updateActivity(record)); proxySocket.on('data', () => this.updateActivity(record));
if (this.settings.enableDetailedLogging) { if (this.settings.enableDetailedLogging) {
console.log( console.log(`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`);
`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`
);
} }
}); });
} }
@ -579,11 +587,11 @@ export class PortProxy {
let queueSize = 0; let queueSize = 0;
let processingQueue = false; let processingQueue = false;
let drainPending = false; let drainPending = false;
// Flag to track if we've switched to the final piping mechanism // Flag to track if we've switched to the final piping mechanism
// Once this is true, we no longer buffer data in dataQueue // Once this is true, we no longer buffer data in dataQueue
let pipingEstablished = false; let pipingEstablished = false;
// Pause the incoming socket to prevent buffer overflows // Pause the incoming socket to prevent buffer overflows
// This ensures we control the flow of data until piping is set up // This ensures we control the flow of data until piping is set up
socket.pause(); socket.pause();
@ -591,22 +599,22 @@ export class PortProxy {
// Function to safely process the data queue without losing events // Function to safely process the data queue without losing events
const processDataQueue = () => { const processDataQueue = () => {
if (processingQueue || dataQueue.length === 0 || pipingEstablished) return; if (processingQueue || dataQueue.length === 0 || pipingEstablished) return;
processingQueue = true; processingQueue = true;
try { try {
// Process all queued chunks with the current active handler // Process all queued chunks with the current active handler
while (dataQueue.length > 0) { while (dataQueue.length > 0) {
const chunk = dataQueue.shift()!; const chunk = dataQueue.shift()!;
queueSize -= chunk.length; queueSize -= chunk.length;
// Once piping is established, we shouldn't get here, // Once piping is established, we shouldn't get here,
// but just in case, pass to the outgoing socket directly // but just in case, pass to the outgoing socket directly
if (pipingEstablished && record.outgoing) { if (pipingEstablished && record.outgoing) {
record.outgoing.write(chunk); record.outgoing.write(chunk);
continue; continue;
} }
// Track bytes received // Track bytes received
record.bytesReceived += chunk.length; record.bytesReceived += chunk.length;
@ -640,7 +648,7 @@ export class PortProxy {
} }
} finally { } finally {
processingQueue = false; processingQueue = false;
// If there's a pending drain and we've processed everything, // If there's a pending drain and we've processed everything,
// signal we're ready for more data if we haven't established piping yet // signal we're ready for more data if we haven't established piping yet
if (drainPending && dataQueue.length === 0 && !pipingEstablished) { if (drainPending && dataQueue.length === 0 && !pipingEstablished) {
@ -654,17 +662,17 @@ export class PortProxy {
const safeDataHandler = (chunk: Buffer) => { const safeDataHandler = (chunk: Buffer) => {
// If piping is already established, just let the pipe handle it // If piping is already established, just let the pipe handle it
if (pipingEstablished) return; if (pipingEstablished) return;
// Add to our queue for orderly processing // Add to our queue for orderly processing
dataQueue.push(Buffer.from(chunk)); // Make a copy to be safe dataQueue.push(Buffer.from(chunk)); // Make a copy to be safe
queueSize += chunk.length; queueSize += chunk.length;
// If queue is getting large, pause socket until we catch up // If queue is getting large, pause socket until we catch up
if (this.settings.maxPendingDataSize && queueSize > this.settings.maxPendingDataSize * 0.8) { if (this.settings.maxPendingDataSize && queueSize > this.settings.maxPendingDataSize * 0.8) {
socket.pause(); socket.pause();
drainPending = true; drainPending = true;
} }
// Process the queue // Process the queue
processDataQueue(); processDataQueue();
}; };
@ -845,19 +853,19 @@ export class PortProxy {
// Process any remaining data in the queue before switching to piping // Process any remaining data in the queue before switching to piping
processDataQueue(); processDataQueue();
// Setup function to establish piping - we'll use this after flushing data // Setup function to establish piping - we'll use this after flushing data
const setupPiping = () => { const setupPiping = () => {
// Mark that we're switching to piping mode // Mark that we're switching to piping mode
pipingEstablished = true; pipingEstablished = true;
// Setup piping in both directions // Setup piping in both directions
socket.pipe(targetSocket); socket.pipe(targetSocket);
targetSocket.pipe(socket); targetSocket.pipe(socket);
// Resume the socket to ensure data flows // Resume the socket to ensure data flows
socket.resume(); socket.resume();
// Process any data that might be queued in the interim // Process any data that might be queued in the interim
if (dataQueue.length > 0) { if (dataQueue.length > 0) {
// Write any remaining queued data directly to the target socket // Write any remaining queued data directly to the target socket
@ -868,7 +876,7 @@ export class PortProxy {
dataQueue.length = 0; dataQueue.length = 0;
queueSize = 0; queueSize = 0;
} }
if (this.settings.enableDetailedLogging) { if (this.settings.enableDetailedLogging) {
console.log( console.log(
`[${connectionId}] Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` + `[${connectionId}] Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
@ -932,10 +940,55 @@ export class PortProxy {
sourceIp: record.remoteIP, sourceIp: record.remoteIP,
sourcePort: record.incoming.remotePort || 0, sourcePort: record.incoming.remotePort || 0,
destIp: record.incoming.localAddress || '', destIp: record.incoming.localAddress || '',
destPort: record.incoming.localPort || 0 destPort: record.incoming.localPort || 0,
}; };
const newSNI = SniHandler.extractSNIWithResumptionSupport(renegChunk, connInfo, this.settings.enableTlsDebugLogging); // Check for session tickets if allowSessionTicket is disabled
if (this.settings.allowSessionTicket === false) {
// Analyze for session resumption attempt (session ticket or PSK)
const resumptionInfo = SniHandler.hasSessionResumption(
renegChunk,
this.settings.enableTlsDebugLogging
);
if (resumptionInfo.isResumption) {
// Always log resumption attempt for easier debugging
// Try to extract SNI for logging
const extractedSNI = SniHandler.extractSNI(
renegChunk,
this.settings.enableTlsDebugLogging
);
console.log(
`[${connectionId}] Session resumption detected in renegotiation. ` +
`Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` +
`SNI value: ${extractedSNI || 'None'}, ` +
`allowSessionTicket: ${this.settings.allowSessionTicket}`
);
// Block if there's session resumption without SNI
if (!resumptionInfo.hasSNI) {
console.log(
`[${connectionId}] Session resumption detected in renegotiation without SNI and allowSessionTicket=false. ` +
`Terminating connection to force new TLS handshake.`
);
this.initiateCleanupOnce(record, 'session_ticket_blocked');
return;
} else {
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Session resumption with SNI detected in renegotiation. ` +
`Allowing connection since SNI is present.`
);
}
}
}
}
const newSNI = SniHandler.extractSNIWithResumptionSupport(
renegChunk,
connInfo,
this.settings.enableTlsDebugLogging
);
// Skip if no SNI was found // Skip if no SNI was found
if (!newSNI) return; if (!newSNI) return;
@ -945,7 +998,7 @@ export class PortProxy {
// Log and terminate the connection for any SNI change // Log and terminate the connection for any SNI change
console.log( console.log(
`[${connectionId}] Renegotiation with different SNI: ${record.lockedDomain} -> ${newSNI}. ` + `[${connectionId}] Renegotiation with different SNI: ${record.lockedDomain} -> ${newSNI}. ` +
`Terminating connection - SNI domain switching is not allowed.` `Terminating connection - SNI domain switching is not allowed.`
); );
this.initiateCleanupOnce(record, 'sni_mismatch'); this.initiateCleanupOnce(record, 'sni_mismatch');
} else if (this.settings.enableDetailedLogging) { } else if (this.settings.enableDetailedLogging) {
@ -967,9 +1020,16 @@ export class PortProxy {
// The renegotiation handler is added when piping is established // The renegotiation handler is added when piping is established
// Making it part of setupPiping ensures proper sequencing of event handlers // Making it part of setupPiping ensures proper sequencing of event handlers
socket.on('data', renegotiationHandler); socket.on('data', renegotiationHandler);
if (this.settings.enableDetailedLogging) { if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`); console.log(
`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`
);
if (this.settings.allowSessionTicket === false) {
console.log(
`[${connectionId}] Session ticket usage is disabled. Connection will be reset on reconnection attempts.`
);
}
} }
} }
@ -1135,7 +1195,7 @@ export class PortProxy {
try { try {
// Remove our safe data handler // Remove our safe data handler
record.incoming.removeAllListeners('data'); record.incoming.removeAllListeners('data');
// Reset the handler references // Reset the handler references
record.renegotiationHandler = undefined; record.renegotiationHandler = undefined;
} catch (err) { } catch (err) {
@ -1361,7 +1421,11 @@ export class PortProxy {
} }
// Initialize NetworkProxy if needed (useNetworkProxy is set but networkProxy isn't initialized) // Initialize NetworkProxy if needed (useNetworkProxy is set but networkProxy isn't initialized)
if (this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0 && !this.networkProxy) { if (
this.settings.useNetworkProxy &&
this.settings.useNetworkProxy.length > 0 &&
!this.networkProxy
) {
await this.initializeNetworkProxy(); await this.initializeNetworkProxy();
} }
@ -1369,12 +1433,16 @@ export class PortProxy {
if (this.networkProxy) { if (this.networkProxy) {
await this.networkProxy.start(); await this.networkProxy.start();
console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`); console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`);
// Log ACME status // Log ACME status
if (this.settings.acme?.enabled) { if (this.settings.acme?.enabled) {
console.log(`ACME certificate management is enabled (${this.settings.acme.useProduction ? 'Production' : 'Staging'} mode)`); console.log(
`ACME certificate management is enabled (${
this.settings.acme.useProduction ? 'Production' : 'Staging'
} mode)`
);
console.log(`ACME HTTP challenge server on port ${this.settings.acme.port}`); console.log(`ACME HTTP challenge server on port ${this.settings.acme.port}`);
// Register domains for ACME certificates if enabled // Register domains for ACME certificates if enabled
if (this.networkProxy.options.acme?.enabled) { if (this.networkProxy.options.acme?.enabled) {
console.log('Registering domains with ACME certificate manager...'); console.log('Registering domains with ACME certificate manager...');
@ -1448,7 +1516,7 @@ export class PortProxy {
// Initialize browser connection tracking // Initialize browser connection tracking
isBrowserConnection: false, isBrowserConnection: false,
domainSwitches: 0, domainSwitches: 0,
}; };
// Apply keep-alive settings if enabled // Apply keep-alive settings if enabled
@ -1495,9 +1563,9 @@ export class PortProxy {
// Check if this connection should be forwarded directly to NetworkProxy // Check if this connection should be forwarded directly to NetworkProxy
// First check port-based forwarding settings // First check port-based forwarding settings
let shouldUseNetworkProxy = this.settings.useNetworkProxy && let shouldUseNetworkProxy =
this.settings.useNetworkProxy.includes(localPort); this.settings.useNetworkProxy && this.settings.useNetworkProxy.includes(localPort);
// We'll look for domain-specific settings after SNI extraction // We'll look for domain-specific settings after SNI extraction
if (shouldUseNetworkProxy) { if (shouldUseNetworkProxy) {
@ -1537,60 +1605,144 @@ export class PortProxy {
initialDataReceived = true; initialDataReceived = true;
connectionRecord.hasReceivedInitialData = true; connectionRecord.hasReceivedInitialData = true;
// Block non-TLS connections on port 443
// Always enforce TLS on standard HTTPS port
if (!SniHandler.isTlsHandshake(chunk) && localPort === 443) {
console.log(
`[${connectionId}] Non-TLS connection detected on port 443. ` +
`Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
);
if (connectionRecord.incomingTerminationReason === null) {
connectionRecord.incomingTerminationReason = 'non_tls_blocked';
this.incrementTerminationStat('incoming', 'non_tls_blocked');
}
socket.end();
this.cleanupConnection(connectionRecord, 'non_tls_blocked');
return;
}
// Check if this looks like a TLS handshake // Check if this looks like a TLS handshake
if (SniHandler.isTlsHandshake(chunk)) { if (SniHandler.isTlsHandshake(chunk)) {
connectionRecord.isTLS = true; connectionRecord.isTLS = true;
// Check for TLS ClientHello with either no SNI or session tickets
if (this.settings.allowSessionTicket === false && SniHandler.isClientHello(chunk)) {
// Extract SNI first
const extractedSNI = SniHandler.extractSNI(
chunk,
this.settings.enableTlsDebugLogging
);
const hasSNI = !!extractedSNI;
// Analyze for session resumption attempt
const resumptionInfo = SniHandler.hasSessionResumption(
chunk,
this.settings.enableTlsDebugLogging
);
// Always log for debugging purposes
console.log(
`[${connectionId}] TLS ClientHello detected with allowSessionTicket=false. ` +
`Has SNI: ${hasSNI ? 'Yes' : 'No'}, ` +
`SNI value: ${extractedSNI || 'None'}, ` +
`Has session resumption: ${resumptionInfo.isResumption ? 'Yes' : 'No'}`
);
// Block if this is a connection with session resumption but no SNI
if (resumptionInfo.isResumption && !hasSNI) {
console.log(
`[${connectionId}] Session resumption detected in initial ClientHello without SNI and allowSessionTicket=false. ` +
`Terminating connection to force new TLS handshake.`
);
if (connectionRecord.incomingTerminationReason === null) {
connectionRecord.incomingTerminationReason = 'session_ticket_blocked';
this.incrementTerminationStat('incoming', 'session_ticket_blocked');
}
socket.end();
this.cleanupConnection(connectionRecord, 'session_ticket_blocked');
return;
}
// Also block if this is a TLS connection without SNI when allowSessionTicket is false
// This forces clients to send SNI which helps with routing
if (!hasSNI && localPort === 443) {
console.log(
`[${connectionId}] TLS ClientHello detected on port 443 without SNI and allowSessionTicket=false. ` +
`Terminating connection to force proper SNI in handshake.`
);
if (connectionRecord.incomingTerminationReason === null) {
connectionRecord.incomingTerminationReason = 'no_sni_blocked';
this.incrementTerminationStat('incoming', 'no_sni_blocked');
}
socket.end();
this.cleanupConnection(connectionRecord, 'no_sni_blocked');
return;
}
}
// Try to extract SNI for domain-specific NetworkProxy handling // Try to extract SNI for domain-specific NetworkProxy handling
const connInfo = { const connInfo = {
sourceIp: remoteIP, sourceIp: remoteIP,
sourcePort: socket.remotePort || 0, sourcePort: socket.remotePort || 0,
destIp: socket.localAddress || '', destIp: socket.localAddress || '',
destPort: socket.localPort || 0 destPort: socket.localPort || 0,
}; };
// Extract SNI to check for domain-specific NetworkProxy settings // Extract SNI to check for domain-specific NetworkProxy settings
const serverName = SniHandler.processTlsPacket( const serverName = SniHandler.processTlsPacket(
chunk, chunk,
connInfo, connInfo,
this.settings.enableTlsDebugLogging this.settings.enableTlsDebugLogging
); );
if (serverName) { if (serverName) {
// If we got an SNI, check for domain-specific NetworkProxy settings // If we got an SNI, check for domain-specific NetworkProxy settings
const domainConfig = this.settings.domainConfigs.find((config) => const domainConfig = this.settings.domainConfigs.find((config) =>
config.domains.some((d) => plugins.minimatch(serverName, d)) config.domains.some((d) => plugins.minimatch(serverName, d))
); );
// Save domain config and SNI in connection record // Save domain config and SNI in connection record
connectionRecord.domainConfig = domainConfig; connectionRecord.domainConfig = domainConfig;
connectionRecord.lockedDomain = serverName; connectionRecord.lockedDomain = serverName;
// Use domain-specific NetworkProxy port if configured // Use domain-specific NetworkProxy port if configured
if (domainConfig?.useNetworkProxy) { if (domainConfig?.useNetworkProxy) {
const networkProxyPort = domainConfig.networkProxyPort || this.settings.networkProxyPort; const networkProxyPort =
domainConfig.networkProxyPort || this.settings.networkProxyPort;
if (this.settings.enableDetailedLogging) { if (this.settings.enableDetailedLogging) {
console.log( console.log(
`[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}` `[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`
); );
} }
// Forward to NetworkProxy with domain-specific port // Forward to NetworkProxy with domain-specific port
this.forwardToNetworkProxy(connectionId, socket, connectionRecord, chunk, networkProxyPort); this.forwardToNetworkProxy(
connectionId,
socket,
connectionRecord,
chunk,
networkProxyPort
);
return; return;
} }
} }
// Forward directly to NetworkProxy without domain-specific settings // Forward directly to NetworkProxy without domain-specific settings
this.forwardToNetworkProxy(connectionId, socket, connectionRecord, chunk); this.forwardToNetworkProxy(connectionId, socket, connectionRecord, chunk);
} else { } else {
// If not TLS, use normal direct connection // If not TLS, use normal direct connection
console.log(`[${connectionId}] Non-TLS connection on NetworkProxy port ${localPort}`); console.log(`[${connectionId}] Non-TLS connection on NetworkProxy port ${localPort}`);
this.setupDirectConnection(connectionId, socket, connectionRecord, undefined, undefined, chunk); this.setupDirectConnection(
connectionId,
socket,
connectionRecord,
undefined,
undefined,
chunk
);
} }
}); });
} else { } else {
// For non-NetworkProxy ports, proceed with normal processing // For non-NetworkProxy ports, proceed with normal processing
@ -1654,9 +1806,9 @@ export class PortProxy {
sourceIp: remoteIP, sourceIp: remoteIP,
sourcePort: socket.remotePort || 0, sourcePort: socket.remotePort || 0,
destIp: socket.localAddress || '', destIp: socket.localAddress || '',
destPort: socket.localPort || 0 destPort: socket.localPort || 0,
}; };
SniHandler.extractSNIWithResumptionSupport(chunk, debugConnInfo, true); SniHandler.extractSNIWithResumptionSupport(chunk, debugConnInfo, true);
} }
} }
@ -1708,7 +1860,7 @@ export class PortProxy {
// Save domain config in connection record // Save domain config in connection record
connectionRecord.domainConfig = domainConfig; connectionRecord.domainConfig = domainConfig;
// Check if this domain should use NetworkProxy (domain-specific setting) // Check if this domain should use NetworkProxy (domain-specific setting)
if (domainConfig?.useNetworkProxy && this.networkProxy) { if (domainConfig?.useNetworkProxy && this.networkProxy) {
if (this.settings.enableDetailedLogging) { if (this.settings.enableDetailedLogging) {
@ -1716,15 +1868,16 @@ export class PortProxy {
`[${connectionId}] Domain ${serverName} is configured to use NetworkProxy` `[${connectionId}] Domain ${serverName} is configured to use NetworkProxy`
); );
} }
const networkProxyPort = domainConfig.networkProxyPort || this.settings.networkProxyPort; const networkProxyPort =
domainConfig.networkProxyPort || this.settings.networkProxyPort;
if (initialChunk && connectionRecord.isTLS) { if (initialChunk && connectionRecord.isTLS) {
// For TLS connections with initial chunk, forward to NetworkProxy // For TLS connections with initial chunk, forward to NetworkProxy
this.forwardToNetworkProxy( this.forwardToNetworkProxy(
connectionId, connectionId,
socket, socket,
connectionRecord, connectionRecord,
initialChunk, initialChunk,
networkProxyPort // Pass the domain-specific NetworkProxy port if configured networkProxyPort // Pass the domain-specific NetworkProxy port if configured
); );
@ -1755,7 +1908,10 @@ export class PortProxy {
)}` )}`
); );
} }
} else if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) { } else if (
this.settings.defaultAllowedIPs &&
this.settings.defaultAllowedIPs.length > 0
) {
if ( if (
!isGlobIPAllowed( !isGlobIPAllowed(
remoteIP, remoteIP,
@ -1875,6 +2031,41 @@ export class PortProxy {
initialDataReceived = true; initialDataReceived = true;
// ADD THE DEBUGGING CODE RIGHT HERE, BEFORE ANY OTHER PROCESSING
if (SniHandler.isClientHello(chunk)) {
// Log more details to understand session resumption
const resumptionInfo = SniHandler.hasSessionResumption(chunk, true);
console.log(
`[${connectionId}] ClientHello details: isResumption=${resumptionInfo.isResumption}, hasSNI=${resumptionInfo.hasSNI}`
);
// Try both extraction methods
const standardSNI = SniHandler.extractSNI(chunk, true);
const pskSNI = SniHandler.extractSNIFromPSKExtension(chunk, true);
console.log(
`[${connectionId}] SNI extraction results: standardSNI=${
standardSNI || 'none'
}, pskSNI=${pskSNI || 'none'}`
);
}
// Block non-TLS connections on port 443
// Always enforce TLS on standard HTTPS port
if (!SniHandler.isTlsHandshake(chunk) && localPort === 443) {
console.log(
`[${connectionId}] Non-TLS connection detected on port 443 in SNI handler. ` +
`Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
);
if (connectionRecord.incomingTerminationReason === null) {
connectionRecord.incomingTerminationReason = 'non_tls_blocked';
this.incrementTerminationStat('incoming', 'non_tls_blocked');
}
socket.end();
this.cleanupConnection(connectionRecord, 'non_tls_blocked');
return;
}
// Try to extract SNI // Try to extract SNI
let serverName = ''; let serverName = '';
@ -1887,21 +2078,68 @@ export class PortProxy {
); );
} }
// Check for session tickets if allowSessionTicket is disabled
if (this.settings.allowSessionTicket === false && SniHandler.isClientHello(chunk)) {
// Analyze for session resumption attempt
const resumptionInfo = SniHandler.hasSessionResumption(
chunk,
this.settings.enableTlsDebugLogging
);
if (resumptionInfo.isResumption) {
// Always log resumption attempt for easier debugging
// Try to extract SNI for logging
const extractedSNI = SniHandler.extractSNI(
chunk,
this.settings.enableTlsDebugLogging
);
console.log(
`[${connectionId}] Session resumption detected in SNI handler. ` +
`Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` +
`SNI value: ${extractedSNI || 'None'}, ` +
`allowSessionTicket: ${this.settings.allowSessionTicket}`
);
// Block if there's session resumption without SNI
if (!resumptionInfo.hasSNI) {
console.log(
`[${connectionId}] Session resumption detected in SNI handler without SNI and allowSessionTicket=false. ` +
`Terminating connection to force new TLS handshake.`
);
if (connectionRecord.incomingTerminationReason === null) {
connectionRecord.incomingTerminationReason = 'session_ticket_blocked';
this.incrementTerminationStat('incoming', 'session_ticket_blocked');
}
socket.end();
this.cleanupConnection(connectionRecord, 'session_ticket_blocked');
return;
} else {
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Session resumption with SNI detected in SNI handler. ` +
`Allowing connection since SNI is present.`
);
}
}
}
}
// Create connection info object for SNI extraction // Create connection info object for SNI extraction
const connInfo = { const connInfo = {
sourceIp: remoteIP, sourceIp: remoteIP,
sourcePort: socket.remotePort || 0, sourcePort: socket.remotePort || 0,
destIp: socket.localAddress || '', destIp: socket.localAddress || '',
destPort: socket.localPort || 0 destPort: socket.localPort || 0,
}; };
// Use the new processTlsPacket method for comprehensive handling // Use the new processTlsPacket method for comprehensive handling
serverName = SniHandler.processTlsPacket( serverName =
chunk, SniHandler.processTlsPacket(
connInfo, chunk,
this.settings.enableTlsDebugLogging, connInfo,
connectionRecord.lockedDomain // Pass any previously negotiated domain as a hint this.settings.enableTlsDebugLogging,
) || ''; connectionRecord.lockedDomain // Pass any previously negotiated domain as a hint
) || '';
} }
// Lock the connection to the negotiated SNI. // Lock the connection to the negotiated SNI.
@ -2230,7 +2468,7 @@ export class PortProxy {
console.log('Stopping NetworkProxy...'); console.log('Stopping NetworkProxy...');
await this.networkProxy.stop(); await this.networkProxy.stop();
console.log('NetworkProxy stopped successfully'); console.log('NetworkProxy stopped successfully');
// Log ACME shutdown if it was enabled // Log ACME shutdown if it was enabled
if (this.settings.acme?.enabled) { if (this.settings.acme?.enabled) {
console.log('ACME certificate manager stopped'); console.log('ACME certificate manager stopped');
@ -2255,4 +2493,4 @@ export class PortProxy {
console.log('PortProxy shutdown complete.'); console.log('PortProxy shutdown complete.');
} }
} }

File diff suppressed because it is too large Load Diff